From 0acf0db753d5d28c983cfd8960fba645b654d3b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Briedis?= Date: Tue, 4 Jun 2024 15:52:32 +0300 Subject: [PATCH 1/3] Support BackedEnum attribute typecast behaviour. --- .../behaviors/AttributeTypecastBehavior.php | 11 ++- .../AttributeTypecastBehaviorTest.php | 86 +++++++++++++++++++ 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/framework/behaviors/AttributeTypecastBehavior.php b/framework/behaviors/AttributeTypecastBehavior.php index b313cac1b61..d3a177b3d9e 100644 --- a/framework/behaviors/AttributeTypecastBehavior.php +++ b/framework/behaviors/AttributeTypecastBehavior.php @@ -267,9 +267,16 @@ protected function typecastValue($value, $type) return StringHelper::floatToString($value); } return (string) $value; - default: - throw new InvalidArgumentException("Unsupported type '{$type}'"); } + + if (PHP_VERSION_ID >= 80100 && is_subclass_of($type, \BackedEnum::class)) { + if ($value instanceof $type) { + return $value; + } + return $type::from($value); + } + + throw new InvalidArgumentException("Unsupported type '{$type}'"); } return call_user_func($type, $value); diff --git a/tests/framework/behaviors/AttributeTypecastBehaviorTest.php b/tests/framework/behaviors/AttributeTypecastBehaviorTest.php index 5af290672e1..124dd483f74 100644 --- a/tests/framework/behaviors/AttributeTypecastBehaviorTest.php +++ b/tests/framework/behaviors/AttributeTypecastBehaviorTest.php @@ -7,11 +7,13 @@ namespace yiiunit\framework\behaviors; +use ValueError; use Yii; use yii\base\DynamicModel; use yii\base\Event; use yii\behaviors\AttributeTypecastBehavior; use yii\db\ActiveRecord; +use yiiunit\framework\db\enums\StatusTypeString; use yiiunit\TestCase; /** @@ -47,6 +49,7 @@ protected function setUp(): void 'price' => 'float', 'isActive' => 'boolean', 'callback' => 'string', + 'status' => 'string', ]; Yii::$app->getDb()->createCommand()->createTable('test_attribute_typecast', $columns)->execute(); } @@ -80,6 +83,55 @@ public function testTypecast() $this->assertSame('callback: foo', $model->callback); } + public function testTypecastEnum() + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Can not be tested on PHP < 8.1'); + } + + $model = new ActiveRecordAttributeTypecastWithEnum(); + + $model->status = StatusTypeString::ACTIVE; + + $model->getAttributeTypecastBehavior()->typecastAttributes(); + + $this->assertSame(StatusTypeString::ACTIVE, $model->status); + } + + /** + * @depends testTypecastEnum + */ + public function testTypecastEnumFromString() + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Can not be tested on PHP < 8.1'); + } + + $model = new ActiveRecordAttributeTypecastWithEnum(); + $model->status = 'active'; // Same as StatusTypeString::ACTIVE->value; + + $model->getAttributeTypecastBehavior()->typecastAttributes(); + + $this->assertSame(StatusTypeString::ACTIVE, $model->status); + } + + /** + * @depends testTypecastEnum + */ + public function testTypecastEnumFailWithInvalidValue() + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Can not be tested on PHP < 8.1'); + } + + $model = new ActiveRecordAttributeTypecastWithEnum(); + $model->status = 'invalid'; + + self::expectException(ValueError::class); + + $model->getAttributeTypecastBehavior()->typecastAttributes(); + } + /** * @depends testTypecast */ @@ -339,3 +391,37 @@ public function getAttributeTypecastBehavior() return $this->getBehavior('attributeTypecast'); } } + +/** + * Test Active Record class with [[AttributeTypecastBehavior]] behavior attached with an enum field. + * + * @property StatusTypeString $status + */ +class ActiveRecordAttributeTypecastWithEnum extends ActiveRecord +{ + public function behaviors() + { + return [ + 'attributeTypecast' => [ + 'class' => AttributeTypecastBehavior::className(), + 'attributeTypes' => [ + 'status' => StatusTypeString::class, + ], + 'typecastBeforeSave' => true, + ], + ]; + } + + public static function tableName() + { + return 'test_attribute_typecast'; + } + + /** + * @return AttributeTypecastBehavior + */ + public function getAttributeTypecastBehavior() + { + return $this->getBehavior('attributeTypecast'); + } +} From 047d68c91a3eb5454c394e2a7c3bc56a1c2f7076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Briedis?= Date: Fri, 6 Dec 2024 19:11:07 +0200 Subject: [PATCH 2/3] Update changelog --- framework/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 600a3420746..1044a3fecd1 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -19,6 +19,7 @@ Yii Framework 2 Change Log - Enh #20279: Add to the `\yii\web\Request` `csrfHeader` property to configure a custom HTTP header for CSRF validation (olegbaturin) - Enh #20279: Add to the `\yii\web\Request` `csrfTokenSafeMethods` property to configure a custom safe HTTP methods list (olegbaturin) - Bug #20140: Fix compatibility with PHP 8.4: calling `session_set_save_handler()` (Izumi-kun) +- New #20185: Add `BackedEnum` support to `AttributeTypecastBehavior` (briedis) 2.0.51 July 18, 2024 -------------------- From 4b4eb9dab1fa025ae44751d895c86bf97a72144f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Briedis?= Date: Fri, 6 Dec 2024 19:23:52 +0200 Subject: [PATCH 3/3] Rename enum cases to match suggested formatting (RFC) --- tests/framework/behaviors/AttributeTypecastBehaviorTest.php | 6 +++--- tests/framework/db/CommandTest.php | 6 +++--- tests/framework/db/enums/Status.php | 4 ++-- tests/framework/db/enums/StatusTypeInt.php | 4 ++-- tests/framework/db/enums/StatusTypeString.php | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/framework/behaviors/AttributeTypecastBehaviorTest.php b/tests/framework/behaviors/AttributeTypecastBehaviorTest.php index 124dd483f74..27c228dea2c 100644 --- a/tests/framework/behaviors/AttributeTypecastBehaviorTest.php +++ b/tests/framework/behaviors/AttributeTypecastBehaviorTest.php @@ -91,11 +91,11 @@ public function testTypecastEnum() $model = new ActiveRecordAttributeTypecastWithEnum(); - $model->status = StatusTypeString::ACTIVE; + $model->status = StatusTypeString::Active; $model->getAttributeTypecastBehavior()->typecastAttributes(); - $this->assertSame(StatusTypeString::ACTIVE, $model->status); + $this->assertSame(StatusTypeString::Active, $model->status); } /** @@ -112,7 +112,7 @@ public function testTypecastEnumFromString() $model->getAttributeTypecastBehavior()->typecastAttributes(); - $this->assertSame(StatusTypeString::ACTIVE, $model->status); + $this->assertSame(StatusTypeString::Active, $model->status); } /** diff --git a/tests/framework/db/CommandTest.php b/tests/framework/db/CommandTest.php index cd3ecc6d686..cc30d748860 100644 --- a/tests/framework/db/CommandTest.php +++ b/tests/framework/db/CommandTest.php @@ -1538,13 +1538,13 @@ public function testBindValuesSupportsEnums() $db = $this->getConnection(); $command = $db->createCommand(); - $command->setSql('SELECT :p1')->bindValues([':p1' => enums\Status::ACTIVE]); + $command->setSql('SELECT :p1')->bindValues([':p1' => enums\Status::Active]); $this->assertSame('ACTIVE', $command->params[':p1']); - $command->setSql('SELECT :p1')->bindValues([':p1' => enums\StatusTypeString::ACTIVE]); + $command->setSql('SELECT :p1')->bindValues([':p1' => enums\StatusTypeString::Active]); $this->assertSame('active', $command->params[':p1']); - $command->setSql('SELECT :p1')->bindValues([':p1' => enums\StatusTypeInt::ACTIVE]); + $command->setSql('SELECT :p1')->bindValues([':p1' => enums\StatusTypeInt::Active]); $this->assertSame(1, $command->params[':p1']); } else { $this->markTestSkipped('Enums are not supported in PHP < 8.1'); diff --git a/tests/framework/db/enums/Status.php b/tests/framework/db/enums/Status.php index 799a552319b..13235e730bc 100644 --- a/tests/framework/db/enums/Status.php +++ b/tests/framework/db/enums/Status.php @@ -4,6 +4,6 @@ enum Status { - case ACTIVE; - case INACTIVE; + case Active; + case Inactive; } diff --git a/tests/framework/db/enums/StatusTypeInt.php b/tests/framework/db/enums/StatusTypeInt.php index ed4739c0227..7a8d539c523 100644 --- a/tests/framework/db/enums/StatusTypeInt.php +++ b/tests/framework/db/enums/StatusTypeInt.php @@ -4,6 +4,6 @@ enum StatusTypeInt: int { - case ACTIVE = 1; - case INACTIVE = 0; + case Active = 1; + case Inactive = 0; } diff --git a/tests/framework/db/enums/StatusTypeString.php b/tests/framework/db/enums/StatusTypeString.php index 019f4273a7f..9f8f2af9979 100644 --- a/tests/framework/db/enums/StatusTypeString.php +++ b/tests/framework/db/enums/StatusTypeString.php @@ -4,6 +4,6 @@ enum StatusTypeString: string { - case ACTIVE = 'active'; - case INACTIVE = 'inactive'; + case Active = 'active'; + case Inactive = 'inactive'; }