From 3776bcf0eb513635081f353399b4cdad81a07c5d Mon Sep 17 00:00:00 2001 From: Jonas Staudenmeir Date: Sat, 4 Dec 2021 13:17:23 +0100 Subject: [PATCH] Refactoring --- .../HasOfDescendantsRelationships.php | 329 ++++++++++++++++++ .../HasRecursiveRelationshipScopes.php | 26 +- src/Eloquent/HasRecursiveRelationships.php | 320 +---------------- .../Relations/IsOfDescendantsRelation.php | 47 ++- 4 files changed, 384 insertions(+), 338 deletions(-) create mode 100644 src/Eloquent/HasOfDescendantsRelationships.php diff --git a/src/Eloquent/HasOfDescendantsRelationships.php b/src/Eloquent/HasOfDescendantsRelationships.php new file mode 100644 index 0000000..6493a8f --- /dev/null +++ b/src/Eloquent/HasOfDescendantsRelationships.php @@ -0,0 +1,329 @@ +newRelatedInstance($related); + + $foreignKey = $foreignKey ?: $this->getForeignKey(); + + $localKey = $localKey ?: $this->getKeyName(); + + return $this->newHasManyOfDescendants( + $instance->newQuery(), + $this, + $instance->qualifyColumn($foreignKey), + $localKey, + false + ); + } + + /** + * Define a one-to-many relationship of the model's descendants and itself. + * + * @param string $related + * @param string|null $foreignKey + * @param string|null $localKey + * @return \Staudenmeir\LaravelAdjacencyList\Eloquent\Relations\HasManyOfDescendants + */ + public function hasManyOfDescendantsAndSelf($related, $foreignKey = null, $localKey = null) + { + $instance = $this->newRelatedInstance($related); + + $foreignKey = $foreignKey ?: $this->getForeignKey(); + + $localKey = $localKey ?: $this->getKeyName(); + + return $this->newHasManyOfDescendants( + $instance->newQuery(), + $this, + $instance->qualifyColumn($foreignKey), + $localKey, + true + ); + } + + /** + * Instantiate a new HasManyOfDescendants relationship. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Model $parent + * @param string $foreignKey + * @param string $localKey + * @param bool $andSelf + * @return \Staudenmeir\LaravelAdjacencyList\Eloquent\Relations\HasManyOfDescendants + */ + protected function newHasManyOfDescendants(Builder $query, Model $parent, $foreignKey, $localKey, $andSelf) + { + return new HasManyOfDescendants($query, $parent, $foreignKey, $localKey, $andSelf); + } + + /** + * Define a many-to-many relationship of the model's descendants. + * + * @param string $related + * @param string|null $table + * @param string|null $foreignPivotKey + * @param string|null $relatedPivotKey + * @param string|null $parentKey + * @param string|null $relatedKey + * @return \Staudenmeir\LaravelAdjacencyList\Eloquent\Relations\BelongsToManyOfDescendants + */ + public function belongsToManyOfDescendants( + $related, + $table = null, + $foreignPivotKey = null, + $relatedPivotKey = null, + $parentKey = null, + $relatedKey = null + ) { + $instance = $this->newRelatedInstance($related); + + $foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey(); + + $relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey(); + + if (is_null($table)) { + $table = $this->joiningTable($related, $instance); + } + + return $this->newBelongsToManyOfDescendants( + $instance->newQuery(), + $this, + $table, + $foreignPivotKey, + $relatedPivotKey, + $parentKey ?: $this->getKeyName(), + $relatedKey ?: $instance->getKeyName(), + false + ); + } + + /** + * Define a many-to-many relationship of the model's descendants and itself. + * + * @param string $related + * @param string|null $table + * @param string|null $foreignPivotKey + * @param string|null $relatedPivotKey + * @param string|null $parentKey + * @param string|null $relatedKey + * @return \Staudenmeir\LaravelAdjacencyList\Eloquent\Relations\BelongsToManyOfDescendants + */ + public function belongsToManyOfDescendantsAndSelf( + $related, + $table = null, + $foreignPivotKey = null, + $relatedPivotKey = null, + $parentKey = null, + $relatedKey = null + ) { + $instance = $this->newRelatedInstance($related); + + $foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey(); + + $relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey(); + + if (is_null($table)) { + $table = $this->joiningTable($related, $instance); + } + + return $this->newBelongsToManyOfDescendants( + $instance->newQuery(), + $this, + $table, + $foreignPivotKey, + $relatedPivotKey, + $parentKey ?: $this->getKeyName(), + $relatedKey ?: $instance->getKeyName(), + true + ); + } + + /** + * Instantiate a new BelongsToManyOfDescendants relationship. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Model $parent + * @param string $table + * @param string $foreignPivotKey + * @param string $relatedPivotKey + * @param string $parentKey + * @param string $relatedKey + * @param bool $andSelf + * @return \Staudenmeir\LaravelAdjacencyList\Eloquent\Relations\BelongsToManyOfDescendants + */ + protected function newBelongsToManyOfDescendants( + Builder $query, + Model $parent, + $table, + $foreignPivotKey, + $relatedPivotKey, + $parentKey, + $relatedKey, + $andSelf + ) { + return new BelongsToManyOfDescendants( + $query, + $parent, + $table, + $foreignPivotKey, + $relatedPivotKey, + $parentKey, + $relatedKey, + $andSelf + ); + } + + /** + * Define a polymorphic many-to-many relationship of the model's descendants. + * + * @param string $related + * @param string $name + * @param string|null $table + * @param string|null $foreignPivotKey + * @param string|null $relatedPivotKey + * @param string|null $parentKey + * @param string|null $relatedKey + * @return \Staudenmeir\LaravelAdjacencyList\Eloquent\Relations\MorphToManyOfDescendants + */ + public function morphToManyOfDescendants( + $related, + $name, + $table = null, + $foreignPivotKey = null, + $relatedPivotKey = null, + $parentKey = null, + $relatedKey = null + ) { + $instance = $this->newRelatedInstance($related); + + $foreignPivotKey = $foreignPivotKey ?: $name.'_id'; + + $relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey(); + + if (! $table) { + $words = preg_split('/(_)/u', $name, -1, PREG_SPLIT_DELIM_CAPTURE); + + $lastWord = array_pop($words); + + $table = implode('', $words).Str::plural($lastWord); + } + + return $this->newMorphToManyOfDescendants( + $instance->newQuery(), + $this, + $name, + $table, + $foreignPivotKey, + $relatedPivotKey, + $parentKey ?: $this->getKeyName(), + $relatedKey ?: $instance->getKeyName(), + false + ); + } + + /** + * Define a polymorphic many-to-many relationship of the model's descendants and itself. + * + * @param string $related + * @param string $name + * @param string|null $table + * @param string|null $foreignPivotKey + * @param string|null $relatedPivotKey + * @param string|null $parentKey + * @param string|null $relatedKey + * @return \Staudenmeir\LaravelAdjacencyList\Eloquent\Relations\MorphToManyOfDescendants + */ + public function morphToManyOfDescendantsAndSelf( + $related, + $name, + $table = null, + $foreignPivotKey = null, + $relatedPivotKey = null, + $parentKey = null, + $relatedKey = null + ) { + $instance = $this->newRelatedInstance($related); + + $foreignPivotKey = $foreignPivotKey ?: $name.'_id'; + + $relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey(); + + if (! $table) { + $words = preg_split('/(_)/u', $name, -1, PREG_SPLIT_DELIM_CAPTURE); + + $lastWord = array_pop($words); + + $table = implode('', $words).Str::plural($lastWord); + } + + return $this->newMorphToManyOfDescendants( + $instance->newQuery(), + $this, + $name, + $table, + $foreignPivotKey, + $relatedPivotKey, + $parentKey ?: $this->getKeyName(), + $relatedKey ?: $instance->getKeyName(), + true + ); + } + + /** + * Instantiate a new MorphToManyOfDescendants relationship. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Model $parent + * @param string $name + * @param string $table + * @param string $foreignPivotKey + * @param string $relatedPivotKey + * @param string $parentKey + * @param string $relatedKey + * @param bool $andSelf + * @return \Staudenmeir\LaravelAdjacencyList\Eloquent\Relations\MorphToManyOfDescendants + */ + protected function newMorphToManyOfDescendants( + Builder $query, + Model $parent, + $name, + $table, + $foreignPivotKey, + $relatedPivotKey, + $parentKey, + $relatedKey, + $andSelf + ) { + return new MorphToManyOfDescendants( + $query, + $parent, + $name, + $table, + $foreignPivotKey, + $relatedPivotKey, + $parentKey, + $relatedKey, + $andSelf + ); + } +} diff --git a/src/Eloquent/HasRecursiveRelationshipScopes.php b/src/Eloquent/HasRecursiveRelationshipScopes.php index 1d0363e..b5c3b7d 100644 --- a/src/Eloquent/HasRecursiveRelationshipScopes.php +++ b/src/Eloquent/HasRecursiveRelationshipScopes.php @@ -247,6 +247,26 @@ protected function getRecursiveQuery(ExpressionGrammar $grammar, $direction, $fr ); } + $this->addRecursiveQueryJoinsAndConstraints($query, $direction, $name, $joinColumns); + + if (!is_null($maxDepth)) { + $query->where($this->getDepthName(), '<', $maxDepth); + } + + return $query; + } + + /** + * Add join and where clauses to the recursive query for a relationship expression. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param string $direction + * @param string $name + * @param array $joinColumns + * @return void + */ + protected function addRecursiveQueryJoinsAndConstraints(Builder $query, $direction, $name, array $joinColumns) + { if ($direction === 'both') { $query->join($name, function (JoinClause $join) use ($joinColumns) { $join->on($joinColumns['asc'][0], '=', $joinColumns['asc'][1]) @@ -269,11 +289,5 @@ protected function getRecursiveQuery(ExpressionGrammar $grammar, $direction, $fr } else { $query->join($name, $joinColumns[$direction][0], '=', $joinColumns[$direction][1]); } - - if (!is_null($maxDepth)) { - $query->where($this->getDepthName(), '<', $maxDepth); - } - - return $query; } } diff --git a/src/Eloquent/HasRecursiveRelationships.php b/src/Eloquent/HasRecursiveRelationships.php index 8235e98..2e31119 100644 --- a/src/Eloquent/HasRecursiveRelationships.php +++ b/src/Eloquent/HasRecursiveRelationships.php @@ -6,17 +6,15 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Str; use Staudenmeir\LaravelAdjacencyList\Eloquent\Relations\Ancestors; -use Staudenmeir\LaravelAdjacencyList\Eloquent\Relations\BelongsToManyOfDescendants; use Staudenmeir\LaravelAdjacencyList\Eloquent\Relations\Bloodline; use Staudenmeir\LaravelAdjacencyList\Eloquent\Relations\Descendants; -use Staudenmeir\LaravelAdjacencyList\Eloquent\Relations\HasManyOfDescendants; -use Staudenmeir\LaravelAdjacencyList\Eloquent\Relations\MorphToManyOfDescendants; use Staudenmeir\LaravelAdjacencyList\Eloquent\Relations\RootAncestor; use Staudenmeir\LaravelAdjacencyList\Eloquent\Relations\Siblings; use Staudenmeir\LaravelCte\Eloquent\QueriesExpressions; trait HasRecursiveRelationships { + use HasOfDescendantsRelationships; use HasRecursiveRelationshipScopes; use QueriesExpressions; @@ -349,322 +347,6 @@ protected function newSiblings(Builder $query, Model $parent, $foreignKey, $loca return new Siblings($query, $parent, $foreignKey, $localKey, $andSelf); } - /** - * Define a one-to-many relationship of the model's descendants. - * - * @param string $related - * @param string|null $foreignKey - * @param string|null $localKey - * @return \Staudenmeir\LaravelAdjacencyList\Eloquent\Relations\HasManyOfDescendants - */ - public function hasManyOfDescendants($related, $foreignKey = null, $localKey = null) - { - $instance = $this->newRelatedInstance($related); - - $foreignKey = $foreignKey ?: $this->getForeignKey(); - - $localKey = $localKey ?: $this->getKeyName(); - - return $this->newHasManyOfDescendants( - $instance->newQuery(), - $this, - $instance->qualifyColumn($foreignKey), - $localKey, - false - ); - } - - /** - * Define a one-to-many relationship of the model's descendants and itself. - * - * @param string $related - * @param string|null $foreignKey - * @param string|null $localKey - * @return \Staudenmeir\LaravelAdjacencyList\Eloquent\Relations\HasManyOfDescendants - */ - public function hasManyOfDescendantsAndSelf($related, $foreignKey = null, $localKey = null) - { - $instance = $this->newRelatedInstance($related); - - $foreignKey = $foreignKey ?: $this->getForeignKey(); - - $localKey = $localKey ?: $this->getKeyName(); - - return $this->newHasManyOfDescendants( - $instance->newQuery(), - $this, - $instance->qualifyColumn($foreignKey), - $localKey, - true - ); - } - - /** - * Instantiate a new HasManyOfDescendants relationship. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Model $parent - * @param string $foreignKey - * @param string $localKey - * @param bool $andSelf - * @return \Staudenmeir\LaravelAdjacencyList\Eloquent\Relations\HasManyOfDescendants - */ - protected function newHasManyOfDescendants(Builder $query, Model $parent, $foreignKey, $localKey, $andSelf) - { - return new HasManyOfDescendants($query, $parent, $foreignKey, $localKey, $andSelf); - } - - /** - * Define a many-to-many relationship of the model's descendants. - * - * @param string $related - * @param string|null $table - * @param string|null $foreignPivotKey - * @param string|null $relatedPivotKey - * @param string|null $parentKey - * @param string|null $relatedKey - * @return \Staudenmeir\LaravelAdjacencyList\Eloquent\Relations\BelongsToManyOfDescendants - */ - public function belongsToManyOfDescendants( - $related, - $table = null, - $foreignPivotKey = null, - $relatedPivotKey = null, - $parentKey = null, - $relatedKey = null - ) { - $instance = $this->newRelatedInstance($related); - - $foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey(); - - $relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey(); - - if (is_null($table)) { - $table = $this->joiningTable($related, $instance); - } - - return $this->newBelongsToManyOfDescendants( - $instance->newQuery(), - $this, - $table, - $foreignPivotKey, - $relatedPivotKey, - $parentKey ?: $this->getKeyName(), - $relatedKey ?: $instance->getKeyName(), - false - ); - } - - /** - * Define a many-to-many relationship of the model's descendants and itself. - * - * @param string $related - * @param string|null $table - * @param string|null $foreignPivotKey - * @param string|null $relatedPivotKey - * @param string|null $parentKey - * @param string|null $relatedKey - * @return \Staudenmeir\LaravelAdjacencyList\Eloquent\Relations\BelongsToManyOfDescendants - */ - public function belongsToManyOfDescendantsAndSelf( - $related, - $table = null, - $foreignPivotKey = null, - $relatedPivotKey = null, - $parentKey = null, - $relatedKey = null - ) { - $instance = $this->newRelatedInstance($related); - - $foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey(); - - $relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey(); - - if (is_null($table)) { - $table = $this->joiningTable($related, $instance); - } - - return $this->newBelongsToManyOfDescendants( - $instance->newQuery(), - $this, - $table, - $foreignPivotKey, - $relatedPivotKey, - $parentKey ?: $this->getKeyName(), - $relatedKey ?: $instance->getKeyName(), - true - ); - } - - /** - * Instantiate a new BelongsToManyOfDescendants relationship. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Model $parent - * @param string $table - * @param string $foreignPivotKey - * @param string $relatedPivotKey - * @param string $parentKey - * @param string $relatedKey - * @param bool $andSelf - * @return \Staudenmeir\LaravelAdjacencyList\Eloquent\Relations\BelongsToManyOfDescendants - */ - protected function newBelongsToManyOfDescendants( - Builder $query, - Model $parent, - $table, - $foreignPivotKey, - $relatedPivotKey, - $parentKey, - $relatedKey, - $andSelf - ) { - return new BelongsToManyOfDescendants( - $query, - $parent, - $table, - $foreignPivotKey, - $relatedPivotKey, - $parentKey, - $relatedKey, - $andSelf - ); - } - - /** - * Define a polymorphic many-to-many relationship of the model's descendants. - * - * @param string $related - * @param string $name - * @param string|null $table - * @param string|null $foreignPivotKey - * @param string|null $relatedPivotKey - * @param string|null $parentKey - * @param string|null $relatedKey - * @return \Staudenmeir\LaravelAdjacencyList\Eloquent\Relations\MorphToManyOfDescendants - */ - public function morphToManyOfDescendants( - $related, - $name, - $table = null, - $foreignPivotKey = null, - $relatedPivotKey = null, - $parentKey = null, - $relatedKey = null - ) { - $instance = $this->newRelatedInstance($related); - - $foreignPivotKey = $foreignPivotKey ?: $name.'_id'; - - $relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey(); - - if (! $table) { - $words = preg_split('/(_)/u', $name, -1, PREG_SPLIT_DELIM_CAPTURE); - - $lastWord = array_pop($words); - - $table = implode('', $words).Str::plural($lastWord); - } - - return $this->newMorphToManyOfDescendants( - $instance->newQuery(), - $this, - $name, - $table, - $foreignPivotKey, - $relatedPivotKey, - $parentKey ?: $this->getKeyName(), - $relatedKey ?: $instance->getKeyName(), - false - ); - } - - /** - * Define a polymorphic many-to-many relationship of the model's descendants and itself. - * - * @param string $related - * @param string $name - * @param string|null $table - * @param string|null $foreignPivotKey - * @param string|null $relatedPivotKey - * @param string|null $parentKey - * @param string|null $relatedKey - * @return \Staudenmeir\LaravelAdjacencyList\Eloquent\Relations\MorphToManyOfDescendants - */ - public function morphToManyOfDescendantsAndSelf( - $related, - $name, - $table = null, - $foreignPivotKey = null, - $relatedPivotKey = null, - $parentKey = null, - $relatedKey = null - ) { - $instance = $this->newRelatedInstance($related); - - $foreignPivotKey = $foreignPivotKey ?: $name.'_id'; - - $relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey(); - - if (! $table) { - $words = preg_split('/(_)/u', $name, -1, PREG_SPLIT_DELIM_CAPTURE); - - $lastWord = array_pop($words); - - $table = implode('', $words).Str::plural($lastWord); - } - - return $this->newMorphToManyOfDescendants( - $instance->newQuery(), - $this, - $name, - $table, - $foreignPivotKey, - $relatedPivotKey, - $parentKey ?: $this->getKeyName(), - $relatedKey ?: $instance->getKeyName(), - true - ); - } - - /** - * Instantiate a new MorphToManyOfDescendants relationship. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Model $parent - * @param string $name - * @param string $table - * @param string $foreignPivotKey - * @param string $relatedPivotKey - * @param string $parentKey - * @param string $relatedKey - * @param bool $andSelf - * @return \Staudenmeir\LaravelAdjacencyList\Eloquent\Relations\MorphToManyOfDescendants - */ - protected function newMorphToManyOfDescendants( - Builder $query, - Model $parent, - $name, - $table, - $foreignPivotKey, - $relatedPivotKey, - $parentKey, - $relatedKey, - $andSelf - ) { - return new MorphToManyOfDescendants( - $query, - $parent, - $name, - $table, - $foreignPivotKey, - $relatedPivotKey, - $parentKey, - $relatedKey, - $andSelf - ); - } - /** * Get the first segment of the model's path. * diff --git a/src/Eloquent/Relations/IsOfDescendantsRelation.php b/src/Eloquent/Relations/IsOfDescendantsRelation.php index 2253a21..2450b8c 100644 --- a/src/Eloquent/Relations/IsOfDescendantsRelation.php +++ b/src/Eloquent/Relations/IsOfDescendantsRelation.php @@ -4,6 +4,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Str; use Staudenmeir\LaravelAdjacencyList\Query\Grammars\ExpressionGrammar; @@ -107,25 +108,18 @@ protected function buildDictionary(Collection $results) { $dictionary = []; + $paths = explode( + $this->getPathListSeparator(), + $results[0]->{$this->pathListAlias} + ); + $foreignKeyName = $this->getEagerLoadingForeignKeyName(); $accessor = $this->getEagerLoadingAccessor(); - $pathSeparator = $this->parent->getPathSeparator(); - $pathListSeparator = $this->getPathListSeparator(); foreach ($results as $result) { - $paths = explode($pathListSeparator, $result->{$this->pathListAlias}); - - $foreignKey = (string) ($accessor ? $result->$accessor : $result)->$foreignKeyName; - foreach ($paths as $path) { - $isDescendant = Str::endsWith($path, $pathSeparator.$foreignKey); - - if ($this->andSelf) { - if (!$isDescendant && $path !== $foreignKey) { - continue; - } - } elseif (!$isDescendant) { + if (!$this->pathMatches($result, $foreignKeyName, $accessor, $pathSeparator, $path)) { continue; } @@ -156,6 +150,33 @@ protected function buildDictionary(Collection $results) return $dictionary; } + /** + * Determine whether a path belongs to a result. + * + * @param \Illuminate\Database\Eloquent\Model $result + * @param string $foreignKeyName + * @param string $accessor + * @param string $pathSeparator + * @param string $path + * @return bool + */ + protected function pathMatches(Model $result, $foreignKeyName, $accessor, $pathSeparator, $path) + { + $foreignKey = (string) ($accessor ? $result->$accessor : $result)->$foreignKeyName; + + $isDescendant = Str::endsWith($path, $pathSeparator.$foreignKey); + + if ($this->andSelf) { + if ($isDescendant || $path === $foreignKey) { + return true; + } + } elseif ($isDescendant) { + return true; + } + + return false; + } + /** * Get the local key name for an eager load of the relation. *