vendor/gedmo/doctrine-extensions/src/Mapping/MappedEventSubscriber.php line 244

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Doctrine Behavioral Extensions package.
  4.  * (c) Gediminas Morkevicius <gediminas.morkevicius@gmail.com> http://www.gediminasm.org
  5.  * For the full copyright and license information, please view the LICENSE
  6.  * file that was distributed with this source code.
  7.  */
  8. namespace Gedmo\Mapping;
  9. use Doctrine\Common\Annotations\AnnotationReader;
  10. use Doctrine\Common\Annotations\PsrCachedReader;
  11. use Doctrine\Common\Annotations\Reader;
  12. use Doctrine\Common\EventArgs;
  13. use Doctrine\Common\EventSubscriber;
  14. use Doctrine\Deprecations\Deprecation;
  15. use Doctrine\ODM\MongoDB\DocumentManager;
  16. use Doctrine\ODM\MongoDB\Mapping\ClassMetadata as DocumentClassMetadata;
  17. use Doctrine\ORM\EntityManagerInterface;
  18. use Doctrine\ORM\Mapping\ClassMetadata as EntityClassMetadata;
  19. use Doctrine\ORM\Mapping\ClassMetadataInfo as LegacyEntityClassMetadata;
  20. use Doctrine\Persistence\Mapping\AbstractClassMetadataFactory;
  21. use Doctrine\Persistence\Mapping\ClassMetadata;
  22. use Doctrine\Persistence\ObjectManager;
  23. use Gedmo\Exception\InvalidArgumentException;
  24. use Gedmo\Mapping\Driver\AttributeReader;
  25. use Gedmo\Mapping\Event\AdapterInterface;
  26. use Gedmo\Mapping\Event\ClockAwareAdapterInterface;
  27. use Gedmo\ReferenceIntegrity\Mapping\Validator as ReferenceIntegrityValidator;
  28. use Gedmo\Uploadable\FilenameGenerator\FilenameGeneratorInterface;
  29. use Gedmo\Uploadable\Mapping\Validator as MappingValidator;
  30. use Psr\Cache\CacheItemPoolInterface;
  31. use Psr\Clock\ClockInterface;
  32. use Symfony\Component\Cache\Adapter\ArrayAdapter;
  33. /**
  34.  * This is extension of event subscriber class and is
  35.  * used specifically for handling the extension metadata
  36.  * mapping for extensions.
  37.  *
  38.  * It dries up some reusable code which is common for
  39.  * all extensions who maps additional metadata through
  40.  * extended drivers
  41.  *
  42.  * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
  43.  */
  44. abstract class MappedEventSubscriber implements EventSubscriber
  45. {
  46.     /**
  47.      * Static List of cached object configurations
  48.      * leaving it static for reasons to look into
  49.      * other listener configuration
  50.      *
  51.      * @var array<string, array<string, array<string, mixed>>>
  52.      *
  53.      * @phpstan-var array<string, array<class-string, array<string, mixed>>>
  54.      */
  55.     protected static $configurations = [];
  56.     /**
  57.      * Listener name, etc: sluggable
  58.      *
  59.      * @var string
  60.      */
  61.     protected $name;
  62.     /**
  63.      * ExtensionMetadataFactory used to read the extension
  64.      * metadata through the extension drivers
  65.      *
  66.      * @var array<int, ExtensionMetadataFactory>
  67.      */
  68.     private array $extensionMetadataFactory = [];
  69.     /**
  70.      * List of event adapters used for this listener
  71.      *
  72.      * @var array<string, AdapterInterface>
  73.      */
  74.     private array $adapters = [];
  75.     /**
  76.      * Custom annotation reader
  77.      *
  78.      * @var Reader|AttributeReader|object|false|null
  79.      */
  80.     private $annotationReader false;
  81.     /**
  82.      * @var Reader|AttributeReader|false|null
  83.      */
  84.     private static $defaultAnnotationReader false;
  85.     /**
  86.      * @var CacheItemPoolInterface|null
  87.      */
  88.     private $cacheItemPool;
  89.     private ?ClockInterface $clock null;
  90.     public function __construct()
  91.     {
  92.         $parts explode('\\'$this->getNamespace());
  93.         $this->name end($parts);
  94.     }
  95.     /**
  96.      * Get the configuration for specific object class
  97.      * if cache driver is present it scans it also
  98.      *
  99.      * @param string $class
  100.      *
  101.      * @phpstan-param class-string $class
  102.      *
  103.      * @return array<string, mixed>
  104.      *
  105.      * @phpstan-return array{
  106.      *  useObjectClass?: class-string,
  107.      *  referenceIntegrity?: array<string, array<string, value-of<ReferenceIntegrityValidator::INTEGRITY_ACTIONS>>>,
  108.      *  filePathField?: string,
  109.      *  uploadable?: bool,
  110.      *  fileNameField?: string,
  111.      *  allowOverwrite?: bool,
  112.      *  appendNumber?: bool,
  113.      *  maxSize?: float,
  114.      *  path?: string,
  115.      *  pathMethod?: string,
  116.      *  allowedTypes?: string[],
  117.      *  disallowedTypes?: string[],
  118.      *  filenameGenerator?: MappingValidator::FILENAME_GENERATOR_*|class-string<FilenameGeneratorInterface>,
  119.      *  fileMimeTypeField?: string,
  120.      *  fileSizeField?: string,
  121.      *  callback?: string,
  122.      * }
  123.      */
  124.     public function getConfiguration(ObjectManager $objectManager$class)
  125.     {
  126.         if (isset(self::$configurations[$this->name][$class])) {
  127.             return self::$configurations[$this->name][$class];
  128.         }
  129.         $config = [];
  130.         $cacheItemPool $this->getCacheItemPool($objectManager);
  131.         $cacheId ExtensionMetadataFactory::getCacheId($class$this->getNamespace());
  132.         $cacheItem $cacheItemPool->getItem($cacheId);
  133.         if ($cacheItem->isHit()) {
  134.             $config $cacheItem->get();
  135.             self::$configurations[$this->name][$class] = $config;
  136.         } else {
  137.             // re-generate metadata on cache miss
  138.             $this->loadMetadataForObjectClass($objectManager$objectManager->getClassMetadata($class));
  139.             if (isset(self::$configurations[$this->name][$class])) {
  140.                 $config self::$configurations[$this->name][$class];
  141.             }
  142.         }
  143.         $objectClass $config['useObjectClass'] ?? $class;
  144.         if ($objectClass !== $class) {
  145.             $this->getConfiguration($objectManager$objectClass);
  146.         }
  147.         return $config;
  148.     }
  149.     /**
  150.      * Get extended metadata mapping reader
  151.      *
  152.      * @return ExtensionMetadataFactory
  153.      */
  154.     public function getExtensionMetadataFactory(ObjectManager $objectManager)
  155.     {
  156.         $oid spl_object_id($objectManager);
  157.         if (!isset($this->extensionMetadataFactory[$oid])) {
  158.             if (false === $this->annotationReader) {
  159.                 // create default annotation/attribute reader for extensions
  160.                 $this->annotationReader $this->getDefaultAnnotationReader();
  161.             }
  162.             $this->extensionMetadataFactory[$oid] = new ExtensionMetadataFactory(
  163.                 $objectManager,
  164.                 $this->getNamespace(),
  165.                 $this->annotationReader,
  166.                 $this->getCacheItemPool($objectManager)
  167.             );
  168.         }
  169.         return $this->extensionMetadataFactory[$oid];
  170.     }
  171.     /**
  172.      * Set the annotation reader instance
  173.      *
  174.      * When originally implemented, `Doctrine\Common\Annotations\Reader` was not available,
  175.      * therefore this method may accept any object implementing these methods from the interface:
  176.      *
  177.      *     getClassAnnotations([reflectionClass])
  178.      *     getClassAnnotation([reflectionClass], [name])
  179.      *     getPropertyAnnotations([reflectionProperty])
  180.      *     getPropertyAnnotation([reflectionProperty], [name])
  181.      *
  182.      * @param Reader|AttributeReader|object $reader
  183.      *
  184.      * @return void
  185.      *
  186.      * NOTE Providing any object is deprecated, as of 4.0 a `Doctrine\Common\Annotations\Reader` or `Gedmo\Mapping\Driver\AttributeReader` will be required
  187.      */
  188.     public function setAnnotationReader($reader)
  189.     {
  190.         if (!$reader instanceof Reader && !$reader instanceof AttributeReader) {
  191.             Deprecation::trigger(
  192.                 'gedmo/doctrine-extensions',
  193.                 'https://github.com/doctrine-extensions/DoctrineExtensions/pull/2558',
  194.                 'Providing an annotation reader which does not implement %s or is not an instance of %s to %s() is deprecated.',
  195.                 Reader::class,
  196.                 AttributeReader::class,
  197.                 __METHOD__
  198.             );
  199.         }
  200.         $this->annotationReader $reader;
  201.     }
  202.     final public function setCacheItemPool(CacheItemPoolInterface $cacheItemPool): void
  203.     {
  204.         $this->cacheItemPool $cacheItemPool;
  205.     }
  206.     final public function setClock(ClockInterface $clock): void
  207.     {
  208.         $this->clock $clock;
  209.     }
  210.     /**
  211.      * Scans the objects for extended annotations
  212.      * event subscribers must subscribe to loadClassMetadata event
  213.      *
  214.      * @param ClassMetadata $metadata
  215.      *
  216.      * @return void
  217.      */
  218.     public function loadMetadataForObjectClass(ObjectManager $objectManager$metadata)
  219.     {
  220.         assert($metadata instanceof DocumentClassMetadata || $metadata instanceof EntityClassMetadata || $metadata instanceof LegacyEntityClassMetadata);
  221.         $factory $this->getExtensionMetadataFactory($objectManager);
  222.         try {
  223.             $config $factory->getExtensionMetadata($metadata);
  224.         } catch (\ReflectionException $e) {
  225.             // entity\document generator is running
  226.             $config = []; // will not store a cached version, to remap later
  227.         }
  228.         if ([] !== $config) {
  229.             self::$configurations[$this->name][$metadata->getName()] = $config;
  230.         }
  231.     }
  232.     /**
  233.      * Get an event adapter to handle event specific
  234.      * methods
  235.      *
  236.      * @throws InvalidArgumentException if event is not recognized
  237.      *
  238.      * @return AdapterInterface
  239.      */
  240.     protected function getEventAdapter(EventArgs $args)
  241.     {
  242.         $class get_class($args);
  243.         if (preg_match('@Doctrine\\\([^\\\]+)@'$class$m) && in_array($m[1], ['ODM''ORM'], true)) {
  244.             if (!isset($this->adapters[$m[1]])) {
  245.                 $adapterClass $this->getNamespace().'\\Mapping\\Event\\Adapter\\'.$m[1];
  246.                 if (!\class_exists($adapterClass)) {
  247.                     $adapterClass 'Gedmo\\Mapping\\Event\\Adapter\\'.$m[1];
  248.                 }
  249.                 $this->adapters[$m[1]] = new $adapterClass();
  250.                 if ($this->adapters[$m[1]] instanceof ClockAwareAdapterInterface && $this->clock instanceof ClockInterface) {
  251.                     $this->adapters[$m[1]]->setClock($this->clock);
  252.                 }
  253.             }
  254.             $this->adapters[$m[1]]->setEventArgs($args);
  255.             return $this->adapters[$m[1]];
  256.         }
  257.         throw new InvalidArgumentException('Event mapper does not support event arg class: '.$class);
  258.     }
  259.     /**
  260.      * Get the namespace of extension event subscriber.
  261.      * used for cache id of extensions also to know where
  262.      * to find Mapping drivers and event adapters
  263.      *
  264.      * @return string
  265.      */
  266.     abstract protected function getNamespace();
  267.     /**
  268.      * Sets the value for a mapped field
  269.      *
  270.      * @param object $object
  271.      * @param string $field
  272.      * @param mixed  $oldValue
  273.      * @param mixed  $newValue
  274.      *
  275.      * @return void
  276.      */
  277.     protected function setFieldValue(AdapterInterface $adapter$object$field$oldValue$newValue)
  278.     {
  279.         $manager $adapter->getObjectManager();
  280.         $meta $manager->getClassMetadata(get_class($object));
  281.         $uow $manager->getUnitOfWork();
  282.         $meta->getReflectionProperty($field)->setValue($object$newValue);
  283.         $uow->propertyChanged($object$field$oldValue$newValue);
  284.         $adapter->recomputeSingleObjectChangeSet($uow$meta$object);
  285.     }
  286.     /**
  287.      * Get the default annotation or attribute reader for extensions, creating it if necessary.
  288.      *
  289.      * If a reader cannot be created due to missing requirements, no default will be set as the reader is only required for annotation or attribute metadata,
  290.      * and the {@see ExtensionMetadataFactory} can handle raising an error if it tries to create a mapping driver that requires this reader.
  291.      *
  292.      * @return Reader|AttributeReader|null
  293.      */
  294.     private function getDefaultAnnotationReader()
  295.     {
  296.         if (false === self::$defaultAnnotationReader) {
  297.             if (class_exists(PsrCachedReader::class)) {
  298.                 self::$defaultAnnotationReader = new PsrCachedReader(new AnnotationReader(), new ArrayAdapter());
  299.             } elseif (\PHP_VERSION_ID >= 80000) {
  300.                 self::$defaultAnnotationReader = new AttributeReader();
  301.             } else {
  302.                 self::$defaultAnnotationReader null;
  303.             }
  304.         }
  305.         return self::$defaultAnnotationReader;
  306.     }
  307.     private function getCacheItemPool(ObjectManager $objectManager): CacheItemPoolInterface
  308.     {
  309.         if (null !== $this->cacheItemPool) {
  310.             return $this->cacheItemPool;
  311.         }
  312.         // TODO: The user should configure its own cache, we are using the one from Doctrine for BC. We should deprecate using
  313.         // the one from Doctrine when the bundle offers an easy way to configure this cache, otherwise users using the bundle
  314.         // will see lots of deprecations without an easy way to avoid them.
  315.         if ($objectManager instanceof EntityManagerInterface || $objectManager instanceof DocumentManager) {
  316.             $metadataFactory $objectManager->getMetadataFactory();
  317.             $getCache \Closure::bind(static fn (AbstractClassMetadataFactory $metadataFactory): ?CacheItemPoolInterface => $metadataFactory->getCache(), null\get_class($metadataFactory));
  318.             $metadataCache $getCache($metadataFactory);
  319.             if (null !== $metadataCache) {
  320.                 $this->cacheItemPool $metadataCache;
  321.                 return $this->cacheItemPool;
  322.             }
  323.         }
  324.         $this->cacheItemPool = new ArrayAdapter();
  325.         return $this->cacheItemPool;
  326.     }
  327. }