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.

1.0.0 2025-10-05 18:28 UTC

This package is auto-updated.

Last update: 2025-10-05 18:29:53 UTC


README

event4u Data Helpers

🚀 Data Helpers

Packagist Version PHP License: MIT GitHub Code Quality Action Status GitHub PHPStan Action Status GitHub Tests Action Status

// 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

👉 Create custom transformers

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

Runnable Examples

🔍 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

Sponsor @matze4u    Sponsor event4u-app

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: