Completed
Push — 3.x ( 3e834f...38b337 )
by Grégoire
03:36
created

src/Datagrid/ListMapper.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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\Datagrid;
15
16
use Sonata\AdminBundle\Admin\AdminInterface;
17
use Sonata\AdminBundle\Admin\FieldDescriptionCollection;
18
use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
19
use Sonata\AdminBundle\Builder\ListBuilderInterface;
20
use Sonata\AdminBundle\Mapper\BaseMapper;
21
22
/**
23
 * This class is used to simulate the Form API.
24
 *
25
 * @final since sonata-project/admin-bundle 3.52
26
 *
27
 * @author Thomas Rabaix <[email protected]>
28
 */
29
class ListMapper extends BaseMapper
30
{
31
    public const TYPE_ACTIONS = 'actions';
32
    public const TYPE_BATCH = 'batch';
33
    public const TYPE_SELECT = 'select';
34
35
    /**
36
     * @var FieldDescriptionCollection
37
     */
38
    protected $list;
39
40
    public function __construct(
41
        ListBuilderInterface $listBuilder,
42
        FieldDescriptionCollection $list,
43
        AdminInterface $admin
44
    ) {
45
        parent::__construct($listBuilder, $admin);
46
        $this->list = $list;
47
    }
48
49
    /**
50
     * @param string      $name
51
     * @param string|null $type
52
     *
53
     * @return $this
54
     */
55
    public function addIdentifier($name, $type = null, array $fieldDescriptionOptions = [])
56
    {
57
        $fieldDescriptionOptions['identifier'] = true;
58
59
        if (!isset($fieldDescriptionOptions['route']['name'])) {
60
            $routeName = ($this->admin->hasAccess('edit') && $this->admin->hasRoute('edit')) ? 'edit' : 'show';
61
            $fieldDescriptionOptions['route']['name'] = $routeName;
62
        }
63
64
        if (!isset($fieldDescriptionOptions['route']['parameters'])) {
65
            $fieldDescriptionOptions['route']['parameters'] = [];
66
        }
67
68
        return $this->add($name, $type, $fieldDescriptionOptions);
69
    }
70
71
    /**
72
     * @param FieldDescriptionInterface|string $name
73
     * @param string|null                      $type
74
     *
75
     * @throws \LogicException
76
     *
77
     * @return $this
78
     */
79
    public function add($name, $type = null, array $fieldDescriptionOptions = [])
80
    {
81
        // Default sort on "associated_property"
82
        if (isset($fieldDescriptionOptions['associated_property'])) {
83
            if (!isset($fieldDescriptionOptions['sortable'])) {
84
                $fieldDescriptionOptions['sortable'] = true;
85
            }
86
            if (!isset($fieldDescriptionOptions['sort_parent_association_mappings'])) {
87
                $fieldDescriptionOptions['sort_parent_association_mappings'] = [[
88
                    'fieldName' => $name,
89
                ]];
90
            }
91
            if (!isset($fieldDescriptionOptions['sort_field_mapping'])) {
92
                $fieldDescriptionOptions['sort_field_mapping'] = [
93
                    'fieldName' => $fieldDescriptionOptions['associated_property'],
94
                ];
95
            }
96
        }
97
98
        // Type-guess the action field here because it is not a model property.
99
        if ('_action' === $name && null === $type) {
100
            $type = self::TYPE_ACTIONS;
101
        }
102
103
        // Change deprecated inline action "view" to "show"
104
        if ('_action' === $name && self::TYPE_ACTIONS === $type) {
105
            if (isset($fieldDescriptionOptions['actions']['view'])) {
106
                @trigger_error(
107
                    'Inline action "view" is deprecated since version 2.2.4 and will be removed in 4.0. '
108
                    .'Use inline action "show" instead.',
109
                    E_USER_DEPRECATED
110
                );
111
112
                $fieldDescriptionOptions['actions']['show'] = $fieldDescriptionOptions['actions']['view'];
113
114
                unset($fieldDescriptionOptions['actions']['view']);
115
            }
116
        }
117
118
        if (\array_key_exists('identifier', $fieldDescriptionOptions) && !\is_bool($fieldDescriptionOptions['identifier'])) {
119
            @trigger_error(
120
                'Passing a non boolean value for the "identifier" option is deprecated since sonata-project/admin-bundle 3.51 and will throw an exception in 4.0.',
121
                E_USER_DEPRECATED
122
            );
123
124
            $fieldDescriptionOptions['identifier'] = (bool) $fieldDescriptionOptions['identifier'];
125
            // NEXT_MAJOR: Remove the previous 6 lines and use commented line below it instead
126
            // throw new \InvalidArgumentException(sprintf('Value for "identifier" option must be boolean, %s given.', gettype($fieldDescriptionOptions['identifier'])));
127
        }
128
129
        if ($name instanceof FieldDescriptionInterface) {
130
            $fieldDescription = $name;
131
            $fieldDescription->mergeOptions($fieldDescriptionOptions);
132
        } elseif (\is_string($name)) {
133
            if ($this->admin->hasListFieldDescription($name)) {
134
                throw new \LogicException(sprintf(
135
                    'Duplicate field name "%s" in list mapper. Names should be unique.',
136
                    $name
137
                ));
138
            }
139
140
            $fieldDescription = $this->admin->getModelManager()->getNewFieldDescriptionInstance(
141
                $this->admin->getClass(),
142
                $name,
143
                $fieldDescriptionOptions
144
            );
145
        } else {
146
            throw new \TypeError(
147
                'Unknown field name in list mapper. '
148
                .'Field name should be either of FieldDescriptionInterface interface or string.'
149
            );
150
        }
151
152
        // NEXT_MAJOR: Remove the argument "sonata_deprecation_mute" in the following call.
153
        if (null === $fieldDescription->getLabel('sonata_deprecation_mute')) {
154
            $fieldDescription->setOption(
155
                'label',
156
                $this->admin->getLabelTranslatorStrategy()->getLabel($fieldDescription->getName(), 'list', 'label')
157
            );
158
        }
159
160
        if (isset($fieldDescriptionOptions['header_style'])) {
161
            @trigger_error(
162
                'The "header_style" option is deprecated, please, use "header_class" option instead.',
163
                E_USER_DEPRECATED
164
            );
165
        }
166
167
        if (!isset($fieldDescriptionOptions['role']) || $this->admin->isGranted($fieldDescriptionOptions['role'])) {
168
            // add the field with the FormBuilder
169
            $this->builder->addField($this->list, $type, $fieldDescription, $this->admin);
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface Sonata\AdminBundle\Builder\BuilderInterface as the method addField() does only exist in the following implementations of said interface: Sonata\AdminBundle\Tests\App\Builder\ListBuilder, Sonata\AdminBundle\Tests\App\Builder\ShowBuilder.

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...
170
171
            // Ensure batch and action pseudo-fields are tagged as virtual
172
            if (\in_array($fieldDescription->getType(), [self::TYPE_ACTIONS, self::TYPE_BATCH, self::TYPE_SELECT], true)) {
173
                $fieldDescription->setOption('virtual_field', true);
174
            }
175
        }
176
177
        return $this;
178
    }
179
180
    public function get($name)
181
    {
182
        return $this->list->get($name);
183
    }
184
185
    public function has($key)
186
    {
187
        return $this->list->has($key);
188
    }
189
190
    public function remove($key)
191
    {
192
        $this->admin->removeListFieldDescription($key);
193
        $this->list->remove($key);
194
195
        return $this;
196
    }
197
198
    final public function keys()
199
    {
200
        return array_keys($this->list->getElements());
201
    }
202
203
    public function reorder(array $keys)
204
    {
205
        $this->list->reorder($keys);
206
207
        return $this;
208
    }
209
}
210