diff --git a/.github/workflows/cs-tests.yml b/.github/workflows/cs-tests.yml new file mode 100644 index 0000000..3da9965 --- /dev/null +++ b/.github/workflows/cs-tests.yml @@ -0,0 +1,46 @@ +on: + - push + +name: Run phpcs checks + +jobs: + mutation: + name: PHP ${{ matrix.php }}-${{ matrix.os }} + + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: + - ubuntu-latest + + php: + - "8.1" + - "8.2" + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "${{ matrix.php }}" + tools: composer:v2, cs2pr + coverage: none + + - name: Determine composer cache directory + run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV + + - name: Cache dependencies installed with composer + uses: actions/cache@v3 + with: + path: ${{ env.COMPOSER_CACHE_DIR }} + key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: | + php${{ matrix.php }}-composer- + - name: Install dependencies with composer + run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi + + - name: Run phpcs checks + run: vendor/bin/phpcs diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml new file mode 100644 index 0000000..74550fc --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,46 @@ +on: + - push + +name: Run static analysis + +jobs: + mutation: + name: PHP ${{ matrix.php }}-${{ matrix.os }} + + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: + - ubuntu-latest + + php: + - "8.1" + - "8.2" + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "${{ matrix.php }}" + tools: composer:v2, cs2pr + coverage: none + + - name: Determine composer cache directory + run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV + + - name: Cache dependencies installed with composer + uses: actions/cache@v3 + with: + path: ${{ env.COMPOSER_CACHE_DIR }} + key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: | + php${{ matrix.php }}-composer- + - name: Install dependencies with composer + run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi + + - name: Run static analysis + run: vendor/bin/psalm --no-cache --output-format=github --show-info=false --threads=4 diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 0000000..593bed9 --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,46 @@ +on: + - push + +name: Run PHPUnit tests + +jobs: + mutation: + name: PHP ${{ matrix.php }}-${{ matrix.os }} + + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: + - ubuntu-latest + + php: + - "8.1" + - "8.2" + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "${{ matrix.php }}" + tools: composer:v2, cs2pr + coverage: none + + - name: Determine composer cache directory + run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV + + - name: Cache dependencies installed with composer + uses: actions/cache@v3 + with: + path: ${{ env.COMPOSER_CACHE_DIR }} + key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: | + php${{ matrix.php }}-composer- + - name: Install dependencies with composer + run: composer install --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi + + - name: Run PHPUnit tests + run: vendor/bin/phpunit --colors=always diff --git a/.gitignore b/.gitignore index c4a2a74..4d3fdcb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ clover.xml coveralls-upload.json -phpunit.xml +.phpcs-cache +.phpunit.result.cache # Created by .ignore support plugin (hsz.mobi) ### JetBrains template diff --git a/README.md b/README.md index d2918bd..83f84b3 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,11 @@ DotKernel component providing twig extensions and customizations ![OSS Lifecycle](https://img.shields.io/osslifecycle/dotkernel/dot-twigrenderer) -![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-twigrenderer/3.2.0) +![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-twigrenderer/3.4.0) [![GitHub issues](https://img.shields.io/github/issues/dotkernel/dot-form)](https://github.com/dotkernel/dot-twigrenderer/issues) [![GitHub forks](https://img.shields.io/github/forks/dotkernel/dot-form)](https://github.com/dotkernel/dot-twigrenderer/network) [![GitHub stars](https://img.shields.io/github/stars/dotkernel/dot-form)](https://github.com/dotkernel/dot-twigrenderer/stargazers) -[![GitHub license](https://img.shields.io/github/license/dotkernel/dot-form)](https://github.com/dotkernel/dot-twigrenderer/blob/3.2.0/LICENSE.md) +[![GitHub license](https://img.shields.io/github/license/dotkernel/dot-form)](https://github.com/dotkernel/dot-twigrenderer/blob/3.0/LICENSE.md) + +[![SymfonyInsight](https://insight.symfony.com/projects/b9a7d75d-d00a-44a9-b1c0-aea8670681cc/big.svg)](https://insight.symfony.com/projects/b9a7d75d-d00a-44a9-b1c0-aea8670681cc) diff --git a/composer.json b/composer.json index dd11487..7b26cd0 100644 --- a/composer.json +++ b/composer.json @@ -16,20 +16,21 @@ } ], "require": { - "php": "~7.4.0 || ~8.0.0 || ~8.1.0", + "php": "~8.1.0 || ~8.2.0", "laminas/laminas-servicemanager": "^3.11", "mezzio/mezzio-twigrenderer": "^2.9", "laminas/laminas-view": "^2.20", - "laminas/laminas-authentication": "^2.10" - }, - "require-dev": { - "phpunit/phpunit": "^9.5", - "squizlabs/php_codesniffer": "^3.6", + "laminas/laminas-authentication": "^2.10", "dotkernel/dot-navigation": "^3.1", "dotkernel/dot-flashmessenger": "^3.1.1", "laminas/laminas-form": "^3.1.1", "doctrine/doctrine-module": "^5.1", - "mezzio/mezzio-authorization": "^1.4" + "dotkernel/dot-authorization": "^3.3" + }, + "require-dev": { + "phpunit/phpunit": "^10.2", + "laminas/laminas-coding-standard": "^2.5", + "vimeo/psalm": "^5.13" }, "autoload": { "psr-4": { @@ -40,5 +41,22 @@ "psr-4": { "DotTest\\Twig\\": "test/" } + }, + "config": { + "sort-packages": true, + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "scripts": { + "check": [ + "@cs-check", + "@test" + ], + "cs-check": "phpcs", + "cs-fix": "phpcbf", + "test": "phpunit --colors=always", + "test-coverage": "phpunit --colors=always --coverage-clover clover.xml", + "static-analysis": "psalm --shepherd --stats" } } diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..1efe663 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + src + test + + + + diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..87aab12 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,12 @@ + + + + + ./test + + + diff --git a/psalm-baseline.xml b/psalm-baseline.xml new file mode 100644 index 0000000..7c53d4a --- /dev/null +++ b/psalm-baseline.xml @@ -0,0 +1,9 @@ + + + + + twig_date_converter($env, $date) + twig_date_converter($env, $now) + + + diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..e6f472e --- /dev/null +++ b/psalm.xml @@ -0,0 +1,18 @@ + + + + + + + + + diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index b565f50..5a22162 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -1,11 +1,6 @@ $this->getDependencyConfig(), - 'view_helpers' => [], ]; } @@ -47,43 +37,37 @@ public function __invoke(): array public function getDependencyConfig(): array { return [ - 'factories' => [ - HelperPluginManager::class => HelperPluginManagerFactory::class, - + 'factories' => [ + HelperPluginManager::class => HelperPluginManagerFactory::class, AuthenticationExtension::class => AuthenticationExtensionFactory::class, - AuthorizationExtension::class => AuthorizationExtensionFactory::class, - NavigationExtension::class => NavigationExtensionFactory::class, + AuthorizationExtension::class => AuthorizationExtensionFactory::class, + NavigationExtension::class => NavigationExtensionFactory::class, FlashMessengerExtension::class => FlashMessengerExtensionFactory::class, - FormElementsExtension::class => InvokableFactory::class, - AuthenticationService::class => AuthenticationServiceFactory::class + FormElementsExtension::class => InvokableFactory::class, + AuthenticationService::class => AuthenticationServiceFactory::class, ], - - 'delegators' => [ - Environment::class => [ + 'delegators' => [ + Environment::class => [ TwigEnvironmentDelegator::class, ], - FlashMessengerRenderer::class => [ LazyServiceFactory::class, ], - NavigationRenderer::class => [ + NavigationRenderer::class => [ LazyServiceFactory::class, - ] + ], ], - 'lazy_services' => [ 'class_map' => [ - NavigationRenderer::class => - NavigationRenderer::class, - - FlashMessengerRenderer::class => - FlashMessengerRenderer::class, - ] + NavigationRenderer::class + => NavigationRenderer::class, + FlashMessengerRenderer::class + => FlashMessengerRenderer::class, + ], ], - - 'aliases' => [ - 'ViewHelperManager' => HelperPluginManager::class, - AuthenticationServiceInterface::class => AuthenticationService::class + 'aliases' => [ + 'ViewHelperManager' => HelperPluginManager::class, + AuthenticationServiceInterface::class => AuthenticationService::class, ], ]; } diff --git a/src/Extension/AuthenticationExtension.php b/src/Extension/AuthenticationExtension.php index 226fffb..3831fb3 100644 --- a/src/Extension/AuthenticationExtension.php +++ b/src/Extension/AuthenticationExtension.php @@ -1,45 +1,28 @@ authentication = $authentication; } - /** - * @return string - */ public function getName(): string { return 'dot-authentication'; } - public function getFunctions() + public function getFunctions(): array { return [ new TwigFunction('hasIdentity', [$this, 'hasIdentity']), @@ -52,7 +35,7 @@ public function hasIdentity(): bool return $this->authentication->hasIdentity(); } - public function getIdentity() + public function getIdentity(): mixed { return $this->authentication->getIdentity(); } diff --git a/src/Extension/AuthorizationExtension.php b/src/Extension/AuthorizationExtension.php index 7881dd3..ea0112b 100644 --- a/src/Extension/AuthorizationExtension.php +++ b/src/Extension/AuthorizationExtension.php @@ -1,29 +1,16 @@ authorization->isGranted($permission); } diff --git a/src/Extension/DateExtension.php b/src/Extension/DateExtension.php index 187dbde..27f9bd2 100644 --- a/src/Extension/DateExtension.php +++ b/src/Extension/DateExtension.php @@ -1,20 +1,19 @@ 'year', 'm' => 'month', @@ -24,10 +23,7 @@ class DateExtension extends AbstractExtension 's' => 'second', ]; - /** - * @return array|TwigFilter[] - */ - public function getFilters() + public function getFilters(): array { return [ new TwigFilter('time_diff', [$this, 'diff'], ['needs_environment' => true]), @@ -36,19 +32,16 @@ public function getFilters() /** * Filters for converting dates to a time ago string like Facebook and Twitter has. - * - * @param Environment $env - * @param string|\DateTime $date a string or DateTime object to convert - * @param string|\DateTime $now A string or DateTime object to compare with. * If none given, the current time will be used. - * - * @return string the converted time */ - public function diff(Environment $env, $date, $now = null) - { + public function diff( + Environment $env, + string|DateTimeInterface|null $date, + string|DateTimeZone|null $now = null + ): string { // Convert both dates to DateTime instances. $date = twig_date_converter($env, $date); - $now = twig_date_converter($env, $now); + $now = twig_date_converter($env, $now); // Get the difference between the two DateTime objects. $diff = $date->diff($now); @@ -65,15 +58,8 @@ public function diff(Environment $env, $date, $now = null) return ''; } - /** - * @param $count - * @param $invert - * @param $unit - * @return string - */ - private function getPluralizedInterval($count, $invert, $unit) + public function getPluralizedInterval(mixed $count, int $invert, string $unit): string { - if (1 !== $count) { $unit .= 's'; } diff --git a/src/Extension/FlashMessengerExtension.php b/src/Extension/FlashMessengerExtension.php index ccf1d5d..5ad70be 100644 --- a/src/Extension/FlashMessengerExtension.php +++ b/src/Extension/FlashMessengerExtension.php @@ -1,11 +1,6 @@ renderer = $renderer; } - /** - * @return string - */ public function getName(): string { return 'dot-messenger'; } - /** - * @return array - */ public function getFunctions(): array { return [ @@ -59,29 +39,17 @@ public function getFunctions(): array ]; } - /** - * @param string|null $type - * @param string $channel - * @return string - */ public function renderMessages( - string $type = null, + ?string $type = null, string $channel = FlashMessengerInterface::DEFAULT_CHANNEL ): string { return $this->renderer->render($type, $channel); } - /** - * @param string $partial - * @param array $params - * @param string|null $type - * @param string $channel - * @return string - */ public function renderMessagesPartial( string $partial, array $params = [], - string $type = null, + ?string $type = null, string $channel = FlashMessengerInterface::DEFAULT_CHANNEL ): string { return $this->renderer->renderPartial($partial, $params, $type, $channel); diff --git a/src/Extension/FormElementsExtension.php b/src/Extension/FormElementsExtension.php index 21555a5..e4b37cf 100644 --- a/src/Extension/FormElementsExtension.php +++ b/src/Extension/FormElementsExtension.php @@ -1,16 +1,9 @@ Button::class, - 'Captcha' => Captcha::class, - 'Checkbox' => Checkbox::class, - 'Collection' => Collection::class, - 'Color' => Color::class, - 'Csrf' => Csrf::class, - 'Date' => Date::class, - 'DateSelect' => DateSelect::class, - 'DateTime' => DateTime::class, - 'DateTimeLocal' => DateTimeLocal::class, + protected array $formElements = [ + 'Button' => Button::class, + 'Captcha' => Captcha::class, + 'Checkbox' => Checkbox::class, + 'Collection' => Collection::class, + 'Color' => Color::class, + 'Csrf' => Csrf::class, + 'Date' => Date::class, + 'DateSelect' => DateSelect::class, + 'DateTime' => DateTime::class, + 'DateTimeLocal' => DateTimeLocal::class, 'DateTimeSelect' => DateTimeSelect::class, - 'Email' => Email::class, - 'File' => File::class, - 'Fieldset' => Fieldset::class, - 'Hidden' => Hidden::class, - 'Image' => Image::class, - 'Month' => Month::class, - 'MonthSelect' => MonthSelect::class, - 'MultiCheckbox' => MultiCheckbox::class, - 'Number' => Number::class, - 'Password' => Password::class, - 'Radio' => Radio::class, - 'Range' => Range::class, - 'Search' => Search::class, - 'Select' => Select::class, - 'Submit' => Submit::class, - 'Tel' => Tel::class, - 'Text' => Text::class, - 'Textarea' => Textarea::class, - 'Time' => Time::class, - 'Url' => Url::class, - 'Week' => Week::class + 'Email' => Email::class, + 'File' => File::class, + 'Fieldset' => Fieldset::class, + 'Hidden' => Hidden::class, + 'Image' => Image::class, + 'Month' => Month::class, + 'MonthSelect' => MonthSelect::class, + 'MultiCheckbox' => MultiCheckbox::class, + 'Number' => Number::class, + 'Password' => Password::class, + 'Radio' => Radio::class, + 'Range' => Range::class, + 'Search' => Search::class, + 'Select' => Select::class, + 'Submit' => Submit::class, + 'Tel' => Tel::class, + 'Text' => Text::class, + 'Textarea' => Textarea::class, + 'Time' => Time::class, + 'Url' => Url::class, + 'Week' => Week::class, ]; - /** - * @return string - */ public function getName(): string { return 'dot-form'; } - /** - * @return array - */ public function getTests(): array { $tests = []; foreach ($this->formElements as $element => $class) { $tests[] = new TwigTest($element, function ($value) use ($class) { - return ($value instanceof $class); + return $value instanceof $class; }); } diff --git a/src/Extension/NavigationExtension.php b/src/Extension/NavigationExtension.php index 3269074..dca30ef 100644 --- a/src/Extension/NavigationExtension.php +++ b/src/Extension/NavigationExtension.php @@ -1,11 +1,6 @@ navigationRenderer = $navigationRenderer; @@ -49,32 +33,21 @@ public function getFunctions(): array ]; } - /** - * @param Page $page - * @return mixed - */ public function htmlAttributes(Page $page): string { return $this->navigationRenderer->htmlAttributes($page->getAttributes()); } - /** - * @param string|NavigationContainer $container - * @return string - */ - public function renderMenu($container): string + public function renderMenu(NavigationContainer|string $container): string { return $this->navigationRenderer->render($container); } - /** - * @param string|NavigationContainer $container - * @param string $partial - * @param array $params - * @return string - */ - public function renderMenuPartial($container, string $partial, array $params = []): string - { + public function renderMenuPartial( + NavigationContainer|string $container, + string $partial, + array $params = [] + ): string { return $this->navigationRenderer->renderPartial($container, $partial, $params); } } diff --git a/src/Extension/Translation/TransNode.php b/src/Extension/Translation/TransNode.php index d0aaa2f..487319b 100644 --- a/src/Extension/Translation/TransNode.php +++ b/src/Extension/Translation/TransNode.php @@ -1,5 +1,7 @@ $body]; if (null !== $count) { @@ -77,8 +72,7 @@ public function compile(Compiler $compiler) if ($vars) { $compiler ->write('echo strtr(' . $function . '(') - ->subcompile($msg) - ; + ->subcompile($msg); if ($this->hasNode('plural')) { $compiler @@ -86,8 +80,7 @@ public function compile(Compiler $compiler) ->subcompile($msg1) ->raw(', abs(') ->subcompile($this->hasNode('count') ? $this->getNode('count') : null) - ->raw(')') - ; + ->raw(')'); } $compiler->raw('), array('); @@ -98,15 +91,13 @@ public function compile(Compiler $compiler) ->string('%count%') ->raw(' => abs(') ->subcompile($this->hasNode('count') ? $this->getNode('count') : null) - ->raw('), ') - ; + ->raw('), '); } else { $compiler ->string('%' . $var->getAttribute('name') . '%') ->raw(' => ') ->subcompile($var) - ->raw(', ') - ; + ->raw(', '); } } @@ -114,8 +105,7 @@ public function compile(Compiler $compiler) } else { $compiler ->write('echo ' . $function . '(') - ->subcompile($msg) - ; + ->subcompile($msg); if ($this->hasNode('plural')) { $compiler @@ -123,20 +113,14 @@ public function compile(Compiler $compiler) ->subcompile($msg1) ->raw(', abs(') ->subcompile($this->hasNode('count') ? $this->getNode('count') : null) - ->raw(')') - ; + ->raw(')'); } $compiler->raw(");\n"); } } - /** - * @param Node $body A Twig_Node instance - * - * @return array - */ - protected function compileString(Node $body) + protected function compileString(Node $body): array { if ( $body instanceof NameExpression || @@ -152,8 +136,8 @@ protected function compileString(Node $body) /** @var Node $node */ foreach ($body as $node) { - if (get_class($node) === 'Node' && $node->getNode(0) instanceof TempNameExpression) { - $node = $node->getNode(1); + if ($node::class === Node::class && $node->getNode("0") instanceof TempNameExpression) { + $node = $node->getNode("1"); } if ($node instanceof PrintNode) { @@ -161,7 +145,7 @@ protected function compileString(Node $body) while ($n instanceof FilterExpression) { $n = $n->getNode('node'); } - $msg .= sprintf('%%%s%%', $n->getAttribute('name')); + $msg .= sprintf('%%%s%%', $n->getAttribute('name')); $vars[] = new NameExpression($n->getAttribute('name'), $n->getTemplateLine()); } else { $msg .= $node->getAttribute('data'); @@ -176,7 +160,6 @@ protected function compileString(Node $body) /** * @param bool $plural Return plural or singular function to use - * * @return string */ protected function getTransFunction($plural) diff --git a/src/Extension/Translation/TransTokenParser.php b/src/Extension/Translation/TransTokenParser.php index 8a42db1..343e428 100644 --- a/src/Extension/Translation/TransTokenParser.php +++ b/src/Extension/Translation/TransTokenParser.php @@ -1,5 +1,7 @@ getLine(); $stream = $this->parser->getStream(); - $count = null; + $count = null; $plural = null; - $notes = null; + $notes = null; - if (!$stream->test(Token::BLOCK_END_TYPE)) { + if (! $stream->test(Token::BLOCK_END_TYPE)) { $body = $this->parser->getExpressionParser()->parseExpression(); } else { $stream->expect(Token::BLOCK_END_TYPE); @@ -58,38 +51,25 @@ public function parse(Token $token) return new TransNode($body, $plural, $count, $notes, $lineno, $this->getTag()); } - /** - * @param Token $token - * @return bool - */ - public function decideForFork(Token $token) + public function decideForFork(Token $token): bool { return $token->test(['plural', 'notes', 'endtrans']); } - /** - * @param Token $token - * @return bool - */ - public function decideForEnd(Token $token) + public function decideForEnd(Token $token): bool { return $token->test('endtrans'); } - /** - * @return string - */ - public function getTag() + public function getTag(): string { return 'trans'; } /** - * @param Node $body - * @param $lineno * @throws SyntaxError */ - private function checkTransString(Node $body, $lineno) + private function checkTransString(Node $body, mixed $lineno): void { foreach ($body as $i => $node) { if ( @@ -101,7 +81,7 @@ private function checkTransString(Node $body, $lineno) } throw new SyntaxError( - sprintf('The text to be translated with "trans" can only contain references to simple variables'), + 'The text to be translated with "trans" can only contain references to simple variables', $lineno ); } diff --git a/src/Extension/TranslationExtension.php b/src/Extension/TranslationExtension.php index 6183727..23e69f1 100644 --- a/src/Extension/TranslationExtension.php +++ b/src/Extension/TranslationExtension.php @@ -1,29 +1,21 @@ get(AuthenticationServiceInterface::class)); + $service = $container->has(AuthenticationServiceInterface::class) ? + $container->get(AuthenticationServiceInterface::class) : null; + + if (! $service instanceof AuthenticationServiceInterface) { + throw new Exception(sprintf('Unable to find %s', AuthenticationServiceInterface::class)); + } + + return new AuthenticationExtension($service); } } diff --git a/src/Factory/AuthorizationExtensionFactory.php b/src/Factory/AuthorizationExtensionFactory.php index 34686e4..61dccf0 100644 --- a/src/Factory/AuthorizationExtensionFactory.php +++ b/src/Factory/AuthorizationExtensionFactory.php @@ -1,30 +1,34 @@ get(AuthorizationInterface::class)); + $authorizationInterface = $container->has(AuthorizationInterface::class) ? + $container->get(AuthorizationInterface::class) : null; + + if (! $authorizationInterface instanceof AuthorizationInterface) { + throw new Exception(sprintf('Unable to find %s', AuthorizationInterface::class)); + } + + return new AuthorizationExtension($authorizationInterface); } } diff --git a/src/Factory/FlashMessengerExtensionFactory.php b/src/Factory/FlashMessengerExtensionFactory.php index e2633be..6345635 100644 --- a/src/Factory/FlashMessengerExtensionFactory.php +++ b/src/Factory/FlashMessengerExtensionFactory.php @@ -1,30 +1,34 @@ get(RendererInterface::class)); + $renderInterface = $container->has(RendererInterface::class) ? + $container->get(RendererInterface::class) : null; + + if (! $renderInterface instanceof RendererInterface) { + throw new Exception(sprintf('Unable to find %s', RendererInterface::class)); + } + + return new FlashMessengerExtension($renderInterface); } } diff --git a/src/Factory/NavigationExtensionFactory.php b/src/Factory/NavigationExtensionFactory.php index 470cf02..735a804 100644 --- a/src/Factory/NavigationExtensionFactory.php +++ b/src/Factory/NavigationExtensionFactory.php @@ -1,31 +1,34 @@ get(RendererInterface::class); - return new NavigationExtension($navigationMenu); + $renderInterface = $container->has(RendererInterface::class) ? + $container->get(RendererInterface::class) : null; + + if (! $renderInterface instanceof RendererInterface) { + throw new Exception(sprintf('Unable to find %s', RendererInterface::class)); + } + + return new NavigationExtension($renderInterface); } } diff --git a/src/Laminas/View/HelperPluginManagerFactory.php b/src/Laminas/View/HelperPluginManagerFactory.php index bbf2ea8..d7eda59 100644 --- a/src/Laminas/View/HelperPluginManagerFactory.php +++ b/src/Laminas/View/HelperPluginManagerFactory.php @@ -1,30 +1,36 @@ get('config')['view_helpers']; - return new HelperPluginManager($container, $config); + if (! $container->has('config')) { + throw new Exception('Unable to find config'); + } + + $config = $container->get('config'); + + if (! array_key_exists('view_helpers', $config)) { + throw new Exception('Unable to find view_helpers config'); + } + + return new HelperPluginManager($container, $config['view_helpers']); } } diff --git a/src/TwigEnvironmentDelegator.php b/src/TwigEnvironmentDelegator.php index 11ae231..a11ac14 100644 --- a/src/TwigEnvironmentDelegator.php +++ b/src/TwigEnvironmentDelegator.php @@ -1,11 +1,6 @@ get('ViewHelperManager'); - $zfRenderer = new PhpRenderer(); + $zfRenderer = new PhpRenderer(); $zfRenderer->setHelperPluginManager($viewHelperManager); $environment->registerUndefinedFunctionCallback( function ($name) use ($viewHelperManager, $zfRenderer) { - if (!$viewHelperManager->has($name)) { + if (! $viewHelperManager->has($name)) { return false; } $callable = [$zfRenderer->plugin($name), '__invoke']; - $options = ['is_safe' => ['html']]; + $options = ['is_safe' => ['html']]; return new TwigFunction($name, $callable, $options); } ); diff --git a/test/ConfigProviderTest.php b/test/ConfigProviderTest.php new file mode 100644 index 0000000..f425e78 --- /dev/null +++ b/test/ConfigProviderTest.php @@ -0,0 +1,75 @@ +config = (new ConfigProvider())(); + } + + public function testHasDependencies(): void + { + $this->assertArrayHasKey('dependencies', $this->config); + } + + public function testDependenciesHasFactories(): void + { + $this->assertArrayHasKey('factories', $this->config['dependencies']); + $this->assertArrayHasKey(HelperPluginManager::class, $this->config['dependencies']['factories']); + $this->assertArrayHasKey(AuthenticationExtension::class, $this->config['dependencies']['factories']); + $this->assertArrayHasKey(AuthorizationExtension::class, $this->config['dependencies']['factories']); + $this->assertArrayHasKey(NavigationExtension::class, $this->config['dependencies']['factories']); + $this->assertArrayHasKey(FlashMessengerExtension::class, $this->config['dependencies']['factories']); + $this->assertArrayHasKey(FormElementsExtension::class, $this->config['dependencies']['factories']); + $this->assertArrayHasKey(AuthenticationService::class, $this->config['dependencies']['factories']); + } + + public function testDependenciesHasAliases(): void + { + $this->assertArrayHasKey('aliases', $this->config['dependencies']); + $this->assertArrayHasKey('ViewHelperManager', $this->config['dependencies']['aliases']); + $this->assertArrayHasKey(AuthenticationServiceInterface::class, $this->config['dependencies']['aliases']); + } + + public function testDependenciesHasLazyServices(): void + { + $this->assertArrayHasKey('lazy_services', $this->config['dependencies']); + $this->assertArrayHasKey('class_map', $this->config['dependencies']['lazy_services']); + $this->assertArrayHasKey( + NavigationRenderer::class, + $this->config['dependencies']['lazy_services']['class_map'] + ); + $this->assertArrayHasKey( + FlashMessengerRenderer::class, + $this->config['dependencies']['lazy_services']['class_map'] + ); + } + + public function testDependenciesHasDelegators(): void + { + $this->assertArrayHasKey('delegators', $this->config['dependencies']); + $this->assertArrayHasKey(Environment::class, $this->config['dependencies']['delegators']); + $this->assertArrayHasKey(FlashMessengerRenderer::class, $this->config['dependencies']['delegators']); + $this->assertArrayHasKey(NavigationRenderer::class, $this->config['dependencies']['delegators']); + } +} diff --git a/test/Extension/AuthenticationExtensionTest.php b/test/Extension/AuthenticationExtensionTest.php new file mode 100644 index 0000000..1b9e713 --- /dev/null +++ b/test/Extension/AuthenticationExtensionTest.php @@ -0,0 +1,37 @@ +createMock(AuthenticationServiceInterface::class); + $this->extension = new AuthenticationExtension($interfaceMock); + } + + public function testCreate(): void + { + $this->assertInstanceOf(AuthenticationExtension::class, $this->extension); + } + + public function testFunctions(): void + { + foreach ($this->extension->getFunctions() as $function) { + $this->assertInstanceOf(TwigFunction::class, $function); + } + } +} diff --git a/test/Extension/AuthorizationExtensionTest.php b/test/Extension/AuthorizationExtensionTest.php new file mode 100644 index 0000000..3d7c88e --- /dev/null +++ b/test/Extension/AuthorizationExtensionTest.php @@ -0,0 +1,37 @@ +createMock(AuthorizationInterface::class); + $this->extension = new AuthorizationExtension($interfaceMock); + } + + public function testCreate(): void + { + $this->assertInstanceOf(AuthorizationExtension::class, $this->extension); + } + + public function testFunctions(): void + { + foreach ($this->extension->getFunctions() as $function) { + $this->assertInstanceOf(TwigFunction::class, $function); + } + } +} diff --git a/test/Extension/DateExtensionTest.php b/test/Extension/DateExtensionTest.php new file mode 100644 index 0000000..2467403 --- /dev/null +++ b/test/Extension/DateExtensionTest.php @@ -0,0 +1,66 @@ +extension = new DateExtension(); + $coreExtension = new CoreExtension(); + $this->env = $this->createMock(Environment::class); + $this->env->method('getExtension')->willReturn($coreExtension); + } + + public function testFilters(): void + { + foreach ($this->extension->getFilters() as $filter) { + $this->assertInstanceOf(TwigFilter::class, $filter); + } + } + + public function testDiffWillReturnString(): void + { + $this->assertIsString($this->extension->diff($this->env, "2023-07-31")); + } + + public function testDiffWillReturnExceptionUnexpectedCharacters(): void + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('Unexpected character'); + $this->extension->diff($this->env, '2023-23-3322'); + } + + public function testDiffWillReturnExceptionNotFoundMessage(): void + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('The timezone could not be found in the database'); + $this->extension->diff($this->env, 'asdas'); + } + + public function testPluralizedInterval(): void + { + $month = 'month'; //can be any word + $this->assertSame('in 1 month', $this->extension->getPluralizedInterval(1, 1, $month)); + $this->assertSame('in 2 months', $this->extension->getPluralizedInterval(2, 1, $month)); + $this->assertSame('1 month ago', $this->extension->getPluralizedInterval(1, 0, $month)); + $this->assertSame('2 months ago', $this->extension->getPluralizedInterval(2, 0, $month)); + } +} diff --git a/test/Extension/FlashMessengerExtensionTest.php b/test/Extension/FlashMessengerExtensionTest.php new file mode 100644 index 0000000..c40507c --- /dev/null +++ b/test/Extension/FlashMessengerExtensionTest.php @@ -0,0 +1,47 @@ +createMock(RendererInterface::class); + $this->extension = new FlashMessengerExtension($interfaceMock); + } + + public function testCreate(): void + { + $this->assertInstanceOf(FlashMessengerExtension::class, $this->extension); + } + + public function testFunctions(): void + { + foreach ($this->extension->getFunctions() as $function) { + $this->assertInstanceOf(TwigFunction::class, $function); + } + } + + public function testWillRenderMessages(): void + { + $this->assertIsString($this->extension->renderMessages()); + } + + public function testWillRenderMessagesPartial(): void + { + $this->assertIsString($this->extension->renderMessagesPartial('partial string')); + } +} diff --git a/test/Extension/FormElementsExtensionTest.php b/test/Extension/FormElementsExtensionTest.php new file mode 100644 index 0000000..0b05487 --- /dev/null +++ b/test/Extension/FormElementsExtensionTest.php @@ -0,0 +1,21 @@ +getTests(); + foreach ($twigTests as $element) { + $this->assertInstanceOf(TwigTest::class, $element); + } + } +} diff --git a/test/Extension/NavigationExtensionTest.php b/test/Extension/NavigationExtensionTest.php new file mode 100644 index 0000000..c7ce71c --- /dev/null +++ b/test/Extension/NavigationExtensionTest.php @@ -0,0 +1,55 @@ +createMock(RendererInterface::class); + $this->extension = new NavigationExtension($interfaceMock); + } + + public function testCreate(): void + { + $this->assertInstanceOf(NavigationExtension::class, $this->extension); + } + + public function testFunctions(): void + { + foreach ($this->extension->getFunctions() as $function) { + $this->assertInstanceOf(TwigFunction::class, $function); + } + } + + public function testHtmlAttributes(): void + { + $page = new Page(); + $this->assertIsString($this->extension->htmlAttributes($page)); + } + + public function testRenderMenu(): void + { + $this->assertIsString($this->extension->renderMenu(new NavigationContainer())); + } + + public function testRenderMenuPartial(): void + { + $this->assertIsString($this->extension->renderMenuPartial(new NavigationContainer(), 'partial')); + } +} diff --git a/test/Extension/TranslationExtensionTest.php b/test/Extension/TranslationExtensionTest.php new file mode 100644 index 0000000..8069675 --- /dev/null +++ b/test/Extension/TranslationExtensionTest.php @@ -0,0 +1,23 @@ +extension = new TranslationExtension(); + } + + public function testTokenParsersIsArray(): void + { + $this->assertIsArray($this->extension->getTokenParsers()); + } +} diff --git a/test/Factory/AuthenticationExtensionFactoryTest.php b/test/Factory/AuthenticationExtensionFactoryTest.php new file mode 100644 index 0000000..f7a8918 --- /dev/null +++ b/test/Factory/AuthenticationExtensionFactoryTest.php @@ -0,0 +1,68 @@ +container = $this->createMock(ContainerInterface::class); + } + + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function testWillNotInstantiateWithoutInterface(): void + { + $this->container->expects($this->once()) + ->method('has') + ->with(AuthenticationServiceInterface::class) + ->willReturn(false); + $this->expectExceptionMessage(sprintf('Unable to find %s', AuthenticationServiceInterface::class)); + (new AuthenticationExtensionFactory())($this->container); + } + + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + * @throws Exception + */ + public function testWillInstantiateWithInterface(): void + { + $this->container->expects($this->once()) + ->method('has') + ->with(AuthenticationServiceInterface::class) + ->willReturn(true); + + $interface = $this->createMock(AuthenticationServiceInterface::class); + $this->container->method('get') + ->willReturnMap([ + [AuthenticationServiceInterface::class, $interface], + ]); + + $factory = (new AuthenticationExtensionFactory())($this->container); + + assertInstanceOf(AuthenticationExtension::class, $factory); + } +} diff --git a/test/Factory/AuthorizationExtensionFactoryTest.php b/test/Factory/AuthorizationExtensionFactoryTest.php new file mode 100644 index 0000000..49ea028 --- /dev/null +++ b/test/Factory/AuthorizationExtensionFactoryTest.php @@ -0,0 +1,67 @@ +container = $this->createMock(ContainerInterface::class); + } + + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function testWillNotInstantiateWithoutInterface(): void + { + $this->container->expects($this->once()) + ->method('has') + ->with(AuthorizationInterface::class) + ->willReturn(false); + $this->expectExceptionMessage(sprintf('Unable to find %s', AuthorizationInterface::class)); + (new AuthorizationExtensionFactory())($this->container); + } + + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + * @throws Exception + */ + public function testWillInstantiateWithInterface(): void + { + $this->container->expects($this->once()) + ->method('has') + ->with(AuthorizationInterface::class) + ->willReturn(true); + + $interface = $this->createMock(AuthorizationInterface::class); + $this->container->method('get') + ->willReturnMap([ + [AuthorizationInterface::class, $interface], + ]); + + $factory = (new AuthorizationExtensionFactory())($this->container); + + self::assertInstanceOf(AuthorizationExtension::class, $factory); + } +} diff --git a/test/Factory/FlashMessengerExtensionFactoryTest.php b/test/Factory/FlashMessengerExtensionFactoryTest.php new file mode 100644 index 0000000..9c5449f --- /dev/null +++ b/test/Factory/FlashMessengerExtensionFactoryTest.php @@ -0,0 +1,68 @@ +container = $this->createMock(ContainerInterface::class); + } + + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function testWillNotInstantiateWithoutInterface(): void + { + $this->container->expects($this->once()) + ->method('has') + ->with(RendererInterface::class) + ->willReturn(false); + + $this->expectExceptionMessage(sprintf('Unable to find %s', RendererInterface::class)); + (new FlashMessengerExtensionFactory())($this->container); + } + + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + * @throws Exception + */ + public function testWillInstantiateWithInterface(): void + { + $this->container->expects($this->once()) + ->method('has') + ->with(RendererInterface::class) + ->willReturn(true); + + $interface = $this->createMock(RendererInterface::class); + $this->container->method('get') + ->willReturnMap([ + [RendererInterface::class, $interface], + ]); + + $factory = (new FlashMessengerExtensionFactory())($this->container); + + self::assertInstanceOf(FlashMessengerExtension::class, $factory); + } +} diff --git a/test/Factory/NavigationExtensionFactoryTest.php b/test/Factory/NavigationExtensionFactoryTest.php new file mode 100644 index 0000000..06b83c8 --- /dev/null +++ b/test/Factory/NavigationExtensionFactoryTest.php @@ -0,0 +1,68 @@ +container = $this->createMock(ContainerInterface::class); + } + + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function testWillNotInstantiateWithoutInterface(): void + { + $this->container->expects($this->once()) + ->method('has') + ->with(RendererInterface::class) + ->willReturn(false); + + $this->expectExceptionMessage(sprintf('Unable to find %s', RendererInterface::class)); + (new NavigationExtensionFactory())($this->container); + } + + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + * @throws Exception + */ + public function testWillInstantiateWithInterface(): void + { + $this->container->expects($this->once()) + ->method('has') + ->with(RendererInterface::class) + ->willReturn(true); + + $interface = $this->createMock(RendererInterface::class); + $this->container->method('get') + ->willReturnMap([ + [RendererInterface::class, $interface], + ]); + + $factory = (new NavigationExtensionFactory())($this->container); + + self::assertInstanceOf(NavigationExtension::class, $factory); + } +} diff --git a/test/Laminas/View/HelperPluginManagerFactoryTest.php b/test/Laminas/View/HelperPluginManagerFactoryTest.php new file mode 100644 index 0000000..9e3ceb4 --- /dev/null +++ b/test/Laminas/View/HelperPluginManagerFactoryTest.php @@ -0,0 +1,90 @@ +container = $this->createMock(ContainerInterface::class); + } + + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function testWillNotInstantiateWithoutConfig(): void + { + $this->container->expects($this->once()) + ->method('has') + ->with('config') + ->willReturn(false); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('Unable to find config'); + (new HelperPluginManagerFactory())($this->container); + } + + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function testWillNotInstantiateWithoutViewHelpers(): void + { + $this->container->expects($this->once()) + ->method('has') + ->with('config') + ->willReturn(true); + + $this->container->expects($this->once()) + ->method('get') + ->with('config') + ->willReturn([ + 'test', + ]); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('Unable to find view_helpers config'); + (new HelperPluginManagerFactory())($this->container); + } + + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function testWillInstantiate(): void + { + $this->container->expects($this->once()) + ->method('has') + ->with('config') + ->willReturn(true); + + $this->container->expects($this->once()) + ->method('get') + ->with('config') + ->willReturn([ + 'view_helpers' => [], + ]); + + $factory = (new HelperPluginManagerFactory())($this->container); + + $this->assertInstanceOf(HelperPluginManager::class, $factory); + } +}