DoctrineExtractor.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Bridge\Doctrine\PropertyInfo;
  11. use Doctrine\Common\Persistence\Mapping\ClassMetadataFactory;
  12. use Doctrine\Common\Persistence\Mapping\MappingException;
  13. use Doctrine\DBAL\Types\Type as DBALType;
  14. use Doctrine\ORM\Mapping\ClassMetadataInfo;
  15. use Doctrine\ORM\Mapping\MappingException as OrmMappingException;
  16. use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
  17. use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
  18. use Symfony\Component\PropertyInfo\Type;
  19. /**
  20. * Extracts data using Doctrine ORM and ODM metadata.
  21. *
  22. * @author Kévin Dunglas <dunglas@gmail.com>
  23. */
  24. class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface
  25. {
  26. private $classMetadataFactory;
  27. public function __construct(ClassMetadataFactory $classMetadataFactory)
  28. {
  29. $this->classMetadataFactory = $classMetadataFactory;
  30. }
  31. /**
  32. * {@inheritdoc}
  33. */
  34. public function getProperties($class, array $context = array())
  35. {
  36. try {
  37. $metadata = $this->classMetadataFactory->getMetadataFor($class);
  38. } catch (MappingException $exception) {
  39. return;
  40. } catch (OrmMappingException $exception) {
  41. return;
  42. }
  43. $properties = array_merge($metadata->getFieldNames(), $metadata->getAssociationNames());
  44. if ($metadata instanceof ClassMetadataInfo && class_exists('Doctrine\ORM\Mapping\Embedded') && $metadata->embeddedClasses) {
  45. $properties = array_filter($properties, function ($property) {
  46. return false === strpos($property, '.');
  47. });
  48. $properties = array_merge($properties, array_keys($metadata->embeddedClasses));
  49. }
  50. return $properties;
  51. }
  52. /**
  53. * {@inheritdoc}
  54. */
  55. public function getTypes($class, $property, array $context = array())
  56. {
  57. try {
  58. $metadata = $this->classMetadataFactory->getMetadataFor($class);
  59. } catch (MappingException $exception) {
  60. return;
  61. } catch (OrmMappingException $exception) {
  62. return;
  63. }
  64. if ($metadata->hasAssociation($property)) {
  65. $class = $metadata->getAssociationTargetClass($property);
  66. if ($metadata->isSingleValuedAssociation($property)) {
  67. if ($metadata instanceof ClassMetadataInfo) {
  68. $associationMapping = $metadata->getAssociationMapping($property);
  69. $nullable = $this->isAssociationNullable($associationMapping);
  70. } else {
  71. $nullable = false;
  72. }
  73. return array(new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $class));
  74. }
  75. $collectionKeyType = Type::BUILTIN_TYPE_INT;
  76. if ($metadata instanceof ClassMetadataInfo) {
  77. $associationMapping = $metadata->getAssociationMapping($property);
  78. if (isset($associationMapping['indexBy'])) {
  79. $indexProperty = $associationMapping['indexBy'];
  80. /** @var ClassMetadataInfo $subMetadata */
  81. $subMetadata = $this->classMetadataFactory->getMetadataFor($associationMapping['targetEntity']);
  82. $typeOfField = $subMetadata->getTypeOfField($indexProperty);
  83. if (null === $typeOfField) {
  84. $associationMapping = $subMetadata->getAssociationMapping($indexProperty);
  85. /** @var ClassMetadataInfo $subMetadata */
  86. $indexProperty = $subMetadata->getSingleAssociationReferencedJoinColumnName($indexProperty);
  87. $subMetadata = $this->classMetadataFactory->getMetadataFor($associationMapping['targetEntity']);
  88. $typeOfField = $subMetadata->getTypeOfField($indexProperty);
  89. }
  90. $collectionKeyType = $this->getPhpType($typeOfField);
  91. }
  92. }
  93. return array(new Type(
  94. Type::BUILTIN_TYPE_OBJECT,
  95. false,
  96. 'Doctrine\Common\Collections\Collection',
  97. true,
  98. new Type($collectionKeyType),
  99. new Type(Type::BUILTIN_TYPE_OBJECT, false, $class)
  100. ));
  101. }
  102. if ($metadata instanceof ClassMetadataInfo && class_exists('Doctrine\ORM\Mapping\Embedded') && isset($metadata->embeddedClasses[$property])) {
  103. return array(new Type(Type::BUILTIN_TYPE_OBJECT, false, $metadata->embeddedClasses[$property]['class']));
  104. }
  105. if ($metadata->hasField($property)) {
  106. $typeOfField = $metadata->getTypeOfField($property);
  107. $nullable = $metadata instanceof ClassMetadataInfo && $metadata->isNullable($property);
  108. switch ($typeOfField) {
  109. case DBALType::DATE:
  110. case DBALType::DATETIME:
  111. case DBALType::DATETIMETZ:
  112. case 'vardatetime':
  113. case DBALType::TIME:
  114. return array(new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, 'DateTime'));
  115. case DBALType::TARRAY:
  116. return array(new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true));
  117. case DBALType::SIMPLE_ARRAY:
  118. return array(new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)));
  119. case DBALType::JSON_ARRAY:
  120. return array(new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true));
  121. default:
  122. $builtinType = $this->getPhpType($typeOfField);
  123. return $builtinType ? array(new Type($builtinType, $nullable)) : null;
  124. }
  125. }
  126. }
  127. /**
  128. * Determines whether an association is nullable.
  129. *
  130. * @param array $associationMapping
  131. *
  132. * @return bool
  133. *
  134. * @see https://github.com/doctrine/doctrine2/blob/v2.5.4/lib/Doctrine/ORM/Tools/EntityGenerator.php#L1221-L1246
  135. */
  136. private function isAssociationNullable(array $associationMapping)
  137. {
  138. if (isset($associationMapping['id']) && $associationMapping['id']) {
  139. return false;
  140. }
  141. if (!isset($associationMapping['joinColumns'])) {
  142. return true;
  143. }
  144. $joinColumns = $associationMapping['joinColumns'];
  145. foreach ($joinColumns as $joinColumn) {
  146. if (isset($joinColumn['nullable']) && !$joinColumn['nullable']) {
  147. return false;
  148. }
  149. }
  150. return true;
  151. }
  152. /**
  153. * Gets the corresponding built-in PHP type.
  154. *
  155. * @param string $doctrineType
  156. *
  157. * @return string|null
  158. */
  159. private function getPhpType($doctrineType)
  160. {
  161. switch ($doctrineType) {
  162. case DBALType::SMALLINT:
  163. case DBALType::INTEGER:
  164. return Type::BUILTIN_TYPE_INT;
  165. case DBALType::FLOAT:
  166. return Type::BUILTIN_TYPE_FLOAT;
  167. case DBALType::BIGINT:
  168. case DBALType::STRING:
  169. case DBALType::TEXT:
  170. case DBALType::GUID:
  171. case DBALType::DECIMAL:
  172. return Type::BUILTIN_TYPE_STRING;
  173. case DBALType::BOOLEAN:
  174. return Type::BUILTIN_TYPE_BOOL;
  175. case DBALType::BLOB:
  176. case 'binary':
  177. return Type::BUILTIN_TYPE_RESOURCE;
  178. case DBALType::OBJECT:
  179. return Type::BUILTIN_TYPE_OBJECT;
  180. }
  181. }
  182. }