2021/07/11

ブラウザゲームで使われるマスターデータの解説 #2(PHPでCSVを扱う方法)

WRITER: t.tanaka

 

はじめに


この記事では、#1(https://developers.10antz.co.jp/archives/1897) で作成したマスターデータをコード側でどう扱うのかをPHPを使用して解説します。

  1. マスターデータの格納先について
  2. PHPを使ってCSVからレコードを取得する方法
  3. クラスオブジェクトにマッピングする方法

 

マスターデータの格納先について


マスターデータの格納先には、以下のような持ち方があります。

RDBMS NoSQL ローカルファイル
形式 MySQL、PostgreSQLなど MongoDB、Redis、memcachedなど CSV、TSV、Excelなど
特徴 ・フレームワーク/ライブラリ等のサポートが手厚い

・SQLで自由度の高いクエリを実行できる

・性能を上げる場合、一手間かける必要がある

・高速なデータ取得が可能

・アプリケーション側で解決しなくてはならない問題が多い

・性能を上げやすい

・実装が単純

・エンジニアでない人でも見ることができる

・性能を上げやすい

 

ブラウザゲームの場合クライアントからもサーバー内にあるマスターデータを見れる必要があるので、マスターデータをサーバー側で管理します。

この記事では、ローカルファイルに格納した場合の具体例として、PHPでCSVをマスタデータとして使用するケースを説明します。

 

PHPを使ってCSVからレコードを取得する方法


PHPからCSVを取得する方法は、多くあると思いますがこの記事では「SplFileObject クラス」を利用します。

https://www.php.net/manual/ja/class.splfileobject.php

SplFileObject クラスは、ファイルをオブジェクト指向で扱う為のクラスで、SPL拡張モジュールのファイル操作クラスです。

※ SPL(Standard PHP Library)は、標準的な処理の為のインターフェイスやクラスを集めた拡張モジュールです。PHP 5.0.0以降はデフォルトで組み込まれています。

先にコードを貼っておきます。

 

1. CSV取得を管理するクラスを作成して、SplFileObject クラスを継承させます。

2. SplFileObject クラスはコンストラクタに以下の引数を渡すことで、該当するCSVファイルのレコードをオブジェクトにしてくれます。

filename(読み込むファイル。)
open_mode(ファイルをオープンするときのモード。r,w など)
use_include_path(filename を探すのに include_path を探索するかどうか。)
context(stream_context_create() で作られる有効なコンテキストリソース。)

詳しくはこちらを参考にしてください。https://www.php.net/manual/ja/splfileobject.construct.php

3. 次に、必要に応じて以下のフラグをセットしてください。

SplFileObject::DROP_NEW_LINE (行末の改行を読み飛ばします。)
SplFileObject::READ_AHEAD (先読み/巻き戻しで読み出します。)
SplFileObject::SKIP_EMPTY (ファイルの空行を読み飛ばします。期待通りに動作させるには、READ_AHEAD フラグを有効にしないといけません。)
SplFileObject::READ_CSV (CSV 列として行を読み込みます。)

今回は、READ_CSVのみセットしました。

4. ヘッダー(カラム名)とレコードを紐付ける必要がある為、toArrayという自作のメソッドを実装し、オブジェクトを整形できるようにしています。

5. このクラスを呼ぶ度に、SplFileObjectのコンストラクタでCSVファイルを開いてしまい無駄な処理が発生する可能性があるので、一度作成したインスタンスは2度以上作成しないようにする処理を追加します。

create() という関数を作成、同じCSVファイルのインスタンスが存在するかどうかを確認し、存在しなければ新たに作成します。

外からこのクラスのインスタンスを作成する際は、create() 関数を使用することで同じインスタンスを使い回すことができるようになります。

 

こちらのクラスを実行するとこのような配列が出力されていると思います。

CSVのプライマリーキーが配列のキー、カラム名が連想配列のキーになっているので array[1][ ‘areaReleasedAt’ ] のようにするだけで、エリア1の開放時間を取得できます。

 

クラスオブジェクトにマッピングする方法


業務で扱っていく上で、array[1][ ‘areaReleasedAt’ ] ではなく area[1]->areaReleasedAt のようにクラスからプロパティにアクセスして値を取得したいケースが出てくると思います。

ここでは、「PHPを使ってCSVからレコードを取得する方法」で取得した配列をクラスオブジェクトにマッピングする方法を解説します。

先にコードを貼っておきます。

 

1. マッピング先のクラスでは各CSVファイル毎に共通の処理が発生するので、それらの処理を持つMasterクラスを作成します。

2. Masterクラスの継承先クラスを作成します。

以下の設定を行なってください。

対象CSVのファイル名(static 変数)
対象CSVの主キーカラム名(static 変数)
対象CSVファイルのカラム名と一致するプロパティ(public 変数)

3. 外部からCSVレコードを検索するトリガーが必要になるので、Masterクラスへ findAll(全件検索) と findById(主キー検索)メソッドを実装します。

このメソッドの中では、以下のことをします。

・CSVレコードの取得 → $lines = self::getCSV();

・取得したCSVレコードを継承先のクラスプロパティにマッピング → self::toObject($lines)

toObject()メソッドでは、CSVファイルのカラム名と継承先クラスのプロパティ名が一致していたら値をプロパティにセットする処理をしています。

継承先クラスへのマッピング方法は賛否両論あると思いますが、このやり方が一番簡単だと思います。

 

上記のコードを実行してみます。

このように、クラスオブジェクトへマッピングされていることが確認できたと思います。

クラスオブジェクトとして扱えるようになると、コードの可読性と柔軟性がかなり上がるので是非このように実装してみてください。

 

この記事で扱った実装環境は以下になります。

PHP 7.3

フォルダ構成

├── README.md
├── app
│   ├── models
│   │   ├── CsvManager.php  CSV管理用クラス
│   │   ├── Master.php  マスターデータ管理用クラス
│   │   └── masters  マッピングするクラスオブジェクト用フォルダ
│   └── resources
│     └── csv  マスターデータ用フォルダ
│     ├── dungeon
│     │   ├── area.csv
│     │   └── floor.csv
├── composer.json
├── index.php
└── vendor

composer.json

実装したものは、githubにあげています。

https://github.com/takahiko-tanaka-10antz/master-article

 

まとめ


CSVからレコードを取得して、クラスオブジェクトにマッピングするまでの流れを解説している記事があまりなかったので書いてみました。

今回標準のパッケージのみで実装しましたが、この記事での内容を簡単に行えるようなライブラリが恐らくあると思うので、そちらも一緒に調べると良いかもしれません。

t.tanaka

t.tanaka

サーバーエンジニア
20年新卒