Среди 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/
Добавить комментарий