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

setRequireContextualProcessingCompatibility()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
crap 1
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)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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