3 * Zend Framework (http://framework.zend.com/)
5 * @link http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license http://framework.zend.com/license/new-bsd New BSD License
10 namespace Zend\Form\Annotation
;
14 use Zend\Code\Annotation\AnnotationCollection
;
15 use Zend\Code\Annotation\AnnotationManager
;
16 use Zend\Code\Annotation\Parser
;
17 use Zend\Code\Reflection\ClassReflection
;
18 use Zend\EventManager\Event
;
19 use Zend\EventManager\EventManager
;
20 use Zend\EventManager\EventManagerAwareInterface
;
21 use Zend\EventManager\EventManagerInterface
;
22 use Zend\Form\Exception
;
23 use Zend\Form\Factory
;
24 use Zend\Form\FormFactoryAwareInterface
;
25 use Zend\Stdlib\ArrayUtils
;
28 * Parses a class' properties for annotations in order to create a form and
29 * input filter definition.
31 class AnnotationBuilder
implements EventManagerAwareInterface
, FormFactoryAwareInterface
34 * @var AnnotationManager
36 protected $annotationManager;
39 * @var EventManagerInterface
46 protected $formFactory;
54 * @var array Default annotations to register
56 protected $defaultAnnotations = array(
77 * Set form factory to use when building form from annotations
79 * @param Factory $formFactory
80 * @return AnnotationBuilder
82 public function setFormFactory(Factory
$formFactory)
84 $this->formFactory
= $formFactory;
89 * Set annotation manager to use when building form from annotations
91 * @param AnnotationManager $annotationManager
92 * @return AnnotationBuilder
94 public function setAnnotationManager(AnnotationManager
$annotationManager)
96 $parser = new Parser\
DoctrineAnnotationParser();
97 foreach ($this->defaultAnnotations
as $annotationName) {
98 $class = __NAMESPACE__
. '\\' . $annotationName;
99 $parser->registerAnnotation($class);
101 $annotationManager->attach($parser);
102 $this->annotationManager
= $annotationManager;
107 * Set event manager instance
109 * @param EventManagerInterface $events
110 * @return AnnotationBuilder
112 public function setEventManager(EventManagerInterface
$events)
114 $events->setIdentifiers(array(
118 $events->attach(new ElementAnnotationsListener());
119 $events->attach(new FormAnnotationsListener());
120 $this->events
= $events;
125 * Retrieve form factory
127 * Lazy-loads the default form factory if none is currently set.
131 public function getFormFactory()
133 if ($this->formFactory
) {
134 return $this->formFactory
;
137 $this->formFactory
= new Factory();
138 return $this->formFactory
;
142 * Retrieve annotation manager
144 * If none is currently set, creates one with default annotations.
146 * @return AnnotationManager
148 public function getAnnotationManager()
150 if ($this->annotationManager
) {
151 return $this->annotationManager
;
154 $this->setAnnotationManager(new AnnotationManager());
155 return $this->annotationManager
;
161 * @return EventManagerInterface
163 public function getEventManager()
165 if (null === $this->events
) {
166 $this->setEventManager(new EventManager());
168 return $this->events
;
172 * Creates and returns a form specification for use with a factory
174 * Parses the object provided, and processes annotations for the class and
175 * all properties. Information from annotations is then used to create
176 * specifications for a form, its elements, and its input filter.
178 * @param string|object $entity Either an instance or a valid class name for an entity
179 * @throws Exception\InvalidArgumentException if $entity is not an object or class name
180 * @return ArrayObject
182 public function getFormSpecification($entity)
184 if (!is_object($entity)) {
185 if ((is_string($entity) && (!class_exists($entity))) // non-existent class
186 ||
(!is_string($entity)) // not an object or string
188 throw new Exception\
InvalidArgumentException(sprintf(
189 '%s expects an object or valid class name; received "%s"',
191 var_export($entity, 1)
196 $this->entity
= $entity;
197 $annotationManager = $this->getAnnotationManager();
198 $formSpec = new ArrayObject();
199 $filterSpec = new ArrayObject();
201 $reflection = new ClassReflection($entity);
202 $annotations = $reflection->getAnnotations($annotationManager);
204 if ($annotations instanceof AnnotationCollection
) {
205 $this->configureForm($annotations, $reflection, $formSpec, $filterSpec);
208 foreach ($reflection->getProperties() as $property) {
209 $annotations = $property->getAnnotations($annotationManager);
211 if ($annotations instanceof AnnotationCollection
) {
212 $this->configureElement($annotations, $property, $formSpec, $filterSpec);
216 if (!isset($formSpec['input_filter'])) {
217 $formSpec['input_filter'] = $filterSpec;
224 * Create a form from an object.
226 * @param string|object $entity
227 * @return \Zend\Form\Form
229 public function createForm($entity)
231 $formSpec = ArrayUtils
::iteratorToArray($this->getFormSpecification($entity));
232 $formFactory = $this->getFormFactory();
233 return $formFactory->createForm($formSpec);
237 * Get the entity used to construct the form.
241 public function getEntity()
243 return $this->entity
;
247 * Configure the form specification from annotations
249 * @param AnnotationCollection $annotations
250 * @param ClassReflection $reflection
251 * @param ArrayObject $formSpec
252 * @param ArrayObject $filterSpec
254 * @triggers discoverName
255 * @triggers configureForm
257 protected function configureForm($annotations, $reflection, $formSpec, $filterSpec)
259 $name = $this->discoverName($annotations, $reflection);
260 $formSpec['name'] = $name;
261 $formSpec['attributes'] = array();
262 $formSpec['elements'] = array();
263 $formSpec['fieldsets'] = array();
265 $events = $this->getEventManager();
266 foreach ($annotations as $annotation) {
267 $events->trigger(__FUNCTION__
, $this, array(
268 'annotation' => $annotation,
270 'formSpec' => $formSpec,
271 'filterSpec' => $filterSpec,
277 * Configure an element from annotations
279 * @param AnnotationCollection $annotations
280 * @param \Zend\Code\Reflection\PropertyReflection $reflection
281 * @param ArrayObject $formSpec
282 * @param ArrayObject $filterSpec
284 * @triggers checkForExclude
285 * @triggers discoverName
286 * @triggers configureElement
288 protected function configureElement($annotations, $reflection, $formSpec, $filterSpec)
290 // If the element is marked as exclude, return early
291 if ($this->checkForExclude($annotations)) {
295 $events = $this->getEventManager();
296 $name = $this->discoverName($annotations, $reflection);
298 $elementSpec = new ArrayObject(array(
304 $inputSpec = new ArrayObject(array(
308 $event = new Event();
309 $event->setParams(array(
311 'elementSpec' => $elementSpec,
312 'inputSpec' => $inputSpec,
313 'formSpec' => $formSpec,
314 'filterSpec' => $filterSpec,
316 foreach ($annotations as $annotation) {
317 $event->setParam('annotation', $annotation);
318 $events->trigger(__FUNCTION__
, $this, $event);
321 // Since "type" is a reserved name in the filter specification,
322 // we need to add the specification without the name as the key.
323 // In all other cases, though, the name is fine.
324 if ($event->getParam('inputSpec')->count() > 1) {
325 if ($name === 'type') {
326 $filterSpec[] = $event->getParam('inputSpec');
328 $filterSpec[$name] = $event->getParam('inputSpec');
332 $elementSpec = $event->getParam('elementSpec');
333 $type = (isset($elementSpec['spec']['type']))
334 ?
$elementSpec['spec']['type']
335 : 'Zend\Form\Element';
337 // Compose as a fieldset or an element, based on specification type
338 if (static::isSubclassOf($type, 'Zend\Form\FieldsetInterface')) {
339 if (!isset($formSpec['fieldsets'])) {
340 $formSpec['fieldsets'] = array();
342 $formSpec['fieldsets'][] = $elementSpec;
344 if (!isset($formSpec['elements'])) {
345 $formSpec['elements'] = array();
347 $formSpec['elements'][] = $elementSpec;
352 * Discover the name of the given form or element
354 * @param AnnotationCollection $annotations
355 * @param \Reflector $reflection
358 protected function discoverName($annotations, $reflection)
360 $results = $this->getEventManager()->trigger('discoverName', $this, array(
361 'annotations' => $annotations,
362 'reflection' => $reflection,
364 return (is_string($r) && !empty($r));
366 return $results->last();
370 * Determine if an element is marked to exclude from the definitions
372 * @param AnnotationCollection $annotations
375 protected function checkForExclude($annotations)
377 $results = $this->getEventManager()->trigger('checkForExclude', $this, array(
378 'annotations' => $annotations,
380 return (true === $r);
382 return (bool) $results->last();
386 * Checks if the object has this class as one of its parents
388 * @see https://bugs.php.net/bug.php?id=53727
389 * @see https://github.com/zendframework/zf2/pull/1807
391 * @param string $className
392 * @param string $type
395 protected static function isSubclassOf($className, $type)
397 if (is_subclass_of($className, $type)) {
400 if (version_compare(PHP_VERSION
, '5.3.7', '>=')) {
403 if (!interface_exists($type)) {
406 $r = new ReflectionClass($className);
407 return $r->implementsInterface($type);