nilportugues / laravel5-json-api-dingo
Laravel5 JSONAPI 和 Dingo 结合,快速构建 API
Requires
Requires (Dev)
- dingo/api: 1.0.*@dev
- friendsofphp/php-cs-fixer: ^1.10
- laravel/laravel: 5.*
- phpunit/phpunit: 4.*
This package is auto-updated.
Last update: 2024-09-06 08:52:18 UTC
README
- 特性
- 安装
- 使用
- JsonApiController
- 示例:使用 API
- GET 查询参数:include、fields、sort 和 page
- POST/PUT/PATCH 带有关系的操作
- 自定义响应头
- 常见错误及解决方案
此包通过使用 Dingo 的 API 路由系统而不是 Laravel 的来使 Laravel 5 JSON API 服务器包 和 Dingo 之间协同工作。
这样做,您可以获得 JSON API 映射的自动格式化以及 Dingo 提供的版本和安全性。
特性
- 该包提供了 JSON API 规范的完整实现,并且被官方网站 推荐!
- 一个 JSON API 转换器,允许您将任何映射对象转换为有效的 JSON API 资源。
- 控制器模板,使用您现有的 现有 Eloquent 模型 编写完全兼容的 JSON API 服务器。
- Dingo 特性包括:
- 多个身份验证适配器
- API 版本控制
- 速率限制
- 内部请求
- API 蓝图文档
安装
使用 Composer 安装此包
$ composer require nilportugues/laravel5-json-api-dingo
现在运行以下 artisan 命令
$ php artisan vendor:publish
配置
打开 config/app.php 并在 providers 数组下添加以下行
'providers' => [ //... NilPortugues\Laravel5\JsonApiDingo\Laravel5JsonApiDingoServiceProvider::class, ],
使用
定义路由
我们将在实现之前规划资源。所有路由都需要一个名称。
这就是我们的 app/Http/routes.php 的样子
<?php $api = app('Dingo\Api\Routing\Router'); $api->version('v1', function ($api) { $api->resource('employees', 'EmployeesController'); $api->get('employees/{employee_id}/orders', [ 'as' => 'employees.orders', 'uses' => 'EmployeesController@getOrdersByEmployee' ]); }); //...
定义
首先,让我们使用 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 []; } /** * Returns an array of properties that are mandatory to be passed in when doing create or update. * * @return array */ public function getRequiredProperties() { 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 []; } /** * Returns an array of properties that are mandatory to be passed in when doing create or update. * * @return array */ public function getRequiredProperties() { return []; } }
使用
打开 config/jsonapi.php。此文件应该返回一个数组,包含所有类映射。
<?php use App\Model\Api\EmployeesTransformer; use App\Model\Api\OrdersTransformer; return [ EmployeesTransformer::class, OrdersTransformer::class, ];
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 "https://: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": "https://:9000/employees/1"
},
"employee_orders": {
"href": "https://: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": "https://:9000/orders/71"
},
"employee": {
"href": "https://:9000/employees/1"
}
}
}
],
"links": {
"employees": {
"href": "https://:9000/employees"
},
"employee_orders": {
"href": "https://:9000/employees/1/orders"
}
},
"jsonapi": {
"version": "1.0"
}
}
POST
POST需要接受所有成员属性,即使是通过映射器隐藏的属性。
例如,attachments成员被隐藏,但它需要,因此需要使用有效值传递。另一方面,full_name成员值不能作为属性传递,否则资源创建将失败。
传递id是可选的,如果提供,将使用它而不是服务器端生成的值。
使用POST将以下数据发送到以下URI https://: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: https://: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": "https://:9000/employees/10"
},
"employee_orders": {
"href": "https://:9000/employees/10/orders"
}
}
},
"links": {
"employees": {
"href": "https://:9000/employees"
},
"employee_orders": {
"href": "https://:9000/employees/10/orders"
}
},
"jsonapi": {
"version": "1.0"
}
}
PUT
PUT需要接受所有成员属性,就像POST一样。
为了这个示例,我们只发送一个新的job_title值,并且保持其他所有内容完全相同。
这次我们被迫传递id,即使它已经被URI传递,当然id值必须匹配。否则将失败。
使用PUT将以下数据发送到以下URI https://: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": "https://:9000/employees/10"
},
"employee_orders": {
"href": "https://:9000/employees/10/orders"
}
}
},
"included": [],
"links": {
"employees": {
"href": "https://:9000/employees"
},
"employee_orders": {
"href": "https://:9000/employees/10/orders"
}
},
"jsonapi": {
"version": "1.0"
}
}
PATCH
PATCH允许部分更新,与PUT不同。
即使它已经被URI传递,我们也需要传递id成员,当然id值必须匹配。否则将失败。
例如,使用以下URIhttps://: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": "https://:9000/employees/10"
},
"employee_orders": {
"href": "https://:9000/employees/10/orders"
}
}
},
"included": [],
"links": {
"employees": {
"href": "https://:9000/employees"
},
"employee_orders": {
"href": "https://:9000/employees/10/orders"
}
},
"jsonapi": {
"version": "1.0"
}
}
DELETE
DELETE是最简单的方法,因为它不需要体。只需向https://: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": "https://:9000/employees/10"
},
"employee_orders": {
"href": "https://:9000/employees/10/orders"
}
}
},
"links": {
"employees": {
"href": "https://:9000/employees"
},
"employee_orders": {
"href": "https://: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作为relationship与我们的Employee一起传递,例如
{
"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 许可证 下发布。