DataAccessor
DataAccessor provides a uniform way to read values from nested data structures including arrays, objects, Laravel Collections, and Eloquent Models. It supports dot-notation paths, numeric indices, and powerful wildcard operations.
Quick Example
Section titled “Quick Example”use Event4u\DataHelpers\DataAccessor;
$data = [ 'users' => [ ['name' => 'Alice', 'email' => 'alice@example.com', 'age' => 30], ['name' => 'Bob', 'email' => 'bob@example.com', 'age' => 25], ['name' => 'Charlie', 'email' => 'charlie@example.com', 'age' => 35], ],];
$accessor = new DataAccessor($data);
// Simple path$name = $accessor->get('users.0.name');// Returns: 'Alice'
// Wildcard - extract all emails$emails = $accessor->get('users.*.email');// Returns: [// 'users.0.email' => 'alice@example.com',// 'users.1.email' => 'bob@example.com',// 'users.2.email' => 'charlie@example.com',// ]
// Default value$country = $accessor->get('users.0.country', 'Unknown');// Returns: 'Unknown' (path doesn't exist)Introduction
Section titled “Introduction”DataAccessor works with multiple data types:
- Arrays - Nested arrays with any depth
- Objects - Plain PHP objects with public properties
- DTOs - Data Transfer Objects
- Laravel Collections -
Illuminate\Support\Collection - Eloquent Models - Including relationships
- Arrayable - Any object implementing
Arrayable - JsonSerializable - Any object implementing
JsonSerializable - JSON strings - Automatically parsed
- XML strings - Automatically parsed
Basic Usage
Section titled “Basic Usage”Creating an Accessor
Section titled “Creating an Accessor”use Event4u\DataHelpers\DataAccessor;
// From array$accessor = new DataAccessor($array);
// From object$accessor = new DataAccessor($object);
// From Collection$accessor = new DataAccessor($collection);
// From Eloquent Model$accessor = new DataAccessor($model);
// From JSON string$accessor = new DataAccessor('{"user":{"name":"Alice"}}');
// From XML string$accessor = new DataAccessor('<user><name>Alice</name></user>');Reading Values
Section titled “Reading Values”$data = [ 'user' => [ 'profile' => [ 'name' => 'John Doe', 'email' => 'john@example.com', ], ],];
$accessor = new DataAccessor($data);
// Dot-notation path$name = $accessor->get('user.profile.name');// Returns: 'John Doe'
// Non-existent path returns null$phone = $accessor->get('user.profile.phone');// Returns: nullDefault Values
Section titled “Default Values”// Provide default value as second parameter$phone = $accessor->get('user.profile.phone', 'N/A');// Returns: 'N/A' (path doesn't exist)
$name = $accessor->get('user.profile.name', 'Anonymous');// Returns: 'John Doe' (path exists, default ignored)Wildcards
Section titled “Wildcards”Wildcards allow you to extract values from multiple items at once.
Basic Wildcards
Section titled “Basic Wildcards”$data = [ 'users' => [ ['email' => 'alice@example.com'], ['email' => 'bob@example.com'], ['email' => 'charlie@example.com'], ],];
$accessor = new DataAccessor($data);$emails = $accessor->get('users.*.email');
// Returns associative array with full paths as keys:// [// 'users.0.email' => 'alice@example.com',// 'users.1.email' => 'bob@example.com',// 'users.2.email' => 'charlie@example.com',// ]Why Full Path Keys?
Section titled “Why Full Path Keys?”The full path keys are intentional and provide:
- Stability - Keys remain consistent across operations
- Traceability - You know exactly where each value came from
- Integration - DataMutator and DataMapper consume this format
- Uniqueness - No key collisions in complex structures
Null Values in Wildcards
Section titled “Null Values in Wildcards”$data = [ 'users' => [ ['email' => 'alice@example.com'], ['email' => null], ['email' => 'bob@example.com'], ],];
$accessor = new DataAccessor($data);$emails = $accessor->get('users.*.email');
// Returns:// [// 'users.0.email' => 'alice@example.com',// 'users.1.email' => null,// 'users.2.email' => 'bob@example.com',// ]
// Filter out nulls if needed$validEmails = array_filter($emails, fn($v) => $v !== null);// [// 'users.0.email' => 'alice@example.com',// 'users.2.email' => 'bob@example.com',// ]Deep Wildcards
Section titled “Deep Wildcards”Multiple wildcards in one path create a flat associative array with full dot-path keys.
Multiple Wildcards
Section titled “Multiple Wildcards”$data = [ 'users' => [ [ 'name' => 'Alice', 'addresses' => [ 'home' => ['city' => 'Berlin'], 'work' => ['city' => 'Hamburg'], ], ], [ 'name' => 'Bob', 'addresses' => [ 'home' => ['city' => 'Munich'], ], ], ],];
$accessor = new DataAccessor($data);$cities = $accessor->get('users.*.addresses.*.city');
// Returns:// [// 'users.0.addresses.home.city' => 'Berlin',// 'users.0.addresses.work.city' => 'Hamburg',// 'users.1.addresses.home.city' => 'Munich',// ]Three-Level Wildcards
Section titled “Three-Level Wildcards”$data = [ 'departments' => [ [ 'users' => [ ['posts' => [['title' => 'Post 1'], ['title' => 'Post 2']]], ['posts' => [['title' => 'Post 3']]], ], ], [ 'users' => [ ['posts' => [['title' => 'Post 4']]], ], ], ],];
$accessor = new DataAccessor($data);$titles = $accessor->get('departments.*.users.*.posts.*.title');
// Returns:// [// 'departments.0.users.0.posts.0.title' => 'Post 1',// 'departments.0.users.0.posts.1.title' => 'Post 2',// 'departments.0.users.1.posts.0.title' => 'Post 3',// 'departments.1.users.0.posts.0.title' => 'Post 4',// ]Working with Collections
Section titled “Working with Collections”DataAccessor seamlessly handles Laravel Collections.
Collection Input
Section titled “Collection Input”use Illuminate\Support\Collection;
$data = [ 'users' => collect([ ['name' => 'Alice', 'email' => 'alice@example.com'], ['name' => 'Bob', 'email' => 'bob@example.com'], ]),];
$accessor = new DataAccessor($data);$emails = $accessor->get('users.*.email');
// Returns:// [// 'users.0.email' => 'alice@example.com',// 'users.1.email' => 'bob@example.com',// ]Nested Collections
Section titled “Nested Collections”$data = [ 'orders' => collect([ [ 'items' => collect([ ['sku' => 'A', 'price' => 10], ['sku' => 'B', 'price' => 20], ]), ], [ 'items' => collect([ ['sku' => 'C', 'price' => 30], ]), ], ]),];
$accessor = new DataAccessor($data);$skus = $accessor->get('orders.*.items.*.sku');
// Returns:// [// 'orders.0.items.0.sku' => 'A',// 'orders.0.items.1.sku' => 'B',// 'orders.1.items.0.sku' => 'C',// ]Accessing by Index
Section titled “Accessing by Index”$data = [ 'users' => collect([ ['name' => 'Alice'], ['name' => 'Bob'], ]),];
$accessor = new DataAccessor($data);
// Access specific index$firstUser = $accessor->get('users.0.name');// Returns: 'Alice'
// Wildcard still works$allNames = $accessor->get('users.*.name');// Returns: ['users.0.name' => 'Alice', 'users.1.name' => 'Bob']Working with Eloquent Models
Section titled “Working with Eloquent Models”DataAccessor works with Eloquent Models and their relationships.
Basic Model Access
Section titled “Basic Model Access”$user = User::find(1);$accessor = new DataAccessor($user);
$name = $accessor->get('name');$email = $accessor->get('email');Accessing Relationships
Section titled “Accessing Relationships”$user = User::with('posts.comments')->first();$accessor = new DataAccessor($user);
// Access relationship$postTitles = $accessor->get('posts.*.title');
// Deep relationship access$commentTexts = $accessor->get('posts.*.comments.*.text');Model Collections
Section titled “Model Collections”$users = User::with('posts')->get();$accessor = new DataAccessor(['users' => $users]);
// Extract all post titles from all users$allPostTitles = $accessor->get('users.*.posts.*.title');JSON and XML Input
Section titled “JSON and XML Input”DataAccessor automatically parses JSON and XML strings.
JSON Strings
Section titled “JSON Strings”$json = '{"users":[{"name":"Alice","age":30},{"name":"Bob","age":25}]}';$accessor = new DataAccessor($json);
$names = $accessor->get('users.*.name');// Returns: ['users.0.name' => 'Alice', 'users.1.name' => 'Bob']
$firstAge = $accessor->get('users.0.age');// Returns: 30XML Strings
Section titled “XML Strings”$xml = '<users><user><name>Alice</name></user><user><name>Bob</name></user></users>';$accessor = new DataAccessor($xml);
$names = $accessor->get('users.user.*.name');// Returns parsed XML as array structureCommon Patterns
Section titled “Common Patterns”Extract All Values from Nested Structure
Section titled “Extract All Values from Nested Structure”$data = [ 'departments' => [ ['users' => [['email' => 'a@x.com'], ['email' => 'b@x.com']]], ['users' => [['email' => 'c@x.com']]], ],];
$accessor = new DataAccessor($data);$emails = $accessor->get('departments.*.users.*.email');
// Returns:// [// 'departments.0.users.0.email' => 'a@x.com',// 'departments.0.users.1.email' => 'b@x.com',// 'departments.1.users.0.email' => 'c@x.com',// ]Safe Access with Default
Section titled “Safe Access with Default”$accessor = new DataAccessor($config);
// Always provide sensible defaults$theme = $accessor->get('app.settings.theme', 'default');$timeout = $accessor->get('app.settings.timeout', 30);$debug = $accessor->get('app.settings.debug', false);Combining with Array Functions
Section titled “Combining with Array Functions”$accessor = new DataAccessor($data);$prices = $accessor->get('products.*.price');
// Calculate total$totalPrice = array_sum($prices);
// Calculate average$avgPrice = count($prices) > 0 ? array_sum($prices) / count($prices) : 0;
// Find max/min$maxPrice = max($prices);$minPrice = min($prices);Root-Level Numeric Indices
Section titled “Root-Level Numeric Indices”$data = [ ['name' => 'Alice', 'age' => 30], ['name' => 'Bob', 'age' => 25],];
$accessor = new DataAccessor($data);
// Access specific index$firstUser = $accessor->get('0.name');// Returns: 'Alice'
// Use wildcard at root level$allNames = $accessor->get('*.name');// Returns: ['0.name' => 'Alice', '1.name' => 'Bob']Filter Null Values
Section titled “Filter Null Values”$data = ['users' => [ ['email' => 'alice@x.com'], ['email' => null], ['email' => 'bob@x.com'],]];
$accessor = new DataAccessor($data);$emails = $accessor->get('users.*.email');
// Filter out nulls$validEmails = array_filter($emails, fn($v) => $v !== null);// Returns: ['users.0.email' => 'alice@x.com', 'users.2.email' => 'bob@x.com']
// Get only values (remove keys)$emailList = array_values($validEmails);// Returns: ['alice@x.com', 'bob@x.com']Best Practices
Section titled “Best Practices”Use Wildcards for Bulk Reads
Section titled “Use Wildcards for Bulk Reads”When you need to extract the same field from multiple items, wildcards are more efficient than looping:
// ❌ Inefficient$emails = [];foreach ($data['users'] as $user) { $emails[] = $user['email'];}
// ✅ Efficient$accessor = new DataAccessor($data);$emails = $accessor->get('users.*.email');Combine with DataMutator
Section titled “Combine with DataMutator”Use DataAccessor to read values and DataMutator to write them into a new structure:
$accessor = new DataAccessor($sourceData);$emails = $accessor->get('users.*.email');
$mutator = new DataMutator([]);$mutator->set('contacts.*.email', $emails);Always Provide Defaults
Section titled “Always Provide Defaults”Avoid null checks by providing sensible defaults:
// ❌ Requires null check$theme = $accessor->get('settings.theme');if ($theme === null) { $theme = 'default';}
// ✅ Clean and safe$theme = $accessor->get('settings.theme', 'default');Leverage Collections
Section titled “Leverage Collections”DataAccessor works seamlessly with Laravel Collections:
$accessor = new DataAccessor($data);$prices = $accessor->get('products.*.price');
// Convert to Collection for chaining$collection = collect($prices);$filtered = $collection->filter(fn($p) => $p > 100)->values();Structure Introspection
Section titled “Structure Introspection”DataAccessor provides methods to analyze the structure of your data with type information.
Get Structure (Flat)
Section titled “Get Structure (Flat)”The getStructure() method returns a flat array with dot-notation paths and type information:
use Event4u\DataHelpers\DataAccessor;
$data = [ 'name' => 'John Doe', 'age' => 30, 'emails' => [ ['email' => 'john@work.com', 'type' => 'work', 'verified' => true], ['email' => 'john@home.com', 'type' => 'home', 'verified' => false], ],];
$accessor = new DataAccessor($data);$structure = $accessor->getStructure();
// Returns:// [// 'name' => 'string',// 'age' => 'int',// 'emails' => 'array',// 'emails.*' => 'array',// 'emails.*.email' => 'string',// 'emails.*.type' => 'string',// 'emails.*.verified' => 'bool',// ]Get Structure (Multidimensional)
Section titled “Get Structure (Multidimensional)”The getStructureMultidimensional() method returns a nested array structure:
$accessor = new DataAccessor($data);$structure = $accessor->getStructureMultidimensional();
// Returns:// [// 'name' => 'string',// 'age' => 'int',// 'emails' => [// '*' => [// 'email' => 'string',// 'type' => 'string',// 'verified' => 'bool',// ],// ],// ]Wildcards in Structure
Section titled “Wildcards in Structure”Arrays use wildcards (*) to represent the structure of all elements:
$data = [ 'departments' => [ [ 'name' => 'Engineering', 'employees' => [ ['name' => 'Alice', 'age' => 30], ['name' => 'Bob', 'age' => 25], ], ], [ 'name' => 'Sales', 'employees' => [ ['name' => 'Charlie', 'age' => 35], ], ], ],];
$accessor = new DataAccessor($data);$structure = $accessor->getStructure();
// Returns:// [// 'departments' => 'array',// 'departments.*' => 'array',// 'departments.*.name' => 'string',// 'departments.*.employees' => 'array',// 'departments.*.employees.*' => 'array',// 'departments.*.employees.*.name' => 'string',// 'departments.*.employees.*.age' => 'int',// ]Union Types
Section titled “Union Types”When array elements have different types, union types are returned:
$data = [ 'values' => [ 'string value', 42, null, true, ],];
$accessor = new DataAccessor($data);$structure = $accessor->getStructure();
// Returns:// [// 'values' => 'array',// 'values.*' => 'bool|int|null|string',// ]Object Types
Section titled “Object Types”Objects are returned with their full namespace:
use Event4u\DataHelpers\SimpleDTO;
class EmailDTO extends SimpleDTO{ public function __construct( public readonly string $email, public readonly bool $verified, ) {}}
$data = [ 'contact' => new EmailDTO('john@example.com', true),];
$accessor = new DataAccessor($data);$structure = $accessor->getStructure();
// Returns:// [// 'contact' => '\EmailDTO',// 'contact.email' => 'string',// 'contact.verified' => 'bool',// ]Use Cases
Section titled “Use Cases”Structure introspection is useful for:
- API Documentation - Generate API schemas automatically
- Validation - Verify data structure matches expectations
- Type Checking - Ensure data types are correct
- Debugging - Understand complex data structures
- Code Generation - Generate TypeScript interfaces or PHP classes
- Testing - Verify data structure in tests
// Example: Validate API response structure$accessor = new DataAccessor($apiResponse);$structure = $accessor->getStructure();
$expectedStructure = [ 'status' => 'string', 'data' => 'array', 'data.users' => 'array', 'data.users.*' => 'array', 'data.users.*.id' => 'int', 'data.users.*.name' => 'string', 'data.users.*.email' => 'string',];
foreach ($expectedStructure as $path => $expectedType) { if (!isset($structure[$path]) || $structure[$path] !== $expectedType) { throw new Exception("Invalid structure at path: $path"); }}Performance Notes
Section titled “Performance Notes”Wildcard Performance
Section titled “Wildcard Performance”- Wildcards traverse all matching elements
- Performance scales with the number of matches
- For large datasets, consider filtering data first
// ❌ Slow on large datasets$accessor = new DataAccessor($hugeDataset);$allEmails = $accessor->get('users.*.email');
// ✅ Filter first$activeUsers = array_filter($hugeDataset['users'], fn($u) => $u['active']);$accessor = new DataAccessor(['users' => $activeUsers]);$emails = $accessor->get('users.*.email');Deep Wildcards
Section titled “Deep Wildcards”Multiple wildcards can be expensive on large nested structures:
// Can be slow on large datasets$accessor->get('departments.*.teams.*.users.*.email');
// Consider limiting depth or filteringCaching
Section titled “Caching”DataAccessor uses internal caching for path resolution, so repeated calls with the same path are fast:
$accessor = new DataAccessor($data);
// First call parses path$value1 = $accessor->get('user.profile.name');
// Subsequent calls use cached path (fast)$value2 = $accessor->get('user.profile.name');Code Examples
Section titled “Code Examples”The following working examples demonstrate DataAccessor in action:
- Basic Usage - Complete example showing dot-notation, wildcards, and default values
- Structure Introspection - Examples of analyzing data structure with type information
All examples are fully tested and can be run directly:
php examples/main-classes/data-accessor/basic-usage.phpphp examples/main-classes/data-accessor/structure-introspection.phpRelated Tests
Section titled “Related Tests”The DataAccessor functionality is thoroughly tested. Key test files:
Unit Tests:
- DataAccessorTest.php - Core functionality tests
- DataAccessorLazyWildcardTest.php - Wildcard behavior tests
- DataAccessorDoctrineTest.php - Doctrine integration tests
- DataAccessorLaravelTest.php - Laravel integration tests
Integration Tests:
- DataAccessorIntegrationTest.php - End-to-end scenarios
Run the tests:
# Run all DataAccessor teststask test:unit -- --filter=DataAccessor
# Run specific test filevendor/bin/pest tests/Unit/DataAccessor/DataAccessorTest.phpSee Also
Section titled “See Also”- DataMutator - Modify nested data
- DataMapper - Transform data structures
- DataFilter - Query and filter data
- Core Concepts: Dot-Notation - Path syntax
- Core Concepts: Wildcards - Wildcard operators
- Examples - 90+ code examples