Completed
Push — master ( 057ffe...a55d6c )
by Jesse
02:02
created

Decide::prepareChoices()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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