DEVELOPERS BLOGデベロッパーズブログ

  1. HOME > 
  2. 加藤 正人のデベロッパーズブログ > 
  3. CsvLoaderBhavior - CSV データを CakePHP で扱うための一手法

加藤 正人のデベロッパーズブログ

加藤 正人

氏名
加藤 正人
役職
多分SE
血液型
秘密
出没
美味しいもののあるところ
特色
タヒチ大好き。ちょいメタボ。

加藤 正人

2015/02/14

CsvLoaderBhavior - CSV データを CakePHP で扱うための一手法

 

しばしば CSV データをデータベース (テーブル) として処理したいことがある。(電力会社から公開される電力使用量データや総務省から公開される全国市区町村一覧等々。)

CsvSource データソースを利用する方法もあるが、こちらは find 時の絞り込み条件指定等は対応していないようで全件読み込み以外の処理には難があるようだし、他のテーブルとのアソシエーションにも難がありそうだし、ロードした後継続的にテーブルとして使用する場合は自分で CSV のフィールドを調べてテーブルを作成し、内容をコピーするようコードを書かなければならない。

それならば、モデルを最初にアクセスする際に CSV ファイルの構造からテーブルを作成しそこに読み込んでしまい、それ以降は通常のデータベーステーブルとしてアクセスする方法があればよいのではないだろうか。

この方法であれば Behavior として実現できそうだし、他のテーブルとのアソシエーションも容易なので試してみた。

コンセプト

使用に先立ち、CSV データを既存またはその時点で作成したテーブルにロードしてしまえば CRUD 部分は通常のデータベースとしてアクセスするだけなので、ビヘイビアの初期化部分 (setup() メソッド) で対応する。

書き込み先テーブルを生成する場合は、その構造 (スキーマ) をユーザが指定することもできるが、可能な限り自動生成できるようにする。

ビヘイビア名を CsvLoader とすると、以下のような感じになる。

01<?php
02class 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
02class 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()  コールバックメソッドの構造は以下のようにできる。

  1. ビヘイビアの生成オプションと既定値オプションをマージする
  2. CSV ファイルをオープンする
  3. テーブルを構築するためのスキーマデータをオプションに従って生成する
  4. データベースにテーブルを作成する
  5. CSV ファイルからレコードを読み込みテーブルに書き込む
  6. EOF に達したらファイルをクローズする

これをコード化したものがこちら。(抜粋)

01<?php
02class 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?>

細かい内部メソッドや省略されている要素にについては次回解説の予定。

関連タグ: CSV  MySQL  Behavior  CakePHP2 

関連エントリー

CakePHP3 で created と modified に日付が自動付与されない場合のメモ

MySQL にタイムゾーンを追加する

別PCからMySQLサーバーをアクセスする

CakePHP のレンダリング結果を保存したい

CakePHP 2.x の Cookie と js.cookie.js