diff --git a/src/AbstractActiveRecord.php b/src/AbstractActiveRecord.php index 511e629cd..6ee393ab9 100644 --- a/src/AbstractActiveRecord.php +++ b/src/AbstractActiveRecord.php @@ -47,7 +47,6 @@ * * See {@see ActiveRecord} for a concrete implementation. * - * @psalm-import-type ModelClass from ActiveQuery * @psalm-import-type RawFrom from QueryInterface */ abstract class AbstractActiveRecord implements ActiveRecordInterface @@ -61,6 +60,12 @@ abstract class AbstractActiveRecord implements ActiveRecordInterface /** @var string[][] */ private array $relationsDependencies = []; + /** + * @template T as ActiveRecordInterface + * @template TModelClass as T|class-string|null + * @psalm-param TModelClass $modelClass + * @psalm-return ActiveQueryInterface<(TModelClass is null ? static : T), null> + */ public function createQuery(ActiveRecordInterface|string|null $modelClass = null): ActiveQueryInterface { $modelClass ??= $this; @@ -419,8 +424,15 @@ public function populateRelation(string $name, array|ActiveRecordInterface|null $this->related[$name] = $records; } + /** + * @template T as ActiveRecordInterface + * @template TModelClass as T|class-string|null + * @psalm-param TModelClass $modelClass + * @psalm-return ActiveQuery<(TModelClass is null ? static : T), null> + */ public static function query(ActiveRecordInterface|string|null $modelClass = null): ActiveQueryInterface { + /** @psalm-var ActiveQuery<(TModelClass is null ? static : T), null> */ return new ActiveQuery($modelClass ?? static::class); } @@ -822,8 +834,10 @@ protected function retrieveRelation(string $name): ActiveRecordInterface|array|n * * @return ActiveQueryInterface The relational query object. * - * @psalm-param ModelClass $modelClass + * @template T as ActiveRecordInterface + * @psalm-param T|class-string $modelClass * @psalm-param array $link + * @psalm-return ActiveQueryInterface * * @see AbstractActiveRecord::hasOne() * @see AbstractActiveRecord::hasMany() @@ -833,6 +847,7 @@ protected function createRelationQuery( array $link, bool $multiple, ): ActiveQueryInterface { + /** @psalm-var ActiveQueryInterface */ return $this->createQuery($modelClass)->primaryModel($this)->link($link)->multiple($multiple); } diff --git a/src/ActiveQuery.php b/src/ActiveQuery.php index b18b94507..ceb8e86d5 100644 --- a/src/ActiveQuery.php +++ b/src/ActiveQuery.php @@ -106,10 +106,11 @@ * and {@see ActiveQuery::on()} which adds a condition that is to be added to relational * query join condition. * - * @psalm-type ModelClass = ActiveRecordInterface|class-string + * @template TModel as ActiveRecordInterface + * @template TAsArray as ?bool + * @implements ActiveQueryInterface * @psalm-import-type IndexBy from QueryInterface * @psalm-import-type Join from QueryInterface - * @psalm-import-type ActiveQueryResult from ActiveQueryInterface * @psalm-import-type Via from ActiveQueryInterface * * @psalm-property IndexBy|null $indexBy @@ -117,9 +118,11 @@ */ class ActiveQuery extends Query implements ActiveQueryInterface { + /** @psalm-var TModel $model */ private ActiveRecordInterface $model; private ?string $sql = null; private array|ExpressionInterface|string|null $on = null; + /** @psalm-var TAsArray $asArray */ private ?bool $asArray = null; private array $with = []; private bool $multiple = false; @@ -155,7 +158,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface private array|ActiveQueryInterface|null $via = null; /** - * @psalm-param ModelClass $modelClass + * @psalm-param TModel|class-string $modelClass */ final public function __construct( ActiveRecordInterface|string $modelClass, @@ -180,9 +183,17 @@ public function __clone() } } + /** + * @template T as ?bool + * @psalm-param T $value + * @psalm-return static + */ public function asArray(?bool $value = true): static { + /** @psalm-suppress InvalidPropertyAssignmentValue */ $this->asArray = $value; + + /** @psalm-var static */ return $this; } @@ -325,11 +336,13 @@ public function populate(array $rows): array $this->findWith($this->with, $models); } + /** @psalm-var non-empty-list<(TAsArray is true ? array : TModel)> $models */ $this->addInverseRelations($models); return $models; } + /** @psalm-return (TAsArray is true ? array : TModel)|null */ public function one(): array|ActiveRecordInterface|null { if ($this->shouldEmulateExecution()) { @@ -451,6 +464,7 @@ public function viaTable(string $tableName, array $link, ?callable $callable = n { $model = $this->primaryModel ?? $this->model; + /** @psalm-suppress UnsafeGenericInstantiation, InvalidTemplateParam */ $relation = (new static($model)) ->from([$tableName]) ->link($link) @@ -509,6 +523,7 @@ public function getSql(): ?string return $this->sql; } + /** @psalm-return (TAsArray is true ? array : TModel)|null */ public function findByPk(array|float|int|string $values): array|ActiveRecordInterface|null { $values = (array) $values; @@ -543,6 +558,7 @@ public function sql(?string $value): static return $this; } + /** @psalm-return TModel */ public function getModel(): ActiveRecordInterface { return clone $this->model; @@ -686,7 +702,7 @@ protected function index(array $rows): array * @return ActiveRecordInterface[]|array[] The model instances. * * @psalm-param non-empty-list> $rows - * @psalm-return non-empty-list + * @psalm-return non-empty-list<(TAsArray is true ? array : TModel)> */ protected function createModels(array $rows): array { @@ -702,12 +718,12 @@ protected function createModels(array $rows): array $rows = ($this->resultCallback)($rows); if ($rows[0] instanceof ActiveRecordInterface) { - /** @psalm-var non-empty-list */ + /** @psalm-var non-empty-list */ return $rows; } } - /** @var non-empty-list> $rows */ + /** @psalm-var non-empty-list> $rows */ return array_map( fn(array $row) => $this->getModel()->populateRecord($row), $rows, @@ -760,6 +776,7 @@ private function removeDuplicatedRows(array $rows): array private function createInstance(): static { + /** @psalm-suppress UnsafeGenericInstantiation, InvalidTemplateParam */ return (new static($this->model)) ->where($this->getWhere()) ->limit($this->getLimit()) @@ -780,7 +797,7 @@ private function createInstance(): static /** * @psalm-param array $row - * @psalm-return ActiveQueryResult + * @psalm-return (TAsArray is true ? array : TModel) */ private function populateOne(array $row): ActiveRecordInterface|array { @@ -800,8 +817,8 @@ private function populateOne(array $row): ActiveRecordInterface|array * @throws ReflectionException * @throws Throwable * - * @psalm-param non-empty-list $models - * @psalm-param-out non-empty-list $models + * @psalm-param non-empty-list> $models + * @psalm-param-out non-empty-list> $models */ private function findWith(array $with, array &$models): void { @@ -872,14 +889,13 @@ private function normalizeRelations(ActiveRecordInterface $model, array $with): * @param ActiveRecordInterface[]|array[] $result the array of related records as generated * by {@see ActiveQuery::populate()} * - * @throws InvalidConfigException - * - * @psalm-param non-empty-list $result - * @psalm-param-out non-empty-list $result + * @psalm-param non-empty-list<(TAsArray is true ? array : TModel)> $result + * @psalm-param-out non-empty-list<(TAsArray is true ? array : TModel)> $result */ private function addInverseRelations(array &$result): void { if ($this->inverseOf === null) { + /** @psalm-var non-empty-list<(TAsArray is true ? array : TModel)> $result */ return; } @@ -889,7 +905,7 @@ private function addInverseRelations(array &$result): void $inverseRelation = $relatedModel->relationQuery($this->inverseOf); $primaryModel = $inverseRelation->isMultiple() ? [$this->primaryModel] : $this->primaryModel; - /** @var ActiveRecordInterface $relatedModel */ + /** @psalm-var TModel $relatedModel */ foreach ($result as $relatedModel) { $relatedModel->populateRelation($this->inverseOf, $primaryModel); } @@ -902,5 +918,7 @@ private function addInverseRelations(array &$result): void $relatedModel[$this->inverseOf] = $primaryModel; } } + + /** @psalm-var non-empty-list<(TAsArray is true ? array : TModel)> $result */ } } diff --git a/src/ActiveQueryInterface.php b/src/ActiveQueryInterface.php index ad8432ead..4474a6b1f 100644 --- a/src/ActiveQueryInterface.php +++ b/src/ActiveQueryInterface.php @@ -19,11 +19,12 @@ /** * A common interface to be implemented by active record query classes. * - * That are methods for all normal queries that return active records but also relational queries in which the query + * These are methods for all normal queries that return active records but also relational queries in which the query * represents a relation between two active record classes and will return related records only. * + * @template TModel as ActiveRecordInterface + * @template TAsArray as ?bool * @psalm-type Via = array{string, ActiveQueryInterface, bool}|ActiveQueryInterface - * @psalm-type ActiveQueryResult = ActiveRecordInterface|array */ interface ActiveQueryInterface extends QueryInterface { @@ -39,16 +40,20 @@ interface ActiveQueryInterface extends QueryInterface * on {@see ActiveQueryInterface::isAsArray()} result. * Empty array if the query results in nothing. * - * @psalm-return array + * @psalm-return (TAsArray is true ? array> : TModel[]) */ public function all(): array; /** * Sets the {@see ActiveQuery::$asArray} property. * - * @param bool|null $value Whether to return the query results in terms of arrays instead of Active Records. + * @param ?bool $value Whether to return the query results in terms of arrays instead of Active Records. * * @return static The query object itself. + * + * @template T as ?bool + * @psalm-param T $value + * @psalm-return static */ public function asArray(?bool $value = true): static; @@ -217,6 +222,7 @@ public function getJoinsWith(): array; * * @psalm-param array|string $with * @psalm-param array|string $joinType + * @psalm-return static */ public function joinWith( array|string $with, @@ -372,8 +378,8 @@ public function sql(?string $value): static; * @psalm-param list> $rows * @psalm-return ( * $rows is non-empty-list> - * ? non-empty-list - * : list + * ? non-empty-list<(TAsArray is true ? array : TModel)> + * : list<(TAsArray is true ? array : TModel)> * ) */ public function populate(array $rows): array; @@ -446,6 +452,12 @@ public function getInverseOf(): ?string; * @throws Throwable if the relation is invalid. * * @return ActiveRecordInterface|ActiveRecordInterface[]|array|array[]|null the related record(s). + * + * @psalm-return ( + * TAsArray is true + * ? array|array> + * : TModel|TModel[] + * )|null */ public function relatedRecords(): ActiveRecordInterface|array|null; @@ -483,12 +495,16 @@ public function relatedRecords(): ActiveRecordInterface|array|null; * $customer = $customerQuery->findByPk($id); * } * ``` + * + * @psalm-return (TAsArray is true ? array : TModel)|null */ public function findByPk(array|float|int|string $values): array|ActiveRecordInterface|null; /** * Returns a value indicating whether the query result rows should be returned as arrays instead of Active Record * models. + * + * @psalm-return TAsArray */ public function isAsArray(): ?bool; @@ -547,6 +563,8 @@ public function getLink(): array; /** * @return ActiveRecordInterface The model instance associated with this query. + * + * @psalm-return TModel */ public function getModel(): ActiveRecordInterface; @@ -581,6 +599,8 @@ public function isMultiple(): bool; * * @return ActiveRecordInterface|array|null The first row as an `array` or instance of {@see ActiveRecordInterface} * of the query result, depends on {@see ActiveQueryInterface::isAsArray()} result. `null` if the query results in nothing. + * + * @psalm-return (TAsArray is true ? array : TModel)|null */ public function one(): array|ActiveRecordInterface|null; @@ -595,8 +615,8 @@ public function one(): array|ActiveRecordInterface|null; * {@see ActiveQueryInterface::link()} is invalid. * @return ActiveRecordInterface[]|array[] The related models. * - * @psalm-param non-empty-list $primaryModels - * @psalm-param-out non-empty-list $primaryModels + * @psalm-param non-empty-list> $primaryModels + * @psalm-param-out non-empty-list> $primaryModels */ public function populateRelation(string $name, array &$primaryModels): array; } diff --git a/src/ActiveRecordInterface.php b/src/ActiveRecordInterface.php index c68bb9098..fcfc5948e 100644 --- a/src/ActiveRecordInterface.php +++ b/src/ActiveRecordInterface.php @@ -17,7 +17,6 @@ use Yiisoft\Db\Schema\TableSchemaInterface; /** - * @psalm-import-type ModelClass from ActiveQuery * @psalm-import-type RawFrom from QueryInterface */ interface ActiveRecordInterface @@ -42,7 +41,10 @@ public function column(string $propertyName): ColumnInterface; * @param ActiveRecordInterface|string|null $modelClass The class name of the related record, or an instance * of the related record. If `null`, the current model will be used. * - * @psalm-param ModelClass|null $modelClass + * @template T as ActiveRecordInterface + * @template TModelClass as T|class-string|null + * @psalm-param TModelClass $modelClass + * @psalm-return ActiveQueryInterface<(TModelClass is null ? static : T), null> */ public function createQuery(self|string|null $modelClass = null): ActiveQueryInterface; @@ -299,8 +301,10 @@ public function hasProperty(string $name): bool; * * @return ActiveQueryInterface The relational query object. * - * @psalm-param ModelClass $modelClass + * @template T as ActiveRecordInterface + * @psalm-param T|class-string $modelClass * @psalm-param array $link + * @psalm-return ActiveQueryInterface */ public function hasMany(self|string $modelClass, array $link): ActiveQueryInterface; @@ -336,8 +340,10 @@ public function hasMany(self|string $modelClass, array $link): ActiveQueryInterf * * @return ActiveQueryInterface The relational query object. * - * @psalm-param ModelClass $modelClass + * @template T as ActiveRecordInterface + * @psalm-param T|class-string $modelClass * @psalm-param array $link + * @psalm-return ActiveQueryInterface */ public function hasOne(self|string $modelClass, array $link): ActiveQueryInterface; @@ -458,7 +464,10 @@ public function populateRelation(string $name, array|self|null $records): void; * @param ActiveRecordInterface|string|null $modelClass The class name of the related record, or an instance * of the related record. If `null`, the current model class will be used. * - * @psalm-param ModelClass|null $modelClass + * @template T as ActiveRecordInterface + * @template TModelClass as T|class-string|null + * @psalm-param TModelClass $modelClass + * @psalm-return ActiveQueryInterface<(TModelClass is null ? static : T), null> */ public static function query(self|string|null $modelClass = null): ActiveQueryInterface; diff --git a/src/Trait/RepositoryTrait.php b/src/Trait/RepositoryTrait.php index 9b5a6923b..fb084b108 100644 --- a/src/Trait/RepositoryTrait.php +++ b/src/Trait/RepositoryTrait.php @@ -50,6 +50,8 @@ trait RepositoryTrait * @param array|ExpressionInterface|string|null $condition The condition to be applied to the query where clause. * No condition is applied if `null` (by default). * @param array $params The parameters to be bound to the SQL statement during execution. + * + * @psalm-return ActiveQueryInterface */ public static function find(array|string|ExpressionInterface|null $condition = null, array $params = []): ActiveQueryInterface { @@ -98,7 +100,7 @@ public static function find(array|string|ExpressionInterface|null $condition = n * Returns all records if `null` (by default). * @param array $params The parameters to be bound to the SQL statement during execution. * - * @return ActiveRecordInterface[]|array[] An array of ActiveRecord instance, or an empty array if nothing matches. + * @return static[]|array[] An array of ActiveRecord instance, or an empty array if nothing matches. */ public static function findAll(array|string|ExpressionInterface|null $condition = null, array $params = []): array { @@ -118,8 +120,7 @@ public static function findAll(array|string|ExpressionInterface|null $condition * * @throws NotFoundException * - * @return ActiveRecordInterface[]|array[] An array of ActiveRecord instance, or throws {@see NotFoundException} - * if nothing matches. + * @return static[]|array[] An array of ActiveRecord instance, or throws {@see NotFoundException} if nothing matches. */ public static function findAllOrFail(array|string|ExpressionInterface|null $condition = null, array $params = []): array { @@ -158,7 +159,7 @@ public static function findAllOrFail(array|string|ExpressionInterface|null $cond * * @param array|float|int|string $values The primary key value(s) to find the record. * - * @return ActiveRecordInterface|array|null Instance matching the primary key value(s), or `null` if nothing matches. + * @return static|array|null Instance matching the primary key value(s), or `null` if nothing matches. */ public static function findByPk(array|float|int|string $values): array|ActiveRecordInterface|null { @@ -176,10 +177,10 @@ public static function findByPk(array|float|int|string $values): array|ActiveRec * * @throws NotFoundException * - * @return ActiveRecordInterface|array|null Instance matching the primary key value(s), + * @return static|array Instance matching the primary key value(s), * or throws {@see NotFoundException} if nothing matches. */ - public static function findByPkOrFail(array|float|int|string $values): array|ActiveRecordInterface|null + public static function findByPkOrFail(array|float|int|string $values): array|ActiveRecordInterface { return static::findByPk($values) ?? throw new NotFoundException('No records found.'); } @@ -204,6 +205,8 @@ public static function findByPkOrFail(array|float|int|string $values): array|Act * @param array $params The parameters to be bound to the SQL statement during execution. * * @return ActiveQueryInterface The newly created {@see ActiveQueryInterface} instance. + * + * @psalm-return ActiveQueryInterface */ public static function findBySql(string $sql, array $params = []): ActiveQueryInterface { @@ -246,7 +249,7 @@ public static function findBySql(string $sql, array $params = []): ActiveQueryIn * Returns the first record if `null` (by default). * @param array $params The parameters to be bound to the SQL statement during execution. * - * @return ActiveRecordInterface|array|null Instance matching the condition, or `null` if nothing matches. + * @return static|array|null Instance matching the condition, or `null` if nothing matches. */ public static function findOne( array|string|ExpressionInterface|null $condition = null, @@ -268,13 +271,13 @@ public static function findOne( * * @throws NotFoundException * - * @return ActiveRecordInterface|array|null Instance matching the condition, or throws {@see NotFoundException} + * @return static|array Instance matching the condition, or throws {@see NotFoundException} * if nothing matches. */ public static function findOneOrFail( array|string|ExpressionInterface|null $condition = null, array $params = [], - ): ActiveRecordInterface|array|null { + ): ActiveRecordInterface|array { return static::findOne($condition, $params) ?? throw new NotFoundException('No records found.'); } }