Completed
Push — master ( 131c91...75b368 )
by Kévin
03:30
created

SubresourceOperationFactory   A

Complexity

Total Complexity 12

Size/Duplication

Total Lines 120
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
wmc 12
lcom 1
cbo 7
dl 0
loc 120
rs 10
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A create() 0 7 1
C computeSubresourceOperations() 0 82 10
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\Operation\Factory;
15
16
use ApiPlatform\Core\Bridge\Symfony\Routing\RouteNameGenerator;
17
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
18
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
19
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
20
use ApiPlatform\Core\Operation\PathSegmentNameGeneratorInterface;
21
22
/**
23
 * @internal
24
 */
25
final class SubresourceOperationFactory implements SubresourceOperationFactoryInterface
26
{
27
    const SUBRESOURCE_SUFFIX = '_subresource';
28
    const FORMAT_SUFFIX = '.{_format}';
29
30
    private $resourceMetadataFactory;
31
    private $propertyNameCollectionFactory;
32
    private $propertyMetadataFactory;
33
    private $pathSegmentNameGenerator;
34
35
    public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, PathSegmentNameGeneratorInterface $pathSegmentNameGenerator)
36
    {
37
        $this->resourceMetadataFactory = $resourceMetadataFactory;
38
        $this->propertyNameCollectionFactory = $propertyNameCollectionFactory;
39
        $this->propertyMetadataFactory = $propertyMetadataFactory;
40
        $this->pathSegmentNameGenerator = $pathSegmentNameGenerator;
41
    }
42
43
    /**
44
     * {@inheritdoc}
45
     */
46
    public function create(string $resourceClass): array
47
    {
48
        $tree = [];
49
        $this->computeSubresourceOperations($resourceClass, $tree);
50
51
        return $tree;
52
    }
53
54
    /**
55
     * Handles subresource operations recursively and declare their corresponding routes.
56
     *
57
     * @param string $resourceClass
58
     * @param array  $tree
59
     * @param string $rootResourceClass null on the first iteration, it then keeps track of the origin resource class
60
     * @param array  $parentOperation   the previous call operation
61
     */
62
    private function computeSubresourceOperations(string $resourceClass, array &$tree, string $rootResourceClass = null, array $parentOperation = null)
63
    {
64
        if (null === $rootResourceClass) {
65
            $rootResourceClass = $resourceClass;
66
        }
67
68
        foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $property) {
69
            $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $property);
70
71
            if (!$propertyMetadata->hasSubresource()) {
72
                continue;
73
            }
74
75
            $subresource = $propertyMetadata->getSubresource();
76
            $subresourceClass = $subresource->getResourceClass();
77
            $subresourceMetadata = $this->resourceMetadataFactory->create($subresourceClass);
78
79
            if (null === $parentOperation) {
80
                $visiting = "$rootResourceClass-$property-$subresourceClass";
81
            } else {
82
                $prefix = '';
83
                $visiting = "{$parentOperation['property']}-{$parentOperation['resource_class']}-$property-$subresourceClass";
84
85
                foreach ($parentOperation['identifiers'] as $key => list($param, $class)) {
86
                    $prefix .= 0 === $key ? $class : "-$param-$class";
87
                }
88
89
                if (false !== strpos($prefix, $visiting)) {
90
                    continue;
91
                }
92
93
                $visiting = $prefix.'-'.$visiting;
94
            }
95
96
            $operationName = 'get';
97
            $operation = [
98
                'property' => $property,
99
                'collection' => $subresource->isCollection(),
100
                'resource_class' => $subresourceClass,
101
                'shortNames' => [$subresourceMetadata->getShortName()],
102
            ];
103
104
            if (null === $parentOperation) {
105
                $rootResourceMetadata = $this->resourceMetadataFactory->create($rootResourceClass);
106
                $rootShortname = $rootResourceMetadata->getShortName();
107
                $operation['identifiers'] = [['id', $rootResourceClass, true]];
108
                $operation['route_name'] = sprintf(
109
                    '%s%s_%s_%s%s',
110
                    RouteNameGenerator::ROUTE_NAME_PREFIX,
111
                    RouteNameGenerator::inflector($rootShortname),
112
                    RouteNameGenerator::inflector($operation['property'], $operation['collection'] ?? false),
113
                    $operationName,
114
                    self::SUBRESOURCE_SUFFIX
115
                );
116
117
                $operation['path'] = sprintf(
118
                    '/%s/{id}/%s%s',
119
                    $this->pathSegmentNameGenerator->getSegmentName($rootShortname, true),
120
                    $this->pathSegmentNameGenerator->getSegmentName($operation['property'], $operation['collection']),
121
                    self::FORMAT_SUFFIX
122
                );
123
124
                $operation['shortNames'][] = $rootShortname;
125
            } else {
126
                $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
127
                $operation['identifiers'] = $parentOperation['identifiers'];
128
                $operation['identifiers'][] = [$parentOperation['property'], $resourceClass, $parentOperation['collection']];
129
                $operation['route_name'] = str_replace('get'.self::SUBRESOURCE_SUFFIX, RouteNameGenerator::inflector($property, $operation['collection']).'_get'.self::SUBRESOURCE_SUFFIX, $parentOperation['route_name']);
130
                $operation['shortNames'][] = $resourceMetadata->getShortName();
131
132
                $operation['path'] = str_replace(self::FORMAT_SUFFIX, '', $parentOperation['path']);
133
                if ($parentOperation['collection']) {
134
                    list($key) = end($operation['identifiers']);
135
                    $operation['path'] .= sprintf('/{%s}', $key);
136
                }
137
                $operation['path'] .= sprintf('/%s%s', $this->pathSegmentNameGenerator->getSegmentName($property, $operation['collection']), self::FORMAT_SUFFIX);
138
            }
139
140
            $tree[$visiting] = $operation;
141
            $this->computeSubresourceOperations($subresourceClass, $tree, $rootResourceClass, $operation);
142
        }
143
    }
144
}
145