Passed
Pull Request — 2.x (#539)
by Aleksei
15:43
created

BulkLoader::load()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 4
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\HeapInterface;
8
use Cycle\ORM\Heap\Node;
9
use Cycle\ORM\ORMInterface;
10
use Cycle\ORM\Reference\ReferenceInterface;
11
use Cycle\ORM\SchemaInterface;
12
use Cycle\ORM\Select\UpdateLoader;
13
use Cycle\ORM\Service\EntityFactoryInterface;
14
use Cycle\ORM\Service\SourceProviderInterface;
15
16
/**
17
 * @internal
18
 */
19
final class BulkLoader implements BulkLoaderInterface, RelationLoaderInterface
20
{
21
    /** @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...
22
    private array $entities = [];
23
24
    private UpdateLoader $loader;
25
    private array $index = [];
26
27
    public function __construct(
28
        private ORMInterface $orm,
29
    ) {}
30
31
    public function collect(object ...$entities): RelationLoaderInterface
32
    {
33
        if ($entities === []) {
34
            return $this;
35
        }
36
37
        $entities = \array_values($entities);
38
39
        // Validate entity roles
40
        $role = $this->orm->resolveRole($entities[0]);
41
        foreach ($entities as $entity) {
42
            // todo STI/JTI support
43
            if ($this->orm->resolveRole($entity) !== $role) {
44
                throw new \InvalidArgumentException('All entities must belong to the same role.');
45
            }
46
        }
47
48
        $clone = clone $this;
49
        $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...
50
        $clone->loader = new UpdateLoader(
51
            $this->orm->getSchema(),
52
            $this->orm->getService(SourceProviderInterface::class),
53
            $this->orm->getFactory(),
54
            $role,
55
        );
56
57
        return $clone;
58
    }
59
60
    public function load(string $relation, array $options = []): static
61
    {
62
        $this->loader->loadRelation($relation, $options, load: true);
63
        return $this;
64
    }
65
66
    public function run(): void
67
    {
68
        $role = $this->loader->getTarget();
69
        $mapper = $this->orm->getMapper($role);
70
        $node = $this->loader->createNode();
71
        $pk = (array) $this->orm->getSchema()->define($role, SchemaInterface::PRIMARY_KEY);
72
        $relMap = $this->orm->getRelationMap($role);
73
        $relations = $relMap->getRelations();
74
        $heap = $this->orm->getHeap();
75
        $factory = $this->orm->getService(EntityFactoryInterface::class);
76
77
        foreach ($this->entities as $entity) {
78
            $data = $mapper->uncast($mapper->fetchFields($entity));
79
            $this->indexEntity($heap, $pk, $data, $entity);
80
            $node->push($data);
81
            unset($data);
82
        }
83
84
        $this->loader->loadData($node, true);
85
        $result = $node->getResult();
86
87
        // Fill entities with loaded relations
88
        foreach ($result as $data) {
89
            [$entity, $node] = $this->getEntity($pk, $data);
90
            $fetched = $mapper->fetchRelations($entity);
91
92
            // Get not resolved (references) or not set relations
93
            $overwrite = [];
94
            foreach ($relations as $name => $_) {
95
                if (\array_key_exists($name, $data) && ($fetched[$name] ?? null) instanceof ReferenceInterface) {
96
                    $overwrite[$name] = $data[$name];
97
                }
98
            }
99
100
            if ($overwrite === []) {
101
                continue;
102
            }
103
104
            $mapper->hydrate($entity, $relMap->init($factory, $node, $mapper->cast($overwrite)));
105
        }
106
107
        $this->index = [];
108
    }
109
110
    /**
111
     * Index entity by provided keys and data.
112
     *
113
     * @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...
114
     * @param non-empty-array<non-empty-string> $data
115
     */
116
    public function indexEntity(HeapInterface $heap, array $keys, array $data, object $entity): void
117
    {
118
        $pool = &$this->index;
119
        foreach ($keys as $k) {
120
            $keyValue = $data[$k] ?? throw new \LogicException("Bulk loader cannot get the value for the key `$k`.");
121
            \is_scalar($keyValue) or throw new \InvalidArgumentException("Invalid value on the key `$k`.");
122
123
            \array_key_exists($keyValue, $pool) or $pool[$keyValue] = [];
124
            $pool = &$pool[$keyValue];
125
        }
126
127
128
        $pool = [
129
            $entity,
130
            $heap->get($entity) ?? throw new \LogicException("Entity node not found in the heap."),
131
        ];
132
    }
133
134
    /**
135
     * Fetch indexed entity by provided keys and data.
136
     *
137
     * @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...
138
     * @param non-empty-array<non-empty-string> $data
139
     *
140
     * @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...
141
     */
142
    private function getEntity(array $pk, array $data): array
143
    {
144
        $result = $this->index;
145
        foreach ($pk as $k) {
146
            $result = $result[$data[$k]] ?? throw new \LogicException('Cannot find indexed entity.');
147
        }
148
149
        return $result;
150
    }
151
}
152