Passed
Push — master ( 660904...4d7c06 )
by Esteban De La Fuente
03:42
created

Selector::clear()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 4
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Derafu: Biblioteca PHP (Núcleo).
7
 * Copyright (C) Derafu <https://www.derafu.org>
8
 *
9
 * Este programa es software libre: usted puede redistribuirlo y/o modificarlo
10
 * bajo los términos de la Licencia Pública General Affero de GNU publicada por
11
 * la Fundación para el Software Libre, ya sea la versión 3 de la Licencia, o
12
 * (a su elección) cualquier versión posterior de la misma.
13
 *
14
 * Este programa se distribuye con la esperanza de que sea útil, pero SIN
15
 * GARANTÍA ALGUNA; ni siquiera la garantía implícita MERCANTIL o de APTITUD
16
 * PARA UN PROPÓSITO DETERMINADO. Consulte los detalles de la Licencia Pública
17
 * General Affero de GNU para obtener una información más detallada.
18
 *
19
 * Debería haber recibido una copia de la Licencia Pública General Affero de GNU
20
 * junto a este programa.
21
 *
22
 * En caso contrario, consulte <http://www.gnu.org/licenses/agpl.html>.
23
 */
24
25
namespace Derafu\Lib\Core\Helper;
26
27
use LogicException;
28
use stdClass;
29
30
/**
31
 * Clase para acceder a los datos de un arreglo de manera simplificada.
32
 */
33
class Selector
34
{
35
    /**
36
     * Asigna un valor a un selector.
37
     *
38
     * @param array $data Conjunto de datos.
39
     * @param string $selector Selector donde se almacenará el valor.
40
     * @param mixed $value Valor que se desea almacenar.
41
     */
42 25
    public static function set(
43
        array &$data,
44
        string $selector,
45
        mixed $value
46
    ): void {
47 25
        self::resolveSelector($data, $selector, $value, true);
48
    }
49
50
    /**
51
     * Obtiene un valor almacenado a un selector.
52
     *
53
     * @param array $data Conjunto de datos.
54
     * @param string $selector Selector del valor que se desea obtener.
55
     * @param mixed $default Valor por defecto si la llave no existe.
56
     * @return mixed Valor almacenado o valor por defecto.
57
     */
58 79
    public static function get(
59
        array $data,
60
        string $selector,
61
        mixed $default = null
62
    ): mixed {
63 79
        $value = self::resolveSelector($data, $selector);
64
65 79
        return $value !== null ? $value : $default;
66
    }
67
68
    /**
69
     * Verifica si existe un valor para un selector.
70
     *
71
     * La validación será positiva siempre que existe el índice del selector y
72
     * que no sea `null` el valor que tenga dicho índice.
73
     *
74
     * @param array $data Conjunto de datos.
75
     * @param string $selector Selector que se desea verificar.
76
     * @return bool True si la llave existe, false en caso contrario.
77
     */
78 4
    public static function has(array $data, string $selector): bool
79
    {
80 4
        return self::get($data, $selector) !== null;
81
    }
82
83
    /**
84
     * Elimina el índice de los datos del selector.
85
     *
86
     * Esto elimina el índice del selector, no lo asigna a `null` pues en
87
     * estricto rigor seguiría existiendo el índice en los datos.
88
     *
89
     * @param array $data Conjunto de datos.
90
     * @param string $selector Selector que se desea eliminar.
91
     */
92
    public static function clear(array &$data, string $selector): void
93
    {
94
        // TODO: se debe hacer de forma recursiva resolviendo el selector.
95
        unset($data[$selector]);
96
    }
97
98
    /**
99
     * Resuelve un selector y obtiene/establece el valor.
100
     *
101
     * @param array $data Datos donde buscar/escribir.
102
     * @param string $selector Selector a resolver.
103
     * @param mixed $value Valor a escribir (null si es lectura).
104
     * @param bool $isWrite True si es escritura, false si es lectura.
105
     * @return mixed Valor leído o null si es escritura.
106
     */
107 82
    private static function resolveSelector(
108
        array &$data,
109
        string $selector,
110
        mixed $value = null,
111
        bool $isWrite = false
112
    ): mixed {
113 82
        $parsed = self::parseSelector($selector);
114
115 82
        switch ($parsed->type) {
116 82
            case 'simple':
117 81
                return self::processSimple($data, $parsed, $value, $isWrite);
118 1
            case 'array':
119 1
                return self::processArray($data, $parsed, $value, $isWrite);
120
        }
121
122
        throw new LogicException(sprintf(
123
            'No existe tipo de selector %s para acceder a los datos.',
124
            $parsed->type
125
        ));
126
    }
127
128
    /**
129
     * Parser del selector.
130
     *
131
     * Convierte el string del selector en una estructura que podemos procesar.
132
     *
133
     * @param string $selector Selector a parsear.
134
     * @return stdClass Estructura del selector.
135
     */
136 82
    private static function parseSelector(string $selector): stdClass
137
    {
138
        // Detectar selector de array.
139 82
        if (preg_match('/^(\w+)\[(\d+)\]$/', $selector, $matches)) {
140 1
            return (object) [
141 1
                'type' => 'array',
142 1
                'key' => $matches[1],
143 1
                'index' => (int) $matches[2],
144 1
            ];
145
        }
146
147
        // Si no es uno selector de los previos, tratarlo como selector simple.
148 81
        $parts = explode('.', $selector);
149 81
        return (object) [
150 81
            'type' => 'simple',
151 81
            'parts' => $parts,
152 81
        ];
153
    }
154
155
    /**
156
     * Procesar usando el tipo de selector: "simple".
157
     */
158 81
    private static function processSimple(
159
        array &$data,
160
        stdClass $selector,
161
        mixed $value,
162
        bool $isWrite
163
    ): mixed {
164 81
        $current = &$data;
165 81
        $parts = $selector->parts;
166 81
        $last = count($parts) - 1;
167
168 81
        foreach ($parts as $i => $part) {
169 81
            if ($isWrite && $i === $last) {
170 25
                $current[$part] = $value;
171 25
                return null;
172
            }
173 79
            if (!isset($current[$part])) {
174 60
                if ($isWrite) {
175 2
                    $current[$part] = [];
176
                } else {
177 58
                    return null;
178
                }
179
            }
180 78
            $current = &$current[$part];
181
        }
182
183 77
        return $current;
184
    }
185
186
    /**
187
     * Procesar usando el tipo de selector: "array".
188
     */
189 1
    private static function processArray(
190
        array &$data,
191
        stdClass $selector,
192
        mixed $value,
193
        bool $isWrite
194
    ): mixed {
195 1
        if (!isset($data[$selector->key]) || !is_array($data[$selector->key])) {
196
            if ($isWrite) {
197
                $data[$selector->key] = [];
198
            } else {
199
                return null;
200
            }
201
        }
202
203 1
        if ($isWrite) {
204
            $data[$selector->key][$selector->index] = $value;
205
            return null;
206
        }
207
208 1
        return $data[$selector->key][$selector->index] ?? null;
209
    }
210
}
211