AbstractDocument   A
last analyzed

Complexity

Total Complexity 14

Size/Duplication

Total Lines 168
Duplicated Lines 0 %

Test Coverage

Coverage 91.43%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 33
dl 0
loc 168
ccs 32
cts 35
cp 0.9143
rs 10
c 2
b 0
f 0
wmc 14

9 Methods

Rating   Name   Duplication   Size   Complexity  
A tryCreateFromString() 0 11 2
A isValid() 0 17 3
A assert() 0 7 3
A extractCheckerDigit() 0 3 1
A __toString() 0 3 1
A __construct() 0 12 1
A __set() 0 3 1
A extractBaseNumber() 0 3 1
A __get() 0 3 1
1
<?php
2
3
namespace Brazanation\Documents;
4
5
/**
6
 * Class AbstractDocument
7
 *
8
 * @package Brazanation\Documents
9
 *
10
 * @property string $number
11
 * @property string $digit
12
 * @property int $length
13
 * @property int $numberOfDigits
14
 * @property string $type
15
 */
16
abstract class AbstractDocument implements DigitCalculable, Formattable
17
{
18
    /**
19
     * @var string
20
     */
21
    protected $number;
22
23
    /**
24
     * @var string
25
     */
26
    protected $digit;
27
28
    /**
29
     * @var int
30
     */
31
    protected $length;
32
33
    /**
34
     * @var int
35
     */
36
    protected $numberOfDigits;
37
38
    /**
39
     * @var string
40
     */
41
    protected $type;
42
43
    /**
44
     * AbstractDocument constructor.
45
     *
46
     * @param string $number Numeric section with checker digit.
47
     * @param int $length Max length of document.
48
     * @param int $numberOfDigits Max length of checker digits.
49
     * @param string $type Document name/type.
50
     */
51 1136
    public function __construct(
52
        string $number,
53
        int $length,
54
        int $numberOfDigits,
55
        string $type
56
    ) {
57 1136
        $this->type = $type;
58 1136
        $this->numberOfDigits = $numberOfDigits;
59 1136
        $this->length = $length;
60 1136
        $this->digit = $this->extractCheckerDigit($number);
61 1136
        $this->assert($number);
62 888
        $this->number = $number;
63
    }
64
65 5
    public function __get(string $name)
66
    {
67 5
        return $this->$name;
68
    }
69
70
    public function __set(string $name, string $value)
71
    {
72
        throw Exception\Readonly::notAllowed(static::class, $name);
73
    }
74
75
    /**
76
     * Create a Document object from given number.
77
     *
78
     * @param string $number Numeric section with checker digit.
79
     *
80
     * @return AbstractDocument|boolean Returns a new Document instance or FALSE on failure.
81
     */
82
    abstract public static function createFromString(string $number);
83
84
    /**
85
     * Try to create a Document object from given number.
86
     *
87
     * @param string $number Numeric section with checker digit.
88
     * @param int $length Max length of document.
89
     * @param int $numberOfDigits Max length of checker digits.
90
     * @param string $type Document name/type.
91
     *
92
     * @return AbstractDocument|boolean Returns a new Document instance or FALSE on failure.
93
     */
94 122
    protected static function tryCreateFromString(
95
        string $class,
96
        string $number,
97
        int $length,
98
        int $numberOfDigits,
99
        string $type
100
    ) {
101
        try {
102 122
            return new $class($number, $length, $numberOfDigits, $type);
103 71
        } catch (Exception\InvalidDocument $exception) {
104 71
            return false;
105
        }
106
    }
107
108
    /**
109
     * Handle number to string.
110
     *
111
     * @return string
112
     */
113 883
    public function __toString() : string
114
    {
115 883
        return "{$this->number}";
116
    }
117
118
    /**
119
     * Check if document number is valid.
120
     *
121
     * @param string $number Numeric section with checker digit.
122
     *
123
     * @throws Exception\InvalidDocument when number is empty
124
     * @throws Exception\InvalidDocument when number is not valid
125
     */
126 1136
    protected function assert(string $number)
127
    {
128 1136
        if (empty($number)) {
129
            throw Exception\InvalidDocument::notEmpty($this->type);
130
        }
131 1136
        if (!$this->isValid($number)) {
132 248
            throw Exception\InvalidDocument::isNotValid($this->type, $number);
133
        }
134
    }
135
136
    /**
137
     * Validates number is a valid.
138
     *
139
     * @param string $number Numeric section with checker digit.
140
     *
141
     * @return bool Returns true if it is a valid number, otherwise false.
142
     */
143 1136
    protected function isValid(string $number) : bool
144
    {
145 1136
        $baseNumber = $this->extractBaseNumber($number);
146
147 1136
        if (!$baseNumber) {
148 20
            return false;
149
        }
150
151 1116
        $isRepeated = preg_match("/^[{$baseNumber[0]}]+$/", $baseNumber);
152
153 1116
        if ($isRepeated) {
154 54
            return false;
155
        }
156
157 1062
        $digit = $this->calculateDigit($baseNumber);
158
159 1062
        return "$digit" === "{$this->digit}";
160
    }
161
162
    /**
163
     * Extracts the base number document.
164
     *
165
     * @param string $number Number of document.
166
     *
167
     * @return string Returns only base number without checker digit.
168
     */
169 236
    protected function extractBaseNumber(string $number) : string
170
    {
171 236
        return substr($number, 0, -($this->numberOfDigits));
172
    }
173
174
    /**
175
     * Extracts the checker digit from document number.
176
     *
177
     * @param string $number Number of document.
178
     *
179
     * @return string Returns only checker digit.
180
     */
181 236
    protected function extractCheckerDigit(string $number) : string
182
    {
183 236
        return substr($number, -($this->numberOfDigits));
184
    }
185
}
186