ClassMetadata   A
last analyzed

Complexity

Total Complexity 40

Size/Duplication

Total Lines 316
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 3
Bugs 0 Features 1
Metric Value
eloc 64
c 3
b 0
f 1
dl 0
loc 316
ccs 88
cts 88
cp 1
rs 9.2
wmc 40

27 Methods

Rating   Name   Duplication   Size   Complexity  
A getFieldNames() 0 3 1
A getAssociationNames() 0 3 1
A getFieldMappings() 0 3 1
A getTypeOfField() 0 3 1
A getIdentifierFieldNames() 0 3 1
A getIdentifier() 0 3 1
A setIdentifier() 0 3 1
A hasField() 0 3 1
A getName() 0 3 1
A __construct() 0 4 1
A setLifecycleCallbacks() 0 3 1
A setIdentifierFieldNames() 0 3 1
A getLifecycleCallbacks() 0 3 1
A addLifecycleCallback() 0 7 3
A getAssociationTargetClass() 0 9 2
A getReflectionClass() 0 3 1
A isIdentifier() 0 3 1
A hasAssociation() 0 3 1
A initReflField() 0 9 2
A isSingleValuedAssociation() 0 4 2
A mapField() 0 13 4
A getIdentifierValues() 0 8 2
A isCollectionValuedAssociation() 0 4 2
A invokeLifecycleCallbacks() 0 16 4
A hasLifecycleCallbacks() 0 3 1
A isAssociationInverseSide() 0 3 1
A getAssociationMappedByTargetField() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like ClassMetadata often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ClassMetadata, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\SkeletonMapper\Mapping;
6
7
use BadMethodCallException;
8
use InvalidArgumentException;
9
use ReflectionClass;
10
use ReflectionProperty;
11
use function array_keys;
12
use function call_user_func_array;
13
use function get_class;
14
use function in_array;
15
use function sprintf;
16
17
/**
18
 * Class used to hold metadata about mapped classes.
19
 */
20
class ClassMetadata implements ClassMetadataInterface
21
{
22
    /** @var string */
23
    public $name;
24
25
    /** @var mixed[] */
26
    public $identifier = [];
27
28
    /** @var string[] */
29
    public $identifierFieldNames = [];
30
31
    /** @var mixed[][] */
32
    public $fieldMappings = [];
33
34
    /** @var mixed[][] */
35
    public $associationMappings = [];
36
37
    /** @var string[][] */
38
    public $lifecycleCallbacks = [];
39
40
    /** @var ReflectionClass */
41
    public $reflClass;
42
43
    /** @var ReflectionProperty[] */
44
    public $reflFields = [];
45
46 54
    public function __construct(string $className)
47
    {
48 54
        $this->name      = $className;
49 54
        $this->reflClass = new ReflectionClass($className);
50 54
    }
51
52
    /**
53
     * @param mixed[] $identifier
54
     */
55 22
    public function setIdentifier(array $identifier) : void
56
    {
57 22
        $this->identifier = $identifier;
58 22
    }
59
60
    /**
61
     * @param string[] $identifierFieldNames
62
     */
63 22
    public function setIdentifierFieldNames(array $identifierFieldNames) : void
64
    {
65 22
        $this->identifierFieldNames = $identifierFieldNames;
66 22
    }
67
68
    /**
69
     * {@inheritDoc}
70
     */
71 35
    public function mapField(array $mapping) : void
72
    {
73 35
        if (! isset($mapping['name'])) {
74 35
            $mapping['name'] = $mapping['fieldName'];
75
        }
76
77 35
        if (isset($mapping['type']) && isset($mapping['targetObject'])) {
78 5
            $this->associationMappings[$mapping['fieldName']] = $mapping;
79
        } else {
80 30
            $this->fieldMappings[$mapping['fieldName']] = $mapping;
81
        }
82
83 35
        $this->initReflField($mapping);
84 35
    }
85
86
    /**
87
     * Gets the fully-qualified class name of this persistent class.
88
     */
89 3
    public function getName() : string
90
    {
91 3
        return $this->name;
92
    }
93
94
    /**
95
     * Gets the mapped identifier field name.
96
     *
97
     * The returned structure is an array of the identifier field names.
98
     *
99
     * @return mixed[]
100
     */
101 27
    public function getIdentifier() : array
102
    {
103 27
        return $this->identifier;
104
    }
105
106
    /**
107
     * Gets the ReflectionClass instance for this mapped class.
108
     */
109 3
    public function getReflectionClass() : ReflectionClass
110
    {
111 3
        return $this->reflClass;
112
    }
113
114
    /**
115
     * {@inheritDoc}
116
     */
117 3
    public function isIdentifier($fieldName) : bool
118
    {
119 3
        return in_array($fieldName, $this->getIdentifierFieldNames(), true);
120
    }
121
122
    /**
123
     * {@inheritDoc}
124
     */
125 4
    public function hasField($fieldName) : bool
126
    {
127 4
        return isset($this->fieldMappings[$fieldName]);
128
    }
129
130
    /**
131
     * A numerically indexed list of field names of this persistent class.
132
     *
133
     * This array includes identifier fields if present on this class.
134
     *
135
     * @return string[]
136
     */
137 3
    public function getFieldNames() : array
138
    {
139 3
        return array_keys($this->fieldMappings);
140
    }
141
142
    /**
143
     * An array of field mappings for this persistent class indexed by field name.
144
     *
145
     * @return mixed[][]
146
     */
147 2
    public function getFieldMappings() : array
148
    {
149 2
        return $this->fieldMappings;
150
    }
151
152
    /**
153
     * {@inheritDoc}
154
     */
155 2
    public function getAssociationNames() : array
156
    {
157 2
        return array_keys($this->associationMappings);
158
    }
159
160
    /**
161
     * {@inheritDoc}
162
     */
163 2
    public function getTypeOfField($fieldName) : ?string
164
    {
165 2
        return $this->fieldMappings[$fieldName]['type'] ?? null;
166
    }
167
168
    /**
169
     * {@inheritDoc}
170
     */
171 2
    public function getAssociationTargetClass($assocName) : string
172
    {
173 2
        if (! isset($this->associationMappings[$assocName])) {
174 1
            throw new InvalidArgumentException(
175 1
                sprintf("Association name expected, '%s' is not an association.", $assocName)
176
            );
177
        }
178
179 1
        return $this->associationMappings[$assocName]['targetObject'];
180
    }
181
182
    /**
183
     * {@inheritDoc}
184
     */
185 3
    public function getIdentifierValues($object) : array
186
    {
187 3
        $identifier = [];
188 3
        foreach ($this->identifierFieldNames as $identifierFieldName) {
189 3
            $identifier[$this->fieldMappings[$identifierFieldName]['name']] = $this->reflFields[$identifierFieldName]->getValue($object);
190
        }
191
192 3
        return $identifier;
193
    }
194
195
    /**
196
     * {@inheritDoc}
197
     *
198
     * Checks whether the class has a mapped association (embed or reference) with the given field name.
199
     */
200 3
    public function hasAssociation($fieldName) : bool
201
    {
202 3
        return isset($this->associationMappings[$fieldName]);
203
    }
204
205
    /**
206
     * {@inheritDoc}
207
     *
208
     * Checks whether the class has a mapped reference or embed for the specified field and
209
     * is a single valued association.
210
     */
211 2
    public function isSingleValuedAssociation($fieldName) : bool
212
    {
213 2
        return isset($this->associationMappings[$fieldName]['type']) &&
214 2
            $this->associationMappings[$fieldName]['type'] === 'one';
215
    }
216
217
    /**
218
     * {@inheritDoc}
219
     *
220
     * Checks whether the class has a mapped reference or embed for the specified field and
221
     * is a collection valued association.
222
     */
223 2
    public function isCollectionValuedAssociation($fieldName) : bool
224
    {
225 2
        return isset($this->associationMappings[$fieldName]['type']) &&
226 2
            $this->associationMappings[$fieldName]['type'] === 'many';
227
    }
228
229
    /**
230
     * {@inheritDoc}
231
     */
232 23
    public function invokeLifecycleCallbacks(string $event, $object, ?array $arguments = null) : void
233
    {
234 23
        if (! $object instanceof $this->name) {
235 1
            throw new InvalidArgumentException(
236 1
                sprintf('Expected class "%s"; found: "%s"', $this->name, get_class($object))
237
            );
238
        }
239
240 22
        foreach ($this->lifecycleCallbacks[$event] as $callback) {
241 22
            if ($arguments !== null) {
242
                /** @var callable $callable */
243 21
                $callable = [$object, $callback];
244
245 21
                call_user_func_array($callable, $arguments);
246
            } else {
247 1
                $object->$callback();
248
            }
249
        }
250 22
    }
251
252
    /**
253
     * Checks whether the class has callbacks registered for a lifecycle event.
254
     *
255
     * @param string $event Lifecycle event
256
     */
257 23
    public function hasLifecycleCallbacks(string $event) : bool
258
    {
259 23
        return isset($this->lifecycleCallbacks[$event]);
260
    }
261
262
    /**
263
     * Gets the registered lifecycle callbacks for an event.
264
     *
265
     * @return string[]
266
     */
267 1
    public function getLifecycleCallbacks(string $event) : array
268
    {
269 1
        return $this->lifecycleCallbacks[$event] ?? [];
270
    }
271
272
    /**
273
     * Adds a lifecycle callback for objects of this class.
274
     *
275
     * If the callback is already registered, this is a NOOP.
276
     */
277 23
    public function addLifecycleCallback(string $callback, string $event) : void
278
    {
279 23
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event], true)) {
280 1
            return;
281
        }
282
283 23
        $this->lifecycleCallbacks[$event][] = $callback;
284 23
    }
285
286
    /**
287
     * Sets the lifecycle callbacks for objects of this class.
288
     *
289
     * Any previously registered callbacks are overwritten.
290
     *
291
     * @param string[][] $callbacks
292
     */
293 1
    public function setLifecycleCallbacks(array $callbacks) : void
294
    {
295 1
        $this->lifecycleCallbacks = $callbacks;
296 1
    }
297
298
    /**
299
     * Returns an array of identifier field names numerically indexed.
300
     *
301
     * @return string[]
302
     */
303 4
    public function getIdentifierFieldNames() : array
304
    {
305 4
        return $this->identifierFieldNames;
306
    }
307
308
    /**
309
     * {@inheritDoc}
310
     */
311 1
    public function getAssociationMappedByTargetField($fieldName)
312
    {
313 1
        throw new BadMethodCallException(__METHOD__ . '() is not implemented yet.');
314
    }
315
316
    /**
317
     * {@inheritDoc}
318
     */
319 1
    public function isAssociationInverseSide($fieldName)
320
    {
321 1
        throw new BadMethodCallException(__METHOD__ . '() is not implemented yet.');
322
    }
323
324
    /**
325
     * @param mixed[] $mapping
326
     */
327 35
    private function initReflField(array $mapping) : void
328
    {
329 35
        if (! $this->reflClass->hasProperty($mapping['fieldName'])) {
330 9
            return;
331
        }
332
333 27
        $reflProp = $this->reflClass->getProperty($mapping['fieldName']);
334 27
        $reflProp->setAccessible(true);
335 27
        $this->reflFields[$mapping['fieldName']] = $reflProp;
336 27
    }
337
}
338