oihana/php-reflect

The Oihana PHP Reflection library

Maintainers

Package info

github.com/BcommeBois/oihana-php-reflect

pkg:composer/oihana/php-reflect

Statistics

Installs: 537

Dependents: 18

Suggesters: 0

Stars: 0

Open Issues: 0

1.1.0 2026-06-10 15:46 UTC

This package is auto-updated.

Last update: 2026-06-10 16:03:06 UTC


README

Oihana PHP - Reflect

Latest Version Total Downloads License

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 Version value 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 as A|B, intersection as A&B)
  • Read instantiated attributes: classAttributes, propertyAttributes, methodAttributes (optionally filtered by attribute class)
  • Describe any callable’s parameters (describeCallableParameters)
  • Cached ReflectionClass instances

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/type or 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) and toArray() (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 an InvalidArgumentException β€” 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)
  • readonly and 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 a HydrationException (independent of strict_types)
  • Error handling: every hydration failure throws a single catchable HydrationException (extends InvalidArgumentException), exposing getClassName(), getPropertyName() and the original error via getPrevious() β€” handy to skip an invalid record when hydrating a batch/stream
  • Symmetric serialization: ReflectionTrait::toArray() serializes DateTimeInterface values to ISO 8601 (overridable), and can emit #[HydrateKey] source keys (opt-in via SerializeOption) β€” round-trip with hydrate()
  • Performance: a per-class hydration plan is cached on first use, so the data-independent reflection work (attributes, @var item 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

  • ReflectionTrait convenience layer and jsonSerializeFromPublicProperties() (with optional reduction)
  • ConstantsTrait utilities over class constants: getAll, includes, enums, getConstant, validate

Value objects

  • Version packs 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