Passed
Push — master ( 9b7aae...d3e42f )
by Julien
05:12 queued 18s
created

Exposer::getValue()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3.1406

Importance

Changes 0
Metric Value
cc 3
eloc 3
c 0
b 0
f 0
nc 4
nop 2
dl 0
loc 5
ccs 3
cts 4
cp 0.75
crap 3.1406
rs 10
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 2
    public static function createBuilder(mixed $object, ?array $columns = null, ?bool $expose = null, ?bool $protected = null): Builder
43
    {
44 2
        $expose ??= true;
45 2
        $protected ??= false;
46
        
47 2
        $builder = new Builder();
48 2
        $builder->setColumns(self::parseColumnsRecursive($columns));
49 2
        $builder->setExpose($expose);
50 2
        $builder->setProtected($protected);
51 2
        $builder->setValue($object);
52 2
        return $builder;
53
    }
54
    
55 1
    private static function getValue(string $string, mixed $value): string
56
    {
57 1
        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 1
            : mb_sprintf($string, $value);
60
    }
61
    
62 2
    private static function checkExpose(Builder $builder): void
63
    {
64 2
        $columns = $builder->getColumns();
65 2
        $fullKey = $builder->getFullKey();
66 2
        $value = $builder->getValue();
67
        
68
        // Check if the key itself exists at first
69 2
        if (isset($columns[$fullKey])) {
70 2
            $column = $columns[$fullKey];
71
            
72
            // If boolean, set expose to the boolean value
73 2
            if (is_bool($column)) {
74 2
                $builder->setExpose($column);
75
            }
76
            
77
            // If callable, set the expose to true, and run the method and passes the builder as parameter
78 1
            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 1
            elseif (is_string($column)) {
114 1
                $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 2
            $parentKey = $fullKey;
122 2
            $parentIndex = strrpos($parentKey, '.');
123
            do {
124 2
                $parentKey = $parentIndex ? substr($parentKey, 0, $parentIndex) : '';
125 2
                if (isset($columns[$parentKey])) {
126 2
                    $column = $columns[$parentKey];
127 2
                    if (is_bool($column)) {
128 2
                        $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 2
                    break;
152
                    // break at the first parent found
153
                }
154
            }
155 1
            while ($parentIndex = strrpos($parentKey, '.'));
156
        }
157
        
158 2
        if (isset($fullKey) && !empty($columns)) {
159
            // Try to find a subentity, or field that has the true value
160 2
            $value = $builder->getValue();
161 2
            if (is_array($value) || is_object($value) || is_callable($value)) {
162 2
                foreach ($columns as $columnKey => $columnValue) {
163 2
                    if ($columnValue === true && str_starts_with($columnKey, $fullKey)) {
164
                        // expose the current instance (which is the parent of the sub column)
165 1
                        $builder->setExpose(true);
166
                    }
167
                }
168
            }
169
        }
170
        
171
        // check for protected setting
172 2
        $key = $builder->getKey();
173 2
        if (!$builder->getProtected() && is_string($key) && str_starts_with($key, '_')) {
174
            $builder->setExpose(false);
175
        }
176
    }
177
    
178
    /**
179
     * @return array|false|Model
180
     */
181 2
    public static function expose(Builder $builder)
182
    {
183 2
        $columns = $builder->getColumns();
184 2
        $value = $builder->getValue();
185
        
186 2
        if (is_array($value) || is_object($value)) {
187 2
            $toParse = is_object($value) && method_exists($value, 'toArray')
188
                ? $value->toArray()
189 2
                : $value;
190
            
191
            // si accused column demandé et que l'expose est à false
192 2
            if (is_null($columns) && !$builder->getExpose()) {
193
                return [];
194
            }
195
            
196
            // Prépare l'array de retour des fields de l'instance
197 2
            $ret = [];
198 2
            $currentContextKey = $builder->getContextKey();
199 2
            $builder->setContextKey($builder->getFullKey());
200 2
            foreach ($toParse as $fieldKey => $fieldValue) {
201 2
                $builder->setParent($value);
202 2
                $builder->setKey($fieldKey);
203 2
                $builder->setValue($fieldValue);
204 2
                self::checkExpose($builder);
205 2
                if ($builder->getExpose()) {
206 2
                    $ret [$fieldKey] = self::expose($builder);
207
                }
208
            }
209 2
            $builder->setContextKey($currentContextKey);
210
        }
211
        else {
212 2
            $ret = $builder->getExpose() ? $value : null;
213
        }
214
        
215 2
        return $ret;
216
    }
217
    
218
    /**
219
     * Here to parse the columns parameter into some kind of flatten array with
220
     * the key path separated by dot "my.path" and the value true, false or a callback function
221
     * including the ExposeBuilder object
222
     *
223
     * @param array|null $columns
224
     * @param string|null $context
225
     *
226
     * @return array|null
227
     */
228 2
    public static function parseColumnsRecursive(?array $columns = null, ?string $context = null): ?array
229
    {
230 2
        if (!isset($columns)) {
231 1
            return null;
232
        }
233 2
        $ret = [];
234 2
        foreach ($columns as $key => $value) {
235 2
            if (is_bool($key)) {
236
                $value = $key;
237
                $key = null;
238
            }
239
            
240 2
            if (is_int($key)) {
241 2
                if (is_string($value)) {
242 1
                    $key = $value;
243 1
                    $value = true;
244
                }
245
                else {
246 2
                    $key = null;
247
                }
248
            }
249
            
250 2
            if (is_string($key)) {
251 2
                $key = trim(mb_strtolower($key));
252
            }
253
            
254 2
            if (is_string($value) && empty($value)) {
255
                $value = true;
256
            }
257 2
            $currentKey = (!empty($context) ? $context . (!empty($key) ? '.' : null) : null) . $key;
258 2
            if (is_array($value) || is_object($value)) {
259 1
                if (is_callable($value)) {
260
                    $ret[$currentKey] = $value;
261
                }
262
                else {
263 1
                    $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

263
                    $subRet = self::parseColumnsRecursive(/** @scrutinizer ignore-type */ $value, $currentKey);
Loading history...
264 1
                    $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

264
                    $ret = array_merge_recursive($ret, /** @scrutinizer ignore-type */ $subRet);
Loading history...
265 1
                    if (!isset($ret[$currentKey])) {
266 1
                        $ret[$currentKey] = false;
267
                    }
268
                }
269
            }
270
            else {
271 2
                $ret[$currentKey] = $value;
272
            }
273
        }
274
        
275 2
        return $ret;
276
    }
277
}
278