Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix typecastValue and typecastAttributes methods in AttributeTypecastBehavior #19528

Closed
wants to merge 11 commits into from
1 change: 1 addition & 0 deletions framework/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Yii Framework 2 Change Log
- Bug #19530: Fix specifying the field id for `yii\widgets\ActiveField` (kv4nt)
- Bug #19537: Fix default expression detection for MariaDB `date` and `time` columns (bizley)
- Bug #19589: Fix Error reporting in to the `BaseArrayHelper::getValue()` (lav45)
- Enh #19528: Add `AttributeTypecastBehavior::$typecastOnlyActiveAttributes` (WinterSilence)


2.0.46 August 18, 2022
Expand Down
64 changes: 39 additions & 25 deletions framework/behaviors/AttributeTypecastBehavior.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,14 +171,19 @@ class AttributeTypecastBehavior extends Behavior
*/
public $typecastAfterSave = false;
/**
* @var bool whether to perform typecasting after retrieving owner model data from
* @var bool whether to perform type casting after retrieving owner model data from
* the database (after find or refresh).
* This option may be disabled in order to achieve better performance.
* For example, in case of [[\yii\db\ActiveRecord]] usage, typecasting after find
* will grant no benefit in most cases an thus can be disabled.
* Note that changing this option value will have no effect after this behavior has been attached to the model.
*/
public $typecastAfterFind = false;
/**
* @var bool whether to type cast only active attributes, by default, cast all attributes in [[attributeTypes]]
* @since 2.0.47
*/
public $typecastOnlyActiveAttributes = false;

/**
* @var array internal static cache for auto detected [[attributeTypes]] values
Expand Down Expand Up @@ -214,25 +219,30 @@ public function attach($owner)

/**
* Typecast owner attributes according to [[attributeTypes]].
*
* @param array|null $attributeNames list of attribute names that should be type-casted.
* If this parameter is empty, it means any attribute listed in the [[attributeTypes]]
* should be type-casted.
*/
public function typecastAttributes($attributeNames = null)
{
$attributeTypes = [];

if ($attributeNames === null) {
$attributeTypes = $this->attributeTypes;
} else {
$attributeTypes = [];
foreach ($attributeNames as $attribute) {
if (!isset($this->attributeTypes[$attribute])) {
throw new InvalidArgumentException("There is no type mapping for '{$attribute}'.");
}
$attributeTypes[$attribute] = $this->attributeTypes[$attribute];
}
}

if ($this->typecastOnlyActiveAttributes) {
$attributeTypes = array_intersect_key(
$attributeTypes,
array_flip($this->owner->activeAttributes())
);
}
foreach ($attributeTypes as $attribute => $type) {
$value = $this->owner->{$attribute};
if ($this->skipOnNull && $value === null) {
Expand All @@ -244,35 +254,39 @@ public function typecastAttributes($attributeNames = null)

/**
* Casts the given value to the specified type.
*
* @param mixed $value value to be type-casted.
* @param string|callable $type type name or typecast callable.
* @param string|callable $type type name or typecast callback: `function (mixed $value): mixed`
*
* @return mixed typecast result.
*/
protected function typecastValue($value, $type)
{
if (is_scalar($type)) {
if (is_object($value) && method_exists($value, '__toString')) {
$value = $value->__toString();
}
if (
!in_array($type, [self::TYPE_INTEGER, self::TYPE_FLOAT, self::TYPE_BOOLEAN, self::TYPE_STRING], true)
&& is_callable($type)
) {
return call_user_func($type, $value);
}

switch ($type) {
case self::TYPE_INTEGER:
return (int) $value;
case self::TYPE_FLOAT:
return (float) $value;
case self::TYPE_BOOLEAN:
return (bool) $value;
case self::TYPE_STRING:
if (is_float($value)) {
return StringHelper::floatToString($value);
}
return (string) $value;
default:
throw new InvalidArgumentException("Unsupported type '{$type}'");
}
if (is_object($value) && method_exists($value, '__toString')) {
$value = $value->__toString();
}
switch ($type) {
case self::TYPE_INTEGER:
return (int) $value;
case self::TYPE_FLOAT:
return (float) $value;
case self::TYPE_BOOLEAN:
return (bool) $value;
case self::TYPE_STRING:
if (is_float($value)) {
return StringHelper::floatToString($value);
}
return (string) $value;
}

return call_user_func($type, $value);
throw new InvalidArgumentException("Unsupported type '$type'");
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ public function rules()
['amount', 'integer'],
['price', 'number'],
['isActive', 'boolean'],
['callback', 'safe'],
];
}

Expand Down