Decide   A
last analyzed

Complexity

Total Complexity 27

Size/Duplication

Total Lines 228
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 83
dl 0
loc 228
rs 10
c 0
b 0
f 0
wmc 27

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A mapThe() 0 5 1
A prepareChoices() 0 12 3
A wiring() 0 17 2
A havingMany() 0 11 2
A label() 0 3 1
A ownWiring() 0 17 2
A basedOn() 0 8 1
A objects() 0 8 1
A which() 0 3 1
A with() 0 7 2
A hydrator() 0 9 2
A identityColumns() 0 3 1
A identifying() 0 7 1
A havingOne() 0 7 1
A addChoiceWiring() 0 10 4
A by() 0 8 1
1
<?php
2
declare(strict_types=1);
3
4
namespace Stratadox\TableLoader\Builder;
5
6
use function array_unshift;
7
use function assert;
8
use function count;
9
use Countable;
10
use Stratadox\HydrationMapper\InvalidMapperConfiguration;
11
use Stratadox\Hydrator\ArrayHydrator;
12
use Stratadox\Hydrator\Hydrates;
13
use Stratadox\Hydrator\OneOfTheseHydrators;
14
use Stratadox\Hydrator\VariadicConstructor;
15
use Stratadox\Instantiator\CannotInstantiateThis;
16
use Stratadox\TableLoader\Loader\From;
17
use Stratadox\TableLoader\Loader\HasMany;
18
use Stratadox\TableLoader\Loader\HasOne;
19
use Stratadox\TableLoader\Loader\Identified;
20
use Stratadox\TableLoader\Loader\MakesConnections;
21
use Stratadox\TableLoader\Loader\MakesObjects;
22
use Stratadox\TableLoader\Loader\Objects;
23
use Stratadox\TableLoader\Loader\Prefixed;
24
use Stratadox\TableLoader\Loader\To;
25
use Stratadox\TableLoader\Loader\Wire;
26
use Stratadox\TableLoader\Loader\Wired;
27
use Stratadox\TableLoader\Loader\WiresObjects;
28
29
/**
30
 * Builds the required infrastructure to load objects that use inheritance.
31
 * @see InCase
32
 *
33
 * @author Stratadox
34
 */
35
final class Decide implements DefinesMultipleClassMapping
36
{
37
    private $label;
38
    private $ownId = ['id'];
39
    private $identityColumnsFor;
40
    private $decisionKey;
41
    /** @var LoadsWhenTriggered[] */
42
    private $choices = [];
43
    /** @var MakesConnections[] */
44
    private $relation = [];
45
46
    private function __construct(string $label)
47
    {
48
        $this->label = $label;
49
        $this->identityColumnsFor[$label] = [$label . '_type', $label . '_id'];
50
    }
51
52
    /**
53
     * Makes a new builder for inheritance structure
54
     *
55
     * @param string $label
56
     * @return DefinesMultipleClassMapping
57
     */
58
    public static function which(string $label): DefinesMultipleClassMapping
59
    {
60
        return new self($label);
61
    }
62
63
    /** @inheritdoc */
64
    public function basedOn(
65
        string $key,
66
        LoadsWhenTriggered ...$choices
67
    ): DefinesMultipleClassMapping {
68
        $new = clone $this;
69
        $new->decisionKey = $key;
70
        $new->choices = $choices;
71
        return $new;
72
    }
73
74
    /** @inheritdoc */
75
    public function by(string ...$columns): DefinesMultipleClassMapping
76
    {
77
        $label = $this->label;
78
        $new = clone $this;
79
        $new->ownId = $columns;
80
        array_unshift($columns, $this->decisionKey);
81
        $new->identityColumnsFor[$label] = $this->mapThe($columns, $label . '_');
82
        return $new;
83
    }
84
85
    /** @inheritdoc */
86
    public function havingOne(
87
        string $property,
88
        string $label
89
    ): DefinesObjectMapping {
90
        $new = clone $this;
91
        $new->relation[$label] = HasOne::in($property);
92
        return $new;
93
    }
94
95
    /** @inheritdoc */
96
    public function havingMany(
97
        string $property,
98
        string $label,
99
        string $collectionClass = null
100
    ): DefinesObjectMapping {
101
        $hydrator = $collectionClass ?
102
            VariadicConstructor::forThe($collectionClass) :
103
            ArrayHydrator::create();
104
        $new = clone $this;
105
        $new->relation[$label] = HasMany::in($property, $hydrator);
106
        return $new;
107
    }
108
109
    /** @inheritdoc */
110
    public function with(array $properties): DefinesMultipleClassMapping
111
    {
112
        $new = clone $this;
113
        foreach ($this->choices as $i => $choice) {
114
            $new->choices[$i] = $choice->with($properties);
115
        }
116
        return $new;
117
    }
118
119
    /** @inheritdoc */
120
    public function identifying(
121
        string $label,
122
        string ...$columns
123
    ): DefinesObjectMapping {
124
        $new = clone $this;
125
        $new->identityColumnsFor[$label] = $columns;
126
        return $new;
127
    }
128
129
    /** @inheritdoc */
130
    public function label(): string
131
    {
132
        return $this->label;
133
    }
134
135
    /** @inheritdoc */
136
    public function identityColumns(): array
137
    {
138
        return $this->identityColumnsFor[$this->label];
139
    }
140
141
    /** @inheritdoc */
142
    public function objects(): MakesObjects
143
    {
144
        assert(isset($this->decisionKey));
145
        // @todo try/catch
146
        return Objects::producedByThis(
147
            $this->hydrator(),
148
            Prefixed::with($this->label),
149
            Identified::by(...$this->ownId)->andForLoading($this->decisionKey)
150
        );
151
    }
152
153
    /** @inheritdoc */
154
    public function wiring(): WiresObjects
155
    {
156
        $wires = $this->addChoiceWiring(
157
            $this->prepareChoices(
158
                $this->choices,
159
                $this->identityColumnsFor
160
            ),
161
            $this->ownWiring(
162
                $this->label,
163
                $this->identityColumnsFor,
164
                $this->relation
165
            )
166
        );
167
        if (count($wires) === 1) {
168
            return $wires[0];
169
        }
170
        return Wired::together(...$wires);
171
    }
172
173
    /**
174
     * @return Hydrates
175
     * @throws InvalidMapperConfiguration
176
     * @throws CannotInstantiateThis
177
     */
178
    private function hydrator(): Hydrates
179
    {
180
        $choices = [];
181
        foreach ($this->choices as $choice) {
182
            $choices[$choice->decisionTrigger()] = $choice->hydrator();
183
        }
184
        return OneOfTheseHydrators::decideBasedOnThe(
185
            $this->decisionKey,
186
            $choices
187
        );
188
    }
189
190
    /**
191
     * Prepends the identifying columns with a label.
192
     *
193
     * @param array $columns
194
     * @return array
195
     */
196
    private function mapThe(array $columns, string $prefix): array
197
    {
198
        return array_map(function(string $column) use ($prefix): string {
199
            return $prefix . $column;
200
        }, $columns);
201
    }
202
203
    /**
204
     * @param string             $ownLabel
205
     * @param string[][]         $identityFor
206
     * @param MakesConnections[] $relations
207
     * @return WiresObjects[]
208
     */
209
    private function ownWiring(
210
        string $ownLabel,
211
        array $identityFor,
212
        array $relations
213
    ): array {
214
        $ownId = $identityFor[$ownLabel];
215
        $wires = [];
216
        foreach ($relations as $otherLabel => $connectThem) {
217
            //@todo $this->mustKnowTheIdentityColumnsFor($otherLabel);
218
            $otherId = $identityFor[$otherLabel];
219
            $wires[] = Wire::it(
220
                From::the($ownLabel, Identified::by(...$ownId)),
221
                To::the($otherLabel, Identified::by(...$otherId)),
222
                $connectThem
223
            );
224
        }
225
        return $wires;
226
    }
227
228
    /**
229
     * @param LoadsWhenTriggered[] $originalChoices
230
     * @param string[][]           $identityColumns
231
     * @return LoadsWhenTriggered[]
232
     */
233
    private function prepareChoices(
234
        array $originalChoices,
235
        array $identityColumns
236
    ): array {
237
        $choices = [];
238
        foreach ($originalChoices as $choice) {
239
            foreach ($identityColumns as $label => $columns) {
240
                $choice = $choice->identifying($label, ...$columns);
241
            }
242
            $choices[] = $choice->labeled($this->label);
243
        }
244
        return $choices;
245
    }
246
247
    /**
248
     * @param LoadsWhenTriggered[] $choices
249
     * @param WiresObjects[]       $wires
250
     * @return WiresObjects[]
251
     * @throws CannotMakeTableMapping
252
     */
253
    private function addChoiceWiring(array $choices, array $wires): array
254
    {
255
        foreach ($choices as $choice) {
256
            $wiring = $choice->wiring();
257
            if ($wiring instanceof Countable && !count($wiring)) {
258
                continue;
259
            }
260
            $wires[] = $wiring;
261
        }
262
        return $wires;
263
    }
264
}
265