phrity/util-accessor
Access nested data (arrays, objects, scalars) using simple slash-delimited paths. Provides get() with optional default return and type coercion, plus has() to check if a path exists. Lightweight utility for safe, convenient data retrieval.
Installation:
composer require phrity/util-accessor
First Use Case: Access nested data in Eloquent models or arrays without manual traversal:
use Phrity\Util\Accessor;
$accessor = new Accessor();
$user = User::find(1);
$email = $accessor->get($user, 'profile/email'); // Access nested attribute
For Eloquent models, pair with a trait (custom implementation):
use Phrity\Util\AccessorTrait;
class User extends Model
{
use AccessorTrait;
public function getFullNameAttribute()
{
return $this->accessorGet($this->attributes, 'first_name') .
' ' . $this->accessorGet($this->attributes, 'last_name');
}
}
Dynamic Attribute Access: Replace manual array/object traversal with path-based access:
$accessor = new Accessor();
$value = $accessor->get($model->toArray(), 'address/city');
Form Request Validation: Validate nested request data:
public function rules()
{
return [
'user.address.city' => 'required|string',
];
}
// Parse with:
$accessor->has($request->all(), 'user/address/city');
API Response Transformation:
Use DataAccessor to normalize responses:
$response = new DataAccessor($user->toArray());
return response()->json([
'data' => [
'name' => $response->get('profile/name'),
'email' => $response->get('profile/email'),
],
]);
Service Container Binding: Bind the accessor as a singleton for global use:
$app->singleton(Accessor::class, fn($app) => new Accessor());
Model Observers:
Use PathAccessor to standardize attribute access in observers:
class UserObserver
{
protected $accessor;
public function __construct()
{
$this->accessor = new PathAccessor('profile/email');
}
public function saving(User $user)
{
$user->setAttribute('profile_email', $this->accessor->get($user->toArray()));
}
}
API Resource Customization:
Extend JsonResource to use DataAccessor for consistent serialization:
public function toArray($request)
{
$accessor = new DataAccessor(parent::toArray($request));
return [
'data' => [
'name' => $accessor->get('name'),
'metadata' => $accessor->get('metadata/*'),
],
];
}
Immutable Objects:
set() fails on immutable objects (e.g., stdClass with __set restrictions). Use DataAccessor for mutable copies:
$dataAccessor = new DataAccessor($immutableObject);
$dataAccessor->set('path/to/value', 'new_value');
$updatedData = $dataAccessor->getSubject();
Circular References:
Deep cloning may fail with circular references. Use unserialize() as a fallback:
$accessor->get($subject, 'path', clone: false);
Type Coercion Edge Cases:
Custom transformers may not handle null inputs. Add null checks:
$accessor = new Accessor(new FirstMatchResolver([
new NullSafeTransformer(), // Custom wrapper
new BasicTypeConverter(),
]));
Path Parsing Errors:
Use accessorParsePath() to validate paths before runtime:
$path = $this->accessorParsePath('invalid#path', '#');
if ($path === false) {
throw new \InvalidArgumentException('Invalid path format');
}
Performance Bottlenecks:
Profile get() calls with nested paths. Consider flattening data structures:
// Before:
$accessor->get($user, 'profile/address/city');
// After (pre-computed):
$user->setAttribute('profile_city', $user->profile->address->city);
Custom Separators:
Override the default / separator globally via a service provider:
$app->bind(Accessor::class, fn() => new Accessor('.'));
Transformer Conflicts:
Order matters in FirstMatchResolver. Place stricter transformers first:
$transformer = new FirstMatchResolver([
new EnumConverter(), // High priority
new StringableConverter(), // Lower priority
]);
Custom Accessor Classes:
Extend Accessor to add domain-specific logic:
class ModelAccessor extends Accessor
{
public function getRelation($subject, string $path, $default = null)
{
$relation = $this->get($subject, $path, $default);
return $relation instanceof Model ? $relation->fresh() : $relation;
}
}
Path Validation: Add a validator for API paths:
use Phrity\Util\Accessor;
class PathValidator extends FormRequest
{
public function rules()
{
return [
'path' => ['required', function ($attribute, $value, $fail) {
$accessor = new Accessor();
if ($accessor->accessorParsePath($value) === false) {
$fail('The '.$attribute.' must be a valid access path.');
}
}],
];
}
}
Event-Driven Access: Dispatch events for accessor operations:
$accessor = new Accessor();
event(new AccessorAttempted(
$subject,
$path,
$accessor->has($subject, $path)
));
Cache Accessors: Cache computed accessors in model events:
protected static function booted()
{
static::retrieved(function ($model) {
if (!$model->offsetExists('cached_accessor')) {
$model->cached_accessor = $model->accessorGet(
$model->toArray(),
'heavy/path',
fn() => Cache::remember('heavy.path', 3600, fn() => $model->computeHeavyPath())
);
}
});
}
Policy Integration: Use accessors in policies for authorization:
public function update(User $user, User $model)
{
return $user->can('edit_profile') &&
$this->accessor->has($model->toArray(), 'profile/role') &&
$model->profile->role === 'admin';
}
How can I help you explore Laravel packages today?