DoctrineOrmEntityLoader   A
last analyzed

Complexity

Total Complexity 21

Size/Duplication

Total Lines 185
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 185
rs 10
c 0
b 0
f 0
wmc 21

7 Methods

Rating   Name   Duplication   Size   Complexity  
A setChunkSize() 0 3 1
A loadUninitializedProxies() 0 10 3
A __construct() 0 10 1
C loadAssociatedUninitializedCollectionsAndProxies() 0 58 9
A splitIntoChunks() 0 7 2
A getUninitializedProxies() 0 6 2
A getUninitializedCollectionOwnerEntities() 0 13 3
1
<?php
2
3
namespace Xsolve\Associate\Loader;
4
5
use Doctrine\Common\Collections\Collection;
6
use Doctrine\ORM\PersistentCollection;
7
use Doctrine\ORM\Proxy\Proxy;
8
use Xsolve\Associate\AssociationCollecting\AssociationCollectingStrategyInterface;
9
use Xsolve\Associate\Metadata\AssociationMetadataWrapper;
10
use Xsolve\Associate\Metadata\ClassMetadataWrapper;
11
use Xsolve\Associate\Metadata\MetadataWrapperProvider;
12
13
class DoctrineOrmEntityLoader
14
{
15
    /**
16
     * @var MetadataWrapperProvider
17
     */
18
    protected $metadataWrapperProvider;
19
20
    /**
21
     * @var AssociationCollectingStrategyInterface
22
     */
23
    protected $associationCollectingStrategy;
24
25
    /**
26
     * @var DoctrineOrmUninitializedProxiesQueryExecutor
27
     */
28
    protected $uninitializedProxiesQueryExecutor;
29
30
    /**
31
     * @var DoctrineOrmNonProxiedAssociationQueryExecutor
32
     */
33
    protected $nonProxiedAssociationQueryExecutor;
34
35
    /**
36
     * @var int|null
37
     */
38
    protected $chunkSize = 1000;
39
40
    /**
41
     * @param MetadataWrapperProvider                       $metadataWrapperProvider
42
     * @param AssociationCollectingStrategyInterface        $associationCollectingStrategy
43
     * @param DoctrineOrmUninitializedProxiesQueryExecutor  $uninitializedProxiesQueryExecutor
44
     * @param DoctrineOrmNonProxiedAssociationQueryExecutor $nonProxiedAssociationQueryExecutor
45
     */
46
    public function __construct(
47
        MetadataWrapperProvider $metadataWrapperProvider,
48
        AssociationCollectingStrategyInterface $associationCollectingStrategy,
49
        DoctrineOrmUninitializedProxiesQueryExecutor $uninitializedProxiesQueryExecutor,
50
        DoctrineOrmNonProxiedAssociationQueryExecutor $nonProxiedAssociationQueryExecutor
51
    ) {
52
        $this->metadataWrapperProvider = $metadataWrapperProvider;
53
        $this->associationCollectingStrategy = $associationCollectingStrategy;
54
        $this->uninitializedProxiesQueryExecutor = $uninitializedProxiesQueryExecutor;
55
        $this->nonProxiedAssociationQueryExecutor = $nonProxiedAssociationQueryExecutor;
56
    }
57
58
    /**
59
     * @param int|null $chunkSize
60
     */
61
    public function setChunkSize(int $chunkSize = null)
62
    {
63
        $this->chunkSize = $chunkSize;
64
    }
65
66
    /**
67
     * @param array                $entities
68
     * @param ClassMetadataWrapper $classMetadataWrapper
69
     */
70
    public function loadUninitializedProxies(array $entities, ClassMetadataWrapper $classMetadataWrapper)
71
    {
72
        $uninitializedProxies = $this->getUninitializedProxies($entities);
73
74
        if (empty($uninitializedProxies)) {
75
            return;
76
        }
77
78
        foreach ($this->splitIntoChunks($uninitializedProxies) as $uninitializedProxiesChunk) {
79
            $this->uninitializedProxiesQueryExecutor->execute($uninitializedProxiesChunk, $classMetadataWrapper);
80
        }
81
    }
82
83
    /**
84
     * @param array                      $entities
85
     * @param AssociationMetadataWrapper $associationMetadataWrapper
86
     *
87
     * @throws \Exception
88
     */
89
    public function loadAssociatedUninitializedCollectionsAndProxies(
90
        array $entities,
91
        AssociationMetadataWrapper $associationMetadataWrapper
92
    ) {
93
        // If we have to-one association and we are on the owning side
94
        // we can collect uninitialized proxies and bulk load them.
95
        if (
96
            $associationMetadataWrapper->isManyToOne()
97
            || (
98
                $associationMetadataWrapper->isOneToOne()
99
                && $associationMetadataWrapper->isOwningSide()
100
            )
101
        ) {
102
            $associatedEntities = $this->associationCollectingStrategy->collect(
103
                $entities,
104
                $associationMetadataWrapper->getName()
105
            );
106
            $this->loadUninitializedProxies(
107
                $associatedEntities,
108
                $associationMetadataWrapper->getTargetClassMetadataWrapper()
109
            );
110
111
            return;
112
        }
113
114
        // Otherwise we have some objects with uninitialized persistent collections.
115
        if (
116
            $associationMetadataWrapper->isOneToMany()
117
            || $associationMetadataWrapper->isManyToMany()
118
        ) {
119
            $associatedCollections = $this->associationCollectingStrategy->collect(
120
                $entities,
121
                $associationMetadataWrapper->getName()
122
            );
123
            $uninitializedSourceEntities = $this->getUninitializedCollectionOwnerEntities($associatedCollections);
124
125
            foreach ($this->splitIntoChunks($uninitializedSourceEntities) as $uninitializedSourceEntitiesChunk) {
126
                $this->nonProxiedAssociationQueryExecutor->execute(
127
                    $uninitializedSourceEntitiesChunk,
128
                    $associationMetadataWrapper
129
                );
130
            }
131
132
            return;
133
        }
134
135
        // Or we have one-to-one association with source being the inverse side.
136
        if (
137
            $associationMetadataWrapper->isOneToOne()
138
            && $associationMetadataWrapper->isInverseSide()
139
        ) {
140
            // We don't have to do anything as these objects are automatically loaded by Doctrine with separate queries
141
            // and there's no way to optimize this.
142
143
            return;
144
        }
145
146
        throw new \Exception('Association not handled.');
147
    }
148
149
    /**
150
     * @param array $entities
151
     *
152
     * @return Proxy[]
153
     */
154
    protected function getUninitializedProxies(array $entities): array
155
    {
156
        return array_filter(
157
            $entities,
158
            function ($entity) {
159
                return $entity instanceof Proxy && !$entity->__isInitialized();
160
            }
161
        );
162
    }
163
164
    /**
165
     * @param array $collections
166
     *
167
     * @return Proxy[]
168
     */
169
    protected function getUninitializedCollectionOwnerEntities(array $collections): array
170
    {
171
        return array_filter(
172
            array_map(
173
                function (Collection $collection) {
174
                    if (
175
                        $collection instanceof PersistentCollection
176
                        && !$collection->isInitialized()
177
                    ) {
178
                        return $collection->getOwner();
179
                    }
180
                },
181
                $collections
182
            )
183
        );
184
    }
185
186
    /**
187
     * @param array $items
188
     *
189
     * @return array
190
     */
191
    protected function splitIntoChunks(array $items): array
192
    {
193
        if (is_null($this->chunkSize)) {
194
            return $items;
195
        }
196
197
        return array_chunk($items, $this->chunkSize);
198
    }
199
}
200