Windows版PHPでSFTP接続するにはphp_ssh2.dllが必要
Web運営のメンテナンス作業(ローカルへのバックアップ作業とか)を自動化する方法について4回にわけて記事にしてきました。
1.~3.については直接的な解決策の提示でしたが、4.についてはこの記事の前準備みたいなもので、レンタルサーバーを借りてWeb運営しつつ、サーバー上のファイルをバックアップしてDLするなら、受け取り側はWindowsが多いよねってことで、PHPをWindowsで使う準備の話しでした。
そしてその準備が整ったはずなので、今度こそPHPでSFTP接続する具体例です。
そんなことしなくても3.で紹介したpsftp.exeを使えば良いんですけどね。
……ロマンです。
PHPでSFTP接続するサンプル
コード自体はとっても簡単です。
PHPサンプル
<?php
//設定
$host = "e1.valueserver.jp"; //接続先サーバー
$user = 'ユーザー名'; //ユーザー名
$pass = 'パスワード'; //パスワード
$target_path = '/virtual/ユーザー名/backup/';
$target_file = 'backup.zip';
//SSHで接続
$conn = ssh2_connect($host, 22);
//認証
if(!ssh2_auth_password($conn, $user, $pass)) {
echo 'Login Failed';
exit();
}
//SFTP接続を行う
$sftp = ssh2_sftp($conn);
//リモートファイルを読み込み
$data = file_get_contents('ssh2.sftp://'.$sftp.$target_path.$target_file);
//ローカルへ書き出す
file_put_contents($target_file, $data);
//サーバーから切断
ssh2_disconnect($conn);
?>
例によって、$host、$user、$pass、$target_path、$target_fileは借りているサーバーの種類によって適宜変更してもらうとして、SFTP接続の部分は実に単純です。
(簡略化のためエラー処理も認証失敗くらいしかチェックしていないので要修正)
PHPでのSFTP接続の流れ
- ssh2_connect関数でSSHサーバーへ接続
- ssh2_sftp関数でSFTPサブシステムを初期化
- file_get_contents関数で対象ファイルをメモリに読み込み
- file_put_contents関数でメモリに読み込んだデータをローカルファイルに書き出す
- ssh2_disconnect関数でSSHサーバーから切断
ここでポイントなのはssh2_sftp関数で、この関数が返すのは "Resource id #ほにゃらら" とかの文字列なのですが、これを使うことで、
ssh2.sftp://[SFTPのリソースID]/virtual/[ユーザー名]/backup/backup.zip
といったURL表記でSFTP上のファイルにアクセスできるようになります。おかげでfopen等のファイル操作系の関数がそのまま使いたい放題。
……とまぁ、コード自体は簡単なんですけど、はじめての人は準備で悩むかも知れません。
SSH関数を使うならphp.iniの書き換えが必要
PHP7.2に同梱されている拡張機能、例えばgd2(画像処理につかう拡張機能)とかなら、c:\php\php.ini に書かれている ;extension=gd2 の頭のセミコロンを外すだけで使えます。これは c:\php\ext\ の下に php_gd2.dll が存在するからなんですね。
同様にssh2を使うためには extension=ssh2 の記述が必要なのですが、デフォルトでは書かれていないため、下記のように追記する必要があります。
ただ、これだけで上述のスクリプトを実行してもエラーが発生して動作しません。
'ssh2'というダイナミックライブラリがロードできませんでしたよ、と言っているんですね。そりゃロードできません。だって、ないもの。
Windows上でSSH関数を使うならphp_ssh2.dllも必要
件のssh2のダイナミックライブラリがどこにあるのかっていうと、ここです。
https://pecl.php.net/package/ssh2
Available Releasesの下のDownloadの下に ssh2-1.1.2.tgz とかありますが、それはソースなのでLinux用です。ありがたいことにその右横にDLLと書かれたリンクがあるのでそちらをクリックします。
すると、このページが開くのですが……
https://pecl.php.net/package/ssh2/1.1.2/windows
PHP 7.2用のDLLがない。なぜ…。PHP 7.2がリリースされてからそろそろ1年近く経つというのになんでか7.2用のDLLがありません。
まぁ、7.1用のでも動くのかな?と淡い期待を込めて試してみたのですが……
(コンパイルされた)バージョンが違うよっ!と出て実行できません。
2019年1月追記:
この記事を書いた2ヶ月後に確認したところ、PHP 7.2用のphp_ssh2.dllも無事本家にアップロードされていました。よかったよかった。
2020年6月追記:
PHP7.3とPHP7.4用のphp_ssh2.dllはこちらからどうぞ。
https://pecl.php.net/package/ssh2/1.2/windows
えー…… Visual Cインストールしてソースからコンパイルしなきゃいけないの?
少し検索すればWindowsでPHP拡張モジュールをコンパイルする方法は出てきますが、けっこう面倒くさい。
もう1年も経つんだし、誰かしらコンパイルしてphp_ssh2.dllを公開している人いるんじゃないかなーと、探してみたら、いましたよ、神が。
stackoverflowの掲示板
https://stackoverflow.com/questions/51157868/missing-php-ssh2-dll-extension-for-php-7-2-windows
神がアップロードした php_ssh2.dll
https://github.com/nufue/pecl-ssh2-windows
本家php.netにないなら、自分でコンパイルするのが一番安全なのでしょうけれど、ぼくは面倒なのでありがたくこのDLLを使わせて頂くことにしました。
先程、php.ini に extension=ssh2 という記述は完了していると思うので、今度は php_ssh2.dll を c:\php\ext ディレクトリへコピーするだけです。
ちなみに、Non Thread Safeの場合、php_ssh2_nts.dll というファイル名になっているので、これを php_ssh2.dll にリネームしてください。
大きいファイルをDLする場合はfreadを使おう
先述のPHPサンプルでは file_get_contents を使ってZIPファイルをDLしましたが、PHPが使えるメモリは限られていますし、そのサイズを超えるファイルを読み込もうとすればエラーになります。
また、ひとつのファイルだけではなく複数、しかも拡張子がzipのファイルだけDLしたい、という要望もあると思いますので、PHPサンプルを書き換えてみました。
PHPサンプル
<?php
//設定
$host = 'e1.valueserver.jp'; //接続先サーバー
$user = 'ユーザー名'; //ユーザー名
$pass = 'パスワード'; //パスワード
$target_path = '/virtual/ユーザー名/backup/';
$download_path = 'c:/test/';
//SSHで接続
$conn = ssh2_connect($host, 22);
//認証
if(!ssh2_auth_password($conn, $user, $pass)) {
echo 'Login Failed';
exit();
}
//SFTP接続を行う
$sftp = ssh2_sftp($conn);
//リモートのディレクトリを開く
$handle = opendir('ssh2.sftp://'.$sftp.$target_path);
while (false !== ($entry = readdir($handle))) {
//zipファイルだけダウンロード
if (substr($entry, -4) == '.zip') {
downloadFile($entry, 'ssh2.sftp://'.$sftp.$target_path, $download_path);
}
}
closedir($handle);
//サーバーから切断
ssh2_disconnect($conn);
/************************************************
*ファイルをダウンロードする
************************************************/
function downloadFile($filename, $path1, $path2)
{
$fp1 = fopen($path1.$filename, 'r');
$fp2 = fopen($path2.$filename, 'w');
while ($buff = fread($fp1, 8192 * 10)) {
fwrite($fp2, $buff);
}
fclose($fp1);
fclose($fp2);
}
?>
PHPでのSFTP接続の流れ(メイン部分)
- ssh2_connect関数でSSHサーバーへ接続
- ssh2_sftp関数でSFTPサブシステムを初期化
- opendir/readdir関数で対象ディレクトリのファイル名を順番に取得
- ファイル名の末尾が '.zip' に該当するファイルだけダウンロード処理(次項)を行う
- ssh2_disconnect関数でSSHサーバーから切断
PHPでのファイルダウンロード処理の概要
- fopen関数にSFTP用のURLを渡してリモートのファイルを開く
- fopen関数を使い、DL対象のファイル名と同名の空のローカルファイルを作成
- fread関数で8192×10バイトずつリモートファイルを読み込む
- fread関数で読み込んだデータをfwrite関数でローカルファイルに書き込む
- fread関数で読み込むデータがなくなったら戻る
極単純なfopen~fread~fwriteの組み合わせです。
ssh2の拡張機能でfopen系の関数をラッパーしてくれるので、普通のファイル操作と変わらずにコーディングできて便利ですね。
尚、fread関数の読み込みバッファー8192×10バイトは適当です。デフォルトが8192と少なすぎるので10倍くらい指定しておくか、という適当っぷり。バッファーが少ないとその分DLも遅くなるので、メモリに余裕があるなら100MB分くらい指定しても良いかも。
まとめ
PHPでSFTP接続するサンプルを書いておしまいのハズが、DLLのところでちょっと躓きました。
検索すれば情報があふれだす世の中ですが、PHPみたいにバージョンアップが早く、仕様もどんどん変更されていく技術だと検索で見つかった情報もどこまで正しいかわかりません。php_ssh2.dllの件を探しているときも、regsvrでDLLを登録しないといけないという情報があったり(実際は不要)別の場所からlibssh2.dllを持ってこなければいけないと書いてあったり(実際はこれも不要、というかphp7.2には同梱されている)、情報があふれすぎて正しいものを見つけるのが難しくなってきているようです。
2018年11月現在においては、Windows版PHPでSFTP接続をするためには
- php.iniにextension=ssh2を追記
- php_ssh2.dll を c:\php\ext へコピー
ここを読んでいるあなたが2018年11月よりずっと未来の人で、「なんだよ、この古い情報!」とがっかりされていないことを祈ります(*´ω`*)