RFC Segregate Eloquent Relation Definition Maravel-Framework 10.65
marius-ciclistu

marius-ciclistu @marius-ciclistu

About: Creator of MaravelQL Definer of Equivalent constant engine torque Creator of maravel-rest-wizard / laravel-crud-wizard-free lib suites Creator of Maravel-Framework Maravel Maravelith

Location:
Romania
Joined:
Nov 17, 2025

RFC Segregate Eloquent Relation Definition Maravel-Framework 10.65

Publish Date: Mar 5
0 0


Maravel-Framework

Challenged by this discussion which proposes PHP attributes for the relation definition to avoid the method definition issues that arise for identifying if a method is relation or not in an active record , after this PR you can segregate the relation definition from the model’s methods without using Reflection :

// model
    protected function segregatedRelationsDefinitionMap(): array
    {
       return [
           'relName' => fn(): HasOne => $this->hasOne(Model::class, 'model_id', 'id'),
           // Reuse the segregatedrelation inside another segregated relation:
           'relNameScoped' => fn(): HasOne => $this->relName()->where('col', '=', 'text'),
           'relNameScoped2' => fn(): HasOne => $this->callSegregatedRelation('relName')->where('col', '=', 'text'),
           // Reuse the method relation:
           'relNameAsMethod' => $this->relNameAsMethod(...),
           'relNameAsMethod' => fn(): HasOne => $this->relNameAsMethod(),
           // AVOID THESE:
           'relNameAsMethod' => [$this, 'relNameAsMethod'],
           'relNameAsMethod' => fn(): HasOne => [$this, 'relNameAsMethod'](),
           // DO NOT USE IT LIKE THIS!:
           'relNameAsMethod' => fn(): HasOne => $this->relNameAsMethod(...)(), // executes the relation inside the map.
       ];
    }
Enter fullscreen mode Exit fullscreen mode

The old way of defining the relations in the model as methods will still work and will be auto-promoted into this new segregated logic on touch (call as property without the ()). If the method is called, it will not be auto-promoted.

If all the method relations are used in a request cycle before calling:

$model->segregatedRelationList(); // no reflection involved
Enter fullscreen mode Exit fullscreen mode

it will return a list with all.

To promote beforehand all method relations to this new segregation logic, you can call:

$model->segregatedRelationList(discoverMethods: true); // reflection involved
Enter fullscreen mode Exit fullscreen mode

Note that this will discover ONLY methods that DEFINE a return type instance of Relation!

If you want to use reflection on the method it self, the \ReflectionFunction can be retrieved via:

$model->getSegregatedRelationReflectionFunction('methodName') // returns null|\ReflectionFunction
Enter fullscreen mode Exit fullscreen mode

This will also auto-promote the method to this new logic. PHP attributes can be used on the method/callback and read this way.

The reflection usage is OPT IN! If the developer does not call the above methods, no reflection is involved.

This PR is a follow up for the laravel-crud-wizard-free / maravel-rest-wizard active record property segregation (BaseModel::initializeActiveRecordSegregationProperties).

    #OperationService example for BaseModelRelations
    public function someFunction(): void
    {
         // BaseModelRelations
         $this->model-r->relName; // has autocomplete - will retrieve the relation result
         $this->model-relName; // has autocomplete - will retrieve the relation result
         $this->model-r->relName(); // has autocomplete - will retrieve the Relation instance
         $this->model->relName(); // has autocomplete - will retrieve the Relation instance
    }
Enter fullscreen mode Exit fullscreen mode

The ->r skips the isRelation check and calls directly getRelationValue method.

In the above mentioned libs, a constant (array as list) is used to manually define the API exposed relations:

public const WITH_RELATIONS = [];
Enter fullscreen mode Exit fullscreen mode

This is used by them to check if a relation exists or not.

The segregatedRelationList(true) would contain more or equal methods than the above constant.

The segregatedRelationList(false) could contain more, equal or less methods than the above constant, depending on:

  • if ALL relations are defined inside segregatedRelationsDefinitionMap, then segregatedRelationList(true) === segregatedRelationList(false).
  • if NONE of the relations are defined inside segregatedRelationsDefinitionMap, then the result is dynamic based on the above.
  • if SOME relations are defined inside segregatedRelationsDefinitionMap, then the result will always contain the ones included and for the rest it will be dynamic based on the above.

The promotion happens ONLY ONCE per Model class, and is bound to each Model instance when used.

A small amout of memory will be needed for the 2 new cache properties introduced:

trait HasRelationships
{
    /**
     * The static cache of relationship blueprints across all model instances.
     * Stored statically so X instances share the same number of closures per model.
     * Structured as: [ModelFqn => [RelationName => Closure]]
     */
    private static array $segregatedRelationsGlobalMap = [];

    /**
     * The static cache for relation ReflectionFunction
     * Structured as: [ModelFqn => [RelationName => false|\ReflectionFunction]]
     */
    private static array $segregatedRelationsReflectionFunctionsMap = [];
Enter fullscreen mode Exit fullscreen mode

Update 2026.03.06
The Closures are cached unbound via:

    $closure->bindTo(null, static::class);
Enter fullscreen mode Exit fullscreen mode

and rebound temporarily only on call:

final public function callSegregatedRelation(string $method, array $parameters = []): Relation
{
    /**
     * Late Binding.
     * We take the shared closure and temporarily bind it to the current instance.
     * This allows the developer to use $this->hasOne(...) inside the closure.
     */
    return $this->resolveSegregatedRelationClosure($method)?->call($this, ...$parameters);
}
Enter fullscreen mode Exit fullscreen mode

avoiding memory bloat or restricting the garbage collector from deleting the model.

For existing projects that use maravel-rest-wizard and laravel-crud-wizard-free, to avoid method_exists calls, the auto-promotion can be fasten up by:

protected function segregatedRelationsDefinitionMap(): array
{
    $map = [
       // fill the relations not exposed via API
    ];

    foreach (static::WITH_RELATIONS as $relation) {
        $map[$relation] = $this->$relation(...);
    }

    return $map;
}
Enter fullscreen mode Exit fullscreen mode

Update 2026.03.07
The PR detaches the relations from models completely. This means that a relation can have ANY name as long as it is defined in the segregatedRelationsDefinitionMap method. To access a relation as method when it has the same name as another method or mutator, use

$model->r->save(); // returns Relation instance

$model->callSegregatedRelation('save'); // returns Relation instance

$model->r->save; // returns relation result(s)

$model->callSegregatedRelation('save')->getResults(); // returns relation result(s)
Enter fullscreen mode Exit fullscreen mode

External libs like maravel-rest-wizard and laravel-crud-wizard-free will still use the old version (method or attribute) for the relations and it will work when the relation does not have clashing name.

Comments 0 total

    Add comment