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

  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 Psr\Cache\CacheItemPoolInterface;
  28. use Psr\Clock\ClockInterface;
  29. use Symfony\Component\Cache\Adapter\ArrayAdapter;
  30. /**
  31.  * This is extension of event subscriber class and is
  32.  * used specifically for handling the extension metadata
  33.  * mapping for extensions.
  34.  *
  35.  * It dries up some reusable code which is common for
  36.  * all extensions who maps additional metadata through
  37.  * extended drivers
  38.  *
  39.  * @phpstan-template TConfig of array
  40.  * @phpstan-template TEventAdapter of AdapterInterface
  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 TConfig
  106.      */
  107.     public function getConfiguration(ObjectManager $objectManager$class)
  108.     {
  109.         if (isset(self::$configurations[$this->name][$class])) {
  110.             return self::$configurations[$this->name][$class];
  111.         }
  112.         $config = [];
  113.         $cacheItemPool $this->getCacheItemPool($objectManager);
  114.         $cacheId ExtensionMetadataFactory::getCacheId($class$this->getNamespace());
  115.         $cacheItem $cacheItemPool->getItem($cacheId);
  116.         if ($cacheItem->isHit()) {
  117.             $config $cacheItem->get();
  118.             self::$configurations[$this->name][$class] = $config;
  119.         } else {
  120.             // re-generate metadata on cache miss
  121.             $this->loadMetadataForObjectClass($objectManager$objectManager->getClassMetadata($class));
  122.             if (isset(self::$configurations[$this->name][$class])) {
  123.                 $config self::$configurations[$this->name][$class];
  124.             }
  125.         }
  126.         $objectClass $config['useObjectClass'] ?? $class;
  127.         if ($objectClass !== $class) {
  128.             $this->getConfiguration($objectManager$objectClass);
  129.         }
  130.         return $config;
  131.     }
  132.     /**
  133.      * Get extended metadata mapping reader
  134.      *
  135.      * @return ExtensionMetadataFactory
  136.      */
  137.     public function getExtensionMetadataFactory(ObjectManager $objectManager)
  138.     {
  139.         $oid spl_object_id($objectManager);
  140.         if (!isset($this->extensionMetadataFactory[$oid])) {
  141.             if (false === $this->annotationReader) {
  142.                 // create default annotation/attribute reader for extensions
  143.                 $this->annotationReader $this->getDefaultAnnotationReader();
  144.             }
  145.             $this->extensionMetadataFactory[$oid] = new ExtensionMetadataFactory(
  146.                 $objectManager,
  147.                 $this->getNamespace(),
  148.                 $this->annotationReader,
  149.                 $this->getCacheItemPool($objectManager)
  150.             );
  151.         }
  152.         return $this->extensionMetadataFactory[$oid];
  153.     }
  154.     /**
  155.      * Set the annotation reader instance
  156.      *
  157.      * When originally implemented, `Doctrine\Common\Annotations\Reader` was not available,
  158.      * therefore this method may accept any object implementing these methods from the interface:
  159.      *
  160.      *     getClassAnnotations([reflectionClass])
  161.      *     getClassAnnotation([reflectionClass], [name])
  162.      *     getPropertyAnnotations([reflectionProperty])
  163.      *     getPropertyAnnotation([reflectionProperty], [name])
  164.      *
  165.      * @param Reader|AttributeReader|object $reader
  166.      *
  167.      * @return void
  168.      *
  169.      * @note Providing any object is deprecated, as of 4.0 an {@see AttributeReader} will be required
  170.      */
  171.     public function setAnnotationReader($reader)
  172.     {
  173.         if ($reader instanceof Reader) {
  174.             Deprecation::trigger(
  175.                 'gedmo/doctrine-extensions',
  176.                 'https://github.com/doctrine-extensions/DoctrineExtensions/pull/2772',
  177.                 'Annotations support is deprecated, migrate your application to use attributes and pass an instance of %s to the %s() method instead.',
  178.                 AttributeReader::class,
  179.                 __METHOD__
  180.             );
  181.         } elseif (!$reader instanceof AttributeReader) {
  182.             Deprecation::trigger(
  183.                 'gedmo/doctrine-extensions',
  184.                 'https://github.com/doctrine-extensions/DoctrineExtensions/pull/2558',
  185.                 'Providing an annotation reader which does not implement %s or is not an instance of %s to %s() is deprecated.',
  186.                 Reader::class,
  187.                 AttributeReader::class,
  188.                 __METHOD__
  189.             );
  190.         }
  191.         $this->annotationReader $reader;
  192.     }
  193.     final public function setCacheItemPool(CacheItemPoolInterface $cacheItemPool): void
  194.     {
  195.         $this->cacheItemPool $cacheItemPool;
  196.     }
  197.     final public function setClock(ClockInterface $clock): void
  198.     {
  199.         $this->clock $clock;
  200.     }
  201.     /**
  202.      * Scans the objects for extended annotations
  203.      * event subscribers must subscribe to loadClassMetadata event
  204.      *
  205.      * @param ClassMetadata<object> $metadata
  206.      *
  207.      * @return void
  208.      */
  209.     public function loadMetadataForObjectClass(ObjectManager $objectManager$metadata)
  210.     {
  211.         assert($metadata instanceof DocumentClassMetadata || $metadata instanceof EntityClassMetadata || $metadata instanceof LegacyEntityClassMetadata);
  212.         $factory $this->getExtensionMetadataFactory($objectManager);
  213.         try {
  214.             $config $factory->getExtensionMetadata($metadata);
  215.         } catch (\ReflectionException $e) {
  216.             // entity\document generator is running
  217.             $config = []; // will not store a cached version, to remap later
  218.         }
  219.         if ([] !== $config) {
  220.             self::$configurations[$this->name][$metadata->getName()] = $config;
  221.         }
  222.     }
  223.     /**
  224.      * Get an event adapter to handle event specific
  225.      * methods
  226.      *
  227.      * @throws InvalidArgumentException if event is not recognized
  228.      *
  229.      * @return AdapterInterface
  230.      *
  231.      * @phpstan-return TEventAdapter
  232.      */
  233.     protected function getEventAdapter(EventArgs $args)
  234.     {
  235.         $class get_class($args);
  236.         if (preg_match('@Doctrine\\\([^\\\]+)@'$class$m) && in_array($m[1], ['ODM''ORM'], true)) {
  237.             if (!isset($this->adapters[$m[1]])) {
  238.                 $adapterClass $this->getNamespace().'\\Mapping\\Event\\Adapter\\'.$m[1];
  239.                 if (!\class_exists($adapterClass)) {
  240.                     $adapterClass 'Gedmo\\Mapping\\Event\\Adapter\\'.$m[1];
  241.                 }
  242.                 $this->adapters[$m[1]] = new $adapterClass();
  243.                 if ($this->adapters[$m[1]] instanceof ClockAwareAdapterInterface && $this->clock instanceof ClockInterface) {
  244.                     $this->adapters[$m[1]]->setClock($this->clock);
  245.                 }
  246.             }
  247.             $this->adapters[$m[1]]->setEventArgs($args);
  248.             return $this->adapters[$m[1]];
  249.         }
  250.         throw new InvalidArgumentException('Event mapper does not support event arg class: '.$class);
  251.     }
  252.     /**
  253.      * Get the namespace of extension event subscriber.
  254.      * used for cache id of extensions also to know where
  255.      * to find Mapping drivers and event adapters
  256.      *
  257.      * @return string
  258.      */
  259.     abstract protected function getNamespace();
  260.     /**
  261.      * Sets the value for a mapped field
  262.      *
  263.      * @param object $object
  264.      * @param string $field
  265.      * @param mixed  $oldValue
  266.      * @param mixed  $newValue
  267.      *
  268.      * @return void
  269.      */
  270.     protected function setFieldValue(AdapterInterface $adapter$object$field$oldValue$newValue)
  271.     {
  272.         $manager $adapter->getObjectManager();
  273.         $meta $manager->getClassMetadata(get_class($object));
  274.         $uow $manager->getUnitOfWork();
  275.         $meta->getReflectionProperty($field)->setValue($object$newValue);
  276.         $uow->propertyChanged($object$field$oldValue$newValue);
  277.         $adapter->recomputeSingleObjectChangeSet($uow$meta$object);
  278.     }
  279.     /**
  280.      * Get the default annotation or attribute reader for extensions, creating it if necessary.
  281.      *
  282.      * 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,
  283.      * and the {@see ExtensionMetadataFactory} can handle raising an error if it tries to create a mapping driver that requires this reader.
  284.      *
  285.      * @return Reader|AttributeReader|null
  286.      */
  287.     private function getDefaultAnnotationReader()
  288.     {
  289.         if (false === self::$defaultAnnotationReader) {
  290.             if (class_exists(PsrCachedReader::class)) {
  291.                 self::$defaultAnnotationReader = new PsrCachedReader(new AnnotationReader(), new ArrayAdapter());
  292.             } elseif (\PHP_VERSION_ID >= 80000) {
  293.                 self::$defaultAnnotationReader = new AttributeReader();
  294.             } else {
  295.                 self::$defaultAnnotationReader null;
  296.             }
  297.         }
  298.         return self::$defaultAnnotationReader;
  299.     }
  300.     private function getCacheItemPool(ObjectManager $objectManager): CacheItemPoolInterface
  301.     {
  302.         if (null !== $this->cacheItemPool) {
  303.             return $this->cacheItemPool;
  304.         }
  305.         // TODO: The user should configure its own cache, we are using the one from Doctrine for BC. We should deprecate using
  306.         // the one from Doctrine when the bundle offers an easy way to configure this cache, otherwise users using the bundle
  307.         // will see lots of deprecations without an easy way to avoid them.
  308.         if ($objectManager instanceof EntityManagerInterface || $objectManager instanceof DocumentManager) {
  309.             $metadataFactory $objectManager->getMetadataFactory();
  310.             $getCache \Closure::bind(static fn (AbstractClassMetadataFactory $metadataFactory): ?CacheItemPoolInterface => $metadataFactory->getCache(), null\get_class($metadataFactory));
  311.             $metadataCache $getCache($metadataFactory);
  312.             if (null !== $metadataCache) {
  313.                 $this->cacheItemPool $metadataCache;
  314.                 return $this->cacheItemPool;
  315.             }
  316.         }
  317.         $this->cacheItemPool = new ArrayAdapter();
  318.         return $this->cacheItemPool;
  319.     }
  320. }