Friction River Software

  • お問い合わせ

CakePHP5入門【WebAPI編⑥】API実装とデプロイ

B美

今回はいよいよ『WebAPI』を実装していきましょう

まずは仕様だけど、どうするの?

A子

日付データを指定すれば、その日の当選番号を返すようにしよう
(「実施回」を指定しても意味無いし…)

あ、「ナンバーズ3」と「ナンバーズ4」は別々にしたほうが良いかな?

C菜

一緒でも問題ないとは思いますけど、そのデータを活用する側からしたら別々のほうが良いかもです~

A子

んじゃ、別々にしよう

URLnumapp/api/get3_ymd/2026/2/27
戻り値6929(実施回)
"2026年2月27日"(年月日)
"金曜日"(曜日1)
"金"(曜日2)
"大安"(六曜)
"682"(当選番号)

上が「ナンバーズ3」で、下が「ナンバーズ4」ね

URLnumapp/api/get4_ymd/2026/2/27
戻り値6929(実施回)
"2026年2月27日"(年月日)
"金曜日"(曜日1)
"金"(曜日2)
"大安"(六曜)
"2586"(当選番号)

ちなみに、ダブルクォートで囲んでいるのは「文字列」って意味だから…

C菜

こうして見てみると一緒でも良いような気がしますね~

違いは「当選番号」だけですし~

A子

「ナンバーズ3」と「ナンバーズ4」を一緒にするなら、こうだね

URLnumapp/api/get_ymd/2026/2/27
戻り値6929(実施回)
"2026年2月27日"(年月日)
"金曜日"(曜日1)
"金"(曜日2)
"大安"(六曜)
"682"(ナンバーズ3の当選番号)
"2586"(ナンバーズ4の当選番号)

B美

そのURLだと、「get_ymd」メソッドには「年」「月」「日」という三つの引数を渡すってことね

C菜

引数を一つにしたほうが良いでしょうか~?

この例だと、「2026-02-27」という一つの引数を渡すということです~

B美

例えば、Formヘルパーの「$this->Form->date()」を使ってカレンダーを表示して、その値(「2026-02-27」という形になる)を渡すのであればそっちのほうが良いかもね

A子

おぉ、だったらこうしよう

URLnumapp/api/get_ymd1/2026/2/27
戻り値6929(実施回)
"2026年2月27日"(年月日)
"金曜日"(曜日1)
"金"(曜日2)
"大安"(六曜)
"682"(ナンバーズ3の当選番号)
"2586"(ナンバーズ4の当選番号)

URLnumapp/api/get_ymd2/2026-02-27
戻り値6929(実施回)
"2026年2月27日"(年月日)
"金曜日"(曜日1)
"金"(曜日2)
"大安"(六曜)
"682"(ナンバーズ3の当選番号)
"2586"(ナンバーズ4の当選番号)

これでどう?

C菜

処理内容は同じですけど、引数の個数が異なる二つのメソッドを用意する…ってことですね~

良いと思います~

A子

先々APIの数については増やしていくとしても、とりあえずはこの二つを実装して、実際にHTMLページから使ってみよう
まずはコントローラーをbakeするよ

cd html/numapp[Enter]
bin/cake bake controller api[Enter]

これで「src/Controller/ApiController.php」ができたね
あ、メソッドは全て削除しといたよ(「index」メソッドも含めて)


C菜

それでは、「get_ymd1」メソッドと「get_ymd2」メソッドの外枠を書きますね~

A子

あ、Numbersクラスのフェッチは?

C菜

そうでした~

AdminController.php」からコピーしますね~

A子

んじゃ、検索の実装だけど…

$numbers = $this->Numbers->find()->where([
    'lottery_date_year' => $year,
    'lottery_date_month' => $month,
    'lottery_date_day' => $day
])->first();

$numbers = $this->Numbers->find()->where([
    'lottery_date_dt' => $ymd
])->first();

この二つで良いかな?
上が「get_ymd1」メソッド用で、下が「get_ymd2」メソッド用ね

B美

ちょっと口を挟むけど、メソッド名の付け方に問題があるわよ

忘れたの?

C菜

あっ、アンダースコアですよ~
privateメソッドは「スネークケース」でもOKですけど、publicメソッドは「キャメルケース」にしなきゃです~

あと、「'lottery_date_dt' => $ymd」という検索条件って、大丈夫なんですか~?
(「'lottery_date_dt' => new DateTime($ymd.' 00:00:00')」にしなくても良いのでしょうか~?)

B美

C菜の指摘はもっともだから、別にそうやっても(「DateTime」クラスを使っても)構わないわよ

ただ、A子の書き方でも問題なく検索はできるわね

A子

ふむ、だったら…

public function getYmd1($year, $month, $day)
{
    //numbersテーブルを検索
    $numbers = $this->Numbers->find()->where([
        'lottery_date_year' => $year,
        'lottery_date_month' => $month,
        'lottery_date_day' => $day
    ])->first();

    if (empty($numbers)) {
        //NG

    } else {
        //OK

    }
}

public function getYmd2($ymd)
{
    //numbersテーブルを検索
    $numbers = $this->Numbers->find()->where([
        'lottery_date_dt' => $ymd
    ])->first();

    if (empty($numbers)) {
        //NG

    } else {
        //OK

    }
}

これで良いんじゃない?

B美

それじゃ、結果を格納する「連想配列」を作ってみなさい
(JSONとか意識せず、普通にPHPの連想配列にすれば良いから…)

C菜

えっと~

if (empty($numbers)) {
    //NG
    $status = [
        'code' => 404,
        'message' => 'Not Found'
    ];
    $result = null;
} else {
    //OK
    $status = [
        'code' => 200,
        'message' => 'Success'
    ];
    $result = [
        'lottery_time' => $numbers['lottery_time'],
        'lottery_date_str' => $numbers['lottery_date_str'],
        'lottery_week_str1' => $numbers['lottery_week_str1'],
        'lottery_week_str2' => $numbers['lottery_week_str2'],
        'lottery_rokuyo_str' => $numbers['lottery_rokuyo_str'],
        'num3_str' => $numbers['num3_str'],
        'num4_str' => $numbers['num4_str']
    ];
}

$json = [
    'status' => $status,
    'result' => $result
];

これでどうでしょうか~?

B美

さすがはC菜ね
(まったく問題ないわ)

A子

この変数「$json」を普通に戻り値として返せば良いの?

return $json;

…って感じ

B美

んなわけあるかい!
手順はこうよ

まずビューへの移動を無効にします

$this->autoRender = false;

次にサーバからクライアントへ返すデータのコンテントタイプを設定します

header("Content-Type: application/json");

最後に、エンコードされたJSONデータを返すの
(「retern」ではなく「echo」で出力)

echo json_encode($json);

ほかのやり方もあるんだけど、私はいつもこうしてるわ
(正解は一つだけじゃない…ってこと)

A子

なるほどねぇ
まとめると、こうかな?

public function getYmd1($year, $month, $day)
{
    //numbersテーブルを検索
    $numbers = $this->Numbers->find()->where([
        'lottery_date_year' => $year,
        'lottery_date_month' => $month,
        'lottery_date_day' => $day
    ])->first();

    if (empty($numbers)) {
        //NG
        $status = [
            'code' => 404,
            'message' => 'Not Found'
        ];
        $result = null;
    } else {
        //OK
        $status = [
            'code' => 200,
            'message' => 'Success'
        ];
        $result = [
            'lottery_time' => $numbers['lottery_time'],
            'lottery_date_str' => $numbers['lottery_date_str'],
            'lottery_week_str1' => $numbers['lottery_week_str1'],
            'lottery_week_str2' => $numbers['lottery_week_str2'],
            'lottery_rokuyo_str' => $numbers['lottery_rokuyo_str'],
            'num3_str' => $numbers['num3_str'],
            'num4_str' => $numbers['num4_str']
        ];
    }

    //JSON化する前の連想配列
    $json = [
        'status' => $status,
        'result' => $result
    ];

    //JSONエンコードした結果を返す
    $this->autoRender = false;
    header("Content-Type: application/json");
    echo json_encode($json);
}

C菜

「getYmd2」メソッドのほうは、データベース検索の箇所だけが異なるわけですね~
…ということはprivateな共通メソッドを用意して、「getYmd1」と「getYmd2」の両方から呼び出すようにすれば良いのでは~?

//年月日の3つの引数からナンバーズの当選番号をJSONで返却
public function getYmd1($year, $month, $day)
{
    //numbersテーブルを検索
    $numbers = $this->Numbers->find()->where([
        'lottery_date_year' => $year,
        'lottery_date_month' => $month,
        'lottery_date_day' => $day
    ])->first();

    //JSON化する前の連想配列を作成
    $json = $this->make_json($numbers);

    //JSONエンコードした結果を返す
    $this->autoRender = false;
    header("Content-Type: application/json");
    echo json_encode($json);
}

//'Y-m-d'形式の引数からナンバーズの当選番号をJSONで返却
public function getYmd2($ymd)
{
    //numbersテーブルを検索
    $numbers = $this->Numbers->find()->where([
        'lottery_date_dt' => $ymd
    ])->first();

    //JSON化する前の連想配列を作成
    $json = $this->make_json($numbers);

    //JSONエンコードした結果を返す
    $this->autoRender = false;
    header("Content-Type: application/json");
    echo json_encode($json);
}

//データベースの検索結果から連想配列を作成
private function make_json($numbers)
{
    if (empty($numbers)) {
        //NG
        $status = [
            'code' => 404,
            'message' => 'Not Found'
        ];
        $result = null;
    } else {
        //OK
        $status = [
            'code' => 200,
            'message' => 'Success'
        ];
        $result = [
            'lottery_time' => $numbers['lottery_time'],
            'lottery_date_str' => $numbers['lottery_date_str'],
            'lottery_week_str1' => $numbers['lottery_week_str1'],
            'lottery_week_str2' => $numbers['lottery_week_str2'],
            'lottery_rokuyo_str' => $numbers['lottery_rokuyo_str'],
            'num3_str' => $numbers['num3_str'],
            'num4_str' => $numbers['num4_str']
        ];
    }

    //JSON化する前の連想配列
    $json = [
        'status' => $status,
        'result' => $result
    ];

    return $json;
}



B美

連想配列を作る個所を一つにまとめるとは、なかなかやるわね
(上記の「make_json」メソッドのこと)

それじゃ、テストしてみなさい
ブラウザのURLに直接入力して良いから…

A子

んじゃ、下記のURLを入れてみるよ

http://192.168.1.205/numapp/api/get_ymd1/2026/2/27

あれ?
2026\u5e742\u670827\u65e5」って何なのよ

まさか文字化け?

B美

文字化けじゃないわよ
「\u5e74」は「年」、「\u6708」は「月」、「\u65e5」は「日」のことなの

これは『Unicodeエスケープ』と呼ばれるもので、日本語をJSON出力する際の標準的な挙動よ

C菜

…ということは、JavaScriptで取り扱うときには問題なく日本語として表示できるってことですよね~?

B美

そういうこと

あ、存在しない年月日でも試してみてね

A子

了解

http://192.168.1.205/numapp/api/get_ymd1/2026/2/28

この日は土曜日だから抽選は無いんだよね

C菜

きちんと「404」というステータスコードが返ってきましたね~

あと

http://192.168.1.205/numapp/api/get_ymd2/2026-02-27



http://192.168.1.205/numapp/api/get_ymd2/2026-02-28

の二つについてもテストしてみました~

どちらも問題なかったです~
(結果については、上記と同じ)

B美

それでは、この『WebAPI』を普通のHTMLページから呼び出してみるよ

まずは「html」ディレクトリの中にサンプルのHTMLファイルを作りましょう

C菜

あ、待ってください~
その前に本番環境への設置デプロイをやったほうが良いのではないでしょうか~?

そうすればこのページの読者様が実際の挙動を体験できると思いますよ~

A子

まーた、メタ発言だよ

読者って誰だよ(苦笑)

B美

それもそうね

それじゃ、あなたたちで設置デプロイしてみなさい
もう慣れたものでしょ?

C菜

了解です~

開発環境側ではこうですね~

1.キャッシュクリアを実行する
2.「config/const.php」の定数「ENVIRONMENT」を「2」に変更する
3.SCPを使って、本番環境へのアップロードを実行する

それから、本番環境側では下記の通りです~

4.SSH接続してから「numapp/logs」の中にあるファイルを全て削除
5.データベースを作成する(定義ファイルを読み込む)
6.「config/app_local.php」の中にあるデータベースパスワードを書き換えると共に、同ファイルのデバッグモードを「false」にする
7.ブラウザでアクセスし、管理ページ上でExcelファイルをインポートする

ちなみに、4番についてはアップロード時にファイルのオーナーが変わっちゃうからです~

A子

うん、そんなもんだね
んじゃ、まずは1番から…

1.キャッシュクリアを実行する

MATE端末」から下記を実行するよ

cd html/numapp[Enter]
bin/cake cache clear_all[Enter]

C菜

次は2番です~

2.「config/const.php」の定数「ENVIRONMENT」を「2」に変更する

ついでに、バージョンについても「0.9.0」にしましたよ~
(未完成なのに「1.0.0」では変なので~)

A子

んじゃ、3番ね

3.SCPを使って、本番環境へのアップロードを実行する

もちろん、データベース定義ファイルの「numbersdb.sql」も一緒にアップするよ

C菜

それでは本番環境側での作業です~

4.SSH接続してから「numapp/logs」の中にあるファイルを全て削除

まずはSSH接続しますね~

ssh -i .ssh/sakura.pem -p nnnn xxxx@friction-river.jp[Enter]

(「nnnn(ポート番号)」と「xxxx(ユーザ名)」の部分は伏字ですよ~)

C菜

そしたら次は、rootユーザになってログファイルを削除します~

su[Enter]
cd html/numapp/logs[Enter]
rm -f *[Enter]
exit[Enter]

あ、よく考えたらrootにならなくても良かったですね~
(ファイルのオーナーが自分自身なので…)

A子

よし、続けて(SSH状態のまま)データベースを作成するよ

5.データベースを作成する(定義ファイルを読み込む)

さっきアップロードした「numbersdb.sql」を読み込ませよう

mysql -u root -p < numbersdb.sql[Enter]

C菜

それでは、次は6番ですね~

6.「config/app_local.php」の中にあるデータベースパスワードを書き換えると共に、同ファイルのデバッグモードを「false」にする

エディターには「vi」を使ってみましたけど、別に「nano」でも良いですよ~



・・・

A子

ようやく最後だね

7.ブラウザでアクセスし、管理ページ上でExcelファイルをインポートする

URLは下記の通り

https://friction-river.jp/numapp/

C菜

ばっちりです~

それでは「管理ページ」に移って、Excelファイルをインポートしましょう~
(あ、BASIC認証も完璧でした~)





A子

URLに『WebAPI』の呼出しを打ち込んで、JSONデータが得られることも確認できたよ

https://friction-river.jp/numapp/api/get_ymd1/2026/2/27
https://friction-river.jp/numapp/api/get_ymd2/2026-02-27

B美

あ、そうそう

Excelファイルなんだけど、私が毎日追記を行っているファイルは『ナンバーズ3&4.xlsx』だから、それもインポートしてみてね
(最新のデータが入っているから…)

A子

OK、OK
やってみよう

・・・

よし、12件のデータが追加されたよ

C菜

JSONについてもこんな感じで、問題ありませんでした~

現時点での最新データは、2026年3月17日ということですね~

B美

ちょっと長くなっちゃったんで、『HTMLファイルの中のJavaScriptプログラムからWebAPIをコールする仕組み』については次回にしましょう

A子

わかったよ

それにしても、ここまではそんなに難しく感じなかったね

C菜

実力がついてきた…ってことじゃないですか~?

B美

最初のほうで私が言った通り「プログラミングなんて慣れ」なのよ

特に最近は「生成AI」も活用できるしね(苦笑)