tdn/php-types
A small PHP utility for working with types: safely cast values, validate data against expected types, and normalize scalars, arrays and objects. Lightweight and framework-agnostic, it drops easily into Laravel or any Composer project.
tdn/php-types is a plain PHP library that boxes scalar and array values in small wrapper objects (StringType, IntType, FloatType, BooleanType, ArrayType-style classes — see the naming caveat below). There is no Laravel service provider, no facade, and no config file, so integration is a single Composer dependency plus whatever wiring you build yourself.
Minimal steps:
composer require tdn/php-types:^2.0 — pin the minor explicitly. The most recent tag is 2.0.1, which is still a 2015-era release (the package is archived; there is no PHP 8 modernization in this line). If Packagist resolution fails on your PHP version, add the GitHub repo as a VCS repository and pin the 2.0.1 commit.src/ first, not the README. Two things changed in 2.0.1 that make this mandatory rather than merely advisable: the release adds class aliases so that names colliding with PHP 7 reserved keywords (String, Int, Float, Boolean, Array) keep working, and it adds an invoke method to Boolean. Do not trust a class name you remembered from 1.x or from a blog post — read which file holds the real implementation and which is the class_alias() shim.$request->input('age') to be an integer, construct an IntType from it once and pass the wrapper inward, unwrapping to a scalar only at the Eloquent save and the JSON response.That boundary-wrap-then-unwrap discipline is the only pattern that consistently pays for itself with a library this small.
Normalize at the edge, unwrap at the edges. The wrapper's value is that it fails or coerces once, in one place, rather than in five scattered (int) casts. Pattern: controller → form request validates shape → construct typed wrappers from the validated input → services and domain methods accept wrappers → repository/Eloquent boundary calls ->getValue() (or (string)/(int) casts where supported) to persist primitives.
DTOs and value objects. If you already hand-roll DTOs for API payloads, wrappers give you cheap invariant enforcement without pulling in a full value-object framework. A Money or Age DTO whose constructor takes IntType documents its contract better than a @param int docblock. Keep it shallow though — one level of wrapping, not wrappers inside wrappers.
Boolean flags via invoke() (new in 2.0.1). BooleanType now exposes an invoke method, which lets you treat the wrapper as a callable — useful inside @if ($flag()) in Blade or when($flag(), ...) in a pipeline without an explicit ->getValue() at every site. Adopt it deliberately: it reads well in views and poorly in domain code, where an explicit unwrap documents the boundary. Pick one convention per layer and stick to it; mixing $flag() and $flag->getValue() in the same service is how reviewers start guessing.
Guard clauses in actions and jobs. Because the wrapped value is validated at construction, queued jobs and service methods can assume the type holds and skip defensive re-checks. This is most useful in jobs where the serialized payload has crossed a process boundary and you re-hydrate from array data.
API resources. Add an explicit unwrap step in your JsonResource::toArray() rather than relying on the wrapper to serialize. Returning the object directly usually yields {} in JSON (see Gotchas). A one-line $this->age->getValue() per field is clearer than debugging a JsonSerializable implementation.
Laravel casts and accessors. Do not point an Eloquent $casts entry at a wrapper class. Eloquent's cast contract expects to hydrate from and dehydrate to scalars and will not know how to reconstruct the wrapper. Use a custom accessor that returns the wrapper and a mutator that unwraps it, or keep wrappers out of models entirely and confine them to the service layer.
Wrap behind your own interface. Define a narrow Age, Email, or Slug interface in your app and have the tdn class implement it via a thin adapter. This is the single highest-leverage move: when you eventually migrate to native typed properties, backed enums, or a maintained package, you swap adapters instead of grepping for IntType across the codebase. Given the package is archived, plan the exit before you plan the entry. The 2.0.1 aliases reinforce this — you do not want your app's ::class strings resolving to a class_alias() shim whose other name may disappear in whatever 2.1 never ships.
instanceof checks and ::class strings depend on which name you imported, and container bindings or config arrays keyed by class string can mismatch silently. Pick one canonical name, use it everywhere, and note it in your adapter so nobody "fixes" it later.new IntType('abc') does — throw, return 0, or return null. If it silently coerces, the library gives you a false sense of type safety and you are better off with a Laravel validation rule.JsonSerializable in an adapter subclass or, more simply, unwrap in your resources. Never let a wrapper reach response()->json() unwrapped.$a == $b and $a === $b will both behave unintuitively for equal values. Look for an equals()/isSame() method and standardize on it; otherwise unwrap both sides before comparing.->map() over thousands of rows.serialize()/unserialize() across a deploy that changes the class shape can break job payloads — and 2.0.1 did touch class shape via aliases, so payloads written by an older deploy are exactly the scenario the docs warn about. Prefer primitives in queue payloads and re-wrap on handle.dd($wrapper) shows object internals rather than the value; dump($wrapper->getValue()) is your reflex. When a value looks wrong, the bug is almost always at construction, not at read. With invoke() in play, also remember that dump($flag) and dump($flag()) print different things — the first shows the object, the second the boolean.AppServiceProvider so you are not scattering new IntType(...) with inconsistent flags. This is also the right place to centralize the canonical-vs-alias naming decision.How can I help you explore Laravel packages today?