Skip to content
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 editable mode on FieldDescriptionInterface::TYPE_ENUM #8219

Merged
merged 1 commit into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Action/SetObjectFieldValueAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ public function __invoke(Request $request): JsonResponse
$value = $dataTransformer->reverseTransform($value);
}

if (null === $value && FieldDescriptionInterface::TYPE_CHOICE === $fieldDescription->getType()) {
if (null === $value && \in_array($fieldDescription->getType(), [FieldDescriptionInterface::TYPE_CHOICE, FieldDescriptionInterface::TYPE_ENUM], true)) {
return new JsonResponse(\sprintf(
'Edit failed, object with id "%s" not found in association "%s".',
$objectId,
Expand Down
73 changes: 73 additions & 0 deletions src/Form/DataTransformer/BackedEnumTransformer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

declare(strict_types=1);

/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Sonata\AdminBundle\Form\DataTransformer;

use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Form\Exception\UnexpectedTypeException;

/**
* @phpstan-template T of \BackedEnum
* @phpstan-implements DataTransformerInterface<T, int|string>
*/
final class BackedEnumTransformer implements DataTransformerInterface
{
/**
* @phpstan-param class-string<T> $className
*/
public function __construct(
private string $className,
) {
}

/**
* @param int|string|null $value
*
* @phpstan-return T|null
*/
public function reverseTransform($value): ?\BackedEnum
{
if (null === $value || '' === $value) {
return null;
}

if (!\is_int($value) && !\is_string($value)) {
throw new TransformationFailedException(\sprintf('Could not transform value: expecting an int or string, got "%s".', get_debug_type($value)));
}

try {
return $this->className::from($value);
} catch (\ValueError|\TypeError) {
throw new TransformationFailedException(\sprintf('Could not transform value "%s".', $value));
}
}

/**
* @param \BackedEnum|null $value
*
* @phpstan-param T|null $value
*/
public function transform($value): string|int|null
{
if (null === $value) {
return null;
}

if (!$value instanceof \BackedEnum) {
throw new UnexpectedTypeException($value, \BackedEnum::class);
}

return $value->value;
}
}
13 changes: 13 additions & 0 deletions src/Form/DataTransformerResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace Sonata\AdminBundle\Form;

use Sonata\AdminBundle\FieldDescription\FieldDescriptionInterface;
use Sonata\AdminBundle\Form\DataTransformer\BackedEnumTransformer;
use Sonata\AdminBundle\Form\DataTransformer\ModelToIdTransformer;
use Sonata\AdminBundle\Model\ModelManagerInterface;
use Symfony\Component\Form\DataTransformerInterface;
Expand Down Expand Up @@ -75,6 +76,18 @@ public function resolve(
return $this->globalCustomTransformers[$fieldType];
}

if (FieldDescriptionInterface::TYPE_ENUM === $fieldType) {
$className = $fieldDescription->getOption('class');

if (
null !== $className
&& \is_string($className)
&& is_a($className, \BackedEnum::class, true)
) {
return new BackedEnumTransformer($className);
}
}

// Handle entity choice association type, transforming the value into entity
if (FieldDescriptionInterface::TYPE_CHOICE === $fieldType) {
$targetModel = $fieldDescription->getTargetModel();
Expand Down
2 changes: 2 additions & 0 deletions src/Resources/views/CRUD/base_list_field.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ file that was distributed with this source code.
{% set data_value = value|date('Y-m-d', options.timezone|default(null)) %}
{% elseif field_description.type == constant('Sonata\\AdminBundle\\FieldDescription\\FieldDescriptionInterface::TYPE_BOOLEAN') and value is empty %}
{% set data_value = 0 %}
{% elseif field_description.type == constant('Sonata\\AdminBundle\\FieldDescription\\FieldDescriptionInterface::TYPE_ENUM') and value is not empty %}
{% set data_value = value.value %}
{% elseif value is iterable %}
{% set data_value = value|json_encode %}
{% else %}
Expand Down
15 changes: 15 additions & 0 deletions src/Resources/views/CRUD/list_enum.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,21 @@ file that was distributed with this source code.

{% extends get_admin_template('base_list_field', admin.code) %}

{% set is_editable =
field_description.option('editable', false) and
admin.hasAccess('edit', object)
%}
{% set x_editable_type = field_description.type|sonata_xeditable_type %}

{% block field_span_attributes %}
{% if is_editable and x_editable_type %}
{% apply spaceless %}
{{ parent() }}
data-source="{{ field_description|sonata_xeditable_choices|json_encode }}"
{% endapply %}
{% endif %}
{% endblock %}

{% block field %}
{%- include '@SonataAdmin/CRUD/display_enum.html.twig' with {
value: value,
Expand Down
6 changes: 6 additions & 0 deletions src/Twig/XEditableRuntime.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
final class XEditableRuntime implements RuntimeExtensionInterface
{
public const FIELD_DESCRIPTION_MAPPING = [
FieldDescriptionInterface::TYPE_ENUM => 'select',
FieldDescriptionInterface::TYPE_CHOICE => 'select',
FieldDescriptionInterface::TYPE_BOOLEAN => 'select',
FieldDescriptionInterface::TYPE_TEXTAREA => 'textarea',
Expand Down Expand Up @@ -83,6 +84,11 @@ public function getXEditableChoices(FieldDescriptionInterface $fieldDescription)
break;
}

if ($text instanceof \BackedEnum) {
$value = $text->value;
$text = $text->name;
}

if (\is_string($catalogue)) {
$text = $this->translator->trans($text, [], $catalogue);
}
Expand Down
78 changes: 78 additions & 0 deletions tests/Form/DataTransformer/BackedEnumTransformerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

declare(strict_types=1);

/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Sonata\AdminBundle\Tests\Form\DataTransformer;

use PHPUnit\Framework\TestCase;
use Sonata\AdminBundle\Form\DataTransformer\BackedEnumTransformer;
use Sonata\AdminBundle\Tests\Fixtures\Enum\Suit;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Form\Exception\UnexpectedTypeException;

/**
* @requires PHP 8.1
*/
final class BackedEnumTransformerTest extends TestCase
{
public function testReverseTransform(): void
{
$transformer = new BackedEnumTransformer(Suit::class);

static::assertNull($transformer->reverseTransform(null));
static::assertNull($transformer->reverseTransform(''));
static::assertSame(Suit::Hearts, $transformer->reverseTransform(Suit::Hearts->value));
}

public function testReverseTransformNotValidValue(): void
{
$this->expectException(TransformationFailedException::class);
$this->expectExceptionMessage('Could not transform value "not_valid_value".');

$transformer = new BackedEnumTransformer(Suit::class);
$transformer->reverseTransform('not_valid_value');
}

/**
* @psalm-suppress InvalidArgument
*/
public function testReverseTransformNotScalar(): void
{
$this->expectException(TransformationFailedException::class);
$this->expectExceptionMessage('Could not transform value: expecting an int or string, got "stdClass".');

$transformer = new BackedEnumTransformer(Suit::class);
// @phpstan-ignore-next-line
$transformer->reverseTransform(new \stdClass());
}

public function testTransform(): void
{
$transformer = new BackedEnumTransformer(Suit::class);

static::assertNull($transformer->transform(null));
static::assertSame(Suit::Clubs->value, $transformer->transform(Suit::Clubs));
}

/**
* @psalm-suppress InvalidArgument
*/
public function testTransformUnexpectedType(): void
{
$this->expectException(UnexpectedTypeException::class);
$this->expectExceptionMessage('Expected argument of type "BackedEnum", "stdClass" given');

$transformer = new BackedEnumTransformer(Suit::class);
// @phpstan-ignore-next-line
$transformer->transform(new \stdClass());
}
}
16 changes: 16 additions & 0 deletions tests/Twig/XEditableRuntimeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

use PHPUnit\Framework\TestCase;
use Sonata\AdminBundle\FieldDescription\FieldDescriptionInterface;
use Sonata\AdminBundle\Tests\Fixtures\Enum\Suit;
use Sonata\AdminBundle\Twig\XEditableRuntime;
use Symfony\Component\Translation\Translator;

Expand Down Expand Up @@ -91,5 +92,20 @@ public function provideGetXEditableChoicesIsIdempotentCases(): iterable
['value' => 'Status2', 'text' => 'Alias2'],
],
];

// TODO: Remove the "if" check when dropping support of PHP < 8.1 and add the case to the list
if (\PHP_VERSION_ID >= 80100) {
yield 'enum cases' => [
[
'required' => false,
'multiple' => false,
'choices' => [Suit::Hearts, Suit::Clubs],
],
[
['value' => 'H', 'text' => 'Hearts'],
['value' => 'C', 'text' => 'Clubs'],
],
];
}
}
}
Loading