diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 61919b6..6cf8a5c 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -20,9 +20,10 @@ We accept contributions via Pull Requests on [GitHub](https://github.com/stauden - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. -## Running Tests +## Running Tests & Static Analysis ``` docker compose run --rm php8.3 composer install docker compose run --rm php8.3 vendor/bin/phpunit +docker compose run --rm php8.3 vendor/bin/phpstan analyse --memory-limit=-1 ``` diff --git a/README.md b/README.md index cedea74..e54cbe6 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![CI](https://github.com/staudenmeir/eloquent-json-relations/actions/workflows/ci.yml/badge.svg)](https://github.com/staudenmeir/eloquent-json-relations/actions/workflows/ci.yml?query=branch%3Amain) [![Code Coverage](https://codecov.io/gh/staudenmeir/eloquent-json-relations/graph/badge.svg?token=T41IX53I5U)](https://codecov.io/gh/staudenmeir/eloquent-json-relations) +[![PHPStan](https://img.shields.io/badge/PHPStan-level%209-brightgreen.svg?style=flat)](https://github.com/staudenmeir/eloquent-json-relations/actions/workflows/static-analysis.yml?query=branch%3Amain) [![Latest Stable Version](https://poser.pugx.org/staudenmeir/eloquent-json-relations/v/stable)](https://packagist.org/packages/staudenmeir/eloquent-json-relations) [![Total Downloads](https://poser.pugx.org/staudenmeir/eloquent-json-relations/downloads)](https://packagist.org/packages/staudenmeir/eloquent-json-relations/stats) [![License](https://poser.pugx.org/staudenmeir/eloquent-json-relations/license)](https://github.com/staudenmeir/eloquent-json-relations/blob/main/LICENSE) diff --git a/composer.json b/composer.json index 8dd88ba..a53169c 100644 --- a/composer.json +++ b/composer.json @@ -15,9 +15,8 @@ }, "require-dev": { "barryvdh/laravel-ide-helper": "^3.0", - "larastan/larastan": "^2.9", "orchestra/testbench": "^9.0", - "phpstan/phpstan": "^1.10", + "phpstan/phpstan": "^1.12", "phpunit/phpunit": "^11.0", "staudenmeir/eloquent-has-many-deep": "^1.20" }, diff --git a/phpstan.neon.dist b/phpstan.neon.dist index ff0027f..3e639cd 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,9 +1,4 @@ -includes: - - ./vendor/larastan/larastan/extension.neon parameters: - level: 2 + level: 9 paths: - src - ignoreErrors: - - "#Called 'first' on Laravel collection, but could have been retrieved as a query.#" - - '#Call to private method take\(\) of parent class Illuminate\\Database\\Eloquent\\Relations\\BelongsTo#' diff --git a/src/Casts/Uuid.php b/src/Casts/Uuid.php index 0c834a0..f04e41a 100644 --- a/src/Casts/Uuid.php +++ b/src/Casts/Uuid.php @@ -4,6 +4,12 @@ use Illuminate\Contracts\Database\Eloquent\CastsAttributes; +/** + * @template TGet + * @template TSet + * + * @implements \Illuminate\Contracts\Database\Eloquent\CastsAttributes + */ class Uuid implements CastsAttributes { /** @@ -12,8 +18,8 @@ class Uuid implements CastsAttributes * @param \Illuminate\Database\Eloquent\Model $model * @param string $key * @param mixed $value - * @param array $attributes - * @return mixed + * @param array $attributes + * @return TGet|null */ public function get($model, $key, $value, $attributes) { @@ -25,8 +31,8 @@ public function get($model, $key, $value, $attributes) * * @param \Illuminate\Database\Eloquent\Model $model * @param string $key - * @param mixed $value - * @param array $attributes + * @param TSet|null $value + * @param array $attributes * @return mixed */ public function set($model, $key, $value, $attributes) diff --git a/src/Grammars/JsonGrammar.php b/src/Grammars/JsonGrammar.php index 4b32a86..b88b8df 100644 --- a/src/Grammars/JsonGrammar.php +++ b/src/Grammars/JsonGrammar.php @@ -9,7 +9,7 @@ interface JsonGrammar /** * Compile a "JSON array" statement into SQL. * - * @param string $column + * @param string|\Illuminate\Database\Query\Expression $column * @return string */ public function compileJsonArray($column); @@ -53,7 +53,7 @@ public function compileMemberOf(string $column, ?string $objectKey, mixed $value * Prepare the bindings for a "member of" statement. * * @param mixed $value - * @return array + * @return list */ public function prepareBindingsForMemberOf(mixed $value): array; diff --git a/src/Grammars/PostgresGrammar.php b/src/Grammars/PostgresGrammar.php index e7c3c5d..5bfb0c1 100644 --- a/src/Grammars/PostgresGrammar.php +++ b/src/Grammars/PostgresGrammar.php @@ -12,7 +12,7 @@ class PostgresGrammar extends Base implements JsonGrammar /** * Compile a "JSON array" statement into SQL. * - * @param string $column + * @param string|\Illuminate\Database\Query\Expression $column * @return string */ public function compileJsonArray($column) @@ -75,7 +75,7 @@ public function compileMemberOf(string $column, ?string $objectKey, mixed $value * Prepare the bindings for a "member of" statement. * * @param mixed $value - * @return array + * @return list */ public function prepareBindingsForMemberOf(mixed $value): array { diff --git a/src/Grammars/SQLiteGrammar.php b/src/Grammars/SQLiteGrammar.php index bec543d..d50fa7c 100644 --- a/src/Grammars/SQLiteGrammar.php +++ b/src/Grammars/SQLiteGrammar.php @@ -11,7 +11,7 @@ class SQLiteGrammar extends Base implements JsonGrammar /** * Compile a "JSON array" statement into SQL. * - * @param string $column + * @param string|\Illuminate\Database\Query\Expression $column * @return string */ public function compileJsonArray($column) @@ -70,7 +70,7 @@ public function compileMemberOf(string $column, ?string $objectKey, mixed $value * Prepare the bindings for a "member of" statement. * * @param mixed $value - * @return array + * @return list */ public function prepareBindingsForMemberOf(mixed $value): array { diff --git a/src/Grammars/SqlServerGrammar.php b/src/Grammars/SqlServerGrammar.php index 51ac5dc..b2df6a5 100644 --- a/src/Grammars/SqlServerGrammar.php +++ b/src/Grammars/SqlServerGrammar.php @@ -11,7 +11,7 @@ class SqlServerGrammar extends Base implements JsonGrammar /** * Compile a "JSON array" statement into SQL. * - * @param string $column + * @param string|\Illuminate\Database\Query\Expression $column * @return string */ public function compileJsonArray($column) @@ -72,7 +72,7 @@ public function compileMemberOf(string $column, ?string $objectKey, mixed $value * Prepare the bindings for a "member of" statement. * * @param mixed $value - * @return array + * @return list */ public function prepareBindingsForMemberOf(mixed $value): array { diff --git a/src/Grammars/Traits/CompilesMySqlJsonQueries.php b/src/Grammars/Traits/CompilesMySqlJsonQueries.php index 2abbf53..7a36db7 100644 --- a/src/Grammars/Traits/CompilesMySqlJsonQueries.php +++ b/src/Grammars/Traits/CompilesMySqlJsonQueries.php @@ -10,7 +10,7 @@ trait CompilesMySqlJsonQueries /** * Compile a "JSON array" statement into SQL. * - * @param string $column + * @param string|\Illuminate\Database\Query\Expression $column * @return string */ public function compileJsonArray($column) @@ -90,7 +90,7 @@ public function compileMemberOf(string $column, ?string $objectKey, mixed $value * Prepare the bindings for a "member of" statement. * * @param mixed $value - * @return array + * @return list */ public function prepareBindingsForMemberOf(mixed $value): array { diff --git a/src/IdeHelper/JsonRelationsHook.php b/src/IdeHelper/JsonRelationsHook.php index a120fd9..5c7f920 100644 --- a/src/IdeHelper/JsonRelationsHook.php +++ b/src/IdeHelper/JsonRelationsHook.php @@ -45,6 +45,9 @@ public function run(ModelsCommand $command, Model $model): void } } + /** + * @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *> $relationship + */ protected function addRelationship(ModelsCommand $command, ReflectionMethod $method, Relation $relationship): void { $type = '\\' . Collection::class . '|\\' . $relationship->getRelated()::class . '[]'; diff --git a/src/IdeHelperServiceProvider.php b/src/IdeHelperServiceProvider.php index 67b5b10..7c02eb8 100644 --- a/src/IdeHelperServiceProvider.php +++ b/src/IdeHelperServiceProvider.php @@ -11,18 +11,21 @@ class IdeHelperServiceProvider extends ServiceProvider implements DeferrableProv { public function register(): void { - /** @var \Illuminate\Contracts\Config\Repository $config */ + /** @var \Illuminate\Config\Repository $config */ $config = $this->app->get('config'); $config->set( 'ide-helper.model_hooks', array_merge( [JsonRelationsHook::class], - $config->get('ide-helper.model_hooks', []) + $config->array('ide-helper.model_hooks', []) ) ); } + /** + * @return list> + */ public function provides(): array { return [ diff --git a/src/Relations/BelongsToJson.php b/src/Relations/BelongsToJson.php index 7feb6a3..fafede6 100644 --- a/src/Relations/BelongsToJson.php +++ b/src/Relations/BelongsToJson.php @@ -13,34 +13,30 @@ use Staudenmeir\EloquentJsonRelations\Relations\Traits\Concatenation\IsConcatenableBelongsToJsonRelation; use Staudenmeir\EloquentJsonRelations\Relations\Traits\IsJsonRelation; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @extends \Illuminate\Database\Eloquent\Relations\BelongsTo + */ class BelongsToJson extends BelongsTo implements ConcatenableRelation { + /** @use \Staudenmeir\EloquentJsonRelations\Relations\InteractsWithPivotRecords */ use InteractsWithPivotRecords; + /** @use \Staudenmeir\EloquentJsonRelations\Relations\Traits\Concatenation\IsConcatenableBelongsToJsonRelation */ use IsConcatenableBelongsToJsonRelation; + /** @use \Staudenmeir\EloquentJsonRelations\Relations\Traits\IsJsonRelation */ use IsJsonRelation; + /** @use \Staudenmeir\EloquentJsonRelations\Relations\Traits\CompositeKeys\SupportsBelongsToJsonCompositeKeys */ use SupportsBelongsToJsonCompositeKeys; - /** - * The foreign key of the parent model. - * - * @var string|array - */ - protected $foreignKey; - - /** - * The associated key on the parent model. - * - * @var string|array - */ - protected $ownerKey; - /** * Create a new belongs to JSON relationship instance. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Model $child - * @param string $foreignKey - * @param string $ownerKey + * @param \Illuminate\Database\Eloquent\Builder $query + * @param TDeclaringModel $child + * @param list|string $foreignKey + * @param list|string $ownerKey * @param string $relationName * @return void */ @@ -53,6 +49,7 @@ public function __construct(Builder $query, Model $child, $foreignKey, $ownerKey $this->path = $segments[0]; $this->key = $segments[1] ?? null; + // @phpstan-ignore-next-line parent::__construct($query, $child, $foreignKey, $ownerKey, $relationName); } @@ -71,8 +68,8 @@ public function getResults() /** * Execute the query as a "select" statement. * - * @param array $columns - * @return \Illuminate\Database\Eloquent\Collection + * @param list $columns + * @return \Illuminate\Database\Eloquent\Collection */ public function get($columns = ['*']) { @@ -109,12 +106,7 @@ public function addConstraints() } } - /** - * Set the constraints for an eager load of the relation. - * - * @param array $models - * @return void - */ + /** @inheritDoc */ public function addEagerConstraints(array $models) { if ($this->hasCompositeKey()) { @@ -129,8 +121,8 @@ public function addEagerConstraints(array $models) /** * Gather the keys from an array of related models. * - * @param array $models - * @return array + * @param array $models + * @return list */ protected function getEagerModelKeys(array $models) { @@ -145,14 +137,7 @@ protected function getEagerModelKeys(array $models) return array_values(array_unique($keys)); } - /** - * Match the eagerly loaded results to their parents. - * - * @param array $models - * @param \Illuminate\Database\Eloquent\Collection $results - * @param string $relation - * @return array - */ + /** @inheritDoc */ public function match(array $models, Collection $results, $relation) { if ($this->hasCompositeKey()) { @@ -177,8 +162,11 @@ public function match(array $models, Collection $results, $relation) foreach ($models as $model) { if ($this->key) { + /** @var \Illuminate\Database\Eloquent\Collection $relatedModels */ + $relatedModels = $model->getRelation($relation); + $this->hydratePivotRelation( - $model->getRelation($relation), + $relatedModels, $model, fn (Model $model, Model $parent) => $parent->{$this->path} ); @@ -191,8 +179,8 @@ public function match(array $models, Collection $results, $relation) /** * Build model dictionary keyed by the relation's foreign key. * - * @param \Illuminate\Database\Eloquent\Collection $results - * @return array + * @param \Illuminate\Database\Eloquent\Collection $results + * @return array */ protected function buildDictionary(Collection $results) { @@ -205,14 +193,7 @@ protected function buildDictionary(Collection $results) return $dictionary; } - /** - * Add the constraints for a relationship query. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery - * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder - */ + /** @inheritDoc */ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { if ($parentQuery->getQuery()->from == $query->getQuery()->from) { @@ -235,17 +216,12 @@ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $this->getRelationExistenceQueryWithCompositeKey($query); } - return $query->select($columns); + $query->select($columns); + + return $query; } - /** - * Add the constraints for a relationship query on the same table. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery - * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder - */ + /** @inheritDoc */ public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*']) { $query->from($query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash()); @@ -262,15 +238,17 @@ public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $query->getQuery()->connection->raw($sql) ); - return $query->select($columns); + $query->select($columns); + + return $query; } /** * Get the owner key for the relationship query. * - * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $query * @param string $ownerKey - * @return array + * @return array{0: string, 1: list} */ protected function relationExistenceQueryOwnerKey(Builder $query, string $ownerKey): array { @@ -303,17 +281,21 @@ protected function relationExistenceQueryOwnerKey(Builder $query, string $ownerK /** * Get the pivot attributes from a model. * - * @param \Illuminate\Database\Eloquent\Model $model - * @param \Illuminate\Database\Eloquent\Model $parent - * @param array $records - * @return array + * @param TRelatedModel $model + * @param TDeclaringModel $parent + * @param list> $records + * @return array */ public function pivotAttributes(Model $model, Model $parent, array $records) { - $key = str_replace('->', '.', $this->key); + /** @var string $key */ + $key = $this->key; + + $key = str_replace('->', '.', $key); $ownerKey = $this->hasCompositeKey() ? $this->ownerKey[0] : $this->ownerKey; + /** @var array $record */ $record = (new BaseCollection($records)) ->filter(function ($value) use ($key, $model, $ownerKey) { return Arr::get($value, $key) == $model->$ownerKey; @@ -326,7 +308,7 @@ public function pivotAttributes(Model $model, Model $parent, array $records) * Get the foreign key values. * * @param \Illuminate\Database\Eloquent\Model|null $model - * @return array + * @return list */ public function getForeignKeys(?Model $model = null) { diff --git a/src/Relations/HasManyJson.php b/src/Relations/HasManyJson.php index 1b1a3d8..6990308 100644 --- a/src/Relations/HasManyJson.php +++ b/src/Relations/HasManyJson.php @@ -13,33 +13,28 @@ use Staudenmeir\EloquentJsonRelations\Relations\Traits\CompositeKeys\SupportsHasManyJsonCompositeKeys; use Staudenmeir\EloquentJsonRelations\Relations\Traits\IsJsonRelation; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @extends \Illuminate\Database\Eloquent\Relations\HasMany + */ class HasManyJson extends HasMany implements ConcatenableRelation { + /** @use \Staudenmeir\EloquentJsonRelations\Relations\Traits\Concatenation\IsConcatenableHasManyJsonRelation */ use IsConcatenableHasManyJsonRelation; + /** @use \Staudenmeir\EloquentJsonRelations\Relations\Traits\IsJsonRelation */ use IsJsonRelation; + /** @use \Staudenmeir\EloquentJsonRelations\Relations\Traits\CompositeKeys\SupportsHasManyJsonCompositeKeys */ use SupportsHasManyJsonCompositeKeys; - /** - * The foreign key of the parent model. - * - * @var string|array - */ - protected $foreignKey; - - /** - * The local key of the parent model. - * - * @var string|array - */ - protected $localKey; - /** * Create a new has many JSON relationship instance. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Model $parent - * @param string $foreignKey - * @param string $localKey + * @param \Illuminate\Database\Eloquent\Builder $query + * @param TDeclaringModel $parent + * @param list|string $foreignKey + * @param list|string $localKey * @return void */ public function __construct(Builder $query, Model $parent, $foreignKey, $localKey) @@ -51,6 +46,7 @@ public function __construct(Builder $query, Model $parent, $foreignKey, $localKe $this->path = $segments[0]; $this->key = $segments[1] ?? null; + // @phpstan-ignore-next-line parent::__construct($query, $parent, $foreignKey, $localKey); } @@ -69,8 +65,8 @@ public function getResults() /** * Execute the query as a "select" statement. * - * @param array $columns - * @return \Illuminate\Database\Eloquent\Collection + * @param list $columns + * @return \Illuminate\Database\Eloquent\Collection */ public function get($columns = ['*']) { @@ -112,12 +108,7 @@ public function addConstraints() } } - /** - * Set the constraints for an eager load of the relation. - * - * @param array $models - * @return void - */ + /** @inheritDoc */ public function addEagerConstraints(array $models) { if ($this->hasCompositeKey()) { @@ -145,11 +136,14 @@ public function addEagerConstraints(array $models) * Embed a parent key in a nested array. * * @param mixed $parentKey - * @return array + * @return array> */ protected function parentKeyToArray($parentKey) { - $keys = explode('->', $this->key); + /** @var string $key */ + $key = $this->key; + + $keys = explode('->', $key); foreach (array_reverse($keys) as $key) { $parentKey = [$key => $parentKey]; @@ -158,15 +152,7 @@ protected function parentKeyToArray($parentKey) return [$parentKey]; } - /** - * Match the eagerly loaded results to their many parents. - * - * @param array $models - * @param \Illuminate\Database\Eloquent\Collection $results - * @param string $relation - * @param string $type - * @return array - */ + /** @inheritDoc */ protected function matchOneOrMany(array $models, Collection $results, $relation, $type) { if ($this->hasCompositeKey()) { @@ -188,12 +174,7 @@ protected function matchOneOrMany(array $models, Collection $results, $relation, return $models; } - /** - * Build model dictionary keyed by the relation's foreign key. - * - * @param \Illuminate\Database\Eloquent\Collection $results - * @return array - */ + /** @inheritDoc */ protected function buildDictionary(Collection $results) { $foreign = $this->getForeignKeyName(); @@ -220,21 +201,14 @@ protected function setForeignAttributesForCreate(Model $model) $foreignKey = explode('.', $this->foreignKey)[1]; if (method_exists($model, 'belongsToJson')) { - /** @var \Staudenmeir\EloquentJsonRelations\Relations\BelongsToJson $relation */ + /** @var \Staudenmeir\EloquentJsonRelations\Relations\BelongsToJson<*, *> $relation */ $relation = $model->belongsToJson(get_class($this->parent), $foreignKey, $this->localKey); $relation->attach($this->getParentKey()); } } - /** - * Add the constraints for a relationship query. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery - * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder - */ + /** @inheritDoc */ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { if ($query->getQuery()->from == $parentQuery->getQuery()->from) { @@ -255,17 +229,13 @@ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $this->getRelationExistenceQueryWithCompositeKey($query); } - return $query->select($columns); + + $query->select($columns); + + return $query; } - /** - * Add the constraints for a relationship query on the same table. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery - * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder - */ + /** @inheritDoc */ public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*']) { $query->from($query->getModel()->getTable() . ' as ' . $hash = $this->getRelationCountHash()); @@ -282,14 +252,17 @@ public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $query->getQuery()->connection->raw($sql) ); - return $query->select($columns); + + $query->select($columns); + + return $query; } /** * Get the parent key for the relationship query. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @return array + * @param \Illuminate\Database\Eloquent\Builder $query + * @return array{0: string, 1: list} */ protected function relationExistenceQueryParentKey(Builder $query): array { @@ -322,17 +295,21 @@ protected function relationExistenceQueryParentKey(Builder $query): array /** * Get the pivot attributes from a model. * - * @param \Illuminate\Database\Eloquent\Model $model - * @param \Illuminate\Database\Eloquent\Model $parent - * @param array $records - * @return array + * @param TRelatedModel $model + * @param TDeclaringModel $parent + * @param list> $records + * @return array */ public function pivotAttributes(Model $model, Model $parent, array $records) { - $key = str_replace('->', '.', $this->key); + /** @var string $key */ + $key = $this->key; + + $key = str_replace('->', '.', $key); $localKey = $this->hasCompositeKey() ? $this->localKey[0] : $this->localKey; + /** @var array $record */ $record = (new BaseCollection($records)) ->filter(function ($value) use ($key, $localKey, $parent) { return Arr::get($value, $key) == $parent->$localKey; @@ -348,7 +325,12 @@ public function pivotAttributes(Model $model, Model $parent, array $records) */ public function getPathName() { - return last(explode('.', $this->path)); + /** @var string $pathName */ + $pathName = last( + explode('.', $this->path) + ); + + return $pathName; } /** diff --git a/src/Relations/HasOneJson.php b/src/Relations/HasOneJson.php index a814ac5..b068883 100644 --- a/src/Relations/HasOneJson.php +++ b/src/Relations/HasOneJson.php @@ -7,6 +7,12 @@ use Illuminate\Database\Eloquent\Relations\Concerns\SupportsDefaultModels; use Illuminate\Database\Eloquent\Relations\HasOneOrMany; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @extends \Staudenmeir\EloquentJsonRelations\Relations\HasManyJson + */ class HasOneJson extends HasManyJson { use SupportsDefaultModels; @@ -48,12 +54,15 @@ public function matchOne(array $models, Collection $results, $relation) if ($this->key) { foreach ($models as $model) { + /** @var \Illuminate\Database\Eloquent\Collection $relatedModel */ + $relatedModel = new Collection( + array_filter([$model->$relation]) + ); + $model->setRelation( $relation, $this->hydratePivotRelation( - new Collection( - array_filter([$model->$relation]) - ), + $relatedModel, $model, fn (Model $model) => $model->{$this->getPathName()} )->first() @@ -64,7 +73,12 @@ public function matchOne(array $models, Collection $results, $relation) return $models; } - /** @inheritDoc */ + /** + * Make a new related instance for the given model. + * + * @param TDeclaringModel $parent + * @return TRelatedModel + */ public function newRelatedInstanceFor(Model $parent) { return $this->related->newInstance(); diff --git a/src/Relations/InteractsWithPivotRecords.php b/src/Relations/InteractsWithPivotRecords.php index 5309ead..f1b58cc 100644 --- a/src/Relations/InteractsWithPivotRecords.php +++ b/src/Relations/InteractsWithPivotRecords.php @@ -7,19 +7,25 @@ use Illuminate\Support\Arr; use Illuminate\Support\Collection as BaseCollection; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + */ trait InteractsWithPivotRecords { /** * Attach models to the relationship. * - * @param mixed $ids - * @return \Illuminate\Database\Eloquent\Model + * @param int|array>|string|\Illuminate\Database\Eloquent\Collection|TRelatedModel|\Illuminate\Support\Collection $ids + * @return TDeclaringModel */ public function attach($ids) { [$records, $others] = $this->decodeRecords(); - $records = $this->formatIds($this->parseIds($ids)) + $records; + $records = $this->formatIds( + $this->parseIds($ids) + ) + $records; $this->child->{$this->path} = $this->encodeRecords($records, $others); @@ -29,17 +35,20 @@ public function attach($ids) /** * Detach models from the relationship. * - * @param mixed $ids - * @return \Illuminate\Database\Eloquent\Model + * @param int|string|\Illuminate\Database\Eloquent\Collection|TRelatedModel|\Illuminate\Support\Collection $ids + * @return TDeclaringModel */ public function detach($ids = null) { [$records, $others] = $this->decodeRecords(); if (!is_null($ids)) { + /** @var list $parsedIds */ + $parsedIds = $this->parseIds($ids); + $records = array_diff_key( $records, - array_flip($this->parseIds($ids)) + array_flip($parsedIds) ); } else { $records = []; @@ -53,14 +62,16 @@ public function detach($ids = null) /** * Sync the relationship with a list of models. * - * @param mixed $ids - * @return \Illuminate\Database\Eloquent\Model + * @param int|array>|string|\Illuminate\Database\Eloquent\Collection|TRelatedModel|\Illuminate\Support\Collection $ids + * @return TDeclaringModel */ public function sync($ids) { [, $others] = $this->decodeRecords(); - $records = $this->formatIds($this->parseIds($ids)); + $records = $this->formatIds( + $this->parseIds($ids) + ); $this->child->{$this->path} = $this->encodeRecords($records, $others); @@ -70,14 +81,16 @@ public function sync($ids) /** * Toggle models from the relationship. * - * @param mixed $ids - * @return \Illuminate\Database\Eloquent\Model + * @param int|array>|string|\Illuminate\Database\Eloquent\Collection|TRelatedModel|\Illuminate\Support\Collection $ids + * @return TDeclaringModel */ public function toggle($ids) { [$records, $others] = $this->decodeRecords(); - $ids = $this->formatIds($this->parseIds($ids)); + $ids = $this->formatIds( + $this->parseIds($ids) + ); $records = array_diff_key( $ids + $records, @@ -92,7 +105,7 @@ public function toggle($ids) /** * Decode the records on the child model. * - * @return array + * @return array{0: array>, 1: list>} */ protected function decodeRecords() { @@ -123,9 +136,9 @@ protected function decodeRecords() /** * Encode the records for the child model. * - * @param array $records - * @param array $others - * @return array + * @param array> $records + * @param list> $others + * @return list>|list */ protected function encodeRecords(array $records, array $others) { @@ -148,8 +161,8 @@ protected function encodeRecords(array $records, array $others) /** * Get all of the IDs from the given mixed value. * - * @param mixed $value - * @return array + * @param int|array>|string|\Illuminate\Database\Eloquent\Collection|TRelatedModel|\Illuminate\Support\Collection $value + * @return array>|list */ protected function parseIds($value) { @@ -158,11 +171,17 @@ protected function parseIds($value) } if ($value instanceof Collection) { - return $value->pluck($this->ownerKey)->all(); + /** @var \Illuminate\Support\Collection $ids */ + $ids = $value->pluck($this->ownerKey); + + return $ids->all(); } if ($value instanceof BaseCollection) { - return $value->toArray(); + /** @var list $ids */ + $ids = $value->toArray(); + + return $ids; } return (array) $value; @@ -171,8 +190,8 @@ protected function parseIds($value) /** * Format the parsed IDs. * - * @param array $ids - * @return array + * @param array>|list $ids + * @return array> */ protected function formatIds(array $ids) { diff --git a/src/Relations/Postgres/BelongsTo.php b/src/Relations/Postgres/BelongsTo.php index 57f201e..486451a 100644 --- a/src/Relations/Postgres/BelongsTo.php +++ b/src/Relations/Postgres/BelongsTo.php @@ -5,17 +5,23 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\BelongsTo as Base; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @extends \Illuminate\Database\Eloquent\Relations\BelongsTo + */ class BelongsTo extends Base { use IsPostgresRelation; /** - * Add the constraints for a relationship query. + * Add the constraints for an internal relationship existence query. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery - * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param list|string $columns + * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { @@ -25,20 +31,22 @@ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $first = $this->jsonColumn($query, $this->related, $this->getQualifiedForeignKeyName(), $this->ownerKey); - return $query->select($columns)->whereColumn( + $query->select($columns)->whereColumn( $first, '=', $query->qualifyColumn($this->ownerKey) ); + + return $query; } /** * Add the constraints for a relationship query on the same table. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery - * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param list|string $columns + * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*']) { @@ -50,9 +58,11 @@ public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $first = $this->jsonColumn($query, $this->related, $this->getQualifiedForeignKeyName(), $this->ownerKey); - return $query->whereColumn( + $query->whereColumn( $first, $hash.'.'.$this->ownerKey ); + + return $query; } } diff --git a/src/Relations/Postgres/HasMany.php b/src/Relations/Postgres/HasMany.php index e493209..ede5f1c 100644 --- a/src/Relations/Postgres/HasMany.php +++ b/src/Relations/Postgres/HasMany.php @@ -4,7 +4,14 @@ use Illuminate\Database\Eloquent\Relations\HasMany as Base; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @extends \Illuminate\Database\Eloquent\Relations\HasMany + */ class HasMany extends Base { + /** @use \Staudenmeir\EloquentJsonRelations\Relations\Postgres\HasOneOrMany */ use HasOneOrMany; } diff --git a/src/Relations/Postgres/HasManyThrough.php b/src/Relations/Postgres/HasManyThrough.php index 3b5425f..f6a7722 100644 --- a/src/Relations/Postgres/HasManyThrough.php +++ b/src/Relations/Postgres/HasManyThrough.php @@ -4,7 +4,15 @@ use Illuminate\Database\Eloquent\Relations\HasManyThrough as Base; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TIntermediateModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @extends \Illuminate\Database\Eloquent\Relations\HasManyThrough + */ class HasManyThrough extends Base { + /** @use \Staudenmeir\EloquentJsonRelations\Relations\Postgres\HasOneOrManyThrough */ use HasOneOrManyThrough; } diff --git a/src/Relations/Postgres/HasOne.php b/src/Relations/Postgres/HasOne.php index c785ab9..e676d0e 100644 --- a/src/Relations/Postgres/HasOne.php +++ b/src/Relations/Postgres/HasOne.php @@ -4,7 +4,14 @@ use Illuminate\Database\Eloquent\Relations\HasOne as Base; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @extends \Illuminate\Database\Eloquent\Relations\HasOne + */ class HasOne extends Base { + /** @use \Staudenmeir\EloquentJsonRelations\Relations\Postgres\HasOneOrMany */ use HasOneOrMany; } diff --git a/src/Relations/Postgres/HasOneOrMany.php b/src/Relations/Postgres/HasOneOrMany.php index e68690a..e984c12 100644 --- a/src/Relations/Postgres/HasOneOrMany.php +++ b/src/Relations/Postgres/HasOneOrMany.php @@ -4,17 +4,21 @@ use Illuminate\Database\Eloquent\Builder; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + */ trait HasOneOrMany { use IsPostgresRelation; /** - * Add the constraints for a relationship query. + * Add the constraints for an internal relationship existence query. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery - * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param list|string $columns + * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { @@ -24,20 +28,22 @@ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $second = $this->jsonColumn($query, $this->parent, $this->getExistenceCompareKey(), $this->localKey); - return $query->select($columns)->whereColumn( + $query->select($columns)->whereColumn( $this->getQualifiedParentKeyName(), '=', - $second + $second // @phpstan-ignore-line ); + + return $query; } /** * Add the constraints for a relationship query on the same table. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery - * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param list|string $columns + * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*']) { @@ -47,10 +53,12 @@ public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $second = $this->jsonColumn($query, $this->parent, $hash.'.'.$this->getForeignKeyName(), $this->localKey); - return $query->select($columns)->whereColumn( + $query->select($columns)->whereColumn( $this->getQualifiedParentKeyName(), '=', - $second + $second // @phpstan-ignore-line ); + + return $query; } } diff --git a/src/Relations/Postgres/HasOneOrManyThrough.php b/src/Relations/Postgres/HasOneOrManyThrough.php index c2a24f1..7c0c9c8 100644 --- a/src/Relations/Postgres/HasOneOrManyThrough.php +++ b/src/Relations/Postgres/HasOneOrManyThrough.php @@ -4,6 +4,11 @@ use Illuminate\Database\Eloquent\Builder; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TIntermediateModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + */ trait HasOneOrManyThrough { use IsPostgresRelation; @@ -11,7 +16,7 @@ trait HasOneOrManyThrough /** * Set the join clause on the query. * - * @param \Illuminate\Database\Eloquent\Builder|null $query + * @param \Illuminate\Database\Eloquent\Builder|null $query * @return void */ protected function performJoin(?Builder $query = null) @@ -29,12 +34,12 @@ protected function performJoin(?Builder $query = null) } /** - * Add the constraints for a relationship query. + * Add the constraints for an internal relationship existence query. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery - * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param list|string $columns + * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { @@ -50,20 +55,22 @@ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $firstKey = $this->jsonColumn($query, $this->farParent, $this->getQualifiedFirstKeyName(), $this->localKey); - return $query->select($columns)->whereColumn( + $query->select($columns)->whereColumn( $this->getQualifiedLocalKeyName(), '=', - $firstKey + $firstKey // @phpstan-ignore-line ); + + return $query; } /** * Add the constraints for a relationship query on the same table. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery - * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param list|string $columns + * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*']) { @@ -75,22 +82,27 @@ public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $query->getModel()->setTable($hash); + /** @var string $parentFrom */ + $parentFrom = $parentQuery->getQuery()->from; + $firstKey = $this->jsonColumn($query, $this->farParent, $this->getQualifiedFirstKeyName(), $this->localKey); - return $query->select($columns)->whereColumn( - $parentQuery->getQuery()->from.'.'.$this->localKey, + $query->select($columns)->whereColumn( + "$parentFrom.$this->localKey", '=', - $firstKey + $firstKey // @phpstan-ignore-line ); + + return $query; } /** * Add the constraints for a relationship query on the same table as the through parent. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery - * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param list|string $columns + * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationExistenceQueryForThroughSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*']) { @@ -105,12 +117,17 @@ public function getRelationExistenceQueryForThroughSelfRelation(Builder $query, $query->whereNull($hash.'.'.$this->throughParent->getDeletedAtColumn()); } + /** @var string $parentFrom */ + $parentFrom = $parentQuery->getQuery()->from; + $firstKey = $this->jsonColumn($query, $this->farParent, $hash.'.'.$this->firstKey, $this->localKey); - return $query->select($columns)->whereColumn( - $parentQuery->getQuery()->from.'.'.$this->localKey, + $query->select($columns)->whereColumn( + "$parentFrom.$this->localKey", '=', - $firstKey + $firstKey // @phpstan-ignore-line ); + + return $query; } } diff --git a/src/Relations/Postgres/HasOneThrough.php b/src/Relations/Postgres/HasOneThrough.php index 863e5c0..b863266 100644 --- a/src/Relations/Postgres/HasOneThrough.php +++ b/src/Relations/Postgres/HasOneThrough.php @@ -4,7 +4,15 @@ use Illuminate\Database\Eloquent\Relations\HasOneThrough as Base; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TIntermediateModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @extends \Illuminate\Database\Eloquent\Relations\HasOneThrough + */ class HasOneThrough extends Base { + /** @use \Staudenmeir\EloquentJsonRelations\Relations\Postgres\HasOneOrManyThrough */ use HasOneOrManyThrough; } diff --git a/src/Relations/Postgres/IsPostgresRelation.php b/src/Relations/Postgres/IsPostgresRelation.php index 594e981..c886869 100644 --- a/src/Relations/Postgres/IsPostgresRelation.php +++ b/src/Relations/Postgres/IsPostgresRelation.php @@ -12,7 +12,7 @@ trait IsPostgresRelation /** * Get the wrapped and cast JSON column. * - * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder<*> $query * @param \Illuminate\Database\Eloquent\Model $model * @param string $column * @param string $key diff --git a/src/Relations/Postgres/MorphMany.php b/src/Relations/Postgres/MorphMany.php index 9e94dda..57a74f5 100644 --- a/src/Relations/Postgres/MorphMany.php +++ b/src/Relations/Postgres/MorphMany.php @@ -4,7 +4,14 @@ use Illuminate\Database\Eloquent\Relations\MorphMany as Base; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @extends \Illuminate\Database\Eloquent\Relations\MorphMany + */ class MorphMany extends Base { + /** @use \Staudenmeir\EloquentJsonRelations\Relations\Postgres\MorphOneOrMany */ use MorphOneOrMany; } diff --git a/src/Relations/Postgres/MorphOne.php b/src/Relations/Postgres/MorphOne.php index e506b7c..1774733 100644 --- a/src/Relations/Postgres/MorphOne.php +++ b/src/Relations/Postgres/MorphOne.php @@ -4,7 +4,14 @@ use Illuminate\Database\Eloquent\Relations\MorphOne as Base; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + * + * @extends \Illuminate\Database\Eloquent\Relations\MorphOne + */ class MorphOne extends Base { + /** @use \Staudenmeir\EloquentJsonRelations\Relations\Postgres\MorphOneOrMany */ use MorphOneOrMany; } diff --git a/src/Relations/Postgres/MorphOneOrMany.php b/src/Relations/Postgres/MorphOneOrMany.php index 8865ede..703041d 100644 --- a/src/Relations/Postgres/MorphOneOrMany.php +++ b/src/Relations/Postgres/MorphOneOrMany.php @@ -4,19 +4,24 @@ use Illuminate\Database\Eloquent\Builder; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + */ trait MorphOneOrMany { + /** @use \Staudenmeir\EloquentJsonRelations\Relations\Postgres\HasOneOrMany */ use HasOneOrMany { getRelationExistenceQuery as getRelationExistenceQueryParent; } /** - * Add the constraints for a relationship query. + * Add the constraints for an internal relationship existence query. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery - * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param list|string $columns + * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { diff --git a/src/Relations/Traits/CompositeKeys/SupportsBelongsToJsonCompositeKeys.php b/src/Relations/Traits/CompositeKeys/SupportsBelongsToJsonCompositeKeys.php index 0da579f..d25814e 100644 --- a/src/Relations/Traits/CompositeKeys/SupportsBelongsToJsonCompositeKeys.php +++ b/src/Relations/Traits/CompositeKeys/SupportsBelongsToJsonCompositeKeys.php @@ -7,6 +7,10 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection as BaseCollection; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + */ trait SupportsBelongsToJsonCompositeKeys { /** @@ -16,7 +20,10 @@ trait SupportsBelongsToJsonCompositeKeys */ protected function hasCompositeKey(): bool { - return is_array($this->foreignKey); + /** @var list|string $foreignKey */ + $foreignKey = $this->foreignKey; + + return is_array($foreignKey); } /** @@ -26,7 +33,10 @@ protected function hasCompositeKey(): bool */ protected function addConstraintsWithCompositeKey(): void { - $columns = array_slice($this->ownerKey, 1); + /** @var list $ownerKey */ + $ownerKey = $this->ownerKey; + + $columns = array_slice($ownerKey, 1); foreach ($columns as $column) { $this->query->where( @@ -40,26 +50,32 @@ protected function addConstraintsWithCompositeKey(): void /** * Set the constraints for an eager load of the relation for a composite key. * - * @param array $models + * @param list $models * @return void */ protected function addEagerConstraintsWithCompositeKey(array $models): void { + /** @var list $foreignKey */ + $foreignKey = $this->foreignKey; + + /** @var list $ownerKey */ + $ownerKey = $this->ownerKey; + $keys = (new BaseCollection($models))->map( - function (Model $model) { + function (Model $model) use ($foreignKey) { return array_map( fn (string $column) => $model[$column], - $this->foreignKey + $foreignKey ); } )->values()->unique(null, true)->all(); $this->query->where( - function (Builder $query) use ($keys) { + function (Builder $query) use ($keys, $ownerKey) { foreach ($keys as $key) { $query->orWhere( - function (Builder $query) use ($key) { - foreach ($this->ownerKey as $i => $column) { + function (Builder $query) use ($key, $ownerKey) { + foreach ($ownerKey as $i => $column) { if ($i === 0) { $query->whereIn( $this->related->qualifyColumn($column), @@ -83,13 +99,16 @@ function (Builder $query) use ($key) { /** * Match the eagerly loaded results to their parents for a composite key. * - * @param array $models - * @param \Illuminate\Database\Eloquent\Collection $results + * @param list $models + * @param \Illuminate\Database\Eloquent\Collection $results * @param string $relation - * @return array + * @return list */ protected function matchWithCompositeKey(array $models, Collection $results, string $relation): array { + /** @var list $ownerKey */ + $ownerKey = $this->ownerKey; + $dictionary = $this->buildDictionaryWithCompositeKey($results); foreach ($models as $model) { @@ -97,7 +116,7 @@ protected function matchWithCompositeKey(array $models, Collection $results, str $additionalValues = array_map( fn (string $key) => $model->$key, - array_slice($this->ownerKey, 1) + array_slice($ownerKey, 1) ); foreach ($this->getForeignKeys($model) as $id) { @@ -121,17 +140,20 @@ protected function matchWithCompositeKey(array $models, Collection $results, str /** * Build model dictionary keyed by the relation's composite foreign key. * - * @param \Illuminate\Database\Eloquent\Collection $results - * @return array + * @param \Illuminate\Database\Eloquent\Collection $results + * @return array> */ protected function buildDictionaryWithCompositeKey(Collection $results): array { + /** @var list $ownerKey */ + $ownerKey = $this->ownerKey; + $dictionary = []; foreach ($results as $result) { $values = array_map( fn (string $key) => $result->$key, - $this->ownerKey + $ownerKey ); $values = implode("\0", $values); @@ -145,12 +167,15 @@ protected function buildDictionaryWithCompositeKey(Collection $results): array /** * Add the constraints for a relationship query for a composite key. * - * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $query * @return void */ public function getRelationExistenceQueryWithCompositeKey(Builder $query): void { - $columns = array_slice($this->foreignKey, 1, preserve_keys: true); + /** @var list $foreignKey */ + $foreignKey = $this->foreignKey; + + $columns = array_slice($foreignKey, 1, preserve_keys: true); foreach ($columns as $i => $column) { $query->whereColumn( diff --git a/src/Relations/Traits/CompositeKeys/SupportsHasManyJsonCompositeKeys.php b/src/Relations/Traits/CompositeKeys/SupportsHasManyJsonCompositeKeys.php index 7082d88..83944ea 100644 --- a/src/Relations/Traits/CompositeKeys/SupportsHasManyJsonCompositeKeys.php +++ b/src/Relations/Traits/CompositeKeys/SupportsHasManyJsonCompositeKeys.php @@ -7,6 +7,10 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection as BaseCollection; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + */ trait SupportsHasManyJsonCompositeKeys { /** @@ -16,7 +20,10 @@ trait SupportsHasManyJsonCompositeKeys */ protected function hasCompositeKey(): bool { - return is_array($this->foreignKey); + /** @var list|string $foreignKey */ + $foreignKey = $this->foreignKey; + + return is_array($foreignKey); } /** @@ -26,7 +33,10 @@ protected function hasCompositeKey(): bool */ protected function addConstraintsWithCompositeKey(): void { - $columns = array_slice($this->localKey, 1); + /** @var list $localKey */ + $localKey = $this->localKey; + + $columns = array_slice($localKey, 1); foreach ($columns as $column) { $this->query->where( @@ -40,26 +50,32 @@ protected function addConstraintsWithCompositeKey(): void /** * Set the constraints for an eager load of the relation for a composite key. * - * @param array $models + * @param list $models * @return void */ protected function addEagerConstraintsWithCompositeKey(array $models): void { + /** @var list $foreignKey */ + $foreignKey = $this->foreignKey; + + /** @var list $localKey */ + $localKey = $this->localKey; + $keys = (new BaseCollection($models))->map( - function (Model $model) { + function (Model $model) use ($localKey) { return array_map( fn (string $column) => $model[$column], - $this->localKey + $localKey ); } )->values()->unique(null, true)->all(); $this->query->where( - function (Builder $query) use ($keys) { + function (Builder $query) use ($foreignKey, $keys) { foreach ($keys as $key) { $query->orWhere( - function (Builder $query) use ($key) { - foreach ($this->foreignKey as $i => $column) { + function (Builder $query) use ($foreignKey, $key) { + foreach ($foreignKey as $i => $column) { if ($i === 0) { $this->whereJsonContainsOrMemberOf( $query, @@ -85,20 +101,23 @@ function (Builder $query) use ($key) { /** * Match the eagerly loaded results to their parents for a composite key. * - * @param array $models - * @param \Illuminate\Database\Eloquent\Collection $results + * @param list $models + * @param \Illuminate\Database\Eloquent\Collection $results * @param string $relation * @param string $type - * @return array + * @return list */ protected function matchWithCompositeKey(array $models, Collection $results, string $relation, string $type): array { + /** @var list $localKey */ + $localKey = $this->localKey; + $dictionary = $this->buildDictionaryWithCompositeKey($results); foreach ($models as $model) { $values = array_map( fn ($key) => $model->$key, - $this->localKey + $localKey ); $key = implode("\0", $values); @@ -117,8 +136,8 @@ protected function matchWithCompositeKey(array $models, Collection $results, str /** * Build model dictionary keyed by the relation's composite foreign key. * - * @param \Illuminate\Database\Eloquent\Collection $results - * @return array + * @param \Illuminate\Database\Eloquent\Collection $results + * @return array> */ protected function buildDictionaryWithCompositeKey(Collection $results): array { @@ -149,7 +168,7 @@ protected function buildDictionaryWithCompositeKey(Collection $results): array /** * Add the constraints for a relationship query for a composite key. * - * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $query * @return void */ public function getRelationExistenceQueryWithCompositeKey(Builder $query): void @@ -168,13 +187,16 @@ public function getRelationExistenceQueryWithCompositeKey(Builder $query): void /** * Get the plain additional foreign keys. * - * @return array + * @return array */ protected function getAdditionalForeignKeyNames(): array { + /** @var list $foreignKey */ + $foreignKey = $this->foreignKey; + $names = []; - $columns = array_slice($this->foreignKey, 1, preserve_keys: true); + $columns = array_slice($foreignKey, 1, preserve_keys: true); foreach ($columns as $i => $column) { $segments = explode('.', $column); diff --git a/src/Relations/Traits/Concatenation/IsConcatenableBelongsToJsonRelation.php b/src/Relations/Traits/Concatenation/IsConcatenableBelongsToJsonRelation.php index 00d3d16..b2a03c3 100644 --- a/src/Relations/Traits/Concatenation/IsConcatenableBelongsToJsonRelation.php +++ b/src/Relations/Traits/Concatenation/IsConcatenableBelongsToJsonRelation.php @@ -6,18 +6,25 @@ use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Query\JoinClause; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + */ trait IsConcatenableBelongsToJsonRelation { + /** @use \Staudenmeir\EloquentJsonRelations\Relations\Traits\Concatenation\IsConcatenableRelation */ use IsConcatenableRelation; /** * Append the relation's through parents, foreign and local keys to a deep relationship. * - * @param \Illuminate\Database\Eloquent\Model[] $through - * @param array $foreignKeys - * @param array $localKeys + * @param non-empty-list $through + * @param non-empty-list $foreignKeys + * @param non-empty-list $localKeys * @param int $position - * @return array + * @return array{0: non-empty-list, + * 1: non-empty-list, + * 2: non-empty-list} */ public function appendToDeepRelationship(array $through, array $foreignKeys, array $localKeys, int $position): array { @@ -55,11 +62,11 @@ public function appendToDeepRelationship(array $through, array $foreignKeys, arr /** * Match the eagerly loaded results for a deep relationship to their parents. * - * @param array $models - * @param \Illuminate\Database\Eloquent\Collection $results + * @param list $models + * @param \Illuminate\Database\Eloquent\Collection $results * @param string $relation * @param string $type - * @return array + * @return list */ public function matchResultsForDeepRelationship( array $models, @@ -91,8 +98,8 @@ public function matchResultsForDeepRelationship( /** * Build the model dictionary for a deep relation. * - * @param \Illuminate\Database\Eloquent\Collection $results - * @return array + * @param \Illuminate\Database\Eloquent\Collection $results + * @return array> */ protected function buildDictionaryForDeepRelationship(Collection $results): array { diff --git a/src/Relations/Traits/Concatenation/IsConcatenableHasManyJsonRelation.php b/src/Relations/Traits/Concatenation/IsConcatenableHasManyJsonRelation.php index 1a91b47..3f4e1f4 100644 --- a/src/Relations/Traits/Concatenation/IsConcatenableHasManyJsonRelation.php +++ b/src/Relations/Traits/Concatenation/IsConcatenableHasManyJsonRelation.php @@ -9,18 +9,25 @@ use Illuminate\Database\Query\JoinClause; use Illuminate\Support\Arr; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + */ trait IsConcatenableHasManyJsonRelation { + /** @use \Staudenmeir\EloquentJsonRelations\Relations\Traits\Concatenation\IsConcatenableRelation */ use IsConcatenableRelation; /** * Append the relation's through parents, foreign and local keys to a deep relationship. * - * @param \Illuminate\Database\Eloquent\Model[] $through - * @param array $foreignKeys - * @param array $localKeys + * @param non-empty-list $through + * @param non-empty-list $foreignKeys + * @param non-empty-list $localKeys * @param int $position - * @return array + * @return array{0: non-empty-list, + * 1: non-empty-list, + * 2: non-empty-list} */ public function appendToDeepRelationship(array $through, array $foreignKeys, array $localKeys, int $position): array { @@ -71,11 +78,11 @@ public function getThroughKeyForDeepRelationships(string $alias): Expression /** * Match the eagerly loaded results for a deep relationship to their parents. * - * @param array $models - * @param \Illuminate\Database\Eloquent\Collection $results + * @param list $models + * @param \Illuminate\Database\Eloquent\Collection $results * @param string $relation * @param string $type - * @return array + * @return list */ public function matchResultsForDeepRelationship( array $models, @@ -106,7 +113,8 @@ public function matchResultsForDeepRelationship( * Build the model dictionary for a deep relation. * * @param \Illuminate\Database\Eloquent\Collection $results - * @return array + * @param \Illuminate\Database\Eloquent\Collection $results + * @return array> */ protected function buildDictionaryForDeepRelationship(Collection $results): array { @@ -115,6 +123,7 @@ protected function buildDictionaryForDeepRelationship(Collection $results): arra $key = $this->key ? str_replace('->', '.', $this->key) : null; foreach ($results as $result) { + /** @var array $values */ $values = json_decode($result->laravel_through_key ?? '[]', true); if ($key) { diff --git a/src/Relations/Traits/Concatenation/IsConcatenableRelation.php b/src/Relations/Traits/Concatenation/IsConcatenableRelation.php index 83ecb39..b211c9a 100644 --- a/src/Relations/Traits/Concatenation/IsConcatenableRelation.php +++ b/src/Relations/Traits/Concatenation/IsConcatenableRelation.php @@ -4,13 +4,17 @@ use Illuminate\Database\Eloquent\Builder; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + */ trait IsConcatenableRelation { /** * Set the constraints for an eager load of the deep relation. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param array $models + * @param \Illuminate\Database\Eloquent\Builder $query + * @param list $models * @return void */ public function addEagerConstraintsToDeepRelationship(Builder $query, array $models): void @@ -23,9 +27,9 @@ public function addEagerConstraintsToDeepRelationship(Builder $query, array $mod /** * Merge the where constraints from another query to the current query. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $from - * @return \Illuminate\Database\Eloquent\Builder + * @param \Illuminate\Database\Eloquent\Builder<*> $query + * @param \Illuminate\Database\Eloquent\Builder<*> $from + * @return \Illuminate\Database\Eloquent\Builder<*> */ public function mergeWhereConstraints(Builder $query, Builder $from): Builder { @@ -33,8 +37,10 @@ public function mergeWhereConstraints(Builder $query, Builder $from): Builder $wheres = $from->getQuery()->wheres; - return $query->withoutGlobalScopes( + $query->withoutGlobalScopes( $from->removedScopes() )->mergeWheres($wheres, $whereBindings); + + return $query; } } diff --git a/src/Relations/Traits/IsJsonRelation.php b/src/Relations/Traits/IsJsonRelation.php index 860d0b4..f8f1dee 100644 --- a/src/Relations/Traits/IsJsonRelation.php +++ b/src/Relations/Traits/IsJsonRelation.php @@ -15,6 +15,10 @@ use Staudenmeir\EloquentJsonRelations\Grammars\SQLiteGrammar; use Staudenmeir\EloquentJsonRelations\Grammars\SqlServerGrammar; +/** + * @template TRelatedModel of \Illuminate\Database\Eloquent\Model + * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model + */ trait IsJsonRelation { /** @@ -27,17 +31,17 @@ trait IsJsonRelation /** * The optional object key of the foreign key. * - * @var string + * @var string|null */ protected $key; /** * Hydrate the pivot relationship on the models. * - * @param \Illuminate\Database\Eloquent\Collection $models - * @param \Illuminate\Database\Eloquent\Model $parent + * @param \Illuminate\Database\Eloquent\Collection $models + * @param TDeclaringModel $parent * @param callable $callback - * @return \Illuminate\Database\Eloquent\Collection + * @return \Illuminate\Database\Eloquent\Collection */ public function hydratePivotRelation(Collection $models, Model $parent, callable $callback): Collection { @@ -56,10 +60,10 @@ public function hydratePivotRelation(Collection $models, Model $parent, callable /** * Get the pivot relationship from the query. * - * @param \Illuminate\Database\Eloquent\Model $model - * @param \Illuminate\Database\Eloquent\Model $parent + * @param TRelatedModel $model + * @param TDeclaringModel $parent * @param callable $callback - * @return \Illuminate\Database\Eloquent\Model + * @return TRelatedModel */ protected function pivotRelation(Model $model, Model $parent, callable $callback) { @@ -71,28 +75,34 @@ protected function pivotRelation(Model $model, Model $parent, callable $callback $attributes = $this->pivotAttributes($model, $parent, $records); - return Pivot::fromAttributes($model, $attributes, null, true); + /** @var TRelatedModel $pivotModel */ + $pivotModel = Pivot::fromAttributes($model, $attributes, null, true); // @phpstan-ignore-line + + return $pivotModel; } /** * Get the pivot attributes from a model. * - * @param \Illuminate\Database\Eloquent\Model $model - * @param \Illuminate\Database\Eloquent\Model $parent - * @param array $records - * @return array + * @param TRelatedModel $model + * @param TDeclaringModel $parent + * @param list> $records + * @return array */ abstract public function pivotAttributes(Model $model, Model $parent, array $records); /** * Execute the query and get the first related model. * - * @param array $columns - * @return mixed + * @param list $columns + * @return TRelatedModel|null */ public function first($columns = ['*']) { - return $this->take(1)->get($columns)->first(); + /** @var \Illuminate\Database\Eloquent\Collection $models */ + $models = $this->take(1)->get($columns); + + return $models->first(); } /** @@ -149,7 +159,8 @@ protected function getJsonGrammar(Builder $query): JsonGrammar /** @var \Illuminate\Database\Connection $connection */ $connection = $query->getConnection(); - return $connection->withTablePrefix( + /** @var \Staudenmeir\EloquentJsonRelations\Grammars\JsonGrammar $grammar */ + $grammar = $connection->withTablePrefix( match ($connection->getDriverName()) { 'mysql' => new MySqlGrammar(), 'mariadb' => new MariaDbGrammar(), @@ -159,6 +170,8 @@ protected function getJsonGrammar(Builder $query): JsonGrammar default => throw new RuntimeException('This database is not supported.') // @codeCoverageIgnore } ); + + return $grammar; } /**