Model Transformers in Lightpack ORM

Model transformers are a powerful feature in Lightpack ORM, designed to help you convert your models (and collections of models) into clean, structured arrays for API responses, view rendering, or any custom output. They empower you to:


What is a Transformer?

A transformer is a dedicated class that defines how a model (or collection of models) should be represented as an array. Transformers encapsulate field selection, relation inclusion, and output formatting logic.

Why use transformers?


How Transformer Resolution Works

Transformers in Lightpack ORM are designed to make your API and view output flexible, maintainable, and secure. Understanding how the framework selects and applies transformers is key to leveraging their full power.

Resolution Process

When you call the transform() method on a model (or collection), Lightpack follows a clear, predictable process:

  1. Check the $transformer Property

    • This property tells Lightpack which transformer class to use.
    • It can be:
      • A string: The class name of a single transformer (e.g., UserTransformer::class).
      • An array: A map of context names (like 'api', 'view') to transformer classes.
      • null: No transformer defined—calling transform() will throw an error.
  2. Context Support

    • If you want different outputs for different consumers (API, admin, etc.), use an array mapping contexts to transformer classes.
    • At runtime, specify the context via the context option:
      $product->transform(['context' => 'api']);
      $product->transform(['context' => 'view']);
  3. Fields and Includes

    • You can pass a fields array to include only specific fields for the main model or for related models.
    • You can pass an includes array to include related data (including nested relations).
    • These options are forwarded directly to the transformer instance before transformation.
      $user->transform([
        'fields' => ['self' => ['name', 'email']],
        'includes' => ['profile', 'roles'],
      ]);

Defining a Transformer


The transform() method on any model is the canonical way to convert it to an array for API or view output. This method:

Examples

// Single transformer
class User extends Model {
    protected $transformer = UserTransformer::class;
}

// Multiple contexts
class Product extends Model {
    protected $transformer = [
        'api' => ProductApiTransformer::class,
        'view' => ProductViewTransformer::class,
    ];
}

// Usage
$user = new User(1);
$userArray = $user->transform(); // Uses UserTransformer

$product = new Product(1);
$productApi = $product->transform(['context' => 'api']);
$productView = $product->transform(['context' => 'view']);

Passing Fields and Includes

You can pass fields and includes options to control the output:

$user->transform([
    'fields' => ['self' => ['name', 'email']],
    'includes' => ['profile', 'roles'],
]);

These options are forwarded to the transformer instance and determine which fields and relations are included in the output.


The transformer class extends Lightpack\Database\Lucid\Transformer and implements a data() method. This method returns an array representation of the model, controlling which fields are exposed and how relations are included.

Basic Transformer Example

use Lightpack\Database\Lucid\Transformer;

class ProjectTransformer extends Transformer
{
    protected function data(Project $project): array
    {
        return [
            'id' => $project->id,
            'name' => $project->name,
        ];
    }
}

Using Fields and Includes in Transformers

You don't have to manually filter fields or include relations in your data() method—Lightpack handles this based on the fields() and includes() options passed at runtime. Your data() method should return the full set of possible fields for the model.

Contextual Transformers

You can define multiple transformers for the same model to support different output contexts (e.g., API, view, admin):

class ProductApiTransformer extends Transformer
{
    protected function data(Product $product): array
    {
        return [
            'name' => $product->name,
            'price' => $product->price,
        ];
    }
}

class ProductViewTransformer extends Transformer
{
    protected function data(Product $product): array
    {
        return [
            'id' => $product->id,
            'name' => $product->name,
            'price' => $product->price,
            'color' => $product->color,
        ];
    }
}

Associating Transformers with Models

To use a default transformer, set the $transformer property on your model:

class Project extends Model
{
    protected $table = 'projects';
    protected $transformer = ProjectTransformer::class;
}

To support multiple contexts, use an array mapping context names to transformer classes:

class Product extends Model
{
    protected $transformer = [
        'api' => ProductApiTransformer::class,
        'view' => ProductViewTransformer::class,
    ];
}

When transforming, specify the context:

$product->transform(['context' => 'api']); // Uses ProductApiTransformer
$product->transform(['context' => 'view']); // Uses ProductViewTransformer

If an invalid context is provided, Lightpack will throw a descriptive exception listing available contexts.


Basic Usage

Transforming a Single Model

$project = new Project(1);
$transformer = new ProjectTransformer();

$result = $transformer->transform($project);
// [ 'id' => 1, 'name' => 'Project 1' ]

Field Selection

Select only specific fields for the main model ("self") or for relations:

$result = $transformer
    ->fields(['self' => ['name']])
    ->transform($project);
// [ 'name' => 'Project 1' ]

Including Relations

To include related models, you must first define transformers for those relations in your transformer's transformerMap() method:

class ProjectTransformer extends Transformer
{
    protected function data(Project $project): array
    {
        return [
            'id' => $project->id,
            'name' => $project->name,
        ];
    }

    protected function transformerMap(): array
    {
        return [
            'tasks' => TaskTransformer::class,
        ];
    }
}

Now you can include related models by name:

$result = $transformer
    ->includes('tasks')
    ->fields([
        'self' => ['name'],
        'tasks' => ['id', 'name'],
    ])
    ->transform($project);
// [ 'name' => 'Project 1', 'tasks' => [ [ 'id' => 1, 'name' => 'Task 1' ] ] ]

Important: If you try to include a relation that is not defined in transformerMap(), a RuntimeException will be thrown with a clear message indicating which relation is missing.


Nested Relations & Deep Includes

You can include nested relations using dot notation. Each level must have its transformer defined:

class ProjectTransformer extends Transformer
{
    protected function data(Project $project): array
    {
        return ['id' => $project->id, 'name' => $project->name];
    }

    protected function transformerMap(): array
    {
        return [
            'tasks' => TaskTransformer::class,
        ];
    }
}

class TaskTransformer extends Transformer
{
    protected function data(Task $task): array
    {
        return ['id' => $task->id, 'name' => $task->name];
    }

    protected function transformerMap(): array
    {
        return [
            'comments' => CommentTransformer::class,
        ];
    }
}

Now you can include nested relations:

$result = $transformer
    ->includes('tasks.comments')
    ->fields([
        'self' => ['name'],
        'tasks' => ['name'],
        'tasks.comments' => ['content']
    ])
    ->transform($project);
// [ 'name' => 'Project 1', 'tasks' => [ [ 'name' => 'Task 1', 'comments' => [ [ 'content' => 'Comment 1' ] ] ] ] ]

You can also include multiple paths at once:

$result = $transformer
    ->includes(['tasks', 'tasks.comments'])
    ->fields([
        'self' => ['id', 'name'],
        'tasks' => ['name'],
        'tasks.comments' => ['content']
    ])
    ->transform($project);

Handling Missing or Null Relations


Transforming Collections

Transformers can handle collections of models seamlessly:

$projects = Project::query()->all();
$result = $transformer
    ->includes(['tasks'])
    ->fields([
        'self' => ['name'],
        'tasks' => ['name']
    ])
    ->transform($projects);
// [ [ 'name' => 'Project 1', 'tasks' => [ ... ] ], ... ]

Model-integrated Transformation

You can call transform() directly on a model or collection, passing options:

$result = $project->transform([
    'includes' => ['tasks.comments'],
    'fields' => [
        'self' => ['name'],
        'tasks' => ['name'],
        'tasks.comments' => ['content'],
    ],
]);

Pagination Support

If you have a paginated collection, you can transform it for API output:

$pagination = new Pagination($projects, $total, $perPage, $currentPage);
$result = $pagination->transform([
    'includes' => ['tasks'],
    'fields' => [
        'self' => ['name'],
        'tasks' => ['name'],
    ],
]);

Output Structure:

[
    'data' => [ /* transformed items */ ],
    'meta' => [
        'current_page' => 1,
        'per_page' => 10,
        'total' => 100,
        'total_pages' => 10
    ],
    'links' => [
        'first' => '/projects?page=1',
        'last' => '/projects?page=10',
        'prev' => null,
        'next' => '/projects?page=2'
    ]
]

Dynamic Includes: Comma and Dot Notation

You can mix and match as needed.


Contextual Transformers

Transformers can support multiple output contexts (e.g., API, view) for the same model:

$product = new Product(1);

// API context
$result = $product->transform(['context' => 'api']);
// Uses ProductApiTransformer

// View context
$result = $product->transform(['context' => 'view']);
// Uses ProductViewTransformer

If an invalid context is provided, a clear exception is thrown listing available contexts.


Defining Relation Transformers

The transformerMap() method is essential for including relations in your transformed output. It maps relation names to their transformer classes:

class UserTransformer extends Transformer
{
    protected function data(User $user): array
    {
        return [
            'id' => $user->id,
            'name' => $user->name,
            'email' => $user->email,
        ];
    }

    protected function transformerMap(): array
    {
        return [
            'profile' => ProfileTransformer::class,
            'roles' => RoleTransformer::class,
            'posts' => PostTransformer::class,
        ];
    }
}

Key Points:


Error Handling & Robustness