Passed
Push — master ( ff1a3c...f7850f )
by Smoren
01:51
created

Schemator::convert()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
c 0
b 0
f 0
dl 0
loc 13
rs 10
cc 3
nc 3
nop 3
1
<?php
2
3
namespace Smoren\Schemator\Components;
4
5
use Smoren\Schemator\Interfaces\NestedAccessorFactoryInterface;
6
use Smoren\Schemator\Interfaces\SchematorInterface;
7
use Smoren\Schemator\Factories\NestedAccessorFactory;
8
use Smoren\Schemator\Structs\FilterContext;
9
use Smoren\Schemator\Exceptions\NestedAccessorException;
10
use Smoren\Schemator\Exceptions\SchematorException;
11
use Throwable;
12
13
/**
14
 * Class for schematic data converting
15
 * @author Smoren <[email protected]>
16
 */
17
class Schemator implements SchematorInterface
18
{
19
    /**
20
     * @var callable[] filters map
21
     */
22
    protected array $filters = [];
23
    /**
24
     * @var string delimiter for multilevel paths
25
     */
26
    protected string $pathDelimiter;
27
    /**
28
     * @var NestedAccessorFactoryInterface nested accessor factory
29
     */
30
    protected NestedAccessorFactoryInterface $nestedAccessorFactory;
31
32
    /**
33
     * Schemator constructor.
34
     * @param string $pathDelimiter delimiter for multilevel paths
35
     */
36
    public function __construct(
37
        string $pathDelimiter = '.',
38
        NestedAccessorFactoryInterface $nestedAccessorFactory = null
39
    ) {
40
        $this->pathDelimiter = $pathDelimiter;
41
        $this->nestedAccessorFactory = $nestedAccessorFactory ?? new NestedAccessorFactory();
42
    }
43
44
    /**
45
     * @inheritDoc
46
     */
47
    public function convert($source, array $schema, bool $strict = false)
48
    {
49
        $toAccessor = $this->nestedAccessorFactory->create($result, $this->pathDelimiter);
50
51
        foreach($schema as $keyTo => $keyFrom) {
52
            $value = $this->getValue($source, $keyFrom, $strict);
53
            if($keyTo === '') {
54
                return $value;
55
            }
56
            $toAccessor->set($keyTo, $value, $strict);
57
        }
58
59
        return $result;
60
    }
61
62
    /**
63
     * @inheritDoc
64
     * @deprecated please use convert() method
65
     */
66
    public function exec($source, array $schema, bool $strict = false)
67
    {
68
        return $this->convert($source, $schema, $strict);
69
    }
70
71
    /**
72
     * @inheritDoc
73
     */
74
    public function getValue($source, $key, bool $strict = false)
75
    {
76
        if($key === '' || $key === null) {
77
            return $source;
78
        }
79
80
        if($source === null || (!is_array($source) && !is_object($source))) {
81
            return $this->getValueFromUnsupportedSource($source, $key, $strict);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getValueFromUnsup...$source, $key, $strict) 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...
82
        }
83
84
        if(is_string($key)) {
85
            return $this->getValueByKey($source, $key, $strict);
0 ignored issues
show
Bug introduced by
It seems like $source can also be of type object; however, parameter $source of Smoren\Schemator\Compone...emator::getValueByKey() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

85
            return $this->getValueByKey(/** @scrutinizer ignore-type */ $source, $key, $strict);
Loading history...
86
        }
87
88
        if(is_array($key)) {
89
            return $this->getValueByFilters($source, $key, $strict);
0 ignored issues
show
Bug introduced by
It seems like $source can also be of type object; however, parameter $source of Smoren\Schemator\Compone...or::getValueByFilters() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

89
            return $this->getValueByFilters(/** @scrutinizer ignore-type */ $source, $key, $strict);
Loading history...
90
        }
91
92
        return $this->getValueByUnsupportedKey($source, $key, $strict);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getValueByUnsuppo...$source, $key, $strict) 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...
93
    }
94
95
    /**
96
     * @inheritDoc
97
     */
98
    public function addFilter(string $filterName, callable $callback): self
99
    {
100
        $this->filters[$filterName] = $callback;
101
        return $this;
102
    }
103
104
    /**
105
     * Returns value got by string key
106
     * @param array $source source to get value from
107
     * @param string $key nested path to get value by
108
     * @param bool $strict when true throw exception if something goes wrong
109
     * @return array|mixed|null value
110
     * @throws SchematorException
111
     */
112
    protected function getValueByKey(array $source, string $key, bool $strict)
113
    {
114
        try {
115
            $fromAccessor = $this->nestedAccessorFactory->create($source, $this->pathDelimiter);
116
            return $fromAccessor->get($key, $strict);
117
        } catch(NestedAccessorException $e) {
118
            throw SchematorException::createAsCannotGetValue($source, $key, $e);
119
        }
120
    }
121
122
    /**
123
     * Returns value got by filters key
124
     * @param array $source source to get value from
125
     * @param array $filters filters config
126
     * @param bool $strict when true throw exception if something goes wrong
127
     * @return array|mixed|null
128
     * @throws SchematorException
129
     */
130
    protected function getValueByFilters(array $source, array $filters, bool $strict)
131
    {
132
        $result = $source;
133
        foreach($filters as $filterConfig) {
134
            if(is_string($filterConfig)) {
135
                $result = $this->getValue($result, $filterConfig, $strict);
136
            } elseif(is_array($filterConfig)) {
137
                $result = $this->runFilter($filterConfig, $result, $source, $strict);
138
            } else {
139
                if($strict) {
140
                    throw SchematorException::createAsUnsupportedFilterConfigType($filterConfig);
141
                }
142
                $result = null;
143
            }
144
        }
145
146
        return $result;
147
    }
148
149
    /**
150
     * Returns value got from unsupported source
151
     * @param mixed $source unsupported source
152
     * @param mixed $key path to get value by
153
     * @param bool $strict when true throw exception
154
     * @return null the only value we can get from unsupported source
155
     * @throws SchematorException
156
     */
157
    protected function getValueFromUnsupportedSource($source, $key, bool $strict)
158
    {
159
        if(!$strict) {
160
            return null;
161
        }
162
        throw SchematorException::createAsUnsupportedSourceType($source, $key);
163
    }
164
165
    /**
166
     * Returns value got by unsupported key
167
     * @param mixed $source source to get value from
168
     * @param mixed $key unsupported key
169
     * @param bool $strict when true throw exception
170
     * @return null the only value we can get by unsupported key
171
     * @throws SchematorException
172
     */
173
    protected function getValueByUnsupportedKey($source, $key, bool $strict)
174
    {
175
        if(!$strict) {
176
            return null;
177
        }
178
        throw SchematorException::createAsUnsupportedKeyType($source, $key);
179
    }
180
181
    /**
182
     * Returns value from source by filter
183
     * @param array $filterConfig filter config [filterName, ...args]
184
     * @param mixed $source source to extract value from
185
     * @param array $rootSource root source
186
     * @param bool $strict when true throw exception if something goes wrong
187
     * @return mixed result value
188
     * @throws SchematorException
189
     */
190
    protected function runFilter(array $filterConfig, $source, array $rootSource, bool $strict)
191
    {
192
        $filterName = array_shift($filterConfig);
193
194
        SchematorException::ensureFilterExists($this->filters, $filterName);
195
196
        try {
197
            return $this->filters[$filterName](
198
                new FilterContext($this, $source, $rootSource),
199
                ...$filterConfig
200
            );
201
        } catch(Throwable $e) {
202
            if($strict) {
203
                throw SchematorException::createAsFilterError($filterName, $filterConfig, $source, $e);
204
            }
205
            return null;
206
        }
207
    }
208
}
209