Completed
Branch master (69deae)
by
unknown
07:46
created

Container::getSchemaByType()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
nc 4
nop 1
dl 0
loc 25
ccs 13
cts 13
cp 1
crap 4
rs 9.52
c 0
b 0
f 0
1
<?php namespace Neomerx\JsonApi\Schema;
2
3
/**
4
 * Copyright 2015-2018 [email protected]
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
use Closure;
20
use InvalidArgumentException;
21
use Neomerx\JsonApi\Contracts\Schema\ContainerInterface;
22
use Neomerx\JsonApi\Contracts\Schema\SchemaFactoryInterface;
23
use Neomerx\JsonApi\Contracts\Schema\SchemaInterface;
24
use Psr\Log\LoggerAwareInterface;
25
use Psr\Log\LoggerAwareTrait;
26
use function Neomerx\JsonApi\I18n\translate as _;
27
28
/**
29
 * @package Neomerx\JsonApi
30
 */
31
class Container implements ContainerInterface, LoggerAwareInterface
32
{
33
    use LoggerAwareTrait;
34
35
    /**
36
     * Message code.
37
     */
38
    const MSG_INVALID_TYPE = 0;
39
40
    /**
41
     * Message code.
42
     */
43
    const MSG_INVALID_SCHEME = self::MSG_INVALID_TYPE + 1;
44
45
    /**
46
     * Message code.
47
     */
48
    const MSG_TYPE_REUSE_FORBIDDEN = self::MSG_INVALID_SCHEME + 1;
49
50
    /**
51
     * Message code.
52
     */
53
    const MSG_UNREGISTERED_SCHEME_FOR_TYPE = self::MSG_TYPE_REUSE_FORBIDDEN + 1;
54
55
    /**
56
     * Message code.
57
     */
58
    const MSG_UNREGISTERED_SCHEME_FOR_RESOURCE_TYPE = self::MSG_UNREGISTERED_SCHEME_FOR_TYPE + 1;
59
60
    /**
61
     * Default messages.
62
     */
63
    const MESSAGES = [
64
        self::MSG_INVALID_TYPE                          => 'Type must be non-empty string.',
65
        self::MSG_INVALID_SCHEME                        =>
66
            'Schema for type \'%s\' must be non-empty string, callable or SchemaInterface instance.',
67
        self::MSG_TYPE_REUSE_FORBIDDEN                  =>
68
            'Type should not be used more than once to register a schema (\'%s\').',
69
        self::MSG_UNREGISTERED_SCHEME_FOR_TYPE          => 'Schema is not registered for type \'%s\'.',
70
        self::MSG_UNREGISTERED_SCHEME_FOR_RESOURCE_TYPE => 'Schema is not registered for resource type \'%s\'.',
71
    ];
72
73
    /**
74
     * @var array
75
     */
76
    private $providerMapping = [];
77
78
    /**
79
     * @var SchemaInterface[]
80
     */
81
    private $createdProviders = [];
82
83
    /**
84
     * @var array
85
     */
86
    private $resType2JsonType = [];
87
88
    /**
89
     * @var SchemaFactoryInterface
90
     */
91
    private $factory;
92
93
    /**
94
     * @var array
95
     */
96
    private $messages;
97
98
    /**
99
     * @param SchemaFactoryInterface $factory
100
     * @param iterable               $schemas
0 ignored issues
show
Documentation introduced by
Should the type for parameter $schemas not be iterable|array? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
101
     * @param array                  $messages
102
     */
103 94
    public function __construct(SchemaFactoryInterface $factory, iterable $schemas = [], $messages = self::MESSAGES)
104
    {
105 94
        $this->factory  = $factory;
106 94
        $this->messages = $messages;
107 94
        $this->registerArray($schemas);
108 92
    }
109
110
    /**
111
     * Register provider for resource type.
112
     *
113
     * @param string         $type
114
     * @param string|Closure $schema
115
     *
116
     * @return void
117
     *
118
     * @SuppressWarnings(PHPMD.StaticAccess)
119
     * @SuppressWarnings(PHPMD.ElseExpression)
120
     */
121 83
    public function register(string $type, $schema): void
122
    {
123 83
        if (empty($type) === true || class_exists($type) === false) {
124 1
            throw new InvalidArgumentException(_($this->messages[self::MSG_INVALID_TYPE]));
125
        }
126
127
        $isOk = (
128 82
            (is_string($schema) === true && empty($schema) === false) ||
129 39
            is_callable($schema) ||
130 82
            $schema instanceof SchemaInterface
131
        );
132 82
        if ($isOk === false) {
133 1
            throw new InvalidArgumentException(_($this->messages[self::MSG_INVALID_SCHEME], $type));
134
        }
135
136 81
        if ($this->hasProviderMapping($type) === true) {
137 1
            throw new InvalidArgumentException(_($this->messages[self::MSG_TYPE_REUSE_FORBIDDEN], $type));
138
        }
139
140 81
        if ($schema instanceof SchemaInterface) {
141 2
            $this->setProviderMapping($type, get_class($schema));
142 2
            $this->setResourceToJsonTypeMapping($schema->getResourceType(), $type);
143 2
            $this->setCreatedProvider($type, $schema);
144
        } else {
145 79
            $this->setProviderMapping($type, $schema);
146
        }
147 81
    }
148
149
    /**
150
     * Register providers for resource types.
151
     *
152
     * @param iterable $schemas
153
     *
154
     * @return void
155
     */
156 94
    public function registerArray(iterable $schemas): void
157
    {
158 94
        foreach ($schemas as $type => $schema) {
159 82
            $this->register($type, $schema);
160
        }
161 92
    }
162
163
    /**
164
     * @inheritdoc
165
     */
166 72
    public function getSchema($resource): ?SchemaInterface
167
    {
168 72
        if ($resource === null) {
169 1
            return null;
170
        }
171
172 71
        $resourceType = $this->getResourceType($resource);
173
174 71
        return $this->getSchemaByType($resourceType);
175
    }
176
177
    /**
178
     * @inheritdoc
179
     */
180 74
    public function hasSchema($resourceObject): bool
181
    {
182 74
        return is_object($resourceObject) === true &&
183 74
            $this->hasProviderMapping($this->getResourceType($resourceObject)) === true;
184
    }
185
186
    /**
187
     * @inheritdoc
188
     *
189
     * @SuppressWarnings(PHPMD.StaticAccess)
190
     * @SuppressWarnings(PHPMD.ElseExpression)
191
     */
192 73
    public function getSchemaByType(string $type): SchemaInterface
193
    {
194 73
        if ($this->hasCreatedProvider($type) === true) {
195 61
            return $this->getCreatedProvider($type);
196
        }
197
198 71
        if ($this->hasProviderMapping($type) === false) {
199 3
            throw new InvalidArgumentException(_($this->messages[self::MSG_UNREGISTERED_SCHEME_FOR_TYPE], $type));
200
        }
201
202 70
        $classNameOrCallable = $this->getProviderMapping($type);
203 70
        if (is_string($classNameOrCallable) === true) {
204 48
            $schema = $this->createSchemaFromClassName($classNameOrCallable);
205
        } else {
206 34
            assert(is_callable($classNameOrCallable) === true);
207 34
            $schema = $this->createSchemaFromCallable($classNameOrCallable);
208
        }
209 70
        $this->setCreatedProvider($type, $schema);
210
211
        /** @var SchemaInterface $schema */
212
213 70
        $this->setResourceToJsonTypeMapping($schema->getResourceType(), $type);
214
215 70
        return $schema;
216
    }
217
218
    /**
219
     * @inheritdoc
220
     *
221
     * @SuppressWarnings(PHPMD.StaticAccess)
222
     * @SuppressWarnings(PHPMD.UnusedLocalVariable)
223
     */
224 48
    public function getSchemaByResourceType(string $resourceType): SchemaInterface
225
    {
226
        // Schema is not found among instantiated schemas for resource type $resourceType
227 48
        $isOk = (is_string($resourceType) === true && $this->hasResourceToJsonTypeMapping($resourceType) === true);
228
229
        // Schema might not be found if it hasn't been searched by type (not resource type) before.
230
        // We instantiate all schemas and then find one.
231 48
        if ($isOk === false) {
232 1
            foreach ($this->getProviderMappings() as $type => $schema) {
233 1
                if ($this->hasCreatedProvider($type) === false) {
234
                    // it will instantiate the schema
235 1
                    $this->getSchemaByType($type);
236
                }
237
            }
238
        }
239
240
        // search one more time
241 48
        $isOk = (is_string($resourceType) === true && $this->hasResourceToJsonTypeMapping($resourceType) === true);
242
243 48
        if ($isOk === false) {
244 1
            throw new InvalidArgumentException(_(
245 1
                $this->messages[self::MSG_UNREGISTERED_SCHEME_FOR_RESOURCE_TYPE],
246 1
                $resourceType
247
            ));
248
        }
249
250 48
        return $this->getSchemaByType($this->getJsonType($resourceType));
251
    }
252
253
    /**
254
     * @return SchemaFactoryInterface
255
     */
256 70
    protected function getFactory(): SchemaFactoryInterface
257
    {
258 70
        return $this->factory;
259
    }
260
261
    /**
262
     * @return array
263
     */
264 1
    protected function getProviderMappings(): array
265
    {
266 1
        return $this->providerMapping;
267
    }
268
269
    /**
270
     * @param string $type
271
     *
272
     * @return bool
273
     */
274 81
    protected function hasProviderMapping(string $type): bool
275
    {
276 81
        return array_key_exists($type, $this->providerMapping);
277
    }
278
279
    /**
280
     * @param string $type
281
     *
282
     * @return mixed
283
     */
284 70
    protected function getProviderMapping(string $type)
285
    {
286 70
        return $this->providerMapping[$type];
287
    }
288
289
    /**
290
     * @param string         $type
291
     * @param string|Closure $schema
292
     *
293
     * @return void
294
     */
295 81
    protected function setProviderMapping(string $type, $schema): void
296
    {
297 81
        $this->providerMapping[$type] = $schema;
298 81
    }
299
300
    /**
301
     * @param string $type
302
     *
303
     * @return bool
304
     */
305 73
    protected function hasCreatedProvider(string $type): bool
306
    {
307 73
        return array_key_exists($type, $this->createdProviders);
308
    }
309
310
    /**
311
     * @param string $type
312
     *
313
     * @return SchemaInterface
314
     */
315 61
    protected function getCreatedProvider(string $type): SchemaInterface
316
    {
317 61
        return $this->createdProviders[$type];
318
    }
319
320
    /**
321
     * @param string          $type
322
     * @param SchemaInterface $provider
323
     *
324
     * @return void
325
     */
326 72
    protected function setCreatedProvider(string $type, SchemaInterface $provider): void
327
    {
328 72
        $this->createdProviders[$type] = $provider;
329 72
    }
330
331
    /**
332
     * @param string $resourceType
333
     *
334
     * @return bool
335
     */
336 48
    protected function hasResourceToJsonTypeMapping(string $resourceType): bool
337
    {
338 48
        return array_key_exists($resourceType, $this->resType2JsonType);
339
    }
340
341
    /**
342
     * @param string $resourceType
343
     *
344
     * @return string
345
     */
346 48
    protected function getJsonType(string $resourceType): string
347
    {
348 48
        return $this->resType2JsonType[$resourceType];
349
    }
350
351
    /**
352
     * @param string $resourceType
353
     * @param string $jsonType
354
     *
355
     * @return void
356
     */
357 72
    protected function setResourceToJsonTypeMapping(string $resourceType, string $jsonType): void
358
    {
359 72
        $this->resType2JsonType[$resourceType] = $jsonType;
360 72
    }
361
362
    /**
363
     * @param object $resource
364
     *
365
     * @return string
366
     */
367 72
    protected function getResourceType($resource): string
368
    {
369 72
        assert(
370 72
            is_object($resource) === true,
371 72
            'Unable to get a type of the resource as it is not an object.'
372
        );
373
374 72
        return get_class($resource);
375
    }
376
377
    /**
378
     * @param callable $callable
379
     *
380
     * @return SchemaInterface
381
     */
382 33
    protected function createSchemaFromCallable(callable $callable): SchemaInterface
383
    {
384 33
        $schema = call_user_func($callable, $this->getFactory());
385
386 33
        return $schema;
387
    }
388
389
    /**
390
     * @param string $className
391
     *
392
     * @return SchemaInterface
393
     */
394 47
    protected function createSchemaFromClassName(string $className): SchemaInterface
395
    {
396 47
        $schema = new $className($this->getFactory());
397
398 47
        return $schema;
399
    }
400
}
401