nandan108/prop-access

Accessor layer for PHP objects using reflection, extensible via custom resolvers

Maintainers

Package info

github.com/Nandan108/prop-access

pkg:composer/nandan108/prop-access

Statistics

Installs: 265

Dependents: 2

Suggesters: 0

Stars: 0

Open Issues: 0

v0.7.0 2026-02-22 22:02 UTC

This package is auto-updated.

Last update: 2026-03-22 22:13:24 UTC


README

CI Coverage Style Packagist

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:

  1. 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
  2. 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

// 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 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 PropAccessConfigException (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.

โš ๏ธ Exceptions & Message IDs

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];

โœ… Quality

  • โœ… 100% test coverage
  • โœ… Psalm level 1
  • โœ… Zero dependencies

MIT License ยฉ nandan108