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
composer require nandan108/prop-access- 🧠 Default resolvers for public properties and
getProp()/setProp()methods - 🧩 Pluggable resolver priority (later-registered resolvers are called first)
- 🧼
CaseConverterutility for camelCase, snake_case, kebab-case, etc. - 🧰 Convenience methods:
getValueMap(),resolveValues(),canGetGetterMap()...
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']);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.
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');
Resolvers can be registered to override or extend behavior:
PropAccess::bootDefaultResolvers(); // Registers built-in property/method resolvers
// Deprecated: use registerResolvers([...]) instead.
PropAccess::registerGetterResolver(new MyCustomGetterResolver());
PropAccess::registerSetterResolver(new MyCustomSetterResolver());
// Register many at once:
PropAccess::registerResolvers([
new MyCustomGetterResolver(),
new MyCustomSetterResolver(),
]);
// Remove resolvers later (by class name or instance), as an array:
PropAccess::unregisterResolvers([
MyCustomGetterResolver::class,
MyCustomSetterResolver::class,
]);Later-registered resolvers are tried first. If ->supports($object) returns false, fallback continues down the chain. Registering the same resolver class multiple times is ignored. registerGetterResolver() and registerSetterResolver() remain available for backward compatibility, but are deprecated.
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()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 PropAccessConfigException (read-only)
$rwProxy = AccessorProxy::GetFor($user, readOnly: false);
$rwProxy['lastName'] = 'Smith'; // works if setLastName() or $lastName is availableIncludes 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.
Exceptions are structured for i18n-ready messages and configuration errors:
AccessorException— runtime access errors (missing resolver/getter/setter).PropAccessConfigException— boot/config misuse (e.g. read-only writes).
AccessorException uses message IDs as templates plus parameters for translation:
use Nandan108\PropAccess\Exception\DefaultMessageMap;
$id = 'prop_access.getter_not_found';
$english = DefaultMessageMap::MESSAGES[$id];- ✅ 100% test coverage
- ✅ Psalm level 1
- ✅ Zero dependencies
MIT License © nandan108