Skip to content

Commit b3c58d5

Browse files
authored
Add RepositoryTrait (#414)
1 parent 0745336 commit b3c58d5

18 files changed

+435
-255
lines changed

src/AbstractActiveRecord.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -326,14 +326,15 @@ public function insert(array|null $propertyNames = null): bool
326326
}
327327

328328
/**
329-
* @param ActiveRecordInterface|Closure|string $arClass The class name of the related record, or an instance of
330-
* the related record, or a Closure to create an {@see ActiveRecordInterface} object.
329+
* @param ActiveRecordInterface|Closure|string|null $arClass The class name of the related record, or an instance of
330+
* the related record, or a Closure to create an {@see ActiveRecordInterface} object. If `null`, the current model
331+
* will be used.
331332
*
332333
* @psalm-param ARClass $arClass
333334
*/
334-
public function instantiateQuery(string|ActiveRecordInterface|Closure $arClass): ActiveQueryInterface
335+
public function query(ActiveRecordInterface|Closure|null|string $arClass = null): ActiveQueryInterface
335336
{
336-
return new ActiveQuery($arClass);
337+
return new ActiveQuery($arClass ?? $this);
337338
}
338339

339340
public function isChanged(): bool
@@ -553,7 +554,7 @@ public function populateRelation(string $name, array|ActiveRecordInterface|null
553554
*/
554555
public function refresh(): bool
555556
{
556-
$record = $this->instantiateQuery(static::class)->findOne($this->getPrimaryKey(true));
557+
$record = $this->query($this)->findOne($this->getPrimaryKey(true));
557558

558559
return $this->refreshInternal($record);
559560
}
@@ -1051,7 +1052,7 @@ private function setRelationDependencies(
10511052
*/
10521053
protected function createRelationQuery(string|ActiveRecordInterface|Closure $arClass, array $link, bool $multiple): ActiveQueryInterface
10531054
{
1054-
return $this->instantiateQuery($arClass)->primaryModel($this)->link($link)->multiple($multiple);
1055+
return $this->query($arClass)->primaryModel($this)->link($link)->multiple($multiple);
10551056
}
10561057

10571058
/**

src/ActiveQuery.php

Lines changed: 87 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@
2323

2424
use function array_column;
2525
use function array_combine;
26+
use function array_filter;
2627
use function array_flip;
2728
use function array_intersect_key;
29+
use function array_keys;
2830
use function array_map;
2931
use function array_merge;
32+
use function array_slice;
3033
use function array_values;
3134
use function count;
3235
use function implode;
@@ -769,12 +772,6 @@ public function alias(string $alias): static
769772
return $this;
770773
}
771774

772-
/**
773-
* @throws CircularReferenceException
774-
* @throws InvalidArgumentException
775-
* @throws NotInstantiableException
776-
* @throws \Yiisoft\Definitions\Exception\InvalidConfigException
777-
*/
778775
public function getTablesUsedInFrom(): array
779776
{
780777
if (empty($this->from)) {
@@ -784,11 +781,6 @@ public function getTablesUsedInFrom(): array
784781
return parent::getTablesUsedInFrom();
785782
}
786783

787-
/**
788-
* @throws CircularReferenceException
789-
* @throws NotInstantiableException
790-
* @throws \Yiisoft\Definitions\Exception\InvalidConfigException
791-
*/
792784
protected function getPrimaryTableName(): string
793785
{
794786
return $this->getARInstance()->getTableName();
@@ -817,82 +809,53 @@ public function getARClass(): string|ActiveRecordInterface|Closure
817809
return $this->arClass;
818810
}
819811

820-
/**
821-
* @throws Exception
822-
* @throws InvalidArgumentException
823-
* @throws InvalidConfigException
824-
* @throws Throwable
825-
*/
826-
public function findOne(mixed $condition): array|ActiveRecordInterface|null
812+
public function find(array|float|int|string $properties): static
827813
{
828-
return $this->findByCondition($condition)->one();
829-
}
830-
831-
/**
832-
* @param mixed $condition The primary key value or a set of column values.
833-
*
834-
* @throws Exception
835-
* @throws InvalidArgumentException
836-
* @throws InvalidConfigException
837-
* @throws Throwable
838-
*
839-
* @return array Of ActiveRecord instance, or an empty array if nothing matches.
840-
*/
841-
public function findAll(mixed $condition): array
842-
{
843-
return $this->findByCondition($condition)->all();
844-
}
814+
if (!is_array($properties)) {
815+
$properties = [$properties];
816+
} elseif (DbArrayHelper::isAssociative($properties)) {
817+
return $this->setWhere($this->filterProperties($properties));
818+
}
845819

846-
/**
847-
* Finds ActiveRecord instance(s) by the given condition.
848-
*
849-
* This method is internally called by {@see findOne()} and {@see findAll()}.
850-
*
851-
* @throws CircularReferenceException
852-
* @throws Exception
853-
* @throws InvalidArgumentException
854-
* @throws NotInstantiableException
855-
*/
856-
protected function findByCondition(mixed $condition): static
857-
{
858820
$arInstance = $this->getARInstance();
821+
$primaryKey = $arInstance->primaryKey();
859822

860-
if (!is_array($condition)) {
861-
$condition = [$condition];
823+
if (empty($primaryKey)) {
824+
throw new InvalidConfigException('"' . $arInstance::class . '" must have a primary key.');
862825
}
863826

864-
if (!DbArrayHelper::isAssociative($condition)) {
865-
/** query by primary key */
866-
$primaryKey = $arInstance->primaryKey();
827+
if (count($primaryKey) < count($properties)) {
828+
throw new InvalidArgumentException('Primary key is composed of ' . count($primaryKey) . ' columns while ' . count($properties) . ' were given');
829+
}
867830

868-
if (isset($primaryKey[0])) {
869-
$pk = $primaryKey[0];
831+
$primaryKey = array_slice($primaryKey, 0, count($properties));
870832

871-
if (!empty($this->getJoins()) || !empty($this->getJoinWith())) {
872-
$pk = $arInstance->getTableName() . '.' . $pk;
873-
}
833+
if (!empty($this->getJoins()) || !empty($this->getJoinWith())) {
834+
$tableName = $arInstance->getTableName();
874835

875-
/**
876-
* if the condition is scalar, search for a single primary key, if it's array, search for many primary
877-
* key values.
878-
*/
879-
$condition = [$pk => array_values($condition)];
880-
} else {
881-
throw new InvalidConfigException('"' . $arInstance::class . '" must have a primary key.');
836+
foreach ($primaryKey as &$pk) {
837+
$pk = "$tableName.$pk";
882838
}
883-
} else {
884-
$aliases = $arInstance->filterValidAliases($this);
885-
$condition = $arInstance->filterCondition($condition, $aliases);
886839
}
887840

888-
return $this->setWhere($condition);
841+
return $this->setWhere(array_combine($primaryKey, $properties));
842+
}
843+
844+
public function findAll(array|float|int|string $properties): array
845+
{
846+
return $this->find($properties)->all();
889847
}
890848

891849
public function findBySql(string $sql, array $params = []): static
892850
{
893851
return $this->sql($sql)->params($params);
894852
}
895853

854+
public function findOne(array|float|int|string $properties): array|ActiveRecordInterface|null
855+
{
856+
return $this->find($properties)->one();
857+
}
858+
896859
public function on(array|string|null $value): static
897860
{
898861
$this->on = $value;
@@ -946,6 +909,62 @@ private function createInstance(): static
946909
->withQueries($this->withQueries);
947910
}
948911

912+
/**
913+
* Filters properties before using them in a query filter.
914+
*
915+
* This method will ensure that the properties are valid column names and will filter keys of values that are arrays.
916+
*
917+
* @param array $properties Key-value pairs to be ensured and filtered.
918+
*/
919+
private function filterProperties(array $properties): array
920+
{
921+
$result = [];
922+
923+
$quoter = $this->db->getQuoter();
924+
$columnNames = $this->getValidColumnNames();
925+
926+
foreach ($properties as $key => $value) {
927+
if (is_string($key) && !in_array($quoter->quoteSql($key), $columnNames, true)) {
928+
throw new InvalidArgumentException(
929+
'Key "' . $key . '" is not a column name and can not be used as a filter.'
930+
);
931+
}
932+
933+
$result[$key] = is_array($value) ? array_values($value) : $value;
934+
}
935+
936+
return $result;
937+
}
938+
939+
/**
940+
* Returns valid column names: table column names and column names prefixed with table name and table aliases.
941+
*/
942+
private function getValidColumnNames(): array
943+
{
944+
$columnNames = [];
945+
946+
$quoter = $this->db->getQuoter();
947+
$tables = $this->getTablesUsedInFrom();
948+
$tableAliases = array_keys($tables);
949+
950+
$tableNames = array_merge($tableAliases, array_filter($tables, 'is_string'));
951+
$tableNames = array_map($quoter->getRawTableName(...), $tableNames);
952+
953+
foreach ($this->getARInstance()->propertyNames() as $columnName) {
954+
$columnNames[] = $columnName;
955+
$quotedColumnName = $quoter->quoteColumnName($columnName);
956+
$columnNames[] = $quotedColumnName;
957+
958+
foreach ($tableNames as $tableName) {
959+
$columnNames[] = "$tableName.$columnName";
960+
$quotedTableName = $quoter->quoteTableName($tableName);
961+
$columnNames[] = "$quotedTableName.$quotedColumnName";
962+
}
963+
}
964+
965+
return $columnNames;
966+
}
967+
949968
private function populateOne(array $row): ActiveRecordInterface|array
950969
{
951970
return $this->populate([$row])[0];

src/ActiveQueryInterface.php

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,57 @@ public function populate(array $rows): array;
377377
*/
378378
public function relatedRecords(): ActiveRecordInterface|array|null;
379379

380+
/**
381+
* Finds ActiveRecord instance(s) by the given property values.
382+
*
383+
* If the property names are provided as array keys, the array will be treated as a set of column values.
384+
*
385+
* ```php
386+
* $user = $query->find(['id' => 1])->one(); // WHERE id = 1
387+
* ```
388+
*
389+
* ```php
390+
* $users = $query->find([
391+
* 'name' => 'John',
392+
* 'status' => 'active',
393+
* ])->all(); // WHERE name = 'John' AND status = 'active'
394+
* ```
395+
*
396+
* Otherwise, the scalar value or array will be treated as a primary key value.
397+
* In the examples below, the `id` column is the primary key of the table.
398+
*
399+
* ```php
400+
* $user = $query->find(1)->one(); // WHERE id = 1
401+
* ```
402+
*
403+
* ```php
404+
* $user = $query->find([1])->one(); // WHERE id = 1
405+
* ```
406+
*
407+
* For finding multiple records by primary key values use an array of arrays of primary key values.
408+
*
409+
* ```php
410+
* $users = $query->find([[1, 2, 3]])->all(); // WHERE id IN (1, 2, 3)
411+
* ```
412+
*
413+
* If the primary key is composite, the array may contain less or equal number of columns than the primary key.
414+
* In case of fewer columns, the method will use the first primary key columns.
415+
* In the examples below, the `id` and `id2` columns are the composite primary key of the table.
416+
*
417+
* ```php
418+
* $orderItem = $query->find([1, 2])->one(); // WHERE id = 1 AND id2 = 2
419+
* ```
420+
*
421+
* ```php
422+
* $orderItems = $query->find(1)->all(); // WHERE id = 1
423+
* ```
424+
*
425+
* ```php
426+
* $orderItems = $query->find([[1, 2], 3])->all(); // WHERE id IN (1, 2) AND id2 = 3
427+
* ```
428+
*/
429+
public function find(array|float|int|string $properties): static;
430+
380431
/**
381432
* Returns a single active record instance by a primary key or an array of column values.
382433
*
@@ -458,7 +509,7 @@ public function relatedRecords(): ActiveRecordInterface|array|null;
458509
*
459510
* @return ActiveRecordInterface|array|null Instance matching the condition, or `null` if nothing matches.
460511
*/
461-
public function findOne(mixed $condition): array|ActiveRecordInterface|null;
512+
public function findOne(array|float|int|string $properties): array|ActiveRecordInterface|null;
462513

463514
/**
464515
* Returns a list of active record that matches the specified primary key value(s) or a set of column values.
@@ -543,7 +594,7 @@ public function findOne(mixed $condition): array|ActiveRecordInterface|null;
543594
*
544595
* @return array An array of ActiveRecord instance, or an empty array if nothing matches.
545596
*/
546-
public function findAll(mixed $condition): array;
597+
public function findAll(array|float|int|string $properties): array;
547598

548599
/**
549600
* Returns a value indicating whether the query result rows should be returned as arrays instead of Active Record

0 commit comments

Comments
 (0)