Skip to content
Open
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
4 changes: 2 additions & 2 deletions doc/fields.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1127,8 +1127,8 @@ the full address is only visible on the detail and edit pages::

[$local, $domain] = explode('@', $email, 2);
$field->setFormattedValue(substr($local, 0, 1).'***@'.$domain);
}
}
}
}

.. tip::

Expand Down
30 changes: 29 additions & 1 deletion src/Filter/ChoiceFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,44 @@ public function setChoices(array $choices): self
}

/**
* @param array<string|TranslatableInterface> $choiceGenerator
* @param array<string|TranslatableInterface|\UnitEnum> $choiceGenerator
*/
public function setTranslatableChoices(array $choiceGenerator): self
{
// Support passing a list of enum cases (e.g. MyEnum::cases()) when the
// enum implements TranslatableInterface. In that case, the submitted
// value must be the enum backing value (or its name for UnitEnum),
// otherwise the keys (0, 1, 2, ...) of the list would be submitted
// and the query would never match any row.
if (array_is_list($choiceGenerator) && [] !== $choiceGenerator && $this->areAllEnums($choiceGenerator)) {
$choices = [];
foreach ($choiceGenerator as $case) {
$key = $case instanceof \BackedEnum ? $case->value : $case->name;
$choices[$key] = $case;
}
$choiceGenerator = $choices;
}

$this->dto->setFormTypeOption('value_type_options.choices', array_keys($choiceGenerator));
$this->dto->setFormTypeOption('value_type_options.choice_label', static fn ($value) => $choiceGenerator[$value]);

return $this;
}

/**
* @param array<mixed> $values
*/
private function areAllEnums(array $values): bool
{
foreach ($values as $value) {
if (!$value instanceof \UnitEnum) {
return false;
}
}

return true;
}

public function renderExpanded(bool $isExpanded = true): self
{
$this->dto->setFormTypeOption('value_type_options.expanded', $isExpanded);
Expand Down
65 changes: 65 additions & 0 deletions tests/Unit/Filter/ChoiceFilterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

namespace EasyCorp\Bundle\EasyAdminBundle\Tests\Unit\Filter;

use EasyCorp\Bundle\EasyAdminBundle\Filter\ChoiceFilter;
use EasyCorp\Bundle\EasyAdminBundle\Tests\Unit\Filter\Fixtures\BackedEnumChoice;
use EasyCorp\Bundle\EasyAdminBundle\Tests\Unit\Filter\Fixtures\TranslatableBackedEnumChoice;
use EasyCorp\Bundle\EasyAdminBundle\Tests\Unit\Filter\Fixtures\TranslatableUnitEnumChoice;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Translation\TranslatableMessage;

class ChoiceFilterTest extends TestCase
{
public function testSetTranslatableChoicesWithTranslatableMessages(): void
{
$filter = ChoiceFilter::new('status')->setTranslatableChoices([
'open' => new TranslatableMessage('Open'),
'closed' => new TranslatableMessage('Closed'),
]);
$dto = $filter->getAsDto();

$this->assertSame(['open', 'closed'], $dto->getFormTypeOption('value_type_options.choices'));

$choiceLabel = $dto->getFormTypeOption('value_type_options.choice_label');
$this->assertIsCallable($choiceLabel);
$this->assertInstanceOf(TranslatableMessage::class, $choiceLabel('open'));
$this->assertInstanceOf(TranslatableMessage::class, $choiceLabel('closed'));
}

public function testSetTranslatableChoicesWithTranslatableBackedEnumCases(): void
{
$filter = ChoiceFilter::new('status')->setTranslatableChoices(TranslatableBackedEnumChoice::cases());
$dto = $filter->getAsDto();

// the submitted values must be the enum backing values, not the list indices (0, 1, ...)
$this->assertSame(['draft', 'published'], $dto->getFormTypeOption('value_type_options.choices'));

$choiceLabel = $dto->getFormTypeOption('value_type_options.choice_label');
$this->assertIsCallable($choiceLabel);
$this->assertSame(TranslatableBackedEnumChoice::Draft, $choiceLabel('draft'));
$this->assertSame(TranslatableBackedEnumChoice::Published, $choiceLabel('published'));
}

public function testSetTranslatableChoicesWithBackedEnumCases(): void
{
$filter = ChoiceFilter::new('status')->setTranslatableChoices(BackedEnumChoice::cases());
$dto = $filter->getAsDto();

$this->assertSame(['draft', 'published'], $dto->getFormTypeOption('value_type_options.choices'));

$choiceLabel = $dto->getFormTypeOption('value_type_options.choice_label');
$this->assertSame(BackedEnumChoice::Draft, $choiceLabel('draft'));
}

public function testSetTranslatableChoicesWithTranslatableUnitEnumCases(): void
{
$filter = ChoiceFilter::new('status')->setTranslatableChoices(TranslatableUnitEnumChoice::cases());
$dto = $filter->getAsDto();

$this->assertSame(['Draft', 'Published'], $dto->getFormTypeOption('value_type_options.choices'));

$choiceLabel = $dto->getFormTypeOption('value_type_options.choice_label');
$this->assertSame(TranslatableUnitEnumChoice::Draft, $choiceLabel('Draft'));
}
}
9 changes: 9 additions & 0 deletions tests/Unit/Filter/Fixtures/BackedEnumChoice.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace EasyCorp\Bundle\EasyAdminBundle\Tests\Unit\Filter\Fixtures;

enum BackedEnumChoice: string
{
case Draft = 'draft';
case Published = 'published';
}
20 changes: 20 additions & 0 deletions tests/Unit/Filter/Fixtures/TranslatableBackedEnumChoice.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace EasyCorp\Bundle\EasyAdminBundle\Tests\Unit\Filter\Fixtures;

use Symfony\Contracts\Translation\TranslatableInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

enum TranslatableBackedEnumChoice: string implements TranslatableInterface
{
case Draft = 'draft';
case Published = 'published';

public function trans(TranslatorInterface $translator, ?string $locale = null): string
{
return match ($this) {
self::Draft => $translator->trans('TranslatableBackedEnumChoice.draft', locale: $locale),
self::Published => $translator->trans('TranslatableBackedEnumChoice.published', locale: $locale),
};
}
}
20 changes: 20 additions & 0 deletions tests/Unit/Filter/Fixtures/TranslatableUnitEnumChoice.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace EasyCorp\Bundle\EasyAdminBundle\Tests\Unit\Filter\Fixtures;

use Symfony\Contracts\Translation\TranslatableInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

enum TranslatableUnitEnumChoice implements TranslatableInterface
{
case Draft;
case Published;

public function trans(TranslatorInterface $translator, ?string $locale = null): string
{
return match ($this) {
self::Draft => $translator->trans('TranslatableUnitEnumChoice.draft', locale: $locale),
self::Published => $translator->trans('TranslatableUnitEnumChoice.published', locale: $locale),
};
}
}
Loading