Download the PHP package alwayscurious/laravel-vin without Composer
On this page you can find all versions of the php package alwayscurious/laravel-vin. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download alwayscurious/laravel-vin
More information about alwayscurious/laravel-vin
Files in alwayscurious/laravel-vin
Package laravel-vin
Short Description Decode US vehicle VINs into year, make, model and more via the NHTSA vPIC API.
License MIT
Homepage https://github.com/alwayscurious/laravel-vin
Informations about the package laravel-vin
Laravel VIN
Decode a US vehicle VIN into year, make, model, series, trim, body class and more via the NHTSA vPIC API. Decodes are cached (a VIN's decode is immutable), with a version knob to invalidate every cached decode at once and a master switch to disable live lookups entirely.
NHTSA is the default provider, but the decoder is a driver — register your own
with Vin::extend() and select it with VIN_DRIVER, exactly like Laravel's cache,
mail and filesystem drivers.
Requirements
- PHP
^8.3 - Laravel 11, 12 or 13 (
illuminate/*^11.0|^12.0|^13.0)
Installation
The service provider is auto-discovered — no manual registration needed.
Publishing the config file is optional; the package ships with sane defaults and reads everything from environment variables:
Configuration
Every setting is driven by an environment variable, so you rarely need to touch the published config file:
| Env var | Config key | Default | Purpose |
|---|---|---|---|
VIN_DRIVER |
vin.driver |
nhtsa |
Which decoder driver lookups use. |
VIN_BASE_URL |
vin.decoders.nhtsa.base_url |
https://vpic.nhtsa.dot.gov/api |
NHTSA vPIC API base URL. |
VIN_TIMEOUT |
vin.decoders.nhtsa.timeout |
10 |
HTTP timeout (seconds) per decode request. |
VIN_ATTRIBUTES |
vin.decoders.nhtsa.attributes |
identity |
How much to hydrate: identity, typed, or full (see below). |
VIN_RETRY_TIMES |
vin.decoders.nhtsa.retry.times |
2 |
Max NHTSA request attempts (1 disables retrying). |
VIN_RETRY_SLEEP |
vin.decoders.nhtsa.retry.sleep |
200 |
Milliseconds between NHTSA retry attempts. |
VIN_CACHE_STORE |
vin.cache.store |
(app default) | Cache store for decodes (blank = the app's default store). |
VIN_CACHE_TTL |
vin.cache.ttl |
86400 |
How long (seconds) a decoded VIN stays cached. |
VIN_CACHE_VERSION |
vin.cache.version |
1 |
Bump to invalidate every cached decode at once. |
VIN_ENABLED |
vin.enabled |
true |
Master switch for live decoding. |
Usage
Use the Vin facade (auto-registered — no alias config needed):
Prefer dependency injection? Type-hint or resolve VinLookupService — it is the
default driver's lookup service and exposes the same lookup() / tryLookup() /
isValid() methods:
tryLookup()
Returns null instead of throwing on any failure:
isValid()
Structurally validate a VIN (17 characters, excluding I, O and Q) without hitting the network:
Validating form input — the Vin rule and check digit
Validate a VIN field with the Rules\Vin rule object. By default it checks structure only (the same
as isValid()); call withCheckDigit() to also verify the ISO 3779 9th-position check digit and
catch a transposed/mistyped VIN before spending a decode call:
isValid() stays structural-only on purpose — some real, decodable VINs don't honor the check digit.
When you want the stricter gate without the network, use Vin::hasValidCheckDigit('…').
inspect() — validate with useful feedback
When a bare true/false isn't enough — you want to tell the user why their VIN was rejected —
Vin::inspect() runs every offline check and returns a VinValidation reporting the overall verdict,
the structural and check-digit dimensions separately, and a typed reason per failure. No network call:
$result->valid is the strong (structure + check digit) verdict; $result->structurallyValid mirrors
the lenient isValid() the decode path uses. toArray() / JSON-encoding gives a flat, API-friendly
shape (vin, valid, structurally_valid, check_digit_valid, errors).
lookupMany() — decode a batch in one request
Importing a fleet? lookupMany() decodes many VINs in a single provider round-trip (NHTSA's
DecodeVinValuesBatch), reusing the per-VIN cache and only decoding the misses. It returns
VehicleData keyed by normalized VIN, in input order:
The enabled gate and caching apply to the whole batch; a structurally invalid VIN throws before any
request (pre-filter with isValid() if your input may be dirty). A custom driver that doesn't
implement batching still works — lookupMany() transparently falls back to looping lookup().
Knowing why a lookup failed
VinLookupException carries a typed ->reason (VinFailureReason), so a single catch can render
the right message instead of pairing isValid() with tryLookup():
Decode events
The package dispatches Events\VinDecoded on every successful lookup (with a fromCache flag) and
Events\VinDecodeFailed on every failure (with the VinFailureReason and the exception) — the latter
fires even when tryLookup() swallows the error. Wire them to your own telemetry:
The VehicleData value object
lookup() / tryLookup() return an immutable VehicleData. By default (VIN_ATTRIBUTES=identity)
it carries the clean core set that covers ~80% of use cases:
Want engine/safety/body/plant specs, series, or the raw NHTSA fields too? Raise the level with
VIN_ATTRIBUTES (see Trim the response) — everything
below this line needs typed or full.
decodedSuccessfully() is stricter than isFullyIdentified(): NHTSA can return
a full year/make/model while still flagging a non-blocking warning (e.g. a model
year mismatch), in which case isFullyIdentified() is true but
decodedSuccessfully() is false.
Filling a model — only() / toColumns()
To persist a decode, project the identity fields into a Model::fill()-ready array. only() keeps
the property names; toColumns() re-keys them onto your column names:
Both throw on an unknown field (so a typo surfaces), and both project only the flat identity fields — your app keeps ownership of which columns win when merging into an existing row.
Extended attributes — engine, safety, body, plant
DecodeVinValues returns far more than identity. Beyond the fields above, four typed,
always-present groups carry the commonly-used specs. Each field is null when NHTSA has no
value for it — a missing numeric field (doors, horsepower, …) is null, never 0:
Each group is itself Arrayable + JsonSerializable. toArray() / json_encode() on the
VehicleData nest them under engine, safety, body and plant.
Raw attribute passthrough
For anything NHTSA returns that the typed groups don't surface (e.g. DestinationMarket,
NCSABodyType, Note), the complete non-empty response row is kept verbatim, keyed by the
original NHTSA field name:
Raw values are strings exactly as NHTSA sent them (trimmed); use the typed groups above when
you want real int / float types. The raw bag is not embedded in toArray() /
json_encode() — it's reachable only via ->attributes and attribute(), so your serialized
payloads stay curated and stable.
Only need year/make/model? Trim the response
The default is deliberately lean. Building the typed groups — and especially keeping the full raw
passthrough — costs a little CPU per decode and, more importantly, makes each cached row
larger. Step up with VIN_ATTRIBUTES (vin.decoders.nhtsa.attributes) only when you need more:
VIN_ATTRIBUTES |
Core identity | series |
Typed groups | Raw attributes |
Use when… |
|---|---|---|---|---|---|
identity (default) |
✓ | You read year/make/model/trim/body class/vehicle type/manufacturer. Smallest cache, least work. | |||
typed |
✓ | ✓ | ✓ | You also want series and engine/safety/body/plant typed, but never the long tail. |
|
full |
✓ | ✓ | ✓ | ✓ | You want everything, including fields not lifted into a typed group. |
Core identity = year, make, model, trim, body class, vehicle type, manufacturer (plus VIN and
decode status, which are always present). At any level the groups are still present (never null) —
lighter levels just leave their fields null and keep ->attributes empty, so
$vehicle->engine->horsepower is always safe to read.
Because the level changes what's stored, bump
VIN_CACHE_VERSIONwhen you change it so already cached VINs are re-decoded at the new level (see below).
Invalidating cached decodes
A VIN's decode never changes, so results are cached for VIN_CACHE_TTL seconds.
If NHTSA corrects its data — or you change what the package stores — bump
VIN_CACHE_VERSION. The version is part of the cache key, so every previously
cached decode is bypassed at once without flushing your whole cache store.
Disabling live decoding
Set VIN_ENABLED=false to turn off all network calls. lookup() then throws a
VinLookupException and tryLookup() returns null — neither hits the API.
Using a different VIN provider
NHTSA is just the default driver. The package uses Laravel's Manager driver
system, so you register your own provider and select it by name — the same
ergonomics as Cache::extend() / Mail::extend().
A driver is any class implementing Contracts\VinDecoder. It only performs the
lookup and maps the response; the VIN it receives is already normalized (uppercased,
trimmed) and structurally validated, and validation, the enabled gate and caching
are applied around it by the package — so a driver never reimplements them.
Register it as a named driver in a service provider's register() (or boot()).
The closure receives the container, so you can pull in config or other services:
Then make it the default for the whole app:
…or use it for a single call while NHTSA stays the default:
Because the package wraps every driver, your provider automatically inherits
structural validation, the VIN_ENABLED master switch, and caching. Cache entries
are namespaced per driver, so two providers never serve each other's cached decode.
Prefer to decode from a fixed dataset, a queue, or an in-memory table? Same seam —
your decode() doesn't have to make an HTTP call at all.
Driver config is captured when the driver is first resolved. If you change a driver's own settings (e.g.
vin.decoders.*) at runtime, callVin::forgetDrivers()to rebuild. The enabled gate and cache version are re-read on every lookup, so those take effect immediately.
Testing
Vin::fake() — the easy way
Swap the decoder for a fake so your tests never touch the network or the NHTSA wire format. Map a VIN
to a VehicleData (build one with VehicleData::fake()); unmapped VINs return a generated fake. The
fake still runs the real validation, enabled gate and caching, and records lookups for assertion:
Map a VIN to a Throwable to exercise a failure path (Vin::fake(['…' => VinLookupException::requestFailed('…', 503)])).
Faking the HTTP layer directly
Prefer to assert on the wire? The package uses Laravel's HTTP client, so you can fake NHTSA instead:
Run the package's own suite with:
Versioning
This package follows Semantic Versioning. As of 1.0.0 the public API — the
Vin facade, the Contracts\VinDecoder driver seam, VehicleData, VinLookupException, and the
vin.* config keys / env vars — is stable and covered by SemVer. See the CHANGELOG
for what each release adds.
License
The MIT License (MIT). See LICENSE.
All versions of laravel-vin with dependencies
illuminate/cache Version ^11.0|^12.0|^13.0
illuminate/contracts Version ^11.0|^12.0|^13.0
illuminate/http Version ^11.0|^12.0|^13.0
illuminate/support Version ^11.0|^12.0|^13.0