Implement the enum function

This commit is contained in:
Nicolas Grekas
2024-09-26 17:30:58 +02:00
parent faf8471d47
commit cff6f1ca80
10 changed files with 172 additions and 0 deletions
+1
View File
@@ -15,6 +15,7 @@
* Support Markup instances (and any other \Stringable) as dynamic mapping keys
* Deprecate the `sandbox` tag
* Improve the way one can deprecate a Twig callable (use `deprecation_info` instead of the other callable options)
* Add the `enum` function
# 3.14.0 (2024-09-09)
+26
View File
@@ -0,0 +1,26 @@
``enum_cases``
==============
.. versionadded:: 3.15
The ``enum`` function was added in Twig 3.15.
``enum`` gives access to enums:
.. code-block:: twig
{# display one specific case of a backed enum #}
{{ enum('App\\MyEnum').SomeCase.value }}
{# get all cases of an enum #}
{% enum('App\\MyEnum').cases() %}
{# call any methods of the enum class #}
{% enum('App\\MyEnum').someMethod() %}
When using a string literal for the ``enum`` argument, it will be validated during compile time to be a valid enum name.
Arguments
---------
* ``enum``: The FQCN of the enum
+1
View File
@@ -10,6 +10,7 @@ Functions
cycle
date
dump
enum
enum_cases
html_classes
html_cva
+26
View File
@@ -50,6 +50,7 @@ use Twig\Node\Expression\Binary\SubBinary;
use Twig\Node\Expression\BlockReferenceExpression;
use Twig\Node\Expression\Filter\DefaultFilter;
use Twig\Node\Expression\FunctionNode\EnumCasesFunction;
use Twig\Node\Expression\FunctionNode\EnumFunction;
use Twig\Node\Expression\GetAttrExpression;
use Twig\Node\Expression\NullCoalesceExpression;
use Twig\Node\Expression\ParentExpression;
@@ -264,6 +265,7 @@ final class CoreExtension extends AbstractExtension
new TwigFunction('include', [self::class, 'include'], ['needs_environment' => true, 'needs_context' => true, 'is_safe' => ['all']]),
new TwigFunction('source', [self::class, 'source'], ['needs_environment' => true, 'is_safe' => ['all']]),
new TwigFunction('enum_cases', [self::class, 'enumCases'], ['node_class' => EnumCasesFunction::class]),
new TwigFunction('enum', [self::class, 'enum'], ['node_class' => EnumFunction::class]),
];
}
@@ -1501,6 +1503,30 @@ final class CoreExtension extends AbstractExtension
return $enum::cases();
}
/**
* Provides the ability to access enums by their class names.
*
* @template T of \UnitEnum
*
* @param class-string<T> $enum
*
* @return T
*
* @internal
*/
public static function enum(string $enum): \UnitEnum
{
if (!enum_exists($enum)) {
throw new RuntimeError(sprintf('"%s" is not an enum.', $enum));
}
if (!$cases = $enum::cases()) {
throw new RuntimeError(sprintf('"%s" is an empty enum.', $enum));
}
return $cases[0];
}
/**
* Provides the ability to get constants from instances as well as class/global constants.
*
@@ -0,0 +1,45 @@
<?php
namespace Twig\Node\Expression\FunctionNode;
use Twig\Compiler;
use Twig\Error\SyntaxError;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\FunctionExpression;
class EnumFunction extends FunctionExpression
{
public function compile(Compiler $compiler): void
{
$arguments = $this->getNode('arguments');
if ($arguments->hasNode('enum')) {
$firstArgument = $arguments->getNode('enum');
} elseif ($arguments->hasNode('0')) {
$firstArgument = $arguments->getNode('0');
} else {
$firstArgument = null;
}
if (!$firstArgument instanceof ConstantExpression || 1 !== \count($arguments)) {
parent::compile($compiler);
return;
}
$value = $firstArgument->getAttribute('value');
if (!\is_string($value)) {
throw new SyntaxError('The first argument of the "enum" function must be a string.', $this->getTemplateLine(), $this->getSourceContext());
}
if (!enum_exists($value)) {
throw new SyntaxError(\sprintf('The first argument of the "enum" function must be the name of an enum, "%s" given.', $value), $this->getTemplateLine(), $this->getSourceContext());
}
if (!$cases = $value::cases()) {
throw new SyntaxError(\sprintf('The first argument of the "enum" function must be a non-empty enum, "%s" given.', $value), $this->getTemplateLine(), $this->getSourceContext());
}
$compiler->raw(\sprintf('%s::%s', $value, $cases[0]->name));
}
}
@@ -0,0 +1,13 @@
--TEST--
"enum" function with invalid dynamic enum class
--CONDITION--
\PHP_VERSION_ID >= 80100
--TEMPLATE--
{% set from_variable = 'Twig\\Tests\\NonExistentEnum' %}
{% for c in enum(from_variable).cases() %}
{{~ c.name }}
{% endfor %}
--DATA--
return []
--EXCEPTION--
Twig\Error\RuntimeError: "Twig\Tests\NonExistentEnum" is not an enum in "index.twig" at line 3.
@@ -0,0 +1,10 @@
--TEST--
"enum" function with invalid enum class
--CONDITION--
\PHP_VERSION_ID >= 80100
--TEMPLATE--
{% for c in enum('Twig\\Tests\\NonExistentEnum').cases() %}
{{~ c.name }}
{% endfor %}
--EXCEPTION--
Twig\Error\SyntaxError: The first argument of the "enum" function must be the name of an enum, "Twig\Tests\NonExistentEnum" given in "index.twig" at line 2.
@@ -0,0 +1,10 @@
--TEST--
"enum" function with missing \ escaping
--CONDITION--
\PHP_VERSION_ID >= 80100
--TEMPLATE--
{% for c in enum('Twig\Tests\DummyBackedEnum').cases() %}
{{~ c.name }}
{% endfor %}
--EXCEPTION--
Twig\Error\SyntaxError: The first argument of the "enum" function must be the name of an enum, "TwigTestsDummyBackedEnum" given in "index.twig" at line 2.
@@ -0,0 +1,10 @@
--TEST--
"enum" function with invalid literal type
--CONDITION--
\PHP_VERSION_ID >= 80100
--TEMPLATE--
{% for c in enum(13).cases() %}
{{~ c.name }}
{% endfor %}
--EXCEPTION--
Twig\Error\SyntaxError: The first argument of the "enum" function must be a string in "index.twig" at line 2.
+30
View File
@@ -0,0 +1,30 @@
--TEST--
"enum" function
--CONDITION--
\PHP_VERSION_ID >= 80100
--TEMPLATE--
{{ enum('Twig\\Tests\\DummyBackedEnum').FOO.value }}
{% for c in enum('Twig\\Tests\\DummyBackedEnum').cases() %}
{{~ c.name }}: {{ c.value }}
{% endfor %}
{{ enum('Twig\\Tests\\DummyUnitEnum').BAR.name }}
{% for c in enum('Twig\\Tests\\DummyUnitEnum').cases() %}
{{~ c.name }}
{% endfor %}
{% set from_variable='Twig\\Tests\\DummyUnitEnum' %}
{{ enum(from_variable).BAR.name }}
{% for c in enum(from_variable).cases() %}
{{~ c.name }}
{% endfor %}
--DATA--
return []
--EXPECT--
foo
FOO: foo
BAR: bar
BAR
BAR
BAZ
BAR
BAR
BAZ