Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve TIntermediateModels generics and add inheritDoc #102

Merged
merged 16 commits into from
Sep 9, 2024
Merged
76 changes: 25 additions & 51 deletions src/Relations/BelongsToThrough.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Query\Expression;
use Illuminate\Support\Str;
use RuntimeException;

/**
* @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\Relation<TRelatedModel>
Expand All @@ -32,7 +32,7 @@ class BelongsToThrough extends Relation
/**
* The "through" parent model instances.
*
* @var TIntermediateModel[]
* @var list<\Illuminate\Database\Eloquent\Model>
*/
protected $throughParents;

Expand Down Expand Up @@ -62,7 +62,7 @@ class BelongsToThrough extends Relation
*
* @param \Illuminate\Database\Eloquent\Builder<TRelatedModel> $query
* @param TDeclaringModel $parent
* @param TIntermediateModel[] $throughParents
* @param list<\Illuminate\Database\Eloquent\Model> $throughParents
* @param string|null $localKey
* @param string $prefix
* @param array<string, string> $foreignKeyLookup
Expand All @@ -88,11 +88,7 @@ public function __construct(
parent::__construct($query, $parent);
}

/**
* Set the base constraints on the relation query.
*
* @return void
*/
/** @inheritDoc */
public function addConstraints()
{
$this->performJoins();
Expand Down Expand Up @@ -179,26 +175,15 @@ public function hasSoftDeletes(Model $model)
return in_array(SoftDeletes::class, class_uses_recursive($model));
}

/**
* Set the constraints for an eager load of the relation.
*
* @param array<int, TDeclaringModel> $models
* @return void
*/
/** @inheritDoc */
public function addEagerConstraints(array $models)
{
$keys = $this->getKeys($models, $this->getFirstForeignKeyName());

$this->query->whereIn($this->getQualifiedFirstLocalKeyName(), $keys);
}

/**
* Initialize the relation on a set of models.
*
* @param array<int, TDeclaringModel> $models
* @param string $relation
* @return array<int, TDeclaringModel>
*/
/** @inheritDoc */
public function initRelation(array $models, $relation)
{
foreach ($models as $model) {
Expand All @@ -208,14 +193,7 @@ public function initRelation(array $models, $relation)
return $models;
}

/**
* Match the eagerly loaded results to their parents.
*
* @param array<int, TDeclaringModel> $models
* @param \Illuminate\Database\Eloquent\Collection<int, TRelatedModel> $results
* @param string $relation
* @return array<int, TDeclaringModel>
*/
/** @inheritDoc */
public function match(array $models, Collection $results, $relation)
{
$dictionary = $this->buildDictionary($results);
Expand All @@ -234,8 +212,8 @@ public function match(array $models, Collection $results, $relation)
/**
* Build model dictionary keyed by the relation's foreign key.
*
* @param \Illuminate\Database\Eloquent\Collection<array-key, \Illuminate\Database\Eloquent\Model> $results
* @return \Illuminate\Database\Eloquent\Model[]
* @param \Illuminate\Database\Eloquent\Collection<int, TRelatedModel> $results
* @return TRelatedModel[]
*/
protected function buildDictionary(Collection $results)
{
Expand Down Expand Up @@ -275,12 +253,7 @@ public function first($columns = ['*'])
return $this->query->first($columns);
}

/**
* Execute the query as a "select" statement.
*
* @param string[] $columns
* @return \Illuminate\Database\Eloquent\Collection<array-key, TRelatedModel>
*/
/** @inheritDoc */
public function get($columns = ['*'])
{
$columns = $this->query->getQuery()->columns ? [] : $columns;
Expand All @@ -296,14 +269,7 @@ public function get($columns = ['*'])
return $this->query->get();
}

/**
* Add the constraints for a relationship query.
*
* @param \Illuminate\Database\Eloquent\Builder<\Illuminate\Database\Eloquent\Model> $query
* @param \Illuminate\Database\Eloquent\Builder<\Illuminate\Database\Eloquent\Model> $parentQuery
* @param string[]|mixed $columns
* @return \Illuminate\Database\Eloquent\Builder<\Illuminate\Database\Eloquent\Model>
*/
/** @inheritDoc */
public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
{
$this->performJoins($query);
Expand All @@ -318,7 +284,7 @@ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery,

$foreignKey = $from . '.' . $this->getFirstForeignKeyName();

/** @var \Illuminate\Database\Eloquent\Builder<\Illuminate\Database\Eloquent\Model> $query */
/** @var \Illuminate\Database\Eloquent\Builder<TRelatedModel> $query */
$query = $query->select($columns)->whereColumn(
$this->getQualifiedFirstLocalKeyName(),
'=',
Expand Down Expand Up @@ -358,7 +324,7 @@ public function withTrashed(...$columns)
/**
* Get the "through" parent model instances.
*
* @return TIntermediateModel[]
* @return list<\Illuminate\Database\Eloquent\Model>
*/
public function getThroughParents()
{
Expand All @@ -372,9 +338,13 @@ public function getThroughParents()
*/
public function getFirstForeignKeyName()
{
/** @var TIntermediateModel $firstThroughParent */
$firstThroughParent = end($this->throughParents);

if ($firstThroughParent === false) {
// @codeCoverageIgnore
throw new RuntimeException('No "through" parent models were specified.');
}

return $this->prefix . $this->getForeignKeyName($firstThroughParent);
}

Expand All @@ -385,10 +355,14 @@ public function getFirstForeignKeyName()
*/
public function getQualifiedFirstLocalKeyName()
{
/** @var TIntermediateModel $lastThroughParent */
$lastThroughParent = end($this->throughParents);
$firstThroughParent = end($this->throughParents);

if ($firstThroughParent === false) {
// @codeCoverageIgnore
throw new RuntimeException('No "through" parent models were specified.');
}

return $lastThroughParent->qualifyColumn($this->getLocalKeyName($lastThroughParent));
return $firstThroughParent->qualifyColumn($this->getLocalKeyName($firstThroughParent));
}

/**
Expand Down
15 changes: 6 additions & 9 deletions src/Traits/BelongsToThrough.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,14 @@ trait BelongsToThrough
* Define a belongs-to-through relationship.
*
* @template TRelatedModel of \Illuminate\Database\Eloquent\Model
* @template TIntermediateModel of \Illuminate\Database\Eloquent\Model
*
* @param class-string<TRelatedModel> $related
* @param class-string<TIntermediateModel>[]|array{0: class-string<TIntermediateModel>, 1: string}[]|class-string<TIntermediateModel> $through
* @param class-string<\Illuminate\Database\Eloquent\Model>[]|array{0: class-string<\Illuminate\Database\Eloquent\Model>, 1: string}[]|class-string<\Illuminate\Database\Eloquent\Model> $through
* @param string|null $localKey
* @param string $prefix
* @param array<class-string<\Illuminate\Database\Eloquent\Model>, string> $foreignKeyLookup
* @param array<class-string<\Illuminate\Database\Eloquent\Model>, string> $localKeyLookup
* @return \Znck\Eloquent\Relations\BelongsToThrough<TRelatedModel, TIntermediateModel, $this>
* @return \Znck\Eloquent\Relations\BelongsToThrough<TRelatedModel, $this>
*/
public function belongsToThrough(
$related,
Expand All @@ -33,7 +32,7 @@ public function belongsToThrough(
/** @var TRelatedModel $relatedInstance */
$relatedInstance = $this->newRelatedInstance($related);

/** @var TIntermediateModel[] $throughParents */
/** @var list<\Illuminate\Database\Eloquent\Model> $throughParents */
$throughParents = [];
$foreignKeys = [];

Expand All @@ -44,11 +43,10 @@ public function belongsToThrough(
/** @var string $foreignKey */
$foreignKey = $model[1];

/** @var class-string<TIntermediateModel> $model */
/** @var class-string<\Illuminate\Database\Eloquent\Model> $model */
$model = $model[0];
}

/** @var TIntermediateModel $instance */
$instance = $this->belongsToThroughParentInstance($model);

if ($foreignKey) {
Expand Down Expand Up @@ -122,17 +120,16 @@ protected function belongsToThroughParentInstance($model)
* Instantiate a new BelongsToThrough relationship.
*
* @template TRelatedModel of \Illuminate\Database\Eloquent\Model
* @template TIntermediateModel of \Illuminate\Database\Eloquent\Model
* @template TDeclaringModel of \Illuminate\Database\Eloquent\Model
*
* @param \Illuminate\Database\Eloquent\Builder<TRelatedModel> $query
* @param TDeclaringModel $parent
* @param TIntermediateModel[] $throughParents
* @param list<\Illuminate\Database\Eloquent\Model> $throughParents
* @param string|null $localKey
* @param string $prefix
* @param array<string, string> $foreignKeyLookup
* @param array<string, string> $localKeyLookup
* @return \Znck\Eloquent\Relations\BelongsToThrough<TRelatedModel, TIntermediateModel, TDeclaringModel>
* @return \Znck\Eloquent\Relations\BelongsToThrough<TRelatedModel, TDeclaringModel>
*/
protected function newBelongsToThrough(
Builder $query,
Expand Down
2 changes: 1 addition & 1 deletion tests/IdeHelper/Models/Comment.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class Comment extends Model
use BelongsToThroughTrait;

/**
* @return \Znck\Eloquent\Relations\BelongsToThrough<\Tests\IdeHelper\Models\Country, \Tests\IdeHelper\Models\User|\Tests\IdeHelper\Models\Post, $this>
* @return \Znck\Eloquent\Relations\BelongsToThrough<\Tests\IdeHelper\Models\Country, $this>
*/
public function country(): BelongsToThroughRelation
{
Expand Down
14 changes: 7 additions & 7 deletions tests/Models/Comment.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ class Comment extends Model
use HasTableAlias;

/**
* @return \Znck\Eloquent\Relations\BelongsToThrough<\Tests\Models\Country, \Tests\Models\User|\Tests\Models\Post, $this>
* @return \Znck\Eloquent\Relations\BelongsToThrough<\Tests\Models\Country, $this>
*/
public function country(): BelongsToThrough
{
return $this->belongsToThrough(Country::class, [User::class, Post::class])->withDefault();
}

/**
* @return \Znck\Eloquent\Relations\BelongsToThrough<\Tests\Models\Country, \Tests\Models\User|\Tests\Models\Post, $this>
* @return \Znck\Eloquent\Relations\BelongsToThrough<\Tests\Models\Country, $this>
*/
public function countryWithCustomForeignKeys(): BelongsToThrough
{
Expand All @@ -37,7 +37,7 @@ public function countryWithCustomForeignKeys(): BelongsToThrough
}

/**
* @return \Znck\Eloquent\Relations\BelongsToThrough<\Tests\Models\Country, \Tests\Models\User|\Tests\Models\Post, $this>
* @return \Znck\Eloquent\Relations\BelongsToThrough<\Tests\Models\Country, $this>
*/
public function countryWithTrashedUser(): BelongsToThrough
{
Expand All @@ -46,24 +46,24 @@ public function countryWithTrashedUser(): BelongsToThrough
}

/**
* @return \Znck\Eloquent\Relations\BelongsToThrough<\Tests\Models\Country, \Tests\Models\User|\Tests\Models\Post, $this>
* @return \Znck\Eloquent\Relations\BelongsToThrough<\Tests\Models\Country, $this>
*/
public function countryWithPrefix(): BelongsToThrough
{
return $this->belongsToThrough(Country::class, [User::class, Post::class], null, 'custom_');
}

/**
* @return \Znck\Eloquent\Relations\BelongsToThrough<self, self, $this>
* @return \Znck\Eloquent\Relations\BelongsToThrough<self, $this>
*/
public function grandparent(): BelongsToThrough
{
/* @phpstan-ignore argument.templateType, argument.type, return.type */
/* @phpstan-ignore argument.type */
return $this->belongsToThrough(self::class, self::class.' as alias', null, '', [self::class => 'parent_id']);
}

/**
* @return \Znck\Eloquent\Relations\BelongsToThrough<\Tests\Models\User, \Tests\Models\Post, $this>
* @return \Znck\Eloquent\Relations\BelongsToThrough<\Tests\Models\User, $this>
*/
public function user(): BelongsToThrough
{
Expand Down
2 changes: 1 addition & 1 deletion tests/Models/CustomerAddress.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
class CustomerAddress extends Model
{
/**
* @return \Znck\Eloquent\Relations\BelongsToThrough<\Tests\Models\VendorCustomer, \Tests\Models\VendorCustomerAddress, $this>
* @return \Znck\Eloquent\Relations\BelongsToThrough<\Tests\Models\VendorCustomer, $this>
*/
public function vendorCustomer(): BelongsToThrough
{
Expand Down