コーディングガイド

プロジェクト

vendorは会社の名前やチームの名前または個人の名前(excite,koriym等)を指定して、packageにはアプリケーション(サービス)の名前(blog, news等)を指定します。 プロジェクトはアプリケーション単位で作成し、Web APIとHTMLを別ホストでサービスする場合でも1つのプロジェクトにします。

スタイル

PSR1, PSR2, PSR4に準拠します。

<?php
declare (strict_types = 1);

namespace Koriym\Blog\Resource\App;

use BEAR\RepositoryModule\Annotation\Cacheable;
use BEAR\Resource\Annotation\Embed;
use BEAR\Resource\Annotation\Link;
use BEAR\Resource\Code;
use BEAR\Resource\ResourceObject;
use BEAR\Sunday\Inject\ResourceInject;
use Ray\AuraSqlModule\AuraSqlInject;

/**
 * @Cacheable
 */
class Entry extends ResourceObject
{
    use AuraSqlInject;
    use ResourceInject;

    /**
     * @Embed(rel="author", src="/author{?author_id}")
     */
    public function onGet(string $author_id, string $slug) : ResourceObject
    {
        // ...

        return $this;
    }

    /**
     * @Link(rel="next_act", href="/act1")
     * @Link(rel="next_act2", href="/act2")
     */
    public function onPost (
        string $tile,
        string $body,
        string $uid,
        string $slug
    ) : ResourceObject {
        // ...
        $this->code = Code::CREATED;

        return $this;
    }
}

リソースのdocBlockコメントはオプションです。リソースURIや引数名だけで説明不十分な時にメソッドの要約(一行)、説明(複数行可)、@paramsを付加します。@paramsの後は空行を空けカスタムアノテーションはその後に記述します。

/**
 * A summary informing the user what the associated element does.
 *
 * A *description*, that can span multiple lines, to go _in-depth_ into the details of this element
 * and to provide some background information or textual references.
 *
 * @param string $arg1 *description*
 * @param string $arg2 *description*
 * @param string $arg3 *description*
 *
 * @Link(rel="next_act", href="/next_act_uri")
 * @Link(rel="next_act2", href="/next_act_uri2")
*/

グローバル

グローバルな値をリソースやアプリケーションのクラスで参照することは推奨されません。(Modulesでのみ使用します)

  • スーパーグローバルの値を参照しない
  • defineは使用しない
  • 設定値を保持するConfigクラスを作成しない
  • グローバルなオブジェクトコンテナ(サービスロケータ)を使用しない [1], [2]
  • date関数やDateTimeクラスで現在時刻を直接取得することは推奨されません。koriym/nowなどを使って外部から時刻をインジェクトします。

スタティックメソッドなどのグローバルなメソッドコールも推奨されません。

アプリケーションコードが必要とする値は設定ファイルなどから取得するのではなく、基本的に全てインジェクトします。(設定ファイルはインジェクトのために使います)Web APIなど外部のシステムの値を利用する時には、クライアントクラスやWeb APIアクセスリソースなど1つにの場所に集中させDIやAOPでモッキングが容易にするようにします。

クラスとオブジェクト

  • インジェクション以外でトレイトは推奨されません。
  • 親クラスのメソッドを子クラスが使うことは推奨されません。共通する機能は継承やtraitで共有するのではなくて専用のクラスにしてそれをインジェクトして使います。継承より合成します。
  • メソッドが1つだけのクラスは機能をクラス名に反映してメソッドの名前を__invoke()にし関数アクセスできるようにします。

スクリプトコマンド

  • composer setupコマンドでアプリケーションのセットアップが完了することが推奨されます。このスクリプトではデータベースの初期化、必要ライブラリの確認が含まれます。.envの設定などマニュアルな操作が必要な場合はその手順が画面表示されることが推奨されます。
  • composer cleanupコマンドでアプリケーションのキャッシュやログが全てクリアされることが推奨されます。
  • composer deployコマンドでアプリケーションのdeployが行われることが推奨されます。

コードチェック

commit毎に以下のコマンドでコードのチェックすることを推奨します。コマンドはbear/qatoolsでインストールできます。

phpcs src tests
phpmd src text ./phpmd.xml
php-cs-fixer fix --config-file=./.php_cs
phpcbf src

リソース

コード

適切なステータスコードを返します。テストが容易になり、botやクローラーにも正しい情報が伝えることができます。

  • 100 Continue 複数のリクエストの継続
  • 200 OK
  • 201 Created リソース作成
  • 202 Accepted キュー/バッチ 受付
  • 204 No Content bodyがない場合
  • 304 Not Modified 未更新
  • 400 Bad Request リクエストに不備
  • 401 Unauthorized 認証が必要
  • 403 Forbidden 禁止
  • 404 Not Found
  • 405 Method Not Allowed
  • 503 Service Unavailable サーバーサイドでの一時的エラー

304@Cacheableアノテーションを使っていると自動設定されます。404はリソースクラスがない場合、405はリソースのメソッドがない場合に自動設定されます。またDBの接続エラーなどは必ず503で返しクローラーに伝えます。[1]

メソッド

onGetメソッドはリソースの状態変更を含めません。(アクセスカウンターなどの副作用を除きます)

onPostは冪等性の無いリソース操作を実装します。例えばオートインクリメント値でURIが決まるリソース作成です。onPostで作成したリソースのURIはLocationヘッダーで示します。

public function onPost(string $title) : ResourceObject
{
    // ...
    $this->code = 201;
    $this->headers['Location'] = "/task?id={$id}";

    return $this;
}

onPutは冪等性のあるリソース操作を実装します。例えばリソース内容の変更や、UIDなど指定したリソース作成です。

onPatchはリソースの一部分の状態変更するときに実装します。

HTMLのFormメソッド

BEAR.SundayはHTMLのWebフォームでPOSTリクエストの時にX-HTTP-Method-Overrideヘッダーや_methodクエリーを用いてメソッドを上書きする事ができますが必ずしも推奨しているわけではありません。PageリソースではonGetonPost以外を実装しない方針でも問題ありません。[1],[2]

ハイパーリンク

リンクを持つリソースは@Linkで示すことが推奨されます。

class User
{
    /**
     * @Link(rel="profile", href="/profile{?id})
     * @Link(rel="blog", href="/blog{?id})
     */
    public function onGet($id)

次のアクションを持つリソースリクエストはhref()(ハイパーリファレンス)で辿る事が推奨されます。

class Order
{
    /**
     * @Link(rel="payment", href="/payment{?order_id, credit_card_number}", method="put")
     */
    public function onPost($drink)
// 上記の注文リソースを作成して支払いリソースにリクエストします
$order = $this->resource
    ->post
    ->uri('app://self/order')
    ->withQuery(['drink' => 'latte'])
    ->eager
    ->request();
$payment = ['credit_card_number' => '123456789'];
$response = $resource->href('payment', $payment);

埋め込みリソース

リソースがリソースを含む時は@Embedで埋め込む事が推奨されます。

/**
 * @Embed(rel="user", src="/user{?user_id}")
 */
public function onGet(string $userId) : ResourceObject
{
/**
 * @Embed(rel="uid", src="/uid")
 */
public function onPost(string $userId, string $title) : ResourceObject
{
    $uid = $this['uid']()->body;

@Embedするリソースのリクエストに必要なクエリーがメソッド無いで決定する時はパラメーターが含まれない不完全なリソースを@Embedしてからクエリーを指定します。

/**
 * @Embed(rel="user", src="/user")
 */
public function onGet() : ResourceObject
{
    ...
    $query = ['userId' => $userId];
    $user = $this['user']->withQuery($query)()->body; // /user?user={$userId}

@EmbedしたURIにクエリーに付加する時はaddQuery()を使います。

/**
 * @Embed(rel="user", src="/user&category=1")
 */
public function onGet() : ResourceObject
{
    ...
    $query = ['userId' => $userId];
    $user = $this['user']->addQuery($query)()->body; // /user?category=1&user=$userId

引数束縛

onGet以外のメソッドで_GETの値を利用するには@QueryParamを使います。その他PHPのスーパーグローバル変数に格納される値はWebコンテキストパラメーター で引数に束縛します。

/**
 * @QueryParam(key="id", param="userId")
 */
public function foo($userId = null) : ResourceObject
{
   // $userId = $_GET['id'];

他のリソースの値を引数に利用する場合には@ResourceParamを使います。

/**
 * @ResourceParam(param=“name”, uri="/login#nickname")
 */
public function onGet($name) : ResourceObject
{

リソースクライアントは可能な限り使わないで @Embedで埋め込んだり@Linkのリンクを使うようにします。埋め込まれたリソースはtoUri()toUriWithMethod()でリクエスト文字列になりテストが容易です。

リソース

リソースのベストプラクティスはリソースベストプラクティスもご覧ください。

DI

  • ライブラリコードではセッターインジェクションは推奨されません。
  • Provider束縛を可能な限り避けtoConstructor束縛を優先することが推奨されます。
  • Moduleで条件に応じて束縛をすることを避けます。 (AvoidConditionalLogicInModules)
  • モジュール無いから環境変数を参照することは推奨されません。コンストラクタで渡します。

ルーター

APIとHTMLなど複数のコンテキストでルーティングを変える場合はルーターファイル route.aura.confでは$schemeHostによってルーターファイルをコンテキスト別にrequireします。

<?php
/* @var $router \BEAR\Package\Provide\Router\AuraRoute */
/* @var $schemeHost string */

require ($schemeHost === 'app://self') ? __DIR__ . '/app.route.conf' : __DIR__ . '/page.route.conf';

環境

Webだけでしか動作しないアプリケーションは推奨されません。テスト可能にするためにコンソールでも動作するようにします。

.envファイルをプロジェクトリポジトリに含まない事が推奨されます。

テスト

リソースクライアントを使ったリソーステストを基本にします。リソースの値を確認して、必要があれば表現(HTMLやJSON)のテストを加えます。

開発ツール

以下のPHPStormプラグインを推奨します。PHPStorm > Preference > Pluginsで設定します。