Completed
Push — master ( e24a08...649c71 )
by Joshua
9s
created

EntityUtility::validateMetadataAttributes()   C

Complexity

Conditions 13
Paths 44

Size

Total Lines 32
Code Lines 17

Duplication

Lines 9
Ratio 28.13 %

Importance

Changes 3
Bugs 1 Features 1
Metric Value
c 3
b 1
f 1
dl 9
loc 32
rs 5.1234
cc 13
eloc 17
nc 44
nop 2

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace As3\Modlr\Util;
4
5
use As3\Modlr\DataTypes\TypeFactory;
6
use As3\Modlr\Exception\MetadataException;
7
use As3\Modlr\Exception\RuntimeException;
8
use As3\Modlr\Metadata\EntityMetadata;
9
use As3\Modlr\Metadata\Interfaces\EmbedInterface;
10
use As3\Modlr\Metadata\Interfaces\AttributeInterface;
11
use As3\Modlr\Metadata\MetadataFactory;
12
use As3\Modlr\Rest\RestConfiguration;
13
14
/**
15
 * Responsibile for formatting entity names, such as entity types and field keys.
16
 *
17
 * @author Jacob Bare <[email protected]>
18
 */
19
class EntityUtility
20
{
21
    /**
22
     * @var RestConfiguration
23
     */
24
    private $config;
25
26
    /**
27
     * @var Inflector
28
     */
29
    private $inflector;
30
31
    /**
32
     * @var TypeFactory
33
     */
34
    private $typeFactory;
35
36
    /**
37
     * Constructor.
38
     *
39
     * @param   RestConfiguration   $config
40
     */
41
    public function __construct(RestConfiguration $config, TypeFactory $typeFactory)
42
    {
43
        $this->config = $config;
44
        $this->typeFactory = $typeFactory;
45
        $this->inflector = new Inflector();
46
    }
47
48
    /**
49
     * Gets the validator service.
50
     *
51
     * @return  Validator
52
     */
53
    public function getValidator()
54
    {
55
        return $this->config->getValidator();
56
    }
57
58
    /**
59
     * Gets the REST configuration object.
60
     *
61
     * @return  RestConfiguration
62
     */
63
    public function getRestConfig()
64
    {
65
        return $this->config;
66
    }
67
68
    /**
69
     * Determines if a field key is valid, based on configuration.
70
     *
71
     * @param   string  $value
72
     * @return  bool
73
     */
74
    public function isFieldKeyValid($value)
75
    {
76
        return $this->getValidator()->isNameValid($this->config->getFieldKeyFormat(), $value);
77
    }
78
79
    /**
80
     * Determines if an entity type, based on configuration.
81
     *
82
     * @param   string  $value
83
     * @return  bool
84
     */
85
    public function isEntityTypeValid($value)
86
    {
87
        return $this->getValidator()->isNameValid($this->config->getEntityFormat(), $value);
88
    }
89
90
    /**
91
     * Validates EntityMetadata.
92
     *
93
     * @param   string          $requestedType
94
     * @param   EntityMetadata  $metadata
95
     * @param   MetadataFactory $mf
96
     * @return  bool
97
     * @throws  MetadataException
98
     */
99
    public function validateMetadata($requestedType, EntityMetadata $metadata, MetadataFactory $mf)
100
    {
101
        if ($requestedType !== $metadata->type) {
102
            throw MetadataException::invalidMetadata($requestedType, 'Metadata type mismatch.');
103
        }
104
        $this->validateMetadataType($metadata);
105
106
        if (null === $metadata->persistence) {
107
            throw MetadataException::invalidMetadata($requestedType, 'No persistence metadata was found. All models must use a persistence layer.');
108
        }
109
110
        $this->validateMetadataInheritance($metadata, $mf);
111
        $this->validateMetadataAttributes($metadata, $mf);
112
        $this->validateMetadataRelationships($metadata, $mf);
113
        $this->validateMetadataEmbeds($metadata, $mf);
114
        return true;
115
    }
116
117
    /**
118
     * Validates the proper entity type on EntityMetadata.
119
     *
120
     * @param   EntityMetadata  $metadata
121
     * @return  bool
122
     * @throws  MetadataException
123
     */
124
    public function validateMetadataType(EntityMetadata $metadata)
125
    {
126
        if (true === $metadata->isChildEntity()) {
127
            $parentType = $metadata->getParentEntityType();
128
            if (0 !== strpos($metadata->type, $parentType)) {
129
                throw MetadataException::invalidMetadata($metadata->type, sprintf('Child class types must be prefixed by the parent class. Expected "%s" prefix.', $parentType));
130
            }
131
        }
132 View Code Duplication
        if (false === $this->isEntityTypeValid($metadata->type)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
133
            throw MetadataException::invalidMetadata($metadata->type, sprintf('The entity type is invalid based on the configured name format "%s"', $this->config->getEntityFormat()));
134
        }
135
        return true;
136
    }
137
138
    /**
139
     * Validates the proper entity inheritance on EntityMetadata.
140
     *
141
     * @param   EntityMetadata  $metadata
142
     * @param   MetadataFactory $mf
143
     * @return  bool
144
     * @throws  MetadataException
145
     */
146
    public function validateMetadataInheritance(EntityMetadata $metadata, MetadataFactory $mf)
147
    {
148
        if (true === $metadata->isPolymorphic()) {
149
            foreach ($metadata->ownedTypes as $child) {
150
151 View Code Duplication
                if (false === $this->isEntityTypeValid($child)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
152
                    throw MetadataException::invalidMetadata($metadata->type, sprintf('The owned entity type "%s" is invalid based on the configured name format "%s"', $child, $this->config->getEntityFormat()));
153
                }
154
155 View Code Duplication
                if (false === $mf->metadataExists($child)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
156
                    throw MetadataException::invalidMetadata($metadata->type, sprintf('The entity owns a type "%s" that does not exist.', $child));
157
                }
158
            }
159
        }
160
161
        if (false === $metadata->isPolymorphic() && true === $metadata->isAbstract()) {
162
            throw MetadataException::invalidMetadata($metadata->type, 'An entity must be polymorphic to be abstract.');
163
        }
164
165
        if (false === $metadata->isChildEntity()) {
166
            return true;
167
        }
168
        if (true === $metadata->isPolymorphic()) {
169
            throw MetadataException::invalidMetadata($metadata->type, 'An entity cannot both be polymorphic and be a child.');
170
        }
171
        if ($metadata->extends === $metadata->type) {
172
            throw MetadataException::invalidMetadata($metadata->type, 'An entity cannot extend itself.');
173
        }
174
175 View Code Duplication
        if (false === $mf->metadataExists($metadata->extends)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
176
            throw MetadataException::invalidMetadata($metadata->type, sprintf('The parent entity type "%s" does not exist.', $metadata->extends));
177
        }
178
179
        $parent = $mf->getMetadataForType($metadata->extends);
180
        if (false === $parent->isPolymorphic()) {
181
            throw MetadataException::invalidMetadata($metadata->type, sprintf('Parent classes must be polymorphic. Parent entity "%s" is not polymorphic.', $metadata->extends));
182
        }
183
        return true;
184
    }
185
186
    /**
187
     * Validates entity attributes on the AttributeInterface.
188
     *
189
     * @param   AttributeInterface  $metadata
190
     * @param   MetadataFactory     $mf
191
     * @return  bool
192
     * @throws  MetadataException
193
     */
194
    public function validateMetadataAttributes(AttributeInterface $metadata, MetadataFactory $mf)
195
    {
196
        $type = $metadata instanceof EntityMetadata ? $metadata->type : (property_exists($metadata, 'name') ? $metadata->name : '');
197
        foreach ($metadata->getAttributes() as $key => $attribute) {
198
199
            if ($key != $attribute->key) {
200
                throw MetadataException::invalidMetadata($type, sprintf('Attribute key mismtach. "%s" !== "%s"', $key, $attribute->key));
201
            }
202
203 View Code Duplication
            if (false === $this->isFieldKeyValid($attribute->key)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
204
                throw MetadataException::invalidMetadata($type, sprintf('The attribute key "%s" is invalid based on the configured name format "%s"', $attribute->key, $this->config->getFieldKeyFormat()));
205
            }
206
207
            if (false === $this->typeFactory->hasType($attribute->dataType)) {
208
                throw MetadataException::invalidMetadata($type, sprintf('The data type "%s" for attribute "%s" is invalid', $attribute->dataType, $attribute->key));
209
            }
210
211 View Code Duplication
            if ($metadata instanceof EntityMetadata && true === $metadata->isChildEntity()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
212
                $parent = $mf->getMetadataForType($metadata->extends);
213
                if ($parent->hasAttribute($attribute->key)) {
214
                    throw MetadataException::invalidMetadata($type, sprintf('Parent entity type "%s" already contains attribute field "%s"', $parent->type, $attribute->key));
215
                }
216
            }
217
218
            if (true === $attribute->isCalculated()) {
219
                if (false === class_exists($attribute->calculated['class']) || false === method_exists($attribute->calculated['class'], $attribute->calculated['method'])) {
220
                    throw MetadataException::invalidMetadata($type, sprintf('The attribute field "%s" is calculated, but was unable to find method "%s:%s"', $attribute->key, $attribute->calculated['class'], $attribute->calculated['method']));
221
                }
222
            }
223
        }
224
        return true;
225
    }
226
227
    /**
228
     * Validates entity relationships on EntityMetadata.
229
     *
230
     * @param   EmbedInterface  $metadata
231
     * @param   MetadataFactory $mf
232
     * @return  bool
233
     */
234
    public function validateMetadataEmbeds(EmbedInterface $metadata, MetadataFactory $mf)
235
    {
236
        foreach ($metadata->getEmbeds() as $key => $embeddedProp) {
237
            $this->validateMetadataAttributes($embeddedProp->embedMeta, $mf);
238
            $this->validateMetadataEmbeds($embeddedProp->embedMeta, $mf);
239
        }
240
        return true;
241
    }
242
243
    /**
244
     * Validates entity relationships on EntityMetadata.
245
     *
246
     * @param   EntityMetadata  $metadata
247
     * @param   MetadataFactory $mf
248
     * @return  bool
249
     * @throws  MetadataException
250
     */
251
    public function validateMetadataRelationships(EntityMetadata $metadata, MetadataFactory $mf)
252
    {
253
        foreach ($metadata->getRelationships() as $key => $relationship) {
254
            if ($key != $relationship->key) {
255
                throw MetadataException::invalidMetadata($metadata->type, sprintf('Relationship key mismtach. "%s" !== "%s"', $key, $relationship->key));
256
            }
257 View Code Duplication
            if (false === $this->isFieldKeyValid($relationship->key)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
258
                throw MetadataException::invalidMetadata($metadata->type, sprintf('The relationship key "%s" is invalid based on the configured name format "%s"', $relationship->key, $this->config->getFieldKeyFormat()));
259
            }
260 View Code Duplication
            if (true === $metadata->isChildEntity()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
261
                $parent = $mf->getMetadataForType($metadata->extends);
262
                if ($parent->hasRelationship($relationship->key)) {
263
                    throw MetadataException::invalidMetadata($metadata->type, sprintf('Parent entity type "%s" already contains relationship field "%s"', $parent->type, $relationship->key));
264
                }
265
            }
266 View Code Duplication
            if (false === $this->isEntityTypeValid($relationship->entityType)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
267
                throw MetadataException::invalidMetadata($metadata->type, sprintf('The related model "%s" for relationship "%s" is invalid based on the configured name format "%s"', $relationship->entityType, $relationship->key, $this->config->getEntityFormat()));
268
            }
269
            if (false === $mf->metadataExists($relationship->entityType)) {
270
                throw MetadataException::invalidMetadata($metadata->type, sprintf('The related model "%s" for relationship "%s" does not exist.', $relationship->entityType, $relationship->key));
271
            }
272
            if (true === $relationship->isInverse) {
273
                if ('one' === $relationship->relType) {
274
                    throw MetadataException::invalidMetadata($metadata->type, 'The relationship is inverse and one, which is currently not supported.');
275
                }
276
                if (empty($relationship->inverseField)) {
277
                    throw MetadataException::invalidMetadata($metadata->type, 'The relationship is inverse, but no inverse field was specified.');
278
                }
279
                $related = ($relationship->entityType === $metadata->type) ? $metadata : $mf->getMetadataForType($relationship->entityType);
280
                if (false === $related->hasRelationship($relationship->inverseField)) {
0 ignored issues
show
Documentation introduced by
$relationship->inverseField is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
281
                    throw MetadataException::invalidMetadata($metadata->type, 'The relationship is inverse, but the related model does not contain the specified inverse field.');
282
                }
283
                $relatedRel = $related->getRelationship($relationship->inverseField);
0 ignored issues
show
Documentation introduced by
$relationship->inverseField is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
284
                if (true === $relatedRel->isInverse) {
285
                    throw MetadataException::invalidMetadata($metadata->type, 'The relationship is inverse, but the relationship it references is also inverse.');
286
                }
287
                if ('one' !== $relatedRel->relType) {
288
                    throw MetadataException::invalidMetadata($metadata->type, 'The relationship is inverse and many, but it\'s reference is not a type of one.');
289
                }
290
291
            }
292
        }
293
        return true;
294
    }
295
296
    /**
297
     * Formats an entity type, based on configuration.
298
     *
299
     * @param   string  $type
300
     * @return  string
301
     */
302
    public function formatEntityType($type)
303
    {
304
        return $this->formatValue($this->config->getEntityFormat(), $type);
305
    }
306
307
    /**
308
     * Formats a field key, based on configuration.
309
     *
310
     * @param   string  $key
311
     * @return  string
312
     */
313
    public function formatFieldKey($key)
314
    {
315
        return $this->formatValue($this->config->getFieldKeyFormat(), $key);
316
    }
317
318
    /**
319
     * Formats a value, based on a formatting type.
320
     *
321
     * @param   string  $format
322
     * @param   string  $value
323
     * @return  string
324
     * @throws  RuntimeException
325
     */
326
    protected function formatValue($format, $value)
327
    {
328
        switch ($format) {
329
            case 'dash':
330
                return $this->inflector->dasherize($value);
331
            case 'underscore':
332
                return $this->inflector->underscore($value);
333
            case 'studlycaps':
334
                return $this->inflector->studlify($value);
335
            case 'camelcase':
336
                return $this->inflector->camelize($value);
337
            default:
338
                throw new RuntimeException('Unable to format value');
339
        }
340
    }
341
}
342