I have created a custom #TimestampAware annotation which can be used to automatically update a timestamp property when persisting or updating my entities (see code below).
When using this annotation directly on an entity class everything works fine. However, when using the annotation on a base class, it is not recognized on the inherited sub-classes.
/**
* #TimestampAware
*/
class SomeEntity { ... } // works fine
/**
* #TimestampAware
*/
class BaseEntity { ... }
class SubEntity extends BaseEntity { ... } // Annotation is not recognized
Is it the intended behavior of Doctrine annotations and the Annotation Reader class to only look for annotation directly on the current class and no include its parent classes? Or is there something wrong with my implementation?
My annotation:
use Doctrine\Common\Annotations\Annotation;
use Doctrine\Common\Annotations\Reader;
/**
* #Annotation
* #Target("CLASS")
*/
final class TimestampAware { }
The annotation listener:
use Doctrine\Common\EventSubscriber;
class TimestampAwareSubscriber implements EventSubscriber {
protected $reader;
protected $logger;
public function __construct(Reader $reader, LoggerInterface $logger) {
$this->reader = $reader;
$this->logger = $logger;
}
public function getSubscribedEvents() {
return [
Events::prePersist,
Events::preUpdate,
];
}
public function prePersist(LifecycleEventArgs $args) {
$this->onPersistOrUpdate($args);
}
public function preUpdate(LifecycleEventArgs $args) {
$this->onPersistOrUpdate($args);
}
protected function onPersistOrUpdate(LifecycleEventArgs $args) {
$this->logger->info("Reader: ".get_class($this->reader));
$entity = $args->getEntity();
$reflection = new \ReflectionClass($entity);
$timestampAware = $this->reader->getClassAnnotation(
$reflection,
TimestampAware::class
);
if (!$timestampAware) {
return;
}
// update timestamp...
}
}
The Annotation Reader inspects only the relevant class, it does not read the annotations of the parent class. This can easily be checked with code like this:
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;
AnnotationRegistry::registerLoader('class_exists');
/**
* #Annotation
* #Target("CLASS")
*/
class Annotated {}
/**
* #Annotated
**/
class ParentFoo {}
class ChildFoo extends ParentFoo {}
$reader = new AnnotationReader();
$parentAnnotated = $reader->getClassAnnotation(
new ReflectionClass(ParentFoo::class),
Annotated::class
);
var_dump($parentAnnotated);
// Outputs `object(Annotated)#10 (0) {}`
$childAnnotated = $reader->getClassAnnotation(
new ReflectionClass(ChildFoo::class),
Annotated::class
);
var_dump($childAnnotated);
// outputs `null`
If you want to check parent classes, you'll have to do it yourself. ReflectionClass provides the getParentClass() method which you could do to check the class hierarchy.
Tangentially, I've found this package that claims to extend annotations so that they are usable with inheritance directly. Haven't checked if it's any good.
In addition to the great answer by #yivi I would like to share the code I used to solve the problem. Maybe this helps others how encounter the same problem:
protected function onPersistOrUpdate(LifecycleEventArgs $args) {
$this->logger->info("Reader: ".get_class($this->reader));
$entity = $args->getEntity();
$reflection = new \ReflectionClass($entity);
$timestampAware = $this->reader->getClassAnnotation(
$reflection,
TimestampAware::class
);
while (!$timestampAware && $reflection = $reflection->getParentClass()) {
$timestampAware = $this->reader->getClassAnnotation(
$reflection,
TimestampLogAware::class
);
}
if (!$timestampAware) {
return;
}
// update timestamp...
}
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 have a Symfony2 controller as follows:
/**
* #Security("is_granted('my_permission')")
*/
class MyController extends Controller
{
/**
* #Security("is_granted('another_permission')")
*/
public function myAction()
{
// ...
}
}
It appears the #Security annotation on the myAction() method overrides/ignores the parent #Security annotation on the MyController class. Is there any way to make these stack, to avoid having to do:
/**
* #Security("is_granted('my_permission') and is_granted('another_permission')")
*/
public function myAction()
{
// ...
}
on every action method in the controller?
It appears the #Security annotation on the myAction method overrides/ignores the parent #Security annotation on the MyController class.
Indeed, Sensio\Bundle\FrameworkExtraBundle\Configuration\Security annotation doesn't allows nested configuration (see allowArray() method). So method configuration overrides class configuration for #Security annotation.
Is there any way to make these stack...
Not in a simple way, you need create three class and one trick to not reimplement the whole parent code:
Security.php
namespace AppBundle\Configuration;
/**
* #Annotation
*/
class Security extends \Sensio\Bundle\FrameworkExtraBundle\Configuration\Security
{
public function getAliasName()
{
return 'app_security';
}
public function allowArray()
{
// allow nested configuration (class/method).
return true;
}
}
SecurityConfiguration.php
This class allow you compound the final security expression through all security configurations (class/method).
namespace AppBundle\Configuration;
class SecurityConfiguration
{
/**
* #var Security[]
*/
private $configurations;
public function __construct(array $configurations)
{
$this->configurations = $configurations;
}
public function getExpression()
{
$expressions = [];
foreach ($this->configurations as $configuration) {
$expressions[] = $configuration->getExpression();
}
return implode(' and ', $expressions);
}
}
SecurityListener.php
namespace AppBundle\EventListener;
use AppBundle\Configuration\SecurityConfiguration;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class SecurityListener extends \Sensio\Bundle\FrameworkExtraBundle\EventListener\SecurityListener
{
public function onKernelController(FilterControllerEvent $event)
{
$request = $event->getRequest();
if (!$configuration = $request->attributes->get('_app_security')) {
return;
}
// trick to simulate one security configuration (all in one class/method).
$request->attributes->set('_security', new SecurityConfiguration($configuration));
parent::onKernelController($event);
}
public static function getSubscribedEvents()
{
// this listener must be called after Sensio\Bundle\FrameworkExtraBundle\EventListener\ControllerListener.
return array(KernelEvents::CONTROLLER => array('onKernelController', -1));
}
}
services.yml
services:
app.security.listener:
class: AppBundle\EventListener\SecurityListener
parent: sensio_framework_extra.security.listener
tags:
- { name: kernel.event_subscriber }
Finally, just use your #AppBundle\Configuration\Security annotation instead the standard one.
Here's my try:
Using, in app/config/security.yml, this role hierarchy:
role_hierarchy:
ROLE_CLASS: ROLE_CLASS
ROLE_METHOD: [ROLE_CLASS, ROLE_METHOD]
And if I have two users: user1 with ROLE_CLASS, and user2 with ROLE_METHOD (which means this user has both roles), then the first user can see all the pages created inside the controller, except the ones that have additional restrictions.
Controller example:
/**
* #Security("is_granted('ROLE_CLASS')")
*/
class SomeController extends Controller
{
/**
* #Route("/page1", name="page1")
* #Security("is_granted('ROLE_METHOD')")
*/
public function page1()
{
return $this->render('default/page1.html.twig');
}
/**
* #Route("/page2", name="page2")
*/
public function page2()
{
return $this->render('default/page2.html.twig');
}
}
So because user1 has ROLE_CLASS, he is able to see just /page2, but not /page1, as he will receive a 403 Expression "is_granted('ROLE_METHOD')" denied access. error (for dev obviously).
On the other hand, user2, having ROLE_METHOD (and ROLE_CLASS), he is able to see both pages.
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.
I want to know if there is a solution on how to unit-test a PHP trait.
I know we can test a class which is using the trait, but I was wondering if there are better approaches.
Thanks for any advice in advance :)
EDIT
One alternative is to use the Trait in the test class itself as I'm going to demonstrate bellow.
But I'm not that keen on this approach since there is no guaranty there are no similar method names between the trait, the class and also the PHPUnit_Framework_TestCase (in this example):
Here is an example trait:
trait IndexableTrait
{
/** #var int */
private $index;
/**
* #param $index
* #return $this
* #throw \InvalidArgumentException
*/
public function setIndex($index)
{
if (false === filter_var($index, FILTER_VALIDATE_INT)) {
throw new \InvalidArgumentException('$index must be integer.');
}
$this->index = $index;
return $this;
}
/**
* #return int|null
*/
public function getIndex()
{
return $this->index;
}
}
and its test:
class TheAboveTraitTest extends \PHPUnit_Framework_TestCase
{
use TheAboveTrait;
public function test_indexSetterAndGetter()
{
$this->setIndex(123);
$this->assertEquals(123, $this->getIndex());
}
public function test_indexIntValidation()
{
$this->setExpectedException(\Exception::class, '$index must be integer.');
$this->setIndex('bad index');
}
}
You can test a Trait using a similar to testing an Abstract Class' concrete methods.
PHPUnit has a method getMockForTrait which will return an object that uses the trait. Then you can test the traits functions.
Here is the example from the documentation:
<?php
trait AbstractTrait
{
public function concreteMethod()
{
return $this->abstractMethod();
}
public abstract function abstractMethod();
}
class TraitClassTest extends PHPUnit_Framework_TestCase
{
public function testConcreteMethod()
{
$mock = $this->getMockForTrait('AbstractTrait');
$mock->expects($this->any())
->method('abstractMethod')
->will($this->returnValue(TRUE));
$this->assertTrue($mock->concreteMethod());
}
}
?>
You can also use getObjectForTrait , then assert the actual result if you want.
class YourTraitTest extends TestCase
{
public function testGetQueueConfigFactoryWillCreateConfig()
{
$obj = $this->getObjectForTrait(YourTrait::class);
$config = $obj->getQueueConfigFactory();
$this->assertInstanceOf(QueueConfigFactory::class, $config);
}
public function testGetQueueServiceWithoutInstanceWillCreateConfig()
{
$obj = $this->getObjectForTrait(YourTrait::class);
$service = $obj->getQueueService();
$this->assertInstanceOf(QueueService::class, $service);
}
}
Since PHP 7 we can now use annonymous classes...
$class = new class {
use TheTraitToTest;
};
// We now have everything available to test using $class
I'm quite new to using abstract classes and interfaces in PHP.
I'm trying to initiate a extend of an abstract class, but it won't work. It might be a Laravel specific issue i'm having.
This is the case:
I have an interface
I have an abstract class that implements the interface
I have 'regular' class that extends the abstract class
I try to implement the class
This is the interface:
<?php namespace Collection\Services\Validation;
interface SomeInterface {
public function with(array $input);
public function passes();
public function errors();
}
This is the abstract class:
<?php namespace Collection\Services\Validation;
use Illuminate\Validation\Factory;
abstract class SomeClass implements SomeInterface {
protected $validator;
protected $data = array();
protected $errors = array();
protected $rules = array();
public function __construct(Factory $validator)
{
$this->validator = $validator;
}
public function with(array $data)
{
$this->data = $data;
return $this;
}
public function passes()
{
$validator = $this->validator->make($this->data, $this->rules);
if( $validator->fails() )
{
$this->errors = $validator->messages();
return false;
}
return true;
}
public function errors()
{
return $this->errors;
}
}
This is the "regular" class:
<?php namespace Collection\Services\Validation;
class SomeClassExtender extends SomeClass {
public function sayBye()
{
return 'bye';
}
}
This is the implementation:
<?php
use Collection\Services\Validation\PageFormValidator;
use Collection\Services\Validation\SomeClassExtender;
class PagesController extends BaseController {
protected $someClass;
public function __construct(SomeClassExtender $class)
{
$this->someClass = $class;
}
And then i get this error:
Illuminate \ Container \ BindingResolutionException
Target [Symfony\Component\Translation\TranslatorInterface] is not instantiable.
If i remove the initiation of the Factory class, the error is gone. The Factory class is also just a regular class.
What am i doing wrong here?
I see that you're following Chris Fidao's book. Got the same error as you are.
This is my solution, put this inside global.php
App::bind('Symfony\Component\Translation\TranslatorInterface', function($app) {
return $app['translator'];
});
EDIT:
I think the problem with Factory is that you need to bind the translator interface to $app['translator']. Here's what I found...
If you look at the Factory class, it requires the translator interface -- A quick look into its public __construct in the API:
public function __construct(TranslatorInterface $translator, Container $container = null)
{
$this->container = $container;
$this->translator = $translator;
}
Then if you look at the public function register() in ValidationServiceProvider, you'll find that Laravel binds the TranslatorInterface to $app['translator']:
$validator = new Factory($app['translator'], $app);
Then seems like a service provider to bind $app['translator'] is needed, or we can just bind it in global.php.
I think this is the best working solution, found the same exact problem . Solved it by,
injecting the already bound "validator" object in the Validator facade.
<?php namespace Illuminate\Support\Facades;
/**
* #see \Illuminate\Validation\Factory
*/
class Validator extends Facade {
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor() { return 'validator'; }
}
Instantiate the Factory class with App::make('validator')
Do it this way,when instantiating your SomeClassExtender class.
$someClassExtender = new SomeClassExtender( App::make('validator') );
This article by #PhilipBrown Advanced Validation as a Service for Laravel 4 - http://culttt.com/2014/01/13/advanced-validation-service-laravel-4/