Load::objects()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 2
nop 0
dl 0
loc 11
rs 10
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
namespace Stratadox\TableLoader\Builder;
5
6
use function count;
7
use Stratadox\Hydration\Mapper\Mapper;
8
use Stratadox\HydrationMapper\InvalidMapperConfiguration;
9
use Stratadox\Hydrator\ArrayHydrator;
10
use Stratadox\Hydrator\Hydrates;
11
use Stratadox\Hydrator\SimpleHydrator;
12
use Stratadox\Hydrator\VariadicConstructor;
13
use Stratadox\Instantiator\CannotInstantiateThis;
14
use Stratadox\TableLoader\Loader\From;
15
use Stratadox\TableLoader\Loader\HasMany;
16
use Stratadox\TableLoader\Loader\HasOne;
17
use Stratadox\TableLoader\Loader\Identified;
18
use Stratadox\TableLoader\Loader\MakesObjects;
19
use Stratadox\TableLoader\Loader\Objects;
20
use Stratadox\TableLoader\Loader\Prefixed;
21
use Stratadox\TableLoader\Loader\To;
22
use Stratadox\TableLoader\Loader\Wire;
23
use Stratadox\TableLoader\Loader\Wired;
24
use Stratadox\TableLoader\Loader\WiresObjects;
25
26
/**
27
 * Builds the required infrastructure to load objects that have no parents.
28
 *
29
 * @author Stratadox
30
 */
31
final class Load implements DefinesSingleClassMapping
32
{
33
    private $label;
34
    private $ownId = ['id'];
35
    private $identityColumnsFor = [];
36
    private $class;
37
    private $properties = [];
38
    private $relation = [];
39
40
    private function __construct(string $label)
41
    {
42
        $this->label = $label;
43
        $this->identityColumnsFor[$label] = [$label . '_id'];
44
    }
45
46
    /**
47
     * Makes a new builder to load each of the entities that have this label.
48
     *
49
     * @param string $label
50
     * @return DefinesSingleClassMapping
51
     */
52
    public static function each(string $label): DefinesSingleClassMapping
53
    {
54
        return new self($label);
55
    }
56
57
    /** @inheritdoc */
58
    public function by(string ...$columns): DefinesSingleClassMapping
59
    {
60
        $label = $this->label;
61
        $new = clone $this;
62
        $new->ownId = $columns;
63
        $new->identityColumnsFor[$label] = $this->mapThe($columns, $label . '_');
64
        return $new;
65
    }
66
67
    /** @inheritdoc */
68
    public function as(
69
        string $class,
70
        array $properties = []
71
    ): DefinesSingleClassMapping {
72
        $new = clone $this;
73
        $new->class = $class;
74
        $new->properties = $properties;
75
        return $new;
76
    }
77
78
    /** @inheritdoc */
79
    public function havingOne(
80
        string $property,
81
        string $label
82
    ): DefinesObjectMapping {
83
        $new = clone $this;
84
        $new->relation[$label] = HasOne::in($property);
85
        return $new;
86
    }
87
88
    /** @inheritdoc */
89
    public function havingMany(
90
        string $property,
91
        string $label,
92
        string $collectionClass = null
93
    ): DefinesObjectMapping {
94
        $hydrator = $collectionClass ?
95
            VariadicConstructor::forThe($collectionClass) :
96
            ArrayHydrator::create();
97
        $new = clone $this;
98
        $new->relation[$label] = HasMany::in($property, $hydrator);
99
        return $new;
100
    }
101
102
    /** @inheritdoc */
103
    public function identifying(
104
        string $label,
105
        string ...$columns
106
    ): DefinesObjectMapping {
107
        $new = clone $this;
108
        $new->identityColumnsFor[$label] = $columns;
109
        return $new;
110
    }
111
112
    /** @inheritdoc */
113
    public function label(): string
114
    {
115
        return $this->label;
116
    }
117
118
    /** @inheritdoc */
119
    public function identityColumns(): array
120
    {
121
        return $this->identityColumnsFor[$this->label];
122
    }
123
124
    /** @inheritdoc */
125
    public function objects(): MakesObjects
126
    {
127
        try {
128
            $hydrator = $this->hydratorFor($this->class, $this->properties);
129
        } catch (CannotInstantiateThis|InvalidMapperConfiguration $exception) {
130
            throw CannotProduceObjects::encountered($exception, $this->label);
131
        }
132
        return Objects::producedByThis(
133
            $hydrator,
134
            Prefixed::with($this->label),
135
            Identified::by(...$this->ownId)
136
        );
137
    }
138
139
    /** @inheritdoc */
140
    public function wiring(): WiresObjects
141
    {
142
        $ownLabel = $this->label;
143
        $ownId = $this->identityColumnsFor[$ownLabel];
144
        $wires = [];
145
        foreach ($this->relation as $otherLabel => $connectThem) {
146
            $this->mustKnowTheIdentityColumnsFor($otherLabel);
147
            $otherId = $this->identityColumnsFor[$otherLabel];
148
            $wires[] = Wire::it(
149
                From::the($ownLabel, Identified::by(...$ownId)),
150
                To::the($otherLabel, Identified::by(...$otherId)),
151
                $connectThem
152
            );
153
        }
154
        if (count($wires) === 1) {
155
            return $wires[0];
156
        }
157
        return Wired::together(...$wires);
158
    }
159
160
    /**
161
     * Prepends the identifying columns with a label.
162
     *
163
     * @param array $columns
164
     * @return array
165
     */
166
    private function mapThe(array $columns, string $prefix): array
167
    {
168
        return array_map(function(string $column) use ($prefix): string {
169
            return $prefix . $column;
170
        }, $columns);
171
    }
172
173
    /**
174
     * Produces a hydrator to prepare the objects.
175
     *
176
     * @param string $class
177
     * @param array  $properties
178
     *
179
     * @return Hydrates
180
     * @throws CannotInstantiateThis
181
     * @throws InvalidMapperConfiguration
182
     */
183
    private function hydratorFor(string $class, array $properties): Hydrates
184
    {
185
        if (empty($properties)) {
186
            return SimpleHydrator::forThe($class);
187
        }
188
        $mapper = Mapper::forThe($class);
189
        foreach ($properties as $name => $instruction) {
190
            $mapper = $mapper->property($name, $instruction);
191
        }
192
        return $mapper->finish();
193
    }
194
195
    /**
196
     * Checks that the columns that identify the label are known.
197
     *
198
     * @param string $label           The label we need to identify.
199
     * @throws CannotMakeTableMapping When the label cannot be identified.
200
     */
201
    private function mustKnowTheIdentityColumnsFor(string $label): void
202
    {
203
        if (!isset($this->identityColumnsFor[$label])) {
204
            throw CannotMakeMapping::missingTheIdentityColumns($label, $this->label);
205
        }
206
    }
207
}
208