vendor/symfony/doctrine-bridge/PropertyInfo/DoctrineExtractor.php line 42

Open in your IDE?
  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\DBAL\Types\Type as DBALType;
  12. use Doctrine\DBAL\Types\Types;
  13. use Doctrine\ORM\EntityManagerInterface;
  14. use Doctrine\ORM\Mapping\ClassMetadata;
  15. use Doctrine\ORM\Mapping\ClassMetadataInfo;
  16. use Doctrine\ORM\Mapping\MappingException as OrmMappingException;
  17. use Doctrine\Persistence\Mapping\ClassMetadataFactory;
  18. use Doctrine\Persistence\Mapping\MappingException;
  19. use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
  20. use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
  21. use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
  22. use Symfony\Component\PropertyInfo\Type;
  23. /**
  24.  * Extracts data using Doctrine ORM and ODM metadata.
  25.  *
  26.  * @author Kévin Dunglas <dunglas@gmail.com>
  27.  */
  28. class DoctrineExtractor implements PropertyListExtractorInterfacePropertyTypeExtractorInterfacePropertyAccessExtractorInterface
  29. {
  30.     private $entityManager;
  31.     private $classMetadataFactory;
  32.     private static $useDeprecatedConstants;
  33.     /**
  34.      * @param EntityManagerInterface $entityManager
  35.      */
  36.     public function __construct($entityManager)
  37.     {
  38.         if ($entityManager instanceof EntityManagerInterface) {
  39.             $this->entityManager $entityManager;
  40.         } elseif ($entityManager instanceof ClassMetadataFactory) {
  41.             @trigger_error(sprintf('Injecting an instance of "%s" in "%s" is deprecated since Symfony 4.2, inject an instance of "%s" instead.'ClassMetadataFactory::class, __CLASS__EntityManagerInterface::class), \E_USER_DEPRECATED);
  42.             $this->classMetadataFactory $entityManager;
  43.         } else {
  44.             throw new \TypeError(sprintf('$entityManager must be an instance of "%s", "%s" given.'EntityManagerInterface::class, \is_object($entityManager) ? \get_class($entityManager) : \gettype($entityManager)));
  45.         }
  46.         if (null === self::$useDeprecatedConstants) {
  47.             self::$useDeprecatedConstants = !class_exists(Types::class);
  48.         }
  49.     }
  50.     /**
  51.      * {@inheritdoc}
  52.      */
  53.     public function getProperties($class, array $context = [])
  54.     {
  55.         if (null === $metadata $this->getMetadata($class)) {
  56.             return null;
  57.         }
  58.         $properties array_merge($metadata->getFieldNames(), $metadata->getAssociationNames());
  59.         if ($metadata instanceof ClassMetadataInfo && class_exists(\Doctrine\ORM\Mapping\Embedded::class) && $metadata->embeddedClasses) {
  60.             $properties array_filter($properties, function ($property) {
  61.                 return !str_contains($property'.');
  62.             });
  63.             $properties array_merge($propertiesarray_keys($metadata->embeddedClasses));
  64.         }
  65.         return $properties;
  66.     }
  67.     /**
  68.      * {@inheritdoc}
  69.      */
  70.     public function getTypes($class$property, array $context = [])
  71.     {
  72.         if (null === $metadata $this->getMetadata($class)) {
  73.             return null;
  74.         }
  75.         if ($metadata->hasAssociation($property)) {
  76.             $class $metadata->getAssociationTargetClass($property);
  77.             if ($metadata->isSingleValuedAssociation($property)) {
  78.                 if ($metadata instanceof ClassMetadataInfo) {
  79.                     $associationMapping $metadata->getAssociationMapping($property);
  80.                     $nullable $this->isAssociationNullable($associationMapping);
  81.                 } else {
  82.                     $nullable false;
  83.                 }
  84.                 return [new Type(Type::BUILTIN_TYPE_OBJECT$nullable$class)];
  85.             }
  86.             $collectionKeyType Type::BUILTIN_TYPE_INT;
  87.             if ($metadata instanceof ClassMetadataInfo) {
  88.                 $associationMapping $metadata->getAssociationMapping($property);
  89.                 if (isset($associationMapping['indexBy'])) {
  90.                     /** @var ClassMetadataInfo $subMetadata */
  91.                     $subMetadata $this->entityManager $this->entityManager->getClassMetadata($associationMapping['targetEntity']) : $this->classMetadataFactory->getMetadataFor($associationMapping['targetEntity']);
  92.                     // Check if indexBy value is a property
  93.                     $fieldName $associationMapping['indexBy'];
  94.                     if (null === ($typeOfField $subMetadata->getTypeOfField($fieldName))) {
  95.                         $fieldName $subMetadata->getFieldForColumn($associationMapping['indexBy']);
  96.                         //Not a property, maybe a column name?
  97.                         if (null === ($typeOfField $subMetadata->getTypeOfField($fieldName))) {
  98.                             //Maybe the column name is the association join column?
  99.                             $associationMapping $subMetadata->getAssociationMapping($fieldName);
  100.                             /** @var ClassMetadataInfo $subMetadata */
  101.                             $indexProperty $subMetadata->getSingleAssociationReferencedJoinColumnName($fieldName);
  102.                             $subMetadata $this->entityManager $this->entityManager->getClassMetadata($associationMapping['targetEntity']) : $this->classMetadataFactory->getMetadataFor($associationMapping['targetEntity']);
  103.                             //Not a property, maybe a column name?
  104.                             if (null === ($typeOfField $subMetadata->getTypeOfField($indexProperty))) {
  105.                                 $fieldName $subMetadata->getFieldForColumn($indexProperty);
  106.                                 $typeOfField $subMetadata->getTypeOfField($fieldName);
  107.                             }
  108.                         }
  109.                     }
  110.                     if (!$collectionKeyType $this->getPhpType($typeOfField)) {
  111.                         return null;
  112.                     }
  113.                 }
  114.             }
  115.             return [new Type(
  116.                 Type::BUILTIN_TYPE_OBJECT,
  117.                 false,
  118.                 'Doctrine\Common\Collections\Collection',
  119.                 true,
  120.                 new Type($collectionKeyType),
  121.                 new Type(Type::BUILTIN_TYPE_OBJECTfalse$class)
  122.             )];
  123.         }
  124.         if ($metadata instanceof ClassMetadataInfo && class_exists(\Doctrine\ORM\Mapping\Embedded::class) && isset($metadata->embeddedClasses[$property])) {
  125.             return [new Type(Type::BUILTIN_TYPE_OBJECTfalse$metadata->embeddedClasses[$property]['class'])];
  126.         }
  127.         if ($metadata->hasField($property)) {
  128.             $typeOfField $metadata->getTypeOfField($property);
  129.             if (!$builtinType $this->getPhpType($typeOfField)) {
  130.                 return null;
  131.             }
  132.             $nullable $metadata instanceof ClassMetadataInfo && $metadata->isNullable($property);
  133.             $enumType null;
  134.             if (null !== $enumClass $metadata->getFieldMapping($property)['enumType'] ?? null) {
  135.                 $enumType = new Type(Type::BUILTIN_TYPE_OBJECT$nullable$enumClass);
  136.             }
  137.             switch ($builtinType) {
  138.                 case Type::BUILTIN_TYPE_OBJECT:
  139.                     switch ($typeOfField) {
  140.                         case self::$useDeprecatedConstants DBALType::DATE Types::DATE_MUTABLE:
  141.                         case self::$useDeprecatedConstants DBALType::DATETIME Types::DATETIME_MUTABLE:
  142.                         case self::$useDeprecatedConstants DBALType::DATETIMETZ Types::DATETIMETZ_MUTABLE:
  143.                         case 'vardatetime':
  144.                         case self::$useDeprecatedConstants DBALType::TIME Types::TIME_MUTABLE:
  145.                             return [new Type(Type::BUILTIN_TYPE_OBJECT$nullable'DateTime')];
  146.                         case 'date_immutable':
  147.                         case 'datetime_immutable':
  148.                         case 'datetimetz_immutable':
  149.                         case 'time_immutable':
  150.                             return [new Type(Type::BUILTIN_TYPE_OBJECT$nullable'DateTimeImmutable')];
  151.                         case 'dateinterval':
  152.                             return [new Type(Type::BUILTIN_TYPE_OBJECT$nullable'DateInterval')];
  153.                     }
  154.                     break;
  155.                 case Type::BUILTIN_TYPE_ARRAY:
  156.                     switch ($typeOfField) {
  157.                         case self::$useDeprecatedConstants DBALType::TARRAY Types::ARRAY:
  158.                         case 'json_array':
  159.                             // return null if $enumType is set, because we can't determine if collectionKeyType is string or int
  160.                             if ($enumType) {
  161.                                 return null;
  162.                             }
  163.                             return [new Type(Type::BUILTIN_TYPE_ARRAY$nullablenulltrue)];
  164.                         case self::$useDeprecatedConstants DBALType::SIMPLE_ARRAY Types::SIMPLE_ARRAY:
  165.                             return [new Type(Type::BUILTIN_TYPE_ARRAY$nullablenulltrue, new Type(Type::BUILTIN_TYPE_INT), $enumType ?? new Type(Type::BUILTIN_TYPE_STRING))];
  166.                     }
  167.                     break;
  168.                 case Type::BUILTIN_TYPE_INT:
  169.                 case Type::BUILTIN_TYPE_STRING:
  170.                     if ($enumType) {
  171.                         return [$enumType];
  172.                     }
  173.                     break;
  174.             }
  175.             return [new Type($builtinType$nullable)];
  176.         }
  177.         return null;
  178.     }
  179.     /**
  180.      * {@inheritdoc}
  181.      */
  182.     public function isReadable($class$property, array $context = [])
  183.     {
  184.         return null;
  185.     }
  186.     /**
  187.      * {@inheritdoc}
  188.      */
  189.     public function isWritable($class$property, array $context = [])
  190.     {
  191.         if (
  192.             null === ($metadata $this->getMetadata($class))
  193.             || ClassMetadata::GENERATOR_TYPE_NONE === $metadata->generatorType
  194.             || !\in_array($property$metadata->getIdentifierFieldNames(), true)
  195.         ) {
  196.             return null;
  197.         }
  198.         return false;
  199.     }
  200.     private function getMetadata(string $class): ?ClassMetadata
  201.     {
  202.         try {
  203.             return $this->entityManager $this->entityManager->getClassMetadata($class) : $this->classMetadataFactory->getMetadataFor($class);
  204.         } catch (MappingException|OrmMappingException $exception) {
  205.             return null;
  206.         }
  207.     }
  208.     /**
  209.      * Determines whether an association is nullable.
  210.      *
  211.      * @see https://github.com/doctrine/doctrine2/blob/v2.5.4/lib/Doctrine/ORM/Tools/EntityGenerator.php#L1221-L1246
  212.      */
  213.     private function isAssociationNullable(array $associationMapping): bool
  214.     {
  215.         if (isset($associationMapping['id']) && $associationMapping['id']) {
  216.             return false;
  217.         }
  218.         if (!isset($associationMapping['joinColumns'])) {
  219.             return true;
  220.         }
  221.         $joinColumns $associationMapping['joinColumns'];
  222.         foreach ($joinColumns as $joinColumn) {
  223.             if (isset($joinColumn['nullable']) && !$joinColumn['nullable']) {
  224.                 return false;
  225.             }
  226.         }
  227.         return true;
  228.     }
  229.     /**
  230.      * Gets the corresponding built-in PHP type.
  231.      */
  232.     private function getPhpType(string $doctrineType): ?string
  233.     {
  234.         switch ($doctrineType) {
  235.             case self::$useDeprecatedConstants DBALType::SMALLINT Types::SMALLINT:
  236.             case self::$useDeprecatedConstants DBALType::INTEGER Types::INTEGER:
  237.                 return Type::BUILTIN_TYPE_INT;
  238.             case self::$useDeprecatedConstants DBALType::FLOAT Types::FLOAT:
  239.                 return Type::BUILTIN_TYPE_FLOAT;
  240.             case self::$useDeprecatedConstants DBALType::BIGINT Types::BIGINT:
  241.             case self::$useDeprecatedConstants DBALType::STRING Types::STRING:
  242.             case self::$useDeprecatedConstants DBALType::TEXT Types::TEXT:
  243.             case self::$useDeprecatedConstants DBALType::GUID Types::GUID:
  244.             case self::$useDeprecatedConstants DBALType::DECIMAL Types::DECIMAL:
  245.                 return Type::BUILTIN_TYPE_STRING;
  246.             case self::$useDeprecatedConstants DBALType::BOOLEAN Types::BOOLEAN:
  247.                 return Type::BUILTIN_TYPE_BOOL;
  248.             case self::$useDeprecatedConstants DBALType::BLOB Types::BLOB:
  249.             case 'binary':
  250.                 return Type::BUILTIN_TYPE_RESOURCE;
  251.             case self::$useDeprecatedConstants DBALType::OBJECT Types::OBJECT:
  252.             case self::$useDeprecatedConstants DBALType::DATE Types::DATE_MUTABLE:
  253.             case self::$useDeprecatedConstants DBALType::DATETIME Types::DATETIME_MUTABLE:
  254.             case self::$useDeprecatedConstants DBALType::DATETIMETZ Types::DATETIMETZ_MUTABLE:
  255.             case 'vardatetime':
  256.             case self::$useDeprecatedConstants DBALType::TIME Types::TIME_MUTABLE:
  257.             case 'date_immutable':
  258.             case 'datetime_immutable':
  259.             case 'datetimetz_immutable':
  260.             case 'time_immutable':
  261.             case 'dateinterval':
  262.                 return Type::BUILTIN_TYPE_OBJECT;
  263.             case self::$useDeprecatedConstants DBALType::TARRAY Types::ARRAY:
  264.             case self::$useDeprecatedConstants DBALType::SIMPLE_ARRAY Types::SIMPLE_ARRAY:
  265.             case 'json_array':
  266.                 return Type::BUILTIN_TYPE_ARRAY;
  267.         }
  268.         return null;
  269.     }
  270. }