Schema
The schema is the source of truth for what is available to clients of your API. The fields that they are allowed to request, the filters they are allowed to call, everything is written in a declarative way in the schema.
Validating the schema
An Artisan command is available to validate that the schema is correctly defined. For example, if we write an association that does not exist on the model, we might get an error like:
App\Schemas\PostSchema
-----------------------------
* Association
* Association [tags] on [App\Schemas\PostSchema] refers to association [tag] which does not exist on the model [App\Models\Post]
This could easily be extended to a test case. Validating the schema in a test case can be especially helpful to catch bugs early (for example, in CI):
<?php
namespace Tests\Feature;
use Apitizer\Support\SchemaValidator;
use Tests\TestCase;
class SchemaTest extends TestCase
{
/** @test */
public function it_has_a_valid_schema()
{
$validator = (new SchemaValidator)->validateAll();
$this->assertFalse(
$validator->hasErrors(),
'Run ./artisan apitizer:validate-schema to see the errors'
);
}
}
Features of the schema
Adding documentation metadata
The optional apidoc
callback method may be overridden to add extra information
to the API documentation as it's created. The two most important options for
this method are to either add a description to the schema, or to add metadata
to the documentation.
<?php
use Apitizer\Types\Apidoc;
class PostSchema extends Schema
{
public function apidoc(Apidoc $apidoc): void
{
$apidoc->setDescription('This is a blog post resource...');
// the metadata may be anything, including arrays, objects, etc.
$apidoc->setMetadata([
'developer' => 'John Doe'
]);
}
}
Building a query
The schema is able to build an Eloquent query based on the current request:
$query = UserSchema::build($request);
You can also build a query from a specification, rather than a request. This specification accepts the same keys as would be present on the request without having to actually build a request object:
$query = (new UserSchema)->fromSpecification([
'fields' => 'id,name,posts(id,title)',
'filters' => ['search' => 'term'],
'sorts' => 'id.asc'
])->buildQuery();
Rendering data
If you fetch the data yourself, you can still use the schema to render data:
$user = $accountService->createUser($parameters);
return UserSchema::make($request)->render($data);
Just like with the query building, the rendering also works with a custom specification.
Fetching and rendering
There are two methods available to perform both query building and rendering in
one step. The first is the all
method which is the same as calling get
on
the query, and the second is the paginate
method. A typical controller that
utilizes some of these methods might look like this:
<?php
namespace App\Http\Controllers;
use App\Schemas\UserSchema;
use App\Models\User;
use Illuminate\Http\Request;
class UserController
{
public function index(Request $request)
{
return UserSchema::make($request)->paginate();
}
public function show(Request $request, User $user)
{
return UserSchema::make($request)->render($user);
}
public function store(Request $request)
{
$schema = UserSchema::make($request);
$user = (new AccountService)->createUser($request->validated());
return $schema->render($user);
}
}
The maximum number of results per page in the pagination object can be
controlled with the maximumLimit
property on the schema, as well as the
getMaximumLimit
and setMaximumLimit
methods.
Before and after query
Often times it's necessary to perform some additional logic to a query, such as
setting the right tenant, or scoping the data to something the current user is
allowed to see. The schema offers two methods to control the query a bit
more: the beforeQuery
and afterQuery
methods. Each of these methods accept
the current query object and the fetch specification, and return a modified
query object.
<?php
use Apitizer\Types\FetchSpec;
use Illuminate\Database\Eloquent\Builder;
class UserSchema extends Schema
{
public function beforeQuery(Builder $query, FetchSpec $fetchSpec): Builder
{
$user = \Auth::user();
if ($user->isNotAdmin()) {
$query->where('user_id', $user->id);
}
return $query;
}
}
Usually, you will only need to implement either the beforeQuery
or the
afterQuery
method.
Validation
There are several helper methods available to get the current validation rules.
The most common helper is validated
to get the validated data for the current
request.
$schema = UserSchema::make($request);
// Get the validation rules for the current request, as can be understood by
// Laravel's Validator object.
$schema->validationRules();
// Get a Validator object with the request's input and the validation rules.
$schema->validator();
// Get the validated data. These two are the same.
$schema->validated();
$schema->validator()->validate();