laravel模块之管道

前言:laravel模块解读,测试是最好的解读

管道pipeline

  • pipeline
    • Hub.php 管道枢纽,只是包了一层,用在服务提供者
    • Pipeline.php 管道
    • PipelineServiceProvider.php 服务提供者

实现的很精简,精髓就是一个 array_reduce方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
<?php

namespace Illuminate\Pipeline;

use Closure;
use RuntimeException;
use Illuminate\Http\Request;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Support\Responsable;
use Illuminate\Contracts\Pipeline\Pipeline as PipelineContract;

class Pipeline implements PipelineContract
{
/**
* The container implementation.
*
* @var \Illuminate\Contracts\Container\Container
*/
protected $container;

/**
* The object being passed through the pipeline.
*
* @var mixed
*/
protected $passable;

/**
* The array of class pipes.
*
* @var array
*/
protected $pipes = [];

/**
* The method to call on each pipe.
*
* @var string
*/
protected $method = 'handle';

/**
* Create a new class instance.
*
* @param \Illuminate\Contracts\Container\Container|null $container
* @return void
*/
public function __construct(Container $container = null)
{
$this->container = $container;
}

/**
* Set the object being sent through the pipeline.
*
* @param mixed $passable
* @return $this
*/
public function send($passable)
{
$this->passable = $passable;

return $this;
}

/**
* Set the array of pipes.
*
* @param array|mixed $pipes
* @return $this
*/
public function through($pipes)
{
$this->pipes = is_array($pipes) ? $pipes : func_get_args();

return $this;
}

/**
* Set the method to call on the pipes.
*
* @param string $method
* @return $this
*/
public function via($method)
{
$this->method = $method;

return $this;
}

/**
* Run the pipeline with a final destination callback.
*
* @param \Closure $destination
* @return mixed
*/
public function then(Closure $destination)
{
$pipeline = array_reduce(
array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
);

return $pipeline($this->passable);
}

/**
* Get the final piece of the Closure onion.
*
* @param \Closure $destination
* @return \Closure
*/
protected function prepareDestination(Closure $destination)
{
return function ($passable) use ($destination) {
return $destination($passable);
};
}

/**
* Get a Closure that represents a slice of the application onion.
*
* @return \Closure
*/
protected function carry()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
if (is_callable($pipe)) {
// If the pipe is an instance of a Closure, we will just call it directly but
// otherwise we'll resolve the pipes out of the container and call it with
// the appropriate method and arguments, returning the results back out.
return $pipe($passable, $stack);
} elseif (! is_object($pipe)) {
list($name, $parameters) = $this->parsePipeString($pipe);

// If the pipe is a string we will parse the string and resolve the class out
// of the dependency injection container. We can then build a callable and
// execute the pipe function giving in the parameters that are required.
$pipe = $this->getContainer()->make($name);

$parameters = array_merge([$passable, $stack], $parameters);
} else {
// If the pipe is already an object we'll just make a callable and pass it to
// the pipe as-is. There is no need to do any extra parsing and formatting
// since the object we're given was already a fully instantiated object.
$parameters = [$passable, $stack];
}

$response = method_exists($pipe, $this->method)
? $pipe->{$this->method}(...$parameters)
: $pipe(...$parameters);

return $response instanceof Responsable
? $response->toResponse($this->container->make(Request::class))
: $response;
};
};
}

/**
* Parse full pipe string to get name and parameters.
*
* @param string $pipe
* @return array
*/
protected function parsePipeString($pipe)
{
list($name, $parameters) = array_pad(explode(':', $pipe, 2), 2, []);

if (is_string($parameters)) {
$parameters = explode(',', $parameters);
}

return [$name, $parameters];
}

/**
* Get the container instance.
*
* @return \Illuminate\Contracts\Container\Container
* @throws \RuntimeException
*/
protected function getContainer()
{
if (! $this->container) {
throw new RuntimeException('A container instance has not been passed to the Pipeline.');
}

return $this->container;
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
<?php
/**
* Created by PhpStorm.
* User: 小粽子
* Date: 2018/6/14
* Time: 21:46
*/

namespace Test\Pipeline;

use Illuminate\Pipeline\Pipeline;
use PHPUnit\Framework\TestCase;
use Closure;

class PipelineTest extends TestCase
{
public function testPipelineBasicUsage()
{
$pipeTwo = function ($piped, $next) {
$_SERVER['__test.pipe.two'] = $piped;

return $next($piped);
};

$result = (new Pipeline(new \Illuminate\Container\Container))
->send('foo')
->through([PipelineTestPipeOne::class, $pipeTwo])
->then(function ($piped) {
return $piped;
});
$this->assertEquals('foo', $result);
$this->assertEquals('foo', $_SERVER['__test.pipe.one']);
$this->assertEquals('foo', $_SERVER['__test.pipe.two']);

unset($_SERVER['__test.pipe.one']);
unset($_SERVER['__test.pipe.two']);
}

public function testPipe()
{
$pipe1 = function ($poster, Closure $next) {
$poster += 1;
$_SERVER['__test.pipe.one1'] = $poster;
$_SERVER['__test.pipe.one2'] = $next($poster);
return $next($poster);
};

$pipe2 = function ($poster, Closure $next) {
if ($poster > 7) {
return $poster;
}

$poster += 3;
$_SERVER['__test.pipe.two1'] = $poster;
$_SERVER['__test.pipe.two2'] = $next($poster);
return $next($poster);
};

$pipe3 = function ($poster, Closure $next) {
$result = $next($poster);
$_SERVER['__test.pipe.three1'] = $poster;
$_SERVER['__test.pipe.three2'] = $result;
$_SERVER['__test.pipe.three3'] = $result * 2;
return $result * 2;
};

$pipe4 = function ($poster, Closure $next) {
$poster += 2;
$_SERVER['__test.pipe.four1'] = $poster;
$_SERVER['__test.pipe.four2'] = $next($poster);
return $next($poster);
};

$pipes = [$pipe1, $pipe2, $pipe3, $pipe4];

function dispatcher($poster, $pipes)
{
return (new Pipeline())
->send($poster)
->through($pipes)
->then(function ($poster) {
return $poster;
});
}

$this->assertEquals(14, dispatcher(1, $pipes));
// dump($_SERVER['__test.pipe.one1']);
// dump($_SERVER['__test.pipe.one2']);
// dump($_SERVER['__test.pipe.two1']);
// dump($_SERVER['__test.pipe.two2']);
// dump($_SERVER['__test.pipe.three1']);
// dump($_SERVER['__test.pipe.three2']);
// dump($_SERVER['__test.pipe.three3']);
// dump($_SERVER['__test.pipe.four1']);
// dump($_SERVER['__test.pipe.four2']);
$this->assertEquals(8, dispatcher(7, $pipes));
}
}

class PipelineTestPipeOne
{
public function handle($piped, $next)
{
$_SERVER['__test.pipe.one'] = $piped;

return $next($piped);
}

public function differentMethod($piped, $next)
{
return $next($piped);
}
}

Laravel Pipeline 组件的实现原理