initial commit

This commit is contained in:
boris
2025-09-30 09:24:25 +01:00
committed by boris
parent a783a12c97
commit c7770ea03b
4695 changed files with 525784 additions and 0 deletions

144
vendor/symfony/twig-bundle/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,144 @@
CHANGELOG
=========
7.3
---
* Enable `#[AsTwigFilter]`, `#[AsTwigFunction]` and `#[AsTwigTest]` attributes
to configure extensions on runtime classes
* Add support for a `twig` validator
* Use `ChainCache` to store warmed-up cache in `kernel.build_dir` and runtime cache in `kernel.cache_dir`
* Make `TemplateCacheWarmer` use `kernel.build_dir` instead of `kernel.cache_dir`
7.1
---
* Mark class `TemplateCacheWarmer` as `final`
7.0
---
* Remove the `Twig_Environment` autowiring alias, use `Twig\Environment` instead
* Remove option `twig.autoescape`; create a class that implements your escaping strategy
(check `FileExtensionEscapingStrategy::guess()` for inspiration) and reference it using
the `twig.autoescape_service` option instead
* Drop support for Twig 2
6.4
---
* Allow omitting the `autoescape_service_method` option when `autoescape_service` is set to an invokable service id
6.3
---
* Deprecate the `Twig_Environment` autowiring alias, use `Twig\Environment` instead
6.2
---
* Add the `twig.mailer.html_to_text_converter` option to allow configuring custom `HtmlToTextConverterInterface`
implementations to be used by the `twig.mime_body_renderer` service
6.1
---
* Add option `twig.file_name_pattern` to restrict which files are compiled by cache warmer and linter
* Deprecate option `twig.autoescape`, use `twig.autoescape_service[_method]` instead
6.0
---
* The `twig` service is now private
5.3
---
* Add support for the new `serialize` filter (from Twig Bridge)
5.2.0
-----
* deprecated the public `twig` service to private
5.0.0
-----
* updated default value for the `strict_variables` option to `%kernel.debug%` parameter
* removed support to load templates from the legacy directories `src/Resources/views/` and `src/Resources/<BundleName>/views/`
* removed `TwigEngine` class, use `Twig\Environment` instead
* removed `FilesystemLoader` and `NativeFilesystemLoader`, use Twig notation for templates instead
* removed `twig.exception_controller` configuration option, use `framework.error_controller` option instead
* removed `ExceptionController`, `PreviewErrorController` and all built-in error templates in favor of the new error renderer mechanism
4.4.0
-----
* marked the `TemplateIterator` as `internal`
* added HTML comment to beginning and end of `exception_full.html.twig`
* deprecated `ExceptionController` and `PreviewErrorController` controllers, use `ErrorController` from the `HttpKernel` component instead
* deprecated all built-in error templates in favor of the new error renderer mechanism
* deprecated `twig.exception_controller` configuration option, set it to "null" and use `framework.error_controller` configuration instead
4.2.0
-----
* deprecated support for legacy templates directories `src/Resources/views/` and `src/Resources/<BundleName>/views/`, use `templates/` and `templates/bundles/<BundleName>/` instead.
4.1.0
-----
* added priority to Twig extensions
* deprecated relying on the default value (`false`) of the `twig.strict_variables` configuration option. The `%kernel.debug%` parameter will be the new default in 5.0
4.0.0
-----
* removed `ContainerAwareRuntimeLoader`
3.4.0
-----
* added exclusive Twig namespace only for root bundles
* deprecated `Symfony\Bundle\TwigBundle\Command\DebugCommand`, use `Symfony\Bridge\Twig\Command\DebugCommand` instead
* deprecated relying on the `ContainerAwareInterface` implementation for `Symfony\Bundle\TwigBundle\Command\LintCommand`
* added option to configure default path templates (via `default_path`)
3.3.0
-----
* Deprecated `ContainerAwareRuntimeLoader`
2.7.0
-----
* made it possible to configure the default formats for both the `date` and the `number_format` filter
* added support for the new Asset component (from Twig bridge)
* deprecated the assets extension (use the one from the Twig bridge instead)
2.6.0
-----
* [BC BREAK] changed exception.json.twig to match same structure as error.json.twig making clients independent of runtime environment.
2.3.0
-----
* added option to configure a custom template escaping guesser (via `autoescape_service` and `autoescape_service_method`)
2.2.0
-----
* moved the exception controller to be a service (`twig.controller.exception:showAction` vs `Symfony\\Bundle\\TwigBundle\\Controller\\ExceptionController::showAction`)
* added support for multiple loaders via the "twig.loader" tag.
* added automatic registration of namespaced paths for registered bundles
* added support for namespaced paths
2.1.0
-----
* added a new setting ("paths") to configure more paths for the Twig filesystem loader
* added contextual escaping based on the template file name (disabled if you explicitly pass an autoescape option)
* added a command that extracts translation messages from templates
* added the real template name when an error occurs in a Twig template
* added the twig:lint command that will validate a Twig template syntax.

View File

@@ -0,0 +1,97 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\TwigBundle\CacheWarmer;
use Psr\Container\ContainerInterface;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
use Twig\Cache\CacheInterface;
use Twig\Cache\NullCache;
use Twig\Environment;
use Twig\Error\Error;
/**
* Generates the Twig cache for all templates.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final since Symfony 7.1
*/
class TemplateCacheWarmer implements CacheWarmerInterface, ServiceSubscriberInterface
{
private Environment $twig;
/**
* As this cache warmer is optional, dependencies should be lazy-loaded, that's why a container should be injected.
*/
public function __construct(
private ContainerInterface $container,
private iterable $iterator,
private ?CacheInterface $cache = null,
) {
}
public function warmUp(string $cacheDir, ?string $buildDir = null): array
{
$this->twig ??= $this->container->get('twig');
$originalCache = $this->twig->getCache();
if ($originalCache instanceof NullCache) {
// There's no point to warm up a cache that won't be used afterward
return [];
}
if (null !== $this->cache) {
if (!$buildDir) {
/*
* The cache has already been warmup during the build of the container, when $buildDir was set.
*/
return [];
}
// Swap the cache for the warmup as the Twig Environment has the ChainCache injected
$this->twig->setCache($this->cache);
}
try {
foreach ($this->iterator as $template) {
try {
$this->twig->load($template);
} catch (Error) {
/*
* Problem during compilation, give up for this template (e.g. syntax errors).
* Failing silently here allows to ignore templates that rely on functions that aren't available in
* the current environment. For example, the WebProfilerBundle shouldn't be available in the prod
* environment, but some templates that are never used in prod might rely on functions the bundle provides.
* As we can't detect which templates are "really" important, we try to load all of them and ignore
* errors. Error checks may be performed by calling the lint:twig command.
*/
}
}
} finally {
$this->twig->setCache($originalCache);
}
return [];
}
public function isOptional(): bool
{
return true;
}
public static function getSubscribedServices(): array
{
return [
'twig' => Environment::class,
];
}
}

View File

@@ -0,0 +1,51 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\TwigBundle\Command;
use Symfony\Bridge\Twig\Command\LintCommand as BaseLintCommand;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* Command that will validate your template syntax and output encountered errors.
*
* @author Marc Weistroff <marc.weistroff@sensiolabs.com>
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*/
#[AsCommand(name: 'lint:twig', description: 'Lint a Twig template and outputs encountered errors')]
final class LintCommand extends BaseLintCommand
{
protected function configure(): void
{
parent::configure();
$this
->setHelp(
$this->getHelp().<<<'EOF'
Or all template files in a bundle:
<info>php %command.full_name% @AcmeDemoBundle</info>
EOF
)
;
}
protected function findFiles(string $filename): iterable
{
if (str_starts_with($filename, '@')) {
$filename = $this->getApplication()->getKernel()->locateResource($filename);
}
return parent::findFiles($filename);
}
}

View File

@@ -0,0 +1,63 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Twig\Attribute\AsTwigFilter;
use Twig\Attribute\AsTwigFunction;
use Twig\Attribute\AsTwigTest;
use Twig\Extension\AbstractExtension;
use Twig\Extension\AttributeExtension;
use Twig\Extension\ExtensionInterface;
/**
* Register an instance of AttributeExtension for each service using the
* PHP attributes to declare Twig callables.
*
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*
* @internal
*/
final class AttributeExtensionPass implements CompilerPassInterface
{
private const TAG = 'twig.attribute_extension';
public static function autoconfigureFromAttribute(ChildDefinition $definition, AsTwigFilter|AsTwigFunction|AsTwigTest $attribute, \ReflectionMethod $reflector): void
{
$class = $reflector->getDeclaringClass();
if ($class->implementsInterface(ExtensionInterface::class)) {
if ($class->isSubclassOf(AbstractExtension::class)) {
throw new LogicException(\sprintf('The class "%s" cannot extend "%s" and use the "#[%s]" attribute on method "%s()", choose one or the other.', $class->name, AbstractExtension::class, $attribute::class, $reflector->name));
}
throw new LogicException(\sprintf('The class "%s" cannot implement "%s" and use the "#[%s]" attribute on method "%s()", choose one or the other.', $class->name, ExtensionInterface::class, $attribute::class, $reflector->name));
}
$definition->addTag(self::TAG);
// The service must be tagged as a runtime to call non-static methods
if (!$reflector->isStatic()) {
$definition->addTag('twig.runtime');
}
}
public function process(ContainerBuilder $container): void
{
foreach ($container->findTaggedServiceIds(self::TAG, true) as $id => $tags) {
$container->register('.twig.extension.'.$id, AttributeExtension::class)
->setArguments([$container->getDefinition($id)->getClass()])
->addTag('twig.extension');
}
}
}

View File

@@ -0,0 +1,148 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler;
use Symfony\Component\Asset\Packages;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Emoji\EmojiTransliterator;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Workflow\Workflow;
use Symfony\Component\Yaml\Yaml;
/**
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*/
class ExtensionPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (!class_exists(Packages::class)) {
$container->removeDefinition('twig.extension.assets');
}
if (!class_exists(\Transliterator::class) || !class_exists(EmojiTransliterator::class)) {
$container->removeDefinition('twig.extension.emoji');
}
if (!class_exists(Expression::class)) {
$container->removeDefinition('twig.extension.expression');
}
if (!interface_exists(UrlGeneratorInterface::class)) {
$container->removeDefinition('twig.extension.routing');
}
if (!class_exists(Yaml::class)) {
$container->removeDefinition('twig.extension.yaml');
}
if (!$container->has('asset_mapper')) {
// edge case where AssetMapper is installed, but not enabled
$container->removeDefinition('twig.extension.importmap');
$container->removeDefinition('twig.runtime.importmap');
}
$viewDir = \dirname((new \ReflectionClass(\Symfony\Bridge\Twig\Extension\FormExtension::class))->getFileName(), 2).'/Resources/views';
$templateIterator = $container->getDefinition('twig.template_iterator');
$templatePaths = $templateIterator->getArgument(1);
$loader = $container->getDefinition('twig.loader.native_filesystem');
if ($container->has('mailer')) {
$emailPath = $viewDir.'/Email';
$loader->addMethodCall('addPath', [$emailPath, 'email']);
$loader->addMethodCall('addPath', [$emailPath, '!email']);
$templatePaths[$emailPath] = 'email';
}
if ($container->has('form.extension')) {
$container->getDefinition('twig.extension.form')->addTag('twig.extension');
$coreThemePath = $viewDir.'/Form';
$loader->addMethodCall('addPath', [$coreThemePath]);
$templatePaths[$coreThemePath] = null;
}
$templateIterator->replaceArgument(1, $templatePaths);
if ($container->has('router')) {
$container->getDefinition('twig.extension.routing')->addTag('twig.extension');
}
if ($container->has('html_sanitizer')) {
$container->getDefinition('twig.extension.htmlsanitizer')->addTag('twig.extension');
}
if ($container->has('fragment.handler')) {
$container->getDefinition('twig.extension.httpkernel')->addTag('twig.extension');
$container->getDefinition('twig.runtime.httpkernel')->addTag('twig.runtime');
if ($container->hasDefinition('fragment.renderer.hinclude')) {
$container->getDefinition('fragment.renderer.hinclude')
->addTag('kernel.fragment_renderer', ['alias' => 'hinclude'])
;
}
}
if ($container->has('request_stack')) {
$container->getDefinition('twig.extension.httpfoundation')->addTag('twig.extension');
}
if ($container->getParameter('kernel.debug')) {
$container->getDefinition('twig.extension.profiler')->addTag('twig.extension');
// only register if the improved version from DebugBundle is *not* present
if (!$container->has('twig.extension.dump')) {
$container->getDefinition('twig.extension.debug')->addTag('twig.extension');
}
}
if ($container->has('web_link.add_link_header_listener')) {
$container->getDefinition('twig.extension.weblink')->addTag('twig.extension');
}
$container->setAlias('twig.loader.filesystem', new Alias('twig.loader.native_filesystem', false));
if ($container->has('assets.packages')) {
$container->getDefinition('twig.extension.assets')->addTag('twig.extension');
}
if ($container->hasDefinition('twig.extension.yaml')) {
$container->getDefinition('twig.extension.yaml')->addTag('twig.extension');
}
if (class_exists(\Symfony\Component\Stopwatch\Stopwatch::class)) {
$container->getDefinition('twig.extension.debug.stopwatch')->addTag('twig.extension');
}
if ($container->hasDefinition('twig.extension.expression')) {
$container->getDefinition('twig.extension.expression')->addTag('twig.extension');
}
if ($container->hasDefinition('twig.extension.emoji')) {
$container->getDefinition('twig.extension.emoji')->addTag('twig.extension');
}
if (!class_exists(Workflow::class) || !$container->has('workflow.registry')) {
$container->removeDefinition('workflow.twig_extension');
} else {
$container->getDefinition('workflow.twig_extension')->addTag('twig.extension');
}
if ($container->has('serializer')) {
$container->getDefinition('twig.runtime.serializer')->addTag('twig.runtime');
$container->getDefinition('twig.extension.serializer')->addTag('twig.extension');
}
}
}

View File

@@ -0,0 +1,39 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* Registers Twig runtime services.
*/
class RuntimeLoaderPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (!$container->hasDefinition('twig.runtime_loader')) {
return;
}
$definition = $container->getDefinition('twig.runtime_loader');
$mapping = [];
foreach ($container->findTaggedServiceIds('twig.runtime', true) as $id => $attributes) {
$def = $container->getDefinition($id);
$mapping[$def->getClass()] = new Reference($id);
}
$definition->replaceArgument(0, ServiceLocatorTagPass::register($container, $mapping));
}
}

View File

@@ -0,0 +1,57 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Adds tagged twig.extension services to twig service.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TwigEnvironmentPass implements CompilerPassInterface
{
use PriorityTaggedServiceTrait;
public function process(ContainerBuilder $container): void
{
if (false === $container->hasDefinition('twig')) {
return;
}
$definition = $container->getDefinition('twig');
// Extensions must always be registered before everything else.
// For instance, global variable definitions must be registered
// afterward. If not, the globals from the extensions will never
// be registered.
$currentMethodCalls = $definition->getMethodCalls();
$twigBridgeExtensionsMethodCalls = [];
$othersExtensionsMethodCalls = [];
foreach ($this->findAndSortTaggedServices('twig.extension', $container) as $extension) {
$methodCall = ['addExtension', [$extension]];
$extensionClass = $container->getDefinition((string) $extension)->getClass();
if (\is_string($extensionClass) && str_starts_with($extensionClass, 'Symfony\Bridge\Twig\Extension')) {
$twigBridgeExtensionsMethodCalls[] = $methodCall;
} else {
$othersExtensionsMethodCalls[] = $methodCall;
}
}
if ($twigBridgeExtensionsMethodCalls || $othersExtensionsMethodCalls) {
$definition->setMethodCalls(array_merge($twigBridgeExtensionsMethodCalls, $othersExtensionsMethodCalls, $currentMethodCalls));
}
}
}

View File

@@ -0,0 +1,60 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Reference;
/**
* Adds services tagged twig.loader as Twig loaders.
*
* @author Daniel Leech <daniel@dantleech.com>
*/
class TwigLoaderPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (false === $container->hasDefinition('twig')) {
return;
}
$prioritizedLoaders = [];
$found = 0;
foreach ($container->findTaggedServiceIds('twig.loader', true) as $id => $attributes) {
$priority = $attributes[0]['priority'] ?? 0;
$prioritizedLoaders[$priority][] = $id;
++$found;
}
if (!$found) {
throw new LogicException('No twig loaders found. You need to tag at least one loader with "twig.loader".');
}
if (1 === $found) {
$container->setAlias('twig.loader', $id);
} else {
$chainLoader = $container->getDefinition('twig.loader.chain');
krsort($prioritizedLoaders);
foreach ($prioritizedLoaders as $loaders) {
foreach ($loaders as $loader) {
$chainLoader->addMethodCall('addLoader', [new Reference($loader)]);
}
}
$container->setAlias('twig.loader', 'twig.loader.chain');
}
}
}

View File

@@ -0,0 +1,234 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\TwigBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Mime\HtmlToTextConverter\HtmlToTextConverterInterface;
/**
* TwigExtension configuration structure.
*
* @author Jeremy Mikola <jmikola@gmail.com>
*/
class Configuration implements ConfigurationInterface
{
/**
* Generates the configuration tree builder.
*/
public function getConfigTreeBuilder(): TreeBuilder
{
$treeBuilder = new TreeBuilder('twig');
$rootNode = $treeBuilder->getRootNode();
$rootNode
->docUrl('https://symfony.com/doc/{version:major}.{version:minor}/reference/configuration/twig.html', 'symfony/twig-bundle')
->beforeNormalization()
->ifTrue(fn ($v) => \is_array($v) && \array_key_exists('exception_controller', $v))
->then(function ($v) {
if (isset($v['exception_controller'])) {
throw new InvalidConfigurationException('Option "exception_controller" under "twig" must be null or unset, use "error_controller" under "framework" instead.');
}
unset($v['exception_controller']);
return $v;
})
->end();
$this->addFormThemesSection($rootNode);
$this->addGlobalsSection($rootNode);
$this->addTwigOptions($rootNode);
$this->addTwigFormatOptions($rootNode);
$this->addMailerSection($rootNode);
return $treeBuilder;
}
private function addFormThemesSection(ArrayNodeDefinition $rootNode): void
{
$rootNode
->fixXmlConfig('form_theme')
->children()
->arrayNode('form_themes')
->addDefaultChildrenIfNoneSet()
->prototype('scalar')->defaultValue('form_div_layout.html.twig')->end()
->example(['@My/form.html.twig'])
->validate()
->ifTrue(fn ($v) => !\in_array('form_div_layout.html.twig', $v, true))
->then(fn ($v) => array_merge(['form_div_layout.html.twig'], $v))
->end()
->end()
->end()
;
}
private function addGlobalsSection(ArrayNodeDefinition $rootNode): void
{
$rootNode
->fixXmlConfig('global')
->children()
->arrayNode('globals')
->normalizeKeys(false)
->useAttributeAsKey('key')
->example(['foo' => '@bar', 'pi' => 3.14])
->prototype('array')
->normalizeKeys(false)
->beforeNormalization()
->ifTrue(fn ($v) => \is_string($v) && str_starts_with($v, '@'))
->then(function ($v) {
if (str_starts_with($v, '@@')) {
return substr($v, 1);
}
return ['id' => substr($v, 1), 'type' => 'service'];
})
->end()
->beforeNormalization()
->ifTrue(function ($v) {
if (\is_array($v)) {
$keys = array_keys($v);
sort($keys);
return $keys !== ['id', 'type'] && $keys !== ['value'];
}
return true;
})
->then(fn ($v) => ['value' => $v])
->end()
->children()
->scalarNode('id')->end()
->scalarNode('type')
->validate()
->ifNotInArray(['service'])
->thenInvalid('The %s type is not supported')
->end()
->end()
->variableNode('value')->end()
->end()
->end()
->end()
->end()
;
}
private function addTwigOptions(ArrayNodeDefinition $rootNode): void
{
$rootNode
->fixXmlConfig('path')
->children()
->scalarNode('autoescape_service')->defaultNull()->end()
->scalarNode('autoescape_service_method')->defaultNull()->end()
->scalarNode('base_template_class')
->setDeprecated('symfony/twig-bundle', '7.1')
->example('Twig\Template')
->cannotBeEmpty()
->end()
->scalarNode('cache')->defaultTrue()->end()
->scalarNode('charset')->defaultValue('%kernel.charset%')->end()
->booleanNode('debug')->defaultValue('%kernel.debug%')->end()
->booleanNode('strict_variables')->defaultValue('%kernel.debug%')->end()
->scalarNode('auto_reload')->end()
->integerNode('optimizations')->min(-1)->end()
->scalarNode('default_path')
->info('The default path used to load templates.')
->defaultValue('%kernel.project_dir%/templates')
->end()
->arrayNode('file_name_pattern')
->example('*.twig')
->info('Pattern of file name used for cache warmer and linter.')
->beforeNormalization()
->ifString()
->then(fn ($value) => [$value])
->end()
->prototype('scalar')->end()
->end()
->arrayNode('paths')
->normalizeKeys(false)
->useAttributeAsKey('paths')
->beforeNormalization()
->ifArray()
->then(function ($paths) {
$normalized = [];
foreach ($paths as $path => $namespace) {
if (\is_array($namespace)) {
// xml
$path = $namespace['value'];
$namespace = $namespace['namespace'];
}
// path within the default namespace
if (ctype_digit((string) $path)) {
$path = $namespace;
$namespace = null;
}
$normalized[$path] = $namespace;
}
return $normalized;
})
->end()
->prototype('variable')->end()
->end()
->end()
;
}
private function addTwigFormatOptions(ArrayNodeDefinition $rootNode): void
{
$rootNode
->children()
->arrayNode('date')
->info('The default format options used by the date filter.')
->addDefaultsIfNotSet()
->children()
->scalarNode('format')->defaultValue('F j, Y H:i')->end()
->scalarNode('interval_format')->defaultValue('%d days')->end()
->scalarNode('timezone')
->info('The timezone used when formatting dates, when set to null, the timezone returned by date_default_timezone_get() is used.')
->defaultNull()
->end()
->end()
->end()
->arrayNode('number_format')
->info('The default format options for the number_format filter.')
->addDefaultsIfNotSet()
->children()
->integerNode('decimals')->defaultValue(0)->end()
->scalarNode('decimal_point')->defaultValue('.')->end()
->scalarNode('thousands_separator')->defaultValue(',')->end()
->end()
->end()
->end()
;
}
private function addMailerSection(ArrayNodeDefinition $rootNode): void
{
$rootNode
->children()
->arrayNode('mailer')
->children()
->scalarNode('html_to_text_converter')
->info(\sprintf('A service implementing the "%s".', HtmlToTextConverterInterface::class))
->defaultNull()
->end()
->end()
->end()
->end()
;
}
}

View File

@@ -0,0 +1,49 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\TwigBundle\DependencyInjection\Configurator;
use Symfony\Bridge\Twig\UndefinedCallableHandler;
use Twig\Environment;
use Twig\Extension\CoreExtension;
/**
* Twig environment configurator.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
class EnvironmentConfigurator
{
public function __construct(
private string $dateFormat,
private string $intervalFormat,
private ?string $timezone,
private int $decimals,
private string $decimalPoint,
private string $thousandsSeparator,
) {
}
public function configure(Environment $environment): void
{
$environment->getExtension(CoreExtension::class)->setDateFormat($this->dateFormat, $this->intervalFormat);
if (null !== $this->timezone) {
$environment->getExtension(CoreExtension::class)->setTimezone($this->timezone);
}
$environment->getExtension(CoreExtension::class)->setNumberFormat($this->decimals, $this->decimalPoint, $this->thousandsSeparator);
// wrap UndefinedCallableHandler in closures for lazy-autoloading
$environment->registerUndefinedFilterCallback(fn ($name) => UndefinedCallableHandler::onUndefinedFilter($name));
$environment->registerUndefinedFunctionCallback(fn ($name) => UndefinedCallableHandler::onUndefinedFunction($name));
}
}

View File

@@ -0,0 +1,259 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\TwigBundle\DependencyInjection;
use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\AttributeExtensionPass;
use Symfony\Component\AssetMapper\AssetMapper;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Config\Resource\FileExistenceResource;
use Symfony\Component\Console\Application;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Form\AbstractRendererEngine;
use Symfony\Component\Form\Form;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\Mailer\Mailer;
use Symfony\Component\Translation\LocaleSwitcher;
use Symfony\Component\Translation\Translator;
use Symfony\Component\Validator\Constraint;
use Symfony\Contracts\Service\ResetInterface;
use Twig\Attribute\AsTwigFilter;
use Twig\Attribute\AsTwigFunction;
use Twig\Attribute\AsTwigTest;
use Twig\Environment;
use Twig\Extension\ExtensionInterface;
use Twig\Extension\RuntimeExtensionInterface;
use Twig\Loader\LoaderInterface;
/**
* TwigExtension.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jeremy Mikola <jmikola@gmail.com>
*/
class TwigExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container): void
{
$loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('twig.php');
if (method_exists(Environment::class, 'resetGlobals')) {
$container->getDefinition('twig')->addTag('kernel.reset', ['method' => 'resetGlobals']);
}
if ($container::willBeAvailable('symfony/form', Form::class, ['symfony/twig-bundle'])) {
$loader->load('form.php');
if (is_subclass_of(AbstractRendererEngine::class, ResetInterface::class)) {
$container->getDefinition('twig.form.engine')->addTag('kernel.reset', [
'method' => 'reset',
]);
}
}
if ($container::willBeAvailable('symfony/console', Application::class, ['symfony/twig-bundle'])) {
$loader->load('console.php');
}
if (!$container::willBeAvailable('symfony/translation', Translator::class, ['symfony/twig-bundle'])) {
$container->removeDefinition('twig.translation.extractor');
}
if ($container::willBeAvailable('symfony/validator', Constraint::class, ['symfony/twig-bundle'])) {
$loader->load('validator.php');
}
foreach ($configs as $key => $config) {
if (isset($config['globals'])) {
foreach ($config['globals'] as $name => $value) {
if (\is_array($value) && isset($value['key'])) {
$configs[$key]['globals'][$name] = [
'key' => $name,
'value' => $value,
];
}
}
}
}
$configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs);
if ($container::willBeAvailable('symfony/mailer', Mailer::class, ['symfony/twig-bundle'])) {
$loader->load('mailer.php');
if ($htmlToTextConverter = $config['mailer']['html_to_text_converter'] ?? null) {
$container->getDefinition('twig.mime_body_renderer')->setArgument('$converter', new Reference($htmlToTextConverter));
}
if (ContainerBuilder::willBeAvailable('symfony/translation', LocaleSwitcher::class, ['symfony/framework-bundle'])) {
$container->getDefinition('twig.mime_body_renderer')->setArgument('$localeSwitcher', new Reference('translation.locale_switcher', ContainerBuilder::IGNORE_ON_INVALID_REFERENCE));
}
}
if ($container::willBeAvailable('symfony/asset-mapper', AssetMapper::class, ['symfony/twig-bundle'])) {
$loader->load('importmap.php');
}
$container->setParameter('twig.form.resources', $config['form_themes']);
$container->setParameter('twig.default_path', $config['default_path']);
$defaultTwigPath = $container->getParameterBag()->resolveValue($config['default_path']);
$envConfiguratorDefinition = $container->getDefinition('twig.configurator.environment');
$envConfiguratorDefinition->replaceArgument(0, $config['date']['format']);
$envConfiguratorDefinition->replaceArgument(1, $config['date']['interval_format']);
$envConfiguratorDefinition->replaceArgument(2, $config['date']['timezone']);
$envConfiguratorDefinition->replaceArgument(3, $config['number_format']['decimals']);
$envConfiguratorDefinition->replaceArgument(4, $config['number_format']['decimal_point']);
$envConfiguratorDefinition->replaceArgument(5, $config['number_format']['thousands_separator']);
$twigFilesystemLoaderDefinition = $container->getDefinition('twig.loader.native_filesystem');
// register user-configured paths
foreach ($config['paths'] as $path => $namespace) {
if (!$namespace) {
$twigFilesystemLoaderDefinition->addMethodCall('addPath', [$path]);
} else {
$twigFilesystemLoaderDefinition->addMethodCall('addPath', [$path, $namespace]);
}
}
// paths are modified in ExtensionPass if forms are enabled
$container->getDefinition('twig.template_iterator')->replaceArgument(1, $config['paths']);
$container->getDefinition('twig.template_iterator')->replaceArgument(3, $config['file_name_pattern']);
if ($container->hasDefinition('twig.command.lint')) {
$container->getDefinition('twig.command.lint')->replaceArgument(1, $config['file_name_pattern'] ?: ['*.twig']);
}
foreach ($this->getBundleTemplatePaths($container, $config) as $name => $paths) {
$namespace = $this->normalizeBundleName($name);
foreach ($paths as $path) {
$twigFilesystemLoaderDefinition->addMethodCall('addPath', [$path, $namespace]);
}
if ($paths) {
// the last path must be the bundle views directory
$twigFilesystemLoaderDefinition->addMethodCall('addPath', [$path, '!'.$namespace]);
}
}
if (file_exists($defaultTwigPath)) {
$twigFilesystemLoaderDefinition->addMethodCall('addPath', [$defaultTwigPath]);
}
$container->addResource(new FileExistenceResource($defaultTwigPath));
if (!empty($config['globals'])) {
$def = $container->getDefinition('twig');
foreach ($config['globals'] as $key => $global) {
if (isset($global['type']) && 'service' === $global['type']) {
$def->addMethodCall('addGlobal', [$key, new Reference($global['id'])]);
} else {
$def->addMethodCall('addGlobal', [$key, $global['value']]);
}
}
}
if (true === $config['cache']) {
$autoReloadOrDefault = $container->getParameterBag()->resolveValue($config['auto_reload'] ?? $config['debug']);
$buildDir = $container->getParameter('kernel.build_dir');
$cacheDir = $container->getParameter('kernel.cache_dir');
if ($autoReloadOrDefault || $cacheDir === $buildDir) {
$config['cache'] = '%kernel.cache_dir%/twig';
}
}
if (true === $config['cache']) {
$config['cache'] = new Reference('twig.template_cache.chain');
} else {
$container->removeDefinition('twig.template_cache.chain');
$container->removeDefinition('twig.template_cache.runtime_cache');
$container->removeDefinition('twig.template_cache.readonly_cache');
$container->removeDefinition('twig.template_cache.warmup_cache');
if (false === $config['cache']) {
$container->removeDefinition('twig.template_cache_warmer');
} else {
$container->getDefinition('twig.template_cache_warmer')->replaceArgument(2, null);
}
}
if (isset($config['autoescape_service'])) {
$config['autoescape'] = [new Reference($config['autoescape_service']), $config['autoescape_service_method'] ?? '__invoke'];
} else {
$config['autoescape'] = 'name';
}
$container->getDefinition('twig')->replaceArgument(1, array_intersect_key($config, [
'debug' => true,
'charset' => true,
'base_template_class' => true,
'strict_variables' => true,
'autoescape' => true,
'cache' => true,
'auto_reload' => true,
'optimizations' => true,
]));
$container->registerForAutoconfiguration(ExtensionInterface::class)->addTag('twig.extension');
$container->registerForAutoconfiguration(LoaderInterface::class)->addTag('twig.loader');
$container->registerForAutoconfiguration(RuntimeExtensionInterface::class)->addTag('twig.runtime');
$container->registerAttributeForAutoconfiguration(AsTwigFilter::class, AttributeExtensionPass::autoconfigureFromAttribute(...));
$container->registerAttributeForAutoconfiguration(AsTwigFunction::class, AttributeExtensionPass::autoconfigureFromAttribute(...));
$container->registerAttributeForAutoconfiguration(AsTwigTest::class, AttributeExtensionPass::autoconfigureFromAttribute(...));
}
private function getBundleTemplatePaths(ContainerBuilder $container, array $config): array
{
$bundleHierarchy = [];
foreach ($container->getParameter('kernel.bundles_metadata') as $name => $bundle) {
$defaultOverrideBundlePath = $container->getParameterBag()->resolveValue($config['default_path']).'/bundles/'.$name;
if (file_exists($defaultOverrideBundlePath)) {
$bundleHierarchy[$name][] = $defaultOverrideBundlePath;
}
$container->addResource(new FileExistenceResource($defaultOverrideBundlePath));
if (file_exists($dir = $bundle['path'].'/Resources/views') || file_exists($dir = $bundle['path'].'/templates')) {
$bundleHierarchy[$name][] = $dir;
}
$container->addResource(new FileExistenceResource($dir));
}
return $bundleHierarchy;
}
private function normalizeBundleName(string $name): string
{
if (str_ends_with($name, 'Bundle')) {
$name = substr($name, 0, -6);
}
return $name;
}
public function getXsdValidationBasePath(): string|false
{
return __DIR__.'/../Resources/config/schema';
}
public function getNamespace(): string
{
return 'http://symfony.com/schema/dic/twig';
}
}

19
vendor/symfony/twig-bundle/LICENSE vendored Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2004-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

13
vendor/symfony/twig-bundle/README.md vendored Normal file
View File

@@ -0,0 +1,13 @@
TwigBundle
==========
TwigBundle provides a tight integration of Twig into the Symfony full-stack
framework.
Resources
---------
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)

View File

@@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Bridge\Twig\Command\DebugCommand;
use Symfony\Bundle\TwigBundle\Command\LintCommand;
return static function (ContainerConfigurator $container) {
$container->services()
->set('twig.command.debug', DebugCommand::class)
->args([
service('twig'),
param('kernel.project_dir'),
param('kernel.bundles_metadata'),
param('twig.default_path'),
service('debug.file_link_formatter')->nullOnInvalid(),
])
->tag('console.command')
->set('twig.command.lint', LintCommand::class)
->args([service('twig'), abstract_arg('File name pattern')])
->tag('console.command')
;
};

View File

@@ -0,0 +1,30 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Bridge\Twig\Extension\FormExtension;
use Symfony\Bridge\Twig\Form\TwigRendererEngine;
use Symfony\Component\Form\FormRenderer;
return static function (ContainerConfigurator $container) {
$container->services()
->set('twig.extension.form', FormExtension::class)
->args([service('translator')->nullOnInvalid()])
->set('twig.form.engine', TwigRendererEngine::class)
->args([param('twig.form.resources'), service('twig')])
->set('twig.form.renderer', FormRenderer::class)
->args([service('twig.form.engine'), service('security.csrf.token_manager')->nullOnInvalid()])
->tag('twig.runtime')
;
};

View File

@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Bridge\Twig\Extension\ImportMapExtension;
use Symfony\Bridge\Twig\Extension\ImportMapRuntime;
return static function (ContainerConfigurator $container) {
$container->services()
->set('twig.runtime.importmap', ImportMapRuntime::class)
->args([service('asset_mapper.importmap.renderer')])
->tag('twig.runtime')
->set('twig.extension.importmap', ImportMapExtension::class)
->tag('twig.extension')
;
};

View File

@@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Bridge\Twig\Mime\BodyRenderer;
use Symfony\Component\Mailer\EventListener\MessageListener;
use Symfony\Component\Mime\BodyRendererInterface;
return static function (ContainerConfigurator $container) {
$container->services()
->set('twig.mailer.message_listener', MessageListener::class)
->args([null, service('twig.mime_body_renderer')])
->tag('kernel.event_subscriber')
->set('twig.mime_body_renderer', BodyRenderer::class)
->args([service('twig')])
->alias(BodyRendererInterface::class, 'twig.mime_body_renderer')
;
};

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" ?>
<xsd:schema xmlns="http://symfony.com/schema/dic/twig"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://symfony.com/schema/dic/twig"
elementFormDefault="qualified">
<xsd:element name="config" type="config" />
<xsd:complexType name="config">
<xsd:sequence>
<xsd:element name="date" type="date" minOccurs="0" maxOccurs="1" />
<xsd:element name="number-format" type="number_format" minOccurs="0" maxOccurs="1" />
<xsd:element name="form-theme" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="global" type="global" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="path" type="path" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="mailer" type="mailer" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
<xsd:attribute name="auto-reload" type="xsd:string" />
<xsd:attribute name="autoescape-service" type="xsd:string" />
<xsd:attribute name="autoescape-service-method" type="xsd:string" />
<xsd:attribute name="base-template-class" type="xsd:string" />
<xsd:attribute name="cache" type="xsd:string" />
<xsd:attribute name="charset" type="xsd:string" />
<xsd:attribute name="debug" type="xsd:string" />
<xsd:attribute name="strict-variables" type="xsd:string" />
<xsd:attribute name="default-path" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="date">
<xsd:attribute name="format" type="xsd:string" />
<xsd:attribute name="interval-format" type="xsd:string" />
<xsd:attribute name="timezone" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="number_format">
<xsd:attribute name="decimals" type="xsd:integer" />
<xsd:attribute name="decimal-point" type="xsd:string" />
<xsd:attribute name="thousands-separator" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="path" mixed="true">
<xsd:attribute name="namespace" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="global" mixed="true">
<xsd:attribute name="key" type="xsd:string" use="required" />
<xsd:attribute name="type" type="global_type" />
<xsd:attribute name="id" type="xsd:string" />
</xsd:complexType>
<xsd:simpleType name="global_type">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="service" />
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="mailer">
<xsd:attribute name="html-to-text-converter" type="xsd:string" />
</xsd:complexType>
</xsd:schema>

View File

@@ -0,0 +1,196 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Psr\Container\ContainerInterface;
use Symfony\Bridge\Twig\AppVariable;
use Symfony\Bridge\Twig\DataCollector\TwigDataCollector;
use Symfony\Bridge\Twig\ErrorRenderer\TwigErrorRenderer;
use Symfony\Bridge\Twig\EventListener\TemplateAttributeListener;
use Symfony\Bridge\Twig\Extension\AssetExtension;
use Symfony\Bridge\Twig\Extension\EmojiExtension;
use Symfony\Bridge\Twig\Extension\ExpressionExtension;
use Symfony\Bridge\Twig\Extension\HtmlSanitizerExtension;
use Symfony\Bridge\Twig\Extension\HttpFoundationExtension;
use Symfony\Bridge\Twig\Extension\HttpKernelExtension;
use Symfony\Bridge\Twig\Extension\HttpKernelRuntime;
use Symfony\Bridge\Twig\Extension\ProfilerExtension;
use Symfony\Bridge\Twig\Extension\RoutingExtension;
use Symfony\Bridge\Twig\Extension\SerializerExtension;
use Symfony\Bridge\Twig\Extension\SerializerRuntime;
use Symfony\Bridge\Twig\Extension\StopwatchExtension;
use Symfony\Bridge\Twig\Extension\TranslationExtension;
use Symfony\Bridge\Twig\Extension\WebLinkExtension;
use Symfony\Bridge\Twig\Extension\WorkflowExtension;
use Symfony\Bridge\Twig\Extension\YamlExtension;
use Symfony\Bridge\Twig\Translation\TwigExtractor;
use Symfony\Bundle\TwigBundle\CacheWarmer\TemplateCacheWarmer;
use Symfony\Bundle\TwigBundle\DependencyInjection\Configurator\EnvironmentConfigurator;
use Symfony\Bundle\TwigBundle\TemplateIterator;
use Twig\Cache\ChainCache;
use Twig\Cache\FilesystemCache;
use Twig\Cache\ReadOnlyFilesystemCache;
use Twig\Environment;
use Twig\ExpressionParser\Infix\BinaryOperatorExpressionParser;
use Twig\Extension\CoreExtension;
use Twig\Extension\DebugExtension;
use Twig\Extension\EscaperExtension;
use Twig\Extension\OptimizerExtension;
use Twig\Extension\StagingExtension;
use Twig\ExtensionSet;
use Twig\Loader\ChainLoader;
use Twig\Loader\FilesystemLoader;
use Twig\Profiler\Profile;
use Twig\RuntimeLoader\ContainerRuntimeLoader;
use Twig\Template;
use Twig\TemplateWrapper;
return static function (ContainerConfigurator $container) {
$container->services()
->set('twig', Environment::class)
->args([service('twig.loader'), abstract_arg('Twig options')])
->call('addGlobal', ['app', service('twig.app_variable')])
->call('addRuntimeLoader', [service('twig.runtime_loader')])
->configurator([service('twig.configurator.environment'), 'configure'])
->tag('container.preload', ['class' => FilesystemCache::class])
->tag('container.preload', ['class' => CoreExtension::class])
->tag('container.preload', ['class' => EscaperExtension::class])
->tag('container.preload', ['class' => OptimizerExtension::class])
->tag('container.preload', ['class' => StagingExtension::class])
->tag('container.preload', ['class' => BinaryOperatorExpressionParser::class])
->tag('container.preload', ['class' => ExtensionSet::class])
->tag('container.preload', ['class' => Template::class])
->tag('container.preload', ['class' => TemplateWrapper::class])
->alias(Environment::class, 'twig')
->set('twig.app_variable', AppVariable::class)
->call('setEnvironment', [param('kernel.environment')])
->call('setDebug', [param('kernel.debug')])
->call('setTokenStorage', [service('security.token_storage')->ignoreOnInvalid()])
->call('setRequestStack', [service('request_stack')->ignoreOnInvalid()])
->call('setLocaleSwitcher', [service('translation.locale_switcher')->ignoreOnInvalid()])
->call('setEnabledLocales', [param('kernel.enabled_locales')])
->set('twig.template_iterator', TemplateIterator::class)
->args([service('kernel'), abstract_arg('Twig paths'), param('twig.default_path'), abstract_arg('File name pattern')])
->set('twig.template_cache.runtime_cache', FilesystemCache::class)
->args([param('kernel.cache_dir').'/twig'])
->set('twig.template_cache.readonly_cache', ReadOnlyFilesystemCache::class)
->args([param('kernel.build_dir').'/twig'])
->set('twig.template_cache.warmup_cache', FilesystemCache::class)
->args([param('kernel.build_dir').'/twig'])
->set('twig.template_cache.chain', ChainCache::class)
->args([[service('twig.template_cache.readonly_cache'), service('twig.template_cache.runtime_cache')]])
->set('twig.template_cache_warmer', TemplateCacheWarmer::class)
->args([
service(ContainerInterface::class),
service('twig.template_iterator'),
service('twig.template_cache.warmup_cache'),
])
->tag('kernel.cache_warmer')
->tag('container.service_subscriber', ['id' => 'twig'])
->set('twig.loader.native_filesystem', FilesystemLoader::class)
->args([[], param('kernel.project_dir')])
->tag('twig.loader')
->set('twig.loader.chain', ChainLoader::class)
->set('twig.extension.profiler', ProfilerExtension::class)
->args([service('twig.profile'), service('debug.stopwatch')->ignoreOnInvalid()])
->set('twig.profile', Profile::class)
->set('data_collector.twig', TwigDataCollector::class)
->args([service('twig.profile'), service('twig')])
->tag('data_collector', ['template' => '@WebProfiler/Collector/twig.html.twig', 'id' => 'twig', 'priority' => 257])
->set('twig.extension.trans', TranslationExtension::class)
->args([service('translator')->nullOnInvalid()])
->tag('twig.extension')
->set('twig.extension.assets', AssetExtension::class)
->args([service('assets.packages')])
->set('twig.extension.routing', RoutingExtension::class)
->args([service('router')])
->set('twig.extension.yaml', YamlExtension::class)
->set('twig.extension.debug.stopwatch', StopwatchExtension::class)
->args([service('debug.stopwatch')->ignoreOnInvalid(), param('kernel.debug')])
->set('twig.extension.expression', ExpressionExtension::class)
->set('twig.extension.emoji', EmojiExtension::class)
->set('twig.extension.htmlsanitizer', HtmlSanitizerExtension::class)
->args([tagged_locator('html_sanitizer', 'sanitizer')])
->set('twig.extension.httpkernel', HttpKernelExtension::class)
->set('twig.runtime.httpkernel', HttpKernelRuntime::class)
->args([service('fragment.handler'), service('fragment.uri_generator')->ignoreOnInvalid()])
->set('twig.extension.httpfoundation', HttpFoundationExtension::class)
->args([service('url_helper')])
->set('twig.extension.debug', DebugExtension::class)
->set('twig.extension.weblink', WebLinkExtension::class)
->args([service('request_stack')])
->set('twig.translation.extractor', TwigExtractor::class)
->args([service('twig')])
->tag('translation.extractor', ['alias' => 'twig'])
->set('workflow.twig_extension', WorkflowExtension::class)
->args([service('workflow.registry')])
->set('twig.configurator.environment', EnvironmentConfigurator::class)
->args([
abstract_arg('date format, set in TwigExtension'),
abstract_arg('interval format, set in TwigExtension'),
abstract_arg('timezone, set in TwigExtension'),
abstract_arg('decimals, set in TwigExtension'),
abstract_arg('decimal point, set in TwigExtension'),
abstract_arg('thousands separator, set in TwigExtension'),
])
->set('twig.runtime_loader', ContainerRuntimeLoader::class)
->args([abstract_arg('runtime locator')])
->set('twig.error_renderer.html', TwigErrorRenderer::class)
->decorate('error_renderer.html')
->args([
service('twig'),
service('twig.error_renderer.html.inner'),
inline_service('bool')
->factory([TwigErrorRenderer::class, 'isDebug'])
->args([service('request_stack'), param('kernel.debug')]),
])
->set('twig.runtime.serializer', SerializerRuntime::class)
->args([service('serializer')])
->set('twig.extension.serializer', SerializerExtension::class)
->set('controller.template_attribute_listener', TemplateAttributeListener::class)
->args([service('twig')])
->tag('kernel.event_subscriber')
;
};

View File

@@ -0,0 +1,22 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Bridge\Twig\Validator\Constraints\TwigValidator;
return static function (ContainerConfigurator $container) {
$container->services()
->set('twig.validator', TwigValidator::class)
->args([service('twig')])
->tag('validator.constraint_validator')
;
};

View File

@@ -0,0 +1,90 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\TwigBundle;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpKernel\KernelInterface;
/**
* Iterator for all templates in bundles and in the application Resources directory.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @internal
*
* @implements \IteratorAggregate<int, string>
*/
class TemplateIterator implements \IteratorAggregate
{
private \Traversable $templates;
/**
* @param array $paths Additional Twig paths to warm
* @param string|null $defaultPath The directory where global templates can be stored
* @param string[] $namePatterns Pattern of file names
*/
public function __construct(
private KernelInterface $kernel,
private array $paths = [],
private ?string $defaultPath = null,
private array $namePatterns = [],
) {
}
public function getIterator(): \Traversable
{
if (isset($this->templates)) {
return $this->templates;
}
$templates = null !== $this->defaultPath ? [$this->findTemplatesInDirectory($this->defaultPath, null, ['bundles'])] : [];
foreach ($this->kernel->getBundles() as $bundle) {
$name = $bundle->getName();
if (str_ends_with($name, 'Bundle')) {
$name = substr($name, 0, -6);
}
$bundleTemplatesDir = is_dir($bundle->getPath().'/Resources/views') ? $bundle->getPath().'/Resources/views' : $bundle->getPath().'/templates';
$templates[] = $this->findTemplatesInDirectory($bundleTemplatesDir, $name);
if (null !== $this->defaultPath) {
$templates[] = $this->findTemplatesInDirectory($this->defaultPath.'/bundles/'.$bundle->getName(), $name);
}
}
foreach ($this->paths as $dir => $namespace) {
$templates[] = $this->findTemplatesInDirectory($dir, $namespace);
}
return $this->templates = new \ArrayIterator(array_unique(array_merge([], ...$templates)));
}
/**
* Find templates in the given directory.
*
* @return string[]
*/
private function findTemplatesInDirectory(string $dir, ?string $namespace = null, array $excludeDirs = []): array
{
if (!is_dir($dir)) {
return [];
}
$templates = [];
foreach (Finder::create()->files()->followLinks()->in($dir)->exclude($excludeDirs)->name($this->namePatterns) as $file) {
$templates[] = (null !== $namespace ? '@'.$namespace.'/' : '').str_replace('\\', '/', $file->getRelativePathname());
}
return $templates;
}
}

View File

@@ -0,0 +1,47 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\TwigBundle;
use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\AttributeExtensionPass;
use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\ExtensionPass;
use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\RuntimeLoaderPass;
use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\TwigEnvironmentPass;
use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\TwigLoaderPass;
use Symfony\Component\Console\Application;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
/**
* Bundle.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TwigBundle extends Bundle
{
public function build(ContainerBuilder $container): void
{
parent::build($container);
// ExtensionPass must be run before the FragmentRendererPass as it adds tags that are processed later
$container->addCompilerPass(new ExtensionPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 10);
$container->addCompilerPass(new AttributeExtensionPass());
$container->addCompilerPass(new TwigEnvironmentPass());
$container->addCompilerPass(new TwigLoaderPass());
$container->addCompilerPass(new RuntimeLoaderPass(), PassConfig::TYPE_BEFORE_REMOVING);
}
public function registerCommands(Application $application): void
{
// noop
}
}

View File

@@ -0,0 +1,51 @@
{
"name": "symfony/twig-bundle",
"type": "symfony-bundle",
"description": "Provides a tight integration of Twig into the Symfony full-stack framework",
"keywords": [],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": ">=8.2",
"composer-runtime-api": ">=2.1",
"symfony/config": "^7.3",
"symfony/dependency-injection": "^6.4|^7.0",
"symfony/twig-bridge": "^7.3",
"symfony/http-foundation": "^6.4|^7.0",
"symfony/http-kernel": "^6.4|^7.0",
"twig/twig": "^3.12"
},
"require-dev": {
"symfony/asset": "^6.4|^7.0",
"symfony/stopwatch": "^6.4|^7.0",
"symfony/expression-language": "^6.4|^7.0",
"symfony/finder": "^6.4|^7.0",
"symfony/form": "^6.4|^7.0",
"symfony/routing": "^6.4|^7.0",
"symfony/translation": "^6.4|^7.0",
"symfony/yaml": "^6.4|^7.0",
"symfony/framework-bundle": "^6.4|^7.0",
"symfony/web-link": "^6.4|^7.0"
},
"conflict": {
"symfony/framework-bundle": "<6.4",
"symfony/translation": "<6.4"
},
"autoload": {
"psr-4": { "Symfony\\Bundle\\TwigBundle\\": "" },
"exclude-from-classmap": [
"/Tests/"
]
},
"minimum-stability": "dev"
}