DEVELOPERS BLOGデベロッパーズブログ
- HOME >
- 加藤 正人のデベロッパーズブログ >
- CsvLoaderBhavior - CSV データを CakePHP で扱うための一手法
加藤 正人のデベロッパーズブログ
- 氏名
- 加藤 正人
- 役職
- 多分SE
- 血液型
- 秘密
- 出没
- 美味しいもののあるところ
- 特色
- タヒチ大好き。ちょいメタボ。
- 2020/12/15
- CakePHP3 のリダイレクトがうまく行かない例
- 2020/11/12
- Apache Bench
- 2020/10/05
- PhpSpreadsheet でExcel ファイルを読み込む (CakePHP3)
しばしば CSV データをデータベース (テーブル) として処理したいことがある。(電力会社から公開される電力使用量データや総務省から公開される全国市区町村一覧等々。)
CsvSource データソースを利用する方法もあるが、こちらは find 時の絞り込み条件指定等は対応していないようで全件読み込み以外の処理には難があるようだし、他のテーブルとのアソシエーションにも難がありそうだし、ロードした後継続的にテーブルとして使用する場合は自分で CSV のフィールドを調べてテーブルを作成し、内容をコピーするようコードを書かなければならない。
それならば、モデルを最初にアクセスする際に CSV ファイルの構造からテーブルを作成しそこに読み込んでしまい、それ以降は通常のデータベーステーブルとしてアクセスする方法があればよいのではないだろうか。
この方法であれば Behavior として実現できそうだし、他のテーブルとのアソシエーションも容易なので試してみた。
コンセプト
使用に先立ち、CSV データを既存またはその時点で作成したテーブルにロードしてしまえば CRUD 部分は通常のデータベースとしてアクセスするだけなので、ビヘイビアの初期化部分 (setup() メソッド) で対応する。
書き込み先テーブルを生成する場合は、その構造 (スキーマ) をユーザが指定することもできるが、可能な限り自動生成できるようにする。
ビヘイビア名を CsvLoader とすると、以下のような感じになる。
01 | <?php |
02 | class CsvLoaderBehavior extends ModelBehavior |
03 | { |
04 | public function setup(Model $model , $settings = array ()) |
05 | { |
06 | // ビヘイビア宣言時のパラメータに基づき処理する |
07 | // $settings の 'csv_file' パラメータをオープンし、 |
08 | // テーブルを生成しデータをロードする |
09 | } |
10 | } |
11 | ?> |
使用する際には、Model 定義で以下のようにする。
01 | <?php |
02 | class MyCsvFile extends AppModel |
03 | { |
04 | public $actsAs = array ( |
05 | 'CsvLoader' => array ( |
06 | 'csv_file' => '/...' , // CSV ファイルへのパス |
07 | 'encoding' => 'Shift_JIS' , // CSV ファイルの文字エンコーディング |
08 | // ... |
09 | ), |
10 | ); |
11 | } |
12 | ?> |
実装
上記を実現するためのビヘイビアの setup() コールバックメソッドの構造は以下のようにできる。
- ビヘイビアの生成オプションと既定値オプションをマージする
- CSV ファイルをオープンする
- テーブルを構築するためのスキーマデータをオプションに従って生成する
- データベースにテーブルを作成する
- CSV ファイルからレコードを読み込みテーブルに書き込む
- EOF に達したらファイルをクローズする
これをコード化したものがこちら。(抜粋)
01 | <?php |
02 | class CsvLoaderBehavior extends ModelBehavior |
03 | { |
04 | // …【中略】… |
05 |
06 | public function setup(Model $model , $settings = array ()) |
07 | { |
08 | // ビヘイビア構成パラメータ処理 (既定値と指定値をマージ) |
09 | $this ->_init_setting( $model , $settings ); |
10 |
11 | // CSV ファイルまたはストリームをオープンしファイルポインタを取得 |
12 | $fp = $this ->_open_csv(@ $this ->_actual_settings[ 'csv_file' ]); |
13 |
14 | // 読み飛ばし行数が指定されている場合は読み飛ばす |
15 | for ( $i =0; $i < $this ->_actual_settings[ 'skiplines' ]; $i ++ ) |
16 | { |
17 | $this ->_fgets( $fp ); // 行を読み飛ばす |
18 | } |
19 |
20 | // データベーステーブルへの書き込みに使用するスキーマを生成する |
21 | $this ->_schema = $this ->_get_schema( $fp , $this ->_actual_settings); |
22 |
23 | // SQL によるテーブル生成処理 (※1) |
24 | $model ->query( "DROP TABLE IF EXISTS `{$model->useTable}`" ); // 一旦既存のテーブルを削除する |
25 | // 指定された、または CSV ファイルから取得した schema に従ってテーブルを再構築する |
26 | $sql = $this ->_generateCreateTableCommand( $model ->useTable, $this ->_schema); |
27 | $model ->query( $sql ); |
28 |
29 | // データ読み込み・設定処理 以下 CSV ファイルからデータを読み込んで SQL の INSERT 文を生成し、テーブルにデータを書き込む (100行ごとに一旦出力する) |
30 |
31 | $fields = array_keys ( $this ->_schema); |
32 | do |
33 | { |
34 | $records = array (); |
35 | for ( $i =0; ( $tmp = $this ->_fgets( $fp )) !== FALSE ; $i ++ ) |
36 | { |
37 | $records [] = '(' . implode( ', ' , $this ->_convert( $this ->_schema, $this ->_str_getcsv( $tmp ))) . ')' ; |
38 | if ( $i >= $this ->_actual_settings[ 'maxrecords' ] ) |
39 | { |
40 | // 読み取ったレコード数が規程行数に達したら INSERT 文として出力する |
41 | $i = 0; |
42 | break ; |
43 | } |
44 | } |
45 | $sql = "INSERT INTO `{$model->useTable}` (`" . implode( '`, `' , $fields ) . "`) VALUES\n" |
46 | . implode( ",\n" , $records ); |
47 | $model ->query( $sql ); |
48 | } while ( $tmp !== FALSE ); |
49 |
50 | // CSV ファイルをクローズする |
51 | fclose( $fp ); |
52 | } |
53 | } |
54 | ?> |
細かい内部メソッドや省略されている要素にについては次回解説の予定。
関連エントリー
- 2018/09/04
- MySQL にタイムゾーンを追加する
- 2018/06/01
- 別PCからMySQLサーバーをアクセスする
- 2018/04/14
- CakePHP のレンダリング結果を保存したい
- 2017/09/06
- CakePHP 2.x の Cookie と js.cookie.js