Completed
Push — master ( 2f5933...8ec5a6 )
by Oscar
02:10
created

JsonValidator::__invoke()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 9.2
c 0
b 0
f 0
cc 3
eloc 12
nc 3
nop 3
1
<?php
2
3
namespace Psr7Middlewares\Middleware;
4
5
use JsonSchema\Validator;
6
use Psr\Http\Message\ResponseInterface;
7
use Psr\Http\Message\ServerRequestInterface;
8
use Psr7Middlewares\Middleware;
9
use Psr7Middlewares\Utils\AttributeTrait;
10
use Psr7Middlewares\Utils\CallableTrait;
11
12
class JsonValidator
13
{
14
    const KEY = 'JSON_VALIDATION_ERRORS';
15
16
    use AttributeTrait;
17
    use CallableTrait;
18
19
    /** @var \stdClass */
20
    private $schema;
21
22
    /** @var callable */
23
    private $errorHandler;
24
25
    /**
26
     * JsonSchema constructor.
27
     * Consider using one of the following factories instead of invoking the controller directly:
28
     *  - JsonValidator::fromFile()
29
     *  - JsonValidator::fromEncodedString()
30
     *  - JsonValidator::fromDecodedObject()
31
     *  - JsonValidator::fromArray()
32
     *
33
     * @param \stdClass $schema A JSON-decoded object-representation of the schema.
34
     */
35
    public function __construct(\stdClass $schema)
36
    {
37
        $this->schema = $schema;
38
        $this->errorHandler = [$this, 'defaultErrorHandler'];
39
    }
40
41
    /**
42
     * @param \stdClass $schema
43
     * @return static|callable
44
     */
45
    public static function fromDecodedObject(\stdClass $schema)
46
    {
47
        return new static($schema);
48
    }
49
50
    /**
51
     * @param \SplFileObject $file
52
     * @return static|callable
53
     */
54
    public static function fromFile(\SplFileObject $file)
55
    {
56
        $schema = (object)[
57
            '$ref' => $file->getPathname(),
58
        ];
59
60
        return new static($schema);
61
    }
62
63
    /**
64
     * @param string $json
65
     * @return static|callable
66
     */
67
    public static function fromEncodedString($json)
68
    {
69
        return static::fromDecodedObject(json_decode($json, false));
70
    }
71
72
    /**
73
     * @param array $json
74
     * @return static|callable
75
     */
76
    public static function fromArray(array $json)
77
    {
78
        return static::fromEncodedString(json_encode($json, JSON_UNESCAPED_SLASHES));
79
    }
80
81
    /**
82
     * Returns the request's JSON validation errors.
83
     *
84
     * @param ServerRequestInterface $request
85
     *
86
     * @return array|null
87
     */
88
    public static function getErrors(ServerRequestInterface $request)
89
    {
90
        return self::getAttribute($request, self::KEY);
91
    }
92
93
    /**
94
     * Execute the middleware.
95
     *
96
     * @param ServerRequestInterface $request
97
     * @param ResponseInterface $response
98
     * @param callable $next
99
     *
100
     * @return ResponseInterface
101
     * @throws \RuntimeException
102
     * @throws \InvalidArgumentException
103
     * @throws \JsonSchema\Exception\ExceptionInterface
104
     */
105
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
106
    {
107
        $value = $request->getParsedBody();
108
        if (!is_object($value)) {
109
            $request = self::setAttribute($request, self::KEY, [
110
                sprintf('Parsed body must be an object. Type %s is invalid.', gettype($value)),
111
            ]);
112
113
            return $this->executeCallable($this->errorHandler, $request, $response);
114
        }
115
116
        $validator = new Validator();
117
        $validator->check($value, $this->schema);
118
119
        if (!$validator->isValid()) {
120
            $request = self::setAttribute($request, self::KEY, $validator->getErrors());
121
122
            return $this->executeCallable($this->errorHandler, $request, $response);
123
        }
124
125
        return $next($request, $response);
126
    }
127
128
    /**
129
     * @param ServerRequestInterface $request
130
     * @param ResponseInterface $response
131
     * @return ResponseInterface
132
     * @throws \RuntimeException
133
     * @throws \InvalidArgumentException
134
     */
135
    private function defaultErrorHandler(
136
        ServerRequestInterface $request,
137
        ResponseInterface $response
138
    ) {
139
        $response = $response->withStatus(422, 'Unprocessable Entity')
140
            ->withHeader('Content-Type', 'application/json');
141
142
        $middlewareAttribute = $request->getAttribute(Middleware::KEY, []);
143
144
        if (isset($middlewareAttribute[self::KEY])) {
145
            /** @var ResponseInterface $response */
146
            $stream = $response->getBody();
147
            $stream->write(json_encode($middlewareAttribute[self::KEY], JSON_UNESCAPED_SLASHES));
148
        }
149
150
        return $response;
151
    }
152
153
    /**
154
     * Has the following method signature:
155
     * function (ServerRequestInterface $request, ResponseInterface $response): ResponseInterface {}
156
     *
157
     * Validation errors are stored in a middleware attribute:
158
     * $request->getAttribute(Middleware::KEY, [])[JsonValidator::KEY];
159
     *
160
     * @param callable $errorHandler
161
     * @return void
162
     */
163
    public function errorHandler(callable $errorHandler)
164
    {
165
        $this->errorHandler = $errorHandler;
166
    }
167
}
168