Builder::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 23
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 23
ccs 0
cts 23
cp 0
rs 9.0856
cc 1
eloc 21
nc 1
nop 10
crap 2

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * AnimeDb package.
4
 *
5
 * @author    Peter Gribanov <[email protected]>
6
 * @copyright Copyright (c) 2011, Peter Gribanov
7
 * @license   http://opensource.org/licenses/GPL-3.0 GPL v3
8
 */
9
10
namespace AnimeDb\Bundle\CatalogBundle\Menu;
11
12
use Knp\Menu\FactoryInterface;
13
use Knp\Menu\ItemInterface;
14
use AnimeDb\Bundle\ApiClientBundle\Service\Client;
15
use AnimeDb\Bundle\CatalogBundle\Plugin\Chain;
16
use AnimeDb\Bundle\CatalogBundle\Entity\Item;
17
use AnimeDb\Bundle\CatalogBundle\Plugin\PluginInMenuInterface;
18
use AnimeDb\Bundle\CatalogBundle\Plugin\Import\Chain as ChainImport;
19
use AnimeDb\Bundle\CatalogBundle\Plugin\Export\Chain as ChainExport;
20
use AnimeDb\Bundle\CatalogBundle\Plugin\Fill\Filler\Chain as ChainFiller;
21
use AnimeDb\Bundle\CatalogBundle\Plugin\Fill\Search\Chain as ChainSearch;
22
use AnimeDb\Bundle\CatalogBundle\Plugin\Setting\Chain as ChainSetting;
23
use AnimeDb\Bundle\CatalogBundle\Plugin\Item\Chain as ChainItem;
24
use AnimeDb\Bundle\CatalogBundle\Plugin\Item\ItemInterface as ItemPluginInterface;
25
use Symfony\Component\HttpFoundation\RequestStack;
26
use Symfony\Component\Translation\TranslatorInterface;
27
28
/**
29
 * Menu builder.
30
 *
31
 * @author  Peter Gribanov <[email protected]>
32
 */
33
class Builder
34
{
35
    /**
36
     * Link to guide by update the application on Windows XP.
37
     *
38
     * @var string
39
     */
40
    const GUIDE_LINK = '/guide/';
41
42
    /**
43
     * @var FactoryInterface
44
     */
45
    protected $factory;
46
47
    /**
48
     * @var RequestStack
49
     */
50
    protected $request_stack;
51
52
    /**
53
     * @var TranslatorInterface
54
     */
55
    protected $translator;
56
57
    /**
58
     * @var Client
59
     */
60
    protected $api_client;
61
62
    /**
63
     * @var ChainImport
64
     */
65
    protected $plugin_import;
66
67
    /**
68
     * @var ChainExport
69
     */
70
    protected $plugin_export;
71
72
    /**
73
     * @var ChainSearch
74
     */
75
    protected $plugin_search;
76
77
    /**
78
     * @var ChainFiller
79
     */
80
    protected $plugin_filler;
81
82
    /**
83
     * @var ChainSetting
84
     */
85
    protected $plugin_setting;
86
87
    /**
88
     * @var ChainItem
89
     */
90
    protected $plugin_item;
91
92
    /**
93
     * @param FactoryInterface $factory
94
     * @param RequestStack $request_stack
95
     * @param TranslatorInterface $translator
96
     * @param Client $api_client
97
     * @param ChainImport $plugin_import
98
     * @param ChainExport $plugin_export
99
     * @param ChainSearch $plugin_search
100
     * @param ChainFiller $plugin_filler
101
     * @param ChainSetting $plugin_setting
102
     * @param ChainItem $plugin_item
103
     */
104
    public function __construct(
105
        FactoryInterface $factory,
106
        RequestStack $request_stack,
107
        TranslatorInterface $translator,
108
        Client $api_client,
109
        ChainImport $plugin_import,
110
        ChainExport $plugin_export,
111
        ChainSearch $plugin_search,
112
        ChainFiller $plugin_filler,
113
        ChainSetting $plugin_setting,
114
        ChainItem $plugin_item
115
    ) {
116
        $this->factory = $factory;
117
        $this->request_stack = $request_stack;
118
        $this->translator = $translator;
119
        $this->api_client = $api_client;
120
        $this->plugin_import = $plugin_import;
121
        $this->plugin_export = $plugin_export;
122
        $this->plugin_search = $plugin_search;
123
        $this->plugin_filler = $plugin_filler;
124
        $this->plugin_setting = $plugin_setting;
125
        $this->plugin_item = $plugin_item;
126
    }
127
128
    /**
129
     * @return ItemInterface
130
     */
131
    public function createMainMenu()
132
    {
133
        /* @var $menu ItemInterface */
134
        $menu = $this->factory
135
            ->createItem('root')
136
            ->setUri($this->request_stack->getMasterRequest()->getRequestUri());
137
138
        $menu
139
            ->addChild('Search', ['route' => 'home_search'])
140
            ->setLinkAttribute('class', 'icon-label icon-gray-search');
141
        $add = $menu
142
            ->addChild('Add record')
143
            ->setLabelAttribute('class', 'icon-label icon-gray-add');
144
145
        // synchronization items
146
        if ($this->plugin_import->hasPlugins() || $this->plugin_export->hasPlugins()) {
147
            $sync = $menu
148
                ->addChild('Synchronization')
149
                ->setLabelAttribute('class', 'icon-label icon-white-cloud-sync');
150
151
            // add import plugin items
152
            $this->addPluginItems(
153
                $this->plugin_import,
154
                $sync,
155
                'Import items',
156
                '',
157
                'icon-label icon-white-cloud-download'
158
            );
159
            // add export plugin items
160
            $this->addPluginItems(
161
                $this->plugin_export,
162
                $sync,
163
                'Export items',
164
                '',
165
                'icon-label icon-white-cloud-arrow-up'
166
            );
167
        }
168
169
        $settings = $menu
170
            ->addChild('Settings')
171
            ->setLabelAttribute('class', 'icon-label icon-gray-settings');
172
173
        // add search plugin items
174
        $this->addPluginItems(
175
            $this->plugin_search,
176
            $add,
177
            'Search by name',
178
            'Search by name the source of filling item',
179
            'icon-label icon-white-search'
180
        );
181
        if ($this->plugin_search->hasPlugins()) {
182
            $add
183
                ->addChild('Search in all plugins', ['route' => 'fill_search_in_all'])
184
                ->setAttribute('title', $this->translator->trans('Search by name in all plugins'))
185
                ->setLinkAttribute('class', 'icon-label icon-white-cloud-search');
186
            $add
187
                ->addChild('Add from URL', ['route' => 'fill_search_filler'])
188
                ->setAttribute('title', $this->translator->trans('Search plugin by the URL for filling item'))
189
                ->setLinkAttribute('class', 'icon-label icon-white-cloud-search');
190
        }
191
        // add filler plugin items
192
        $this->addPluginItems(
193
            $this->plugin_filler,
194
            $add,
195
            'Fill from source',
196
            'Fill record from source (example source is URL)',
197
            'icon-label icon-white-share'
198
        );
199
        // add manually
200
        $add
201
            ->addChild('Fill manually', ['route' => 'item_add_manually'])
202
            ->setLinkAttribute('class', 'icon-label icon-white-add');
203
204
        $settings
205
            ->addChild('File storages', ['route' => 'storage_list'])
206
            ->setLinkAttribute('class', 'icon-label icon-white-storage');
207
        $settings
208
            ->addChild('List of notice', ['route' => 'notice_list'])
209
            ->setLinkAttribute('class', 'icon-label icon-white-alert');
210
        $settings
211
            ->addChild('Labels', ['route' => 'label'])
212
            ->setLinkAttribute('class', 'icon-label icon-white-label');
213
        $plugins = $settings
214
            ->addChild('Plugins')
215
            ->setLabelAttribute('class', 'icon-label icon-white-plugin');
216
        $settings
217
            ->addChild('Update', ['route' => 'update'])
218
            ->setLinkAttribute('class', 'icon-label icon-white-update');
219
        $settings
220
            ->addChild('General', ['route' => 'home_settings'])
221
            ->setLinkAttribute('class', 'icon-label icon-white-settings');
222
223
        // plugins
224
        $plugins
225
            ->addChild('Installed', ['route' => 'plugin_installed'])
226
            ->setLinkAttribute('class', 'icon-label icon-white-plugin');
227
        $plugins
228
            ->addChild('Store', ['route' => 'plugin_store'])
229
            ->setLinkAttribute('class', 'icon-label icon-white-shop');
230
        // add settings plugin items
231
        foreach ($this->plugin_setting->getPlugins() as $plugin) {
232
            /* @var $plugin PluginInMenuInterface */
233
            $plugin->buildMenu($plugins);
234
        }
235
236
        // add link to guide
237
        $settings
238
            ->addChild('Help', ['uri' => $this->api_client->getSiteUrl(self::GUIDE_LINK)])
239
            ->setLinkAttribute('class', 'icon-label icon-white-help');
240
241
        return $menu;
242
    }
243
244
    /**
245
     * @param Chain $chain
246
     * @param ItemInterface $root
247
     * @param string $label
248
     * @param string|null $title
249
     * @param string|null $class
250
     */
251
    private function addPluginItems(Chain $chain, ItemInterface $root, $label, $title = '', $class = '')
252
    {
253
        if ($chain->hasPlugins()) {
254
            $group = $root->addChild($label);
255
            if ($title) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $title 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...
256
                $group->setAttribute('title', $this->translator->trans($title));
257
            }
258
            if ($class) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $class 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...
259
                $group->setLabelAttribute('class', $class);
260
            }
261
262
            // add child items
263
            foreach ($chain->getPlugins() as $plugin) {
264
                if ($plugin instanceof PluginInMenuInterface) {
265
                    $plugin->buildMenu($group);
266
                }
267
            }
268
        }
269
    }
270
271
    /**
272
     * @param array $options
273
     *
274
     * @return ItemInterface
275
     */
276
    public function createItemMenu(array $options)
277
    {
278
        if (empty($options['item']) || !($options['item'] instanceof Item)) {
279
            throw new \InvalidArgumentException('Item is not found');
280
        }
281
282
        /* @var $item Item */
283
        $item = $options['item'];
284
285
        /* @var $menu ItemInterface */
286
        $menu = $this->factory
287
            ->createItem('root')
288
            ->setUri($this->request_stack->getMasterRequest()->getRequestUri());
289
        $params = [
290
            'id' => $item->getId(),
291
            'name' => $item->getUrlName(),
292
        ];
293
294
        $menu
295
            ->addChild('Change record', ['route' => 'item_change', 'routeParameters' => $params])
296
            ->setLinkAttribute('class', 'icon-label icon-edit');
297
298
        // add settings plugin items
299
        /* @var $plugin ItemPluginInterface */
300
        foreach ($this->plugin_item->getPlugins() as $plugin) {
301
            $plugin->buildMenu($menu, $options['item']);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface AnimeDb\Bundle\CatalogBu...\Plugin\PluginInterface as the method buildMenu() does only exist in the following implementations of said interface: AnimeDb\Bundle\CatalogBundle\Plugin\Export\Export, AnimeDb\Bundle\CatalogBu...ugin\Fill\Filler\Filler, AnimeDb\Bundle\CatalogBu...ugin\Fill\Search\Search, AnimeDb\Bundle\CatalogBundle\Plugin\Import\Import, AnimeDb\Bundle\CatalogBundle\Plugin\Item\Item, AnimeDb\Bundle\CatalogBu...\Plugin\Setting\Setting.

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...
302
        }
303
304
        $menu->addChild('Delete record', ['route' => 'item_delete', 'routeParameters' => $params])
305
            ->setLinkAttribute('class', 'icon-label icon-delete')
306
            ->setLinkAttribute('data-message', $this->translator->trans(
307
                'Are you sure want to delete %name%?',
308
                ['%name%' => $item->getName()]
309
            ));
310
311
        return $menu;
312
    }
313
}
314