Browse Source

Auto generate select options for fields mapped to enum type.

ADmad 2 years ago
parent
commit
b50d7d1ed7

+ 34 - 0
src/View/Helper/FormHelper.php

@@ -19,6 +19,8 @@ namespace Cake\View\Helper;
 use BackedEnum;
 use Cake\Core\Configure;
 use Cake\Core\Exception\CakeException;
+use Cake\Database\Type\EnumType;
+use Cake\Database\TypeFactory;
 use Cake\Form\FormProtector;
 use Cake\Routing\Router;
 use Cake\Utility\Hash;
@@ -1313,6 +1315,20 @@ class FormHelper extends Helper
             return $options;
         }
 
+        $internalType = $this->_getContext()->type($fieldName);
+        if ($internalType && str_starts_with($internalType, 'enum-')) {
+            $dbType = TypeFactory::build($internalType);
+            if ($dbType instanceof EnumType) {
+                if ($options['type'] !== 'radio') {
+                    $options['type'] = 'select';
+                }
+
+                $options['options'] = $this->enumOptions($dbType->getEnumClassName());
+
+                return $options;
+            }
+        }
+
         $pluralize = true;
         if (str_ends_with($fieldName, '._ids')) {
             $fieldName = substr($fieldName, 0, -5);
@@ -1338,6 +1354,24 @@ class FormHelper extends Helper
     }
 
     /**
+     * Get map of enum value => label for select/radio options.
+     *
+     * @param class-string<\BackedEnum> $enumClass Enum class name.
+     * @return array<int|string, string>
+     */
+    protected function enumOptions(string $enumClass): array
+    {
+        assert(is_subclass_of($enumClass, BackedEnum::class));
+
+        $values = [];
+        foreach ($enumClass::cases() as $case) {
+            $values[$case->value] = method_exists($case, 'label') ? $case->label() : $case->name;
+        }
+
+        return $values;
+    }
+
+    /**
      * Magically set option type and corresponding options
      *
      * @param string $fieldName The name of the field to generate options for.

+ 71 - 0
tests/TestCase/View/Helper/FormHelperTest.php

@@ -20,6 +20,7 @@ use ArrayObject;
 use Cake\Collection\Collection;
 use Cake\Core\Configure;
 use Cake\Core\Exception\CakeException;
+use Cake\Database\Type\EnumType;
 use Cake\Form\Form;
 use Cake\Http\ServerRequest;
 use Cake\I18n\Date;
@@ -38,6 +39,7 @@ use InvalidArgumentException;
 use ReflectionProperty;
 use TestApp\Model\Entity\Article;
 use TestApp\Model\Enum\ArticleStatus;
+use TestApp\Model\Enum\ArticleStatusLabel;
 use TestApp\Model\Table\ContactsTable;
 use TestApp\Model\Table\ValidateUsersTable;
 use TestApp\View\Form\StubContext;
@@ -3621,6 +3623,53 @@ class FormHelperTest extends TestCase
             '/div',
         ];
         $this->assertHtml($expected, $result);
+
+        $articlesTable = $this->getTableLocator()->get('Articles');
+        $articlesTable->getSchema()->setColumnType(
+            'published',
+            EnumType::from(ArticleStatus::class)
+        );
+        $this->Form->create($articlesTable->newEmptyEntity());
+        $result = $this->Form->control('published');
+        $expected = [
+            'div' => ['class' => 'input select'],
+            'label' => ['for' => 'published'],
+            'Published',
+            '/label',
+            'select' => ['name' => 'published', 'id' => 'published'],
+            ['option' => ['value' => 'Y']],
+            'PUBLISHED',
+            '/option',
+            ['option' => ['value' => 'N', 'selected' => 'selected']],
+            'UNPUBLISHED',
+            '/option',
+            '/select',
+            '/div',
+        ];
+        $this->assertHtml($expected, $result);
+
+        $articlesTable->getSchema()->setColumnType(
+            'published',
+            EnumType::from(ArticleStatusLabel::class)
+        );
+        $this->Form->create($articlesTable->newEmptyEntity());
+        $result = $this->Form->control('published');
+        $expected = [
+            'div' => ['class' => 'input select'],
+            'label' => ['for' => 'published'],
+            'Published',
+            '/label',
+            'select' => ['name' => 'published', 'id' => 'published'],
+            ['option' => ['value' => 'Y']],
+            'Is published',
+            '/option',
+            ['option' => ['value' => 'N', 'selected' => 'selected']],
+            'Is unpublished',
+            '/option',
+            '/select',
+            '/div',
+        ];
+        $this->assertHtml($expected, $result);
     }
 
     /**
@@ -4701,6 +4750,28 @@ class FormHelperTest extends TestCase
             '/div',
         ];
         $this->assertHtml($expected, $result);
+
+        $articlesTable = $this->getTableLocator()->get('Articles');
+        $articlesTable->getSchema()->setColumnType(
+            'published',
+            EnumType::from(ArticleStatus::class)
+        );
+        $this->Form->create($articlesTable->newEmptyEntity());
+        $result = $this->Form->control('published', ['type' => 'radio', 'label' => false,]);
+        $expected = [
+            ['div' => ['class' => 'input radio']],
+                'input' => ['type' => 'hidden', 'name' => 'published', 'value' => '', 'id' => 'published'],
+                ['label' => ['for' => 'published-y']],
+                ['input' => ['type' => 'radio', 'name' => 'published', 'value' => 'Y', 'id' => 'published-y']],
+                'PUBLISHED',
+                '/label',
+                ['label' => ['for' => 'published-n']],
+                ['input' => ['type' => 'radio', 'name' => 'published', 'value' => 'N', 'id' => 'published-n', 'checked' => 'checked']],
+                'UNPUBLISHED',
+                '/label',
+            '/div',
+        ];
+        $this->assertHtml($expected, $result);
     }
 
     /**

+ 26 - 0
tests/test_app/TestApp/Model/Enum/ArticleStatusLabel.php

@@ -0,0 +1,26 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ * @since         5.0.0
+ * @license       https://opensource.org/licenses/mit-license.php MIT License
+ */
+namespace TestApp\Model\Enum;
+
+enum ArticleStatusLabel: string
+{
+    case PUBLISHED = 'Y';
+    case UNPUBLISHED = 'N';
+
+    public function label(): string
+    {
+        return 'Is ' . strtolower($this->name);
+    }
+}