nilportugues/laravel5-json-api-dingo

Laravel5 JSONAPI 和 Dingo 结合,快速构建 API

1.0.2 2017-04-06 14:46 UTC

This package is auto-updated.

Last update: 2024-09-06 08:52:18 UTC


README

Scrutinizer Code Quality SensioLabsInsight Latest Stable Version Total Downloads License Donate

此包通过使用 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 定义 EmployeesOrders 模型。

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 提供的一些方法:createResourceCallableupdateResourceCallablepatchResourceCallable

下面是 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 定义后的 EmployeesOrders 的示例。

带有 $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 的命名空间。请再次检查,如果缺失,请添加它并刷新资源。问题应该会解决!

贡献

对包的贡献总是受欢迎的!

支持

您可以使用以下方式之一与我联系

作者

许可协议

代码库在 MIT 许可证 下发布。