Processor   A
last analyzed

Complexity

Total Complexity 31

Size/Duplication

Total Lines 379
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 31
dl 0
loc 379
ccs 67
cts 67
cp 1
rs 9.8
c 0
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
A getBuilderFactory() 0 3 1
A transformInput() 0 3 1
A setAlwaysHydrateAfterBuilding() 0 5 1
A getRequireContextualProcessingCompatibility() 0 3 1
A setInputTransformer() 0 5 1
A setBuilderFactory() 0 5 1
A setHydratorFactory() 0 5 1
A setRequireContextualProcessingCompatibility() 0 5 1
A getHydratorFactory() 0 3 1
A processForModel() 0 5 1
A processForType() 0 16 3
A getInputTransformer() 0 3 1
A getAlwaysHydrateAfterBuilding() 0 3 1
A __construct() 0 12 2
A buildModel() 0 11 3
A getBuilderForType() 0 7 2
A getHydratorForModel() 0 7 2
A hydrateModel() 0 11 3
A enforceProcessCompatibility() 0 5 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
        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
     * @return mixed The hydrated model.
320
     */
321 24
    protected function hydrateModel($input_data, $model, Hydrator $hydrator = null, Map $context = null)
322
    {
323 24
        $hydrator = $hydrator ?: $this->getHydratorForModel($model);
324
325 18
        $this->enforceProcessCompatibility(($hydrator instanceof ContextualHydrator), (null !== $context), $hydrator);
326
327 15
        if ($hydrator instanceof ContextualHydrator) {
328 3
            return $hydrator->hydrate($input_data, $model, $context);
329
        }
330
331 12
        return $hydrator->hydrate($input_data, $model);
332
    }
333
334
    /**
335
     * Build a model from incoming data.
336
     *
337
     * If a builder isn't provided, an attempt will be made to automatically
338
     * resolve and build an appropriate builder from the provided factory.
339
     *
340
     * @param mixed $input_data The input data.
341
     * @param string $type The type to build.
342
     * @param Builder|null $builder The builder to use.
343
     * @param Map|null $context An optional generic key-value map, for providing
344
     *  contextual values during the process.
345
     * @return mixed The built model.
346
     */
347 24
    protected function buildModel($input_data, string $type, Builder $builder = null, Map $context = null)
348
    {
349 24
        $builder = $builder ?: $this->getBuilderForType($type);
350
351 21
        $this->enforceProcessCompatibility(($builder instanceof ContextualBuilder), (null !== $context), $builder);
352
353 18
        if ($builder instanceof ContextualBuilder) {
354 3
            return $builder->build($input_data, $context);
355
        }
356
357 15
        return $builder->build($input_data);
358
    }
359
360
    /**
361
     * Get a Hydrator for a given model.
362
     *
363
     * @param mixed $model The model to get a hydrator for.
364
     * @throws UnresolvableHydratorException If a hydrator can't be resolved for
365
     *  the given model.
366
     * @return Hydrator The resulting hydrator.
367
     */
368 12
    protected function getHydratorForModel($model): Hydrator
369
    {
370 12
        if (null === $this->hydrator_factory) {
371 6
            throw UnresolvableHydratorException::forModel($model);
372
        }
373
374 6
        return $this->hydrator_factory->buildForModel($model);
375
    }
376
377
    /**
378
     * Get a Builder for a given model.
379
     *
380
     * @param string $type The type to get a builder for.
381
     * @throws UnresolvableBuilderException If a builder can't be resolved for
382
     *  the given model.
383
     * @return Builder The resulting builder.
384
     */
385 6
    protected function getBuilderForType(string $type): Builder
386
    {
387 6
        if (null === $this->builder_factory) {
388 3
            throw UnresolvableBuilderException::forType($type);
389
        }
390
391 3
        return $this->builder_factory->buildForType($type);
392
    }
393
394
    /**
395
     * Enforce that a provided process (hydrator, builder, etc) is compatible
396
     * with the processing strategy being used.
397
     *
398
     * @param bool $is_context_compatible Whether or not the process is
399
     *  compatible with contexts.
400
     * @param bool $context_provided Whether or not a context has been provided
401
     *  in the process.
402
     * @param object|null $process The process to enforce compatibility for.
403
     * @throws IncompatibleProcessException If the builder isn't compatible with
404
     *  the given process strategy.
405
     * @return void
406
     */
407 33
    protected function enforceProcessCompatibility(bool $is_context_compatible, bool $context_provided, $process = null)
408
    {
409 33
        if ($context_provided && !$is_context_compatible
410 33
            && $this->require_contextual_processing_compatibility) {
411 6
            throw IncompatibleProcessException::forRequiredContextCompatibility($process);
412
        }
413 27
    }
414
}
415