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

Processor::enforceProcessCompatibility()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 5
ccs 4
cts 4
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 3
nc 2
nop 3
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
        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 18
        $is_context_compatible = ($hydrator instanceof ContextualHydrator);
325
326 18
        $this->enforceProcessCompatibility($is_context_compatible, (null !== $context), $hydrator);
327
328 15
        if ($is_context_compatible) {
329 3
            return $hydrator->hydrate($input_data, $model, $context);
0 ignored issues
show
Unused Code introduced by
The call to Incoming\Hydrator\Hydrator::hydrate() has too many arguments starting with $context. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

329
            return $hydrator->/** @scrutinizer ignore-call */ hydrate($input_data, $model, $context);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
330
        }
331
332 12
        return $hydrator->hydrate($input_data, $model);
333
    }
334
335
    /**
336
     * Build a model from incoming data.
337
     *
338
     * If a builder isn't provided, an attempt will be made to automatically
339
     * resolve and build an appropriate builder from the provided factory.
340
     *
341
     * @param mixed $input_data The input data.
342
     * @param string $type The type to build.
343
     * @param Builder|null $builder The builder to use.
344
     * @param Map|null $context An optional generic key-value map, for providing
345
     *  contextual values during the process.
346
     * @return mixed The built model.
347
     */
348 24
    protected function buildModel($input_data, string $type, Builder $builder = null, Map $context = null)
349
    {
350 24
        $builder = $builder ?: $this->getBuilderForType($type);
351 21
        $is_context_compatible = ($builder instanceof ContextualBuilder);
352
353 21
        $this->enforceProcessCompatibility($is_context_compatible, (null !== $context), $builder);
354
355 18
        if ($is_context_compatible) {
356 3
            return $builder->build($input_data, $context);
0 ignored issues
show
Unused Code introduced by
The call to Incoming\Hydrator\Builder::build() has too many arguments starting with $context. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

356
            return $builder->/** @scrutinizer ignore-call */ build($input_data, $context);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
357
        }
358
359 15
        return $builder->build($input_data);
360
    }
361
362
    /**
363
     * Get a Hydrator for a given model.
364
     *
365
     * @param mixed $model The model to get a hydrator for.
366
     * @throws UnresolvableHydratorException If a hydrator can't be resolved for
367
     *  the given model.
368
     * @return Hydrator The resulting hydrator.
369
     */
370 12
    protected function getHydratorForModel($model): Hydrator
371
    {
372 12
        if (null === $this->hydrator_factory) {
373 6
            throw UnresolvableHydratorException::forModel($model);
374
        }
375
376 6
        return $this->hydrator_factory->buildForModel($model);
377
    }
378
379
    /**
380
     * Get a Builder for a given model.
381
     *
382
     * @param string $type The type to get a builder for.
383
     * @throws UnresolvableBuilderException If a builder can't be resolved for
384
     *  the given model.
385
     * @return Builder The resulting builder.
386
     */
387 6
    protected function getBuilderForType(string $type): Builder
388
    {
389 6
        if (null === $this->builder_factory) {
390 3
            throw UnresolvableBuilderException::forType($type);
391
        }
392
393 3
        return $this->builder_factory->buildForType($type);
394
    }
395
396
    /**
397
     * Enforce that a provided process (hydrator, builder, etc) is compatible
398
     * with the processing strategy being used.
399
     *
400
     * @param bool $is_context_compatible Whether or not the process is
401
     *  compatible with contexts.
402
     * @param bool $context_provided Whether or not a context has been provided
403
     *  in the process.
404
     * @param object|null $process The process to enforce compatibility for.
405
     * @throws IncompatibleProcessException If the builder isn't compatible with
406
     *  the given process strategy.
407
     * @return void
408
     */
409 33
    protected function enforceProcessCompatibility(bool $is_context_compatible, bool $context_provided, $process = null)
410
    {
411 33
        if ($context_provided && !$is_context_compatible
412 33
            && $this->require_contextual_processing_compatibility) {
413 6
            throw IncompatibleProcessException::forRequiredContextCompatibility($process);
414
        }
415 27
    }
416
}
417