-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support reading enums from PHP 8.1 #3681
Comments
Well, for the cases where you already have the enum instance in a variable (coming from an object getter for instance), you would have to use |
@bertoost Is a function like |
Definitely true @stof ... but when you don't have a getter to assign it to the template, then @ThomasLandauer could be, or just enum() since the value of the key is probably the only thing you want to use/compare against. Therefore there should be an option to retrieve the enum itself too. For example when you want to build a dropdown of checklist/radio-list with the enum values... eq. |
OK, that's 2 different things:
|
@ThomasLandauer there is nothing like getter the entire enum. Calling |
Wouldn't it suffice to add an Even if it ended up being just a decorator of what powers |
At first I tried to add a twig function for native enums like this: public function enum(string $className): object
{
if (!is_subclass_of($className, Enum::class)) {
throw new \InvalidArgumentException(sprintf('"%s" is not an enum.', $className));
}
return new class ($className) {
public function __construct(private string $className)
{
}
public function __call(string $caseName, array $arguments): mixed
{
Assert::count($arguments, 0);
return ($this->className)::$caseName();
}
};
} Which allows to use it in templates: {% set PostStatus = enum('Acme\\Post\\PostStatus') %}
{% if post.status == PostStatus.Posted %}
{# ... #}
{% endif %}
{% for status in PostStatus.cases() %}
{# ... #}
{% endfor %} In the end I decided to use isser methods on my entities and not exposing enums to templates. Sharing this in case someone will find it useful. :) This code will need some changes to work with built-in enums. |
Hi, my solution: <?php
declare(strict_types=1);
namespace App\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class EnumExtension extends AbstractExtension
{
public function getFunctions(): array
{
return [
new TwigFunction('enum', [$this, 'enum']),
];
}
public function enum(string $fullClassName): object
{
$parts = explode('::', $fullClassName);
$className = $parts[0];
$constant = $parts[1] ?? null;
if (!enum_exists($className)) {
throw new \InvalidArgumentException(sprintf('"%s" is not an enum.', $className));
}
if ($constant) {
return constant($fullClassName);
}
return new class($fullClassName) {
public function __construct(private string $fullClassName)
{
}
public function __call(string $caseName, array $arguments): mixed
{
return call_user_func_array([$this->fullClassName, $caseName], $arguments);
}
};
}
} Templates: {% dump(enum('App\\Entity\\Status').cases()) %}
{% dump(enum('App\\Entity\\Status').customStaticMethod()) %}
{% dump(enum('App\\Entity\\Status::NEW')) %}
{% dump(enum('App\\Entity\\Status::NEW').name()) %}
{% dump(enum('App\\Entity\\Status::NEW').customMethod()) %} |
I was a bit skeptical at first but both ideas from @luxemate and @codeg-pl look interesting to me.
About this use case, the |
Slightly different implementation from @codeg-pl's version that allows for something closer to PHP's syntax. <?php declare(strict_types=1);
namespace App\Twig;
use BadMethodCallException;
use InvalidArgumentException;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class EnumExtension extends AbstractExtension
{
/**
* @return TwigFunction[]
*/
public function getFunctions(): array
{
return [
new TwigFunction('enum', [$this, 'createProxy']),
];
}
public function createProxy(string $enumFQN): object
{
return new class($enumFQN) {
public function __construct(private readonly string $enum)
{
if (!enum_exists($this->enum)) {
throw new InvalidArgumentException("$this->enum is not an Enum type and cannot be used in this function");
}
}
public function __call(string $name, array $arguments)
{
$enumFQN = sprintf('%s::%s', $this->enum, $name);
if (defined($enumFQN)) {
return constant($enumFQN);
}
if (method_exists($this->enum, $name)) {
return $this->enum::$name(...$arguments);
}
throw new BadMethodCallException("Neither \"{$enumFQN}\" nor \"{$enumFQN}::{$name}()\" exist in this runtime.");
}
};
}
} {% set OrderStatus = enum('\\App\\Helpers\\OrderStatus') %}
{% set waitingStatus = [ OrderStatus.Placed, OrderStatus.BeingPrepared ] %}
{% if order.status in waitingStatus %}
Be patient
{% elseif order.status == OrderStatus.Completed %}
Order complete!
{% endif %}
...
<select>
{% for type in OrderStatus.cases() %}
<option value="{{ type.value }}">
{{ type.stringLiteral() }} {# getStringLiteral is a custom method in my enum #}
</option>
{% endfor %}
</select> Updates
|
Excellent. Thanks |
The @allejo solution should be in the core IMHO. |
Such function would give access to any static method available in PHP (at least the suggested implementation). This cannot go in core as is (integrating that with the sandbox system would be a nightmare). |
Ohai, never thought other people would find my snippet helpful. @stof is 100% right, my snippet I'm not too familiar with Twig's sandboxing other than it being a whitelist-only system. The Edit: It just hit me, |
Thanks @allejo ! This works like charm. Should be added to the core. |
A way to work with ENUMS:enum MyEnum : string
{
case READY = 'в очереди';
case PROCESSING = 'обрабатывается';
case REJECTED = 'забраковано';
public static function getAsAssociatedArray () : array
{
$to_return = [];
foreach (self::cases() as $status) {
$to_return[$status->name] = $status;
$to_return[strtolower($status->name)] = $status;
}
return $to_return;
} Controller (new \App\Twig)->render('template.twig', ["my_enums" => MyEnum::getAsAssociatedArray()]); TWIG {# @var my_enums MyEnum #}
{{ dump(my_enums.ready) }}
{{ dump(my_enums.ready.name) }}
{{ dump(my_enums.READY.value) }} |
@stof Do your objections still hold with the updates made to #3681 (comment)? If I am not missing anything, it takes a solution like this to be able to pass beim cases e. g. into methods from Twig? |
Being pendantic here, but that should be |
@EarthDweller A simpler way is: (new \App\Twig)->render('template.twig', ["my_enums" => array_column(Module::cases(), null, 'name')]); Then you don't need the getAsAssociatedArray function. |
@michelbrons TWIG code more accurate and readable when all in lower_snake_case, BUT sometimes more visually usefull UPPERCASE |
Only uppercase works.. It would be nice if a developer can pass enums to templates and the designer can use autocompletion using my_enums.R...{Modal} |
@michelbrons |
Why pass ENUM through controller? Use global solution: #3681 (comment) I use it in production :) |
Depends from how many templates use enum, if only one, controller good way, if uses in more than one template, Twig\Extension – good way. |
I made a small modification to the sollution of @codeg-pl #3681 (comment) I added the code below to the
In Twig I can now use Enum values from the database like:
|
Definitely the best advice, thanks |
@allejo It could be possible to limit the issue with a check if it is one of the cases.
|
Hi,
Since PHP 8.1 we can use Enums. Which in my opinion are a great asset to PHP.
But unfortunate there is not an elegant way of retrieving values inside Twig templates.
I currently use this in my Symfony project;
Which will return the value of the key in de the Enum.
Maybe it's good to add some specific functions for this in the Twig core?
Regards, Bert
The text was updated successfully, but these errors were encountered: