symfony/property-access
Symfony PropertyAccess lets you read and write values on objects and arrays using a simple property path string notation. It supports nested access, getters/setters, and array indexes, making data mapping and form handling easier.
Installation:
composer require symfony/property-access
For Laravel, prefer using symfony/property-access directly or via Symfony’s PropertyAccessComponent if using other Symfony components.
Basic Usage:
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
$accessor = PropertyAccess::createPropertyAccessor();
$data = ['user' => ['name' => 'John', 'age' => 30]];
// Read
$name = $accessor->getValue($data, '[user][name]'); // 'John'
// Write
$accessor->setValue($data, '[user][age]', 31);
First Use Case: Replace manual nested property access in a Laravel controller or service:
// Before
$user = $request->user();
$city = $user->getAddress()->getCity();
// After
$city = $accessor->getValue($user, 'address.city');
PropertyAccessorInterface for type-hinted usage in services.[array][keys], object.property, getter()).// Laravel API Request Handling
public function update(Request $request, User $user) {
$accessor = PropertyAccess::createPropertyAccessor();
$data = $request->all();
// Update nested user data
$accessor->setValue($user, 'profile.settings.notifications', $data['notifications']);
// Return transformed response
return response()->json([
'user' => $accessor->getValue($user, 'profile')
]);
}
// Laravel Form Request Validation
public function rules() {
return [
'user.profile.address.city' => 'required|string',
'user.preferences.theme' => 'nullable|string'
];
}
// Access form data dynamically
$city = $accessor->getValue($request->all(), '[user][profile][address][city]');
// Transform API payload to domain object
$dto = new UserProfileDTO();
$accessor->setValue($dto, 'fullName', $accessor->getValue($payload, 'user.name'));
$accessor->setValue($dto, 'age', $accessor->getValue($payload, 'user.age'));
// Dynamic feature flags
$config = config('features');
$isEnabled = $accessor->getValue($config, 'user.tier.discount');
Create a facade to simplify usage:
// app/Providers/AppServiceProvider.php
use Symfony\Component\PropertyAccess\PropertyAccess;
public function register() {
$this->app->singleton('property-access', function () {
return PropertyAccess::createPropertyAccessor();
});
}
// app/Facades/PropertyAccess.php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class PropertyAccess extends Facade {
protected static function getFacadeAccessor() {
return 'property-access';
}
}
// Usage
$value = PropertyAccess::getValue($object, 'nested.path');
Bind the accessor to Laravel’s container for dependency injection:
$this->app->bind(PropertyAccessorInterface::class, function () {
return PropertyAccess::createPropertyAccessor();
});
Use PropertyPath to validate paths before access:
use Symfony\Component\PropertyAccess\PropertyPath;
$path = new PropertyPath('user.profile.settings');
if ($accessor->isReadable($object, $path)) {
$value = $accessor->getValue($object, $path);
}
For performance-critical paths (e.g., APIs), cache the accessor:
$accessor = PropertyAccess::createPropertyAccessor();
$accessor->setUsedAttributes(['user.profile', 'user.settings']); // Optimize
Extend the accessor for Laravel-specific types (e.g., Carbon, Collection):
$accessor = PropertyAccess::createPropertyAccessor();
$accessor->addType('carbon', function ($value) {
return $value instanceof \Carbon\Carbon ? $value->format('Y-m-d') : null;
});
[key] notation ([user][name]).property or getter() (address.city or getAddress()).[] for arrays inside objects (user[0].name).[] for array keys causes PropertyNotFoundException.name and getName()), the component prioritizes the property by default.getter() in the path: user.getName().PropertyAccess::createPropertyAccessor()->setUseGetterForMissingProperties(true) to prefer getters.PropertyNotFoundException.isReadable() or isWritable() to check first:
if ($accessor->isReadable($object, 'user.profile')) {
$value = $accessor->getValue($object, 'user.profile');
}
setUsedAttributes() to pre-optimize paths.public getName(): string) may require explicit path syntax.user.password).PropertyPath validation.$accessor = PropertyAccess::createPropertyAccessor();
$accessor->setDebug(true); // Logs path resolution
Catch specific exceptions for graceful degradation:
use Symfony\Component\PropertyAccess\Exception\ExceptionInterface;
try {
$value = $accessor->getValue($object, 'invalid.path');
} catch (ExceptionInterface $e) {
// Fallback logic
}
PropertyNotFoundException: Path doesn’t exist.InvalidArgumentException: Invalid path syntax.UnexpectedTypeException: Type mismatch (e.g., setting a string to a non-string property).Implement PropertyAccessorInterface for domain-specific logic:
class CustomAccessor implements PropertyAccessorInterface {
public function getValue($object, $propertyPath) {
// Custom logic
}
// ... other methods
}
Add support for custom types (e.g., Laravel Collections):
$accessor->addType('collection', function ($value) {
return $value instanceof \Illuminate\Support\Collection ? $value->toArray() : null;
});
Use PropertyPath to transform paths dynamically:
$path = new PropertyPath('user.profile');
$path->addProperty('settings'); // Transforms to 'user.profile.settings'
Combine with Arr::get() for hybrid array/object access:
$value = $accessor->getValue($array, '[key][nested]');
// or
$value = Arr::get($accessor->getValue($object, 'arrayProperty'), 'key');
How can I help you explore Laravel packages today?