Passed
Pull Request — 2.x (#539)
by Aleksei
17:25
created

BulkLoader::run()   B

Complexity

Conditions 7
Paths 14

Size

Total Lines 45
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

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