📜 ⬆️ ⬇️

Bindings from vendor bundle to the working project Symfony2

Among php-developers lately Symfony2 is gaining more and more popularity. This framework allows you to use any modules (in the symphony they are called bundles) to create basic features of the project. In fact, the standard supply of symphony is a set of modules. But what if you have several projects and you need the same set of functions for them, but there is no suitable module among the open ones? Do not worry, you can write your own.

Regarding the creation of a bundle on Habré, there is an article "Creating your own vendor bundle in Symfony2" , which describes the basic points. In my article I would like to tell about some methods of work from an external bundle with the project on which it is installed. I will show the solutions proposed by me based on my bandl likes.

Communication external entity


In this part, we will try to identify possible mechanisms for working with an entity from a project within our bundle.

Interfaces


Interfaces allow us to describe which methods a class should possess, and we can check whether the entity satisfies this interface or not. In the case of likes, we create an interface that describes how to add likes, remove likes and get likes. These methods will allow us to work with likes regardless of the entity to which they are attached, as long as the interface is consistent.
')
interface LikeableInterface { public function getId(); public function addLike(Like $like); public function removeLike(Like $like); public function getLikes(); } 

Mapping


The implementation of the interface does not guarantee that the user of our bundle has implemented the connection in the table that we need. But we can verify this with doctrine and metadata. Metadata stores information about all connections between objects, use it:

 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)) ); } } 

Dynamic Binding Creation


There is no class for users in a symphony, and each project can have its own class. But we need to consider which users put likes. Therefore, we use the dynamic creation of a connection in the database through the doctrine for an existing field:

 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', ]); } } } 

Feedback (event dispatching) Tagged services


Update As suggested by korotovsky in the comments, it's better to use the native event_dispatcher for events. I leave this part only as an example of working with tags.

It also needed a mechanism that would allow tracking the installation of the likes. For this we will use tagged services. All that our bandl needs to do is to go through the container and write down the marked services that need to be called upon actions with likes.

 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) ); } } } 

To work with these services we will make a small dispatcher:

 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


In addition to some server logic, an external project sometimes has to give files for the browser (styles, images and javascript). We store these files in the Resource / public folder. In the symphony there are assets for connecting files from the bundle. Actually, its (assets: install) and use so that the files are available in a public folder.
For some projects we use assetic as a more flexible solution. But here you have to put up with the fact that js and css are in the public part, but not used.

PS


I hope that these small tips will help you in creating your own bundles. If you have comments, arguments for or against these options - write them in the comments, happy to talk with you.

The likes of the bandl can be found here: github.com/UnDeleteRU/LikesBundle

Source: https://habr.com/ru/post/255191/


All Articles