From 326ee58eb20f6f3aa9987dbdb242a68ba821e777 Mon Sep 17 00:00:00 2001 From: KyleKatarn Date: Sat, 14 Jan 2023 15:40:02 +0100 Subject: [PATCH 01/17] Upgrade to Symfony 6 --- composer.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index c2cccba..803c8eb 100644 --- a/composer.json +++ b/composer.json @@ -6,17 +6,17 @@ "description": "Pug template engine for Symfony", "type": "library", "require": { - "php": "^7.2.5 || ^8.0", + "php": ">=8.1", "phug/component": "^1.1.0", "pug/installer": "^1.0.0", "pug-php/pug": "^3.4.0", "pug-php/pug-assets": "^1.0.1", - "symfony/framework-bundle": "^5.0", - "symfony/http-foundation": "^5.0", - "symfony/http-kernel": "^5.0", - "symfony/security-bundle": "^5.0", - "symfony/templating": "^5.0", - "symfony/twig-bridge": "^5.0", + "symfony/framework-bundle": "^6.0", + "symfony/http-foundation": "^6.0", + "symfony/http-kernel": "^6.0", + "symfony/security-bundle": "^6.0", + "symfony/templating": "^6.0", + "symfony/twig-bridge": "^6.0", "twig/twig": "^3.0.0" }, "require-dev": { From ef07578b0bfc322c6079f5230a9f1b0d9dc5a4ec Mon Sep 17 00:00:00 2001 From: KyleKatarn Date: Sat, 14 Jan 2023 18:26:38 +0100 Subject: [PATCH 02/17] Use attribute in README --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index aa33e8f..11379d4 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ In the root directory of your Symfony project, open a terminal and enter. ```shell composer require pug-php/pug-symfony ``` -When your are asked to install automatically needed settings, enter yes. +When you are asked to install automatically needed settings, enter yes. It for any reason, you do not can or want to use it, you will have to add to your **config/bundles.php** file: @@ -46,9 +46,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; class MyController extends AbstractController { - /** - * @Route("/contact") - */ + #[Route('/contact')] public function contactAction() { return $this->render('contact/contact.pug', [ @@ -81,7 +79,7 @@ public function contactAction(\Pug\PugSymfonyEngine $pug) } ``` -Same can be ran globally on a given event such as `onKernelView` to apply customization before any +Same can be run globally on a given event such as `onKernelView` to apply customization before any view rendering. See the options in the pug-php documentation: https://phug-lang.com/#options @@ -108,7 +106,7 @@ twig: ``` -Make the translator available in every views: +Make the translator available in every view: ```pug p=translator.trans('Hello %name%', {'%name%': 'Jack'}) ``` From e6799f8bd4804ab8256a58ad21c18f35bccfa65e Mon Sep 17 00:00:00 2001 From: KyleKatarn Date: Sun, 15 Jan 2023 20:50:20 +0100 Subject: [PATCH 03/17] Add PugRenderer trait --- README.md | 70 ++++++++++++++--- composer.json | 12 +-- src/Pug/Exceptions/ReservedVariable.php | 2 + .../Command/AssetsPublishCommand.php | 23 +++--- src/Pug/PugSymfonyBundle/PugExtension.php | 25 +++++++ src/Pug/PugSymfonyBundle/PugSymfonyBundle.php | 44 +++-------- src/Pug/PugSymfonyBundle/config/pug.php | 24 ++++++ src/Pug/PugSymfonyEngine.php | 75 +++++++++++++------ .../Symfony/Contracts/InstallerInterface.php | 2 + .../Contracts/InterceptorInterface.php | 2 + src/Pug/Symfony/CssExtension.php | 2 + src/Pug/Symfony/MixedLoader.php | 2 + src/Pug/Symfony/RenderEvent.php | 2 + src/Pug/Symfony/Traits/Filters.php | 2 + src/Pug/Symfony/Traits/HelpersHandler.php | 51 ++++--------- src/Pug/Symfony/Traits/Installer.php | 16 +++- src/Pug/Symfony/Traits/Options.php | 6 +- .../Traits/PrivatePropertyAccessor.php | 11 ++- src/Pug/Symfony/Traits/PugRenderer.php | 29 +++++++ src/Pug/Twig/Environment.php | 50 ++++--------- 20 files changed, 285 insertions(+), 165 deletions(-) create mode 100644 src/Pug/PugSymfonyBundle/PugExtension.php create mode 100644 src/Pug/PugSymfonyBundle/config/pug.php create mode 100644 src/Pug/Symfony/Traits/PugRenderer.php diff --git a/README.md b/README.md index 11379d4..c502ef7 100644 --- a/README.md +++ b/README.md @@ -23,29 +23,54 @@ your **config/bundles.php** file: Pug\PugSymfonyBundle\PugSymfonyBundle::class => ['all' => true], ``` +Then + ## Usage Create Pug views by creating files with .pug extension -in **app/Resources/views** such as contact.pug: +in **templates** such as contact.pug: ```pug h1 | Contact =name ``` -Note: standard Twig functions are also available in your pug templates, for instance: -```pug -!=form_start(form, {method: 'GET'}) +Then inject `Pug\PugSymfonyEngine` to call it in your controller: +```php +namespace App\Controller; + +use Pug\PugSymfonyEngine; +use Symfony\Component\HttpKernel\Attribute\AsController; +use Symfony\Component\Routing\Annotation\Route; + +#[AsController] +class MyController +{ + #[Route('/contact')] + public function contactAction(PugSymfonyEngine $pug) + { + return $pug->renderResponse('contact/contact.pug', [ + 'name' => 'Us', + ]); + } +} ``` -Then call it in your controller: +Or alternatively you can use `\Pug\Symfony\Traits\PugRenderer` to call directly `->render()` from +any method of a controller (or service): + ```php namespace App\Controller; -use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Pug\Symfony\Traits\PugRenderer; +use Symfony\Component\HttpKernel\Attribute\AsController; +use Symfony\Component\Routing\Annotation\Route; -class MyController extends AbstractController +#[AsController] +class MyController { + use PugRenderer; + #[Route('/contact')] public function contactAction() { @@ -56,6 +81,30 @@ class MyController extends AbstractController } ``` +No matter if your controller extends `AbstractController` as it can also render twig views, so it will just +work the same as before rather you `->render('view.html.twig')` or `->render('view.pug')`. + +Note: standard Twig functions are also available in your pug templates, for instance: +```pug +!=form(form) +``` + +As per https://symfony.com/doc/current/forms.html + +Pass the FormView as usual from the controller: +```php +$task = new Task(); +// ... + +$form = $this->createFormBuilder($task) + // ... + ->getForm(); + +return $pug->renderResponse('home.pug', [ + 'form' => $form->createView(), +]); +``` + ## Configure You can inject `Pug\PugSymfonyEngine` to change options, share values, add plugins to Pug @@ -63,6 +112,7 @@ at route level: ```php // In a controller method +#[Route('/contact')] public function contactAction(\Pug\PugSymfonyEngine $pug) { $pug->setOptions(array( @@ -72,13 +122,15 @@ public function contactAction(\Pug\PugSymfonyEngine $pug) )); $pug->share('globalVar', 'foo'); $pug->getRenderer()->addKeyword('customKeyword', $bar); - - return $this->render('contact/contact.pug', [ + + return $pug->renderResponse('contact/contact.pug', [ 'name' => 'Us', ]); } ``` +If you use the `PugRenderer` trait, you don't need to inject the service again and can just use `$this->pug`. + Same can be run globally on a given event such as `onKernelView` to apply customization before any view rendering. diff --git a/composer.json b/composer.json index 803c8eb..f9622fb 100644 --- a/composer.json +++ b/composer.json @@ -7,21 +7,21 @@ "type": "library", "require": { "php": ">=8.1", - "phug/component": "^1.1.0", - "pug/installer": "^1.0.0", - "pug-php/pug": "^3.4.0", - "pug-php/pug-assets": "^1.0.1", + "phug/component": "^1.1.4", + "pug/installer": "^1.0.1", + "pug-php/pug": "^3.5.0", + "pug-php/pug-assets": "^1.1.4", "symfony/framework-bundle": "^6.0", "symfony/http-foundation": "^6.0", "symfony/http-kernel": "^6.0", "symfony/security-bundle": "^6.0", "symfony/templating": "^6.0", "symfony/twig-bridge": "^6.0", - "twig/twig": "^3.0.0" + "twig/twig": "^3.5.0" }, "require-dev": { "phpunit/phpunit": "^8.5", - "symfony/symfony": "^5.0" + "symfony/symfony": "^6.0" }, "minimum-stability": "stable", "license": "MIT", diff --git a/src/Pug/Exceptions/ReservedVariable.php b/src/Pug/Exceptions/ReservedVariable.php index 24bf360..df78dab 100644 --- a/src/Pug/Exceptions/ReservedVariable.php +++ b/src/Pug/Exceptions/ReservedVariable.php @@ -1,5 +1,7 @@ pugSymfonyEngine = $pugSymfonyEngine; - parent::__construct(null); - } - - protected function configure() + public function __construct(protected readonly PugSymfonyEngine $pugSymfonyEngine) { - $this - ->setName('assets:publish') - ->setDescription('Export your assets in the web directory.'); + parent::__construct(); } - protected function cacheTemplates(Renderer $pug) + protected function cacheTemplates(Renderer $pug): array { $success = 0; $errors = 0; diff --git a/src/Pug/PugSymfonyBundle/PugExtension.php b/src/Pug/PugSymfonyBundle/PugExtension.php new file mode 100644 index 0000000..3ded5b7 --- /dev/null +++ b/src/Pug/PugSymfonyBundle/PugExtension.php @@ -0,0 +1,25 @@ + + * @author Jeremy Mikola + */ +class PugExtension extends Extension +{ + public function load(array $configs, ContainerBuilder $container) + { + $loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/config')); + $loader->load('pug.php'); + } +} diff --git a/src/Pug/PugSymfonyBundle/PugSymfonyBundle.php b/src/Pug/PugSymfonyBundle/PugSymfonyBundle.php index 777fea1..4b6be9b 100644 --- a/src/Pug/PugSymfonyBundle/PugSymfonyBundle.php +++ b/src/Pug/PugSymfonyBundle/PugSymfonyBundle.php @@ -1,54 +1,32 @@ container = $container; - - if ($container) { - /** @var KernelInterface $kernel */ - $kernel = $container->get('kernel'); - $engine = new PugSymfonyEngine($kernel); - /** @var ReflectionProperty $propertyAccessor */ - $services = static::getPrivateProperty($container, 'services', $propertyAccessor); - $services[PugSymfonyEngine::class] = $engine; - $propertyAccessor->setValue($container, $services); - } + $extension = new PugExtension(); + $containerBuilder->registerExtension($extension); + $containerBuilder->loadFromExtension($extension->getAlias()); } - public function registerCommands(Application $application) { - $method = new ReflectionMethod(AssetsPublishCommand::class, '__construct'); - $class = $method->getNumberOfParameters() === 1 ? $method->getParameters()[0]->getClass() : null; - - if ($class && $class->getName() === PugSymfonyEngine::class) { - /** @var PugSymfonyEngine $engine */ - $engine = $this->container->get(PugSymfonyEngine::class); - - $application->addCommands([ - new AssetsPublishCommand($engine), - ]); - } + $application->addCommands([ + $this->container->get(AssetsPublishCommand::class), + ]); } } diff --git a/src/Pug/PugSymfonyBundle/config/pug.php b/src/Pug/PugSymfonyBundle/config/pug.php new file mode 100644 index 0000000..b4137f7 --- /dev/null +++ b/src/Pug/PugSymfonyBundle/config/pug.php @@ -0,0 +1,24 @@ +services() + ->defaults() + ->autowire() + ->autoconfigure() + ; + + $services->load('Pug\\', __DIR__ . '/../../*') + ->exclude([ + __DIR__ . '/../../Exceptions', + __DIR__ . '/../../PugSymfonyBundle', + __DIR__ . '/../../Symfony', + __DIR__ . '/../../Twig', + ]) + ; + $services->load('Pug\\PugSymfonyBundle\\Command\\', __DIR__ . '/../../PugSymfonyBundle/Command/*') + ->public(); +}; diff --git a/src/Pug/PugSymfonyEngine.php b/src/Pug/PugSymfonyEngine.php index 71f78cd..ea0a927 100644 --- a/src/Pug/PugSymfonyEngine.php +++ b/src/Pug/PugSymfonyEngine.php @@ -1,5 +1,7 @@ getContainer(); $this->kernel = $kernel; $this->container = $container; $this->userOptions = ($this->container->hasParameter('pug') ? $this->container->getParameter('pug') : null) ?: []; - $this->enhanceTwig(); + $this->enhanceTwig($twig); $this->onNode([$this, 'handleTwigInclude']); } @@ -98,7 +106,7 @@ protected function crawlDirectories(string $srcDir, array &$assetsDirectories, a protected function getFileFromName(string $name, string $directory = null): string { - $parts = explode(':', strval($name)); + $parts = explode(':', $name); if (count($parts) > 1) { $name = $parts[2]; @@ -166,7 +174,7 @@ public function getParameters(array $locals = []): array $locals = array_merge( $this->getOptionDefault('globals', []), $this->getOptionDefault('shared_variables', []), - $locals + $locals, ); foreach (['context', 'blocks', 'macros', 'this'] as $forbiddenKey) { @@ -183,8 +191,8 @@ public function getParameters(array $locals = []): array /** * Render a template by name. * - * @param string|\Symfony\Component\Templating\TemplateReferenceInterface $name - * @param array $parameters + * @param string|TemplateReferenceInterface $name + * @param array $parameters * * @throws ErrorException when a forbidden parameter key is used * @throws Exception when the PHP code generated from the pug code throw an exception @@ -195,15 +203,15 @@ public function render($name, array $parameters = []): string { return $this->getRenderer()->renderFile( $this->getFileFromName($name), - $this->getParameters($parameters) + $this->getParameters($parameters), ); } /** * Render a template string. * - * @param string|\Symfony\Component\Templating\TemplateReferenceInterface $name - * @param array $locals + * @param string|TemplateReferenceInterface $name + * @param array $locals * * @throws ErrorException when a forbidden parameter key is used * @@ -211,19 +219,16 @@ public function render($name, array $parameters = []): string */ public function renderString($code, array $locals = []): string { - $pug = $this->getRenderer(); - $method = method_exists($pug, 'renderString') ? 'renderString' : 'render'; - - return $pug->$method( + return $this->getRenderer()->renderString( $code, - $this->getParameters($locals) + $this->getParameters($locals), ); } /** * Check if a template exists. * - * @param string|\Symfony\Component\Templating\TemplateReferenceInterface $name + * @param string|TemplateReferenceInterface $name * * @return bool */ @@ -241,14 +246,14 @@ public function exists($name): bool /** * Check if a file extension is supported by Pug. * - * @param string|\Symfony\Component\Templating\TemplateReferenceInterface $name + * @param string|TemplateReferenceInterface $name * * @return bool */ public function supports($name): bool { foreach ($this->getOptionDefault('extensions', ['.pug', '.jade']) as $extension) { - if (substr($name, -strlen($extension)) === $extension) { + if (str_ends_with($name, $extension)) { return true; } } @@ -266,9 +271,10 @@ public function getRenderArguments(string $name, array $locals): array $dispatcher = $container->get('event_dispatcher'); $dispatcher->dispatch($event, RenderEvent::NAME); - $interceptors = array_map(static function (string $interceptorClass) use ($container) { - return $container->get($interceptorClass); - }, $this->userOptions['interceptors'] ?? []); + $interceptors = array_map( + static fn (string $interceptorClass) => $container->get($interceptorClass), + $this->userOptions['interceptors'] ?? [], + ); array_walk($interceptors, static function (InterceptorInterface $interceptor) use ($event) { $interceptor->intercept($event); @@ -278,10 +284,31 @@ public function getRenderArguments(string $name, array $locals): array return [$event->getName(), $this->getParameters($event->getLocals())]; } + public function renderResponse( + string|TemplateReferenceInterface $view, + array $parameters = [], + ?Response $response = null, + ): Response { + $content = $this->render($view, $parameters); + $response ??= new Response(); + + if ($response->getStatusCode() === 200) { + foreach ($parameters as $v) { + if ($v instanceof FormInterface && $v->isSubmitted() && !$v->isValid()) { + $response->setStatusCode(422); + + break; + } + } + } + + $response->setContent($content); + + return $response; + } + protected static function extractUniquePaths(array $paths): array { - return array_unique(array_map(function ($path) { - return realpath($path) ?: $path; - }, $paths)); + return array_unique(array_map(static fn ($path) => realpath($path) ?: $path, $paths)); } } diff --git a/src/Pug/Symfony/Contracts/InstallerInterface.php b/src/Pug/Symfony/Contracts/InstallerInterface.php index 1229ce3..6455f28 100644 --- a/src/Pug/Symfony/Contracts/InstallerInterface.php +++ b/src/Pug/Symfony/Contracts/InstallerInterface.php @@ -1,5 +1,7 @@ twigHelpers[$name] = $function; } - protected function enhanceTwig(): void + protected function enhanceTwig($twig): void { - $this->twig = $this->container->has('twig') ? $this->container->get('twig') : null; + $twig ??= $this->container->has('twig') ? $this->container->get('twig') : null; - if (!($this->twig instanceof TwigEnvironment)) { + if (!($twig instanceof TwigEnvironment)) { throw new RuntimeException('Twig needs to be configured.'); } - $this->twig = Environment::fromTwigEnvironment($this->twig, $this, $this->container); - - $services = static::getPrivateProperty($this->container, 'services', $propertyAccessor); - $key = isset($services['.container.private.twig']) ? '.container.private.twig' : 'twig'; - $services[$key] = $this->twig; - $propertyAccessor->setValue($this->container, $services); + $this->twig = Environment::fromTwigEnvironment($twig, $this, $this->container); } protected function getTwig(): Environment diff --git a/src/Pug/Symfony/Traits/Installer.php b/src/Pug/Symfony/Traits/Installer.php index c24e72a..ffc086e 100644 --- a/src/Pug/Symfony/Traits/Installer.php +++ b/src/Pug/Symfony/Traits/Installer.php @@ -1,5 +1,7 @@ isInteractive() || $io->askConfirmation($message); } - protected static function installSymfonyBundle(IOInterface $io, $dir, $bundle, $bundleClass, $proceedTask, &$flags) - { + protected static function installSymfonyBundle( + IOInterface $io, + string $dir, + string $bundle, + string $bundleClass, + callable $proceedTask, + int &$flags, + ): void { $appFile = $dir.'/config/bundles.php'; $contents = @file_get_contents($appFile) ?: ''; @@ -52,7 +60,7 @@ protected static function installSymfonyBundle(IOInterface $io, $dir, $bundle, $ * * @return bool */ - protected static function installInSymfony5($event, $dir) + protected static function installInSymfony5($event, $dir): bool { $io = $event->getIO(); $baseDirectory = __DIR__.'/../../../..'; diff --git a/src/Pug/Symfony/Traits/Options.php b/src/Pug/Symfony/Traits/Options.php index ede1b38..d8bf166 100644 --- a/src/Pug/Symfony/Traits/Options.php +++ b/src/Pug/Symfony/Traits/Options.php @@ -1,5 +1,7 @@ getRenderer(); - return method_exists($pug, 'hasOption') && !$pug->hasOption($name) - ? $default - : $pug->getOption($name); + return $pug->hasOption($name) ? $pug->getOption($name) : $default; } /** diff --git a/src/Pug/Symfony/Traits/PrivatePropertyAccessor.php b/src/Pug/Symfony/Traits/PrivatePropertyAccessor.php index 4971566..c3ed4ce 100644 --- a/src/Pug/Symfony/Traits/PrivatePropertyAccessor.php +++ b/src/Pug/Symfony/Traits/PrivatePropertyAccessor.php @@ -1,5 +1,7 @@ setAccessible(true); + try { + $propertyAccessor = new ReflectionProperty($object, $property); - return $propertyAccessor->getValue($object); + return $propertyAccessor->getValue($object); + } catch (ReflectionException) { + return null; + } } } diff --git a/src/Pug/Symfony/Traits/PugRenderer.php b/src/Pug/Symfony/Traits/PugRenderer.php new file mode 100644 index 0000000..0d1ea7d --- /dev/null +++ b/src/Pug/Symfony/Traits/PugRenderer.php @@ -0,0 +1,29 @@ +pug = $pug; + } + + public function render( + string|TemplateReferenceInterface $view, + array $parameters = [], + ?Response $response = null, + ): Response { + return $this->pug->renderResponse($view, $parameters, $response); + } +} diff --git a/src/Pug/Twig/Environment.php b/src/Pug/Twig/Environment.php index fdbb08e..6ebeedd 100644 --- a/src/Pug/Twig/Environment.php +++ b/src/Pug/Twig/Environment.php @@ -1,11 +1,13 @@ rootEnv->getRuntime($class); - } catch (RuntimeError $_) { + } catch (RuntimeError) { throw $error; } } } - public static function fromTwigEnvironment(TwigEnvironment $baseTwig, PugSymfonyEngine $pugSymfonyEngine, ContainerInterface $container) - { + public static function fromTwigEnvironment( + TwigEnvironment $baseTwig, + PugSymfonyEngine $pugSymfonyEngine, + ContainerInterface $container, + ): static { $twig = new static($baseTwig->getLoader(), [ 'debug' => $baseTwig->isDebug(), 'charset' => $baseTwig->getCharset(), @@ -118,17 +111,11 @@ public static function fromTwigEnvironment(TwigEnvironment $baseTwig, PugSymfony return $twig; } - /** - * @param PugSymfonyEngine $pugSymfonyEngine - */ - public function setPugSymfonyEngine(PugSymfonyEngine $pugSymfonyEngine) + public function setPugSymfonyEngine(PugSymfonyEngine $pugSymfonyEngine): void { $this->pugSymfonyEngine = $pugSymfonyEngine; } - /** - * @param ContainerInterface $container - */ public function setContainer(ContainerInterface $container): void { $this->container = $container; @@ -196,11 +183,7 @@ public function compileSource(Source $source): string public function loadTemplate(string $cls, string $name, int $index = null): Template { - if ($index !== null) { - $cls .= '___'.$index; - } - - $this->classNames[$name] = $cls; + $this->classNames[$name] = $cls.($index === null ? '' : '___'.$index); return parent::loadTemplate($cls, $name, $index); } @@ -226,7 +209,6 @@ public function getTemplateClass(string $name, int $index = null): string public function compileCode(TwigFunction $function, string $code) { $name = $function->getName(); - $arguments[] = $name; $parser = new Parser($this); $path = '__twig_function_'.$name.'_'.sha1($code).'.html.twig'; $stream = $this->tokenize(new Source($code, $path, $path)); From 61271afe21840c9d260da102b6892824d0807c76 Mon Sep 17 00:00:00 2001 From: KyleKatarn Date: Sat, 21 Jan 2023 23:29:45 +0100 Subject: [PATCH 04/17] Add extension in Twig if it's not yet registered --- src/Pug/Symfony/Traits/HelpersHandler.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Pug/Symfony/Traits/HelpersHandler.php b/src/Pug/Symfony/Traits/HelpersHandler.php index 1ef719b..39b272d 100644 --- a/src/Pug/Symfony/Traits/HelpersHandler.php +++ b/src/Pug/Symfony/Traits/HelpersHandler.php @@ -92,6 +92,7 @@ protected function getRendererOptions(): array } $srcDir = $projectDirectory.'/src'; + $assetsDirectories[] = $srcDir.'/Resources/assets'; $webDir = $projectDirectory.'/public'; $baseDir = isset($this->userOptions['baseDir']) ? $this->userOptions['baseDir'] @@ -318,7 +319,10 @@ protected function copyTwigFunctions(): void if (!$assetExtension) { $assetExtension = new AssetExtension(new Packages(new Package(new EmptyVersionStrategy()))); $twig->extensions[AssetExtension::class] = $assetExtension; - $twig->addExtension($assetExtension); + + if (!$twig->hasExtension(AssetExtension::class)) { + $twig->addExtension($assetExtension); + } } $helpers = [ @@ -331,7 +335,10 @@ protected function copyTwigFunctions(): void if (!isset($twig->extensions[$class])) { $twig->extensions[$class] = $helper; - $twig->addExtension($helper); + + if (!$twig->hasExtension($class)) { + $twig->addExtension($helper); + } } } From 78df332120bece2be556d84881f5efdb32307631 Mon Sep 17 00:00:00 2001 From: KyleKatarn Date: Sat, 21 Jan 2023 23:39:01 +0100 Subject: [PATCH 05/17] Fix code style --- README.md | 2 -- src/Pug/PugSymfonyBundle/PugSymfonyBundle.php | 4 +--- src/Pug/PugSymfonyBundle/config/pug.php | 7 +++---- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index c502ef7..4c8eaa3 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,6 @@ your **config/bundles.php** file: Pug\PugSymfonyBundle\PugSymfonyBundle::class => ['all' => true], ``` -Then - ## Usage Create Pug views by creating files with .pug extension diff --git a/src/Pug/PugSymfonyBundle/PugSymfonyBundle.php b/src/Pug/PugSymfonyBundle/PugSymfonyBundle.php index 4b6be9b..768ff65 100644 --- a/src/Pug/PugSymfonyBundle/PugSymfonyBundle.php +++ b/src/Pug/PugSymfonyBundle/PugSymfonyBundle.php @@ -5,10 +5,7 @@ namespace Pug\PugSymfonyBundle; use Pug\PugSymfonyBundle\Command\AssetsPublishCommand; -use Pug\PugSymfonyEngine; use Pug\Symfony\Traits\PrivatePropertyAccessor; -use ReflectionMethod; -use ReflectionNamedType; use Symfony\Component\Console\Application; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; @@ -23,6 +20,7 @@ public function build(ContainerBuilder $containerBuilder): void $containerBuilder->registerExtension($extension); $containerBuilder->loadFromExtension($extension->getAlias()); } + public function registerCommands(Application $application) { $application->addCommands([ diff --git a/src/Pug/PugSymfonyBundle/config/pug.php b/src/Pug/PugSymfonyBundle/config/pug.php index b4137f7..c067da2 100644 --- a/src/Pug/PugSymfonyBundle/config/pug.php +++ b/src/Pug/PugSymfonyBundle/config/pug.php @@ -8,8 +8,7 @@ $services = $configurator->services() ->defaults() ->autowire() - ->autoconfigure() - ; + ->autoconfigure(); $services->load('Pug\\', __DIR__ . '/../../*') ->exclude([ @@ -17,8 +16,8 @@ __DIR__ . '/../../PugSymfonyBundle', __DIR__ . '/../../Symfony', __DIR__ . '/../../Twig', - ]) - ; + ]); + $services->load('Pug\\PugSymfonyBundle\\Command\\', __DIR__ . '/../../PugSymfonyBundle/Command/*') ->public(); }; From 65b81db23c29a0a64dc637de4f6e9b4109257c0c Mon Sep 17 00:00:00 2001 From: KyleKatarn Date: Sat, 21 Jan 2023 23:45:03 +0100 Subject: [PATCH 06/17] Fix class compatibility --- tests/Pug/Composer/BaseIO.php | 81 ++++++++--------------------------- 1 file changed, 18 insertions(+), 63 deletions(-) diff --git a/tests/Pug/Composer/BaseIO.php b/tests/Pug/Composer/BaseIO.php index 6dba919..2a6ff3f 100644 --- a/tests/Pug/Composer/BaseIO.php +++ b/tests/Pug/Composer/BaseIO.php @@ -16,6 +16,7 @@ use Composer\Util\ProcessExecutor; use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; +use Stringable; abstract class BaseIO implements IOInterface, LoggerInterface { @@ -149,15 +150,10 @@ public function loadConfiguration(Config $config) /** * System is unusable. - * - * @param string $message - * @param array $context - * - * @return null */ - public function emergency($message, array $context = []) + public function emergency(Stringable|string $message, array $context = []): void { - return $this->log(LogLevel::EMERGENCY, $message, $context); + $this->log(LogLevel::EMERGENCY, $message, $context); } /** @@ -165,44 +161,29 @@ public function emergency($message, array $context = []) * * Example: Entire website down, database unavailable, etc. This should * trigger the SMS alerts and wake you up. - * - * @param string $message - * @param array $context - * - * @return null */ - public function alert($message, array $context = []) + public function alert(Stringable|string $message, array $context = []): void { - return $this->log(LogLevel::ALERT, $message, $context); + $this->log(LogLevel::ALERT, $message, $context); } /** * Critical conditions. * * Example: Application component unavailable, unexpected exception. - * - * @param string $message - * @param array $context - * - * @return null */ - public function critical($message, array $context = []) + public function critical(Stringable|string $message, array $context = []): void { - return $this->log(LogLevel::CRITICAL, $message, $context); + $this->log(LogLevel::CRITICAL, $message, $context); } /** * Runtime errors that do not require immediate action but should typically * be logged and monitored. - * - * @param string $message - * @param array $context - * - * @return null */ - public function error($message, array $context = []) + public function error(Stringable|string $message, array $context = []): void { - return $this->log(LogLevel::ERROR, $message, $context); + $this->log(LogLevel::ERROR, $message, $context); } /** @@ -210,68 +191,42 @@ public function error($message, array $context = []) * * Example: Use of deprecated APIs, poor use of an API, undesirable things * that are not necessarily wrong. - * - * @param string $message - * @param array $context - * - * @return null */ - public function warning($message, array $context = []) + public function warning(Stringable|string $message, array $context = []): void { - return $this->log(LogLevel::WARNING, $message, $context); + $this->log(LogLevel::WARNING, $message, $context); } /** * Normal but significant events. - * - * @param string $message - * @param array $context - * - * @return null */ - public function notice($message, array $context = []) + public function notice(Stringable|string $message, array $context = []): void { - return $this->log(LogLevel::NOTICE, $message, $context); + $this->log(LogLevel::NOTICE, $message, $context); } /** * Interesting events. * * Example: User logs in, SQL logs. - * - * @param string $message - * @param array $context - * - * @return null */ - public function info($message, array $context = []) + public function info(Stringable|string $message, array $context = []): void { - return $this->log(LogLevel::INFO, $message, $context); + $this->log(LogLevel::INFO, $message, $context); } /** * Detailed debug information. - * - * @param string $message - * @param array $context - * - * @return null */ - public function debug($message, array $context = []) + public function debug(Stringable|string $message, array $context = []): void { - return $this->log(LogLevel::DEBUG, $message, $context); + $this->log(LogLevel::DEBUG, $message, $context); } /** * Logs with an arbitrary level. - * - * @param mixed $level - * @param string $message - * @param array $context - * - * @return null */ - public function log($level, $message, array $context = []) + public function log($level, Stringable|string $message, array $context = []): void { if (in_array($level, [LogLevel::EMERGENCY, LogLevel::ALERT, LogLevel::CRITICAL, LogLevel::ERROR])) { $this->writeError(''.$message.'', true, self::NORMAL); From 6df5083beac2bb56e47e81f1b0902d4d1a6f3893 Mon Sep 17 00:00:00 2001 From: KyleKatarn Date: Sat, 21 Jan 2023 23:53:37 +0100 Subject: [PATCH 07/17] Update PHP version for GitHub Actions --- .github/workflows/coverage.yml | 2 +- .github/workflows/tests.yml | 2 +- src/Pug/PugSymfonyBundle/config/pug.php | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 018670d..1b43546 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -14,7 +14,7 @@ jobs: fail-fast: false matrix: os: ['ubuntu-latest'] - php: ['7.4'] + php: ['8.2'] name: PHP ${{ matrix.php }} - ${{ matrix.os }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 24f7bd5..2421cb7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,7 @@ jobs: fail-fast: false matrix: os: ['ubuntu-latest', 'windows-latest'] - php: ['7.2', '7.3', '7.4', '8.0', '8.1'] + php: ['8.1', '8.2', '8.3'] name: PHP ${{ matrix.php }} - ${{ matrix.os }} diff --git a/src/Pug/PugSymfonyBundle/config/pug.php b/src/Pug/PugSymfonyBundle/config/pug.php index c067da2..697c9dc 100644 --- a/src/Pug/PugSymfonyBundle/config/pug.php +++ b/src/Pug/PugSymfonyBundle/config/pug.php @@ -10,14 +10,14 @@ ->autowire() ->autoconfigure(); - $services->load('Pug\\', __DIR__ . '/../../*') + $services->load('Pug\\', __DIR__.'/../../*') ->exclude([ - __DIR__ . '/../../Exceptions', - __DIR__ . '/../../PugSymfonyBundle', - __DIR__ . '/../../Symfony', - __DIR__ . '/../../Twig', + __DIR__.'/../../Exceptions', + __DIR__.'/../../PugSymfonyBundle', + __DIR__.'/../../Symfony', + __DIR__.'/../../Twig', ]); - $services->load('Pug\\PugSymfonyBundle\\Command\\', __DIR__ . '/../../PugSymfonyBundle/Command/*') + $services->load('Pug\\PugSymfonyBundle\\Command\\', __DIR__.'/../../PugSymfonyBundle/Command/*') ->public(); }; From f45ea6d23a5fbe5644f9899a80b3071de8877663 Mon Sep 17 00:00:00 2001 From: KyleKatarn Date: Sat, 21 Jan 2023 23:58:05 +0100 Subject: [PATCH 08/17] Fix symfony test compatibility --- tests/Pug/PugSymfonyEngineTest.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/Pug/PugSymfonyEngineTest.php b/tests/Pug/PugSymfonyEngineTest.php index fefde50..69c8723 100644 --- a/tests/Pug/PugSymfonyEngineTest.php +++ b/tests/Pug/PugSymfonyEngineTest.php @@ -20,6 +20,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\DependencyInjection\Container; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage as BaseTokenStorage; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator as BaseLogoutUrlGenerator; use Twig\Error\LoaderError; use Twig\Loader\ArrayLoader; @@ -27,9 +28,9 @@ class TokenStorage extends BaseTokenStorage { - public function getToken(): string + public function getToken(): ?TokenInterface { - return 'the token'; + return new NullToken(); } } From 65e115b6f562dcc95e338dd820daf142c1dddd33 Mon Sep 17 00:00:00 2001 From: KyleKatarn Date: Sun, 22 Jan 2023 12:28:42 +0100 Subject: [PATCH 09/17] Adapt tests for Symfony 6 --- composer.json | 3 ++- src/Pug/Symfony/Traits/HelpersHandler.php | 23 +++++++++---------- tests/Pug/AbstractTestCase.php | 6 ++--- tests/Pug/PugSymfonyEngineTest.php | 14 +++++------ tests/Pug/TestCsrfTokenManager.php | 2 +- .../project-s5/config/packages/framework.yaml | 2 +- .../project-s5/config/packages/security.yaml | 1 - .../project-s5/src/Service/PugInterceptor.php | 12 ++++------ 8 files changed, 29 insertions(+), 34 deletions(-) diff --git a/composer.json b/composer.json index f9622fb..ccbc510 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,8 @@ }, "require-dev": { "phpunit/phpunit": "^8.5", - "symfony/symfony": "^6.0" + "symfony/symfony": "^6.0", + "monolog/monolog": "^3.2" }, "minimum-stability": "stable", "license": "MIT", diff --git a/src/Pug/Symfony/Traits/HelpersHandler.php b/src/Pug/Symfony/Traits/HelpersHandler.php index 39b272d..ff73f25 100644 --- a/src/Pug/Symfony/Traits/HelpersHandler.php +++ b/src/Pug/Symfony/Traits/HelpersHandler.php @@ -76,6 +76,11 @@ public function onNode(callable $nodeHandler): void $this->nodeHandler = $nodeHandler; } + public function getTwig(): Environment + { + return $this->twig; + } + protected function getRendererOptions(): array { if ($this->options === null) { @@ -94,9 +99,8 @@ protected function getRendererOptions(): array $srcDir = $projectDirectory.'/src'; $assetsDirectories[] = $srcDir.'/Resources/assets'; $webDir = $projectDirectory.'/public'; - $baseDir = isset($this->userOptions['baseDir']) - ? $this->userOptions['baseDir'] - : $this->crawlDirectories($srcDir, $assetsDirectories, $viewDirectories); + $baseDir = $this->userOptions['baseDir'] + ?? $this->crawlDirectories($srcDir, $assetsDirectories, $viewDirectories); $baseDir = $baseDir && file_exists($baseDir) ? realpath($baseDir) : $baseDir; $this->defaultTemplateDirectory = $baseDir; @@ -123,9 +127,10 @@ protected function getRendererOptions(): array (new Filesystem())->mkdir($cache); } - $options['paths'] = array_unique(array_filter($options['viewDirectories'], function ($path) use ($baseDir) { - return $path !== $baseDir; - })); + $options['paths'] = array_unique(array_filter( + $options['viewDirectories'], + static fn ($path) => $path !== $baseDir, + )); $this->options = $options; } @@ -298,16 +303,10 @@ protected function enhanceTwig($twig): void $this->twig = Environment::fromTwigEnvironment($twig, $this, $this->container); } - protected function getTwig(): Environment - { - return $this->twig; - } - protected function copyTwigFunctions(): void { $this->twigHelpers = []; $twig = $this->getTwig(); - $twig->env = $twig; $loader = new MixedLoader($twig->getLoader()); $twig->setLoader($loader); $this->share('twig', $twig); diff --git a/tests/Pug/AbstractTestCase.php b/tests/Pug/AbstractTestCase.php index 48a34c4..503b167 100644 --- a/tests/Pug/AbstractTestCase.php +++ b/tests/Pug/AbstractTestCase.php @@ -74,15 +74,15 @@ public function setUp(): void self::bootKernel(); - $this->addFormRenderer(static::$container); + $this->addFormRenderer(); } - protected function addFormRenderer(ContainerInterface $container) + protected function addFormRenderer() { require_once __DIR__.'/TestCsrfTokenManager.php'; /** @var Environment $twig */ - $twig = $container->get('twig'); + $twig = self::getContainer()->get('twig'); $csrfManager = new TestCsrfTokenManager(); $formEngine = new TwigRendererEngine(['form_div_layout.html.twig'], $twig); diff --git a/tests/Pug/PugSymfonyEngineTest.php b/tests/Pug/PugSymfonyEngineTest.php index 69c8723..19713cd 100644 --- a/tests/Pug/PugSymfonyEngineTest.php +++ b/tests/Pug/PugSymfonyEngineTest.php @@ -24,6 +24,7 @@ use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator as BaseLogoutUrlGenerator; use Twig\Error\LoaderError; use Twig\Loader\ArrayLoader; +use Twig\Loader\FilesystemLoader; use Twig\TwigFunction; class TokenStorage extends BaseTokenStorage @@ -230,7 +231,6 @@ public function testSecurityToken() $tokenStorage = new TokenStorage(); $container = self::$kernel->getContainer(); $reflectionProperty = new ReflectionProperty($container, 'services'); - $reflectionProperty->setAccessible(true); $services = $reflectionProperty->getValue($container); $services['security.token_storage'] = $tokenStorage; $reflectionProperty->setValue($container, $services); @@ -254,7 +254,6 @@ public function testLogoutHelper() if ($extension instanceof LogoutUrlExtension) { $reflectionClass = new \ReflectionClass('Symfony\Bridge\Twig\Extension\LogoutUrlExtension'); $reflectionProperty = $reflectionClass->getProperty('generator'); - $reflectionProperty->setAccessible(true); $reflectionProperty->setValue($extension, $generator); $generator = null; } @@ -495,7 +494,6 @@ public function testCustomBaseDir() { $container = self::$kernel->getContainer(); $property = new ReflectionProperty($container, 'parameters'); - $property->setAccessible(true); $value = $property->getValue($container); $value['pug']['prettyprint'] = false; $value['pug']['baseDir'] = __DIR__.'/../project-s5/templates-bis'; @@ -516,7 +514,6 @@ public function testCustomPaths() { $container = self::$kernel->getContainer(); $property = new ReflectionProperty($container, 'parameters'); - $property->setAccessible(true); $value = $property->getValue($container); $value['pug']['prettyprint'] = false; $value['pug']['paths'] = [__DIR__.'/../project-s5/templates-bis']; @@ -649,15 +646,16 @@ public function testRenderInterceptor() { $container = self::$kernel->getContainer(); $property = new ReflectionProperty($container, 'parameters'); - $property->setAccessible(true); $value = $property->getValue($container); $value['pug']['interceptors'] = [PugInterceptor::class]; $property->setValue($container, $value); $controller = new TestController(); $controller->setContainer($container); - $pugSymfony = new PugSymfonyEngine(self::$kernel); - /** @var Environment $twig */ - $twig = $container->get('twig'); + $twig = new Environment(new FilesystemLoader( + __DIR__.'/../project-s5/templates/', + )); + $pugSymfony = new PugSymfonyEngine(self::$kernel, $twig); + $twig->setPugSymfonyEngine($pugSymfony); self::assertSame(Environment::class, trim($twig->render('new-var.pug'))); diff --git a/tests/Pug/TestCsrfTokenManager.php b/tests/Pug/TestCsrfTokenManager.php index 4a30891..2deb39c 100644 --- a/tests/Pug/TestCsrfTokenManager.php +++ b/tests/Pug/TestCsrfTokenManager.php @@ -13,7 +13,7 @@ public function __construct(TokenGeneratorInterface $generator = null, TokenStor { } - public function getToken(string $tokenId) + public function getToken(string $tokenId): CsrfToken { if ($tokenId === 'special') { return new CsrfToken('special', 'the token'); diff --git a/tests/project-s5/config/packages/framework.yaml b/tests/project-s5/config/packages/framework.yaml index e408b96..05a4fa3 100644 --- a/tests/project-s5/config/packages/framework.yaml +++ b/tests/project-s5/config/packages/framework.yaml @@ -6,7 +6,7 @@ framework: csrf_protection: true validation: { enable_annotations: true } session: - storage_id: session.storage.filesystem + handler_id: null parameters: pug: diff --git a/tests/project-s5/config/packages/security.yaml b/tests/project-s5/config/packages/security.yaml index 375954b..cbb3e9a 100644 --- a/tests/project-s5/config/packages/security.yaml +++ b/tests/project-s5/config/packages/security.yaml @@ -3,7 +3,6 @@ security: in_memory: { memory: ~ } firewalls: main: - anonymous: ~ logout: path: /logout target: / \ No newline at end of file diff --git a/tests/project-s5/src/Service/PugInterceptor.php b/tests/project-s5/src/Service/PugInterceptor.php index c36b6dc..5118451 100644 --- a/tests/project-s5/src/Service/PugInterceptor.php +++ b/tests/project-s5/src/Service/PugInterceptor.php @@ -2,6 +2,7 @@ namespace App\Service; +use Pug\PugSymfonyEngine; use Pug\Symfony\Contracts\InterceptorInterface; use Pug\Symfony\RenderEvent; use Symfony\Contracts\EventDispatcher\Event; @@ -9,21 +10,18 @@ class PugInterceptor implements InterceptorInterface { - /** - * @var Environment - */ - private $twig; + private PugSymfonyEngine $pug; - public function __construct(Environment $twig) + public function __construct(PugSymfonyEngine $pug) { - $this->twig = $twig; + $this->pug = $pug; } public function intercept(Event $event) { if ($event instanceof RenderEvent) { $locals = $event->getLocals(); - $locals['newVar'] = get_class($this->twig); + $locals['newVar'] = get_class($this->pug->getTwig()); $event->setLocals($locals); if ($event->getEngine()->getOptionDefault('special-thing', false)) { From d9a6b9cfc24d997a1c97195248dd837b2796020d Mon Sep 17 00:00:00 2001 From: KyleKatarn Date: Sun, 22 Jan 2023 14:15:05 +0100 Subject: [PATCH 10/17] Use getter to create PugSymfonyEngine --- tests/Pug/AbstractTestCase.php | 27 +++++++- .../Command/AssetsPublishCommandTest.php | 2 +- tests/Pug/PugSymfonyEngineTest.php | 67 +++++++++---------- .../project-s5/src/Service/PugInterceptor.php | 6 +- 4 files changed, 59 insertions(+), 43 deletions(-) diff --git a/tests/Pug/AbstractTestCase.php b/tests/Pug/AbstractTestCase.php index 503b167..9670363 100644 --- a/tests/Pug/AbstractTestCase.php +++ b/tests/Pug/AbstractTestCase.php @@ -2,12 +2,15 @@ namespace Pug\Tests; +use Pug\PugSymfonyEngine; use Pug\Twig\Environment; use Symfony\Bridge\Twig\Form\TwigRendererEngine; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Form\FormRenderer; +use Symfony\Component\HttpKernel\KernelInterface; +use Twig\Loader\FilesystemLoader; use Twig\RuntimeLoader\FactoryRuntimeLoader; abstract class AbstractTestCase extends KernelTestCase @@ -16,6 +19,26 @@ abstract class AbstractTestCase extends KernelTestCase protected static $cachePath = __DIR__.'/../project-s5/var/cache/test'; + protected ?Environment $twig = null; + + protected ?PugSymfonyEngine $pugSymfony = null; + + protected function getTwigEnvironment(): Environment + { + return $this->twig ??= new Environment(new FilesystemLoader( + __DIR__.'/../project-s5/templates/', + )); + } + + protected function getPugSymfonyEngine(?KernelInterface $kernel = null): PugSymfonyEngine + { + $twig = $this->getTwigEnvironment(); + $this->pugSymfony ??= new PugSymfonyEngine($kernel ?? self::$kernel, $twig); + $twig->setPugSymfonyEngine($this->pugSymfony); + + return $this->pugSymfony; + } + private static function getConfigFiles(): array { return [ @@ -77,12 +100,12 @@ public function setUp(): void $this->addFormRenderer(); } - protected function addFormRenderer() + protected function addFormRenderer(\Psr\Container\ContainerInterface $container) { require_once __DIR__.'/TestCsrfTokenManager.php'; /** @var Environment $twig */ - $twig = self::getContainer()->get('twig'); + $twig = ($container ?? self::getContainer())->get('twig'); $csrfManager = new TestCsrfTokenManager(); $formEngine = new TwigRendererEngine(['form_div_layout.html.twig'], $twig); diff --git a/tests/Pug/PugSymfonyBundle/Command/AssetsPublishCommandTest.php b/tests/Pug/PugSymfonyBundle/Command/AssetsPublishCommandTest.php index 0b8ff09..fddd19b 100644 --- a/tests/Pug/PugSymfonyBundle/Command/AssetsPublishCommandTest.php +++ b/tests/Pug/PugSymfonyBundle/Command/AssetsPublishCommandTest.php @@ -19,7 +19,7 @@ public function testCommand() self::$kernel->boot(); $application = new Application(self::$kernel); - $application->add(new AssetsPublishCommand(new PugSymfonyEngine(self::$kernel))); + $application->add(new AssetsPublishCommand($this->getPugSymfonyEngine())); // Convert PHP style files to JS style $customHelperFile = __DIR__.'/../../../project-s5/templates/custom-helper.pug'; diff --git a/tests/Pug/PugSymfonyEngineTest.php b/tests/Pug/PugSymfonyEngineTest.php index 19713cd..d95a03c 100644 --- a/tests/Pug/PugSymfonyEngineTest.php +++ b/tests/Pug/PugSymfonyEngineTest.php @@ -125,7 +125,7 @@ public function testRequireTwig() $propertyAccessor->setValue($container, $services); } - new PugSymfonyEngine(self::$kernel); + $this->getPugSymfonyEngine(); } /** @@ -139,7 +139,7 @@ public function testPreRenderPhp() ]); }); $kernel->boot(); - $pugSymfony = new PugSymfonyEngine($kernel); + $pugSymfony = $this->getPugSymfonyEngine(); self::assertSame('

/foo

', $pugSymfony->renderString('p=asset("/foo")')); self::assertSame( @@ -150,7 +150,7 @@ public function testPreRenderPhp() public function testMixin() { - $pugSymfony = new PugSymfonyEngine(self::$kernel); + $pugSymfony = $this->getPugSymfonyEngine(); $pugSymfony->setOption('expressionLanguage', 'js'); $pugSymfony->setOption('prettyprint', false); @@ -171,7 +171,7 @@ public function testPreRenderJs() ]); }); $kernel->boot(); - $pugSymfony = new PugSymfonyEngine($kernel); + $pugSymfony = $this->getPugSymfonyEngine(); self::assertSame('

/foo

', $pugSymfony->renderString('p=asset("/foo")')); } @@ -184,7 +184,7 @@ public function testPreRenderFile() ]); }); $kernel->boot(); - $pugSymfony = new PugSymfonyEngine($kernel); + $pugSymfony = $this->getPugSymfonyEngine(); self::assertSame(implode('', [ '', @@ -207,7 +207,7 @@ public function testPreRenderCsrfToken() ]); }); $kernel->boot(); - $pugSymfony = new PugSymfonyEngine($kernel); + $pugSymfony = $this->getPugSymfonyEngine($kernel); $this->addFormRenderer($kernel->getContainer()); self::assertSame('

Hello

', $pugSymfony->renderString('p Hello')); @@ -217,7 +217,7 @@ public function testPreRenderCsrfToken() public function testGetEngine() { - $pugSymfony = new PugSymfonyEngine(self::$kernel); + $pugSymfony = $this->getPugSymfonyEngine(); self::assertInstanceOf(Pug::class, $pugSymfony->getRenderer()); } @@ -234,7 +234,7 @@ public function testSecurityToken() $services = $reflectionProperty->getValue($container); $services['security.token_storage'] = $tokenStorage; $reflectionProperty->setValue($container, $services); - $pugSymfony = new PugSymfonyEngine(self::$kernel); + $pugSymfony = $this->getPugSymfonyEngine(); $this->addFormRenderer($container); self::assertSame('

the token

', trim($pugSymfony->render('token.pug'))); @@ -265,7 +265,7 @@ public function testLogoutHelper() self::$kernel->getContainer()->set('templating.helper.logout_url', $logoutUrlHelper); } - $pugSymfony = new PugSymfonyEngine(self::$kernel); + $pugSymfony = $this->getPugSymfonyEngine(); self::assertSame('', trim($pugSymfony->render('logout.pug'))); } @@ -275,7 +275,7 @@ public function testLogoutHelper() */ public function testFormHelpers() { - $pugSymfony = new PugSymfonyEngine(self::$kernel); + $pugSymfony = $this->getPugSymfonyEngine(); $container = self::$kernel->getContainer(); $this->addFormRenderer($container); $controller = new TestController(); @@ -328,7 +328,7 @@ public function testServicesSharing() /** @var Environment $twig */ $twig = self::$kernel->getContainer()->get('twig'); $twig->addGlobal('t', self::$kernel->getContainer()->get('translator')); - $pugSymfony = new PugSymfonyEngine(self::$kernel); + $pugSymfony = $this->getPugSymfonyEngine(); self::assertSame('

Hello Bob

', trim($pugSymfony->renderString('p=t.trans("Hello %name%", {"%name%": "Bob"})'))); } @@ -339,7 +339,7 @@ public function testServicesSharing() public function testTwigGlobals() { $container = self::$kernel->getContainer(); - $pugSymfony = new PugSymfonyEngine(self::$kernel); + $pugSymfony = $this->getPugSymfonyEngine(); /** @var Environment $twig */ $twig = $container->get('twig'); $twig->addGlobal('answer', 42); @@ -349,7 +349,7 @@ public function testTwigGlobals() public function testOptions() { - $pugSymfony = new PugSymfonyEngine(self::$kernel); + $pugSymfony = $this->getPugSymfonyEngine(); $pugSymfony->setOptions(['foo' => 'bar']); self::assertSame('bar', $pugSymfony->getOptionDefault('foo')); @@ -360,7 +360,7 @@ public function testOptions() */ public function testBundleView() { - $pugSymfony = new PugSymfonyEngine(self::$kernel); + $pugSymfony = $this->getPugSymfonyEngine(); self::assertSame('

Hello

', trim($pugSymfony->render('TestBundle::bundle.pug', ['text' => 'Hello']))); self::assertSame('
World
', trim($pugSymfony->render('TestBundle:directory:file.pug'))); @@ -371,7 +371,7 @@ public function testBundleView() */ public function testAssetHelperPhp() { - $pugSymfony = new PugSymfonyEngine(self::$kernel); + $pugSymfony = $this->getPugSymfonyEngine(); $pugSymfony->setOption('expressionLanguage', 'php'); self::assertSame( @@ -396,7 +396,7 @@ public function testAssetHelperPhp() */ public function testAssetHelperJs() { - $pugSymfony = new PugSymfonyEngine(self::$kernel); + $pugSymfony = $this->getPugSymfonyEngine(); $pugSymfony->setOption('expressionLanguage', 'js'); self::assertSame( @@ -421,7 +421,7 @@ public function testAssetHelperJs() */ public function testFilter() { - $pugSymfony = new PugSymfonyEngine(self::$kernel); + $pugSymfony = $this->getPugSymfonyEngine(); self::assertFalse($pugSymfony->hasFilter('upper')); @@ -439,7 +439,7 @@ public function testFilter() public function testExists() { - $pugSymfony = new PugSymfonyEngine(self::$kernel); + $pugSymfony = $this->getPugSymfonyEngine(); self::assertTrue($pugSymfony->exists('logout.pug')); self::assertFalse($pugSymfony->exists('login.pug')); @@ -448,7 +448,7 @@ public function testExists() public function testSupports() { - $pugSymfony = new PugSymfonyEngine(self::$kernel); + $pugSymfony = $this->getPugSymfonyEngine(); self::assertTrue($pugSymfony->supports('foo-bar.pug')); self::assertTrue($pugSymfony->supports('foo-bar.jade')); @@ -461,7 +461,7 @@ public function testSupports() */ public function testCustomOptions() { - $pugSymfony = new PugSymfonyEngine(self::$kernel); + $pugSymfony = $this->getPugSymfonyEngine(); $pugSymfony->setOptions([ 'prettyprint' => ' ', 'cache' => null, @@ -498,7 +498,7 @@ public function testCustomBaseDir() $value['pug']['prettyprint'] = false; $value['pug']['baseDir'] = __DIR__.'/../project-s5/templates-bis'; $property->setValue($container, $value); - $pugSymfony = new PugSymfonyEngine(self::$kernel); + $pugSymfony = $this->getPugSymfonyEngine(); self::assertSame( '

', @@ -518,7 +518,7 @@ public function testCustomPaths() $value['pug']['prettyprint'] = false; $value['pug']['paths'] = [__DIR__.'/../project-s5/templates-bis']; $property->setValue($container, $value); - $pugSymfony = new PugSymfonyEngine(self::$kernel); + $pugSymfony = $this->getPugSymfonyEngine(); self::assertSame( '

alt

', @@ -535,7 +535,7 @@ public function testMissingDir() $kernel = new TestKernel(); $kernel->boot(); $kernel->setProjectDirectory(__DIR__.'/../project'); - $pugSymfony = new PugSymfonyEngine($kernel); + $pugSymfony = $this->getPugSymfonyEngine(); self::assertSame( '

Page

', @@ -548,7 +548,7 @@ public function testForbidThis() self::expectException(ReservedVariable::class); self::expectExceptionMessage('"this" is a reserved variable name, you can\'t overwrite it.'); - (new PugSymfonyEngine(self::$kernel))->render('p.pug', ['this' => 42]); + $this->getPugSymfonyEngine()->render('p.pug', ['this' => 42]); } public function testForbidBlocks() @@ -556,12 +556,12 @@ public function testForbidBlocks() self::expectException(ReservedVariable::class); self::expectExceptionMessage('"blocks" is a reserved variable name, you can\'t overwrite it.'); - (new PugSymfonyEngine(self::$kernel))->render('p.pug', ['blocks' => 42]); + $this->getPugSymfonyEngine()->render('p.pug', ['blocks' => 42]); } public function testIssue11BackgroundImage() { - $pugSymfony = new PugSymfonyEngine(self::$kernel); + $pugSymfony = $this->getPugSymfonyEngine(); $pugSymfony->setOption('expressionLanguage', 'js'); $html = trim($pugSymfony->render('background-image.pug', ['image' => 'foo'])); $html = preg_replace('/]+)>/', '', $html); @@ -596,7 +596,7 @@ public function testCompileException() self::expectException(RuntimeException::class); self::expectExceptionMessage('Unable to compile void function.'); - new PugSymfonyEngine(self::$kernel); + $this->getPugSymfonyEngine(); /** @var Environment $twig */ $twig = self::$kernel->getContainer()->get('twig'); $twig->compileCode(new TwigFunction('void'), '{# comment #}'); @@ -604,7 +604,7 @@ public function testCompileException() public function testLoadTemplate() { - new PugSymfonyEngine(self::$kernel); + $this->getPugSymfonyEngine(); /** @var Environment $twig */ $twig = self::$kernel->getContainer()->get('twig'); @@ -621,7 +621,7 @@ public function testLoadTemplate() public function testDefaultOption() { - $pugSymfony = new PugSymfonyEngine(self::$kernel); + $pugSymfony = $this->getPugSymfonyEngine(); self::assertSame(42, $pugSymfony->getOptionDefault('does-not-exist', 42)); @@ -632,7 +632,7 @@ public function testDefaultOption() public function testGetSharedVariables() { - $pugSymfony = new PugSymfonyEngine(self::$kernel); + $pugSymfony = $this->getPugSymfonyEngine(); $pugSymfony->share('foo', 'bar'); self::assertSame('bar', $pugSymfony->getSharedVariables()['foo']); @@ -651,11 +651,8 @@ public function testRenderInterceptor() $property->setValue($container, $value); $controller = new TestController(); $controller->setContainer($container); - $twig = new Environment(new FilesystemLoader( - __DIR__.'/../project-s5/templates/', - )); - $pugSymfony = new PugSymfonyEngine(self::$kernel, $twig); - $twig->setPugSymfonyEngine($pugSymfony); + $twig = $this->getTwigEnvironment(); + $pugSymfony = $this->getPugSymfonyEngine(); self::assertSame(Environment::class, trim($twig->render('new-var.pug'))); diff --git a/tests/project-s5/src/Service/PugInterceptor.php b/tests/project-s5/src/Service/PugInterceptor.php index 5118451..0704207 100644 --- a/tests/project-s5/src/Service/PugInterceptor.php +++ b/tests/project-s5/src/Service/PugInterceptor.php @@ -6,15 +6,11 @@ use Pug\Symfony\Contracts\InterceptorInterface; use Pug\Symfony\RenderEvent; use Symfony\Contracts\EventDispatcher\Event; -use Twig\Environment; class PugInterceptor implements InterceptorInterface { - private PugSymfonyEngine $pug; - - public function __construct(PugSymfonyEngine $pug) + public function __construct(private PugSymfonyEngine $pug) { - $this->pug = $pug; } public function intercept(Event $event) From 0fa499db79b4ce2679f05c4628ca20cb3de9cb4d Mon Sep 17 00:00:00 2001 From: KyleKatarn Date: Sun, 22 Jan 2023 14:36:19 +0100 Subject: [PATCH 11/17] Fix dynamic properties --- src/Pug/Symfony/Traits/HelpersHandler.php | 1 + src/Pug/Twig/Environment.php | 2 ++ tests/Pug/AbstractTestCase.php | 4 ++-- .../Command/AssetsPublishCommandTest.php | 1 - tests/Pug/PugSymfonyEngineTest.php | 10 +++++----- tests/Pug/TestKernel.php | 14 +++++--------- 6 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/Pug/Symfony/Traits/HelpersHandler.php b/src/Pug/Symfony/Traits/HelpersHandler.php index ff73f25..2c8cf6a 100644 --- a/src/Pug/Symfony/Traits/HelpersHandler.php +++ b/src/Pug/Symfony/Traits/HelpersHandler.php @@ -307,6 +307,7 @@ protected function copyTwigFunctions(): void { $this->twigHelpers = []; $twig = $this->getTwig(); + $twig->env = $twig; $loader = new MixedLoader($twig->getLoader()); $twig->setLoader($loader); $this->share('twig', $twig); diff --git a/src/Pug/Twig/Environment.php b/src/Pug/Twig/Environment.php index 6ebeedd..0214fe3 100644 --- a/src/Pug/Twig/Environment.php +++ b/src/Pug/Twig/Environment.php @@ -35,6 +35,8 @@ class Environment extends TwigEnvironment public TwigEnvironment $rootEnv; + public TwigEnvironment $env; + public function __construct(LoaderInterface $loader, $options = []) { parent::__construct($loader, $options); diff --git a/tests/Pug/AbstractTestCase.php b/tests/Pug/AbstractTestCase.php index 9670363..577a962 100644 --- a/tests/Pug/AbstractTestCase.php +++ b/tests/Pug/AbstractTestCase.php @@ -2,11 +2,11 @@ namespace Pug\Tests; +use Psr\Container\ContainerInterface; use Pug\PugSymfonyEngine; use Pug\Twig\Environment; use Symfony\Bridge\Twig\Form\TwigRendererEngine; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; -use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Form\FormRenderer; use Symfony\Component\HttpKernel\KernelInterface; @@ -100,7 +100,7 @@ public function setUp(): void $this->addFormRenderer(); } - protected function addFormRenderer(\Psr\Container\ContainerInterface $container) + protected function addFormRenderer(?ContainerInterface $container = null) { require_once __DIR__.'/TestCsrfTokenManager.php'; diff --git a/tests/Pug/PugSymfonyBundle/Command/AssetsPublishCommandTest.php b/tests/Pug/PugSymfonyBundle/Command/AssetsPublishCommandTest.php index fddd19b..c2317b6 100644 --- a/tests/Pug/PugSymfonyBundle/Command/AssetsPublishCommandTest.php +++ b/tests/Pug/PugSymfonyBundle/Command/AssetsPublishCommandTest.php @@ -3,7 +3,6 @@ namespace Pug\Tests\PugSymfonyBundle\Command; use Pug\PugSymfonyBundle\Command\AssetsPublishCommand; -use Pug\PugSymfonyEngine; use Pug\Tests\AbstractTestCase; use Pug\Tests\TestKernel; use Symfony\Bundle\FrameworkBundle\Console\Application; diff --git a/tests/Pug/PugSymfonyEngineTest.php b/tests/Pug/PugSymfonyEngineTest.php index d95a03c..35f7e78 100644 --- a/tests/Pug/PugSymfonyEngineTest.php +++ b/tests/Pug/PugSymfonyEngineTest.php @@ -24,7 +24,6 @@ use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator as BaseLogoutUrlGenerator; use Twig\Error\LoaderError; use Twig\Loader\ArrayLoader; -use Twig\Loader\FilesystemLoader; use Twig\TwigFunction; class TokenStorage extends BaseTokenStorage @@ -133,7 +132,7 @@ public function testRequireTwig() */ public function testPreRenderPhp() { - $kernel = new TestKernel(function (Container $container) { + $kernel = new TestKernel(static function (Container $container) { $container->setParameter('pug', [ 'expressionLanguage' => 'php', ]); @@ -165,7 +164,7 @@ public function testMixin() */ public function testPreRenderJs() { - $kernel = new TestKernel(function (Container $container) { + $kernel = new TestKernel(static function (Container $container) { $container->setParameter('pug', [ 'expressionLanguage' => 'js', ]); @@ -178,13 +177,14 @@ public function testPreRenderJs() public function testPreRenderFile() { - $kernel = new TestKernel(function (Container $container) { + $kernel = new TestKernel(static function (Container $container) { $container->setParameter('pug', [ 'expressionLanguage' => 'js', ]); }); $kernel->boot(); $pugSymfony = $this->getPugSymfonyEngine(); + $pugSymfony->setOption('prettyprint', false); self::assertSame(implode('', [ '', @@ -201,7 +201,7 @@ public function testPreRenderFile() */ public function testPreRenderCsrfToken() { - $kernel = new TestKernel(function (Container $container) { + $kernel = new TestKernel(static function (Container $container) { $container->setParameter('pug', [ 'expressionLanguage' => 'js', ]); diff --git a/tests/Pug/TestKernel.php b/tests/Pug/TestKernel.php index f97826f..20030c8 100644 --- a/tests/Pug/TestKernel.php +++ b/tests/Pug/TestKernel.php @@ -9,19 +9,15 @@ class TestKernel extends Kernel { - /** - * @var Closure - */ - private $containerConfigurator; + private Closure $containerConfigurator; - /** - * @var string - */ - private $projectDirectory; + private ?string $projectDirectory = null; + + private string $rootDir; public function __construct(Closure $containerConfigurator = null, $environment = 'test', $debug = false) { - $this->containerConfigurator = $containerConfigurator ?? function () { + $this->containerConfigurator = $containerConfigurator ?? static function () { }; parent::__construct($environment, $debug); From 74d872e97d86c7e6e4fa31c516d61b72d421d89c Mon Sep 17 00:00:00 2001 From: KyleKatarn Date: Sun, 22 Jan 2023 14:49:49 +0100 Subject: [PATCH 12/17] Fix asset helper test --- tests/Pug/PugSymfonyEngineTest.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/Pug/PugSymfonyEngineTest.php b/tests/Pug/PugSymfonyEngineTest.php index 35f7e78..aa8dcad 100644 --- a/tests/Pug/PugSymfonyEngineTest.php +++ b/tests/Pug/PugSymfonyEngineTest.php @@ -377,17 +377,20 @@ public function testAssetHelperPhp() self::assertSame( '
'."\n". '
', preg_replace( '/<', - str_replace(['\'assets/', "\r"], ['\'/assets/', ''], trim($pugSymfony->render('style-php.pug'))) - ) + strtr(trim($pugSymfony->render('style-php.pug')), [ + "\r" => '', + ''' => "'", + ]), + ), ); } From b0aefcd6ba18ed86826024a0158bd8a0e9095bc6 Mon Sep 17 00:00:00 2001 From: KyleKatarn Date: Sun, 22 Jan 2023 19:27:52 +0100 Subject: [PATCH 13/17] Fix injection for stack and context --- src/Pug/PugSymfonyEngine.php | 7 +- src/Pug/Symfony/Traits/HelpersHandler.php | 11 +- src/Pug/Twig/Environment.php | 2 +- tests/Pug/AbstractController.php | 7 -- tests/Pug/AbstractTestCase.php | 39 ++++--- tests/Pug/PugSymfonyEngineTest.php | 121 +++++++++++----------- 6 files changed, 95 insertions(+), 92 deletions(-) delete mode 100644 tests/Pug/AbstractController.php diff --git a/src/Pug/PugSymfonyEngine.php b/src/Pug/PugSymfonyEngine.php index ea0a927..421e211 100644 --- a/src/Pug/PugSymfonyEngine.php +++ b/src/Pug/PugSymfonyEngine.php @@ -21,8 +21,10 @@ use Pug\Symfony\Traits\Options; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Form\FormInterface; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Component\Routing\RequestContext; use Symfony\Component\Templating\EngineInterface; use Symfony\Component\Templating\TemplateReferenceInterface; use Twig\Environment as TwigEnvironment; @@ -50,11 +52,12 @@ class PugSymfonyEngine implements EngineInterface, InstallerInterface protected $defaultTemplateDirectory; public function __construct( - KernelInterface $kernel, + protected readonly KernelInterface $kernel, TwigEnvironment $twig, + private readonly ?RequestStack $stack = null, + private readonly ?RequestContext $context = null, ) { $container = $kernel->getContainer(); - $this->kernel = $kernel; $this->container = $container; $this->userOptions = ($this->container->hasParameter('pug') ? $this->container->getParameter('pug') : null) ?: []; $this->enhanceTwig($twig); diff --git a/src/Pug/Symfony/Traits/HelpersHandler.php b/src/Pug/Symfony/Traits/HelpersHandler.php index 2c8cf6a..e2e3764 100644 --- a/src/Pug/Symfony/Traits/HelpersHandler.php +++ b/src/Pug/Symfony/Traits/HelpersHandler.php @@ -21,8 +21,6 @@ use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\UrlHelper; -use Symfony\Component\HttpKernel\Kernel; -use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\Routing\RequestContext; use Twig\Environment as TwigEnvironment; use Twig\Extension\ExtensionInterface; @@ -40,8 +38,6 @@ trait HelpersHandler protected Environment $twig; - protected Kernel|KernelInterface $kernel; - protected ?Pug $pug = null; protected array $userOptions = []; @@ -353,12 +349,13 @@ protected function copyTwigFunctions(): void protected function getHttpFoundationExtension(): HttpFoundationExtension { /* @var RequestStack $stack */ - $stack = $this->container->get('request_stack'); + $stack = $this->stack ?? $this->container->get('request_stack'); /* @var RequestContext $context */ - $context = $this->container->has('router.request_context') + $context = $this->context ?? ($this->container->has('router.request_context') ? $this->container->get('router.request_context') - : $this->container->get('router')->getContext(); + : $this->container->get('router')->getContext() + ); return new HttpFoundationExtension(new UrlHelper($stack, $context)); } diff --git a/src/Pug/Twig/Environment.php b/src/Pug/Twig/Environment.php index 0214fe3..90a78b3 100644 --- a/src/Pug/Twig/Environment.php +++ b/src/Pug/Twig/Environment.php @@ -59,7 +59,7 @@ public function getRuntime(string $class) try { return parent::getRuntime($class); } catch (RuntimeError $error) { - if (!$this->rootEnv) { + if (!($this->rootEnv ?? null)) { throw $error; } diff --git a/tests/Pug/AbstractController.php b/tests/Pug/AbstractController.php deleted file mode 100644 index f4ed673..0000000 --- a/tests/Pug/AbstractController.php +++ /dev/null @@ -1,7 +0,0 @@ -twig ??= new Environment(new FilesystemLoader( - __DIR__.'/../project-s5/templates/', - )); + if (!isset($this->twig)) { + $this->twig = new Environment(new FilesystemLoader( + __DIR__.'/../project-s5/templates/', + )); + $this->twig->addExtension(new CsrfExtension()); + $this->twig->addRuntimeLoader(new FactoryRuntimeLoader([ + CsrfRuntime::class => static fn () => new CsrfRuntime(new TestCsrfTokenManager()), + ])); + } + + return $this->twig; } protected function getPugSymfonyEngine(?KernelInterface $kernel = null): PugSymfonyEngine { $twig = $this->getTwigEnvironment(); - $this->pugSymfony ??= new PugSymfonyEngine($kernel ?? self::$kernel, $twig); + $this->pugSymfony ??= new PugSymfonyEngine( + $kernel ?? self::$kernel, + $twig, + new RequestStack(), + new RequestContext(), + ); $twig->setPugSymfonyEngine($this->pugSymfony); return $this->pugSymfony; @@ -100,19 +118,16 @@ public function setUp(): void $this->addFormRenderer(); } - protected function addFormRenderer(?ContainerInterface $container = null) + protected function addFormRenderer() { require_once __DIR__.'/TestCsrfTokenManager.php'; - /** @var Environment $twig */ - $twig = ($container ?? self::getContainer())->get('twig'); + $twig = $this->getTwigEnvironment(); $csrfManager = new TestCsrfTokenManager(); $formEngine = new TwigRendererEngine(['form_div_layout.html.twig'], $twig); $twig->addRuntimeLoader(new FactoryRuntimeLoader([ - FormRenderer::class => static function () use ($formEngine, $csrfManager) { - return new FormRenderer($formEngine, $csrfManager); - }, + FormRenderer::class => static fn () => new FormRenderer($formEngine, $csrfManager), ])); } } diff --git a/tests/Pug/PugSymfonyEngineTest.php b/tests/Pug/PugSymfonyEngineTest.php index aa8dcad..3ed99b9 100644 --- a/tests/Pug/PugSymfonyEngineTest.php +++ b/tests/Pug/PugSymfonyEngineTest.php @@ -5,23 +5,32 @@ use App\Service\PugInterceptor; use DateTime; use ErrorException; -use InvalidArgumentException; +use Phug\CompilerException; +use Phug\Util\SourceLocation; use Pug\Exceptions\ReservedVariable; use Pug\Filter\AbstractFilter; use Pug\Pug; use Pug\PugSymfonyEngine; use Pug\Symfony\MixedLoader; +use Pug\Symfony\Traits\HelpersHandler; use Pug\Symfony\Traits\PrivatePropertyAccessor; use Pug\Twig\Environment; use ReflectionException; use ReflectionProperty; use RuntimeException; use Symfony\Bridge\Twig\Extension\LogoutUrlExtension; -use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\Form\Extension\Core\Type\DateType; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\FormFactory; +use Symfony\Component\Form\FormRegistry; +use Symfony\Component\Form\ResolvedFormTypeFactory; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage as BaseTokenStorage; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator as BaseLogoutUrlGenerator; +use Symfony\Component\Translation\Translator; use Twig\Error\LoaderError; use Twig\Loader\ArrayLoader; use Twig\TwigFunction; @@ -83,27 +92,17 @@ public function setDueDate(DateTime $dueDate = null) } } -if (!class_exists('Symfony\Bundle\FrameworkBundle\Controller\AbstractController')) { - include __DIR__.'/AbstractController.php'; -} - -class TestController extends AbstractController +class TestController { public function index() { - try { - return $this->createFormBuilder(new Task()) - ->add('name', 'Symfony\Component\Form\Extension\Core\Type\TextType') - ->add('dueDate', 'Symfony\Component\Form\Extension\Core\Type\DateType') - ->add('save', 'Symfony\Component\Form\Extension\Core\Type\SubmitType', ['label' => 'Foo']) - ->getForm(); - } catch (InvalidArgumentException $e) { - return $this->createFormBuilder(new Task()) - ->add('name', 'text') - ->add('dueDate', 'date') - ->add('save', 'submit', ['label' => 'Foo']) - ->getForm(); - } + $factory = new FormFactory(new FormRegistry([], new ResolvedFormTypeFactory())); + + return $factory->createBuilder(FormType::class, new Task()) + ->add('name', TextType::class) + ->add('dueDate', DateType::class) + ->add('save', SubmitType::class, ['label' => 'Foo']) + ->getForm(); } } @@ -116,15 +115,16 @@ public function testRequireTwig() self::expectException(RuntimeException::class); self::expectExceptionMessage('Twig needs to be configured.'); - $container = self::$kernel->getContainer(); - foreach (['services', 'aliases', 'fileMap', 'methodMap'] as $name) { - /** @var ReflectionProperty $propertyAccessor */ - $services = static::getPrivateProperty($container, $name, $propertyAccessor); - unset($services['twig']); - $propertyAccessor->setValue($container, $services); - } + $object = new class () { + use HelpersHandler; - $this->getPugSymfonyEngine(); + public function wrongEnhance(): void + { + $this->enhanceTwig(new \stdClass()); + } + }; + + $object->wrongEnhance(); } /** @@ -139,11 +139,12 @@ public function testPreRenderPhp() }); $kernel->boot(); $pugSymfony = $this->getPugSymfonyEngine(); + $pugSymfony->setOption('prettyprint', false); - self::assertSame('

/foo

', $pugSymfony->renderString('p=asset("/foo")')); + self::assertSame('

/foo

', trim($pugSymfony->renderString('p=asset("/foo")'))); self::assertSame( 'My Site

/foo

Some footer text
', - $pugSymfony->render('asset.pug') + $pugSymfony->render('asset.pug'), ); } @@ -155,7 +156,7 @@ public function testMixin() self::assertSame( '
  • cat
  • dog
  • pig
', - $pugSymfony->render('mixin.pug') + $pugSymfony->render('mixin.pug'), ); } @@ -172,7 +173,7 @@ public function testPreRenderJs() $kernel->boot(); $pugSymfony = $this->getPugSymfonyEngine(); - self::assertSame('

/foo

', $pugSymfony->renderString('p=asset("/foo")')); + self::assertSame('

/foo

', trim($pugSymfony->renderString('p=asset("/foo")'))); } public function testPreRenderFile() @@ -208,7 +209,7 @@ public function testPreRenderCsrfToken() }); $kernel->boot(); $pugSymfony = $this->getPugSymfonyEngine($kernel); - $this->addFormRenderer($kernel->getContainer()); + $this->addFormRenderer(); self::assertSame('

Hello

', $pugSymfony->renderString('p Hello')); @@ -235,7 +236,7 @@ public function testSecurityToken() $services['security.token_storage'] = $tokenStorage; $reflectionProperty->setValue($container, $services); $pugSymfony = $this->getPugSymfonyEngine(); - $this->addFormRenderer($container); + $this->addFormRenderer(); self::assertSame('

the token

', trim($pugSymfony->render('token.pug'))); } @@ -247,8 +248,7 @@ public function testSecurityToken() public function testLogoutHelper() { $generator = new LogoutUrlGenerator(); - /* @var Environment $twig */ - $twig = self::$kernel->getContainer()->get('twig'); + $twig = $this->getTwigEnvironment(); foreach ($twig->getExtensions() as $extension) { if ($extension instanceof LogoutUrlExtension) { @@ -276,10 +276,8 @@ public function testLogoutHelper() public function testFormHelpers() { $pugSymfony = $this->getPugSymfonyEngine(); - $container = self::$kernel->getContainer(); - $this->addFormRenderer($container); + $this->addFormRenderer(); $controller = new TestController(); - $controller->setContainer($container); self::assertRegExp('/^'.implode('', [ '
', @@ -303,8 +301,8 @@ public function testRenderViaTwig() $container = self::$kernel->getContainer(); $controller = new TestController(); $controller->setContainer($container); - /** @var Environment $twig */ - $twig = $container->get('twig'); + $twig = $this->getTwigEnvironment(); + $this->getPugSymfonyEngine(); self::assertInstanceOf(Environment::class, $twig); self::assertInstanceOf(PugSymfonyEngine::class, $twig->getEngine()); @@ -325,9 +323,8 @@ public function testRenderViaTwig() */ public function testServicesSharing() { - /** @var Environment $twig */ - $twig = self::$kernel->getContainer()->get('twig'); - $twig->addGlobal('t', self::$kernel->getContainer()->get('translator')); + $twig = $this->getTwigEnvironment(); + $twig->addGlobal('t', new Translator('en_US')); $pugSymfony = $this->getPugSymfonyEngine(); self::assertSame('

Hello Bob

', trim($pugSymfony->renderString('p=t.trans("Hello %name%", {"%name%": "Bob"})'))); @@ -338,11 +335,9 @@ public function testServicesSharing() */ public function testTwigGlobals() { - $container = self::$kernel->getContainer(); - $pugSymfony = $this->getPugSymfonyEngine(); - /** @var Environment $twig */ - $twig = $container->get('twig'); + $twig = $this->getTwigEnvironment(); $twig->addGlobal('answer', 42); + $pugSymfony = $this->getPugSymfonyEngine(); self::assertSame('

42

', trim($pugSymfony->renderString('p=answer'))); } @@ -405,17 +400,20 @@ public function testAssetHelperJs() self::assertSame( '
'."\n". '
', preg_replace( '/<', - str_replace(['\'assets/', "\r"], ['\'/assets/', ''], trim($pugSymfony->render('style-js.pug'))) - ) + strtr(trim($pugSymfony->render('style-js.pug')), [ + "\r" => '', + ''' => "'", + ]), + ), ); } @@ -535,15 +533,16 @@ public function testCustomPaths() */ public function testMissingDir() { + self::expectExceptionObject(new CompilerException( + new SourceLocation('page.pug', 1,0), + 'Source file page.pug not found', + )); + $kernel = new TestKernel(); $kernel->boot(); $kernel->setProjectDirectory(__DIR__.'/../project'); - $pugSymfony = $this->getPugSymfonyEngine(); - self::assertSame( - '

Page

', - trim($pugSymfony->render('page.pug')) - ); + $this->getPugSymfonyEngine()->render('page.pug'); } public function testForbidThis() @@ -600,16 +599,14 @@ public function testCompileException() self::expectExceptionMessage('Unable to compile void function.'); $this->getPugSymfonyEngine(); - /** @var Environment $twig */ - $twig = self::$kernel->getContainer()->get('twig'); + $twig = $this->getTwigEnvironment(); $twig->compileCode(new TwigFunction('void'), '{# comment #}'); } public function testLoadTemplate() { $this->getPugSymfonyEngine(); - /** @var Environment $twig */ - $twig = self::$kernel->getContainer()->get('twig'); + $twig = $this->getTwigEnvironment(); try { $twig->loadTemplate('a', 'b', 1); @@ -652,8 +649,6 @@ public function testRenderInterceptor() $value = $property->getValue($container); $value['pug']['interceptors'] = [PugInterceptor::class]; $property->setValue($container, $value); - $controller = new TestController(); - $controller->setContainer($container); $twig = $this->getTwigEnvironment(); $pugSymfony = $this->getPugSymfonyEngine(); From 5395257ac3364101b2d65352472d369d214ac5ec Mon Sep 17 00:00:00 2001 From: KyleKatarn Date: Sun, 5 Mar 2023 18:18:41 +0100 Subject: [PATCH 14/17] Allow only non-empty extension to check support --- src/Pug/PugSymfonyEngine.php | 2 +- src/Pug/Symfony/Traits/HelpersHandler.php | 7 ++++--- tests/Pug/PugSymfonyEngineTest.php | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Pug/PugSymfonyEngine.php b/src/Pug/PugSymfonyEngine.php index 421e211..d174439 100644 --- a/src/Pug/PugSymfonyEngine.php +++ b/src/Pug/PugSymfonyEngine.php @@ -256,7 +256,7 @@ public function exists($name): bool public function supports($name): bool { foreach ($this->getOptionDefault('extensions', ['.pug', '.jade']) as $extension) { - if (str_ends_with($name, $extension)) { + if ($extension && str_ends_with($name, $extension)) { return true; } } diff --git a/src/Pug/Symfony/Traits/HelpersHandler.php b/src/Pug/Symfony/Traits/HelpersHandler.php index e2e3764..33e3d36 100644 --- a/src/Pug/Symfony/Traits/HelpersHandler.php +++ b/src/Pug/Symfony/Traits/HelpersHandler.php @@ -352,9 +352,10 @@ protected function getHttpFoundationExtension(): HttpFoundationExtension $stack = $this->stack ?? $this->container->get('request_stack'); /* @var RequestContext $context */ - $context = $this->context ?? ($this->container->has('router.request_context') - ? $this->container->get('router.request_context') - : $this->container->get('router')->getContext() + $context = $this->context ?? ( + $this->container->has('router.request_context') + ? $this->container->get('router.request_context') + : $this->container->get('router')->getContext() ); return new HttpFoundationExtension(new UrlHelper($stack, $context)); diff --git a/tests/Pug/PugSymfonyEngineTest.php b/tests/Pug/PugSymfonyEngineTest.php index 3ed99b9..97a4b92 100644 --- a/tests/Pug/PugSymfonyEngineTest.php +++ b/tests/Pug/PugSymfonyEngineTest.php @@ -115,7 +115,7 @@ public function testRequireTwig() self::expectException(RuntimeException::class); self::expectExceptionMessage('Twig needs to be configured.'); - $object = new class () { + $object = new class() { use HelpersHandler; public function wrongEnhance(): void @@ -382,7 +382,7 @@ public function testAssetHelperPhp() '/<', strtr(trim($pugSymfony->render('style-php.pug')), [ - "\r" => '', + "\r" => '', ''' => "'", ]), ), @@ -410,7 +410,7 @@ public function testAssetHelperJs() '/<', strtr(trim($pugSymfony->render('style-js.pug')), [ - "\r" => '', + "\r" => '', ''' => "'", ]), ), @@ -534,7 +534,7 @@ public function testCustomPaths() public function testMissingDir() { self::expectExceptionObject(new CompilerException( - new SourceLocation('page.pug', 1,0), + new SourceLocation('page.pug', 1, 0), 'Source file page.pug not found', )); From 25681ca592947df9c31a669f19793e42722fba48 Mon Sep 17 00:00:00 2001 From: KyleKatarn Date: Sun, 5 Mar 2023 18:21:31 +0100 Subject: [PATCH 15/17] Load FormExtension for tests --- tests/Pug/AbstractTestCase.php | 4 +- .../src/Controller/DefaultController.php | 6 - .../templates/form_div_layout.html.twig | 484 ++++++++++++++++++ 3 files changed, 486 insertions(+), 8 deletions(-) create mode 100644 tests/project-s5/templates/form_div_layout.html.twig diff --git a/tests/Pug/AbstractTestCase.php b/tests/Pug/AbstractTestCase.php index a689528..4263a5c 100644 --- a/tests/Pug/AbstractTestCase.php +++ b/tests/Pug/AbstractTestCase.php @@ -2,11 +2,11 @@ namespace Pug\Tests; -use Psr\Container\ContainerInterface; use Pug\PugSymfonyEngine; use Pug\Twig\Environment; use Symfony\Bridge\Twig\Extension\CsrfExtension; use Symfony\Bridge\Twig\Extension\CsrfRuntime; +use Symfony\Bridge\Twig\Extension\FormExtension; use Symfony\Bridge\Twig\Form\TwigRendererEngine; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\Filesystem\Filesystem; @@ -15,7 +15,6 @@ use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\Routing\RequestContext; use Twig\Loader\FilesystemLoader; -use Twig\RuntimeLoader\ContainerRuntimeLoader; use Twig\RuntimeLoader\FactoryRuntimeLoader; abstract class AbstractTestCase extends KernelTestCase @@ -35,6 +34,7 @@ protected function getTwigEnvironment(): Environment __DIR__.'/../project-s5/templates/', )); $this->twig->addExtension(new CsrfExtension()); + $this->twig->addExtension(new FormExtension()); $this->twig->addRuntimeLoader(new FactoryRuntimeLoader([ CsrfRuntime::class => static fn () => new CsrfRuntime(new TestCsrfTokenManager()), ])); diff --git a/tests/project-s5/src/Controller/DefaultController.php b/tests/project-s5/src/Controller/DefaultController.php index f9ac889..8867b62 100644 --- a/tests/project-s5/src/Controller/DefaultController.php +++ b/tests/project-s5/src/Controller/DefaultController.php @@ -6,18 +6,12 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\FormRenderer; use Symfony\Component\Security\Csrf\CsrfTokenManager; -use Twig\Environment; use Twig\RuntimeLoader\FactoryRuntimeLoader; class DefaultController extends AbstractController { protected $twig; -// public function __construct(Environment $twig) -// { -// $this->twig = $twig; -// } - public function index() { $defaultFormTheme = 'form_div_layout.html.twig'; diff --git a/tests/project-s5/templates/form_div_layout.html.twig b/tests/project-s5/templates/form_div_layout.html.twig new file mode 100644 index 0000000..61f64e3 --- /dev/null +++ b/tests/project-s5/templates/form_div_layout.html.twig @@ -0,0 +1,484 @@ +{# Widgets #} + +{%- block form_widget -%} + {% if compound %} + {{- block('form_widget_compound') -}} + {% else %} + {{- block('form_widget_simple') -}} + {% endif %} +{%- endblock form_widget -%} + +{%- block form_widget_simple -%} + {%- set type = type|default('text') -%} + {%- if type == 'range' or type == 'color' -%} + {# Attribute "required" is not supported #} + {%- set required = false -%} + {%- endif -%} + +{%- endblock form_widget_simple -%} + +{%- block form_widget_compound -%} +
+ {%- if form is rootform -%} + {{ form_errors(form) }} + {%- endif -%} + {{- block('form_rows') -}} + {{- form_rest(form) -}} +
+{%- endblock form_widget_compound -%} + +{%- block collection_widget -%} + {% if prototype is defined and not prototype.rendered %} + {%- set attr = attr|merge({'data-prototype': form_row(prototype) }) -%} + {% endif %} + {{- block('form_widget') -}} +{%- endblock collection_widget -%} + +{%- block textarea_widget -%} + +{%- endblock textarea_widget -%} + +{%- block choice_widget -%} + {% if expanded %} + {{- block('choice_widget_expanded') -}} + {% else %} + {{- block('choice_widget_collapsed') -}} + {% endif %} +{%- endblock choice_widget -%} + +{%- block choice_widget_expanded -%} +
+ {%- for child in form %} + {{- form_widget(child) -}} + {{- form_label(child, null, {translation_domain: choice_translation_domain}) -}} + {% endfor -%} +
+{%- endblock choice_widget_expanded -%} + +{%- block choice_widget_collapsed -%} + {%- if required and placeholder is none and not placeholder_in_choices and not multiple and (attr.size is not defined or attr.size <= 1) -%} + {% set required = false %} + {%- endif -%} + +{%- endblock choice_widget_collapsed -%} + +{%- block choice_widget_options -%} + {% for group_label, choice in options %} + {%- if choice is iterable -%} + + {% set options = choice %} + {{- block('choice_widget_options') -}} + + {%- else -%} + + {%- endif -%} + {% endfor %} +{%- endblock choice_widget_options -%} + +{%- block checkbox_widget -%} + +{%- endblock checkbox_widget -%} + +{%- block radio_widget -%} + +{%- endblock radio_widget -%} + +{%- block datetime_widget -%} + {% if widget == 'single_text' %} + {{- block('form_widget_simple') -}} + {%- else -%} +
+ {{- form_errors(form.date) -}} + {{- form_errors(form.time) -}} + {{- form_widget(form.date) -}} + {{- form_widget(form.time) -}} +
+ {%- endif -%} +{%- endblock datetime_widget -%} + +{%- block date_widget -%} + {%- if widget == 'single_text' -%} + {{ block('form_widget_simple') }} + {%- else -%} +
+ {{- date_pattern|replace({ + '{{ year }}': form_widget(form.year), + '{{ month }}': form_widget(form.month), + '{{ day }}': form_widget(form.day), + })|raw -}} +
+ {%- endif -%} +{%- endblock date_widget -%} + +{%- block time_widget -%} + {%- if widget == 'single_text' -%} + {{ block('form_widget_simple') }} + {%- else -%} + {%- set vars = widget == 'text' ? { 'attr': { 'size': 1 }} : {} -%} +
+ {{ form_widget(form.hour, vars) }}{% if with_minutes %}:{{ form_widget(form.minute, vars) }}{% endif %}{% if with_seconds %}:{{ form_widget(form.second, vars) }}{% endif %} +
+ {%- endif -%} +{%- endblock time_widget -%} + +{%- block dateinterval_widget -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} +
+ {{- form_errors(form) -}} + + + + {%- if with_years %}{% endif -%} + {%- if with_months %}{% endif -%} + {%- if with_weeks %}{% endif -%} + {%- if with_days %}{% endif -%} + {%- if with_hours %}{% endif -%} + {%- if with_minutes %}{% endif -%} + {%- if with_seconds %}{% endif -%} + + + + + {%- if with_years %}{% endif -%} + {%- if with_months %}{% endif -%} + {%- if with_weeks %}{% endif -%} + {%- if with_days %}{% endif -%} + {%- if with_hours %}{% endif -%} + {%- if with_minutes %}{% endif -%} + {%- if with_seconds %}{% endif -%} + + + + {%- if with_invert %}{{ form_widget(form.invert) }}{% endif -%} +
+ {%- endif -%} +{%- endblock dateinterval_widget -%} + +{%- block number_widget -%} + {# type="number" doesn't work with floats in localized formats #} + {%- set type = type|default('text') -%} + {{ block('form_widget_simple') }} +{%- endblock number_widget -%} + +{%- block integer_widget -%} + {%- set type = type|default('number') -%} + {{ block('form_widget_simple') }} +{%- endblock integer_widget -%} + +{%- block money_widget -%} + {{ money_pattern|form_encode_currency(block('form_widget_simple')) }} +{%- endblock money_widget -%} + +{%- block url_widget -%} + {%- set type = type|default('url') -%} + {{ block('form_widget_simple') }} +{%- endblock url_widget -%} + +{%- block search_widget -%} + {%- set type = type|default('search') -%} + {{ block('form_widget_simple') }} +{%- endblock search_widget -%} + +{%- block percent_widget -%} + {%- set type = type|default('text') -%} + {{ block('form_widget_simple') }}{% if symbol %} {{ symbol|default('%') }}{% endif %} +{%- endblock percent_widget -%} + +{%- block password_widget -%} + {%- set type = type|default('password') -%} + {{ block('form_widget_simple') }} +{%- endblock password_widget -%} + +{%- block hidden_widget -%} + {%- set type = type|default('hidden') -%} + {{ block('form_widget_simple') }} +{%- endblock hidden_widget -%} + +{%- block email_widget -%} + {%- set type = type|default('email') -%} + {{ block('form_widget_simple') }} +{%- endblock email_widget -%} + +{%- block range_widget -%} + {% set type = type|default('range') %} + {{- block('form_widget_simple') -}} +{%- endblock range_widget %} + +{%- block button_widget -%} + {%- if label is empty -%} + {%- if label_format is not empty -%} + {% set label = label_format|replace({ + '%name%': name, + '%id%': id, + }) %} + {%- elseif label is not same as(false) -%} + {% set label = name|humanize %} + {%- endif -%} + {%- endif -%} + +{%- endblock button_widget -%} + +{%- block submit_widget -%} + {%- set type = type|default('submit') -%} + {{ block('button_widget') }} +{%- endblock submit_widget -%} + +{%- block reset_widget -%} + {%- set type = type|default('reset') -%} + {{ block('button_widget') }} +{%- endblock reset_widget -%} + +{%- block tel_widget -%} + {%- set type = type|default('tel') -%} + {{ block('form_widget_simple') }} +{%- endblock tel_widget -%} + +{%- block color_widget -%} + {%- set type = type|default('color') -%} + {{ block('form_widget_simple') }} +{%- endblock color_widget -%} + +{%- block week_widget -%} + {%- if widget == 'single_text' -%} + {{ block('form_widget_simple') }} + {%- else -%} + {%- set vars = widget == 'text' ? { 'attr': { 'size': 1 }} : {} -%} +
+ {{ form_widget(form.year, vars) }}-{{ form_widget(form.week, vars) }} +
+ {%- endif -%} +{%- endblock week_widget -%} + +{# Labels #} + +{%- block form_label -%} + {% if label is not same as(false) -%} + {% if not compound -%} + {% set label_attr = label_attr|merge({'for': id}) %} + {%- endif -%} + {% if required -%} + {% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' required')|trim}) %} + {%- endif -%} + <{{ element|default('label') }}{% if label_attr %}{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}{% endif %}> + {{- block('form_label_content') -}} + + {%- endif -%} +{%- endblock form_label -%} + +{%- block form_label_content -%} + {%- if label is empty -%} + {%- if label_format is not empty -%} + {% set label = label_format|replace({ + '%name%': name, + '%id%': id, + }) %} + {%- else -%} + {% set label = name|humanize %} + {%- endif -%} + {%- endif -%} + {%- if translation_domain is same as(false) -%} + {%- if label_html is same as(false) -%} + {{- label -}} + {%- else -%} + {{- label|raw -}} + {%- endif -%} + {%- else -%} + {%- if label_html is same as(false) -%} + {{- label|trans(label_translation_parameters, translation_domain) -}} + {%- else -%} + {{- label|trans(label_translation_parameters, translation_domain)|raw -}} + {%- endif -%} + {%- endif -%} +{%- endblock form_label_content -%} + +{%- block button_label -%}{%- endblock -%} + +{# Help #} + +{% block form_help -%} + {%- if help is not empty -%} + {%- set help_attr = help_attr|merge({class: (help_attr.class|default('') ~ ' help-text')|trim}) -%} +
+ {{- block('form_help_content') -}} +
+ {%- endif -%} +{%- endblock form_help %} + +{% block form_help_content -%} + {%- if translation_domain is same as(false) -%} + {%- if help_html is same as(false) -%} + {{- help -}} + {%- else -%} + {{- help|raw -}} + {%- endif -%} + {%- else -%} + {%- if help_html is same as(false) -%} + {{- help|trans(help_translation_parameters, translation_domain) -}} + {%- else -%} + {{- help|trans(help_translation_parameters, translation_domain)|raw -}} + {%- endif -%} + {%- endif -%} +{%- endblock form_help_content %} + +{# Rows #} + +{%- block repeated_row -%} + {# + No need to render the errors here, as all errors are mapped + to the first child (see RepeatedTypeValidatorExtension). + #} + {{- block('form_rows') -}} +{%- endblock repeated_row -%} + +{%- block form_row -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%} + {%- endif -%} + + {{- form_label(form) -}} + {{- form_errors(form) -}} + {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}} + +{%- endblock form_row -%} + +{%- block button_row -%} + + {{- form_widget(form) -}} + +{%- endblock button_row -%} + +{%- block hidden_row -%} + {{ form_widget(form) }} +{%- endblock hidden_row -%} + +{# Misc #} + +{%- block form -%} + {{ form_start(form) }} + {{- form_widget(form) -}} + {{ form_end(form) }} +{%- endblock form -%} + +{%- block form_start -%} + {%- do form.setMethodRendered() -%} + {% set method = method|upper %} + {%- if method in ["GET", "POST"] -%} + {% set form_method = method %} + {%- else -%} + {% set form_method = "POST" %} + {%- endif -%} + + {%- if form_method != method -%} + + {%- endif -%} +{%- endblock form_start -%} + +{%- block form_end -%} + {%- if not render_rest is defined or render_rest -%} + {{ form_rest(form) }} + {%- endif -%} + +{%- endblock form_end -%} + +{%- block form_errors -%} + {%- if errors|length > 0 -%} +
    + {%- for error in errors -%} +
  • {{ error.message }}
  • + {%- endfor -%} +
+ {%- endif -%} +{%- endblock form_errors -%} + +{%- block form_rest -%} + {% for child in form -%} + {% if not child.rendered %} + {{- form_row(child) -}} + {% endif %} + {%- endfor -%} + + {% if not form.methodRendered and form is rootform %} + {%- do form.setMethodRendered() -%} + {% set method = method|upper %} + {%- if method in ["GET", "POST"] -%} + {% set form_method = method %} + {%- else -%} + {% set form_method = "POST" %} + {%- endif -%} + + {%- if form_method != method -%} + + {%- endif -%} + {% endif -%} +{% endblock form_rest %} + +{# Support #} + +{%- block form_rows -%} + {% for child in form|filter(child => not child.rendered) %} + {{- form_row(child) -}} + {% endfor %} +{%- endblock form_rows -%} + +{%- block widget_attributes -%} + id="{{ id }}" name="{{ full_name }}" + {%- if disabled %} disabled="disabled"{% endif -%} + {%- if required %} required="required"{% endif -%} + {{ block('attributes') }} +{%- endblock widget_attributes -%} + +{%- block widget_container_attributes -%} + {%- if id is not empty %}id="{{ id }}"{% endif -%} + {{ block('attributes') }} +{%- endblock widget_container_attributes -%} + +{%- block button_attributes -%} + id="{{ id }}" name="{{ full_name }}"{% if disabled %} disabled="disabled"{% endif -%} + {{ block('attributes') }} +{%- endblock button_attributes -%} + +{% block attributes -%} + {%- for attrname, attrvalue in attr -%} + {{- " " -}} + {%- if attrname in ['placeholder', 'title'] -%} + {{- attrname }}="{{ translation_domain is same as(false) or attrvalue is null ? attrvalue : attrvalue|trans(attr_translation_parameters, translation_domain) }}" + {%- elseif attrvalue is same as(true) -%} + {{- attrname }}="{{ attrname }}" + {%- elseif attrvalue is not same as(false) -%} + {{- attrname }}="{{ attrvalue }}" + {%- endif -%} + {%- endfor -%} +{%- endblock attributes -%} From 7615ab4a026a8ada108a4ccff5d3ebaecfba5ff0 Mon Sep 17 00:00:00 2001 From: KyleKatarn Date: Sun, 5 Mar 2023 18:44:32 +0100 Subject: [PATCH 16/17] Add csrf extension to form factory in tests --- tests/Pug/AbstractTestCase.php | 2 ++ tests/Pug/PugSymfonyEngineTest.php | 32 +++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/tests/Pug/AbstractTestCase.php b/tests/Pug/AbstractTestCase.php index 4263a5c..fd8ad59 100644 --- a/tests/Pug/AbstractTestCase.php +++ b/tests/Pug/AbstractTestCase.php @@ -7,6 +7,7 @@ use Symfony\Bridge\Twig\Extension\CsrfExtension; use Symfony\Bridge\Twig\Extension\CsrfRuntime; use Symfony\Bridge\Twig\Extension\FormExtension; +use Symfony\Bridge\Twig\Extension\TranslationExtension; use Symfony\Bridge\Twig\Form\TwigRendererEngine; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\Filesystem\Filesystem; @@ -35,6 +36,7 @@ protected function getTwigEnvironment(): Environment )); $this->twig->addExtension(new CsrfExtension()); $this->twig->addExtension(new FormExtension()); + $this->twig->addExtension(new TranslationExtension()); $this->twig->addRuntimeLoader(new FactoryRuntimeLoader([ CsrfRuntime::class => static fn () => new CsrfRuntime(new TestCsrfTokenManager()), ])); diff --git a/tests/Pug/PugSymfonyEngineTest.php b/tests/Pug/PugSymfonyEngineTest.php index 97a4b92..7efed79 100644 --- a/tests/Pug/PugSymfonyEngineTest.php +++ b/tests/Pug/PugSymfonyEngineTest.php @@ -24,11 +24,18 @@ use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\Extension\Csrf\CsrfExtension; use Symfony\Component\Form\FormFactory; use Symfony\Component\Form\FormRegistry; use Symfony\Component\Form\ResolvedFormTypeFactory; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage as BaseTokenStorage; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Csrf\CsrfTokenManager; +use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator; +use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage; +use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage; +use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface; use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator as BaseLogoutUrlGenerator; use Symfony\Component\Translation\Translator; use Twig\Error\LoaderError; @@ -96,7 +103,30 @@ class TestController { public function index() { - $factory = new FormFactory(new FormRegistry([], new ResolvedFormTypeFactory())); + $csrfGenerator = new UriSafeTokenGenerator(); + $csrfManager = new CsrfTokenManager($csrfGenerator, new class() implements TokenStorageInterface { + public function getToken(string $tokenId): string + { + return "token:$tokenId"; + } + + public function setToken(string $tokenId, #[\SensitiveParameter] string $token) + { + // noop + } + + public function removeToken(string $tokenId): ?string + { + return "token:$tokenId"; + } + + public function hasToken(string $tokenId): bool + { + return true; + } + }); + $extensions = [new CsrfExtension($csrfManager)]; + $factory = new FormFactory(new FormRegistry($extensions, new ResolvedFormTypeFactory())); return $factory->createBuilder(FormType::class, new Task()) ->add('name', TextType::class) From 74451b1b57139b47433d5b9f572f637a85e26d52 Mon Sep 17 00:00:00 2001 From: KyleKatarn Date: Sun, 5 Mar 2023 19:09:12 +0100 Subject: [PATCH 17/17] Add LogoutUrlExtension in tests --- tests/Pug/AbstractTestCase.php | 2 ++ tests/Pug/PugSymfonyEngineTest.php | 11 ----------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/tests/Pug/AbstractTestCase.php b/tests/Pug/AbstractTestCase.php index fd8ad59..cc2b6d2 100644 --- a/tests/Pug/AbstractTestCase.php +++ b/tests/Pug/AbstractTestCase.php @@ -7,6 +7,7 @@ use Symfony\Bridge\Twig\Extension\CsrfExtension; use Symfony\Bridge\Twig\Extension\CsrfRuntime; use Symfony\Bridge\Twig\Extension\FormExtension; +use Symfony\Bridge\Twig\Extension\LogoutUrlExtension; use Symfony\Bridge\Twig\Extension\TranslationExtension; use Symfony\Bridge\Twig\Form\TwigRendererEngine; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; @@ -36,6 +37,7 @@ protected function getTwigEnvironment(): Environment )); $this->twig->addExtension(new CsrfExtension()); $this->twig->addExtension(new FormExtension()); + $this->twig->addExtension(new LogoutUrlExtension(new LogoutUrlGenerator())); $this->twig->addExtension(new TranslationExtension()); $this->twig->addRuntimeLoader(new FactoryRuntimeLoader([ CsrfRuntime::class => static fn () => new CsrfRuntime(new TestCsrfTokenManager()), diff --git a/tests/Pug/PugSymfonyEngineTest.php b/tests/Pug/PugSymfonyEngineTest.php index 7efed79..01874e9 100644 --- a/tests/Pug/PugSymfonyEngineTest.php +++ b/tests/Pug/PugSymfonyEngineTest.php @@ -28,13 +28,10 @@ use Symfony\Component\Form\FormFactory; use Symfony\Component\Form\FormRegistry; use Symfony\Component\Form\ResolvedFormTypeFactory; -use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage as BaseTokenStorage; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Csrf\CsrfTokenManager; use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator; -use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage; -use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage; use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface; use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator as BaseLogoutUrlGenerator; use Symfony\Component\Translation\Translator; @@ -289,12 +286,6 @@ public function testLogoutHelper() } } - if ($generator) { - include_once __DIR__.'/LogoutUrlHelper.php'; - $logoutUrlHelper = new LogoutUrlHelper($generator); - self::$kernel->getContainer()->set('templating.helper.logout_url', $logoutUrlHelper); - } - $pugSymfony = $this->getPugSymfonyEngine(); self::assertSame('', trim($pugSymfony->render('logout.pug'))); @@ -328,9 +319,7 @@ public function testFormHelpers() */ public function testRenderViaTwig() { - $container = self::$kernel->getContainer(); $controller = new TestController(); - $controller->setContainer($container); $twig = $this->getTwigEnvironment(); $this->getPugSymfonyEngine();