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

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.         $sourcePath is_dir($metadata['path'].'/src') ? $metadata['path'].'/src' $metadata['path'];
  177.         $directory $sourcePath.DIRECTORY_SEPARATOR.'Document'.DIRECTORY_SEPARATOR.'Areabrick';
  178.         // update cache when directory is added/removed
  179.         $container->addResource(new FileExistenceResource($directory));
  180.         if (!is_dir($directory)) {
  181.             return [];
  182.         }
  183.         // update container cache when areabricks are added/changed
  184.         $container->addResource(new DirectoryResource($directory'/\.php$/'));
  185.         $finder = new Finder();
  186.         $finder
  187.             ->files()
  188.             ->in($directory)
  189.             ->name('*.php');
  190.         $areas = [];
  191.         foreach ($finder as $classPath) {
  192.             $shortClassName $classPath->getBasename('.php');
  193.             // relative path in bundle path
  194.             $relativePath str_replace($sourcePath''$classPath->getPathInfo());
  195.             $relativePath trim($relativePathDIRECTORY_SEPARATOR);
  196.             // namespace starting from bundle path
  197.             $relativeNamespace str_replace(DIRECTORY_SEPARATOR'\\'$relativePath);
  198.             // sub-namespace in Document\Areabrick
  199.             $subNamespace str_replace('Document\\Areabrick'''$relativeNamespace);
  200.             $subNamespace trim($subNamespace'\\');
  201.             // fully qualified class name
  202.             $className $metadata['namespace'] . '\\' $relativeNamespace '\\' $shortClassName;
  203.             // do not autoload areas which were already defined as service via config
  204.             if (in_array($className$excludedClasses)) {
  205.                 continue;
  206.             }
  207.             if (class_exists($className)) {
  208.                 $reflector = new \ReflectionClass($className);
  209.                 if ($reflector->isInstantiable() && $reflector->implementsInterface(AreabrickInterface::class)) {
  210.                     $brickId $this->generateBrickId($reflector);
  211.                     $serviceId $this->generateServiceId($name$subNamespace$shortClassName);
  212.                     $areas[] = [
  213.                         'brickId' => $brickId,
  214.                         'serviceId' => $serviceId,
  215.                         'reflector' => $reflector,
  216.                     ];
  217.                 }
  218.             }
  219.         }
  220.         return $areas;
  221.     }
  222.     /**
  223.      * GalleryTeaserRow -> gallery-teaser-row
  224.      *
  225.      * @param \ReflectionClass $reflector
  226.      *
  227.      * @return string
  228.      */
  229.     protected function generateBrickId(\ReflectionClass $reflector)
  230.     {
  231.         $id $this->inflector->tableize($reflector->getShortName());
  232.         $id str_replace('_''-'$id);
  233.         return $id;
  234.     }
  235.     /**
  236.      * Generate service ID from bundle name and sub-namespace
  237.      *
  238.      *  - MyBundle\Document\Areabrick\Foo         -> my.area.brick.foo
  239.      *  - MyBundle\Document\Areabrick\Foo\Bar\Baz -> my.area.brick.foo.bar.baz
  240.      *
  241.      * @param string $bundleName
  242.      * @param string $subNamespace
  243.      * @param string $className
  244.      *
  245.      * @return string
  246.      */
  247.     protected function generateServiceId($bundleName$subNamespace$className)
  248.     {
  249.         $bundleName str_replace('Bundle'''$bundleName);
  250.         $bundleName $this->inflector->tableize($bundleName);
  251.         if (!empty($subNamespace)) {
  252.             $subNamespaceParts = [];
  253.             foreach (explode('\\'$subNamespace) as $subNamespacePart) {
  254.                 $subNamespaceParts[] = $this->inflector->tableize($subNamespacePart);
  255.             }
  256.             $subNamespace implode('.'$subNamespaceParts) . '.';
  257.         } else {
  258.             $subNamespace '';
  259.         }
  260.         $brickName $this->inflector->tableize($className);
  261.         return sprintf('%s.area.brick.%s%s'$bundleName$subNamespace$brickName);
  262.     }
  263. }