Dispatcher   A
last analyzed

Complexity

Total Complexity 21

Size/Duplication

Total Lines 138
Duplicated Lines 0 %

Test Coverage

Coverage 98%

Importance

Changes 0
Metric Value
eloc 50
dl 0
loc 138
ccs 49
cts 50
cp 0.98
rs 10
c 0
b 0
f 0
wmc 21

6 Methods

Rating   Name   Duplication   Size   Complexity  
A throw() 0 6 2
A dispatch() 0 9 2
A __construct() 0 3 1
A call() 0 13 4
A isKnownSignal() 0 11 3
B recoil() 0 51 9
1
<?php
2
/**
3
 * This file is part of PHPinnacle/Ensign.
4
 *
5
 * (c) PHPinnacle Team <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
declare(strict_types = 1);
12
13
namespace PHPinnacle\Ensign;
14
15
use Amp\Coroutine;
16
use Amp\Failure;
17
use Amp\InvalidYieldError;
18
use Amp\Promise;
19
use Amp\Success;
20
21
final class Dispatcher
22
{
23
    /**
24
     * @var HandlerRegistry
25
     */
26
    private $handlers;
27
28
    /**
29
     * @param HandlerRegistry $handlers
30
     */
31 8
    public function __construct(HandlerRegistry $handlers)
32
    {
33 8
        $this->handlers = $handlers;
34 8
    }
35
36
    /**
37
     * @param string|object    $signal
38
     * @param mixed[]       ...$arguments
39
     *
40
     * @return Promise
41
     */
42 8
    public function dispatch($signal, ...$arguments): Promise
43
    {
44 8
        if (\is_object($signal)) {
45 3
            \array_unshift($arguments, $signal);
46
47 3
            $signal = \get_class($signal);
48
        }
49
50 8
        return $this->call($this->handlers->get($signal), $arguments);
51
    }
52
53
    /**
54
     * @param callable $handler
55
     * @param mixed[]  $arguments
56
     *
57
     * @return Promise
58
     */
59 8
    private function call(callable $handler, array $arguments): Promise
60
    {
61
        try {
62 8
            $result = $handler(...$arguments);
63 2
        } catch (\Throwable $error) {
64 2
            return new Failure($error);
65
        }
66
67 7
        if ($result instanceof \Generator) {
68 5
            return new Coroutine($this->recoil($result));
69
        }
70
71 6
        return $result instanceof Promise ? $result : new Success($result);
72
    }
73
74
    /**
75
     * @param \Generator $generator
76
     *
77
     * @return \Generator
78
     */
79 5
    private function recoil(\Generator $generator): \Generator
80
    {
81 5
        $step = 0;
82
83 5
        while ($generator->valid()) {
84
            try {
85
                // yield new Promise
86
                // yield [$promiseOne, $promiseTwo]
87
88
                // yield new Signal
89
                // yield new Signal => 'arg'
90
                // yield new Signal => ['arg', 'two']
91
                // yield Signal::class => [new Signal, 'arg', 'two']
92
                // yield 'signal'
93
                // yield 'signal' => 'arg'
94
                // yield 'signal' => ['arg', 'two']
95
96
                // yield function () {...}
97
                // yield function () {...} => ['arg', 'two']
98 5
                $current = $generator->current();
99 5
                $key     = $generator->key();
100
101 5
                if ($key === $step) {
102 3
                    $interrupt = $current;
103 3
                    $arguments = [];
104
                } else {
105 4
                    $interrupt = $key;
106 4
                    $arguments = \is_array($current) ? $current : [$current];
107
108 4
                    $step--;
109
                }
110
111 5
                if (\is_callable($interrupt)) {
112 2
                    $result = $this->call($interrupt, $arguments);
113 3
                } elseif (\is_array($interrupt) || $interrupt instanceof Promise) {
114
                    $result = yield $interrupt;
115 3
                } elseif ($this->isKnownSignal($interrupt)) {
116 2
                    $result = yield $this->dispatch($interrupt, ...$arguments);
117
                } else {
118 1
                    throw new Exception\InvalidYield($interrupt);
119
                }
120
121 4
                $generator->send($result);
122 2
            } catch (\Throwable $error) {
123 2
                $this->throw($generator, $error, $step);
124 4
            } finally {
125 5
                $step++;
126
            }
127
        };
128
129 4
        return $generator->getReturn();
130
    }
131
132
    /**
133
     * @param \Generator $generator
134
     * @param \Throwable $error
135
     * @param int        $step
136
     *
137
     * @return void
138
     */
139 2
    private function throw(\Generator $generator, \Throwable $error, int $step): void
140
    {
141
        try {
142 2
            $generator->throw($error);
0 ignored issues
show
Bug introduced by
The method throw() does not exist on Generator. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

142
            $generator->/** @scrutinizer ignore-call */ 
143
                        throw($error);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
143 1
        } catch (\Throwable $error) {
144 1
            throw new Exception\BadActionCall($step, $error);
145
        }
146 1
    }
147
148 3
    private function isKnownSignal($interrupt)
149
    {
150 3
        if (\is_object($interrupt)) {
151 2
            $interrupt = \get_class($interrupt);
152
        }
153
154 3
        if (false === \is_string($interrupt)) {
155 1
            return false;
156
        }
157
158 2
        return $this->handlers->has($interrupt);
159
    }
160
}
161