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

Builder::createMainMenu()   B

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
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