青木ITプランニング

PHP、Smarty、ケータイサイトについて発信中。書籍 「Smarty動的webサイト構築入門」(技術評論社) 好評発売中
RSS icon Home icon
  • phpMySplitDump


    ダウンロード

    ======================================================================
    ■phpMySplitDumpとは
    ======================================================================

    MySQLのテーブルが大きすぎると、phpMyAdminやmysqldumpでエクスポートできない場合があります。
    ・phpMyAdminでエクスポート中、タイムアウトしてしまった。
    ・mysqldumpでファイル保存しようとしても、ディスク容量が不足。

    phpmyadmin、mysqldumpは、範囲指定して、一部分をエクスポートできますが、
    分割数が多くなると手動操作は難しくなります。

    そこで、自動で分割エクスポートするために、phpMySplitDumpを作りました。

    ★サイトを「メンテナンス中」にしてから、本ソフトを実行することを前提にしています。★
    ★サイトを停止せず運用中のまま、MySQLバックアップを目的としていません。★

    きっかけは、OpenPNE2系のサーバ引越しでした。
    c_imageテーブルが2500行、3GB以上あり、phpMyAdminのエクスポートはタイムアウト。
    SSHログインしてのmysqldump出力は3GB以上になるはずですが、
    レンタルサーバ(1GBプラン)のディスク容量の残りは500MBでした。

    ======================================================================
    ■対象ユーザ
    ======================================================================

    サイト管理者向けのソフトです。

    次のスキルがあると、おもしろくも何ともない、このリードミーでも、
    インストール、確認、実行がスムーズに進む可能性が高まります。

    ・MySQLの接続パラメータ
    ・PHPスクリプトの簡単な編集
    ・phpMyAdminの操作
    ・wgetの操作
    ・コマンドプロンプトの操作

    ======================================================================
    ■動作環境
    ======================================================================
    これ以前であっても動いたり、これ以降であっても動かなかい可能性があることをご理解ください。

    □サーバー側
    ・Apache
    ・MySQL4以上
    ・PHPのsystem関数で、mysqldumpを実行できること。
    ・PHP4以上
    ・PEAR

    □クライアント側
    ・Windows XP
    ・GNU wget
    ・ディスク容量の余りが充分にあること
    DB使用容量の2倍が目安です。

    ※DB使用量は、phpMyAdminで確認してください。
    ・phpMyAdminにログインしてください
    ・左側のデータベース一覧から、目的のデータベースを選択します。
    ・右側のテーブル一覧のサイズ列の一番下の欄が、DB使用量です。

    ======================================================================
    ■インストール
    ======================================================================

    1) .htmysql.ini
    1-1) .htmysql.ini.sampleをコピーして、.htmysql.iniを作成してください。
    1-2) 目的のデータベースに合わせて編集してください。

    [client]
    host=localhost
    user=dummy_1234
    password=dummy_F4opGiCf
    database=dummy_openpne

    2) config.php
    2-1) config.php.sampleをコピーして、config.phpを作成してください。
    2-2) BASIC認証のユーザ名とパスワードを設定してください。

    $GLOBALS['BASIC_AUTH_USER'] = ”;
    $GLOBALS['BASIC_AUTH_PW'  ] = ”;

    2-3) サーバのmysqldumpのパスに合わせて、設定してください。

    $GLOBALS['MYSQLDUMP_EXE'] = ‘/usr/bin/mysqldump’;

    3) ファイルのアップロード
    *.sampleを除く全ファイルを、サーバ上へアップロードしてください。
    ファイル属性、ディレクトリ属性の設定はありません。
    ここでは、http://localhost/phpMySplitDump/index.php へ設置したと仮定します。

    ======================================================================
    ■確認
    ======================================================================

    1) .htmysql.ini を表示できないことを確認してください。
    Webブラウザで http://localhost/phpMySplitDump/.htmysql.ini を表示してください。
    このとき、
    Access forbbidden
    Error 403
    となることを確認してください。

    2) Webブラウザで http://localhost/phpMySplitDump/ を表示してください。
    エラーが表示されている場合は、インストールの .htmysql.ini と config.php の設定を確認してください。

    3) 次に、http://localhost/phpMySplitDump/ の次のリンクをクリックして、ページ移動できるか確認してください。
    エラーが表示されている場合は、インストールの .htmysql.ini と config.php の設定を確認してください。

    2-1) Step1 の「wgetrc.txt」リンク
    2-2) Step2 の「phpMySplitDump.txt」リンク
    2-3) Menuの「Table Status(before dump)」リンク
    2-4) Menuの「mysqldump list」リンク
    2-5) Menuの「Table Status(after dump)」リンク

    4) http://localhost/phpMySplitDump/ で、Menuの「mysqldump list」リンクをクリックして、
    http://localhost/phpMySplitDump/list.php を表示してください。

    次のリンクをクリックしてください。

    4-1) 先頭行の create_table.sql
    ・ダウンロードが始まること
    ・保存ファイルが、空(から)でないこと
    ・保存ファイルをテキストエディタで開き、目視確認で、mysqldumpの結果として正しいこと
    ・保存ファイルをテキストエディタで開き、「error」「warning」を検索し、みつからないこと
    を確認してください。

    4-2) 2行目の xxxx.sql
    ・ダウンロードが始まること
    ・保存ファイルが、空(から)でないこと
    ・保存ファイルをテキストエディタで開き、目視確認で、mysqldumpの結果として正しいこと
    ・保存ファイルをテキストエディタで開き、「error」「warning」を検索し、みつからないこと
    を確認してください。

    ======================================================================
    ■実行
    ======================================================================

    サイトを運用したまま、練習できます。
    本番前に練習して、ファイル数、ディスク容量、所要時間を調べておいてください。

    (引越しの)本番では、サイトに「メンテナンス中」表示をして、
    ユーザや管理者をサイトから追い出してから、この作業をします。

    1) Webブラウザで http://localhost/phpMySplitDump/ を表示してください

    2) Step1にある wgetrc.txt のリンクを右クリックして、保存してください。
    保存場所は、D:\host_db\wgetrc.txt と仮定します。

    3) Step2にある phpMySplitDump.txt のリンクを右クリックして、保存してください。
    保存場所は、D:\host_db\phpMySplitDump.txt と仮定します。
    次に、拡張子を .txt から .bat へ変更してください。
    これで、D:\host_db\phpMySplitDump.bat ができました。

    4) D:\host_db\phpMySplitDump.batを実行します。
    エクスプローラの場合は、D:\host_db\phpMySplitDump.batのアイコンをダブルクリックします。
    コマンドプロンプトの場合は、
    D:
    cd \host_db
    phpMySplitDump.bat
    と入力してください。

    5) D:\host_db\local_save\ の下に、sqlファイルが保存されていきます。
    最後に、何かキーを押してください。
    参考記事:
    Usagi Project Mynets>[質問] 1.1.1から1.2.0のコンバートエラー (2008/09/15)
    ターズの備事録>Openpne 移設 (2008-09-29)

    2009-11-06 aoki 2 comments 02.PHP
  • A4 3000枚、Canon iP4600のインク使用量は?


    090813_221247-320x240セレクトショップの郵送カタログ500部(A5サイズ12ページ)を印刷することになりました。印刷は、A4サイズ3枚に両面印刷します。つまりA4片面3000ページ相当の印刷です。

    インク交換の時間も含めて、約18時間かかりました。ぶっ通しではなく、1日6時間づつ3日かかりました。、A4片面1枚で、約22秒です。

    使ったインク量は、
    BCI-320PGBK: 7本 x @980 = 6860円
    BCI-321BK: 1本 x @890 = 890円
    BCI-321C: 11本 x @890 = 9790円
    BCI-321M: 10本 x @890 = 8900円
    BCI-321Y: 9本 x @890 = 8010円
    合計 33640円

    インク価格は近所のヤマダ電機のインク価格です。ちなみに、イトーヨカードーでは定価で販売していました。

    A4片面1枚印刷するインク代は、約11円。
    A5サイズ12ページを1部印刷するインク代は、約67円。
    和紙のような紙を使いましたが、紙代は不明です。

    データ入稿の印刷屋さんに頼むと、A5サイズ12ページ500部で、5~6万円です。インク代や手間を考えると、印刷屋さんに頼んだほうが楽チンですね。

    2009-08-13 aoki No comments 未分類
  • MTOSでXML-RPC投稿したら、メインページが文字化け


    MTOS 4.25で、XML-RPCを使った投稿フォームを作りました。MTOS側の再構築するページは、メインページとブログ記事です。投稿フォーム側は、記事一覧(index.php)、投稿フォーム(form.php)、その送信先(save.php)です。実験環境は、Windows + xamppです。

    投稿フォーム(form.php)の送信先(save.php)で、XML-RPCのmetaWeblog.editPostを使って、MTOSに送信します。ログイン成功、XML-RPCの戻り値も正常です。ところが、メインページが文字化けしました。メインページの内、投稿したエントリーだけは読めますが、それ以外の部分がまったく読めません。

    20090522

    MTOSの管理画面では、投稿したエントリーを正しく表示できていました。また、管理画面で再構築したところ、文字化けしませんでした。

    mt-config.cgiの「PublishCharset UTF-8」や「NoPublishMeansDraft 1」も試しましたが、効果はありませんでした。

    いろいろ試していたら、metaWeblog.editPostで投稿後、mt.publishPostで再構築したら、メインページの文字化けしなくなりました。まあ、いいか?

    2009-06-01 aoki No comments 未分類
  • MTの記事タイトルの先頭数字だけを表示する


    こんにちは、aokiです。今回は、MovableType(以下、MT)のTipsです。

    MTのカテゴリーやエントリーの並び順を整えるために、先頭に「<01>」「<02>」を付けるワザを教えてもらいました。例えば、都道府県カテゴリーは「<01>北海道」「<11>青森」のようにします。テンプレートでは、remove_html=”1″をつけて表示します。こうすると、「<01>」の部分が削除されて、「北海道」と表示します。

    <$MTCategoryLabel remove_html="1"$>

    さて、この数字が単なる並び順ではなく、発行番号など意味のある場合、この数字だけを表示したいことがあります。例えば、「01」と表示するには、regex_replaceを使って、

    <MTSetVarBlock name="x"><$MT:EntryTitle regex_replace="^<|>.*$/g">"$></MTSetVarBlock>
    <$MTGetVar name="x"$>

    とします。

    また、「<01>」と表示するには、

    <MTSetVarBlock name="x"><$MT:EntryTitle regex_replace="/>.*/",">"$></MTSetVarBlock>
    <$MTGetVar name="x" encode_html="1"$>

    とします。

    MTSetVarBlockを使わなくても可能ですが、好みの問題で、別の変数に割り当てたほうがわかりやすい気がします。

    2009-05-25 aoki No comments 未分類
  • ケータイサイトでPCアクセスをBASIC認証


     ケータイサイトでPCからのアクセスを制限すると、自分のPCのFireFox + UserAgentSwitcherや各キャリアのシミュレータで表示確認できなくなります。私はPCのほうに慣れているので、これは不便です。

     事務所のIPアドレスが固定なら、そのIPアドレスを許可リストに追加します。しかし、関係者全員が固定IPをもっているともかぎりません。

     そこで、PCからのアクセスはBASIC認証で許可します。.htaccess に「Satisfy Any」を記述します。

    # BASIC認証
    AuthUserFile /path/to/.htpasswd
    AuthGroupFile /dev/null
    AuthName "Type your password please."
    AuthType Basic
    require valid-user
    
    Satisfy Any
    
    order deny,allow
    deny from all
    
    # imode
    allow from 210.153.84.0/24
    allow from 210.136.161.0/24
     .... 以下、省略 ....
    2009-05-18 aoki No comments 04.ケータイ
  • ケータイのIPアドレス


    ケータイサイトでPCからのアクセスを禁止したいとき、ケータイからのIPアドレスだけを許可します。また、ケータイ向けの検索エンジンのクローラも許可します。

    次のリストは、各キャリアの公式サイトからIPアドレスを取り出したものです。メールやPCビューワからのIPアドレスは含んでいません。これらのIPアドレスはときどき変更されるので、メンテナンスが必要です。

    order deny,allow
    deny from all
    # i-mode
    # http://www.nttdocomo.co.jp/service/imode/make/content/ip/index.html
    allow from 210.153.84.0/24
    allow from 210.136.161.0/24
    allow from 210.153.86.0/24
    allow from 124.146.174.0/24
    # EZweb
    # http://www.au.kddi.com/ezfactory/tec/spec/ezsava_ip.html
    allow from 210.230.128.224/28
    allow from 121.111.227.160/27
    allow from 61.117.1.0/28
    allow from 219.108.158.0/27
    allow from 219.125.146.0/28
    allow from 61.117.2.32/29
    allow from 61.117.2.40/29
    allow from 219.108.158.40/29
    allow from 219.125.148.0/25
    allow from 222.5.63.0/25
    allow from 222.5.63.128/25
    allow from 222.5.62.128/25
    allow from 59.135.38.128/25
    allow from 219.108.157.0/25
    allow from 219.125.145.0/25
    allow from 121.111.231.0/25
    allow from 121.111.227.0/25
    allow from 118.152.214.192/26
    allow from 118.159.131.0/25
    allow from 118.159.133.0/25
    allow from 118.159.132.160/27
    # softbank
    # http://creation.mb.softbank.jp/web/web_ip.html
    allow from 123.108.236.0/24
    allow from 123.108.237.0/27
    allow from 202.179.204.0/24
    allow from 202.253.96.224/27
    allow from 210.146.7.192/26
    allow from 210.146.60.192/26
    allow from 210.151.9.128/26
    allow from 210.169.130.112/28
    allow from 210.175.1.128/25
    allow from 210.228.189.0/24
    allow from 211.8.159.128/25
    #------------------------------------------------------------
    # ケータイ向けの検索クローラ
    #------------------------------------------------------------
    # moba-crawler
    # http://crawler.dena.jp/
    allow from 202.238.103.126
    allow from 202.213.221.97
    # froute
    # http://search.froute.jp/howto/crawler.html
    allow from 60.43.36.253
    # モバイルgoo
    # http://help.goo.ne.jp/help/article/1142/
    allow from 210.150.10.32/27
    allow from 203.131.250.0/24
    # Livedoor
    # http://helpguide.livedoor.com/help/search/qa/grp627
    allow from 203.104.254.0/24
    # Google
    # http://googlejapan.blogspot.com/2008/05/google.html
    allow from 72.14.199.0/25
    allow from 209.85.238.0/25
    # Yahoo!
    # http://help.yahoo.co.jp/help/jp/search/indexing/indexing-27.html
    allow from 124.83.159.146/27
    allow from 124.83.159.178/29
    allow from 124.83.159.224/28
    allow from 124.83.159.240/29
    # MSN Search
    # (Microsoft社のIP範囲からMSNクローラがアクセスしてくる、ということらしいです)
    allow from 65.52.0.0/14
    2009-05-12 aoki No comments 04.ケータイ
  • 処理時間が長いとき、すばやくページ表示したい(6)


      フォーム送信後、サーバ側の処理時間が長いとき、なかなか画面が変わらないことがあります。サーバ側の処理時間が長いときでも、すばやくページを表示するいくつかの方法について説明します。

    (6) 前回(5)の改良版

     前回の(5)では、ブラウザと直接つながっているサーバ側スクリプトがメイン処理をしました。connection:closeとflush()を使った、変則的な方法なので、ブラウザによっては挙動が違うかもしれません。例えばケータイです。softbank 931SHでは、期待どおりだったのは(2)だけで、(1)(3)(4)(5)は期待どおりではありませんでした。

     そこで、ブラウザ – task.php – task_server.php、という構成にします。task.phpからは、サーバ内通信で、task_server.phpへリクエストします。task_server.phpは、チケットを取得し、メイン処理をします。task.php と task_server.php は私の手の届く範囲ですから、多少変則的なことをしても、ブラウザの違いによる影響を受けません。

     オモテ向きのページ遷移は、form.html ⇒ task.php ⇒ show_status.php ⇒ thanks.html となります。オモテにはでてきませんが、task_server.php がメイン処理を担当します。

    5_11 5_21

     最初は、task.phpで、ここでのポイントは1つです。

     task_server.phpとの通信に、file_get_contents()を使わないようにします。fgets()で1行読み込んだら、すぐにfclose()します。

     task_server.phpの設置場所に合わせて、YOUR_HOST、YOUR_PATHを置き換えてください。また、fopenで、http:// を使えないphp設定のwebサーバもあります。その場合は、このスクリプトは動きません。

    <?php
    $url = "http://YOUR_HOST/YOUR_PATH/task_server.php";
     
    $fp = fopen( $url, "rb" );
    assert( $fp );
    $buf = fgets( $fp );
    fclose( $fp );
     
    $vars = unserialize( $buf );
     
    session_start();
    $_SESSION['ticket_id'] = $vars['ticket_id'];
    session_write_close();
    ?>
    <meta http-equiv="refresh" content="0; url=show_status.php" />

     次は、task_server.php です。チケットIDをserializeして返します。このとき、最後に、”\n” を追加します。これがないと、task_server.phpが終わるまで、呼び出し元の task.php の fgets($fp) が終わりません。

    <?php
    require( 'ticket.php' );
     
    $ticket = new Ticket;
    $ticket_id = $ticket->create( 'Start' );
     
    $vars['ticket_id'] = $ticket_id;
    $buf = serialize( $vars );
     
    ob_start();
    echo "$buf\n";
    $buf = ob_get_clean();
    $len = strlen( $buf );
     
    header( "connection: close" );
    header( "Content-length: $len" );
    echo $buf;
    flush();
     
    // メイン処理
    for ( $i = 0; $i < 10; ++$i ) {
     sleep( 1 );
     $ticket->update( $ticket_id, "Running\t$i/10" );
    }
     
    $ticket->update( $ticket_id, 'Finished' );
    exit();
    ?>

     ケータイの softbank 931SHでの結果は、(4)と同じく、metaタグのリロード回数の制限があるのか、途中で「ページを表示できません」と表示されます。その後、手動で「更新」すると、thanks.htmlへ移動しますので、サーバ側は無事にメイン処理を終えているようです。プログレスバーをあきらめて、ページ遷移を変更するのも手です。最近のケータイ機種なら、FlashやAjaxを使う方法もありそうです。

    実験ページ(6)

     このテーマは今回で終わりです。

     task.phpとtask_server.phpの接続が変則的で気になる人向けに、もう1つ方法を紹介します。

     task.php がバッチをキューに登録します。1分に1回起動するcronジョブや常駐サービスが、キューからバッチを取り出して、処理をします。これは、先祖がえり的なものを感じますね。

    2009-04-26 aoki No comments 02.PHP
  • 処理時間が長いとき、すばやくページ表示したい(5)


      フォーム送信後、サーバ側の処理時間が長いとき、なかなか画面が変わらないことがあります。サーバ側の処理時間が長いときでも、すばやくページを表示するいくつかの方法について説明します。

    (5) いきなり難しい方法

     前回の(4)ではプログレスバーを表示しました。これまでの(1)~(4)は、ブラウザへの表示処理を工夫しました。しかし、ブラウザがメイン処理の終わりを待っているという点に切り込んでいません。

     これは例えるなら次のような状況です。鈴木さん(ブラウザ)が高橋商店(サーバ)の田中さん(メイン処理)に電話をかけて、至急の仕事を頼みました。ところが、鈴木さん(ブラウザ)は電話を切らずに、田中さん(メイン処理)の仕事が終わるまで、ぼんやりと待っています。

     通常は、いったん電話を切り、田中さんが仕事を終えてから、鈴木さんへ電話で報告します。今回は、この方法に近いものを目指しました。ただし、田中さん(メイン処理)から鈴木さん(ブラウザ)へ電話で報告する、という部分が難しいです。そこで、田中さん(メイン処理)は進行状況をホワイトボードに書き、鈴木さん(ブラウザ)は不定期に電話をかけて、田中さん以外の人にホワイドボードを見てもらう、と置き換えています。

     ページ遷移は、form.html ⇒ task.php ⇒ show_status.php ⇒ thanks.html となります。オモテにはでてきませんが、ホワイトボードに相当するのは、ticket.phpです。

    5_11 5_21

     最初は、task.phpで、ポイントは3つあります。

     1つめは、進行状況の仲介役のチケットです。最初にチケットを新規発行し、メイン処理の進行に合わせて、チケットのstatusを更新します。チケットIDをURLで引き回すと、第三者がURLでアクセスする可能性があるので、セッションに保存しました。

     2つめは、レスポンスヘッダのcontent-lengthです。これを設定することで、task.phpがメイン処理を終わっていないのに、ブラウザはページロードを完了します。つまり、次のshow_status.phpへすぐに移動できるというわけです。

     3つめは、レスポンスヘッダのconnection: closeです。これがないと、ブラウザがshow_status.phpへリクエストできても、サーバ側では、すぐにはshow_status.phpを実行できず、task.php のメイン処理の終了を待ちます。

    <?php
    require( 'ticket.php' );
     
    $ticket = new Ticket;
    $ticket_id = $ticket->create( 'Start' );
     
    session_start();
    $_SESSION['ticket_id'] = $ticket_id;
    session_write_close();
     
    header( "connection: close" );
     
    ob_start();
    print( '<meta http-equiv="refresh" content="0; url=show_status.php" />' );
    $buf = ob_get_clean();
    $len = strlen( $buf );
    header( "Content-length: $len" );
    echo $buf;
    flush();
     
    // メイン処理
    for ( $i = 0; $i < 10; ++$i ) {
     sleep( 1 );
     $ticket->update( $ticket_id, "Running\t$i/10" );
    }
     
    $ticket->update( $ticket_id, 'Finished' );
    exit();
    ?>

     次は、show_status.php です。セッションからチケットIDを取り出し、進行状況を問い合わせます。 

    <?php
    require( 'ticket.php' );
     
    // セッションからチケットIDを取り出し、進行状況を問い合わせる
    session_start();
    $ticket = new Ticket;
    $status = $ticket->get( $_SESSION['ticket_id'] );
    session_write_close();
    
    if ( 'Finished' == $status ) {
     // 終了していたら、thanks.htmlへページ移動する
     echo '<meta http-equiv="refresh" content="0; url=thanks.html" />';
     
    } else if ( preg_match( '/Running\s+([0-9]+)\/([0-9]+)/', $status, $matches ) ) {
     $tpl['rcd_pos'] = $matches[1];
     $tpl['rcd_cnt'] = $matches[2];
     $tpl['percent'] = floor(100 * $tpl['rcd_pos'] / $tpl['rcd_cnt']);
     
    ?>
    <meta http-equiv="refresh" content="1" />
    <style>
    .frame {
     border: 1px solid #000;
     width: 400px;
     height: 50px;
    }
    .progress {
     width: <?php echo $tpl['percent'] ?>%;
     height: 50px;
     background: #00f;
    }
    </style>
    処理中です<br />
    全件数 : <?php echo $tpl['rcd_cnt'] ?><br />
    処理済み: <?php echo $tpl['rcd_pos'] ?><br />
    <div class="frame">
      <div class="progress">
        <br />
      </div>
    </div>
    <?php
     
    } else {
     // 1秒毎に進行状況を表示する
     echo '<meta http-equiv="refresh" content="1" />';
     echo 'show_status<br />';
     echo "$status<br />";
    }
    ?>

     最後に、ticket.php です。MySQLとPEAR::DBを使いました。idとstatusの2カラムのテーブルを作成し、DB_USER、DB_PASS、DB_HOST、DB_NAMEを置き換えてください。

    <?php
    /*
    CREATE TABLE ticket (
    `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
    `status` VARCHAR( 255 ) NOT NULL
    );
    */
    require_once( 'DB.php' );
     
    class Ticket {
     var $db;
     
     function Ticket() {
      $this->db = DB::connect( 'mysql://DB_USER:DB_PASS@DB_HOST/DB_NAME' );
      if ( PEAR::isError($this->db) ) {
       die( $this->db->getMessage() );
      }
      $res = $this->db->query( "SET NAMES 'UTF8'" );
      if ( PEAR::isError($res) ) {
       die( $sth->getMessage() );
      }
      $this->db->freeResult( $res );
     }
     
     function create ( $i_status ) {
      $query = "INSERT INTO ticket(id,status) VALUES(null,?)";
      $arr = array( $i_status );
    
      $sth = $this->db->prepare( $query );
      if ( PEAR::isError($sth) ) {
       die( $sth->getMessage() );
      }
      $res = $this->db->execute( $sth, $arr );
      if ( PEAR::isError($res) ) {
       die( $sth->getMessage() );
      }
    
      $this->db->freeResult( $res );
      $this->db->freePrepared( $sth );
      
      $id = $this->db->getOne( 'SELECT LAST_INSERT_ID() FROM ticket' );
      return $id;
     }
     
     function update ( $i_id, $i_status ) {
      $query = "UPDATE ticket SET status = ? WHERE id = ?";
      $arr = array( $i_status, $i_id );
    
      $sth = $this->db->prepare( $query );
      if ( PEAR::isError($sth) ) {
       die( $sth->getMessage() );
      }
      $res = $this->db->execute( $sth, $arr );
      if ( PEAR::isError($res) ) {
       die( $sth->getMessage() );
      }
      $this->db->freeResult( $res );
      $this->db->freePrepared( $sth );
     }
     function get ( $i_id ) {
      $query = "SELECT status FROM ticket WHERE id = ?";
      $arr = array( $i_id );
      
      $status = $this->db->getOne( $query, $arr );
      return $status;
     }
    }
    ?>

     今回の方法は、ブラウザを閉じたり、[中止]ボタンを押しても、メイン処理は停止しません。キャンセルをしたい場合は、キャンセルの仕組みをチケットサーバとメイン処理に仕込む必要があります。

    実験ページ(5)

    2009-04-19 aoki No comments 02.PHP
  • 処理時間が長いとき、すばやくページ表示したい(4)


     フォーム送信後、サーバ側の処理時間が長いとき、なかなか画面が変わらないことがあります。サーバ側の処理時間が長いときでも、すばやくページを表示するいくつかの方法について説明します。

    (4) 難しい方法 (プログレスバーを表示する)

     今回は、10000回のループ処理で時間がかかっている場合に、ループ処理を複数のリクエストに分けます。データベースのクエリー1回で時間がかかっている場合には、今回の方法は使えません。

     人が10000件の一覧を見るときに例えると、10000件を1ページで見るのが、前回までの方法。100件づつ100ページに分けて、次へ次へで見ていくのが、今回の方法です。

     ページ遷移は form.html ⇒ task.php ⇒ thanks.html  となります。ただし、task.phpは、1回だけではなく、task.php   ⇒ task.php?rcd_pos=0 ⇒ task.php?rcd_pos=200 と何回も呼ばれます。

     次の例は、0.01秒 x 10000件の処理です。約2秒おきに進捗状況を表示します。

     4_1 4_2

     いろいろな実装方法があります。今回の実装では、セッション変数を使いました。クエリー変数 $_GET['rcd_pos']はループ処理には利用していません。また、100回づつで分けるのではなく、2秒ごとに分けました。

    <?php
    /*
     * task.php?rcd_pos=123
     */
    define( 'MAX_PROCESS_SEC', 2 );
    session_start();
    
    if ( ! isset($_GET['rcd_pos']) ) {
     $_SESSION['rcd_cnt'] = 10000;
     $_SESSION['rcd_pos'] = 0;
     $_SESSION['time_st'] = time();
    } else {
     $t0 = time();
    
     for ( ; $_SESSION['rcd_pos'] < $_SESSION['rcd_cnt']; ++$_SESSION['rcd_pos'] ) {
      if ( MAX_PROCESS_SEC <= time() - $t0 ) {
       break;
      }
      usleep( 10000 ); // 0.01秒
     }
    }
    
    if ( $_SESSION['rcd_cnt'] <= $_SESSION['rcd_pos'] ) {
     $url = "thanks.html";
    } else {
     $url = "task.php?rcd_pos=" . $_SESSION['rcd_pos'];
    }
    
    $tpl['rcd_cnt'] = $_SESSION['rcd_cnt'];
    $tpl['rcd_pos'] = $_SESSION['rcd_pos'];
    $tpl['elapsed_sec'] = time() - $_SESSION['time_st'];
    $tpl['percent'] = floor(100 * $_SESSION['rcd_pos'] / $_SESSION['rcd_cnt']);
    ?>
    <html>
    <head>
    <meta http-equiv="refresh" content="0; url=<?php echo $url?>" />
    <style>
    .frame {
     border: 1px solid #000;
     width: 400px;
     height: 50px;
    }
    .progress {
     width: <?php echo $tpl['percent'] ?>%;
     height: 50px;
     background: #00f;
    }
    </style>
    </head>
    <body>
    処理中です<br />
    全件数 : <?php echo $tpl['rcd_cnt'] ?><br />
    処理済み: <?php echo $tpl['rcd_pos'] ?><br />
    経過時間: <?php echo $tpl['elapsed_sec'] ?>sec<br />
    
    <div class="frame">
      <div class="progress">
        <br />
      </div>
    </div>

     ブラウザがリクエストしなければ、次の処理へ進まないので、途中停止が簡単です。サーバ側にキャンセルという仕組みを用意する必要がありません。ブラウザの[中止]ボタン、[閉じる]ボタン、キャンセルページへのリンク、どれでも途中停止します。

    実験ページ(4)

    2009-04-12 aoki No comments 02.PHP
  • 処理時間が長いとき、すばやくページ表示したい(3)


     フォーム送信後、サーバ側の処理時間が長いとき、なかなか画面が変わらないことがあります。サーバ側の処理時間が長いときでも、すばやくページを表示するいくつかの方法について説明します。

    (3) 簡単な方法 (進行状況を表示する)

     初回の(1)は、flush()を使って「処理中です」をすばやく表示しますが、進行状況はわかりませんでした。今回は、textaeraとjavascriptを使って、進行状況を表示します。

     ページ遷移は form.html ⇒ task.php  となります。thanks.htmlへページ移動しないのは、進行状況を表示したままにしたかったからで、他の理由はありません。必要であれば、thanks.htmlへページ移動することも可能です。

    3_1 3_2

     task.phpは、進行状況表示用のtextareaを含むページを表示して、flush()します。その後、処理のところどころで、javascriptを吐き出します。そのjavascriptが、textareaに進行状況を表示します。デバグや確認用のprint文をちりばめるのと似ています。

    <html>
    <body>
    <!-- dummy dummy dummy dummy dummy dummy dummy dummy  -->
    <!-- dummy dummy dummy dummy dummy dummy dummy dummy  -->
    <!-- dummy dummy dummy dummy dummy dummy dummy dummy  -->
    <!-- dummy dummy dummy dummy dummy dummy dummy dummy  -->
    処理中です<br />
    <textarea id="progress" rows="15"></textarea>
    <?php
    flush();
     
    for ( $i = 0; $i < 10; ++$i ) {
     progress( "step1: $i" );
     sleep(1);
    }
     
    progress( "step2: " );
    sleep(1);
     
    progress( "step3: " );
    sleep(1);
     
    progress( "Finished" );
     
     
    function progress ( $i_msg ) {
     $msg = escape_js( $i_msg );
     print '<script type="text/javascript">';
     print "document.getElementById('progress').value += '$msg\\n';";
     print '</script>';
     flush(); //★重要★
    }
    
    function escape_js( $i_buf ) {
     // 参考:smartyのescape修正子 {$x|escape:javascript}
     // smarty/libs/plugins/modifier.escape.php
     $replace_pairs = array(
      '\\' => '\\\\' ,
      "'"  => "\\'"  ,
      '"'  => '\\"'  ,
      "\r" => '\\r'  ,
      "\n" => '\\n'  ,
      '</' => '<\/' 
      );
     $buf = strtr($i_buf, $replace_pairs);
     return $buf;
    }
    ?>
    </body>
    </html>

     今ある仕組みを少し改造することで、進行状況を表示できるようになります。

     また、サーバ側にキャンセルという仕組みを用意する必要がありません。ブラウザの[中止]ボタン、[閉じる]ボタン、キャンセルページへのリンク、どれでも途中停止します。

    実験ページ(3)

    2009-04-05 aoki No comments 02.PHP