- Config is now defined on a per-API basis, with a separate config file for each API.
- The
Requestclass has been removed to reduce the number of units per resource. - The
Searchclass has been merged into theAdapterunit to reduce the number of units per resource.
We have removed support for Laravel 5.1 and 5.2. The library no longer works with 5.1 because of a bug in Laravel for which our PR to solve the problem was closed without being merged. Support for 5.2 has been dropped as 5.3 and 5.4 have now both been out for some time.
These are the steps to upgrade
To prevent errors when installing the new packages, do the following first:
- Move the
config/json-api-errors.phpfile out of the config directory to a safe place. - Move the
config/json-api.phpfile out of the config directory to a safe place. - Comment out all route definitions for your JSON API routes.
Then:
$ composer require cloudcreativity/laravel-json-api:^0.8 --update-with-dependenciesCreate an API configuration file:
$ php artisan make:json-apiIf you have multiple APIs, you can create config for each API using
php artisan make:json-api <name>
Transfer the config from your old json-api.php config file into the new file, noting the following:
- There are no paging settings in the new config file (that's now in the
Adapterclass). - Rather than defining a schema map, you now define a map of JSON API resource types to the model/entity
class to which they relate. E.g.
'posts' => App\JsonApi\Posts\Schema::classin your old schemas config would now be added to the resources list in the new config as'posts' => App\Post::class. - None of the adapter config needs to be transferred.
Any custom errors in the json-api-errors.php file needs to be transferred to the errors key in the
new API config file. You ONLY need to transfer custom errors - default errors provided by the package are
now automatically added.
Once you're finished you can delete your old json-api.php and json-api-errors.php files.
Routing has been improved. If you had this:
Route::group([
'prefix' => 'api',
'as' => 'api::',
'namespace' => 'Api',
'middleware' => ['json-api:default', 'my-middleware'],
], function () {
JsonApi::resource('posts');
JsonApi::resource('comments');
});It would now become this:
JsonApi::api('default', [
'prefix' => 'api',
'as' => 'api::',
'namespace' => 'Api',
'middleware' => ['my-middleware'],
], function ($api, $router) {
$api->resource('posts', ['has-many' => ['comments', 'tags']);
$api->resource('comments', ['has-one' => 'post']);
});The routing functionality now includes a lot more options to allow you to only register the exact JSON API routes that you want per-resource and per-resource-relationship. See the Wiki for full details.
Any authorizer classes that you have written need $resourceType added as the first parameter for the canCreate
and canReadMany methods. See the AuthorizerInterface for the updated signature.
We've added this parameter because it is common to have authorizers that handle multiple resource types. For instance we are planning to add a generic Laravel authorizer to this library in the near future.
Validator classes are so specific to a resource type that we have decided to remove the $resourceType parameter
from all methods on the class. You will need to update you validator methods accordingly, and add the $resourceType
property to the class.
We have also renamed the following attributes and methods:
filterRulesbecomesqueryRulesfilterMessagesbecomesqueryMessagesfilterCustomAttributesbecomesqueryCustomAttributes
These now validate filter, paging and unrecognized parameters, which means you need to add filter. to the
front of any rules that previously validated filters.
For example, this:
class Validators extends AbstractValidatorProvider
{
// ...
protected $filterRules = ['title' => 'string|min:1'];
protected function attributeRules($resourceType, $record = null)
{
// ...
}
protected function relationshipRules(RelationshipsValidatorInterface $relationships, $resourceType, $record = null)
{
// ...
}
}Becomes this:
class Validators extends AbstractValidatorProvider
{
protected $resourceType = 'posts';
protected $queryRules = [
'filter.title' => 'string|min:1',
// example page parameter validation...
'page.number' => 'integer|min:1',
'page.size' => 'integer|between:1,50',
];
// ...
protected function attributeRules($record = null)
{
// ...
}
protected function relationshipRules(RelationshipsValidatorInterface $relationships, $record = null)
{
// ...
}
}If you are using a single validator class for multiple resource types, you can refactor to use extension or traits to provide the abstraction required.
Your Request classes for each resource can be removed once you have transferred their settings to new locations.
The following properties should be defined in your route configuration:
hasMany: use thehas-manyoption as per above example.hasOne: use thehas-oneoption as per above example.
The following properties should be moved to the Validators class for the resource:
allowedSortParametersallowedFilteringParametersallowUnrecognizedParametersallowedIncludePathsallowedFieldSetTypes
Allowed paging parameters can be set using the allowedPagingParameters parameter on the Validators class.
If you were injecting an Authorizer instance into the Request constructor, authorizers are now automatically
detected. E.g. for the posts resource it will look for Posts\Authorizer if you are storing classes by resource,
or Authorizers\Post if not storing by resource.
If you need to use a different Authorizer class, this is defined in route configuration, for example:
JsonApi::api('default', [], function ($api, $router) {
$api->resource('posts', ['authorizer' => MyAuthorizer::class]);
});Route configuration allows for a default authorizers to be set. See the authorizer page in the Wiki for more information.
If you were injecting a Validators class into the Request constructor, these are now automatically detected
either as Posts\Validators or Validators\Post depending on your API's by-resource config setting.
If you need to use a non-standard validator class, you can define this in your route configuration, for example:
JsonApi::api('default', [], function ($api, $router) {
$api->resource('posts', ['validators' => MyValidators::class]);
});If your resource type does not have a Search class, generate a new adapter:
$ php artisan make:json-api:adapter <resource> [<api>] -eIf it does have a Search class, rename it to Adapter and extend EloquentAdapter.
You will then need to add a constructor that injects a model instance and a paging strategy (if needed).
For example, this:
use CloudCreativity\LaravelJsonApi\Search\AbstractSearch;
class Search extends AbstractSearch
{
protected function filter(Builder $builder, Collection $filters)
{
// ...
}
protected function isSearchOne(Collection $filters)
{
return false;
}Is changed to this:
use App\Post;
use CloudCreativity\LaravelJsonApi\Pagination\StandardStrategy;
use CloudCreativity\LaravelJsonApi\Store\EloquentAdapter;
class Adapter extends EloquentAdapter
{
public function __construct(StandardStrategy $paging)
{
// $paging does not need to be provided if the resource cannot be paged.
parent::__construct(new Post(), $paging);
}
protected function filter(Builder $builder, Collection $filters)
{
// ...
}
protected function isSearchOne(Collection $filters)
{
return false;
}If you were previously using the $perPage or $maxPerPage attributes on the Search instance, you must
now set page validation rules in the Validators class:
class Validators extends AbstractValidatorProvider
{
protected $queryRules = [
'page.number' => 'integer|min:1',
'page.size' => 'integer|between:1,50',
];
// ...
}If you want to force your resource to always be paginated even if the client does not provide page parameters,
then on your Adapter set the $defaultPagination attribute to an array of the paging parameters to use
if the client has not provided any. E.g.:
class Adapter extends EloquentAdapter
{
protected $defaultPagination = [
'number' => 1,
'size' => 50,
];
}Paging can now be customised on a per-resource basis by injecting a paging strategy into the adapter. The one
provided by the package - StandardStrategy - is standard page number/size pagination.
The StandardStrategy instance has a number of methods (starting with with) that allow you to customise
its output. E.g.:
class Adapter extends EloquentAdapter
{
public function __construct(StandardStrategy $paging)
{
$paging->withPerPageKey('limit')->withUnderscoredMetaKeys();
parent::__construct(new Post(), $paging);
}
}If you need to customise the pagination for multiple resources, create a new class that extends StandardStrategy
and inject that into your Adapter instead.
The AdapterInterface has changed: in summary, there is now a single adapter per resource type, and it also now
supports the querying of resources. Generally you will be able to write an abstract class (such as our
EloquentAdapter) and extend from that if your entities share common adapter functionality.
To generate an adapter for a resource that is not Eloquent:
$ php artisan make:json-api:adapter <resource> [<api>] -NMake the following changes:
- Remove all references to the old search classes (you do NOT need to change
them to the new
Adapterclass). - Delete the
getRequestHandlermethod. - If you have extended the
indexmethod, you will need to update the method signature by addingApiInterface $apias the first parameter (refer to the parent controller).
For example, this:
use CloudCreativity\LaravelJsonApi\Http\Controllers\EloquentController;
use App\Post;
use App\JsonApi\Posts;
class PostsController extends EloquentController
{
public function __construct(Posts\Hydrator $hydrator, Posts\Search $search)
{
parent::__construct(new Post(), $hydrator, $search);
}
protected function getRequestHandler()
{
return Posts\Request::class;
}
}Would change to this:
use CloudCreativity\LaravelJsonApi\Http\Controllers\EloquentController;
use App\Post;
use App\JsonApi\Posts;
class PostsController extends EloquentController
{
public function __construct(Posts\Hydrator $hydrator)
{
parent::__construct(new Post(), $hydrator);
}
}You can remove the getRequestHandler() method.
Please note that the JsonApiController now contains little of value and it is our intention to remove it in
a future version. For example, our EloquentController no longer extends from it.
If you want to stop extending it, then remember to add the traits to your controller that it uses.
All routes registered for a resource now have a route name - check the routing section in the Wiki for details. As such, we've updated the link factory, as follows:
- removed
resource(): useread(),update()ordelete() - removed
relationship(): usereadRelationship(),replaceRelationship(),addRelationship(),removeRelationship()instead.