diff --git a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php index 0123d221ce81..2e092a3a944b 100644 --- a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php +++ b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Doctrine\Form; use Doctrine\Common\Persistence\ManagerRegistry; +use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\Common\Persistence\Mapping\MappingException; use Doctrine\ORM\Mapping\MappingException as LegacyMappingException; use Symfony\Component\Form\FormTypeGuesserInterface; @@ -83,14 +84,39 @@ public function guessType($class, $property) */ public function guessRequired($class, $property) { - $ret = $this->getMetadata($class); - if ($ret && $ret[0]->hasField($property)) { - if (!$ret[0]->isNullable($property)) { + $classMetadatas = $this->getMetadata($class); + + if (!$classMetadatas) { + return null; + } + + /* @var ClassMetadataInfo $classMetadata */ + $classMetadata = $classMetadatas[0]; + + // Check whether the field exists and is nullable or not + if ($classMetadata->hasField($property)) { + if (!$classMetadata->isNullable($property)) { return new ValueGuess(true, Guess::HIGH_CONFIDENCE); } return new ValueGuess(false, Guess::MEDIUM_CONFIDENCE); } + + // Check whether the association exists, is a to-one association and its + // join column is nullable or not + if ($classMetadata->isAssociationWithSingleJoinColumn($property)) { + $mapping = $classMetadata->getAssociationMapping($property); + + if (!isset($mapping['joinColumns'][0]['nullable'])) { + // The "nullable" option defaults to true, in that case the + // field should not be required. + return new ValueGuess(false, Guess::HIGH_CONFIDENCE); + } + + return new ValueGuess(!$mapping['joinColumns'][0]['nullable'], Guess::HIGH_CONFIDENCE); + } + + return null; } /** diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/DoctrineOrmTypeGuesserTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/DoctrineOrmTypeGuesserTest.php new file mode 100644 index 000000000000..cb69d8b6ae32 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/DoctrineOrmTypeGuesserTest.php @@ -0,0 +1,89 @@ +assertEquals($expected, $this->getGuesser($classMetadata)->guessRequired('TestEntity', 'field')); + } + + public function requiredProvider() + { + $return = array(); + + // Simple field, not nullable + $classMetadata = $this->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadata')->disableOriginalConstructor()->getMock(); + $classMetadata->expects($this->once())->method('hasField')->with('field')->will($this->returnValue(true)); + $classMetadata->expects($this->once())->method('isNullable')->with('field')->will($this->returnValue(false)); + + $return[] = array($classMetadata, new ValueGuess(true, Guess::HIGH_CONFIDENCE)); + + // Simple field, nullable + $classMetadata = $this->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadata')->disableOriginalConstructor()->getMock(); + $classMetadata->expects($this->once())->method('hasField')->with('field')->will($this->returnValue(true)); + $classMetadata->expects($this->once())->method('isNullable')->with('field')->will($this->returnValue(true)); + + $return[] = array($classMetadata, new ValueGuess(false, Guess::MEDIUM_CONFIDENCE)); + + // One-to-one, nullable (by default) + $classMetadata = $this->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadata')->disableOriginalConstructor()->getMock(); + $classMetadata->expects($this->once())->method('hasField')->with('field')->will($this->returnValue(false)); + $classMetadata->expects($this->once())->method('isAssociationWithSingleJoinColumn')->with('field')->will($this->returnValue(true)); + + $mapping = array('joinColumns' => array(array())); + $classMetadata->expects($this->once())->method('getAssociationMapping')->with('field')->will($this->returnValue($mapping)); + + $return[] = array($classMetadata, new ValueGuess(false, Guess::HIGH_CONFIDENCE)); + + // One-to-one, nullable (explicit) + $classMetadata = $this->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadata')->disableOriginalConstructor()->getMock(); + $classMetadata->expects($this->once())->method('hasField')->with('field')->will($this->returnValue(false)); + $classMetadata->expects($this->once())->method('isAssociationWithSingleJoinColumn')->with('field')->will($this->returnValue(true)); + + $mapping = array('joinColumns' => array(array('nullable'=>true))); + $classMetadata->expects($this->once())->method('getAssociationMapping')->with('field')->will($this->returnValue($mapping)); + + $return[] = array($classMetadata, new ValueGuess(false, Guess::HIGH_CONFIDENCE)); + + // One-to-one, not nullable + $classMetadata = $this->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadata')->disableOriginalConstructor()->getMock(); + $classMetadata->expects($this->once())->method('hasField')->with('field')->will($this->returnValue(false)); + $classMetadata->expects($this->once())->method('isAssociationWithSingleJoinColumn')->with('field')->will($this->returnValue(true)); + + $mapping = array('joinColumns' => array(array('nullable'=>false))); + $classMetadata->expects($this->once())->method('getAssociationMapping')->with('field')->will($this->returnValue($mapping)); + + $return[] = array($classMetadata, new ValueGuess(true, Guess::HIGH_CONFIDENCE)); + + // One-to-many, no clue + $classMetadata = $this->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadata')->disableOriginalConstructor()->getMock(); + $classMetadata->expects($this->once())->method('hasField')->with('field')->will($this->returnValue(false)); + $classMetadata->expects($this->once())->method('isAssociationWithSingleJoinColumn')->with('field')->will($this->returnValue(false)); + + $return[] = array($classMetadata, null); + + return $return; + } + + private function getGuesser(ClassMetadata $classMetadata) + { + $em = $this->getMock('Doctrine\Common\Persistence\ObjectManager'); + $em->expects($this->once())->method('getClassMetaData')->with('TestEntity')->will($this->returnValue($classMetadata)); + + $registry = $this->getMock('Doctrine\Common\Persistence\ManagerRegistry'); + $registry->expects($this->once())->method('getManagers')->will($this->returnValue(array($em))); + + return new DoctrineOrmTypeGuesser($registry); + } + +} \ No newline at end of file