Completed
Push — 1.x ( 2c7718...1ed52f )
by Akihito
13s queued 11s
created

JsonSchemaInterceptor::invoke()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

Changes 8
Bugs 0 Features 2
Metric Value
dl 0
loc 18
ccs 10
cts 10
cp 1
rs 9.2
c 8
b 0
f 2
cc 4
eloc 10
nc 4
nop 1
crap 4
1
<?php
2
3
declare(strict_types=1);
4
/**
5
 * This file is part of the BEAR.Resource package.
6
 *
7
 * @license http://opensource.org/licenses/MIT MIT
8
 */
9
namespace BEAR\Resource\Interceptor;
10
11
use BEAR\Resource\Annotation\JsonSchema;
12
use BEAR\Resource\Code;
13
use BEAR\Resource\Exception\JsonSchemaErrorException;
14
use BEAR\Resource\Exception\JsonSchemaException;
15
use BEAR\Resource\Exception\JsonSchemaNotFoundException;
16
use BEAR\Resource\ResourceObject;
17
use JsonSchema\Constraints\Constraint;
18
use JsonSchema\Validator;
19
use Ray\Aop\MethodInterceptor;
20
use Ray\Aop\MethodInvocation;
21
use Ray\Aop\ReflectionMethod;
22
use Ray\Aop\WeavedInterface;
23
use Ray\Di\Di\Named;
24
25
final class JsonSchemaInterceptor implements MethodInterceptor
26
{
27
    /**
28
     * @var string
29
     */
30
    private $schemaDir;
31
32
    /**
33
     * @var string
34
     */
35
    private $validateDir;
36
37
    /**
38
     * @param string $schemaDir
39
     * @param string $validateDir
40
     *
41
     * @Named("schemaDir=json_schema_dir,validateDir=json_validate_dir")
42
     */
43 7
    public function __construct($schemaDir, $validateDir)
44
    {
45 7
        $this->schemaDir = $schemaDir;
46 7
        $this->validateDir = $validateDir;
47 7
    }
48
49
    /**
50
     * {@inheritdoc}
51
     */
52 7
    public function invoke(MethodInvocation $invocation)
53
    {
54
        /** @var ReflectionMethod $method */
55 7
        $method = $invocation->getMethod();
56
        /** @var JsonSchema $jsonSchema */
57 7
        $jsonSchema = $method->getAnnotation(JsonSchema::class);
58 7
        if ($jsonSchema->params) {
59 3
            $arguments = $this->getNamedArguments($invocation);
60 3
            $this->validateRequest($jsonSchema, $arguments);
61
        }
62
        /** @var ResourceObject $ro */
63 6
        $ro = $invocation->proceed();
64 6
        if ($ro->code === 200 || $ro->code == 201) {
65 5
            $this->validateResponse($jsonSchema, $ro);
66
        }
67
68 3
        return $ro;
69
    }
70
71 3
    private function validateRequest(JsonSchema $jsonSchema, array $arguments)
72
    {
73 3
        $schemaFile = $this->validateDir . '/' . $jsonSchema->params;
74 3
        $this->validateFileExists($schemaFile);
75 3
        $this->validate($arguments, $schemaFile);
76 2
    }
77
78 5
    private function validateResponse(JsonSchema $jsonSchema, ResourceObject $ro)
79
    {
80 5
        $schemeFile = $this->getSchemaFile($jsonSchema, $ro);
81 4
        $body = isset($ro->body[$jsonSchema->key]) ? $ro->body[$jsonSchema->key] : $ro->body;
82 4
        $this->validate($body, $schemeFile);
83 2
    }
84
85 5
    private function validate($scanObject, $schemaFile)
86
    {
87 5
        $validator = new Validator;
88 5
        $schema = (object) ['$ref' => 'file://' . $schemaFile];
89 5
        $validator->validate($scanObject, $schema, Constraint::CHECK_MODE_TYPE_CAST);
90 5
        $isValid = $validator->isValid();
91 5
        if ($isValid) {
92 3
            return;
93
        }
94 3
        $e = null;
95 3
        foreach ($validator->getErrors() as $error) {
96 3
            $msg = sprintf('[%s] %s', $error['property'], $error['message']);
97 3
            $e = $e ? new JsonSchemaErrorException($msg, 0, $e) : new JsonSchemaErrorException($msg);
98
        }
99 3
        throw new JsonSchemaException($schemaFile, Code::ERROR, $e);
100
    }
101
102 5
    private function getSchemaFile(JsonSchema $jsonSchema, ResourceObject $ro) : string
103
    {
104 5
        if (! $jsonSchema->schema) {
105
            // for BC only
106 1
            $ref = new \ReflectionClass($ro);
107 1
            $roFileName = $ro instanceof WeavedInterface ? $roFileName = $ref->getParentClass()->getFileName() : $ref->getFileName();
108 1
            $bcFile = str_replace('.php', '.json', $roFileName);
109 1
            if (file_exists($bcFile)) {
110 1
                return $bcFile;
111
            }
112
        }
113 4
        $schemaFile = $this->schemaDir . '/' . $jsonSchema->schema;
114 4
        $this->validateFileExists($schemaFile);
115
116 3
        return $schemaFile;
117
    }
118
119 5
    private function validateFileExists(string $schemaFile)
120
    {
121 5
        if (! file_exists($schemaFile) || is_dir($schemaFile)) {
122 1
            throw new JsonSchemaNotFoundException($schemaFile);
123
        }
124 4
    }
125
126 3
    private function getNamedArguments(MethodInvocation $invocation)
127
    {
128 3
        $parameters = $invocation->getMethod()->getParameters();
129 3
        $values = $invocation->getArguments();
130 3
        $arguments = [];
131 3
        foreach ($parameters as $index => $parameter) {
132 3
            $arguments[$parameter->name] = $values[$index] ?? $parameter->getDefaultValue();
133
        }
134
135 3
        return $arguments;
136
    }
137
}
138