Reflection
Reflection describes the practice to retrieve information about a program itself and it’s internals during runtime. It usually also allows to modify behavior and properties.
PHP already provides reflection capabilities, using them it’s possible to, for
example, change the accessibility of properties, e.g. from protected
to
public
, and access methods even though access to them is restricted.
Additionally it’s possible to gain information about what arguments a method expects, and whether these are required or optional.
Reflection in Flow
Flow provides a powerful extension to PHP’s own basic reflection functionality, not only adding more capabilities, but also speeding up reflection massively. It makes heavy use of the annotations (tags) found in the documentation blocks, which is another important reason why you should exercise care about a correct formatting and respecting some rules when applying these.
Note
A specific description about these DocComment formatting requirements is available in the Coding Guidelines.
The reflection of Flow is handled via the Reflection Service which can be injected as usual.
Example: defining and accessing simple reflection information
/**
* This is the description of the class.
*/
class CustomizedGoodsOrder extends AbstractOrder {
/**
* @var \Magrathea\Erp\Service\OrderNumberGenerator
*/
protected $orderNumberGenerator;
/**
* @var \DateTime
*/
protected $timestamp;
/**
* The customer who placed this order
* @var \Magrathea\Erp\Domain\Model\Customer
*/
protected $customer;
/**
* The order number, for example ME-3020-BB
* @var string
*/
protected $orderNumber;
/**
* @param \Magrathea\Erp\Domain\Model\Customer $customer;
*/
public function __construct(Customer $customer) {
$this->timestamp = new \DateTime();
$this->customer = $customer;
$this->orderNumber = $this->orderNumberGenerator->createOrderNumber();
}
/**
* @return \Magrathea\Erp\Domain\Model\Customer
*/
public function getCustomer() {
return $this->customer;
}
}
In an application, after wiring $reflectionService with
\Neos\Flow\Reflection\ReflectionService
via, for example, Dependency
Injection, there are a couple of options available. The following two examples
just should give a slight overview.
Listing all sub classes of the AbstractOrder
class*
$this->reflectionService->getAllSubClassNamesForClass('Magrathea\Erp\Domain\Model\AbstractOrder'));
returns array('Magrathea\Erp\Domain\Model\CustomizedGoodsOrder')
.
Fetching the plain annotation tags of the customer
property from the
CustomizedGoodsOrder
class
$this->reflectionService->getPropertyTagsValues('Magrathea\Erp\Domain\Model\CustomizedGoodsOrder', 'customer'));``
returns array('var' => '\Magrathea\Erp\Domain\Model\Customer')
The API doc of the ReflectionService shows all available methods. Generally said, whatever information is needed to gain information about classes, their properties and methods and their sub or parent classes and interface implementations, can be retrieved via the reflection service.
Custom annotation classes
A powerful feature is the ability to introduce customized annotation classes;
this achieves, for example, what across the framework often can be seen with
the @Flow\…
or @ORM\…
annotations.
Create an annotation class
An annotation class is best created in a direct subdirectory of your
Classes
one and carries the name Annotations
. The class itself receives
the name exactly like the annotation should be.
Example: a ``Reportable`` annotation for use as class and property annotation:
<?php
namespace Magrathea\Erp\Annotations;
/**
* Marks the class or property as reportable, It will then be doing
* foo and bar, but not quux.
*
* @Annotation
* @Target({"CLASS", "PROPERTY"})
*/
final class Reportable {
/**
* The name of the report. (Can be given as anonymous argument.)
* @var string
*/
public $reportName;
/**
* @param array $values
*/
public function __construct(array $values) {
if (!isset($values['value']) && !isset($values['reportName'])) {
throw new \InvalidArgumentException('A Reporting annotation must specify a report name.', 1234567890);
}
$this->reportName = isset($values['reportName']) ? $values['reportName'] : $values['value'];
}
}
?>
This defines a Reportable
annotation, with one argument, reportName
,
which is required in this case. It can be given with it’s name or anonymous,
as the sole (and/or first) argument to the value. The annotation can only be
used on classes or properties, using it on a method will throw an exception.
This is checked by the annotation parser, based on the Target
annotation.
The documentation of the class and it’s properties can be used to generate
annotation reference documentation, so provide helpful descriptions and names.
Note
An annotation can also be simpler, using only public properties. The use of a constructor allows for some checks and gives the possibility to have anonymous arguments, if needed.
This annotation now can be set to arbitrary classes or properties, also across
packages. The namespace is introduced using the use
statement and to
shorten the annotation; in the class this annotation can be set to the class
itself and to properties:
use Magrathea\Erp\Annotations as ERP;
/**
* This is the description of the class.
* @ERP\Reportable(reportName="OrderReport")
*/
class CustomizedGoodsOrder extends AbstractOrder {
/**
* @ERP\Reportable
* @var \Magrathea\Erp\Service\OrderNumberGenerator
*/
protected $orderNumberGenerator;
Accessing annotation classes
With the reflection service, just an instance of your created annotation class is returned, populated with the appropriate information of the annotation itself! So complying with the walkthrough, the following approach is possible:
$classAnnotation = $this->reflectionService->getClassAnnotation(
'Magrathea\Erp\Domain\Model\CustomizedGoodsOrder',
'Magrathea\Erp\Annotations\Reportable'
);
$classAnnotation instanceof \Magrathea\Erp\Annotations\Reportable;
$classAnnotation->reportName === 'OrderReport';
$propertyAnnotation = $this->reflectionService->getPropertyAnnotation(
'Magrathea\Erp\Domain\Model\CustomizedGoodsOrder',
'orderNumberGenerator',
'Magrathea\Erp\Annotations\Reportable'
);
$propertyAnnotation instanceof \Magrathea\Erp\Annotations\Reportable;
$propertyAnnotation->reportName === null;
It’s even possible to collect all annotation classes of a particular class, done via
reflectionService->getClassAnnotations('Magrathea\Erp\Domain\Model\CustomizedGoodsOrder');
which returns an array of annotations, in this case Neos\Flow\Annotations\Entity
and our Magrathea\Erp\Annotations\Reportable
.