nandan108 / prop-access
Accessor layer for PHP objects using reflection, extensible via custom resolvers
Requires
- php: ^8.1
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.75
- nunomaduro/collision: ^6.4
- phpunit/phpunit: ^10.0
- vimeo/psalm: ^6.10
README
A minimal and extensible property accessor library for PHP objects.
Provides getter and setter resolution via reflection, supporting both public properties and get*/set*
methods.
Designed to be:
- โ Framework-agnostic
- ๐ Easily extensible to support more object types
๐ Installation
composer require nandan108/prop-access
๐ง Features
- ๐ง Default resolvers for public properties and
getProp()
/setProp()
methods - ๐งฉ Pluggable resolver priority (later-registered resolvers are called first)
- ๐งผ
CaseConverter
utility for camelCase, snake_case, kebab-case, etc. - ๐งฐ Convenience methods:
getValueMap()
,resolveValues()
,canGetGetterMap()
...
๐ Usage
Accessor maps are cached by class name, so the returned closures are stateless and require the target object to be passed as an argument:
use Nandan108\PropAccess\PropAccess; $getterMap = PropAccess::getGetterMap($myObj); $value = $getterMap['propertyName']($myObj); $setterMap = PropAccess::getSetterMap($myObj); $setterMap['propertyName']($myObj, $newValue);
To resolve only specific properties:
$getters = PropAccess::getGetterMap($myObj, ['foo_bar']);
๐งฐ Convenience Utilities
Quickly resolve values from a target object:
use Nandan108\PropAccess\PropAccess; $values = PropAccess::getValueMap($myDto); // โ ['prop1' => 'value1', 'prop2' => 42, ...]
You can also resolve values from a previously obtained getter map:
$getters = PropAccess::getGetterMap($entity, ['foo', 'bar']); $values = PropAccess::resolveValues($getters, $entity);
Check if accessors are supported for a given target:
if (PropAccess::canGetGetterMap($target)) { // Safe to call getGetterMap() }
These methods are especially useful when working with dynamic sources, fallbacks, or introspection-based tools.
๐ง Resolution Behavior
You can call getGetterMap()
/ getSetterMap()
in two ways:
-
Without property list: Returns a full canonical map using camelCase keys. If both a public property (e.g.
my_prop
) and a corresponding getter (getMyProp()
) exist, only the getter will be included to avoid duplication and ensure value transformation logic is preserved.$map = PropAccess::getGetterMap($entity); $map['myProp']($entity); // uses getMyProp(), not $entity->my_prop
-
With a property list: Allows access to both public properties and getter/setter methods via multiple aliases:
foo_bar
โ accesses the public property (if available)fooBar
โ accesses the getter/setter method (if available)
[$directSetter, $indirectSetter] = PropAccess::getSetterMap($myObj, ['foo_bar', 'fooBar']); $directSetter($myObj, 'A'); // -> $myObj->foo_bar = 'A'; $indirectSetter($myObj, 'B'); // -> $myObj->setFooBar('B');
๐ Custom Accessor Resolvers
Resolvers can be registered to override or extend behavior:
PropAccess::bootDefaultResolvers(); // Registers built-in property/method resolvers PropAccess::registerGetterResolver(new MyCustomGetterResolver()); PropAccess::registerSetterResolver(new MyCustomSetterResolver());
Later-registered resolvers are tried first. If ->supports($object)
returns false, fallback continues down the chain.
๐งฌ CaseConverter Utility
CaseConverter::toCamel('user_name'); // "userName" CaseConverter::toPascal('user_name'); // "UserName" CaseConverter::toSnake('UserName'); // "user_name" CaseConverter::toKebab('UserName'); // "user-name" CaseConverter::toUpperSnake('UserName'); // "USER_NAME"
You can also use the generic method:
CaseConverter::to('camel', 'foo_bar'); // Equivalent to toCamel()
๐ AccessorProxy Helper
Need array-style access to object properties? AccessorProxy
wraps an object and exposes property access via ArrayAccess
, Traversable
, and Countable
.
use Nandan108\PropAccess\AccessorProxy; $proxy = AccessorProxy::getFor($user); // read-only by default echo $proxy['firstName']; // -> $user->getFirstName() $proxy['lastName'] = 'Smith'; // throws LogicException (read-only) $rwProxy = AccessorProxy::GetFor($user, readOnly: false); $rwProxy['lastName'] = 'Smith'; // works if setLastName() or $lastName is available
Includes convenience methods:
$proxy->toArray(); // ['firstName' => 'John', ...] $proxy->readableKeys(); // ['firstName', 'lastName', ...] $proxy->writeableKeys();
Use AccessorProxy::getFor()
to fail gracefully:
$proxy = AccessorProxy::getFor($target); if (!$proxy) { // target does not support accessors }
๐ See docs/AccessorProxy.md for full reference.
โ Quality
- โ 100% test coverage
- โ Psalm level 1
- โ Zero dependencies
MIT License ยฉ nandan108