Pipeline.php 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. <?php
  2. declare(strict_types=1);
  3. namespace Yansongda\Supports;
  4. use Closure;
  5. use Psr\Container\ContainerInterface;
  6. /**
  7. * This file mostly code come from illuminate/pipe and hyperf/utils,
  8. * thanks provide such a useful class.
  9. */
  10. class Pipeline
  11. {
  12. protected ContainerInterface $container;
  13. protected mixed $passable;
  14. protected array $pipes = [];
  15. protected string $method = 'handle';
  16. public function __construct(ContainerInterface $container)
  17. {
  18. $this->container = $container;
  19. }
  20. public function send(mixed $passable): self
  21. {
  22. $this->passable = $passable;
  23. return $this;
  24. }
  25. public function through(mixed $pipes): self
  26. {
  27. $this->pipes = is_array($pipes) ? $pipes : func_get_args();
  28. return $this;
  29. }
  30. /**
  31. * Set the method to call on the pipes.
  32. */
  33. public function via(string $method): self
  34. {
  35. $this->method = $method;
  36. return $this;
  37. }
  38. public function then(Closure $destination): mixed
  39. {
  40. $pipeline = array_reduce(array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination));
  41. return $pipeline($this->passable);
  42. }
  43. protected function prepareDestination(Closure $destination): Closure
  44. {
  45. return static function ($passable) use ($destination) {
  46. return $destination($passable);
  47. };
  48. }
  49. protected function carry(): Closure
  50. {
  51. return function ($stack, $pipe) {
  52. return function ($passable) use ($stack, $pipe) {
  53. if (is_callable($pipe)) {
  54. // If the pipe is an instance of a Closure, we will just call it directly, but
  55. // otherwise we'll resolve the pipes out of the container and call it with
  56. // the appropriate method and arguments, returning the results back out.
  57. return $pipe($passable, $stack);
  58. }
  59. if (!is_object($pipe)) {
  60. [$name, $parameters] = $this->parsePipeString($pipe);
  61. // If the pipe is a string we will parse the string and resolve the class out
  62. // of the dependency injection container. We can then build a callable and
  63. // execute the pipe function giving in the parameters that are required.
  64. $pipe = $this->container->get($name);
  65. $parameters = array_merge([$passable, $stack], $parameters);
  66. } else {
  67. // If the pipe is already an object we'll just make a callable and pass it to
  68. // the pipe as-is. There is no need to do any extra parsing and formatting
  69. // since the object we're given was already a fully instantiated object.
  70. $parameters = [$passable, $stack];
  71. }
  72. $carry = method_exists($pipe, $this->method) ? $pipe->{$this->method}(...$parameters) : $pipe(...$parameters);
  73. return $this->handleCarry($carry);
  74. };
  75. };
  76. }
  77. protected function parsePipeString(string $pipe): array
  78. {
  79. [$name, $parameters] = array_pad(explode(':', $pipe, 2), 2, []);
  80. if (is_string($parameters)) {
  81. $parameters = explode(',', $parameters);
  82. }
  83. return [$name, $parameters];
  84. }
  85. protected function handleCarry(mixed $carry): mixed
  86. {
  87. return $carry;
  88. }
  89. }