Completed
Push — master ( 2178d1...145b3d )
by Raffael
06:24 queued 03:25
created

AttributeMap::getDiff()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 2
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\Diff;
19
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...
20
use Tubee\AttributeMap\Transform;
21
use Tubee\V8\Engine as V8Engine;
22
use V8Js;
23
24
class AttributeMap implements AttributeMapInterface
25
{
26
    /**
27
     * Attribute map.
28
     *
29
     * @var array
30
     */
31
    protected $map = [];
32
33
    /**
34
     * Logger.
35
     *
36
     * @var LoggerInterface
37
     */
38
    protected $logger;
39
40
    /**
41
     * V8.
42
     *
43
     * @var V8Engine
44
     */
45
    protected $v8;
46
47
    /**
48
     * Init attribute map.
49
     */
50 24
    public function __construct(array $map = [], V8Engine $v8, LoggerInterface $logger)
51
    {
52 24
        $this->map = $map;
53 24
        $this->logger = $logger;
54 24
        $this->v8 = $v8;
55 24
    }
56
57
    /**
58
     * {@inheritdoc}
59
     */
60
    public function getMap(): array
61
    {
62
        return $this->map;
63
    }
64
65
    /**
66
     * {@inheritdoc}
67
     */
68
    public function getAttributes(): array
69
    {
70
        return array_column($this->map, 'name');
71
    }
72
73
    /**
74
     * {@inheritdoc}
75
     */
76 24
    public function map(array $data): array
77
    {
78 24
        $this->v8->object = $data;
79 24
        $attrv = null;
80
81 24
        $result = [];
82 24
        foreach ($this->map as $value) {
83 24
            if (isset($attrv)) {
84
                unset($attrv);
85
            }
86
87 24
            $attr = $value['name'];
88
89 24
            if (isset($value['ensure'])) {
90 4
                if ($value['ensure'] === AttributeMapInterface::ENSURE_MERGE && isset($value['type']) && $value['type'] !== AttributeMapInterface::TYPE_ARRAY) {
91 1
                    throw new InvalidArgumentException('attribute '.$attr.' ensure is set to merge but type is not an array');
92
                }
93
94 3
                if ($value['ensure'] === AttributeMapInterface::ENSURE_ABSENT) {
95 1
                    continue;
96
                }
97
            }
98
99 22
            $mapped = $this->mapField($attr, $value, $data);
100
101 18
            if (isset($value['name'])) {
102 18
                $attr = $value['name'];
103
            }
104
105 18
            if ($mapped !== null) {
106 15
                $result[$attr] = $mapped;
107
108 15
                $this->logger->debug('mapped attribute ['.$attr.'] to [<'.gettype($result[$attr]).'> {value}]', [
109 15
                    'category' => get_class($this),
110 18
                    '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...
111
                ]);
112
            }
113
        }
114
115 19
        return $result;
116
    }
117
118
    /**
119
     * {@inheritdoc}
120
     */
121
    public function getDiff(array $object, array $endpoint_object): array
122
    {
123
        return Diff::calculate($this->map, $object, $endpoint_object);
124
    }
125
126
    /**
127
     * Map field.
128
     */
129 22
    protected function mapField($attr, $value, $data)
130
    {
131 22
        $attrv = $this->resolveValue($attr, $value, $data);
132 22
        $attrv = $this->transformAttribute($attr, $value, $attrv);
133
134 21
        if ($this->requireAttribute($attr, $value, $attrv) === null) {
135 3
            return;
136
        }
137
138 16
        if (isset($value['type'])) {
139 8
            $attrv = Transform::convertType($attrv, $attr, $value['type']);
140
        }
141
142 16
        if (isset($value['unwind'])) {
143 5
            $unwind = [];
144 5
            foreach ($attrv as $key => $element) {
145 5
                $result = $this->mapField($attr, $value['unwind'], [
146 5
                    'root' => $element,
147
                ]);
148
149 4
                if ($result !== null) {
150 4
                    $unwind[$key] = $result;
151
                }
152
            }
153
154 4
            $attrv = $unwind;
155
        }
156
157 15
        return $attrv;
158
    }
159
160
    /**
161
     * Check if attribute is required.
162
     */
163 21
    protected function requireAttribute(string $attr, array $value, $attrv)
164
    {
165 21
        if ($attrv === null || is_string($attrv) && strlen($attrv) === 0 || is_array($attrv) && count($attrv) === 0) {
166 5
            if (isset($value['required']) && $value['required'] === false || !isset($value['required'])) {
167 3
                $this->logger->debug('found attribute ['.$attr.'] but source attribute is empty, remove attribute from mapping', [
168 3
                     'category' => get_class($this),
169
                ]);
170
171 3
                return null;
172
            }
173
174 2
            throw new Exception\AttributeNotResolvable('required attribute '.$attr.' could not be resolved');
175
        }
176
177 16
        return $attrv;
178
    }
179
180
    /**
181
     * Transform attribute.
182
     */
183 22
    protected function transformAttribute(string $attr, array $value, $attrv)
184
    {
185 22
        if ($attrv === null) {
186 5
            return null;
187
        }
188
189 17
        if (isset($value['type']) && $value['type'] !== AttributeMapInterface::TYPE_ARRAY && is_array($attrv)) {
190 1
            $attrv = $this->firstArrayElement($attrv, $attr);
191
        }
192
193 17
        if (isset($value['rewrite'])) {
194 6
            $attrv = $this->rewrite($attrv, $value['rewrite']);
195
        }
196
197 17
        if (isset($value['require_regex'])) {
198 4
            if (!preg_match($value['require_regex'], $attrv)) {
199 2
                throw new Exception\AttributeRegexNotMatch('resolve attribute '.$attr.' value does not match require_regex');
200
            }
201
        }
202
203 16
        return $attrv;
204
    }
205
206
    /**
207
     * Check if attribute is required.
208
     */
209 22
    protected function resolveValue(string $attr, array $value, array $data)
210
    {
211 22
        $result = null;
212
213 22
        if (isset($value['value'])) {
214 1
            $result = $value['value'];
215
        }
216
217
        try {
218 22
            if (isset($value['from'])) {
219 22
                $result = Helper::getArrayValue($data, $value['from']);
220
            }
221 3
        } catch (\Exception $e) {
222 3
            $this->logger->warning('failed to resolve value of attribute ['.$attr.'] from ['.$value['from'].']', [
223 3
                'category' => get_class($this),
224 3
                'exception' => $e,
225
            ]);
226
        }
227
228
        try {
229 22
            if (isset($value['script'])) {
230 2
                $this->v8->executeString($value['script'], '', V8Js::FLAG_FORCE_ARRAY);
231 22
                $result = $this->v8->getLastResult();
232
            }
233
        } catch (\Exception $e) {
234
            $this->logger->warning('failed to execute script ['.$value['script'].'] of attribute ['.$attr.']', [
235
                'category' => get_class($this),
236
                'exception' => $e,
237
            ]);
238
        }
239
240
        //if (isset($data[$attr])) {
241
        //    return $data[$attr];
242
        //}
243
244 22
        return $result;
245
    }
246
247
    /**
248
     * Shift first array element.
249
     */
250 1
    protected function firstArrayElement(Iterable $value, string $attribute)
251
    {
252 1
        if (empty($value)) {
253
            return $value;
254
        }
255
256 1
        $this->logger->debug('resolved value for attribute ['.$attribute.'] is an array but is not declared as an array, use first array element instead', [
257 1
             'category' => get_class($this),
258
        ]);
259
260 1
        return current($value);
261
    }
262
263
    /**
264
     * Process ruleset.
265
     */
266 6
    protected function rewrite($value, array $ruleset)
267
    {
268 6
        foreach ($ruleset as $rule) {
269 6
            if (isset($rule['from'])) {
270 2
                if ($value === $rule['from']) {
271 2
                    $value = $rule['to'];
272
273 2
                    return $value;
274
                }
275 4
            } elseif (isset($rule['match'])) {
276 4
                $value = preg_replace($rule['match'], $rule['to'], $value, -1, $count);
277 4
                if ($count > 0) {
278 5
                    return $value;
279
                }
280
            }
281
        }
282
283 1
        return $value;
284
    }
285
}
286