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

src/Datagrid/ListMapper.php (1 issue)

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