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’s components via symfony/finder or symfony/var-dumper packages if already in use.
Basic Usage:
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyPathInterface;
$accessor = PropertyAccess::createPropertyAccessor();
$data = ['user' => ['name' => 'John', 'address' => ['city' => 'Paris']]];
// Read
$city = $accessor->getValue($data, '[user][address][city]');
// or with dot notation (for objects/arrays)
$city = $accessor->getValue($data, 'user.address.city');
// Write
$accessor->setValue($data, 'user.address.city', 'Lyon');
First Use Case: Transform an API request payload into a domain object:
$payload = $request->json()->all();
$user = new User();
$accessor = PropertyAccess::createPropertyAccessor();
$accessor->setValue($user, 'email', $payload['data']['user']['email']);
$accessor->setValue($user, 'address.city', $payload['data']['user']['address']['city']);
PropertyPath class for advanced path manipulation (e.g., wildcards, filters).PropertyAccessorInterface for method signatures and expected behavior.$accessor = PropertyAccess::createPropertyAccessor();
$domainObject = new Order();
$accessor->setValue($domainObject, 'customer.name', $request->input('data.customer.name'));
$formData = $request->all();
$user = $accessor->getValue($formData, 'user');
$accessor->setValue($user, 'preferences.notifications.email', true);
$rulePath = 'user.tier.' . $tier . '.discount';
$discount = $accessor->getValue($config, $rulePath);
$variant = $accessor->getValue($user, 'experiment.variant.' . $experimentId);
$this->app->singleton('property.accessor', function () {
return PropertyAccess::createPropertyAccessor();
});
// app/Facades/PropertyAccess.php
public static function get($object, string $path) {
return app('property.accessor')->getValue($object, $path);
}
Usage:
$city = PropertyAccess::get($user, 'address.city');
$validator = Validator::make($data, [
'user.address.city' => 'required|string',
], [
'user.address.city.required' => 'City is required.',
]);
$serialized = $accessor->getValue($resource, '[data][attributes][name]');
Extend PropertyAccessor to handle Laravel-specific logic:
use Symfony\Component\PropertyAccess\PropertyAccessor;
class LaravelPropertyAccessor extends PropertyAccessor {
public function getValue($object, $propertyPath) {
// Add Laravel-specific logic (e.g., Eloquent relationships)
if (str_starts_with($propertyPath, 'relationship.')) {
$relation = substr($propertyPath, 12);
return $object->$relation;
}
return parent::getValue($object, $propertyPath);
}
}
Create reusable path strings:
function buildUserPath(string $segment): string {
return "user.{$segment}";
}
$email = $accessor->getValue($data, buildUserPath('email'));
Use PropertyPath for complex queries:
$path = PropertyPath::create('user[addresses][*][city]');
$cities = $accessor->getValue($data, $path);
$users = User::all();
$cities = $users->map(fn ($user) => PropertyAccess::get($user, 'address.city'));
Path Syntax Quirks:
[key] notation ([user][address][city]).user.address.city).user.address[city] (invalid; use [user][address][city] for arrays).Null/Undefined Properties:
null. Use PropertyPath::create() with PropertyPath::WILDCARD or PropertyPath::EXISTS to check existence:
if ($accessor->isReadable($data, 'user.address.city')) {
$city = $accessor->getValue($data, 'user.address.city');
}
Getter/Setter Conflicts:
getName() and $name), the component prioritizes the property by default. To force method calls, use:
$accessor->setMethodInvoker(function ($object, $method, array $arguments) {
return $object->$method(...$arguments);
});
Performance Overhead:
// Slow (avoid in loops)
foreach ($users as $user) {
$city = $accessor->getValue($user, 'address.city');
}
// Faster (if possible)
foreach ($users as $user) {
$city = $user->address->city; // Direct access
}
Security Risks:
$_GET['path']) can expose sensitive data. Validate paths strictly:
$allowedPaths = ['user.name', 'user.email'];
if (!in_array($path, $allowedPaths)) {
abort(403);
}
user.logout()).PHP 8.4+ Features:
readonly properties in PHP 8.2+.public properties and private/protected getters/setters.Enable Debug Mode:
$accessor = PropertyAccess::createPropertyAccessor();
$accessor->setDebug(true); // Logs path resolution issues
Validate Paths:
Use PropertyPath::create() to validate syntax before access:
try {
$path = PropertyPath::create('user.address[city]');
$value = $accessor->getValue($data, $path);
} catch (\Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException $e) {
// Handle invalid path
}
Check for Circular References:
The component throws CircularReferenceException for recursive structures. Break cycles manually or use:
$accessor->setCircularReferenceHandler(function ($path, $value) {
return '[Circular Reference]';
});
Inspect Reflection Cache: For debugging, inspect the internal cache:
$reflectionCache = $accessor->getReflectionCache();
Custom Property Accessors:
Implement PropertyAccessorInterface to override default behavior (e.g., Laravel-specific logic).
Path Transformers:
Use PropertyPath::create() with custom transformers for dynamic paths:
$path = PropertyPath::create('user[addresses
How can I help you explore Laravel packages today?