2023-03-11 12:04:29 +03:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* Zend Framework (http://framework.zend.com/)
|
|
|
|
*
|
|
|
|
* @link http://github.com/zendframework/zf2 for the canonical source repository
|
2023-04-01 09:03:34 +03:00
|
|
|
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
|
2023-03-11 12:04:29 +03:00
|
|
|
* @license http://framework.zend.com/license/new-bsd New BSD License
|
|
|
|
*/
|
|
|
|
|
|
|
|
namespace Zend\Form\Annotation;
|
|
|
|
|
|
|
|
use ArrayObject;
|
|
|
|
use Zend\Code\Annotation\AnnotationCollection;
|
|
|
|
use Zend\Code\Annotation\AnnotationManager;
|
|
|
|
use Zend\Code\Annotation\Parser;
|
|
|
|
use Zend\Code\Reflection\ClassReflection;
|
|
|
|
use Zend\EventManager\Event;
|
|
|
|
use Zend\EventManager\EventManager;
|
|
|
|
use Zend\EventManager\EventManagerAwareInterface;
|
|
|
|
use Zend\EventManager\EventManagerInterface;
|
|
|
|
use Zend\Form\Exception;
|
|
|
|
use Zend\Form\Factory;
|
|
|
|
use Zend\Form\FormFactoryAwareInterface;
|
|
|
|
use Zend\Stdlib\ArrayUtils;
|
|
|
|
|
|
|
|
/**
|
2023-04-01 09:03:34 +03:00
|
|
|
* Parses the properties of a class for annotations in order to create a form
|
|
|
|
* and input filter definition.
|
2023-03-11 12:04:29 +03:00
|
|
|
*/
|
|
|
|
class AnnotationBuilder implements EventManagerAwareInterface, FormFactoryAwareInterface
|
|
|
|
{
|
2023-04-01 09:03:34 +03:00
|
|
|
/**
|
|
|
|
* @var Parser\DoctrineAnnotationParser
|
|
|
|
*/
|
|
|
|
protected $annotationParser;
|
|
|
|
|
2023-03-11 12:04:29 +03:00
|
|
|
/**
|
|
|
|
* @var AnnotationManager
|
|
|
|
*/
|
|
|
|
protected $annotationManager;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var EventManagerInterface
|
|
|
|
*/
|
|
|
|
protected $events;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var Factory
|
|
|
|
*/
|
|
|
|
protected $formFactory;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var object
|
|
|
|
*/
|
|
|
|
protected $entity;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array Default annotations to register
|
|
|
|
*/
|
|
|
|
protected $defaultAnnotations = array(
|
|
|
|
'AllowEmpty',
|
|
|
|
'Attributes',
|
|
|
|
'ComposedObject',
|
2023-04-01 09:03:34 +03:00
|
|
|
'ContinueIfEmpty',
|
2023-03-11 12:04:29 +03:00
|
|
|
'ErrorMessage',
|
|
|
|
'Exclude',
|
|
|
|
'Filter',
|
|
|
|
'Flags',
|
|
|
|
'Hydrator',
|
|
|
|
'Input',
|
|
|
|
'InputFilter',
|
2023-04-01 09:03:34 +03:00
|
|
|
'Instance',
|
2023-03-11 12:04:29 +03:00
|
|
|
'Name',
|
|
|
|
'Object',
|
|
|
|
'Options',
|
|
|
|
'Required',
|
|
|
|
'Type',
|
|
|
|
'ValidationGroup',
|
|
|
|
'Validator'
|
|
|
|
);
|
|
|
|
|
2023-04-01 09:03:34 +03:00
|
|
|
/**
|
|
|
|
* @var bool
|
|
|
|
*/
|
|
|
|
protected $preserveDefinedOrder = false;
|
|
|
|
|
2023-03-11 12:04:29 +03:00
|
|
|
/**
|
|
|
|
* Set form factory to use when building form from annotations
|
|
|
|
*
|
|
|
|
* @param Factory $formFactory
|
|
|
|
* @return AnnotationBuilder
|
|
|
|
*/
|
|
|
|
public function setFormFactory(Factory $formFactory)
|
|
|
|
{
|
|
|
|
$this->formFactory = $formFactory;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set annotation manager to use when building form from annotations
|
|
|
|
*
|
|
|
|
* @param AnnotationManager $annotationManager
|
|
|
|
* @return AnnotationBuilder
|
|
|
|
*/
|
|
|
|
public function setAnnotationManager(AnnotationManager $annotationManager)
|
|
|
|
{
|
2023-04-01 09:03:34 +03:00
|
|
|
$parser = $this->getAnnotationParser();
|
2023-03-11 12:04:29 +03:00
|
|
|
foreach ($this->defaultAnnotations as $annotationName) {
|
|
|
|
$class = __NAMESPACE__ . '\\' . $annotationName;
|
|
|
|
$parser->registerAnnotation($class);
|
|
|
|
}
|
|
|
|
$annotationManager->attach($parser);
|
|
|
|
$this->annotationManager = $annotationManager;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set event manager instance
|
|
|
|
*
|
|
|
|
* @param EventManagerInterface $events
|
|
|
|
* @return AnnotationBuilder
|
|
|
|
*/
|
|
|
|
public function setEventManager(EventManagerInterface $events)
|
|
|
|
{
|
|
|
|
$events->setIdentifiers(array(
|
|
|
|
__CLASS__,
|
|
|
|
get_class($this),
|
|
|
|
));
|
|
|
|
$events->attach(new ElementAnnotationsListener());
|
|
|
|
$events->attach(new FormAnnotationsListener());
|
|
|
|
$this->events = $events;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve form factory
|
|
|
|
*
|
|
|
|
* Lazy-loads the default form factory if none is currently set.
|
|
|
|
*
|
|
|
|
* @return Factory
|
|
|
|
*/
|
|
|
|
public function getFormFactory()
|
|
|
|
{
|
|
|
|
if ($this->formFactory) {
|
|
|
|
return $this->formFactory;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->formFactory = new Factory();
|
|
|
|
return $this->formFactory;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve annotation manager
|
|
|
|
*
|
|
|
|
* If none is currently set, creates one with default annotations.
|
|
|
|
*
|
|
|
|
* @return AnnotationManager
|
|
|
|
*/
|
|
|
|
public function getAnnotationManager()
|
|
|
|
{
|
|
|
|
if ($this->annotationManager) {
|
|
|
|
return $this->annotationManager;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->setAnnotationManager(new AnnotationManager());
|
|
|
|
return $this->annotationManager;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get event manager
|
|
|
|
*
|
|
|
|
* @return EventManagerInterface
|
|
|
|
*/
|
|
|
|
public function getEventManager()
|
|
|
|
{
|
|
|
|
if (null === $this->events) {
|
|
|
|
$this->setEventManager(new EventManager());
|
|
|
|
}
|
|
|
|
return $this->events;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates and returns a form specification for use with a factory
|
|
|
|
*
|
|
|
|
* Parses the object provided, and processes annotations for the class and
|
|
|
|
* all properties. Information from annotations is then used to create
|
|
|
|
* specifications for a form, its elements, and its input filter.
|
|
|
|
*
|
|
|
|
* @param string|object $entity Either an instance or a valid class name for an entity
|
|
|
|
* @throws Exception\InvalidArgumentException if $entity is not an object or class name
|
|
|
|
* @return ArrayObject
|
|
|
|
*/
|
|
|
|
public function getFormSpecification($entity)
|
|
|
|
{
|
|
|
|
if (!is_object($entity)) {
|
|
|
|
if ((is_string($entity) && (!class_exists($entity))) // non-existent class
|
|
|
|
|| (!is_string($entity)) // not an object or string
|
|
|
|
) {
|
|
|
|
throw new Exception\InvalidArgumentException(sprintf(
|
|
|
|
'%s expects an object or valid class name; received "%s"',
|
|
|
|
__METHOD__,
|
|
|
|
var_export($entity, 1)
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->entity = $entity;
|
|
|
|
$annotationManager = $this->getAnnotationManager();
|
|
|
|
$formSpec = new ArrayObject();
|
|
|
|
$filterSpec = new ArrayObject();
|
|
|
|
|
|
|
|
$reflection = new ClassReflection($entity);
|
|
|
|
$annotations = $reflection->getAnnotations($annotationManager);
|
|
|
|
|
|
|
|
if ($annotations instanceof AnnotationCollection) {
|
|
|
|
$this->configureForm($annotations, $reflection, $formSpec, $filterSpec);
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($reflection->getProperties() as $property) {
|
|
|
|
$annotations = $property->getAnnotations($annotationManager);
|
|
|
|
|
|
|
|
if ($annotations instanceof AnnotationCollection) {
|
|
|
|
$this->configureElement($annotations, $property, $formSpec, $filterSpec);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isset($formSpec['input_filter'])) {
|
|
|
|
$formSpec['input_filter'] = $filterSpec;
|
2023-04-01 09:03:34 +03:00
|
|
|
} elseif (is_array($formSpec['input_filter'])) {
|
|
|
|
$formSpec['input_filter'] = ArrayUtils::merge($filterSpec->getArrayCopy(), $formSpec['input_filter']);
|
2023-03-11 12:04:29 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return $formSpec;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a form from an object.
|
|
|
|
*
|
|
|
|
* @param string|object $entity
|
|
|
|
* @return \Zend\Form\Form
|
|
|
|
*/
|
|
|
|
public function createForm($entity)
|
|
|
|
{
|
|
|
|
$formSpec = ArrayUtils::iteratorToArray($this->getFormSpecification($entity));
|
|
|
|
$formFactory = $this->getFormFactory();
|
|
|
|
return $formFactory->createForm($formSpec);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the entity used to construct the form.
|
|
|
|
*
|
|
|
|
* @return object
|
|
|
|
*/
|
|
|
|
public function getEntity()
|
|
|
|
{
|
|
|
|
return $this->entity;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Configure the form specification from annotations
|
|
|
|
*
|
|
|
|
* @param AnnotationCollection $annotations
|
|
|
|
* @param ClassReflection $reflection
|
|
|
|
* @param ArrayObject $formSpec
|
|
|
|
* @param ArrayObject $filterSpec
|
|
|
|
* @return void
|
|
|
|
* @triggers discoverName
|
|
|
|
* @triggers configureForm
|
|
|
|
*/
|
|
|
|
protected function configureForm($annotations, $reflection, $formSpec, $filterSpec)
|
|
|
|
{
|
|
|
|
$name = $this->discoverName($annotations, $reflection);
|
|
|
|
$formSpec['name'] = $name;
|
|
|
|
$formSpec['attributes'] = array();
|
|
|
|
$formSpec['elements'] = array();
|
|
|
|
$formSpec['fieldsets'] = array();
|
|
|
|
|
|
|
|
$events = $this->getEventManager();
|
|
|
|
foreach ($annotations as $annotation) {
|
|
|
|
$events->trigger(__FUNCTION__, $this, array(
|
|
|
|
'annotation' => $annotation,
|
|
|
|
'name' => $name,
|
|
|
|
'formSpec' => $formSpec,
|
|
|
|
'filterSpec' => $filterSpec,
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Configure an element from annotations
|
|
|
|
*
|
|
|
|
* @param AnnotationCollection $annotations
|
|
|
|
* @param \Zend\Code\Reflection\PropertyReflection $reflection
|
|
|
|
* @param ArrayObject $formSpec
|
|
|
|
* @param ArrayObject $filterSpec
|
|
|
|
* @return void
|
|
|
|
* @triggers checkForExclude
|
|
|
|
* @triggers discoverName
|
|
|
|
* @triggers configureElement
|
|
|
|
*/
|
|
|
|
protected function configureElement($annotations, $reflection, $formSpec, $filterSpec)
|
|
|
|
{
|
|
|
|
// If the element is marked as exclude, return early
|
|
|
|
if ($this->checkForExclude($annotations)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$events = $this->getEventManager();
|
|
|
|
$name = $this->discoverName($annotations, $reflection);
|
|
|
|
|
|
|
|
$elementSpec = new ArrayObject(array(
|
|
|
|
'flags' => array(),
|
|
|
|
'spec' => array(
|
|
|
|
'name' => $name
|
|
|
|
),
|
|
|
|
));
|
|
|
|
$inputSpec = new ArrayObject(array(
|
|
|
|
'name' => $name,
|
|
|
|
));
|
|
|
|
|
|
|
|
$event = new Event();
|
|
|
|
$event->setParams(array(
|
|
|
|
'name' => $name,
|
|
|
|
'elementSpec' => $elementSpec,
|
|
|
|
'inputSpec' => $inputSpec,
|
|
|
|
'formSpec' => $formSpec,
|
|
|
|
'filterSpec' => $filterSpec,
|
|
|
|
));
|
|
|
|
foreach ($annotations as $annotation) {
|
|
|
|
$event->setParam('annotation', $annotation);
|
|
|
|
$events->trigger(__FUNCTION__, $this, $event);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Since "type" is a reserved name in the filter specification,
|
|
|
|
// we need to add the specification without the name as the key.
|
|
|
|
// In all other cases, though, the name is fine.
|
|
|
|
if ($event->getParam('inputSpec')->count() > 1) {
|
|
|
|
if ($name === 'type') {
|
|
|
|
$filterSpec[] = $event->getParam('inputSpec');
|
|
|
|
} else {
|
|
|
|
$filterSpec[$name] = $event->getParam('inputSpec');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$elementSpec = $event->getParam('elementSpec');
|
|
|
|
$type = (isset($elementSpec['spec']['type']))
|
|
|
|
? $elementSpec['spec']['type']
|
|
|
|
: 'Zend\Form\Element';
|
|
|
|
|
2023-04-01 09:03:34 +03:00
|
|
|
// Compose as a fieldset or an element, based on specification type.
|
|
|
|
// If preserve defined order is true, all elements are composed as elements to keep their ordering
|
|
|
|
if (!$this->preserveDefinedOrder() && is_subclass_of($type, 'Zend\Form\FieldsetInterface')) {
|
2023-03-11 12:04:29 +03:00
|
|
|
if (!isset($formSpec['fieldsets'])) {
|
|
|
|
$formSpec['fieldsets'] = array();
|
|
|
|
}
|
|
|
|
$formSpec['fieldsets'][] = $elementSpec;
|
|
|
|
} else {
|
|
|
|
if (!isset($formSpec['elements'])) {
|
|
|
|
$formSpec['elements'] = array();
|
|
|
|
}
|
|
|
|
$formSpec['elements'][] = $elementSpec;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-01 09:03:34 +03:00
|
|
|
/**
|
|
|
|
* @param bool $preserveDefinedOrder
|
|
|
|
* @return $this
|
|
|
|
*/
|
|
|
|
public function setPreserveDefinedOrder($preserveDefinedOrder)
|
|
|
|
{
|
|
|
|
$this->preserveDefinedOrder = (bool) $preserveDefinedOrder;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function preserveDefinedOrder()
|
|
|
|
{
|
|
|
|
return $this->preserveDefinedOrder;
|
|
|
|
}
|
|
|
|
|
2023-03-11 12:04:29 +03:00
|
|
|
/**
|
|
|
|
* Discover the name of the given form or element
|
|
|
|
*
|
|
|
|
* @param AnnotationCollection $annotations
|
|
|
|
* @param \Reflector $reflection
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
protected function discoverName($annotations, $reflection)
|
|
|
|
{
|
|
|
|
$results = $this->getEventManager()->trigger('discoverName', $this, array(
|
|
|
|
'annotations' => $annotations,
|
|
|
|
'reflection' => $reflection,
|
|
|
|
), function ($r) {
|
|
|
|
return (is_string($r) && !empty($r));
|
|
|
|
});
|
|
|
|
return $results->last();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determine if an element is marked to exclude from the definitions
|
|
|
|
*
|
|
|
|
* @param AnnotationCollection $annotations
|
|
|
|
* @return true|false
|
|
|
|
*/
|
|
|
|
protected function checkForExclude($annotations)
|
|
|
|
{
|
|
|
|
$results = $this->getEventManager()->trigger('checkForExclude', $this, array(
|
|
|
|
'annotations' => $annotations,
|
|
|
|
), function ($r) {
|
|
|
|
return (true === $r);
|
|
|
|
});
|
|
|
|
return (bool) $results->last();
|
|
|
|
}
|
|
|
|
|
2023-04-01 09:03:34 +03:00
|
|
|
/**
|
|
|
|
* @return \Zend\Code\Annotation\Parser\DoctrineAnnotationParser
|
|
|
|
*/
|
|
|
|
public function getAnnotationParser()
|
|
|
|
{
|
|
|
|
if (null === $this->annotationParser) {
|
|
|
|
$this->annotationParser = new Parser\DoctrineAnnotationParser();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->annotationParser;
|
|
|
|
}
|
|
|
|
|
2023-03-11 12:04:29 +03:00
|
|
|
/**
|
|
|
|
* Checks if the object has this class as one of its parents
|
|
|
|
*
|
|
|
|
* @see https://bugs.php.net/bug.php?id=53727
|
|
|
|
* @see https://github.com/zendframework/zf2/pull/1807
|
|
|
|
*
|
2023-04-01 09:03:34 +03:00
|
|
|
* @deprecated since zf 2.3 requires PHP >= 5.3.23
|
|
|
|
*
|
2023-03-11 12:04:29 +03:00
|
|
|
* @param string $className
|
|
|
|
* @param string $type
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
protected static function isSubclassOf($className, $type)
|
|
|
|
{
|
2023-04-01 09:03:34 +03:00
|
|
|
return is_subclass_of($className, $type);
|
2023-03-11 12:04:29 +03:00
|
|
|
}
|
|
|
|
}
|