alex-kalanis /
simple-vin
| 1 | <?php |
||
| 2 | |||
| 3 | namespace kalanis\simple_vin; |
||
| 4 | |||
| 5 | |||
| 6 | use DateTime; |
||
| 7 | use Psr\Clock\ClockInterface; |
||
| 8 | |||
| 9 | |||
| 10 | /** |
||
| 11 | * Basic class for accessing VIN data |
||
| 12 | */ |
||
| 13 | class SimpleVin |
||
| 14 | { |
||
| 15 | protected const VALID_VIN_LENGTH = 17; |
||
| 16 | protected const CHECK_INDEX_ON_DIGIT = 8; |
||
| 17 | // Character weights for 17 characters in VIN |
||
| 18 | // Beware - some cars (like in Australia) does not have 17 characters VIN; then you cannot use this library |
||
| 19 | /** @var int[] */ |
||
| 20 | protected static array $CharacterWeights = [8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2]; |
||
| 21 | |||
| 22 | protected readonly int $startYear; |
||
| 23 | protected readonly int $nextYear; |
||
| 24 | |||
| 25 | protected readonly Datasources\Years $years; |
||
| 26 | protected readonly Datasources\ValidCharacters $validCheckCharacters; |
||
| 27 | protected readonly Datasources\CharacterTransliteration $characterTransliteration; |
||
| 28 | protected readonly Datasources\WorldManufacturerIdentifiers $worldManufacturerIdentifiers; |
||
| 29 | |||
| 30 | 37 | public function __construct( |
|
| 31 | ?ClockInterface $clock = null, |
||
| 32 | ) |
||
| 33 | { |
||
| 34 | // no DI |
||
| 35 | 37 | $this->years = new Datasources\Years(); |
|
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
| 36 | 37 | $this->validCheckCharacters = new Datasources\ValidCharacters(); |
|
|
0 ignored issues
–
show
|
|||
| 37 | 37 | $this->characterTransliteration = new Datasources\CharacterTransliteration(); |
|
|
0 ignored issues
–
show
|
|||
| 38 | 37 | $this->worldManufacturerIdentifiers = new Datasources\WorldManufacturerIdentifiers(); |
|
|
0 ignored issues
–
show
|
|||
| 39 | |||
| 40 | 37 | $clocks = $clock ? $clock->now() : new DateTime(); // because it has not a correct interface! |
|
| 41 | |||
| 42 | 37 | $this->startYear = intdiv(intval($clocks->format('Y')), 30) * 30; // base year |
|
|
0 ignored issues
–
show
|
|||
| 43 | 37 | $this->nextYear = intval($clocks->format('Y')) + 1; // next year against current date |
|
|
0 ignored issues
–
show
|
|||
| 44 | } |
||
| 45 | |||
| 46 | 15 | public function isValid(string $vin): bool |
|
| 47 | { |
||
| 48 | 15 | if (static::VALID_VIN_LENGTH != strlen($vin)) { |
|
| 49 | 2 | return false; |
|
| 50 | } |
||
| 51 | |||
| 52 | 13 | $checkCharacter = $vin[static::CHECK_INDEX_ON_DIGIT]; |
|
| 53 | |||
| 54 | 13 | $calculated = $this->calculateChecksum($vin); |
|
| 55 | 13 | if (is_null($calculated)) { |
|
| 56 | 2 | return false; |
|
| 57 | } |
||
| 58 | |||
| 59 | 11 | return $calculated == $this->validCheckCharacters[$checkCharacter]; |
|
| 60 | } |
||
| 61 | |||
| 62 | 2 | public function restoreChecksum(string $vin): ?string |
|
| 63 | { |
||
| 64 | 2 | $char = $this->restoreChecksumCharacter($vin); |
|
| 65 | |||
| 66 | 2 | if (is_null($char)) { |
|
| 67 | 1 | return null; |
|
| 68 | } |
||
| 69 | |||
| 70 | 1 | $vin[static::CHECK_INDEX_ON_DIGIT] = $char; |
|
| 71 | 1 | return $vin; |
|
| 72 | } |
||
| 73 | |||
| 74 | 5 | public function restoreChecksumCharacter(string $vin): ?string |
|
| 75 | { |
||
| 76 | 5 | if (static::VALID_VIN_LENGTH != strlen($vin)) { |
|
| 77 | 2 | return null; |
|
| 78 | } |
||
| 79 | |||
| 80 | 3 | $calculated = $this->calculateChecksum($vin); |
|
| 81 | 3 | if (is_null($calculated)) { |
|
| 82 | 1 | return null; |
|
| 83 | } |
||
| 84 | |||
| 85 | 2 | $chars = array_map('strval', array_flip($this->validCheckCharacters->getArrayCopy())); |
|
| 86 | |||
| 87 | 2 | return $chars[$calculated] ?? null; |
|
| 88 | } |
||
| 89 | |||
| 90 | 16 | protected function calculateChecksum(string $vin): ?int |
|
| 91 | { |
||
| 92 | 16 | $value = 0; |
|
| 93 | |||
| 94 | 16 | for ($i = 0; $i < static::VALID_VIN_LENGTH; $i++) { |
|
| 95 | 16 | if (empty(static::$CharacterWeights[$i])) { |
|
| 96 | 13 | continue; |
|
| 97 | } |
||
| 98 | 16 | if (!isset($this->characterTransliteration[$vin[$i]])) { |
|
| 99 | 3 | return null; |
|
| 100 | } |
||
| 101 | 16 | $value += (static::$CharacterWeights[$i] * ($this->characterTransliteration[$vin[$i]])); |
|
| 102 | } |
||
| 103 | |||
| 104 | 13 | return $value % 11; |
|
| 105 | } |
||
| 106 | |||
| 107 | 6 | public function getWorldManufacturer(string $vinOrWmi): string |
|
| 108 | { |
||
| 109 | 6 | if (empty($vinOrWmi)) { |
|
| 110 | 1 | return ''; |
|
| 111 | } |
||
| 112 | |||
| 113 | 5 | if (2 > strlen($vinOrWmi)) { |
|
| 114 | 1 | return ''; |
|
| 115 | } |
||
| 116 | |||
| 117 | 4 | $prefix = substr($vinOrWmi, 0, 3); |
|
| 118 | 4 | if (2 < strlen($vinOrWmi) && isset($this->worldManufacturerIdentifiers[$prefix])) { |
|
| 119 | 2 | return $this->worldManufacturerIdentifiers[$prefix]; |
|
| 120 | } |
||
| 121 | |||
| 122 | 2 | $prefix = substr($vinOrWmi, 0, 2); |
|
| 123 | 2 | return $this->worldManufacturerIdentifiers[$prefix] ?? ''; |
|
| 124 | } |
||
| 125 | |||
| 126 | 17 | public function getModelYear(string $ident, ?int $startYear = null, ?int $nextYear = null): ?int |
|
| 127 | { |
||
| 128 | 17 | if (empty($ident)) { |
|
| 129 | 1 | return null; |
|
| 130 | } |
||
| 131 | |||
| 132 | 16 | if (empty($startYear)) { |
|
| 133 | 13 | $startYear = $this->startYear; |
|
| 134 | } |
||
| 135 | |||
| 136 | 16 | if (empty($nextYear)) { |
|
| 137 | 15 | $nextYear = $this->nextYear; |
|
| 138 | } |
||
| 139 | |||
| 140 | 16 | if (!$this->years->checkStart($startYear)) { |
|
| 141 | 1 | return null; |
|
| 142 | } |
||
| 143 | |||
| 144 | 15 | if (10 > strlen($ident)) { |
|
| 145 | 2 | return $this->getModelYearByNumber($ident, $startYear, $nextYear); |
|
| 146 | } |
||
| 147 | |||
| 148 | 13 | return $this->getModelYearByNumber($ident[9], $startYear, $nextYear); |
|
| 149 | } |
||
| 150 | |||
| 151 | 15 | protected function getModelYearByNumber(string $yearCharacter, int $startYear, int $nextYear): ?int |
|
| 152 | { |
||
| 153 | 15 | if (isset($this->years[$yearCharacter])) { |
|
| 154 | 12 | $year = $startYear + $this->years[$yearCharacter]; |
|
| 155 | 12 | if ($year > $nextYear) { |
|
| 156 | 6 | $year -= 30; |
|
| 157 | } |
||
| 158 | 12 | return $year; |
|
| 159 | } |
||
| 160 | |||
| 161 | 3 | return null; |
|
| 162 | } |
||
| 163 | } |
||
| 164 |