忍者ブログ
[PR]
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。



2025/01/17 22:34 |
PHP1-47:PHPで圧縮解凍

File_Archive 1.5.4 (stable)
http://pear.php.net/package/File_Archive

Linuxだったらexecやsystemで直接tar呼ぶのが手っ取り早いですが、Windowsだと当然使えません。
またBzip2やZlib等のライブラリで圧縮解凍を行うことが出来ますが、アーカイバ毎に個別のコマンドを使用する必要があり面倒です。
http://jp.php.net/manual/ja/refs.compression.php

そんな場合は例によって抽象化です。
File_Archiveは、読み込みや書き込みの際に、ファイル名の拡張子から勝手にアーカイバを識別して、それぞれに対応した方法で圧縮解凍を行ってくれます。
対応拡張子はtar、zip、jar、gz、tgz、tbz、bz2、bzi、ar、debと、Linuxで使用されるひととおりの拡張子に対応しています。
ただ例えばbz2はbzopenを呼んでいるだけだったりするので、一部の拡張子については各モジュールをインストールする必要があります。

またこのFile_Archive、使用法が少々特殊なので慣れないと面倒です。

file_archive.php 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php
    require_once "File/Archive.php";
    
    //解凍
    //test.zip内にあるsample.txtを/tmpに解凍します
    File_Archive::extract(
          File_Archive::read('test.zip/sample.txt')
        , $dest='/tmp/'
    );
    
    //圧縮
    ///tmp/testフォルダの中身を全部/tmp/sample.zipに圧縮します
    File_Archive::extract(
          $src= '/tmp/test/'
        , File_Archive::toArchive(
              '/tmp/sample.zip'
            , File_Archive::toFiles()
        )
    );
    
    //アーカイブ形式変換
    //test.zipを読み出して、test.tgzとして保存しただけ
    File_Archive::extract(
          File_Archive::read('test.zip')
        , File_Archive::toArchive(
              'test.tgz'
            , File_Archive::toFiles()
        )
    );


基本的に何もかもをFile_Archive::extractの中で処理します。
引数はふたつで、基本は前者を後者に変換となります。
引数はFile_Archiveオブジェクト、もしくは変数で与えることができます。
extractの引数がextract(&$sourceToConvert, &$destToConvert, $autoClose = true, $bufferSize = 0)となっているため、上記のように$src=と変数で与えると大丈夫ですが、直接'test.zip'とか書くとエラーになります。

第二引数の指定方法ですが、toFilesを呼べば現在のファイル構造をそのままファイルとして保存します。
toArchiveを呼べばアーカイブを作成します。
toArchiveの場合はそのままではアーカイブ化するだけで出力を行わないので、さらに第二引数を指定します。
第二引数にtoFilesを呼べば作成したアーカイブを保存し、toOutputを呼ぶとブラウザからダウンロードさせることができます。


さてFile_Archiveにもイテレータのようなものが実装されています。
圧縮ファイル内のファイルをnextで取得してgetFilenameやgetStatで状態を取得できます。
まあcountは無いですしrewindはなんかうまく動いてないっぽいしで微妙ですが。

file_archive2.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
    require_once "File/Archive.php";
    
    //圧縮ファイルのイテレータ
    $source = File_Archive::read('sample.zip/');
    while($source->next()) {
        var_dump($source->getFilename(),$source->getStat());
    }
    
    //sample.zipから、1キロバイト以下のファイルを抜き出す
    File_Archive::extract(
        File_Archive::filter(
                File_Archive::predNot(
                    File_Archive::predMinSize(1024)
                ),
            File_Archive::read('sample.zip/')
        ),
        File_Archive::toArchive(
            'directory.zip',
            File_Archive::toFiles()
        )
    );


圧縮ファイルを作成する際、File_Archive::filterで任意のファイルを抜き出すことができます。
抜き出し方はわかりやすいので簡単にできるのですが、これもまた普通に&&とか書くことができず、
File_Archive::predAnd(File_Archive::predMinSize($size),File_Archive::predMinTime($time))
みたいな書き方をしないといけないため、入れ子だらけになります。
上のサンプルですら既に嫌になる状態ですが、マニュアルのサンプルはさらに凄いことになっています。
もうちょっとどうにかならなかったものでしょうか。
 

 

PR


2009/01/20 18:39 | Comments(0) | TrackBack() | PHP
PHP1-46:PHPでPDFつづき

前回の続き?

前回とりあえずPDFの作成はできるようになりましたが、AdobeAcrobatのように自由にレイアウトを作成するにはほど遠いものがあります。
フォーム上などでレイアウトしたとおりのPDFを作成するにはHTMLを解析して座標を計算してどうのこうのと面倒なことを行わねばならないので、正直手に余ります。

しかし世の中は広いもので、そのようなサービスをフリーで提供してくれているところがあります。
HTML2PDF.BIZ
http://www.html2pdf.biz/
ありがたく利用させていただきましょう。

使用方法は見ての通りGET投げるだけなので難しくも何ともありません。
http://www.html2pdf.biz/api.php

html2pdf.php 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php
    
    //初期設定
        //サーバ
        $html2pdf_server='http://html2pdf.biz/api';
        //生成時間用
        ini_set('max_execution_time',120);
        set_time_limit(120);
    
    //設定
        $html2pdf_url='http://example.com/';
        $html2pdf_ret='pdf';
        $html2pdf_name='hoge.pdf';
        $html2pdf_ts=true;
    
    //URL作成
        $html2pdf=
             $html2pdf_server
            .'?url='.urlencode($html2pdf_url)
            .'&ret='.(isset($html2pdf_ret)?$html2pdf_ret:'pdf')
            .'&ts='.(isset($html2pdf_ts)?'true':'false');
    
    //取得
        $pdf=file_get_contents($html2pdf);
        if(!$pdf){die();}
        
    //表示
        header('Content-Type: application/pdf');
        header('Content-Length: '.strlen($pdf));
        header('Content-Disposition: inline; filename="'.$html2pdf_name.'"');
        header('Cache-Control: private, max-age=0, must-revalidate');
        header('Pragma: public');
        ini_set('zlib.output_compression','0');
        print($pdf);


$html2pdf_urlに入れたURLを、$html2pdf_ret形式で取得します。
$html2pdf_ts=trueだとタイムスタンプが入ります。
$html2pdf_nameは出力されたPDFを保存する場合のデフォルト名です。

PDF生成に2,30秒かかるので、念のためタイムアウト時間を2分に設定しています。
以上でHTMLから簡単にPDFの作成ができました。
PDFにHTML2PDF.BIZのロゴが入りますが、これはフリー版なので仕方ありません。
これで自力でではありませんがHTMLをそのままPDF化することに成功しました。

しかし重いせいなのかなんなのか、しょっちゅうfile_get_contentsに失敗してしまうのが難点。

 



2009/01/19 10:24 | Comments(0) | TrackBack() | PHP
PHP1-45:PHPでPDF

PDFを作成するには本来AdobeAcrobatというソフトが必要です。
イラストを駆使したような美しいデザインのPDFを作成するには購入したほうがいいでしょうが、簡単な内容のPDFならPHPで作成することもできます。

PHPには元々PDFlibを利用したPDF関数があり、またPear::File_PDFパッケージもありますが、何故かFPDFというライブラリがよく使われています。

現時点で最新のFPDF Japanese version 1.6を使用してみます。
使用法は簡単で、ダウンロードしたFPDFフォルダを何処かに突っ込んでfpdf.phpをreuireするだけです。
include_pathの設定も必要ありません。

pdf.php 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?php
    
    //FPDF
        require_once('fpdf.php');
        $pdf= new FPDF();
    
    //所有者、タイトル等
        $pdf->SetAuthor('Author');
        $pdf->SetTitle('Title');
    
    //表示モード
        $pdf->SetDisplayMode('real','default');
    
    //全体の設定
        $pdf->SetFont('times','',20);
        $pdf->SetTextColor(255,100,100);
        $pdf->SetDrawColor(100,255,100);
        $pdf->SetFillColor(100,100,255);
        $pdf->SetFontSize(10);
    
    //ページ作成
        $pdf->AddPage('P');
    
    //テキスト表示
        $pdf->SetXY(10,50);
        $pdf->Write(5,'text');
    
    //画像を表示
        $pdf->Image('a2.jpg',10,20,33,0,'jpg','http://example.com/');
    
    //リンクを作成
        $pdf->Rect(30, 30, 30, 30, 'DF');
        $pdf->Link(30, 30, 30, 30, 'http://lexample.com/');
    
    //
        $pdf->SetTextColor(50,60,255);
        $pdf->SetFontSize(20);
        $pdf->SetXY(50,20);
        $pdf->Cell(100,10,'sample1','LRT',1,'C',0);
        $pdf->SetX(50);
        $pdf->Cell(100,10,'sample2',1,1,'',0);
 
    //表示の実行
        $pdf->Output();

後はこのように、要素を一つずつ順番に書き出していくだけです。
他にも多様なメソッド、引数がありますが、そこらへんは日本語ヘルプも存在するのでそれを見るとよいでしょう。
現時点では1.52用しかありませんが、内容は概ね同じなので大丈夫です。

FPDFには大きな問題点が二つほど存在します。
ひとつは、FPDFは素の状態では日本語に対応していないことです。
FPDF::WriteやCellに日本語を突っ込むと文字化けしてしまいます。
日本語対応のMBFPDFを使用してみましょう。

FPDFのサイトトップから「What languages can I use?」を選択、mbfpdfをダウンロードします。
FPDFと同じフォルダに置いたらmbfpdf.phpをrequireするだけです。
日本語フォントを使用できる以外はfpdf.phpとほぼ同じです。
サンプルとしてexja.phpが入っているので見てみるとよいでしょう。

mbpdf.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
    
    //MBFPDF
        require_once('mbfpdf.php');
        $pdf= new MBFPDF();
    
    //フォントの追加
        $pdf->AddMBFont(MINCHO ,'SJIS');
        $pdf->SetFont(MINCHO,'',20);
        
    //全体の設定
        $pdf->SetTextColor(255,100,100);
        $pdf->SetFontSize(20);
    
    //ページ作成
        $pdf->AddPage('P');
    
    //テキスト表示
        $text=mb_convert_encoding('あいうえお','SJIS','UTF-8');
        $pdf->SetXY(50,10);
        $pdf->Write(10,$text);
    
    //表示の実行
        $pdf->Output();


ひとつだけ準備が必要で、MBFPDF::AddMBFontで使用するフォントを通知する必要があります。
使用できるフォントはmbfpdf.phpの頭に書いてありますが、現在はMS明朝とMSゴシックの5種類だけのようです。
また、$pdfに渡すフォントはSJISである必要があります。
EUC-JPは一部問題があり、UTF-8には対応していません。

無事にPDFファイルが作成されました。めでたし。


さてもう一つの問題点ですが、なんといっても面倒臭いこと。
座標を毎度毎度指定しないといけないので、テキスト部分だけを差し替えるような内容なら簡単にできますが、レイアウト自体を変更するようなアプリを書くには大掛かりな仕掛けが必要になります。
YUIなりFCKeditorなりでレイアウト作成させてHTTPRequestから座標計算して各メソッド割り振ってうひゃあ面倒い。


2009/01/16 10:51 | Comments(0) | TrackBack() | PHP
PHP1-44:PHPでFTPつづき

前回の続き。

こんな有用なラッパ関数誰かが作っていないはずがない、ということで当然既にPearになっています。

Pear::Net_FTP 1.3.7 (stable)
http://pear.php.net/manual/ja/package.networking.net-ftp.php

使用方法は前回のftpClassとほぼ同じです。

ftp.php 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
    
    //接続用設定
        $ftp_server='example.com';
        $ftp_user='user';
        $ftp_pass='password';
    
    //FTP接続
        require_once('Net/FTP.php');
        $ftp=new Net_FTP($ftp_server);
        $ftp->connect();
        $ftp->login($ftp_user,$ftp_pass);
        
    //ダウンロード
        $ftp->get('hoge.txt','local.txt');
    
    //アップロード
        $ftp->put('local.txt','hogehoge.txt',true);
    
    //削除
        $ftp->rm('hoge.txt');

他にもカレントディレクトリを移動するcd、ディレクトリ一覧を取得するls等Linux的なコマンドが幾つか用意されています。
ディレクトリ丸ごとアップロード等もできて便利。
ただ、何故かテキストやストリームとして受信する手段がありません。
$text=$ftp->get('hoge.txt');
のような使用法ができず、必ずファイルとして保存しなければなりません。

実はこれPear::Net_FTPだけではなく、大元のFTP関数にも存在しないんですよね。
何故かファイルかファイルポインタにしか保存できません。
まあ元々FTPはファイル転送用のプロトコルですから当然と言えば当然ではありますが。

PHP5以降なら'php://temp'というダミーのポインタを利用することでテキストとして受信することはできますが、そもそもPHP5だったらURLラッパでアクセスした方が手っ取り早いという。

 



2009/01/14 16:46 | Comments(0) | TrackBack() | PHP
PHP1-43:PHPでFTP

ブログとWiki全盛のこの時代ですが、旧来のホームページを作成する場合にはFTPは欠かせません。
まあこれもブラウザ上から行うことが可能ですが、当然PHPにもFTP接続を行う関数が用意されています。

ftp.php 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
    
    //FTP
        $ftp_server='example.com';
        $ftp_user='user';
        $ftp_pass='password';
    
    //ダウンロード
        $ftp_file='/download.txt';
        $ftp_url='ftp://'.$ftp_user.':'.$ftp_pass.'@'.$ftp_server.$ftp_file;
        $f = file_get_contents($ftp_url);
    
    //アップロード
        $ftp_file='/upload.txt';
        $str='hogehoge';
        //デフォルトでは上書きが禁止されているので許可する
        $opts = array('ftp' => array('overwrite' => true));
        $context = stream_context_create($opts);
        
        $ftp_url='ftp://'.$ftp_user.':'.$ftp_pass.'@'.$ftp_server.$ftp_file;
        $f=file_put_contents($ftp_url,$str,LOCK_EX,$context);

FTP関数は?

実は関数を使わなくても、簡単な方法でFTPアクセスを行うことができます。
上記の'ftp://'から始まるURLはURLラッパーと呼ばれ、FTPにアクセスする標準的な実装です。
別にPHP固有のものでも何でもなく、実際作成された$ftp_urlをブラウザに入力すると普通にファイル内容を取得することができます。

そのURLに対しfile_get_contentsすればダウンロードできますし、file_put_contentsすればアップロードすることができます。
ということになっているのですが、手元の適当なサーバに試してみたところ、file_put_contentsにLOCK_EXを掛けた場合何故かアップロードに失敗するという事態に。
バージョンは対応しているはずなのにぃ。

普通に
    $fp=fopen($ftp_url,'w',false,$context);
    flock($fp,LOCK_EX);
    fwrite($fp,$str);
    fclose($fp);

としてみたらflockがfalseになった。
残念ながらflockに非対応のサーバだったようです。


さてこのFTPラッパー、表現的にはわかりやすいしPHP5ではmkdirからunlinkまで何でもできるのですが、プログラム組む際には少々扱いにくいです。
例によってクラスに突っ込んでみましょう。

ftp.class.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
<?php
 
class ftpClass{
    
    private $ftp=array();
    private $fc='';
    private $context='';
    
    //コンストラクタ
    function __construct($ftp_server,$ftp_user,$ftp_pass){
        $this->ftp['ftp_server']=$ftp_server;
        $this->ftp['ftp_user']=$ftp_user;
        $this->ftp['ftp_pass']=$ftp_pass;
        
        $opts = array('ftp' => array('overwrite' => true));
        $this->context = stream_context_create($opts);
    }
    
    //ステータス
    function status($ftp_file){
        $this->ftp['ftp_file']=$ftp_file;
        $this->_ftp_url();
        
        $this->fc=stat($this->ftp['ftp_url']);
        if(!$this->fc){$this->_error('取得失敗');}
        return $this->fc;
    }
    
    //削除
    function delete($ftp_file){
        $this->ftp['ftp_file']=$ftp_file;
        $this->_ftp_url();
        
        $this->fc=unlink($this->ftp['ftp_url']);
        if(!$this->fc){$this->_error('削除失敗');}
        return true;
    }
    
    //アップロード
    function put($ftp_file,$str){
        $this->ftp['ftp_file']=$ftp_file;
        $this->_ftp_url();
        
        $fp=fopen($this->ftp['ftp_url'],'w',false,$this->context);
        if(!$fp){$this->_error('ファイルオープン失敗');}
        $ret=flock($fp,LOCK_EX);
        //if(!$ret){$this->_error('ファイルロック失敗');}
        $ret=fwrite($fp,$str);
        if(!$ret){$this->_error('ファイル書き込み失敗');}
        fclose($fp);
        return true;
    }
    
    //ダウンロード
    function get($ftp_file){
        $this->ftp['ftp_file']=$ftp_file;
        $this->_ftp_url();
        
        $this->fc=file_get_contents($this->ftp['ftp_url']);
        if(!$this->fc){$this->_error('取得失敗');}
        $this->fc=mb_convert_encoding(
             $this->fc
            ,'UTF-8'
            ,mb_detect_encoding(
                $this->fc,'UTF-8,eucjp-win,sjis-win'
            )
        );
        return $this->fc;
    }
    
    //URLを作成
    function _ftp_url(){
        if(
               !$this->ftp['ftp_user']
            || !$this->ftp['ftp_pass']
            || !$this->ftp['ftp_server']
            || !$this->ftp['ftp_file']
        ){
            $this->_error('引数不足');
        }
        
        if($this->ftp['ftp_file'][0]!=='/'){
            $slash='/';
        }else{
            $slash='';
        }
        
        $this->ftp['ftp_url']=
             'ftp://'
            .$this->ftp['ftp_user']
            .':'
            .$this->ftp['ftp_pass']
            .'@'
            .$this->ftp['ftp_server']
            .$slash
            .$this->ftp['ftp_file'];
    }
    
    //えらー
    function _error($str=''){
        print("<html><pre>");var_dump($str,$this);die();
    }
 
#↓クラスのおわり}

思ったより長くなってしまった。
$this->ftp['ftp_file']以外は先に作っておいた方が負荷が軽くなりそうだとか、file_get_contentsした後はクラス内に保持しておくべきとか、エラー処理が適当すぎるだろとか色々改善点もありますが、まあいいや。

使用する場合はこのように。

ftp.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
    
    //接続用設定
        $ftp_server='example.com';
        $ftp_user='user';
        $ftp_pass='password';
    
    //FTP
    require_once('./ftp.class.php');
    $ftp=new ftpClass($ftp_server,$ftp_user,$ftp_pass);
    
    //ダウンロード
        $filename='sample1.txt';
        $ret=$ftp->get($filename);
    
    //アップロード
        $filename='sample2.txt';
        $str='テスト';
        $ret=$ftp->put($filename,$str);
    
    //ステータス
        $ret=$ftp->status($filename);
    
    //削除
        $filename='sample2.txt';
        $ret=$ftp->delete($filename);

今回はサーバがflock使えなかったのでエラー部分をコメントアウトしてあります。

これでFTP関数とか'ftp://'とかの面倒な処理を書かなくて済むようになりました。
いつものようにnewしてファイル名を突っ込むだけで簡単にFTPアクセスを行うことができます。

 



2009/01/13 12:58 | Comments(0) | TrackBack() | PHP
runkit_return_value_usedの返り値が微妙におかしい件

関数内で呼び出すと、その関数の返り値が使用されているかどうかを調べてくれるという、どういう使い道があるのかさっぱりわからない関数。
使ってみましょう。

runkit_return_value_used.php

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
    //関数
    function a(){
        var_dump(runkit_return_value_used());
    }
    
    a();
    $a=a();
    echo(a());
    print(a());
    print_r(a());
    var_dump(a());
    var_export(a());

結果
bool(false)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
NULL
bool(true)
NULL


最初のNULLはvar_dump()の表示、その次のtrueと最後のNULLはvar_export()によるものです。
echo()やprint()ではきちんとtrueが表示されるのに、var_dump()では何故かNULL。
var_export()に至っては二つも表示されてイミフ。

まあこんな関数誰も使わないと思うからいいけどさ。



2009/01/09 19:29 | Comments(0) | TrackBack() | PHP
PHP1-43:PHPでPDOつづき

前回の続き。

いよいよPDOのプリペアドステートメントを使用してみます。

pdo.php 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
    
    //PDO
        define('DB_DSN','mysql:host=localhost;dbname=testdb');
        define('DB_USERNAME','testuser');
        define('DB_PASSWORD','testpass');
        $db = new PDO(DB_DSN,DB_USERNAME,DB_PASSWORD);
    
    //引数
        $prepare_id=isset($_REQUEST['id'])?$_REQUEST['id']:1;
    
    //select文
        $sql='SELECT * FROM books where id = ? ';
    
    //プリペアドステートメント
        $prepare =$db->prepare($sql);
        $prepare->bindValue(1,$prepare_id,PDO::PARAM_INT);
    
    //実行
        $prepare->execute();
        $ret=$prepare->fetchAll();

まずSQLを作成する際、引数を入力するところを?にします。
次に$db->prepareとすることでプリペアドステートメントを準備し、bindValueで実際の引数を当て嵌めます。
さて本プログラムのポイントは、$prepare_idを一切エスケープしていないことです。
プリペアドステートメントを使用した場合、DB側でエスケープを行ってくれます
このエスケープは非常に強力で、まず滅多なことでは突破されません(それでも時折話題になる)

bindValueの第一引数の1は、一番目の?に対して変数を割り当てるという意味です。
SQL文に?を複数書いた場合は、bindValueを複数回実行してそれぞれの?に変数を割り当てる必要があります。
また、bindValueにおいて指定しているPDO::PARAM_INT等の定数は、パラメータの型を表します。
が、必須ではありませんし、指定して別の型を突っ込んでもエラーになるわけでもないので、いまいち存在理由がわかりません。

さてプリペアドステートメントは、検索条件が動的な場合の検索には向いていません。
先にSQLを準備して後からその中に変数を突っ込むという構造上、検索条件が変動する場合のSQL構築が面倒なのです。
まあとりあえず作ってみましょう。

pdo2.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php
    
    //PDO
        define('DB_DSN','mysql:host=localhost;dbname=testdb');
        define('DB_USERNAME','testuser');
        define('DB_PASSWORD','testpass');
        $db = new PDO(DB_DSN,DB_USERNAME,DB_PASSWORD);
    
    //プリペアドステートメント
        $sql='SELECT * FROM books';
        if(
               isset($_REQUEST['id'])
            || isset($_REQUEST['name'])
            || isset($_REQUEST['author'])
        ){
            $sql.=' where ';
            if(isset($_REQUEST['id']))    {$sql.=' id = :id and ';}
            if(isset($_REQUEST['name']))  {$sql.=' name like :name and ';}
            if(isset($_REQUEST['author'])){$sql.=' author like :author and ';}
            $sql=substr($sql,0,-4);
        }
    
    //バインド
        $prepare =$db->prepare($sql);
        if(isset($_REQUEST['id']))    {
            $prepare->bindValue(
                ':id', $_REQUEST['id'], PDO::PARAM_INT
            );
        }
        if(isset($_REQUEST['name']))  {
            $prepare->bindValue(
                ':name', '%'.$_REQUEST['name'].'%', PDO::PARAM_STR
            );
        }
        if(isset($_REQUEST['author'])){
            $prepare->bindValue(
                ':author', '%'.$_REQUEST['author'].'%', PDO::PARAM_STR
            );
        }
    
    //実行
        $prepare->execute();
        $ret=$prepare->fetchAll();

SQLを構築する部分とパラメータをバインドする部分、2箇所に対して条件分岐が必要となります。
少々見通しの悪いプログラムになってしまいました。

さて、今回は?ではなくid = :idというふうに指定しています。
その場合、bindValue(':id', 変数)と引数を指定することにより、':id'に対して変数を割り当てることができます。
=ではなくlikeで検索したい場合は、変数を%で挟めば大丈夫です。

以上がPDOの基本的な使い方となります。
あとは適当にマニュアル読んでください。


ところで昔の記事では、「無理に使う必要はないかもしれません」なんてことを書いているわけですが、忘れてください。
「可能な限り使用しなければなりません」に修正します。

 



2009/01/08 12:22 | Comments(0) | TrackBack() | PHP
PHP1-42:PHPでPDO

XAMPPはMySQLですが、他に有名なフリーのDBとしてPostgreSQLが存在します。
商用としては高性能なOracleが、Windows用にはMSSQL等があります。
PHPはデフォルトで、それぞれのDBMSに対して専用の接続関数を用意しています。
MySQLならmysql_connect()、PostgreSQLならpg_connect()、Oracleならoci_connect()といったかんじです。
しかしこれではDBが変更になった際など、全てのプログラムからDB関係の関数を全て探し出して変更しなければなりません。
そんなことをすると恐ろしい手間がかかるので、それを避けるためにDataAccessObjectが編み出されました。

まあ簡単に言うとDBにアクセスするクラスを作成し、コンストラクタに'mysql'なり'postgres'なりの文字を与えれば、あとの接続やSQLの実行といった部分は全部クラス内でやってくれますよということです。
これによって、例えばDBが変更になった場合でも、最初の引数だけ変更すればOKとなるわけです。
更にDBのインスタンス化を行う部分を一つのファイルに纏めてしまえば、DBが変更になっても変更箇所はたった一箇所で済むようになります。
まあ実際はそううまくいくわけもなく、ベンダ固有のSQL実装など修正部分が出てくるのは避けられませんが、それでも素のDB接続関数を用いるよりは遙かに簡単になります。

PHPでDataAccessObjectパターンを行う手段は幾つか存在します。
有名どころではPear::MDB2やPear::DB_DataObjectがありますが、PHP5でようやくモジュールとしてPDOが登場しました。
http://jp.php.net/manual/ja/book.pdo.php
Pearと違ってCで書かれているので高速性が期待できます。

早速使ってみましょう。
Linuxではインストールが必要ですが、Windowsではphp.iniのコメントを外すだけです。
http://jp.php.net/manual/ja/pdo.installation.php

まずはDBの準備です。
phpMyAdminから編集するなりSQLを打つなりでテスト用DBとテーブル、ユーザを作成します。
 

CREATE DATABASE `testdb` DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;

CREATE TABLE `books` (
 `id` int(11) NOT NULL auto_increment,
 `name` varchar(128) NOT NULL,
 `author` varchar(128) NOT NULL,
 `publisher` varchar(128) default NULL,
 `price` int(10) default '0',
 `isbn` varchar(16) default NULL,
 `date` date default NULL,
 `comment` text,
 `created_at` timestamp NOT NULL default CURRENT_TIMESTAMP,
 `updated_at` datetime default NULL,
 `deleted_at` datetime default NULL,
 PRIMARY KEY  (`id`),
 KEY `index_name` (`name`),
 KEY `index_author` (`author`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

CREATE USER 'testuser'@'localhost' IDENTIFIED BY 'testpass';
GRANT USAGE ON * . * TO 'testuser'@'localhost' IDENTIFIED BY 'testpass' WITH MAX_QUERIES_PER_HOUR 0 MAX_CONNECTIONS_PER_HOUR 0 MAX_UPDATES_PER_HOUR 0 MAX_USER_CONNECTIONS 0 ;
GRANT ALL PRIVILEGES ON `testdb` . * TO 'testuser'@'localhost' WITH GRANT OPTION ;


できました。
booksテーブルは別にこんなに立派である必要はありませんが、昔作ったのを流用しました。
とりあえず適当に一件登録しておいてください。

次はPHPの用意です。

pdo.php 

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
    
    //PDO
    define('DB_DSN','mysql:host=localhost;dbname=testdb');
    define('DB_USERNAME','testuser');
    define('DB_PASSWORD','testpass');
    
    $db = new PDO(DB_DSN,DB_USERNAME,DB_PASSWORD);
    
    $select=$db->query('SELECT * FROM books');
    while($loop=$select->fetch(PDO::FETCH_ASSOC)){
        $ret[]=$loop;
    }

簡単に$dbにPDOオブジェクトが作成されました。
後はこれに対しSQLを書いていくだけですが、PDOを経由しているので実行する時にDBの種類を考える必要がありません。

 

もしDBが変更になった場合、最初のDB_DSNを、
define('DB_DSN','pgsql:host=localhost port=8888 dbname=testdb');
等と変更するだけです。
移植性、独立性の高いコードを書くことが出来ました。

さっそく実行。
 

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  array(11) {
    ["id"]=>
    string(1) "1"
    ["name"]=>
    string(5) "?????"
    ["author"]=>
    string(2) "??"
    ["publisher"]=>
    string(6) "??????"
    ["price"]=>
    string(4) "1050"
    ["isbn"]=>
    string(14) "978-4883810451"
    ["date"]=>
    string(10) "2006-10-01"
    ["comment"]=>
    string(7) "????(?)"
    ["created_at"]=>
    string(19) "2008-08-15 11:45:49"
    ["updated_at"]=>
    NULL
    ["deleted_at"]=>
    NULL

お?
見事に文字化けしています。
PHPもUTF8、MySQLもUTF8、ブラウザもUTF8なのに何故?

実はこれMySQLからPHPへの出力時に変な文字コードで出力しているのが原因です。
MySQLからの出力は、テーブルの文字コードそのままではなく、設定ファイルに従って変換された上で出力されます。
このデフォルト値がlatin1なる日本語の使えないコードであるため、日本語が全て文字化けしてしまうのです。

というわけで
$db->query('SET NAMES utf8');
の一文を入れるだけで直るのですが、セキュリティ上の理由から勧められていません。
http://nonn-et-twk.net/twk/why-set-names-in-php-is-bad
かわりにmysql_set_charset()を使えと書いてありますが、PDOでどうしろと。

MySQL側で動作を修正した場合、MySQL全体に影響が及ぶので、他のシステムが存在した場合そちらが動作不具合を起こす可能性もあります。
http://dev.mysql.com/doc/refman/4.1/ja/charset-connection.html

まあ今回はローカルで特に問題ないのでmy.cnfを修正しました。
マニュアルには
default-character-set-name=character_set_name
とか書いてありますが、正しくは
default-character-set = utf8
です。

修正方法は、my.cnf(Windows上では短縮ダイアルに見える)の適当なところに、
  [mysqld]
  default-character-set = utf8
  skip-character-set-client-handshake

という文字を突っ込むだけです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
array(11) {
    ["id"]=>
    string(1) "1"
    ["name"]=>
    string(16) "恋空〈上〉"
    ["author"]=>
    string(7) "美嘉"
    ["publisher"]=>
    string(19) "スターツ出版"
    ["price"]=>
    string(1) "0"
    ["isbn"]=>
    string(15) "978-4883810451"
    ["date"]=>
    string(10) "2006-10-01"
    ["comment"]=>
    string(18) "スイーツ(笑)"
    ["created_at"]=>
    string(19) "2008-01-01 01:01:01"
    ["updated_at"]=>
    NULL
    ["deleted_at"]=>
    NULL

めでたし。


2009/01/07 10:43 | Comments(4) | TrackBack() | PHP
PHP1-42:ちょっと便利なdBug

dBug March 22, 2007
http://dbug.ospinto.com/

簡易デバッグにはvar_dump()を使用することが多いと思いますが、配列の中身が巨大な場合等あまり見やすいとは言えません。
といって本格的なデバッグを行うほどでもない、そんなときに便利なのがdBugです。
なんといっても使い方が超簡単。
requireしてnewするだけです。

index.php 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
    
    require_once('../dBug.php');
    
    class testclass{var $hoge;function hoge(){return $this->hoge;}}
    
    
    $test1=array('hoge','xx'=>'yy',array(1,2,'2'=>'3'));
    $test2=new testclass();
    $test3=imagecreatefromjpeg('index.jpg');
    $test4='<xml>xmlxml<a>aa</a><b c="cc">bb</b></xml>';
    
    $dbug=new dBug($test1);
    $dbug=new dBug($test2);
    $dbug=new dBug($test3);
    $dbug=new dBug($test4,'xml');

デフォルトで配列、オブジェクト、MySQLやGD等のリソース型に対応しています。
また、何気に知られていませんが、第二引数に'xml'を指定するとXMLのパースも行えます。

実行結果は以下のようになります。
$test1 (array)
0 hoge
xx yy
1
array
0 1
1 2
2 3
$test2 (object)
hoge [empty string]
hoge [function]
$test3 (resource)
gd
Width 70
Height 70
Colors 0
$test4,'xml' (xml document)
xmlRoot
xml element
xmlName xml
xmlAttributes  
xmlText xmlxml
xmlComment  
xmlChildren
xml element
xmlName a
xmlAttributes  
xmlText aa
xmlComment  
xmlChildren  
xml element
xmlName b
xmlAttributes
array
c cc
xmlText bb
xmlComment  
xmlChildren  

邪魔な要素は閉じたりすることもできて便利です。
 てか、そんなことよりこんなJavaScriptを普通に受け入れる忍者ブログが恐ろしい。 


2008/12/26 14:06 | Comments(0) | TrackBack() | PHP
PHP1-41:PHPでrunkit

とりあえずデフォルトでは入っていないのでpeclコマンドでインストールする必要があります。
ただXAMPPの場合最初から入っているので、php.iniのextension=php_runkit.dllの行のコメントを外すだけです。

本来はRunkit_Sandboxクラスを利用したデバッグ等に威力を発揮するのでしょうが、使い方がよくわかりません。
http://jp2.php.net/manual/ja/runkit.sandbox.php

さて、このモジュールには他にはない強力なメソッドが含まれています。
なんといっても定数の定義を覆すrunkit_constant_redefineが反則過ぎる。

runkit_constant_redefine.php 

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
    
    //定数定義
        define('HOGE','aaa');
        print(HOGE);
    
    //定数再定義
        runkit_constant_redefine('HOGE','bbb');
        print(HOGE);
    
    //定数削除
        runkit_constant_remove('HOGE');
        print(HOGE);

 

5行目で'aaa'、9行目で'bbb'が表示され、13行目で定数未定義のNoticeが発生します。
定数って何?
ちなみに、通常のdefineは再定義しようとするとエラーになりますが、runkit_constant_redefineは未定義の定数に対して使用するとエラーになります。


クラスの拡張はextendsやimplementsを使うことになっていますが、実行時に元のクラスを直接いじくってしまうrunkit_method関数群が用意されています。
extendsと何が違うかというと、クラス自体を変更するのでprivateなインスタンス変数にもアクセスできてしまいます。

runkit_method.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
    
    //クラス
        class testClass{
            private $a=1;
        }
    
    //通常のextends
        class testClass2 extends testClass{
            private $a=2;
            function add2($a){$this->a=$a;}
        }
        $test2=new testClass2();
        $test2->add2(5);
        print("<html><pre>");var_dump($test2);
    
    //メソッド追加
        runkit_method_add('testClass','add','$a','$this->a=$a;');
    
    //実行
        $test=new testClass();
        $test->add(5);
        var_dump($test);die();

実行結果は以下のようになります。
object(testClass2)#1 (2) {
  ["a:private"]=>
  int(1)
  ["a"]=>
  int(5)
}
object(testClass)#2 (1) {
  ["a:private"]=>
  int(5)
}

Javaなんかだとprivateな$aは引き継がれなかったような気がしますが、PHPではprivateな$aと、extendsしたクラスで再定義した$aは別物として扱われます。
作ろうと思えば以下のようなクラスが作れてしまうのでキモい。
  ["a:private"]=>
  int(3)
  ["a:private"]=>
  int(2)
  ["a:private"]=>
  int(1)


後者の場合testClass自体を書き換えてしまうので、testClass::addはtestClass::$aにしっかりとアクセスしてくれます。
他にも基底クラスを無理矢理extendsされたクラスにしてしまうrunkit_class_adoptといった楽しい関数が揃っています。

runkit_function_redefinerunkit_lint_fileを試してみたところ、手元のXAMPPではApacheを巻き込んで落ちてくれやがりました。何故。

しかしこのrunkit関数、定数やクラスという定義をぶち壊してしまう強力過ぎにも程がある関数ばかりなので、使用は控えておいたほうがいいと思います、というか使うべきではないような。

 


2008/12/25 15:19 | Comments(0) | TrackBack() | PHP

<<前のページ | HOME | 次のページ>>
忍者ブログ[PR]