nandan108/prop-access

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

v0.5.0 2025-07-05 10:14 UTC

This package is auto-updated.

Last update: 2025-07-05 10:17:01 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

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