Installation
composer require zingle/api-resource-mapper
Register ApiResourceMapperProvider in config/app.php under providers.
Define Mapping Configuration
Create a mapping file (e.g., Resources/config/mapping/foo_module.json) in your module:
{
"FooModel": {
"source": ["id", "name", "api_field"],
"target": ["id", "name", "custom_name"],
"transform": {
"custom_name": "strtoupper"
}
}
}
Register Mapper in Module Provider
Add the registerMapper() method to your module’s service provider:
private function registerMapper()
{
$this->app->bind('zingle.foo_module.meta_loader', function ($app) {
$module = $app->make('laravel_modules.repository')->find('FooModule');
return new \Zingle\ApiResourceMapper\Loader($module->getExtraPath('Resources/config/mapping'));
});
$this->app->bind('zingle.foo_module.model_meta_factory', function ($app) {
return new \Zingle\ApiResourceMapper\ModelMetaFactory($app->make('zingle.foo_module.meta_loader'));
});
}
First Use Case
Extend AbstractResource and inject the mapper in the constructor:
use Zingle\ApiResourceMapper\AbstractResource;
class FooResource extends AbstractResource
{
public function __construct($mapperFactory)
{
$this->mapper = $mapperFactory->getMapper(app('zingle.foo_module.model_meta_factory'));
}
public function toArray($request)
{
return $this->mapper->map($this->resource);
}
}
Mapping Definition
Define mappings in JSON files under Resources/config/mapping/ for each module. Example:
{
"User": {
"source": ["api_id", "email", "created_at"],
"target": ["id", "email", "created_at"],
"transform": {
"email": "strtolower"
}
}
}
Dynamic Resource Mapping
Use the mapper in toArray() or toResponse() methods:
public function toArray($request)
{
return $this->mapper->map($this->resource, 'User'); // 'User' is the model key in mapping
}
Collection Handling
Map collections by leveraging the mapCollection method:
public function toArray($request)
{
return $this->mapper->mapCollection($this->resource->all(), 'User');
}
Conditional Mapping
Use the conditionalMap method for runtime logic:
$this->mapper->conditionalMap($resource, 'User', function ($item) {
return $item->isActive();
});
Integration with API Clients Combine with HTTP clients (e.g., Guzzle) to fetch and map data:
$response = $client->get('api/users');
$users = json_decode($response->getBody(), true);
return $this->mapper->mapCollection($users, 'User');
Missing Mapping Files
Ensure Resources/config/mapping/ exists and is accessible. Double-check paths in Loader initialization.
Case Sensitivity
Model keys in JSON mappings must match the class name exactly (e.g., User, not user).
Circular Dependencies Avoid binding the mapper factory to the same module’s provider if the module depends on the mapper during bootstrapping.
Transform Functions
Custom transform functions (e.g., "transform": {"field": "myFunction"}) must be globally available or autoloaded. Use fully qualified namespaces if needed:
"transform": {
"field": "\\App\\Transformers::customTransform"
}
Performance with Large Collections
For large datasets, cache the ModelMetaFactory instance to avoid reloading mappings:
$this->app->singleton('zingle.foo_module.model_meta_factory', function ($app) {
return new ModelMetaFactory($app->make('zingle.foo_module.meta_loader'));
});
Validate Mappings
Use php artisan tinker to test mappings interactively:
$mapper = app('zingle.foo_module.model_meta_factory')->getMapper();
$mapper->map($rawData, 'User'); // Check output for errors
Logging
Enable Laravel’s debug mode (APP_DEBUG=true) to log missing mappings or transform errors.
Fallback Handling
Implement a fallback in AbstractResource for unmapped fields:
public function toArray($request)
{
$mapped = $this->mapper->map($this->resource, 'User');
return array_merge($mapped, $this->resource->toArray()); // Fallback to raw data
}
Custom Mappers
Extend AbstractMapper to add logic (e.g., nested resource handling):
class NestedMapper extends AbstractMapper
{
public function map($resource, $modelKey)
{
// Custom logic
return parent::map($resource, $modelKey);
}
}
Dynamic JSON Loading
Override the Loader class to fetch mappings from a database or external API:
class DatabaseLoader extends Loader
{
public function load($path)
{
return json_decode($this->fetchFromDatabase($path), true);
}
}
Event-Based Mapping
Dispatch events before/after mapping (e.g., MappingStarted, MappingCompleted):
use Illuminate\Support\Facades\Event;
Event::listen('zingle.mapping.started', function ($resource, $modelKey) {
// Pre-process resource
});
How can I help you explore Laravel packages today?