Framework for quick and easy setup of a JSON-API compliant server.
- see if we want to use the staticrelation interface
- if so, discuss it in the documentation, if not, find a better approach (config based?)
Via Composer
$ composer require czim/laravel-jsonapi
If you have any problems with 'dev-master' rights, add the following package requirements:
$ composer require znck/belongs-to-through
$ composer require neomerx/json-api
Add this line of code to the providers
array located in your config/app.php
file:
Czim\JsonApi\JsonApiServiceProvider::class,
And add this line of code to the aliases
array in config/app.php
for the Facade:
'Encoder' => Czim\JsonApi\Facades\Encoder::class,
Publish the configuration:
$ php artisan vendor:publish
In app/Http/Kernel.php
, add the following to the $routeMiddleware
property.
'jsonapi.headers' => \Czim\JsonApi\Middleware\JsonApiHeaders::class,
'jsonapi.parameters' => \Czim\JsonApi\Middleware\JsonApiParametersSetup::class,
Then set up the middleware for your routes in app/routes.php
, for instance as follows:
Route::group(
[
'prefix' => 'v1',
'namespace' => 'v1',
'middleware' => [
'jsonapi.headers',
'jsonapi.parameters',
],
],
function () {
// Your API routes...
}
);
Additionally, do not forget to remove or alternatively handle the VerifyCsrfToken
middleware, if you intend to accept POST requests to your API.
To make the error handler output errors in the correct format, you'll need to modify app/Exceptions/Handler.php
.
When any routes using the JSON-API standard are hit, error response should be a list of errors formatted as JSON-API error objects. This may be done by setting up the render
method as follows:
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Exception $e
* @return \Illuminate\Http\Response
*/
public function render($request, Exception $e)
{
if ($e instanceof ModelNotFoundException) {
$e = new NotFoundHttpException($e->getMessage(), $e);
}
// For json, encode errors according to JSON-API formatting
if ($request->isJson()) {
return \Encoder::errors($e);
}
return parent::render($request, $e);
}
Optionally you can set up validation message translations for JSON-API structure errors.
To do so, add the following to, for instance, resources/lang/en/validation.php
:
'jsonapi_errors' => 'The JSON-API errors list is malformed.',
'jsonapi_resource' => 'The JSON-API resource is malformed.',
'jsonapi_links' => 'The JSON-API links list is malformed.',
'jsonapi_link' => 'The JSON-API link object is malformed.',
'jsonapi_relationships' => 'The JSON-API relationships list is malformed.',
'jsonapi_jsonapi' => 'The JSON-API json-api object is malformed.',
The way relationships are handled during encoding can be configured in the jsonapi.php
config file.
You can define which relationships must be hidden from encoded json, per fully qualified classname of encodable resource, through the relations.hide
and relations.hide_defaults
entries.
You can define which relationships must have their data (not just reference links) included, per fully qualified classname of encodable resource, through the relations.always_show_data
entry.
By default always_show_data_for_single
is set to true, which will result in all to-one relationships to include data objects.
See the config (after publishing, located at config/jsonapi.php
) for more information.
When using Encoder::encode()
, the data provide needs to be a resource objects or an array or Collection of them.
Resource objects must implement the Czim\JsonApi\Contracts\ResourceInterface
.
For Eloquent models, a provided trait may be used to do so (Czim\JsonApi\Encoding\JsonApiResourceEloquentTrait
).
Example setup:
<?php
namespace App\Models;
use Czim\JsonApi\Contracts\ResourceInterface;
use Czim\JsonApi\Encoding\JsonApiResourceEloquentTrait;
use Illuminate\Database\Eloquent\Model;
class Post extends Model implements ResourceInterface
{
use JsonApiResourceEloquentTrait;
Of course, you can roll your own implementation for any class or model.
If you wish to make sure that the date format output for date fields is output in a proper timezoned format, you can take the following approach. Let all your API-relevant models extend an abstract class such as this:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class AbstractModel extends Model
{
protected $jsonDateFormat = 'c';
/**
* Prepare a date for array / JSON serialization.
*
* @param DateTime $date
* @return string
*/
protected function serializeDate(DateTime $date)
{
return $date->format($this->jsonDateFormat);
}
}
To validate a POST
or PUT
request with JSON-API content, use Laravel's service container to instantiate an instance of Czim\JsonApi\Requests\JsonApiRequest
.
In a controller:
use Czim\JsonApi\Requests\JsonApiRequest;
class TestController extends Controller
{
public function someAction(JsonApiRequest $request)
{
// ...
Or anywhere outside of it:
$request = App::make(\Czim\JsonApi\Requests\JsonApiRequest::class);
This will perform validation on the general JSON-API structure and throw the normal request 422 validation errors, formatted as JSON-API error list. Note that empty requests will pass validation, unless specific validation rules are set up in classes extending the JsonApiRequest
class (for requiring content, see the section on specific validation below).
The instantiated request has accessor methods to make it easier to extract typical JSON-API data from the request.
// resource data object (from single resource request or first from list)
$resource = $request->getResource();
// list of relationships as an associative array of data objects
$relationships = $request->getRelationships();
// specific relationship data object
$relationship = $request->getRelationship('categories');
// attributes collection data object for first or single resource
$attributes = $request->getAttributes();
// value for a specific attribute by key
$value = $request->getAttribute('some_attribute');
// nested value for a specific attribute by key in dot notation
$value = $request->getAttribute('some_array.some_attribute');
For more options and information, see JsonApiDataAccessorsInterface.php
.
To set up normal validation, extend the JsonApiRequest
as you would for a normal FormRequest.
Data validation for specific requests may then be handled normally. For instance, for a request that requires some attributes and a relationship be provided:
public function rules()
{
return [
// Attribute rules
'data.attributes.from-date' => 'required|date',
'data.attributes.to-date' => 'required|date',
'data.attributes.description' => 'string',
// Relationship rules
'data.relationships.address.data.type' => 'required|in:addresses',
'data.relationships.address.data.id' => 'required|exists:addresses,id',
];
}
Any encodable data may be encoded into a response as follows:
// a Collection of models
\Encoder::response( \App\Models\Post::all() );
// or a single one
\Encoder::response( \App\Models\Comment::with('post')->where('id', 1)->first() );
Relationships are only included in responses if they are (eager-) loaded into the models being encoded. So if you're missing relationship data in the responses, make sure the relationships are actually loaded in.
Error responses can be created with the Encoder::errors()
method. For its parameter, it can deal with:
- A single object that implements
Neomerx\JsonApi\Contracts\Document\ErrorInterface
, or an array of them. - The errors returned by a
Validator
'smessages()
method. - An
Exception
instance or an array of them. If the Exceptions implement agetStatusCode()
method, it will be used in the error representation. - A string with an error message (which will result in a single error in the errors list created).
For example:
// Though note that this would not be very useful in practice,
// if you already set up the Exceptions\Handler correctly.
try {
// some code that may throw some exception
} catch(\Exception $e) {
return \Encoder::errors($e);
}
Meta data is prepared for the next encoding by storing data in a dataobject singleton. You can access it as follows:
// method 1: through the Encoder facade
$meta = \Encoder::getMeta();
// method 2: directly through the bound interface
$meta = App::make(\Czim\JsonApi\Contracts\JsonApiCurrentMetaInterface::class);
// method 3: through the JsonApiEncoder binding
$meta = app(\Czim\JsonApi\Encoding\JsonApiEncoderInterface::class)->getMeta();
$meta['some-key'] = 'some value';
The data object is an instance of a DataObject
(czim/laravel-dataobject
) instance.
By default, after calling the response()
(or encode()
) method on the Encoder, the meta-data will be reset.
This means that the lifetime of set meta-data ordinarily lasts until a response is fired.
Special request parameters, such as included paths, filters and sorting options are automatically read when the JsonApiParametersSetup
middleware runs. After that, the settings read can be accessed through a bound singleton, similar to that for the Meta data.
// method 1: through the Encoder facade
$parameters = \Encoder::getParameters();
// method 2: directly through the bound interface
$parameters = App::make(\Czim\JsonApi\Contracts\JsonApiParametersInterface::class);
// method 3: through the JsonApiEncoder binding
$parameters = app(\Czim\JsonApi\Encoding\JsonApiEncoderInterface::class)->getParameters();
// Get an array of paths to include, which take the dot notation
$parameters->getIncludePaths();
// Get all filter values as an array
$parameters->getFilter();
// Get a specific filter value by key
$parameters->getFilterValue('ids');
// Get an array with sort parameters in order
// These implement the `Czim\JsonApi\Contracts\SortParameterInterface`
$parameters->getSortParameters();
Please see CONTRIBUTING for details.
The MIT License (MIT). Please see License File for more information.