Builder::createMainMenu()   B
last analyzed

Complexity

Conditions 5
Paths 8

Size

Total Lines 112
Code Lines 83

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 112
ccs 0
cts 92
cp 0
rs 8.1463
cc 5
eloc 83
nc 8
nop 0
crap 30

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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