Skip to content
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

[5.7] Add thenReturn method to Pipeline #27429

Merged
merged 1 commit into from
Feb 6, 2019
Merged

[5.7] Add thenReturn method to Pipeline #27429

merged 1 commit into from
Feb 6, 2019

Conversation

timacdonald
Copy link
Member

@timacdonald timacdonald commented Feb 6, 2019

Often when using a pipeline there is additional work to be done to the object passed through. A good example of this is using the Pipeline to apply additional filters to a query builder, perhaps based off a request's query parameters. Current you could do the following...

$users = app(Pipeline::class)
    ->send(User::query())
    ->through($pipes)
    ->then(function ($query) {
        return $query
            ->whereConfirmedEmail()
            ->whereAccountActive()
            ->get();
    });

or...

app(Pipeline::class)
    ->send($query = User::query())
    ->through($pipes)
    ->then(function () {});

$users = $query->whereConfirmedEmail()->whereAccountActive()->get();

or...

$query = User::whereConfirmedEmail()->whereAccountActive();

$users = app(Pipeline::class)
    ->send($query)
    ->through($pipes)
    ->then(function ($query) {
        return $query->get();
    });

or you could merge in a closure based pipe, and so on.

Adding a helper method to return the $passable allows for continued chaining in the same scope, so the above examples become...

$users = app(Pipeline::class)
    ->send(User::query())
    ->through($pipes)
    ->thenReturn()
    ->whereConfirmedEmail()
    ->whereAccountActive()
    ->get();

which I personally think feels nice when simple running the pipeline does not complete the preparation/manipulation of the $passable.

There are also scenarios where you just wanna run something through a pipeline and you don't need to do any additional work to it...

return app(Pipeline::class)
    ->send($thing)
    ->through($pipes)
    // ->then(function ($thing) {
    //     return $thing;
    // });
    ->thenReturn();

Although I have named the method thenReturn, it may not be the best name for this. I thought run might also be an option, as you are running the passable through the pipeline.

app(Pipeline::class)
    ->send($thing)
    ->through($pipes)
    ->run()
    ...

@taylorotwell taylorotwell merged commit 1af1348 into laravel:5.7 Feb 6, 2019
@torkiljohnsen
Copy link

@timacdonald Nice addition! I would be interested in seeing how you use pipeline to build a query. More of a full example if you can? Always found this part tedious and I am currently looking for alternatives to my "IndexQueryBuilder" solution, which I am not happy with.

@timacdonald
Copy link
Member Author

timacdonald commented Feb 13, 2019

Hey @torkiljohnsen I am using a variation of what @davidhemphill introduced me to during his LaraconAU talk last year. You should check it out because his way of handling it a lot simpler than mine, and it is a really nice pattern (plus it is just a really good talk): https://www.youtube.com/watch?v=dd-iHhmSi2k

I've altered my approach slightly so that instead of passing the request through the stack, I pass the query through. Hoping to get a chance to write it up in more detail, but here is what I end up with. Hopefully it makes sense with just this code dump haha...

Controller

class TeamController
{
    public function index(TeamRequest $request)
    {
        $teams = pipe(Team::query())
            ->through($request->queryFilters())
            ->thenReturn()
            ->orderBy('name')
            ->withCount('members')
            ->simplePaginate(50);

        // ...
    }
}

Request

class TeamRequest extends Request
{
    protected $queryFilters = [
        \App\Http\Filters\TeamIsFullFilter::class,
        \App\Http\Filters\TeamIsPublicFilter::class,
    ];
}

Query filter

class TeamIsPublicFilter
{
    private $request;

    public function __construct($request)
    {
        $this->request = $request;
    }

    public function handle($query, $next)
    {
        if ($this->request->filled('is_public')) {
            $query->wherePublic((bool) $this->request->query('is_public'));
        }

        return $next($query);
    }
}

Then everything below this point is just the infrastructure I setup to allow it all to come together...

Global pipe helper

function pipe($passable)
{
    return app(Pipeline::class)->send($passable);
}

Base request

abstract class Request extends FormRequest
{
    protected $queryFilters = [];

    public function queryFilters()
    {
        return collect($this->queryFilters)->map(function ($class) {
            return new $class($this);
        });
    }

    public function authorize()
    {
        return true;
    }

    public function rules()
    {
        return [];
    }
}

@timacdonald
Copy link
Member Author

timacdonald commented Feb 13, 2019

If ya have any questions after seeing all this, feel free to hit me up 👍

@timacdonald
Copy link
Member Author

I should mention that if you want a package to throw at the problem you could check out https://github.com/spatie/laravel-query-builder

@torkiljohnsen
Copy link

Awesome stuff, will look into those things! Thanks a lot 👍

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.

3 participants