Passed
Branch master (0b4ab1)
by Esteban De La Fuente
74:02 queued 50:02
created

JsonContainer::applyDefaults()   B

Complexity

Conditions 8
Paths 5

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 8

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 10
c 1
b 0
f 0
nc 5
nop 2
dl 0
loc 20
ccs 10
cts 10
cp 1
crap 8
rs 8.4444
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\Support\Store;
26
27
use Derafu\Lib\Core\Support\Store\Abstract\AbstractStore;
28
use Derafu\Lib\Core\Support\Store\Contract\DataContainerInterface;
29
use InvalidArgumentException;
30
use Opis\JsonSchema\Errors\ErrorFormatter;
31
use Opis\JsonSchema\Helper;
32
use Opis\JsonSchema\Validator;
33
use stdClass;
34
35
/**
36
 * Clase para contenedor de datos estructurados con JSON Schema.
37
 */
38
class JsonContainer extends AbstractStore implements DataContainerInterface
39
{
40
    /**
41
     * Configuración del schema de datos.
42
     *
43
     * @var stdClass
44
     */
45
    protected ?stdClass $schema = null;
46
47
    /**
48
     * Instancia que representa el formateador de los errores del validador.
49
     *
50
     * @var ErrorFormatter
51
     */
52
    private ErrorFormatter $formatter;
53
54
    /**
55
     * Constructor del contenedor.
56
     *
57
     * @param array $data Datos iniciales.
58
     * @param array $schema Schema inicial.
59
     */
60 12
    public function __construct(array $data = [], array $schema = [])
61
    {
62 12
        $this->formatter = new ErrorFormatter();
63 12
        $this->setSchema($schema);
64 12
        $this->data = $this->resolve($data, $this->schema);
0 ignored issues
show
Bug introduced by
It seems like $this->schema can also be of type null; however, parameter $schema of Derafu\Lib\Core\Support\...sonContainer::resolve() does only seem to accept stdClass, 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

64
        $this->data = $this->resolve($data, /** @scrutinizer ignore-type */ $this->schema);
Loading history...
65
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70 12
    public function setSchema(array $schema): static
71
    {
72 12
        $schema = array_merge([
73 12
            '$schema' => 'https://json-schema.org/draft/2020-12/schema',
74 12
            'type' => 'object',
75 12
        ], $schema);
76
77 12
        $this->schema = Helper::toJSON($schema);
0 ignored issues
show
Documentation Bug introduced by
It seems like Opis\JsonSchema\Helper::toJSON($schema) can also be of type array or array. However, the property $schema is declared as type stdClass. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
78
79 12
        return $this;
80
    }
81
82
    /**
83
     * {@inheritdoc}
84
     */
85 1
    public function getSchema(): array
86
    {
87 1
        return json_decode(json_encode($this->schema), true);
88
    }
89
90
    /**
91
     * {@inheritdoc}
92
     */
93
    public function validate(): void
94
    {
95
        $this->resolve($this->data, $this->schema);
0 ignored issues
show
Bug introduced by
It seems like $this->schema can also be of type null; however, parameter $schema of Derafu\Lib\Core\Support\...sonContainer::resolve() does only seem to accept stdClass, 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

95
        $this->resolve($this->data, /** @scrutinizer ignore-type */ $this->schema);
Loading history...
96
    }
97
98
    /**
99
     * Valida y resuelve datos usando un schema JSON.
100
     *
101
     * @param array $data Datos a validar.
102
     * @param stdClass $schema Schema JSON a usar.
103
     * @return array Datos validados y con valores por defecto aplicados.
104
     */
105 12
    private function resolve(array $data, stdClass $schema): array
106
    {
107 12
        if (!isset($schema->properties)) {
108 2
            return $data;
109
        }
110
111 10
        $data = $this->applyDefaults($data, $schema);
112 10
        $json = json_decode(json_encode($data));
113
114 10
        $validator = new Validator();
115 10
        $result = $validator->validate($json, $schema);
116
117 10
        if ($result->hasError()) {
118 5
            $errors = [];
119 5
            foreach ($this->formatter->format($result->error()) as $section => $messages) {
120 5
                foreach ($messages as $message) {
121 5
                    $errors[] = $message . ' in ' . $section . '.';
122
                }
123
            }
124 5
            throw new InvalidArgumentException(sprintf(
125 5
                'Error al validar el esquema JSON de los datos. %s',
126 5
                implode(' ', $errors)
127 5
            ));
128
        }
129
130 5
        return $data;
131
    }
132
133
    /**
134
     * Aplica valores por defecto recursivamente.
135
     *
136
     * @param array $data Datos a procesar.
137
     * @param stdClass $schema Schema con valores por defecto.
138
     * @return array Datos con valores por defecto aplicados.
139
     */
140 10
    private function applyDefaults(array $data, stdClass $schema): array
141
    {
142 10
        foreach ($schema->properties as $key => $property) {
143
            // Aplicar valor por defecto si la propiedad no existe.
144 10
            if (!isset($data[$key]) && isset($property->default)) {
145 4
                $data[$key] = $property->default;
146
            }
147
148
            // Recursión para objetos anidados.
149
            if (
150 10
                isset($data[$key])
151 10
                && isset($property->type)
152 10
                && $property->type === 'object'
153 10
                && isset($property->properties)
154
            ) {
155 3
                $data[$key] = $this->applyDefaults($data[$key], $property);
156
            }
157
        }
158
159 10
        return $data;
160
    }
161
}
162