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

Protect private properties #338

Merged
merged 3 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 73 additions & 43 deletions src/AbstractActiveRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
use function array_search;
use function array_values;
use function count;
use function get_object_vars;
use function in_array;
use function is_array;
use function is_int;
Expand All @@ -48,12 +47,47 @@ abstract class AbstractActiveRecord implements ActiveRecordInterface
private array $relationsDependencies = [];

public function __construct(
protected ConnectionInterface $db,
private ConnectionInterface $db,
private ActiveRecordFactory|null $arFactory = null,
private string $tableName = ''
) {
}

/**
* Returns the public and protected property values of an Active Record object.
*
* This method is provided because a direct call of {@see get_object_vars()} within the {@see AbstractActiveRecord}
* class will return also private property values of {@see AbstractActiveRecord} class.
*
* @param ActiveRecordInterface $object
*
* @return array
* @link https://www.php.net/manual/en/function.get-object-vars.php
*
* @psalm-return array<string, mixed>
*/
abstract protected function getObjectVars(ActiveRecordInterface $object): array;

/**
* Inserts Active Record values into DB without considering transaction.
*
* @param array|null $attributes List of attributes that need to be saved. Defaults to `null`, meaning all
* attributes that are loaded from DB will be saved.
*
* @throws Exception
* @throws InvalidArgumentException
* @throws InvalidConfigException
* @throws Throwable
*
* @return bool Whether the record is inserted successfully.
*/
abstract protected function insertInternal(array $attributes = null): bool;

/**
* Sets the value of the named attribute.
*/
abstract protected function populateAttribute(string $name, mixed $value): void;

public function delete(): int
{
return $this->deleteInternal();
Expand All @@ -78,19 +112,18 @@ public function equals(ActiveRecordInterface $record): bool

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

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

if ($except !== []) {
$names = array_diff($names, $except);
}

return array_intersect_key($attributes, array_flip($names));
return array_intersect_key($this->getObjectVars($this), array_flip($names));
}

public function getIsNewRecord(): bool
Expand Down Expand Up @@ -293,8 +326,6 @@ public function insert(array $attributes = null): bool
return $this->insertInternal($attributes);
}

abstract protected function insertInternal(array $attributes = null): bool;

/**
* @psalm-param class-string<ActiveRecordInterface> $arClass
*/
Expand All @@ -314,11 +345,11 @@ public function instantiateQuery(string $arClass): ActiveQueryInterface
*/
public function isAttributeChanged(string $name, bool $identical = true): bool
{
if (isset($this->oldAttributes[$name])) {
return $this->$name !== $this->oldAttributes[$name];
if (!isset($this->oldAttributes[$name])) {
return array_key_exists($name, $this->getObjectVars($this));
}

return false;
return $this->getAttribute($name) !== $this->oldAttributes[$name];
}

public function isPrimaryKey(array $keys): bool
Expand Down Expand Up @@ -372,7 +403,7 @@ public function link(string $name, ActiveRecordInterface $arClass, array $extraC
*/
foreach ($viaLink as $a => $b) {
/** @psalm-var mixed */
$columns[$a] = $this->$b;
$columns[$a] = $this->getAttribute($b);
}

$link = $relation->getLink();
Expand All @@ -383,7 +414,7 @@ public function link(string $name, ActiveRecordInterface $arClass, array $extraC
*/
foreach ($link as $a => $b) {
/** @psalm-var mixed */
$columns[$b] = $arClass->$a;
$columns[$b] = $arClass->getAttribute($a);
}

/**
Expand All @@ -401,7 +432,7 @@ public function link(string $name, ActiveRecordInterface $arClass, array $extraC
* @psalm-var mixed $value
*/
foreach ($columns as $column => $value) {
$viaClass->$column = $value;
$viaClass->setAttribute($column, $value);
}

$viaClass->insert();
Expand Down Expand Up @@ -441,9 +472,9 @@ public function link(string $name, ActiveRecordInterface $arClass, array $extraC
$indexBy = $relation->getIndexBy();
if ($indexBy !== null) {
if ($indexBy instanceof Closure) {
$index = $relation->$indexBy($arClass::class);
$index = $indexBy($arClass->getAttributes());
} else {
$index = $arClass->$indexBy;
$index = $arClass->getAttribute($indexBy);
}

if ($index !== null) {
Expand Down Expand Up @@ -609,12 +640,12 @@ public function setAttribute(string $name, mixed $value): void
{
if (
isset($this->relationsDependencies[$name])
&& (!isset(get_object_vars($this)[$name]) || $this->$name !== $value)
&& ($value === null || $this->getAttribute($name) !== $value)
) {
$this->resetDependentRelations($name);
}

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

/**
Expand All @@ -630,7 +661,7 @@ public function setAttributes(array $values): void

/** @psalm-var mixed $value */
foreach ($values as $name => $value) {
$this->$name = $value;
$this->populateAttribute($name, $value);
}
}

Expand All @@ -643,7 +674,7 @@ public function setAttributes(array $values): void
*/
public function setIsNewRecord(bool $value): void
{
$this->oldAttributes = $value ? null : get_object_vars($this);
$this->oldAttributes = $value ? null : $this->getObjectVars($this);
}

/**
Expand Down Expand Up @@ -791,8 +822,8 @@ public function updateCounters(array $counters): bool
}

foreach ($counters as $name => $value) {
$value += $this->$name ?? 0;
$this->$name = $value;
$value += $this->getAttribute($name) ?? 0;
$this->populateAttribute($name, $value);
$this->oldAttributes[$name] = $value;
}

Expand Down Expand Up @@ -825,14 +856,14 @@ public function unlink(string $name, ActiveRecordInterface $arClass, bool $delet

foreach ($viaRelation->getLink() as $a => $b) {
/** @psalm-var mixed */
$columns[$a] = $this->$b;
$columns[$a] = $this->getAttribute($b);
}

$link = $relation->getLink();

foreach ($link as $a => $b) {
/** @psalm-var mixed */
$columns[$b] = $arClass->$a;
$columns[$b] = $arClass->getAttribute($a);
}

$nulls = array_fill_keys(array_keys($columns), null);
Expand Down Expand Up @@ -862,22 +893,22 @@ public function unlink(string $name, ActiveRecordInterface $arClass, bool $delet
$arClass->delete();
} else {
foreach ($relation->getLink() as $a => $b) {
$arClass->$a = null;
$arClass->setAttribute($a, null);
}
$arClass->save();
}
} elseif ($arClass->isPrimaryKey(array_keys($relation->getLink()))) {
foreach ($relation->getLink() as $a => $b) {
/** @psalm-var mixed $values */
$values = $this->$b;
$values = $this->getAttribute($b);
/** relation via array valued attribute */
if (is_array($values)) {
if (($key = array_search($arClass->$a, $values, false)) !== false) {
if (($key = array_search($arClass->getAttribute($a), $values, false)) !== false) {
unset($values[$key]);
$this->$b = array_values($values);
$this->setAttribute($b, array_values($values));
}
} else {
$this->$b = null;
$this->setAttribute($b, null);
}
}
$delete ? $this->delete() : $this->save();
Expand Down Expand Up @@ -943,7 +974,7 @@ public function unlinkAll(string $name, bool $delete = false): void
foreach ($viaRelation->getLink() as $a => $b) {
$nulls[$a] = null;
/** @psalm-var mixed */
$condition[$a] = $this->$b;
$condition[$a] = $this->getAttribute($b);
}

if (!empty($viaRelation->getWhere())) {
Expand Down Expand Up @@ -973,9 +1004,9 @@ public function unlinkAll(string $name, bool $delete = false): void
$relatedModel = $relation->getARInstance();

$link = $relation->getLink();
if (!$delete && count($link) === 1 && is_array($this->{$b = reset($link)})) {
if (!$delete && count($link) === 1 && is_array($this->getAttribute($b = reset($link)))) {
/** relation via array valued attribute */
$this->$b = [];
$this->setAttribute($b, []);
$this->save();
} else {
$nulls = [];
Expand All @@ -984,7 +1015,7 @@ public function unlinkAll(string $name, bool $delete = false): void
foreach ($relation->getLink() as $a => $b) {
$nulls[$a] = null;
/** @psalm-var mixed */
$condition[$a] = $this->$b;
$condition[$a] = $this->getAttribute($b);
}

if (!empty($relation->getWhere())) {
Expand Down Expand Up @@ -1077,7 +1108,7 @@ protected function deleteInternal(): int
$lock = $this->optimisticLock();

if ($lock !== null) {
$condition[$lock] = $this->$lock;
$condition[$lock] = $this->getAttribute($lock);

$result = $this->deleteAll($condition);

Expand Down Expand Up @@ -1109,7 +1140,7 @@ protected function refreshInternal(array|ActiveRecordInterface $record = null):
}

foreach ($this->attributes() as $name) {
$this->$name = $record->getAttribute($name);
$this->populateAttribute($name, $record->getAttribute($name));
}

$this->oldAttributes = $record->getOldAttributes();
Expand Down Expand Up @@ -1142,7 +1173,7 @@ protected function updateInternal(array $attributes = null): int
$lock = $this->optimisticLock();

if ($lock !== null) {
$lockValue = $this->$lock;
$lockValue = $this->getAttribute($lock);

$condition[$lock] = $lockValue;
$values[$lock] = ++$lockValue;
Expand All @@ -1153,7 +1184,7 @@ protected function updateInternal(array $attributes = null): int
throw new StaleObjectException('The object being updated is outdated.');
}

$this->$lock = $lockValue;
$this->populateAttribute($lock, $lockValue);
} else {
$rows = $this->updateAll($values, $condition);
}
Expand All @@ -1171,7 +1202,7 @@ private function bindModels(
/** @psalm-var string[] $link */
foreach ($link as $fk => $pk) {
/** @psalm-var mixed $value */
$value = $primaryModel->$pk;
$value = $primaryModel->getAttribute($pk);

if ($value === null) {
throw new InvalidCallException(
Expand All @@ -1181,12 +1212,11 @@ private function bindModels(

/**
* relation via array valued attribute
*
* @psalm-suppress MixedArrayAssignment
*/
if (is_array($foreignModel->getAttribute($fk))) {
if (is_array($fkValue = $foreignModel->getAttribute($fk))) {
/** @psalm-var mixed */
$foreignModel->{$fk}[] = $value;
$fkValue[] = $value;
$foreignModel->setAttribute($fk, $fkValue);
} else {
$foreignModel->setAttribute($fk, $value);
}
Expand Down Expand Up @@ -1223,8 +1253,8 @@ public function getTableName(): string
return $this->tableName;
}

protected function populateAttribute(string $name, mixed $value): void
protected function db(): ConnectionInterface
{
$this->$name = $value;
return $this->db;
}
}
4 changes: 2 additions & 2 deletions src/ActiveRecordInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,14 @@ public function delete(): int;
* For example, to delete all customers whose status is 3:
*
* ```php
* $customer = new Customer($this->db);
* $customer = new Customer($db);
* $customer->deleteAll('status = 3');
* ```
*
* > Warning: If you don't specify any condition, this method will delete **all** rows in the table.
*
* ```php
* $customerQuery = new ActiveQuery(Customer::class, $this->db);
* $customerQuery = new ActiveQuery(Customer::class, $db);
* $aqClasses = $customerQuery->where('status = 3')->all();
* foreach ($aqClasses as $aqClass) {
* $aqClass->delete();
Expand Down
Loading