Skip to content

[9.x] Add pending has-many-through and has-one-through builder #45894

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

Merged
merged 7 commits into from
Feb 5, 2023

Conversation

timacdonald
Copy link
Member

@timacdonald timacdonald commented Feb 1, 2023

Alternative to and inspired by #45826

This PR takes a slightly different approach. Everything is just using standard HasMany or HasOne relationships with standard key ordering. Essentially there is no new concept to learn.

I also feel by starting the chain with "through" it personally helps my mental model of what is happening.

"From A through B give me C"

$a->through($a->b())->has(fn ($b) => $b->c());

HasManyThrough

Using the examples from the docs, and assuming we have the relationships already defined...

class Project extends Model
{
    public function environments()
    {
        return $this->hasMany(Environment::class);
    }
}

class Environment extends Model
{
    public function deployments()
    {
        return $this->hasMany(Deployment::class);
    }
}

class Deployment extends Model
{
    //
}

This allows us to use those relationships to define our has-many-through relationship on the Project:

class Project extends Model
{
    public function deployments()
    {
        return $this->through($this->environments())
            ->has(fn (Environment $env) => $env->deployments());
    }

    public function environments()
    {
        return $this->hasMany(Environment::class);
    }
}

The closure passed to the has() method receives an instance of the Environment model.

HasOneThrough

There is not a new API for this method. Using the example from the docs...

class Mechanic extends Model
{
    public function car()
    {
         return $this->hasOne(Car::class);
    }
}


class Car extends Model
{
    public function owner()
    {
         return $this->hasOne(Owner::class);
    }
}

class Owner extends Model
{
    //
}

We may use these existing relationships to create a hasOneThrough relationship.

class Mechanic extends Model
{
    public function owner()
    {
        return $this->through($this->car())
            ->has(fn (Car $car) => $car->owner());
    }

    public function car()
    {
         return $this->hasOne(Car::class);
    }
}

Creating relationships on the fly

These examples have all shown usage of exiting model relationships, but it is also possible to use this when relationships that you create on the fly. The additional keys are not required here, they are just there for illustration...

class Project extends Model
{
    public function deployments()
    {
        return $this->through($this->hasMany(Project::class, 'project_id', 'id'))
            ->has(fn (Environment $env) => $env->hasMany(Deployment::class, 'environment_id'));
    }
}
class Mechanic extends Model
{
    public function owner()
    {
        return $this->through($this->hasOne(Car::class, 'mechanic_id', 'id'))
            ->has(fn (Car $car) => $car->hasOne(Owner::class, 'car_id', 'id'));
    }
}

This means that the ordering or has many through disappears and all a developer needs to know is the standard parameters of HasMany and HasOne.

String / Higher order API

The following APIs are all equivalent.

public function deployments()
{
    // explicit...
    return $this->through($this->environments())->has(fn ($env) => $env->deployments());

    // stringy...
    return $this->through('environments')->has('deployments');

    // magic...
    return $this->throughEnvironments()->hasDeployments();
}

@timacdonald timacdonald marked this pull request as ready for review February 1, 2023 01:49
Comment on lines 2313 to 2316
if (Str::startsWith($method, 'through') && method_exists($this, $relationMethod = Str::of($method)->after('through')->lcfirst()->toString())) {
return $this->through($relationMethod);
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of this change, we should consider if it should target 9 or 10.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants