Test Failed
Push — master ( 537660...c4a471 )
by Julien
06:00
created

Exposer   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 244
Duplicated Lines 0 %

Test Coverage

Coverage 61.6%

Importance

Changes 3
Bugs 1 Features 0
Metric Value
eloc 128
c 3
b 1
f 0
dl 0
loc 244
ccs 77
cts 125
cp 0.616
rs 4.5599
wmc 58

5 Methods

Rating   Name   Duplication   Size   Complexity  
A getValue() 0 5 3
A createBuilder() 0 11 1
F checkExpose() 0 114 29
B expose() 0 42 10
F parseColumnsRecursive() 0 48 15

How to fix   Complexity   

Complex Class

Complex classes like Exposer 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.

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 Exposer, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * This file is part of the Zemit Framework.
5
 *
6
 * (c) Zemit Team <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE.txt
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Zemit\Support\Exposer;
13
14
use Zemit\Mvc\Model;
15
16
/**
17
 * Class Expose
18
 * @todo rewrite this code
19
 * @todo write unit test for this
20
 *
21
 * Example
22
 *
23
 * Simple
24
 * ```php
25
 * $this->expose() // expose everything except protected properties
26
 * $this->expose(null, true, true); // expose everything including protected properties
27
 * $this->expose(array('Source.id' => false)) // expose everything except 'id' and protected properties
28
 * $this->expose(array('Source' => array('id', 'title')) // expose only 'id' and 'title'
29
 * $this->expose(array('Source' => true, false) // expose everything from Source except protected properties
30
 * $this->expose(array('Source.Sources' => true, false) // expose everything from Source->Sources except protected properties
31
 * $this->expose(array('Source.Sources' => array('id'), false) // expose only the 'id' field of the sub array "Sources"
32
 * $this->expose(array('Source.Sources' => array(true, 'id' => false), false) // expose everything from the sub array "Sources" except the 'id' field
33
 * $this->expose(array('Source' => array(false, 'Sources' => array(true, 'id' => false))) // expose everything from the sub array "Sources" except the 'id' field
34
 * ```
35
 * Complexe
36
 *
37
 *
38
 * @package Zemit\Mvc\Model\Expose
39
 */
40
class Exposer
41
{
42 1
    public static function createBuilder($object, ?array $columns = null, ?bool $expose = null, ?bool $protected = null): Builder
43
    {
44 1
        $expose ??= true;
45 1
        $protected ??= false;
46
        
47 1
        $builder = new Builder();
48 1
        $builder->setColumns(self::parseColumnsRecursive($columns));
49 1
        $builder->setExpose($expose);
50 1
        $builder->setProtected($protected);
51 1
        $builder->setValue($object);
52 1
        return $builder;
53
    }
54
    
55
    private static function getValue(string $string, $value): string
56
    {
57
        return (is_array($value) || is_object($value))
0 ignored issues
show
Bug Best Practice introduced by
The expression return is_array($value) ...printf($string, $value) could return the type false which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
58
            ? sprintfn($string, $value)
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type object; however, parameter $args of sprintfn() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

58
            ? sprintfn($string, /** @scrutinizer ignore-type */ $value)
Loading history...
59
            : mb_sprintf($string, $value);
60
    }
61
    
62 1
    private static function checkExpose(Builder $builder): void
63
    {
64 1
        $columns = $builder->getColumns();
65 1
        $fullKey = $builder->getFullKey();
66 1
        $value = $builder->getValue();
67
        
68
        // Check if the key itself exists at first
69 1
        if (isset($columns[$fullKey])) {
70 1
            $column = $columns[$fullKey];
71
            
72
            // If boolean, set expose to the boolean value
73 1
            if (is_bool($column)) {
74 1
                $builder->setExpose($column);
75
            }
76
            
77
            // If callable, set the expose to true, and run the method and passes the builder as parameter
78
            elseif (is_callable($column)) {
79
                $builder->setExpose(true);
80
                $callbackReturn = $column($builder);
81
                
82
                // If builder is returned, no nothing
83
                if ($callbackReturn instanceof BuilderInterface) {
84
                    $builder = $callbackReturn;
85
                }
86
                
87
                // If string is returned, set expose to true and parse with mb_sprintfn or mb_sprintf
88
                elseif (is_string($callbackReturn)) {
89
                    $builder->setExpose(true);
90
                    $builder->setValue(self::getValue($callbackReturn, $value));
91
                }
92
                
93
                // If bool is returned, set expose to boolean value
94
                elseif (is_bool($callbackReturn)) {
95
                    $builder->setExpose($callbackReturn);
96
                }
97
                
98
                // If array is returned, parse the columns from the current context key and merge it with the builder
99
                elseif (is_array($callbackReturn)) {
100
                    $columns = self::parseColumnsRecursive($callbackReturn, $builder->getFullKey());
101
                    
102
                    // If not set, set expose to false by default
103
                    if (!isset($columns[$builder->getFullKey()])) {
104
                        $columns[$builder->getFullKey()] = false;
105
                    }
106
                    
107
                    //@TODO handle with array_merge_recursive and handle array inside the columns parameters ^^
108
                    $builder->setColumns(array_merge($builder->getColumns(), $columns));
0 ignored issues
show
Bug introduced by
It seems like $builder->getColumns() can also be of type null; however, parameter $arrays of array_merge() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

108
                    $builder->setColumns(array_merge(/** @scrutinizer ignore-type */ $builder->getColumns(), $columns));
Loading history...
109
                }
110
            }
111
            
112
            // If is string, set expose to true and parse with mb_sprintfn or mb_sprintf
113
            elseif (is_string($column)) {
114
                $builder->setExpose(true);
115 1
                $builder->setValue(self::getValue($column, $value));
116
            }
117
        }
118
        
119
        // Otherwise, check if a parent key exists
120
        else {
121 1
            $parentKey = $fullKey;
122 1
            $parentIndex = false;
123
            do {
124 1
                $parentKey = $parentIndex? substr($parentKey, 0, $parentIndex) : '';
125 1
                if (isset($columns[$parentKey])) {
126 1
                    $column = $columns[$parentKey];
127 1
                    if (is_bool($column)) {
128 1
                        $builder->setExpose($column);
129
                    }
130
                    elseif (is_callable($column)) {
131
                        $builder->setExpose(true);
132
                        $callbackReturn = $column($builder);
133
                        if ($callbackReturn instanceof BuilderInterface) {
134
                            $builder = $callbackReturn;
135
                        }
136
                        elseif (is_string($callbackReturn)) {
137
                            $builder->setExpose(true);
138
                            $builder->setValue(self::getValue($callbackReturn, $value));
139
                        }
140
                        elseif (is_bool($callbackReturn)) {
141
                            $builder->setExpose($callbackReturn);
142
                        }
143
                        elseif (is_array($callbackReturn)) {
144
                            // Since it is a parent, we're not supposed to re-re-merge the existing columns
145
                        }
146
                    }
147
                    elseif (is_string($column)) {
148
                        $builder->setExpose(false);
149
                        $builder->setValue(self::getValue($column, $value));
150
                    }
151 1
                    break;
152
                    // break at the first parent found
153
                }
154 1
            } while ($parentIndex = strrpos($parentKey, '.'));
155
        }
156
        
157
        // Try to find a subentity, or field that has the true value
158 1
        $value = $builder->getValue();
159 1
        if ((is_array($value) || is_object($value) || is_callable($value))) {
160 1
            $subColumns = is_null($columns) ? $columns : array_filter($columns, function ($columnValue, $columnKey) use ($builder) {
0 ignored issues
show
Unused Code introduced by
The assignment to $subColumns is dead and can be removed.
Loading history...
161
                
162 1
                $ret = strpos($columnKey, $builder->getFullKey()) === 0;
0 ignored issues
show
Bug introduced by
It seems like $builder->getFullKey() can also be of type null; however, parameter $needle of strpos() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

162
                $ret = strpos($columnKey, /** @scrutinizer ignore-type */ $builder->getFullKey()) === 0;
Loading history...
163 1
                if ($ret && $columnValue === true) {
164
                    // expose the current instance (which is the parent of the sub column)
165
                    $builder->setExpose(true);
166
                }
167
                
168 1
                return $ret;
169 1
            }, ARRAY_FILTER_USE_BOTH);
170
        }
171
        
172
        // check for protected setting
173 1
        $key = $builder->getKey();
174 1
        if (!$builder->getProtected() && is_string($key) && strpos($key, '_') === 0) {
175
            $builder->setExpose(false);
176
        }
177
    }
178
    
179
    /**
180
     * @return array|false|Model
181
     */
182 1
    public static function expose(Builder $builder)
183
    {
184 1
        $columns = $builder->getColumns();
185 1
        $value = $builder->getValue();
186
        
187 1
        if (is_array($value) || is_object($value)) {
188 1
            $toParse = [];
189 1
            if (is_array($value)) {
190 1
                $toParse = $value;
191
            }
192 1
            elseif (method_exists($value, 'toArray')) {
193
                $toParse = $value->toArray();
194
            }
195
            else {
196 1
                $toParse = $value;
197
            }
198
            
199
            // si aucune column demandé et que l'expose est à false
200 1
            if (is_null($columns) && !$builder->getExpose()) {
201
                return [];
202
            }
203
            
204
            // Prépare l'array de retour des fields de l'instance
205 1
            $ret = [];
206 1
            $currentContextKey = $builder->getContextKey();
207 1
            $builder->setContextKey($builder->getFullKey());
208 1
            foreach ($toParse as $fieldKey => $fieldValue) {
209 1
                $builder->setParent($value);
210 1
                $builder->setKey($fieldKey);
211 1
                $builder->setValue($fieldValue);
212 1
                self::checkExpose($builder);
213 1
                if ($builder->getExpose()) {
214 1
                    $ret [$fieldKey] = self::expose($builder);
215
                }
216
            }
217 1
            $builder->setContextKey($currentContextKey);
218
        }
219
        else {
220 1
            $ret = $builder->getExpose() ? $value : false;
221
        }
222
        
223 1
        return $ret;
224
    }
225
    
226
    /**
227
     * Here to parse the columns parameter into some kind of flatten array with
228
     * the key path separated by dot "my.path" and the value true, false or a callback function
229
     * including the ExposeBuilder object
230
     *
231
     * @param array|null $columns
232
     * @param string|null $context
233
     *
234
     * @return array|null
235
     */
236 1
    public static function parseColumnsRecursive(?array $columns = null, ?string $context = null): ?array
237
    {
238 1
        if (!isset($columns)) {
239 1
            return null;
240
        }
241 1
        $ret = [];
242 1
        foreach ($columns as $key => $value) {
243 1
            if (is_bool($key)) {
244
                $value = $key;
245
                $key = null;
246
            }
247
            
248 1
            if (is_int($key)) {
249 1
                if (is_string($value)) {
250 1
                    $key = $value;
251 1
                    $value = true;
252
                }
253
                else {
254 1
                    $key = null;
255
                }
256
            }
257
            
258 1
            if (is_string($key)) {
259 1
                $key = trim(mb_strtolower($key));
260
            }
261
            
262 1
            if (is_string($value) && empty($value)) {
263
                $value = true;
264
            }
265 1
            $currentKey = (!empty($context) ? $context . (!empty($key) ? '.' : null) : null) . $key;
266 1
            if (is_array($value) || is_object($value)) {
267
                if (is_callable($value)) {
268
                    $ret[$currentKey] = $value;
269
                }
270
                else {
271
                    $subRet = self::parseColumnsRecursive($value, $currentKey);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type object; however, parameter $columns of Zemit\Support\Exposer\Ex...parseColumnsRecursive() does only seem to accept array|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

271
                    $subRet = self::parseColumnsRecursive(/** @scrutinizer ignore-type */ $value, $currentKey);
Loading history...
272
                    $ret = array_merge_recursive($ret, $subRet);
0 ignored issues
show
Bug introduced by
It seems like $subRet can also be of type null; however, parameter $arrays of array_merge_recursive() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

272
                    $ret = array_merge_recursive($ret, /** @scrutinizer ignore-type */ $subRet);
Loading history...
273
                    if (!isset($ret[$currentKey])) {
274
                        $ret[$currentKey] = false;
275
                    }
276
                }
277
            }
278
            else {
279 1
                $ret[$currentKey] = $value;
280
            }
281
        }
282
        
283 1
        return $ret;
284
    }
285
}
286