Skip to content

Commit

Permalink
Create MagicPropertiesTrait (#318)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tigrov authored May 20, 2024
1 parent 13c782f commit 0c89029
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 93 deletions.
17 changes: 12 additions & 5 deletions src/ActiveQueryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
namespace Yiisoft\ActiveRecord;

use Closure;
use ReflectionException;
use Throwable;
use Yiisoft\Db\Exception\Exception;
use Yiisoft\Db\Exception\InvalidArgumentException;
use Yiisoft\Db\Exception\InvalidConfigException;
use Yiisoft\Db\Query\QueryInterface;
Expand Down Expand Up @@ -334,16 +337,19 @@ public function sql(string|null $value): self;
public function populate(array $rows, Closure|string|null $indexBy = null): array;

/**
* Finds the related records for the specified primary record.
* Returns related record(s).
*
* This method is invoked when a relation of an ActiveRecord is being accessed in a lazy fashion.
*
* @param string $name The relation name.
* @param ActiveRecordInterface $model The primary model.
* @throws Exception
* @throws InvalidArgumentException
* @throws InvalidConfigException
* @throws ReflectionException
* @throws Throwable if the relation is invalid.
*
* @return mixed The related record(s).
* @return ActiveRecordInterface|array|null the related record(s).
*/
public function findFor(string $name, ActiveRecordInterface $model): mixed;
public function relatedRecords(): ActiveRecordInterface|array|null;

/**
* Returns a single active record instance by a primary key or an array of column values.
Expand Down Expand Up @@ -423,6 +429,7 @@ public function findFor(string $name, ActiveRecordInterface $model): mixed;
* ```
*
* @throws InvalidConfigException
*
* @return ActiveRecordInterface|array|null Instance matching the condition, or `null` if nothing matches.
*/
public function findOne(mixed $condition): array|ActiveRecordInterface|null;
Expand Down
2 changes: 2 additions & 0 deletions src/ActiveRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Yiisoft\ActiveRecord\Trait\ArrayableTrait;
use Yiisoft\ActiveRecord\Trait\ArrayAccessTrait;
use Yiisoft\ActiveRecord\Trait\ArrayIteratorTrait;
use Yiisoft\ActiveRecord\Trait\MagicPropertiesTrait;
use Yiisoft\ActiveRecord\Trait\MagicRelationsTrait;
use Yiisoft\Arrays\ArrayableInterface;
use Yiisoft\Db\Exception\Exception;
Expand Down Expand Up @@ -96,6 +97,7 @@ class ActiveRecord extends BaseActiveRecord implements ArrayableInterface, Array
use ArrayableTrait;
use ArrayAccessTrait;
use ArrayIteratorTrait;
use MagicPropertiesTrait;
use MagicRelationsTrait;

/**
Expand Down
18 changes: 2 additions & 16 deletions src/ActiveRelationTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace Yiisoft\ActiveRecord;

use ReflectionException;
use ReflectionMethod;
use Stringable;
use Throwable;
use Yiisoft\Db\Exception\Exception;
Expand All @@ -25,8 +24,6 @@
use function is_scalar;
use function is_string;
use function key;
use function lcfirst;
use function method_exists;
use function reset;
use function serialize;

Expand Down Expand Up @@ -180,21 +177,10 @@ public function inverseOf(string $relationName): static
* @throws ReflectionException
* @throws Throwable if the relation is invalid.
*
* @return array|object|null the related record(s).
* @return ActiveRecordInterface|array|null the related record(s).
*/
public function findFor(string $name, ActiveRecordInterface $model): array|null|object
public function relatedRecords(): ActiveRecordInterface|array|null
{
if (method_exists($model, 'get' . $name)) {
$method = new ReflectionMethod($model, 'get' . $name);
$realName = lcfirst(substr($method->getName(), 3));
if ($realName !== $name) {
throw new InvalidArgumentException(
'Relation names are case sensitive. ' . $model::class
. " has a relation named \"$realName\" instead of \"$name\"."
);
}
}

return $this->multiple ? $this->all() : $this->onePopulate();
}

Expand Down
80 changes: 47 additions & 33 deletions src/BaseActiveRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,6 @@
*/
abstract class BaseActiveRecord implements ActiveRecordInterface
{
use BaseActiveRecordTrait;

private array $attributes = [];
private array|null $oldAttributes = null;
private array $related = [];
/** @psalm-var string[][] */
Expand Down Expand Up @@ -81,13 +78,13 @@ public function equals(ActiveRecordInterface $record): bool

public function getAttribute(string $name): mixed
{
return $this->attributes[$name] ?? null;
return get_object_vars($this)[$name] ?? null;
}

public function getAttributes(array $names = null, array $except = []): array
{
$names ??= $this->attributes();
$attributes = array_merge($this->attributes, get_object_vars($this));
$attributes = get_object_vars($this);

if ($except !== []) {
$names = array_diff($names, $except);
Expand Down Expand Up @@ -210,9 +207,9 @@ public function getRelatedRecords(): array
return $this->related;
}

public function hasAttribute($name): bool
public function hasAttribute(string $name): bool
{
return isset($this->attributes[$name]) || in_array($name, $this->attributes(), true);
return in_array($name, $this->attributes(), true);
}

/**
Expand Down Expand Up @@ -310,15 +307,11 @@ public function instantiateQuery(string $arClass): ActiveQueryInterface
*/
public function isAttributeChanged(string $name, bool $identical = true): bool
{
if (isset($this->attributes[$name], $this->oldAttributes[$name])) {
if ($identical) {
return $this->attributes[$name] !== $this->oldAttributes[$name];
}

return $this->attributes[$name] !== $this->oldAttributes[$name];
if (isset($this->oldAttributes[$name])) {
return $this->$name !== $this->oldAttributes[$name];
}

return isset($this->attributes[$name]) || isset($this->oldAttributes[$name]);
return false;
}

public function isPrimaryKey(array $keys): bool
Expand Down Expand Up @@ -510,12 +503,7 @@ public function optimisticLock(): string|null
public function populateRecord(array|object $row): void
{
foreach ($row as $name => $value) {
if (property_exists($this, $name)) {
$this->$name = $value;
} else {
$this->attributes[$name] = $value;
}

$this->populateAttribute($name, $value);
$this->oldAttributes[$name] = $value;
}

Expand Down Expand Up @@ -545,6 +533,25 @@ public function refresh(): bool
return $this->refreshInternal($record);
}

public function resetRelation(string $name): void
{
foreach ($this->relationsDependencies as &$relationNames) {
unset($relationNames[$name]);
}

unset($this->related[$name]);
}

protected function retrieveRelation(string $name): ActiveRecordInterface|array|null
{
/** @var ActiveQueryInterface $query */
$query = $this->getRelation($name);

$this->setRelationDependencies($name, $query);

return $this->related[$name] = $query->relatedRecords();
}

/**
* Saves the current record.
*
Expand Down Expand Up @@ -582,17 +589,14 @@ public function save(array $attributeNames = null): bool

public function setAttribute(string $name, mixed $value): void
{
if ($this->hasAttribute($name)) {
if (
!empty($this->relationsDependencies[$name])
&& (!array_key_exists($name, $this->attributes) || $this->attributes[$name] !== $value)
) {
$this->resetDependentRelations($name);
}
$this->attributes[$name] = $value;
} else {
throw new InvalidArgumentException(static::class . ' has no attribute named "' . $name . '".');
if (
isset($this->relationsDependencies[$name])
&& (!isset(get_object_vars($this)[$name]) || $this->$name !== $value)
) {
$this->resetDependentRelations($name);
}

$this->$name = $value;
}

/**
Expand Down Expand Up @@ -621,7 +625,7 @@ public function setAttributes(array $values): void
*/
public function setIsNewRecord(bool $value): void
{
$this->oldAttributes = $value ? null : $this->attributes;
$this->oldAttributes = $value ? null : get_object_vars($this);
}

/**
Expand Down Expand Up @@ -991,7 +995,7 @@ public function unlinkAll(string $name, bool $delete = false): void
* @param ActiveQueryInterface $relation relation instance.
* @param string|null $viaRelationName intermediate relation.
*/
private function setRelationDependencies(
protected function setRelationDependencies(
string $name,
ActiveQueryInterface $relation,
string $viaRelationName = null
Expand Down Expand Up @@ -1173,12 +1177,17 @@ private function bindModels(
$foreignModel->save();
}

protected function hasDependentRelations(string $attribute): bool
{
return isset($this->relationsDependencies[$attribute]);
}

/**
* Resets dependent related models checking if their links contain specific attribute.
*
* @param string $attribute The changed attribute name.
*/
private function resetDependentRelations(string $attribute): void
protected function resetDependentRelations(string $attribute): void
{
foreach ($this->relationsDependencies[$attribute] as $relation) {
unset($this->related[$relation]);
Expand All @@ -1195,4 +1204,9 @@ public function getTableName(): string

return $this->tableName;
}

protected function populateAttribute(string $name, mixed $value): void
{
$this->$name = $value;
}
}
Loading

0 comments on commit 0c89029

Please sign in to comment.