vendor\symfony\doctrine-bridge\PropertyInfo\DoctrineExtractor.php line 227

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <[email protected]>
  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\Collections\Collection;
  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\Embedded;
  17. use Doctrine\ORM\Mapping\MappingException as OrmMappingException;
  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 <[email protected]>
  27.  */
  28. class DoctrineExtractor implements PropertyListExtractorInterfacePropertyTypeExtractorInterfacePropertyAccessExtractorInterface
  29. {
  30.     private $entityManager;
  31.     public function __construct(EntityManagerInterface $entityManager)
  32.     {
  33.         $this->entityManager $entityManager;
  34.     }
  35.     /**
  36.      * {@inheritdoc}
  37.      */
  38.     public function getProperties(string $class, array $context = [])
  39.     {
  40.         if (null === $metadata $this->getMetadata($class)) {
  41.             return null;
  42.         }
  43.         $properties array_merge($metadata->getFieldNames(), $metadata->getAssociationNames());
  44.         if ($metadata instanceof ClassMetadataInfo && class_exists(Embedded::class) && $metadata->embeddedClasses) {
  45.             $properties array_filter($properties, function ($property) {
  46.                 return !str_contains($property'.');
  47.             });
  48.             $properties array_merge($propertiesarray_keys($metadata->embeddedClasses));
  49.         }
  50.         return $properties;
  51.     }
  52.     /**
  53.      * {@inheritdoc}
  54.      */
  55.     public function getTypes(string $classstring $property, array $context = [])
  56.     {
  57.         if (null === $metadata $this->getMetadata($class)) {
  58.             return null;
  59.         }
  60.         if ($metadata->hasAssociation($property)) {
  61.             $class $metadata->getAssociationTargetClass($property);
  62.             if ($metadata->isSingleValuedAssociation($property)) {
  63.                 if ($metadata instanceof ClassMetadataInfo) {
  64.                     $associationMapping $metadata->getAssociationMapping($property);
  65.                     $nullable $this->isAssociationNullable($associationMapping);
  66.                 } else {
  67.                     $nullable false;
  68.                 }
  69.                 return [new Type(Type::BUILTIN_TYPE_OBJECT$nullable$class)];
  70.             }
  71.             $collectionKeyType Type::BUILTIN_TYPE_INT;
  72.             if ($metadata instanceof ClassMetadataInfo) {
  73.                 $associationMapping $metadata->getAssociationMapping($property);
  74.                 if (isset($associationMapping['indexBy'])) {
  75.                     /** @var ClassMetadataInfo $subMetadata */
  76.                     $subMetadata $this->entityManager->getClassMetadata($associationMapping['targetEntity']);
  77.                     // Check if indexBy value is a property
  78.                     $fieldName $associationMapping['indexBy'];
  79.                     if (null === ($typeOfField $subMetadata->getTypeOfField($fieldName))) {
  80.                         $fieldName $subMetadata->getFieldForColumn($associationMapping['indexBy']);
  81.                         // Not a property, maybe a column name?
  82.                         if (null === ($typeOfField $subMetadata->getTypeOfField($fieldName))) {
  83.                             // Maybe the column name is the association join column?
  84.                             $associationMapping $subMetadata->getAssociationMapping($fieldName);
  85.                             /** @var ClassMetadataInfo $subMetadata */
  86.                             $indexProperty $subMetadata->getSingleAssociationReferencedJoinColumnName($fieldName);
  87.                             $subMetadata $this->entityManager->getClassMetadata($associationMapping['targetEntity']);
  88.                             // Not a property, maybe a column name?
  89.                             if (null === ($typeOfField $subMetadata->getTypeOfField($indexProperty))) {
  90.                                 $fieldName $subMetadata->getFieldForColumn($indexProperty);
  91.                                 $typeOfField $subMetadata->getTypeOfField($fieldName);
  92.                             }
  93.                         }
  94.                     }
  95.                     if (!$collectionKeyType $this->getPhpType($typeOfField)) {
  96.                         return null;
  97.                     }
  98.                 }
  99.             }
  100.             return [new Type(
  101.                 Type::BUILTIN_TYPE_OBJECT,
  102.                 false,
  103.                 Collection::class,
  104.                 true,
  105.                 new Type($collectionKeyType),
  106.                 new Type(Type::BUILTIN_TYPE_OBJECTfalse$class)
  107.             )];
  108.         }
  109.         if ($metadata instanceof ClassMetadataInfo && class_exists(Embedded::class) && isset($metadata->embeddedClasses[$property])) {
  110.             return [new Type(Type::BUILTIN_TYPE_OBJECTfalse$metadata->embeddedClasses[$property]['class'])];
  111.         }
  112.         if ($metadata->hasField($property)) {
  113.             $typeOfField $metadata->getTypeOfField($property);
  114.             if (!$builtinType $this->getPhpType($typeOfField)) {
  115.                 return null;
  116.             }
  117.             $nullable $metadata instanceof ClassMetadataInfo && $metadata->isNullable($property);
  118.             $enumType null;
  119.             if (null !== $enumClass $metadata->getFieldMapping($property)['enumType'] ?? null) {
  120.                 $enumType = new Type(Type::BUILTIN_TYPE_OBJECT$nullable$enumClass);
  121.             }
  122.             switch ($builtinType) {
  123.                 case Type::BUILTIN_TYPE_OBJECT:
  124.                     switch ($typeOfField) {
  125.                         case Types::DATE_MUTABLE:
  126.                         case Types::DATETIME_MUTABLE:
  127.                         case Types::DATETIMETZ_MUTABLE:
  128.                         case 'vardatetime':
  129.                         case Types::TIME_MUTABLE:
  130.                             return [new Type(Type::BUILTIN_TYPE_OBJECT$nullable'DateTime')];
  131.                         case Types::DATE_IMMUTABLE:
  132.                         case Types::DATETIME_IMMUTABLE:
  133.                         case Types::DATETIMETZ_IMMUTABLE:
  134.                         case Types::TIME_IMMUTABLE:
  135.                             return [new Type(Type::BUILTIN_TYPE_OBJECT$nullable'DateTimeImmutable')];
  136.                         case Types::DATEINTERVAL:
  137.                             return [new Type(Type::BUILTIN_TYPE_OBJECT$nullable'DateInterval')];
  138.                     }
  139.                     break;
  140.                 case Type::BUILTIN_TYPE_ARRAY:
  141.                     switch ($typeOfField) {
  142.                         case Types::ARRAY:
  143.                         case 'json_array':
  144.                             // return null if $enumType is set, because we can't determine if collectionKeyType is string or int
  145.                             if ($enumType) {
  146.                                 return null;
  147.                             }
  148.                             return [new Type(Type::BUILTIN_TYPE_ARRAY$nullablenulltrue)];
  149.                         case Types::SIMPLE_ARRAY:
  150.                             return [new Type(Type::BUILTIN_TYPE_ARRAY$nullablenulltrue, new Type(Type::BUILTIN_TYPE_INT), $enumType ?? new Type(Type::BUILTIN_TYPE_STRING))];
  151.                     }
  152.                     break;
  153.                 case Type::BUILTIN_TYPE_INT:
  154.                 case Type::BUILTIN_TYPE_STRING:
  155.                     if ($enumType) {
  156.                         return [$enumType];
  157.                     }
  158.                     break;
  159.             }
  160.             return [new Type($builtinType$nullable)];
  161.         }
  162.         return null;
  163.     }
  164.     /**
  165.      * {@inheritdoc}
  166.      */
  167.     public function isReadable(string $classstring $property, array $context = [])
  168.     {
  169.         return null;
  170.     }
  171.     /**
  172.      * {@inheritdoc}
  173.      */
  174.     public function isWritable(string $classstring $property, array $context = [])
  175.     {
  176.         if (
  177.             null === ($metadata $this->getMetadata($class))
  178.             || ClassMetadata::GENERATOR_TYPE_NONE === $metadata->generatorType
  179.             || !\in_array($property$metadata->getIdentifierFieldNames(), true)
  180.         ) {
  181.             return null;
  182.         }
  183.         return false;
  184.     }
  185.     private function getMetadata(string $class): ?ClassMetadata
  186.     {
  187.         try {
  188.             return $this->entityManager->getClassMetadata($class);
  189.         } catch (MappingException|OrmMappingException $exception) {
  190.             return null;
  191.         }
  192.     }
  193.     /**
  194.      * Determines whether an association is nullable.
  195.      *
  196.      * @see https://github.com/doctrine/doctrine2/blob/v2.5.4/lib/Doctrine/ORM/Tools/EntityGenerator.php#L1221-L1246
  197.      */
  198.     private function isAssociationNullable(array $associationMapping): bool
  199.     {
  200.         if (isset($associationMapping['id']) && $associationMapping['id']) {
  201.             return false;
  202.         }
  203.         if (!isset($associationMapping['joinColumns'])) {
  204.             return true;
  205.         }
  206.         $joinColumns $associationMapping['joinColumns'];
  207.         foreach ($joinColumns as $joinColumn) {
  208.             if (isset($joinColumn['nullable']) && !$joinColumn['nullable']) {
  209.                 return false;
  210.             }
  211.         }
  212.         return true;
  213.     }
  214.     /**
  215.      * Gets the corresponding built-in PHP type.
  216.      */
  217.     private function getPhpType(string $doctrineType): ?string
  218.     {
  219.         switch ($doctrineType) {
  220.             case Types::SMALLINT:
  221.             case Types::INTEGER:
  222.                 return Type::BUILTIN_TYPE_INT;
  223.             case Types::FLOAT:
  224.                 return Type::BUILTIN_TYPE_FLOAT;
  225.             case Types::BIGINT:
  226.             case Types::STRING:
  227.             case Types::TEXT:
  228.             case Types::GUID:
  229.             case Types::DECIMAL:
  230.                 return Type::BUILTIN_TYPE_STRING;
  231.             case Types::BOOLEAN:
  232.                 return Type::BUILTIN_TYPE_BOOL;
  233.             case Types::BLOB:
  234.             case Types::BINARY:
  235.                 return Type::BUILTIN_TYPE_RESOURCE;
  236.             case Types::OBJECT:
  237.             case Types::DATE_MUTABLE:
  238.             case Types::DATETIME_MUTABLE:
  239.             case Types::DATETIMETZ_MUTABLE:
  240.             case 'vardatetime':
  241.             case Types::TIME_MUTABLE:
  242.             case Types::DATE_IMMUTABLE:
  243.             case Types::DATETIME_IMMUTABLE:
  244.             case Types::DATETIMETZ_IMMUTABLE:
  245.             case Types::TIME_IMMUTABLE:
  246.             case Types::DATEINTERVAL:
  247.                 return Type::BUILTIN_TYPE_OBJECT;
  248.             case Types::ARRAY:
  249.             case Types::SIMPLE_ARRAY:
  250.             case 'json_array':
  251.                 return Type::BUILTIN_TYPE_ARRAY;
  252.         }
  253.         return null;
  254.     }
  255. }