1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Incoming |
4
|
|
|
* |
5
|
|
|
* @author Trevor Suarez (Rican7) |
6
|
|
|
* @copyright (c) Trevor Suarez |
7
|
|
|
* @link https://github.com/Rican7/incoming |
8
|
|
|
* @license MIT |
9
|
|
|
*/ |
10
|
|
|
|
11
|
|
|
declare(strict_types=1); |
12
|
|
|
|
13
|
|
|
namespace Incoming; |
14
|
|
|
|
15
|
|
|
use Incoming\Hydrator\Builder; |
16
|
|
|
use Incoming\Hydrator\BuilderFactory; |
17
|
|
|
use Incoming\Hydrator\ContextualBuilder; |
18
|
|
|
use Incoming\Hydrator\ContextualHydrator; |
19
|
|
|
use Incoming\Hydrator\Exception\IncompatibleProcessException; |
20
|
|
|
use Incoming\Hydrator\Exception\UnresolvableBuilderException; |
21
|
|
|
use Incoming\Hydrator\Exception\UnresolvableHydratorException; |
22
|
|
|
use Incoming\Hydrator\Hydrator; |
23
|
|
|
use Incoming\Hydrator\HydratorFactory; |
24
|
|
|
use Incoming\Structure\Map; |
25
|
|
|
use Incoming\Transformer\StructureBuilderTransformer; |
26
|
|
|
use Incoming\Transformer\Transformer; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* A default implementation of both the `ModelProcessor` and `TypeProcessor` |
30
|
|
|
* for processing input data with an optional input transformation phase and |
31
|
|
|
* automatic hydrator and builder resolution. |
32
|
|
|
*/ |
33
|
|
|
class Processor implements ModelProcessor, TypeProcessor |
34
|
|
|
{ |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* Properties |
38
|
|
|
*/ |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* An input transformer to pre-process the input data before hydration. |
42
|
|
|
* |
43
|
|
|
* @var Transformer |
44
|
|
|
*/ |
45
|
|
|
private $input_transformer; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* A factory for building hydrators for a given model. |
49
|
|
|
* |
50
|
|
|
* @var HydratorFactory|null |
51
|
|
|
*/ |
52
|
|
|
private $hydrator_factory; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* A factory for building builders for a given model. |
56
|
|
|
* |
57
|
|
|
* @var BuilderFactory|null |
58
|
|
|
*/ |
59
|
|
|
private $builder_factory; |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* A configuration flag that denotes whether hydration should always be run |
63
|
|
|
* after building a new model when processing specified types. |
64
|
|
|
* |
65
|
|
|
* @var bool |
66
|
|
|
*/ |
67
|
|
|
private $always_hydrate_after_building = false; |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* A configuration flag that denotes whether processing (hydration/building) |
71
|
|
|
* should require contextual compatibility when a context is provided. |
72
|
|
|
* |
73
|
|
|
* @var bool |
74
|
|
|
*/ |
75
|
|
|
private $require_contextual_processing_compatibility = false; |
76
|
|
|
|
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Methods |
80
|
|
|
*/ |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* Constructor |
84
|
|
|
* |
85
|
|
|
* @param Transformer|null $input_transformer The input transformer. |
86
|
|
|
* @param HydratorFactory|null $hydrator_factory A hydrator factory. |
87
|
|
|
* @param BuilderFactory|null $builder_factory A builder factory. |
88
|
|
|
* @param bool $always_hydrate_after_building A configuration flag that |
89
|
|
|
* denotes whether hydration should always be run after building a new |
90
|
|
|
* model when processing specified types. |
91
|
|
|
* @param bool $require_contextual_processing_compatibility A configuration |
92
|
|
|
* flag that denotes whether processing (hydration/building) should require |
93
|
|
|
* contextual compatibility when a context is provided. |
94
|
|
|
*/ |
95
|
54 |
|
public function __construct( |
96
|
|
|
Transformer $input_transformer = null, |
97
|
|
|
HydratorFactory $hydrator_factory = null, |
98
|
|
|
BuilderFactory $builder_factory = null, |
99
|
|
|
bool $always_hydrate_after_building = false, |
100
|
|
|
bool $require_contextual_processing_compatibility = false |
101
|
|
|
) { |
102
|
54 |
|
$this->input_transformer = $input_transformer ?: new StructureBuilderTransformer(); |
103
|
54 |
|
$this->hydrator_factory = $hydrator_factory; |
104
|
54 |
|
$this->builder_factory = $builder_factory; |
105
|
54 |
|
$this->always_hydrate_after_building = $always_hydrate_after_building; |
106
|
54 |
|
$this->require_contextual_processing_compatibility = $require_contextual_processing_compatibility; |
107
|
54 |
|
} |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* Get the input transformer. |
111
|
|
|
* |
112
|
|
|
* @return Transformer The input transformer. |
113
|
|
|
*/ |
114
|
3 |
|
public function getInputTransformer(): Transformer |
115
|
|
|
{ |
116
|
3 |
|
return $this->input_transformer; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* Set the input transformer. |
121
|
|
|
* |
122
|
|
|
* @param Transformer $input_transformer The input transformer. |
123
|
|
|
* @return $this This instance. |
124
|
|
|
*/ |
125
|
3 |
|
public function setInputTransformer(Transformer $input_transformer): self |
126
|
|
|
{ |
127
|
3 |
|
$this->input_transformer = $input_transformer; |
128
|
|
|
|
129
|
3 |
|
return $this; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* Get the hydrator factory. |
134
|
|
|
* |
135
|
|
|
* @return HydratorFactory|null The hydrator factory. |
136
|
|
|
*/ |
137
|
3 |
|
public function getHydratorFactory() |
138
|
|
|
{ |
139
|
3 |
|
return $this->hydrator_factory; |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
/** |
143
|
|
|
* Set the hydrator factory. |
144
|
|
|
* |
145
|
|
|
* @param HydratorFactory|null $hydrator_factory The hydrator factory. |
146
|
|
|
* @return $this This instance. |
147
|
|
|
*/ |
148
|
9 |
|
public function setHydratorFactory(HydratorFactory $hydrator_factory = null): self |
149
|
|
|
{ |
150
|
9 |
|
$this->hydrator_factory = $hydrator_factory; |
151
|
|
|
|
152
|
9 |
|
return $this; |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* Get the builder factory. |
157
|
|
|
* |
158
|
|
|
* @return BuilderFactory|null The builder factory. |
159
|
|
|
*/ |
160
|
3 |
|
public function getBuilderFactory() |
161
|
|
|
{ |
162
|
3 |
|
return $this->builder_factory; |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* Set the builder factory. |
167
|
|
|
* |
168
|
|
|
* @param BuilderFactory|null $builder_factory The builder factory. |
169
|
|
|
* @return $this This instance. |
170
|
|
|
*/ |
171
|
6 |
|
public function setBuilderFactory(BuilderFactory $builder_factory = null): self |
172
|
|
|
{ |
173
|
6 |
|
$this->builder_factory = $builder_factory; |
174
|
|
|
|
175
|
6 |
|
return $this; |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* Get the value of the configuration flag that denotes whether hydration |
180
|
|
|
* should always be run after building a new model when processing |
181
|
|
|
* specified types. |
182
|
|
|
* |
183
|
|
|
* @return bool The value of the flag. |
184
|
|
|
*/ |
185
|
3 |
|
public function getAlwaysHydrateAfterBuilding(): bool |
186
|
|
|
{ |
187
|
3 |
|
return $this->always_hydrate_after_building; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
/** |
191
|
|
|
* Set the value of the configuration flag that denotes whether hydration |
192
|
|
|
* should always be run after building a new model when processing |
193
|
|
|
* specified types. |
194
|
|
|
* |
195
|
|
|
* @param bool $always_hydrate_after_building Whether or not to always |
196
|
|
|
* hydrate after building a new model when processing types. |
197
|
|
|
* @return $this This instance. |
198
|
|
|
*/ |
199
|
9 |
|
public function setAlwaysHydrateAfterBuilding(bool $always_hydrate_after_building): self |
200
|
|
|
{ |
201
|
9 |
|
$this->always_hydrate_after_building = $always_hydrate_after_building; |
202
|
|
|
|
203
|
9 |
|
return $this; |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
/** |
207
|
|
|
* Get the value of the configuration flag that denotes whether processing |
208
|
|
|
* (hydration/building) should require contextual compatibility when a |
209
|
|
|
* context is provided. |
210
|
|
|
* |
211
|
|
|
* @return bool The value of the flag. |
212
|
|
|
*/ |
213
|
3 |
|
public function getRequireContextualProcessingCompatibility(): bool |
214
|
|
|
{ |
215
|
3 |
|
return $this->require_contextual_processing_compatibility; |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
/** |
219
|
|
|
* Set the value of the configuration flag that denotes whether processing |
220
|
|
|
* (hydration/building) should require contextual compatibility when a |
221
|
|
|
* context is provided. |
222
|
|
|
* |
223
|
|
|
* @param bool $require_contextual_processing_compatibility Whether or not |
224
|
|
|
* to require contextual processing compatibility when a context is |
225
|
|
|
* provided. |
226
|
|
|
* @return $this This instance. |
227
|
|
|
*/ |
228
|
9 |
|
public function setRequireContextualProcessingCompatibility(bool $require_contextual_processing_compatibility): self |
229
|
|
|
{ |
230
|
9 |
|
$this->require_contextual_processing_compatibility = $require_contextual_processing_compatibility; |
231
|
|
|
|
232
|
9 |
|
return $this; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
/** |
236
|
|
|
* {@inheritdoc} |
237
|
|
|
* |
238
|
|
|
* If a hydrator isn't provided, an attempt will be made to automatically |
239
|
|
|
* resolve and build an appropriate hydrator from the provided factory. |
240
|
|
|
* |
241
|
|
|
* @param mixed $input_data The input data. |
242
|
|
|
* @param mixed $model The model to hydrate. |
243
|
|
|
* @param Hydrator|null $hydrator The hydrator to use in the process. |
244
|
|
|
* @param Map|null $context An optional generic key-value map, for providing |
245
|
|
|
* contextual values during the process. |
246
|
|
|
* @return mixed The hydrated model. |
247
|
|
|
*/ |
248
|
15 |
|
public function processForModel($input_data, $model, Hydrator $hydrator = null, Map $context = null) |
249
|
|
|
{ |
250
|
15 |
|
$input_data = $this->transformInput($input_data); |
251
|
|
|
|
252
|
15 |
|
return $this->hydrateModel($input_data, $model, $hydrator, $context); |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
/** |
256
|
|
|
* {@inheritdoc} |
257
|
|
|
* |
258
|
|
|
* If a builder isn't provided, an attempt will be made to automatically |
259
|
|
|
* resolve and build an appropriate builder from the provided factory. |
260
|
|
|
* |
261
|
|
|
* If a hydrator is provided, it will be used to hydrate the provided type |
262
|
|
|
* after building via the builder. |
263
|
|
|
* |
264
|
|
|
* If a hydrator isn't provided, but the "always_hydrate_after_building" |
265
|
|
|
* property is set to true, an attempt to hydrate the type will be made |
266
|
|
|
* after building via the builder, and the hydrator will be automatically |
267
|
|
|
* resolved from the provided factory. |
268
|
|
|
* |
269
|
|
|
* @param mixed $input_data The input data. |
270
|
|
|
* @param string $type The type to build. |
271
|
|
|
* @param Builder|null $builder The builder to use in the process. |
272
|
|
|
* @param Hydrator|null $hydrator An optional hydrator to use in the |
273
|
|
|
* process, after the type is built, to aid in the full hydration of the |
274
|
|
|
* resulting model. |
275
|
|
|
* @param Map|null $context An optional generic key-value map, for providing |
276
|
|
|
* contextual values during the process. |
277
|
|
|
* @return mixed The built model. |
278
|
|
|
*/ |
279
|
24 |
|
public function processForType( |
280
|
|
|
$input_data, |
281
|
|
|
string $type, |
282
|
|
|
Builder $builder = null, |
283
|
|
|
Hydrator $hydrator = null, |
284
|
|
|
Map $context = null |
285
|
|
|
) { |
286
|
24 |
|
$input_data = $this->transformInput($input_data); |
287
|
|
|
|
288
|
24 |
|
$model = $this->buildModel($input_data, $type, $builder, $context); |
289
|
|
|
|
290
|
18 |
|
if (null !== $hydrator || $this->always_hydrate_after_building) { |
291
|
9 |
|
$model = $this->hydrateModel($input_data, $model, $hydrator, $context); |
292
|
|
|
} |
293
|
|
|
|
294
|
15 |
|
return $model; |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
/** |
298
|
|
|
* Transform the input data. |
299
|
|
|
* |
300
|
|
|
* @param mixed $input_data The input data. |
301
|
|
|
* @return mixed The resulting transformed data. |
302
|
|
|
*/ |
303
|
39 |
|
protected function transformInput($input_data) |
304
|
|
|
{ |
305
|
39 |
|
return $this->input_transformer->transform($input_data); |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
/** |
309
|
|
|
* Hydrate a model from incoming data. |
310
|
|
|
* |
311
|
|
|
* If a hydrator isn't provided, an attempt will be made to automatically |
312
|
|
|
* resolve and build an appropriate hydrator from the provided factory. |
313
|
|
|
* |
314
|
|
|
* @param mixed $input_data The input data. |
315
|
|
|
* @param mixed $model The model to hydrate. |
316
|
|
|
* @param Hydrator|null $hydrator The hydrator to use. |
317
|
|
|
* @param Map|null $context An optional generic key-value map, for providing |
318
|
|
|
* contextual values during the process. |
319
|
|
|
* @throws IncompatibleProcessException If the hydrator isn't compatible |
320
|
|
|
* with the given process strategy. |
321
|
|
|
* @return mixed The hydrated model. |
322
|
|
|
*/ |
323
|
24 |
View Code Duplication |
protected function hydrateModel($input_data, $model, Hydrator $hydrator = null, Map $context = null) |
|
|
|
|
324
|
|
|
{ |
325
|
24 |
|
if (null === $hydrator) { |
326
|
12 |
|
$hydrator = $this->getHydratorForModel($model); |
327
|
|
|
} |
328
|
|
|
|
329
|
18 |
|
if ($hydrator instanceof ContextualHydrator) { |
330
|
3 |
|
return $hydrator->hydrate($input_data, $model, $context); |
331
|
15 |
|
} elseif ($this->require_contextual_processing_compatibility |
332
|
15 |
|
&& !($hydrator instanceof ContextualHydrator) && null !== $context) { |
333
|
3 |
|
throw IncompatibleProcessException::forRequiredContextCompatibility($hydrator); |
334
|
|
|
} |
335
|
|
|
|
336
|
12 |
|
return $hydrator->hydrate($input_data, $model); |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
/** |
340
|
|
|
* Build a model from incoming data. |
341
|
|
|
* |
342
|
|
|
* If a builder isn't provided, an attempt will be made to automatically |
343
|
|
|
* resolve and build an appropriate builder from the provided factory. |
344
|
|
|
* |
345
|
|
|
* @param mixed $input_data The input data. |
346
|
|
|
* @param string $type The type to build. |
347
|
|
|
* @param Builder|null $builder The builder to use. |
348
|
|
|
* @param Map|null $context An optional generic key-value map, for providing |
349
|
|
|
* contextual values during the process. |
350
|
|
|
* @throws IncompatibleProcessException If the builder isn't compatible with |
351
|
|
|
* the given process strategy. |
352
|
|
|
* @return mixed The built model. |
353
|
|
|
*/ |
354
|
24 |
View Code Duplication |
protected function buildModel($input_data, string $type, Builder $builder = null, Map $context = null) |
|
|
|
|
355
|
|
|
{ |
356
|
24 |
|
if (null === $builder) { |
357
|
6 |
|
$builder = $this->getBuilderForType($type); |
358
|
|
|
} |
359
|
|
|
|
360
|
21 |
|
if ($builder instanceof ContextualBuilder) { |
361
|
3 |
|
return $builder->build($input_data, $context); |
362
|
18 |
|
} elseif ($this->require_contextual_processing_compatibility |
363
|
18 |
|
&& !($builder instanceof ContextualBuilder) && null !== $context) { |
364
|
3 |
|
throw IncompatibleProcessException::forRequiredContextCompatibility($builder); |
365
|
|
|
} |
366
|
|
|
|
367
|
15 |
|
return $builder->build($input_data); |
368
|
|
|
} |
369
|
|
|
|
370
|
|
|
/** |
371
|
|
|
* Get a Hydrator for a given model. |
372
|
|
|
* |
373
|
|
|
* @param mixed $model The model to get a hydrator for. |
374
|
|
|
* @throws UnresolvableHydratorException If a hydrator can't be resolved for |
375
|
|
|
* the given model. |
376
|
|
|
* @return Hydrator The resulting hydrator. |
377
|
|
|
*/ |
378
|
12 |
|
protected function getHydratorForModel($model): Hydrator |
379
|
|
|
{ |
380
|
12 |
|
if (null === $this->hydrator_factory) { |
381
|
6 |
|
throw UnresolvableHydratorException::forModel($model); |
382
|
|
|
} |
383
|
|
|
|
384
|
6 |
|
return $this->hydrator_factory->buildForModel($model); |
385
|
|
|
} |
386
|
|
|
|
387
|
|
|
/** |
388
|
|
|
* Get a Builder for a given model. |
389
|
|
|
* |
390
|
|
|
* @param string $type The type to get a builder for. |
391
|
|
|
* @throws UnresolvableBuilderException If a builder can't be resolved for |
392
|
|
|
* the given model. |
393
|
|
|
* @return Builder The resulting builder. |
394
|
|
|
*/ |
395
|
6 |
|
protected function getBuilderForType(string $type): Builder |
396
|
|
|
{ |
397
|
6 |
|
if (null === $this->builder_factory) { |
398
|
3 |
|
throw UnresolvableBuilderException::forType($type); |
399
|
|
|
} |
400
|
|
|
|
401
|
3 |
|
return $this->builder_factory->buildForType($type); |
402
|
|
|
} |
403
|
|
|
} |
404
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.