diff --git a/src/Trait/ArrayAccessTrait.php b/src/Trait/ArrayAccessTrait.php index e85e4dc2b..5f5a72060 100644 --- a/src/Trait/ArrayAccessTrait.php +++ b/src/Trait/ArrayAccessTrait.php @@ -4,10 +4,36 @@ namespace Yiisoft\ActiveRecord\Trait; +use InvalidArgumentException; +use Yiisoft\ActiveRecord\ActiveRecordInterface; + +use function get_object_vars; +use function is_array; use function property_exists; /** * Trait to implement {@see ArrayAccess} interface for ActiveRecord. + * + * @method mixed getAttribute(string $name) + * @see ActiveRecordInterface::getAttribute() + * + * @method bool hasAttribute(string $name) + * @see ActiveRecordInterface::hasAttribute() + * + * @method void setAttribute(string $name, mixed $value) + * @see ActiveRecordInterface::getAttribute() + * + * @method ActiveRecordInterface|array|null relation(string $name) + * @see ActiveRecordInterface::relation() + * + * @method bool isRelationPopulated(string $name) + * @see ActiveRecordInterface::isRelationPopulated() + * + * @method void populateRelation(string $name, ActiveRecordInterface|array|null $record) + * @see ActiveRecordInterface::populateRelation() + * + * @method void resetRelation(string $name) + * @see ActiveRecordInterface::resetRelation() */ trait ArrayAccessTrait { @@ -18,16 +44,41 @@ trait ArrayAccessTrait * * It is implicitly called when you use something like `isset($model[$offset])`. * + * @param string $offset the offset to check on. + * * @return bool whether or not an offset exists. */ public function offsetExists(mixed $offset): bool { - return isset($this->$offset); + if ($this->hasAttribute($offset)) { + return $this->getAttribute($offset) !== null; + } + + if (property_exists($this, $offset)) { + return isset(get_object_vars($this)[$offset]); + } + + if ($this->isRelationPopulated($offset)) { + return $this->relation($offset) !== null; + } + + return false; } + /** + * @param string $offset the offset to retrieve element. + */ public function offsetGet(mixed $offset): mixed { - return $this->$offset; + if ($this->hasAttribute($offset)) { + return $this->getAttribute($offset); + } + + if (property_exists($this, $offset)) { + return get_object_vars($this)[$offset] ?? null; + } + + return $this->relation($offset); } /** @@ -36,10 +87,27 @@ public function offsetGet(mixed $offset): mixed * This method is required by the SPL interface {@see ArrayAccess}. * * It is implicitly called when you use something like `$model[$offset] = $item;`. + * + * @param string $offset the offset to set element. */ public function offsetSet(mixed $offset, mixed $value): void { - $this->$offset = $value; + if ($this->hasAttribute($offset)) { + $this->setAttribute($offset, $value); + return; + } + + if (property_exists($this, $offset)) { + $this->$offset = $value; + return; + } + + if ($value instanceof ActiveRecordInterface || is_array($value) || $value === null) { + $this->populateRelation($offset, $value); + return; + } + + throw new InvalidArgumentException('Setting unknown property: ' . static::class . '::' . $offset); } /** @@ -48,13 +116,21 @@ public function offsetSet(mixed $offset, mixed $value): void * This method is required by the SPL interface {@see ArrayAccess}. * * It is implicitly called when you use something like `unset($model[$offset])`. + * + * @param string $offset the offset to unset element */ public function offsetUnset(mixed $offset): void { - if (is_string($offset) && property_exists($this, $offset)) { + if ($this->hasAttribute($offset)) { + $this->setAttribute($offset, null); + return; + } + + if (property_exists($this, $offset)) { $this->$offset = null; - } else { - unset($this->$offset); + return; } + + $this->resetRelation($offset); } }