vendor/zenstruck/twig-service-bundle/src/DependencyInjection/ZenstruckTwigServiceExtension.php line 91

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the zenstruck/twig-service-bundle package.
  4.  *
  5.  * (c) Kevin Bond <kevinbond@gmail.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Zenstruck\Twig\DependencyInjection;
  11. use Symfony\Component\Config\Definition\Builder\TreeBuilder;
  12. use Symfony\Component\Config\Definition\ConfigurationInterface;
  13. use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
  14. use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
  15. use Symfony\Component\DependencyInjection\ChildDefinition;
  16. use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
  17. use Symfony\Component\DependencyInjection\ContainerBuilder;
  18. use Symfony\Component\DependencyInjection\Exception\LogicException;
  19. use Symfony\Component\DependencyInjection\Reference;
  20. use Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension;
  21. use Zenstruck\Twig\AsTwigFunction;
  22. use Zenstruck\Twig\AsTwigService;
  23. use Zenstruck\Twig\Command\ListCommand;
  24. use Zenstruck\Twig\Service\InvokableCallable;
  25. use Zenstruck\Twig\Service\InvokableServiceMethod;
  26. use Zenstruck\Twig\Service\TwigFunctionRuntime;
  27. use Zenstruck\Twig\Service\TwigServiceExtension;
  28. use Zenstruck\Twig\Service\TwigServiceRuntime;
  29. /**
  30.  * @author Kevin Bond <kevinbond@gmail.com>
  31.  *
  32.  * @internal
  33.  *
  34.  * @phpstan-type Function = callable-string|(array{0:class-string,1:string}&callable)|array{0:string,1:string}
  35.  */
  36. final class ZenstruckTwigServiceExtension extends ConfigurableExtension implements ConfigurationInterfaceCompilerPassInterface
  37. {
  38.     public function getConfigTreeBuilder(): TreeBuilder
  39.     {
  40.         $builder = new TreeBuilder('zenstruck_twig_service');
  41.         $builder->getRootNode() // @phpstan-ignore-line
  42.             ->children()
  43.                 ->arrayNode('functions')
  44.                     ->info('Callables to make available with fn() twig function/filter')
  45.                     ->example(['strlen''alias' => ['Some\Class''somePublicStaticMethod']])
  46.                     ->variablePrototype()
  47.                         ->validate()
  48.                             ->ifTrue(fn($v) => \is_object($v) || \is_object($v[0] ?? null))
  49.                             ->thenInvalid('Callable objects are not supported.')
  50.                         ->end()
  51.                     ->end()
  52.                 ->end()
  53.             ->end()
  54.         ;
  55.         return $builder;
  56.     }
  57.     public function process(ContainerBuilder $container): void
  58.     {
  59.         $container->getDefinition('parameter_bag')
  60.             ->addTag('twig.service', ['alias' => TwigServiceRuntime::PARAMETER_BAG])
  61.         ;
  62.         /** @var array<string,Function> $configuredFunctions */
  63.         $configuredFunctions $container->getParameter('zenstruck_twig_service.functions');
  64.         $container->getParameterBag()->remove('zenstruck_twig_service.functions');
  65.         $functions = [];
  66.         foreach ($configuredFunctions as $alias => $value) {
  67.             $functions self::addToFunctions($functions$alias$value);
  68.         }
  69.         foreach ($container->getDefinitions() as $id => $definition) {
  70.             if ($definition->hasTag('container.excluded') || $definition->isAbstract() || $definition->isSynthetic()) {
  71.                 continue;
  72.             }
  73.             if (!$class $definition->getClass()) {
  74.                 continue;
  75.             }
  76.             try {
  77.                 if (!\class_exists($class)) {
  78.                     continue;
  79.                 }
  80.             } catch (\Throwable) {
  81.                 continue;
  82.             }
  83.             foreach ((new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
  84.                 if (!$attribute $method->getAttributes(AsTwigFunction::class)[0] ?? null) {
  85.                     continue;
  86.                 }
  87.                 $alias $attribute->newInstance()->alias ?? $method->name;
  88.                 $functions self::addToFunctions(
  89.                     $functions,
  90.                     $alias,
  91.                     [$method->isStatic() ? $class $id$method->name]
  92.                 );
  93.             }
  94.         }
  95.         foreach (\get_defined_functions()['user'] as $function) {
  96.             $ref = new \ReflectionFunction($function);
  97.             if (!$attribute $ref->getAttributes(AsTwigFunction::class)[0] ?? null) {
  98.                 continue;
  99.             }
  100.             $alias $attribute->newInstance()->alias ?? $ref->getShortName();
  101.             $functions self::addToFunctions($functions$alias$ref->name); // @phpstan-ignore-line
  102.         }
  103.         foreach ($functions as $alias => $function) {
  104.             $definition $container->register('.zenstruck_twig_service.function.'.$alias)
  105.                 ->addTag('twig.function', ['alias' => $alias])
  106.             ;
  107.             if (\is_callable($function)) {
  108.                 $definition->setClass(InvokableCallable::class)->addArgument($function);
  109.                 continue;
  110.             }
  111.             $definition
  112.                 ->setClass(InvokableServiceMethod::class)
  113.                 ->setArguments([new Reference($function[0]), $function[1]])
  114.             ;
  115.         }
  116.     }
  117.     public function getConfiguration(array $configContainerBuilder $container): ConfigurationInterface // @phpstan-ignore-line
  118.     {
  119.         return $this;
  120.     }
  121.     protected function loadInternal(array $mergedConfigContainerBuilder $container): void // @phpstan-ignore-line
  122.     {
  123.         $functions = [];
  124.         foreach ($mergedConfig['functions'] as $key => $value) {
  125.             if (!\is_numeric($key)) {
  126.                 $functions[$key] = $value;
  127.                 continue;
  128.             }
  129.             if (\is_string($value) && \is_callable($value)) {
  130.                 $functions[(new \ReflectionFunction($value))->getShortName()] = $value;
  131.                 continue;
  132.             }
  133.             $functions[$value[1] ?? throw new \LogicException('Invalid callable.')] = $value;
  134.         }
  135.         $container->setParameter('zenstruck_twig_service.functions'$functions);
  136.         $container->registerAttributeForAutoconfiguration(
  137.             AsTwigService::class,
  138.             static function(ChildDefinition $definitionAsTwigService $attribute) {
  139.                 $definition->addTag('twig.service', ['alias' => $attribute->alias]);
  140.             }
  141.         );
  142.         $container->register('.zenstruck.twig.service_extension'TwigServiceExtension::class)
  143.             ->addTag('twig.extension')
  144.         ;
  145.         $container->register('.zenstruck.twig.service_runtime'TwigServiceRuntime::class)
  146.             ->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('twig.service''alias'needsIndexestrue)))
  147.             ->addTag('twig.runtime')
  148.         ;
  149.         $container->register('.zenstruck.twig.function_runtime'TwigFunctionRuntime::class)
  150.             ->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('twig.function''alias'needsIndexestrue)))
  151.             ->addTag('twig.runtime')
  152.         ;
  153.         if ($container->getParameter('kernel.debug')) {
  154.             $container->register('.zenstruck.twig.list_command'ListCommand::class)
  155.                 ->setArguments([
  156.                     new Reference('.zenstruck.twig.service_runtime'), new Reference('.zenstruck.twig.function_runtime'),
  157.                 ])
  158.                 ->addTag('console.command')
  159.             ;
  160.         }
  161.     }
  162.     /**
  163.      * @param array<string,Function> $functions
  164.      * @param Function               $what
  165.      *
  166.      * @return array<string,Function>
  167.      */
  168.     private static function addToFunctions(array $functionsstring $aliasstring|array $what): array
  169.     {
  170.         if (isset($functions[$alias])) {
  171.             throw new LogicException(\sprintf('The function alias "%s" is already being used for "%s".'$alias\is_string($functions[$alias]) ? $functions[$alias] : \implode('::'$functions[$alias])));
  172.         }
  173.         $functions[$alias] = $what;
  174.         return $functions;
  175.     }
  176. }