Passed
Push — master ( f6722b...c3d303 )
by Smoren
01:41
created

Schemator::needToThrow()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 1
c 1
b 0
f 1
dl 0
loc 3
rs 10
cc 1
nc 1
nop 1
1
<?php
2
3
namespace Smoren\Schemator\Components;
4
5
use Smoren\BitmapTools\Helpers\BitmapHelper;
6
use Smoren\Schemator\Interfaces\NestedAccessorFactoryInterface;
7
use Smoren\Schemator\Interfaces\SchematorInterface;
8
use Smoren\Schemator\Factories\NestedAccessorFactory;
9
use Smoren\Schemator\Structs\FilterContext;
10
use Smoren\Schemator\Exceptions\NestedAccessorException;
11
use Smoren\Schemator\Exceptions\SchematorException;
12
use Throwable;
13
14
/**
15
 * Class for schematic data converting
16
 * @author Smoren <[email protected]>
17
 */
18
class Schemator implements SchematorInterface
19
{
20
    public const ERRORS_LEVEL_DEFAULT = 114;
21
22
    /**
23
     * @var array<string, callable> filters map
24
     */
25
    protected array $filterMap = [];
26
    /**
27
     * @var non-empty-string delimiter for multilevel paths
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
28
     */
29
    protected string $pathDelimiter;
30
    /**
31
     * @var int bitmap errors level mask
32
     */
33
    protected int $errorsLevelMask;
34
    /**
35
     * @var NestedAccessorFactoryInterface nested accessor factory
36
     */
37
    protected NestedAccessorFactoryInterface $nestedAccessorFactory;
38
39
    /**
40
     * Creates bitmap errors level mask
41
     * @param array<int> $errorCodes
42
     * @return int
43
     */
44
    public static function createErrorsLevelMask(array $errorCodes): int
45
    {
46
        return BitmapHelper::create($errorCodes);
47
    }
48
49
    /**
50
     * Schemator constructor.
51
     * @param non-empty-string $pathDelimiter delimiter for multilevel paths
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
52
     * @param int $errorsLevelMask bitmap errors level mask
53
     */
54
    public function __construct(
55
        string $pathDelimiter = '.',
56
        int $errorsLevelMask = self::ERRORS_LEVEL_DEFAULT,
57
        NestedAccessorFactoryInterface $nestedAccessorFactory = null
58
    ) {
59
        $this->pathDelimiter = $pathDelimiter;
60
        $this->errorsLevelMask = $errorsLevelMask;
61
        $this->nestedAccessorFactory = $nestedAccessorFactory ?? new NestedAccessorFactory();
62
    }
63
64
    /**
65
     * @inheritDoc
66
     */
67
    public function convert($source, array $schema)
68
    {
69
        $toAccessor = $this->nestedAccessorFactory->create($result, $this->pathDelimiter);
70
71
        foreach($schema as $keyTo => $keyFrom) {
72
            $value = $this->getValue($source, $keyFrom);
73
            if($keyTo === '') {
74
                return $value;
75
            }
76
            $toAccessor->set($keyTo, $value);
77
        }
78
79
        return $result;
80
    }
81
82
    /**
83
     * @inheritDoc
84
     */
85
    public function getValue($source, $key)
86
    {
87
        if($key === '' || $key === null) {
88
            return $source;
89
        }
90
91
        if($source === null || (!is_array($source) && !is_object($source))) {
92
            return $this->getValueFromUnsupportedSource($source, $key);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getValueFromUnsup...edSource($source, $key) targeting Smoren\Schemator\Compone...FromUnsupportedSource() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
93
        }
94
95
        if(is_string($key)) {
96
            return $this->getValueByKey($source, $key);
97
        }
98
99
        if(is_array($key)) {
100
            return $this->getValueByFilters($source, $key);
101
        }
102
103
        return $this->getValueByUnsupportedKey($source, $key);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getValueByUnsupportedKey($source, $key) targeting Smoren\Schemator\Compone...ValueByUnsupportedKey() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
104
    }
105
106
    /**
107
     * @inheritDoc
108
     */
109
    public function setPathDelimiter(string $value): void
110
    {
111
        $this->pathDelimiter = $value;
112
    }
113
114
    /**
115
     * @inheritDoc
116
     */
117
    public function setErrorsLevelMask(int $value): void
118
    {
119
        $this->errorsLevelMask = $value;
120
    }
121
122
    /**
123
     * @inheritDoc
124
     */
125
    public function addFilter(string $filterName, callable $callback): self
126
    {
127
        $this->filterMap[$filterName] = $callback;
128
        return $this;
129
    }
130
131
    /**
132
     * Returns value got by string key
133
     * @param array<string, mixed>|object $source source to get value from
134
     * @param string $key nested path to get value by
135
     * @return array|mixed|null value
136
     * @throws SchematorException
137
     */
138
    protected function getValueByKey($source, string $key)
139
    {
140
        try {
141
            $fromAccessor = $this->nestedAccessorFactory->create($source, $this->pathDelimiter);
142
            return $fromAccessor->get(
143
                $key,
144
                BitmapHelper::intersects($this->errorsLevelMask, [SchematorException::CANNOT_GET_VALUE])
145
            );
146
        } catch(NestedAccessorException $e) {
147
            throw SchematorException::createAsCannotGetValue($source, $key, $e);
148
        }
149
    }
150
151
    /**
152
     * Returns value got by filters key
153
     * @param array<string, mixed>|object $source source to get value from
154
     * @param array<int, string|array<int, mixed>> $filters filters config
155
     * @return mixed|null
156
     * @throws SchematorException
157
     */
158
    protected function getValueByFilters($source, array $filters)
159
    {
160
        $result = $source;
161
        foreach($filters as $filterConfig) {
162
            if(is_string($filterConfig)) {
163
                $result = $this->getValue($result, $filterConfig);
164
            } elseif(is_array($filterConfig)) {
165
                $result = $this->runFilter($filterConfig, $result, $source);
166
            } else {
167
                if(
168
                    BitmapHelper::intersects(
169
                        $this->errorsLevelMask,
170
                        [SchematorException::UNSUPPORTED_FILTER_CONFIG_TYPE]
171
                    )
172
                ) {
173
                    throw SchematorException::createAsUnsupportedFilterConfigType($filterConfig);
174
                }
175
                $result = null;
176
            }
177
        }
178
179
        return $result;
180
    }
181
182
    /**
183
     * Returns value got from unsupported source
184
     * @param mixed $source unsupported source
185
     * @param mixed $key path to get value by
186
     * @return null the only value we can get from unsupported source
187
     * @throws SchematorException
188
     */
189
    protected function getValueFromUnsupportedSource($source, $key)
190
    {
191
        if(BitmapHelper::intersects($this->errorsLevelMask, [SchematorException::UNSUPPORTED_SOURCE_TYPE])) {
192
            throw SchematorException::createAsUnsupportedSourceType($source, $key);
193
        }
194
        return null;
195
    }
196
197
    /**
198
     * Returns value got by unsupported key
199
     * @param array<string, mixed>|object $source source to get value from
200
     * @param mixed $key unsupported key
201
     * @return null the only value we can get by unsupported key
202
     * @throws SchematorException
203
     */
204
    protected function getValueByUnsupportedKey($source, $key)
205
    {
206
        if(BitmapHelper::intersects($this->errorsLevelMask, [SchematorException::UNSUPPORTED_KEY_TYPE])) {
207
            throw SchematorException::createAsUnsupportedKeyType($source, $key);
208
        }
209
        return null;
210
    }
211
212
    /**
213
     * Returns value from source by filter
214
     * @param array<int, mixed> $filterConfig filter config [filterName, ...args]
215
     * @param array<string, mixed>|object|mixed $source source to extract value from
216
     * @param array<string, mixed>|object $rootSource root source
217
     * @return mixed result value
218
     * @throws SchematorException
219
     */
220
    protected function runFilter(array $filterConfig, $source, $rootSource)
221
    {
222
        $filterName = strval(array_shift($filterConfig));
223
224
        if(
225
            !isset($this->filterMap[$filterName])
226
            && BitmapHelper::intersects($this->errorsLevelMask, [SchematorException::FILTER_NOT_FOUND])
227
        ) {
228
            throw SchematorException::createAsFilterNotFound($filterName);
229
        }
230
231
        try {
232
            return $this->filterMap[$filterName](
233
                new FilterContext($this, $source, $rootSource),
234
                ...$filterConfig
235
            );
236
        } catch(SchematorException $e) {
237
            if(BitmapHelper::intersects($this->errorsLevelMask, [$e->getCode()])) {
238
                throw $e;
239
            }
240
            return null;
241
        } catch(Throwable $e) {
242
            if(BitmapHelper::intersects($this->errorsLevelMask, [SchematorException::FILTER_ERROR])) {
243
                throw SchematorException::createAsFilterError($filterName, $filterConfig, $source, $e);
244
            }
245
            return null;
246
        }
247
    }
248
249
    /**
250
     * Returns true if there is given error code in errors level mask
251
     * @param int $errorCode
252
     * @return bool
253
     */
254
    protected function needToThrow(int $errorCode): bool
255
    {
256
        return BitmapHelper::intersects($this->errorsLevelMask, [$errorCode]);
257
    }
258
}
259