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

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