RetrieveAutocompleteItemsAction::__invoke()   D
last analyzed

Complexity

Conditions 16
Paths 67

Size

Total Lines 133

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 133
rs 4.4532
c 0
b 0
f 0
cc 16
nc 67
nop 1

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
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\AdminBundle\Action;
15
16
use Sonata\AdminBundle\Admin\AdminInterface;
17
use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
18
use Sonata\AdminBundle\Admin\Pool;
19
use Sonata\AdminBundle\Filter\FilterInterface;
20
use Symfony\Component\Form\Form;
21
use Symfony\Component\HttpFoundation\JsonResponse;
22
use Symfony\Component\HttpFoundation\Request;
23
use Symfony\Component\HttpFoundation\Response;
24
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
25
26
final class RetrieveAutocompleteItemsAction
27
{
28
    /**
29
     * @var Pool
30
     */
31
    private $pool;
32
33
    public function __construct(Pool $pool)
34
    {
35
        $this->pool = $pool;
36
    }
37
38
    /**
39
     * Retrieve list of items for autocomplete form field.
40
     *
41
     * @throws \RuntimeException
42
     * @throws AccessDeniedException
43
     */
44
    public function __invoke(Request $request): JsonResponse
45
    {
46
        $admin = $this->pool->getInstance($request->get('admin_code'));
47
        $admin->setRequest($request);
48
        $context = $request->get('_context', '');
49
50
        if ('filter' === $context) {
51
            $admin->checkAccess('list');
52
        } elseif (!$admin->hasAccess('create') && !$admin->hasAccess('edit')) {
53
            throw new AccessDeniedException();
54
        }
55
56
        // subject will be empty to avoid unnecessary database requests and keep autocomplete function fast
57
        $admin->setSubject($admin->getNewInstance());
58
59
        if ('filter' === $context) {
60
            // filter
61
            $fieldDescription = $this->retrieveFilterFieldDescription($admin, $request->get('field'));
62
            $filterAutocomplete = $admin->getDatagrid()->getFilter($fieldDescription->getName());
63
64
            $property = $filterAutocomplete->getFieldOption('property');
65
            $callback = $filterAutocomplete->getFieldOption('callback');
66
            $minimumInputLength = $filterAutocomplete->getFieldOption('minimum_input_length', 3);
67
            $itemsPerPage = $filterAutocomplete->getFieldOption('items_per_page', 10);
68
            $reqParamPageNumber = $filterAutocomplete->getFieldOption('req_param_name_page_number', '_page');
69
            $toStringCallback = $filterAutocomplete->getFieldOption('to_string_callback');
70
            $targetAdminAccessAction = $filterAutocomplete->getFieldOption('target_admin_access_action', 'list');
71
        } else {
72
            // create/edit form
73
            $fieldDescription = $this->retrieveFormFieldDescription($admin, $request->get('field'));
74
            $formAutocomplete = $admin->getForm()->get($fieldDescription->getName());
75
76
            $formAutocompleteConfig = $formAutocomplete->getConfig();
77
            if ($formAutocompleteConfig->getAttribute('disabled')) {
78
                throw new AccessDeniedException(
79
                    'Autocomplete list can`t be retrieved because the form element is disabled or read_only.'
80
                );
81
            }
82
83
            $property = $formAutocompleteConfig->getAttribute('property');
84
            $callback = $formAutocompleteConfig->getAttribute('callback');
85
            $minimumInputLength = $formAutocompleteConfig->getAttribute('minimum_input_length');
86
            $itemsPerPage = $formAutocompleteConfig->getAttribute('items_per_page');
87
            $reqParamPageNumber = $formAutocompleteConfig->getAttribute('req_param_name_page_number');
88
            $toStringCallback = $formAutocompleteConfig->getAttribute('to_string_callback');
89
            $targetAdminAccessAction = $formAutocompleteConfig->getAttribute('target_admin_access_action');
90
        }
91
92
        $searchText = $request->get('q', '');
93
94
        $targetAdmin = $fieldDescription->getAssociationAdmin();
95
96
        // check user permission
97
        $targetAdmin->checkAccess($targetAdminAccessAction);
98
99
        if (mb_strlen($searchText, 'UTF-8') < $minimumInputLength) {
100
            return new JsonResponse(['status' => 'KO', 'message' => 'Too short search string.'], Response::HTTP_FORBIDDEN);
101
        }
102
103
        $targetAdmin->setFilterPersister(null);
104
        $datagrid = $targetAdmin->getDatagrid();
105
106
        if (null !== $callback) {
107
            if (!\is_callable($callback)) {
108
                throw new \RuntimeException('Callback does not contain callable function.');
109
            }
110
111
            $callback($targetAdmin, $property, $searchText);
112
        } else {
113
            if (\is_array($property)) {
114
                // multiple properties
115
                foreach ($property as $prop) {
116
                    if (!$datagrid->hasFilter($prop)) {
117
                        throw new \RuntimeException(sprintf(
118
                            'To retrieve autocomplete items,'
119
                            .' you should add filter "%s" to "%s" in configureDatagridFilters() method.',
120
                            $prop,
121
                            \get_class($targetAdmin)
122
                        ));
123
                    }
124
125
                    $filter = $datagrid->getFilter($prop);
126
                    $filter->setCondition(FilterInterface::CONDITION_OR);
127
128
                    $datagrid->setValue($filter->getFormName(), null, $searchText);
129
                }
130
            } else {
131
                if (!$datagrid->hasFilter($property)) {
132
                    throw new \RuntimeException(sprintf(
133
                        'To retrieve autocomplete items,'
134
                        .' you should add filter "%s" to "%s" in configureDatagridFilters() method.',
135
                        $property,
136
                        \get_class($targetAdmin)
137
                    ));
138
                }
139
140
                $datagrid->setValue($datagrid->getFilter($property)->getFormName(), null, $searchText);
141
            }
142
        }
143
144
        $datagrid->setValue('_per_page', null, $itemsPerPage);
145
        $datagrid->setValue('_page', null, $request->query->get($reqParamPageNumber, 1));
146
        $datagrid->buildPager();
147
148
        $pager = $datagrid->getPager();
149
150
        $items = [];
151
        $results = $pager->getResults();
152
153
        foreach ($results as $model) {
154
            if (null !== $toStringCallback) {
155
                if (!\is_callable($toStringCallback)) {
156
                    throw new \RuntimeException('Option "to_string_callback" does not contain callable function.');
157
                }
158
159
                $label = $toStringCallback($model, $property);
160
            } else {
161
                $resultMetadata = $targetAdmin->getObjectMetadata($model);
162
                $label = $resultMetadata->getTitle();
163
            }
164
165
            $items[] = [
166
                'id' => $admin->id($model),
167
                'label' => $label,
168
            ];
169
        }
170
171
        return new JsonResponse([
172
            'status' => 'OK',
173
            'more' => !$pager->isLastPage(),
174
            'items' => $items,
175
        ]);
176
    }
177
178
    /**
179
     * Retrieve the filter field description given by field name.
180
     *
181
     * @throws \RuntimeException
182
     */
183
    private function retrieveFilterFieldDescription(
184
        AdminInterface $admin,
185
        string $field
186
    ): FieldDescriptionInterface {
187
        if (!$admin->hasFilterFieldDescription($field)) {
188
            throw new \RuntimeException(sprintf('The field "%s" does not exist.', $field));
189
        }
190
191
        $fieldDescription = $admin->getFilterFieldDescription($field);
192
193
        if (null === $fieldDescription->getTargetModel()) {
194
            throw new \RuntimeException(sprintf('No associated entity with field "%s".', $field));
195
        }
196
197
        return $fieldDescription;
198
    }
199
200
    /**
201
     * Retrieve the form field description given by field name.
202
     *
203
     * @throws \RuntimeException
204
     */
205
    private function retrieveFormFieldDescription(
206
        AdminInterface $admin,
207
        string $field
208
    ): FieldDescriptionInterface {
209
        if (!$admin->hasFormFieldDescription($field)) {
210
            throw new \RuntimeException(sprintf('The field "%s" does not exist.', $field));
211
        }
212
213
        $fieldDescription = $admin->getFormFieldDescription($field);
214
215
        if (null === $fieldDescription->getTargetModel()) {
216
            throw new \RuntimeException(sprintf('No associated entity with field "%s".', $field));
217
        }
218
219
        return $fieldDescription;
220
    }
221
}
222