Привязки из вендорного бандла к рабочему проекту Symfony2

от автора

Среди php-разработчиков последнее время все сильнее набирает популярность Symfony2. Этот фреймворк позволяет использовать любые модули (в симфони они называются бандлы) для создания базовых фич проекта. По сути стандартная поставка симфони и является набором модулей. Но что если у вас несколько проектов, и вам необходим одинаковый набор функций на них, но подходящего модуля среди открытых нет? Не беда, можно написать свой.

По поводу создания бандла на Хабре есть статья «Создание собственного вендорного бандла в Symfony2», в которой описаны базовые моменты. В своей статье я хотел бы рассказать о некоторых методах работы из внешнего бандла с проектом, на которой он устанавливается. Предложенные мной решения буду показывать на основе своего бандла лайков.

Связь внешних энтити

В этой части мы попробуем определить возможные механизмы для работы с энтити из проекта внутри нашего бандла.

Интерфейсы

Интерфейсы позволяют описать, какими методами должен обладать класс, и мы можем проверять, удовлетворяет ли энтити данному интерфейсу или нет. В случае бандла лайков мы создаем интерфейс, описывающий методы добавления лайка, удаления лайка и получение лайков. Эти методы позволят нам работать с лайками независимо от энтити, к которой они привязаны, главное, чтобы соответствовал интерфейс.

interface LikeableInterface {     public function getId();     public function addLike(Like $like);     public function removeLike(Like $like);     public function getLikes(); } 

Мапинг

Реализация интерфейса еще не гарантирует, что пользователь нашего бандла реализовал ту связь в таблице, которая нам нужна. Но это мы можем проверить с помощью doctrine и metadata. Метадата хранит в себе информацию о всех связях между объектами, воспользуемся ей:

class LikeHelper {     /* @var EntityManager */     private $em;      protected function checkAssociation(LikeableInterface $entity)     {         $metadata = $this->em->getClassMetadata(get_class($entity));         $mapping = false;          if ($metadata->hasAssociation('likes')) {             $mapping = $metadata->getAssociationMapping('likes');         }          if (!$mapping || ($mapping['targetEntity'] != 'Undelete\LikesBundle\Entity\Like')) {             throw new NoLikeAssociationException(                 sprintf('Association with like entity not found in entity %s', get_class($entity))             );         } } 

Динамическое создание привязки

В симфони не существует класса для пользователей и в каждом проекте может быть свой класс. Но нам нужно учитывать, какие пользователи ставили лайки. Поэтому мы используем динамическое создание связи в БД через доктрину для уже существующего поля:

namespace Undelete\LikesBundle\Mapping;  use Doctrine\ORM\Event\LoadClassMetadataEventArgs; use Doctrine\ORM\Mapping\ClassMetadataInfo;  class Like {     private $userClass;      public function __construct($userClass)     {         $this->userClass = $userClass;     }      public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)     {         /* @var $metadata ClassMetadataInfo */         $metadata = $eventArgs->getClassMetadata();          if ($metadata->getName() == 'Undelete\LikesBundle\Entity\Like') {             $metadata->mapManyToOne([                 'targetEntity' => $this->userClass,                 'fieldName' => 'user',             ]);         }     } } 

Обратная связь (event dispatching)

Так же был необходим механизм, который позволял бы отслеживать установку лайка. Для этого мы будем использовать тэгированые сервисы. Всё, что нужно сделать нашему бандлу — это пройтись по контейнеру и записать помеченные сервисы, которые надо вызывать при действиях с лайками.

class LikePass implements CompilerPassInterface {     public function process(ContainerBuilder $container)     {         $definition = $container->getDefinition(             'undelete.likes.event.dispatcher'         );          $taggedServices = $container->findTaggedServiceIds(             'like_listener'         );          foreach ($taggedServices as $id => $tags) {             $onLike = isset($tags[0]['onLike']) ? $tags[0]['onLike'] : false;             $onLikeRemove = isset($tags[0]['onLikeRemove']) ? $tags[0]['onLike'] : false;              $definition->addMethodCall(                 'addListener',                 array(new Reference($id), $onLike, $onLikeRemove)             );         }     } } 

Для работы с этими сервисами сделаем небольшой диспетчер:

class LikeEventDispatcher {     private $listeners = [];      public function addListener($service, $onLike, $onLikeRemove)     {         $this->listeners[] = [             'service' => $service,             'onLike' => $onLike,             'onLikeRemove' => $onLikeRemove,         ];     }      public function dispatchEvent($kind, LikeEvent $event)     {         foreach ($this->listeners as $listener) {             $method = false;              if ($kind == LikeEvent::ON_LIKE) {                 $method = $listener['onLike'];             } elseif ($kind == LikeEvent::ON_LIKE_REMOVE) {                 $method = $listener['onLikeRemove'];             }              if ($method) {                 $listener['service']->$method($event);             }         }     } } 

Front end

Помимо какой-то серверной логики на внешний проект иногда приходится отдавать и файлы для браузера (стили, картинки и javascript). Эти файлы мы храним в папке Resource/public. В симфони есть assets для подключения файлов из бандла. Собственно, его (assets:install) и используем чтобы файлы были доступны в публичной папке.
Для некоторых проектов мы используем assetic как более гибкое решение. Но здесь приходиться мириться с тем, что js и css лежат в публичной части, но не используются.

ЗЫ

Надеюсь, что эти небольшие советы помогут вам при создании своих бандлов. Если у вас есть замечания, аргументы за или против этих вариантов — пишите их в комментариях, с радостью с вами побеседую.

Бандл лайков можно найти здесь: github.com/UnDeleteRU/LikesBundle

ссылка на оригинал статьи http://habrahabr.ru/post/255191/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *