SerialiserLowLevelWriters   A
last analyzed

Complexity

Total Complexity 28

Size/Duplication

Total Lines 174
Duplicated Lines 0 %

Importance

Changes 6
Bugs 2 Features 0
Metric Value
wmc 28
eloc 86
c 6
b 2
f 0
dl 0
loc 174
rs 10

4 Methods

Rating   Name   Duplication   Size   Complexity  
B writeBagValue() 0 26 8
B writeComplexValue() 0 62 9
A writePrimitiveProperties() 0 20 3
B primitiveToString() 0 24 8
1
<?php
2
3
declare(strict_types=1);
4
/**
5
 * Created by PhpStorm.
6
 * User: alex
7
 * Date: 16/02/20
8
 * Time: 12:12 AM.
9
 */
10
namespace AlgoWeb\PODataLaravel\Serialisers;
11
12
use Illuminate\Database\Eloquent\Model;
13
use POData\Common\InvalidOperationException;
14
use POData\Common\Messages;
15
use POData\ObjectModel\ODataBagContent;
16
use POData\ObjectModel\ODataProperty;
17
use POData\ObjectModel\ODataPropertyContent;
18
use POData\Providers\Metadata\ResourceProperty;
19
use POData\Providers\Metadata\ResourcePropertyKind;
20
use POData\Providers\Metadata\ResourceType;
21
use POData\Providers\Metadata\ResourceTypeKind;
22
use POData\Providers\Metadata\Type\Binary;
23
use POData\Providers\Metadata\Type\Boolean;
24
use POData\Providers\Metadata\Type\DateTime;
25
use POData\Providers\Metadata\Type\IType;
26
use POData\Providers\Metadata\Type\StringType;
27
28
abstract class SerialiserLowLevelWriters
29
{
30
    /**
31
     * @param Model           $entryObject
32
     * @param ModelSerialiser $modelSerialiser
33
     * @param array[]         $nonRelProp
34
     * @throws InvalidOperationException
35
     * @return ODataPropertyContent
36
     */
37
    public static function writePrimitiveProperties(
38
        Model $entryObject,
39
        ModelSerialiser $modelSerialiser,
40
        array $nonRelProp
41
    ): ODataPropertyContent {
42
        $propertyContent = new ODataPropertyContent([]);
43
        $cereal          = $modelSerialiser->bulkSerialise($entryObject);
44
        $cereal          = array_intersect_key($cereal, $nonRelProp);
45
46
        foreach ($cereal as $corn => $flake) {
47
            $corn  = strval($corn);
48
            $rType = $nonRelProp[$corn]['type'];
49
            $value = isset($flake) ? SerialiserLowLevelWriters::primitiveToString($rType, $flake) : null;
50
51
            /** @var ResourceProperty $nrp */
52
            $nrp                                = $nonRelProp[$corn]['prop'];
53
            $subProp                            = new ODataProperty($corn, $nrp->getResourceType()->getFullName(), $value);
54
            $propertyContent[$corn]             = $subProp;
55
        }
56
        return $propertyContent;
57
    }
58
59
    /**
60
     * @param ResourceType $resourceType
61
     * @param mixed[]|null $result
62
     * @return ODataBagContent|null
63
     * @throws InvalidOperationException
64
     * @throws \ReflectionException
65
     * @return ODataBagContent|null
66
     */
67
    public static function writeBagValue(ResourceType &$resourceType, ?array $result): ?ODataBagContent
68
    {
69
        $typeKind = $resourceType->getResourceTypeKind();
70
        $kVal     = $typeKind;
71
        if (!(ResourceTypeKind::PRIMITIVE() == $kVal || ResourceTypeKind::COMPLEX() == $kVal)) {
72
            $msg = '$bagItemResourceTypeKind != ResourceTypeKind::PRIMITIVE'
73
                   . ' && $bagItemResourceTypeKind != ResourceTypeKind::COMPLEX';
74
            throw new InvalidOperationException($msg);
75
        }
76
        if (null == $result) {
77
            return null;
78
        }
79
        $bag    = new ODataBagContent();
80
        $result = array_filter($result);
81
        foreach ($result as $value) {
82
            if (ResourceTypeKind::PRIMITIVE() == $kVal) {
83
                $instance = $resourceType->getInstanceType();
84
                if (!$instance instanceof IType) {
85
                    throw new InvalidOperationException(get_class($instance));
86
                }
87
                $bag->addPropertyContent(SerialiserLowLevelWriters::primitiveToString($instance, $value));
88
            } elseif (ResourceTypeKind::COMPLEX() == $kVal) {
89
                $bag->addPropertyContent(SerialiserLowLevelWriters::writeComplexValue($resourceType, $value));
90
            }
91
        }
92
        return $bag;
93
    }
94
95
    /**
96
     * @param  ResourceType              $resourceType
97
     * @param  object                    $result
98
     * @param  string|null               $propertyName
99
     * @param  object[]                  $instanceCollection
100
     * @throws InvalidOperationException
101
     * @throws \ReflectionException
102
     * @return ODataPropertyContent
103
     */
104
    public static function writeComplexValue(
105
        ResourceType &$resourceType,
106
        &$result,
107
        $propertyName = null,
108
        array &$instanceCollection = []
109
    ): ODataPropertyContent {
110
        if (!is_object($result)) {
111
            throw new InvalidOperationException('Supplied $customObject must be an object');
112
        }
113
114
        $count = count($instanceCollection);
115
        for ($i = 0; $i < $count; ++$i) {
116
            if ($instanceCollection[$i] === $result) {
117
                throw new InvalidOperationException(
118
                    Messages::objectModelSerializerLoopsNotAllowedInComplexTypes($propertyName)
119
                );
120
            }
121
        }
122
123
        $instanceCollection[$count] = &$result;
124
125
        $internalContent    = new ODataPropertyContent([]);
126
        $resourceProperties = $resourceType->getAllProperties();
127
        // first up, handle primitive properties
128
        foreach ($resourceProperties as $prop) {
129
            $resourceKind           = $prop->getKind();
130
            $propName               = $prop->getName();
131
            $internalProperty       = new ODataProperty($propName, null, null);
132
            if (SerialiserUtilities::isMatchPrimitive($resourceKind->getValue())) {
133
                $iType = $prop->getInstanceType();
134
                if (!$iType instanceof IType) {
135
                    throw new InvalidOperationException(get_class($iType));
136
                }
137
                $internalProperty->setTypeName($iType->getFullTypeName());
138
139
                $rType = $prop->getResourceType()->getInstanceType();
140
                if (!$rType instanceof IType) {
141
                    throw new InvalidOperationException(get_class($rType));
142
                }
143
144
145
                $value = SerialiserLowLevelWriters::primitiveToString($rType, $result->{$propName});
146
                $internalProperty->setValue($value);
147
148
                $internalContent[$propName] = $internalProperty;
149
            } elseif (ResourcePropertyKind::COMPLEX_TYPE() == $resourceKind) {
150
                $rType                      = $prop->getResourceType();
151
                $internalProperty->setTypeName($rType->getFullName());
152
                $value                      = SerialiserLowLevelWriters::writeComplexValue(
153
                    $rType,
154
                    $result->{$propName},
155
                    $propName,
156
                    $instanceCollection
157
                );
158
                $internalProperty->setValue($value);
159
160
                $internalContent[$propName] = $internalProperty;
161
            }
162
        }
163
164
        unset($instanceCollection[$count]);
165
        return $internalContent;
166
    }
167
168
169
    /**
170
     * Convert the given primitive value to string.
171
     * Note: This method will not handle null primitive value.
172
     *
173
     * @param IType $type                   Type of the primitive property needing conversion
174
     * @param mixed $primitiveValue         Primitive value to convert
175
     *
176
     * @return string
177
     */
178
    public static function primitiveToString(IType &$type, $primitiveValue): string
179
    {
180
        // kludge to enable switching on type of $type without getting tripped up by mocks as we would with get_class
181
        // switch (true) means we unconditionally enter, and then lean on case statements to match given block
182
        switch (true) {
183
            case $type instanceof StringType:
184
                $primitiveValue = strval($primitiveValue);
185
                $isUTF8         = 'UTF-8' === mb_detect_encoding($primitiveValue);
186
                $stringValue    = $isUTF8 ? $primitiveValue : utf8_encode($primitiveValue);
187
                break;
188
            case $type instanceof Boolean:
189
                $stringValue = (true === $primitiveValue) ? 'true' : 'false';
190
                break;
191
            case $type instanceof Binary:
192
                $stringValue = base64_encode($primitiveValue);
193
                break;
194
            case $type instanceof DateTime && $primitiveValue instanceof \DateTime:
195
                $stringValue = $primitiveValue->format(\DateTime::ATOM);
196
                break;
197
            default:
198
                $stringValue = strval($primitiveValue);
199
        }
200
201
        return $stringValue;
202
    }
203
}
204