Version::parse()   B
last analyzed

Complexity

Conditions 10
Paths 18

Size

Total Lines 49
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 10.9841

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 28
c 1
b 0
f 0
nc 18
nop 1
dl 0
loc 49
ccs 22
cts 28
cp 0.7856
crap 10.9841
rs 7.6666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 27
    public function __construct(int $major, int $minor, $build = null, $revision = null)
64
    {
65 27
        parent::__construct();
66
67 27
        if ($major < 0) {
68 1
            $args = [
69 1
                'name'   => 'major',
70 1
                'pos'    => 0,
71 1
                'actual' => $major,
72 1
            ];
73
74 1
            $msg  = msg('Invalid argument value.');
75 1
            $msg .= msg(
76 1
                ' "{name}" (position {pos}) must to be a positive number; "{actual}" given.',
77 1
                $args
78 1
            );
79
80 1
            throw new InvalidArgumentException($msg);
81
        }
82
83 26
        if ($minor < 0) {
84 1
            $args = [
85 1
                'name'   => 'minor',
86 1
                'pos'    => 1,
87 1
                'actual' => $minor,
88 1
            ];
89
90 1
            $msg  = msg('Invalid argument value.');
91 1
            $msg .= msg(
92 1
                ' "{name}" (position {pos}) must to be a positive number; "{actual}" given.',
93 1
                $args
94 1
            );
95
96 1
            throw new InvalidArgumentException($msg);
97
        }
98
99 25
        $build    = VersionComponent::parse($build);
100 24
        $revision = VersionComponent::parse($revision);
101
102 22
        $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 28
        $major    = (int) $version[0];
148 28
        $minor    = (int) $version[1];
149 28
        $build    = null;
150 28
        $revision = null;
151
152 28
        if (count($version) >= 3) {
153 22
            $build = VersionComponent::parse($version[2]);
154
155 22
            if (count($version) == 4) {
156 12
                $revision = VersionComponent::parse($version[3]);
157
            }
158
        }
159
160 28
        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 25
    protected function getMajor()
170
    {
171 25
        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 24
            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 14
                $r = $this->major - $other->major;
338
339 14
                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 14
                            $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 8
                        $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