Symfony DI : Circular service reference with Doctrine event subscriber - php

In order to refactor the code about the ticket notification systems, I created a Doctrine listener:
final class TicketNotificationListener implements EventSubscriber
{
/**
* #var TicketMailer
*/
private $mailer;
/**
* #var TicketSlackSender
*/
private $slackSender;
/**
* #var NotificationManager
*/
private $notificationManager;
/**
* We must wait the flush to send closing notification in order to
* be sure to have the latest message of the ticket.
*
* #var Ticket[]|ArrayCollection
*/
private $closedTickets;
/**
* #param TicketMailer $mailer
* #param TicketSlackSender $slackSender
* #param NotificationManager $notificationManager
*/
public function __construct(TicketMailer $mailer, TicketSlackSender $slackSender, NotificationManager $notificationManager)
{
$this->mailer = $mailer;
$this->slackSender = $slackSender;
$this->notificationManager = $notificationManager;
$this->closedTickets = new ArrayCollection();
}
// Stuff...
}
The goal is to dispatch notifications when a Ticket or a TicketMessage entity is created or updated trough mail, Slack and internal notification, using Doctrine SQL.
I already had a circular dependencies issue with Doctrine, so I injected the entity manager from the event args instead:
class NotificationManager
{
/**
* Must be set instead of extending the EntityManagerDecorator class to avoid circular dependency.
*
* #var EntityManagerInterface
*/
private $entityManager;
/**
* #var NotificationRepository
*/
private $notificationRepository;
/**
* #var RouterInterface
*/
private $router;
/**
* #param RouterInterface $router
*/
public function __construct(RouterInterface $router)
{
$this->router = $router;
}
/**
* #param EntityManagerInterface $entityManager
*/
public function setEntityManager(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
$this->notificationRepository = $this->entityManager->getRepository('AppBundle:Notification');
}
// Stuff...
}
The manager is injected form the TicketNotificationListener
public function postPersist(LifecycleEventArgs $args)
{
// Must be lazy set from here to avoid circular dependency.
$this->notificationManager->setEntityManager($args->getEntityManager());
$entity = $args->getEntity();
}
The web application is working, but when I try to run a command like doctrine:database:drop for example, I got this:
[Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException]
Circular reference detected for service "doctrine.dbal.default_connection", path: "doctrine.dbal.default_connection -> mailer.ticket -> twig -> security.authorization_checker -> security.authentication.manager -> fos_user.user_provider.username_email -> fos_user.user_manager".
But this is concerning vendor services.
How to solve this one? Why I have this error only on cli?
Thanks.

Had the same architectural problem lately, assuming you use Doctrine 2.4+ the best thing to do is not use the EventSubscriber (which triggers for all events), but use EntityListeners on the two entities you mention.
Assuming that the behavior of both entities should be the same, you could even create one listener and configure it for both entities. The annotation looks like this:
/**
* #ORM\Entity()
* #ORM\EntityListeners({"AppBundle\Entity\TicketNotificationListener"})
*/
class TicketMessage
Thereafter you can create the TicketNotificationListener class and let a service definition do the rest:
app.entity.ticket_notification_listener:
class: AppBundle\Entity\TicketNotificationListener
calls:
- [ setDoctrine, ['#doctrine.orm.entity_manager'] ]
- [ setSlackSender, ['#app.your_slack_sender'] ]
tags:
- { name: doctrine.orm.entity_listener }
You might not even need the entity manager here, because the entity itself is available via the postPersist method directly:
/**
* #ORM\PostPersist()
*/
public function postPersist($entity, LifecycleEventArgs $event)
{
$this->slackSender->doSomething($entity);
}
More info on Doctrine entity listeners: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#entity-listeners

IMHO you are mixing 2 different concepts here:
Domain Events (TicketWasClosed for example)
Doctrine's Life-cycle Events (PostPersist for example)
Doctrine's event system is meant to hook into the persistence flow, to deal stuff directly related to saving to and loading from the database. It shouldn't be used for anything else.
To me it looks like what you want to happen is:
When a ticket was closed, send a notification.
This has nothing to do with Doctrine or persistence in general. What you need is another event system dedicated to Domain Events.
You can still use the EventManager from Doctrine, but make sure you create a second instance which you use for Domain Events.
You can also use something else. Symfony's EventDispatcher for example. If you're using the Symfony framework, the same thing applies here as well: don't use Symfony's instance, create your own for Domain Events.
Personally I like SimpleBus, which uses objects as events instead of a string (with an object as "arguments"). It also follows the Message Bus and Middleware patterns, which give a lot more options for customization.
PS: There are a lot of really good articles on Domain Events out there. Google is your friend :)
Example
Usually Domain Events are recorded within entities themselves, when performing an action on them. So the Ticket entity would have a method like:
public function close()
{
// insert logic to close ticket here
$this->record(new TicketWasClosed($this->id));
}
This ensures the entities remain fully responsible for their state and behavior, guarding their invariants.
Of course we need a way to get the recorded Domain Events out of the entity:
/** #return object[] */
public function recordedEvents()
{
// return recorded events
}
From here we probably want 2 things:
Collect these events into a single dispatcher/publisher.
Only dispatch/publish these events after a successful transaction.
With the Doctrine ORM you can subscribe a listener to Doctrine's OnFlush event, that will call recordedEvents() on all entities that are flushed (to collect the Domain Events), and PostFlush that can pass those to a dispatcher/publisher (only when successful).
SimpleBus provides a DoctrineORMBridge that supplies this functionality.

Related

Referencing an extended class from inside the base class in PHP

Sorry for perhaps not the most accurate title. I'm having trouble figuring out what this would even be called.
I'm somewhat new to OOP with php, as most of my time is spent with procedural programming. In an effort to better learn these concepts, I'm taking an existing application and rewriting portions of it using OOP. The below example is how I set up the base class, and then I extended the base class into several smaller classes for easier maintainability. Below, you can see how I extended the base class to create a user class. Please note, that my class definitions are in separate files, but I have a working autoloader that automatically registers them
class EventScheduler{
function __construct(){
// set up database connections here
}
}
class User extends EventScheduler{
private function getUserProfile($username){
// return an array here representing
// details of passed username from database
}
public function getUserType($username){
return $this->getUserProfile($username)['user_type'];
}
}
What I'd like to be able to do is reference the User class from inside the base class, like this:
$eventApp = new EventScheduler();
$userType = $eventApp->User->getUserProfile("nameHere");
What I'm currently doing is this:
$eventApp = new EventScheduler();
//do some stuff here using the base class
$users = new User();
$userType = $users->getUserProfile("nameHere");
But as I add more child classes, I don't want to have to instantiate every extended class like I did there, I'd like to have them all grouped under the base object, rather than having each extended class in it's own object.
What you want to do is
$users = new User();
//do some stuff here using the base class
// ie $users->someFunctionFromEventScheduler();
$userType = $users->getUserProfile("nameHere");
This is a good reason to start reading up on common design patterns in OOP. There are plenty of good resources for this online and a quick google search will yield plenty of results and examples mostly hosted on github.
The specific pattern I believe you are looking for is the mediator pattern (Mediator pattern example in PHP). Rather than extending a class, as you are doing in your example, the mediator pattern is useful when you want an instance of a class that has access to many other classes that can all communicate with each other through one base class. The premise is that 'One good friend is better than many acquaintances.`.
An example for you: (interfaces are incredibly useful here as they define specific characteristics that are required in each of the classes)
/**
* Interface Mediator
*/
interface Mediator {
/**
* #param string $key
* #param Mediated $mediated
* #return void
*/
public function attach($key, Mediated $mediated);
/**
* #param $key
* #return Mediated
*/
public function getAttached($key);
}
/**
* Interface Mediated
*/
interface Mediated {
/**
* #param Mediator $mediator
* #return void
*/
public function setMediator(Mediator $mediator);
/**
* #return Mediator
*/
public function getMediator();
}
Now we need a base mediator class, I'll use your event scheduler example. Notice that it implements the Mediator interface and must, as a result implement the methods the interface requires.
/**
* Class EventScheduler
*/
class EventScheduler implements Mediator {
/**
* A collection of mediated instances.
*
* #var array
*/
protected $mediated = [];
/**
* #param string $key
* #param Mediated $mediated
* #return void
*/
public function attach($key, Mediated $mediated)
{
// So upon attaching a mediated instance we can build the two
// way binding in one place using the key as the identifier.
// First we set $this on the mediated instance.
$mediated->setMediator($this);
// Then we add this instance to our mediated array inside this instance
$this->mediated[$key] = $mediated;
}
/**
* #param $key
* #return Mediated
*/
public function getAttached($key)
{
return $this->mediated[$key];
}
}
Now we can setup a mediated instance. That can be attached to the mediator. Notice it implements the Mediated interface.
/**
* Class User
*/
class User implements Mediated {
/**
* #var Mediator
*/
protected $mediator;
/**
* #param Mediator $mediator
* #return void
*/
public function setMediator(Mediator $mediator)
{
$this->mediator = $mediator;
}
/**
* #return Mediator
*/
public function getMediator()
{
return $this->mediator;
}
}
You can create as many of the mediated instances as you like and attach them to the Mediator instance. Bear in mind that this isn't a specific mediator, in that many instances can be attached, in most cases it's better to be explicit with which classes can be attached rather than allowing dynamic registration by a key.
$scheduler = new EventScheduler();
$user = new User();
$scheduler->attach('user', $user);
// Now we know that we can get the mediator from the User class
$user->getMediator();
// We can also get the User from the mediator itself.
$scheduler->getAttached('user');
As you attach more classes you'll notice that each of them can use their mediator to get instances of the other attached classes, this is where the concept of one good friend comes from.
This is just an example and not fully featured for brevity, but should give you a good idea why using common design patterns will help you enormously and is a very good place to start when learning OOP if you want to develop good habits.

Can a Doctrine entity read itself?

just curious .. I have some code like so:
//$em is EntityManager of Doctrine
//$className is a type that uses polymorphism (subtype of a parent type)
$pricing = $em->getRepository($className)->findOneBy(array(
'active' => true,
'product_id' => (int) $this->id
));
//gets serialization of certain variables of $className
return $pricing->getSerialization();
But ... instead of calling findOneBy outside of $className, can I move getSerialization() method inside the Entity (which is $className), and return class-parameters from there?
I imagine it is not possible, since Entity cannot read itself. Correct?
The problem I am trying to solve is. ... In the example above Entity is populated via Doctrine and then it returns data. Therefore I have to use another class to populate the entity. Without Doctrine I know it's possible to do things such as read data from inside the Entity i.e. via mysqli, and then return properties directly or via a method. In other words, do I absolutely need another location (class/function/method outside of Entity) to populate the entity?
Sample Entity looks like so
class Pricing
{
function getSerialization(){}
/**
* #var integer #Column(name="id", type="integer", nullable=false)
* #Id
* #GeneratedValue(strategy="IDENTITY")
*/
protected $id;
//etc, a typical Doctrine Entity
}
Yes, the instance of an entity class can read itself.
But I guess your question should have been: "Can a Doctrine entity load and read itself?". The answer to that is no...
Loading of entities is managed by doctrine internals. If you would want the entity classes to load themselves it would mean injecting an EntityManager into the entity class.
This is a bad idea, I quote #BryanM. his answer on another stackoverflow question that covers this nicely:
It is not a good idea to allow an entity object to rely on the entity manager. It ties the entity to the persistence layer, which was a problem Doctrine 2 was specifically trying to solve. The biggest hassle in relying on the entity manager is that it makes your model hard to test in isolation, away from the database.
You should probably be relying on service objects to handle the operations that rely on the entity manager.
It means you need to take care of loading entities externally. I still don't see the problem with getSerialization. It can be inside the Entity class and can be used after the entity is loaded right?
If you want to do loading and serializing at once I would suggest making a PricingService in which you inject the repository or entity manager and where you define a public methods that does all that. For example:
<?php
use Application\Entity\Pricing;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;
class PricingService
{
/**
* #var EntityManager
*/
protected $entityManager;
/**
* #param EntityManager $entityManager
*/
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* #return EntityRepository;
*/
protected function getRepository()
{
return $this->entityManager->getRepository(`Application\Entity\Pricing`);
}
/**
* #param $params
* #return array
*/
public function findSerializedBy($params)
{
$pricing = $this->getRepository()->findOneBy($params);
return $pricing->getSerialization();
}
}
Now you can work with your PricingService directly:
$serializedPricing = $pricingService->findSerializedBy(array(
'active' => true,
'product_id' => (int) $this->id
));
You can of course generalize your service by adding another parameter with the $classname.

Circular Dependency with Doctrine ORM, Doctrine PHPCR-ODM, and Event Listeners

I am working on a complex Symfony project that blends Doctrine ORM objects with Doctrine PHPCR-ODM documents. Everything works fine, but I have been unable to solve circular dependency injection problems between listeners in the container.
The scenario is, I have multiple ODM documents that set ORM references as they are loaded, which is accomplished via an event listener. An example configuration is:
services.yml:
example.event_listener.my_document:
class: Example\Common\EventListener\MyDocumentEventListener
arguments: [#doctrine]
tags:
- { name: doctrine_phpcr.event_listener, event: postLoad }
- { name: doctrine_phpcr.event_listener, event: prePersist }
Example\Common\EventListener\MyDocumentEventListener.php:
namespace Example\Common\EventListener;
use Example\Common\ODM\Document\MyDocument;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\ODM\PHPCR\DocumentManager;
/**
* Listener for {#link Example\Common\ODM\Document\MyDocument} events.
*/
class MyDocumentEventListener
{
/*
* #var Doctrine\Common\Persistence\ManagerRegistry
*/
private $managerRegistry;
/**
* Constructor.
*
* #param Doctrine\Common\Persistence\ManagerRegistry $documentManager A Doctrine {#link Doctrine\Common\Persistence\ManagerRegistry}.
*/
public function __construct(ManagerRegistry $managerRegistry)
{
$this->managerRegistry = $managerRegistry;
}
/**
* After loading a document, ensure that the references exist
* to each ORM dependency.
*
* #param Doctrine\Common\Persistence\Event\LifecycleEventArgs $args
*/
public function postLoad(LifecycleEventArgs $args)
{
if (get_class($args->getObject()) == 'Example\Common\ODM\Document\MyDocument') {
$this->loadDependencies($args->getObject(), $args->getObjectManager());
}
}
/**
* Prior to persisting a document, ensure that the references exist
* to each ORM dependency.
*
* #param Doctrine\Common\Persistence\Event\LifecycleEventArgs $args
*/
public function prePersist(LifecycleEventArgs $args)
{
if (get_class($args->getObject()) == 'Example\Common\ODM\Document\MyDocument') {
$this->loadDependencies($args->getObject(), $args->getObjectManager());
}
}
/**
* Pull relational information from the ORM database to populate
* those fields in the {#link Example\Common\ODM\Document\MyDocument} document that
* require it. Each field is populated as a reference, so it will be
* loaded from the database only if necessary.
*
* #param Example\Common\ODM\Document\MyDocument $document The MyDocument to load dependencies for.
* #param Doctrine\ODM\PHPCR\DocumentManager $documentManager The DocumentManager for the MyDocument.
*/
private function loadDependencies(MyDocument $document, DocumentManager $documentManager)
{
$reflectionClass = $documentManager->getClassMetadata(get_class($document))->getReflectionClass();
$exampleProperty = $reflectionClass->getProperty('example');
$exampleProperty->setAccessible(true);
$exampleProperty->setValue(
$document,
$this->managerRegistry->getManager()->getReference('Example\Common\ORM\Entity\MyEntity', $document->getExampleId())
);
}
}
Everything above works perfectly fine when working with MyDocument objects. (This is basically an exact implementation of what is described in the Doctrine documentation for blending ORM and MongoDB ODM).
Now the problem is when I also want to do the reverse within the same application -- that is to say, I also want to have an ORM entity which has a listener that fills in a reference or references to ODM documents.
Without adding more code, let's say I extend my services.yml configuration to:
example.event_listener.my_document:
class: Example\Common\EventListener\MyDocumentEventListener
arguments: [#doctrine]
tags:
- { name: doctrine_phpcr.event_listener, event: postLoad }
- { name: doctrine_phpcr.event_listener, event: prePersist }
example.event_listener.my_entity:
class: Example\Common\EventListener\MyEntityEventListener
arguments: [#doctrine_phpcr]
tags:
- { name: doctrine.event_listener, event: prePersist }
- { name: doctrine.event_listener, event: postLoad }
This will now fail, because we have a circular dependency: the container tries to inject the ODM listener into the DocumentManager's listeners, which in turn tries to inject the EntityManager, which in turn tries to inject its own listeners, which each try to inject the DocumentManager, and so on. (Note that this example uses the Registry rather than the manager, but the result is the same).
I have tried a few different approaches to resolve this but haven't hit on one that works yet. Has anybody been able to get bi-directional listeners between ORM and ODM to work like this in a single project?
I have found few examples around this, unfortunately. My workaround thus far would be to create a service to handle the loading/persisting of these objects and then run everything through that, but it seems very hackish compared to using an elegant event-driven system.
Cleaning up this old question with what turned out to be the correct answer as noted in a comment above: injecting the full container. Normally I avoid this, but it turned out to be the only way to solve this particular problem.
In case anybody is looking for an example of an ODM listener that loads ORM-based dependencies, here is a working example of what I arrived at:
Service definition:
example.event_listener.odm.my_document:
class: Example\Common\EventListener\MyDocumentEventListener
arguments: [#service_container]
tags:
- { name: doctrine_phpcr.event_listener, event: postLoad }
The listener:
<?php
namespace Example\Common\EventListener;
use Example\Common\Document\MyDocument;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use Doctrine\ODM\PHPCR\DocumentManager;
use Symfony\Component\DependencyInjection\Container;
class MyDocumentEventListener
{
/*
* #var \Symfony\Component\DependencyInjection\Container
*/
private $container;
/**
* Constructor.
*
* #param \Symfony\Component\DependencyInjection\Container $container A Symfony dependency injection container.
*/
public function __construct(Container $container)
{
$this->container = $container;
}
/**
* After loading a document, ensure that the references exist
* to each ORM dependency.
*
* #param \Doctrine\Common\Persistence\Event\LifecycleEventArgs $args
*/
public function postLoad(LifecycleEventArgs $args)
{
if (get_class($args->getObject()) == 'Example\Common\Document\MyDocument') {
$this->loadDependencies($args->getObject(), $args->getObjectManager());
}
}
/**
* Pull relational information from the ORM database to populate
* those fields in the document that require it. Each field is
* populated as a reference, so it will be loaded from the database only
* if necessary.
*
* #param \Example\Common\Document\MyDocument $document The document to load dependencies for.
* #param \Doctrine\ODM\PHPCR\DocumentManager $documentManager The DocumentManager for the document.
*/
private function loadDependencies(MyDocument $document, DocumentManager $documentManager)
{
$documentReflectionClass = $documentManager->getClassMetadata(get_class($document))->getReflectionClass();
$someOrmProperty = $documentReflectionClass->getProperty('orm_property');
$someOrmProperty->setAccessible(true);
$someOrmProperty->setValue(
$document,
$this->container->get('doctrine.orm.entity_manager')->getReference('Example\Common\Model\MyModel', $document->getOrmPropertyId())
);
}
}
This allows the document class to store an ID to an ORM model entity, and each time the document is loaded, it fills in the actual reference to the ORM model within the document. This makes it behave as if it knew about the ORM property all along, and works pretty well.
This example uses PHPCR-ODM but would work just fine for MongoDB ODM as well.

How to get the instance of Kernel in the Entity class in Symfony2

The title explains the question pretty well. I am in the lifecycle callback of the Doctrine Entity class and want to do some extra DB entries. For this I need to get an instance of the Kernel. How can I do this?
Needing the container/kernel in an entity is most of the time, wrong. An entity shouldn't be aware of any services. Why is that?
Basically, an entity is an object which represents a thing. An entity is mostly used in a relationnal database, but you can at any time use this entity for other matters (serialize it, instanciate it from an HTTP layer...).
You want your entity to be unit-testable, this means you need to be able to instanciate your entity easily, without anything around, mostly, without any piece of business logic.
You should move your logic into another layer, the one that will instanciate your entity.
For your use case, I think, the most easy way is to use a doctrine event.
services.yml
services:
acme_foo.bar_listener:
class: Acme\FooBundle\Bar\BarListener
arguments:
- #kernel
tags:
- { name: doctrine.event_listener, event: postLoad }
Acme\FooBundle\Bar\BarListener
use Symfony\Component\HttpKernel\KernelInterface;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Acme\FooBundle\Entity\Bar;
class BarListener
{
protected $kernel;
/**
* Constructor
*
* #param KernelInterface $kernel A kernel instance
*/
public function __construct(KernelInterface $kernel)
{
$this->kernel = $kernel;
}
/**
* On Post Load
* This method will be trigerred once an entity gets loaded
*
* #param LifecycleEventArgs $args Doctrine event
*/
public function postLoad(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if (!($entity instanceof Bar)) {
return;
}
$entity->setEnvironment($this->kernel->getEnvironment());
}
}
And there you go, your entity remains flat without dependencies, and you can easily unit test your event listener
if you have to use some service, you shouldn't use whole container or kernel instance especially.
use the services itself - always try to inject single service, not whole container
your case looks like you should use doctrine events

Cached user class vs seperate class holding necessary cached fields

I need to cache some info about a user who is logged in (such as security groups, name, username, etc.)
Currently I have a separate class to achieve just this, lets call it CurrentUserHelper. Given a user object, it will cache the appropriate data and save store info in the $_SESSION variable.
The issue is I'm finding a bunch of classes relying just on CurrentUserHelper because they only need a couple of common fields. In fact, most of the functions have the same name as my User class. There's a couple of functions in CurrentUserHelper, such as getSecurityGroupsNames(), that contains a cache of all security group names, but there is no reason this function name could not be in the user class also.
Instead, should I create a CachedUser class and pass this around? This class can extend User, but then I can override getName(), getSecurityGroups(), etc, and returned the cached data, and not preform db requests to get the data.
The downside of passing around a CachedUser object is that this kind of hides the fact the data isn't really up to date if a constructor/function is accepting a type User. I also need to find way to handle merging the entity with Doctrine 2, and making sure entities associating themselves with a CachedUser won't break. If I decide to cache some temporary data (such as # of page views since logged in), this property shouldn't be part of the User class, it's more about the current user's session.
If I continue using the CurrentUserHelper class, maybe I should create an interface and have both CurrentUserHelper and User for the common functionality the two classes would share?
Preface: Extension isn't the best way for these sorts of things.. I'm sure you've heard composition over inheritance shouted at you over and over again. In fact, you can even gain inheritance without using extends!
This sounds like a good use-case for the decorator pattern. Basically, you wrap your existing object with another one that implements the same interface, so it has the same methods as the inner object, but this object's method adds the extra stuff around the method call to the inner object, like caching, for example.
Imagine you have a UserRepository:
/**
* Represents an object capable of providing a user from persistence
*/
interface UserProvider
{
/**
* #param int $id
*
* #return User
*/
public function findUser($id);
}
/**
* Our object that already does the stuff we want to do, without caching
*/
class UserRepository implements UserProvider
{
/**
* #var DbAbstraction
*/
protected $db;
/**
* #var UserMapper
*/
protected $mapper;
/**
* #param DbAbstraction $db
* #param UserMapper $mapper
*/
public function __construct(DbAbstraction $db, UserMapper $mapper)
{
$this->db = $db;
$this->mapper = $mapper;
}
/**
* {#inheritDoc}
*/
public function findUser($id)
{
$data = $this->db->find(['id' => $id]);
/** Data mapper pattern - map data to the User object **/
$user = $this->mapper->map($data);
return $user;
}
}
The above is a really simple example. It'll retrieve the user data from it's persistence (a database, filesystem, whatever), map that data to an actual User object, then return it.
Great, so now we want to add caching. Where should we do this, within the UserRepository? No, because it's not the responsibility of the repository to perform caching, even if you Dependency Inject a caching object (or even a logger!).. this is where the decorator pattern would come in useful.
/**
* Decorates the UserRepository to provide caching
*/
class CachedUserRepository implements UserProvider
{
/**
* #var UserRepository
*/
protected $repo;
/**
* #var CachingImpl
*/
protected $cache;
/**
* #param UserRepository $repo
*/
public function __construct(UserRepository $repo, CachingImpl $cache)
{
$this->repo = $repo;
$this->cache = $cache;
}
/**
* {#inheritDoc}
*
* So, because this class also implements UserProvider, it has to
* have the same method in the interface. We FORWARD the call to
* the ACTUAL user provider, but put caching AROUND it...
*/
public function findUser($id)
{
/** Has this been cached? **/
if ($this->cache->hasKey($id))
{
/**
* Returns your user object, or maps data or whatever
*/
return $this->cache->get($id);
}
/** Hasn't been cached, forward the call to our user repository **/
$user = $this->repo->findUser($id);
/** Store the user in the cache for next time **/
$this->cache->add($id, $user);
return $user;
}
}
Very simply, we've wrapped the original object and method call with some additional caching functionality. The cool thing about this is that, not only can you switch out this cached version for the non-cached version at any time (because they both rely on the same interface), but you can remove the caching completely as a result, just by changing how you instantiate this object (you could take a look at the factory pattern for that, and even decide which factory (abstract factory?) depending on a configuration variable).

Categories