-
-
Notifications
You must be signed in to change notification settings - Fork 855
Add support for HasManyDeep relationships in EloquentDataTable #3262
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
base: master
Are you sure you want to change the base?
Changes from 2 commits
be709ca
b21f1de
61367b9
b6bfe43
00fbbda
b546989
f03c184
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -160,6 +160,229 @@ protected function isMorphRelation($relation) | |||||
| return $isMorph; | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Check if a relation is a HasManyDeep relationship. | ||||||
| * | ||||||
| * @param Relation $model | ||||||
|
||||||
| */ | ||||||
| protected function isHasManyDeep($model): bool | ||||||
| { | ||||||
| return class_exists('Staudenmeir\EloquentHasManyDeep\HasManyDeep') | ||||||
| && $model instanceof \Staudenmeir\EloquentHasManyDeep\HasManyDeep; | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Get the foreign key name for a HasManyDeep relationship. | ||||||
| * This is the foreign key on the final related table that points to the intermediate table. | ||||||
| * | ||||||
| * @param Relation $model | ||||||
|
||||||
| */ | ||||||
| protected function getHasManyDeepForeignKey($model): string | ||||||
| { | ||||||
| // Try to get from relationship definition using reflection | ||||||
| try { | ||||||
| $reflection = new \ReflectionClass($model); | ||||||
| if ($reflection->hasProperty('foreignKeys')) { | ||||||
| $property = $reflection->getProperty('foreignKeys'); | ||||||
| $property->setAccessible(true); | ||||||
| $foreignKeys = $property->getValue($model); | ||||||
|
|
||||||
| if (is_array($foreignKeys) && ! empty($foreignKeys)) { | ||||||
| // Get the last foreign key (for the final join) | ||||||
| $lastFK = end($foreignKeys); | ||||||
| if (is_string($lastFK) && str_contains($lastFK, '.')) { | ||||||
| $parts = explode('.', $lastFK); | ||||||
|
|
||||||
| return end($parts); | ||||||
|
||||||
| } | ||||||
|
|
||||||
| return $lastFK; | ||||||
| } | ||||||
| } | ||||||
| } catch (\Exception $e) { | ||||||
| // Fallback | ||||||
yajra marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| } | ||||||
|
|
||||||
| // Try to get the foreign key using common HasManyDeep methods | ||||||
| if (method_exists($model, 'getForeignKeyName')) { | ||||||
| return $model->getForeignKeyName(); | ||||||
| } | ||||||
|
|
||||||
| // HasManyDeep may use getQualifiedForeignKeyName() and extract the column | ||||||
| if (method_exists($model, 'getQualifiedForeignKeyName')) { | ||||||
| $qualified = $model->getQualifiedForeignKeyName(); | ||||||
yajra marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| $parts = explode('.', $qualified); | ||||||
|
|
||||||
| return end($parts); | ||||||
| } | ||||||
|
|
||||||
| // Fallback: try to infer from intermediate model | ||||||
| $intermediateTable = $this->getHasManyDeepIntermediateTable($model, ''); | ||||||
| if ($intermediateTable) { | ||||||
| // Assume the related table has a foreign key named {intermediate_table}_id | ||||||
| return $intermediateTable.'_id'; | ||||||
|
||||||
| } | ||||||
|
|
||||||
| // Final fallback: use the related model's key name | ||||||
| return $model->getRelated()->getKeyName(); | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Get the local key name for a HasManyDeep relationship. | ||||||
| * This is the local key on the intermediate table (or parent if no intermediate). | ||||||
| * | ||||||
| * @param Relation $model | ||||||
|
||||||
| */ | ||||||
| protected function getHasManyDeepLocalKey($model): string | ||||||
| { | ||||||
| // Try to get from relationship definition using reflection | ||||||
| try { | ||||||
| $reflection = new \ReflectionClass($model); | ||||||
| if ($reflection->hasProperty('localKeys')) { | ||||||
| $property = $reflection->getProperty('localKeys'); | ||||||
| $property->setAccessible(true); | ||||||
| $localKeys = $property->getValue($model); | ||||||
|
|
||||||
| if (is_array($localKeys) && ! empty($localKeys)) { | ||||||
| // Get the last local key (for the final join) | ||||||
| $lastLK = end($localKeys); | ||||||
| if (is_string($lastLK) && str_contains($lastLK, '.')) { | ||||||
| $parts = explode('.', $lastLK); | ||||||
|
|
||||||
| return end($parts); | ||||||
| } | ||||||
|
|
||||||
| return $lastLK; | ||||||
| } | ||||||
| } | ||||||
| } catch (\Exception $e) { | ||||||
| // Fallback | ||||||
| } | ||||||
yajra marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
|
||||||
| // Try to get the local key using common HasManyDeep methods | ||||||
| if (method_exists($model, 'getLocalKeyName')) { | ||||||
| return $model->getLocalKeyName(); | ||||||
| } | ||||||
|
|
||||||
| // HasManyDeep may use getQualifiedLocalKeyName() and extract the column | ||||||
| if (method_exists($model, 'getQualifiedLocalKeyName')) { | ||||||
| $qualified = $model->getQualifiedLocalKeyName(); | ||||||
| $parts = explode('.', $qualified); | ||||||
|
|
||||||
| return end($parts); | ||||||
| } | ||||||
|
|
||||||
| // Fallback: use the intermediate model's key name, or parent if no intermediate | ||||||
| $intermediateTable = $this->getHasManyDeepIntermediateTable($model, ''); | ||||||
| if ($intermediateTable) { | ||||||
| try { | ||||||
| $reflection = new \ReflectionClass($model); | ||||||
| if ($reflection->hasProperty('through')) { | ||||||
| $property = $reflection->getProperty('through'); | ||||||
| $property->setAccessible(true); | ||||||
| $through = $property->getValue($model); | ||||||
| if (is_array($through) && ! empty($through)) { | ||||||
| $firstThrough = is_string($through[0]) ? $through[0] : get_class($through[0]); | ||||||
| if (class_exists($firstThrough)) { | ||||||
| $throughModel = new $firstThrough; | ||||||
|
||||||
| $throughModel = new $firstThrough; | |
| $throughModel = app($firstThrough); |
Outdated
Copilot
AI
Nov 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Empty catch blocks silently swallow all exceptions. Consider logging the exception or adding a comment explaining why it's safe to ignore.
Outdated
Copilot
AI
Nov 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The @param type hint incorrectly states Relation $model but should be more specific like \Staudenmeir\EloquentHasManyDeep\HasManyDeep $model to accurately reflect the expected parameter type.
yajra marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
yajra marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
yajra marked this conversation as resolved.
Show resolved
Hide resolved
yajra marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
yajra marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
yajra marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
Outdated
Copilot
AI
Nov 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The @param type hint incorrectly states Relation $model but should be more specific like \Staudenmeir\EloquentHasManyDeep\HasManyDeep $model to accurately reflect the expected parameter type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The package
staudenmeir/eloquent-has-many-deepshould be added to therequiresection instead ofrequire-devsince it's used in the production code (EloquentDataTable.php). Therequire-devsection is for packages only needed during development and testing.