phpでftp

他システムとの連携などで、データファイルをFTPしたい時があるのではないでしょうか。
私はあります。

 

以前はシェルの中でftpコマンドファイルを出力し、それをftpコマンドの入力にして実行していたのですが、phpにはFTP関連の関数も用意されていて、とても便利です。
http://php.net/manual/ja/ref.ftp.php

と、思ったけれどあんまりうまくいかない。
環境によってうまくいったりいかなかったり。

うまく行く場合は当然ながら正常に接続できて、ファイルも正常に送信されるのですが、うまくいかない場合はftp_put()がなかなか終了せず、やっと終了したと思ったら0バイトのファイルができています

VPSの環境ではうまくいきましたが、とあるレンタルサーバーでは上記のような状態です。
どちらの環境でもftpコマンドでは正常にファイル送信でき、処理の内容も以下の同じプログラムです。

$conn_id = ftp_connect(FTP_SERVER);
$login_result = ftp_login($conn_id, FTP_USER, FTP_PASS);
$result = ftp_chdir($conn_id, DOMAIN_NAME);
if($result === false){
    $result = ftp_mkdir($conn_id, DIRNAME_NAME);
    if($result === false){
        echo date('Y/m/d H:i:s')." [ERROR]\n";
        exit(1);
    }
    $result = ftp_chdir($conn_id, DIRNAME_NAME);
    if($result === false) {
        echo date('Y/m/d H:i:s') . " [ERROR]\n";
        exit(1);
    }
}

// ftp put file
$result = ftp_put($conn_id, $web_file, $dist_dir.'/'.$web_file, FTP_BINARY);
if ($result === false) {
    echo date('Y/m/d H:i:s')." [ERROR]\n";
    exit(1);
}

上記ソースの中のftp_putで延々と待たされた挙句、

PHP Warning: ftp_put(): Failed to establish connection. in /home/barbwire/backup/exec_backup.php on line 103

といったエラーで終了し、FTP先には0バイトのファイルが作成されています。
どうしたらいいのか。
とりあえずftp_pasv()を実行してPASVにしてみたりしても、根拠もなくPASVにしてみただけなので特に変化はありません。

ftp_put()という関数を使わずに、FTPコマンドを直接記述できるftp_raw()という関数もあるので、そちらを利用してみましたが、これも結果は全く変わりません。
同じftpのライブラリを使っているので、当たり前といえば当たり前なのかもしれません。




ここまででかなり時間を使ってしまったのでphpの関数を使う方法はあきらめ、system()でftpコマンドを発行する、もっと言えばftpコマンドでファイルのコマンドを実行させる従来の方法に立ち返ってみました。

ftpコマンドにファイルの内容を実行させるとはどういうことなのかは、以下のとても古い記事などが参考になります。
ftpなどは枯れた技術なので、まったくそのままで動きます。
http://www.itmedia.co.jp/help/tips/linux/l0462.html

気持ちも新たにphpのプログラムを書き換えました。

$ftp_com = array();
$ftp_com[] = 'open '.FTP_SERVER."\n";
$ftp_com[] = 'user '.FTP_USER.' '.FTP_PASS."\n";
$ftp_com[] = 'mkdir '.DIRNAME_NAME."\n";
$ftp_com[] = 'cd '.DIRNAME_NAME."\n";
$ftp_com[] = 'lcd '.$dist_dir."\n";
$ftp_com[] = 'put '.$web_dump_file."\n";
$ftp_com[] = 'bye'."\n";

file_put_contents(BACKUP_DIST_PATH.'/'.DIRNAME_NAME.'.ftp', $ftp_com);
$result = system('ftp -n < '.BACKUP_DIST_PATH.'/'.DIRNAME_NAME.'.ftp');
if($result === false){
    echo date('Y/m/d H:i:s')." [ERROR]\n";
    exit(1);
}

サクっと動きました。
しかもコードも短く、分かりやすい。
この方法の欠点は、ftpコマンドの途中どこかでエラーになったことをキャッチできないということです。
そのためには、ftpコマンドの出力内容をチェックする必要があります。
system()関数ではなくpassthru()などを使って出力を受け取ることで実現できそうです。

何というか、phpじゃなくてシェルでやれば良かったんじゃないでしょうか。

 

ftp_put()でお悩みのあなた、参考にしてください。