What is the difference between LifecycleEventArgs::getObject() and LifecycleEventArgs::getEntity()?
namespace App\EventListener;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Events;
/**
* Class MyListener
*
* #package App\EventListener
*/
class MyListener implements EventSubscriber
{
/**
* #return array|string[]
*/
public function getSubscribedEvents()
{
return [
Events::postUpdate,
];
}
/**
* #param LifecycleEventArgs $event
*/
public function postUpdate(LifecycleEventArgs $event)
{
$entity = $event->getEntity();
$object = $event->getObject();
$entity === $object; //true...
}
}
So far as I can tell these two methods return the exact same object, ie they point to the same instance of a given Entity.
Is that always the case?
Should one be used over the other or does it not matter?
There is no difference. The getObject() method comes from the parent class of the LifecycleEventArgs class which is provided by the doctrine/persistence package.
The base event class is mainly helpful when you want to build an integration layer for several Doctrine implementations (e.g. ORM and ODM) and in which case you would use getObject().
Related
I have an abstract class called AbstractMediaService and a some specific implementations of this abstract class:
abstract class AbstractMediaService
{
private $em;
private $media;
public function __construct(EntityManagerInterface $em, Media $media)
{
$this->em = $em;
$this->media = $media;
}
public function dosomethingInCommon();
abstract public function dosomethingSpecific();
}
class PhotoMediaService extends AbstractMediaService
{
public function dosomethingSpecific()
{
echo 'i am a photo service';
}
}
class VideoMediaService extends AbstractMedia
{
public function dosomethingSpecific()
{
echo 'i am a video service';
}
}
These objects require a Media entity to work with
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
class Media
{}
Controller
/**
* #Route("/{_locale}/infos/{idMedia}.html", name="info", methods={"GET"}, requirements={
* "idMedia" = "\d+",
* })
*/
public function infosPhotoAction(RequestStack $requestStack, Media $media)
{
$request = $requestStack->getCurrentRequest();
$session = $requestStack->getSession();
$media = new PhotoMedia($media);
// return response
}
Problem is that I need some dependencies like the Security service or the EntityManager.
I would like to know how autowire AbstractMediaService service.
This is wrong. You cannot autowire Media to be injected into a service, because entities are not services.
public function __construct(EntityManagerInterface $em, Media $media)
If VideoMediaService and PhotoMediaService (I renamed them for clarity, since sharing the name with your entity made it look like it were related) need an instance of Media to perform some work, just make that a parameter for the corresponding methods.
public function dosomethingInCommon(Media $media);
abstract public function dosomethingSpecific(Media $media);
Or alternatively, simply have a setMedia(Media $media) method on that class for that:
public function setMedia(Media $media) {
$this->media = $media;
}
Frankly, this latter approach does not seem like a great idea. You would need to make the methods that work on $media aware of the possibility of setMedia() not having been called yet, or subsequent calls to setMedia() would change how the service behaved. Just making it a parameter of the appropriate method is much cleaner, clearer and safer.
Injecting those services is done like any other service. That they extend an abstract class is irrelevant.
/**
* #Route("/{_locale}/infos/{idMedia}.html", name="info", methods= {"GET"}, requirements={
* "idMedia" = "\d+",
* })
*/
public function infosPhotoAction(RequestStack $requestStack, Media $media, PhotoMediaService $photoMediaService): Response
{
$request = $requestStack->getCurrentRequest();
$session = $requestStack->getSession();
$photoMediaService->doSomethingSpecific($media)
return new Response('all done');
}
I want to make a repository with hard dependencies. I found this blog post by Jurian Sluisman but he suggests getting the repository from the service manager and injecting it into the service where needed.
It would be much better if I would be able to get my custom repositories with injected dependencies like normally from my EntityManager or ObjectManager instance by using the getRepository method:
$objectManager->getRepository('My\Entity\Class');
How can I use constructor injection in my Repositories and still get them like normally from the ObjectManager directly with the getRepository method?
Doctrine uses a factory class Doctrine\ORM\EntityManagerInterface\DefaultRepositoryFactory for creating repository instances. If no custom factory is set this default factory is created here in the getRepositoryFactory method in the Doctrine\ORM\Configuration class.
By defining a custom repository_factory we can overwrite this default factory class and add custom logic to the factory that will inject the hard dependencies:
To illustrate how you can do this I will show an example where the repository factory class creates repositories that are dependent on a ServiceLocator instance through constructor injection.
1) make a custom factory class that implements the doctrine RepositoryFactory interface
This class looks very similar to the doctrine DefaultRepositoryFactory class.
<?php
namespace My\ORM\Repository;
use Doctrine\Common\Persistence\ObjectRepository;
use Doctrine\ORM\Repository\RepositoryFactory;
use Doctrine\ORM\EntityManagerInterface;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorAwareTrait;
use Zend\ServiceManager\ServiceLocatorInterface;
class CustomRepositoryFactory implements RepositoryFactory, ServiceLocatorAwareInterface
{
use ServiceLocatorAwareTrait;
/**
* #var ObjectRepository[]
*/
private $repositoryList = array();
/**
* #var ServiceLocator
*/
protected $serviceLocator;
/**
* #param ServiceLocatorInterface $serviceLocator
*/
public function __construct(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
}
/**
* {#inheritdoc}
*/
public function getRepository(EntityManagerInterface $entityManager, $entityName)
{
$repositoryHash = $entityManager->getClassMetadata($entityName)->getName() . spl_object_hash($entityManager);
if (isset($this->repositoryList[$repositoryHash])) {
return $this->repositoryList[$repositoryHash];
}
return $this->repositoryList[$repositoryHash] = $this->createRepository($entityManager, $entityName);
}
/**
* #param EntityManagerInterface $entityManager The EntityManager instance.
* #param string $entityName The name of the entity.
* #return ObjectRepository
*/
private function createRepository(EntityManagerInterface $entityManager, $entityName)
{
/* #var $metadata \Doctrine\ORM\Mapping\ClassMetadata */
$metadata = $entityManager->getClassMetadata($entityName);
$repositoryClassName = $metadata->customRepositoryClassName
?: $entityManager->getConfiguration()->getDefaultRepositoryClassName();
// Constructor injection, I check with subclass of but it is just an example
if(is_subclass_of($repositoryClassName, ServiceLocatorAwareInterface::class)){
$serviceLocator = $this->getServiceLocator()
$repository = new $repositoryClassName($entityManager, $metadata, $serviceLocator);
}else{
$repository = new $repositoryClassName($entityManager, $metadata);
}
return $repository;
}
}
2) Create a factory for the repository factory
<?php
namespace My\ORM\Repository\Factory;
use My\ORM\Repository\CustomRepositoryFactory;
use Zend\Cache\Storage\StorageInterface;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class CustomRepositoryFactoryFactory implements FactoryInterface
{
/**
* #param ServiceLocatorInterface $serviceLocator
* #return StorageInterface
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
return new CustomRepositoryFactory($serviceLocator);
}
}
3) register the factory for the repository factory in the service_manager config
'service_manager' => array(
'factories' => array(
'My\ORM\Repository\CustomRepositoryFactory' => 'My\ORM\Repository\Factory\CustomRepositoryFactoryFactory'
)
)
4) register the repository factory in the doctrine config
'doctrine' => array(
'configuration' => array(
'orm_default' => array(
'repository_factory' => 'My\ORM\Repository\CustomRepositoryFactory'
)
)
)
This question can be viewed through a prism of ZF2 + Doctrine + MVC programming practices, or it can be viewed through just an OOP perspective.
My concern is about Separation of Concerns, and on removing dependencies.
I am using code in my controllers that goes something like this:
class MyController
{
private $em; //entityManager
function __construct()
{
$this->em = DoctrineConnector::getEntityManager();
}
function indexAction()
{
//Input
$inputParameter = filter_input(...);
//request for Data
$queryBuilder = $this->em->createQuery(...)
->setParameter('param', $inputParameter);
$query = $queryBuilder->getQuery();
//$services is the user-defined data type requested
$services = $query->getResult();
//use data to produce a view model
$view = new ViewModel();
$view->setVariables(array('services' => $services));
return $view;
}
}
I am not entirely comfortable with the above and wanted a second opinion. For one, my EntityManager is part of the class, so my class is cognizant of the entity manager construct, when I think it should not be a part of the controller. Do I perhaps use a Factory or Builder design pattern to help me create MyController class?
If I do, I can move my em (entityManager) construct into the Factory pattern and create and populate my MyController inside the Factory. Then, the MyController can have a private variable $services instead.
i.e.
class MyController
{
private $services;
function setServices($services)
{
$this->services = $services;
}
function indexAction()
{
//use data to produce a view model
$view = new ViewModel();
$view->setVariables(array('services' => $this->services));
return $view;
}
}
class MyFactoryMethod
{
function createMyController()
{
//Input
$inputParameter = filter_input(INPUT_GET...);
//request for Data
$queryBuilder = $this->em->createQuery(...)
->setParameter('param', $inputParameter);
$query = $queryBuilder->getQuery();
//$services is the user-defined data type requested
$services = $query->getResult();
//create and return MyController instance
$controller = new MyController();
$controller->setServices($services);
return $controller;
}
}
I typically tried to do this PHP's mysql extension to remove dependency on data out of my various objects. I am using Doctrine2 now which is an ORM, and wondering if I should keep doing the same thing (namely preferring 2nd example rather than the first...
Question:
I can write code both ways. It works essentially the same. My question is -- is the code, as it is written in my 2nd example preferred more than the code as it is written in my first?
Notes / Clarifications:
In my case variable $services is a domain-specific variable (not ZF2's ServiceLocator). i.e. think of MyController as a controller for business-specific "services".
I am not harnessing full power of ZF2 with configs, routers, events, and everything. I am using ZF2 modules on an existing legacy codebase on as-needed basis.
When your controller has hard dependencies I would suggest to use the common ZF2 solution by creating the controller and injecting the dependency in a factory instance and registering the controller under the 'factories' key in your 'controllers' config array.
In your module.config.php
'controllers' => array(
'factories' => array(
'Application\Controller\MyController' => 'Application\Controller\MyControllerFactory'
)
)
In your controller I would set hard dependency in the __construct method. Like this you prevent the controller from ever being instantiated without your dependencies (it will throw an exception).
Never inject something like $services (if this is a ServiceLocator) from which you will pull the actual dependencies since it is not clear what the class actually needs. It will be harder to understand for other developers and it is also hard to test since you cannot set mocks for your individual dependencies so easily.
Your Controller class:
<?php
namespace Application\Controller;
use Doctrine\ORM\EntityManager;
class MyController
{
/**
* #var EntityManager
*/
private $entityManager;
/**
* #param EntityManager $entityManager
*/
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
/**
*
*/
function indexAction()
{
//Do stuff
$entityManager = $this->getEntityManager();
}
/**
* #return EntityManager
*/
public function getEntityManager()
{
return $this->entityManager;
}
}
Your Factory:
<?php
namespace Application\Controller;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Doctrine\ORM\EntityManager;
class MyControllerFactory implements FactoryInterface
{
/**
* #param ServiceLocatorInterface $serviceLocator
* #return MyController
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
/** #var EntityManager $entityManager */
$serviceManager = $serviceLocator->getServiceLocator()
$entityManager = $serviceManager->get('doctrine.entitymanager.orm_default');
$myController = new MyController($entityManager);
return $myController;
}
}
There are two different approaches to this problem that are provided by ZF2.
Use the ServiceLocator to retrieve the EntityManager via a Factory.
In Module.php, add an anonymous function or Factory.
public function getServiceConfig()
{
return [
'factories' => [
'Doctrine\ORM\EntityManager' => function (ServiceManager $sm) {
$entityManager = $sm->get('doctrine.entitymanager.orm_default');
return $entityManager;
}
],
],
}
In your Controller
$em = $this->getServiceLocator()->get('Doctrine\ORM\EntityManager');
Create an Initializer and AwareInterface to inject the EntityManger into your controllers.
The AwareInterface can be added to any class which is initialized by the ServiceManager.
interface EntityManagerAwareInterface
{
/**
* Set EntityManager locator
*
* #param EntityManager $entityManager
*/
public function setEntityManager(EntityManager $entityManager);
/**
* Get service locator
*
* #return EntityManager
*/
public function getServiceLocator();
}
The Initializer is run when services are initialized by the ServiceManager. A check is performed to so if $instance is a EntityManagerAwareInterface.
use Application\EntityManager\EntityManagerAwareInterface;
use Zend\ServiceManager\InitializerInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class EntityManagerInitializer implements InitializerInterface
{
/**
* Initialize
*
* #param $instance
* #param ServiceLocatorInterface $serviceLocator
* #return mixed
*/
public function initialize($instance, ServiceLocatorInterface $serviceLocator)
{
if ($instance instanceof EntityManagerAwareInterface) {
$entityManager = $serviceLocator->get('doctrine.entitymanager.orm_default');
$instance->setEntityManager($entityManager);
}
}
}
Next add the Initializer to Module.php
public function getServiceConfig()
{
return [
'initializers' => [
'entityManager' => new EntityManagerInitializer(),
],
],
}
The advantage of going the Initializer route is there is a one time setup. Any class that implements the EntityManagerAwareInterface will have the EntityManager injected when the class is initialized.
My error message:
Illuminate \ Container \ BindingResolutionException
Target [Project\Backend\Service\Validation\ValidableInterface] is not instantiable.
I understand that interfaces and abstract classes are not instantiable so I know that Laravel should not be trying to instantiate my interface. Yet somehow it's trying to and I suspect this may be a binding issue...even though I believe I have bound it correctly and have registered it as a service provider.
I should mention that I have taken this example out of Chris Fidao's "Implementing Laravel" and it's almost identical!
This is the first couple of lines of my form class:
namespace Project\Backend\Service\Form\Job;
use Project\Backend\Service\Validation\ValidableInterface;
use Project\Backend\Repo\Job\JobInterface;
class JobForm {
/**
* Form Data
*
* #var array
*/
protected $data;
/**
* Validator
*
* #var \Project\Backend\Form\Service\ValidableInterface
*/
protected $validator;
/**
* Job repository
*
* #var \Project\Backend\Repo\Job\JobInterface
*/
protected $job;
public function __construct(ValidableInterface $validator, JobInterface $job)
{
$this->validator = $validator;
$this->job = $job;
}
This is the first few lines of my validator class:
namespace Project\Backend\Service\Form\Job;
use Project\Backend\Service\Validation\AbstractLaravelValidator;
class JobFormValidator extends AbstractLaravelValidator {
// Includes some validation rules
This is the abstract validator:
namespace Project\Backend\Service\Validation;
use Illuminate\Validation\Factory;
abstract class AbstractLaravelValidator implements ValidableInterface {
/**
* Validator
*
* #var \Illuminate\Validation\Factory
*/
protected $validator;
/**
* Validation data key => value array
*
* #var Array
*/
protected $data = array();
/**
* Validation errors
*
* #var Array
*/
protected $errors = array();
/**
* Validation rules
*
* #var Array
*/
protected $rules = array();
/**
* Custom validation messages
*
* #var Array
*/
protected $messages = array();
public function __construct(Factory $validator)
{
$this->validator = $validator;
}
This is the code where I bind it all to the app:
namespace Project\Backend\Service\Validation;
use Illuminate\Support\ServiceProvider;
use Project\Backend\Service\Form\Job\JobFormValidator;
class ValidationServiceProvider extends ServiceProvider {
public function register()
{
$app = $this->app;
$app->bind('Project\Backend\Service\Form\Job\JobFormValidator', function($app)
{
return new JobFormValidator($app['validator']);
});
}
}
This is then registered in app/config/app.php:
.....
'Project\Backend\Service\Validation\ValidationServiceProvider',
....
Finally these are the first few lines of my controller:
use Project\Backend\Repo\Job\JobInterface;
use Project\Backend\Service\Form\Job\JobForm;
class JobController extends \BaseController {
protected $jobform;
function __construct(JobInterface $job, JobForm $jobform)
{
$this->job = $job;
$this->jobform = $jobform;
}
You need to tell Laravel which instance it should use for a certain interface when injecting it into the constructor via type hinting.
You do this using the bind() method (in your service provider for example)
$app->bind('JobInterface', 'Job'); // Job being the class you want to be used
I highly recommend you watch the video here where Taylor Otwell, the creator of Laravel, explains this and some other things.
First you need to bind using
/app/Providers/AppServiceProvider.php
<?php namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider {
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
$this->app->bind('JobInterface', 'Job');
}
}
Once you complete this change
Run composer update
I am implementing a repository pattern in Laravel, and it seems to be very tedious. For example, let's say I have products then I have to create a ProductRepository interface then a ProductRepository class that implements that interface, now I have some very generic methods on the ProductRepository like:
retrieveAll
store
update
delete
And now I have to do the same thing for ingredients. It would be nice if I could simply create a ModelRepository interface with all those generic methods and implement it by passing a generic data type (namely the model), something similar to Java Generics:
<?php
interface ModelRepositoryInterface<T> {
function retrieveAll(): Collection<T>;
function store(T $item);
function update(int $id, T $data);
function delete(int $id);
}
But since php doesn't support generics how can I achieve this simplicity?
You can create a RepositoryServiceProvider to bind your repository interfaces to actual classes.
You can create a abstract Repository class with retrieveAll, store, update, delete and extend your Repositories and implement the interface. I have included below example with magic functions to be able to eloquent methods if I don't have any customization.
The below is not tested but its just to get the idea.
<?php
namespace App\Repositories;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
abstract class AbstractRepository implements RepositoryInterface
{
/**
* #var Builder|Model
*/
protected $model;
/**
* #return mixed
*/
public function getModel()
{
return $this->model;
}
/**
* #param array $columns
* #return \Illuminate\Database\Eloquent\Collection|Model[]
*/
public function all($columns = ['*'])
{
return $this->model->all($columns);
}
/**
* #param $name
* #param $arguments
* #return mixed
*/
public function __call($name, $arguments)
{
return $this->model->{$name}($arguments);
}
}
OrderRepository
<?php
namespace App\Repositories;
use App\Models\Order;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\DB;
class OrderRepository extends AbstractRepository implements OrderRepositoryInterface
{
/**
* OrderRepository constructor.
* #param Order $model
*/
public function __construct(Order $model)
{
$this->model = $model;
}
public function countPaid(): int
{
return $this->model->paid()->count();
}
/**
* #return int
*/
public function countReady(): int
{
return $this->model->ready()->count();
}
/**
* #return int
*/
public function countCancelled(): int
{
return $this->model->cancelled()->count();
}
}
OrderRepositoryInterface
<?php
namespace App\Repositories;
interface OrderRepositoryInterface
{
}
RepositoryServiceProvider
<?php
namespace App\Providers;
use App\Repositories\OrderRepository;
use App\Repositories\OrderRepositoryInterface;
use Illuminate\Support\ServiceProvider;
class RepositoryServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->bind(OrderRepositoryInterface::class, OrderRepository::class);
}
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
//
}
}
RepositoryInterface
<?php
namespace App\Repositories;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
interface RepositoryInterface
{
function retrieveAll(): Collection;
function store(Model $item);
function update(int $id, Model $data);
function delete(int $id);
}