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

LinkToFunction   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 142
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 4

Test Coverage

Coverage 76.19%

Importance

Changes 2
Bugs 0 Features 1
Metric Value
wmc 21
c 2
b 0
f 1
lcom 0
cbo 4
dl 0
loc 142
ccs 32
cts 42
cp 0.7619
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
B getContent() 0 18 5
A getModelName() 0 11 3
A getClass() 0 8 2
A get() 0 7 1
B __invoke() 0 29 6
A isLinkable() 0 18 4
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