diff --git a/src/EloquentJoins.php b/src/EloquentJoins.php index 785f3e7..97b83ae 100644 --- a/src/EloquentJoins.php +++ b/src/EloquentJoins.php @@ -15,7 +15,7 @@ class EloquentJoins /** * Register macros with Eloquent. */ - public static function registerEloquentMacros() + public static function registerEloquentMacros(): void { EloquentQueryBuilder::mixin(new JoinRelationship); EloquentQueryBuilder::mixin(new QueryRelationshipExistence); diff --git a/src/JoinsHelper.php b/src/JoinsHelper.php index ae27b74..b8b8c11 100644 --- a/src/JoinsHelper.php +++ b/src/JoinsHelper.php @@ -8,6 +8,9 @@ class JoinsHelper { + /** + * @var array + */ static array $instances = []; protected function __construct() @@ -25,7 +28,6 @@ public static function make(): static /** * Cache to not join the same relationship twice. * - * @var array */ private array $joinRelationshipCache = []; @@ -71,7 +73,7 @@ public function generateAliasForRelationship(Relation $relation, string $relatio /** * Get the join alias name from all the different options. * - * @return string|null + * @return array|string|null */ public function getAliasName(bool $useAlias, Relation $relation, string $relationName, string $tableName, $callback) { diff --git a/src/Mixins/JoinRelationship.php b/src/Mixins/JoinRelationship.php index 9894604..2faaa27 100644 --- a/src/Mixins/JoinRelationship.php +++ b/src/Mixins/JoinRelationship.php @@ -28,7 +28,8 @@ public function powerJoin(): Closure { return function ($table, $first, $operator = null, $second = null, $type = 'inner', $where = false): static { $model = $operator instanceof Model ? $operator : null; - $join = $this->newPowerJoinClause($this->query, $type, $table, $model); + /** @var PowerJoinClause $join */ + $join = $this->newPowerJoinClause($this->getQuery(), $type, $table, $model); // If the first "column" of the join is really a Closure instance the developer // is trying to build a join with a complex "on" clause containing more than @@ -36,9 +37,9 @@ public function powerJoin(): Closure if ($first instanceof Closure) { $first($join); - $this->query->joins[] = $join; + $this->getQuery()->joins[] = $join; - $this->query->addBinding($join->getBindings(), 'join'); + $this->getQuery()->addBinding($join->getBindings(), 'join'); } // If the column is simply a string, we can assume the join simply has a basic @@ -47,9 +48,9 @@ public function powerJoin(): Closure else { $method = $where ? 'where' : 'on'; - $this->query->joins[] = $join->$method($first, $operator, $second); + $this->getQuery()->joins[] = $join->$method($first, $operator, $second); - $this->query->addBinding($join->getBindings(), 'join'); + $this->getQuery()->addBinding($join->getBindings(), 'join'); } return $this; @@ -200,28 +201,28 @@ public function joinRelation(): Closure public function leftJoinRelationship(): Closure { - return function ($relation, $callback = null, $useAlias = false, bool $disableExtraConditions = false) { + return function (string $relation, $callback = null, $useAlias = false, bool $disableExtraConditions = false) { return $this->joinRelationship($relation, $callback, 'leftJoin', $useAlias, $disableExtraConditions); }; } public function leftJoinRelation(): Closure { - return function ($relation, $callback = null, $useAlias = false, bool $disableExtraConditions = false) { + return function (string $relation, $callback = null, $useAlias = false, bool $disableExtraConditions = false) {https://github.com/kirschbaum-development/eloquent-power-joins return $this->joinRelationship($relation, $callback, 'leftJoin', $useAlias, $disableExtraConditions); }; } public function rightJoinRelationship(): Closure { - return function ($relation, $callback = null, $useAlias = false, bool $disableExtraConditions = false) { + return function (string $relation, $callback = null, $useAlias = false, bool $disableExtraConditions = false) { return $this->joinRelationship($relation, $callback, 'rightJoin', $useAlias, $disableExtraConditions); }; } public function rightJoinRelation(): Closure { - return function ($relation, $callback = null, $useAlias = false, bool $disableExtraConditions = false) { + return function (string $relation, $callback = null, $useAlias = false, bool $disableExtraConditions = false) { return $this->joinRelationship($relation, $callback, 'rightJoin', $useAlias, $disableExtraConditions); }; } @@ -314,7 +315,7 @@ public function joinNestedRelationship(): Closure */ public function orderByPowerJoins(): Closure { - return function ($sort, $direction = 'asc', $aggregation = null, $joinType = 'join') { + return function ($sort, string $direction = 'asc', $aggregation = null, $joinType = 'join') { if (is_array($sort)) { $relationships = explode('.', $sort[0]); $column = $sort[1]; @@ -366,7 +367,7 @@ public function orderByPowerJoins(): Closure public function orderByLeftPowerJoins(): Closure { - return function ($sort, $direction = 'asc') { + return function ($sort, string $direction = 'asc') { return $this->orderByPowerJoins($sort, $direction, null, 'leftJoin'); }; } @@ -376,14 +377,14 @@ public function orderByLeftPowerJoins(): Closure */ public function orderByPowerJoinsCount(): Closure { - return function ($sort, $direction = 'asc') { + return function ($sort, string $direction = 'asc') { return $this->orderByPowerJoins($sort, $direction, 'COUNT'); }; } public function orderByLeftPowerJoinsCount(): Closure { - return function ($sort, $direction = 'asc') { + return function ($sort, string $direction = 'asc') { return $this->orderByPowerJoins($sort, $direction, 'COUNT', 'leftJoin'); }; } @@ -393,14 +394,14 @@ public function orderByLeftPowerJoinsCount(): Closure */ public function orderByPowerJoinsSum(): Closure { - return function ($sort, $direction = 'asc') { + return function ($sort, string $direction = 'asc') { return $this->orderByPowerJoins($sort, $direction, 'SUM'); }; } public function orderByLeftPowerJoinsSum(): Closure { - return function ($sort, $direction = 'asc') { + return function ($sort, string $direction = 'asc') { return $this->orderByPowerJoins($sort, $direction, 'SUM', 'leftJoin'); }; } @@ -410,14 +411,14 @@ public function orderByLeftPowerJoinsSum(): Closure */ public function orderByPowerJoinsAvg(): Closure { - return function ($sort, $direction = 'asc') { + return function ($sort, string $direction = 'asc') { return $this->orderByPowerJoins($sort, $direction, 'AVG'); }; } public function orderByLeftPowerJoinsAvg(): Closure { - return function ($sort, $direction = 'asc') { + return function ($sort, string $direction = 'asc') { return $this->orderByPowerJoins($sort, $direction, 'AVG', 'leftJoin'); }; } @@ -427,14 +428,14 @@ public function orderByLeftPowerJoinsAvg(): Closure */ public function orderByPowerJoinsMin(): Closure { - return function ($sort, $direction = 'asc') { + return function ($sort, string $direction = 'asc') { return $this->orderByPowerJoins($sort, $direction, 'MIN'); }; } public function orderByLeftPowerJoinsMin(): Closure { - return function ($sort, $direction = 'asc') { + return function ($sort, string $direction = 'asc') { return $this->orderByPowerJoins($sort, $direction, 'MIN', 'leftJoin'); }; } @@ -444,14 +445,14 @@ public function orderByLeftPowerJoinsMin(): Closure */ public function orderByPowerJoinsMax(): Closure { - return function ($sort, $direction = 'asc') { + return function ($sort, string $direction = 'asc') { return $this->orderByPowerJoins($sort, $direction, 'MAX'); }; } public function orderByLeftPowerJoinsMax(): Closure { - return function ($sort, $direction = 'asc') { + return function ($sort, string $direction = 'asc') { return $this->orderByPowerJoins($sort, $direction, 'MAX', 'leftJoin'); }; } @@ -487,7 +488,7 @@ public function powerJoinHas(): Closure public function hasNestedUsingJoins(): Closure { - return function ($relations, $operator = '>=', $count = 1, $boolean = 'and', Closure|array $callback = null): static { + return function (string $relations, $operator = '>=', $count = 1, $boolean = 'and', Closure|array $callback = null): static { $relations = explode('.', $relations); /** @var Relation */ @@ -516,7 +517,7 @@ public function hasNestedUsingJoins(): Closure public function powerJoinDoesntHave(): Closure { - return function ($relation, $boolean = 'and', Closure $callback = null) { + return function (string $relation, $boolean = 'and', Closure $callback = null) { return $this->powerJoinHas($relation, '<', 1, $boolean, $callback); }; @@ -524,7 +525,7 @@ public function powerJoinDoesntHave(): Closure public function powerJoinWhereHas(): Closure { - return function ($relation, $callback = null, $operator = '>=', $count = 1) { + return function (string $relation, $callback = null, $operator = '>=', $count = 1) { return $this->powerJoinHas($relation, $operator, $count, 'and', $callback); }; } diff --git a/src/Mixins/QueryBuilderExtraMethods.php b/src/Mixins/QueryBuilderExtraMethods.php index 06bbd9e..d1c8c29 100644 --- a/src/Mixins/QueryBuilderExtraMethods.php +++ b/src/Mixins/QueryBuilderExtraMethods.php @@ -2,18 +2,23 @@ namespace Kirschbaum\PowerJoins\Mixins; +use Illuminate\Database\Query\Builder; + +/** + * @mixin Builder + */ class QueryBuilderExtraMethods { - public function getGroupBy() + public function getGroupBy(): \Closure { - return function () { + return function (): ?array { return $this->groups; }; } - public function getSelect() + public function getSelect(): \Closure { - return function () { + return function (): ?array { return $this->columns; }; } diff --git a/src/Mixins/QueryRelationshipExistence.php b/src/Mixins/QueryRelationshipExistence.php index 3441a31..e6303a3 100644 --- a/src/Mixins/QueryRelationshipExistence.php +++ b/src/Mixins/QueryRelationshipExistence.php @@ -2,30 +2,32 @@ namespace Kirschbaum\PowerJoins\Mixins; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\Relation; +/** + * @mixin Builder + */ class QueryRelationshipExistence { - public function getGroupBy() + public function getGroupBy(): \Closure { - return function () { + return function (): ?array { return $this->getQuery()->getGroupBy(); }; } - public function getSelect() + public function getSelect(): \Closure { - return function () { + return function (): ?array { return $this->getQuery()->getSelect(); }; } - protected function getRelationWithoutConstraintsProxy() + protected function getRelationWithoutConstraintsProxy(): \Closure { - return function ($relation) { - return Relation::noConstraints(function () use ($relation) { - return $this->getModel()->{$relation}(); - }); + return function (string $relation): ?Relation { + return Relation::noConstraints(fn () => $this->getModel()->{$relation}()); }; } } diff --git a/src/Mixins/RelationshipsExtraMethods.php b/src/Mixins/RelationshipsExtraMethods.php index b838c66..f142fd8 100644 --- a/src/Mixins/RelationshipsExtraMethods.php +++ b/src/Mixins/RelationshipsExtraMethods.php @@ -2,20 +2,21 @@ namespace Kirschbaum\PowerJoins\Mixins; -use Stringable; -use Illuminate\Support\Str; -use Kirschbaum\PowerJoins\StaticCache; -use Kirschbaum\PowerJoins\PowerJoinClause; -use Kirschbaum\PowerJoins\Tests\Models\Post; -use Illuminate\Database\Eloquent\SoftDeletes; -use Illuminate\Database\Eloquent\Relations\HasOne; -use Illuminate\Database\Eloquent\Relations\HasMany; -use Illuminate\Database\Eloquent\Relations\MorphTo; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Builder as EloquentBuilder; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; -use Illuminate\Database\Eloquent\Relations\MorphToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasManyThrough; +use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\MorphOneOrMany; +use Illuminate\Database\Eloquent\Relations\MorphTo; +use Illuminate\Database\Eloquent\Relations\MorphToMany; +use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Support\Str; +use Kirschbaum\PowerJoins\PowerJoinClause; +use Kirschbaum\PowerJoins\StaticCache; /** * @method \Illuminate\Database\Eloquent\Model getModel() @@ -23,8 +24,8 @@ * @method string getForeignPivotKeyName() * @method string getRelatedPivotKeyName() * @method bool isOneOfMany() - * @method \Illuminate\Database\Eloquent\Builder|void getOneOfManySubQuery() - * @method \Illuminate\Database\Eloquent\Builder getQuery() + * @method EloquentBuilder|void getOneOfManySubQuery() + * @method EloquentBuilder getQuery() * @method \Illuminate\Database\Eloquent\Model getThroughParent() * @method string getForeignKeyName() * @method string getMorphType() @@ -35,7 +36,7 @@ * @mixin \Illuminate\Database\Eloquent\Relations\Relation * @mixin \Illuminate\Database\Eloquent\Relations\HasOneOrMany * @mixin \Illuminate\Database\Eloquent\Relations\BelongsToMany - * @property \Illuminate\Database\Eloquent\Builder $query + * @property EloquentBuilder $query * @property \Illuminate\Database\Eloquent\Model $parent * @property \Illuminate\Database\Eloquent\Model $throughParent * @property string $foreignKey @@ -51,17 +52,31 @@ class RelationshipsExtraMethods /** * Perform the JOIN clause for eloquent power joins. */ - public function performJoinForEloquentPowerJoins() + public function performJoinForEloquentPowerJoins(): \Closure { - return function ($builder, $joinType = 'leftJoin', $callback = null, $alias = null, bool $disableExtraConditions = false, string $morphable = null) { + return function ( + Builder $builder, + string $joinType = 'leftJoin', + $callback = null, + $alias = null, + bool $disableExtraConditions = false, + string $morphable = null + ) { return match (true) { - $this instanceof MorphToMany => $this->performJoinForEloquentPowerJoinsForMorphToMany($builder, $joinType, $callback, $alias, $disableExtraConditions), - $this instanceof BelongsToMany => $this->performJoinForEloquentPowerJoinsForBelongsToMany($builder, $joinType, $callback, $alias, $disableExtraConditions), - $this instanceof MorphOneOrMany => $this->performJoinForEloquentPowerJoinsForMorph($builder, $joinType, $callback, $alias, $disableExtraConditions), - $this instanceof HasMany || $this instanceof HasOne => $this->performJoinForEloquentPowerJoinsForHasMany($builder, $joinType, $callback, $alias, $disableExtraConditions), - $this instanceof HasManyThrough => $this->performJoinForEloquentPowerJoinsForHasManyThrough($builder, $joinType, $callback, $alias, $disableExtraConditions), - $this instanceof MorphTo => $this->performJoinForEloquentPowerJoinsForMorphTo($builder, $joinType, $callback, $alias, $disableExtraConditions, $morphable), - default => $this->performJoinForEloquentPowerJoinsForBelongsTo($builder, $joinType, $callback, $alias, $disableExtraConditions), + $this instanceof MorphToMany => $this->performJoinForEloquentPowerJoinsForMorphToMany($builder, + $joinType, $callback, $alias, $disableExtraConditions), + $this instanceof BelongsToMany => $this->performJoinForEloquentPowerJoinsForBelongsToMany($builder, + $joinType, $callback, $alias, $disableExtraConditions), + $this instanceof MorphOneOrMany => $this->performJoinForEloquentPowerJoinsForMorph($builder, $joinType, + $callback, $alias, $disableExtraConditions), + $this instanceof HasMany || $this instanceof HasOne => $this->performJoinForEloquentPowerJoinsForHasMany($builder, + $joinType, $callback, $alias, $disableExtraConditions), + $this instanceof HasManyThrough => $this->performJoinForEloquentPowerJoinsForHasManyThrough($builder, + $joinType, $callback, $alias, $disableExtraConditions), + $this instanceof MorphTo => $this->performJoinForEloquentPowerJoinsForMorphTo($builder, $joinType, + $callback, $alias, $disableExtraConditions, $morphable), + default => $this->performJoinForEloquentPowerJoinsForBelongsTo($builder, $joinType, $callback, $alias, + $disableExtraConditions), }; }; } @@ -69,13 +84,25 @@ public function performJoinForEloquentPowerJoins() /** * Perform the JOIN clause for the BelongsTo (or similar) relationships. */ - protected function performJoinForEloquentPowerJoinsForBelongsTo() + protected function performJoinForEloquentPowerJoinsForBelongsTo(): \Closure { - return function ($query, $joinType, $callback = null, $alias = null, bool $disableExtraConditions = false) { - $joinedTable = $this->query->getModel()->getTable(); + return function ( + $query, + $joinType, + $callback = null, + $alias = null, + bool $disableExtraConditions = false + ): void { + $joinedTable = $this->getQuery()->getModel()->getTable(); $parentTable = StaticCache::getTableOrAliasForModel($this->parent); - $query->{$joinType}($joinedTable, function ($join) use ($callback, $joinedTable, $parentTable, $alias, $disableExtraConditions) { + $query->{$joinType}($joinedTable, function (PowerJoinClause $join) use ( + $callback, + $joinedTable, + $parentTable, + $alias, + $disableExtraConditions + ) { if ($alias) { $join->as($alias); } @@ -86,8 +113,8 @@ protected function performJoinForEloquentPowerJoinsForBelongsTo() "{$joinedTable}.{$this->ownerKey}" ); - if ($disableExtraConditions === false && $this->usesSoftDeletes($this->query->getModel())) { - $join->whereNull("{$joinedTable}.{$this->query->getModel()->getDeletedAtColumn()}"); + if ($disableExtraConditions === false && $this->usesSoftDeletes($this->getQuery()->getModel())) { + $join->whereNull("{$joinedTable}.{$this->getQuery()->getModel()->getDeletedAtColumn()}"); } if ($disableExtraConditions === false) { @@ -97,61 +124,69 @@ protected function performJoinForEloquentPowerJoinsForBelongsTo() if ($callback && is_callable($callback)) { $callback($join); } - }, $this->query->getModel()); + }, $this->getQuery()->getModel()); }; } /** * Perform the JOIN clause for the BelongsToMany (or similar) relationships. */ - protected function performJoinForEloquentPowerJoinsForBelongsToMany() + protected function performJoinForEloquentPowerJoinsForBelongsToMany(): \Closure { - return function ($builder, $joinType, $callback = null, $alias = null, bool $disableExtraConditions = false) { + return function ( + EloquentBuilder $builder, + string $joinType, + $callback = null, + $alias = null, + bool $disableExtraConditions = false + ): static { [$alias1, $alias2] = $alias; $joinedTable = $alias1 ?: $this->getTable(); $parentTable = StaticCache::getTableOrAliasForModel($this->parent); - $builder->{$joinType}($this->getTable(), function ($join) use ($callback, $joinedTable, $parentTable, $alias1) { - if ($alias1) { - $join->as($alias1); - } - - $join->on( - "{$joinedTable}.{$this->getForeignPivotKeyName()}", - '=', - "{$parentTable}.{$this->parentKey}" - ); + $builder->{$joinType}($this->getTable(), + function (PowerJoinClause $join) use ($callback, $joinedTable, $parentTable, $alias1) { + if ($alias1) { + $join->as($alias1); + } - if (is_array($callback) && isset($callback[$this->getTable()])) { - $callback[$this->getTable()]($join); - } - }); - - $builder->{$joinType}($this->getModel()->getTable(), function ($join) use ($callback, $joinedTable, $alias2, $disableExtraConditions) { - if ($alias2) { - $join->as($alias2); - } + $join->on( + "{$joinedTable}.{$this->getForeignPivotKeyName()}", + '=', + "{$parentTable}.{$this->parentKey}" + ); - $join->on( - "{$this->getModel()->getTable()}.{$this->getModel()->getKeyName()}", - '=', - "{$joinedTable}.{$this->getRelatedPivotKeyName()}" - ); + if (is_array($callback) && isset($callback[$this->getTable()])) { + $callback[$this->getTable()]($join); + } + }); + + $builder->{$joinType}($this->getModel()->getTable(), + function (PowerJoinClause $join) use ($callback, $joinedTable, $alias2, $disableExtraConditions) { + if ($alias2) { + $join->as($alias2); + } + + $join->on( + "{$this->getModel()->getTable()}.{$this->getModel()->getKeyName()}", + '=', + "{$joinedTable}.{$this->getRelatedPivotKeyName()}" + ); - if ($disableExtraConditions === false && $this->usesSoftDeletes($this->query->getModel())) { - $join->whereNull($this->query->getModel()->getQualifiedDeletedAtColumn()); - } + if ($disableExtraConditions === false && $this->usesSoftDeletes($this->getQuery()->getModel())) { + $join->whereNull($this->getQuery()->getModel()->getQualifiedDeletedAtColumn()); + } - // applying any extra conditions to the belongs to many relationship - if ($disableExtraConditions === false) { - $this->applyExtraConditions($join); - } + // applying any extra conditions to the belongs to many relationship + if ($disableExtraConditions === false) { + $this->applyExtraConditions($join); + } - if (is_array($callback) && isset($callback[$this->getModel()->getTable()])) { - $callback[$this->getModel()->getTable()]($join); - } - }, $this->getModel()); + if (is_array($callback) && isset($callback[$this->getModel()->getTable()])) { + $callback[$this->getModel()->getTable()]($join); + } + }, $this->getModel()); return $this; }; @@ -160,15 +195,27 @@ protected function performJoinForEloquentPowerJoinsForBelongsToMany() /** * Perform the JOIN clause for the MorphToMany (or similar) relationships. */ - protected function performJoinForEloquentPowerJoinsForMorphToMany() + protected function performJoinForEloquentPowerJoinsForMorphToMany(): \Closure { - return function ($builder, $joinType, $callback = null, $alias = null, bool $disableExtraConditions = false) { + return function ( + EloquentBuilder $builder, + string $joinType, + $callback = null, + $alias = null, + bool $disableExtraConditions = false + ): static { [$alias1, $alias2] = $alias; $joinedTable = $alias1 ?: $this->getTable(); $parentTable = StaticCache::getTableOrAliasForModel($this->parent); - $builder->{$joinType}($this->getTable(), function ($join) use ($callback, $joinedTable, $parentTable, $alias1, $disableExtraConditions) { + $builder->{$joinType}($this->getTable(), function (PowerJoinClause $join) use ( + $callback, + $joinedTable, + $parentTable, + $alias1, + $disableExtraConditions + ) { if ($alias1) { $join->as($alias1); } @@ -189,25 +236,26 @@ protected function performJoinForEloquentPowerJoinsForMorphToMany() } }); - $builder->{$joinType}($this->getModel()->getTable(), function ($join) use ($callback, $joinedTable, $alias2, $disableExtraConditions) { - if ($alias2) { - $join->as($alias2); - } + $builder->{$joinType}($this->getModel()->getTable(), + function (PowerJoinClause $join) use ($callback, $joinedTable, $alias2, $disableExtraConditions) { + if ($alias2) { + $join->as($alias2); + } - $join->on( - "{$this->getModel()->getTable()}.{$this->getModel()->getKeyName()}", - '=', - "{$joinedTable}.{$this->getRelatedPivotKeyName()}" - ); + $join->on( + "{$this->getModel()->getTable()}.{$this->getModel()->getKeyName()}", + '=', + "{$joinedTable}.{$this->getRelatedPivotKeyName()}" + ); - if ($disableExtraConditions === false && $this->usesSoftDeletes($this->query->getModel())) { - $join->whereNull($this->query->getModel()->getQualifiedDeletedAtColumn()); - } + if ($disableExtraConditions === false && $this->usesSoftDeletes($this->getQuery()->getModel())) { + $join->whereNull($this->getQuery()->getModel()->getQualifiedDeletedAtColumn()); + } - if (is_array($callback) && isset($callback[$this->getModel()->getTable()])) { - $callback[$this->getModel()->getTable()]($join); - } - }, $this->getModel()); + if (is_array($callback) && isset($callback[$this->getModel()->getTable()])) { + $callback[$this->getModel()->getTable()]($join); + } + }, $this->getModel()); return $this; }; @@ -216,28 +264,35 @@ protected function performJoinForEloquentPowerJoinsForMorphToMany() /** * Perform the JOIN clause for the Morph (or similar) relationships. */ - protected function performJoinForEloquentPowerJoinsForMorph() + protected function performJoinForEloquentPowerJoinsForMorph(): \Closure { - return function ($builder, $joinType, $callback = null, $alias = null, bool $disableExtraConditions = false) { - $builder->{$joinType}($this->getModel()->getTable(), function ($join) use ($callback, $disableExtraConditions) { - $join->on( - "{$this->getModel()->getTable()}.{$this->getForeignKeyName()}", - '=', - "{$this->parent->getTable()}.{$this->localKey}" - )->where("{$this->getModel()->getTable()}.{$this->getMorphType()}", '=', $this->getMorphClass()); - - if ($disableExtraConditions === false && $this->usesSoftDeletes($this->query->getModel())) { - $join->whereNull($this->query->getModel()->getQualifiedDeletedAtColumn()); - } - - if ($disableExtraConditions === false) { - $this->applyExtraConditions($join); - } - - if ($callback && is_callable($callback)) { - $callback($join); - } - }, $this->getModel()); + return function ( + Builder $builder, + string $joinType, + $callback = null, + $alias = null, + bool $disableExtraConditions = false + ) { + $builder->{$joinType}($this->getModel()->getTable(), + function (PowerJoinClause $join) use ($callback, $disableExtraConditions) { + $join->on( + "{$this->getModel()->getTable()}.{$this->getForeignKeyName()}", + '=', + "{$this->parent->getTable()}.{$this->localKey}" + )->where("{$this->getModel()->getTable()}.{$this->getMorphType()}", '=', $this->getMorphClass()); + + if ($disableExtraConditions === false && $this->usesSoftDeletes($this->getQuery()->getModel())) { + $join->whereNull($this->getQuery()->getModel()->getQualifiedDeletedAtColumn()); + } + + if ($disableExtraConditions === false) { + $this->applyExtraConditions($join); + } + + if ($callback && is_callable($callback)) { + $callback($join); + } + }, $this->getModel()); return $this; }; @@ -246,30 +301,39 @@ protected function performJoinForEloquentPowerJoinsForMorph() /** * Perform the JOIN clause for when calling the morphTo method from the morphable class. */ - protected function performJoinForEloquentPowerJoinsForMorphTo() + protected function performJoinForEloquentPowerJoinsForMorphTo(): \Closure { - return function ($builder, $joinType, $callback = null, $alias = null, bool $disableExtraConditions = false, string $morphable = null) { + return function ( + Builder $builder, + string $joinType, + $callback = null, + $alias = null, + bool $disableExtraConditions = false, + string $morphable = null + ) { $modelInstance = new $morphable; - $builder->{$joinType}($modelInstance->getTable(), function ($join) use ($modelInstance, $callback, $disableExtraConditions) { - $join->on( - "{$this->getModel()->getTable()}.{$this->getForeignKeyName()}", - '=', - "{$modelInstance->getTable()}.{$modelInstance->getKeyName()}" - )->where("{$this->getModel()->getTable()}.{$this->getMorphType()}", '=', $modelInstance->getMorphClass()); + $builder->{$joinType}($modelInstance->getTable(), + function ($join) use ($modelInstance, $callback, $disableExtraConditions) { + $join->on( + "{$this->getModel()->getTable()}.{$this->getForeignKeyName()}", + '=', + "{$modelInstance->getTable()}.{$modelInstance->getKeyName()}" + )->where("{$this->getModel()->getTable()}.{$this->getMorphType()}", '=', + $modelInstance->getMorphClass()); - if ($disableExtraConditions === false && $this->usesSoftDeletes($modelInstance)) { - $join->whereNull($modelInstance->getQualifiedDeletedAtColumn()); - } + if ($disableExtraConditions === false && $this->usesSoftDeletes($modelInstance)) { + $join->whereNull($modelInstance->getQualifiedDeletedAtColumn()); + } - if ($disableExtraConditions === false) { - $this->applyExtraConditions($join); - } + if ($disableExtraConditions === false) { + $this->applyExtraConditions($join); + } - if ($callback && is_callable($callback)) { - $callback($join); - } - }, $modelInstance); + if ($callback && is_callable($callback)) { + $callback($join); + } + }, $modelInstance); return $this; }; @@ -278,10 +342,16 @@ protected function performJoinForEloquentPowerJoinsForMorphTo() /** * Perform the JOIN clause for the HasMany (or similar) relationships. */ - protected function performJoinForEloquentPowerJoinsForHasMany() + protected function performJoinForEloquentPowerJoinsForHasMany(): \Closure { - return function ($builder, $joinType, $callback = null, $alias = null, bool $disableExtraConditions = false) { - $joinedTable = $alias ?: $this->query->getModel()->getTable(); + return function ( + EloquentBuilder $builder, + string $joinType, + callable $callback = null, + $alias = null, + bool $disableExtraConditions = false + ): void { + $joinedTable = $alias ?: $this->getQuery()->getModel()->getTable(); $parentTable = StaticCache::getTableOrAliasForModel($this->parent); $isOneOfMany = method_exists($this, 'isOneOfMany') ? $this->isOneOfMany() : false; @@ -293,7 +363,13 @@ protected function performJoinForEloquentPowerJoinsForHasMany() $builder->take(1); } - $builder->{$joinType}($this->query->getModel()->getTable(), function ($join) use ($callback, $joinedTable, $parentTable, $alias, $disableExtraConditions) { + $builder->{$joinType}($this->getQuery()->getModel()->getTable(), function (PowerJoinClause $join) use ( + $callback, + $joinedTable, + $parentTable, + $alias, + $disableExtraConditions + ) { if ($alias) { $join->as($alias); } @@ -304,9 +380,9 @@ protected function performJoinForEloquentPowerJoinsForHasMany() "{$parentTable}.{$this->localKey}" ); - if ($disableExtraConditions === false && $this->usesSoftDeletes($this->query->getModel())) { + if ($disableExtraConditions === false && $this->usesSoftDeletes($this->getQuery()->getModel())) { $join->whereNull( - "{$joinedTable}.{$this->query->getModel()->getDeletedAtColumn()}" + "{$joinedTable}.{$this->getQuery()->getModel()->getDeletedAtColumn()}" ); } @@ -317,67 +393,75 @@ protected function performJoinForEloquentPowerJoinsForHasMany() if ($callback && is_callable($callback)) { $callback($join); } - }, $this->query->getModel()); + }, $this->getQuery()->getModel()); }; } /** * Perform the JOIN clause for the HasManyThrough relationships. */ - protected function performJoinForEloquentPowerJoinsForHasManyThrough() + protected function performJoinForEloquentPowerJoinsForHasManyThrough(): \Closure { - return function ($builder, $joinType, $callback = null, $alias = null, bool $disableExtraConditions = false) { + return function ( + EloquentBuilder $builder, + string $joinType, + $callback = null, + $alias = null, + bool $disableExtraConditions = false + ): static { [$alias1, $alias2] = $alias; $throughTable = $alias1 ?: $this->getThroughParent()->getTable(); $farTable = $alias2 ?: $this->getModel()->getTable(); - $builder->{$joinType}($this->getThroughParent()->getTable(), function (PowerJoinClause $join) use ($callback, $throughTable, $alias1, $disableExtraConditions) { - if ($alias1) { - $join->as($alias1); - } + $builder->{$joinType}($this->getThroughParent()->getTable(), + function (PowerJoinClause $join) use ($callback, $throughTable, $alias1, $disableExtraConditions) { + if ($alias1) { + $join->as($alias1); + } - $join->on( - "{$throughTable}.{$this->getFirstKeyName()}", - '=', - $this->getQualifiedLocalKeyName() - ); - - if ($disableExtraConditions === false && $this->usesSoftDeletes($this->getThroughParent())) { - $join->whereNull($this->getThroughParent()->getQualifiedDeletedAtColumn()); - } - - if ($disableExtraConditions === false) { - $this->applyExtraConditions($join); - } - - if (is_array($callback) && isset($callback[$this->getThroughParent()->getTable()])) { - $callback[$this->getThroughParent()->getTable()]($join); - } - - if ($callback && is_callable($callback)) { - $callback($join); - } - }, $this->getThroughParent()); - - $builder->{$joinType}($this->getModel()->getTable(), function (PowerJoinClause $join) use ($callback, $throughTable, $farTable, $alias1, $alias2) { - if ($alias2) { - $join->as($alias2); - } + $join->on( + "{$throughTable}.{$this->getFirstKeyName()}", + '=', + $this->getQualifiedLocalKeyName() + ); - $join->on( - "{$farTable}.{$this->secondKey}", - '=', - "{$throughTable}.{$this->secondLocalKey}" - ); + if ($disableExtraConditions === false && $this->usesSoftDeletes($this->getThroughParent())) { + $join->whereNull($this->getThroughParent()->getQualifiedDeletedAtColumn()); + } + + if ($disableExtraConditions === false) { + $this->applyExtraConditions($join); + } + + if (is_array($callback) && isset($callback[$this->getThroughParent()->getTable()])) { + $callback[$this->getThroughParent()->getTable()]($join); + } + + if ($callback && is_callable($callback)) { + $callback($join); + } + }, $this->getThroughParent()); + + $builder->{$joinType}($this->getModel()->getTable(), + function (PowerJoinClause $join) use ($callback, $throughTable, $farTable, $alias1, $alias2) { + if ($alias2) { + $join->as($alias2); + } + + $join->on( + "{$farTable}.{$this->secondKey}", + '=', + "{$throughTable}.{$this->secondLocalKey}" + ); - if ($this->usesSoftDeletes($this->getModel())) { - $join->whereNull("{$farTable}.{$this->getModel()->getDeletedAtColumn()}"); - } + if ($this->usesSoftDeletes($this->getModel())) { + $join->whereNull("{$farTable}.{$this->getModel()->getDeletedAtColumn()}"); + } - if (is_array($callback) && isset($callback[$this->getModel()->getTable()])) { - $callback[$this->getModel()->getTable()]($join); - } - }, $this->getModel()); + if (is_array($callback) && isset($callback[$this->getModel()->getTable()])) { + $callback[$this->getModel()->getTable()]($join); + } + }, $this->getModel()); return $this; }; @@ -386,19 +470,22 @@ protected function performJoinForEloquentPowerJoinsForHasManyThrough() /** * Perform the "HAVING" clause for eloquent power joins. */ - public function performHavingForEloquentPowerJoins() + public function performHavingForEloquentPowerJoins(): \Closure { - return function ($builder, $operator, $count, string $morphable = null) { + return function (EloquentBuilder $builder, string $operator, int $count, string $morphable = null): void { if ($morphable) { $modelInstance = new $morphable; $builder - ->selectRaw(sprintf('count(%s) as %s_count', $modelInstance->getQualifiedKeyName(), $modelInstance->getTable())) + ->selectRaw(sprintf('count(%s) as %s_count', $modelInstance->getQualifiedKeyName(), + $modelInstance->getTable())) ->havingRaw(sprintf('count(%s) %s %d', $modelInstance->getQualifiedKeyName(), $operator, $count)); } else { $builder - ->selectRaw(sprintf('count(%s) as %s_count', $this->query->getModel()->getQualifiedKeyName(), $this->query->getModel()->getTable())) - ->havingRaw(sprintf('count(%s) %s %d', $this->query->getModel()->getQualifiedKeyName(), $operator, $count)); + ->selectRaw(sprintf('count(%s) as %s_count', $this->getQuery()->getModel()->getQualifiedKeyName(), + $this->getQuery()->getModel()->getTable())) + ->havingRaw(sprintf('count(%s) %s %d', $this->getQuery()->getModel()->getQualifiedKeyName(), + $operator, $count)); } }; } @@ -406,9 +493,9 @@ public function performHavingForEloquentPowerJoins() /** * Checks if the relationship model uses soft deletes. */ - public function usesSoftDeletes() + public function usesSoftDeletes(): \Closure { - return function ($model) { + return function (Model $model): bool { return in_array(SoftDeletes::class, class_uses_recursive($model)); }; } @@ -416,9 +503,9 @@ public function usesSoftDeletes() /** * Get the throughParent for the HasManyThrough relationship. */ - public function getThroughParent() + public function getThroughParent(): \Closure { - return function () { + return function (): Model { return $this->throughParent; }; } @@ -441,7 +528,7 @@ public function applyExtraConditions() continue; } - if (!in_array($condition['type'], ['Basic', 'Null', 'NotNull', 'Nested'])) { + if (! in_array($condition['type'], ['Basic', 'Null', 'NotNull', 'Nested'])) { continue; } @@ -451,30 +538,30 @@ public function applyExtraConditions() }; } - public function applyBasicCondition() + public function applyBasicCondition(): \Closure { - return function ($join, $condition) { + return function (PowerJoinClause $join, array $condition) { $join->where($condition['column'], $condition['operator'], $condition['value'], $condition['boolean']); }; } - public function applyNullCondition() + public function applyNullCondition(): \Closure { - return function ($join, $condition) { + return function (PowerJoinClause $join, array $condition) { $join->whereNull($condition['column'], $condition['boolean']); }; } - public function applyNotNullCondition() + public function applyNotNullCondition(): \Closure { - return function ($join, $condition) { + return function (PowerJoinClause $join, array $condition) { $join->whereNotNull($condition['column'], $condition['boolean']); }; } - public function applyNestedCondition() + public function applyNestedCondition(): \Closure { - return function ($join, $condition) { + return function (PowerJoinClause $join, array $condition) { $join->where(function ($q) use ($condition) { foreach ($condition['query']->wheres as $condition) { $method = "apply{$condition['type']}Condition"; @@ -484,9 +571,9 @@ public function applyNestedCondition() }; } - public function shouldNotApplyExtraCondition() + public function shouldNotApplyExtraCondition(): \Closure { - return function ($condition) { + return function (array $condition) { if (isset($condition['column']) && Str::endsWith($condition['column'], '.')) { return true; } @@ -507,7 +594,7 @@ public function shouldNotApplyExtraCondition() }; } - public function getPowerJoinExistenceCompareKey() + public function getPowerJoinExistenceCompareKey(): \Closure { return function () { if ($this instanceof MorphTo) { diff --git a/src/PowerJoinClause.php b/src/PowerJoinClause.php index 365cf66..33f527d 100644 --- a/src/PowerJoinClause.php +++ b/src/PowerJoinClause.php @@ -3,27 +3,22 @@ namespace Kirschbaum\PowerJoins; use Closure; -use Illuminate\Support\Str; -use InvalidArgumentException; -use Illuminate\Database\Query\Builder; use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Query\JoinClause; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletingScope; +use Illuminate\Database\Query\Builder; +use Illuminate\Database\Query\JoinClause; +use Illuminate\Support\Str; +use InvalidArgumentException; class PowerJoinClause extends JoinClause { - /** - * @var \Illuminate\Database\Eloquent\Model - */ - public $model; + public ?Model $model; /** * Table name backup in case an alias is being used. - * - * @var string */ - public $tableName; + public string $tableName; /** * Alias name. @@ -37,14 +32,8 @@ class PowerJoinClause extends JoinClause /** * Create a new join clause instance. - * - * @param \Illuminate\Database\Query\Builder $parentQuery - * @param string $type - * @param string $table - * @param \Illuminate\Database\Eloquent\Model $model - * @return void */ - public function __construct(Builder $parentQuery, $type, $table, Model $model = null) + public function __construct(Builder $parentQuery, string $type, string $table, ?Model $model) { parent::__construct($parentQuery, $type, $table); @@ -77,7 +66,7 @@ public function on($first, $operator = null, $second = null, $boolean = 'and'): return $this; } - public function getModel() + public function getModel(): ?Model { return $this->model; } @@ -116,11 +105,9 @@ protected function useTableAliasInConditions(): self return $this; } - $this->wheres = collect($this->wheres)->filter(function ($where) { + $this->wheres = collect($this->wheres)->filter(function (array $where) { return in_array($where['type'] ?? '', ['Column', 'Basic']); - })->map(function ($where) { - $key = $this->model->getKeyName(); - $table = $this->tableName; + })->map(function (array $where) { $replaceMethod = sprintf('useAliasInWhere%sType', ucfirst($where['type'])); return $this->{$replaceMethod}($where); @@ -135,7 +122,8 @@ protected function useAliasInWhereColumnType(array $where): array $table = $this->tableName; // if it was already replaced, skip - if (Str::startsWith($where['first'] . '.', $this->alias . '.') || Str::startsWith($where['second'] . '.', $this->alias . '.')) { + if (Str::startsWith($where['first'] . '.', $this->alias . '.') || Str::startsWith($where['second'] . '.', + $this->alias . '.')) { return $where; } @@ -169,7 +157,7 @@ protected function useAliasInWhereBasicType(array $where): array return $where; } - public function whereNull($columns, $boolean = 'and', $not = false) + public function whereNull($columns, $boolean = 'and', $not = false): self { if ($this->alias && Str::contains($columns, $this->tableName)) { $columns = str_replace("{$this->tableName}.", "{$this->alias}.", $columns); @@ -180,7 +168,8 @@ public function whereNull($columns, $boolean = 'and', $not = false) public function newQuery(): self { - return new static($this->newParentQuery(), $this->type, $this->table, $this->model); // <-- The model param is needed + return new static($this->newParentQuery(), $this->type, $this->table, + $this->model); // <-- The model param is needed } public function where($column, $operator = null, $value = null, $boolean = 'and'): self diff --git a/src/PowerJoinsServiceProvider.php b/src/PowerJoinsServiceProvider.php index 3229599..25559c3 100644 --- a/src/PowerJoinsServiceProvider.php +++ b/src/PowerJoinsServiceProvider.php @@ -9,14 +9,14 @@ class PowerJoinsServiceProvider extends ServiceProvider /** * Bootstrap the application services. */ - public function boot() + public function boot(): void { } /** * Register the application services. */ - public function register() + public function register(): void { EloquentJoins::registerEloquentMacros(); }