oihana / php-reflect
The Oihana PHP Reflection library
1.1.0
2026-06-10 15:46 UTC
Requires
- php: >=8.4
- oihana/php-core: dev-main
Requires (Dev)
- mikey179/vfsstream: ^1.6
- nunomaduro/collision: ^8.8
- phpdocumentor/shim: ^3.8
- phpunit/phpunit: ^12
README
Lightweight reflection and hydration helpers for modern PHP, part of the Oihana PHP ecosystem.
It provides:
- Friendly wrappers around PHP's Reflection API
- Array-to-object hydration with attribute-based mapping
- Utilities to expose public properties as arrays
- Simple constant βenumsβ helpers
- A compact
Versionvalue object
π Documentation
Full documentation: https://bcommebois.github.io/oihana-php-reflect
π¦ Installation
Requires PHP 8.4+
composer require oihana/php-reflect
β¨ Features
Reflection helpers
- List constants, methods, properties with visibility filters
- Inspect method parameters: type, default, nullable, optional, variadic
- Inspect classes/properties:
hasMethod,hasProperty,propertyType,namespace(union types rendered asA|B, intersection asA&B) - Read instantiated attributes:
classAttributes,propertyAttributes,methodAttributes(optionally filtered by attribute class) - Describe any callableβs parameters (
describeCallableParameters) - Cached
ReflectionClassinstances
Hydration
- Instantiate and hydrate objects from associative arrays (recursively)
- Supports union types and nullability
- Attribute-based mapping:
#[HydrateKey('source_key')]to rename incoming keys (accepts several fallback keys:#[HydrateKey('user_name', 'username')]β first present wins)#[HydrateWith(Foo::class, Bar::class)]for arrays of objects, including polymorphism via@type/typeor property-guessing#[HydrateAs(Foo::class)]to override ambiguous types (object,array,mixed, unions)#[Transient](or its alias#[HydrateIgnore]) to exclude a public property from both hydration (input) andtoArray()(output) β e.g. computed/derived properties
- PHPDoc
@var Type[]and@var array<Type>support for array element types - Backed enums: scalar values are resolved to enum cases via
Enum::from()(single values, and arrays of enums via#[HydrateWith]or@var Enum[]). Pure (non-backed) enums have no scalar representation: hydrating one from a scalar throws anInvalidArgumentExceptionβ declare a backed enum instead DateTimeInterface: scalar values are resolved to date instances (string β parsed date, int β Unix timestamp). In a union with a scalar (e.g.string|DateTimeInterface), the raw value is kept unless#[HydrateAs(DateTimeImmutable::class)]forces the conversion- Classes with a required constructor: instantiated via
newInstanceWithoutConstructor()and populated from the data (a no-argument-callable constructor is still invoked normally) readonlyand asymmetric-visibility properties (public private(set)/public protected(set), PHP 8.4): values are assigned via reflection, so they are initialized correctly (scalar coercion preserved)- Scalar coercion: values are converted to the declared scalar type following PHP's coercive typing (
'42'βint 42,7βstring '7'); a value that cannot be coerced raises aHydrationException(independent ofstrict_types) - Error handling: every hydration failure throws a single catchable
HydrationException(extendsInvalidArgumentException), exposinggetClassName(),getPropertyName()and the original error viagetPrevious()β handy to skip an invalid record when hydrating a batch/stream - Symmetric serialization:
ReflectionTrait::toArray()serializesDateTimeInterfacevalues to ISO 8601 (overridable), and can emit#[HydrateKey]source keys (opt-in viaSerializeOption) β round-trip withhydrate() - Performance: a per-class hydration plan is cached on first use, so the data-independent reflection work (attributes,
@varitem types, constructor strategy, builtin types) is computed once and reused for every object of that class. The cache is in-memory, bounded by the number of hydrated classes, and needs no eviction. On nested documents this cuts hydration time by roughly a third (the deeper the nesting, the larger the gain)
Traits
ReflectionTraitconvenience layer andjsonSerializeFromPublicProperties()(with optional reduction)ConstantsTraitutilities over class constants:getAll,includes,enums,getConstant,validate
Value objects
Versionpacks major/minor/build/revision into a 32-bit int with configurable string output
π Quick start
Reflection basics
use oihana\reflect\Reflection; $ref = new Reflection(); // Constants $constants = $ref->constants(MyEnum::class); // ['ACTIVE' => 'active'] // Methods / Properties $methods = $ref->methods(MyClass::class); $props = $ref->properties(MyDto::class); // Parameters inspection $type = $ref->parameterType(MyClass::class, 'setName', 'name'); // 'string' $default = $ref->parameterDefaultValue(MyClass::class, 'setAge', 'age'); // 30 $nullable = $ref->isParameterNullable(MyClass::class, 'setNickname', 'nickname'); // true
Describe any callable
$fn = fn(string $name, int $age = 42, ...$tags) => null; $params = (new Reflection())->describeCallableParameters($fn); /* [ ['name' => 'name', 'type' => 'string', 'optional' => false, 'nullable' => false, 'variadic' => false], ['name' => 'age', 'type' => 'int', 'optional' => true, 'nullable' => false, 'variadic' => false, 'default' => 42], ['name' => 'tags', 'type' => null, 'optional' => false, 'nullable' => false, 'variadic' => true], ] */
Hydration: flat and nested
class Address { public string $city; } class User { public string $name; public ?Address $address = null; } $data = ['name' => 'Alice', 'address' => ['city' => 'Paris']]; $user = (new Reflection())->hydrate($data, User::class);
Hydration with attributes
use oihana\reflect\attributes\{HydrateKey, HydrateWith, HydrateAs}; class WithKey { #[HydrateKey('user_name')] public string $name; } // Maps input key 'user_name' to property 'name' class Geo { #[HydrateWith(Address::class)] public array $locations = []; } // Hydrates each element of an array property as Address class Wrapper { #[HydrateAs(Address::class)] public object $payload; } // Overrides ambiguous type (object/array/mixed/union)
Arrays of objects via PHPDoc
class Address { public string $city; } class Geo { /** @var Address[] */ public array $locations = []; } $geo = (new Reflection())->hydrate( ['locations' => [ ['city' => 'Lyon'], ['city' => 'Nice'] ]], Geo::class );
Polymorphic arrays with HydrateWith
class A { public string $type = 'A'; } class B { public string $type = 'B'; } class Box { #[HydrateWith(A::class, B::class)] public array $items = []; } // Chooses the right class using '@type' or 'type', or best-guess by properties
Trait: ReflectionTrait
use oihana\reflect\traits\ReflectionTrait; class Product { use ReflectionTrait; public string $name = 'Book'; public ?string $desc = null; } $p = new Product(); $data = $p->jsonSerializeFromPublicProperties(Product::class, true); // ['name' => 'Book']
Trait: ConstantsTrait
use oihana\reflect\traits\ConstantsTrait; final class Status { use ConstantsTrait; public const string OPEN = 'open'; public const string CLOSED = 'closed'; } Status::includes('open'); // true Status::enums(); // ['closed', 'open'] (sorted unique values) Status::getConstant('open'); // 'OPEN'
Value object: Version
use oihana\reflect\Version; $v = new Version(1, 2, 3, 4); $v->fields = 3; // print as 1.2.3 echo (string) $v; // "1.2.3" $v->major = 2; // mutate safely $n = $v->valueOf(); // packed 32-bit int
β Running Unit Tests
composer test
Run a specific test file:
composer test ./tests/oihana/reflect/VersionTest.php
π οΈ Generate the API Docs
We use phpDocumentor to generate the HTML docs into ./docs.
composer doc
π§Ύ License
Licensed under the Mozilla Public License 2.0 (MPL-2.0). See LICENSE.
π€ Author
- Author: Marc ALCARAZ (aka eKameleon)
- Email: marc@ooop.fr
- Website:
http://www.ooop.fr
