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

Processor   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 369
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 33
dl 0
loc 369
ccs 69
cts 69
cp 1
rs 9.3999
c 0
b 0
f 0

18 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
B buildModel() 0 14 6
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 getBuilderForType() 0 7 2
A processForType() 0 16 3
A getInputTransformer() 0 3 1
A getHydratorForModel() 0 7 2
A getAlwaysHydrateAfterBuilding() 0 3 1
B hydrateModel() 0 14 6
A __construct() 0 12 2
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
    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
    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