RestDocHandler::setDescription()   F
last analyzed

Complexity

Conditions 12
Paths 320

Size

Total Lines 39
Code Lines 25

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 39
rs 3.7956
cc 12
eloc 25
nc 320
nop 4

How to fix   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
namespace Oro\Bundle\ApiBundle\Routing;
4
5
use Symfony\Component\Routing\Route;
6
7
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
8
use Nelmio\ApiDocBundle\Extractor\HandlerInterface;
9
10
use Oro\Bundle\ApiBundle\Config\EntityDefinitionConfig;
11
use Oro\Bundle\ApiBundle\Config\DescriptionsConfigExtra;
12
use Oro\Bundle\ApiBundle\Config\FiltersConfigExtra;
13
use Oro\Bundle\ApiBundle\Config\SortersConfigExtra;
14
use Oro\Bundle\ApiBundle\Config\StatusCodesConfig;
15
use Oro\Bundle\ApiBundle\Config\StatusCodesConfigExtra;
16
use Oro\Bundle\ApiBundle\Filter\FilterCollection;
17
use Oro\Bundle\ApiBundle\Filter\StandaloneFilter;
18
use Oro\Bundle\ApiBundle\Processor\ActionProcessorBagInterface;
19
use Oro\Bundle\ApiBundle\Processor\Context;
20
use Oro\Bundle\ApiBundle\Request\DataType;
21
use Oro\Bundle\ApiBundle\Request\RequestType;
22
use Oro\Bundle\ApiBundle\Request\ValueNormalizer;
23
use Oro\Bundle\ApiBundle\Util\DoctrineHelper;
24
use Oro\Bundle\EntityBundle\Provider\EntityClassNameProviderInterface;
25
26
class RestDocHandler implements HandlerInterface
27
{
28
    const ID_DESCRIPTION     = 'The identifier of an entity';
29
    const FORMAT_DESCRIPTION = 'The response format';
30
31
    protected $templates = [
32
        'get'      => [
33
            'description'          => 'Get {name}',
34
            'fallback_description' => 'Get {class}',
35
            'get_name_method'      => 'getEntityClassName',
36
            'description_key'      => EntityDefinitionConfig::LABEL,
37
            'documentation_key'    => EntityDefinitionConfig::DESCRIPTION
38
        ],
39
        'get_list' => [
40
            'description'          => 'Get {name}',
41
            'fallback_description' => 'Get a list of {class}',
42
            'get_name_method'      => 'getEntityClassPluralName',
43
            'description_key'      => EntityDefinitionConfig::PLURAL_LABEL,
44
            'documentation_key'    => EntityDefinitionConfig::DESCRIPTION
45
        ],
46
        'delete' => [
47
            'description'          => 'Delete {name}',
48
            'fallback_description' => 'Delete an record of {class}',
49
            'get_name_method'      => 'getEntityClassName',
50
            'description_key'      => EntityDefinitionConfig::LABEL,
51
            'documentation_key'    => EntityDefinitionConfig::DESCRIPTION
52
        ],
53
        'delete_list' => [
54
            'description'          => 'Delete {name}',
55
            'fallback_description' => 'Delete a list of {class}',
56
            'get_name_method'      => 'getEntityClassName',
57
            'description_key'      => EntityDefinitionConfig::PLURAL_LABEL,
58
            'documentation_key'    => EntityDefinitionConfig::DESCRIPTION
59
        ],
60
    ];
61
62
    /** @var RestDocViewDetector */
63
    protected $docViewDetector;
64
65
    /** @var ActionProcessorBagInterface */
66
    protected $processorBag;
67
68
    /** @var EntityClassNameProviderInterface */
69
    protected $entityClassNameProvider;
70
71
    /** @var DoctrineHelper */
72
    protected $doctrineHelper;
73
74
    /** @var ValueNormalizer */
75
    protected $valueNormalizer;
76
77
    /** @var RequestType */
78
    protected $requestType;
79
80
    /**
81
     * @param RestDocViewDetector              $docViewDetector
82
     * @param ActionProcessorBagInterface      $processorBag
83
     * @param EntityClassNameProviderInterface $entityClassNameProvider
84
     * @param DoctrineHelper                   $doctrineHelper
85
     * @param ValueNormalizer                  $valueNormalizer
86
     */
87
    public function __construct(
88
        RestDocViewDetector $docViewDetector,
89
        ActionProcessorBagInterface $processorBag,
90
        EntityClassNameProviderInterface $entityClassNameProvider,
91
        DoctrineHelper $doctrineHelper,
92
        ValueNormalizer $valueNormalizer
93
    ) {
94
        $this->docViewDetector         = $docViewDetector;
95
        $this->processorBag            = $processorBag;
96
        $this->entityClassNameProvider = $entityClassNameProvider;
97
        $this->doctrineHelper          = $doctrineHelper;
98
        $this->valueNormalizer         = $valueNormalizer;
99
        $this->requestType             = new RequestType([RequestType::REST, RequestType::JSON_API]);
100
    }
101
102
    /**
103
     * {@inheritdoc}
104
     */
105
    public function handle(ApiDoc $annotation, array $annotations, Route $route, \ReflectionMethod $method)
106
    {
107
        if ($route->getOption('group') !== RestRouteOptionsResolver::ROUTE_GROUP) {
108
            return;
109
        }
110
        $action = $route->getDefault('_action');
111
        if (empty($action)) {
112
            return;
113
        }
114
115
        $entityType = $this->getEntityType($route);
116
        if ($entityType) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $entityType of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
117
            $entityClass = $this->getEntityClass($entityType);
118
            $config = $this->getConfig($action, $entityClass);
119
            $statusCodes = $config->getConfig()->getStatusCodes();
120
            $config->getConfig()->setStatusCodes();
121
122
            $annotation->setSection($entityType);
123
            $this->setDescription($annotation, $action, $config->getConfig()->toArray(), $entityClass);
124
            if ($statusCodes) {
125
                $this->setStatusCodes($annotation, $statusCodes);
126
            }
127
            if ($this->hasAttribute($route, RestRouteOptionsResolver::ID_PLACEHOLDER)) {
128
                $this->addIdRequirement(
129
                    $annotation,
130
                    $entityClass,
131
                    $route->getRequirement(RestRouteOptionsResolver::ID_ATTRIBUTE)
132
                );
133
            }
134
            if ($config->hasConfigExtra(FiltersConfigExtra::NAME) && method_exists($config, 'getFilters')) {
135
                $this->addFilters($annotation, $config->getFilters());
136
            }
137
        }
138
        $formatRequirement = $route->getRequirement(RestRouteOptionsResolver::FORMAT_ATTRIBUTE);
139
        if ($formatRequirement) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $formatRequirement of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
140
            $this->addFormatRequirement($annotation, $formatRequirement);
141
        }
142
    }
143
144
    /**
145
     * @param Route $route
146
     *
147
     * @return string|null
148
     */
149
    protected function getEntityType(Route $route)
150
    {
151
        return $route->getDefault(RestRouteOptionsResolver::ENTITY_ATTRIBUTE);
152
    }
153
154
    /**
155
     * @param string $entityType
156
     *
157
     * @return string
158
     */
159
    protected function getEntityClass($entityType)
160
    {
161
        return $this->valueNormalizer->normalizeValue(
162
            $entityType,
163
            DataType::ENTITY_CLASS,
164
            $this->requestType
165
        );
166
    }
167
168
    /**
169
     * @param string $action
170
     * @param string $entityClass
171
     *
172
     * @return Context
173
     */
174
    protected function getConfig($action, $entityClass)
175
    {
176
        $processor = $this->processorBag->getProcessor($action);
177
        /** @var Context $context */
178
        $context = $processor->createContext();
179
        $context->removeConfigExtra(SortersConfigExtra::NAME);
180
        $context->addConfigExtra(new DescriptionsConfigExtra($action));
0 ignored issues
show
Unused Code introduced by
The call to DescriptionsConfigExtra::__construct() has too many arguments starting with $action.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
181
        $context->addConfigExtra(new StatusCodesConfigExtra($action));
182
        $context->getRequestType()->add(RequestType::REST);
183
        if ('rest_json_api' === $this->docViewDetector->getView()) {
184
            $context->getRequestType()->add(RequestType::JSON_API);
185
        }
186
        $context->setLastGroup('initialize');
187
        $context->setClassName($entityClass);
188
189
        $processor->process($context);
190
191
        return $context;
192
    }
193
194
    /**
195
     * @param ApiDoc      $annotation
196
     * @param string      $action
197
     * @param array       $config
198
     * @param string|null $entityClass
199
     *
200
     * @SuppressWarnings(PHPMD.NPathComplexity)
201
     */
202
    protected function setDescription(ApiDoc $annotation, $action, array $config, $entityClass = null)
203
    {
204
        $templates  = $this->templates[$action];
205
        $entityName = false;
206
207
        // set description
208
        $description = null;
209
        if (!empty($config[$templates['description_key']])) {
210
            $description = $config[$templates['description_key']];
211
        }
212
        if ($description) {
213
            $description = strtr($templates['description'], ['{name}' => $description]);
214
        } elseif ($entityClass) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $entityClass of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
215
            $entityName  = $this->entityClassNameProvider->{$templates['get_name_method']}($entityClass);
216
            $description = $entityName
217
                ? strtr($templates['description'], ['{name}' => $entityName])
218
                : strtr($templates['fallback_description'], ['{class}' => $entityClass]);
219
        }
220
        if ($description) {
221
            $annotation->setDescription($description);
222
        }
223
224
        // set documentation
225
        $documentation = null;
226
        if (!empty($config[$templates['documentation_key']])) {
227
            $documentation = $config[$templates['documentation_key']];
228
        }
229
        if (!$documentation && $entityClass) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $entityClass of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
230
            if (false === $entityName) {
231
                $documentation = $this->entityClassNameProvider->{$templates['get_name_method']}($entityClass);
232
            }
233
            if ($entityName) {
234
                $documentation = $entityName;
235
            }
236
        }
237
        if ($documentation) {
238
            $annotation->setDocumentation($documentation);
239
        }
240
    }
241
242
    /**
243
     * @param ApiDoc            $annotation
244
     * @param StatusCodesConfig $statusCodes
245
     */
246
    protected function setStatusCodes(ApiDoc $annotation, StatusCodesConfig $statusCodes)
247
    {
248
        $codes = $statusCodes->getCodes();
249
        foreach ($codes as $statusCode => $code) {
250
            if (!$code->isExcluded()) {
251
                $annotation->addStatusCode($statusCode, $code->getDescription());
252
            }
253
        }
254
    }
255
256
    /**
257
     * @param ApiDoc $annotation
258
     * @param string $requirement
259
     */
260
    protected function addFormatRequirement(ApiDoc $annotation, $requirement)
261
    {
262
        $annotation->addRequirement(
263
            RestRouteOptionsResolver::FORMAT_ATTRIBUTE,
264
            [
265
                'dataType'    => ApiDocDataTypeConverter::convertToApiDocDataType(DataType::STRING),
266
                'requirement' => $requirement,
267
                'description' => self::FORMAT_DESCRIPTION
268
            ]
269
        );
270
    }
271
272
    /**
273
     * @param ApiDoc $annotation
274
     * @param string $entityClass
275
     * @param string $requirement
276
     */
277
    protected function addIdRequirement(ApiDoc $annotation, $entityClass, $requirement)
278
    {
279
        $metadata = $this->doctrineHelper->getEntityMetadataForClass($entityClass);
280
        $idFields = $metadata->getIdentifierFieldNames();
281
        $dataType = count($idFields) === 1
282
            ? $metadata->getTypeOfField(reset($idFields))
283
            : DataType::STRING;
284
285
        $annotation->addRequirement(
286
            RestRouteOptionsResolver::ID_ATTRIBUTE,
287
            [
288
                'dataType'    => ApiDocDataTypeConverter::convertToApiDocDataType($dataType),
289
                'requirement' => $requirement,
290
                'description' => self::ID_DESCRIPTION
291
            ]
292
        );
293
    }
294
295
    /**
296
     * @param ApiDoc           $annotation
297
     * @param FilterCollection $filters
298
     */
299
    protected function addFilters(ApiDoc $annotation, FilterCollection $filters)
300
    {
301
        foreach ($filters as $key => $filter) {
302
            if ($filter instanceof StandaloneFilter) {
303
                $options = [
304
                    'description' => $filter->getDescription(),
305
                    'requirement' => $this->valueNormalizer->getRequirement(
306
                        $filter->getDataType(),
307
                        $this->requestType,
308
                        $filter->isArrayAllowed()
309
                    )
310
                ];
311
                $default = $filter->getDefaultValueString();
312
                if (!empty($default)) {
313
                    $options['default'] = $default;
314
                }
315
                $operators = $filter->getSupportedOperators();
316
                if (!empty($operators) && !(count($operators) === 1 && $operators[0] === StandaloneFilter::EQ)) {
317
                    $options['operators'] = implode(',', $operators);
318
                }
319
                $annotation->addFilter($key, $options);
320
            }
321
        }
322
    }
323
324
    /**
325
     * Checks if a route has the given placeholder in a path.
326
     *
327
     * @param Route  $route
328
     * @param string $placeholder
329
     *
330
     * @return bool
331
     */
332
    protected function hasAttribute(Route $route, $placeholder)
333
    {
334
        return false !== strpos($route->getPath(), $placeholder);
335
    }
336
}
337