Completed
Push — master ( 8158f1...90d7fd )
by Konstantinos
11:17 queued 06:55
created

LinkToFunction::__invoke()   B

Complexity

Conditions 6
Paths 9

Size

Total Lines 29
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 6.1666

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 29
ccs 10
cts 12
cp 0.8333
rs 8.439
cc 6
eloc 20
nc 9
nop 8
crap 6.1666

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
namespace BZIon\Twig;
4
5
class LinkToFunction
6
{
7
    /**
8
     * Get a link literal to a Model
9
     *
10
     * @param          $context
11
     * @param  \Model  $model     The model we want to link to
12
     * @param  string  $icon      A font awesome icon identifier to show instead of text
13
     * @param  string  $action    The action to link to (e.g show or edit)
14
     * @param  bool $linkAll   Whether to link to inactive or deleted models
15
     * @param  string  $class     The CSS class(es) to apply to the link
16
     * @param  bool $forceText Whether to show both the icon and text
17
     * @param  string  $content   Override the content that will automatically be used
18
     *
19
     * @return string The HTML link
20
     */
21 1
    public function __invoke(
22
        $context,
23
        $model,
24
        $icon = null,
25
        $action = 'show',
26
        $linkAll = false,
27
        $class = '',
28
        $forceText = false,
29
        $content = ''
30
    ) {
31 1
        if ($content === '' || $content === null) {
32 1
            $content = $this->getContent($model, $icon, $forceText);
33 1
        } elseif ($icon) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $icon 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...
34
            $content = "<i class=\"fa fa-$icon\"></i> " . $content;
35
        }
36
37 1
        if ($this->isLinkable($model, $linkAll, $context)) {
38 1
            $params = array();
39 1
            if ($linkAll) {
40
                $params['showDeleted'] = true;
41
            }
42
43 1
            $url = $model->getURL($action, false, $params);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Model as the method getURL() does only exist in the following sub-classes of Model: AliasModel, AvatarModel, Ban, Conversation, Invitation, Map, Match, News, NewsCategory, Page, Player, Role, Server, Team, UrlModel. 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...
44
45 1
            return '<a' . $this->getClass($class) . ' href="' . $url . '">' . $content . '</a>';
46
        }
47
48 1
        return '<span' . $this->getClass("$class disabled-link") . '>' . $content . '</span>';
49
    }
50
51
    /**
52
     * Get the content of the link to show
53
     *
54
     * @param  \Model  $model     The model we want to link to
55
     * @param  string  $icon      A font awesome icon identifier to show instead of text
56
     * @param  bool $forceText Whether to show both the icon and text
57
     * @return string  The link's content
58
     */
59 1
    private function getContent($model, $icon, $forceText)
60
    {
61 1
        $content = "";
62
63 1
        if ($icon) {
64
            $content .= "<i class=\"fa fa-$icon\"></i>";
65
66
            if ($forceText) {
67
                $content .= " ";
68
            }
69
        }
70
71 1
        if (!$icon || $forceText) {
72 1
            $content .= \Model::escape($this->getModelName($model));
73
        }
74
75 1
        return $content;
76
    }
77
78
    /**
79
     * Get the name of any model
80
     *
81
     * @param  \Model $model
82
     * @return string The name of the model
83
     */
84 1
    private function getModelName($model)
85
    {
86 1
        if ($model instanceof \NamedModel) {
87 1
            return $model->getName();
88
        }
89
        if ($model instanceof \AliasModel) {
90
            return $model->getAlias();
91
        }
92
93
        return $model->getId();
94
    }
95
96
    /**
97
     * Create a CSS class string
98
     *
99
     * @param  $class string The CSS class(es), without `class=".."`
100
     * @return string
101
     */
102 1
    private function getClass($class)
103
    {
104 1
        if (trim($class) == '') {
105 1
            return $class;
106
        }
107
108 1
        return ' class="' . $class . '"';
109
    }
110
111
    /**
112
     * Find out if a link should be provided to an object, instead of just a
113
     * reference to its name
114
     *
115
     * @param  \Model $model   The model to test
116
     * @param  bool   $linkAll Whether to link deleted and inactive models
117
     * @param  array  $context Twig's context
118
     * @return bool
119
     */
120 1
    private function isLinkable($model, $linkAll, &$context)
121
    {
122
        // Models that don't have a URL can't be linked
123 1
        if (!$model instanceof \UrlModel) {
124 1
            return false;
125
        }
126
127 1
        if ($linkAll) {
128
            return true;
129
        }
130
131 1
        if (!$context['app']) {
132
            // Only link active models by default
133
            return $model->isActive();
134
        }
135
136 1
        return $context['app']->getController()->canSee($model);
137
    }
138
139 1
    public static function get()
140
    {
141 1
        return new \Twig_SimpleFunction('link_to', new self(), array(
142 1
            'is_safe'       => array('html'),
143
            'needs_context' => true
144
        ));
145
    }
146
}
147