diff --git a/src/AttributeTypecastBehavior.php b/src/TypecastBehavior.php similarity index 84% rename from src/AttributeTypecastBehavior.php rename to src/TypecastBehavior.php index acf6b8f..9db325a 100644 --- a/src/AttributeTypecastBehavior.php +++ b/src/TypecastBehavior.php @@ -15,20 +15,103 @@ use Yii; /** + * TypecastBehavior provides an ability of automatic model attribute typecasting. + * + * This behavior should be attached to {@see \CModel} or {@see \CActiveRecord} descendant. + * + * For example: + * + * ```php + * use yii1tech\model\typecast\TypecastBehavior; + * + * class Item extends CActiveRecord + * { + * public function behaviors() + * { + * return [ + * 'typecastBehavior' => [ + * 'class' => TypecastBehavior::class, + * 'attributeTypes' => [ + * 'amount' => TypecastBehavior::TYPE_INTEGER, + * 'price' => TypecastBehavior::TYPE_FLOAT, + * 'is_active' => TypecastBehavior::TYPE_BOOLEAN, + * 'created_at' => TypecastBehavior::TYPE_DATETIME, + * 'json_data' => TypecastBehavior::TYPE_ARRAY_OBJECT, + * ], + * 'typecastAfterValidate' => true, + * 'typecastBeforeSave' => false, + * 'typecastAfterSave' => true, + * 'typecastAfterFind' => true, + * ], + * ]; + * } + * + * // ... + * } + * ``` + * + * Tip: you may left {@see $attributeTypes} blank - in this case its value will be detected + * automatically based on owner DB table schema, or validation rules. + * + * Note: you can manually trigger attribute typecasting anytime invoking {@see typecastAttributes()} method: + * + * ```php + * $model = new Item(); + * $model->price = '38.5'; + * $model->is_active = 1; + * $model->typecastAttributes(); + * ``` + * + * This behavior allows automatic conversion of {@see \DateTime} instances into ISO datetime string, and array into JSON string + * on model saving. For example: + * + * ```php + * $model = new Item(); + * $model->created_at = new DateTime('now'); // will be saved in DB as '2023-12-22 10:14:17' + * $model->json_data = [ // will be saved in DB as '{foo: "bar"}' + * 'foo' => 'bar', + * ]; + * $model->save(); + * ``` + * * @property \CModel|\CActiveRecord $owner The owner component that this behavior is attached to. * * @author Paul Klimov * @since 1.0 */ -class AttributeTypecastBehavior extends CBehavior +class TypecastBehavior extends CBehavior { + /** + * Converts attribute to `int`. + */ const TYPE_INTEGER = 'integer'; + /** + * Converts attribute to `float`. + */ const TYPE_FLOAT = 'float'; + /** + * Converts attribute to `bool`. + */ const TYPE_BOOLEAN = 'boolean'; + /** + * Converts attribute to `string`. + */ const TYPE_STRING = 'string'; + /** + * Converts JSON to array and vice versa. + */ const TYPE_ARRAY = 'array'; + /** + * Converts JSON to {@see \ArrayObject} and vice versa. + */ const TYPE_ARRAY_OBJECT = 'array-object'; + /** + * Converts ISO datetime string into {@see \DateTime} and vice versa. + */ const TYPE_DATETIME = 'datetime'; + /** + * Converts integer Unix timestamp into {@see \DateTime} and vice versa. + */ const TYPE_TIMESTAMP = 'timestamp'; /** @@ -54,7 +137,7 @@ class AttributeTypecastBehavior extends CBehavior /** * @var bool whether to skip typecasting of `null` values. * If enabled attribute value which equals to `null` will not be type-casted (e.g. `null` remains `null`), - * otherwise it will be converted according to the type configured at [[attributeTypes]]. + * otherwise it will be converted according to the type configured at {@see attributeTypes}. */ public $skipOnNull = true; /** @@ -98,7 +181,7 @@ class AttributeTypecastBehavior extends CBehavior /** * @var array internal static cache for auto detected {@see $attributeTypes} values - * in format: ownerClassName => attributeTypes + * in format: `ownerClassName => attributeTypes`. */ private static $autoDetectedAttributeTypes = []; @@ -201,13 +284,13 @@ protected function typecastValue($value, $type) case self::TYPE_STRING: return (string) $value; case self::TYPE_ARRAY: - if ($value === null || is_iterable($value)) { + if (empty($value) || is_iterable($value)) { return $value; } return json_decode($value, true); case self::TYPE_ARRAY_OBJECT: - if ($value === null || is_iterable($value)) { + if (empty($value) || is_iterable($value)) { return $value; } diff --git a/tests/TestCase.php b/tests/TestCase.php index b293b3a..71fde76 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -5,7 +5,7 @@ use CConsoleApplication; use CMap; use Yii; -use yii1tech\model\typecast\AttributeTypecastBehavior; +use yii1tech\model\typecast\TypecastBehavior; class TestCase extends \PHPUnit\Framework\TestCase { @@ -26,7 +26,7 @@ protected function setUp(): void */ protected function tearDown(): void { - AttributeTypecastBehavior::clearAutoDetectedAttributeTypes(); + TypecastBehavior::clearAutoDetectedAttributeTypes(); $this->destroyApplication(); diff --git a/tests/AttributeTypecastBehaviorTest.php b/tests/TypecastBehaviorTest.php similarity index 90% rename from tests/AttributeTypecastBehaviorTest.php rename to tests/TypecastBehaviorTest.php index beb2a75..8bdbe81 100644 --- a/tests/AttributeTypecastBehaviorTest.php +++ b/tests/TypecastBehaviorTest.php @@ -4,12 +4,12 @@ use ArrayObject; use DateTime; -use yii1tech\model\typecast\AttributeTypecastBehavior; +use yii1tech\model\typecast\TypecastBehavior; use yii1tech\model\typecast\test\data\FormWithTypecast; use yii1tech\model\typecast\test\data\Item; use yii1tech\model\typecast\test\data\ItemWithTypecast; -class AttributeTypecastBehaviorTest extends TestCase +class TypecastBehaviorTest extends TestCase { public function testTypecast(): void { @@ -94,7 +94,7 @@ public function testAfterValidateEvent(): void */ public function testSaveEvents() { - $baseBehavior = new AttributeTypecastBehavior(); + $baseBehavior = new TypecastBehavior(); $baseBehavior->attributeTypes = [ 'callback' => function ($value) { return 'callback: ' . $value; @@ -250,7 +250,7 @@ public function testDetectedAttributeTypesFromRules(): void */ public function testDetectedAttributeTypesFromSchema(): void { - $baseBehavior = new AttributeTypecastBehavior(); + $baseBehavior = new TypecastBehavior(); $baseBehavior->attributeTypes = null; $model = new Item(); @@ -258,10 +258,10 @@ public function testDetectedAttributeTypesFromSchema(): void $this->assertNotEmpty($baseBehavior->attributeTypes); - $this->assertSame(AttributeTypecastBehavior::TYPE_INTEGER, $baseBehavior->attributeTypes['category_id']); - $this->assertSame(AttributeTypecastBehavior::TYPE_STRING, $baseBehavior->attributeTypes['name']); - $this->assertSame(AttributeTypecastBehavior::TYPE_FLOAT, $baseBehavior->attributeTypes['price']); - $this->assertSame(AttributeTypecastBehavior::TYPE_DATETIME, $baseBehavior->attributeTypes['created_date']); - $this->assertSame(AttributeTypecastBehavior::TYPE_ARRAY_OBJECT, $baseBehavior->attributeTypes['data_array_object']); + $this->assertSame(TypecastBehavior::TYPE_INTEGER, $baseBehavior->attributeTypes['category_id']); + $this->assertSame(TypecastBehavior::TYPE_STRING, $baseBehavior->attributeTypes['name']); + $this->assertSame(TypecastBehavior::TYPE_FLOAT, $baseBehavior->attributeTypes['price']); + $this->assertSame(TypecastBehavior::TYPE_DATETIME, $baseBehavior->attributeTypes['created_date']); + $this->assertSame(TypecastBehavior::TYPE_ARRAY_OBJECT, $baseBehavior->attributeTypes['data_array_object']); } } \ No newline at end of file diff --git a/tests/data/FormWithTypecast.php b/tests/data/FormWithTypecast.php index 0e5e8bf..5e10262 100644 --- a/tests/data/FormWithTypecast.php +++ b/tests/data/FormWithTypecast.php @@ -3,7 +3,7 @@ namespace yii1tech\model\typecast\test\data; use CFormModel; -use yii1tech\model\typecast\AttributeTypecastBehavior; +use yii1tech\model\typecast\TypecastBehavior; class FormWithTypecast extends CFormModel { @@ -35,7 +35,7 @@ public function behaviors(): array { return [ 'typecastBehavior' => [ - 'class' => AttributeTypecastBehavior::class, + 'class' => TypecastBehavior::class, 'attributeTypes' => null, ], ]; diff --git a/tests/data/ItemWithTypecast.php b/tests/data/ItemWithTypecast.php index 4721dd5..3802aef 100644 --- a/tests/data/ItemWithTypecast.php +++ b/tests/data/ItemWithTypecast.php @@ -2,12 +2,12 @@ namespace yii1tech\model\typecast\test\data; -use yii1tech\model\typecast\AttributeTypecastBehavior; +use yii1tech\model\typecast\TypecastBehavior; /** - * @mixin \yii1tech\model\typecast\AttributeTypecastBehavior + * @mixin \yii1tech\model\typecast\TypecastBehavior * - * @property-read \yii1tech\model\typecast\AttributeTypecastBehavior $typecastBehavior + * @property-read \yii1tech\model\typecast\TypecastBehavior $typecastBehavior */ class ItemWithTypecast extends Item { @@ -26,21 +26,21 @@ public function behaviors(): array { return [ 'typecastBehavior' => [ - 'class' => AttributeTypecastBehavior::class, + 'class' => TypecastBehavior::class, 'typecastBeforeSave' => true, 'typecastAfterFind' => true, 'attributeTypes' => [ - 'name' => AttributeTypecastBehavior::TYPE_STRING, - 'category_id' => AttributeTypecastBehavior::TYPE_INTEGER, - 'price' => AttributeTypecastBehavior::TYPE_FLOAT, - 'is_active' => AttributeTypecastBehavior::TYPE_BOOLEAN, + 'name' => TypecastBehavior::TYPE_STRING, + 'category_id' => TypecastBehavior::TYPE_INTEGER, + 'price' => TypecastBehavior::TYPE_FLOAT, + 'is_active' => TypecastBehavior::TYPE_BOOLEAN, 'callback' => function ($value) { return 'callback: ' . $value; }, - 'created_date' => AttributeTypecastBehavior::TYPE_DATETIME, - 'created_timestamp' => AttributeTypecastBehavior::TYPE_TIMESTAMP, - 'data_array' => AttributeTypecastBehavior::TYPE_ARRAY, - 'data_array_object' => AttributeTypecastBehavior::TYPE_ARRAY_OBJECT, + 'created_date' => TypecastBehavior::TYPE_DATETIME, + 'created_timestamp' => TypecastBehavior::TYPE_TIMESTAMP, + 'data_array' => TypecastBehavior::TYPE_ARRAY, + 'data_array_object' => TypecastBehavior::TYPE_ARRAY_OBJECT, ], ], ];