DatagridBuilder::addFilter()   B
last analyzed

Complexity

Conditions 7
Paths 8

Size

Total Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 44
rs 8.2826
c 0
b 0
f 0
cc 7
nc 8
nop 4
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\DoctrineORMAdminBundle\Builder;
15
16
use Doctrine\ORM\Mapping\ClassMetadata;
17
use Sonata\AdminBundle\Admin\AdminInterface;
18
use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
19
use Sonata\AdminBundle\Builder\DatagridBuilderInterface;
20
use Sonata\AdminBundle\Datagrid\Datagrid;
21
use Sonata\AdminBundle\Datagrid\DatagridInterface;
22
use Sonata\AdminBundle\Datagrid\PagerInterface;
23
use Sonata\AdminBundle\Datagrid\SimplePager;
24
use Sonata\AdminBundle\Filter\FilterFactoryInterface;
25
use Sonata\AdminBundle\Guesser\TypeGuesserInterface;
26
use Sonata\DoctrineORMAdminBundle\Datagrid\Pager;
27
use Sonata\DoctrineORMAdminBundle\Filter\ModelAutocompleteFilter;
28
use Symfony\Component\Form\Extension\Core\Type\FormType;
29
use Symfony\Component\Form\FormFactoryInterface;
30
31
class DatagridBuilder implements DatagridBuilderInterface
32
{
33
    /**
34
     * @var FilterFactoryInterface
35
     */
36
    protected $filterFactory;
37
38
    /**
39
     * @var FormFactoryInterface
40
     */
41
    protected $formFactory;
42
43
    /**
44
     * @var TypeGuesserInterface
45
     */
46
    protected $guesser;
47
48
    /**
49
     * @var bool
50
     */
51
    protected $csrfTokenEnabled;
52
53
    /**
54
     * @param bool $csrfTokenEnabled
55
     */
56
    public function __construct(
57
        FormFactoryInterface $formFactory,
58
        FilterFactoryInterface $filterFactory,
59
        TypeGuesserInterface $guesser,
60
        $csrfTokenEnabled = true
61
    ) {
62
        $this->formFactory = $formFactory;
63
        $this->filterFactory = $filterFactory;
64
        $this->guesser = $guesser;
65
        $this->csrfTokenEnabled = $csrfTokenEnabled;
66
    }
67
68
    public function fixFieldDescription(AdminInterface $admin, FieldDescriptionInterface $fieldDescription): void
69
    {
70
        // set default values
71
        $fieldDescription->setAdmin($admin);
72
73
        if ($admin->getModelManager()->hasMetadata($admin->getClass())) {
74
            list($metadata, $lastPropertyName, $parentAssociationMappings) = $admin->getModelManager()
75
                ->getParentMetadataForProperty($admin->getClass(), $fieldDescription->getName());
76
77
            // set the default field mapping
78
            if (isset($metadata->fieldMappings[$lastPropertyName])) {
79
                $fieldDescription->setOption(
80
                    'field_mapping',
81
                    $fieldDescription->getOption(
82
                        'field_mapping',
83
                        $fieldMapping = $metadata->fieldMappings[$lastPropertyName]
84
                    )
85
                );
86
87
                if ('string' === $fieldMapping['type']) {
88
                    $fieldDescription->setOption('global_search', $fieldDescription->getOption('global_search', true)); // always search on string field only
89
                }
90
91
                if (!empty($embeddedClasses = $metadata->embeddedClasses)
92
                    && isset($fieldMapping['declaredField'])
93
                    && \array_key_exists($fieldMapping['declaredField'], $embeddedClasses)
94
                ) {
95
                    $fieldDescription->setOption(
96
                        'field_name',
97
                        $fieldMapping['fieldName']
98
                    );
99
                }
100
            }
101
102
            // set the default association mapping
103
            if (isset($metadata->associationMappings[$lastPropertyName])) {
104
                $fieldDescription->setOption(
105
                    'association_mapping',
106
                    $fieldDescription->getOption(
107
                        'association_mapping',
108
                        $metadata->associationMappings[$lastPropertyName]
109
                    )
110
                );
111
            }
112
113
            $fieldDescription->setOption(
114
                'parent_association_mappings',
115
                $fieldDescription->getOption('parent_association_mappings', $parentAssociationMappings)
116
            );
117
        }
118
119
        $fieldDescription->setOption('code', $fieldDescription->getOption('code', $fieldDescription->getName()));
120
        $fieldDescription->setOption('name', $fieldDescription->getOption('name', $fieldDescription->getName()));
121
122
        if (\in_array($fieldDescription->getMappingType(), [
123
            ClassMetadata::ONE_TO_MANY,
124
            ClassMetadata::MANY_TO_MANY,
125
            ClassMetadata::MANY_TO_ONE,
126
            ClassMetadata::ONE_TO_ONE,
127
        ], true)) {
128
            $admin->attachAdminClass($fieldDescription);
129
        }
130
    }
131
132
    public function addFilter(DatagridInterface $datagrid, $type, FieldDescriptionInterface $fieldDescription, AdminInterface $admin): void
133
    {
134
        if (null === $type) {
135
            $guessType = $this->guesser->guessType($admin->getClass(), $fieldDescription->getName(), $admin->getModelManager());
136
137
            $type = $guessType->getType();
138
139
            $fieldDescription->setType($type);
140
141
            $options = $guessType->getOptions();
142
143
            foreach ($options as $name => $value) {
144
                if (\is_array($value)) {
145
                    $fieldDescription->setOption($name, array_merge($value, $fieldDescription->getOption($name, [])));
146
                } else {
147
                    $fieldDescription->setOption($name, $fieldDescription->getOption($name, $value));
148
                }
149
            }
150
        } else {
151
            $fieldDescription->setType($type);
152
        }
153
154
        $this->fixFieldDescription($admin, $fieldDescription);
155
        $admin->addFilterFieldDescription($fieldDescription->getName(), $fieldDescription);
156
157
        $fieldDescription->mergeOption('field_options', ['required' => false]);
158
159
        if (ModelAutocompleteFilter::class === $type) {
160
            $fieldDescription->mergeOption('field_options', [
161
                'class' => $fieldDescription->getTargetModel(),
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sonata\AdminBundle\Admin\FieldDescriptionInterface as the method getTargetModel() does only exist in the following implementations of said interface: Sonata\DoctrineORMAdminB...\Admin\FieldDescription.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
162
                'model_manager' => $fieldDescription->getAdmin()->getModelManager(),
163
                'admin_code' => $admin->getCode(),
164
                'context' => 'filter',
165
            ]);
166
        }
167
168
        $filter = $this->filterFactory->create($fieldDescription->getName(), $type, $fieldDescription->getOptions());
169
170
        if (false !== $filter->getLabel() && !$filter->getLabel()) {
171
            $filter->setLabel($admin->getLabelTranslatorStrategy()->getLabel($fieldDescription->getName(), 'filter', 'label'));
172
        }
173
174
        $datagrid->addFilter($filter);
175
    }
176
177
    public function getBaseDatagrid(AdminInterface $admin, array $values = [])
178
    {
179
        $pager = $this->getPager($admin->getPagerType());
180
181
        $pager->setCountColumn($admin->getModelManager()->getIdentifierFieldNames($admin->getClass()));
182
183
        $defaultOptions = ['validation_groups' => false];
184
        if ($this->csrfTokenEnabled) {
185
            $defaultOptions['csrf_protection'] = false;
186
        }
187
188
        $formBuilder = $this->formFactory->createNamedBuilder('filter', FormType::class, [], $defaultOptions);
189
190
        return new Datagrid($admin->createQuery(), $admin->getList(), $pager, $formBuilder, $values);
191
    }
192
193
    /**
194
     * Get pager by pagerType.
195
     *
196
     * @param string $pagerType
197
     *
198
     * @throws \RuntimeException If invalid pager type is set
199
     *
200
     * @return PagerInterface
201
     */
202
    protected function getPager($pagerType)
203
    {
204
        switch ($pagerType) {
205
            case Pager::TYPE_DEFAULT:
206
                return new Pager();
207
208
            case Pager::TYPE_SIMPLE:
209
                return new SimplePager();
210
211
            default:
212
                throw new \RuntimeException(sprintf('Unknown pager type "%s".', $pagerType));
213
        }
214
    }
215
}
216