Skip to content

Commit

Permalink
rename AttributeTypecastBehavior to TypecastBehavior
Browse files Browse the repository at this point in the history
  • Loading branch information
klimov-paul committed Dec 22, 2023
1 parent a20d91e commit 4779cba
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 30 deletions.
93 changes: 88 additions & 5 deletions src/AttributeTypecastBehavior.php → src/TypecastBehavior.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 <klimov.paul@gmail.com>
* @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';

/**
Expand All @@ -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;
/**
Expand Down Expand Up @@ -98,7 +181,7 @@ class AttributeTypecastBehavior extends CBehavior

/**
* @var array<string, array> internal static cache for auto detected {@see $attributeTypes} values
* in format: ownerClassName => attributeTypes
* in format: `ownerClassName => attributeTypes`.
*/
private static $autoDetectedAttributeTypes = [];

Expand Down Expand Up @@ -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;
}

Expand Down
4 changes: 2 additions & 2 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -26,7 +26,7 @@ protected function setUp(): void
*/
protected function tearDown(): void
{
AttributeTypecastBehavior::clearAutoDetectedAttributeTypes();
TypecastBehavior::clearAutoDetectedAttributeTypes();

$this->destroyApplication();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -250,18 +250,18 @@ public function testDetectedAttributeTypesFromRules(): void
*/
public function testDetectedAttributeTypesFromSchema(): void
{
$baseBehavior = new AttributeTypecastBehavior();
$baseBehavior = new TypecastBehavior();
$baseBehavior->attributeTypes = null;

$model = new Item();
$baseBehavior->attach($model);

$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']);
}
}
4 changes: 2 additions & 2 deletions tests/data/FormWithTypecast.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -35,7 +35,7 @@ public function behaviors(): array
{
return [
'typecastBehavior' => [
'class' => AttributeTypecastBehavior::class,
'class' => TypecastBehavior::class,
'attributeTypes' => null,
],
];
Expand Down
24 changes: 12 additions & 12 deletions tests/data/ItemWithTypecast.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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,
],
],
];
Expand Down

0 comments on commit 4779cba

Please sign in to comment.