ResourceType   F
last analyzed

Complexity

Total Complexity 102

Size/Duplication

Total Lines 969
Duplicated Lines 0 %

Test Coverage

Coverage 72.86%

Importance

Changes 5
Bugs 2 Features 0
Metric Value
wmc 102
eloc 319
c 5
b 2
f 0
dl 0
loc 969
ccs 255
cts 350
cp 0.7286
rs 2

34 Methods

Rating   Name   Duplication   Size   Complexity  
A getInstanceType() 0 3 1
A hasETagProperties() 0 4 1
A hasNamedStream() 0 13 2
A getName() 0 3 1
A isAbstract() 0 3 1
A getAllProperties() 0 13 3
B addProperty() 0 50 9
A getPropertiesDeclaredOnThisType() 0 3 1
A getResourceTypeKind() 0 3 1
A isMediaLinkEntry() 0 3 1
A getCustomState() 0 3 1
A getNamespace() 0 3 1
A isAssignableFrom() 0 12 3
A tryResolveNamedStreamByName() 0 7 2
A hasBaseType() 0 3 1
A resolveProperty() 0 7 2
A getAllNamedStreams() 0 15 3
A validateType() 0 7 3
A setCustomState() 0 3 1
A getETagProperties() 0 13 4
A setMediaLinkEntry() 0 9 2
A __wakeup() 0 3 2
A __unserialize() 0 20 2
A getKeyProperties() 0 18 6
A resolvePropertyDeclaredOnThisType() 0 7 2
A tryResolveNamedStreamDeclaredOnThisTypeByName() 0 7 2
B __construct() 0 45 7
A addNamedStream() 0 29 4
D getPrimitiveResourceType() 0 118 17
A getNamedStreamsDeclaredOnThisType() 0 3 1
A getBaseType() 0 3 1
A __serialize() 0 22 2
B hasBagProperty() 0 72 11
A getFullName() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like ResourceType 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 ResourceType, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace POData\Providers\Metadata;
4
5
use POData\Providers\Metadata\Type\Binary;
6
use POData\Providers\Metadata\Type\Boolean;
7
use POData\Providers\Metadata\Type\Byte;
8
use POData\Providers\Metadata\Type\DateTime;
9
use POData\Providers\Metadata\Type\DateTimeTz;
10
use POData\Providers\Metadata\Type\Decimal;
11
use POData\Providers\Metadata\Type\Double;
12
use POData\Providers\Metadata\Type\Guid;
13
use POData\Providers\Metadata\Type\Int16;
14
use POData\Providers\Metadata\Type\Int32;
15
use POData\Providers\Metadata\Type\Int64;
16
use POData\Providers\Metadata\Type\SByte;
17
use POData\Providers\Metadata\Type\Single;
18
use POData\Providers\Metadata\Type\StringType;
19
use POData\Providers\Metadata\Type\EdmPrimitiveType;
20
use POData\Providers\Metadata\Type\IType;
21
use POData\Providers\Metadata\Type\Time;
0 ignored issues
show
Bug introduced by
The type POData\Providers\Metadata\Type\Time was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
22
use POData\Common\Messages;
23
use POData\Common\InvalidOperationException;
24
use ReflectionClass;
25
use InvalidArgumentException;
26
27
/**
28
 * Class ResourceType
29
 *
30
 * A type to describe an entity type, complex type or primitive type.
31
 *
32
 * @package POData\Providers\Metadata
33
 */
34
class ResourceType
35
{
36
    /**
37
     * Name of the resource described by this class instance.
38
     *
39
     * @var string
40
     */
41
    protected $_name;
42
43
    /**
44
     * Namespace name in which resource described by this class instance
45
     * belongs to.
46
     *
47
     * @var string
48
     */
49
    protected $_namespaceName;
50
51
    /**
52
     * The fully qualified name of the resource described by this class instance.
53
     *
54
     * @var string
55
     */
56
    protected $_fullName;
57
58
    /**
59
     * The type the resource described by this class instance.
60
     * Note: either Entity or Complex Type
61
     *
62
     * @var ResourceTypeKind|int
63
     */
64
    protected $_resourceTypeKind;
65
66
    /**
67
     * @var boolean
68
     */
69
    protected $_abstractType;
70
71
    /**
72
     * Refrence to ResourceType instance for base type, if any.
73
     *
74
     * @var ResourceType
75
     */
76
    protected $_baseType;
77
78
    /**
79
     * Collection of ResourceProperty for all properties declared on the
80
     * resource described by this class instance (This does not include
81
     * base type properties).
82
     *
83
     * @var ResourceProperty[] indexed by name
84
     */
85
    protected $_propertiesDeclaredOnThisType = array();
86
87
    /**
88
     * Collection of ResourceStreamInfo for all named streams declared on
89
     * the resource described by this class instance (This does not include
90
     * base type properties).
91
     *
92
     * @var ResourceStreamInfo[] indexed by name
93
     */
94
    protected $_namedStreamsDeclaredOnThisType = array();
95
96
    /**
97
     * Collection of ReflectionProperty instances for each property declared
98
     * on this type
99
     *
100
     * @var array(ResourceProperty, ReflectionProperty)
101
     */
102
    protected $_propertyInfosDeclaredOnThisType = array();
103
104
    /**
105
     * Collection of ResourceProperty for all properties declared on this type.
106
     * and base types.
107
     *
108
     * @var ResourceProperty[] indexed by name
109
     */
110
    protected $_allProperties = array();
111
112
    /**
113
     * Collection of ResourceStreamInfo for all named streams declared on this type.
114
     * and base types
115
     *
116
     * @var ResourceStreamInfo[]
117
     */
118
    protected $_allNamedStreams = array();
119
120
    /**
121
     * Collection of properties which has etag defined subset of $_allProperties
122
     * @var ResourceProperty[]
123
     */
124
    protected $_etagProperties = array();
125
126
    /**
127
     * Collection of key properties subset of $_allProperties
128
     *
129
     * @var ResourceProperty[]
130
     */
131
    protected $_keyProperties = array();
132
133
    /**
134
     * Whether the resource type described by this class instance is a MLE or not
135
     *
136
     * @var boolean
137
     */
138
    protected $_isMediaLinkEntry = false;
139
140
    /**
141
     * Whether the resource type described by this class instance has bag properties
142
     * Note: This has been initialized with null, later in hasBagProperty method,
143
     * this flag will be set to boolean value
144
     *
145
     * @var boolean
146
     */
147
    protected $_hasBagProperty = null;
148
149
    /**
150
     * Whether the resource type described by this class instance has named streams
151
     * Note: This has been intitialized with null, later in hasNamedStreams method,
152
     * this flag will be set to boolean value
153
     *
154
     * @var boolean
155
     */
156
    protected $_hasNamedStreams = null;
157
158
    /**
159
     * ReflectionClass (for complex/Entity) or IType (for Primitive) instance for
160
     * the resource (type) described by this class instance
161
     *
162
     * @var ReflectionClass|IType
163
     */
164
    protected $_type;
165
166
    /**
167
     * To store any custom information related to this class instance
168
     *
169
     * @var Object
170
     */
171
    protected $_customState;
172
173
    /**
174
     * Array to detect looping in bag's complex type
175
     *
176
     * @var array
177
     */
178
    protected $_arrayToDetectLoopInComplexBag;
179
180
    /**
181
     * Create new instance of ResourceType
182
     *
183
     * @param ReflectionClass|IType $instanceType     Instance type for the resource,
184
     *                                                for entity and
185
     *                                                complex this will
186
     *                                                be 'ReflectionClass' and for
187
     *                                                primitive type this
188
     *                                                will be IType
189
     * @param ResourceTypeKind|int  $resourceTypeKind Kind of resource (Entity, Complex or Primitive)
190
     * @param string                $name             Name of the resource
191
     * @param string                $namespaceName    Namespace of the resource
192
     * @param ResourceType          $baseType         Base type of the resource, if exists
193
     *
194
     * @param boolean               $isAbstract       Whether resource is abstract
195
     *
196
     * @throws InvalidArgumentException
197
     */
198 162
    public function __construct(
199
        $instanceType,
200
        $resourceTypeKind,
201
        $name,
202
        $namespaceName = null,
203
        ResourceType $baseType = null,
204
        $isAbstract = false
205
    ) {
206 162
        $this->_type = $instanceType;
207 162
        if ($resourceTypeKind == ResourceTypeKind::PRIMITIVE) {
208 162
            if ($baseType != null) {
209 1
                throw new InvalidArgumentException(
210 1
                    Messages::resourceTypeNoBaseTypeForPrimitive()
211 1
                );
212
            }
213
214 162
            if ($isAbstract) {
215 1
                throw new InvalidArgumentException(
216 1
                    Messages::resourceTypeNoAbstractForPrimitive()
217 1
                );
218
            }
219
220 162
            if (!($instanceType instanceof IType)) {
221 162
                throw new InvalidArgumentException(
222 162
                    Messages::resourceTypeTypeShouldImplementIType('$instanceType')
223 162
                );
224
            }
225
        } else {
226 160
            if (!($instanceType instanceof ReflectionClass)) {
227 1
                throw new InvalidArgumentException(
228 1
                    Messages::resourceTypeTypeShouldReflectionClass('$instanceType')
229 1
                );
230
            }
231
        }
232
233 162
        $this->_resourceTypeKind = $resourceTypeKind;
234 162
        $this->_name = $name;
235 162
        $this->_baseType = $baseType;
236 162
        $this->_namespaceName = $namespaceName;
237 162
        $this->_fullName
238 162
            = is_null($namespaceName) ? $name : $namespaceName . '.' . $name;
239 162
        $this->_abstractType = $isAbstract;
0 ignored issues
show
Documentation Bug introduced by
It seems like $isAbstract can also be of type false. However, the property $_abstractType is declared as type POData\Providers\Metadata\Type\Boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
240 162
        $this->_isMediaLinkEntry = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type POData\Providers\Metadata\Type\Boolean of property $_isMediaLinkEntry.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
241 162
        $this->_customState = null;
242 162
        $this->_arrayToDetectLoopInComplexBag = array();
243
        //TODO: Set MLE if base type has MLE Set
244
    }
245
246
    /**
247
     * Get reference to ResourceType for base class
248
     *
249
     * @return ResourceType
250
     */
251 1
    public function getBaseType()
252
    {
253 1
        return $this->_baseType;
254
    }
255
256
    /**
257
     * To check whether this resource type has base type
258
     *
259
     * @return boolean True if base type is defined, false otherwise
260
     */
261 1
    public function hasBaseType()
262
    {
263 1
        return !is_null($this->_baseType);
0 ignored issues
show
Bug Best Practice introduced by
The expression return ! is_null($this->_baseType) returns the type boolean which is incompatible with the documented return type POData\Providers\Metadata\Type\Boolean.
Loading history...
264
    }
265
266
    /**
267
     * To get custom state object for this type
268
     *
269
     * @return object
270
     */
271 160
    public function getCustomState()
272
    {
273 160
        return $this->_customState;
274
    }
275
276
    /**
277
     * To set custom state object for this type
278
     *
279
     * @param ResourceSet $object The custom object.
280
     *
281
     * @return void
282
     */
283 160
    public function setCustomState($object)
284
    {
285 160
        $this->_customState = $object;
286
    }
287
288
    /**
289
     * Get the instance type. If the resource type describes a complex or entity type
290
     * then this function returns refernece to ReflectionClass instance for the type.
291
     * If resource type describes a primitive type then this function returns ITYpe.
292
     *
293
     * @return ReflectionClass|IType
294
     */
295 160
    public function getInstanceType()
296
    {
297 160
        return $this->_type;
298
    }
299
300
    /**
301
     * Get name of the type described by this resource type
302
     *
303
     * @return string
304
     */
305 162
    public function getName()
306
    {
307 162
        return $this->_name;
308
    }
309
310
    /**
311
     * Get the namespace under which the type described by this resource type is
312
     * defined.
313
     *
314
     * @return string
315
     */
316 2
    public function getNamespace()
317
    {
318 2
        return $this->_namespaceName;
319
    }
320
321
    /**
322
     * Get full name (namespacename.typename) of the type described by this resource
323
     * type.
324
     *
325
     * @return string
326
     */
327 11
    public function getFullName()
328
    {
329 11
        return $this->_fullName;
330
    }
331
332
    /**
333
     * To check whether the type described by this resource type is abstract or not
334
     *
335
     * @return boolean True if type is abstract else False
336
     */
337 1
    public function isAbstract()
338
    {
339 1
        return $this->_abstractType;
340
    }
341
342
    /**
343
     * To get the kind of type described by this resource class
344
     *
345
     * @return ResourceTypeKind
346
     */
347 160
    public function getResourceTypeKind()
348
    {
349 160
        return $this->_resourceTypeKind;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_resourceTypeKind also could return the type integer which is incompatible with the documented return type POData\Providers\Metadata\ResourceTypeKind.
Loading history...
350
    }
351
352
    /**
353
     * To check whether the type described by this resource type is MLE
354
     *
355
     * @return boolean True if type is MLE else False
356
     */
357 2
    public function isMediaLinkEntry()
358
    {
359 2
        return $this->_isMediaLinkEntry;
360
    }
361
362
    /**
363
     * Set the resource type as MLE or non-MLE
364
     *
365
     * @param boolean $isMLE True to set as MLE, false for non-MLE
366
     *
367
     * @return void
368
     */
369 140
    public function setMediaLinkEntry($isMLE)
370
    {
371 140
        if ($this->_resourceTypeKind != ResourceTypeKind::ENTITY) {
372 1
            throw new InvalidOperationException(
373 1
                Messages::resourceTypeHasStreamAttributeOnlyAppliesToEntityType()
374 1
            );
375
        }
376
377 140
        $this->_isMediaLinkEntry = $isMLE;
378
    }
379
380
    /**
381
     * Add a property belongs to this resource type instance
382
     *
383
     * @param ResourceProperty $property Property to add
384
     *
385
     * @throws InvalidOperationException
386
     * @return void
387
     */
388 160
    public function addProperty(ResourceProperty $property)
389
    {
390 160
        if ($this->_resourceTypeKind == ResourceTypeKind::PRIMITIVE) {
391 1
            throw new InvalidOperationException(
392 1
                Messages::resourceTypeNoAddPropertyForPrimitive()
393 1
            );
394
        }
395
396 160
        $name = $property->getName();
397 160
        foreach (array_keys($this->_propertiesDeclaredOnThisType) as $propertyName) {
398 160
            if (strcasecmp($propertyName, $name) == 0) {
399 1
                throw new InvalidOperationException(
400 1
                    Messages::resourceTypePropertyWithSameNameAlreadyExists(
401 1
                        $propertyName, $this->_name
402 1
                    )
403 1
                );
404
            }
405
        }
406
407 160
        if ($property->isKindOf(ResourcePropertyKind::KEY)) {
408 160
            if ($this->_resourceTypeKind != ResourceTypeKind::ENTITY) {
409 1
                throw new InvalidOperationException(
410 1
                    Messages::resourceTypeKeyPropertiesOnlyOnEntityTypes()
411 1
                );
412
            }
413
414 160
            if ($this->_baseType != null) {
415
                throw new InvalidOperationException(
416
                    Messages::resourceTypeNoKeysInDerivedTypes()
417
                );
418
            }
419
        }
420
421 160
        if ($property->isKindOf(ResourcePropertyKind::ETAG)
422 160
            && ($this->_resourceTypeKind != ResourceTypeKind::ENTITY)
423
        ) {
424
            throw new InvalidOperationException(
425
                Messages::resourceTypeETagPropertiesOnlyOnEntityTypes()
426
            );
427
        }
428
429
        //Check for Base class properties
430 160
        $this->_propertiesDeclaredOnThisType[$name] = $property;
431
        // Set $this->_allProperties to null, this is very important because the
432
        // first call to getAllProperties will initilaize $this->_allProperties,
433
        // further call to getAllProperties will not reinitialize _allProperties
434
        // so if addProperty is called after calling getAllProperties then the
435
        // property just added will not be reflected in $this->_allProperties
436 160
        unset($this->_allProperties);
437 160
        $this->_allProperties = array();
438
    }
439
440
    /**
441
     * Get collection properties belongs to this resource type (excluding base class
442
     * properties). This function returns  empty array in case of resource type
443
     * for primitive types.
444
     *
445
     * @return ResourceProperty[]
446
     */
447 2
    public function getPropertiesDeclaredOnThisType()
448
    {
449 2
        return $this->_propertiesDeclaredOnThisType;
450
    }
451
452
    /**
453
     * Get collection properties belongs to this resource type including base class
454
     * properties. This function returns  empty array in case of resource type
455
     * for primitive types.
456
     *
457
     * @return ResourceProperty[]
458
     */
459 160
    public function getAllProperties()
460
    {
461 160
        if (empty($this->_allProperties)) {
462 160
            if ($this->_baseType != null) {
463
                $this->_allProperties = $this->_baseType->getAllProperties();
464
            }
465
466 160
            $this->_allProperties = array_merge(
467 160
                $this->_allProperties, $this->_propertiesDeclaredOnThisType
468 160
            );
469
        }
470
471 160
        return $this->_allProperties;
472
    }
473
474
    /**
475
     * Get collection key properties belongs to this resource type. This
476
     * function returns non-empty array only for resource type representing
477
     * an entity type.
478
     *
479
     * @return ResourceProperty[]
480
     */
481 86
    public function getKeyProperties()
482
    {
483 86
        if (empty($this->_keyProperties)) {
484 86
            $baseType = $this;
485 86
            while ($baseType->_baseType != null) {
486
                $baseType = $baseType->_baseType;
487
            }
488
489 86
            foreach ($baseType->_propertiesDeclaredOnThisType
490 86
                as $propertyName => $resourceProperty
491
            ) {
492 86
                if ($resourceProperty->isKindOf(ResourcePropertyKind::KEY) || $resourceProperty->isKindOf(ResourcePropertyKind::KEY_RESOURCE_REFERENCE)) {
493 86
                    $this->_keyProperties[$propertyName] = $resourceProperty;
494
                }
495
            }
496
        }
497
498 86
        return $this->_keyProperties;
499
    }
500
501
    /**
502
     * Get collection of e-tag properties belongs to this type.
503
     *
504
     * @return ResourceProperty[]
505
     */
506 1
    public function getETagProperties()
507
    {
508 1
        if (empty ($this->_etagProperties)) {
509 1
            foreach ($this->getAllProperties()
510 1
                as $propertyName => $resourceProperty
511
            ) {
512 1
                if ($resourceProperty->isKindOf(ResourcePropertyKind::ETAG)) {
513
                    $this->_etagProperties[$propertyName] = $resourceProperty;
514
                }
515
            }
516
        }
517
518 1
        return $this->_etagProperties;
519
    }
520
521
    /**
522
     * To check this type has any eTag properties
523
     *
524
     * @return boolean
525
     */
526
    public function hasETagProperties()
527
    {
528
        $this->getETagProperties();
529
        return !empty($this->_etagProperties);
0 ignored issues
show
Bug Best Practice introduced by
The expression return ! empty($this->_etagProperties) returns the type boolean which is incompatible with the documented return type POData\Providers\Metadata\Type\Boolean.
Loading history...
530
    }
531
532
    /**
533
     * Try to get ResourceProperty for a property defined for this resource type
534
     * excluding base class properties
535
     *
536
     * @param string $propertyName The name of the property to resolve.
537
     *
538
     * @return ResourceProperty|null
539
     */
540 46
    public function resolvePropertyDeclaredOnThisType($propertyName)
541
    {
542 46
        if (array_key_exists($propertyName, $this->_propertiesDeclaredOnThisType)) {
543 46
            return $this->_propertiesDeclaredOnThisType[$propertyName];
544
        }
545
546
        return null;
547
    }
548
549
    /**
550
     * Try to get ResourceProperty for a property defined for this resource type
551
     * including base class properties
552
     *
553
     * @param string $propertyName The name of the property to resolve.
554
     *
555
     * @return ResourceProperty|null
556
     */
557 160
    public function resolveProperty($propertyName)
558
    {
559 160
        if (array_key_exists($propertyName, $this->getAllProperties())) {
560 160
            return $this->_allProperties[$propertyName];
561
        }
562
563 17
        return null;
564
    }
565
566
    /**
567
     * Add a named stream belongs to this resource type instance
568
     *
569
     * @param ResourceStreamInfo $namedStream ResourceStreamInfo instance describing the named stream to add.
570
     *
571
     * @return void
572
     *
573
     * @throws InvalidOperationException
574
     */
575 119
    public function addNamedStream(ResourceStreamInfo $namedStream)
576
    {
577 119
        if ($this->_resourceTypeKind != ResourceTypeKind::ENTITY) {
578
            throw new InvalidOperationException(
579
                Messages::resourceTypeNamedStreamsOnlyApplyToEntityType()
580
            );
581
        }
582
583 119
        $name = $namedStream->getName();
584 119
        foreach (array_keys($this->_namedStreamsDeclaredOnThisType)
585 119
            as $namedStreamName
586
        ) {
587 1
            if (strcasecmp($namedStreamName, $name) == 0) {
588 1
                throw new InvalidOperationException(
589 1
                    Messages::resourceTypeNamedStreamWithSameNameAlreadyExists(
590 1
                        $name, $this->_name
591 1
                    )
592 1
                );
593
            }
594
        }
595
596 119
        $this->_namedStreamsDeclaredOnThisType[$name] = $namedStream;
597
        // Set $this->_allNamedStreams to null, the first call to getAllNamedStreams
598
        // will initialize $this->_allNamedStreams, further call to
599
        // getAllNamedStreams will not reinitialize _allNamedStreams
600
        // so if addNamedStream is called after calling getAllNamedStreams then the
601
        // property just added will not be reflected in $this->_allNamedStreams
602 119
        unset($this->_allNamedStreams);
603 119
        $this->_allNamedStreams = array();
604
    }
605
606
    /**
607
     * Get collection of ResourceStreamInfo describing the named streams belongs
608
     * to this resource type (excluding base class properties)
609
     *
610
     * @return ResourceStreamInfo[]
611
     */
612 1
    public function getNamedStreamsDeclaredOnThisType()
613
    {
614 1
        return $this->_namedStreamsDeclaredOnThisType;
615
    }
616
617
    /**
618
     * Get collection of ResourceStreamInfo describing the named streams belongs
619
     * to this resource type including base class named streams.
620
     *
621
     * @return ResourceStreamInfo[]
622
     */
623 60
    public function getAllNamedStreams()
624
    {
625 60
        if (empty($this->_allNamedStreams)) {
626 60
            if ($this->_baseType != null) {
627
                $this->_allNamedStreams = $this->_baseType->getAllNamedStreams();
628
            }
629
630 60
            $this->_allNamedStreams
631 60
                = array_merge(
632 60
                    $this->_allNamedStreams,
633 60
                    $this->_namedStreamsDeclaredOnThisType
634 60
                );
635
        }
636
637 60
        return $this->_allNamedStreams;
638
    }
639
640
    /**
641
     * Try to get ResourceStreamInfo for a named stream defined for this
642
     * resource type excluding base class named streams
643
     *
644
     * @param string $namedStreamName Name of the named stream to resolve.
645
     *
646
     * @return ResourceStreamInfo|null
647
     */
648
    public function tryResolveNamedStreamDeclaredOnThisTypeByName($namedStreamName)
649
    {
650
        if (array_key_exists($namedStreamName, $this->_namedStreamsDeclaredOnThisType)) {
651
            return $this->_namedStreamsDeclaredOnThisType[$namedStreamName];
652
        }
653
654
        return null;
655
    }
656
657
    /**
658
     * Try to get ResourceStreamInfo for a named stream defined for this resource
659
     * type including base class named streams
660
     *
661
     * @param string $namedStreamName Name of the named stream to resolve.
662
     *
663
     * @return ResourceStreamInfo|NULL
664
     */
665 3
    public function tryResolveNamedStreamByName($namedStreamName)
666
    {
667 3
        if (array_key_exists($namedStreamName, $this->getAllNamedStreams())) {
668 2
            return $this->_allNamedStreams[$namedStreamName];
669
        }
670
671 1
        return null;
672
    }
673
674
    /**
675
     * Check this resource type instance has named stream associated with it
676
     * Note: This is an internal method used by library. Devs don't use this.
677
     *
678
     * @return boolean true if resource type instance has named stream else false
679
     */
680 58
    public function hasNamedStream()
681
    {
682
        // Note: Calling this method will initialize _allNamedStreams
683
        // and _hasNamedStreams flag to a boolean value
684
        // from null depending on the current state of _allNamedStreams
685
        // array, so method should be called only after adding all
686
        // named streams
687 58
        if (is_null($this->_hasNamedStreams)) {
688 58
            $this->getAllNamedStreams();
689 58
            $this->_hasNamedStreams = !empty($this->_allNamedStreams);
0 ignored issues
show
Documentation Bug introduced by
It seems like ! empty($this->_allNamedStreams) of type boolean is incompatible with the declared type POData\Providers\Metadata\Type\Boolean of property $_hasNamedStreams.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
690
        }
691
692 58
        return $this->_hasNamedStreams;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_hasNamedStreams also could return the type boolean which is incompatible with the documented return type POData\Providers\Metadata\Type\Boolean.
Loading history...
693
    }
694
695
    /**
696
     * Check this resource type instance has bag property associated with it
697
     * Note: This is an internal method used by library. Devs don't use this.
698
     *
699
     * @param array &$arrayToDetectLoopInComplexType array for detecting loop.
700
     *
701
     * @return boolean|null true if resource type instance has bag property else false
702
     */
703 58
    public function hasBagProperty(&$arrayToDetectLoopInComplexType)
704
    {
705
        // Note: Calling this method will initialize _bagProperties
706
        // and _hasBagProperty flag to a boolean value
707
        // from null depending on the current state of
708
        // _propertiesDeclaredOnThisType array, so method
709
        // should be called only after adding all properties
710 58
        if (!is_null($this->_hasBagProperty)) {
711
            return $this->_hasBagProperty;
712
        }
713
714 58
        if ($this->_baseType != null && $this->_baseType->hasBagProperty($arrayToDetectLoopInComplexType)) {
715
            $this->_hasBagProperty = true;
0 ignored issues
show
Documentation Bug introduced by
It seems like true of type true is incompatible with the declared type POData\Providers\Metadata\Type\Boolean of property $_hasBagProperty.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
716
        } else {
717 58
            foreach ($this->_propertiesDeclaredOnThisType as $resourceProperty) {
718 58
                $hasBagInComplex = false;
719 58
                if ($resourceProperty->isKindOf(ResourcePropertyKind::COMPLEX_TYPE)) {
720
                    //We can say current ResouceType ("this")
721
                    //is contains a bag property if:
722
                    //1. It contain a property of kind bag.
723
                    //2. It contains a normal complex property
724
                    //with a sub property of kind bag.
725
                    //The second case can be further expanded, i.e.
726
                    //if the normal complex property
727
                    //has a normal complex sub property with a
728
                    //sub property of kind bag.
729
                    //So for complex type we recursively call this
730
                    //function to check for bag.
731
                    //Shown below how looping can happen in complex type:
732
                    //Customer ResourceType (id1)
733
                    //{
734
                    //  ....
735
                    //  Address: Address ResourceType (id2)
736
                    //  {
737
                    //    .....
738
                    //    AltAddress: Address ResourceType (id2)
739
                    //    {
740
                    //      ...
741
                    //    }
742
                    //  }
743
                    //}
744
                    //
745
                    //Here the resource type of Customer::Address and
746
                    //Customer::Address::AltAddress
747
                    //are same, this is a loop, we need to detect
748
                    //this and avoid infinite recursive loop.
749
                    //
750 21
                    $count = count($arrayToDetectLoopInComplexType);
751 21
                    $foundLoop = false;
752 21
                    for ($i = 0; $i < $count; $i++) {
753 21
                        if ($arrayToDetectLoopInComplexType[$i] === $resourceProperty->getResourceType()) {
754 14
                            $foundLoop = true;
755 14
                            break;
756
                        }
757
                    }
758
759 21
                    if (!$foundLoop) {
760 21
                        $arrayToDetectLoopInComplexType[$count] = $resourceProperty->getResourceType();
761 21
                        $hasBagInComplex = $resourceProperty->getResourceType()->hasBagProperty($arrayToDetectLoopInComplexType);
762 21
                        unset($arrayToDetectLoopInComplexType[$count]);
763
                    }
764
                }
765
766 58
                if ($resourceProperty->isKindOf(ResourcePropertyKind::BAG) || $hasBagInComplex) {
767 16
                    $this->_hasBagProperty = true;
768 16
                    break;
769
                }
770
            }
771
        }
772
773
774 58
        return $this->_hasBagProperty;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_hasBagProperty also could return the type true which is incompatible with the documented return type POData\Providers\Metadata\Type\Boolean|null.
Loading history...
775
    }
776
777
    /**
778
     * Validate the type
779
     *
780
     * @return void
781
     *
782
     * @throws InvalidOperationException
783
     */
784 1
    public function validateType()
785
    {
786 1
        $keyProperties = $this->getKeyProperties();
787 1
        if (($this->_resourceTypeKind == ResourceTypeKind::ENTITY) && empty($keyProperties)) {
788 1
            throw new InvalidOperationException(
789 1
                Messages::resourceTypeMissingKeyPropertiesForEntity(
790 1
                    $this->getFullName()
791 1
                )
792 1
            );
793
        }
794
    }
795
796
    /**
797
     * To check the type described by this resource type is assignable from
798
     * a type described by another resource type. Or this type is a sub-type
799
     * of (derived from the) given resource type
800
     *
801
     * @param ResourceType $resourceType Another resource type.
802
     *
803
     * @return boolean
804
     */
805 160
    public function isAssignableFrom(ResourceType $resourceType)
806
    {
807 160
        $base = $resourceType;
808 160
        while ($base != null) {
809 160
            if ($this == $base) {
810 160
                return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the documented return type POData\Providers\Metadata\Type\Boolean.
Loading history...
811
            }
812
813
            $base = $base->_baseType;
814
        }
815
816
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type POData\Providers\Metadata\Type\Boolean.
Loading history...
817
    }
818
819
    /**
820
     * Get predefined ResourceType for a primitive type
821
     *
822
     * @param EdmPrimitiveType|int $typeCode Typecode of primitive type
823
     *
824
     * @return ResourceType
825
     *
826
     * @throws InvalidArgumentException
827
     */
828 160
    public static function getPrimitiveResourceType($typeCode)
829
    {
830
        switch ($typeCode) {
831 160
            case EdmPrimitiveType::BINARY:
832 119
                return new ResourceType(
833 119
                    new Binary(), ResourceTypeKind::PRIMITIVE,
834 119
                    'Binary', 'Edm'
835 119
                );
836
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
837 160
            case EdmPrimitiveType::BOOLEAN:
838 117
                return new ResourceType(
839 117
                    new Boolean(),
840 117
                    ResourceTypeKind::PRIMITIVE,
841 117
                    'Boolean', 'Edm'
842 117
                );
843
                break;
844 160
            case EdmPrimitiveType::BYTE:
845
                return new ResourceType(
846
                    new Byte(),
847
                    ResourceTypeKind::PRIMITIVE,
848
                    'Byte', 'Edm'
849
                );
850
                break;
851 160
            case EdmPrimitiveType::DATE:
852
                return new ResourceType(
853
                    new DateTime(),
854
                    ResourceTypeKind::PRIMITIVE,
855
                    'Date', 'Edm'
856
                );
857
                break;
858 160
            case EdmPrimitiveType::DATETIME:
859 160
                return new ResourceType(
860 160
                    new DateTime(),
861 160
                    ResourceTypeKind::PRIMITIVE,
862 160
                    'DateTime', 'Edm'
863 160
                );
864
                break;
865 160
            case EdmPrimitiveType::DATETIMETZ:
866
                return new ResourceType(
867
                    new DateTimeTz(),
868
                    ResourceTypeKind::PRIMITIVE,
869
                    'DateTime', 'Edm'
870
                );
871
                break;
872 160
            case EdmPrimitiveType::DECIMAL:
873 140
                return new ResourceType(
874 140
                    new Decimal(),
875 140
                    ResourceTypeKind::PRIMITIVE,
876 140
                    'Decimal', 'Edm'
877 140
                );
878
                break;
879 160
            case EdmPrimitiveType::DOUBLE:
880 117
                return new ResourceType(
881 117
                    new Double(),
882 117
                    ResourceTypeKind::PRIMITIVE,
883 117
                    'Double', 'Edm'
884 117
                );
885
                break;
886 160
            case EdmPrimitiveType::GUID:
887 117
                return new ResourceType(
888 117
                    new Guid(),
889 117
                    ResourceTypeKind::PRIMITIVE,
890 117
                    'Guid', 'Edm'
891 117
                );
892
                break;
893 160
            case EdmPrimitiveType::INT16:
894 160
                return new ResourceType(
895 160
                    new Int16(),
896 160
                    ResourceTypeKind::PRIMITIVE,
897 160
                    'Int16', 'Edm'
898 160
                );
899
                break;
900 160
            case EdmPrimitiveType::INT32:
901 160
                return new ResourceType(
902 160
                    new Int32(),
903 160
                    ResourceTypeKind::PRIMITIVE,
904 160
                    'Int32', 'Edm'
905 160
                );
906
                break;
907 160
            case EdmPrimitiveType::INT64:
908 1
                return new ResourceType(
909 1
                    new Int64(),
910 1
                    ResourceTypeKind::PRIMITIVE,
911 1
                    'Int64', 'Edm'
912 1
                );
913
                break;
914 160
            case EdmPrimitiveType::SBYTE:
915
                return new ResourceType(
916
                    new SByte(),
917
                    ResourceTypeKind::PRIMITIVE,
918
                    'SByte', 'Edm'
919
                );
920
                break;
921 160
            case EdmPrimitiveType::SINGLE:
922 140
                return new ResourceType(
923 140
                    new Single(),
924 140
                    ResourceTypeKind::PRIMITIVE,
925 140
                    'Single', 'Edm'
926 140
                );
927
                break;
928 160
            case EdmPrimitiveType::STRING:
929 160
                return new ResourceType(
930 160
                    new StringType(),
931 160
                    ResourceTypeKind::PRIMITIVE,
932 160
                    'String', 'Edm'
933 160
                );
934
                break;
935 1
            case EdmPrimitiveType::TIMESPAN:
936
                return new ResourceType(
937
                    new StringType(),
938
                    ResourceTypeKind::PRIMITIVE,
939
                    'TimeSpan', 'Edm'
940
                );
941
                break;
942
            default:
943 1
                throw new InvalidArgumentException(
944 1
                    Messages::commonNotValidPrimitiveEDMType(
945 1
                        '$typeCode', 'getPrimitiveResourceType'
946 1
                    )
947 1
                );
948
        }
949
    }
950
951
952
    function __wakeup() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
953
        if ($this->_type instanceof ReflectionClass) {
954
            $this->_type = new ReflectionClass($this->_type->name);
955
        }
956
    }
957
958
    public function __serialize() : array
959
    {
960
        return [
961
            '_name' => $this->_name,
962
            '_namespaceName' => $this->_namespaceName,
963
            '_fullName' => $this->_fullName,
964
            '_resourceTypeKind' => $this->_resourceTypeKind,
965
            '_abstractType' => $this->_abstractType,
966
            '_baseType' => $this->_baseType,
967
            '_propertiesDeclaredOnThisType' => $this->_propertiesDeclaredOnThisType,
968
            '_namedStreamsDeclaredOnThisType' => $this->_namedStreamsDeclaredOnThisType,
969
            '_propertyInfosDeclaredOnThisType' => $this->_propertyInfosDeclaredOnThisType,
970
            '_allProperties' => $this->_allProperties,
971
            '_allNamedStreams' => $this->_allNamedStreams,
972
            '_etagProperties' => $this->_etagProperties,
973
            '_keyProperties' => $this->_keyProperties,
974
            '_isMediaLinkEntry' => $this->_isMediaLinkEntry,
975
            '_hasBagProperty' => $this->_hasBagProperty,
976
            '_hasNamedStreams' => $this->_hasNamedStreams,
977
            '_type' => $this->_type instanceof ReflectionClass ? $this->_type->getName() : $this->_type,
978
            '_customState' => $this->_customState,
979
            '_arrayToDetectLoopInComplexBag' => $this->_arrayToDetectLoopInComplexBag
980
        ];
981
    }
982
983
    public function __unserialize(array $data) {
984
        $this->_name = $data["_name"];
985
        $this->_namespaceName = $data["_namespaceName"];
986
        $this->_fullName = $data["_fullName"];
987
        $this->_resourceTypeKind = $data["_resourceTypeKind"];
988
        $this->_abstractType = $data["_abstractType"];
989
        $this->_baseType = $data["_baseType"];
990
        $this->_propertiesDeclaredOnThisType = $data["_propertiesDeclaredOnThisType"];
991
        $this->_namedStreamsDeclaredOnThisType = $data["_namedStreamsDeclaredOnThisType"];
992
        $this->_propertyInfosDeclaredOnThisType = $data["_propertyInfosDeclaredOnThisType"];
993
        $this->_allProperties = $data["_allProperties"];
994
        $this->_allNamedStreams = $data["_allNamedStreams"];
995
        $this->_etagProperties = $data["_etagProperties"];
996
        $this->_keyProperties = $data["_keyProperties"];
997
        $this->_isMediaLinkEntry = $data["_isMediaLinkEntry"];
998
        $this->_hasBagProperty = $data["_hasBagProperty"];
999
        $this->_hasNamedStreams = $data["_hasNamedStreams"];
1000
        $this->_type = is_string($data["_type"]) ? new ReflectionClass($data["_type"]) : $data["_type"];
1001
        $this->_customState = $data["_customState"];
1002
        $this->_arrayToDetectLoopInComplexBag = $data["_arrayToDetectLoopInComplexBag"];
1003
    }
1004
}
1005