Passed
Pull Request — master (#2144)
by Alan
03:26
created

SubresourceDataProvider::buildAggregation()   B

Complexity

Conditions 10
Paths 15

Size

Total Lines 62
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 35
nc 15
nop 5
dl 0
loc 62
rs 7.6666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
declare(strict_types=1);
13
14
namespace ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm;
15
16
use ApiPlatform\Core\Bridge\Doctrine\Common\Util\IdentifierManagerTrait;
17
use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationCollectionExtensionInterface;
18
use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationItemExtensionInterface;
19
use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationResultCollectionExtensionInterface;
20
use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationResultItemExtensionInterface;
21
use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface;
22
use ApiPlatform\Core\Exception\ResourceClassNotSupportedException;
23
use ApiPlatform\Core\Exception\RuntimeException;
24
use ApiPlatform\Core\Identifier\IdentifierConverterInterface;
25
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
26
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
27
use Doctrine\Common\Persistence\ManagerRegistry;
28
use Doctrine\ODM\MongoDB\Aggregation\Builder;
29
use Doctrine\ODM\MongoDB\DocumentManager;
30
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
31
32
/**
33
 * Subresource data provider for the Doctrine MongoDB ODM.
34
 *
35
 * @experimental
36
 *
37
 * @author Antoine Bluchet <[email protected]>
38
 * @author Alan Poulain <[email protected]>
39
 */
40
final class SubresourceDataProvider implements SubresourceDataProviderInterface
41
{
42
    use IdentifierManagerTrait;
43
44
    private $managerRegistry;
45
    private $collectionExtensions;
46
    private $itemExtensions;
47
48
    /**
49
     * @param AggregationCollectionExtensionInterface[] $collectionExtensions
50
     * @param AggregationItemExtensionInterface[]       $itemExtensions
51
     */
52
    public function __construct(ManagerRegistry $managerRegistry, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, iterable $collectionExtensions = [], iterable $itemExtensions = [])
53
    {
54
        $this->managerRegistry = $managerRegistry;
55
        $this->propertyNameCollectionFactory = $propertyNameCollectionFactory;
56
        $this->propertyMetadataFactory = $propertyMetadataFactory;
57
        $this->collectionExtensions = $collectionExtensions;
58
        $this->itemExtensions = $itemExtensions;
59
    }
60
61
    /**
62
     * {@inheritdoc}
63
     *
64
     * @throws RuntimeException
65
     */
66
    public function getSubresource(string $resourceClass, array $identifiers, array $context, string $operationName = null)
67
    {
68
        $manager = $this->managerRegistry->getManagerForClass($resourceClass);
69
        if (null === $manager) {
70
            throw new ResourceClassNotSupportedException(sprintf('The object manager associated with the "%s" resource class cannot be retrieved.', $resourceClass));
71
        }
72
73
        $repository = $manager->getRepository($resourceClass);
74
        if (!method_exists($repository, 'createAggregationBuilder')) {
75
            throw new RuntimeException('The repository class must have a "createAggregationBuilder" method.');
76
        }
77
78
        if (!isset($context['identifiers'], $context['property'])) {
79
            throw new ResourceClassNotSupportedException('The given resource class is not a subresource.');
80
        }
81
82
        $aggregationBuilder = $this->buildAggregation($identifiers, $context, $repository->createAggregationBuilder(), \count($context['identifiers']));
83
84
        if (true === $context['collection']) {
85
            foreach ($this->collectionExtensions as $extension) {
86
                $extension->applyToCollection($aggregationBuilder, $resourceClass, $operationName, $context);
87
                if ($extension instanceof AggregationResultCollectionExtensionInterface && $extension->supportsResult($resourceClass, $operationName, $context)) {
88
                    return $extension->getResult($aggregationBuilder, $resourceClass, $operationName, $context);
89
                }
90
            }
91
        } else {
92
            foreach ($this->itemExtensions as $extension) {
93
                $extension->applyToItem($aggregationBuilder, $resourceClass, $identifiers, $operationName, $context);
94
                if ($extension instanceof AggregationResultItemExtensionInterface && $extension->supportsResult($resourceClass, $operationName, $context)) {
95
                    return $extension->getResult($aggregationBuilder, $resourceClass, $operationName, $context);
96
                }
97
            }
98
        }
99
100
        $iterator = $aggregationBuilder->hydrate($resourceClass)->execute();
101
102
        return $context['collection'] ? $iterator->toArray() : ($iterator->current() ?: null);
103
    }
104
105
    /**
106
     * @throws RuntimeException
107
     */
108
    private function buildAggregation(array $identifiers, array $context, Builder $previousAggregationBuilder, int $remainingIdentifiers, Builder $topAggregationBuilder = null): Builder
109
    {
110
        if ($remainingIdentifiers <= 0) {
111
            return $previousAggregationBuilder;
112
        }
113
114
        $topAggregationBuilder = $topAggregationBuilder ?? $previousAggregationBuilder;
115
116
        [$identifier, $identifierResourceClass] = $context['identifiers'][$remainingIdentifiers - 1];
117
        $previousAssociationProperty = $context['identifiers'][$remainingIdentifiers][0] ?? $context['property'];
118
119
        $manager = $this->managerRegistry->getManagerForClass($identifierResourceClass);
120
121
        if (!$manager instanceof DocumentManager) {
122
            throw new RuntimeException("The manager for $identifierResourceClass must be a DocumentManager.");
123
        }
124
125
        $classMetadata = $manager->getClassMetadata($identifierResourceClass);
126
127
        if (!$classMetadata instanceof ClassMetadata) {
0 ignored issues
show
introduced by
$classMetadata is always a sub-type of Doctrine\ODM\MongoDB\Mapping\ClassMetadata.
Loading history...
128
            throw new RuntimeException(
129
                "The class metadata for $identifierResourceClass must be an instance of ClassMetadata."
130
            );
131
        }
132
133
        $aggregation = $manager->createAggregationBuilder($identifierResourceClass);
134
        $normalizedIdentifiers = [];
135
136
        if (isset($identifiers[$identifier])) {
137
            // if it's an array it's already normalized, the IdentifierManagerTrait is deprecated
138
            if ($context[IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER] ?? false) {
139
                $normalizedIdentifiers = $identifiers[$identifier];
140
            } else {
141
                $normalizedIdentifiers = $this->normalizeIdentifiers($identifiers[$identifier], $manager, $identifierResourceClass);
142
            }
143
        }
144
145
        if ($classMetadata->hasAssociation($previousAssociationProperty)) {
146
            $aggregation->lookup($previousAssociationProperty)->alias($previousAssociationProperty);
147
            foreach ($normalizedIdentifiers as $key => $value) {
148
                $aggregation->match()->field($key)->equals($value);
149
            }
150
        } elseif ($classMetadata->isIdentifier($previousAssociationProperty)) {
151
            foreach ($normalizedIdentifiers as $key => $value) {
152
                $aggregation->match()->field($key)->equals($value);
153
            }
154
155
            return $aggregation;
156
        }
157
158
        // Recurse aggregations
159
        $aggregation = $this->buildAggregation($identifiers, $context, $aggregation, --$remainingIdentifiers, $topAggregationBuilder);
160
161
        $results = $aggregation->execute()->toArray();
162
        $in = array_reduce($results, function ($in, $result) use ($previousAssociationProperty) {
163
            return $in + array_map(function ($result) {
164
                return $result['_id'];
165
            }, $result[$previousAssociationProperty] ?? []);
166
        }, []);
167
        $previousAggregationBuilder->match()->field('_id')->in($in);
168
169
        return $previousAggregationBuilder;
170
    }
171
}
172