理解laravel的管道

当我们搬开别人架下的绊脚石时,也许恰恰是在为自己铺路。

Basically, using laravel pipelines you can pass an object between several classes in a fluid way to perform any type of task and finally return the resulting value once all the “tasks” have been executed.

The most clear example about how pipelines works resides in one of the most used components of the framework itself. I’m talking about middlewares.

Middleware provide a convenient mechanism for filtering HTTP requests entering your application… This is how a basic middleware looks like:

1
2
3
4
5
6
7
8
<?php
app(Pipeline::class)
->send($content)
->through($pipes)
->via(‘customMethodName’) // <---- This one :)
->then(function ($content) {
return Post::create(['content' => $content]);
});

These “middlewares” are in fact just pipes by where the request is going to be sent thru, in order to perform any needed task. Here you can check if the request is an HTTP request, a JSON request, if there is any user authenticated, etc.

If you take a quick look to the Illuminate\Foundation\Http\Kernel class, you’ll see how the middlewares are executed by using a new instance of the Pipeline class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
/**
* Send the given request through the middleware / router.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
$this->bootstrap();
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}

You can read in the code something like: a new pipeline that sends the request through a list of middlewares and then dispatch the router.

Don’t worry if this seems a little overwhelmed to you. Let’s try to clarify the concept with the follow example.

Working on a class that requires to run multiple tasks

Consider this situation. Let’s say you are building a forum where people can create threads and leave comments. But your client ask you to auto-remove tags or edit them on every piece of content when it’s created.

So this is what you are asked to do:

  • Replace link tags with plain text.
  • Replace bad words with “*”
  • Remove script tags entirely from the content

Probably you end up creating classes to handle each of these “tasks”.

1
2
3
4
5
6

$pipes = [
RemoveBadWords::class
ReplaceLinkTags::clas
RemoveScriptTags::class
];

What we are going to do is to pass the given “content” to each task and then return the result to the next one. We can do this using a pipeline.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
public function create(Request $request)
{
$pipes = [
RemoveBadWords::class,
ReplaceLinkTags::clas,
RemoveScriptTags::class
];
$post = app(Pipeline::class)
->send($request->content)
->through($pipes)
->then(function ($content) {
return Post::create(['content' => 'content']);
});
// return any type of response
}

Each “task” class should have a “handle” method to perform the action. Maybe it would be a good idea to have a contract to be implemented by each class:

1
2
3
4
5
6
7
<?php
namespace App;
use Closure;
interface Pipe
{
public function handle($content, Closure $next);
}
1
2
3
4
5
6
7
8
9
10
11
12
<?php
namespace App;
use Closure;
class RemoveBadWords implements Pipe
{
public function handle($content, Closure $next)
{
// Here you perform the task and return the updated $content
// to the next pipe
return $next($content);
}
}

The method used to perform the task should receive two parameters, the first one would be the passable object, and the second one would be a closure where the object is going to be redirected to after running the last pipe.

You can use a custom method name instead of ‘handle’. Then you need to specify the method name to be used by the pipeline, like so

1
2
3
4
5
6
7
8
<?php
app(Pipeline::class)
->send($content)
->through($pipes)
->via(‘customMethodName’) // <---- This one :)
->then(function ($content) {
return Post::create(['content' => $content]);
});

What happens at the end ?

What should happen here is that the post content is going to be modified by each one of the $pipes and at the end, this resulting content is going to be stored.

1
2
3
4
5
6
$post = app(Pipeline::class)
->send($request->all())
->through($pipes)
->then(function ($content) {
return Post::create(['content' => $content]);
});

Final words

Remember, there are tons of ways you can approach this type of issues. What you decide to do it’s up to you. But it is good to know that you have this tool in your arsenal to be used if necessary. I hope this example gives you a better understanding of what these “laravel pipelines” are and how to use them. You can also take a look at api laravel documents if you want to know more about how this

https://jeffochoa.me/understanding-laravel-pipelines