Completed
Pull Request — master (#228)
by
unknown
02:23
created

Columns   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 283
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 17
Bugs 1 Features 3
Metric Value
wmc 44
c 17
b 1
f 3
lcom 1
cbo 7
dl 0
loc 283
rs 8.3396

4 Methods

Rating   Name   Duplication   Size   Complexity  
A translate() 0 20 4
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\ServiceManager\ServiceLocatorAwareInterface;
5
use Zend\ServiceManager\ServiceLocatorAwareTrait;
6
use Zend\View\Helper\AbstractHelper;
7
use ZfcDatagrid\Column;
8
use ZfcDatagrid\Column\Type;
9
use ZfcDatagrid\Filter;
10
11
/**
12
 * View Helper
13
 */
14
class Columns extends AbstractHelper implements ServiceLocatorAwareInterface
15
{
16
    private $translator;
17
18
    const STYLE_BOLD = 'cellvalue = \'<span style="font-weight: bold;">\' + cellvalue + \'</span>\';';
19
20
    const STYLE_ITALIC = 'cellvalue = \'<span style="font-style: italic;">\' + cellvalue + \'</span>\';';
21
22
    const STYLE_STRIKETHROUGH = 'cellvalue = \'<span style="text-decoration: line-through;">\' + cellvalue + \'</span>\';';
23
24
    use ServiceLocatorAwareTrait;
25
26
    /**
27
     *
28
     * @param  string $message
29
     * @return string
30
     */
31
    private function translate($message)
32
    {
33
        if (false === $this->translator) {
34
            return $message;
35
        }
36
37
        if (null === $this->translator) {
38
            if ($this->getServiceLocator()
39
                ->has('translator')) {
40
                $this->translator = $this->getServiceLocator()
41
                    ->get('translator');
42
            } else {
43
                $this->translator = false;
44
45
                return $message;
46
            }
47
        }
48
49
        return $this->translator->translate($message);
50
    }
51
52
    /**
53
     *
54
     * @param  array  $columns
55
     * @return string
56
     */
57
    public function __invoke(array $columns)
58
    {
59
        $return = [];
60
61
        foreach ($columns as $column) {
62
            /* @var $column \ZfcDatagrid\Column\AbstractColumn */
63
64
            $options = [
65
                'name'  => (string) $column->getUniqueId(),
66
                'index' => (string) $column->getUniqueId(),
67
                'label' => $this->translate((string) $column->getLabel()),
68
69
                'width'    => $column->getWidth(),
70
                'hidden'   => (bool) $column->isHidden(),
71
                'sortable' => (bool) $column->isUserSortEnabled(),
72
                'search'   => (bool) $column->isUserFilterEnabled(),
73
            ];
74
75
            /*
76
             * Formatting
77
             */
78
            $formatter = $this->getFormatter($column);
79
            if ($formatter != '') {
80
                $options['formatter'] = (string) $formatter;
81
            }
82
83
            $alignAlreadyDefined = false;
84
            if ($column->hasStyles()) {
85
                foreach ($column->getStyles() as $style) {
86
                    /** @var \ZfcDatagrid\Column\Style\Align $style */
87
                    if (get_class($style) == 'ZfcDatagrid\Column\Style\Align') {
88
                        $options['align'] = $style->getAlignment();
89
                        $alignAlreadyDefined = true;
90
                        break;
91
                    }
92
                }
93
            }
94
95
            if (!$alignAlreadyDefined && $column->getType() instanceof Type\Number) {
96
                $options['align'] = Column\Style\Align::$RIGHT;
97
            }
98
99
            /*
100
             * Cellattr
101
             */
102
            $rendererParameters = $column->getRendererParameters('jqGrid');
103
            if (isset($rendererParameters['cellattr'])) {
104
                $options['cellattr'] = (string) $rendererParameters['cellattr'];
105
            }
106
            if (isset($rendererParameters['classes'])) {
107
                $options['classes'] = (string) $rendererParameters['classes'];
108
            }
109
110
            /*
111
             * Filtering
112
             */
113
            $searchoptions                = [];
114
            $searchoptions['clearSearch'] = false;
115
            if ($column->hasFilterSelectOptions() === true) {
116
                $options['stype']       = 'select';
117
                $searchoptions['value'] = $column->getFilterSelectOptions();
118
119
                if ($column->hasFilterDefaultValue() === true) {
120
                    $searchoptions['defaultValue'] = $column->getFilterDefaultValue();
121
                } else {
122
                    $searchoptions['defaultValue'] = '';
123
                }
124
            } elseif ($column->hasFilterDefaultValue() === true) {
125
                $filter = new \ZfcDatagrid\Filter();
126
                $filter->setFromColumn($column, $column->getFilterDefaultValue());
127
128
                $searchoptions['defaultValue'] = $filter->getDisplayColumnValue();
129
            }
130
131
            if (count($searchoptions) > 0) {
132
                $options['searchoptions'] = $searchoptions;
133
            }
134
135
            /**
136
             * Because with json_encode we get problems, it's custom made!
137
             */
138
            $colModel = [];
139
            foreach ($options as $key => $value) {
140
                if (is_array($value)) {
141
                    $value = json_encode($value);
142
                } elseif (is_bool($value)) {
143
                    if (true === $value) {
144
                        $value = 'true';
145
                    } else {
146
                        $value = 'false';
147
                    }
148
                } elseif ('formatter' == $key) {
149
                    if (stripos($value, 'formatter') === false && stripos($value, 'function') === false) {
150
                        $value = '"' . $value . '"';
151
                    }
152
                } elseif ('cellattr' == $key) {
153
                    // SKIP THIS
154
                } else {
155
                    $value = '"' . $value . '"';
156
                }
157
158
                $colModel[] = (string) $key . ': ' . $value;
159
            }
160
161
            $return[] = '{' . implode(',', $colModel) . '}';
162
        }
163
164
        return '[' . implode(',', $return) . ']';
165
    }
166
167
    /**
168
     *
169
     * @param  Column\AbstractColumn $column
170
     * @return string
171
     */
172
    private function getFormatter(Column\AbstractColumn $column)
173
    {
174
        /*
175
         * User defined formatter
176
         */
177
        $rendererParameters = $column->getRendererParameters('jqGrid');
178
        if (isset($rendererParameters['formatter'])) {
179
            return $rendererParameters['formatter'];
180
        }
181
182
        /*
183
         * Formatter based on column options + styles
184
         */
185
        $formatter = '';
186
187
        $formatter .= implode(' ', $this->getStyles($column));
188
189
        switch (get_class($column->getType())) {
190
191
            case 'ZfcDatagrid\Column\Type\PhpArray':
192
                $formatter .= 'cellvalue = \'<pre>\' + cellvalue.join(\'<br />\') + \'</pre>\';';
193
                break;
194
        }
195
196
        if ($column instanceof Column\Action) {
197
            $formatter .= ' cellvalue = cellvalue; ';
198
        }
199
200
        if ($formatter != '') {
201
            $prefix = 'function (cellvalue, options, rowObject) {';
202
            $suffix = ' return cellvalue; }';
203
204
            $formatter = $prefix . $formatter . $suffix;
205
        }
206
207
        return $formatter;
208
    }
209
210
    /**
211
     *
212
     * @param  Column\AbstractColumn $col
213
     * @throws \Exception
214
     * @return array
215
     */
216
    private function getStyles(Column\AbstractColumn $col)
217
    {
218
        $styleFormatter = [];
219
220
        /*
221
         * First all based on value (only one works) @todo
222
         */
223
        foreach ($col->getStyles() as $style) {
224
            $prepend = '';
225
            $append  = '';
226
227
            /* @var $style \ZfcDatagrid\Column\Style\AbstractStyle */
228
            foreach ($style->getByValues() as $rule) {
229
                $colString = $rule['column']->getUniqueId();
230
                $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...
231
                switch ($rule['operator']) {
232
233
                    case Filter::EQUAL:
234
                        $operator = '==';
235
                        break;
236
237
                    case Filter::NOT_EQUAL:
238
                        $operator = '!=';
239
                        break;
240
241
                    default:
242
                        throw new \Exception('Currently not supported filter operation: "' . $rule['operator'] . '"');
243
                        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...
244
                }
245
246
                $prepend = 'if (rowObject.' . $colString . ' ' . $operator . ' \'' . $rule['value'] . '\') {';
247
                $append .= '}';
248
            }
249
250
            $styleString = '';
251
            switch (get_class($style)) {
252
253
                case 'ZfcDatagrid\Column\Style\Bold':
254
                    $styleString = self::STYLE_BOLD;
255
                    break;
256
257
                case 'ZfcDatagrid\Column\Style\Italic':
258
                    $styleString = self::STYLE_ITALIC;
259
                    break;
260
261
                case 'ZfcDatagrid\Column\Style\Strikethrough':
262
                    $styleString = self::STYLE_STRIKETHROUGH;
263
                    break;
264
265
                case 'ZfcDatagrid\Column\Style\Color':
266
                    $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...
267
                    break;
268
269
                case 'ZfcDatagrid\Column\Style\CSSClass':
270
                    $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...
271
                    break;
272
273
                case 'ZfcDatagrid\Column\Style\BackgroundColor':
274
                    // do NOTHING! this is done by loadComplete event...
275
                    // At this stage jqgrid haven't created the columns...
276
                    break;
277
278
                case 'ZfcDatagrid\Column\Style\Html':
279
                    // do NOTHING! just pass the HTML!
280
                    break;
281
282
                case 'ZfcDatagrid\Column\Style\Align':
283
                    $styleString = 'cellvalue = \'<span style="text-align: ' . $style->getAlignment() . ';">\' + 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 getAlignment() does only exist in the following sub-classes of ZfcDatagrid\Column\Style\AbstractStyle: ZfcDatagrid\Column\Style\Align. 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...
284
                    break;
285
286
                default:
287
                    throw new \Exception('Not defined style: "' . get_class($style) . '"');
288
                    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...
289
            }
290
291
            $styleFormatter[] = $prepend . $styleString . $append;
292
        }
293
294
        return $styleFormatter;
295
    }
296
}
297