fetchDataFromFileSource()   A
last analyzed

Complexity

Conditions 5
Paths 8

Size

Total Lines 29
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 20
c 1
b 0
f 0
nc 8
nop 1
dl 0
loc 29
ccs 0
cts 20
cp 0
crap 30
rs 9.2888
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\Package\Prime\Component\Entity\Worker;
26
27
use ArrayObject;
28
use Derafu\Lib\Core\Foundation\Abstract\AbstractWorker;
29
use Derafu\Lib\Core\Helper\Arr;
30
use Derafu\Lib\Core\Package\Prime\Component\Entity\Contract\DatasourceProviderWorkerInterface;
31
use Derafu\Lib\Core\Package\Prime\Component\Entity\Exception\DatasourceProviderException;
32
use Psr\SimpleCache\CacheInterface;
33
use Symfony\Component\Yaml\Yaml;
34
35
/**
36
 * Worker "prime.entity.datasource_provider".
37
 */
38
class DatasourceProviderWorker extends AbstractWorker implements DatasourceProviderWorkerInterface
39
{
40
    /**
41
     * Esquema de configuración del worker.
42
     *
43
     * @var array
44
     */
45
    protected array $configurationSchema = [
46
        'normalization' => [
47
            'types' => 'array',
48
            'default' => [],
49
            'schema' => [
50
                'idAttribute' => [
51
                    'types' => 'string',
52
                    'default' => 'id',
53
                ],
54
                'nameAttribute' => [
55
                    'types' => 'string',
56
                    'default' => 'name',
57
                ],
58
            ],
59
        ],
60
    ];
61
62
    /**
63
     * Listado de fuentes de datos de repositorios de entidades.
64
     *
65
     * Es una mapa que contiene en el índice la clase de la entidad asociada al
66
     * origen y en el valor el origen.
67
     *
68
     * Si el índice no es una clase válida se mapeará a una clase de entidad
69
     * estándar por defecto.
70
     *
71
     * La entidad puede proporcionar un repositorio personalizado para su
72
     * gestión.
73
     *
74
     * Un mismo origen (valor) pueden estar en diferentes entidades (índice).
75
     *
76
     * @var array<string, string>
77
     */
78
    private array $sources;
79
80
    /**
81
     * Instancia para acceder a una caché a buscar los datos.
82
     *
83
     * @var CacheInterface|null
84
     */
85
    private ?CacheInterface $cache;
86
87
    /**
88
     * Orígenes de datos en memoria que ya han sido cargado sus datos.
89
     *
90
     * @var array<string,ArrayObject>
91
     */
92
    private array $loaded;
93
94
    /**
95
     * Constructor del worker.
96
     *
97
     * @param array<string,string> $sources Origenes de datos (ID y origen).
98
     */
99
    public function __construct(array $sources = [], ?CacheInterface $cache = null)
100
    {
101
        $this->sources = $sources;
102
        $this->cache = $cache;
103
    }
104
105
    /**
106
     * {@inheritDoc}
107
     */
108
    public function fetch(string $source): ArrayObject
109
    {
110
        // Si el origen no está cargado se carga.
111
        if (!isset($this->loaded[$source])) {
112
            $data = $this->fetchData($source);
113
114
            $normalizationConfig = $this->getConfiguration()->get(
115
                'normalization'
116
            );
117
            $data = $this->normalizeData($data, $normalizationConfig);
118
119
            $this->loaded[$source] = new ArrayObject($data);
120
        }
121
122
        // Entregar los datos cargados del origen.
123
        return $this->loaded[$source];
124
    }
125
126
    /**
127
     * Centraliza la carga de datos para un origen.
128
     *
129
     * Esto permite cargar los datos desde una caché, archivos o en el futuro
130
     * otros orígenes donde puedan estar los datos.
131
     *
132
     * @param string $source
133
     * @return array
134
     */
135
    private function fetchData(string $source): array
136
    {
137
        // Cargar los datos del origen desde una caché.
138
        $data = $this->fetchDataFromCacheSource($source);
139
        if ($data !== null) {
140
            return $data;
141
        }
142
143
        // Si no hay fuente de datos para el origen se genera un error.
144
        if (!isset($this->sources[$source])) {
145
            throw new DatasourceProviderException(sprintf(
146
                'No existe un origen de datos configurado para de %s.',
147
                $source
148
            ));
149
        }
150
151
        // Cargar los datos del origen desde un archivo.
152
        $data = $this->fetchDataFromFileSource($source);
153
154
        // Guardar los datos en caché.
155
        if (isset($this->cache)) {
156
            $key = $this->createCacheKey($source);
157
            $this->cache->set($key, $data);
0 ignored issues
show
Bug introduced by
The method set() does not exist on null. ( Ignorable by Annotation )

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

157
            $this->cache->/** @scrutinizer ignore-call */ 
158
                          set($key, $data);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
158
        }
159
160
        // Entregar los datos encontrados.
161
        return $data;
162
    }
163
164
    /**
165
     * Carga los datos del origen desde una caché (si está disponible).
166
     *
167
     * @param string $source
168
     * @return array|null
169
     */
170
    private function fetchDataFromCacheSource(string $source): ?array
171
    {
172
        $key = $this->createCacheKey($source);
173
174
        if (isset($this->cache) && $this->cache->has($key)) {
175
            return $this->cache->get($key);
176
        }
177
178
        return null;
179
    }
180
181
    /**
182
     * Carga los datos del origen desde un archivo.
183
     *
184
     * El archivo puede ser: .php, .json o .yaml
185
     *
186
     * @param string $source
187
     * @return array
188
     */
189
    private function fetchDataFromFileSource(string $source): array
190
    {
191
        $filepath = $this->sources[$source];
192
193
        $extension = strtolower(pathinfo($filepath, PATHINFO_EXTENSION));
0 ignored issues
show
Bug introduced by
It seems like pathinfo($filepath, Dera...ker\PATHINFO_EXTENSION) can also be of type array; however, parameter $string of strtolower() 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

193
        $extension = strtolower(/** @scrutinizer ignore-type */ pathinfo($filepath, PATHINFO_EXTENSION));
Loading history...
194
195
        switch ($extension) {
196
            case 'php':
197
                $data = require $filepath;
198
                break;
199
            case 'json':
200
                $data = json_decode(file_get_contents($filepath), true);
201
                break;
202
            case 'yaml':
203
                $data = Yaml::parseFile(file_get_contents($filepath));
204
                break;
205
            default:
206
                $data = $this->handleExtension($source, $filepath, $extension);
207
        }
208
209
        if (!is_array($data)) {
210
            throw new DatasourceProviderException(sprintf(
211
                'Los datos del origen %s no son válidos para ser usados como origen de datos. Ruta: %s.',
212
                $source,
213
                $filepath
214
            ));
215
        }
216
217
        return $data;
218
    }
219
220
    /**
221
     * Maneja el caso cuando una extensión de archivo no es soportada.
222
     *
223
     * Funciona como "hook" para personalizar mediante herencia el
224
     * comportamiento.
225
     *
226
     * @param string $source
227
     * @param string $filepath
228
     * @param string $extension
229
     * @return array
230
     */
231
    protected function handleExtension(
232
        string $source,
233
        string $filepath,
234
        string $extension
235
    ): array {
236
        throw new DatasourceProviderException(sprintf(
237
            'Formato de archivo %s del origen de datos %s no es soportado. Ruta: %s.',
238
            $extension,
239
            $source,
240
            $filepath
241
        ));
242
    }
243
244
    /**
245
     * Normaliza los datos en caso que sea un arreglo de valores y no un arreglo
246
     * de arreglos.
247
     *
248
     * @param array $data
249
     * @return array<int|string, array>
250
     */
251
    private function normalizeData(array $data, array $config): array
252
    {
253
        $nameAttribute = $config['nameAttribute'];
254
255
        $data = array_map(function ($entity) use ($nameAttribute) {
256
            if (!is_array($entity)) {
257
                return [
258
                    $nameAttribute => $entity,
259
                ];
260
            }
261
            return $entity;
262
        }, $data);
263
264
        return Arr::addIdAttribute($data, $config['idAttribute']);
265
    }
266
267
    /**
268
     * Crea la llave a partir del identificador del origen de datos.
269
     *
270
     * @param string $source Identificador del origen de datos.
271
     * @return string Llave para utilizar con la caché.
272
     */
273
    private function createCacheKey(string $source): string
274
    {
275
        return 'datasource:' . $source;
276
    }
277
}
278