Test Failed
Push — master ( 52a3ba...d2fd7f )
by Julien
04:33
created

Exposer::checkExpose()   F

Complexity

Conditions 29
Paths 162

Size

Total Lines 114
Code Lines 61

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 168.9213

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 29
eloc 61
c 2
b 0
f 0
nc 162
nop 1
dl 0
loc 114
ccs 27
cts 60
cp 0.45
crap 168.9213
rs 3.65

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
                    $key = $value;
251
                    $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