Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
| 1 | <?php |
||
| 36 | final class ApiLoader extends Loader |
||
| 37 | { |
||
| 38 | const ROUTE_NAME_PREFIX = 'api_'; |
||
| 39 | const DEFAULT_ACTION_PATTERN = 'api_platform.action.'; |
||
| 40 | const SUBRESOURCE_SUFFIX = '_get_subresource'; |
||
| 41 | |||
| 42 | private $fileLoader; |
||
| 43 | private $propertyNameCollectionFactory; |
||
| 44 | private $propertyMetadataFactory; |
||
| 45 | private $resourceNameCollectionFactory; |
||
| 46 | private $resourceMetadataFactory; |
||
| 47 | private $operationPathResolver; |
||
| 48 | private $container; |
||
| 49 | private $formats; |
||
| 50 | |||
| 51 | public function __construct(KernelInterface $kernel, ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, OperationPathResolverInterface $operationPathResolver, ContainerInterface $container, array $formats, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory = null, PropertyMetadataFactoryInterface $propertyMetadataFactory = null) |
||
| 66 | |||
| 67 | /** |
||
| 68 | * {@inheritdoc} |
||
| 69 | */ |
||
| 70 | public function load($data, $type = null) |
||
| 101 | |||
| 102 | /** |
||
| 103 | * Transforms a given string to a tableized, pluralized string. |
||
| 104 | * |
||
| 105 | * @param string $name usually a ResourceMetadata shortname |
||
| 106 | * |
||
| 107 | * @return string A string that is a part of the route name |
||
| 108 | */ |
||
| 109 | private function routeNameResolver(string $name, bool $pluralize = true): string |
||
| 115 | |||
| 116 | /** |
||
| 117 | * Handles subresource operations recursively and declare their corresponding routes. |
||
| 118 | * |
||
| 119 | * @param RouteCollection $routeCollection |
||
| 120 | * @param string $resourceClass |
||
| 121 | * @param string $rootResourceClass null on the first iteration, it then keeps track of the origin resource class |
||
| 122 | * @param string $rootResourceClass null on the first iteration, it then keeps track of the parent operation |
||
| 123 | */ |
||
| 124 | private function computeSubresourceOperations(RouteCollection $routeCollection, string $resourceClass, $rootResourceClass = null, $parentOperation = null) |
||
| 125 | { |
||
| 126 | if (null === $this->propertyNameCollectionFactory || null === $this->propertyMetadataFactory) { |
||
| 127 | return; |
||
| 128 | } |
||
| 129 | |||
| 130 | if (null === $rootResourceClass) { |
||
| 131 | $rootResourceClass = $resourceClass; |
||
| 132 | } |
||
| 133 | |||
| 134 | foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $property) { |
||
| 135 | $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $property); |
||
| 136 | |||
| 137 | if (!$propertyMetadata->hasSubresource()) { |
||
| 138 | continue; |
||
| 139 | } |
||
| 140 | |||
| 141 | $isCollection = $propertyMetadata->getType()->isCollection(); |
||
| 142 | $subresource = $isCollection ? $propertyMetadata->getType()->getCollectionValueType()->getClassName() : $propertyMetadata->getType()->getClassName(); |
||
| 143 | |||
| 144 | $propertyName = $this->routeNameResolver($property, $isCollection); |
||
| 145 | |||
| 146 | $operation = [ |
||
| 147 | 'property' => $property, |
||
| 148 | 'collection' => $isCollection, |
||
| 149 | ]; |
||
| 150 | |||
| 151 | if (null === $parentOperation) { |
||
| 152 | $rootResourceMetadata = $this->resourceMetadataFactory->create($rootResourceClass); |
||
| 153 | $rootShortname = $rootResourceMetadata->getShortName(); |
||
| 154 | $resourceRouteName = $this->routeNameResolver($rootShortname); |
||
| 155 | |||
| 156 | $operation['identifiers'] = [['id', $rootResourceClass]]; |
||
| 157 | $operation['route_name'] = sprintf('%s%s_%s%s', self::ROUTE_NAME_PREFIX, $resourceRouteName, $propertyName, self::SUBRESOURCE_SUFFIX); |
||
| 158 | |||
| 159 | $operation['path'] = $this->operationPathResolver->resolveOperationPath($rootShortname, $operation, OperationType::SUBRESOURCE); |
||
| 160 | } else { |
||
| 161 | $identifier = $parentOperation['property']; |
||
| 162 | |||
| 163 | $operation['identifiers'] = $parentOperation['identifiers']; |
||
| 164 | $operation['identifiers'][] = [$identifier, $resourceClass]; |
||
| 165 | $operation['route_name'] = str_replace(self::SUBRESOURCE_SUFFIX, "_$propertyName".self::SUBRESOURCE_SUFFIX, $parentOperation['route_name']); |
||
| 166 | |||
| 167 | $operation['path'] = $this->operationPathResolver->resolveOperationPath($parentOperation['path'], $operation, OperationType::SUBRESOURCE); |
||
| 168 | } |
||
| 169 | |||
| 170 | $route = new Route( |
||
| 171 | $operation['path'], |
||
| 172 | [ |
||
| 173 | '_controller' => self::DEFAULT_ACTION_PATTERN.'get_subresource', |
||
| 174 | '_format' => null, |
||
| 175 | '_api_resource_class' => $subresource, |
||
| 176 | '_api_subresource_operation_name' => 'get', |
||
| 177 | '_api_subresource_context' => [ |
||
| 178 | 'property' => $operation['property'], |
||
| 179 | 'identifiers' => $operation['identifiers'], |
||
| 180 | 'collection' => $isCollection, |
||
| 181 | ], |
||
| 182 | ], |
||
| 183 | [], |
||
| 184 | [], |
||
| 185 | '', |
||
| 186 | [], |
||
| 187 | ['GET'] |
||
| 188 | ); |
||
| 189 | |||
| 190 | $routeCollection->add($operation['route_name'], $route); |
||
| 191 | |||
| 192 | $this->computeSubresourceOperations($routeCollection, $subresource, $rootResourceClass, $operation); |
||
| 193 | } |
||
| 194 | } |
||
| 195 | |||
| 196 | /** |
||
| 197 | * {@inheritdoc} |
||
| 198 | */ |
||
| 199 | public function supports($resource, $type = null) |
||
| 203 | |||
| 204 | /** |
||
| 205 | * Load external files. |
||
| 206 | * |
||
| 207 | * @param RouteCollection $routeCollection |
||
| 208 | */ |
||
| 209 | private function loadExternalFiles(RouteCollection $routeCollection) |
||
| 217 | |||
| 218 | /** |
||
| 219 | * Creates and adds a route for the given operation to the route collection. |
||
| 220 | * |
||
| 221 | * @param RouteCollection $routeCollection |
||
| 222 | * @param string $resourceClass |
||
| 223 | * @param string $operationName |
||
| 224 | * @param array $operation |
||
| 225 | * @param string $resourceShortName |
||
| 226 | * @param OperationType $operationType |
||
| 227 | * |
||
| 228 | * @throws RuntimeException |
||
| 229 | */ |
||
| 230 | private function addRoute(RouteCollection $routeCollection, string $resourceClass, string $operationName, array $operation, string $resourceShortName, string $operationType) |
||
| 277 | } |
||
| 278 |
If you suppress an error, we recommend checking for the error condition explicitly: