rhacoとか触ってみた 既存DBからproject.xmlのdatabaseセクションを作成するバッチ

こんな記事をみて
やめだやめだ!Ethnaでいくぞ! - 肉とご飯と甘いもの @ sotarok

私は、ここ数日でひとつの決心をした。

Cakeだのsymfonyだの、そんなもんはやめだ、やめ!

EthnaEthna。そして時々rhacoだ。

rhaco


そういえば、もうひとつきっかけがありました。
デザイナーでも作れるスマートWebアプリ その2 - maru.cc@はてな」このエントリのリンク元にrhaco-jaのlingrのURLがあって入ってみたってのがあった。(追記)


というわけで、調べて触ってみた。
rhaco-ja
rhaco | Free Development software downloads at SourceForge.net
sourceforge.netから、リリース版を落としてきて、チュートリアル通りにとりあえず設定して、DB操作画面とか。
リリース版(1.4.0)という書き方をした理由は、SVNのtrunk版(1.4.9)がメジャーバージョンアップじゃね?というぐらい違うからですw


リリース版の「簡易DB管理画面」というのがテーブル単位の修正や、依存関係の定義など、xmlに書く必要はありますが、書きさえすれば、結構ちゃんと出来ていて、これは期待できるかも。
で、サンプルのテーブル2つだと、実際にどんな感じかわからなかったので、ちょうど今開発を行っている案件のテーブルのxml定義を起こして動かしてみようと思いました。


で、作成したのがmake_rhaco_databasexml.php
これを使って、作成したdatabase定義を作成して、setup.phpで「生成する」ボタンを押してみたら、タイムアウトに。
setup.phpの先頭に以下を追加して再実行。

set_time_limit(0);

で、テーブル関連のクラスを作成して実行したところ、エラーに。。。
追うのもめんどくさいなぁと思ってたら、trunk版と大きく違うというのを教えてもらいました。
で、trunk版を落としてきて実行。


trunk版はデザインや各ツールも大きく変わっていました。
同じようにdatabase定義をproject.xmlにコピペして、setup.phpで「setting」を実行。
今度は動きました。
library/modelとlibrary/model/tableの下にそれぞれ174個のクラスファイルがわらわら作成されていました。


簡易DB管理画面の方も大きく変わっていていました。
触ってみた感触は、ちょっと予想と違う感じ。
質問してみたところ

maru_cc
 この setup.php/database/admin って どんな位置づけなのでしょうか?
 DBデータをいじるphpMySQLAdminっぽい使い方なのか
tokushima
 位置づけというと? 管理者による簡易DB管理画面 というような?
maru_cc
 それとも これをベースにクライアントにも見せる管理画面の元っぽいイメージなのか
tokushima
 クライアントに見せるイメージではないですね
maru_cc
 なるほどです
tokushima
 管理者が、ちょっとデータいじりたいときに使うもの です

とのことでしたので、phpMySQLAdminのプロジェクトに特化した簡易機能という感じでした。
確かに名前も「簡易DB管理画面」ですもんね。


勝手に、実運用時の管理画面の雛形みたいなイメージを持ってしまっていました。
作りこみが必要なところは別途作るとして、設定値変更画面とかはこれをそのまま使えるみたいな。
でも、用途が違うのであれば納得な感じ。

riaf
 汎用的な管理画面キットみたいなのを用意したらいいんですね。。。!
tokushima
 そっすねw そしてたら、installにいれてみますか

とのことですので、今後に期待です〜


実行方法

まずは、実行方法ですが、シェル上で以下のように叩いています。
需要があって、自分も使いたくなったらWebから使えるようにしようと思ったのですが、とりあえずテストなのでこれで十分。

php make_rhaco_databasexml.php > test.xml

make_rhaco_databasexml.phpの中身

mysql_field_flagsとかを使うと取れる情報が限られているので、show create tableとかの結果をパースとかすれば、もっと詳細なものが作れるかもなーと思ったのですが、MySQLのバージョンに依存しまくるので、とりあえず無視。
あと、作成されるxmlの内容が少し古いようです。 admin="true" とかもう無いとのことでしたが、とりあえずそのまま。

<?php
/**
 * Make Database Section for racho project.xml
 *
 * author maru_cc
 */

$opt = array(
        'dbtype'=>'mysql',
        'server'=>'HOSTNAME',
        'database'=>'DBNAME',
        'username'=>'USERNAME',
        'password'=>'PASSWORD',
        );
$dbinfo = dbinfo::getInstance($opt);
if (!$dbinfo) exit('error');

echo $dbinfo->makeXml();

class dbinfo {
  var $_opt;
  var $_db;

  function &getInstance($opt) {
    $classname = 'dbinfo_' . $opt['dbtype'];
    if (!class_exists($classname)) return false;
    $dbinfo =& new $classname($opt);
    return $dbinfo;
  }

  function dbinfo($opt) {
    $this->_opt = $opt;
    return $this->_dbconnect();
  }
  function _dbconnect() {
    trigger_error('this method must overried!', E_USER_ERROR);
  }
  function makeXml() {
    $infolist = $this->_getinfolist();
    if (!is_array($infolist) || !count($infolist)) return false;
    $xmltables = '';
    foreach ($infolist as $tablename=>$columns) {
      $xmlcols = '';
      if (is_array($columns)) {
        foreach ($columns as $col) {
          $xmlcols.= "    <column name=\"" . $col['name'] . "\"";
          $xmlcols.= " type=\"" . $col['type'] . "\"";
          if (strlen($col['default'])) $xmlcols.= " default=\"\"";
          $xmlcols.= " size=\"" . $col['size'] . "\"";
          if ($col['require']) $xmlcols.= " require=\"true\""; //true|false
          if ($col['primary']) $xmlcols.= " primary=\"true\""; //true|false
          if (strlen($col['reference'])) $xmlcols.= " reference=\"\"";
          if ($col['index']) $xmlcols.= " index=\"true\""; //true|false
          if ($col['unique']) $xmlcols.= " unique=\"true\""; //true|false
          if (strlen($col['uniquewith'])) $xmlcols.= " uniquewith=\"" . $col['uniquewith'] . "\"";
          $xmlcols.= " />\n";
        }
      }
      $xmltables.= "  <table name=\"{$tablename}\" admin=\"true\">\n";
      $xmltables.= $xmlcols;
      $xmltables.= "  </table>\n";
    }

    $xml = "<database name=\"" . $this->_opt['database'] . "\">\n";
    $xml.= $xmltables;
    $xml.= "</database>\n";

    return $xml;
  }
  function _getinfolist() {
    trigger_error('this method must overried!', E_USER_ERROR);
  }
}

class dbinfo_mysql extends dbinfo {
  function _dbconnect() {
    $db = mysql_connect($this->_opt['server'], $this->_opt['username'], $this->_opt['password']);
    if (!$db) return false;
    if (!mysql_select_db($this->_opt['database'], $db)) return false;
    $this->_db = $db;
    return true;
  }
  function _getinfolist() {
    $columntypes = array('datetime'=>'timestamp','real'=>'float','blob'=>'text');

    if (!$this->_db) return false;
    $tables = array();
    $tablesres = mysql_query("show tables", $this->_db);
    if (!$tablesres) return false;
    while ($tablerow = mysql_fetch_array($tablesres)) {
      $tablename = $tablerow[0];
      $res = mysql_query("select * from {$tablename}", $this->_db);
      if (!$res) return false;
      $columncount = mysql_num_fields($res);
      $columns = array();
      for ($i=0; $i < $columncount; $i++) {
        $type = mysql_field_type($res, $i);
        if (isset($columntypes[$type])) $type = $columntypes[$type];
        $len = mysql_field_len($res, $i);
        $flags = mysql_field_flags($res, $i);
        $require = (strpos($flags, 'not_null')!==false);
        $primary = (strpos($flags, 'primary_key')!==false);
        if (strpos($flags, 'auto_increment')!==false) $type = 'serial';
        $columns[$i] = array(
                'name'=>strval(mysql_field_name($res, $i)),
                'type'=>$type,
                'default'=>'',///TODO
                'size'=>$len,
                'require'=>$require,
                'primary'=>$primary,
                'reference'=>'', ///TODO
                'index'=>false,///TODO
                'unique'=>false,///TODO
                'uniquewith'=>'',///TODO
                'flags'=>$flags,
                );
      }
      $tables[$tablerow[0]] = $columns;
    }

    return $tables;
  }
}

setup.phpに食わせてエラーになったら、$columntypes = array('datetime'=>'timestamp','real'=>'float','blob'=>'text'); の定義の部分の対応表を増やしてくださいませ。
自分の環境で動くことしか考えていません。。。

追記

$type = 'serial'の部分修正
if ($primary) $type = 'serial';
if (strpos($flags, 'auto_increment')!==false) $type = 'serial';


なんで、イージーミスって書き込み前に気づかないかなぁ…