Add transparent type_ref conversion in WHERE clauses
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
namespace App\Database;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use App\RSpade\Core\Database\TypeRefs\Type_Ref_Registry;
|
||||
|
||||
/**
|
||||
* Custom Eloquent query builder that prevents eager loading and unsafe operations
|
||||
@@ -10,9 +11,186 @@ use Illuminate\Database\Eloquent\Builder;
|
||||
* This builder overrides dangerous methods to enforce RSpade framework safety rules:
|
||||
* - All eager loading methods throw exceptions (with/withCount/etc)
|
||||
* - DELETE without WHERE clause throws exception to prevent accidental mass deletion
|
||||
* - Automatic type_ref column conversion in WHERE clauses
|
||||
*/
|
||||
class RestrictedEloquentBuilder extends Builder
|
||||
{
|
||||
/**
|
||||
* Cached list of type_ref columns for this builder's model
|
||||
* @var array|null
|
||||
*/
|
||||
protected $_type_ref_columns_cache = null;
|
||||
|
||||
/**
|
||||
* Get the type_ref columns for the current model
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function _get_type_ref_columns(): array
|
||||
{
|
||||
if ($this->_type_ref_columns_cache === null) {
|
||||
$model = $this->getModel();
|
||||
$reflection = new \ReflectionClass($model);
|
||||
|
||||
// Access protected static property
|
||||
if ($reflection->hasProperty('type_ref_columns')) {
|
||||
$property = $reflection->getProperty('type_ref_columns');
|
||||
$property->setAccessible(true);
|
||||
$this->_type_ref_columns_cache = $property->getValue() ?: [];
|
||||
} else {
|
||||
$this->_type_ref_columns_cache = [];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->_type_ref_columns_cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a column is a type_ref column
|
||||
*
|
||||
* @param string $column
|
||||
* @return bool
|
||||
*/
|
||||
protected function _is_type_ref_column(string $column): bool
|
||||
{
|
||||
return in_array($column, $this->_get_type_ref_columns());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a type_ref value (class name string) to its integer ID
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
*/
|
||||
protected function _convert_type_ref_value($value)
|
||||
{
|
||||
if ($value === null || $value === '') {
|
||||
return $value;
|
||||
}
|
||||
|
||||
// If already an integer, return as-is
|
||||
if (is_int($value) || (is_string($value) && ctype_digit($value))) {
|
||||
return (int) $value;
|
||||
}
|
||||
|
||||
// Convert class name to ID
|
||||
return Type_Ref_Registry::class_to_id((string) $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override where() to auto-convert type_ref columns
|
||||
*
|
||||
* @param \Closure|string|array $column
|
||||
* @param mixed $operator
|
||||
* @param mixed $value
|
||||
* @param string $boolean
|
||||
* @return $this
|
||||
*/
|
||||
public function where($column, $operator = null, $value = null, $boolean = 'and')
|
||||
{
|
||||
// Handle closure form - no conversion needed
|
||||
if ($column instanceof \Closure) {
|
||||
return parent::where($column, $operator, $value, $boolean);
|
||||
}
|
||||
|
||||
// Handle array form: ['column' => 'value']
|
||||
if (is_array($column)) {
|
||||
$converted = [];
|
||||
foreach ($column as $col => $val) {
|
||||
if (is_string($col) && $this->_is_type_ref_column($col)) {
|
||||
$converted[$col] = $this->_convert_type_ref_value($val);
|
||||
} else {
|
||||
$converted[$col] = $val;
|
||||
}
|
||||
}
|
||||
return parent::where($converted, $operator, $value, $boolean);
|
||||
}
|
||||
|
||||
// Handle 2-arg form: where('column', 'value') -> operator is actually value
|
||||
// Handle 3-arg form: where('column', '=', 'value')
|
||||
if (is_string($column) && $this->_is_type_ref_column($column)) {
|
||||
if (func_num_args() === 2) {
|
||||
// 2-arg form: $operator is actually the value
|
||||
$operator = $this->_convert_type_ref_value($operator);
|
||||
} else {
|
||||
// 3+ arg form: $value is the value
|
||||
$value = $this->_convert_type_ref_value($value);
|
||||
}
|
||||
}
|
||||
|
||||
return parent::where($column, $operator, $value, $boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override orWhere() to auto-convert type_ref columns
|
||||
*
|
||||
* @param \Closure|string|array $column
|
||||
* @param mixed $operator
|
||||
* @param mixed $value
|
||||
* @return $this
|
||||
*/
|
||||
public function orWhere($column, $operator = null, $value = null)
|
||||
{
|
||||
return $this->where($column, $operator, $value, 'or');
|
||||
}
|
||||
|
||||
/**
|
||||
* Override whereIn() to auto-convert type_ref columns
|
||||
*
|
||||
* @param string $column
|
||||
* @param mixed $values
|
||||
* @param string $boolean
|
||||
* @param bool $not
|
||||
* @return $this
|
||||
*/
|
||||
public function whereIn($column, $values, $boolean = 'and', $not = false)
|
||||
{
|
||||
if (is_string($column) && $this->_is_type_ref_column($column)) {
|
||||
if (is_array($values)) {
|
||||
$values = array_map(fn($v) => $this->_convert_type_ref_value($v), $values);
|
||||
}
|
||||
}
|
||||
|
||||
return parent::whereIn($column, $values, $boolean, $not);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override orWhereIn() to auto-convert type_ref columns
|
||||
*
|
||||
* @param string $column
|
||||
* @param mixed $values
|
||||
* @return $this
|
||||
*/
|
||||
public function orWhereIn($column, $values)
|
||||
{
|
||||
return $this->whereIn($column, $values, 'or');
|
||||
}
|
||||
|
||||
/**
|
||||
* Override whereNotIn() to auto-convert type_ref columns
|
||||
*
|
||||
* @param string $column
|
||||
* @param mixed $values
|
||||
* @param string $boolean
|
||||
* @return $this
|
||||
*/
|
||||
public function whereNotIn($column, $values, $boolean = 'and')
|
||||
{
|
||||
return $this->whereIn($column, $values, $boolean, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override orWhereNotIn() to auto-convert type_ref columns
|
||||
*
|
||||
* @param string $column
|
||||
* @param mixed $values
|
||||
* @return $this
|
||||
*/
|
||||
public function orWhereNotIn($column, $values)
|
||||
{
|
||||
return $this->whereIn($column, $values, 'or', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent eager loading via with()
|
||||
*
|
||||
|
||||
@@ -89,12 +89,32 @@ The `Type_Ref_Registry::register_morph_map()` method is called during framework
|
||||
$model->attachable_type = class_basename($related);
|
||||
```
|
||||
|
||||
## Query Builder Integration
|
||||
|
||||
The `RestrictedEloquentBuilder` automatically converts type_ref columns in WHERE clauses:
|
||||
|
||||
```php
|
||||
// All of these work transparently - 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()`
|
||||
|
||||
## Important Notes
|
||||
|
||||
- **Simple Names Only**: Always use simple class names (`Contact_Model`), never FQCNs
|
||||
- **Auto-Registration**: New classes are auto-registered when first used
|
||||
- **Transparent**: After setup, code works identically to VARCHAR storage
|
||||
- **Laravel Compatible**: Works with `morphTo()`, `morphMany()`, etc.
|
||||
- **Query Logs**: Show integer IDs, not class names (expected behavior)
|
||||
|
||||
## Reference
|
||||
|
||||
|
||||
Reference in New Issue
Block a user