LinkExtension::victoireLinkUrl()   F
last analyzed

Complexity

Conditions 22
Paths 360

Size

Total Lines 82
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
dl 0
loc 82
rs 3.7658
c 1
b 1
f 0
cc 22
eloc 56
nc 360
nop 3

How to fix   Long Method    Complexity   

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
namespace Victoire\Bundle\WidgetBundle\Twig;
4
5
use Doctrine\ORM\EntityManager;
6
use Doctrine\ORM\EntityRepository;
7
use Psr\Log\LoggerInterface;
8
use Symfony\Bundle\FrameworkBundle\Routing\Router;
9
use Symfony\Component\HttpFoundation\RequestStack;
10
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
11
use Victoire\Bundle\BusinessEntityBundle\Helper\BusinessEntityHelper;
12
use Victoire\Bundle\BusinessPageBundle\Helper\BusinessPageHelper;
13
use Victoire\Bundle\CoreBundle\Entity\Link;
14
use Victoire\Bundle\CoreBundle\Entity\View;
15
use Victoire\Bundle\CoreBundle\Entity\WebViewInterface;
16
use Victoire\Bundle\PageBundle\Helper\PageHelper;
17
use Victoire\Bundle\TwigBundle\Entity\ErrorPage;
18
use Victoire\Bundle\ViewReferenceBundle\Exception\ViewReferenceNotFoundException;
19
use Victoire\Bundle\ViewReferenceBundle\ViewReference\ViewReference;
20
use Victoire\Bundle\WidgetBundle\Entity\Widget;
21
22
/**
23
 * Twig extension for rendering a link.
24
 */
25
class LinkExtension extends \Twig_Extension
26
{
27
    protected $router;
28
    protected $analytics;
29
    protected $businessEntityHelper;
30
    protected $BusinessPageHelper;
31
    protected $pageHelper;
32
    protected $em;
33
    protected $errorPageRepository;
34
    protected $abstractBusinessTemplates;
35
    protected $defaultLocale;
36
37
    /**
38
     * LinkExtension constructor.
39
     *
40
     * @param Router               $router
41
     * @param RequestStack         $requestStack
42
     * @param string               $analytics
43
     * @param BusinessEntityHelper $businessEntityHelper
44
     * @param BusinessPageHelper   $BusinessPageHelper
45
     * @param PageHelper           $pageHelper
46
     * @param EntityManager        $em
47
     * @param LoggerInterface      $logger
48
     * @param EntityRepository     $errorPageRepository
49
     * @param string               $defaultLocale
50
     * @param array                $abstractBusinessTemplates
51
     */
52
    public function __construct(
53
        Router $router,
54
        RequestStack $requestStack,
55
        $analytics,
56
        BusinessEntityHelper $businessEntityHelper,
57
        BusinessPageHelper $BusinessPageHelper,
0 ignored issues
show
Coding Style introduced by
Variable "BusinessPageHelper" is not in valid camel caps format
Loading history...
58
        PageHelper $pageHelper,
59
        EntityManager $em,
0 ignored issues
show
Bug introduced by
You have injected the EntityManager via parameter $em. This is generally not recommended as it might get closed and become unusable. Instead, it is recommended to inject the ManagerRegistry and retrieve the EntityManager via getManager() each time you need it.

The EntityManager might become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:

function someFunction(ManagerRegistry $registry) {
    $em = $registry->getManager();
    $em->getConnection()->beginTransaction();
    try {
        // Do something.
        $em->getConnection()->commit();
    } catch (\Exception $ex) {
        $em->getConnection()->rollback();
        $em->close();

        throw $ex;
    }
}

If that code throws an exception and the EntityManager is closed. Any other code which depends on the same instance of the EntityManager during this request will fail.

On the other hand, if you instead inject the ManagerRegistry, the getManager() method guarantees that you will always get a usable manager instance.

Loading history...
60
        LoggerInterface $logger,
61
        EntityRepository $errorPageRepository,
62
        $defaultLocale,
63
        $abstractBusinessTemplates = []
64
    ) {
65
        $this->router = $router;
66
        $this->request = $requestStack->getCurrentRequest();
0 ignored issues
show
Bug introduced by
The property request does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
67
        $this->analytics = $analytics;
68
        $this->businessEntityHelper = $businessEntityHelper;
69
        $this->BusinessPageHelper = $BusinessPageHelper;
0 ignored issues
show
Coding Style introduced by
Variable "BusinessPageHelper" is not in valid camel caps format
Loading history...
70
        $this->pageHelper = $pageHelper;
71
        $this->em = $em;
72
        $this->errorPageRepository = $errorPageRepository;
73
        $this->logger = $logger;
0 ignored issues
show
Bug introduced by
The property logger does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
74
        $this->abstractBusinessTemplates = $abstractBusinessTemplates;
75
        $this->defaultLocale = $defaultLocale;
76
    }
77
78
    /**
79
     * Returns a list of functions to add to the existing list.
80
     *
81
     * @return \Twig_SimpleFunction[] An array of functions
82
     */
83
    public function getFunctions()
84
    {
85
        return [
86
            new \Twig_SimpleFunction('vic_link_url', [$this, 'victoireLinkUrl']),
87
            new \Twig_SimpleFunction('vic_link', [$this, 'victoireLink'], ['is_safe' => ['html']]),
88
            new \Twig_SimpleFunction('vic_menu_link', [$this, 'victoireMenuLink'], ['is_safe' => ['html']]),
89
            new \Twig_SimpleFunction('vic_business_link', [$this, 'victoireBusinessLink'], ['is_safe' => ['html']]),
90
            new \Twig_SimpleFunction('is_vic_link_active', [$this, 'isVicLinkActive'], ['is_safe' => ['html']]),
91
        ];
92
    }
93
94
    /**
95
     * Generate the complete URL of a link.
96
     *
97
     * @param array  $parameters   The link parameters (go to LinkTrait to have the list)
98
     * @param string $avoidRefresh Do we have to refresh or not ?
99
     * @param array  $url          Fallback url
100
     *
101
     * @return string
102
     */
103
    public function victoireLinkUrl($parameters, $avoidRefresh = true, $url = '#')
104
    {
105
        $referenceType = isset($parameters['referenceType']) ? $parameters['referenceType'] : UrlGeneratorInterface::ABSOLUTE_PATH;
106
        if (!isset($parameters['locale'])) {
107
            if ($this->request) {
108
                $parameters['locale'] = $this->request->getLocale();
109
            } else {
110
                $parameters['locale'] = $this->defaultLocale;
111
            }
112
        }
113
114
        $viewReference = isset($parameters['viewReference']) ? $parameters['viewReference'] : null;
115
        switch ($parameters['linkType']) {
116
            case 'viewReference':
117
                if ($viewReference instanceof ViewReference) {
118
                    $viewReference = $viewReference->getId();
119
                }
120
                $linkUrl = '';
121
                if (!empty($parameters['viewReferencePage'])) {
122
                    $page = $parameters['viewReferencePage'];
123
                } else {
124
                    $params = [
125
                        'id'     => $viewReference,
126
                        'locale' => $parameters['locale'],
127
                    ];
128
                    try {
129
                        $page = $this->pageHelper->findPageByParameters($params);
130
                    } catch (ViewReferenceNotFoundException $e) {
131
                        $this->logger->error($e->getMessage(), $params);
132
                        /** @var ErrorPage $page */
133
                        $page = $this->errorPageRepository->findOneByCode(404);
134
                        $linkUrl = $this->router->generate(
135
                            'victoire_core_page_show', array_merge([
136
                            '_locale' => $parameters['locale'],
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 32 spaces, but found 28.
Loading history...
137
                            'url'     => $page->getSlug(),
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 32 spaces, but found 28.
Loading history...
138
                        ], $params));
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 28 spaces, but found 24.
Loading history...
139
                    }
140
                }
141
142
                if ($page instanceof WebViewInterface) {
143
                    $linkUrl = $this->router->generate(
144
                        'victoire_core_page_show', [
145
                            '_locale' => $parameters['locale'],
146
                            'url'     => $page->getReference($parameters['locale'])->getUrl(),
147
                        ],
148
                        $referenceType
149
                    );
150
                }
151
152
                if (!$this->request || ($this->request && $this->request->getRequestUri() !== $linkUrl) || !$avoidRefresh) {
153
                    $url = $linkUrl;
154
                }
155
                break;
156
            case 'route':
157
                $url = $this->router->generate($parameters['route'], $parameters['routeParameters'], $referenceType);
158
                break;
159
            case Link::TYPE_WIDGET:
160
                $attachedWidget = $parameters[Link::TYPE_WIDGET];
161
                $url = '';
162
163
                //If Widget's View has an url and Widget is not in the current View, add this url in the link
164
                if ($attachedWidget && method_exists($attachedWidget->getWidgetMap()->getView(), 'getUrl')
165
                    && (!$this->request || rtrim($this->request->getRequestUri(), '/') != rtrim($url, '/'))
166
                ) {
167
                    /** @var View $view */
168
                    $view = $attachedWidget->getWidgetMap()->getView();
169
                    if (!$locale = $attachedWidget->guessLocale()) {
170
                        $locale = $this->request ? $this->request->getLocale() : $this->defaultLocale;
171
                    }
172
                    $view->translate($locale);
173
                    $url .= $this->router->generate('victoire_core_page_show', ['_locale' => $locale, 'url' => $view->getUrl()], $referenceType);
174
                }
175
176
                //Add anchor part
177
                $url .= '#widget-'.$attachedWidget->getId();
178
                break;
179
            default:
180
                $url = $parameters['url'];
181
        }
182
183
        return $url;
184
    }
185
186
    /**
0 ignored issues
show
introduced by
Doc comment for parameter "$currentClass" missing
Loading history...
introduced by
Doc comment for parameter "$url" missing
Loading history...
187
     * Generate the complete link (with the a tag).
188
     *
189
     * @param array  $parameters The link parameters (go to LinkTrait to have the list)
190
     * @param string $label      link label
191
     * @param array  $attr       custom attributes
192
     *
193
     * @return string
194
     */
195
    public function victoireLink($parameters, $label, $attr = [], $currentClass = 'active', $url = '#')
196
    {
197
        $referenceLink = UrlGeneratorInterface::ABSOLUTE_PATH;
198
        /** @var Widget $attachedWidget */
199
        $attachedWidget = isset($parameters[Link::TYPE_WIDGET]) ? $parameters[Link::TYPE_WIDGET] : null;
200
201
        if ($parameters['linkType'] == Link::TYPE_WIDGET && $attachedWidget && method_exists($attachedWidget->getWidgetMap()->getView(), 'getUrl')) {
202
            $viewUrl = $this->router->generate('victoire_core_page_show', ['_locale' => $attachedWidget->getWidgetMap()->getView()->getCurrentLocale(), 'url' => $attachedWidget->getWidgetMap()->getView()->getUrl()], $referenceLink);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Victoire\Bundle\CoreBundle\Entity\View as the method getUrl() does only exist in the following sub-classes of Victoire\Bundle\CoreBundle\Entity\View: Victoire\Bundle\BlogBundle\Entity\Blog, Victoire\Bundle\Business...dle\Entity\BusinessPage, Victoire\Bundle\Business...ity\VirtualBusinessPage, Victoire\Bundle\PageBundle\Entity\BasePage, Victoire\Bundle\PageBundle\Entity\Page. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
203
            if ($this->request && (rtrim($this->request->getRequestUri(), '/') == rtrim($viewUrl, '/'))) {
204
                $attr['data-scroll'] = 'smooth';
205
            }
206
        }
207
208
        //Avoid to refresh page if not needed
209 View Code Duplication
        if ($this->request && ($this->request->getRequestUri() == $this->victoireLinkUrl($parameters, false))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
210
            $this->addAttr('class', $currentClass, $attr);
211
        }
212
213
        //Build the target attribute
214
        if ($parameters['target'] == Link::TARGET_MODAL) {
215
            $attr['data-toggle'] = 'viclink-modal';
216
        } elseif ($parameters['target'] == '') {
217
            $attr['target'] = '_parent';
218
        } else {
219
            $attr['target'] = $parameters['target'];
220
        }
221
222
        //Add the analytics tracking code attribute
223
        if (isset($parameters['analyticsTrackCode'])) {
224
            $this->addAttr('onclick', $parameters['analyticsTrackCode'], $attr);
225
        }
226
227
        $url = $this->victoireLinkUrl($parameters, true, $url);
228
        // if modalLayout is set, we add it as GET parameter
229
        if ($parameters['target'] == Link::TARGET_MODAL && !empty($parameters['modalLayout'])) {
230
            $url .= !preg_match('/\?/', $url) ? '?' : '&';
231
            $url .= 'modalLayout='.$parameters['modalLayout'];
232
        }
233
        //Creates a new twig environment
234
        $twig = new \Twig_Environment(new \Twig_Loader_Array(['linkTemplate' => '{{ link|raw }}']));
235
236
        return $twig->render('linkTemplate', ['link' => '<a href="'.$url.'" '.$this->formatAttributes($attr).'>'.$label.'</a>']);
237
    }
238
239
    /**
0 ignored issues
show
introduced by
Doc comment for parameter "$linkAttr" missing
Loading history...
introduced by
Doc comment for parameter "$listAttr" missing
Loading history...
240
     * Generate the complete menu link item (with the li tag).
241
     *
242
     * @param array  $parameters The link parameters (go to LinkTrait to have the list)
243
     * @param string $label      link label
244
     * @param array  $attr       custom attributes
0 ignored issues
show
Bug introduced by
There is no parameter named $attr. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
introduced by
Doc comment for parameter $attr does not match actual variable name $linkAttr
Loading history...
245
     *
246
     * @return string
247
     */
248
    public function victoireMenuLink($parameters, $label, $linkAttr = [], $listAttr = [])
249
    {
250 View Code Duplication
        if ($this->request && ($this->request->getRequestUri() == $this->victoireLinkUrl($parameters, false))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
251
            $this->addAttr('class', 'active', $listAttr);
252
        }
253
254
        return '<li '.$this->formatAttributes($listAttr).'>'.$this->victoireLink($parameters, $label, $linkAttr, false, '#top').'</li>';
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
255
    }
256
257
    public function victoireBusinessLink($businessEntityInstance, $templateId = null, $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
258
    {
259
        if (isset($this->abstractBusinessTemplates[$templateId])) {
260
            $templateId = $this->abstractBusinessTemplates[$templateId];
261
        }
262
        if (!$templateId) {
263
            $templateId = $this->BusinessPageHelper
0 ignored issues
show
Coding Style introduced by
Variable "BusinessPageHelper" is not in valid camel caps format
Loading history...
264
                ->guessBestPatternIdForEntity($businessEntityInstance, $this->em);
265
        }
266
267
        $page = $this->pageHelper->findPageByParameters([
268
            'templateId' => $templateId,
269
            'entityId'   => $businessEntityInstance->getId(),
270
            'locale'     => $this->request ? $this->request->getLocale() : $this->defaultLocale,
271
        ]);
272
273
        $parameters = [
274
            'linkType'        => 'route',
275
            'route'           => 'victoire_core_page_show',
276
            'routeParameters' => [
277
                'url'     => $page->getReference()->getUrl(),
278
                '_locale' => $page->getCurrentLocale(),
279
            ],
280
            'referenceType' => $referenceType,
281
        ];
282
283
        return $this->victoireLinkUrl($parameters);
284
    }
285
286
    /**
287
     * Check if a given Link is active for current request.
288
     *
289
     * @param Link $link
290
     *
291
     * @return bool
292
     */
293
    public function isVicLinkActive(Link $link)
294
    {
295
        return $this->request && ($this->request->getRequestUri() == $this->victoireLinkUrl($link->getParameters(), false));
296
    }
297
298
    /**
299
     * Add a given attribute to given attributes.
300
     *
301
     * @param string $label
302
     * @param string $value
303
     * @param array  $attr  The current attributes array
304
     *
305
     * @return LinkExtension
306
     **/
307
    protected function addAttr($label, $value, &$attr)
308
    {
309
        if (!isset($attr[$label])) {
310
            $attr[$label] = '';
311
        } else {
312
            $attr[$label] .= ' ';
313
        }
314
        $attr[$label] .= $value;
315
316
        return $this;
317
    }
318
319
    /**
320
     * Returns the name of the extension.
321
     *
322
     * @return string The extension name
323
     */
324
    public function getName()
0 ignored issues
show
introduced by
Declare public methods first,then protected ones and finally private ones
Loading history...
325
    {
326
        return 'victoire_link_extension';
327
    }
328
329
    private function formatAttributes($attributes)
330
    {
331
        array_walk($attributes, function (&$item, $key) {
332
            if (is_array($item)) {
333
                $item = implode($item, ' ');
334
            }
335
            $item = $key.'="'.$item.'"';
336
        });
337
338
        return implode($attributes, ' ');
339
    }
340
}
341