sebastian/type
sebastian/type provides lightweight value objects for representing and working with PHP’s type system (built-in, class, union, intersection, nullable, etc.). Useful for static analysis tools, reflection helpers, and test utilities that need precise type modeling.
Installation:
composer require sebastian/type --dev
Add as a --dev dependency if only needed for testing/static analysis.
First Use Case:
Validate a type from a ReflectionType (e.g., from a method parameter or return type):
use SebastianBergmann\Type\ReflectionMapper;
use SebastianBergmann\Type\Type;
$reflectionType = new \ReflectionNamedType(DateTimeImmutable::class);
$type = ReflectionMapper::fromType($reflectionType);
if ($type->isAssignableFrom(Type::fromString('DateTime'))) {
// Type is assignable
}
Key Classes to Explore:
ReflectionMapper: Converts PHP reflection types to Type objects.Type: Base class for all type representations (e.g., ObjectType, UnionType).Type::fromString(): Parse type strings (e.g., "int|string").Where to Look First:
Use ReflectionMapper to extract types from PHP reflection objects (e.g., methods, properties):
$method = new \ReflectionMethod(MyClass::class, 'myMethod');
$returnType = ReflectionMapper::fromType($method->getReturnType());
if ($returnType->isObject()) {
$objectType = $returnType->asObjectType();
// Work with $objectType (e.g., validate against expected classes)
}
Extend Laravel’s Validator or FormRequest with type-aware rules:
use SebastianBergmann\Type\Type;
use SebastianBergmann\Type\UnionType;
$validator = Validator::make($data, [
'field' => ['required', function ($attribute, $value, $fail) {
$expectedType = UnionType::fromTypes([
Type::fromString('int'),
Type::fromString('string'),
]);
$actualType = Type::fromValue($value);
if (!$expectedType->isAssignableFrom($actualType)) {
$fail('The :attribute must be an int or string.');
}
}],
]);
Create custom rules using Type objects to enforce project-specific type constraints:
use SebastianBergmann\Type\Type;
use PHPStan\Rules\Rule;
class CustomTypeRule implements Rule
{
public function getNodeType(): string
{
return \PHPStan\Node\MethodNode::class;
}
public function processNode(Node $node): array
{
$returnType = ReflectionMapper::fromType($node->getReturnType());
if (!$returnType->isAssignableFrom(Type::fromString('App\Models\DTO'))) {
return [sprintf(
'Return type %s must be assignable from App\Models\DTO',
$returnType->toString()
)];
}
return [];
}
}
Validate incoming data against strict type contracts:
use SebastianBergmann\Type\ObjectType;
use SebastianBergmann\Type\UnionType;
class UserRequest extends FormRequest
{
public function rules()
{
return [
'name' => ['required', 'string'],
'age' => ['nullable', function ($attribute, $value, $fail) {
$expectedType = UnionType::fromTypes([
Type::fromString('int'),
Type::fromString('null'),
]);
$actualType = Type::fromValue($value);
if (!$expectedType->isAssignableFrom($actualType)) {
$fail('The :attribute must be an int or null.');
}
}],
];
}
}
Generate OpenAPI/Swagger schemas from Laravel controllers:
use SebastianBergmann\Type\ReflectionMapper;
use OpenApi\Generator;
$generator = new Generator();
$controller = new \ReflectionClass(MyController::class);
foreach ($controller->getMethods() as $method) {
$returnType = ReflectionMapper::fromType($method->getReturnType());
$generator->addResponse(
$method->getName(),
200,
['schema' => ['type' => $returnType->toString()]]
);
}
Service Container Binding Validation: Ensure DI bindings respect type hints:
$container = app();
$binding = $container->getBinding('MyService');
$expectedType = Type::fromString('App\Services\MyServiceInterface');
$actualType = Type::fromString($binding->getConcrete()[0]);
if (!$expectedType->isAssignableFrom($actualType)) {
throw new \RuntimeException('Invalid binding type for MyService');
}
Eloquent Model Attribute Types: Validate model attributes against expected types:
use Illuminate\Database\Eloquent\Model;
use SebastianBergmann\Type\Type;
class User extends Model
{
protected static function booted()
{
static::saving(function (self $user) {
foreach ($user->getAttributes() as $attribute => $value) {
$expectedType = Type::fromString($user->getTypeForAttribute($attribute));
$actualType = Type::fromValue($value);
if (!$expectedType->isAssignableFrom($actualType)) {
throw new \InvalidArgumentException(
"Attribute {$attribute} must be {$expectedType->toString()}"
);
}
}
});
}
}
Custom Type Extensions:
Extend Type for domain-specific types (e.g., Money, Email):
use SebastianBergmann\Type\Type;
class MoneyType extends Type
{
public static function fromString(string $type): self
{
return new self($type);
}
public function isAssignableFrom(Type $other): bool
{
return $other->toString() === 'Money' ||
$other->toString() === 'int';
}
}
Cache ReflectionMapper Instances:
$mapper = new ReflectionMapper();
// Reuse $mapper across multiple type checks
Lazy-Load Types: Defer type parsing until needed (e.g., in static analysis):
$typeString = $method->getReturnType()->getName();
$type = Type::fromString($typeString); // Parse only when required
Assert Type Compatibility:
use SebastianBergmann\Type\Type;
public function testTypeAssignability()
{
$expected = Type::fromString('DateTimeInterface');
$actual = Type::fromString('DateTimeImmutable');
$this->assertTrue($expected->isAssignableFrom($actual));
}
Mock Type Checks:
$mockType = $this->createMock(Type::class);
$mockType->method('isAssignableFrom')->willReturn(true);
$this->assertTrue($mockType->isAssignableFrom(Type::fromString('string')));
Class Alias Resolution:
ReflectionMapper failed to resolve class aliases (e.g., DateTimeImmutable as DateTime).^7.0.1 and test with:
$aliasType = Type::fromString('DateTimeImmutable');
$baseType = Type::fromString('DateTime');
$this->assertTrue($baseType->isAssignableFrom($aliasType));
Union Types with iterable:
iterable (e.g., iterable|ArrayObject).^6.0.2 or later. Test with:
$union = UnionType::fromTypes([
Type::fromString('iterable'),
Type::fromString('ArrayObject'),
]);
$this->assertTrue($union->isAssignableFrom(Type::fromString('array')));
**PHP Version
How can I help you explore Laravel packages today?