Completed
Pull Request — master (#9)
by Trevor N.
01:27
created

Processor::hydrateModel()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 6
cts 6
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 5
nc 3
nop 4
crap 4
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
        $hydrator = $hydrator ?: $this->getHydratorForModel($model);
253
254 12
        return $this->hydrateModel($input_data, $model, $hydrator, $context);
255
    }
256
257
    /**
258
     * {@inheritdoc}
259
     *
260
     * If a builder isn't provided, an attempt will be made to automatically
261
     * resolve and build an appropriate builder from the provided factory.
262
     *
263
     * If a hydrator is provided, it will be used to hydrate the provided type
264
     * after building via the builder.
265
     *
266
     * If a hydrator isn't provided, but the "always_hydrate_after_building"
267
     * property is set to true, an attempt to hydrate the type will be made
268
     * after building via the builder, and the hydrator will be automatically
269
     * resolved from the provided factory.
270
     *
271
     * @param mixed $input_data The input data.
272
     * @param string $type The type to build.
273
     * @param Builder|null $builder The builder to use in the process.
274
     * @param Hydrator|null $hydrator An optional hydrator to use in the
275
     *  process, after the type is built, to aid in the full hydration of the
276
     *  resulting model.
277
     * @param Map|null $context An optional generic key-value map, for providing
278
     *  contextual values during the process.
279
     * @return mixed The built model.
280
     */
281 24
    public function processForType(
282
        $input_data,
283
        string $type,
284
        Builder $builder = null,
285
        Hydrator $hydrator = null,
286
        Map $context = null
287
    ) {
288 24
        $input_data = $this->transformInput($input_data);
289
290 24
        $builder = $builder ?: $this->getBuilderForType($type);
291
292 21
        $model = $this->buildModel($input_data, $type, $builder, $context);
293
294 18
        if (null !== $hydrator || $this->always_hydrate_after_building) {
295 9
            $hydrator = $hydrator ?: $this->getHydratorForModel($model);
296
297 6
            $model = $this->hydrateModel($input_data, $model, $hydrator, $context);
298
        }
299
300 15
        return $model;
301
    }
302
303
    /**
304
     * Transform the input data.
305
     *
306
     * @param mixed $input_data The input data.
307
     * @return mixed The resulting transformed data.
308
     */
309 39
    protected function transformInput($input_data)
310
    {
311 39
        return $this->input_transformer->transform($input_data);
312
    }
313
314
    /**
315
     * Hydrate a model from incoming data.
316
     *
317
     * If a hydrator isn't provided, an attempt will be made to automatically
318
     * resolve and build an appropriate hydrator from the provided factory.
319
     *
320
     * @param mixed $input_data The input data.
321
     * @param mixed $model The model to hydrate.
322
     * @param Hydrator $hydrator The hydrator to use.
323
     * @param Map|null $context An optional generic key-value map, for providing
324
     *  contextual values during the process.
325
     * @throws IncompatibleProcessException If the hydrator isn't compatible
326
     *  with the given process strategy.
327
     * @return mixed The hydrated model.
328
     */
329 18
    protected function hydrateModel($input_data, $model, Hydrator $hydrator, Map $context = null)
330
    {
331 18
        if ($hydrator instanceof ContextualHydrator) {
332 3
            return $hydrator->hydrate($input_data, $model, $context);
333 15
        } elseif ($this->require_contextual_processing_compatibility && null !== $context) {
334 3
            throw IncompatibleProcessException::forRequiredContextCompatibility($hydrator);
335
        }
336
337 12
        return $hydrator->hydrate($input_data, $model);
338
    }
339
340
    /**
341
     * Build a model from incoming data.
342
     *
343
     * If a builder isn't provided, an attempt will be made to automatically
344
     * resolve and build an appropriate builder from the provided factory.
345
     *
346
     * @param mixed $input_data The input data.
347
     * @param string $type The type to build.
348
     * @param Builder $builder The builder to use.
349
     * @param Map|null $context An optional generic key-value map, for providing
350
     *  contextual values during the process.
351
     * @throws IncompatibleProcessException If the builder isn't compatible with
352
     *  the given process strategy.
353
     * @return mixed The built model.
354
     */
355 21
    protected function buildModel($input_data, string $type, Builder $builder, Map $context = null)
356
    {
357 21
        if ($builder instanceof ContextualBuilder) {
358 3
            return $builder->build($input_data, $context);
359 18
        } elseif ($this->require_contextual_processing_compatibility && null !== $context) {
360 3
            throw IncompatibleProcessException::forRequiredContextCompatibility($builder);
361
        }
362
363 15
        return $builder->build($input_data);
364
    }
365
366
    /**
367
     * Get a Hydrator for a given model.
368
     *
369
     * @param mixed $model The model to get a hydrator for.
370
     * @throws UnresolvableHydratorException If a hydrator can't be resolved for
371
     *  the given model.
372
     * @return Hydrator The resulting hydrator.
373
     */
374 12
    protected function getHydratorForModel($model): Hydrator
375
    {
376 12
        if (null === $this->hydrator_factory) {
377 6
            throw UnresolvableHydratorException::forModel($model);
378
        }
379
380 6
        return $this->hydrator_factory->buildForModel($model);
381
    }
382
383
    /**
384
     * Get a Builder for a given model.
385
     *
386
     * @param string $type The type to get a builder for.
387
     * @throws UnresolvableBuilderException If a builder can't be resolved for
388
     *  the given model.
389
     * @return Builder The resulting builder.
390
     */
391 6
    protected function getBuilderForType(string $type): Builder
392
    {
393 6
        if (null === $this->builder_factory) {
394 3
            throw UnresolvableBuilderException::forType($type);
395
        }
396
397 3
        return $this->builder_factory->buildForType($type);
398
    }
399
}
400