Completed
Push — 0.4.27 ( 28516b...22b0a7 )
by Peter
04:29 queued 01:23
created

Builder::__construct()   A

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