arielespinoza07 / refract
A fluent, immutable and fully typed API for PHP's native reflection: classes, enums, methods, functions, properties, parameters, attributes and types.
Requires
- php: ^8.3
Requires (Dev)
- laravel/pint: ^1.32.1
- pestphp/pest: ^4.7.8
- pestphp/pest-plugin-type-coverage: ^4.0
- phpstan/phpstan: ^2.3.1
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-21 07:59:58 UTC
README
A fluent, immutable and fully typed API for PHP's native reflection: classes, enums, methods, functions, properties, parameters, attributes and types.
Warning
Refract is in early development. Until 1.0.0, minor releases may contain breaking changes; each one is listed
in the CHANGELOG.
Why Refract
PHP's Reflection* classes are powerful but verbose: filtering methods by attribute, resolving union types, or
reading promoted constructor parameters usually means loops, instanceof checks, and ReflectionException
handling. Refract wraps the native API in a small, chainable layer that keeps the same behavior while making
common inspection tasks read like a sentence:
- Every declaration has its own entry point. Reflect a method, property, parameter or attribute directly, without going through its class first.
- Filterable collections. Methods, properties, constants and parameters come back as immutable collections
with filters like
public(),declaredBy(),promoted()orwithAttribute(). - Types you can query. Inspect named, union, intersection and DNF types, and check whether a value satisfies
one, the way PHP does with
strict_types=1. - Precise for static analysis. Generic types flow through, so PHPStan knows that
ReflectedClass::from(User::class)->newInstance()returns aUser. - Explicit errors. Every failure throws its own exception, such as
MethodNotFound, and all of them implementRefractException. NativeReflectionExceptions never reach your code. - PHP 8.4 ready. Property hooks, asymmetric visibility and lazy objects are supported, with safe fallbacks on 8.3.
Refract is framework-agnostic and has no runtime dependencies.
Requirements
Installation
composer require arielespinoza07/refract
Usage
Every type can be used on its own, and offers the same entry points:
| Method | Returns | When the target does not exist |
|---|---|---|
from(...) |
one reflection | throws a specific exception, e.g. ClassNotFound |
tryFrom(...) |
one reflection or null |
returns null, safe for untrusted input |
collectFrom(...) |
a typed collection (members and parameters) | throws for an unknown class or function |
fromNative(...) |
wraps a native Reflection* object |
— |
native() always returns the underlying native object.
For complete, runnable code, see the examples/ folder (in the repository, not the installed package):
router.php: builds a route table from#[Route]attributes on controllers, adds closure routes, then dispatches requests, passing path values to the handler's parameters converted to their declared types.container.php: a minimal dependency injection container that builds objects from their constructor parameters, and calls any callable (closures, methods, invokable objects) with its dependencies.
php examples/router.php php examples/container.php
Classes
use Refract\ReflectedClass; $class = ReflectedClass::from(User::class); // or ReflectedClass::from($user) $class->name(); // 'App\Models\User' $class->kind(); // ClassKind::Standard $class->isFinal(); $class->parent()?->name(); $class->implements(Arrayable::class); $class->methods()->public()->declaredBy(User::class)->names(); // ['save', 'delete', ...] $class->properties()->promoted()->readonly(); $class->attribute(Table::class)->newInstance()->name; $class->attributeInstances(Route::class); // list<Route>: every #[Route], created and typed $user = $class->newInstance(email: 'ada@example.com'); // typed as User for PHPStan
Methods, properties and constants
use Refract\ReflectedConstant; use Refract\ReflectedMethod; use Refract\ReflectedProperty; $method = ReflectedMethod::from(User::class, 'save'); $method->parameters()->required(); $method->returnType()?->name(); // 'bool' $method->invoke($user, force: true); // works on private methods too ReflectedMethod::collectFrom(User::class)->static()->withAttribute(Command::class); $property = ReflectedProperty::from(User::class, 'email'); $property->type()?->allows('ada@example.com'); // true $property->value($user); $property->isInitialized($user); ReflectedConstant::from(Status::class, 'Active')->backingValue(); // 'active'
Functions, closures and parameters
use Refract\ReflectedCallable; use Refract\ReflectedFunction; use Refract\ReflectedParameter; $function = ReflectedFunction::from(fn (int $id): User => User::find($id)); $function->parameter('id')->type()?->name(); // 'int' // Any callable, as the function or method it refers to: ReflectedCallable::from([UserController::class, 'show']); // ReflectedMethod ReflectedCallable::from($user->save(...)); // ReflectedMethod: first-class callables are unwrapped ReflectedCallable::from(new SendInvoice()); // ReflectedMethod: its __invoke() ReflectedCallable::from(fn (int $id) => $id); // ReflectedFunction ReflectedCallable::from($handler)->parameters()->required(); // the same API for all of them (FunctionLike) // Parameters accept the same callable forms: ReflectedParameter::from([UserController::class, 'show'], 'id'); ReflectedParameter::from('App\Support\format', 0); ReflectedParameter::collectFrom(new SendInvoice())->withAttribute(Inject::class);
Types
$type = ReflectedProperty::from(Order::class, 'total')->type(); $type->kind(); // TypeKind::Union $type->name(); // 'int|float' $type->types(); // a collection of the member types $type->allows(9.99); // true: checked the way PHP does with strict_types=1 $type->nonNullName(); // 'int|float' for int|float|null, 'int' for ?int // The classes a type accepts, e.g. for dependency injection: ?Logger, (A&B)|C, static... ReflectedParameter::from([UserController::class, '__construct'], 'logger')->type()?->classNames(); // ['Psr\Log\LoggerInterface']
Enums and attributes
use Refract\ReflectedAttribute; use Refract\ReflectedEnum; $enum = ReflectedEnum::from(Status::class); $enum->backingType()?->name(); // 'string' $enum->case('Active')->value(); // Status::Active ReflectedClass::from(Status::class)->asEnum(); // from a class reflection to an enum reflection $routes = ReflectedAttribute::collectFrom(UserController::class, Route::class); $routes->first()?->arguments(); // [0 => '/users'] or ['path' => '/users'] $routes->first()?->target(); // AttributeTarget::ClassLike
Errors
Every exception implements Refract\Exceptions\RefractException, so all of them can be caught at once. Each failure has
its own exception, such as ClassNotFound, MethodNotFound or InstanceRequired; native ReflectionExceptions never
reach your code.
Reflections cannot be serialized, so don't store them in a cache, a session or a queued job: serialize() throws
NotSerializable. Store the name you reflected instead, and call from() again; reflecting is cheap.
PHP 8.4 features
Property hooks, virtual properties, asymmetric visibility and final or abstract properties are available through
ReflectedProperty, and lazy ghosts and proxies through ReflectedClass::newLazyGhost() and newLazyProxy().
On PHP 8.3, the inspection methods return false, and creating a lazy object throws FeatureNotSupported.
Development
composer install composer check
| Command | What it does |
|---|---|
composer lint |
Checks code style with Pint |
composer lint:fix |
Fixes code style with Pint |
composer analyse |
Runs PHPStan at level max |
composer test |
Runs the Pest test suite |
composer test:types |
Enforces 100% type coverage |
composer test:coverage |
Enforces 100% line coverage (needs Xdebug or PCOV) |
composer test:mutate |
Runs mutation testing with a 95% minimum score |
composer bench |
Compares Refract with native reflection (PHPBench) |
composer check |
Runs lint, analyse, type coverage, and test |
Versioning
Refract follows Semantic Versioning. See CHANGELOG.md for release notes.
Contributing
Contributions are welcome. Please read CONTRIBUTING.md before opening a pull request.
Security
Please report vulnerabilities privately as described in SECURITY.md.