Completed
Pull Request — master (#717)
by Antoine
03:25
created

EagerLoadingExtension::applyToCollection()   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
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
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
        $properties = [];
56
57
        foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $property) {
58
            $properties[$property] = $this->propertyMetadataFactory->create($resourceClass, $property);
59
        }
60
61
        return $properties;
62
    }
63
64
    /**
65
     * Left joins relations to eager load.
66
     *
67
     * @param QueryBuilder $queryBuilder
68
     * @param string       $resourceClass
69
     */
70
    private function joinRelations(QueryBuilder $queryBuilder, string $resourceClass, string $originAlias = 'o', string &$relationAlias = 'a')
71
    {
72
        $classMetadata = $queryBuilder->getEntityManager()->getClassMetadata($resourceClass);
73
        $j = 0;
74
75
        foreach ($classMetadata->getAssociationNames() as $i => $association) {
76
            $mapping = $classMetadata->associationMappings[$association];
77
            $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $association);
78
79
            if (ClassMetadataInfo::FETCH_EAGER !== $mapping['fetch'] || false === $propertyMetadata->isReadableLink()) {
80
                continue;
81
            }
82
83
            $method = false === $mapping['joinColumns'][0]['nullable'] ? 'innerJoin' : 'leftJoin';
84
85
            $associationAlias = $relationAlias.$i;
86
            $queryBuilder->{$method}($originAlias.'.'.$association, $associationAlias);
87
            $select = [];
88
            $targetClassMetadata = $queryBuilder->getEntityManager()->getClassMetadata($mapping['targetEntity']);
89
90
            //@TODO useless at the moment, but Doctrine queries non-necessary relations because addSelect($associationAlias) lazy fetch every association
91
            //By using this array of properties, it Lazy fetch association, which are initially in an INNER JOIN (fetches them twice ?!)
92
            //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
93
            foreach ($this->getMetadataProperties($mapping['targetEntity']) as $property => $propertyMetadata) {
94
                if (true === $targetClassMetadata->hasField($property) && true === $propertyMetadata->isReadableLink()) {
95
                    $select[] = $associationAlias.'.'.$property;
96
                }
97
            }
98
99
            $queryBuilder->addSelect($associationAlias);
100
101
            $relationAlias = $relationAlias.++$j;
102
            $this->joinRelations($queryBuilder, $mapping['targetEntity'], $associationAlias, $relationAlias);
103
        }
104
    }
105
}
106