Hooks & Events
Use hooks and events to customize DataMapper behavior.
Introduction
Section titled “Introduction”Hooks allow you to intercept and modify data during mapping:
- ✅ BeforeAll - Before mapping starts
- ✅ BeforeTransform - Before transforming value
- ✅ AfterTransform - After transforming value
- ✅ BeforeWrite - Before writing to target
- ✅ AfterAll - After mapping completes
Basic Usage
Section titled “Basic Usage”Creating Hooks
Section titled “Creating Hooks”use event4u\DataHelpers\DataMapper\Hooks;use event4u\DataHelpers\Enums\DataMapperHook;
$hooks = Hooks::make() ->on(DataMapperHook::BeforeTransform, fn($value) => trim($value)) ->toArray();
$result = DataMapper::source($src) ->target($tgt) ->template($template) ->hooks($hooks) ->map() ->getTarget();Available Hooks
Section titled “Available Hooks”BeforeAll
Section titled “BeforeAll”Runs before mapping starts:
$hooks = Hooks::make() ->on(DataMapperHook::BeforeAll, function($context) { // Initialize shared state $context->set('start_time', microtime(true)); }) ->toArray();BeforeTransform
Section titled “BeforeTransform”Runs before transforming each value:
$hooks = Hooks::make() ->on(DataMapperHook::BeforeTransform, function($value, $context) { // Trim strings return is_string($value) ? trim($value) : $value; }) ->toArray();AfterTransform
Section titled “AfterTransform”Runs after transforming each value:
$hooks = Hooks::make() ->on(DataMapperHook::AfterTransform, function($value, $context) { // Log transformed value Log::debug('Transformed', ['value' => $value]);
return $value; }) ->toArray();BeforeWrite
Section titled “BeforeWrite”Runs before writing to target:
$hooks = Hooks::make() ->on(DataMapperHook::BeforeWrite, function($value, $context) { // Skip null values if ($value === null) { return '__skip__'; }
return $value; }) ->toArray();AfterAll
Section titled “AfterAll”Runs after mapping completes:
$hooks = Hooks::make() ->on(DataMapperHook::AfterAll, function($context) { // Log completion time $duration = microtime(true) - $context->get('start_time'); Log::info('Mapping completed', ['duration' => $duration]); }) ->toArray();Real-World Examples
Section titled “Real-World Examples”Data Sanitization
Section titled “Data Sanitization”$hooks = Hooks::make() ->on(DataMapperHook::BeforeTransform, function($value) { if (is_string($value)) { // Trim whitespace $value = trim($value);
// Remove HTML tags $value = strip_tags($value);
// Sanitize $value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); }
return $value; }) ->toArray();Audit Trail
Section titled “Audit Trail”$hooks = Hooks::make() ->on(DataMapperHook::BeforeAll, function($context) { $context->set('changes', []); }) ->on(DataMapperHook::AfterTransform, function($value, $context) { $changes = $context->get('changes'); $changes[] = [ 'path' => $context->getPath(), 'value' => $value, 'timestamp' => now(), ]; $context->set('changes', $changes);
return $value; }) ->on(DataMapperHook::AfterAll, function($context) { AuditLog::create([ 'changes' => $context->get('changes'), 'user_id' => auth()->id(), ]); }) ->toArray();Data Validation
Section titled “Data Validation”$hooks = Hooks::make() ->on(DataMapperHook::BeforeTransform, function($value, $context) { $path = $context->getPath();
// Validate email if ($path === 'email' && !filter_var($value, FILTER_VALIDATE_EMAIL)) { throw new ValidationException('Invalid email'); }
// Validate age if ($path === 'age' && ($value < 0 || $value > 150)) { throw new ValidationException('Invalid age'); }
return $value; }) ->toArray();Default Values
Section titled “Default Values”$hooks = Hooks::make() ->on(DataMapperHook::BeforeWrite, function($value, $context) { $path = $context->getPath();
// Set default values if ($value === null) { return match($path) { 'status' => 'active', 'role' => 'user', 'created_at' => now(), default => $value, }; }
return $value; }) ->toArray();Data Transformation
Section titled “Data Transformation”$hooks = Hooks::make() ->on(DataMapperHook::AfterTransform, function($value, $context) { $path = $context->getPath();
// Transform specific fields return match($path) { 'email' => strtolower($value), 'name' => ucwords($value), 'phone' => preg_replace('/[^0-9]/', '', $value), default => $value, }; }) ->toArray();Conditional Hooks
Section titled “Conditional Hooks”For Specific Paths
Section titled “For Specific Paths”$hooks = Hooks::make() ->onForSrc(DataMapperHook::BeforeTransform, 'user.name', function($value) { return ucwords($value); }) ->onForTgt(DataMapperHook::BeforeWrite, 'profile.email', function($value) { return strtolower($value); }) ->toArray();For Specific Modes
Section titled “For Specific Modes”$hooks = Hooks::make() ->onForMode(DataMapperHook::BeforeAll, 'simple', function($context) { // Only for simple mode }) ->toArray();Multiple Hooks
Section titled “Multiple Hooks”Chaining Hooks
Section titled “Chaining Hooks”$hooks = Hooks::make() ->on(DataMapperHook::BeforeTransform, fn($v) => trim($v)) ->on(DataMapperHook::AfterTransform, fn($v) => strtolower($v)) ->on(DataMapperHook::BeforeWrite, fn($v) => $v ?: null) ->toArray();Merging Hooks
Section titled “Merging Hooks”$baseHooks = Hooks::make() ->on(DataMapperHook::BeforeTransform, fn($v) => trim($v)) ->toArray();
$customHooks = Hooks::make() ->on(DataMapperHook::AfterTransform, fn($v) => strtolower($v)) ->toArray();
$allHooks = array_merge($baseHooks, $customHooks);Hook Context
Section titled “Hook Context”Reading Context
Section titled “Reading Context”$hooks = Hooks::make() ->on(DataMapperHook::BeforeTransform, function($value, $context) { $path = $context->getPath(); $source = $context->getSource(); $target = $context->getTarget();
return $value; }) ->toArray();Setting Context
Section titled “Setting Context”$hooks = Hooks::make() ->on(DataMapperHook::BeforeAll, function($context) { $context->set('user_id', auth()->id()); $context->set('timestamp', now()); }) ->on(DataMapperHook::AfterTransform, function($value, $context) { $userId = $context->get('user_id'); // Use user_id
return $value; }) ->toArray();Skipping Values
Section titled “Skipping Values”Skip Null Values
Section titled “Skip Null Values”$hooks = Hooks::make() ->on(DataMapperHook::BeforeWrite, function($value) { return $value === null ? '__skip__' : $value; }) ->toArray();Skip Empty Strings
Section titled “Skip Empty Strings”$hooks = Hooks::make() ->on(DataMapperHook::BeforeWrite, function($value) { return $value === '' ? '__skip__' : $value; }) ->toArray();Conditional Skip
Section titled “Conditional Skip”$hooks = Hooks::make() ->on(DataMapperHook::BeforeWrite, function($value, $context) { $path = $context->getPath();
// Skip specific fields if (in_array($path, ['password', 'secret'])) { return '__skip__'; }
return $value; }) ->toArray();Error Handling
Section titled “Error Handling”Try-Catch
Section titled “Try-Catch”$hooks = Hooks::make() ->on(DataMapperHook::BeforeTransform, function($value) { try { return transform($value); } catch (Exception $e) { Log::error('Transform failed', ['error' => $e->getMessage()]); return $value; } }) ->toArray();Validation Errors
Section titled “Validation Errors”$hooks = Hooks::make() ->on(DataMapperHook::BeforeTransform, function($value, $context) { if (!validate($value)) { throw new ValidationException("Invalid value at {$context->getPath()}"); }
return $value; }) ->toArray();Best Practices
Section titled “Best Practices”Keep Hooks Simple
Section titled “Keep Hooks Simple”// ✅ Good - simple hook$hooks = Hooks::make() ->on(DataMapperHook::BeforeTransform, fn($v) => trim($v)) ->toArray();
// ❌ Bad - complex hook$hooks = Hooks::make() ->on(DataMapperHook::BeforeTransform, function($v) { // 100 lines of code }) ->toArray();Use Specific Hooks
Section titled “Use Specific Hooks”// ✅ Good - specific hook$hooks = Hooks::make() ->onForSrc(DataMapperHook::BeforeTransform, 'user.name', fn($v) => ucwords($v)) ->toArray();
// ❌ Bad - generic hook with conditions$hooks = Hooks::make() ->on(DataMapperHook::BeforeTransform, function($v, $ctx) { if ($ctx->getPath() === 'user.name') { return ucwords($v); } return $v; }) ->toArray();See Also
Section titled “See Also”- DataMapper - DataMapper guide
- Pipelines - Pipeline processing
- Custom Casts - Custom type casts