Passed
Push — master ( 745402...3d5cea )
by Marcel
43s
created

Server::attach()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 9
ccs 5
cts 5
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace UMA\JsonRpc;
6
7
use Psr\Container\ContainerExceptionInterface;
8
use Psr\Container\ContainerInterface;
9
use Psr\Container\NotFoundExceptionInterface;
10
use UMA\JsonRpc\Internal\Pipeliner;
11
use UMA\JsonRpc\Internal\Validator;
12
use UMA\JsonRpc\Internal\Input;
13
14
class Server
15
{
16
    /**
17
     * @var ContainerInterface
18
     */
19
    private $container;
20
21
    /**
22
     * @var string[]
23
     */
24
    private $methods;
25
26
    /**
27
     * @var Middleware[]
28
     */
29
    private $middlewares;
30
31
    /**
32
     * @var int|null
33
     */
34
    protected $batchLimit;
35
36 38
    public function __construct(ContainerInterface $container, int $batchLimit = null)
37
    {
38 38
        $this->container = $container;
39 38
        $this->batchLimit = $batchLimit;
40 38
        $this->methods = [];
41 38
        $this->middlewares = [];
42 38
    }
43
44 37
    public function set(string $method, string $serviceId): Server
45
    {
46 37
        if (!$this->container->has($serviceId)) {
47 1
            throw new \LogicException("Cannot find service '$serviceId' in the container");
48
        }
49
50 36
        $this->methods[$method] = $serviceId;
51
52 36
        return $this;
53
    }
54
55 3
    public function attach(string $serviceId): Server
56
    {
57 3
        if (!$this->container->has($serviceId)) {
58 1
            throw new \LogicException("Cannot find service '$serviceId' in the container");
59
        }
60
61 2
        $this->middlewares[$serviceId] = null;
62
63 2
        return $this;
64
    }
65
66 36
    public function run(string $raw): ?string
67
    {
68 36
        $input = Input::fromString($raw);
69
70 36
        if (!$input->parsable()) {
71 4
            return self::end(Error::parsing());
72
        }
73
74 32
        if ($input->isArray()) {
75 11
            return $this->batch($input);
76
        }
77
78 21
        return $this->single($input);
79
    }
80
81 5
    protected function batch(Input $input): ?string
82
    {
83 5
        \assert($input->isArray());
84
85 5
        if ($this->tooManyBatchRequests($input)) {
86 1
            return self::end(Error::tooManyBatchRequests($this->batchLimit));
87
        }
88
89 4
        $responses = [];
90 4
        foreach ($input->data() as $request) {
91 4
            $pseudoInput = Input::fromSafeData($request);
92
93 4
            if (null !== $response = $this->single($pseudoInput)) {
94 4
                $responses[] = $response;
95
            }
96
        }
97
98 4
        return empty($responses) ?
99 4
            null : \sprintf('[%s]', \implode(',', $responses));
100
    }
101
102 25
    protected function single(Input $input): ?string
103
    {
104 25
        if (!$input->isRpcRequest()) {
105 7
            return self::end(Error::invalidRequest());
106
        }
107
108 19
        $request = new Request($input);
109
110 19
        if (!array_key_exists($request->method(), $this->methods)) {
111 4
            return self::end(Error::unknownMethod($request->id()), $request);
112
        }
113
114
        try {
115 17
            $procedure = $this->container->get($this->methods[$request->method()]);
116 1
        } catch (ContainerExceptionInterface | NotFoundExceptionInterface $e) {
117 1
            return self::end(Error::internal($request->id()), $request);
118
        }
119
120 16
        if (!$procedure instanceof Procedure) {
121 1
            return self::end(Error::internal($request->id()), $request);
122
        }
123
124 15
        $spec = $procedure->getSpec();
125
126 15
        if ($spec instanceof \stdClass && !Validator::validate($spec, $request->params())) {
127 1
            return self::end(Error::invalidParams($request->id()), $request);
128
        }
129
130
        try {
131 14
            $pipeline = Pipeliner::build($this->container, $procedure, $this->middlewares);
132 1
        } catch (\Throwable $t) {
133 1
            return self::end(Error::internal($request->id()), $request);
134
        }
135
136 13
        return self::end($pipeline($request), $request);
137
    }
138
139 11
    protected function tooManyBatchRequests(Input $input): bool
140
    {
141 11
        \assert($input->isArray());
142
143 11
        return \is_int($this->batchLimit) && $this->batchLimit < \count($input->data());
144
    }
145
146 31
    protected static function end(Response $response, Request $request = null): ?string
147
    {
148 31
        return $request instanceof Request && null === $request->id() ?
149 31
            null : \json_encode($response);
150
    }
151
}
152