Skip to content
Open
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
300 changes: 133 additions & 167 deletions src/AbstractActiveRecord.php

Large diffs are not rendered by default.

43 changes: 43 additions & 0 deletions src/AbstractActiveRecordModel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace Yiisoft\ActiveRecord;

use ReflectionClass;
use Yiisoft\Db\Connection\ConnectionInterface;
use Yiisoft\Db\Exception\InvalidArgumentException;

/**
* The base class for active record models.
*/
abstract class AbstractActiveRecordModel implements ActiveRecordModelInterface
{
private ActiveRecord $activeRecord;

public function activeRecord(): ActiveRecord
{
/** @psalm-suppress RedundantPropertyInitializationCheck */
return $this->activeRecord ??= new ActiveRecord($this);
}

public function db(): ConnectionInterface
{
return ConnectionProvider::get();
}

public function relationQuery(string $name): ActiveQueryInterface
{
throw new InvalidArgumentException(static::class . ' has no relation named "' . $name . '".');
}

public function tableName(): string
{
$name = (new ReflectionClass($this))->getShortName();
/** @var string $name */
$name = preg_replace('/[A-Z]([A-Z](?![a-z]))*/', '_$0', $name);
$name = strtolower(ltrim($name, '_'));

return '{{%' . $name . '}}';
}
}
92 changes: 47 additions & 45 deletions src/ActiveQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
* as inverse of another relation and {@see onCondition()} which adds a condition that is to be added to relational
* query join condition.
*
* @psalm-import-type ARClass from ActiveQueryInterface
* @psalm-import-type ModelClass from ActiveQueryInterface
* @psalm-import-type IndexKey from ArArrayHelper
*
* @psalm-property IndexKey|null $indexBy
Expand All @@ -116,12 +116,12 @@ class ActiveQuery extends Query implements ActiveQueryInterface
private array $joinWith = [];

/**
* @psalm-param ARClass $arClass
* @psalm-param ModelClass $modelClass
*/
final public function __construct(
protected string|ActiveRecordInterface|Closure $arClass
protected string|ActiveRecordModelInterface|Closure $modelClass
) {
parent::__construct($this->getARInstance()->db());
parent::__construct($this->getModelInstance()->db());
}

public function each(): DataReaderInterface
Expand Down Expand Up @@ -169,6 +169,7 @@ public function prepare(QueryBuilderInterface $builder): QueryInterface
if ($this->primaryModel === null) {
$query = $this->createInstance();
} else {
$activeRecord = $this->primaryModel->activeRecord();
$where = $this->getWhere();

if ($this->via instanceof ActiveQueryInterface) {
Expand All @@ -183,21 +184,21 @@ public function prepare(QueryBuilderInterface $builder): QueryInterface
if ($viaQuery->getMultiple()) {
if ($viaCallableUsed) {
$viaModels = $viaQuery->all();
} elseif ($this->primaryModel->isRelationPopulated($viaName)) {
/** @var ActiveRecordInterface[]|array[] $viaModels */
$viaModels = $this->primaryModel->relation($viaName);
} elseif ($activeRecord->isRelationPopulated($viaName)) {
/** @var ActiveRecordModelInterface[]|array[] $viaModels */
$viaModels = $activeRecord->relation($viaName);
} else {
$viaModels = $viaQuery->all();
$this->primaryModel->populateRelation($viaName, $viaModels);
$activeRecord->populateRelation($viaName, $viaModels);
}
} else {
if ($viaCallableUsed) {
$model = $viaQuery->one();
} elseif ($this->primaryModel->isRelationPopulated($viaName)) {
$model = $this->primaryModel->relation($viaName);
} elseif ($activeRecord->isRelationPopulated($viaName)) {
$model = $activeRecord->relation($viaName);
} else {
$model = $viaQuery->one();
$this->primaryModel->populateRelation($viaName, $model);
$activeRecord->populateRelation($viaName, $model);
}
$viaModels = $model === null ? [] : [$model];
}
Expand Down Expand Up @@ -228,8 +229,8 @@ public function prepare(QueryBuilderInterface $builder): QueryInterface
* @psalm-param list<array> $rows
* @psalm-return (
* $rows is non-empty-list<array>
* ? non-empty-list<ActiveRecordInterface|array>
* : list<ActiveRecordInterface|array>
* ? non-empty-list<ActiveRecordModelInterface|array>
* : list<ActiveRecordModelInterface|array>
* )
*/
public function populate(array $rows): array
Expand Down Expand Up @@ -274,8 +275,8 @@ public function populate(array $rows): array
*/
private function removeDuplicatedRows(array $rows): array
{
$instance = $this->getARInstance();
$pks = $instance->primaryKey();
$instance = $this->getModelInstance();
$pks = $instance->activeRecord()->primaryKey();

if (empty($pks)) {
throw new InvalidConfigException('Primary key of "' . $instance::class . '" can not be empty.');
Expand All @@ -301,7 +302,7 @@ private function removeDuplicatedRows(array $rows): array
return array_values(array_combine($hash, $rows));
}

public function one(): array|ActiveRecordInterface|null
public function one(): array|ActiveRecordModelInterface|null
{
if ($this->shouldEmulateExecution()) {
return null;
Expand Down Expand Up @@ -417,10 +418,10 @@ public function buildJoinWith(): void

$this->join = [];

$arClass = $this->getARInstance();
$model = $this->getModelInstance();

foreach ($this->joinWith as [$with, $eagerLoading, $joinType]) {
$this->joinWithRelations($arClass, $with, $joinType);
$this->joinWithRelations($model, $with, $joinType);

if (is_array($eagerLoading)) {
foreach ($with as $name => $callback) {
Expand Down Expand Up @@ -480,7 +481,7 @@ public function innerJoinWith(array|string $with, array|bool $eagerLoading = tru
/**
* Modifies the current query by adding join fragments based on the given relations.
*
* @param ActiveRecordInterface $arClass The primary model.
* @param ActiveRecordModelInterface $model The primary model.
* @param array $with The relations to be joined.
* @param array|string $joinType The join type.
*
Expand All @@ -489,7 +490,7 @@ public function innerJoinWith(array|string $with, array|bool $eagerLoading = tru
* @throws NotInstantiableException
* @throws \Yiisoft\Definitions\Exception\InvalidConfigException
*/
private function joinWithRelations(ActiveRecordInterface $arClass, array $with, array|string $joinType): void
private function joinWithRelations(ActiveRecordModelInterface $model, array $with, array|string $joinType): void
{
$relations = [];

Expand All @@ -499,7 +500,7 @@ private function joinWithRelations(ActiveRecordInterface $arClass, array $with,
$callback = null;
}

$primaryModel = $arClass;
$primaryModel = $model;
$parent = $this;
$prefix = '';

Expand All @@ -518,7 +519,7 @@ private function joinWithRelations(ActiveRecordInterface $arClass, array $with,
}

if ($relation instanceof ActiveQueryInterface) {
$primaryModel = $relation->getARInstance();
$primaryModel = $relation->getModelInstance();
$parent = $relation;
}

Expand Down Expand Up @@ -564,7 +565,7 @@ private function getJoinType(array|string $joinType, string $name): string
}

/**
* Returns the table name and the table alias for {@see arClass}.
* Returns the table name and the table alias for {@see $modelClass}.
*
* @throws CircularReferenceException
* @throws InvalidConfigException
Expand Down Expand Up @@ -731,11 +732,11 @@ public function orOnCondition(array|string $condition, array $params = []): stat
return $this;
}

public function viaTable(string $tableName, array $link, callable $callable = null): static
public function viaTable(string $tableName, array $link, callable|null $callable = null): static
{
$arClass = $this->primaryModel ?? $this->arClass;
$modelClass = $this->primaryModel ?? $this->modelClass;

$relation = (new static($arClass))
$relation = (new static($modelClass))
->from([$tableName])
->link($link)
->multiple(true)
Expand Down Expand Up @@ -791,7 +792,7 @@ public function getTablesUsedInFrom(): array
*/
protected function getPrimaryTableName(): string
{
return $this->getARInstance()->getTableName();
return $this->getModelInstance()->tableName();
}

public function getOn(): array|string|null
Expand All @@ -812,9 +813,9 @@ public function getSql(): string|null
return $this->sql;
}

public function getARClass(): string|ActiveRecordInterface|Closure
public function getModelClass(): string|ActiveRecordModelInterface|Closure
{
return $this->arClass;
return $this->modelClass;
}

/**
Expand All @@ -823,7 +824,7 @@ public function getARClass(): string|ActiveRecordInterface|Closure
* @throws InvalidConfigException
* @throws Throwable
*/
public function findOne(mixed $condition): array|ActiveRecordInterface|null
public function findOne(mixed $condition): array|ActiveRecordModelInterface|null
{
return $this->findByCondition($condition)->one();
}
Expand Down Expand Up @@ -855,21 +856,22 @@ public function findAll(mixed $condition): array
*/
protected function findByCondition(mixed $condition): static
{
$arInstance = $this->getARInstance();
$modelInstance = $this->getModelInstance();
$activeRecord = $modelInstance->activeRecord();

if (!is_array($condition)) {
$condition = [$condition];
}

if (!DbArrayHelper::isAssociative($condition)) {
/** query by primary key */
$primaryKey = $arInstance->primaryKey();
$primaryKey = $activeRecord->primaryKey();

if (isset($primaryKey[0])) {
$pk = $primaryKey[0];

if (!empty($this->getJoins()) || !empty($this->getJoinWith())) {
$pk = $arInstance->getTableName() . '.' . $pk;
$pk = $modelInstance->tableName() . '.' . $pk;
}

/**
Expand All @@ -878,11 +880,11 @@ protected function findByCondition(mixed $condition): static
*/
$condition = [$pk => array_values($condition)];
} else {
throw new InvalidConfigException('"' . $arInstance::class . '" must have a primary key.');
throw new InvalidConfigException('"' . $modelInstance::class . '" must have a primary key.');
}
} else {
$aliases = $arInstance->filterValidAliases($this);
$condition = $arInstance->filterCondition($condition, $aliases);
$aliases = $activeRecord->filterValidAliases($this);
$condition = $activeRecord->filterCondition($condition, $aliases);
}

return $this->setWhere($condition);
Expand All @@ -905,18 +907,18 @@ public function sql(string|null $value): static
return $this;
}

public function getARInstance(): ActiveRecordInterface
public function getModelInstance(): ActiveRecordModelInterface
{
if ($this->arClass instanceof ActiveRecordInterface) {
return clone $this->arClass;
if ($this->modelClass instanceof ActiveRecordModelInterface) {
return clone $this->modelClass;
}

if ($this->arClass instanceof Closure) {
return ($this->arClass)();
if ($this->modelClass instanceof Closure) {
return ($this->modelClass)();
}

/** @psalm-var class-string<ActiveRecordInterface> $class */
$class = $this->arClass;
/** @psalm-var class-string<ActiveRecordModelInterface> $class */
$class = $this->modelClass;

return new $class();
}
Expand All @@ -928,7 +930,7 @@ protected function index(array $rows): array

private function createInstance(): static
{
return (new static($this->arClass))
return (new static($this->modelClass))
->where($this->getWhere())
->limit($this->getLimit())
->offset($this->getOffset())
Expand All @@ -946,7 +948,7 @@ private function createInstance(): static
->withQueries($this->withQueries);
}

private function populateOne(array $row): ActiveRecordInterface|array
private function populateOne(array $row): ActiveRecordModelInterface|array
{
return $this->populate([$row])[0];
}
Expand Down
Loading