Completed
Push — master ( ac0b2b...25e509 )
by Nate
02:57
created

PropertyCollectionFactory   B

Complexity

Total Complexity 14

Size/Duplication

Total Lines 227
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 17

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 14
lcom 1
cbo 17
dl 0
loc 227
ccs 79
cts 79
cp 1
rs 7.8571
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 21 1
C create() 0 115 11
A excludeProperty() 0 7 2
1
<?php
2
/*
3
 * Copyright (c) Nate Brunette.
4
 * Distributed under the MIT License (http://opensource.org/licenses/MIT)
5
 */
6
7
namespace Tebru\Gson\Internal\Data;
8
9
use Doctrine\Common\Cache\Cache;
10
use ReflectionClass;
11
use ReflectionProperty;
12
use Tebru\Gson\Annotation\JsonAdapter;
13
use Tebru\Gson\Annotation\VirtualProperty;
14
use Tebru\Gson\Internal\AccessorMethodProvider;
15
use Tebru\Gson\Internal\AccessorStrategy\GetByMethod;
16
use Tebru\Gson\Internal\AccessorStrategy\SetByNull;
17
use Tebru\Gson\Internal\AccessorStrategyFactory;
18
use Tebru\Gson\Internal\Excluder;
19
use Tebru\Gson\Internal\MetadataFactory;
20
use Tebru\Gson\Internal\Naming\PropertyNamer;
21
use Tebru\Gson\PhpType;
22
use Tebru\Gson\Internal\PhpTypeFactory;
23
use Tebru\Gson\Internal\TypeAdapterProvider;
24
use Tebru\Gson\PropertyMetadata;
25
26
/**
27
 * Class PropertyCollectionFactory
28
 *
29
 * Aggregates information about class properties to be used during
30
 * future parsing.
31
 *
32
 * @author Nate Brunette <[email protected]>
33
 */
34
final class PropertyCollectionFactory
35
{
36
    /**
37
     * @var ReflectionPropertySetFactory
38
     */
39
    private $reflectionPropertySetFactory;
40
41
    /**
42
     * @var AnnotationCollectionFactory
43
     */
44
    private $annotationCollectionFactory;
45
46
    /**
47
     * @var MetadataFactory
48
     */
49
    private $metadataFactory;
50
51
    /**
52
     * @var PropertyNamer
53
     */
54
    private $propertyNamer;
55
56
    /**
57
     * @var AccessorMethodProvider
58
     */
59
    private $accessorMethodProvider;
60
61
    /**
62
     * @var AccessorStrategyFactory
63
     */
64
    private $accessorStrategyFactory;
65
66
    /**
67
     * @var PhpTypeFactory
68
     */
69
    private $phpTypeFactory;
70
71
    /**
72
     * @var Excluder
73
     */
74
    private $excluder;
75
76
    /**
77
     * @var Cache
78
     */
79
    private $cache;
80
81
    /**
82
     * Constructor
83
     *
84
     * @param ReflectionPropertySetFactory $reflectionPropertySetFactory
85
     * @param AnnotationCollectionFactory $annotationCollectionFactory
86
     * @param MetadataFactory $metadataFactory
87
     * @param PropertyNamer $propertyNamer
88
     * @param AccessorMethodProvider $accessorMethodProvider
89
     * @param AccessorStrategyFactory $accessorStrategyFactory
90
     * @param PhpTypeFactory $phpTypeFactory
91
     * @param Excluder $excluder
92
     * @param Cache $cache
93
     */
94 4
    public function __construct(
95
        ReflectionPropertySetFactory $reflectionPropertySetFactory,
96
        AnnotationCollectionFactory $annotationCollectionFactory,
97
        MetadataFactory $metadataFactory,
98
        PropertyNamer $propertyNamer,
99
        AccessorMethodProvider $accessorMethodProvider,
100
        AccessorStrategyFactory $accessorStrategyFactory,
101
        PhpTypeFactory $phpTypeFactory,
102
        Excluder $excluder,
103
        Cache $cache
104
    ) {
105 4
        $this->reflectionPropertySetFactory = $reflectionPropertySetFactory;
106 4
        $this->annotationCollectionFactory = $annotationCollectionFactory;
107 4
        $this->metadataFactory = $metadataFactory;
108 4
        $this->propertyNamer = $propertyNamer;
109 4
        $this->accessorMethodProvider = $accessorMethodProvider;
110 4
        $this->accessorStrategyFactory = $accessorStrategyFactory;
111 4
        $this->phpTypeFactory = $phpTypeFactory;
112 4
        $this->excluder = $excluder;
113 4
        $this->cache = $cache;
114 4
    }
115
116
    /**
117
     * Create a [@see PropertyCollection] based on the properties of the provided type
118
     *
119
     * @param PhpType $phpType
120
     * @param TypeAdapterProvider $typeAdapterProvider
121
     * @return PropertyCollection
122
     * @throws \RuntimeException If the value is not valid
123
     * @throws \Tebru\Gson\Exception\MalformedTypeException If the type cannot be parsed
124
     * @throws \InvalidArgumentException if the type cannot be handled by a type adapter
125
     * @throws \InvalidArgumentException If the type does not exist
126
     */
127 4
    public function create(PhpType $phpType, TypeAdapterProvider $typeAdapterProvider): PropertyCollection
128
    {
129 4
        $class = $phpType->getClass();
130
131 4
        $data = $this->cache->fetch($class);
132 4
        if (false !== $data) {
133 1
            return $data;
134
        }
135
136 4
        $reflectionClass = new ReflectionClass($class);
137 4
        $reflectionProperties = $this->reflectionPropertySetFactory->create($reflectionClass);
138 4
        $properties = new PropertyCollection();
139
140
        /** @var ReflectionProperty $reflectionProperty */
141 4
        foreach ($reflectionProperties as $reflectionProperty) {
142 4
            $annotations = $this->annotationCollectionFactory->createPropertyAnnotations(
143 4
                $reflectionProperty->getDeclaringClass()->getName(),
144 4
                $reflectionProperty->getName()
145
            );
146
147 4
            $serializedName = $this->propertyNamer->serializedName($reflectionProperty->getName(), $annotations, AnnotationSet::TYPE_PROPERTY);
148 4
            $getterMethod = $this->accessorMethodProvider->getterMethod($reflectionClass, $reflectionProperty, $annotations);
149 4
            $setterMethod = $this->accessorMethodProvider->setterMethod($reflectionClass, $reflectionProperty, $annotations);
150 4
            $getterStrategy = $this->accessorStrategyFactory->getterStrategy($reflectionProperty, $getterMethod);
151 4
            $setterStrategy = $this->accessorStrategyFactory->setterStrategy($reflectionProperty, $setterMethod);
152 4
            $type = $this->phpTypeFactory->create($annotations, AnnotationSet::TYPE_PROPERTY, $getterMethod, $setterMethod);
153
154 4
            $property = new Property(
155 4
                $reflectionProperty->getName(),
156
                $serializedName,
157
                $type,
158
                $getterStrategy,
159
                $setterStrategy,
160
                $annotations,
161 4
                $reflectionProperty->getModifiers(),
162 4
                false
163
            );
164
165 4
            $classMetadata = $this->metadataFactory->createClassMetadata($reflectionProperty->getDeclaringClass()->getName());
166 4
            $propertyMetadata = $this->metadataFactory->createPropertyMetadata($property, $classMetadata);
167
168 4
            $skipSerialize = $this->excludeProperty($propertyMetadata, true);
169 4
            $skipDeserialize = $this->excludeProperty($propertyMetadata, false);
170
171
            // if we're skipping serialization and deserialization, we don't need
172
            // to add the property to the collection
173 4
            if ($skipSerialize && $skipDeserialize) {
174 1
                continue;
175
            }
176
177
            /** @var JsonAdapter $jsonAdapterAnnotation */
178 3
            $jsonAdapterAnnotation = $annotations->getAnnotation(JsonAdapter::class, AnnotationSet::TYPE_PROPERTY);
179 3
            $adapter = null !== $jsonAdapterAnnotation
180 1
                ? $typeAdapterProvider->getAdapterFromAnnotation($type, $jsonAdapterAnnotation)
181 3
                : $typeAdapterProvider->getAdapter($type);
182
183 3
            $property->setTypeAdapter($adapter);
184 3
            $property->setSkipSerialize($skipSerialize);
185 3
            $property->setSkipDeserialize($skipDeserialize);
186
187 3
            $properties->add($property);
188
        }
189
190
        // add virtual properties
191 4
        foreach ($reflectionClass->getMethods() as $reflectionMethod) {
192 4
            $annotations = $this->annotationCollectionFactory->createMethodAnnotations($reflectionMethod->getDeclaringClass()->getName(), $reflectionMethod->getName());
193 4
            if (null === $annotations->getAnnotation(VirtualProperty::class, AnnotationSet::TYPE_METHOD)) {
194 2
                continue;
195
            }
196
197 4
            $serializedName = $this->propertyNamer->serializedName($reflectionMethod->getName(), $annotations, AnnotationSet::TYPE_METHOD);
198 4
            $type = $this->phpTypeFactory->create($annotations, AnnotationSet::TYPE_METHOD, $reflectionMethod);
199 4
            $getterStrategy = new GetByMethod($reflectionMethod->getName());
200 4
            $setterStrategy = new SetByNull();
201
202 4
            $property = new Property(
203 4
                $reflectionMethod->getName(),
204
                $serializedName,
205
                $type,
206
                $getterStrategy,
207
                $setterStrategy,
208
                $annotations,
209 4
                $reflectionMethod->getModifiers(),
210 4
                true
211
            );
212
213 4
            $classMetadata = $this->metadataFactory->createClassMetadata($reflectionMethod->getDeclaringClass()->getName());
214 4
            $propertyMetadata = $this->metadataFactory->createPropertyMetadata($property, $classMetadata);
215
216 4
            $skipSerialize = $this->excludeProperty($propertyMetadata, true);
217 4
            $skipDeserialize = $this->excludeProperty($propertyMetadata, false);
218
219
            // if we're skipping serialization and deserialization, we don't need
220
            // to add the property to the collection
221 4
            if ($skipSerialize && $skipDeserialize) {
222 1
                continue;
223
            }
224
225
            /** @var JsonAdapter $jsonAdapterAnnotation */
226 3
            $jsonAdapterAnnotation = $annotations->getAnnotation(JsonAdapter::class, AnnotationSet::TYPE_METHOD);
227 3
            $adapter = null !== $jsonAdapterAnnotation
228 1
                ? $typeAdapterProvider->getAdapterFromAnnotation($type, $jsonAdapterAnnotation)
229 3
                : $typeAdapterProvider->getAdapter($type);
230
231 3
            $property->setTypeAdapter($adapter);
232 3
            $property->setSkipSerialize($skipSerialize);
233 3
            $property->setSkipDeserialize($skipDeserialize);
234
235 3
            $properties->add($property);
236
        }
237
238 4
        $this->cache->save($class, $properties);
239
240 4
        return $properties;
241
    }
242
243
    /**
244
     * Returns true if we should skip this property
245
     *
246
     * Asks the excluder if we should skip the property or class
247
     *
248
     * @param PropertyMetadata $propertyMetadata
249
     * @param bool $serialize
250
     * @return bool
251
     * @throws \InvalidArgumentException If the type does not exist
252
     */
253 4
    private function excludeProperty(PropertyMetadata $propertyMetadata, bool $serialize): bool
254
    {
255 4
        $excludeClass = $this->excluder->excludeClass($propertyMetadata->getDeclaringClassMetadata(), $serialize);
256 4
        $excludeProperty = $this->excluder->excludeProperty($propertyMetadata, $serialize);
257
258 4
        return $excludeClass || $excludeProperty;
259
    }
260
}
261