Completed
Push — caching ( 6c9b2f )
by Nate
03:39
created

TypeAdapterProvider::getAdapterFromProperty()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
c 0
b 0
f 0
dl 0
loc 7
rs 10
cc 2
nc 2
nop 1
1
<?php
2
/*
3
 * Copyright (c) Nate Brunette.
4
 * Distributed under the MIT License (http://opensource.org/licenses/MIT)
5
 */
6
7
declare(strict_types=1);
8
9
namespace Tebru\Gson\Internal;
10
11
use InvalidArgumentException;
12
use Tebru\Gson\Annotation\JsonAdapter;
13
use Tebru\Gson\Internal\Data\Property;
14
use Tebru\Gson\JsonDeserializer;
15
use Tebru\Gson\JsonSerializer;
16
use Tebru\Gson\TypeAdapter;
17
use Tebru\Gson\TypeAdapter\CustomWrappedTypeAdapter;
18
use Tebru\Gson\TypeAdapterFactory;
19
use Tebru\PhpType\TypeToken;
20
21
/**
22
 * Class TypeAdapterProvider
23
 *
24
 * @author Nate Brunette <[email protected]>
25
 */
26
final class TypeAdapterProvider
27
{
28
    /**
29
     * A cache of created type adapters
30
     *
31
     * @var TypeAdapter[]
32
     */
33
    private $typeAdapters = [];
34
35
    /**
36
     * All registered [@see TypeAdapter] factories
37
     *
38
     * @var TypeAdapterFactory[]
39
     */
40
    private $typeAdapterFactories;
41
42
    /**
43
     * @var ConstructorConstructor
44
     */
45
    private $constructorConstructor;
46
47
    /**
48
     * Constructor
49
     *
50
     * @param array $typeAdapterFactories
51
     * @param ConstructorConstructor $constructorConstructor
52
     */
53
    public function __construct(array $typeAdapterFactories, ConstructorConstructor $constructorConstructor)
54
    {
55
        $this->typeAdapterFactories = $typeAdapterFactories;
56
        $this->constructorConstructor = $constructorConstructor;
57
    }
58
59
    public function getAdapterFromProperty(Property $property): TypeAdapter
60
    {
61
        $adapterAnnotation = $property->jsonAdapter;
62
63
        return $adapterAnnotation
64
            ? $this->getAdapterFromAnnotation($property->type, $property->jsonAdapter)
65
            : $this->getAdapter($property->type);
66
    }
67
68
    /**
69
     * Creates a key based on the type, and optionally the class that should be skipped.
70
     * Returns the [@see TypeAdapter] if it has already been created, otherwise loops
71
     * over all of the factories and finds a type adapter that supports the type.
72
     *
73
     * @param TypeToken $type
74
     * @param TypeAdapterFactory|null $skip
75
     * @return TypeAdapter
76
     * @throws InvalidArgumentException if the type cannot be handled by a type adapter
77
     */
78
    public function getAdapter(TypeToken $type, TypeAdapterFactory $skip = null): TypeAdapter
79
    {
80
        $key = (string)$type;
81
        if (null === $skip && isset($this->typeAdapters[$key])) {
82
            return $this->typeAdapters[$key];
83
        }
84
85
        foreach ($this->typeAdapterFactories as $typeAdapterFactory) {
86
            if ($typeAdapterFactory === $skip) {
87
                continue;
88
            }
89
90
            $adapter = $typeAdapterFactory->create($type, $this);
91
            if ($adapter === null) {
92
                continue;
93
            }
94
95
            // do not save skipped adapters
96
            if ($skip === null) {
97
                $this->typeAdapters[$key] = $adapter;
98
            }
99
100
            return $adapter;
101
        }
102
103
        throw new InvalidArgumentException(sprintf(
104
            'The type "%s" could not be handled by any of the registered type adapters',
105
            (string)$type
106
        ));
107
    }
108
109
    /**
110
     * Get a type adapter from a [@see JsonAdapter] annotation
111
     *
112
     * The class may be a TypeAdapter, TypeAdapterFactory, JsonSerializer, or JsonDeserializer
113
     *
114
     * @param TypeToken $type
115
     * @param JsonAdapter $jsonAdapterAnnotation
116
     * @return TypeAdapter
117
     * @throws InvalidArgumentException
118
     */
119
    public function getAdapterFromAnnotation(TypeToken $type, JsonAdapter $jsonAdapterAnnotation): TypeAdapter
120
    {
121
        $object = $this->constructorConstructor->get(TypeToken::create($jsonAdapterAnnotation->getValue()))->construct();
122
123
        if ($object instanceof TypeAdapter) {
124
            return $object;
125
        }
126
127
        if ($object instanceof TypeAdapterFactory) {
128
            return $object->create($type, $this);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $object->create($type, $this) could return the type null which is incompatible with the type-hinted return Tebru\Gson\TypeAdapter. Consider adding an additional type-check to rule them out.
Loading history...
129
        }
130
131
        if ($object instanceof JsonSerializer && $object instanceof JsonDeserializer) {
132
            return new CustomWrappedTypeAdapter($type, $object, $object);
133
        }
134
135
        if ($object instanceof JsonSerializer) {
136
            return new CustomWrappedTypeAdapter($type, $object);
137
        }
138
139
        if ($object instanceof JsonDeserializer) {
140
            return new CustomWrappedTypeAdapter($type, null, $object);
141
        }
142
143
        throw new InvalidArgumentException(sprintf(
144
            'The type adapter must be an instance of TypeAdapter, TypeAdapterFactory, JsonSerializer, or JsonDeserializer, but "%s" was found',
145
            get_class($object)
146
        ));
147
    }
148
}
149