How to Use API v3
This guide provides practical instructions for working with the Sun API v3 architecture, including code generation, controller usage, and transformer implementation.
In This Document
Code Generation
The API v3 comes with a convenient artisan command to generate all necessary components (controller, repository, and transformer) for a specific model:
php artisan make:v3 "App\Modules\Shop\Entities\Order"
This command will create:
- A controller in the appropriate namespace
- A repository for data access
- A transformer for response formatting
Base Controller
All API controllers must extend the APIBaseController class, which implements conventional CRUD methods and other utility functions.
namespace App\Modules\Shop\Http\Controllers\V3;
use App\Http\Controllers\APIBaseController;
class OrderController extends APIBaseController
{
// Your custom methods here
}
Key-Value List Method
One of the useful methods provided by APIBaseController is the keyValList method, which is designed for integration with select2 JavaScript plugin:
public function getOrderStatusOptions()
{
return $this->keyValList(
$this->repository->getStatusOptions(),
'id',
'name'
);
}
Transformer Implementation
Transformers are a critical part of the API v3 architecture. They format your data for API responses and handle relationships between entities.
Example: PostTransformer
Below is an example of a transformer for a Post entity:
- Complete Transformer
- Configuration Properties
- Include Methods
- Additional Field Methods
<?php
namespace App\Modules\CMS\Http\Transformers\V3\Client;
use App\Core\Comment\Http\Transformers\V3\Client\CommentTransformer;
use App\Core\Term\Http\Transformers\V3\Client\TermTransformer;
use App\Core\User\Http\Transformers\V3\Client\UserTransformer;
use App\Transformers\DateTimeTransformer;
use App\Modules\CMS\Entities\Post;
use App\Transformers\BaseTransformer;
use League\Fractal\Resource\Collection;
use League\Fractal\Resource\Item;
class PostTransformer extends BaseTransformer
{
protected array $additionalFields = [
'views',
];
protected array $blackListFields = [
'created_at',
'updated_at',
];
protected array $fieldsTransformer = [
'published_at'=> DateTimeTransformer::class,
];
protected array $availableIncludes = [
'comments',
'approved_comments',
];
protected array $defaultIncludes = [
'user',
'main_category',
'categories',
'tags'
];
public function includeUser(Post $post): Item
{
$user = $post->user;
return $this->item($user, new UserTransformer($this->request), 'user');
}
public function includeCategories(Post $post): Collection
{
$categories = $post->categories;
return $this->collection($categories, new TermTransformer($this->request), 'categories');
}
public function includeMainCategory(Post $post)
{
$category = $post->mainCategory()->first();
return is_null($category)
? $this->null() :
$this->item($category, new TermTransformer($this->request), 'mainCategory');
}
public function includeTags(Post $post): Collection
{
$tags = $post->tags;
return $this->collection($tags, new TermTransformer($this->request), 'tags');
}
public function includeComments(Post $post): Collection
{
$comments = $post->comments;
return $this->collection($comments, new CommentTransformer($this->request), 'comments');
}
public function includeApprovedComments(Post $post): Collection
{
$comments = $post->approvedComments;
return $this->collection($comments, new CommentTransformer($this->request), 'approvedComments');
}
public function addViews(Post $post)
{
return $post->views;
}
}
class PostTransformer extends BaseTransformer
{
// Fields to be added to the response that aren't direct model attributes
protected array $additionalFields = [
'views',
];
// Fields to be excluded from the response
protected array $blackListFields = [
'created_at',
'updated_at',
];
// Defines how specific fields should be transformed
protected array $fieldsTransformer = [
'published_at'=> DateTimeTransformer::class,
];
// Optional relationships that can be included via query parameters
protected array $availableIncludes = [
'comments',
'approved_comments',
];
// Relationships that are always included in the response
protected array $defaultIncludes = [
'user',
'main_category',
'categories',
'tags'
];
// ... methods ...
}
// Single relation (returns Item)
public function includeUser(Post $post): Item
{
$user = $post->user;
return $this->item($user, new UserTransformer($this->request), 'user');
}
// Multiple relations (returns Collection)
public function includeCategories(Post $post): Collection
{
$categories = $post->categories;
return $this->collection($categories, new TermTransformer($this->request), 'categories');
}
// Relation that might be null
public function includeMainCategory(Post $post)
{
$category = $post->mainCategory()->first();
return is_null($category)
? $this->null() :
$this->item($category, new TermTransformer($this->request), 'mainCategory');
}
// Method to add a field that's not directly on the model
public function addViews(Post $post)
{
return $post->views;
}
Key Transformer Components
Configuration Properties
$additionalFields: Fields to be added to the response that aren't direct model attributes$blackListFields: Fields to be excluded from the response$fieldsTransformer: Defines how specific fields should be transformed$availableIncludes: Optional relationships that can be included via query parameters$defaultIncludes: Relationships that are always included in the response
Include Methods
For each relationship, you need to create an include{RelationName} method that:
- Retrieves the related model(s)
- Transforms them using the appropriate transformer
- Returns them as an
Item(single relation) orCollection(multiple relations)
Additional Field Methods
For each additional field defined in $additionalFields, you need to create an add{FieldName} method that returns the value for that field.
Best Practices
Following these best practices will help you maintain a clean, efficient, and consistent API implementation.
- Naming Conventions
- Separation of Concerns
- Reuse Transformers
- Optimize Includes
- Type Hints
1. Consistent Naming
Follow the naming conventions for controllers, repositories, and transformers:
App\Modules\{Module}\Http\Controllers\V3\{Audience}\{Resource}Controller
App\Modules\{Module}\Repositories\{Resource}Repository
App\Modules\{Module}\Http\Transformers\V3\{Audience}\{Resource}Transformer
2. Separation of Concerns
- Controllers: Handle HTTP requests and responses
- Repositories: Contain business logic and data access
- Transformers: Format data for API responses
// Controller - handles the request
public function index(Request $request)
{
$posts = $this->repository->paginate();
return $this->response($posts);
}
// Repository - handles the business logic
public function getPublishedPosts()
{
return $this->model->where('status', 'published')->get();
}
// Transformer - handles the presentation
public function transform(Post $post)
{
return [
'id' => $post->id,
'title' => $post->title,
// ...
];
}
3. Reuse Transformers
Create base transformers for common entities and extend them as needed:
// Base transformer
class BaseUserTransformer extends BaseTransformer
{
public function transform(User $user)
{
return [
'id' => $user->id,
'name' => $user->name,
// Common fields
];
}
}
// Extended transformer
class AdminUserTransformer extends BaseUserTransformer
{
public function transform(User $user)
{
$data = parent::transform($user);
return array_merge($data, [
// Admin-specific fields
'email' => $user->email,
'role' => $user->role,
]);
}
}
4. Optimize Includes
Be mindful of which relationships are included by default versus on-demand:
// Too many default includes can cause performance issues
protected array $defaultIncludes = [
'user',
'category',
// Only include what's needed by default
];
// Make heavy relations available on-demand
protected array $availableIncludes = [
'comments',
'tags',
'relatedPosts',
// Include these only when requested
];
5. Type Hints
Always use proper type hints for method parameters and return types:
// Good
public function includeUser(Post $post): Item
{
$user = $post->user;
return $this->item($user, new UserTransformer($this->request), 'user');
}
// Not as good
public function includeUser($post)
{
$user = $post->user;
return $this->item($user, new UserTransformer($this->request), 'user');
}
By following these guidelines, you can effectively leverage the power and flexibility of the Sun API v3 architecture.