atournayre/types
Lightweight PHP library providing reusable types/value objects. Installable via Composer, intended to standardize and validate common domain data. Open-source on GitHub with issue tracker and MIT license.
Installation
composer require atournayre/types
Add the service provider to config/app.php (if not auto-discovered):
'providers' => [
// ...
ATournayre\Types\TypesServiceProvider::class,
],
Basic Usage Define a custom type in a PHP class:
use ATournayre\Types\Type;
class UserId extends Type
{
public static function validate($value): bool
{
return is_string($value) && preg_match('/^[a-f0-9]{24}$/', $value);
}
}
New in 0.9.0: Use the built-in EmailAddress type for email validation:
use ATournayre\Types\EmailAddress;
// No need to define your own email validation
First Use Case Validate input in a Laravel request:
use ATournayre\Types\Type;
use ATournayre\Types\EmailAddress;
public function store(Request $request)
{
$request->validate([
'user_id' => ['required', function ($attribute, $value, $fail) {
if (!UserId::validate($value)) {
$fail('The '.$attribute.' must be a valid UserId.');
}
}],
'email' => ['required', EmailAddress::class], // Directly use EmailAddress
]);
}
Basic Type
class Age extends Type
{
public static function validate($value, $min = 18, $max = 120): bool
{
return is_int($value) && $value >= $min && $value <= $max;
}
}
Predefined Types (New in 0.9.0)
Use built-in types like EmailAddress instead of reinventing:
use ATournayre\Types\EmailAddress;
// No need to define your own email validation
Type with Conversion
class Json extends Type
{
public static function validate($value): bool
{
return is_string($value) && is_array(json_decode($value, true));
}
public static function convert($value)
{
return json_decode($value, true);
}
}
Form Request Validation
use ATournayre\Types\EmailAddress;
public function rules()
{
return [
'email' => ['required', EmailAddress::class], // Directly use EmailAddress
'user_id' => ['required', function ($attribute, $value, $fail) {
if (!UserId::validate($value)) {
$fail('The '.$attribute.' must be a valid UserId.');
}
}]
];
}
Model Casting
use ATournayre\Types\EmailAddress;
protected $casts = [
'email' => EmailAddress::class,
'user_id' => UserId::class,
];
API Resource Formatting
public function toArray($request)
{
return [
'id' => $this->id,
'email' => EmailAddress::convert($this->email),
'user_id' => UserId::convert($this->user_id),
];
}
Middleware for Type Enforcement
public function handle($request, Closure $next)
{
if (!$request->has('email') || !EmailAddress::validate($request->email)) {
abort(400, 'Invalid EmailAddress');
}
return $next($request);
}
Avoid Redundant Validation
New in 0.9.0: Do not redefine EmailAddress validation. Use the built-in type instead:
// ❌ Avoid this (redundant)
class Email extends Type { ... }
// ✅ Use this instead
use ATournayre\Types\EmailAddress;
Performance Overhead Custom types add minor overhead. Cache type definitions if used extensively in hot paths:
class CachedType extends Type
{
protected static $cache = [];
public static function validate($value)
{
$cacheKey = static::class . serialize($value);
if (!isset(self::$cache[$cacheKey])) {
self::$cache[$cacheKey] = parent::validate($value);
}
return self::$cache[$cacheKey];
}
}
Type Conversion Pitfalls
Ensure convert() methods handle edge cases (e.g., invalid input):
public static function convert($value)
{
if (!self::validate($value)) {
throw new \InvalidArgumentException('Invalid type value');
}
return json_decode($value, true);
}
Validation Failures Log type validation failures for debugging:
public static function validate($value): bool
{
$isValid = /* validation logic */;
if (!$isValid) {
\Log::debug('Type validation failed for ' . static::class, ['value' => $value]);
}
return $isValid;
}
Type Hierarchy Issues If extending types, ensure parent methods are called:
class ExtendedEmail extends EmailAddress
{
public static function validate($value): bool
{
return parent::validate($value) && /* additional logic */;
}
}
Custom Type Collections Group related types for easier management:
class TypeCollection
{
public static function getTypes(): array
{
return [
UserId::class,
EmailAddress::class, // Include built-in types
// ...
];
}
}
Type Registry Register types globally for reuse:
$types = app()->make('types');
$types->register(UserId::class);
$types->register(EmailAddress::class); // Register built-in types
Dynamic Type Loading Load types dynamically from a config file:
$types = config('types.custom');
foreach ($types as $type) {
$types->register($type);
}
Service Provider Binding
Ensure the service provider binds the Type class correctly. Override bindings if needed:
$this->app->bind('types', function () {
return new CustomTypeRegistry();
});
Autoloading
If types are in a custom namespace, ensure composer.json autoloads them:
{
"autoload": {
"psr-4": {
"App\\Types\\": "app/Types/"
}
}
}
}
Built-in Type Usage
New in 0.9.0: The EmailAddress type is now available out-of-the-box. No additional setup is required.
// Works directly in validation rules
$request->validate(['email' => EmailAddress::class]);
How can I help you explore Laravel packages today?