Completed
Pull Request — master (#717)
by Antoine
06:27 queued 01:47
created

EagerLoadingExtension::joinRelations()   C

Complexity

Conditions 8
Paths 8

Size

Total Lines 35
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 35
rs 5.3846
cc 8
eloc 19
nc 8
nop 4
1
<?php
2
3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace ApiPlatform\Core\Bridge\Doctrine\Orm\Extension;
13
14
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
15
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
16
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
17
use Symfony\Component\PropertyInfo\Type;
18
use Doctrine\ORM\Mapping\ClassMetadataInfo;
19
use Doctrine\ORM\QueryBuilder;
20
21
/**
22
 * Eager loads relations.
23
 *
24
 * @author Charles Sarrazin <[email protected]>
25
 * @author Kévin Dunglas <[email protected]>
26
 */
27
final class EagerLoadingExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface
28
{
29
    private $propertyNameCollectionFactory;
30
    private $propertyMetadataFactory;
31
32
    public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory)
33
    {
34
        $this->propertyMetadataFactory = $propertyMetadataFactory;
35
        $this->propertyNameCollectionFactory = $propertyNameCollectionFactory;
36
    }
37
38
    /**
39
     * {@inheritdoc}
40
     */
41
    public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
42
    {
43
        $this->joinRelations($queryBuilder, $resourceClass);
44
    }
45
46
    /**
47
     * {@inheritdoc}
48
     */
49
    public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, string $operationName = null)
50
    {
51
        $this->joinRelations($queryBuilder, $resourceClass);
52
    }
53
54
    public function getMetadataProperties($resourceClass): array
55
    {
56
        $properties = [];
57
58
        foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $property) {
59
            $properties[$property] = $this->propertyMetadataFactory->create($resourceClass, $property);
60
        }
61
62
        return $properties;
63
    }
64
65
    /**
66
     * Left joins relations to eager load.
67
     *
68
     * @param QueryBuilder $queryBuilder
69
     * @param string       $resourceClass
70
     */
71
    private function joinRelations(QueryBuilder $queryBuilder, string $resourceClass, string $originAlias = 'o', string &$relationAlias = 'a')
72
    {
73
        $classMetadata = $queryBuilder->getEntityManager()->getClassMetadata($resourceClass);
74
        $j = 0;
75
76
        foreach ($classMetadata->getAssociationNames() as $i => $association) {
77
            $mapping = $classMetadata->associationMappings[$association];
78
            $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $association);
79
80
            if (ClassMetadataInfo::FETCH_EAGER !== $mapping['fetch'] || false === $propertyMetadata->isReadableLink()) {
81
                continue;
82
            }
83
84
            $method = false === $mapping['joinColumns'][0]['nullable'] ? 'innerJoin' : 'leftJoin';
85
86
            $associationAlias = $relationAlias.$i;
87
            $queryBuilder->{$method}($originAlias.'.'.$association, $associationAlias);
88
            $select = [];
89
            $targetClassMetadata = $queryBuilder->getEntityManager()->getClassMetadata($mapping['targetEntity']);
90
91
            //@TODO useless at the moment, but Doctrine queries non-necessary relations because addSelect($associationAlias) lazy fetch every association
92
            //By using this array of properties, it Lazy fetch association, which are initially in an INNER JOIN (fetches them twice ?!)
93
            //If I try to use associationAlias when it has every property, but the properties array when it doesn't it fails because it can not fetch parent association
94
            foreach ($this->getMetadataProperties($mapping['targetEntity']) as $property => $propertyMetadata) {
95
                if (true === $targetClassMetadata->hasField($property) && true === $propertyMetadata->isReadableLink()) {
96
                    $select[] = $associationAlias.'.'.$property;
97
                }
98
            }
99
100
            $queryBuilder->addSelect($associationAlias);
101
102
            $relationAlias = $relationAlias.++$j;
103
            $this->joinRelations($queryBuilder, $mapping['targetEntity'], $associationAlias, $relationAlias);
104
        }
105
    }
106
}
107