Passed
Pull Request — 2.x (#543)
by Aleksei
16:54
created

BulkLoader::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 0
nc 1
nop 1
dl 0
loc 3
rs 10
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Cycle\ORM\Relation;
6
7
use Cycle\ORM\Heap\Node;
8
use Cycle\ORM\ORMInterface;
9
use Cycle\ORM\Reference\ReferenceInterface;
10
use Cycle\ORM\SchemaInterface;
11
use Cycle\ORM\Select\UpdateLoader;
12
use Cycle\ORM\Service\EntityFactoryInterface;
13
use Cycle\ORM\Service\SourceProviderInterface;
14
15
/**
16
 * @internal
17
 */
18
final class BulkLoader implements BulkLoaderInterface, RelationLoaderInterface
19
{
20
    /** @var list<object> */
0 ignored issues
show
Bug introduced by
The type Cycle\ORM\Relation\list was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
21
    private array $entities = [];
22
23
    private UpdateLoader $loader;
24
    private array $index = [];
25
26
    public function __construct(
27
        private ORMInterface $orm,
28
    ) {}
29
30
    public function collect(object ...$entities): RelationLoaderInterface
31
    {
32
        if ($entities === []) {
33
            return new class implements RelationLoaderInterface {
34
                public function load(string $relation, array $options = []): static
35
                {
36
                    return $this;
37
                }
38
39
                public function run(): void {}
40
            };
41
        }
42
        $entities = \array_values($entities);
43
44
        // Validate entity roles
45
        $role = $this->orm->resolveRole($entities[0]);
46
        foreach ($entities as $entity) {
47
            // todo STI/JTI support
48
            if ($this->orm->resolveRole($entity) !== $role) {
49
                throw new \InvalidArgumentException('All entities must belong to the same role.');
50
            }
51
        }
52
53
        $clone = clone $this;
54
        $clone->entities = \array_merge($this->entities, $entities);
0 ignored issues
show
Documentation Bug introduced by
It seems like array_merge($this->entities, $entities) of type array is incompatible with the declared type Cycle\ORM\Relation\list of property $entities.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
55
        $clone->loader = new UpdateLoader(
56
            $this->orm->getSchema(),
57
            $this->orm->getService(SourceProviderInterface::class),
58
            $this->orm->getFactory(),
59
            $role,
60
        );
61
62
        return $clone;
63
    }
64
65
    public function load(string $relation, array $options = []): static
66
    {
67
        $this->loader->loadRelation($relation, $options, load: true);
68
        return $this;
69
    }
70
71
    public function run(): void
72
    {
73
        $role = $this->loader->getTarget();
74
        $mapper = $this->orm->getMapper($role);
75
        $node = $this->loader->createNode();
76
        $pk = (array) $this->orm->getSchema()->define($role, SchemaInterface::PRIMARY_KEY);
77
        $relMap = $this->orm->getRelationMap($role);
78
        $relations = $relMap->getRelations();
79
        $heap = $this->orm->getHeap();
80
        $factory = $this->orm->getService(EntityFactoryInterface::class);
81
82
        foreach ($this->entities as $entity) {
83
            $n = $heap->get($entity) ?? throw new \LogicException("Entity node not found in the heap.");
84
            // Use Node data to load relations instead of actual entity data
85
            // to avoid inconsistent state in the Heap
86
            $data = $n->getData();
87
            $this->indexEntity($n, $pk, $data, $entity);
88
            $node->push($data);
89
            unset($data);
90
        }
91
92
        $this->loader->loadData($node, true);
93
        $result = $node->getResult();
94
95
        // Fill entities with loaded relations
96
        foreach ($result as $data) {
97
            [$entity, $node] = $this->getEntity($pk, $data);
98
            $fetched = $mapper->fetchRelations($entity);
99
100
            // Get not resolved (references) or not set relations
101
            $overwrite = [];
102
            foreach ($relations as $name => $_) {
103
                if (\array_key_exists($name, $data) && ($fetched[$name] ?? null) instanceof ReferenceInterface) {
104
                    $overwrite[$name] = $data[$name];
105
                }
106
            }
107
108
            if ($overwrite === []) {
109
                continue;
110
            }
111
112
            $mapper->hydrate($entity, $relMap->init($factory, $node, $mapper->cast($overwrite)));
113
        }
114
115
        $this->index = [];
116
    }
117
118
    /**
119
     * Index entity by provided keys and data.
120
     *
121
     * @param non-empty-array<non-empty-string> $keys
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-array<non-empty-string> at position 0 could not be parsed: Unknown type name 'non-empty-array' at position 0 in non-empty-array<non-empty-string>.
Loading history...
122
     * @param non-empty-array<non-empty-string> $data
123
     */
124
    private function indexEntity(Node $node, array $keys, array $data, object $entity): void
125
    {
126
        $pool = &$this->index;
127
        foreach ($keys as $k) {
128
            $keyValue = $data[$k] ?? throw new \LogicException("Bulk loader cannot get the value for the key `$k`.");
129
            \is_scalar($keyValue) or throw new \InvalidArgumentException("Invalid value on the key `$k`.");
130
131
            \array_key_exists($keyValue, $pool) or $pool[$keyValue] = [];
132
            $pool = &$pool[$keyValue];
133
        }
134
135
136
        $pool = [$entity, $node];
137
    }
138
139
    /**
140
     * Fetch indexed entity by provided keys and data.
141
     *
142
     * @param non-empty-array<non-empty-string> $pk
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-array<non-empty-string> at position 0 could not be parsed: Unknown type name 'non-empty-array' at position 0 in non-empty-array<non-empty-string>.
Loading history...
143
     * @param non-empty-array<non-empty-string> $data
144
     *
145
     * @return array{object, Node} The indexed entity and its node
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{object, Node} at position 2 could not be parsed: Expected ':' at position 2, but found 'object'.
Loading history...
146
     */
147
    private function getEntity(array $pk, array $data): array
148
    {
149
        $result = $this->index;
150
        foreach ($pk as $k) {
151
            $result = $result[$data[$k]] ?? throw new \LogicException('Cannot find indexed entity.');
152
        }
153
154
        return $result;
155
    }
156
}
157