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 "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
值必须匹配。否则将失败。
例如,使用以下URIhttp://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
作为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 许可证 下发布。