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); 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() * 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()` 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 ## Important Notes
- **Simple Names Only**: Always use simple class names (`Contact_Model`), never FQCNs - **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 The morph map uses simple class names (e.g., "Contact_Model") not fully
qualified names, matching how RSX models work throughout the framework. 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 FORM HANDLING
Client-Side Format Client-Side Format