Completed
Push — master ( ea9cfc...8dc8d9 )
by Raffael
12:43 queued 03:57
created

AttributeMap::rewrite()   B

Complexity

Conditions 11
Paths 8

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 11

Importance

Changes 0
Metric Value
dl 0
loc 27
ccs 15
cts 15
cp 1
rs 7.3166
c 0
b 0
f 0
cc 11
nc 8
nop 2
crap 11

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
declare(strict_types=1);
4
5
/**
6
 * tubee.io
7
 *
8
 * @copyright   Copryright (c) 2017-2018 gyselroth GmbH (https://gyselroth.com)
9
 * @license     GPL-3.0 https://opensource.org/licenses/GPL-3.0
10
 */
11
12
namespace Tubee;
13
14
use InvalidArgumentException;
15
use Psr\Log\LoggerInterface;
16
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
17
use Tubee\AttributeMap\AttributeMapInterface;
18
use Tubee\AttributeMap\Exception;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Tubee\Exception.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
19
use Tubee\AttributeMap\Transform;
20
21
class AttributeMap implements AttributeMapInterface
22
{
23
    /**
24
     * Attribute map.
25
     *
26
     * @var iterable
27
     */
28
    protected $map = [];
29
30
    /**
31
     * Logger.
32
     *
33
     * @var LoggerInterface
34
     */
35
    protected $logger;
36
37
    /**
38
     * Expression language.
39
     *
40
     * @var ExpressionLanguage
41
     */
42
    protected $expression;
43
44
    /**
45
     * Init attribute map.
46
     */
47 29
    public function __construct(array $map = [], ExpressionLanguage $expression, LoggerInterface $logger)
48
    {
49 29
        $this->map = $map;
0 ignored issues
show
Documentation Bug introduced by
It seems like $map of type array is incompatible with the declared type object<Tubee\iterable> of property $map.

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...
50 29
        $this->logger = $logger;
51 29
        $this->expression = $expression;
52 29
    }
53
54
    /**
55
     * {@inheritdoc}
56
     */
57
    public function getMap(): Iterable
58
    {
59
        return $this->map;
60
    }
61
62
    /**
63
     * {@inheritdoc}
64
     */
65
    public function getAttributes(): array
66
    {
67
        return array_keys($this->map);
68
    }
69
70
    /**
71
     * {@inheritdoc}
72
     */
73 27
    public function map(array $data): array
74
    {
75 27
        $result = [];
76 27
        foreach ($this->map as $attr => $value) {
77 27
            if (isset($attrv)) {
78
                unset($attrv);
79
            }
80
81 27
            if (isset($value['ensure'])) {
82 4
                if ($value['ensure'] === AttributeMapInterface::ENSURE_MERGE && isset($value['type']) && $value['type'] !== AttributeMapInterface::TYPE_ARRAY) {
83 1
                    throw new InvalidArgumentException('attribute '.$attr.' ensure is set to merge but type is not an array');
84
                }
85
86 3
                if ($value['ensure'] === AttributeMapInterface::ENSURE_ABSENT) {
87 1
                    continue;
88
                }
89
            }
90
91 25
            $mapped = $this->mapField($attr, $value, $data);
92
93 21
            if ($mapped !== null) {
94 17
                $result[$attr] = $mapped;
95
96 17
                $this->logger->debug('mapped attribute ['.$attr.'] to [<'.gettype($result[$attr]).'> {value}]', [
97 17
                    'category' => get_class($this),
98 21
                    'value' => $result[$attr],
99
                ]);
100
            }
101
        }
102
103 22
        return $result;
104
    }
105
106
    /**
107
     * {@inheritdoc}
108
     */
109 2
    public function getDiff(array $object, array $endpoint_object): array
110
    {
111 2
        $diff = [];
112 2
        foreach ($this->map as $attr => $value) {
113 2
            if (isset($value['ensure'])) {
114 1
                $exists = isset($endpoint_object[$attr]);
115
116 1
                if ($value['ensure'] === AttributeMapInterface::ENSURE_EXISTS && ($exists === true || !isset($object[$attr]))) {
117
                    continue;
118
                }
119 1
                if (($value['ensure'] === AttributeMapInterface::ENSURE_LAST || $value['ensure'] === AttributeMapInterface::ENSURE_EXISTS) && isset($object[$attr])) {
120 1
                    if ($exists && is_array($object[$attr]) && is_array($endpoint_object[$attr]) && Helper::arrayEqual($endpoint_object[$attr], $object[$attr])) {
121
                        continue;
122
                    }
123 1
                    if ($exists && $object[$attr] === $endpoint_object[$attr]) {
124
                        continue;
125
                    }
126
127 1
                    $diff[$attr] = [
128 1
                        'action' => AttributeMapInterface::ACTION_REPLACE,
129 1
                        'value' => $object[$attr],
130
                    ];
131
                } elseif ($value['ensure'] === AttributeMapInterface::ENSURE_ABSENT && isset($endpoint_object[$attr]) || isset($endpoint_object[$attr]) && !isset($object[$attr]) && $value['ensure'] !== AttributeMapInterface::ENSURE_MERGE) {
132
                    $diff[$attr] = [
133
                        'action' => AttributeMapInterface::ACTION_REMOVE,
134
                    ];
135
                } elseif ($value['ensure'] === AttributeMapInterface::ENSURE_MERGE && isset($object[$attr])) {
136
                    $new_values = [];
137
138
                    foreach ($object[$attr] as $val) {
139
                        if (!$exists) {
140
                            $new_values[] = $val;
141
                        } elseif (is_array($endpoint_object[$attr]) && in_array($val, $endpoint_object[$attr]) || $val === $endpoint_object[$attr]) {
142
                            continue;
143
                        } else {
144
                            $new_values[] = $val;
145
                        }
146
                    }
147
148
                    if (!empty($new_values)) {
149
                        $diff[$attr] = [
150
                            'action' => AttributeMapInterface::ACTION_ADD,
151 2
                            'value' => $new_values,
152
                        ];
153
                    }
154
                }
155
            }
156
        }
157
158 2
        return $diff;
159
    }
160
161
    /**
162
     * Map field.
163
     */
164 25
    protected function mapField($attr, $value, $data)
165
    {
166 25
        $attrv = $this->resolveValue($attr, $value, $data);
167 25
        $attrv = $this->transformAttribute($attr, $value, $attrv);
168
169 22
        if ($this->requireAttribute($attr, $value, $attrv) === null) {
170 4
            return;
171
        }
172
173 18
        if (isset($value['type'])) {
174 8
            $attrv = Transform::convertType($attrv, $attr, $value['type']);
175
        }
176
177 18
        if (isset($value['unwind'])) {
178 5
            $unwind = [];
179 5
            foreach ($attrv as $key => $element) {
180 5
                $result = $this->mapField($attr, $value['unwind'], [
181 5
                    'root' => $element,
182
                ]);
183
184 4
                if ($result !== null) {
185 4
                    $unwind[$key] = $result;
186
                }
187
            }
188
189 4
            $attrv = $unwind;
190
        }
191
192 17
        return $attrv;
193
    }
194
195
    /**
196
     * Check if attribute is required.
197
     */
198 22
    protected function requireAttribute(string $attr, array $value, $attrv)
199
    {
200 22
        if ($attrv === null || is_string($attrv) && strlen($attrv) === 0 || is_array($attrv) && count($attrv) === 0) {
201 4
            if (isset($value['required']) && $value['required'] === false || !isset($value['required'])) {
202 4
                $this->logger->debug('found attribute ['.$attr.'] but source attribute is empty, remove attribute from mapping', [
203 4
                     'category' => get_class($this),
204
                ]);
205
206 4
                return null;
207
            }
208
209
            throw new Exception\AttributeNotResolvable('required attribute '.$attr.' could not be resolved');
210
        }
211
212 18
        return $attrv;
213
    }
214
215
    /**
216
     * Transform attribute.
217
     */
218 25
    protected function transformAttribute(string $attr, array $value, $attrv)
219
    {
220 25
        if ($attrv === null) {
221 4
            return null;
222
        }
223
224 21
        if (isset($value['type']) && $value['type'] !== AttributeMapInterface::TYPE_ARRAY && is_array($attrv)) {
225 1
            $attrv = $this->firstArrayElement($attrv, $attr);
226
        }
227
228 21
        if (isset($value['rewrite'])) {
229 8
            $attrv = $this->rewrite($attrv, $value['rewrite']);
230
        }
231
232 19
        if (isset($value['require_regex'])) {
233 4
            if (!preg_match($value['require_regex'], $attrv)) {
234 2
                throw new Exception\AttributeRegexNotMatch('resolve attribute '.$attr.' value does not match require_regex');
235
            }
236
        }
237
238 18
        return $attrv;
239
    }
240
241
    /**
242
     * Check if attribute is required.
243
     */
244 25
    protected function resolveValue(string $attr, array $value, array $data)
245
    {
246 25
        $result = null;
247
248 25
        if (isset($value['value'])) {
249 1
            $result = $value['value'];
250
        }
251
252
        try {
253 25
            if (isset($value['from'])) {
254 25
                $result = Helper::getArrayValue($data, $value['from']);
255
            }
256 3
        } catch (\Exception $e) {
257 3
            $this->logger->warning('failed to resolve value of attribute ['.$attr.'] from ['.$value['from'].']', [
258 3
                'category' => get_class($this),
259 3
                'exception' => $e,
260
            ]);
261
        }
262
263
        try {
264 25
            if (isset($value['script'])) {
265 25
                $result = $this->expression->evaluate($value['script'], $data);
266
            }
267 1
        } catch (\Exception $e) {
268 1
            $this->logger->warning('failed to execute script ['.$value['script'].'] of attribute ['.$attr.']', [
269 1
                'category' => get_class($this),
270 1
                'exception' => $e,
271
            ]);
272
        }
273
274
        //if (isset($data[$attr])) {
275
        //    return $data[$attr];
276
        //}
277
278 25
        return $result;
279
    }
280
281
    /**
282
     * Shift first array element.
283
     */
284 1
    protected function firstArrayElement(Iterable $value, string $attribute)
285
    {
286 1
        if (empty($value)) {
287
            return $value;
288
        }
289
290 1
        $this->logger->debug('resolved value for attribute ['.$attribute.'] is an array but is not declared as an array, use first array element instead', [
291 1
             'category' => get_class($this),
292
        ]);
293
294 1
        return current($value);
295
    }
296
297
    /**
298
     * Process ruleset.
299
     */
300 8
    protected function rewrite($value, array $ruleset)
301
    {
302 8
        foreach ($ruleset as $rule) {
303 8
            if (!isset($rule['match'])) {
304 1
                throw new InvalidArgumentException('match in filter is not set');
305
            }
306
307 7
            if (!isset($rule['to'])) {
308 1
                throw new InvalidArgumentException('to in filter is not set');
309
            }
310
311 6
            if (isset($rule['regex']) && $rule['regex'] === false) {
312 2
                if ($value === $rule['match']) {
313 2
                    $value = $rule['to'];
314
315 2
                    return $value;
316
                }
317 4
            } elseif (isset($rule['regex']) && $rule['regex'] === true || !isset($rule['regex'])) {
318 4
                $value = preg_replace($rule['match'], $rule['to'], $value, -1, $count);
319 4
                if ($count > 0) {
320 5
                    return $value;
321
                }
322
            }
323
        }
324
325 1
        return $value;
326
    }
327
}
328