読者です 読者をやめる 読者になる 読者になる

【fuelphp】RestApiをつくったときのまとめ

fuelphp

はじめに

フロントエンドのアプリ開発がメインの案件ではあったのですが、結局サーバ側もけっこういじりました。

自分がメインの案件ではなかったのですが
そこそこの期間をかけてAPIを実装して今までにない経験もしたのでそろそろメモしておきます。

自分流とは1番違う点としてはfuelphpのクエリビルダーを駆使したことでしょうか

設定系

rest api設定

本番環境ではSSLでの実装になるので、いらないっちゃいらないのですが気休め程度にBasic認証をいれました

fuel/app/config/***/rest.php

return array(
    'auth' => 'basic',
    'valid_logins' => array('hoge' => 'hogepass'),
);
db設定

dbは基本的なmysqlのマスター・スレーブ構成になります。

fuel/app/config/***/db.php

return array(
    'rw'    => array(
        'type'  => 'pdo',
        'connection' => array(
            'dsn' => 'mysql:host=localhost;dbname=hoge'
            'username' => 'hoge_rw',
            'password' => 'hoge',
            'persistent'    => false,
            'compress'      => false,
        ),
        'identifier'    => '`',
        'table_prefix'  => '',
        'charset'       => 'utf8',
        'enable_cache'  => true,
        'profiling'     => true,
        'readonly'      => false,
    ),

    'ro'    => array(
        'type'  => 'pdo',
        'connection' => array(
            'dsn' => 'mysql:host=localhost;dbname=hoge
            'username'      => 'hoge_ro',
            'password'      => 'hoge',
            'persistent'    => false,
            'compress'      => false,
        ),
        'identifier'    => '`',
        'table_prefix'  => '',
        'charset'       => 'utf8',
        'enable_cache'  => true,
        'profiling'     => true,
        'readonly'      => false,
    ),
);
SQLクエリログ

初めて使ったクエリビルダーです。
便利と感じることも手間とかんじることも両方ありましたが勉強になりました。
で、特に開発中はログを確認しながらというのはよくあることなのだと思うのでこれも必須でしょうか

上記でもそうしましたが、db.phpのprofiling = trueにすること必要になります
ついでに重そうなクエリも早めに気づくように簡単に重そうなクエリのログ出力もいれてみました(これは、あまり見てませんが、、、)

fuel/app/config/***/event.php

return array(
    'fuelphp' => array(
        'shutdown' => function() {
            $ref = new ReflectionClass('Profiler');
            $prop = $ref->getProperty('profiler');
            $prop->setAccessible(true);
            $profiler = $prop->getValue();
            if ($profiler) {
                $profiler->db = $profiler;
                $profiler->gatherQueryData();
                foreach ($profiler->output["queries"] as $v) {
                    $is_slow = "";
                    if (preg_match('/([0-9]{1,}\.[0-9]{1,}) ms/', $v["time"], $matches)) {
                        if ("100" < $matches[1]) {
                            $is_slow = "SQL IS SLOW";
                        }
                    } else if (preg_match('/([0-9]{1,}\.[0-9]{1,}) s/', $v["time"], $matches)) {
                        $is_slow = "SQL IS SLOW";
                    }
                    \Log::info(sprintf("[SQLデバッグ]%s\t%s\t%s", htmlspecialchars_decode($v["sql"], ENT_QUOTES), $v["time"], $is_slow));
                }
            }
        },
    ),
);

このevent.phpに記述する方法はQuitaのどこかの記事を参考にさせていただいたので、どの記事かみつけたら紹介します

ベースコントローラ

baseコントローラの作成

ありがちなベースコントローラ

<?php
class ValidateException extends Exception {} // バリデーションエラー        
class SystemErrorException extends Exception {} // PHPエラーなど            

class Controller_Base extends Controller_Rest
{
   protected $format = 'json';

   public function before()
   {
       $this->start_time = microtime(true);

       parent::before();

       try
       {
           //アクセスログ出力                                                        
           Log::info(sprintf(
               "ACCESS\t%s\t%s\t%s\t%s\t%s\t%s"
               ,Input::server("REMOTE_ADDR")
               ,Input::server("HTTP_USER_AGENT")
               ,urldecode(Input::server("REQUEST_URI"))
               ,Input::server("HTTP_REFERER")
               ,json_encode(Input::get())
               ,json_encode(Input::post())
           ));

           Log::info(sprintf(
               "DEBUG:%s\t%s", Request::main()->controller, Request::active()->action
           ));
       }
    }

    // 正常時のレスポンスをかえすメソッド
    protected function success($results = null)
    {
        $response = array(
            'status' => 'OK',
            'results' => $results,
        );
        return $this->response($response);
    }

    // 異常時のレスポンスをかえすメソッド
    protected function error($message = null)
    {
        $response = array(
           'status' => 'NG',
           'message' => $message,
        );
        return $this->response($response);
    }

    // システムエラー時のレスポンスをかえすメソッド
    protected function system_error($ex)
    {
        Log::info("システムエラー::" . $ex->getMessage());
        Config::load("const", true);
        $title = "[システム名]システムエラー";
        $body  = "▪️システムエラー内容" . "\n";
        $body .= $ex->getMessage() . "\n\n";
        $body .= "▪️コントローラ/アクション\n";
        $body .= Request::main()->controller . "/" . Request::active()->action . "\n\n";
        $body .= "▪️詳細" . "\n";
        $body .= print_r($ex, true);
        $email = Email::forge();
        $email->from(Config::get("const.email.system_from"), Config::get("const.email.system_from"));
        $email->to(Config::get("const.email.alert_to"));
        $email->subject($title);
        $email->body($body);
        $email->send();
        $this->error();
    }

    // dbエラー時のレスポンスをかえすメソッド
    protected function db_error($ex)
    {
        Log::info("DBエラー::" . $ex->getMessage());
        Config::load("const", true);
        $title = "[システム名]DBエラー";
        $body  = "▪️SQL" . "\n";
        $body .= $ex->getMessage() . "\n\n";
        $body .= "▪️コントローラ/アクション\n";
        $body .= Request::main()->controller . "/" . Request::active()->action . "\n\n";
        $body .= "▪️詳細" . "\n";
        $body .= print_r($ex, true);
        $email = Email::forge();
        $email->from(Config::get("const.email.system_from"), Config::get("const.email.system_from"));
        $email->to(Config::get("const.email.alert_to"));
        $email->subject($title);
        $email->body($body);
        $email->send();
        $this->error();
    }

各コントローラのフォーマット

基本的にDBエラーとシステムエラー(致命的なエラー)は確実に検出できるようにかならずtry〜catchでくくるようにしました

class Controller_Hoge extends Controller_Base
{
    public function action_sample()
    {
        try
        {
            // 必須のパラメータチェックなど
            if (!Input::post("parameter"))
            {
                throw new ValidateException;
             }
    }
    catch(ValidateException $ex)
    {
        return $this->error(trim($ex->getMessage()));
    }
    // DBエラー
    catch(Database_Exception $ex)
    {
        $this->db_error($ex);
    }
    // システムエラー
    catch(System_Exception $ex)
    {
        $this->system_error($ex);
    }
}

こんなものでしょうか。
後日、追加するかもしれませんが

以上です