<< Prev Page Next Page >>

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。


PHPでSFTPが遅い時の対処法

PHPで、よそのサーバにSFTPで接続して転送して…ということをやりたかった。SCPが使えない相手サーバなので、あくまでSFTP。

普通に書くとこうなる。(エラー処理は全部省略して)

// 接続してー
$connect = ssh2_connect($dstHost, 22);
// ログインしてー
ssh2_auth_password($connect, $username, $password);
// 謎の文字列にしてー
$sftp = ssh2_sftp($connect);
$sftpPath = 'ssh2.sftp://' . $sftp . '/' . $dstPath;
// copyで転送
copy($srcPath, $sftpPath);

簡単で助かるなあPHP。と思ってたら、これがびっくりするほど遅かった。100MB送信するのに2〜3分かかる始末。

「まーね、相手のSFTPサーバが遅いからしょうがないんですよこーいうのはー」とか言いつつ、ためしにLinuxシェルから普通にsftpコマンド叩いてputしてみたら、100MBの転送が5秒ぐらいで終わった。なんじゃそら!

そりゃああんまりだ。copyじゃなくてfreadとfwriteを使ったりしてみたけど変わらず。とにかくPHPのSFTPラッパーが遅いんだ多分。

しかたがないので、シェルスクリプトを作った。

#!/bin/sh

HOST=$1
USER=$2
PASS=$3
SRC=$4
DSTDIR=$5

expect -c "
set timeout 30
spawn sftp $USER@$HOST
expect \"password:\"
send \"${PASS}\r\"
expect \"sftp>\"
send \"cd ${DSTDIR}\r\"
expect \"sftp>\"
send \"put ${SRC}\r\"
expect \"sftp>\"
send \"bye\r\"
"

んで、PHPから呼ぶ。

exec("/path/to/script.sh {$host} {$user} {$password} {$srcPath} {$dstDir}");

すんごい速くなった。いや、ほんと、こんなことしてダメなのに、さすがに20倍の速度差となると、ダメなこともしなきゃいけなくなるという。


スポンサーサイト

PHPでコンストラクタが何度でも呼べる件

あ…ありのまま 今 起こった事を話すぜ!
「PHPでコンストラクタ何回も呼べた」
な… 何を言ってるのか わからねーと思うが おれも何をされたのかわからなかった

新しく入ってきた子に教えてたんですよ。で、
「このコンストラクタってなんのためにあるんですか。普通の関数と何が違うんですか」
と聞かれたんで(なかなか賢い有望な子です)、たぶんいろんな理由でこの仕様が生まれたんだろうけど、僕が思うに一番便利というかこれがないと困るっていうのは、やっぱりコンストラクタ引数を利用してImmutableなオブジェクトを作れるっていうことかな。たとえば、


class MyClass {
private $name;
function __construct($name) {
$this->name = $name;
}

function getName() {
return $this->name;
}
}
こういうクラスを作ると、このクラスは生成-初期化時にname変数が設定された後は二度とnameを変えられないので、Immutable(変更できない)って呼ばれるんだよ。こういうクラスは他のソースコードから取り扱いが簡単になるので設計の複雑さを多少軽減する効果があるんだ。

と、教えていたのです。で、「じゃあ実演してみるね」と軽快に実行コードを作って

$a = new MyClass("Alice");
echo $a->getName(), "\n";
$a->__construct("Bob2");
echo $a->getName(), "\n";
「まさかこんな書き方はできないわけでさ」と言いながらッターン!とF5(実行)キーをタイプしたんです。

Alice
Bob

は?エラーじゃないの?

頭がどうにかなりそうだった…

おそろしいなPHP!まさかそういうことになってるとはな!PHPには色々驚かされてきたけれど、これもかなりの大ネタだったな!

コンストラクタってただの関数みたい。ただちょっとnewのときに自動で呼ばれるってだけで、あとは他の関数と何も変わらないよ。

っていうのは嘘で、こういう呼び方できるけど、うちの社内では絶対やっちゃダメだからね。絶対だよ!と教えましたとさ。


CentOS5.5でHudsonでPhingでPHPUnitでCloverな生活

HudsonいいねHudson。Jenkinsに名前変わったみたいなのでまた後で乗り換えるけれども。

昔某ソフトハウスにいた頃、まだmavenもなかった頃、頑張ってantを駆使してJUnitとJCoverageをデイリービルドで回して計測してエラーがあったらみんなにメールが飛ぶって、そういう環境を自分でも作ったし使っていたのに、今の会社で全然そういうことをしていないのが嫌で嫌で。

そしたらPHP用のAntであるところのPhingもあるしデイリービルドをやってくれるHudsonもあるし、これはと思って1日頑張ってみた。OSはCentOS5.5。

1.CentOS5.5にPHP5.3を入れる

CentOS 5系でPHP5.3系やMySQL5.1系を使いたい場合 - RX-7乗りの適当な日々
こちらのサイトを参考に、--enablerepo=remiで
  • php
  • php-devel
  • php-pecl-xdebug
  • php-mbstring
  • php-pdo
といろんなものを全部入れる。xdebugはコードカバレージ計測のために必須。yumのおかげで便利な世の中だよ。

2.PHPUnitをインストール

# pear install phpunit/PHPUnit

3.phingをインストール

# pear channel-discover pear.phing.info # pear install phing/phing
ここで足りないものがあれば入れておく。

4.ちょっとbuild.xml書いてみる

<?xml version="1.0"?> <project name="minfew-set" default="test" basedir="."> <target name="lint"> <phplint> <fileset dir="src"> <exclude name="includes/mfwork/**" /> <exclude name="includes/PEAR/**" /> <exclude name="includes/smarty/**" /> <include name="**/*.php"/> </fileset> </phplint> </target> </project>
で、
$ phing
おお、実行された。よしよし。

5.Hudsonインストール + tomcatにデプロイ

rpm一発でいけた。あっけなく起動も成功。すごい。でもサービス起動しようとするとなんか固まっちゃって怪しいのでtomcatに載せることにした。

/etc/tomcat6/tomcat.conf に、
HUDSON_HOME = /var/lib/hudson
を追記。hudson.warをtomcat6にデプロイしていっちょあがり。
あと、hudson単体で一度起動している場合、/var/lib/hudsonのownerがhudsonになっているので、
# chown -R tomcat.tomcat /var/lib/hudson
しないとビルド実行でハマる。

6.ジョブを登録

俺プロジェクトをHudsonに「新規ジョブ登録」する。もちろんプロジェクトはいくつも登録することができる。
プロジェクトの設定でSVNのURLを記入して「ビルド実行」・・・うんもうすごい。えらい。「ワークスペース」にソースコードをチェックアウトしてくれている。

7.xUnit、Clover、phingプラグインをインストール

HudsonでPHPのユニットテスト - ssogabeの日記
HudsonでPHPメモ (1) PHPUnitとカバレッジ - ssogabeの日記
こちらのサイトを超、超参考に!というかほとんどこのまま。Hudsonの画面上でプラグインをインストール。プラグイン画面でチェックしてボタン押すだけの簡単なお仕事。
なお、上記参考サイト様ではphpunitをexecで実行しているけど、その後、PHPUnitTaskがclover対応してくれたので、ちゃんとphpunitタグで書いてスムーズにいけます。

8.プロジェクトの設定

hudson project1
定期実行する設定と、Phing呼び出し設定。
ちなみに、build.xmlのphpdocターゲットはこんな感じ。APIリファレンスが毎日どんどん作られていくよ!
<target name="phpdoc"> <phpdoc title="SAT Library Documentation" destdir="apidocs" sourcecode="false" output="HTML:Smarty:PHP"> <fileset dir="src"> <include name="includes/minfew/**/*.php" /> <include name="includes/pmi/**/*.php" /> <include name="includes/satlib/**/*.php" /> </fileset> <projdocfileset dir="."> <include name="README" /> <include name="INSTALL" /> <include name="CHANGELOG" /> </projdocfileset> </phpdoc> </target>

testターゲットはこう。カバレージ出力のためにcoverage-setupも必要みたい。
<target name="clean"> <delete dir="reports" includeemptydirs="true" /> <delete dir="clover" includeemptydirs="true" /> </target> <target name="coverage" depends="clean"> <mkdir dir="reports/tests" /> <mkdir dir="clover" /> <coverage-setup database="./reports/coverage.db"> <fileset dir="src/includes/"> <include name="**/satlib/classes/*.php"/> <include name="**/minfew/classes/core/*.php"/> <include name="**/minfew/classes/support/*.php"/> <include name="*.php"/> </fileset> </coverage-setup> <phpunit haltonfailure="true" printsummary="true" codecoverage="true"> <formatter todir="reports" type="clover"/> <formatter todir="reports" type="xml"/> <batchtest> <fileset dir="./test"> <include name="*Test.php"/> </fileset> </batchtest> </phpunit> <coverage-report outfile="reports/coverage.xml"> <report todir="clover" /> </coverage-report> </target>

で、phpunitとcloverのレポートを結果画面に出力させるためのオプションを設定。うわあこんな簡単なのかー。
Hudson project2

ここまでの設定で、こんな画面ができあがる!ブラボー!
hudson

いや、まあ、ここまでくるまでにはエラーメッセージと格闘しながらいろいろ頑張った。基本的にはエラーメッセージをよーーく見て、「これをするには何かが足りない」と言ってるので、それをインストールするという流れ。yumとpearをひたすらたたき続ける。あと、PHPソースコードのrequireとかにエラーがあると、カバレージがコケるので、ソースコードも直し直ししながらの道のりでした。ああ、これでやっとUnitTestをどんどん書いていくだけの気持ちいい世界にたどり着いた。


API考

最近Symfonyを触っているのですけどね。
xoopsのときも思ったしCakeのときも思ったしZend frameworkのときも思ったのだけど、
なんでそんなにクラス名とかメソッド名とか長いのみんな。
マゾなの?

あと、たかが<input>タグを表示するためだけになんでタグより多い文字数書かなきゃならんのか意味がわからない。フレームワークって楽をするためのものじゃなかったのか。

文字数を比較してみよう!

Symfonyの場合

$w = new sfWidgetFormInputText(); $w->setAttribute('maxlength, 100); $this->widgetSchema['email'] = $w; $this->setDefault(array('email' => $email)); ----- <?php echo $form['email'] ?>

PHP直書きの場合(eっていうecho htmlspecialchars()な関数を一つ作っておく)

<input type="text" name="email" value="<?php e($email)?> maxlength="100">

僕がSmarty pluginとして作った「input」利用の場合

{input type="text" name="email" value=$email maxlength=100}

ちなみにこの「input」優れもので、{input type="date"}とすればカレンダーつきの日付入力になるし、{input type="int"}とすれば整数値だけの入力になるし、{input type="checkbox" options=$options}とすればチェックボックスたくさんになるし、{input type="select" options=$options}とすればドロップダウンになるし、{input type="html"}とすればnicEditになる。ほかにもいろいろなる。

Smartyにはもともとhtml_optionsとかあるけどね。長いねん、名前が。

日々、楽をする方法を模索しています。もっと楽しようよ!もっと文字数少なくいこうよ!そう思わないか。(誰)


フレームワークの知見

さて自分で作って自分で仕事に使っているPHPのWebフレームワーク、目的はただ一つ、

コードを一行でも少なくすることにより、早く帰る+家で徹夜しない
という崇高なものなのに、ムチャな納期でもギリギリ請け負えてしまうため結局家で仕事する羽目に陥ってるという。いや、でも、ここは改善していけるはず。ていうか改善しないならわざわざ会社やってる意味がない。

さて、一般公開に向けて水面下でチマチマ準備しているけれど、それに先がけてというかなんというか、作って使ってみて「ああ、なるほど」と思ったWebフレームワーク一般に応用可能かもしれないことをいくつか。

・DB規約は絶対必要
 RDBを使うアプリケーションなら、DB規約と、規約に従ったテーブルを便利に扱うクラスは絶対必要。「どんなテーブルでも扱えるように」肥大化したクラスなんか邪魔。規約に従ってないテーブルには、SQL直打ちで対処。もちろん、PEAR:MDB2みたいな抽象化レイヤーはありがたーく使わせて貰ってます。
RDBの定義ルール - とっくりばー


・一覧画面をいかに楽にするか
 一覧画面をなるたけ楽に作れるようにする仕掛けはAPIのセンスの見せ所。


・一つのファイルで複数の画面・機能=幸せ
 たとえば「編集画面」と「DB登録処理」みたく、関連する2つの画面で共通点が多いとき、「1ファイル1画面」だったら、共通処理をまた別のファイルに書いて3ファイル必要になる。「1ファイル複数画面」だったら、共通処理は単にメソッドを一つ増やすだけでいいので見通しも良いし楽ちん。


・WHERE節を生成するAPIに凝る
 長いことかけて熟成してきたWHERE節生成メソッド。現在は
$where = DBUtil::whereCruise(array(
array(
'YEAR(modified_time) >=' => $yfrom,
'MONTH(modified_time) >=' => $mfrom
),
'OR name LIKE' => $db->wild($name), // %$name%になる。もちろん$name部はサニタイズする
));
なんてカンジでWHERE節が作れる。ここだけすでに別言語みたいに。でも文字列でゴリゴリ書くとSQLインジェクション対策とか、条件分岐のコードが汚くなったりとか、やっぱり良いこと無いからね。


・UPDATEとかINSERTとかDELETEとか書かないで済むようにする
MDB2_ExtendedのautoExecuteも長すぎて鬱陶しいからラッパーにしましょ。あと保存するレコードにidが入ってたらUPDATEで、入ってなかったらINSERTとか自動で判断するのもイカす。


・CSVダウンロード用クラスは一覧画面用クラスと似たAPIにする
結局の所、RDBになんかの条件でSELECT飛ばして帰った結果になにか細工して出すわけだから。


・viewのヘルパーに凝る
Smartyのhtml_optionsとかがすごい便利だってことは、ほかにも作れば便利になるものいろいろアルヨ。ってわけで、画像アップロード用ヘルパーとか、日付入力用ヘルパーとか、誕生日から年齢を算出するヘルパーとか、作ってみるとviewのコードがあら1行に収まるじゃないの。

年齢:{$person.birthday|age}歳
なんて書くとRDBに入ってた誕生日を元に年齢を表示するのです。


・キャッシュを利用する
PEAR::Cache_Liteモリモリ使わせていただいてます。マスターテーブルの内容をキャッシュしといていちいちRDBアクセスしないで中身を取り出すとかすごい便利。<select>タグを作るhtml_optionsに渡すための連想配列を一行で生成できるようにしてみた。
$prefOptions = MasterUtil::assoc('pref', 'pref_name')
なんて書くとprefテーブルからpref_idとpref_nameとってきた連想配列が得られるのです。キャッシュの有効期限は30分。マスタ編集画面なんかでは明示的にキャッシュを消すようにすればリアルタイム性も問題なし。


・フレームワーク自体を超薄型にする。
根幹に関わる部分はたぶん300行行ってない。で、規約とヘルパークラスを充実させる。フレームワークは必要最小限のことしかしない。


・出力形式ごとのメソッド名ルールは殊の外具合が良い
たとえば
http://hostname/member/edit_confirm.html
というリクエストで
(path)/member/.code/Edit.php

Member_Edit#confirm_html()
が呼ばれて、smartyテンプレート名を返せばHTML出力が走り、
http://hostname/member/edit_validate.json
というリクエストで
(path)/member/.code/Edit.php

Member_Edit#validate_json()
が呼ばれて、trueを返せばJS側の処理が続行される。

HTMLを表示するメソッドとバリデーションを行うメソッドが同じクラスに共存できて、なおかつeclipseのメソッド一覧が見やすくて、拡張子によって違う返値のルールを持てる。素晴らしい。
プログラムコードとURLの関係 - とっくりばー


・認証系に必要なメソッド、イベント
1. 「このページは認証が必要」と宣言するメソッド
2. 認証に関わりなく、必ず呼ばれるイベント(ここで上記1のメソッドを宣言すればアプリケーション全体に鍵をかけられる)
3. 「認証ユーザーがアクセスした」イベント
4. 「認証が必要だが認証されていない」イベント
5. 「認証が必要で、usernameとpasswordが送信され、認証成功した」イベント(その後3のイベントも発火する)
6. 「(同上)認証失敗した」イベント(その後4のイベントも発火する)


長くなってしまったけど、とりあえずこんなところ。



上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。