Passed
Pull Request — develop (#61)
by
unknown
02:05
created

Schema   B

Complexity

Total Complexity 53

Size/Duplication

Total Lines 241
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 2

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 53
lcom 3
cbo 2
dl 0
loc 241
ccs 143
cts 143
cp 1
rs 7.4757
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A cast() 0 4 1
A castWithoutExtraneous() 0 9 2
A castByMap() 0 14 3
A __construct() 0 12 3
A castProperties() 0 12 3
A castPropertiesWithoutExtraneous() 0 9 3
B castPropertyWithoutExtraneous() 0 13 5
A castPropertiesByMap() 0 15 3
A setPropertyValue() 0 18 3
A getPropertyValue() 0 18 3
A castPropertiesDefault() 0 6 2
B castPropertyDefault() 0 13 5
A __get() 0 10 2
schema() 0 1 ?
A toArray() 0 4 1
A setFieldDefaults() 0 9 1
B setNestedFieldDefaults() 0 13 5
A setRemainingFieldDefaults() 0 10 2
A setFieldDefaultValue() 0 8 3
A validate() 0 18 3

How to fix   Complexity   

Complex Class

Complex classes like Schema 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 Schema, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace Fathomminds\Rest;
3
4
use Fathomminds\Rest\Contracts\ISchema;
5
use Fathomminds\Rest\Schema\SchemaValidator;
6
use Fathomminds\Rest\Schema\TypeValidators\ValidatorFactory;
7
use Fathomminds\Rest\Exceptions\RestException;
8
9
abstract class Schema implements ISchema
10
{
11
    const REPLACE_MODE = 'replace';
12
    const UPDATE_MODE = 'update';
13
14 36
    public static function cast($object)
15
    {
16 36
        return new static($object);
17
    }
18
19 3
    public static function castWithoutExtraneous($object)
20
    {
21 3
        if (gettype($object) !== 'object') {
22 1
            throw new RestException('Schema castWithoutExtraneous method expects object as parameter', [
23 1
                'parameter' => $object,
24
            ]);
25
        }
26 2
        return new static($object, ['withoutExtraneous' => true]);
27
    }
28
29 3
    public static function castByMap($object, $map)
30
    {
31 3
        if (gettype($object) !== 'object') {
32 1
            throw new RestException('Schema castByMap method expects object as first parameter', [
33 1
                'parameter' => $object,
34
            ]);
35
        }
36 2
        if (!is_array($map)) {
37 1
            throw new RestException('Schema castByMap method expects array as second parameter', [
38 1
                'parameter' => $map,
39
            ]);
40
        }
41 1
        return new static($object, ['map' => $map]);
42
    }
43
44 89
    public function __construct($object = null, $params = [])
45
    {
46 89
        if ($object === null) {
47 73
            return;
48
        }
49 52
        if (gettype($object) !== 'object') {
50 1
            throw new RestException('Schema constructor expects object or null as parameter', [
51 1
                'parameter' => $object,
52
            ]);
53
        }
54 51
        $this->castProperties($object, $this->schema(), $params);
55 51
    }
56
57 51
    private function castProperties($object, $schema, $params)
58
    {
59 51
        if (isset($params['withoutExtraneous'])) {
60 2
            $this->castPropertiesWithoutExtraneous($object, $schema);
61 2
            return;
62
        }
63 51
        if (isset($params['map'])) {
64 1
            $this->castPropertiesByMap($schema, $object, $params['map']);
65 1
            return;
66
        }
67 51
        $this->castPropertiesDefault($object, $schema);
68 51
    }
69
70 2
    private function castPropertiesWithoutExtraneous($object, $schema)
71
    {
72 2
        foreach (get_object_vars($object) as $name => $value) {
73 2
            list($propertyExists, $propertyValue) = $this->castPropertyWithoutExtraneous($schema, $name, $value);
74 2
            if ($propertyExists) {
75 2
                $this->{$name} = $propertyValue;
76
            }
77
        }
78 2
    }
79
80 2
    private function castPropertyWithoutExtraneous($schema, $name, $value)
81
    {
82 2
        if (!array_key_exists($name, $schema)) {
83 2
            return [false, null];
84
        }
85 2
        if (isset($schema[$name]['type']) && $schema[$name]['type'] === 'schema') {
86 2
            return [true, $schema[$name]['validator']['class']::castWithoutExtraneous($value)];
87
        }
88 2
        $params = empty($schema[$name]['validator']['params'])
89 2
            ? null
90 2
            : $schema[$name]['validator']['params'];
91 2
        return [true, $schema[$name]['validator']['class']::cast($value, $params)];
92
    }
93
94 1
    private function castPropertiesByMap($schema, $object, $map)
95
    {
96 1
        $mappedObject = new \StdClass();
97 1
        foreach ($map as $targetFieldName => $sourceFieldName) {
98 1
            list($propertyExists, $propertyValue) = $this->getPropertyValue($object, $sourceFieldName);
99 1
            if ($propertyExists) {
100 1
                $mappedObject = $this->setPropertyValue(
101 1
                    $mappedObject,
102 1
                    $targetFieldName,
103 1
                    $propertyValue
104
                );
105
            }
106
        }
107 1
        $this->castPropertiesWithoutExtraneous($mappedObject, $schema);
108 1
    }
109
110 1
    private function setPropertyValue($mappedObject, $targetFieldName, $propertyValue)
111
    {
112 1
        $fieldNameArr = explode('.', $targetFieldName);
113 1
        $fieldName = array_shift($fieldNameArr);
114 1
        if (count($fieldNameArr) !== 0) {
115 1
            if (!property_exists($mappedObject, $fieldName)) {
116 1
                $mappedObject->{$fieldName} = new \StdClass();
117
            }
118 1
            $mappedObject->{$fieldName} = $this->setPropertyValue(
119 1
                $mappedObject->{$fieldName},
120 1
                implode('.', $fieldNameArr),
121 1
                $propertyValue
122
            );
123 1
            return $mappedObject;
124
        }
125 1
        $mappedObject->{$fieldName} = $propertyValue;
126 1
        return $mappedObject;
127
    }
128
129 1
    private function getPropertyValue($object, $sourceFieldName)
130
    {
131 1
        $fieldNameArr = explode('.', $sourceFieldName);
132 1
        $fieldName = array_shift($fieldNameArr);
133 1
        if (!property_exists($object, $fieldName)) {
134
            return [
135 1
                false,
136
                null
137
            ];
138
        }
139 1
        if (count($fieldNameArr) === 0) {
140
            return [
141 1
                true,
142 1
                json_decode(json_encode($object->{$fieldName}))
143
            ];
144
        }
145 1
        return $this->getPropertyValue($object->{$fieldName}, implode('.', $fieldNameArr));
146
    }
147
148 51
    private function castPropertiesDefault($object, $schema)
149
    {
150 51
        foreach (get_object_vars($object) as $name => $value) {
151 31
            $this->{$name} = $this->castPropertyDefault($schema, $name, $value);
152
        }
153 51
    }
154
155 31
    private function castPropertyDefault($schema, $name, $value)
156
    {
157 31
        if (!array_key_exists($name, $schema)) {
158 1
            return $value;
159
        }
160 31
        if (isset($schema[$name]['type']) && $schema[$name]['type'] === 'schema') {
161 12
            return $schema[$name]['validator']['class']::cast($value);
162
        }
163 31
        $params = empty($schema[$name]['validator']['params'])
164 31
            ? null
165 31
            : $schema[$name]['validator']['params'];
166 31
        return $schema[$name]['validator']['class']::cast($value, $params);
167
    }
168
169 2
    public function __get($name)
170
    {
171 2
        if (!isset($this->{$name})) {
172 1
            throw new RestException(
173 1
                'Trying to access undefined property ' . $name,
174 1
                []
175
            );
176
        }
177 1
        return $this->{$name};
178
    }
179
180
    abstract public function schema();
181
182 4
    public function toArray()
183
    {
184 4
        return json_decode(json_encode($this), true);
185
    }
186
187 5
    public function setFieldDefaults()
188
    {
189 5
        $schemaValidator = new SchemaValidator(static::class);
190 5
        $schemaFields = $schemaValidator->getSchemaFieldsWithDetails($this);
191 5
        $defaultFields = $schemaValidator->getFieldsWithDefaults($this);
192 5
        $this->setNestedFieldDefaults($schemaFields);
193 5
        $this->setRemainingFieldDefaults($defaultFields);
194 5
        return $this;
195
    }
196
197 5
    protected function setNestedFieldDefaults($schemaFields)
198
    {
199 5
        foreach ($schemaFields as $schemaFieldName => $schemaFieldDetails) {
200 5
            $propertyExists = property_exists($this, $schemaFieldName);
201 5
            if (isset($schemaFieldDetails['default']) && !$propertyExists) {
202 5
                $this->setFieldDefaultValue($schemaFieldName, $schemaFieldDetails['default']);
203 5
                $propertyExists = true;
204
            }
205 5
            if ($propertyExists) {
206 5
                $this->{$schemaFieldName}->setFieldDefaults();
207
            }
208
        }
209 5
    }
210
211 5
    protected function setRemainingFieldDefaults($defaultFields)
212
    {
213 5
        $properties = array_diff_key(
214 5
            $defaultFields,
215 5
            get_object_vars($this)
216
        );
217 5
        foreach ($properties as $fieldName => $field) {
218 5
            $this->setFieldDefaultValue($fieldName, $field['default']);
219
        }
220 5
    }
221
222 5
    protected function setFieldDefaultValue($fieldName, $value)
223
    {
224 5
        if (gettype($value) === 'object' && is_callable($value)) {
225 5
            $this->{$fieldName} = $value();
226 5
            return;
227
        }
228 5
        $this->{$fieldName} = $value;
229 5
    }
230
231 6
    public function validate($mode = null)
232
    {
233 6
        $schemaValidator = new SchemaValidator(static::class);
234
        switch ($mode) {
235 6
            case self::REPLACE_MODE:
236 2
                $schemaValidator->replaceMode(true);
237 2
                $schemaValidator->updateMode(false);
238 2
                break;
239 4
            case self::UPDATE_MODE:
240 1
                $schemaValidator->replaceMode(false);
241 1
                $schemaValidator->updateMode(true);
242 1
                break;
243
            default:
244 3
                break;
245
        }
246 6
        $schemaValidator->validate($this);
247 3
        return $this;
248
    }
249
}
250