Skip to content

Commit

Permalink
feature #113 Expose Enum static methods to Twig (Tom32i, ogizanagi)
Browse files Browse the repository at this point in the history
This PR was merged into the 1.x-dev branch.

Discussion
----------

Expose Enum static methods to Twig

- [x] Twig extension
- [x] Remove Twig extension service definition if Twig is not loaded
- [x] Documentation
- [x] Tests

Commits
-------

00f40fd Continue Twig extension feature
25f62a0 Expose Enum static methods to Twig
  • Loading branch information
ogizanagi committed Dec 10, 2020
2 parents 159dc4f + 00f40fd commit f7badd1
Show file tree
Hide file tree
Showing 10 changed files with 257 additions and 13 deletions.
5 changes: 3 additions & 2 deletions .php_cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@ $finder = PhpCsFixer\Finder::create()
->notPath('tests/Fixtures/Bridge/Symfony/Validator/Constraint/ObjectWithEnumChoiceAsPhpAttribute.php')
;

return PhpCsFixer\Config::create()
return (new PhpCsFixer\Config())
->setRiskyAllowed(true)
->setUsingCache(true)
->setFinder($finder)
->setRules([
'@Symfony' => true,
'php_unit_namespaced' => true,
'psr0' => false,
'php_unit_method_casing' => false,
'psr_autoloading' => true,
'concat_space' => ['spacing' => 'one'],
'phpdoc_summary' => false,
'phpdoc_annotation_without_dot' => false,
Expand Down
10 changes: 8 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ test:
# Lint #
########

fix-phpcsfixer: export PHP_CS_FIXER_FUTURE_MODE = 1
fix-phpcsfixer:
lint: lint-php-cs-fixer

fix-php-cs-fixer: export PHP_CS_FIXER_FUTURE_MODE = 1
fix-php-cs-fixer:
vendor/bin/php-cs-fixer fix --config=.php_cs --no-interaction

lint-php-cs-fixer: export PHP_CS_FIXER_FUTURE_MODE = 1
lint-php-cs-fixer:
vendor/bin/php-cs-fixer fix --config=.php_cs --no-interaction --dry-run --diff
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ Table of Contents
* [Api-Platform](#api-platform)
* [OpenApi / Swagger](#openapi--swagger)
* [JavaScript](#javascript)
* [Twig](#twig)
* [API](#api)
* [Simple enum](#simple-enum)
* [Readable enum](#readable-enum)
Expand Down Expand Up @@ -963,6 +964,18 @@ Then, use the CLI command to generate the JS files:
bin/console elao:enum:dump-js [--lib-path] [--base-dir] [<enum:path>...]
```

## Twig

The [EnumExtension](src/Bridge/Twig/Extension/EnumExtension.php) exposes
static methods of the enum classes through the following Twig functions:

- `enum_get($class, $value)`
- `enum_values($class)`
- `enum_accepts($class, $value)`
- `enum_instances($class)`
- `enum_readables($class)`
- `enum_readable_for($class, $value)`

# API

## Simple enum
Expand Down
13 changes: 10 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
{
"name": "elao/enum",
"description": "Enumerations for PHP and frameworks integrations",
"keywords": ["enum", "symfony", "doctrine"],
"keywords": [
"enum",
"symfony",
"doctrine"
],
"type": "library",
"homepage": "https://github.com/Elao/PhpEnums",
"license": "MIT",
Expand All @@ -23,7 +27,9 @@
"psr-4": {
"Elao\\Enum\\": "src/"
},
"files": ["src/Bridge/Symfony/VarDumper/Resources/register_caster.php"]
"files": [
"src/Bridge/Symfony/VarDumper/Resources/register_caster.php"
]
},
"autoload-dev": {
"psr-4": {
Expand Down Expand Up @@ -54,7 +60,8 @@
"symfony/translation": "^4.4|^5.1",
"symfony/twig-bundle": "^4.4|^5.1",
"symfony/validator": "^4.4|^5.1",
"symfony/yaml": "^4.4|^5.1"
"symfony/yaml": "^4.4|^5.1",
"twig/twig": "^2.12|^3.0"
},
"extra": {
"branch-alias": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@

namespace Elao\Enum\Bridge\Symfony\Bundle\DependencyInjection;

use ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle;
use ApiPlatform\Core\JsonSchema\TypeFactory;
use Elao\Enum\Bridge\ApiPlatform\Core\JsonSchema\Type\ElaoEnumType;
use Elao\Enum\Bridge\Doctrine\DBAL\Types\TypesDumper;
use Elao\Enum\Bridge\Symfony\Console\Command\DumpJsEnumsCommand;
use Elao\Enum\Bridge\Symfony\HttpKernel\Controller\ArgumentResolver\EnumValueResolver;
use Elao\Enum\Bridge\Symfony\Serializer\Normalizer\EnumNormalizer;
use Elao\Enum\Bridge\Symfony\Translation\Extractor\EnumExtractor;
use Elao\Enum\Bridge\Twig\Extension\EnumExtension;
use Elao\Enum\FlaggedEnum;
use Symfony\Bundle\TwigBundle\TwigBundle;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
Expand Down Expand Up @@ -77,7 +80,9 @@ public function load(array $configs, ContainerBuilder $container)
$this->registerTranslationExtractorConfiguration($config['translation_extractor'], $container);
}

if (!class_exists(TypeFactory::class)) {
$bundles = $container->getParameter('kernel.bundles');

if (!class_exists(TypeFactory::class) || !\in_array(ApiPlatformBundle::class, $bundles, true)) {
$container->removeDefinition(ElaoEnumType::class);
}

Expand All @@ -90,6 +95,10 @@ public function load(array $configs, ContainerBuilder $container)
);
}

if (!\in_array(TwigBundle::class, $bundles, true)) {
$container->removeDefinition(EnumExtension::class);
}

$jsEnums = $config['js'];
$container->getDefinition(DumpJsEnumsCommand::class)
->replaceArgument(0, $jsEnums['paths'])
Expand Down
4 changes: 4 additions & 0 deletions src/Bridge/Symfony/Bundle/Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,9 @@
<service id="Elao\Enum\Bridge\ApiPlatform\Core\JsonSchema\Type\ElaoEnumType" decorates="api_platform.json_schema.type_factory">
<argument type="service" id="Elao\Enum\Bridge\ApiPlatform\Core\JsonSchema\Type\ElaoEnumType.inner" />
</service>

<service id="Elao\Enum\Bridge\Twig\Extension\EnumExtension">
<tag name="twig.extension" />
</service>
</services>
</container>
86 changes: 86 additions & 0 deletions src/Bridge/Twig/Extension/EnumExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

/*
* This file is part of the "elao/enum" package.
*
* Copyright (C) Elao
*
* @author Elao <[email protected]>
*/

namespace Elao\Enum\Bridge\Twig\Extension;

use Elao\Enum\EnumInterface;
use Elao\Enum\Exception\InvalidArgumentException;
use Elao\Enum\ReadableEnumInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;

class EnumExtension extends AbstractExtension
{
public function getFunctions()
{
return [
new TwigFunction('enum_get', [$this, 'get']),
new TwigFunction('enum_values', [$this, 'values']),
new TwigFunction('enum_accepts', [$this, 'accepts']),
new TwigFunction('enum_instances', [$this, 'instances']),
new TwigFunction('enum_readables', [$this, 'readables']),
new TwigFunction('enum_readable_for', [$this, 'readableFor']),
];
}

public function get(string $className, $value): EnumInterface
{
if (!is_a($className, EnumInterface::class, true)) {
throw new InvalidArgumentException(sprintf('"%s" is not an "%s".', $className, EnumInterface::class));
}

return \call_user_func([$className, 'get'], $value);
}

public function values(string $className): array
{
if (!is_a($className, EnumInterface::class, true)) {
throw new InvalidArgumentException(sprintf('"%s" is not an "%s".', $className, EnumInterface::class));
}

return \call_user_func([$className, 'values']);
}

public function accepts(string $className, $value): bool
{
if (!is_a($className, EnumInterface::class, true)) {
throw new InvalidArgumentException(sprintf('"%s" is not an "%s".', $className, EnumInterface::class));
}

return \call_user_func([$className, 'accepts'], $value);
}

public function instances(string $className): array
{
if (!is_a($className, EnumInterface::class, true)) {
throw new InvalidArgumentException(sprintf('"%s" is not an "%s".', $className, EnumInterface::class));
}

return \call_user_func([$className, 'instances']);
}

public function readables(string $className): array
{
if (!is_a($className, ReadableEnumInterface::class, true)) {
throw new InvalidArgumentException(sprintf('"%s" is not a "%s".', $className, ReadableEnumInterface::class));
}

return \call_user_func([$className, 'readables']);
}

public function readableFor(string $className, $value): string
{
if (!is_a($className, ReadableEnumInterface::class, true)) {
throw new InvalidArgumentException(sprintf('"%s" is not a "%s".', $className, ReadableEnumInterface::class));
}

return \call_user_func([$className, 'readableFor'], $value);
}
}
5 changes: 0 additions & 5 deletions tests/Fixtures/Integration/Symfony/config/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,6 @@ services:
class: Psr\Log\NullLogger
public: false

Elao\Enum\Bridge\Symfony\HttpKernel\Controller\ArgumentResolver\EnumValueResolver:
class: Elao\Enum\Bridge\Symfony\HttpKernel\Controller\ArgumentResolver\EnumValueResolver
tags:
- { name: controller.argument_value_resolver, priority: 101 }

controllers:
namespace: App\Controller\
resource: '%kernel.project_dir%/src/Controller'
Expand Down
2 changes: 2 additions & 0 deletions tests/Fixtures/Integration/Symfony/src/AppKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/

use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
use Elao\Enum\Bridge\Symfony\Bundle\ElaoEnumBundle;
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\TwigBundle\TwigBundle;
use Symfony\Component\Config\Loader\LoaderInterface;
Expand All @@ -25,6 +26,7 @@ public function registerBundles()
new FrameworkBundle(),
new TwigBundle(),
new DoctrineBundle(),
new ElaoEnumBundle(),
];
}

Expand Down
121 changes: 121 additions & 0 deletions tests/Unit/Bridge/Twig/Extension/EnumExtensionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

/*
* This file is part of the "elao/enum" package.
*
* Copyright (C) Elao
*
* @author Elao <[email protected]>
*/

namespace Elao\Enum\Tests\Unit\Bridge\Twig\Extension;

use Elao\Enum\Bridge\Twig\Extension\EnumExtension;
use Elao\Enum\Exception\InvalidArgumentException;
use Elao\Enum\Tests\Fixtures\Enum\Gender;
use Elao\Enum\Tests\TestCase;
use Twig\TwigFunction;

class EnumExtensionTest extends TestCase
{
/** @var EnumExtension */
private $extension;

protected function setUp(): void
{
$this->extension = new EnumExtension();
}

public function test enum_get(): void
{
self::assertSame(
Gender::FEMALE(),
$this->getFunction('enum_get')->getCallable()(Gender::class, Gender::FEMALE),
);
}

public function test enum_get with invalid class(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('"stdClass" is not an "Elao\Enum\EnumInterface"');

$this->getFunction('enum_get')->getCallable()(\stdClass::class, Gender::FEMALE);
}

public function test enum_values(): void
{
self::assertSame(Gender::values(), $this->getFunction('enum_values')->getCallable()(Gender::class));
}

public function test enum_values with invalid class(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('"stdClass" is not an "Elao\Enum\EnumInterface"');

self::assertSame(Gender::values(), $this->getFunction('enum_values')->getCallable()(\stdClass::class));
}

public function test enum_accepts(): void
{
self::assertTrue($this->getFunction('enum_accepts')->getCallable()(Gender::class, Gender::FEMALE));
self::assertFalse($this->getFunction('enum_accepts')->getCallable()(Gender::class, 'not a valid value'));
}

public function test enum_accepts with invalid class(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('"stdClass" is not an "Elao\Enum\EnumInterface"');

self::assertSame(Gender::values(), $this->getFunction('enum_accepts')->getCallable()(\stdClass::class, Gender::FEMALE));
}

public function test enum_instances(): void
{
self::assertSame(Gender::instances(), $this->getFunction('enum_instances')->getCallable()(Gender::class));
}

public function test enum_instances with invalid class(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('"stdClass" is not an "Elao\Enum\EnumInterface"');

self::assertSame(Gender::values(), $this->getFunction('enum_instances')->getCallable()(\stdClass::class));
}

public function test enum_readables(): void
{
self::assertSame(Gender::readables(), $this->getFunction('enum_readables')->getCallable()(Gender::class));
}

public function test enum_readables with invalid class(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('"stdClass" is not a "Elao\Enum\ReadableEnumInterface"');

self::assertSame(Gender::values(), $this->getFunction('enum_readables')->getCallable()(\stdClass::class));
}

public function test enum_readable_for(): void
{
self::assertSame(Gender::FEMALE()->getReadable(), $this->getFunction('enum_readable_for')->getCallable()(Gender::class, Gender::FEMALE));
}

public function test enum_readable_for with invalid class(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('"stdClass" is not a "Elao\Enum\ReadableEnumInterface"');

self::assertSame(Gender::values(), $this->getFunction('enum_readable_for')->getCallable()(\stdClass::class, Gender::FEMALE));
}

private function getFunction(string $method): TwigFunction
{
foreach ($this->extension->getFunctions() as $function) {
if ($method === $function->getName()) {
return $function;
}
}

self::fail("Twig function \"$method\" is not registered");
}
}

0 comments on commit f7badd1

Please sign in to comment.