Passed
Pull Request — 2.x (#541)
by Aleksei
16:39
created

BelongsToMorphedLoader::applyCriteria()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 2
dl 0
loc 11
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Cycle\ORM\Select\Loader\Morphed;
6
7
use Cycle\Database\Query\SelectQuery;
8
use Cycle\Database\StatementInterface;
9
use Cycle\ORM\FactoryInterface;
10
use Cycle\ORM\Parser\AbstractNode;
11
use Cycle\ORM\Parser\ProxyNode;
12
use Cycle\ORM\Parser\SingularNode;
13
use Cycle\ORM\Relation;
14
use Cycle\ORM\SchemaInterface;
15
use Cycle\ORM\Select\AbstractLoader;
16
use Cycle\ORM\Select\LoaderInterface;
17
use Cycle\ORM\Select\ScopeInterface;
18
use Cycle\ORM\Select\Traits\ColumnsTrait;
19
use Cycle\ORM\Select\Traits\ScopeTrait;
20
use Cycle\ORM\Service\SourceProviderInterface;
21
22
/**
23
 * Creates an additional query constrain based on parent entity alias.
24
 */
25
final class BelongsToMorphedLoader extends AbstractLoader
26
{
27
    use ScopeTrait;
28
    use ColumnsTrait;
29
30
    /**
31
     * Loader that contains current loader
32
     */
33
    protected ?LoaderInterface $parent = null;
34
35
    protected array $options = [
36
        'load' => false,
37
        'scope' => true,
38
        'minify' => true,
39
    ];
40
41
    /** @var non-empty-string */
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
42
    private string $morphKey;
43
44
    /** @var array<non-empty-string> */
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<non-empty-string> at position 2 could not be parsed: Unknown type name 'non-empty-string' at position 2 in array<non-empty-string>.
Loading history...
45
    private array $innerKey;
46
47
    /** @var array<non-empty-string> */
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<non-empty-string> at position 2 could not be parsed: Unknown type name 'non-empty-string' at position 2 in array<non-empty-string>.
Loading history...
48
    private array $outerKey;
49
50
    /**
51
     * @param array<non-empty-string, mixed> $schema Relation schema
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<non-empty-string, mixed> at position 2 could not be parsed: Unknown type name 'non-empty-string' at position 2 in array<non-empty-string, mixed>.
Loading history...
52
     */
53
    public function __construct(
54
        SchemaInterface $ormSchema,
55
        SourceProviderInterface $sourceProvider,
56
        FactoryInterface $factory,
57
        array $schema,
58
    ) {
59
        $this->ormSchema = $ormSchema;
60
        $this->sourceProvider = $sourceProvider;
61
        $this->factory = $factory;
62
63
        $this->morphKey = $schema[Relation::MORPH_KEY];
64
        $this->innerKey = (array) $schema[Relation::INNER_KEY];
65
        $this->outerKey = (array) $schema[Relation::OUTER_KEY];
66
    }
67
68
    public function getAlias(): string
69
    {
70
        return $this->target ?? throw new \RuntimeException('Target role is not defined yet.');
71
    }
72
73
    public function getTarget(): string
74
    {
75
        return $this->target ?? throw new \RuntimeException('Target role is not defined yet.');
76
    }
77
78
    public function initNode(): AbstractNode
79
    {
80
        return new ProxyNode([$this->morphKey, ...$this->innerKey]);
81
    }
82
83
    public function loadData(AbstractNode $node, bool $includeRole = false): void
84
    {
85
        // Get references from the parent node
86
        $references = $node->getReferenceValues();
87
88
        // Group references by their role (morph type)
89
        $groupedByRole = $this->groupReferencesByRole($references);
90
91
        // Load data for each role
92
        foreach ($groupedByRole as $role => $roleReferences) {
93
            $this->loadRoleData($node, $role, $roleReferences);
94
        }
95
    }
96
97
    public function isLoaded(): bool
98
    {
99
        // This loader is always loaded
100
        return true;
101
    }
102
103
    private function applyCriteria(SelectQuery $query, array $criteria): SelectQuery
104
    {
105
        // Map criteria to inner keys
106
        $where = [];
107
        foreach ($this->innerKey as $i => $key) {
108
            $where[$this->outerKey[$i]] = $criteria[$key];
109
        }
110
111
        $query->where(\key($where), \reset($where));
112
113
        return $query;
114
    }
115
116
    /**
117
     * Group references by their morph role.
118
     *
119
     * @return array<non-empty-string, array<non-empty-string, mixed>>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<non-empty-string, ...n-empty-string, mixed>> at position 2 could not be parsed: Unknown type name 'non-empty-string' at position 2 in array<non-empty-string, array<non-empty-string, mixed>>.
Loading history...
120
     */
121
    private function groupReferencesByRole(array $references): array
122
    {
123
        $grouped = [];
124
        foreach ($references as $ref) {
125
            $role = $ref[$this->morphKey];
126
            unset($ref[$this->morphKey]);
127
            $grouped[$role] = $ref;
128
        }
129
130
        return $grouped;
131
    }
132
133
    /**
134
     * Load data for a specific role.
135
     *
136
     * @param non-empty-string $role
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
137
     */
138
    private function loadRoleData(
139
        AbstractNode $node,
140
        string $role,
141
        array $references,
142
    ): void {
143
        $self = $this->cloneForRole($role);
144
        $newNode = new SingularNode(
145
            columns: \array_keys($self->columns),
146
            primaryKeys: (array) $self->ormSchema->define($role, SchemaInterface::PRIMARY_KEY),
147
            innerKeys: $self->outerKey,
148
            outerKeys: [$self->morphKey, ...$self->innerKey],
149
            role: $role,
150
        );
151
152
        // Register this role in the morphed node
153
        $roleNode = $node->addNode($role, $newNode);
0 ignored issues
show
Bug introduced by
The method addNode() does not exist on Cycle\ORM\Parser\AbstractNode. It seems like you code against a sub-type of Cycle\ORM\Parser\AbstractNode such as Cycle\ORM\Parser\ProxyNode. ( Ignorable by Annotation )

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

153
        /** @scrutinizer ignore-call */ 
154
        $roleNode = $node->addNode($role, $newNode);
Loading history...
154
155
        // Build Query
156
        $query = $self->source->getDatabase()->select()->from(
157
            \sprintf('%s AS %s', $self->source->getTable(), $self->getAlias()),
158
        );
159
160
        // Scopes
161
        $self->scope = match (true) {
162
            $self->options['scope'] === true => $self->getSource()->getScope(),
163
            $self->options['scope'] instanceof ScopeInterface => $self->options['scope'],
164
            \is_string($self->options['scope']) => $self->factory->make($self->options['scope']),
165
            default => null,
166
        };
167
168
        // Configure WHERE IN condition
169
        $self->applyCriteria($query, $references);
170
        $self->mountColumns($query, $self->options['minify'], '', true);
171
        $self->configureQuery($query);
172
173
        // Execute query
174
        $statement = $query->run();
175
176
        // Parse fetched rows into the ROLE-SPECIFIC node
177
        foreach ($statement->fetchAll(StatementInterface::FETCH_NUM) as $row) {
178
            $roleNode->parseRow(0, $row);
179
        }
180
181
        $statement->close();
182
    }
183
184
    /**
185
     * @param non-empty-string $role
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
186
     */
187
    private function cloneForRole(string $role): self
188
    {
189
        $self = clone $this;
190
        $self->target = $role;
191
        $self->children = $self->ormSchema->getInheritedRoles($role);
192
        $self->source = $self->sourceProvider->getSource($role);
193
        $self->columns = $self->normalizeColumns($self->ormSchema->define($role, SchemaInterface::COLUMNS));
194
195
        return $self;
196
    }
197
}
198