Completed
Branch master (9645b9)
by Neomerx
02:19
created

SchemaContainer::setCreatedProvider()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Neomerx\JsonApi\Schema;
4
5
/**
6
 * Copyright 2015-2019 [email protected]
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 * http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
21
use Closure;
22
use Neomerx\JsonApi\Contracts\Factories\FactoryInterface;
23
use Neomerx\JsonApi\Contracts\Schema\SchemaContainerInterface;
24
use Neomerx\JsonApi\Contracts\Schema\SchemaInterface;
25
use Neomerx\JsonApi\Exceptions\InvalidArgumentException;
26
use function Neomerx\JsonApi\I18n\format as _;
27
28
/**
29
 * @package Neomerx\JsonApi
30
 */
31
class SchemaContainer implements SchemaContainerInterface
32
{
33
    /**
34
     * Message code.
35
     */
36
    const MSG_INVALID_MODEL_TYPE = 'Invalid model type.';
37
38
    /**
39
     * Message code.
40
     */
41
    const MSG_INVALID_SCHEME = 'Schema for type `%s` must be non-empty string, callable or SchemaInterface instance.';
42
43
    /**
44
     * Message code.
45
     */
46
    const MSG_TYPE_REUSE_FORBIDDEN = 'Type should not be used more than once to register a schema (`%s`).';
47
48
    /**
49
     * @var array
50
     */
51
    private $providerMapping = [];
52
53
    /**
54
     * @var SchemaInterface[]
55
     */
56
    private $createdProviders = [];
57
58
    /**
59
     * @var array
60
     */
61
    private $resType2JsonType = [];
62
63
    /**
64
     * @var FactoryInterface
65
     */
66
    private $factory;
67
68
    /**
69
     * @param FactoryInterface $factory
70
     * @param iterable         $schemas
71
     */
72 90
    public function __construct(FactoryInterface $factory, iterable $schemas)
73
    {
74 90
        $this->factory = $factory;
75 90
        $this->registerCollection($schemas);
76 88
    }
77
78
    /**
79
     * Register provider for resource type.
80
     *
81
     * @param string         $type
82
     * @param string|Closure $schema
83
     *
84
     * @return void
85
     *
86
     * @SuppressWarnings(PHPMD.StaticAccess)
87
     * @SuppressWarnings(PHPMD.ElseExpression)
88
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
89
     */
90 73
    public function register(string $type, $schema): void
91
    {
92 73
        if (empty($type) === true || class_exists($type) === false) {
93 1
            throw new InvalidArgumentException(_(static::MSG_INVALID_MODEL_TYPE));
94
        }
95
96
        $isOk = (
97
            (
98 72
                is_string($schema) === true &&
99 72
                empty($schema) === false &&
100 72
                class_exists($schema) === true &&
101 46
                in_array(SchemaInterface::class, class_implements($schema)) === true
102
            ) ||
103 40
            is_callable($schema) ||
104 72
            $schema instanceof SchemaInterface
105
        );
106 72
        if ($isOk === false) {
107 1
            throw new InvalidArgumentException(_(static::MSG_INVALID_SCHEME, $type));
108
        }
109
110 71
        if ($this->hasProviderMapping($type) === true) {
111 1
            throw new InvalidArgumentException(_(static::MSG_TYPE_REUSE_FORBIDDEN, $type));
112
        }
113
114 71
        if ($schema instanceof SchemaInterface) {
115 2
            $this->setProviderMapping($type, get_class($schema));
116 2
            $this->setResourceToJsonTypeMapping($schema->getType(), $type);
117 2
            $this->setCreatedProvider($type, $schema);
118
        } else {
119 70
            $this->setProviderMapping($type, $schema);
120
        }
121 71
    }
122
123
    /**
124
     * Register providers for resource types.
125
     *
126
     * @param iterable $schemas
127
     *
128
     * @return void
129
     */
130 90
    public function registerCollection(iterable $schemas): void
131
    {
132 90
        foreach ($schemas as $type => $schema) {
133 72
            $this->register($type, $schema);
134
        }
135 88
    }
136
137
    /**
138
     * @inheritdoc
139
     */
140 63
    public function getSchema($resource): SchemaInterface
141
    {
142 63
        assert($this->hasSchema($resource));
143
144 63
        $resourceType = $this->getResourceType($resource);
145
146 63
        return $this->getSchemaByType($resourceType);
147
    }
148
149
    /**
150
     * @inheritdoc
151
     */
152 72
    public function hasSchema($resourceObject): bool
153
    {
154 72
        return is_object($resourceObject) === true &&
155 72
            $this->hasProviderMapping($this->getResourceType($resourceObject)) === true;
156
    }
157
158
    /**
159
     * @inheritdoc
160
     *
161
     * @SuppressWarnings(PHPMD.StaticAccess)
162
     * @SuppressWarnings(PHPMD.ElseExpression)
163
     */
164 63
    protected function getSchemaByType(string $type): SchemaInterface
165
    {
166 63
        if ($this->hasCreatedProvider($type) === true) {
167 31
            return $this->getCreatedProvider($type);
168
        }
169
170 62
        $classNameOrCallable = $this->getProviderMapping($type);
171 62
        if (is_string($classNameOrCallable) === true) {
172 34
            $schema = $this->createSchemaFromClassName($classNameOrCallable);
173
        } else {
174 37
            assert(is_callable($classNameOrCallable) === true);
175 37
            $schema = $this->createSchemaFromCallable($classNameOrCallable);
176
        }
177 62
        $this->setCreatedProvider($type, $schema);
178
179
        /** @var SchemaInterface $schema */
180
181 62
        $this->setResourceToJsonTypeMapping($schema->getType(), $type);
182
183 62
        return $schema;
184
    }
185
186
    /**
187
     * @return FactoryInterface
188
     */
189 62
    protected function getFactory(): FactoryInterface
190
    {
191 62
        return $this->factory;
192
    }
193
194
    /**
195
     * @return array
196
     */
197 73
    protected function getProviderMappings(): array
198
    {
199 73
        return $this->providerMapping;
200
    }
201
202
    /**
203
     * @param string $type
204
     *
205
     * @return bool
206
     */
207 73
    protected function hasProviderMapping(string $type): bool
208
    {
209 73
        return array_key_exists($type, $this->getProviderMappings());
210
    }
211
212
    /**
213
     * @param string $type
214
     *
215
     * @return mixed
216
     */
217 62
    protected function getProviderMapping(string $type)
218
    {
219 62
        return $this->getProviderMappings()[$type];
220
    }
221
222
    /**
223
     * @param string         $type
224
     * @param string|Closure $schema
225
     *
226
     * @return void
227
     */
228 71
    protected function setProviderMapping(string $type, $schema): void
229
    {
230 71
        $this->providerMapping[$type] = $schema;
231 71
    }
232
233
    /**
234
     * @param string $type
235
     *
236
     * @return bool
237
     */
238 63
    protected function hasCreatedProvider(string $type): bool
239
    {
240 63
        return array_key_exists($type, $this->createdProviders);
241
    }
242
243
    /**
244
     * @param string $type
245
     *
246
     * @return SchemaInterface
247
     */
248 31
    protected function getCreatedProvider(string $type): SchemaInterface
249
    {
250 31
        return $this->createdProviders[$type];
251
    }
252
253
    /**
254
     * @param string          $type
255
     * @param SchemaInterface $provider
256
     *
257
     * @return void
258
     */
259 63
    protected function setCreatedProvider(string $type, SchemaInterface $provider): void
260
    {
261 63
        $this->createdProviders[$type] = $provider;
262 63
    }
263
264
    /**
265
     * @param string $resourceType
266
     * @param string $jsonType
267
     *
268
     * @return void
269
     */
270 63
    protected function setResourceToJsonTypeMapping(string $resourceType, string $jsonType): void
271
    {
272 63
        $this->resType2JsonType[$resourceType] = $jsonType;
273 63
    }
274
275
    /**
276
     * @param object $resource
277
     *
278
     * @return string
279
     */
280 67
    protected function getResourceType($resource): string
281
    {
282 67
        assert(
283 67
            is_object($resource) === true,
284 67
            'Unable to get a type of the resource as it is not an object.'
285
        );
286
287 67
        return get_class($resource);
288
    }
289
290
    /**
291
     * @param callable $callable
292
     *
293
     * @return SchemaInterface
294
     */
295 37
    protected function createSchemaFromCallable(callable $callable): SchemaInterface
296
    {
297 37
        $schema = call_user_func($callable, $this->getFactory());
298
299 37
        return $schema;
300
    }
301
302
    /**
303
     * @param string $className
304
     *
305
     * @return SchemaInterface
306
     */
307 34
    protected function createSchemaFromClassName(string $className): SchemaInterface
308
    {
309 34
        $schema = new $className($this->getFactory());
310
311 34
        return $schema;
312
    }
313
}
314