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