nilportugues / laravel5-json-api
Laravel 5 JSON API Transformer 包
Requires
Requires (Dev)
- friendsofphp/php-cs-fixer: ^1.10
- laravel/laravel: 5.*
- laravel/lumen: ^5.2
- phpunit/phpunit: 4.*
- dev-master
- 2.4.7
- 2.4.6
- 2.4.5
- 2.4.4
- 2.4.3
- 2.4.2
- 2.4.1
- 2.4.0
- 2.3.1
- 2.3.0
- 2.2.0
- 2.1.15
- 2.1.14
- 2.1.12
- 2.1.11
- 2.1.10
- 2.1.9
- 2.1.8
- 2.1.7
- 2.1.6
- 2.1.5
- 2.1.4
- 2.1.3
- 2.1.2
- 2.1.1
- 2.1.0
- 2.0.x-dev
- 2.0.2
- 2.0.1
- 2.0.0
- 2.0.0-rc3
- 2.0.0-rc1
- 1.4.1
- 1.4.0
- 1.3.0
- 1.2.10
- 1.2.9
- 1.2.8
- 1.2.7
- 1.2.6
- 1.2.5
- 1.2.4
- 1.2.3
- 1.2.2
- 1.2.1
- 1.2.0
- 1.1.2
- 1.1.1
- 1.1.0
- 1.0.x-dev
- 1.0.3
- 1.0.2
- 1.0.1
- 1.0.0
This package is auto-updated.
Last update: 2024-09-06 09:31:47 UTC
README
兼容 Laravel 5.0、5.1 和 5.2
- 包提供了 JSON API 规范的完整实现,并在官方网站上被推荐!
- 一个 JSON API Transformer,可以将任何映射的对象转换为有效的 JSON API 资源。
- 控制器模板,使用您现有的 Eloquent 模型编写完全兼容的 JSON API 服务器。
- 适用于 Laravel 5 和 Lumen 框架。
- 安装
- 配置(Laravel 5 & Lumen)
- JsonApiController
- 示例:使用 API
- GET 查询参数:include、fields、sort 和 page
- POST/PUT/PATCH 关联关系
- 自定义响应头
- 常见错误和解决方案
安装
使用 Composer 安装包
composer require nilportugues/laravel5-json-api
现在运行以下 artisan 命令
php artisan vendor:publish
配置(Laravel 5 & Lumen)
为了有一个真实的生活例子,此配置将指导您如何为两个资源设置 7 个端点,即 Employees
和 Orders
。
Employees
和 Orders
资源都将使用 Eloquent 模型,彼此相关。
此外,Employees
将使用 Eloquent 功能,即 appended fields
,以展示如何充分利用 Eloquent 和此包。
Laravel 5 配置
步骤 1:添加服务提供者
打开 config/app.php
并在 providers
数组下添加以下行
'providers' => [ //... NilPortugues\Laravel5\JsonApi\Laravel5JsonApiServiceProvider::class, ],
步骤 2:定义路由
我们将在实现之前规划资源。所有路由都需要有名称。
这就是我们的 app/Http/routes.php
将看起来像
<?php Route::group(['namespace' => 'Api'], function() { Route::resource('employees', 'EmployeesController'); Route::get( 'employees/{employee_id}/orders', [ 'as' => 'employees.orders', 'uses' => 'EmployeesController@getOrdersByEmployee' ]); }); //...
步骤 3:定义
首先,让我们使用 Eloquent 定义 Employees
和 Orders
模型。
Employees (Eloquent 模型)
<?php namespace App\Model\Database; use Illuminate\Database\Eloquent\Model; use Illuminate\Foundation\Validation\ValidatesRequests; class Employees extends Model { public $timestamps = false; protected $table = 'employees'; protected $primaryKey = 'id'; protected $appends = ['full_name']; /** * @return \Illuminate\Database\Eloquent\Relations\HasOne */ public function latestOrders() { return $this->hasMany(Orders::class, 'employee_id')->limit(10); } /** * @return string */ public function getFullNameAttribute() { return $this->first_name.' '.$this->last_name; } }
Employees SQL
CREATE TABLE `employees` ( `id` int(11) NOT NULL AUTO_INCREMENT, `company` varchar(50) DEFAULT NULL, `last_name` varchar(50) DEFAULT NULL, `first_name` varchar(50) DEFAULT NULL, `email_address` varchar(50) DEFAULT NULL, `job_title` varchar(50) DEFAULT NULL, `business_phone` varchar(25) DEFAULT NULL, `home_phone` varchar(25) DEFAULT NULL, `mobile_phone` varchar(25) DEFAULT NULL, `fax_number` varchar(25) DEFAULT NULL, `address` longtext, `city` varchar(50) DEFAULT NULL, `state_province` varchar(50) DEFAULT NULL, `zip_postal_code` varchar(15) DEFAULT NULL, `country_region` varchar(50) DEFAULT NULL, `web_page` longtext, `notes` longtext, `attachments` longblob, PRIMARY KEY (`id`), KEY `city` (`city`), KEY `company` (`company`), KEY `first_name` (`first_name`), KEY `last_name` (`last_name`), KEY `zip_postal_code` (`zip_postal_code`), KEY `state_province` (`state_province`) ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8; INSERT INTO `employees` (`id`, `company`, `last_name`, `first_name`, `email_address`, `job_title`, `business_phone`, `home_phone`, `mobile_phone`, `fax_number`, `address`, `city`, `state_province`, `zip_postal_code`, `country_region`, `web_page`, `notes`, `attachments`) VALUES (10, 'Acme Industries', 'Smith', 'Mike', 'mike.smith@mail.com', 'Horticultarlist', '0118 9843212', NULL, NULL, NULL, '343 Friary Road', 'Manchester', 'Lancs.', 'M3 3DL', 'United Kingdom', NULL, NULL, NULL);
Orders (Eloquent 模型)
<?php namespace App\Model\Database; use Illuminate\Database\Eloquent\Model; class Orders extends Model { public $timestamps = false; protected $table = 'orders'; protected $primaryKey = 'id'; /** * @return \Illuminate\Database\Eloquent\Relations\HasOne */ public function employee() { return $this->belongsTo(Employees::class, 'employee_id'); } }
Orders SQL
CREATE TABLE `orders` ( `id` int(11) NOT NULL AUTO_INCREMENT, `employee_id` int(11) DEFAULT NULL, `customer_id` int(11) DEFAULT NULL, `order_date` datetime DEFAULT NULL, `shipped_date` datetime DEFAULT NULL, `shipper_id` int(11) DEFAULT NULL, `ship_name` varchar(50) DEFAULT NULL, `ship_address` longtext, `ship_city` varchar(50) DEFAULT NULL, `ship_state_province` varchar(50) DEFAULT NULL, `ship_zip_postal_code` varchar(50) DEFAULT NULL, `ship_country_region` varchar(50) DEFAULT NULL, `shipping_fee` decimal(19,4) DEFAULT '0.0000', `taxes` decimal(19,4) DEFAULT '0.0000', `payment_type` varchar(50) DEFAULT NULL, `paid_date` datetime DEFAULT NULL, `notes` longtext, `tax_rate` double DEFAULT '0', `tax_status_id` tinyint(4) DEFAULT NULL, `status_id` tinyint(4) DEFAULT '0', PRIMARY KEY (`id`), KEY `customer_id` (`customer_id`), KEY `employee_id` (`employee_id`), KEY `id` (`id`), KEY `shipper_id` (`shipper_id`), KEY `tax_status` (`tax_status_id`), KEY `ship_zip_postal_code` (`ship_zip_postal_code`), KEY `fk_orders_orders_status1` (`status_id`), CONSTRAINT `fk_orders_employees1` FOREIGN KEY (`employee_id`) REFERENCES `employees` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION ) ENGINE=InnoDB AUTO_INCREMENT=82 DEFAULT CHARSET=utf8; INSERT INTO `orders` (`id`, `employee_id`, `customer_id`, `order_date`, `shipped_date`, `shipper_id`, `ship_name`, `ship_address`, `ship_city`, `ship_state_province`, `ship_zip_postal_code`, `ship_country_region`, `shipping_fee`, `taxes`, `payment_type`, `paid_date`, `notes`, `tax_rate`, `tax_status_id`, `status_id`) VALUES (82, 10, NULL, '2015-03-12 00:00:00', '2015-03-12 00:00:00', NULL, NULL, '43, Borrowed Drive', 'New Oreleans', 'Louisiana', '4322', 'USA', 1.4000, 0.0000, NULL, NULL, NULL, 0, NULL, 0);
接下来,我们将创建转换器。每个类需要一个转换器,并且它必须实现 \NilPortugues\Api\Mappings\JsonApiMapping
接口。
我们将把这些文件放在 app/Model/Api
目录下
EmployeesTransformer
<?php namespace App\Model\Api; use App\Model\Database\Employees; use NilPortugues\Api\Mappings\JsonApiMapping; class EmployeesTransformer implements JsonApiMapping { /** * Returns a string with the full class name, including namespace. * * @return string */ public function getClass() { return Employees::class; } /** * Returns a string representing the resource name * as it will be shown after the mapping. * * @return string */ public function getAlias() { return 'employee'; } /** * Returns an array of properties that will be renamed. * Key is current property from the class. * Value is the property's alias name. * * @return array */ public function getAliasedProperties() { return [ 'last_name' => 'surname', ]; } /** * List of properties in the class that will be ignored by the mapping. * * @return array */ public function getHideProperties() { return [ 'attachments' ]; } /** * Returns an array of properties that are used as an ID value. * * @return array */ public function getIdProperties() { return ['id']; } /** * Returns a list of URLs. This urls must have placeholders * to be replaced with the getIdProperties() values. * * @return array */ public function getUrls() { return [ 'self' => ['name' => 'employees.show', 'as_id' => 'id'], 'employees' => ['name' => 'employees.index'], 'employee_orders' => ['name' => 'employees.orders', 'as_id' => 'id'] ]; } /** * Returns an array containing the relationship mappings as an array. * Key for each relationship defined must match a property of the mapped class. * * @return array */ public function getRelationships() { return []; } }
对于 Orders
,这些文件也将放在 app/Model/Api
目录下
OrdersTransformer
<?php namespace App\Model\Api; use App\Model\Database\Orders; use NilPortugues\Api\Mappings\JsonApiMapping; class OrdersTransformer implements JsonApiMapping { /** * {@inheritDoc} */ public function getClass() { return Orders::class; } /** * {@inheritDoc} */ public function getAlias() { return 'order'; } /** * {@inheritDoc} */ public function getAliasedProperties() { return []; } /** * {@inheritDoc} */ public function getHideProperties() { return []; } /** * {@inheritDoc} */ public function getIdProperties() { return ['id']; } /** * {@inheritDoc} */ public function getUrls() { return [ 'self' => ['name' => 'orders.show', 'as_id' => 'id'], 'employee' => ['name' => 'employees.show', 'as_id' => 'employee_id'], ]; } /** * {@inheritDoc} */ public function getRelationships() { return []; } /** * List the fields that are mandatory in a persitence action (POST/PUT). * If empty array is returned, all fields are mandatory. */ public function getRequiredProperties() { return []; } }
步骤 4:使用方法
创建文件 config/jsonapi.php
。此文件应返回一个数组,其中包含所有类映射。
<?php use App\Model\Api\EmployeesTransformer; use App\Model\Api\OrdersTransformer; return [ EmployeesTransformer::class, OrdersTransformer::class, ];
Lumen 配置
步骤 1:添加服务提供者
打开 bootstrap/app.php
并在 return $app;
语句之前添加以下行
$app->register(\NilPortugues\Laravel5\JsonApi\Laravel5JsonApiServiceProvider::class); $app->configure('jsonapi');
同样,通过取消注释启用 Facades
$app->withFacades();
步骤 2:定义路由
我们将在实现之前规划资源。所有路由都需要有名称。
这就是我们的 app/Http/routes.php
将看起来像
<?php $app->group( ['namespace' => 'Api'], function($app) { $app->get( 'employees', [ 'as' => 'employees.index', 'uses' =>'EmployeesController@index' ]); $app->post( 'employees', [ 'as' => 'employees.store', 'uses' =>'EmployeesController@store' ]); $app->get( 'employees/{employee_id}', [ 'as' => 'employees.show', 'uses' =>'EmployeesController@show' ]); $app->put( 'employees/{employee_id}', [ 'as' => 'employees.update', 'uses' =>'EmployeesController@update' ]); $app->patch( 'employees/{employee_id}', [ 'as' => 'employees.patch', 'uses' =>'EmployeesController@update' ]); $app->delete( 'employees/{employee_id}', [ 'as' => 'employees.destroy', 'uses' =>'EmployeesController@destroy' ]); $app->get( 'employees/{employee_id}/orders', [ 'as' => 'employees.orders', 'uses' => 'EmployeesController@getOrdersByEmployee' ]); } ); //...
步骤 3:定义
与 Laravel 5 相同。
步骤 4:使用方法
与 Laravel 5 相同。
JsonApiController
无论是 Laravel 5 还是 Lumen,用法都是完全相同的。
让我们创建一个新的控制器,它扩展了此包提供的 JsonApiController
,如下所示
Lumen 用户必须从 LumenJsonApiController
扩展,而不是从 JsonApiController
扩展.
<?php namespace App\Http\Controllers; use App\Model\Database\Employees; use NilPortugues\Laravel5\JsonApi\Controller\JsonApiController; class EmployeesController extends JsonApiController { /** * Return the Eloquent model that will be used * to model the JSON API resources. * * @return \Illuminate\Database\Eloquent\Model */ public function getDataModel() { return new Employees(); } }
如果您需要覆盖任何默认行为,则 JsonApiController 方法是
//Constructor and defined actions public function __construct(JsonApiSerializer $serializer); public function listAction(); public function getAction(Request $request); public function postAction(Request $request); public function patchAction(Request $request); public function putAction(Request $request); public function deleteAction(Request $request); //Methods returning callables that access the persistence layer protected function totalAmountResourceCallable(); protected function listResourceCallable(); protected function findResourceCallable(Request $request); protected function createResourceCallable(); protected function updateResourceCallable(); //Allows modification of the response object protected function addHeaders(Response $response);
但是等等!我们遗漏了一个动作,即 EmployeesController@getOrdersByEmployee
。
正如其名所示,它应该列出订单,因此行为应该与 ListAction
相同。
如果您查看listAction
,会发现类似以下代码,但我们只是调整了行为,并在控制器中使用它来支持额外的操作
<?php namespace App\Http\Controllers; use App\Model\Database\Employees; use App\Model\Database\Orders; use NilPortugues\Laravel5\JsonApi\Controller\JsonApiController; class EmployeesController extends JsonApiController { /** * Return the Eloquent model that will be used * to model the JSON API resources. * * @return \Illuminate\Database\Eloquent\Model */ public function getDataModel() { return new Employees(); } /** * @param Request $request * * @return \Symfony\Component\HttpFoundation\Response */ public function getOrdersByEmployee(Request $request) { $apiRequest = RequestFactory::create(); $page = $apiRequest->getPage(); if (!$page->size()) { $page->setSize(10); //Default elements per page } $resource = new ListResource( $this->serializer, $page, $apiRequest->getFields(), $apiRequest->getSort(), $apiRequest->getIncludedRelationships(), $apiRequest->getFilters() ); $totalAmount = function() use ($request) { $id = (new Orders())->getKeyName(); return Orders::query() ->where('employee_id', '=', $request->employee_id) ->get([$id]) ->count(); }; $results = function() use ($request) { return EloquentHelper::paginate( $this->serializer, Orders::query() ->where('employee_id', '=', $request->employee_id) )->get(); }; $uri = route('employees.orders', ['employee_id' => $request->employee_id]); return $resource->get($totalAmount, $results, $uri, Orders::class); } }
一切准备就绪。是的,这真的那么简单!
示例:使用 API
GET
这是通过命令行方法调用:curl -X GET "http://localhost:9000/employees/1"
消耗EmployeesController@getAction
的输出。
输出
HTTP/1.1 200 OK
Cache-Control: private, max-age=0, must-revalidate
Content-type: application/vnd.api+json
{ "data": { "type": "employee", "id": "1", "attributes": { "company": "Northwind Traders", "surname": "Freehafer", "first_name": "Nancy", "email_address": "nancy@northwindtraders.com", "job_title": "Sales Representative", "business_phone": "(123)555-0100", "home_phone": "(123)555-0102", "mobile_phone": null, "fax_number": "(123)555-0103", "address": "123 1st Avenue", "city": "Seattle", "state_province": "WA", "zip_postal_code": "99999", "country_region": "USA", "web_page": "http://northwindtraders.com", "notes": null, "full_name": "Nancy Freehafer" }, "links": { "self": { "href": "http://localhost:9000/employees/1" }, "employee_orders": { "href": "http://localhost:9000/employees/1/orders" } }, "relationships": { "latest_orders": [ { "data": { "type": "order", "id": "71" } } ] } }, "included": [ { "type": "order", "id": "71", "attributes": { "employee_id": "1", "customer_id": "1", "order_date": "2006-05-24 00:00:00", "shipped_date": null, "shipper_id": "3", "ship_name": "Anna Bedecs", "ship_address": "123 1st Street", "ship_city": "Seattle", "ship_state_province": "WA", "ship_zip_postal_code": "99999", "ship_country_region": "USA", "shipping_fee": "0.0000", "taxes": "0.0000", "payment_type": null, "paid_date": null, "notes": null, "tax_rate": "0", "tax_status_id": null, "status_id": "0" }, "links": { "self": { "href": "http://localhost:9000/orders/71" }, "employee": { "href": "http://localhost:9000/employees/1" } } } ], "links": { "employees": { "href": "http://localhost:9000/employees" }, "employee_orders": { "href": "http://localhost:9000/employees/1/orders" } }, "jsonapi": { "version": "1.0" } }
POST
POST需要接受所有成员属性,即使是那些被映射器隐藏的属性。
例如,attachments
成员被隐藏,但它需要,因此需要传递一个有效的值。另一方面,full_name
成员的值不得作为属性传递,否则资源创建将失败。
传递和id
是可选的,如果提供了,将使用它代替服务器生成的值。
使用POST
将以下数据发送到以下URI:http://localhost:9000/employees
{ "data": { "type": "employee", "attributes": { "company": "NilPortugues.com", "surname": "Portugués", "first_name": "Nil", "email_address": "nilportugues@example.com", "job_title": "Web Developer", "business_phone": "(123)555-0100", "home_phone": "(123)555-0102", "mobile_phone": null, "fax_number": "(123)555-0103", "address": "Plaça Catalunya 1", "city": "Barcelona", "state_province": "Barcelona", "zip_postal_code": "08028", "country_region": "Spain", "web_page": "http://nilportugues.com", "notes": null, "attachments": null } } }
将产生
HTTP/1.1 201 Created
Cache-Control: private, max-age=0, must-revalidate
Content-type: application/vnd.api+json
Location: http://localhost:9000/employees/10
注意返回了201 HTTP状态代码,以及Location头。另外,attachments
不再存在,并且显示了full_name
。
{ "data": { "type": "employee", "id": "10", "attributes": { "company": "NilPortugues.com", "surname": "Portugués", "first_name": "Nil", "email_address": "nilportugues@example.com", "job_title": "Web Developer", "business_phone": "(123)555-0100", "home_phone": "(123)555-0102", "mobile_phone": null, "fax_number": "(123)555-0103", "address": "Plaça Catalunya 1", "city": "Barcelona", "state_province": "Barcelona", "zip_postal_code": "08028", "country_region": "Spain", "web_page": "http://nilportugues.com", "notes": null, "full_name": "Nil Portugués" }, "links": { "self": { "href": "http://localhost:9000/employees/10" }, "employee_orders": { "href": "http://localhost:9000/employees/10/orders" } } }, "links": { "employees": { "href": "http://localhost:9000/employees" }, "employee_orders": { "href": "http://localhost:9000/employees/10/orders" } }, "jsonapi": { "version": "1.0" } }
PUT
PUT需要接受所有成员属性,就像POST一样。
为了这个示例,我们只发送一个新的job_title
值,并且保持其他一切完全相同。
这次,我们需要传递id
,即使它已经通过URI传递,当然id
值必须匹配。否则它将失败。
使用PUT
将以下数据发送到以下URI:http://localhost:9000/employees/10
{ "data": { "type": "employee", "id": 10, "attributes": { "company": "NilPortugues.com", "surname": "Portugués", "first_name": "Nil", "email_address": "nilportugues@example.com", "job_title": "Full Stack Web Developer", "business_phone": "(123)555-0100", "home_phone": "(123)555-0102", "mobile_phone": null, "fax_number": "(123)555-0103", "address": "Plaça Catalunya 1", "city": "Barcelona", "state_province": "Barcelona", "zip_postal_code": "08028", "country_region": "Spain", "web_page": "http://nilportugues.com", "notes": null, "attachments": null } } }
将产生
HTTP/1.1 200 OK
Cache-Control: private, max-age=0, must-revalidate
Content-type: application/vnd.api+json
{ "data": { "type": "employee", "id": "10", "attributes": { "company": "NilPortugues.com", "surname": "Portugués", "first_name": "Nil", "email_address": "contact@nilportugues.com", "job_title": "Full Stack Web Developer", "business_phone": "(123)555-0100", "home_phone": "(123)555-0102", "mobile_phone": null, "fax_number": "(123)555-0103", "address": "Plaça Catalunya 1", "city": "Barcelona", "state_province": "Barcelona", "zip_postal_code": "08028", "country_region": "Spain", "web_page": "http://nilportugues.com", "notes": null, "full_name": "Nil Portugués" }, "links": { "self": { "href": "http://localhost:9000/employees/10" }, "employee_orders": { "href": "http://localhost:9000/employees/10/orders" } } }, "included": [], "links": { "employees": { "href": "http://localhost:9000/employees" }, "employee_orders": { "href": "http://localhost:9000/employees/10/orders" } }, "jsonapi": { "version": "1.0" } }
PATCH
PATCH允许部分更新,与PUT不同。
即使它已经通过URI传递,我们也必须传递id
成员,当然id
值必须匹配。否则它将失败。
例如,通过以下URI发送以下数据:http://localhost:9000/employees/10
{ "data": { "type": "employee", "id": 10, "attributes": { "email_address": "contact@nilportugues.com" } } }
将产生
HTTP/1.1 200 OK
Cache-Control: private, max-age=0, must-revalidate
Content-type: application/vnd.api+json
{ "data": { "type": "employee", "id": "10", "attributes": { "company": "NilPortugues.com", "surname": "Portugués", "first_name": "Nil", "email_address": "contact@nilportugues.com", "job_title": "Full Stack Web Developer", "business_phone": "(123)555-0100", "home_phone": "(123)555-0102", "mobile_phone": null, "fax_number": "(123)555-0103", "address": "Plaça Catalunya 1", "city": "Barcelona", "state_province": "Barcelona", "zip_postal_code": "08028", "country_region": "Spain", "web_page": "http://nilportugues.com", "notes": null, "full_name": "Nil Portugués" }, "links": { "self": { "href": "http://localhost:9000/employees/10" }, "employee_orders": { "href": "http://localhost:9000/employees/10/orders" } } }, "included": [], "links": { "employees": { "href": "http://localhost:9000/employees" }, "employee_orders": { "href": "http://localhost:9000/employees/10/orders" } }, "jsonapi": { "version": "1.0" } }
DELETE
DELETE是使用最简单的方法,因为它不需要主体。只需向http://localhost:9000/employees/10/
发出DELETE,ID为10的Employee
就会消失。
它将产生以下输出
HTTP/1.1 204 No Content
Cache-Control: private, max-age=0, must-revalidate
Content-type: application/vnd.api+json
请注意响应将是空的
GET 查询参数:include、fields、sort 和 page
根据标准,对于GET方法,可以
- 使用
fields
查询参数仅显示请求的字段。- &fields[resource]=field1,field2
例如,传递/employees/10?fields[employee]=company,first_name
将产生以下输出
{ "data": { "type": "employee", "id": "10", "attributes": { "company": "NilPortugues.com", "first_name": "Nil" }, "links": { "self": { "href": "http://localhost:9000/employees/10" }, "employee_orders": { "href": "http://localhost:9000/employees/10/orders" } } }, "links": { "employees": { "href": "http://localhost:9000/employees" }, "employee_orders": { "href": "http://localhost:9000/employees/10/orders" } }, "jsonapi": { "version": "1.0" } }
- 通过传递它们之间的关系(通过点分隔),或仅传递通过逗号分隔的资源列表,来仅显示那些
include
资源。- &include=resource1
- &include=resource1.resource2,resource2.resource3
例如,/employees?include=order
将仅在include
成员中加载订单类型数据,但/employees?include=order.employee
将仅加载与employee
类型相关的订单。
-
使用
sort
排序和传递在data[type]
成员中定义的主资源成员名称。如果它以-
开头,则排序为DESCENDING
,否则为ASCENDING
。- &sort=field1,-field2
- &sort=-field1,field2
例如:/employees?sort=surname,-first_name
- 还定义了分页,允许进行分页、游标分页或偏移分页。
- &page[number]
- &page[limit]
- &page[cursor]
- &page[offset]
- &page[size]
例如:/employees?page[number]=1&page[size]=10
POST/PUT/PATCH 关联关系
JSON API允许创建和修改资源,并传递将创建或修改现有资源的relationships
。
假设我们想创建一个新的Employee
,并传递它的第一个Order
。
这可以通过向端点发出2个POST
来完成(一个用于Employee,一个用于Order)或通过将第一个Order
作为与我们的Employee
的relationship
传递,例如
{ "data": { "type": "employee", "attributes": { "company": "Northwind Traders", "surname": "Giussani", "first_name": "Laura", "email_address": "laura@northwindtraders.com", "job_title": "Sales Coordinator", "business_phone": "(123)555-0100", "home_phone": "(123)555-0102", "mobile_phone": null, "fax_number": "(123)555-0103", "address": "123 8th Avenue", "city": "Redmond", "state_province": "WA", "zip_postal_code": "99999", "country_region": "USA", "web_page": "http://northwindtraders.com", "notes": "Reads and writes French.", "full_name": "Laura Giussani" }, "relationships": { "order": { "data": [ { "type": "order", "attributes": { "customer_id": "28", "order_date": "2006-05-11 00:00:00", "shipped_date": "2006-05-11 00:00:00", "shipper_id": "3", "ship_name": "Amritansh Raghav", "ship_address": "789 28th Street", "ship_city": "Memphis", "ship_state_province": "TN", "ship_zip_postal_code": "99999", "ship_country_region": "USA", "shipping_fee": "10.0000", "taxes": "0.0000", "payment_type": "Check", "paid_date": "2006-05-11 00:00:00", "notes": null, "tax_rate": "0", "tax_status_id": null, "status_id": "0" } } ] } } } }
由于存在此用例,我们不得不调整我们的控制器实现,覆盖一些由 JsonApiController 提供的方法: createResourceCallable
、updateResourceCallable
和 patchResourceCallable
。
下面是如何对 createResourceCallable
进行操作的示例。
<?php namespace App\Http\Controllers; use App\Model\Database\Employees; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\DB; use NilPortugues\Api\JsonApi\Server\Errors\Error; use NilPortugues\Api\JsonApi\Server\Errors\ErrorBag; use NilPortugues\Laravel5\JsonApi\Controller\JsonApiController; class EmployeesController extends JsonApiController { /** * Now you can actually create Employee and Orders at once. * Use transactions - DB::beginTransaction() for data integrity! * * @return callable */ protected function createResourceCallable() { $createOrderResource = function (Model $model, array $data) { if (!empty($data['relationships']['order']['data'])) { $orderData = $data['relationships']['order']['data']; if (!empty($orderData['type'])) { $orderData = [$orderData]; } foreach ($orderData as $order) { $attributes = array_merge($order['attributes'], ['employee_id' => $model->getKey()]); Orders::create($attributes); } } }; return function (array $data, array $values, ErrorBag $errorBag) use ($createOrderResource) { $attributes = []; foreach ($values as $name => $value) { $attributes[$name] = $value; } if (!empty($data['id'])) { $attributes[$this->getDataModel()->getKeyName()] = $values['id']; } DB::beginTransaction(); try { $model = $this->getDataModel()->create($attributes); $createOrderResource($model, $data); DB::commit(); return $model; } catch(\Exception $e) { DB::rollback(); $errorBag[] = new Error('creation_error', 'Resource could not be created'); throw $e; } }; } }
为了使用事务,需要在 Eloquent
模型中定义 $fillable
值。
以下是定义了 $fillable
的 Employees
和 Orders
的示例。
带有 $fillable 的 Employees (Eloquent 模型)
<?php namespace App\Model\Database; use Illuminate\Database\Eloquent\Model; use Illuminate\Foundation\Validation\ValidatesRequests; class Employees extends Model { public $timestamps = false; protected $table = 'employees'; protected $primaryKey = 'id'; protected $appends = ['full_name']; /** * @var array */ protected $fillable = [ 'company', 'last_name', 'first_name', 'email_address', 'job_title', 'business_phone', 'home_phone', 'mobile_phone', 'fax_number', 'address', 'city', 'state_province', 'zip_postal_code', 'country_region', 'web_page', 'notes', 'attachments', ]; /** * @return \Illuminate\Database\Eloquent\Relations\HasOne */ public function latestOrders() { return $this->hasMany(Orders::class, 'employee_id')->limit(10); } /** * @return string */ public function getFullNameAttribute() { return $this->first_name.' '.$this->last_name; } }
带有 $fillable 的 Orders (Eloquent 模型)
<?php namespace App\Model\Database; use Illuminate\Database\Eloquent\Model; class Orders extends Model { public $timestamps = false; protected $table = 'orders'; protected $primaryKey = 'id'; /** * @var array */ protected $fillable = [ 'employee_id', 'customer_id', 'order_date', 'shipped_date', 'shipper_id', 'ship_name', 'ship_address', 'ship_city', 'ship_state_province', 'ship_zip_postal_code', 'ship_country_region', 'shipping_fee', 'taxes', 'payment_type', 'paid_date', 'notes', 'tax_rate', 'tax_status_id', 'status_id', ]; /** * @return \Illuminate\Database\Eloquent\Relations\HasOne */ public function employee() { return $this->belongsTo(Employees::class, 'employee_id'); } }
自定义响应头
添加自定义响应头可能有多个原因: 版本控制、设置过期头、缓存、设置私有或公开的内容...
为了做到这一点,只需覆盖 JsonApiController 的 addHeaders
方法即可。例如,让我们以 EmployeeController 为例。
<?php namespace App\Http\Controllers; use App\Model\Database\Employees; use NilPortugues\Laravel5\JsonApi\Controller\JsonApiController; use Symfony\Component\HttpFoundation\Response; class EmployeesController extends JsonApiController { //All your supported methods... /** * @param Response $response * * @return \Symfony\Component\HttpFoundation\Response */ protected function addHeaders(Response $response) { $response->headers->set('X-API-Version', '1.0'); $response->setPublic(); $response->setMaxAge(60); $response->setSharedMaxAge(60); return $response; } }
现在所有支持的操作都将包括添加的自定义头。
常见错误和解决方案
"未定义索引: @type"
这种情况通常是因为您在 config/jsonapi.php 中没有编写您的 Mapping
命名空间。请再次检查,如果缺失,则添加它并刷新资源。它应该会消失!
贡献
欢迎对包的贡献!
支持
使用以下方式之一与我联系
- 通过 contact@nilportugues.com 发送电子邮件给我
- 打开 问题
作者
许可证
代码库是在 MIT 许可证 下许可的。