Pipeline.php 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. <?php
  2. namespace Illuminate\Pipeline;
  3. use Closure;
  4. use Illuminate\Contracts\Container\Container;
  5. use Illuminate\Contracts\Pipeline\Pipeline as PipelineContract;
  6. use Illuminate\Support\Traits\Conditionable;
  7. use Illuminate\Support\Traits\Macroable;
  8. use RuntimeException;
  9. use Throwable;
  10. class Pipeline implements PipelineContract
  11. {
  12. use Conditionable;
  13. use Macroable;
  14. /**
  15. * The container implementation.
  16. *
  17. * @var \Illuminate\Contracts\Container\Container|null
  18. */
  19. protected $container;
  20. /**
  21. * The object being passed through the pipeline.
  22. *
  23. * @var mixed
  24. */
  25. protected $passable;
  26. /**
  27. * The array of class pipes.
  28. *
  29. * @var array
  30. */
  31. protected $pipes = [];
  32. /**
  33. * The method to call on each pipe.
  34. *
  35. * @var string
  36. */
  37. protected $method = 'handle';
  38. /**
  39. * The final callback to be executed after the pipeline ends regardless of the outcome.
  40. *
  41. * @var \Closure|null
  42. */
  43. protected $finally;
  44. /**
  45. * Indicates whether to wrap the pipeline in a database transaction.
  46. *
  47. * @var string|null|\UnitEnum|false
  48. */
  49. protected $withinTransaction = false;
  50. /**
  51. * Create a new class instance.
  52. *
  53. * @param \Illuminate\Contracts\Container\Container|null $container
  54. */
  55. public function __construct(?Container $container = null)
  56. {
  57. $this->container = $container;
  58. }
  59. /**
  60. * Set the object being sent through the pipeline.
  61. *
  62. * @param mixed $passable
  63. * @return $this
  64. */
  65. public function send($passable)
  66. {
  67. $this->passable = $passable;
  68. return $this;
  69. }
  70. /**
  71. * Set the array of pipes.
  72. *
  73. * @param mixed $pipes
  74. * @return $this
  75. */
  76. public function through($pipes)
  77. {
  78. $this->pipes = is_array($pipes) ? $pipes : func_get_args();
  79. return $this;
  80. }
  81. /**
  82. * Push additional pipes onto the pipeline.
  83. *
  84. * @param mixed $pipes
  85. * @return $this
  86. */
  87. public function pipe($pipes)
  88. {
  89. array_push($this->pipes, ...(is_array($pipes) ? $pipes : func_get_args()));
  90. return $this;
  91. }
  92. /**
  93. * Set the method to call on the pipes.
  94. *
  95. * @param string $method
  96. * @return $this
  97. */
  98. public function via($method)
  99. {
  100. $this->method = $method;
  101. return $this;
  102. }
  103. /**
  104. * Run the pipeline with a final destination callback.
  105. *
  106. * @param \Closure $destination
  107. * @return mixed
  108. */
  109. public function then(Closure $destination)
  110. {
  111. $pipeline = array_reduce(
  112. array_reverse($this->pipes()), $this->carry(), $this->prepareDestination($destination)
  113. );
  114. try {
  115. return $this->withinTransaction !== false
  116. ? $this->getContainer()->make('db')->connection($this->withinTransaction)->transaction(fn () => $pipeline($this->passable))
  117. : $pipeline($this->passable);
  118. } finally {
  119. if ($this->finally) {
  120. ($this->finally)($this->passable);
  121. }
  122. }
  123. }
  124. /**
  125. * Run the pipeline and return the result.
  126. *
  127. * @return mixed
  128. */
  129. public function thenReturn()
  130. {
  131. return $this->then(function ($passable) {
  132. return $passable;
  133. });
  134. }
  135. /**
  136. * Set a final callback to be executed after the pipeline ends regardless of the outcome.
  137. *
  138. * @param \Closure $callback
  139. * @return $this
  140. */
  141. public function finally(Closure $callback)
  142. {
  143. $this->finally = $callback;
  144. return $this;
  145. }
  146. /**
  147. * Get the final piece of the Closure onion.
  148. *
  149. * @param \Closure $destination
  150. * @return \Closure
  151. */
  152. protected function prepareDestination(Closure $destination)
  153. {
  154. return function ($passable) use ($destination) {
  155. try {
  156. return $destination($passable);
  157. } catch (Throwable $e) {
  158. return $this->handleException($passable, $e);
  159. }
  160. };
  161. }
  162. /**
  163. * Get a Closure that represents a slice of the application onion.
  164. *
  165. * @return \Closure
  166. */
  167. protected function carry()
  168. {
  169. return function ($stack, $pipe) {
  170. return function ($passable) use ($stack, $pipe) {
  171. try {
  172. if (is_callable($pipe)) {
  173. // If the pipe is a callable, then we will call it directly, but otherwise we
  174. // will resolve the pipes out of the dependency container and call it with
  175. // the appropriate method and arguments, returning the results back out.
  176. return $pipe($passable, $stack);
  177. } elseif (! is_object($pipe)) {
  178. [$name, $parameters] = $this->parsePipeString($pipe);
  179. // If the pipe is a string we will parse the string and resolve the class out
  180. // of the dependency injection container. We can then build a callable and
  181. // execute the pipe function giving in the parameters that are required.
  182. $pipe = $this->getContainer()->make($name);
  183. $parameters = array_merge([$passable, $stack], $parameters);
  184. } else {
  185. // If the pipe is already an object we'll just make a callable and pass it to
  186. // the pipe as-is. There is no need to do any extra parsing and formatting
  187. // since the object we're given was already a fully instantiated object.
  188. $parameters = [$passable, $stack];
  189. }
  190. $carry = method_exists($pipe, $this->method)
  191. ? $pipe->{$this->method}(...$parameters)
  192. : $pipe(...$parameters);
  193. return $this->handleCarry($carry);
  194. } catch (Throwable $e) {
  195. return $this->handleException($passable, $e);
  196. }
  197. };
  198. };
  199. }
  200. /**
  201. * Parse full pipe string to get name and parameters.
  202. *
  203. * @param string $pipe
  204. * @return array
  205. */
  206. protected function parsePipeString($pipe)
  207. {
  208. [$name, $parameters] = array_pad(explode(':', $pipe, 2), 2, null);
  209. if (! is_null($parameters)) {
  210. $parameters = explode(',', $parameters);
  211. } else {
  212. $parameters = [];
  213. }
  214. return [$name, $parameters];
  215. }
  216. /**
  217. * Get the array of configured pipes.
  218. *
  219. * @return array
  220. */
  221. protected function pipes()
  222. {
  223. return $this->pipes;
  224. }
  225. /**
  226. * Execute each pipeline step within a database transaction.
  227. *
  228. * @param string|null|\UnitEnum|false $withinTransaction
  229. * @return $this
  230. */
  231. public function withinTransaction($withinTransaction = null)
  232. {
  233. $this->withinTransaction = $withinTransaction;
  234. return $this;
  235. }
  236. /**
  237. * Get the container instance.
  238. *
  239. * @return \Illuminate\Contracts\Container\Container
  240. *
  241. * @throws \RuntimeException
  242. */
  243. protected function getContainer()
  244. {
  245. if (! $this->container) {
  246. throw new RuntimeException('A container instance has not been passed to the Pipeline.');
  247. }
  248. return $this->container;
  249. }
  250. /**
  251. * Set the container instance.
  252. *
  253. * @param \Illuminate\Contracts\Container\Container $container
  254. * @return $this
  255. */
  256. public function setContainer(Container $container)
  257. {
  258. $this->container = $container;
  259. return $this;
  260. }
  261. /**
  262. * Handle the value returned from each pipe before passing it to the next.
  263. *
  264. * @param mixed $carry
  265. * @return mixed
  266. */
  267. protected function handleCarry($carry)
  268. {
  269. return $carry;
  270. }
  271. /**
  272. * Handle the given exception.
  273. *
  274. * @param mixed $passable
  275. * @param \Throwable $e
  276. * @return mixed
  277. *
  278. * @throws \Throwable
  279. */
  280. protected function handleException($passable, Throwable $e)
  281. {
  282. throw $e;
  283. }
  284. }