Passed
Push — master ( ec8404...680b0c )
by Nelson
01:44 queued 12s
created

Version::equals()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
eloc 6
nc 5
nop 1
dl 0
loc 13
rs 9.2222
c 0
b 0
f 0
ccs 7
cts 7
cp 1
crap 6
1
<?php
2
3
/**
4
 * PHP: Nelson Martell Library file
5
 *
6
 * Copyright © 2015-2021 Nelson Martell (http://nelson6e65.github.io)
7
 *
8
 * Licensed under The MIT License (MIT)
9
 * For full copyright and license information, please see the LICENSE
10
 * Redistributions of files must retain the above copyright notice.
11
 *
12
 * @copyright 2015-2021 Nelson Martell
13
 * @link      http://nelson6e65.github.io/php_nml/
14
 * @since     0.1.1
15
 * @license   http://www.opensource.org/licenses/mit-license.php The MIT License (MIT)
16
 * */
17
18
declare(strict_types=1);
19
20
namespace NelsonMartell;
21
22
use InvalidArgumentException;
23
24
/**
25
 * Representa el número de versión de un programa o ensamblado, de la forma "1.2.3.4". Sólo
26
 * siendo obligatorios el primer y segundo componente.
27
 * No se puede heredar esta clase.
28
 *
29
 * @author Nelson Martell <[email protected]>
30
 * @since 0.1.1
31
 *
32
 * @property-read int               $major    Obtiene el valor del componente principal del número de versión. Esta
33
 *   propiedad es de sólo lectura.
34
 * @property-read int               $minor    Obtiene el valor del componente secundario del número de versión. Esta
35
 *   propiedad es de sólo lectura.
36
 * @property-read VersionComponent  $build    Obtiene el valor del componente de compilación del número de versión.
37
 *   Esta propiedad es de sólo lectura.
38
 * @property-read VersionComponent  $revision Obtiene el valor del componente de revisión del número de versión. Esta
39
 *   propiedad es de sólo lectura.
40
 *
41
 * */
42
final class Version extends StrictObject implements IEquatable, IComparable, IMagicPropertiesContainer
43
{
44
    /**
45
     * The version metadata.
46
     *
47
     * @var array
48
     */
49
    private $versionMetaData = [];
50
51
    /**
52
     * Crea una nueva instancia con los números principal, secundario, de
53
     * compilación (opcional) y revisión (opcional).
54
     * Para comprobar si la versión es válida, usar el método isValid.
55
     *
56
     * @param int                              $major    Componente principal
57
     * @param int                              $minor    Componente secundario
58
     * @param int|string|VersionComponent|null $build    Componente de compilación
59
     * @param int|string|VersionComponent|null $revision Componente de revisión
60
     *
61
     * @throws InvalidArgumentException
62
     * */
63 28
    public function __construct(int $major, int $minor, $build = null, $revision = null)
64
    {
65 28
        parent::__construct();
66
67 28
        if ($major < 0) {
68
            $args = [
69
                'name'   => 'major',
70
                'pos'    => 0,
71
                'actual' => $major,
72
            ];
73
74 1
            $msg  = msg('Invalid argument value.');
75 1
            $msg .= msg(
76
                ' "{name}" (position {pos}) must to be a positive number; "{actual}" given.',
77
                $args
78
            );
79
80 1
            throw new InvalidArgumentException($msg);
81
        }
82
83 27
        if ($minor < 0) {
84
            $args = [
85
                'name'   => 'minor',
86
                'pos'    => 1,
87
                'actual' => $minor,
88
            ];
89
90 1
            $msg  = msg('Invalid argument value.');
91 1
            $msg .= msg(
92
                ' "{name}" (position {pos}) must to be a positive number; "{actual}" given.',
93
                $args
94
            );
95
96 1
            throw new InvalidArgumentException($msg);
97
        }
98
99 26
        $build    = VersionComponent::parse($build);
100 25
        $revision = VersionComponent::parse($revision);
101
102 23
        $this->versionMetaData = compact('major', 'minor', 'build', 'revision');
103
    }
104
105
    /**
106
     * Convierte una cadena a su representación del tipo Version.
107
     *
108
     * @param Version|string|int|float|array $value Objeto a convertir.
109
     *
110
     * @return Version Objeto convertido desde $value.
111
     * */
112 34
    public static function parse($value): Version
113
    {
114 34
        if ($value instanceof Version) {
115
            return $value;
116
        }
117
118 34
        $version = [];
119
120
        // Try to convert into an array
121 34
        if (is_integer($value)) {
122
            // Integer for major value
123
            $version = [$value, 0];
124 34
        } elseif (is_float($value)) {
125
            // Integer part as major, and decimal part as minor
126
            $version = sprintf('%F', $value);
127
            $version = explode('.', $version);
128 34
        } elseif (is_array($value)) {
129
            // Implode first 4 places for major, minor, build and revision respectivally.
130 10
            $version = array_slice($value, 0, 4);
131 24
        } elseif (is_string($value)) {
0 ignored issues
show
introduced by
The condition is_string($value) is always true.
Loading history...
132 24
            $version = explode('.', $value);
133
        } else {
134
            $msg = msg('Unable to parse. Argument passed has an invalid type: "{0}".', typeof($value));
135
            throw new InvalidArgumentException($msg);
136
        }
137
138
        // $value ya debería ser un array.
139 34
        $c = count($version);
140
141 34
        if ($c > 4 || $c < 2) {
142 7
            $msg = msg('Unable to parse. Argument passed has an invalid format: "{0}".', $value);
143 7
            throw new InvalidArgumentException($msg);
144
        }
145
146
147 29
        $major    = (int) $version[0];
148 29
        $minor    = (int) $version[1];
149 29
        $build    = null;
150 29
        $revision = null;
151
152 29
        if (count($version) >= 3) {
153 23
            $build = VersionComponent::parse($version[2]);
154
155 23
            if (count($version) == 4) {
156 12
                $revision = VersionComponent::parse($version[3]);
157
            }
158
        }
159
160 29
        return new Version($major, $minor, $build, $revision);
161
    }
162
163
    /**
164
     * Getter for major property.
165
     *
166
     * @return int
167
     * @see    Version::$major
168
     */
169 26
    protected function getMajor()
170
    {
171 26
        return $this->versionMetaData['major'];
172
    }
173
174
    /**
175
     * Getter for minor property.
176
     *
177
     * @return int
178
     * @see    Version::$minor
179
     */
180 18
    protected function getMinor()
181
    {
182 18
        return $this->versionMetaData['minor'];
183
    }
184
185
    /**
186
     * Getter for build property.
187
     *
188
     * @return VersionComponent
189
     * @see    Version::$build
190
     */
191 17
    protected function getBuild(): VersionComponent
192
    {
193 17
        return $this->versionMetaData['build'];
194
    }
195
196
    /**
197
     * Getter for revision property.
198
     *
199
     * @return VersionComponent
200
     * @see    Version::$revision
201
     */
202 12
    protected function getRevision(): VersionComponent
203
    {
204 12
        return $this->versionMetaData['revision'];
205
    }
206
207
208
    /**
209
     * Convierte la instancia actual en su representación en cadena.
210
     * Por defecto, si no están definidos los componentes de compilación y
211
     * revisión, no se incluyen en la salida.
212
     * Use el método isValid si quiere determinar si la versión es válida
213
     * antes de devolver esta cadena.
214
     *
215
     * @return string Representación de la versión en forma de cadena:
216
     *   'major.minor[.build[.revision]]'
217
     * @see    VersionComponent::isNull()
218
     * @see    Version::isValid()
219
     * */
220 8
    public function toString(): string
221
    {
222 8
        $s[0] = $this->major;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$s was never initialized. Although not strictly required by PHP, it is generally a good practice to add $s = array(); before regardless.
Loading history...
223 8
        $s[1] = $this->minor;
224
225 8
        if ($this->revision->isNotNull()) {
226 3
            $s[2] = $this->build;
227 3
            $s[3] = $this->revision;
228
        } else {
229 5
            if ($this->build->isNotNull()) {
230 3
                $s[2] = $this->build;
231
            }
232
        }
233 8
        $v = implode('.', $s);
234
235 8
        return $v;
236
    }
237
238
    /**
239
     * Indica si la instancia actual es un número de versión válido.
240
     *
241
     * Se considera válido si:
242
     * 1. major o minor es mayor a cero (0). No puede ser '0.0'.
243
     * 2. build y revision son nulos (no están definidos).
244
     * 3. build está definido pero revision no.
245
     * 4. Ambos están definidos, pero no poseen la parte de la cadena.
246
     * 5. Ambos están definidos, pero build no posee la parte de cadena.
247
     * 6. build está definido y tiene la cadena, pero revision no está definido.
248
     * 7. revision posee cadena, pero build no.
249
     *
250
     * @return bool Un valor que indica si la instancia actual es válida.
251
     * */
252 17
    public function isValid(): bool
253
    {
254
        // Validación de major y minor:
255 17
        $r = ($this->major > 0 || $this->minor > 0); // 1
256
257
        // Validación de build y revision:
258 17
        if ($r) {
259 16
            $r = ($this->build->isNull() && $this->revision->isNull()); // 2
260
261 16
            if (!$r) {
262 14
                if ($this->build->isNotNull() && $this->revision->isNotNull()) {
263
                    // Si ambos están definidos...
264
265 9
                    $r = $this->build->stringValue == ''; // 5
266
267 9
                    if (!$r) {
268
                        // 4
269 6
                        $r = ($this->build->stringValue == '') && ($this->revision->stringValue == '');
270
271 6
                        if (!$r) {
272 6
                            if ($this->build->stringValue != '') {
273 6
                                $r = $this->revision->isNull(); // 6
274
                            }
275
276 6
                            if ($this->revision->stringValue != '') {
277 9
                                $r = ($this->build->stringValue == ''); // 7
278
                            }
279
                        }
280
                    }
281
                } else {
282 5
                    $r = ($this->build->isNotNull() && $this->revision->isNull()); // 3
283
                }
284
            }
285
        }
286
287 17
        return $r;
288
    }
289
290
    /**
291
     * Indicates whether the specified object is equal to the current instance.
292
     *
293
     * @param Version|mixed $other The other object to compare with.
294
     *
295
     * @return bool
296
     * */
297 33
    public function equals($other): bool
298
    {
299 33
        if ($other instanceof Version) {
300 25
            if ($this->major == $other->major && $this->minor == $other->minor) {
301 16
                if ($this->build->equals($other->build)) {
302 11
                    if ($this->revision->equals($other->revision)) {
303 10
                        return true;
304
                    }
305
                }
306
            }
307
        }
308
309 30
        return false;
310
    }
311
312
313
    // region IComparable
314
315
    /**
316
     * Determina la posición relativa de esta instancia con respecto al objeto especificado.
317
     *
318
     * For types different than `Version` (`null`, unparseable `string` and other value types) are considered lower
319
     * than `Version`.
320
     *
321
     * @param Version|string|mixed $other The other object to compare with. If this is of type `string`, it will
322
     *   try to convert to a `Version` object before the comparation.
323
     *
324
     * @return int|null
325
     *   - ``= 0`` if this instance is considered equivalent to $other;
326
     *   - ``> 0`` si esta instancia se considera mayor a $other;
327
     *   - ``< 0`` si esta instancia se considera menor a $other.
328
     *   - ``null`` if this instance can't be compared against $other .
329
     * @see \NelsonMartell\Extensions\Objects::compare()
330
     * */
331 25
    public function compareTo($other)
332
    {
333 25
        $r = $this->equals($other) ? 0 : 9999;
334
335 25
        if ($r !== 0) {
336 23
            if ($other instanceof Version) {
337 15
                $r = $this->major - $other->major;
338
339 15
                if ($r === 0) {
340 7
                    $r = $this->minor - $other->minor;
341
342 7
                    if ($r === 0) {
343 6
                        $r = $this->build->compareTo($other->build);
344
345 6
                        if ($r === 0) {
346 15
                            $r = $this->revision->compareTo($other->revision);
347
                        }
348
                    }
349
                }
350 18
            } elseif (typeof($other)->isValueType() || $other === null) {
351 16
                $r = 1;
352
353 16
                if (typeof($other)->name === 'string' || typeof($other)->name === 'array') {
354
                    try {
355 11
                        $tmp = Version::parse($other);
356 9
                        $r   = $this->compareTo($tmp);
357 4
                    } catch (InvalidArgumentException $e) {
358
                        // Siempre es mayor a strings o arrays que no se puedan convertir
359 16
                        $r = 1;
360
                    }
361
                }
362
            } else {
363
                // No se puede determinar comparando a otros objetos.
364 2
                $r = null;
365
            }
366
        }
367
368 25
        return $r;
369
    }
370
371
    // endregion
372
}
373