Completed
Push — master ( fbf6e8...36b98c )
by Kévin
13s
created

EagerLoadingExtension::getMetadataProperties()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 10
rs 9.4285
cc 2
eloc 5
nc 2
nop 1
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 Doctrine\ORM\Mapping\ClassMetadataInfo;
18
use Doctrine\ORM\QueryBuilder;
19
20
/**
21
 * Eager loads relations.
22
 *
23
 * @author Charles Sarrazin <[email protected]>
24
 * @author Kévin Dunglas <[email protected]>
25
 */
26
final class EagerLoadingExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface
27
{
28
    private $propertyNameCollectionFactory;
29
    private $propertyMetadataFactory;
30
31
    public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory)
32
    {
33
        $this->propertyMetadataFactory = $propertyMetadataFactory;
34
        $this->propertyNameCollectionFactory = $propertyNameCollectionFactory;
35
    }
36
37
    /**
38
     * {@inheritdoc}
39
     */
40
    public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
41
    {
42
        $this->joinRelations($queryBuilder, $resourceClass);
43
    }
44
45
    /**
46
     * {@inheritdoc}
47
     */
48
    public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, string $operationName = null)
49
    {
50
        $this->joinRelations($queryBuilder, $resourceClass);
51
    }
52
53
    public function getMetadataProperties(string $resourceClass): array
54
    {
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
            foreach ($this->getMetadataProperties($mapping['targetEntity']) as $property => $propertyMetadata) {
91
                if (true === $propertyMetadata->isIdentifier()) {
92
                    $select[] = $property;
93
                    continue;
94
                }
95
96
                //the field test allows to add methods to a Resource which do not reflect real database fields
97
                if (true === $targetClassMetadata->hasField($property) && true === $propertyMetadata->isReadable()) {
98
                    $select[] = $property;
99
                }
100
            }
101
102
            $queryBuilder->addSelect(sprintf('partial %s.{%s}', $associationAlias, implode(',', $select)));
103
104
            $relationAlias = $relationAlias.++$j;
105
            $this->joinRelations($queryBuilder, $mapping['targetEntity'], $associationAlias, $relationAlias);
106
        }
107
    }
108
}
109