Add polymorphic join helpers (joinMorph, leftJoinMorph, rightJoinMorph)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-12-27 23:55:05 +00:00
parent 94dce4f021
commit 61fec79af0
3 changed files with 187 additions and 0 deletions

View File

@@ -191,6 +191,104 @@ class RestrictedEloquentBuilder extends Builder
return $this->whereIn($column, $values, 'or', true);
}
/**
* ==========================================
* POLYMORPHIC JOIN HELPERS
* ==========================================
*/
/**
* Add a polymorphic INNER JOIN to the query
*
* Joins a table with polymorphic columns (e.g., fileable_type, fileable_id)
* to the current model's table, automatically handling type_ref conversion.
*
* @param string $table The table containing the polymorphic columns
* @param string $morphName The morph column prefix (e.g., 'fileable' for fileable_type/fileable_id)
* @param string|null $morphClass The class name to match. If null, uses current model's class.
* @return $this
*
* @example
* // Get contacts with their attachments
* Contact_Model::query()
* ->joinMorph('file_attachments', 'fileable')
* ->get();
*
* // Explicit class (when joining from a different model)
* SomeModel::query()
* ->joinMorph('file_attachments', 'fileable', Contact_Model::class)
* ->get();
*/
public function joinMorph(string $table, string $morphName, ?string $morphClass = null): self
{
return $this->_addMorphJoin('join', $table, $morphName, $morphClass);
}
/**
* Add a polymorphic LEFT JOIN to the query
*
* @param string $table The table containing the polymorphic columns
* @param string $morphName The morph column prefix (e.g., 'fileable')
* @param string|null $morphClass The class name to match. If null, uses current model's class.
* @return $this
*
* @example
* // Get all contacts, with attachments if they exist
* Contact_Model::query()
* ->leftJoinMorph('file_attachments', 'fileable')
* ->get();
*/
public function leftJoinMorph(string $table, string $morphName, ?string $morphClass = null): self
{
return $this->_addMorphJoin('leftJoin', $table, $morphName, $morphClass);
}
/**
* Add a polymorphic RIGHT JOIN to the query
*
* @param string $table The table containing the polymorphic columns
* @param string $morphName The morph column prefix (e.g., 'fileable')
* @param string|null $morphClass The class name to match. If null, uses current model's class.
* @return $this
*/
public function rightJoinMorph(string $table, string $morphName, ?string $morphClass = null): self
{
return $this->_addMorphJoin('rightJoin', $table, $morphName, $morphClass);
}
/**
* Internal helper to add a polymorphic join
*
* @param string $joinMethod The join method to use (join, leftJoin, rightJoin)
* @param string $table The table containing the polymorphic columns
* @param string $morphName The morph column prefix
* @param string|null $morphClass The class name to match
* @return $this
*/
protected function _addMorphJoin(string $joinMethod, string $table, string $morphName, ?string $morphClass): self
{
// Determine the class name to use
if ($morphClass === null) {
// Use the current model's simple class name
$morphClass = class_basename($this->getModel());
} else {
// Extract simple class name if FQCN was provided
$morphClass = class_basename($morphClass);
}
// Get the type_ref ID for the class
$typeRefId = Type_Ref_Registry::class_to_id($morphClass);
// Get the current model's table name
$baseTable = $this->getModel()->getTable();
// Build the join
return $this->$joinMethod($table, function ($join) use ($table, $baseTable, $morphName, $typeRefId) {
$join->on("{$table}.{$morphName}_id", '=', "{$baseTable}.id")
->where("{$table}.{$morphName}_type", '=', $typeRefId);
});
}
/**
* Prevent eager loading via with()
*

View File

@@ -108,6 +108,35 @@ File_Attachment_Model::where('fileable_type', 42)->get();
Supported methods: `where()`, `orWhere()`, `whereIn()`, `orWhereIn()`, `whereNotIn()`, `orWhereNotIn()`
## Polymorphic Join Helpers
Join tables with polymorphic columns using dedicated helpers:
```php
// Get contacts that have attachments (INNER JOIN)
Contact_Model::query()
->joinMorph('file_attachments', 'fileable')
->select('contacts.*', 'file_attachments.filename')
->get();
// Get all contacts, with attachments if they exist (LEFT JOIN)
Contact_Model::query()
->leftJoinMorph('file_attachments', 'fileable')
->get();
// Explicit class (when querying from a different model)
SomeModel::query()
->leftJoinMorph('file_attachments', 'fileable', Contact_Model::class)
->get();
```
Available methods: `joinMorph()`, `leftJoinMorph()`, `rightJoinMorph()`
Parameters:
- `$table` - Table with polymorphic columns (e.g., `'file_attachments'`)
- `$morphName` - Column prefix (e.g., `'fileable'` for `fileable_type`/`fileable_id`)
- `$morphClass` - Optional class name (defaults to current model)
## Important Notes
- **Simple Names Only**: Always use simple class names (`Contact_Model`), never FQCNs

View File

@@ -130,6 +130,66 @@ LARAVEL MORPH MAP INTEGRATION
The morph map uses simple class names (e.g., "Contact_Model") not fully
qualified names, matching how RSX models work throughout the framework.
QUERY BUILDER INTEGRATION
Transparent WHERE Clauses
The query builder automatically converts type_ref columns in WHERE
clauses. You can use class name strings directly:
// All of these work - class names auto-converted to IDs
File_Attachment_Model::where('fileable_type', 'Contact_Model')->get();
File_Attachment_Model::where('fileable_type', '=', 'Contact_Model')->get();
File_Attachment_Model::where(['fileable_type' => 'Contact_Model'])->get();
// whereIn also works
File_Attachment_Model::whereIn('fileable_type', [
'Contact_Model',
'Project_Model'
])->get();
// Integer IDs still work (pass-through)
File_Attachment_Model::where('fileable_type', 42)->get();
Supported methods: where(), orWhere(), whereIn(), orWhereIn(),
whereNotIn(), orWhereNotIn()
Polymorphic Join Helpers
Join tables with polymorphic columns using dedicated helpers:
// Get contacts that have attachments (INNER JOIN)
Contact_Model::query()
->joinMorph('file_attachments', 'fileable')
->select('contacts.*', 'file_attachments.filename')
->get();
// Get all contacts, with attachments if they exist (LEFT JOIN)
Contact_Model::query()
->leftJoinMorph('file_attachments', 'fileable')
->get();
// RIGHT JOIN
Contact_Model::query()
->rightJoinMorph('file_attachments', 'fileable')
->get();
Parameters:
$table - Table with polymorphic columns (e.g., 'file_attachments')
$morphName - Column prefix (e.g., 'fileable' for fileable_type/fileable_id)
$morphClass - Optional class name (defaults to current model)
Explicit class (when querying from a different model context):
SomeModel::query()
->leftJoinMorph('file_attachments', 'fileable', Contact_Model::class)
->get();
The generated SQL is equivalent to:
LEFT JOIN file_attachments
ON file_attachments.fileable_id = contacts.id
AND file_attachments.fileable_type = <type_ref_id>
FORM HANDLING
Client-Side Format