際際滷

際際滷Share a Scribd company logo
LaravelでAPI協xを砿尖する
Laravel/Vue茶氏#2@2017/11/24
KenjiroKubota
Pro le
Kenjiro Kubota
istyle.inc
徭各テクノロジ`云何y毅輝
PHP, HHVM/Hack, Javascript
書晩すこと
ADR
REST API Level3
Swagger PHP
閣顎艶の三はありません。ごめんなさい。
REST APIの協x砿尖ってどうしてます
Wiki
Markdownファイル
採かしらのAPIドキュメントツ`ル
API旋喘宀(フロントエンジニア)にこんなこと冱われない
仝gHに卦ってくるパラメ`タと協x`くない 々
(あ、ドキュメント厚仟してないっす)
尖襪g廾 = 協xですよね
g廾から協吶慕を伏撹する三
未まえて、書晩すこと
ADR = APIg廾の
REST API Level3 = レスポンス侘塀の
Swagger PHP = APIドキュメントの
ADR
ADR
Action Domain Responder
の待です。Laralab vol.1という茶氏でB初させていただきました。
Responsableを聞ったADRg廾
/KenjiroKubota/responsableadr
MVCから塗伏したUIア`キテクチャパタ`ン
Model Domain
View Responder
Controller Action
MVCと`うところ
1つのEndpointに1つのActionClass
歌深g廾
書Lk咾離鴬`ムソフトを卦すエンドポイントを恬ります。
(卦すのはダミ`デ`タです)
Github: kubotak-is/laravel-swagger-sample
<?php
namespace AppHttpActionApi;
final class GetThisWeeksGameSoftwareRelease
{
private $service;
public function __construct(ThisWeeksGameSoftwareRelease $service)
{
$this->service = $service;
}
public function __invoke(GetThisWeeksGameSoftwareReleaseRequest $request): Responder
{
$validated = $request->validated();
$limit = $validated['limit'] ?? 3;
$offset = $validated['offset'] ?? 0;
$collection = $this->service->getCollection((int) $limit, (int) $offset);
return new GetThisWeeksGameSoftwareReleaseResponder($collection);
}
}
class RouteServiceProvider extends ServiceProvider
{
public function register()
{
parent::register();
/** @var Router $router */
$router = $this->app['router'];
$router->group(['prefix' => 'api'], function (Router $router) {
$router->get(
'/game_software/release/week',
['uses' => GetThisWeeksGameSoftwareRelease::class]
);
});
}
}
RouterでDispatchされたClassは __invoke() がg佩される
FatContollerにならない
(おまけ)酷看姻馨檎艶援顎艶壊岳
<?php
namespace AppHttpRequestApi;
use IlluminateFoundationHttpFormRequest;
class GetThisWeeksGameSoftwareReleaseRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'limit' => 'integer|min:1',
'offset' => 'integer|min:0',
];
}
}
ActionClass
メソッドインジェクションしたFormRequestは屡にバリデ`ションg
みのパラメ`タを函誼できる。
Laraevl5.5からは ->validated(); でバリデ`ション協xされてかつ、バ
リデ`ションされた、里瀏ゝ辰辛嬬
public function __invoke(GetThisWeeksGameSoftwareReleaseRequest $request): Responder
{
$validated = $request->validated();
$limit = $validated['limit'] ?? 3;
$offset = $validated['offset'] ?? 0;
$collection = $this->service->getCollection((int) $limit, (int) $offset);
return new Responder($collection);
}
Responder
<?php
namespace AppHttpResponderApi;
use AppHttpResponderHateoasResponder;
use IlluminateContractsSupportResponsable;
use AppDomainCollectionGameSoftwareCollection;
class GetThisWeeksGameSoftwareReleaseResponder implements Responsable
{
use HateoasResponder;
private $resource;
public function __construct(GameSoftwareCollection $collection)
{
$this->resource = $collection;
}
public function toResponse($request): IlluminateHttpResponse
{
return $this->hal($this->resource);
}
}
REST API Level3
RESTの4粁AのModel
https://martinfowler.com/articles/richardsonMaturityModel.html
REST API Model
Level 0
HTTPを喘いてXMLレスポンスを卦抜すること
Level 1
URLでリソ`スを燕すこと
Level 2
HTTPメソッドを屎しく聞い蛍けること
Level 3
ハイパ`メディアコントロ`ル
ハイパ`メディアコントロ`ル
Webサイトのにテキストのリンクを旋喘して旋喘宀をナビゲ`トす
るように、
APIのレスポンスにもeのエンドポイントへのナビゲ`トを根めること
HATEOAS
Hypermedia As The Engine Of Application State
HATEOASとは
RestfullAPIをするア`キテクチャパタ`ン
彜Bw卞を燕Fする
レスポンス坪にリンクを根める
そのリンクを{ることで彜Bw卞を燕Fする
willdurand/hateoas
https://github.com/willdurand/Hateoas
アノテ`ションを弖紗することでレスポンスパラメ`タをするライ
ブラリ
$ composer require willdurand/hateoas
Laravelでアノテ`ションを旋喘できるようにする
<?php
namespace AppProviders;
use DoctrineCommonAnnotationsAnnotationReader;
use DoctrineCommonAnnotationsAnnotationRegistry;
use IlluminateSupportServiceProvider;
class AnnotationRegisterServiceProvider extends ServiceProvider
{
public function register()
{
$loader = require base_path().'/vendor/autoload.php';
AnnotationRegistry::registerLoader([$loader, 'loadClass']);
}
}
AppProvidersAnnotationRegisterServiceProvider::class,
Entity
<?php
namespace AppDomainEntity;
use AppHttpResponderHateoasResource;
use PHPMentorsDomainKataEntityEntityInterface;
class GameSoftware implements EntityInterface, HateoasResource
{
protected $id;
protected $title;
protected $description;
protected $releaseDate;
protected $price;
protected $retailPriceDesired;
protected $platform;
protected $thumbnail;
}
Add Annotation
/**
* Class GameSoftware
* @package AppDomainEntity
* @HateoasRelation(
* "self",
* href = "expr('/api/game_software/' ~ object.getId())"
* )
* @HateoasRelation(
* "page",
* href = "expr('/game_software/' ~ object.getId())"
* )
*/
class GameSoftware implements EntityInterface, HateoasResource
{
/**
* @var string
* @Accessor("getReleaseDate")
*/
protected $releaseDate;
/**
* @var int
* @Type("int")
*/
protected $price;
public function getId(): int
{
return $this->id;
}
public function getReleaseDate(string $format = "Y-m-d H:i:s"): string
{
return (new DateTime($this->releaseDate))->format($format);
}
<?php
namespace AppHttpResponder;
use HateoasHateoas;
use HateoasHateoasBuilder;
use IlluminateHttpResponse;
use HateoasUrlGeneratorCallableUrlGenerator;
trait HateoasResponder
{
protected function hal(
HateoasResource $resource,
int $status = 200,
array $headers = []
): Response
{
return new Response(
$this->builder()->serialize($resource, 'json'),
$status,
array_merge(['Content-Type' => 'application/hal+json'], $headers)
);
}
protected function builder(): Hateoas
{
return HateoasBuilder::create()
->setUrlGenerator(
null,
new CallableUrlGenerator(function ($route, array $parameters) {
return route($route, $parameters);
})
)
->build();
}
}
<?php
namespace AppHttpResponderApi;
use AppHttpResponderHateoasResponder;
use IlluminateContractsSupportResponsable;
use AppDomainCollectionGameSoftwareCollection;
class GetThisWeeksGameSoftwareReleaseResponder implements Responsable
{
use HateoasResponder;
private $resource;
public function __construct(GameSoftwareCollection $collection)
{
$this->resource = $collection;
}
public function toResponse($request): IlluminateHttpResponse
{
return $this->hal($this->resource);
}
}
こんな湖じのレスポンスになります。
{
"id": 1,
"title": "ス`パ`マリオオデッセイ",
"description": "マリオ、弊順の唾へ。Nintendo Switch鬚吋愁侫箸撲戴3Dマリオが鞠します。´",
"release_date": "2017-10-27 00:00:00",
"price": 5545,
"retail_price_desired": 6458,
"platform": "Nintendo Switch",
"thumbnail": "https://images-na.ssl-images-amazon.com/images/I/´.jpg",
"_links": {
"self": {
"href": "/api/game_software/1"
},
"page": {
"href": "/game_software/1"
}
}
}
(おまけ)書指聞ったHateoas參翌のライブラリのB初
zendframework/zend-hydrator
https://docs.zendframework.com/zend-hydrator/
public function findReleaseThisWeek(int $limit, int $offset): GameSoftwareCollection
{
$data = $this->criteria->getReleaseThisWeek($limit, $offset);
$collection = new GameSoftwareCollection();
foreach ($data as $item) {
$hydrator = new ReflectionHydrator();
$namingStrategy = new CompositeNamingStrategy([
'release_date' => new MapNamingStrategy([
'release_date' => 'releaseDate'
]),
'retail_price_desired' => new MapNamingStrategy([
'retail_price_desired' => 'retailPriceDesired'
]),
]);
$hydrator->setNamingStrategy($namingStrategy);
$gameSoft = $hydrator->hydrate(
$item,
new GameSoftware()
);
$collection->add($gameSoft);
}
return $collection;
}
protected, privateプロパティにして
constructやsetterなしでオブジェクトマッピングしてくれます
Swagger PHP
Swaggerとは
https://swagger.io
THE WORLD'S MOST POPULAR API TOOLING
RESTful APIのドキュメントや、サ`バ、クライアントコ`ド、エディ
タ、またそれらをQうための碧などを戻工するフレ`ムワ`ク
Swagger Speci cationをSwagger UIにiみzませることで協xを伏
撹する
晦温姻温厩艶鉛て?粥永鴛協吶を砿尖する
Swagger PHPではアノテ`ションを旋喘して
Swagger Speci 界温岳庄看稼(逮壊看稼)を伏撹
g廾 = 協xができる
DarkaOnLine/L5-Swagger
https://github.com/DarkaOnLine/L5-Swagger
LaravelでSwaggerPHPを聞うライブラリ
ArtisanコマンドでSwagger Speci cationを伏撹してSwaggerUIにm喘
したペ`ジが燕幣できる
$ composer require "darkaonline/l5-swagger:5.5.*"
L5SwaggerL5SwaggerServiceProvider::class,
_kh廠のみ旋喘したいのでProviderでLocalとDeveloph廠の栽の
み鞠hする
$env = $this->app->environment();
if ($env === 'develop' || $env === 'local') {
$this->app->register(L5SwaggerL5SwaggerServiceProvider::class);
}
$ php artisan l5-swagger:publish
con g/l5-swagger.php
public/vendor/l5-swagger
resources/views/vendor/l5-swagger
駅勣なファイルをpublishで卞
HATEOASで旋喘したアノテ`ション鞠hでSwagger PHPのアノテ`シ
ョン @SWG のClassがつからないエラ`がk伏するのでこのアノテ`シ
ョンはIgnoreさせる
class AnnotationRegisterServiceProvider extends ServiceProvider
{
public function register()
{
$loader = require base_path().'/vendor/autoload.php';
AnnotationRegistry::registerLoader([$loader, 'loadClass']);
AnnotationReader::addGlobalIgnoredNamespace('SWG'); // Add
}
}
Modelの協x
/**
* Class GameSoftware
* @package AppDomainEntity
* @HateoasRelation(
* "self",
* href = "expr('/api/game_software/' ~ object.getId())"
* )
* @HateoasRelation(
* "page",
* href = "expr('/game_software/' ~ object.getId())"
* )
* @SWGDefinition(
* type="object",
* @SWGXml(name="GameSoftware")
* )
*/
class GameSoftware implements EntityInterface, HateoasResource
プロパティのアノテ`ション
/**
* @var int
* @SWGProperty(format="int64")
*/
protected $id;
/**
* @var string
* @SWGProperty()
*/
protected $title;
/**
* @SWGProperty(
* property="_links",
* type="object",
* @SWGProperty(
* property="self",
* type="object",
* @SWGProperty(
* property="href",
* type="string"
* )
* ),
* @SWGProperty(
* property="page",
* type="object",
* @SWGProperty(
* property="href",
* type="string"
* )
* )
* )
*/
Collectionの協x
/**
* Class GameSoftwareCollection
* @package AppDomainCollection
* @SWGDefinition(
* type="object",
* @SWGXml(name="GameSoftwareCollection")
* )
*/
class GameSoftwareCollection implements EntityCollectionInterface, HateoasResource
{
/**
* @var GameSoftware[]
* @SerializedName("game_software")
* @SWGProperty(
* property="game_software",
* @SWGXml(name="GameSoftware", wrapped=true)
* )
*/
protected $entities = [];
Responseの協x
(ResponderClass)
/**
* @param IlluminateHttpRequest $request
* @return IlluminateHttpResponse
* @SWGResponse(
* response="GetThisWeeksGameSoftwareReleaseResponder",
* description="書Lk咾離鴬`ムを卦す",
* @SWGSchema(ref="#/definitions/GameSoftwareCollection")
* )
*/
public function toResponse($request): IlluminateHttpResponse
{
return $this->hal($this->resource);
}
RequestParameterの協x
/**
* @SWGParameter(
* parameter="GetThisWeeksGameSoftwareReleaseRequest_limit",
* name="limit",
* description="函誼周方",
* in="query",
* required=false,
* type="integer",
* format="int32"
* )
* @SWGParameter(
* parameter="GetThisWeeksGameSoftwareReleaseRequest_offset",
* name="offset",
* description="函誼了崔",
* in="query",
* required=false,
* type="integer",
* format="int32"
* )
*/
public function rules(): array
Endpointの協x
/**
* Class GetThisWeeksGameSoftwareRelease
* @package AppHttpActionApi
* @SWGGet(
* path="/game_software/release/week",
* summary="書Lk咾離鴬`ムソフト",
* description="",
* consumes={"application/json"},
* produces={"application/hal+json"},
* @SWGParameter(ref="#/parameters/GetThisWeeksGameSoftwareReleaseRequest_limit"),
* @SWGParameter(ref="#/parameters/GetThisWeeksGameSoftwareReleaseRequest_offset"),
* @SWGResponse(
* response="default",
* ref="#/responses/GetThisWeeksGameSoftwareReleaseResponder"
* )
* )
*/
final class GetThisWeeksGameSoftwareRelease
swagger.jsonの伏撹
$ php artisan l5-swagger:generate
_kh廠にデプロイするHにg佩
もしくは config/l5-swagger.php の 'generate_always' => true にすることで
SwaggerUIにアクセスするたびに伏撹されます。
/api/documentation にアクセスするとSwaggerUIが婢_される。
エンドポイントを筝したい栽は config/l5-swagger.php の
'api' => 'api/documentation', の朕から筝できます。
まとめ
ADRで佞鮹_に
REST API Level3はHAETOASでgF
Swagger-php(L5-Swagger)を旋喘してg廾=協x
ADRでg廾することでアノテ`ションもModel,Response,Request
で蛍xできる
PRレビュ`を佩っている栽はアノテ`ションとg廾が匯崑して
いるかを_J並にできる
フロントエンジニアに恷仟の彜Bの協xを戻工できる
thanks :)

More Related Content

晦温姻温厩艶鉛て?粥永鴛協吶を砿尖する