10yroの開発日記

福岡にある株式会社10yro(トイロ)のエンジニアが書いています

PHP LaravelでRESTful API開発 その2

前回の続きです。
前回の記事は以下です。

dev.10yro.co.jp

前回はControllerにPOSTメソッドの作成まででしたので、今回はその他のメソッドを実装したいと思います。

目次

Bookテーブルのレコードを取得する(GETメソッドの実装)

Bookテーブルから全件取得したいと思います。
Bookテーブルには以下の2レコードが登録されています。

ControllerのgetAllメソッドを以下のように修正します。

<?php
    /**
     * すべてのbookを取得
     *
     * @return object $result
     */
    public function getAll()
    {
        $books = Book::get()->toJson(JSON_PRETTY_PRINT);
        return response($books, 200);
    }

postmanで呼び出すと、以下のように取得結果がJson形式で返ってきます。

Bookテーブルのレコードをキー指定で1件取得する(GETメソッドの実装)

ControllerのgetByIdメソッドを以下のように修正します。

<?php
    /**
     * IDを指定して1件取得
     *
     * @param integer $id
     * @return Bool $result
     */
    public function getById(int $id)
    {
        $books = Book::find($id)->toJson(JSON_PRETTY_PRINT);
        return response($books, 200);
    }
?>

postmanで呼び出すと、以下のように取得結果がJson形式で返ってきます。

Bookテーブルのレコードを更新する(PUTメソッドの実装)

ControllerのupdateByIdメソッドを以下のように修正します。

<?php
    /**
     * Bookを更新
     *
     * @param Request $request
     * @param integer $id
     * @return void
     */
    public function updateById(Request $request, int $id)
    {
        if (Book::where('id', $id)->exists())
        {
            $book = Book::find($id);
            if (!empty($request->title)) {
                $book->title = $request->title;
            }
            if (!empty($request->author)) {
                $book->author = $request->author;
            }
            if (isset($request->memo)) {
                $book->memo = $request->memo;
            }
            $book->save();

            return response()->json([
                'message' => 'updated book'
            ], 201);
        } else {
            return response()->json([
                'message' => 'book not found'
            ], 404);
        }
    }

postmanで呼び出すと、以下のように正常終了したメッセージが返ってきました。

テーブルを見るとid = 1のレコードが更新されています。

Bookテーブルのレコードを削除する(DELETEメソッドの実装)

ControllerのdeleteByIdメソッドを以下のように修正します。

<?php
    /**
     * Bookを削除
     *
     * @param integer $id
     * @return void
     */
    public function deleteById(int $id)
    {
        if (Book::where('id', $id)->exists())
        {
            $book = Book::find($id);
            $book->delete();

            return response()->json([
                'message' => 'updated book'
            ], 202);
        } else {
            return response()->json([
                'message' => 'book not found'
            ], 404);
        }
    }

postmanで呼び出すと、以下のように正常終了したメッセージが返ってきました。

テーブルを見るとid = 2のレコードが削除されています。

まとめ

Laravelを使って基本的なAPIの機能を実装してみました。
データベースへのアクセス等も簡単に実装できるので、とても良かったです。
今までPHPは深くやったことなかったのですが、今後はちゃんと使っていきたいと思いました。

今回のソースコード一式は以下に置いています。
github.com

PHP LaravelでRESTful API開発 その1

今回はPHPフレームワークLaravelを利用して、RESTful APIのサンプルを作成したいと思います。
まずは1回目として、Laravelプロジェクトの作成、利用するDBの作成、Modelの作成、Controllerでpostメソッドの実装までをやっていきます。

目次

MySQLにデータベースを作成

まず、APIで利用するデータベースを作成します。
今回はMySQLを利用します。

MySQL Workbenchを利用してデータベースを作成します。

SCHEMASを右クリック→Craeta Schema...をクリック

Nameにapi_sampleと入力して、Applyをクリック

以下の画面でApplyをクリック

以下の画面でFinishをクリック

SCHEMASにapi_sampleが作成されます

Laravelのプロジェクトを作成

プロジェクトを作成するフォルダまで移動して以下のコマンドを実行します。

laravel new api-sample

以下のコマンドで起動してみます。

php artisan serve

以下のように表示されればOKです。

ブラウザで http://127.0.0.1:8000/ にアクセスすると以下のような画面が表示されます。

ModelとMigrationを作成

先程作成したDBにテーブルを作るため、ModelとMigrationファイルを作成します。
今回は本のタイトルや著者などを管理するテーブル「book」を作成します。

以下のコマンドを実行します。

 php artisan make:model Book -m

以下のようにapp/ModelsにBook.php、database/migrationsに2022_11_24_053031_create_books_table.phpというファイルが作成されました。

作成されたmigrationのファイルを見てみます。
upメソッドでテーブルを生成する処理(create table)が実行されます。
デフォルトではidとtimestampsが定義されていますので、必要に応じてこのメソッド内にテーブルのカラムの定義を記述します。
timestampsはcreated_at, updated_atの2つのカラムが生成されます。

<?php
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('books', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
        });
    }

downメソッドでは、テーブルを削除する処理(drop table)が実行されます。
upメソッドで行った変更を元に戻す処理を記述することとなります。

<?php
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('books');
    }
};

upメソッドを以下のように修正します。
title, author, memoという文字列のカラムを追加しました。
memoについてはnull許可としています。

<?php
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('books', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->string('author');
            $table->string('memo')->nullable();
            $table->timestamps();
        });
    }

次はModelを以下のように修正します。
protected $fillableの記述を追加しました。

<?php
class Book extends Model
{
    use HasFactory;

    protected $fillable = [
        'title',
        'author',
        'memo'
    ];
}

.envにDB接続先情報を設定

プロジェクトのルートにある.envファイルのDB接続先情報を修正します。

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=api_sample
DB_USERNAME=<your user name>
DB_PASSWORD=<your password>

Migrationの実行

コマンドプロンプトで以下のコマンドを実行し、DBにBookテーブルを作成します。

php artisan migrate

以下のように実行結果が表示されます。
(book以外はlaravelのデフォルトで含まれるmigrationファイルですので、不要であれば実行前に削除してください)

MySQL Workbench等のツールでDBを確認するとbookテーブルが生成されていると思います。

Controllerの作成とRouteの設定

次はControllerを作成します。
以下のコマンドを実行してBookControllerを作成します。

php artisan make:controller BookController

app/Http/Controllersの下にBookController.phpが作成されます。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class BookController extends Controller
{
    //
}

取得、追加、更新、削除のメソッドを追加します。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class BookController extends Controller
{
    /**
     * すべてのbookを取得
     *
     * @return object $result
     */
    public function getAll()
    {
        return "Called getAll";
    }

    /**
     * IDを指定して1件取得
     *
     * @param integer $id
     * @return Bool $result
     */
    public function getById(int $id)
    {
        return "Called getById";
    }

    /**
     * Bookを追加
     *
     * @param Request $request
     * @return void
     */
    public function create(Request $request)
    {
        return "Called create";
    }

    /**
     * Bookを更新
     *
     * @param Request $request
     * @param integer $id
     * @return void
     */
    public function updateById(Request $request, int $id)
    {
        return "Called updateById";
    }

    /**
     * Bookを削除
     *
     * @param integer $id
     * @return void
     */
    public function deleteById(int $id)
    {
        return "Called deleteById";
    }
}

routes/api.phpに以下を追加します。

<?php

Route::get('book', [BookController::class, 'getAll']);
Route::get('book/{id}', [BookController::class, 'getById']);
Route::post('book', [BookController::class, 'create']);
Route::put('book/{id}', [BookController::class, 'updateById']);
Route::delete('book/{id}', [BookController::class, 'deleteById']);
Route::get('book', [BookController::class, 'getAll']);

例えば上記記述をすることで、http://127.0.0.1:8000/api/bookのgetメソッドにアクセスすると、BookControllerのgetAllメソッドが呼び出されることになります。

ここまで終わったら、一度確認のため動かしてみます。
以下のコマンドを実行してください。

php artisan serve

Laravelが起動したら、ブラウザで http://127.0.0.1:8000/api/book アクセスしてみます。
以下の文字が表示されればOKです。

Bookテーブルにレコードを作成(postメソッドの実装)

postメソッドが呼ばれたときに、bookテーブルにレコードを作成します。
コントローラーのcreateメソッドを以下のように修正します。

<?php
    /**
     * Bookを追加
     *
     * @param Request $request
     * @return void
     */
    public function create(Request $request)
    {
        $book = new Book();
        $book->title = $request->title;
        $book->author = $request->author;
        $book->memo = $request->memo;
        $book->save();

        return response()->json([
            'message' => 'created book'
        ], 201);
    }

Postmanというツールでpostメソッドを呼び出します。 以下はPostmanの画面です。

以下のように呼び出した結果がBodyに表示されたらOKです。

Bookテーブルの中身を確認すると、ちゃんとレコードが作成されています。

まとめ

LaravelでAPIの実装どうするんだろ?と思ってましたが、他のサイトを参考にしながらですが、ここまでは順調にできました。
Laravel便利すぎです。 次回はその他メソッドの実装を行っていきたいと思います。

C# PCSC Sharpを使ってICカードのIDを読み取る

今回はC#ICカードに入っているIDを取得する方法についてです。
ICカードリーダーとの連携は1から実装するのは気が遠くなりそうなので、今回はPCSC Sharpという以下のライブラリを利用しました。
ICカードはマイフェア(Myfair)、フェリカ(Felica)を想定しています。

github.com

PCSC Sharpのインストール

まずWindows Formsの新規プロジェクトを作成します。
その後にNugetでPCSC、PCSC.Iso7816をインストールしましょう。

プロジェクトを右クリック→「Nugetパッケージの管理」をクリックします。
参照で「pcsc」と入力して、「PCSC」「PCSC.Iso7816」をインストールします。

インストールされました。

画面

今回は以下のような簡単な画面を作成しました。
「モニター開始」をクリックすると、カードリーダーを監視し、ICカードを認識したらボタンの下にある欄(リストボックス)に取得したIDを表示します。

初期処理

初期処理として、画面ロード時にICカードリーダーの存在チェックを行います。 11行目でPCに接続されているICカードリーダーを取得して、その中から対象となるICカードリーダーが存在するかチェックしています。

モニター開始

モニター開始ボタンをクリックすると、ICカードリーダーにICカードがかざされるのをモニターします。
ICカードを検知する(ICカードリーダーの状態が変わる)と、27行目のStatusChangedというイベントが発火されますので、そのイベントにMonitor_StatusChangedというメソッドを登録しています。
このメソッド内でIDを取得してリストボックスにIDを表示します。

実際にIDを取得するメソッドは以下となります。

実行してみる

以下の動画のようにモニター開始後にリーダーにカードをかざすと、IDを読み取ってくれます。

PCSCSharpを使うと簡単に実装できました。
ソースコードは以下に置いています。

github.com

弊社では一緒に働いてくれる仲間を募集しています。
ご興味ある方はご連絡ください! 10yro.co.jp

【Angular】システム変更通知

SPA アプリケーションにおいてシステムアップデート(デプロイ)した際に

キャッシュのファイルを参照しており更新されないケースを想定し、

ユーザにキャッシュしているファイルを更新してほしい旨通知する方法を記載します。


今回やることはrevision管理用ファイル(何らかの設定ファイル)にrevisionを記載しておき

システム起動時にその時のrevisionを保持しておきます。

そして、任意のタイミングでそのファイルのrevisionを確認し、変更されていたらユーザにその旨通知するという内容となります。


手順としては以下となります。



1. revision 保持用のrevisionファイルを用意 (json 等)

以下のような設定ファイルを追加します。

assets/application.config.json

{
    "revision": "b2a91a752e068ccf4a3b0f4759d66ec096c14821"
}

2. revisionファイルからrevisionを取得

revision 管理用の Serviceを作成し、取得用の関数を追加する。

revision.service.ts

revision : string | null = null;  // ※今のrevisionを保持する変数やLocal strage 等を用意

get(): Observable<string | null> {
    let hash = new Date().getTime();
    return this.http.get('assets/application.config.json?' + hash).pipe(
        map((conf: any) => {
            return conf['revision'] ?? null;
        })
    );
}

set(): void {
    this.get().subscribe((rev) => {
        this.revision = rev;
    });
}

また、このServiceはベースのコンポーネントでinjectし、setしておきます。

これにより、システム起動時、リロード時などベースコンポーネントが更新されるタイミングで

最新のrevisionがセットされるため画面のファイルキャッシュ状態と合わせておきます。

app.component.ts

export class AppComponent {
    constructor(
        private revision: RevisionService,
        ...
    ) {
        revision.set();
    }
}

3. 任意のタイミング(画面遷移時 等)で再度revisionファイルを確認する。

今回は画面遷移時にチェックしたいので

Routing moduleにチェック処理を追加します。

layout-routing.module.ts

constructor(
    private revision: RevisionService,
    private router: Router
) {
    this.router.events
        .pipe(filter((e: Event): e is RouterEvent => e instanceof RouterEvent))
        .subscribe((e: RouterEvent) => {
            if (e instanceof NavigationEnd) {
                // チェック処理を追加 ※↓のisRevised() は (4)にて追加する。
                this.revision.isRevised();
            }
        });
}

4. 変更があればAlert等ユーザに通知

先ほどのrevision serviceにチェック処理を追加する。

revision.service.ts

this.get().subscribe((rev) => {
    if (this.revision != rev) {
        this.notifyRevised();
    }
});

notifyRevised(): void {
    // Alert を出すなどユーザに通知
    // 例)「システムがアップデートされました。画面をリロードしてください。」メッセージ出力
}

便利なMicrosoft公式ツール

Windowsの作業効率をアップさせるMicrosoft公式ツール「Powertoys」を解説します。
今回は個人的によく使用するものを一部紹介しますが、他にもいろいろな機能があるので、試してみるととても便利です。
また、今回は解説しませんが、「DevToys」というエンジニア向けのツールもかなり重宝します。

1. Color Picker

画面レイアウトとかを作成する際に、簡単に色をクリップボードにコピーできます。
ショートカットキーの Windowsキー + Shift + C をクリック。

ColorPicker-1

対象の個所をクリックすると、いかが画面が表示されるので右側のアイコンをクリックしてコピー。

ColorPicker-2

2. マウスユーティリティ

マウスカーソルの変更できます。 リモート会議とか、画面を共有して説明するときなどに使用できます。 ショートカットキーのCtrlキーを2回クリック。

マウスの検索

ショートカットキーの Windowsキー + Shift + H をクリック。

マウスの蛍光ペン

3. PowerToys Run

いろいろな機能を起動するクリックランチャー。 ショートカットキーの Altキー + Space をクリック。 ツールの起動のほかにも、電卓としても使用できます。

PowerToysRun-1

PowerToysRun-2

docs.microsoft.com

devtoys.app

【Windows】フォルダ構成、ファイル名を一覧化して出力する(treeコマンド)

新人の頃、納品物の大量のソースファイルをお客さんへ送る際に、
全ファイル名を一覧化したドキュメントを作成する作業を振られたことがありました。
全部書き出すの??と途方に暮れそうになっていた時にtreeコマンドの存在を知り、
当時の私はちょっと感動した記憶があります。

今でも時々使う機会があるので、改めて書いておきます。

今回は、コマンドプロンプトを使用します。

 

①まずは中身を一覧化したいフォルダをエクスプローラーで開きます。

f:id:tomi510dev:20220218183830p:plain

②アドレスバーに「cmd」を入力してエンターキーを押します。

f:id:tomi510dev:20220218185520p:plain

 

すると、コマンドプロンプトが起動します。

f:id:tomi510dev:20220218184511p:plain

③以下のコマンドを入力してエンターキーを押下します。

tree /f

すると、こんな感じでツリー上に書き出されます。

f:id:tomi510dev:20220218185915p:plain

 

ツリーの結果をテキストファイルに書き出したい場合は、以下のコマンドを入力します。

tree /f >ファイル名.txt

f:id:tomi510dev:20220218190358p:plain

すると、指定したファイル名のテキストファイルが生成されます。

f:id:tomi510dev:20220218190633p:plain

f:id:tomi510dev:20220218191131p:plain

簡単ですね。

困ってる新人さんがいたら教えましょう~

【JavaScript/TypeScript】日付操作のいろいろ

JavaScriptで日付型(Date)を扱う際、ある日付の月初、月末を取る、決算期として期の開始を取得 等々

日付を加工して利用したいケースが多々あるかと思います。

今回、日付操作に関する方法を残しておきたいと思います。


目次:



1. 年を取得 (number)

getYear(date: Date): number {
    return date.getFullYear();
}

例)2022/02/07 13:00:00 (Date) ⇒ 2022 (number)

2. 月を取得 (number)

getMonth(date: Date): number {
    return date.getMonth() + 1;
}

※ getMonth() は 0 start (1月=0, 2月=1 ...)のため +1 するのをお忘れなく
例)2022/02/07 13:00:00 (Date) ⇒ 2 (number)

3. 日を取得 (number)

getDay(date: Date): number {
    return date.getDate();
}

例)2022/02/07 13:00:00 (Date) ⇒ 7 (number)

4. 月初を取得 (Date)

getFirstDateTimeOfMonth(date: Date): Date {
    return new Date(date.getFullYear(), date.getMonth(), 1, 0, 0, 0, 0);
}

例)2022/02/07 13:00:00 (Date) ⇒ 2022/02/01 00:00:00.000 (Date)
時刻も最初 00:00:00.000 を指定しています。
時間はdateのものとしたい場合は末尾の引数 「0, 0, 0, 0」 を↓に置き換えればOK
date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds()

5. 月末を取得 (Date)

getLastDateTimeOfMonth(date: Date): Date {
    return new Date(date.getFullYear(), date.getMonth() + 1, 0, 23, 59, 59, 999);
}

例)2022/02/07 13:00:00 (Date) ⇒ 2022/02/28 23:59:59:999 (Date)
時刻も最後 23:59:59.999 を指定しています。
ポイントは月を +1 して 日を 0 (-1) としているところです。

6. 決算年度を取得 (number)

getFiscicalYear(date: Date): number {
    const year = date.getFullYear();
    const month = date.getMonth() + 1;     
    let tmpQuarter = Math.floor((month - 1) / 3) + 1;
    const quarter = tmpQuarter - 1 === 0 ? 4 : tmpQuarter - 1;
    return quarter == 4 ? year - 1 : year;
}

例)2022/02/07 13:00:00 (Date) ⇒ 2021 (number)
4半期(3月決算)を前提とした値(計算)となっています。
4半期のため1期3か月なので Math.floor((month - 1) / 3) しています。
※それに+1しているのは1-3月が0になるため1始まりにしています。(特に+1せず期を算出する際の -1を消してもOKです。)
そして、3月決算で4月始まりの為1-3月は4とし、その他を繰り下げます。
後は4期は前年度にあたるため 年は -1 になります。 これは「7. 決算期を取得」の内容を絡め記載してますが、決算年度を得るだけであれば以下のように -3 ヶ月したものの年を求めることでもできます。

getFiscicalYear(date: Date): number {
        let date = new Date();
        date.setMonth(xxx.getMonth() - 3);
        return date.getFullYear();
}

7. 決算期を取得 (number)

getQuarter(date: Date): number {
    const month = date.getMonth() + 1;
    let quarter = Math.floor((month - 1) / 3) + 1;
    return quarter - 1 === 0 ? 4 : quarter - 1;
}

例)2022/02/07 13:00:00 (Date) ⇒ 4 (number)
4半期(3月決算)を前提とした値(計算)となっています。

8. 期の最初の日付を取得 (Date)

getFirstDateTimeOfQuarter(year: number, quarter: number): Date {
    const month = (quarter - 1) * 3 + 1;
    const date = new Date(year, month - 1, 1);
    // fiscal year end is March (+3 : Shift one fiscal year)
    date.setMonth(date.getMonth() + 3);
    return Date(date.getFullYear(), date.getMonth(), 1, 0, 0, 0, 0);
}

例)year = 2021 / quarter = 4 ⇒ 2022/01/01 00:00:00 (Date)
4半期(3月決算)を前提とした値(計算)となっています。
※ここでは12月決算(1月始まり)で計算し、後に+3か月しています。
時刻も最初 00:00:00.000 を指定しています。

9. 期の最後の日付を取得 (Date)

getLastDateTimeOfQuarter(year: number, quarter: number): Date {
    const month = quarter * 3;
    const date = new Date(year, month - 1, 1);
    // fiscal year end is March (+3 : Shift one fiscal year)
    date.setMonth(date.getMonth() + 3);
    return new Date(date.getFullYear(), date.getMonth() + 1, 0, 23, 59, 59, 999);
}

例)year = 2021 / quarter = 4 ⇒ 2022/03/31 23:59:59.999 (Date)
4半期(3月決算)を前提とした値(計算)となっています。
時刻も最後 23:59:59.999 を指定しています。

10. 00:00:00の日時を取得 (Date)

getFirstTimeOfDay(date: Date): Date {
    let firstDatetime = new Date(date);
    firstDatetime.setHours(0, 0, 0, 0);
    return firstDatetime;
}

例)2022/02/07 13:00:00 (Date) ⇒ 2022/02/07 00:00:00.000 (Date)
setHours は (時, 分, 秒, ミリ秒) なので、他の時刻にしたい場合ここを変更すればOKです。