月別アーカイブ: 2012年4月

PHPでシンプルな文字列検索をする

PCREの場合globの場合とPHPのパターン検索方法を挙げましたが、単純に文字列中で特定文字列に完全一致する部分を探すのであれば、strpos関数やstrstr関数を使用すれば事足ります。
strpos関数は文字列中の検索文字列が最初に出現する場所のインデックスを返します。さらに一致する箇所が無い場合にFALSEを返すので、文字列中に検索文字列が存在するか/しないかを調べるPHPの関数も、実はstrposです。

<?php
$string = "abcdefgh";
$search = "def";
if(($pos = strpos($string,$search)) === FALSE){
echo "検索文字列と一致する箇所は無い";
}
else {
echo ($pos + 1)."文字目から一致";
}
?>

引数1が検索対象、引数2が探す文字列、オプションの引数3は探し始める位置です。比較演算子に===を使うのは、FALSE以外のときでも返り値で0を返す可能性があるからですね。なおこのstrposのヴァリエーションとして、大文字小文字の区別をしないstripos、最後に現れる位置を調べるstrrposなどがあります。

strstr関数は、検索文字列以降の文字列を返り値として得る際に使います。

strstr(引数1,引数2,引数3);
引数1,2strposに同じ。
引数3 検索文字列より前の部分を返すフラグ。オプション。

大文字小文字の区別をしないstristrというヴァリエーションもあります。

両方とも使いたいときには名前が出てこない影の薄い関数なので、メモっておきました。


PHPのもう一つのパターンマッチング関数glob

PHP4.3.0以降搭載されているglob関数は、パス文字列を引数に与えることでパターンマッチするパスを探して配列として返してくれる便利な関数です。パターンマッチということで、正規表現のパターンを与えるとマッチングをしてくれるPCRE関数(preg_matchなど)に近いように思われますが、globが解釈するパターンはUNIXシェルのパス名マッチのパターンであり、正規表現より煩雑にならない書き方ができるようです。
たとえば前回preg_grep関数と正規表現を用いて実装した、ディレクトリを検索してその中の拡張子が.jpgのファイルだけ抽出し、セレクトメニューの項目名にするという処理は、globを使うとこのような感じになります。

<?php
$dir = "sample";
$parentlength = strlen($dir) + 1;
echo '<form name="" method="" action=""><select name="">';
foreach(glob($dir."/*.csv") as $val){
echo '<option value="'.htmlentities(substr($val,$parentlength),ENT_QUOTES,"UTF-8").'">'
.htmlentities(substr($val,$parentlength),ENT_QUOTES,"UTF-8").'</option>';
}
echo '</select><input type="submit" value="表示"></form>';
?>

preg_grepでの例の再現のため、項目名としてファイル名のみを取り出そうとしてややこしくなっている部分はありますが、注目すべきはかの例にあったscandir関数のような、ディレクトリ内のパスを取得し配列に納めるというプロセスが省略されているという点です。glob関数はパス検索に特化しており、scandirの直接の上位互換と捉えても良いかもしれません。
さて、関数の説明です。まずパターン文字列の表記方法ですが、PCRE関数のように文字列の前後をスラッシュで囲む必要は無く、パスにワイルドカードなどの特殊記号を交えたものをそのまま与えます。以下が特殊記号です。

記号 意味 使用例 マッチするテキスト
*(アスタリスク) 0文字以上の任意の文字列(ワイルドカード) /home/*/lib /home/userA/lib,home/userB/lib
?(クエスチョンマーク) 任意の1文字 /img/img???.jpg /img/img001.jpg,/img/img002.jpg
¥(円マーク) 特殊文字のエスケープ(windows) /post.php¥?* /post.php?post=””
\(バックスラッシュ) 特殊文字のエスケープ(windows以外) /post.php\?* /post.php?post=””
[](角括弧) 括弧内に現れる文字のどれか /te[xs]t.php /text.php,/test.php
-(ハイフン)を使った応用編 /te[a-z]t.php /tebt.php,/tewt.php(aからzまで可)
-(ハイフン)を使った応用編 /te[1-9]t.php /te3t.php,/te8t.php(1から9まで可)
!(エクスクラメーション)は角括弧中で否定の意 /te[!nx]t.php /test.php(text,tentはダメ)

正規表現と細かな違いがありますね。アスタリスクがワイルドカードの役割をするという辺りは、CSSやSQLの熟練者にはわかり易くて良いのではないでしょうか。
書式と定数の説明です。

glob(引数1,引数2);
引数1 : パターン文字列
引数2 : オプションフラグ。省略可。以下の定数をとり得る。
GLOB_MARK ディレクトリがマッチした場合最後にセパレータのスラッシュを付加
GLOB_NOSORT 結果として返す配列をソートしない(デフォルトはソート)
GLOB_NOCHECK マッチする結果が無かった場合、検索パターン自体を返す
GLOB_NOESCAPE エスケープ文字(¥,\)を機能させない
GLOB_BRACE {パターン1,パターン2,パターン3}という指定で複数パターンの検索をする
GLOB_ONLYDIR マッチしたディレクトリのみを返す
GLOB_ERR エラー時に停止する(PHP5.1.0以降)

正規表現における|(パイプライン)でのOR検索は、引数にGLOB_BRACEを与えた上で{}(中括弧)で括って行う必要があります。

preg_grepを使った例では、読み込んだファイル名を一旦ユーザインターフェースに出力しユーザに選ばせるという、完全にリモートのWEBサービスを意識した構成でした。それに対してglobの強みが生きてくるのは、ディレクトリに追加されたファイルを強制的に全部読み込んで表示するといった、ユーザの了解を省略したローカルディレクトリベースのアプリケーションなのかもしれません。


PHPのPCRE関数を使ってデイレクトリ内の特定拡張子ファイルのリストを取得する

前回解説した正規表現とPCRE関数を使って、ディレクトリ内の特定の拡張子のファイルのリストを作成してみます。今回のサンプルプログラムではさらに、特定の拡張子のファイルリストを得た後それをhtmlのselect要素に整形することで、ユーザにファイルを選ばせて処理を加えるWEBアプリケーションのインターフェース部分という体裁で書いてみようと思います。

<?php
$dir = "sample";
echo '<form name="" method="" action=""><select name="">';
foreach(preg_grep("/\.jpg$/",scandir($dir)) as $val){
echo '<option value="'.htmlentities($val,ENT_QUOTES,"UTF-8").'">'
.htmlentities($val,ENT_QUOTES,"UTF-8").'</option>';
}
echo '</select></form>';
?>

ディレクトリを検索してその中の拡張子が.jpgのファイルだけ抽出し、セレクトメニューの項目名にしています。ここで使っている関数は、引数2に配列を与えると引数1に与えた正規表現文字列にマッチする要素だけを配列に格納し、返り値として返すpreg_grepという関数です。

preg_grep(引数1,引数2,引数3);
引数1 : 正規表現文字列(前後にスラッシュで囲む)
引数2 : 抽出元の配列
引数3 : 反転フラグ。PREG_GREP_INVERTを与えると、条件にマッチしない要素の配列を返す。省略可。

例では引数2にscandir関数を与えて、ディレクトリ内の全てのファイル/ディレクトリ名を配列にしています。その中からの拡張子.jpgの抽出を、preg_grep関数が担当するようになっています。
この例ではあまり問題になりませんが、preg_grep関数には気をつけなければならない点が一点あります。返り値として返す配列の添字が、引数に与えた元の配列のものを継承するということです。つまり配列の添字が必ずしも連番になっていないので、添字をカウントアップさせて要素をひとつずつ取り出すループとは上手く連携できません。そうした処理と連携する場合は、array_values関数を使って配列の要素に連番の添字をアサインし直しましょう。


PHPで正規表現による文字列のパターンマッチングを行う

文字列の中の特定のパターンを検索する方法として、正規表現というものがあります。以前、.htaccessでディレクトリ内の特定文字列を含むファイルへのアクセスを禁止する方法について書いたエントリの中で少し触れたのですが、今回は書式のおさらいと、PHPでの対応する関数の紹介を行います。

まず正規表現の書式ですが、以前紹介したエスケープ(\または¥)、行頭(^)、行末($)、または(|)の他にもいくつか記号があるので、一覧表にしてみます。

記号 意味 使用例 マッチするテキスト
.(ピリオド) 任意の一文字(ワイルドカード) te.t text,tent,te!t
*(アスタリスク) 直前の文字の0回以上の繰り返し te.*t text,texxxt,tet
+(プラス) 直前の文字の1回以上の繰り返し te.+t text,texxxt
?(クエスチョンマーク) 直前の文字が0または1回登場 te.?t text,tet
¥(円マーク) 特殊文字のエスケープ(windows) te¥.¥?t te.?t
\(バックスラッシュ) 特殊文字のエスケープ(windows以外) te\.\?t te.?t
^(カレット) 行頭 ^\.ht .htaccess,.htpasswd
$(ドル) 行末 nt$ tent,plant
[](角括弧) 括弧内に現れる文字のどれか te[xyz]t text,tezt
-(ハイフン)を使った応用編 te[a-z]t tebt,tewt(aからzまで可)
-(ハイフン)を使った応用編 te[1-9]t te3t,te8t(1から9まで可)
^(カレット)は角括弧中で否定の意 te[^nx]t test(text,tentはダメ)
()(丸括弧) グルーピング (te)*xt xt,text,tetext
|(パイプライン) または ([a-z]|[1-9])* aaa,a,9999

PHPで正規表現によるマッチングを行うには、PCRE関数というPerlでの正規表現に準拠した関数群を使います。preg_という接頭辞がついたこの関数群に正規表現のパターンを与える時は、パターン文字列の前後をスラッシュで囲むという約束事があります。基本的なpreg_match関数で例を見てみましょう。

<?php
$teststring = "Hello!";
preg_match("/He[a-z]+/",$teststring);
?>

引数1にパターン文字列、引数2に検索対象を与えています。引数3、4、5もあるのですが、省略可です。 パターン文字列にマッチしたものがあると、preg_match関数は返り値として1を返します。ちなみに返り値として検索がマッチした回数を返してくれる、preg_match_allという関数もあります。
その他、検索置換機能を備えたpreg_replaceや、配列を精査してくれるpreg_grepという関数もありますので、それらの活用例もいずれエントリでアップできればと思っています。


サイトC:000webhostで頻発するwordpressアップデートエラーを回避する

このサイトで契約しているサーバ000webhostですが、無料であることに加え、MySQLのデータベースを2つ使える、設定パネルとしてcPanelの簡易版が使える、広告が入らない、商用可などのメリットがあり、他と比べてもかなり条件の良いサーバであると個人的に思っています。特にMySQLデータベースが2つまで使えるということは、データベースをまるまる1つ占有してしまうwordpressなどのCMSを気兼ねなく導入できるということで、CMSの実験用として契約するユーザも多数存在するのではないかと予想できます。

一見するとCMSとの相性が良さそうに見えるこのレンタルサーバでは、しかしCMS自体の機能を使ったアップデートやプラグインの導入などの際に、エラーが発生してしまうということが多々あります。加えて、ユーザのアップロードが成功せず何回もトライを続けていると、負荷の低減のためかあるいは不正使用と誤認してなのか判りませんが、当該IPアドレスでのアクセスをしばらく禁止されてしまいます。それにより総合的なユーザエクスペリエンスが最低となり、実験用に契約した場合でも、「このサーバは使えない」という結論に繋がってしまう可能性があります。
そこで、そもそもこういった負の連鎖の原因となるアップロード失敗の原因を突き止め、問題を回避する方法を考えましょう。
結論から言いますと、アップデート時のエラーが頻発するのはサーバ側でPHPプログラムの動作を制限して軽い処理しかできないようにしているためで、000webhostのように.htaccessが使える場合、あるいは他の安サーバでもphp.iniが使える場合、それらの設定を変更することでエラー回避の道が開ける場合もあるというのは知っておいて損のない知識です。具体的に000webhostの場合で見てみましょう。

まずはphpinfo関数でphpの設定を調べて下さい。エラーに関係する項目のみ挙げますが、000webhostの場合デフォルトでmax_execution_timeが10、memory_limitが64M、post_max_sizeが2M、upload_max_filesizeが2Mとなっています。それぞれPHPスクリプトがタイムアウトになるまでの秒数、確保するメモリ容量、POSTで渡せる最大ファイルサイズ、アップロードできる最大ファイルサイズを表しています。
参考までに、サイトAのために契約しているWebhostingPadではこの値が30、128M、8M、2Mになっています。つまり000webhostではこれらの値を厳し目に設定することで、無料ユーザを総合した負荷を抑えるようにしているというわけのようです。
これらの変更を、ユーザのルートディレクトリに置く.htaccessで行いましょう。具体的には、max_execution_timeを少し長めの180秒程度にする、postやuploadのmaxサイズをmemoryリミットの制限内で上げるなどです。.htaccessまたはphp.iniでどのように書けばよいかは、少し前に上げたエントリ(php.ini.htaccess)を参考にして下さい。あまり詳しく言及すると000webhostの勘気に触れるのではないかという個人的な危惧があります(笑)

少しの欠点がありますが、対処を覚えれば便利なサーバーですね。いちユーザの意見としてポジティブに評価しておきましょう。

(2012.6追記:勝手にページを非公開にされ、コンテンツの返還に有料契約を要求された事件があり、現在このサーバは他人にお薦めしません。少し詳しい経緯などはこちら


GDで透過ありインデックスカラー画像を合成すると

前回エントリではインデックスカラー画像の合成を試みました。サンプルプログラムのように周囲に余白が無い画像の場合には期待通りの合成ができるのですが、たとえばimagerotate関数で45度傾けた長方形の場合などには、合成する画像の周囲の余白を透明部分として処理する方法が無く暗幕を被せたようになってしまいます。これは前回も触れたとおりです。
ではGDライブラリがインデックスカラーの透過色に全く対応していないかというと、そういうことでもないようです。画像編集ソフトで透過色ありの設定で保存したPNGを読み込むと、それはそのまま透過色ありの画像として扱われ、他の画像に合成しても透過の設定は残ります。例で見てみましょう。

<?php
$img = imagecreate(200,200);
$colour1 = imagecolorallocate($img, 255, 0, 255);
$colour2 = imagecolorallocate($img, 100, 90, 100);
imagefilledrectangle($img, 0, 160, 200, 200, $colour2);
$img2 = imagecreate(200,20);
$colour4 = imagecolorallocate($img2, 255, 255, 255);
$img3 = imagecreatefrompng("test.png");
$img4 = imagerotate($img4,90,$colour4,0);
imagecopy($img,$img4,10,0,0,0,150,200);
header('Content-Type: image/png');
imagepng($img);
imagedestroy($img);
imagedestroy($img2);
imagedestroy($img3);
imagedestroy($img4);
?>

前回とほとんど変わらないプログラムです。新しく出てきた関数imagecreatefrompngはPNG画像を読み込んだキャンバスを作成するもので、他にimagecreategifなどもあるというのはご想像のとおりです。引数に同ディレクトリ内の透過色ありPNGを指定したのですが、結果このように表示されました。

(この部分の画像は000webhostに接収されてしまいました)

判りにくいのですが、元の透過PNGの透過設定が保持され、合成先画像のピンクの背景を突き抜けて透過しています。傾いた長方形の周囲の白い部分は、黒字の背景のページに設置すると黒になります。つまり合成先画像のカラーパレットと、透過色設定を含んだ合成元画像のカラーパレットを単純に結合したためこういう事態になっているということのようです。ちなみにimagerotate関数の引数4を0以外にしても見え方は変わりませんでした。
こうした仕様もあり、GDで複数の画像を合成して画像を作成する場合には、フルカラー画像を前提にしてプログラムを書いた方が予期せぬ壁に阻まれることがないだろう、という教訓も得られようというものです。