vendor/pimcore/pimcore/bundles/CoreBundle/DependencyInjection/Compiler/AreabrickPass.php line 251

Open in your IDE?
  1. <?php
  2. /**
  3.  * Pimcore
  4.  *
  5.  * This source file is available under two different licenses:
  6.  * - GNU General Public License version 3 (GPLv3)
  7.  * - Pimcore Commercial License (PCL)
  8.  * Full copyright and license information is available in
  9.  * LICENSE.md which is distributed with this source code.
  10.  *
  11.  *  @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  12.  *  @license    http://www.pimcore.org/license     GPLv3 and PCL
  13.  */
  14. namespace Pimcore\Bundle\CoreBundle\DependencyInjection\Compiler;
  15. use Doctrine\Inflector\Inflector;
  16. use Doctrine\Inflector\InflectorFactory;
  17. use Pimcore\Extension\Document\Areabrick\AreabrickInterface;
  18. use Pimcore\Extension\Document\Areabrick\AreabrickManager;
  19. use Pimcore\Extension\Document\Areabrick\Exception\ConfigurationException;
  20. use Pimcore\Templating\Renderer\EditableRenderer;
  21. use Symfony\Component\Config\Resource\DirectoryResource;
  22. use Symfony\Component\Config\Resource\FileExistenceResource;
  23. use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
  24. use Symfony\Component\DependencyInjection\ContainerAwareInterface;
  25. use Symfony\Component\DependencyInjection\ContainerBuilder;
  26. use Symfony\Component\DependencyInjection\Definition;
  27. use Symfony\Component\DependencyInjection\Reference;
  28. use Symfony\Component\Finder\Finder;
  29. /**
  30.  * @internal
  31.  */
  32. final class AreabrickPass implements CompilerPassInterface
  33. {
  34.     /**
  35.      * @var Inflector
  36.      */
  37.     private $inflector;
  38.     public function __construct()
  39.     {
  40.         $this->inflector InflectorFactory::create()->build();
  41.     }
  42.     /**
  43.      * {@inheritdoc}
  44.      */
  45.     public function process(ContainerBuilder $container)
  46.     {
  47.         $config $container->getParameter('pimcore.config');
  48.         $areabrickManager $container->getDefinition(AreabrickManager::class);
  49.         $areabrickLocator $container->getDefinition('pimcore.document.areabrick.brick_locator');
  50.         $taggedServices $container->findTaggedServiceIds('pimcore.area.brick');
  51.         // keep a list of areas loaded via tags - those classes won't be autoloaded
  52.         $taggedAreas = [];
  53.         // the service mapping for the service locator
  54.         $locatorMapping = [];
  55.         foreach ($taggedServices as $id => $tags) {
  56.             $definition $container->getDefinition($id);
  57.             $taggedAreas[] = $definition->getClass();
  58.             // tags must define the id attribute which will be used to register the brick
  59.             // e.g. { name: pimcore.area.brick, id: blockquote }
  60.             foreach ($tags as $tag) {
  61.                 if (!array_key_exists('id'$tag)) {
  62.                     throw new ConfigurationException(sprintf('Missing "id" attribute on areabrick DI tag for service %s'$id));
  63.                 }
  64.                 // add the service to the locator
  65.                 $locatorMapping[$tag['id']] = new Reference($id);
  66.                 // register the brick with its ID on the areabrick manager
  67.                 $areabrickManager->addMethodCall('registerService', [$tag['id'], $id]);
  68.             }
  69.             // handle bricks implementing ContainerAwareInterface
  70.             $this->handleContainerAwareDefinition($container$definition);
  71.             $this->handleEditableRendererCall($definition);
  72.         }
  73.         // autoload areas from bundles if not yet defined via service config
  74.         if ($config['documents']['areas']['autoload']) {
  75.             $locatorMapping $this->autoloadAreabricks($container$areabrickManager$locatorMapping$taggedAreas);
  76.         }
  77.         $areabrickLocator->setArgument(0$locatorMapping);
  78.     }
  79.     /**
  80.      * To be autoloaded, an area must fulfill the following conditions:
  81.      *
  82.      *  - implement AreabrickInterface
  83.      *  - be situated in a bundle in the sub-namespace Document\Areabrick (can be nested into a deeper namespace)
  84.      *  - the class is not already defined as areabrick through manual config (not included in the tagged results above)
  85.      *
  86.      * Valid examples:
  87.      *
  88.      *  - MyBundle\Document\Areabrick\Foo
  89.      *  - MyBundle\Document\Areabrick\Foo\Bar\Baz
  90.      *
  91.      * @param ContainerBuilder $container
  92.      * @param Definition $areaManagerDefinition
  93.      * @param array $locatorMapping
  94.      * @param array $excludedClasses
  95.      *
  96.      * @return array
  97.      */
  98.     protected function autoloadAreabricks(
  99.         ContainerBuilder $container,
  100.         Definition $areaManagerDefinition,
  101.         array $locatorMapping,
  102.         array $excludedClasses
  103.     ) {
  104.         $bundles $container->getParameter('kernel.bundles_metadata');
  105.         //Find bricks from /src since AppBundle is removed
  106.         $bundles['App'] = [
  107.             'path' => PIMCORE_PROJECT_ROOT '/src',
  108.             'namespace' => 'App',
  109.         ];
  110.         foreach ($bundles as $bundleName => $bundleMetadata) {
  111.             $bundleAreas $this->findBundleBricks($container$bundleName$bundleMetadata$excludedClasses);
  112.             foreach ($bundleAreas as $bundleArea) {
  113.                 /** @var \ReflectionClass $reflector */
  114.                 $reflector $bundleArea['reflector'];
  115.                 $definition = new Definition($reflector->getName());
  116.                 $definition
  117.                     ->setPublic(false)
  118.                     ->setAutowired(true)
  119.                     ->setAutoconfigured(true);
  120.                 // add brick definition to container
  121.                 $container->setDefinition($bundleArea['serviceId'], $definition);
  122.                 // add the service to the locator
  123.                 $locatorMapping[$bundleArea['brickId']] = new Reference($bundleArea['serviceId']);
  124.                 // register brick on the areabrick manager
  125.                 $areaManagerDefinition->addMethodCall('registerService', [
  126.                     $bundleArea['brickId'],
  127.                     $bundleArea['serviceId'],
  128.                 ]);
  129.                 // handle bricks implementing ContainerAwareInterface
  130.                 $this->handleContainerAwareDefinition($container$definition$reflector);
  131.                 $this->handleEditableRendererCall($definition);
  132.             }
  133.         }
  134.         return $locatorMapping;
  135.     }
  136.     /**
  137.      * @param Definition $definition
  138.      *
  139.      * @throws \ReflectionException
  140.      */
  141.     private function handleEditableRendererCall(Definition $definition)
  142.     {
  143.         $reflector = new \ReflectionClass($definition->getClass());
  144.         if ($reflector->hasMethod('setEditableRenderer')) {
  145.             $definition->addMethodCall('setEditableRenderer', [new Reference(EditableRenderer::class)]);
  146.         }
  147.     }
  148.     /**
  149.      * Adds setContainer() call to bricks implementing ContainerAwareInterface
  150.      *
  151.      * @param ContainerBuilder $container
  152.      * @param Definition $definition
  153.      * @param \ReflectionClass|null $reflector
  154.      */
  155.     protected function handleContainerAwareDefinition(ContainerBuilder $containerDefinition $definition\ReflectionClass $reflector null)
  156.     {
  157.         if (null === $reflector) {
  158.             $reflector = new \ReflectionClass($definition->getClass());
  159.         }
  160.         if ($reflector->implementsInterface(ContainerAwareInterface::class)) {
  161.             $definition->addMethodCall('setContainer', [new Reference('service_container')]);
  162.         }
  163.     }
  164.     /**
  165.      * Look for classes implementing AreabrickInterface in each bundle's Document\Areabrick sub-namespace
  166.      *
  167.      * @param ContainerBuilder $container
  168.      * @param string $name
  169.      * @param array $metadata
  170.      * @param array $excludedClasses
  171.      *
  172.      * @return array
  173.      */
  174.     protected function findBundleBricks(ContainerBuilder $containerstring $name, array $metadata, array $excludedClasses = []): array
  175.     {
  176.         $directory implode(DIRECTORY_SEPARATOR, [
  177.             $metadata['path'],
  178.             'Document',
  179.             'Areabrick',
  180.         ]);
  181.         // update cache when directory is added/removed
  182.         $container->addResource(new FileExistenceResource($directory));
  183.         if (!file_exists($directory) || !is_dir($directory)) {
  184.             return [];
  185.         } else {
  186.             // update container cache when areabricks are added/changed
  187.             $container->addResource(new DirectoryResource($directory'/\.php$/'));
  188.         }
  189.         $finder = new Finder();
  190.         $finder
  191.             ->files()
  192.             ->in($directory)
  193.             ->name('*.php');
  194.         $areas = [];
  195.         foreach ($finder as $classPath) {
  196.             $shortClassName $classPath->getBasename('.php');
  197.             // relative path in bundle path
  198.             $relativePath str_replace($metadata['path'], ''$classPath->getPathInfo());
  199.             $relativePath trim($relativePathDIRECTORY_SEPARATOR);
  200.             // namespace starting from bundle path
  201.             $relativeNamespace str_replace(DIRECTORY_SEPARATOR'\\'$relativePath);
  202.             // sub-namespace in Document\Areabrick
  203.             $subNamespace str_replace('Document\\Areabrick'''$relativeNamespace);
  204.             $subNamespace trim($subNamespace'\\');
  205.             // fully qualified class name
  206.             $className $metadata['namespace'] . '\\' $relativeNamespace '\\' $shortClassName;
  207.             // do not autoload areas which were already defined as service via config
  208.             if (in_array($className$excludedClasses)) {
  209.                 continue;
  210.             }
  211.             if (class_exists($className)) {
  212.                 $reflector = new \ReflectionClass($className);
  213.                 if ($reflector->isInstantiable() && $reflector->implementsInterface(AreabrickInterface::class)) {
  214.                     $brickId $this->generateBrickId($reflector);
  215.                     $serviceId $this->generateServiceId($name$subNamespace$shortClassName);
  216.                     $areas[] = [
  217.                         'brickId' => $brickId,
  218.                         'serviceId' => $serviceId,
  219.                         'bundleName' => $name,
  220.                         'bundleMetadata' => $metadata,
  221.                         'reflector' => $reflector,
  222.                     ];
  223.                 }
  224.             }
  225.         }
  226.         return $areas;
  227.     }
  228.     /**
  229.      * GalleryTeaserRow -> gallery-teaser-row
  230.      *
  231.      * @param \ReflectionClass $reflector
  232.      *
  233.      * @return string
  234.      */
  235.     protected function generateBrickId(\ReflectionClass $reflector)
  236.     {
  237.         $id $this->inflector->tableize($reflector->getShortName());
  238.         $id str_replace('_''-'$id);
  239.         return $id;
  240.     }
  241.     /**
  242.      * Generate service ID from bundle name and sub-namespace
  243.      *
  244.      *  - MyBundle\Document\Areabrick\Foo         -> my.area.brick.foo
  245.      *  - MyBundle\Document\Areabrick\Foo\Bar\Baz -> my.area.brick.foo.bar.baz
  246.      *
  247.      * @param string $bundleName
  248.      * @param string $subNamespace
  249.      * @param string $className
  250.      *
  251.      * @return string
  252.      */
  253.     protected function generateServiceId($bundleName$subNamespace$className)
  254.     {
  255.         $bundleName str_replace('Bundle'''$bundleName);
  256.         $bundleName $this->inflector->tableize($bundleName);
  257.         if (!empty($subNamespace)) {
  258.             $subNamespaceParts = [];
  259.             foreach (explode('\\'$subNamespace) as $subNamespacePart) {
  260.                 $subNamespaceParts[] = $this->inflector->tableize($subNamespacePart);
  261.             }
  262.             $subNamespace implode('.'$subNamespaceParts) . '.';
  263.         } else {
  264.             $subNamespace '';
  265.         }
  266.         $brickName $this->inflector->tableize($className);
  267.         return sprintf('%s.area.brick.%s%s'$bundleName$subNamespace$brickName);
  268.     }
  269. }