PHPプログラミング言語を使った「SFTPへの接続」、「ファイルのリストアップ」、「アップロード」、「ダウンロード」のやり方をここで習得しましょう。

SFTP は、データやファイルの安全な転送や共有のための標準的で安全なプロトコルです。どのような場合であれ、プログラムで SFTP サーバーに接続するのは簡単ではありません。次の投稿では、本格的な PHP ベースの SFTP クライアントを徐々に構築していき、このガイドの終盤には自分で全て出来るようになるでしょう。

必要事項

まずは、接続するための SFTP サーバが必要です。もしお持ちでなければ、SFTP To Goで30秒もかからずSFTPエンドポイントの取得ができます。

SFTP To Go with maximum security and reliability
SFTP To Go offers managed SFTP/FTPS/S3 as a service - maximum reliability, security, availability, with 1 minute setup. Great for companies of any size, any scale.
Try SFTP To Go now!

PHP には SFTP ライブラリが最初から含まれていないため、 PECL (ピクル:PHP Extension Community Library)を使って必要なライブラリ ssh2 のインストールが必要です。このライブラリは libssh2 に依存しているので、まずそれを確実にマシンにインストールしておく必要があります。例えば、Ubuntu マシンでは、以下を実行します:

$ apt-get install libssh2-1-dev
$ pecl install -a ssh2-1.3

12 factor-app クラウド開発手法に従って、次のcomposer.json のようにアプリの依存関係を明確に定義する必要があります。

{
  "require": {
    "ext-ssh2": "*"
  }
}

本当に面白いのは今からです!

SFTP接続の作成と削除

まず、始めは新しいクラスの作成からです。ここにはSFTPクライアントに必要な機能が入っています。最初に追加するパブリックメソッドは、パスワード認証またはキー認証のいずれかを使ってサーバーに接続し、既存のセッションを切断する手段を提供します。

<?php
  class SFTPClient
  {
    private $connection;
    private $sftp;

    public function __construct($host, $port=22)
    {
      $this->connection = @ssh2_connect($host, $port);
      if (! $this->connection)
        throw new Exception("Failed to connect to ${host} on port ${port}.");
    }

    // Login with user and password
    public function auth_password($username, $password)
    {
      if (! @ssh2_auth_password($this->connection, $username, $password))
        throw new Exception("Failed to authenticate with username $username " .
                            "and password.");

      $this->sftp = @ssh2_sftp($this->connection);
      if (! $this->sftp)
        throw new Exception("Could not initialize SFTP subsystem.");
    }

    // Disconnect session
    public function disconnect()
    {
      @ssh2_disconnect($this->connection);
    }
  }

?>
connect-class.php

新しいメソッドを使うには、メイン関数でクラスをインスタンス化してauth_password 関数の呼び出しによって接続を開始し、直ちに disconnect関数で接続を閉じます。次のようなURI 形式の環境変数 SFTPTOGO_URLを使って、SFTP サーバーへの接続に必要なすべての情報を取得します:sftp://user:password@host。該当変数は関数内で解析され、ユーザー名、パスワード、ホスト、オプションのポートが抽出されます。

<?php
  function main()
  {
    $raw_url = getenv('SFTPTOGO_URL');

    // Parse URL
    $parsed_url = parse_url($raw_url);

    if($parsed_url === false)
    {
      fwrite(STDERR, "Failed to parse SFTP To Go URL.\n");
      exit(1);
      
    }

    // Get user name and password
    $user = isset( $parsed_url['user'] ) ? $parsed_url['user'] : null;
    $pass = isset( $parsed_url['pass'] ) ? $parsed_url['pass'] : null;

    // Parse Host and Port
    $host = isset( $parsed_url['host'] ) ? $parsed_url['host'] : null;

    // Port is always 22
    $port = isset( $parsed_url['port'] ) ? $parsed_url['port'] : 22;

    fwrite(STDOUT, "Connecting to [${host}] ...\n");
    $client = new SFTPClient($host, $port);
    $client->auth_password($user, $pass);

    fwrite(STDOUT, "Disconnecting from [${host}] ...\n");
    $client->disconnect();

  }

  main();
?>
connect-main.php

ファイルの一覧表示

接続がうまくいったら、クラスにリモート SFTP サーバー上のファイルをリストアップするために使う関数を追加します。listFiles関数は $remote_dir 引数をとり、 ファイル名の配列とそのサイズ、更新時刻を返します。この関数を呼び出すには、単にパスを渡して (.でカレントディレクトリの内容が全て閲覧できる)、 取得した配列を処理します。

<?php
  // List remote directory files
  function listFiles($remote_dir) {
    $sftp = $this->sftp;
    $realpath = ssh2_sftp_realpath($sftp, $remote_dir);
    $dir = "ssh2.sftp://$sftp$realpath";
    $tempArray = array();

    fwrite(STDOUT, "Listing [${remote_dir}] ...\n\n");

    if (is_dir($dir)) {
      if ($dh = opendir($dir)) {
        while (($file = readdir($dh)) !== false) {
          $realpath = ssh2_sftp_realpath($sftp, $file);
          $filetype = filetype($dir . $realpath);
          $modTime = "";
          $size = sprintf("%.2f", filesize($dir . $realpath));

          if($filetype == "dir") {
            $file = $file . "/";
            $modTime = "";
            $size = "PRE";
          }

          $tempArray[] = $file;

          printf("%19s %12s %s\n", $modTime, $size, $file);
        }
        closedir($dh);
      }
    }
    return $tempArray;
  }

?>
listfiles.php

ファイルのアップロード

では、ファイルをアップロードしましょう。uploadFile関数を使って、【送信元のローカルファイルのパス】と【送信先のリモートパス】の2つの引数を渡します。そして関数の呼び出しは次のようになります:$client->uploadFile("./local.txt", "./remote.txt");

<?php
  // Upload local file to remote file
  public function uploadFile($local_file, $remote_file)
  {
    fwrite(STDOUT, "Uploading [${local_file}] to [${remote_file}] ...\n");

    $sftp = $this->sftp;
    $realpath = ssh2_sftp_realpath($sftp, $remote_file);
    $stream = @fopen("ssh2.sftp://$sftp$realpath", 'w');
    if (! $stream)
      throw new Exception("Could not open file: $realpath");
    $data_to_send = @file_get_contents($local_file);
    if ($data_to_send === false)
      throw new Exception("Could not open local file: $local_file.");
    if (@fwrite($stream, $data_to_send) === false)
      throw new Exception("Could not send data from file: $local_file.");
    @fclose($stream);
  }
?>
listfiles.php

ファイルのダウンロード

最後に大事な一点: ファイルをダウンロードするには、downloadFile関数を使いましょう。リモートファイルへのパスと、ダウンロードしたファイルを保存するローカルパスを関数に渡します。この関数は、$client->downloadFile("./remote.txt", "./download.txt");のように呼び出すことになります。

<?php
  // Download remote file to local file
  public function downloadFile($remote_file, $local_file)
  {
    fwrite(STDOUT, "Downloading [${remote_file}] to [${local_file}] ...\n");

    $sftp = $this->sftp;
    $realpath = ssh2_sftp_realpath($sftp, $remote_file);
    $stream = @fopen("ssh2.sftp://$sftp$realpath", 'r');
    if (! $stream)
      throw new Exception("Could not open file: $realpath");
    $contents = stream_get_contents($stream);
    file_put_contents ($local_file, $contents);
    @fclose($stream);
  }

  // Delete remote file
  public function deleteFile($remote_file){
    fwrite(STDOUT, "Deleting [${remote_file}] ...\n");
    $sftp = $this->sftp;
    $realpath = ssh2_sftp_realpath($sftp, $remote_file);
    unlink("ssh2.sftp://$sftp$realpath");
  }
?>
uploadfile.php
SFTP To Go with maximum security and reliability
SFTP To Go offers managed SFTP/FTPS/S3 as a service - maximum reliability, security, availability, with 1 minute setup. Great for companies of any size, any scale.
Check out SFTP To Go!

全体像

以上です!このプログラムを最初から最後まで通して実行したい場合は、以下のコードをコピーしてmain.phpとして保存してください:

<?php

class SFTPClient
{
  private $connection;
  private $sftp;

  public function __construct($host, $port=22)
  {
    $this->connection = @ssh2_connect($host, $port);
    if (! $this->connection)
      throw new Exception("Failed to connect to ${host} on port ${port}.");
  }

  // Login with user and password
  public function auth_password($username, $password)
  {
    if (! @ssh2_auth_password($this->connection, $username, $password))
      throw new Exception("Failed to authenticate with username $username " .
                          "and password.");

    $this->sftp = @ssh2_sftp($this->connection);
    if (! $this->sftp)
      throw new Exception("Could not initialize SFTP subsystem.");
  }

  // Login with SSH agent
  public function auth_agent($username)
  {
    if (! @ssh2_auth_agent($this->connection, $username))
      throw new Exception("Failed to authenticate with username $username " .
                          "and public key.");

    $this->sftp = @ssh2_sftp($this->connection);
    if (! $this->sftp)
      throw new Exception("Could not initialize SFTP subsystem.");
  }

  // Disconnect session
  public function disconnect()
  {
    @ssh2_disconnect($this->connection);
  }
  
  // List remote directory files
  function listFiles($remote_dir) {
    $sftp = $this->sftp;
    $realpath = ssh2_sftp_realpath($sftp, $remote_dir);
    $dir = "ssh2.sftp://$sftp$realpath";
    $tempArray = array();

    fwrite(STDOUT, "Listing [${remote_dir}] ...\n\n");

    if (is_dir($dir)) {
      if ($dh = opendir($dir)) {
        while (($file = readdir($dh)) !== false) {
          $realpath = ssh2_sftp_realpath($sftp, $file);
          $filetype = filetype($dir . $realpath);
          $modTime = "";
          $size = sprintf("%.2f", filesize($dir . $realpath));

          if($filetype == "dir") {
            $file = $file . "/";
            $modTime = "";
            $size = "PRE";
          }

          $tempArray[] = $file;

          printf("%19s %12s %s\n", $modTime, $size, $file);
        }
        closedir($dh);
      }
    }

    return $tempArray;
  }

  // Upload local file to remote file
  public function uploadFile($local_file, $remote_file)
  {
    fwrite(STDOUT, "Uploading [${local_file}] to [${remote_file}] ...\n");

    $sftp = $this->sftp;
    $realpath = ssh2_sftp_realpath($sftp, $remote_file);
    $stream = @fopen("ssh2.sftp://$sftp$realpath", 'w');
    if (! $stream)
      throw new Exception("Could not open file: $realpath");
    $data_to_send = @file_get_contents($local_file);
    if ($data_to_send === false)
      throw new Exception("Could not open local file: $local_file.");
    if (@fwrite($stream, $data_to_send) === false)
      throw new Exception("Could not send data from file: $local_file.");
    @fclose($stream);
  }

  // Download remote file to local file
  public function downloadFile($remote_file, $local_file)
  {
    fwrite(STDOUT, "Downloading [${remote_file}] to [${local_file}] ...\n");

    $sftp = $this->sftp;
    $realpath = ssh2_sftp_realpath($sftp, $remote_file);
    $contents = stream_get_contents($stream);
    if (! $stream)
      throw new Exception("Could not open file: $realpath");
    $contents = fread($stream, filesize("ssh2.sftp://$sftp$realpath"));
    file_put_contents ($local_file, $contents);
    @fclose($stream);
  }

  // Delete remote file
  public function deleteFile($remote_file){
    fwrite(STDOUT, "Deleting [${remote_file}] ...\n");
    $sftp = $this->sftp;
    $realpath = ssh2_sftp_realpath($sftp, $remote_file);
    unlink("ssh2.sftp://$sftp$realpath");
  }
}

function main()
{
  $raw_url = getenv('SFTPTOGO_URL');

  // Parse URL
  $parsed_url = parse_url($raw_url);

  if($parsed_url === false)
  {
    fwrite(STDERR, "Failed to parse SFTP To Go URL.\n");
    exit(1);
  }

  // Get user name and password
  $user = isset( $parsed_url['user'] ) ? $parsed_url['user'] : null;
  $pass = isset( $parsed_url['pass'] ) ? $parsed_url['pass'] : null;

  // Parse Host and Port
  $host = isset( $parsed_url['host'] ) ? $parsed_url['host'] : null;

  // Port is always 22
  $port = isset( $parsed_url['port'] ) ? $parsed_url['port'] : 22;

  fwrite(STDOUT, "Connecting to [${host}] ...\n");

  try
  {
    $client = new SFTPClient($host, $port);
    $client->auth_password($user, $pass);

    //*
    //* List working directory files
    //*
    $client->listFiles(".");

    //*
    //* Upload local file to remote file
    //*
    $client->uploadFile("./local.txt", "./remote.txt");

    //*
    //* Download remote file to local file
    //*
    $client->downloadFile("./remote.txt", "./download.txt");

    //*
    //* Delete remote file
    //*
    $client->deleteFile("./remote.txt");
  }
  catch (Exception $e)
  {
    echo $e->getMessage() . "\n";
  }
}

main();

?>
main.php

コードの実行には、下記のコマンドを使いましょう:

php main.php

また、Githubで我々のリポジトリをクローンしたり、他のコードサンプルをチェックアウトすることもできます。