diff --git a/src/DoctrineORMModule/Form/Annotation/AnnotationBuilder.php b/src/DoctrineORMModule/Form/Annotation/AnnotationBuilder.php index 12bd5484..c3ea2df7 100644 --- a/src/DoctrineORMModule/Form/Annotation/AnnotationBuilder.php +++ b/src/DoctrineORMModule/Form/Annotation/AnnotationBuilder.php @@ -1,66 +1,133 @@ . + */ namespace DoctrineORMModule\Form\Annotation; -use Doctrine\ORM\EntityManager; +use Doctrine\Common\Persistence\Mapping\ClassMetadata; +use Doctrine\Common\Persistence\ObjectManager; use Zend\Code\Annotation\AnnotationManager; -use Zend\Code\Annotation\Parser\DoctrineAnnotationParser; use Zend\EventManager\EventManagerInterface; use Zend\Form\Annotation\AnnotationBuilder as ZendAnnotationBuilder; +/** + * @author Kyle Spraggs + */ class AnnotationBuilder extends ZendAnnotationBuilder { + const EVENT_CONFIGURE_FIELD = 'configureField'; + const EVENT_CONFIGURE_ASSOCIATION = 'configureAssociation'; + const EVENT_EXCLUDE_FIELD = 'excludeField'; + const EVENT_EXCLUDE_ASSOCIATION = 'excludeAssociation'; + /** - * @var \Doctrine\ORM\EntityManager + * @var \Doctrine\Common\Persistence\ObjectManager */ - protected $entityManager; + protected $objectManager; /** - * Constructor. Ensures EntityManager is present. + * Constructor. Ensures ObjectManager is present. * - * @param \Doctrine\ORM\EntityManager $entityManager + * @param \Doctrine\Common\Persistence\ObjectManager $objectManager + */ + public function __construct(ObjectManager $objectManager) + { + $this->objectManager = $objectManager; + } + + /** + * {@inheritDoc} */ - public function __construct(EntityManager $entityManager) + public function setEventManager(EventManagerInterface $events) { - $this->entityManager = $entityManager; + parent::setEventManager($events); + + $this->getEventManager()->attach(new ElementAnnotationsListener($this->objectManager)); + + return $this; } /** - * Set annotation manager to use when building form from annotations + * Overrides the base getFormSpecification() to additionally iterate through each + * field/association in the metadata and trigger the associated event. + * + * This allows building of a form from metadata instead of requiring annotations. + * Annotations are still allowed through the ElementAnnotationsListener. * - * @param AnnotationManager $annotationManager - * @return AnnotationBuilder + * {@inheritDoc} */ - public function setAnnotationManager(AnnotationManager $annotationManager) + public function getFormSpecification($entity) { - parent::setAnnotationManager($annotationManager); + $formSpec = parent::getFormSpecification($entity); + $metadata = $this->objectManager->getClassMetadata(get_class($entity)); - $parser = new DoctrineAnnotationParser($this->entityManager); + $inputSpec = $formSpec['input_filter']; + foreach ($formSpec['elements'] as $key => $elementSpec) { + $name = isset($elementSpec['spec']['name']) ? $elementSpec['spec']['name'] : null; - $parser->registerAnnotation('Doctrine\ORM\Mapping\Column'); - $parser->registerAnnotation('Doctrine\ORM\Mapping\GeneratedValue'); - $parser->registerAnnotation('Doctrine\ORM\Mapping\OneToMany'); - $parser->registerAnnotation('Doctrine\ORM\Mapping\OneToOne'); - $parser->registerAnnotation('Doctrine\ORM\Mapping\ManyToMany'); - $parser->registerAnnotation('Doctrine\ORM\Mapping\ManyToOne'); + if (!$name) { + continue; + } - $this->annotationManager->attach($parser); + $params = array( + 'metadata' => $metadata, + 'name' => $name, + 'elementSpec' => $elementSpec, + 'inputSpec' => isset($inputSpec[$name]) ? $inputSpec[$name] : new \ArrayObject() + ); - return $this; + if ($this->checkForExcludeElementFromMetadata($metadata, $name)) { + unset($formSpec['elements'][$key]); + unset($inputSpec[$name]); + continue; + } + + if ($metadata->hasField($name)) { + $this->getEventManager()->trigger(static::EVENT_CONFIGURE_FIELD, $this, $params); + } elseif ($metadata->hasAssociation($name)) { + $this->getEventManager()->trigger(static::EVENT_CONFIGURE_ASSOCIATION, $this, $params); + } + } + + return $formSpec; } /** - * Set event manager instance - * - * @param EventManagerInterface $events - * @return self + * @param ClassMetadata $metadata + * @param $name + * @return bool */ - public function setEventManager(EventManagerInterface $events) + protected function checkForExcludeElementFromMetadata(ClassMetadata $metadata, $name) { - parent::setEventManager($events); + $params = array('metadata' => $metadata, 'name' => $name); + $test = function($r) { return (true === $r); }; + $result = false; - $this->getEventManager()->attach(new ElementAnnotationsListener($this->entityManager)); + if ($metadata->hasField($name)) { + $result = $this->getEventManager()->trigger(static::EVENT_EXCLUDE_FIELD, $this, $params, $test); + } elseif ($metadata->hasAssociation($name)) { + $result = $this->getEventManager()->trigger(static::EVENT_EXCLUDE_ASSOCIATION, $this, $params, $test); + } - return $this; + if ($result) { + $result = (bool) $result->last(); + } + + return $result; } } diff --git a/src/DoctrineORMModule/Form/Annotation/ElementAnnotationsListener.php b/src/DoctrineORMModule/Form/Annotation/ElementAnnotationsListener.php index 0d89d819..10b1fe79 100644 --- a/src/DoctrineORMModule/Form/Annotation/ElementAnnotationsListener.php +++ b/src/DoctrineORMModule/Form/Annotation/ElementAnnotationsListener.php @@ -1,32 +1,45 @@ . + */ + namespace DoctrineORMModule\Form\Annotation; +use ArrayObject; use Doctrine\Common\Persistence\ObjectManager; -use Doctrine\ORM\Mapping\Column; -use Doctrine\ORM\Mapping\GeneratedValue; -use Doctrine\ORM\Mapping\ManyToMany; -use Doctrine\ORM\Mapping\ManyToOne; -use Doctrine\ORM\Mapping\OneToMany; -use Doctrine\ORM\Mapping\OneToOne; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\ClassMetadataInfo; use DoctrineModule\Form\Element; +use Zend\EventManager\AbstractListenerAggregate; use Zend\EventManager\EventInterface; use Zend\EventManager\EventManagerInterface; -use Zend\EventManager\ListenerAggregateInterface; -class ElementAnnotationsListener implements ListenerAggregateInterface +/** + * @author Kyle Spraggs + */ +class ElementAnnotationsListener extends AbstractListenerAggregate { - /** - * @var \Zend\Stdlib\CallbackHandler[] - */ - protected $listeners = array(); - /** * @var \Doctrine\Common\Persistence\ObjectManager */ protected $objectManager; /** - * @param \Doctrine\Common\Persistence\ObjectManager $objectManager + * @param ObjectManager $objectManager */ public function __construct(ObjectManager $objectManager) { @@ -34,162 +47,108 @@ public function __construct(ObjectManager $objectManager) } /** - * Detach listeners - * - * @param EventManagerInterface $events - * - * @return void - */ - public function detach(EventManagerInterface $events) - { - foreach ($this->listeners as $index => $listener) { - if (false !== $events->detach($listener)) { - unset($this->listeners[$index]); - } - } - } - - /** - * Attach listeners - * - * @param \Zend\EventManager\EventManagerInterface $events - * - * @return void + * {@inheritDoc} */ public function attach(EventManagerInterface $events) { - $this->listeners[] = $events->attach('configureElement', array($this, 'handleAttributesAnnotation')); - $this->listeners[] = $events->attach('configureElement', array($this, 'handleFilterAnnotation')); - $this->listeners[] = $events->attach('configureElement', array($this, 'handleRequiredAnnotation')); - $this->listeners[] = $events->attach('configureElement', array($this, 'handleTypeAnnotation')); - $this->listeners[] = $events->attach('configureElement', array($this, 'handleValidatorAnnotation')); - $this->listeners[] = $events->attach('configureElement', array($this, 'handleToManyAnnotation')); - $this->listeners[] = $events->attach('checkForExclude', array($this, 'handleExcludeAnnotation')); + $this->listeners[] = $events->attach(AnnotationBuilder::EVENT_CONFIGURE_FIELD, array($this, 'handleFilterField')); + $this->listeners[] = $events->attach(AnnotationBuilder::EVENT_CONFIGURE_FIELD, array($this, 'handleTypeField')); + $this->listeners[] = $events->attach(AnnotationBuilder::EVENT_CONFIGURE_FIELD, array($this, 'handleValidatorField')); + $this->listeners[] = $events->attach(AnnotationBuilder::EVENT_CONFIGURE_FIELD, array($this, 'handleRequiredField')); + $this->listeners[] = $events->attach(AnnotationBuilder::EVENT_EXCLUDE_FIELD, array($this, 'handleExcludeField')); + + $this->listeners[] = $events->attach(AnnotationBuilder::EVENT_CONFIGURE_ASSOCIATION, array($this, 'handleToOne')); + $this->listeners[] = $events->attach(AnnotationBuilder::EVENT_CONFIGURE_ASSOCIATION, array($this, 'handleToMany')); + $this->listeners[] = $events->attach(AnnotationBuilder::EVENT_CONFIGURE_ASSOCIATION, array($this, 'handleRequiredAssociation')); + $this->listeners[] = $events->attach(AnnotationBuilder::EVENT_EXCLUDE_ASSOCIATION, array($this, 'handleExcludeAssociation')); } /** - * Handle ToOne relationships. - * - * @param \Zend\EventManager\EventInterface $event - * - * @return void + * @param EventInterface $event + * @internal */ - public function handleToOneAnnotation(EventInterface $event) + public function handleToOne(EventInterface $event) { - $annotation = $event->getParam('annotation'); - - if (!($annotation instanceof ManyToOne || $annotation instanceof OneToOne)) { + /** @var \Doctrine\ORM\Mapping\ClassMetadata $metadata */ + $metadata = $event->getParam('metadata'); + $mapping = $this->getAssociationMapping($event); + if (!$mapping || !$metadata->isSingleValuedAssociation($event->getParam('name'))) { return; } - $elementSpec = $event->getParam('elementSpec'); - $elementSpec['spec']['type'] = 'DoctrineORMModule\Form\Element\EntitySelect'; - $elementSpec['spec']['options'] = array( - 'object_manager' => $this->objectManager, - 'target_class' => $annotation->targetEntity - ); + $this->prepareEvent($event); + $this->mergeAssociationOptions($event->getParam('elementSpec'), $mapping['targetEntity']); } /** - * Handle ToMany relationships. - * - * @param \Zend\EventManager\EventInterface $event - * - * @return void + * @param EventInterface $event + * @internal */ - public function handleToManyAnnotation(EventInterface $event) + public function handleToMany(EventInterface $event) { - $annotation = $event->getParam('annotation'); - - if (!($annotation instanceof ManyToMany || $annotation instanceof OneToMany)) { + /** @var \Doctrine\ORM\Mapping\ClassMetadata $metadata */ + $metadata = $event->getParam('metadata'); + $mapping = $this->getAssociationMapping($event); + if (!$mapping || !$metadata->isCollectionValuedAssociation($event->getParam('name'))) { return; } - $elementSpec = $event->getParam('elementSpec'); - $elementSpec['spec']['type'] = 'DoctrineORMModule\Form\Element\EntitySelect'; + $this->prepareEvent($event); + + /** @var \ArrayObject $elementSpec */ + $elementSpec = $event->getParam('elementSpec'); + $inputSpec = $event->getParam('inputSpec'); + $inputSpec['required'] = false; + + $this->mergeAssociationOptions($elementSpec, $mapping['targetEntity']); + $elementSpec['spec']['attributes']['multiple'] = true; - $elementSpec['spec']['options'] = array( - 'object_manager' => $this->objectManager, - 'target_class' => $annotation->targetEntity - ); } /** - * Handle the Attributes annotation - * - * Sets the attributes array of the element specification. - * - * @param \Zend\EventManager\EventInterface $event - * - * @return void + * @param EventInterface $event + * @internal + * @return bool */ - public function handleAttributesAnnotation(EventInterface $event) + public function handleExcludeAssociation(EventInterface $event) { - $annotation = $event->getParam('annotation'); - if (!$annotation instanceof Column) { - return; - } - - $elementSpec = $event->getParam('elementSpec'); - switch ($annotation->type) { - case 'bool': - case 'boolean': - $elementSpec['spec']['attributes']['type'] = 'checkbox'; - break; - case 'text': - $elementSpec['spec']['attributes']['type'] = 'textarea'; - break; - } + /** @var \Doctrine\ORM\Mapping\ClassMetadataInfo $metadata */ + $metadata = $event->getParam('metadata'); + return $metadata && $metadata->isAssociationInverseSide($event->getParam('name')); } /** - * Handle the AllowEmpty annotation - * - * Sets the allow_empty flag on the input specification array. - * - * @param \Zend\EventManager\EventInterface $event - * + * @param EventInterface $event + * @internal * @return bool */ - public function handleExcludeAnnotation(EventInterface $event) + public function handleExcludeField(EventInterface $event) { - $annotations = $event->getParam('annotations'); + /** @var \Doctrine\ORM\Mapping\ClassMetadataInfo $metadata */ + $metadata = $event->getParam('metadata'); + $identifiers = $metadata->getIdentifierFieldNames(); - foreach ($annotations as $annotation) { - if ($annotation instanceof GeneratedValue) { - if ('AUTO' === $annotation->strategy) { - return true; - } - } - } - - return false; + return in_array($event->getParam('name'), $identifiers) && + $metadata->generatorType === ClassMetadata::GENERATOR_TYPE_IDENTITY; } /** - * Handle the Filter annotation - * - * Adds a filter to the filter chain specification for the input. - * - * @param \Zend\EventManager\EventInterface $event - * - * @return void + * @param EventInterface $event + * @internal */ - public function handleFilterAnnotation(EventInterface $event) + public function handleFilterField(EventInterface $event) { - $annotation = $event->getParam('annotation'); - - if (!$annotation instanceof Column) { + /** @var \Doctrine\ORM\Mapping\ClassMetadata $metadata */ + $metadata = $event->getParam('metadata'); + if (!$metadata || !$metadata->hasField($event->getParam('name'))) { return; } - $inputSpec = $event->getParam('inputSpec'); + $this->prepareEvent($event); - if (!isset($inputSpec['filters'])) { - $inputSpec['filters'] = array(); - } + $inputSpec = $event->getParam('inputSpec'); - switch ($annotation->type) { + switch ($metadata->getTypeOfField($event->getParam('name'))) { case 'bool': case 'boolean': $inputSpec['filters'][] = array('name' => 'Boolean'); @@ -211,88 +170,125 @@ public function handleFilterAnnotation(EventInterface $event) } /** - * Handle the Required annotation - * - * Sets the required flag on the input based on the annotation value. - * - * @param \Zend\EventManager\EventInterface $event - * - * @return void + * @param EventInterface $event + * @internal */ - public function handleRequiredAnnotation(EventInterface $event) + public function handleRequiredAssociation(EventInterface $event) { - $annotation = $event->getParam('annotation'); - if (!$annotation instanceof Column) { + /** @var \Doctrine\ORM\Mapping\ClassMetadata $metadata */ + $metadata = $event->getParam('metadata'); + $mapping = $this->getAssociationMapping($event); + if (!$mapping) { return; } - $inputSpec = $event->getParam('inputSpec'); - $inputSpec['required'] = (bool) !$annotation->nullable; + $this->prepareEvent($event); + + $inputSpec = $event->getParam('inputSpec'); + $elementSpec = $event->getParam('elementSpec'); + + if ($metadata->isCollectionValuedAssociation($event->getParam('name'))) { + $inputSpec['required'] = false; + } elseif (isset($mapping['joinColumns'])) { + $required = true; + foreach ($mapping['joinColumns'] as $joinColumn) { + if (isset($joinColumn['nullable']) && $joinColumn['nullable']) { + $required = false; + + if (!isset($elementSpec['spec']['options']['empty_option'])) { + $elementSpec['spec']['options']['empty_option'] = 'NULL'; + } + break; + } + } + + $inputSpec['required'] = $required; + } } /** - * Handle the Type annotation - * - * Sets the element class type to use in the element specification. - * - * @param \Zend\EventManager\EventInterface $event - * - * @return void + * @param EventInterface $event + * @internal */ - public function handleTypeAnnotation(EventInterface $event) + public function handleRequiredField(EventInterface $event) { - $annotation = $event->getParam('annotation'); + $mapping = $this->getFieldMapping($event); + if (!$mapping) { + return; + } - if (!$annotation instanceof Column) { + $this->prepareEvent($event); + + $inputSpec = $event->getParam('inputSpec'); + $inputSpec['required'] = isset($mapping['nullable']) ? !$mapping['nullable'] : true; + } + + /** + * @param EventInterface $event + * @internal + */ + public function handleTypeField(EventInterface $event) + { + /** @var \Doctrine\ORM\Mapping\ClassMetadata $metadata */ + $metadata = $event->getParam('metadata'); + $mapping = $this->getFieldMapping($event); + if (!$mapping) { return; } - $type = $annotation->type; - switch ($type) { + $this->prepareEvent($event); + + $elementSpec = $event->getParam('elementSpec'); + + switch ($metadata->getTypeOfField($event->getParam('name'))) { + case 'bigint': + case 'integer': + case 'smallint': + $type = 'Zend\Form\Element\Number'; + break; case 'bool': case 'boolean': $type = 'Zend\Form\Element\Checkbox'; break; case 'date': - $type = 'Zend\Form\Element\Date'; + $type = 'Zend\Form\Element\DateSelect'; break; + case 'datetimetz': case 'datetime': - $type = 'Zend\Form\Element\DateTime'; + $type = 'Zend\Form\Element\DateTimeSelect'; + break; + case 'time': + $type = 'Zend\Form\Element\Time'; + break; + case 'text': + $type = 'Zend\Form\Element\Text'; break; default: $type = 'Zend\Form\Element'; break; } - $elementSpec = $event->getParam('elementSpec'); - $elementSpec['spec']['type'] = $type; } /** - * Handle the Validator annotation - * - * Adds a validator to the validator chain of the input specification. - * - * @param \Zend\EventManager\EventInterface $event - * - * @return void + * @param EventInterface $event + * @internal */ - public function handleValidatorAnnotation(EventInterface $event) + public function handleValidatorField(EventInterface $event) { - $annotation = $event->getParam('annotation'); - - if (!$annotation instanceof Column) { + /** @var \Doctrine\ORM\Mapping\ClassMetadata $metadata */ + $mapping = $this->getFieldMapping($event); + $metadata = $event->getParam('metadata'); + if (!$mapping) { return; } - $inputSpec = $event->getParam('inputSpec'); + $this->prepareEvent($event); - if (!isset($inputSpec['validators'])) { - $inputSpec['validators'] = array(); - } + $inputSpec = $event->getParam('inputSpec'); - switch ($annotation->type) { + switch ($metadata->getTypeOfField($event->getParam('name'))) { case 'bool': case 'boolean': $inputSpec['validators'][] = array( @@ -309,13 +305,87 @@ public function handleValidatorAnnotation(EventInterface $event) $inputSpec['validators'][] = array('name' => 'Int'); break; case 'string': - if ($annotation->length) { + if (isset($mapping['length'])) { $inputSpec['validators'][] = array( 'name' => 'StringLength', - 'options' => array('max' => $annotation->length) + 'options' => array('max' => $mapping['length']) ); } break; } } + + /** + * @param EventInterface $event + * @return array|null + */ + protected function getFieldMapping(EventInterface $event) + { + /** @var \Doctrine\ORM\Mapping\ClassMetadataInfo $metadata */ + $metadata = $event->getParam('metadata'); + if ($metadata && $metadata->hasField($event->getParam('name'))) { + return $metadata->getFieldMapping($event->getParam('name')); + } + return null; + } + + /** + * @param EventInterface $event + * @return array|null + */ + protected function getAssociationMapping(EventInterface $event) + { + /** @var \Doctrine\ORM\Mapping\ClassMetadataInfo $metadata */ + $metadata = $event->getParam('metadata'); + if ($metadata && $metadata->hasAssociation($event->getParam('name'))) { + return $metadata->getAssociationMapping($event->getParam('name')); + } + return null; + } + + /** + * @param ArrayObject $elementSpec + * @param string $targetEntity + */ + protected function mergeAssociationOptions(ArrayObject $elementSpec, $targetEntity) + { + $options = isset($elementSpec['spec']['options']) ? $elementSpec['spec']['options'] : array(); + $options = array_merge( + array( + 'object_manager' => $this->objectManager, + 'target_class' => $targetEntity + ), + $options + ); + + $elementSpec['spec']['options'] = $options; + $elementSpec['spec']['type'] = 'DoctrineORMModule\Form\Element\EntitySelect'; + } + + /** + * Normalizes event setting all expected parameters. + * + * @param EventInterface $event + */ + protected function prepareEvent(EventInterface $event) + { + foreach (array('elementSpec', 'inputSpec') as $type) { + if (!$event->getParam($type)) { + $event->setParam($type, new ArrayObject()); + } + } + + $elementSpec = $event->getParam('elementSpec'); + $inputSpec = $event->getParam('inputSpec'); + + if (!isset($elementSpec['spec'])) { + $elementSpec['spec'] = array(); + } + if (!isset($inputSpec['filters'])) { + $inputSpec['filters'] = array(); + } + if (!isset($inputSpec['validators'])) { + $inputSpec['validators'] = array(); + } + } } diff --git a/tests/DoctrineORMModuleTest/Assets/Entity/FormEntity.php b/tests/DoctrineORMModuleTest/Assets/Entity/FormEntity.php new file mode 100644 index 00000000..13f9f940 --- /dev/null +++ b/tests/DoctrineORMModuleTest/Assets/Entity/FormEntity.php @@ -0,0 +1,119 @@ +. + */ + +namespace DoctrineORMModuleTest\Assets\Entity; + +use Doctrine\ORM\Mapping as ORM; +use Zend\Form\Annotation as Form; + +/** + * @ORM\Entity + * @ORM\Table(name="doctrine_orm_module_form_entity") + * + * @author Kyle Spraggs + */ +class FormEntity +{ + /** + * @ORM\Id + * @ORM\Column(type="integer") + */ + protected $id; + + /** + * @ORM\Column(type="bool") + */ + protected $bool; + + /** + * @ORM\Column(type="boolean") + */ + protected $boolean; + + /** + * @ORM\Column(type="float") + */ + protected $float; + + /** + * @ORM\Column(type="bigint") + */ + protected $bigint; + + /** + * @ORM\Column(type="integer") + */ + protected $integer; + + /** + * @ORM\Column(type="smallint") + */ + protected $smallint; + + /** + * @ORM\Column(type="datetime") + */ + protected $datetime; + + /** + * @ORM\Column(type="datetimetz") + */ + protected $datetimetz; + + /** + * @ORM\Column(type="date") + */ + protected $date; + + /** + * @ORM\Column(type="time") + */ + protected $time; + + /** + * @ORM\Column(type="text") + */ + protected $text; + + /** + * @ORM\Column(type="string", nullable=false, length=20) + */ + protected $string; + + /** + * @ORM\Column(type="string", nullable=true) + */ + protected $stringNullable; + + /** + * @ORM\OneToOne(targetEntity="TargetInterface") + */ + protected $targetOne; + + /** + * @ORM\OneToOne(targetEntity="TargetInterface") + * @ORM\JoinColumn(nullable=true) + */ + protected $targetOneNullable; + + /** + * @ORM\OneToMany(targetEntity="FormEntityTarget", mappedBy="formEntity") + */ + protected $targetMany; +} diff --git a/tests/DoctrineORMModuleTest/Assets/Entity/FormEntityTarget.php b/tests/DoctrineORMModuleTest/Assets/Entity/FormEntityTarget.php new file mode 100644 index 00000000..2f995316 --- /dev/null +++ b/tests/DoctrineORMModuleTest/Assets/Entity/FormEntityTarget.php @@ -0,0 +1,37 @@ +. + */ + +namespace DoctrineORMModuleTest\Assets\Entity; + +use Doctrine\ORM\Mapping as ORM; + +/** + * @ORM\Entity + * + * @author Kyle Spraggs + */ +class FormEntityTarget +{ + /** + * @ORM\Id + * @ORM\Column(type="integer") + * @ORM\GeneratedValue(strategy="AUTO") + */ + protected $id; +} diff --git a/tests/DoctrineORMModuleTest/Form/ElementAnnotationsListenerTest.php b/tests/DoctrineORMModuleTest/Form/ElementAnnotationsListenerTest.php index ecd1cab4..d0830e12 100644 --- a/tests/DoctrineORMModuleTest/Form/ElementAnnotationsListenerTest.php +++ b/tests/DoctrineORMModuleTest/Form/ElementAnnotationsListenerTest.php @@ -3,106 +3,318 @@ namespace DoctrineORMModuleTest\Form; use ArrayObject; -use Doctrine\ORM\Mapping\Column; -use Doctrine\ORM\Mapping\ManyToOne; -use Doctrine\ORM\Mapping\ManyToMany; use DoctrineORMModule\Form\Annotation\ElementAnnotationsListener; use DoctrineORMModuleTest\Framework\TestCase; -use PHPUnit_Framework_TestCase; use Zend\EventManager\Event; -/** - * Description of ElementAnnotationsListenerTest - * - * @author otterdijk - */ class ElementAnnotationsListenerTest extends TestCase { /** - * @dataProvider eventProvider + * @var ElementAnnotationsListener */ - public function testHandleAnnotationType($type, $expectedType) + protected $listener; + + public function setUp() { - $listener = new ElementAnnotationsListener($this->getEntityManager()); - $event = new Event(); - $checkboxAnnotation = new Column(); - $checkboxAnnotation->type = $type; + $this->listener = new ElementAnnotationsListener($this->getEntityManager()); + } - $event->setParam('annotation', $checkboxAnnotation); - $event->setParam('elementSpec', new ArrayObject(array('spec' => array()))); - $listener->handleTypeAnnotation($event); + /** + * @dataProvider eventNameProvider + */ + public function testEventsWithNoMetadata($method) + { + $event = $this->getMetadataEvent(); + $this->listener->{$method}($event); + } - $spec = $event->getParam('elementSpec'); + public function testToOne() + { + $listener = $this->listener; + $event = $this->getMetadataEvent(); + + $event->setParam('name', 'targetOne'); + $listener->handleToOne($event); - $this->assertEquals($expectedType, $spec['spec']['type']); + $elementSpec = $event->getParam('elementSpec'); + $this->assertEquals($this->getEntityManager(), $elementSpec['spec']['options']['object_manager']); + $this->assertEquals('DoctrineORMModuleTest\Assets\Entity\TargetEntity', $elementSpec['spec']['options']['target_class']); + $this->assertEquals('DoctrineORMModule\Form\Element\EntitySelect', $elementSpec['spec']['type']); } - - public function testToOneReturnsEntitySelect() + + public function testToOneMergesOptions() { - $listener = new ElementAnnotationsListener($this->getEntityManager()); - $event = new Event(); - $annotation = new ManyToOne(); - $annotation->targetEntity = 'DoctrineORMModuleTest\Assets\Entity\Category'; + $listener = $this->listener; + $event = $this->getMetadataEvent(); + $elementSpec = new ArrayObject(); + $elementSpec['spec']['options']['foo'] = 'bar'; - $event->setParam('annotation', $annotation); - $event->setParam( - 'elementSpec', - new ArrayObject(array('spec' => array('attributes' => array('class' => 'foo')))) - ); + $event->setParam('name', 'targetOne'); + $event->setParam('elementSpec', $elementSpec); + $listener->handleToOne($event); - $listener->handleToOneAnnotation($event); - $spec = $event->getParam('elementSpec'); - $this->assertArrayNotHasKey('multiple', $spec['spec']['attributes']); - $this->assertEquals('DoctrineORMModule\Form\Element\EntitySelect', $spec['spec']['type']); - $this->assertEquals('DoctrineORMModuleTest\Assets\Entity\Category', $spec['spec']['options']['target_class']); + $this->assertEquals('bar', $elementSpec['spec']['options']['foo']); + $this->assertCount(3, $elementSpec['spec']['options']); } - public function testToManyReturnsEntitySelect() + public function testToOneHasNoEffectOnToMany() { - $listener = new ElementAnnotationsListener($this->getEntityManager()); - $event = new Event(); - $annotation = new ManyToMany(); - $annotation->targetEntity = 'DoctrineORMModuleTest\Assets\Entity\Category'; + $listener = $this->listener; + $event = $this->getMetadataEvent(); - $event->setParam('annotation', $annotation); - $event->setParam('elementSpec', new ArrayObject()); + $event->setParam('name', 'targetMany'); + $listener->handleToOne($event); - $listener->handleToManyAnnotation($event); - $spec = $event->getParam('elementSpec'); - $this->assertArrayHasKey('multiple', $spec['spec']['attributes']); - $this->assertEquals('DoctrineORMModule\Form\Element\EntitySelect', $spec['spec']['type']); - $this->assertEquals('DoctrineORMModuleTest\Assets\Entity\Category', $spec['spec']['options']['target_class']); + $this->assertNull($event->getParam('elementSpec')); + $this->assertNull($event->getParam('inputSpec')); } - public function testHandleAnnotationAttributesShallAppend() + public function testToManyHasNoEffectOnToOne() { - $listener = new ElementAnnotationsListener($this->getEntityManager()); - $event = new Event(); - $annotation = new Column(); - $annotation->type = 'text'; + $listener = $this->listener; + $event = $this->getMetadataEvent(); - $event->setParam('annotation', $annotation); - $event->setParam( - 'elementSpec', - new ArrayObject(array('spec' => array('attributes' => array('attr1' => 'value')))) - ); + $event->setParam('name', 'targetOne'); + $listener->handleToMany($event); + + $this->assertNull($event->getParam('elementSpec')); + $this->assertNull($event->getParam('inputSpec')); + } + + public function testToMany() + { + $listener = $this->listener; + $event = $this->getMetadataEvent(); + + $event->setParam('name', 'targetMany'); + $listener->handleToMany($event); + + $elementSpec = $event->getParam('elementSpec'); + $inputSpec = $event->getParam('inputSpec'); + + $this->assertTrue($elementSpec['spec']['attributes']['multiple']); + $this->assertEquals($this->getEntityManager(), $elementSpec['spec']['options']['object_manager']); + $this->assertEquals('DoctrineORMModuleTest\Assets\Entity\FormEntityTarget', $elementSpec['spec']['options']['target_class']); + $this->assertEquals('DoctrineORMModule\Form\Element\EntitySelect', $elementSpec['spec']['type']); + $this->assertFalse($inputSpec['required']); + } + + public function testToManyMergesOptions() + { + $listener = $this->listener; + $event = $this->getMetadataEvent(); + $elementSpec = new ArrayObject(); + $elementSpec['spec']['options']['foo'] = 'bar'; + + $event->setParam('name', 'targetMany'); + $event->setParam('elementSpec', $elementSpec); + $listener->handleToMany($event); + + $this->assertEquals('bar', $elementSpec['spec']['options']['foo']); + $this->assertCount(3, $elementSpec['spec']['options']); + } + + public function testHandleExcludeAssociation() + { + $listener = $this->listener; + $event = $this->getMetadataEvent(); + $event->setParam('name', 'targetMany'); + + $this->assertTrue($listener->handleExcludeAssociation($event)); + + $event->setParam('name', 'targetOne'); + $this->assertFalse($listener->handleExcludeAssociation($event)); + } + + /** + * @dataProvider eventFilterProvider + */ + public function testHandleFilterField($name, $type) + { + $listener = $this->listener; + $event = $this->getMetadataEvent(); + + $listener->handleFilterField($event); + $this->assertNull($event->getParam('inputSpec')); + + $event->setParam('name', $name); + $listener->handleFilterField($event); + + $inputSpec = $event->getParam('inputSpec'); + $this->assertEquals($type, $inputSpec['filters'][0]['name']); + } + + public function testHandlRequiredAssociation() + { + $listener = $this->listener; + $event = $this->getMetadataEvent(); + + $listener->handleRequiredAssociation($event); + $this->assertNull($event->getParam('inputSpec')); + + $event->setParam('name', 'targetMany'); + $listener->handleRequiredAssociation($event); + + $inputSpec = $event->getParam('inputSpec'); + $this->assertFalse($inputSpec['required']); + } + + public function testHandlRequiredAssociationSetsNullOption() + { + $listener = $this->listener; + $event = $this->getMetadataEvent(); + $event->setParam('name', 'targetOneNullable'); + $listener->handleRequiredAssociation($event); + + $elementSpec = $event->getParam('elementSpec'); + + $this->assertEquals('NULL', $elementSpec['spec']['options']['empty_option']); + + $listener->handleRequiredAssociation($event); + $elementSpec['spec']['options']['empty_option'] = 'foo'; + + $this->assertEquals('foo', $elementSpec['spec']['options']['empty_option']); + + $elementSpec['spec']['options']['empty_option'] = null; + $listener->handleRequiredAssociation($event); + $this->assertEquals('NULL', $elementSpec['spec']['options']['empty_option']); + } + + public function testHandleRequiredField() + { + $listener = $this->listener; + $event = $this->getMetadataEvent(); + + $event->setParam('name', 'datetime'); + $listener->handleRequiredField($event); + $inputSpec = $event->getParam('inputSpec'); + $this->assertTrue($inputSpec['required']); + + $event->setParam('name', 'string'); + $listener->handleRequiredField($event); + $this->assertTrue($inputSpec['required']); + + $event->setParam('name', 'stringNullable'); + $listener->handleRequiredField($event); + $this->assertFalse($inputSpec['required']); + } + + /** + * @dataProvider eventTypeProvider + */ + public function testHandleTypeField($name, $type) + { + $listener = $this->listener; + $event = $this->getMetadataEvent(); + + $listener->handleFilterField($event); + $this->assertNull($event->getParam('elementSpec')); + + $event->setParam('name', $name); + $listener->handleTypeField($event); + + $elementSpec = $event->getParam('elementSpec'); + $this->assertEquals($type, $elementSpec['spec']['type']); + } + + /** + * @dataProvider eventValidatorProvider + */ + public function testHandlevalidatorField($name, $type) + { + $listener = $this->listener; + $event = $this->getMetadataEvent(); - $listener->handleAttributesAnnotation($event); + $listener->handleFilterField($event); + $this->assertNull($event->getParam('inputSpec')); - $spec = $event->getParam('elementSpec'); + $event->setParam('name', $name); + $listener->handleValidatorField($event); - $this->assertCount(2, $spec['spec']['attributes']); - $this->assertArrayHasKey('attr1', $spec['spec']['attributes']); - $this->assertEquals('textarea', $spec['spec']['attributes']['type']); - $this->assertEquals('value', $spec['spec']['attributes']['attr1']); + $inputSpec = $event->getParam('inputSpec'); + if (null === $type) { + $this->assertEmpty($inputSpec['validators']); + } else { + $this->assertEquals($type, $inputSpec['validators'][0]['name']); + } + } + + public function eventValidatorProvider() + { + return array( + array('bool', 'InArray'), + array('boolean', 'InArray'), + array('bigint', 'Int'), + array('float', 'Float'), + array('integer', 'Int'), + array('smallint', 'Int'), + array('datetime', null), + array('datetimetz', null), + array('date', null), + array('time', null), + array('string', 'StringLength'), + array('stringNullable', null), + array('text', null), + ); + } + + public function eventFilterProvider() + { + return array( + array('bool', 'Boolean'), + array('boolean', 'Boolean'), + array('bigint', 'Int'), + array('integer', 'Int'), + array('smallint', 'Int'), + array('datetime', 'StringTrim'), + array('datetimetz', 'StringTrim'), + array('date', 'StringTrim'), + array('time', 'StringTrim'), + array('string', 'StringTrim'), + array('text', 'StringTrim'), + ); } - public function eventProvider() + public function eventTypeProvider() { return array( array('bool', 'Zend\Form\Element\Checkbox'), array('boolean', 'Zend\Form\Element\Checkbox'), + array('bigint', 'Zend\Form\Element\Number'), + array('integer', 'Zend\Form\Element\Number'), + array('smallint', 'Zend\Form\Element\Number'), + array('datetime', 'Zend\Form\Element\DateTimeSelect'), + array('datetimetz', 'Zend\Form\Element\DateTimeSelect'), + array('date', 'Zend\Form\Element\DateSelect'), + array('time', 'Zend\Form\Element\Time'), array('string', 'Zend\Form\Element'), + array('text', 'Zend\Form\Element\Text'), ); } + + public function eventNameProvider() + { + return array( + array( + 'handleFilterField', + 'handleTypeField', + 'handleValidatorField', + 'handleRequiredField', + 'handleRequiredAssociation', + 'handleExcludeField', + 'handleExcludeAssociation', + 'handleToOne', + 'handleToMany' + ) + ); + } + + protected function getMetadataEvent() + { + $event = new Event(); + $metadata = $this->getEntityManager()->getClassMetadata('DoctrineORMModuleTest\Assets\Entity\FormEntity'); + $event->setParam('metadata', $metadata); + + return $event; + } }