event4u / data-helpers
Framework-agnostic helpers for structured data access, mutation, and mapping using dot-notation paths with wildcards. Works with Laravel, Symfony/Doctrine, or standalone PHP.
Fund package maintenance!
event4u-app
matze4u
Requires
- php: ^8.2
- composer-plugin-api: ^2.0
Requires (Dev)
- composer/composer: ^2.0
- doctrine/collections: ^2.0
- doctrine/orm: ^3.0
- ergebnis/phpstan-rules: ^2.12
- illuminate/database: ^11.0
- illuminate/support: ^11.0
- jangregor/phpstan-prophecy: ^2.2
- pestphp/pest: ^3.0
- phpat/phpat: ^0.12.0
- phpbench/phpbench: ^1.4
- phpstan/phpstan: ^2.1
- phpstan/phpstan-mockery: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- rector/rector: ^2.1
- spaze/phpstan-disallowed-calls: ^4.6
- symplify/coding-standard: ^12.4
- symplify/easy-coding-standard: ^12.6
- timeweb/phpstan-enum: ^4.0
Suggests
- doctrine/collections: Required for Doctrine Collection support (^1.6 || ^2.0)
- doctrine/orm: Required for Doctrine Entity support (^2.10 || ^3.0)
- illuminate/database: Required for Eloquent Model support (^11.0)
- illuminate/support: Required for Laravel Collection and Arrayable support (^11.0)
README

🚀 Data Helpers
// From this messy API response... $apiResponse = [ 'data' => [ 'departments' => [ ['users' => [['email' => 'alice@example.com'], ['email' => 'bob@example.com']]], ['users' => [['email' => 'charlie@example.com']]], ], ], ]; // ...to this clean result in one line $accessor = new DataAccessor($apiResponse); $emails = $accessor->get('data.departments.*.users.*.email'); // ['alice@example.com', 'bob@example.com', 'charlie@example.com']
Framework-agnostic • Works with Laravel, Symfony/Doctrine, or standalone PHP • Zero required dependencies
💡 Why use this?
🎯 Stop writing nested loops and array checks
// ❌ Without Data Helpers $emails = []; foreach ($data['departments'] ?? [] as $dept) { foreach ($dept['users'] ?? [] as $user) { if (isset($user['email'])) { $emails[] = $user['email']; } } } // ✅ With Data Helpers $emails = $accessor->get('departments.*.users.*.email');
🔄 Transform data structures with ease
Map between different data formats, APIs, or database schemas without writing repetitive transformation code.
🛡️ Type-safe and well-tested
PHPStan Level 9 compliant with 400+ tests. Works reliably with arrays, objects, Collections, Models, JSON, and XML.
⚡ Framework-agnostic with smart detection
Use it anywhere - Laravel, Symfony, Doctrine, or plain PHP. Framework support is automatically detected at runtime.
📦 Installation
composer require event4u/data-helpers
Requirements: PHP 8.2+
Framework support (all optional):
- 🔴 Laravel 8+ - Collections, Eloquent Models
- ⚫ Symfony/Doctrine - Collections, Entities
- 🔧 Standalone PHP - Works out of the box
👉 See detailed framework setup guide
⚡ Quick Start
1️⃣ DataAccessor - Read nested data
use event4u\DataHelpers\DataAccessor; $data = [ 'users' => [ ['name' => 'Alice', 'email' => 'alice@example.com'], ['name' => 'Bob', 'email' => 'bob@example.com'], ], ]; $accessor = new DataAccessor($data); // Get all emails with wildcard $emails = $accessor->get('users.*.email'); // ['alice@example.com', 'bob@example.com'] // Works with JSON too $accessor = new DataAccessor('{"users":[{"name":"Alice"}]}'); $name = $accessor->get('users.0.name'); // 'Alice'
2️⃣ DataMutator - Modify nested data
use event4u\DataHelpers\DataMutator; $data = []; // Set deeply nested values $data = DataMutator::set($data, 'user.profile.name', 'Alice'); // ['user' => ['profile' => ['name' => 'Alice']]] // Merge arrays deeply $data = DataMutator::merge($data, 'user.profile', ['age' => 30]); // ['user' => ['profile' => ['name' => 'Alice', 'age' => 30]]] // Unset multiple paths $data = DataMutator::unset($data, ['user.profile.age', 'user.unknown']);
3️⃣ DataMapper - Transform data structures
use event4u\DataHelpers\DataMapper; $source = [ 'firstName' => 'Alice', 'lastName' => 'Smith', 'contact' => ['email' => 'alice@example.com'], ]; $mapping = [ 'profile' => [ 'name' => 'firstName', 'surname' => 'lastName', ], 'email' => 'contact.email', ]; $result = DataMapper::map($source, [], $mapping); // [ // 'profile' => ['name' => 'Alice', 'surname' => 'Smith'], // 'email' => 'alice@example.com' // ]
🎯 Core Features
Dot-Notation Paths with Wildcards
Access deeply nested data without writing loops:
$data = [ 'company' => [ 'departments' => [ ['name' => 'Engineering', 'employees' => [['name' => 'Alice'], ['name' => 'Bob']]], ['name' => 'Sales', 'employees' => [['name' => 'Charlie']]], ], ], ]; $accessor = new DataAccessor($data); // Single wildcard $deptNames = $accessor->get('company.departments.*.name'); // ['Engineering', 'Sales'] // Multi-level wildcards $allEmployees = $accessor->get('company.departments.*.employees.*.name'); // ['Alice', 'Bob', 'Charlie']
Works with Multiple Data Types
// Arrays $accessor = new DataAccessor(['user' => ['name' => 'Alice']]); // Objects $accessor = new DataAccessor((object)['user' => (object)['name' => 'Alice']]); // JSON strings $accessor = new DataAccessor('{"user":{"name":"Alice"}}'); // XML strings $accessor = new DataAccessor('<root><user><name>Alice</name></user></root>'); // Laravel Collections (if illuminate/support is installed) $accessor = new DataAccessor(collect(['user' => ['name' => 'Alice']])); // Doctrine Collections (if doctrine/collections is installed) $accessor = new DataAccessor(new ArrayCollection(['user' => ['name' => 'Alice']]));
Type-Safe Getters
$accessor = new DataAccessor(['age' => '25', 'active' => 'true']); $age = $accessor->getInt('age'); // 25 (int) $active = $accessor->getBool('active'); // true (bool) $name = $accessor->getString('name', 'Unknown'); // 'Unknown' (default)
🚀 Advanced Features
Pipeline API - Compose Transformers
Build reusable data transformation pipelines:
use event4u\DataHelpers\DataMapper; use event4u\DataHelpers\DataMapper\Pipeline\Transformers\{TrimStrings, LowercaseEmails, SkipEmptyValues}; $source = [ 'user' => [ 'name' => ' Alice ', 'email' => ' ALICE@EXAMPLE.COM ', 'phone' => '', ], ]; $mapping = [ 'profile' => [ 'name' => 'user.name', 'email' => 'user.email', 'phone' => 'user.phone', ], ]; $result = DataMapper::pipe([ TrimStrings::class, LowercaseEmails::class, SkipEmptyValues::class, ])->map($source, [], $mapping); // Result: { // "profile": { // "name": "Alice", // "email": "alice@example.com" // // phone is skipped (empty) // } // }
Built-in transformers: TrimStrings
, LowercaseEmails
, SkipEmptyValues
, UppercaseStrings
, ConvertToNull
Template Expressions - Powerful Mapping
Use Twig-like expressions in your templates:
$template = [ 'user' => [ 'id' => '{{ user.id }}', 'name' => '{{ user.firstName | ucfirst }} {{ user.lastName | ucfirst }}', 'email' => '{{ user.email | lower | trim }}', 'role' => '{{ user.role | upper ?? "USER" }}', 'tags' => '{{ user.tags }}', 'tagCount' => '{{ user.tags | count }}', ], ]; $sources = [ 'user' => [ 'id' => 123, 'firstName' => 'alice', 'lastName' => 'smith', 'email' => ' ALICE@EXAMPLE.COM ', 'role' => null, 'tags' => ['php', 'laravel'], ], ]; $result = DataMapper::mapFromTemplate($template, $sources); // { // "user": { // "id": 123, // "name": "Alice Smith", // "email": "alice@example.com", // "role": "USER", // "tags": ["php", "laravel"], // "tagCount": 2 // } // }
15 built-in filters: lower
, upper
, trim
, ucfirst
, ucwords
, count
, first
, last
, keys
, values
, reverse
, sort
,
unique
, join
, json
, default
👉 See all filters and create custom ones
AutoMap - Automatic Property Mapping
Automatically map properties with matching names:
$source = [ 'id' => 1, 'name' => 'Alice', 'email' => 'alice@example.com', 'extra' => 'ignored', ]; $target = [ 'id' => null, 'name' => null, 'email' => null, ]; $result = DataMapper::autoMap($source, $target); // ['id' => 1, 'name' => 'Alice', 'email' => 'alice@example.com']
🔧 Framework Support
This package works with any PHP 8.2+ project. Framework support is optional and automatically detected.
Standalone PHP (No dependencies)
✅ Arrays, Objects, JSON, XML
Laravel 8+ (Optional)
composer require illuminate/support illuminate/database
✅ Collections, Eloquent Models, Arrayable interface
Symfony/Doctrine (Optional)
composer require doctrine/collections doctrine/orm
✅ Doctrine Collections, Entities
Mixed Environments
Use Laravel and Doctrine together - automatic detection handles both!
📖 Full framework integration guide with compatibility matrix and examples
📖 Documentation
Comprehensive Guides
- Data Accessor - Read nested data with wildcards, Collections, and Models
- Data Mutator - Write, merge, and unset nested values with wildcards
- Data Mapper - Map between structures with templates, transforms, and hooks
- Template Expressions - Powerful expression engine with filters and defaults
- Dot-Path Syntax - Path notation reference and best practices
- Optional Dependencies - Framework integration guide
Runnable Examples
- 01-data-accessor.php - Basic array access with wildcards
- 02-data-mutator.php - Mutating arrays
- 03-data-mapper-simple.php - Simple mapping
- 04-data-mapper-with-hooks.php - Advanced mapping with hooks
- 05-data-mapper-pipeline.php - Pipeline API with transformers
- 06-laravel.php - Laravel Collections, Eloquent Models
- 07-symfony-doctrine.php - Doctrine Collections and Entities
- 08-template-expressions.php - Template expressions with filters
🔍 Common Use Cases
API Response Transformation
// Transform external API response to your internal format $apiResponse = $client->get('/users'); $mapping = [ 'users' => [ '*' => [ 'userId' => 'data.*.id', 'email' => 'data.*.attributes.email', 'name' => 'data.*.attributes.profile.name', ], ], ]; $result = DataMapper::map($apiResponse, [], $mapping);
Database Migration
// Map old database structure to new schema $oldData = $oldDb->query('SELECT * FROM legacy_users'); $mapping = [ 'profile' => [ 'firstName' => 'first_name', 'lastName' => 'last_name', ], 'contact' => [ 'email' => 'email_address', ], ]; foreach ($oldData as $row) { $newData = DataMapper::map($row, [], $mapping); $newDb->insert('users', $newData); }
Form Data Normalization
// Clean and normalize user input $formData = $_POST; $result = DataMapper::pipe([ TrimStrings::class, LowercaseEmails::class, SkipEmptyValues::class, ])->map($formData, [], $mapping);
🧪 Testing & Quality
- ✅ 400+ tests with 1500+ assertions
- ✅ PHPStan Level 9 - Highest static analysis level
- ✅ 100% type coverage - All methods fully typed
- ✅ Pest - Modern testing framework
- ✅ Continuous Integration - Automated testing
⚡ Performance
All operations are highly optimized and run in microseconds:
DataAccessor
Operation | Time | Description |
---|---|---|
Simple Get | 0.321μs | Get value from flat array |
Nested Get | 0.424μs | Get value from nested path |
Wildcard Get | 4.710μs | Get values using single wildcard |
Deep Wildcard Get | 72.335μs | Get values using multiple wildcards (10 depts × 20 employees) |
Typed Get String | 0.361μs | Get typed string value |
Typed Get Int | 0.361μs | Get typed int value |
Create Accessor | 0.083μs | Instantiate DataAccessor |
DataMutator
Operation | Time | Description |
---|---|---|
Simple Set | 0.595μs | Set value in flat array |
Nested Set | 0.947μs | Set value in nested path |
Deep Set | 1.146μs | Set value creating new nested structure |
Multiple Set | 1.721μs | Set multiple values at once |
Merge | 0.980μs | Deep merge arrays |
Unset | 0.898μs | Remove single value |
Multiple Unset | 1.485μs | Remove multiple values |
DataMapper
Operation | Time | Description |
---|---|---|
Simple Mapping | 6.288μs | Map flat structure |
Nested Mapping | 7.129μs | Map nested structure |
Auto Map | 6.804μs | Automatic field mapping |
Map From Template | 5.091μs | Map using template expressions |
Key Insights:
- Simple and nested access is extremely fast (~0.3-0.4μs)
- Wildcards add minimal overhead (~5μs for single level)
- All mutation operations are sub-microsecond
- Mapping operations are in the 5-7μs range
Run benchmarks yourself: composer bench
📋 Requirements
- PHP 8.2+
- Optional: Laravel 8+, Symfony 5+, Doctrine Collections 1.6+, Doctrine ORM 2.10+
🤝 Contributing
Contributions are welcome! Please see docs/contributing.md for details.
💖 Sponsoring
This package is part of the event4u ecosystem - a comprehensive event management platform. Your sponsorship helps us:
- 🚀 Develop event4u - The next-generation event management app
- 📦 Maintain open-source packages - Like this Data Helpers library
- 🔧 Build new tools - More packages and utilities for the PHP community
- 📚 Improve documentation - Better guides and examples
- 🐛 Fix bugs faster - Dedicated time for maintenance and support
Support the Development
Every contribution, no matter how small, makes a difference and is greatly appreciated! 🙏
📄 License
MIT License. See LICENSE for details.
🌟 Show Your Support
If this package helps you, please consider:
- ⭐ Giving it a star on GitHub
- 💖 Sponsoring the development
- 📢 Sharing it with others