Passed
Push — master ( 35ee53...f51ed5 )
by Smoren
01:55
created

Schemator::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 8
rs 10
cc 1
nc 1
nop 3
1
<?php
2
3
namespace Smoren\Schemator\Components;
4
5
use Smoren\BitmapTools\Interfaces\BitmapInterface;
6
use Smoren\BitmapTools\Models\Bitmap;
7
use Smoren\Schemator\Interfaces\NestedAccessorFactoryInterface;
8
use Smoren\Schemator\Interfaces\SchematorInterface;
9
use Smoren\Schemator\Factories\NestedAccessorFactory;
10
use Smoren\Schemator\Structs\ErrorsLevelMask;
11
use Smoren\Schemator\Structs\FilterContext;
12
use Smoren\Schemator\Exceptions\NestedAccessorException;
13
use Smoren\Schemator\Exceptions\SchematorException;
14
use Throwable;
15
16
/**
17
 * Class for schematic data converting
18
 * @author Smoren <[email protected]>
19
 */
20
class Schemator implements SchematorInterface
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 BitmapInterface bitmap errors level mask
32
     */
33
    protected BitmapInterface $errorsLevelMask;
34
    /**
35
     * @var NestedAccessorFactoryInterface nested accessor factory
36
     */
37
    protected NestedAccessorFactoryInterface $nestedAccessorFactory;
38
39
    /**
40
     * Schemator constructor.
41
     * @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...
42
     * @param BitmapInterface|null $errorsLevelMask bitmap errors level mask
43
     * @param NestedAccessorFactoryInterface|null $nestedAccessorFactory nested accessor factory
44
     */
45
    public function __construct(
46
        string $pathDelimiter = '.',
47
        ?BitmapInterface $errorsLevelMask = null,
48
        NestedAccessorFactoryInterface $nestedAccessorFactory = null
49
    ) {
50
        $this->pathDelimiter = $pathDelimiter;
51
        $this->errorsLevelMask = $errorsLevelMask ?? ErrorsLevelMask::default();
52
        $this->nestedAccessorFactory = $nestedAccessorFactory ?? new NestedAccessorFactory();
53
    }
54
55
    /**
56
     * @inheritDoc
57
     * @throws SchematorException
58
     */
59
    public function convert($source, array $schema)
60
    {
61
        $toAccessor = $this->nestedAccessorFactory->create($result, $this->pathDelimiter);
62
63
        foreach($schema as $keyTo => $keyFrom) {
64
            $value = $this->getValue($source, $keyFrom);
65
            if($keyTo === '') {
66
                return $value;
67
            }
68
            $toAccessor->set($keyTo, $value);
69
        }
70
71
        return $result;
72
    }
73
74
    /**
75
     * @inheritDoc
76
     * @throws SchematorException
77
     */
78
    public function getValue($source, $key)
79
    {
80
        if($key === '' || $key === null) {
81
            return $source;
82
        }
83
84
        if($source === null || (!is_array($source) && !is_object($source))) {
85
            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...
86
        }
87
88
        if(is_string($key)) {
89
            return $this->getValueByKey($source, $key);
90
        }
91
92
        if(is_array($key)) {
93
            return $this->getValueByFilters($source, $key);
94
        }
95
96
        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...
97
    }
98
99
    /**
100
     * @inheritDoc
101
     */
102
    public function setPathDelimiter(string $value): void
103
    {
104
        $this->pathDelimiter = $value;
105
    }
106
107
    /**
108
     * @inheritDoc
109
     */
110
    public function setErrorsLevelMask(BitmapInterface $value): void
111
    {
112
        $this->errorsLevelMask = $value;
113
    }
114
115
    /**
116
     * @inheritDoc
117
     */
118
    public function addFilter(string $filterName, callable $callback): self
119
    {
120
        $this->filterMap[$filterName] = $callback;
121
        return $this;
122
    }
123
124
    /**
125
     * Returns value got by string key
126
     * @param array<string, mixed>|object $source source to get value from
127
     * @param string $key nested path to get value by
128
     * @return array|mixed|null value
129
     * @throws SchematorException
130
     */
131
    protected function getValueByKey($source, string $key)
132
    {
133
        try {
134
            $fromAccessor = $this->nestedAccessorFactory->create($source, $this->pathDelimiter);
135
136
            return $fromAccessor->get(
137
                $key,
138
                $this->needToThrow(SchematorException::CANNOT_GET_VALUE)
139
            );
140
        } catch(NestedAccessorException $e) {
141
            throw SchematorException::createAsCannotGetValue($source, $key, $e);
142
        }
143
    }
144
145
    /**
146
     * Returns value got by filters key
147
     * @param array<string, mixed>|object $source source to get value from
148
     * @param array<int, string|array<int, mixed>> $filters filters config
149
     * @return mixed|null
150
     * @throws SchematorException
151
     */
152
    protected function getValueByFilters($source, array $filters)
153
    {
154
        $result = $source;
155
        foreach($filters as $filterConfig) {
156
            if(is_string($filterConfig)) {
157
                $result = $this->getValue($result, $filterConfig);
158
            } elseif(is_array($filterConfig)) {
159
                $result = $this->runFilter($filterConfig, $result, $source);
160
            } else {
161
                if($this->needToThrow(SchematorException::UNSUPPORTED_FILTER_CONFIG_TYPE)) {
162
                    throw SchematorException::createAsUnsupportedFilterConfigType($filterConfig);
163
                }
164
                $result = null;
165
            }
166
        }
167
168
        return $result;
169
    }
170
171
    /**
172
     * Returns value got from unsupported source
173
     * @param mixed $source unsupported source
174
     * @param mixed $key path to get value by
175
     * @return null the only value we can get from unsupported source
176
     * @throws SchematorException
177
     */
178
    protected function getValueFromUnsupportedSource($source, $key)
179
    {
180
        if($this->needToThrow(SchematorException::UNSUPPORTED_SOURCE_TYPE)) {
181
            throw SchematorException::createAsUnsupportedSourceType($source, $key);
182
        }
183
        return null;
184
    }
185
186
    /**
187
     * Returns value got by unsupported key
188
     * @param array<string, mixed>|object $source source to get value from
189
     * @param mixed $key unsupported key
190
     * @return null the only value we can get by unsupported key
191
     * @throws SchematorException
192
     */
193
    protected function getValueByUnsupportedKey($source, $key)
194
    {
195
        if($this->needToThrow(SchematorException::UNSUPPORTED_KEY_TYPE)) {
196
            throw SchematorException::createAsUnsupportedKeyType($source, $key);
197
        }
198
        return null;
199
    }
200
201
    /**
202
     * Returns value from source by filter
203
     * @param array<int, mixed> $filterConfig filter config [filterName, ...args]
204
     * @param array<string, mixed>|object|mixed $source source to extract value from
205
     * @param array<string, mixed>|object $rootSource root source
206
     * @return mixed result value
207
     * @throws SchematorException
208
     */
209
    protected function runFilter(array $filterConfig, $source, $rootSource)
210
    {
211
        $filterName = strval(array_shift($filterConfig));
212
213
        if(
214
            !isset($this->filterMap[$filterName])
215
            && $this->needToThrow(SchematorException::FILTER_NOT_FOUND)
216
        ) {
217
            throw SchematorException::createAsFilterNotFound($filterName);
218
        }
219
220
        try {
221
            return $this->filterMap[$filterName](
222
                new FilterContext($this, $source, $rootSource, $filterConfig),
223
                ...$filterConfig
224
            );
225
        } catch(SchematorException $e) {
226
            if($this->needToThrow($e->getCode())) {
227
                throw $e;
228
            }
229
            return null;
230
        } catch(Throwable $e) {
231
            if($this->needToThrow(SchematorException::FILTER_ERROR)) {
232
                throw SchematorException::createAsFilterError($filterName, $filterConfig, $source, $e);
233
            }
234
            return null;
235
        }
236
    }
237
238
    /**
239
     * Returns true if there is given error code in errors level mask
240
     * @param int $errorCode
241
     * @return bool
242
     */
243
    protected function needToThrow(int $errorCode): bool
244
    {
245
        return $this->errorsLevelMask->intersectsWith(Bitmap::create([$errorCode]));
246
    }
247
}
248