mirror of
https://github.com/twigphp/Twig.git
synced 2026-09-12 18:36:53 +00:00
feature #4352 Implement the enum function (nicolas-grekas)
This PR was merged into the 3.x branch.
Discussion
----------
Implement the `enum` function
Fixes #3681
Requires #4353
Commits
-------
cff6f1ca80 Implement the `enum` function
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -10,6 +10,7 @@ Functions
|
||||
cycle
|
||||
date
|
||||
dump
|
||||
enum
|
||||
enum_cases
|
||||
html_classes
|
||||
html_cva
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user