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 $container, Definition $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 $container, string $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($relativePath, DIRECTORY_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. }