Completed
Pull Request — master (#230)
by Oliver
03:58
created

Columns   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 281
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 17
Bugs 1 Features 5
Metric Value
wmc 43
c 17
b 1
f 5
lcom 1
cbo 5
dl 0
loc 281
rs 8.3157

5 Methods

Rating   Name   Duplication   Size   Complexity  
A setTranslator() 0 6 1
A translate() 0 8 2
F __invoke() 0 109 22
B getFormatter() 0 37 5
C getStyles() 0 80 13

How to fix   Complexity   

Complex Class

Complex classes like Columns often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Columns, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace ZfcDatagrid\Renderer\JqGrid\View\Helper;
3
4
use Zend\View\Helper\AbstractHelper;
5
use ZfcDatagrid\Column;
6
use ZfcDatagrid\Column\Type;
7
use ZfcDatagrid\Filter;
8
9
/**
10
 * View Helper
11
 */
12
class Columns extends AbstractHelper
13
{
14
    /** @var  \Zend\I18n\Translator\Translator|null|false */
15
    private $translator;
16
17
    const STYLE_BOLD = 'cellvalue = \'<span style="font-weight: bold;">\' + cellvalue + \'</span>\';';
18
19
    const STYLE_ITALIC = 'cellvalue = \'<span style="font-style: italic;">\' + cellvalue + \'</span>\';';
20
21
    const STYLE_STRIKETHROUGH = 'cellvalue = \'<span style="text-decoration: line-through;">\' + cellvalue + \'</span>\';';
22
23
    /**
24
     * @param  false|null|\Zend\I18n\Translator\Translator $translator
25
     * @return self
26
     */
27
    public function setTranslator($translator)
28
    {
29
        $this->translator = $translator;
30
31
        return $this;
32
    }
33
34
    /**
35
     *
36
     * @param  string $message
37
     * @return string
38
     */
39
    private function translate($message)
40
    {
41
        if (null === $this->translator) {
42
            return $message;
43
        }
44
45
        return $this->translator->translate($message);
46
    }
47
48
    /**
49
     *
50
     * @param  array  $columns
51
     * @return string
52
     */
53
    public function __invoke(array $columns)
54
    {
55
        $return = [];
56
57
        foreach ($columns as $column) {
58
            /* @var $column \ZfcDatagrid\Column\AbstractColumn */
59
60
            $options = [
61
                'name'  => (string) $column->getUniqueId(),
62
                'index' => (string) $column->getUniqueId(),
63
                'label' => $this->translate((string) $column->getLabel()),
64
65
                'width'    => $column->getWidth(),
66
                'hidden'   => (bool) $column->isHidden(),
67
                'sortable' => (bool) $column->isUserSortEnabled(),
68
                'search'   => (bool) $column->isUserFilterEnabled(),
69
            ];
70
71
            /*
72
             * Formatting
73
             */
74
            $formatter = $this->getFormatter($column);
75
            if ($formatter != '') {
76
                $options['formatter'] = (string) $formatter;
77
            }
78
79
            $alignAlreadyDefined = false;
80
            if ($column->hasStyles()) {
81
                foreach ($column->getStyles() as $style) {
82
                    /** @var \ZfcDatagrid\Column\Style\Align $style */
83
                    if (get_class($style) == 'ZfcDatagrid\Column\Style\Align') {
84
                        $options['align']    = $style->getAlignment();
85
                        $alignAlreadyDefined = true;
86
                        break;
87
                    }
88
                }
89
            }
90
91
            if (!$alignAlreadyDefined && $column->getType() instanceof Type\Number) {
92
                $options['align'] = Column\Style\Align::$RIGHT;
93
            }
94
95
            /*
96
             * Cellattr
97
             */
98
            $rendererParameters = $column->getRendererParameters('jqGrid');
99
            if (isset($rendererParameters['cellattr'])) {
100
                $options['cellattr'] = (string) $rendererParameters['cellattr'];
101
            }
102
            if (isset($rendererParameters['classes'])) {
103
                $options['classes'] = (string) $rendererParameters['classes'];
104
            }
105
106
            /*
107
             * Filtering
108
             */
109
            $searchoptions                = [];
110
            $searchoptions['clearSearch'] = false;
111
            if ($column->hasFilterSelectOptions() === true) {
112
                $options['stype']       = 'select';
113
                $searchoptions['value'] = $column->getFilterSelectOptions();
114
115
                if ($column->hasFilterDefaultValue() === true) {
116
                    $searchoptions['defaultValue'] = $column->getFilterDefaultValue();
117
                } else {
118
                    $searchoptions['defaultValue'] = '';
119
                }
120
            } elseif ($column->hasFilterDefaultValue() === true) {
121
                $filter = new \ZfcDatagrid\Filter();
122
                $filter->setFromColumn($column, $column->getFilterDefaultValue());
123
124
                $searchoptions['defaultValue'] = $filter->getDisplayColumnValue();
125
            }
126
127
            if (count($searchoptions) > 0) {
128
                $options['searchoptions'] = $searchoptions;
129
            }
130
131
            /**
132
             * Because with json_encode we get problems, it's custom made!
133
             */
134
            $colModel = [];
135
            foreach ($options as $key => $value) {
136
                if (is_array($value)) {
137
                    $value = json_encode($value);
138
                } elseif (is_bool($value)) {
139
                    if (true === $value) {
140
                        $value = 'true';
141
                    } else {
142
                        $value = 'false';
143
                    }
144
                } elseif ('formatter' == $key) {
145
                    if (stripos($value, 'formatter') === false && stripos($value, 'function') === false) {
146
                        $value = '"' . $value . '"';
147
                    }
148
                } elseif ('cellattr' == $key) {
149
                    // SKIP THIS
150
                } else {
151
                    $value = '"' . $value . '"';
152
                }
153
154
                $colModel[] = (string) $key . ': ' . $value;
155
            }
156
157
            $return[] = '{' . implode(',', $colModel) . '}';
158
        }
159
160
        return '[' . implode(',', $return) . ']';
161
    }
162
163
    /**
164
     *
165
     * @param  Column\AbstractColumn $column
166
     * @return string
167
     */
168
    private function getFormatter(Column\AbstractColumn $column)
169
    {
170
        /*
171
         * User defined formatter
172
         */
173
        $rendererParameters = $column->getRendererParameters('jqGrid');
174
        if (isset($rendererParameters['formatter'])) {
175
            return $rendererParameters['formatter'];
176
        }
177
178
        /*
179
         * Formatter based on column options + styles
180
         */
181
        $formatter = '';
182
183
        $formatter .= implode(' ', $this->getStyles($column));
184
185
        switch (get_class($column->getType())) {
186
187
            case 'ZfcDatagrid\Column\Type\PhpArray':
188
                $formatter .= 'cellvalue = \'<pre>\' + cellvalue.join(\'<br />\') + \'</pre>\';';
189
                break;
190
        }
191
192
        if ($column instanceof Column\Action) {
193
            $formatter .= ' cellvalue = cellvalue; ';
194
        }
195
196
        if ($formatter != '') {
197
            $prefix = 'function (cellvalue, options, rowObject) {';
198
            $suffix = ' return cellvalue; }';
199
200
            $formatter = $prefix . $formatter . $suffix;
201
        }
202
203
        return $formatter;
204
    }
205
206
    /**
207
     *
208
     * @param  Column\AbstractColumn $col
209
     * @throws \Exception
210
     * @return array
211
     */
212
    private function getStyles(Column\AbstractColumn $col)
213
    {
214
        $styleFormatter = [];
215
216
        /*
217
         * First all based on value (only one works) @todo
218
         */
219
        foreach ($col->getStyles() as $style) {
220
            $prepend = '';
221
            $append  = '';
222
223
            /* @var $style \ZfcDatagrid\Column\Style\AbstractStyle */
224
            foreach ($style->getByValues() as $rule) {
225
                $colString = $rule['column']->getUniqueId();
226
                $operator  = '';
0 ignored issues
show
Unused Code introduced by
$operator is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
227
                switch ($rule['operator']) {
228
229
                    case Filter::EQUAL:
230
                        $operator = '==';
231
                        break;
232
233
                    case Filter::NOT_EQUAL:
234
                        $operator = '!=';
235
                        break;
236
237
                    default:
238
                        throw new \Exception('Currently not supported filter operation: "' . $rule['operator'] . '"');
239
                        break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
240
                }
241
242
                $prepend = 'if (rowObject.' . $colString . ' ' . $operator . ' \'' . $rule['value'] . '\') {';
243
                $append .= '}';
244
            }
245
246
            $styleString = '';
247
            switch (get_class($style)) {
248
249
                case 'ZfcDatagrid\Column\Style\Bold':
250
                    $styleString = self::STYLE_BOLD;
251
                    break;
252
253
                case 'ZfcDatagrid\Column\Style\Italic':
254
                    $styleString = self::STYLE_ITALIC;
255
                    break;
256
257
                case 'ZfcDatagrid\Column\Style\Strikethrough':
258
                    $styleString = self::STYLE_STRIKETHROUGH;
259
                    break;
260
261
                case 'ZfcDatagrid\Column\Style\Color':
262
                    $styleString = 'cellvalue = \'<span style="color: #' . $style->getRgbHexString() . ';">\' + cellvalue + \'</span>\';';
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ZfcDatagrid\Column\Style\AbstractStyle as the method getRgbHexString() does only exist in the following sub-classes of ZfcDatagrid\Column\Style\AbstractStyle: ZfcDatagrid\Column\Style\AbstractColor, ZfcDatagrid\Column\Style\BackgroundColor, ZfcDatagrid\Column\Style\Color. 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...
263
                    break;
264
265
                case 'ZfcDatagrid\Column\Style\CSSClass':
266
                    $styleString = 'cellvalue = \'<span class="' . $style->getClass() . '">\' + cellvalue + \'</span>\';';
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ZfcDatagrid\Column\Style\AbstractStyle as the method getClass() does only exist in the following sub-classes of ZfcDatagrid\Column\Style\AbstractStyle: ZfcDatagrid\Column\Style\CSSClass. 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...
267
                    break;
268
269
                case 'ZfcDatagrid\Column\Style\BackgroundColor':
270
                    // do NOTHING! this is done by loadComplete event...
271
                    // At this stage jqgrid haven't created the columns...
272
                    break;
273
274
                case 'ZfcDatagrid\Column\Style\Html':
275
                    // do NOTHING! just pass the HTML!
276
                    break;
277
278
                case 'ZfcDatagrid\Column\Style\Align':
279
                    // do NOTHING! we have to add the align style in the gridcell and not in a span!
280
                    break;
281
282
                default:
283
                    throw new \Exception('Not defined style: "' . get_class($style) . '"');
284
                    break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
285
            }
286
287
            $styleFormatter[] = $prepend . $styleString . $append;
288
        }
289
290
        return $styleFormatter;
291
    }
292
}
293