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); |
|
|
|
|
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
if(is_string($key)) { |
85
|
|
|
return $this->getValueByKey($source, $key, $strict); |
|
|
|
|
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
if(is_array($key)) { |
89
|
|
|
return $this->getValueByFilters($source, $key, $strict); |
|
|
|
|
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
return $this->getValueByUnsupportedKey($source, $key, $strict); |
|
|
|
|
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
|
|
|
|
This check looks for function or method calls that always return null and whose return value is used.
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.