Rut::addThousandsSeparator()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
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 UnexpectedValueException;
28
29
/**
30
 * Clase para trabajar con identificadores RUT, y RUN, de Chile.
31
 */
32
class Rut
33
{
34
    /**
35
     * Se define que un RUT no puede ser menos que 1.000.000.
36
     *
37
     * Si bien legalmente podrían existir RUT o RUN menores a este. En la
38
     * práctica (año 2024) no deberían existir RUT o RUN vigentes, en uso, que
39
     * sean menores a este mínimo definido acá.
40
     */
41
    private const RUT_MIN = 1000000;
42
43
    /**
44
     * Se define que un RUT no puede ser mayor que 99.999.999.
45
     *
46
     * Si bien legalmente podrían existir RUT o RUN mayores a este. En la
47
     * práctica (año 2024) no deberían existir RUT o RUN vigentes, en uso, que
48
     * sean mayores a este máximo definido acá.
49
     */
50
    private const RUT_MAX = 99999999;
51
52
    /**
53
     * Extrae de un RUT las 2 partes: el RUT en si (solo el número) y el dígito
54
     * verificador.
55
     *
56
     * @param string $rut RUT completo, con DV (puntos y guión opcionales).
57
     * @return array Un arreglo con 2 elementos: rut (int) y dv (string).
58
     */
59 7
    public static function toArray(string $rut): array
60
    {
61 7
        $rut = self::removeThousandsSeparatorAndDash($rut);
62 7
        $dv = strtoupper(substr($rut, -1));
63 7
        $rut = substr($rut, 0, -1);
64
65 7
        return [(int) $rut, $dv];
66
    }
67
68
    /**
69
     * Formatea un RUT al formato: 11222333-4.
70
     *
71
     * NOTE: Este método no agrega puntos al RUT, si se desea usar el RUT con
72
     * puntos se debe utilizar formatFull().
73
     *
74
     * @param string|int $rut
75
     * @return string
76
     */
77 3
    public static function format(string|int $rut): string
78
    {
79
        // Si es un string, se espera que venga con DV, el guión es opcional.
80 3
        if (is_string($rut)) {
81 3
            return self::formatFromString($rut);
82
        }
83
84
        // Si es un int, es solo la parte numérica del RUT (sin DV).
85 2
        return self::formatFromInt($rut);
86
    }
87
88
    /**
89
     * Formatea un RUT al formato: 11.222.333-4.
90
     *
91
     * @param string|integer $rut
92
     * @return string
93
     */
94 2
    public static function formatFull(string|int $rut): string
95
    {
96 2
        $rut = self::format($rut);
97 2
        [$rut, $dv] = self::toArray($rut);
98
99 2
        return self::addThousandsSeparator($rut) . '-' . $dv;
100
    }
101
102
    /**
103
     * Calcula el dígito verificador de un RUT.
104
     *
105
     * @param int $rut RUT al que se calculará el dígito verificador.
106
     * @return string Dígito verificador calculado para el RUT indicado.
107
     */
108 6
    public static function calculateDv(int $rut): string
109
    {
110 6
        $s = 1;
111 6
        for ($m = 0; $rut != 0; $rut /= 10) {
112 6
            $rut = (int) $rut;
113 6
            $s = ($s + $rut % 10 * (9 - $m++ % 6)) % 11;
114
        }
115 6
        return strtoupper(chr($s ? $s + 47 : 75));
116
    }
117
118
    /**
119
     * Valida el RUT ingresado.
120
     *
121
     * @param string $rut RUT con dígito verificador (puntos y guión opcionales).
122
     * @throws UnexpectedValueException Si se encontró algún problema al
123
     * validar el RUT.
124
     */
125 4
    public static function validate(string $rut): void
126
    {
127 4
        $originalRut = $rut;
128 4
        [$rut, $dv] = self::toArray($rut);
129
130
        // Validar mínimo del RUT.
131 4
        if ($rut < self::RUT_MIN) {
132 1
            throw new UnexpectedValueException(sprintf(
133 1
                'El RUT no puede ser menor a %s y se encontró el valor %s.',
134 1
                self::addThousandsSeparator(self::RUT_MIN),
135 1
                self::addThousandsSeparator($rut)
136 1
            ));
137
        }
138
139
        // Validar máximo del RUT.
140 3
        if ($rut > self::RUT_MAX) {
141 1
            throw new UnexpectedValueException(sprintf(
142 1
                'El RUT no puede ser mayor a %s y se encontró el valor %s.',
143 1
                self::addThousandsSeparator(self::RUT_MAX),
144 1
                self::addThousandsSeparator($rut)
145 1
            ));
146
        }
147
148
        // Validar que el dígito verificador sea entre 0-9 o 'K'.
149 2
        if (!preg_match('/^[0-9K]$/', $dv)) {
150
            throw new UnexpectedValueException(sprintf(
151
                'El dígito verificador debe ser un caracter entre "0" y "9", o la letra "K" mayúscula. Se encontró el valor "%s".',
152
                $dv
153
            ));
154
        }
155
156
        // Validar que el DV sea correcto para el RUT.
157 2
        $real_dv = self::calculateDv((int) $rut);
158 2
        if ($dv !== $real_dv) {
159 1
            throw new UnexpectedValueException(sprintf(
160 1
                'El dígito verificador del RUT %s es incorrecto. Se encontró el valor "%s" y para la parte numérica %s del RUT se espera que el dígito verificador sea "%s".',
161 1
                self::formatFull($originalRut),
162 1
                $dv,
163 1
                self::addThousandsSeparator($rut),
164 1
                $real_dv
165 1
            ));
166
        }
167
    }
168
169
    /**
170
     * Entrega la parte numérica del RUT a partir de un RUT que tiene DV.
171
     *
172
     * @param string $rut RUT completo, con DV (puntos y guión opcionales).
173
     * @return integer Parte numérica del RUT (no incluye DV).
174
     */
175 1
    public static function removeDv(string $rut): int
176
    {
177 1
        $rut = self::removeThousandsSeparatorAndDash($rut);
178
179 1
        return (int) substr($rut, 0, -1);
180
    }
181
182
    /**
183
     * Agrega el dígito verificador al RUT y lo entrega como un string con el
184
     * DV concatenado al final (sin formato).
185
     *
186
     * @param int $rut RUT en formato número y sin dígito verificador.
187
     * @return string String con el RUT con dígito verificador, sin formato.
188
     */
189 3
    public static function addDv(int $rut): string
190
    {
191 3
        return ((string) $rut) . self::calculateDv($rut);
192
    }
193
194
    /**
195
     * Da formato al RUT a partir de un RUT que venía como string (con DV).
196
     *
197
     * @param string $rut
198
     * @return string
199
     */
200 3
    private static function formatFromString(string $rut): string
201
    {
202 3
        return self::formatAsString(trim($rut));
203
    }
204
205
    /**
206
     * Da formato al RUT a partir de un RUT que venía como número (sin DV).
207
     *
208
     * @param integer $rut
209
     * @return string
210
     */
211 2
    private static function formatFromInt(int $rut): string
212
    {
213 2
        $rut = self::addDv($rut);
214
215 2
        return self::formatAsString($rut);
216
    }
217
218
    /**
219
     * Limpia el RUT quitando el separador de miles (que pude venir con punto
220
     * o comas) y el guión.
221
     *
222
     * @param string $rut RUT completo posiblemente con puntos y guión.
223
     * @return string Entrega el RUT en formato 112223334.
224
     */
225 8
    private static function removeThousandsSeparatorAndDash(string $rut): string
226
    {
227 8
        return str_replace(['.', ',', '-'], '', $rut);
228
    }
229
230
    /**
231
     * Agrega el separador de miles al RUT y lo entrega como string.
232
     *
233
     * @param integer $rut Solo el número del RUT, sin DV.
234
     * @return string Parte numérica del RUT como string con separador de miles.
235
     */
236 4
    private static function addThousandsSeparator(int $rut): string
237
    {
238 4
        return number_format($rut, 0, '', '.');
239
    }
240
241
    /**
242
     * Entrega el RUT formateado como string pero sin puntos.
243
     *
244
     * Básicamente, toma el RUT y:
245
     *
246
     *   - Le quita los separadores de miles.
247
     *   - Se asegura de que tenga guión, si no lo tenía.
248
     *
249
     * @param string $rut RUT con DV (puntos y guión opcionales).
250
     * @return string RUT con DV y guión, sin puntos.
251
     */
252 3
    private static function formatAsString(string $rut): string
253
    {
254 3
        [$rut, $dv] = self::toArray($rut);
255
256 3
        return $rut . '-' . $dv;
257
    }
258
}
259