Completed
Push — master ( 316baf...2178d1 )
by Raffael
67:25 queued 62:39
created

AttributeMap::rewrite()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 11
cts 11
cp 1
rs 9.0111
c 0
b 0
f 0
cc 6
nc 6
nop 2
crap 6
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * tubee.io
7
 *
8
 * @copyright   Copryright (c) 2017-2019 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 MongoDB\BSON\Binary;
16
use Psr\Log\LoggerInterface;
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
use Tubee\V8\Engine as V8Engine;
21
use V8Js;
22
23
class AttributeMap implements AttributeMapInterface
24
{
25
    /**
26
     * Attribute map.
27
     *
28
     * @var array
29
     */
30
    protected $map = [];
31
32
    /**
33
     * Logger.
34
     *
35
     * @var LoggerInterface
36
     */
37
    protected $logger;
38
39
    /**
40
     * V8.
41
     *
42
     * @var V8Engine
43
     */
44
    protected $v8;
45
46
    /**
47
     * Init attribute map.
48
     */
49 27
    public function __construct(array $map = [], V8Engine $v8, LoggerInterface $logger)
50
    {
51 27
        $this->map = $map;
52 27
        $this->logger = $logger;
53 27
        $this->v8 = $v8;
54 27
    }
55
56
    /**
57
     * {@inheritdoc}
58
     */
59
    public function getMap(): array
60
    {
61
        return $this->map;
62
    }
63
64
    /**
65
     * {@inheritdoc}
66
     */
67
    public function getAttributes(): array
68
    {
69
        return array_column($this->map, 'name');
70
    }
71
72
    /**
73
     * {@inheritdoc}
74
     */
75 25
    public function map(array $data): array
76
    {
77 25
        $this->v8->object = $data;
78 25
        $attrv = null;
79
80 25
        $result = [];
81 25
        foreach ($this->map as $value) {
82 25
            if (isset($attrv)) {
83
                unset($attrv);
84
            }
85
86 25
            $attr = $value['name'];
87
88 25
            if (isset($value['ensure'])) {
89 4
                if ($value['ensure'] === AttributeMapInterface::ENSURE_MERGE && isset($value['type']) && $value['type'] !== AttributeMapInterface::TYPE_ARRAY) {
90 1
                    throw new InvalidArgumentException('attribute '.$attr.' ensure is set to merge but type is not an array');
91
                }
92
93 3
                if ($value['ensure'] === AttributeMapInterface::ENSURE_ABSENT) {
94 1
                    continue;
95
                }
96
            }
97
98 23
            $mapped = $this->mapField($attr, $value, $data);
99
100 19
            if (isset($value['name'])) {
101 19
                $attr = $value['name'];
102
            }
103
104 19
            if ($mapped !== null) {
105 16
                $result[$attr] = $mapped;
106
107 16
                $this->logger->debug('mapped attribute ['.$attr.'] to [<'.gettype($result[$attr]).'> {value}]', [
108 16
                    'category' => get_class($this),
109 19
                    'value' => ($result[$attr] instanceof Binary) ? '<bin '.mb_strlen($result[$attr]->getData()).'>' : $result[$attr],
0 ignored issues
show
Bug introduced by
The class MongoDB\BSON\Binary does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
110
                ]);
111
            }
112
        }
113
114 20
        return $result;
115
    }
116
117
    /**
118
     * {@inheritdoc}
119
     */
120 2
    public function getDiff(array $object, array $endpoint_object): array
121
    {
122 2
        $diff = [];
123 2
        foreach ($this->map as $value) {
124 2
            $attr = $value['name'];
125 2
            $exists = isset($endpoint_object[$attr]);
126
127 2
            if ($value['ensure'] === AttributeMapInterface::ENSURE_EXISTS && ($exists === true || !isset($object[$attr]))) {
128
                continue;
129
            }
130 2
            if (($value['ensure'] === AttributeMapInterface::ENSURE_LAST || $value['ensure'] === AttributeMapInterface::ENSURE_EXISTS) && isset($object[$attr])) {
131 2
                if ($exists && is_array($object[$attr]) && is_array($endpoint_object[$attr]) && Helper::arrayEqual($endpoint_object[$attr], $object[$attr])) {
132
                    continue;
133
                }
134 2
                if ($exists && $object[$attr] === $endpoint_object[$attr]) {
135 1
                    continue;
136
                }
137
138 1
                $diff[$attr] = [
139 1
                    'action' => AttributeMapInterface::ACTION_REPLACE,
140 1
                    'value' => $object[$attr],
141
                ];
142
            } elseif ($value['ensure'] === AttributeMapInterface::ENSURE_ABSENT && isset($endpoint_object[$attr]) || isset($endpoint_object[$attr]) && !isset($object[$attr]) && $value['ensure'] !== AttributeMapInterface::ENSURE_MERGE) {
143
                $diff[$attr] = [
144
                    'action' => AttributeMapInterface::ACTION_REMOVE,
145
                ];
146
            } elseif ($value['ensure'] === AttributeMapInterface::ENSURE_MERGE && isset($object[$attr])) {
147
                $new_values = [];
148
149
                foreach ($object[$attr] as $val) {
150
                    if (!$exists) {
151
                        $new_values[] = $val;
152
                    } elseif (is_array($endpoint_object[$attr]) && in_array($val, $endpoint_object[$attr]) || $val === $endpoint_object[$attr]) {
153
                        continue;
154
                    } else {
155
                        $new_values[] = $val;
156
                    }
157
                }
158
159
                if (!empty($new_values)) {
160
                    $diff[$attr] = [
161
                        'action' => AttributeMapInterface::ACTION_ADD,
162 1
                        'value' => $new_values,
163
                    ];
164
                }
165
            }
166
        }
167
168 2
        return $diff;
169
    }
170
171
    /**
172
     * Map field.
173
     */
174 23
    protected function mapField($attr, $value, $data)
175
    {
176 23
        $attrv = $this->resolveValue($attr, $value, $data);
177 23
        $attrv = $this->transformAttribute($attr, $value, $attrv);
178
179 22
        if ($this->requireAttribute($attr, $value, $attrv) === null) {
180 3
            return;
181
        }
182
183 17
        if (isset($value['type'])) {
184 8
            $attrv = Transform::convertType($attrv, $attr, $value['type']);
185
        }
186
187 17
        if (isset($value['unwind'])) {
188 5
            $unwind = [];
189 5
            foreach ($attrv as $key => $element) {
190 5
                $result = $this->mapField($attr, $value['unwind'], [
191 5
                    'root' => $element,
192
                ]);
193
194 4
                if ($result !== null) {
195 4
                    $unwind[$key] = $result;
196
                }
197
            }
198
199 4
            $attrv = $unwind;
200
        }
201
202 16
        return $attrv;
203
    }
204
205
    /**
206
     * Check if attribute is required.
207
     */
208 22
    protected function requireAttribute(string $attr, array $value, $attrv)
209
    {
210 22
        if ($attrv === null || is_string($attrv) && strlen($attrv) === 0 || is_array($attrv) && count($attrv) === 0) {
211 5
            if (isset($value['required']) && $value['required'] === false || !isset($value['required'])) {
212 3
                $this->logger->debug('found attribute ['.$attr.'] but source attribute is empty, remove attribute from mapping', [
213 3
                     'category' => get_class($this),
214
                ]);
215
216 3
                return null;
217
            }
218
219 2
            throw new Exception\AttributeNotResolvable('required attribute '.$attr.' could not be resolved');
220
        }
221
222 17
        return $attrv;
223
    }
224
225
    /**
226
     * Transform attribute.
227
     */
228 23
    protected function transformAttribute(string $attr, array $value, $attrv)
229
    {
230 23
        if ($attrv === null) {
231 5
            return null;
232
        }
233
234 18
        if (isset($value['type']) && $value['type'] !== AttributeMapInterface::TYPE_ARRAY && is_array($attrv)) {
235 1
            $attrv = $this->firstArrayElement($attrv, $attr);
236
        }
237
238 18
        if (isset($value['rewrite'])) {
239 6
            $attrv = $this->rewrite($attrv, $value['rewrite']);
240
        }
241
242 18
        if (isset($value['require_regex'])) {
243 4
            if (!preg_match($value['require_regex'], $attrv)) {
244 2
                throw new Exception\AttributeRegexNotMatch('resolve attribute '.$attr.' value does not match require_regex');
245
            }
246
        }
247
248 17
        return $attrv;
249
    }
250
251
    /**
252
     * Check if attribute is required.
253
     */
254 23
    protected function resolveValue(string $attr, array $value, array $data)
255
    {
256 23
        $result = null;
257
258 23
        if (isset($value['value'])) {
259 2
            $result = $value['value'];
260
        }
261
262
        try {
263 23
            if (isset($value['from'])) {
264 23
                $result = Helper::getArrayValue($data, $value['from']);
265
            }
266 3
        } catch (\Exception $e) {
267 3
            $this->logger->warning('failed to resolve value of attribute ['.$attr.'] from ['.$value['from'].']', [
268 3
                'category' => get_class($this),
269 3
                'exception' => $e,
270
            ]);
271
        }
272
273
        try {
274 23
            if (isset($value['script'])) {
275 2
                $this->v8->executeString($value['script'], '', V8Js::FLAG_FORCE_ARRAY);
276 23
                $result = $this->v8->getLastResult();
277
            }
278
        } catch (\Exception $e) {
279
            $this->logger->warning('failed to execute script ['.$value['script'].'] of attribute ['.$attr.']', [
280
                'category' => get_class($this),
281
                'exception' => $e,
282
            ]);
283
        }
284
285
        //if (isset($data[$attr])) {
286
        //    return $data[$attr];
287
        //}
288
289 23
        return $result;
290
    }
291
292
    /**
293
     * Shift first array element.
294
     */
295 1
    protected function firstArrayElement(Iterable $value, string $attribute)
296
    {
297 1
        if (empty($value)) {
298
            return $value;
299
        }
300
301 1
        $this->logger->debug('resolved value for attribute ['.$attribute.'] is an array but is not declared as an array, use first array element instead', [
302 1
             'category' => get_class($this),
303
        ]);
304
305 1
        return current($value);
306
    }
307
308
    /**
309
     * Process ruleset.
310
     */
311 6
    protected function rewrite($value, array $ruleset)
312
    {
313 6
        foreach ($ruleset as $rule) {
314 6
            if (isset($rule['from'])) {
315 2
                if ($value === $rule['from']) {
316 2
                    $value = $rule['to'];
317
318 2
                    return $value;
319
                }
320 4
            } elseif (isset($rule['match'])) {
321 4
                $value = preg_replace($rule['match'], $rule['to'], $value, -1, $count);
322 4
                if ($count > 0) {
323 5
                    return $value;
324
                }
325
            }
326
        }
327
328 1
        return $value;
329
    }
330
}
331