Completed
Push — master ( e76dd9...901d9a )
by Joshua
8s
created

EntityUtility::isEntityTypeValid()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
namespace As3\Modlr\Util;
4
5
use As3\Modlr\Exception\RuntimeException;
6
use As3\Modlr\Exception\MetadataException;
7
use As3\Modlr\Rest\RestConfiguration;
8
use As3\Modlr\Metadata\EntityMetadata;
9
use As3\Modlr\Metadata\MetadataFactory;
10
use As3\Modlr\DataTypes\TypeFactory;
11
12
/**
13
 * Responsibile for formatting entity names, such as entity types and field keys.
14
 *
15
 * @author Jacob Bare <[email protected]>
16
 */
17
class EntityUtility
18
{
19
    /**
20
     * @var RestConfiguration
21
     */
22
    private $config;
23
24
    /**
25
     * @var Inflector
26
     */
27
    private $inflector;
28
29
    /**
30
     * @var TypeFactory
31
     */
32
    private $typeFactory;
33
34
    /**
35
     * Constructor.
36
     *
37
     * @param   RestConfiguration   $config
38
     */
39
    public function __construct(RestConfiguration $config, TypeFactory $typeFactory)
40
    {
41
        $this->config = $config;
42
        $this->typeFactory = $typeFactory;
43
        $this->inflector = new Inflector();
44
    }
45
46
    /**
47
     * Gets the validator service.
48
     *
49
     * @return  Validator
50
     */
51
    public function getValidator()
52
    {
53
        return $this->config->getValidator();
54
    }
55
56
    /**
57
     * Gets the REST configuration object.
58
     *
59
     * @return  RestConfiguration
60
     */
61
    public function getRestConfig()
62
    {
63
        return $this->config;
64
    }
65
66
    /**
67
     * Determines if a field key is valid, based on configuration.
68
     *
69
     * @param   string  $value
70
     * @return  bool
71
     */
72
    public function isFieldKeyValid($value)
73
    {
74
        return $this->getValidator()->isNameValid($this->config->getFieldKeyFormat(), $value);
75
    }
76
77
    /**
78
     * Determines if an entity type, based on configuration.
79
     *
80
     * @param   string  $value
81
     * @return  bool
82
     */
83
    public function isEntityTypeValid($value)
84
    {
85
        return $this->getValidator()->isNameValid($this->config->getEntityFormat(), $value);
86
    }
87
88
    /**
89
     * Validates EntityMetadata.
90
     *
91
     * @param   string          $requestedType
92
     * @param   EntityMetadata  $metadata
93
     * @param   MetadataFactory $mf
94
     * @return  bool
95
     * @throws  MetadataException
96
     */
97
    public function validateMetadata($requestedType, EntityMetadata $metadata, MetadataFactory $mf)
98
    {
99
        if ($requestedType !== $metadata->type) {
100
            throw MetadataException::invalidMetadata($requestedType, 'Metadata type mismatch.');
101
        }
102
        $this->validateMetadataType($metadata);
103
104
        if (null === $metadata->persistence) {
105
            throw MetadataException::invalidMetadata($requestedType, 'No persistence metadata was found. All models must use a persistence layer.');
106
        }
107
108
        $this->validateMetadataInheritance($metadata, $mf);
109
        $this->validateMetadataAttributes($metadata, $mf);
110
        $this->validateMetadataRelationships($metadata, $mf);
111
        return true;
112
    }
113
114
    /**
115
     * Validates the proper entity type on EntityMetadata.
116
     *
117
     * @param   EntityMetadata  $metadata
118
     * @return  bool
119
     * @throws  MetadataException
120
     */
121
    public function validateMetadataType(EntityMetadata $metadata)
122
    {
123
        if (true === $metadata->isChildEntity()) {
124
            $parentType = $metadata->getParentEntityType();
125
            if (0 !== strpos($metadata->type, $parentType)) {
126
                throw MetadataException::invalidMetadata($metadata->type, sprintf('Child class types must be prefixed by the parent class. Expected "%s" prefix.', $parentType));
127
            }
128
        }
129 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...
130
            throw MetadataException::invalidMetadata($metadata->type, sprintf('The entity type is invalid based on the configured name format "%s"', $this->config->getEntityFormat()));
131
        }
132
        return true;
133
    }
134
135
    /**
136
     * Validates the proper entity inheritance on EntityMetadata.
137
     *
138
     * @param   EntityMetadata  $metadata
139
     * @param   MetadataFactory $mf
140
     * @return  bool
141
     * @throws  MetadataException
142
     */
143
    public function validateMetadataInheritance(EntityMetadata $metadata, MetadataFactory $mf)
144
    {
145
        if (true === $metadata->isPolymorphic()) {
146
            foreach ($metadata->ownedTypes as $child) {
147
148 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...
149
                    throw MetadataException::invalidMetadata($metadata->type, sprintf('The owned entity type "%s" is invalid based on the configured name format "%s"', $child, $this->config->getEntityFormat()));
150
                }
151
152 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...
153
                    throw MetadataException::invalidMetadata($metadata->type, sprintf('The entity owns a type "%s" that does not exist.', $child));
154
                }
155
            }
156
        }
157
158
        if (false === $metadata->isPolymorphic() && true === $metadata->isAbstract()) {
159
            throw MetadataException::invalidMetadata($metadata->type, 'An entity must be polymorphic to be abstract.');
160
        }
161
162
        if (false === $metadata->isChildEntity()) {
163
            return true;
164
        }
165
        if (true === $metadata->isPolymorphic()) {
166
            throw MetadataException::invalidMetadata($metadata->type, 'An entity cannot both be polymorphic and be a child.');
167
        }
168
        if ($metadata->extends === $metadata->type) {
169
            throw MetadataException::invalidMetadata($metadata->type, 'An entity cannot extend itself.');
170
        }
171
172 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...
173
            throw MetadataException::invalidMetadata($metadata->type, sprintf('The parent entity type "%s" does not exist.', $metadata->extends));
174
        }
175
176
        $parent = $mf->getMetadataForType($metadata->extends);
0 ignored issues
show
Documentation introduced by
$metadata->extends 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...
177
        if (false === $parent->isPolymorphic()) {
178
            throw MetadataException::invalidMetadata($metadata->type, sprintf('Parent classes must be polymorphic. Parent entity "%s" is not polymorphic.', $metadata->extends));
179
        }
180
        return true;
181
    }
182
183
    /**
184
     * Validates entity attributes on EntityMetadata.
185
     *
186
     * @param   EntityMetadata  $metadata
187
     * @param   MetadataFactory $mf
188
     * @return  bool
189
     * @throws  MetadataException
190
     */
191
    public function validateMetadataAttributes(EntityMetadata $metadata, MetadataFactory $mf)
192
    {
193
        foreach ($metadata->getAttributes() as $key => $attribute) {
194
            if ($key != $attribute->key) {
195
                throw MetadataException::invalidMetadata($metadata->type, sprintf('Attribute key mismtach. "%s" !== "%s"', $key, $attribute->key));
196
            }
197
198 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...
199
                throw MetadataException::invalidMetadata($metadata->type, sprintf('The attribute key "%s" is invalid based on the configured name format "%s"', $attribute->key, $this->config->getFieldKeyFormat()));
200
            }
201
202
            if (false === $this->typeFactory->hasType($attribute->dataType)) {
203
                throw MetadataException::invalidMetadata($metadata->type, sprintf('The data type "%s" for attribute "%s" is invalid', $attribute->dataType, $attribute->key));
204
            }
205
206 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...
207
                $parent = $mf->getMetadataForType($metadata->extends);
0 ignored issues
show
Documentation introduced by
$metadata->extends 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...
208
                if ($parent->hasAttribute($attribute->key)) {
209
                    throw MetadataException::invalidMetadata($metadata->type, sprintf('Parent entity type "%s" already contains attribute field "%s"', $parent->type, $attribute->key));
210
                }
211
            }
212
213
            if (true === $attribute->isCalculated()) {
214
                if (false === class_exists($attribute->calculated['class']) || false === method_exists($attribute->calculated['class'], $attribute->calculated['method'])) {
215
                    throw MetadataException::invalidMetadata($metadata->type, sprintf('The attribute field "%s" is calculated, but was unable to find method "%s:%s"', $attribute->key, $attribute->calculated['class'], $attribute->calculated['method']));
216
                }
217
            }
218
        }
219
        return true;
220
    }
221
222
    /**
223
     * Validates entity relationships on EntityMetadata.
224
     *
225
     * @param   EntityMetadata  $metadata
226
     * @param   MetadataFactory $mf
227
     * @return  bool
228
     * @throws  MetadataException
229
     */
230
    public function validateMetadataRelationships(EntityMetadata $metadata, MetadataFactory $mf)
231
    {
232
        foreach ($metadata->getRelationships() as $key => $relationship) {
233
            if ($key != $relationship->key) {
234
                throw MetadataException::invalidMetadata($metadata->type, sprintf('Relationship key mismtach. "%s" !== "%s"', $key, $relationship->key));
235
            }
236 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...
237
                throw MetadataException::invalidMetadata($metadata->type, sprintf('The relationship key "%s" is invalid based on the configured name format "%s"', $relationship->key, $this->config->getFieldKeyFormat()));
238
            }
239 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...
240
                $parent = $mf->getMetadataForType($metadata->extends);
0 ignored issues
show
Documentation introduced by
$metadata->extends 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...
241
                if ($parent->hasRelationship($relationship->key)) {
242
                    throw MetadataException::invalidMetadata($metadata->type, sprintf('Parent entity type "%s" already contains relationship field "%s"', $parent->type, $relationship->key));
243
                }
244
            }
245 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...
246
                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()));
247
            }
248
            if (false === $mf->metadataExists($relationship->entityType)) {
249
                throw MetadataException::invalidMetadata($metadata->type, sprintf('The related model "%s" for relationship "%s" does not exist.', $relationship->entityType, $relationship->key));
250
            }
251
            if (true === $relationship->isInverse) {
252
                if ('one' === $relationship->relType) {
253
                    throw MetadataException::invalidMetadata($metadata->type, 'The relationship is inverse and one, which is currently not supported.');
254
                }
255
                if (empty($relationship->inverseField)) {
256
                    throw MetadataException::invalidMetadata($metadata->type, 'The relationship is inverse, but no inverse field was specified.');
257
                }
258
                $related = ($relationship->entityType === $metadata->type) ? $metadata : $mf->getMetadataForType($relationship->entityType);
259
                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...
260
                    throw MetadataException::invalidMetadata($metadata->type, 'The relationship is inverse, but the related model does not contain the specified inverse field.');
261
                }
262
                $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...
263
                if (true === $relatedRel->isInverse) {
264
                    throw MetadataException::invalidMetadata($metadata->type, 'The relationship is inverse, but the relationship it references is also inverse.');
265
                }
266
                if ('one' !== $relatedRel->relType) {
267
                    throw MetadataException::invalidMetadata($metadata->type, 'The relationship is inverse and many, but it\'s reference is not a type of one.');
268
                }
269
270
            }
271
        }
272
        return true;
273
    }
274
275
    /**
276
     * Formats an entity type, based on configuration.
277
     *
278
     * @param   string  $type
279
     * @return  string
280
     */
281
    public function formatEntityType($type)
282
    {
283
        return $this->formatValue($this->config->getEntityFormat(), $type);
284
    }
285
286
    /**
287
     * Formats a field key, based on configuration.
288
     *
289
     * @param   string  $key
290
     * @return  string
291
     */
292
    public function formatFieldKey($key)
293
    {
294
        return $this->formatValue($this->config->getFieldKeyFormat(), $key);
295
    }
296
297
    /**
298
     * Formats a value, based on a formatting type.
299
     *
300
     * @param   string  $format
301
     * @param   string  $value
302
     * @return  string
303
     * @throws  RuntimeException
304
     */
305
    protected function formatValue($format, $value)
306
    {
307
        switch ($format) {
308
            case 'dash':
309
                return $this->inflector->dasherize($value);
310
            case 'underscore':
311
                return $this->inflector->underscore($value);
312
            case 'studlycaps':
313
                return $this->inflector->studlify($value);
314
            case 'camelcase':
315
                return $this->inflector->camelize($value);
316
            default:
317
                throw new RuntimeException('Unable to format value');
318
        }
319
    }
320
}
321