[PHP]効率よくFTP上のファイルの存在を確認する方法

Read More

PHPでFTP上のファイルの存在を確認する方法について。


FTP上の一つ一つのファイル毎に存在チェックをかましていたら大量のファイルの存在チェックをしたときに時間がかかる。
そこで初回の存在チェックを走らせたタイミングで同階層のファイル・ディレクトリの一覧データを取得してそれをキャッシュさせ、2回目以降の存在チェックにはそのキャッシュから一致するファイルがあるかどうか、という調べ方をするように組んでみた。

■FTP上のファイルの存在を確認する
<?php
/**
 * FTP上のファイル・ディレクトリの存在を確認する
 * 
 * @param resouce $ftp_conn
 * @param string $file_path
 * @return boolean
 */
function ftp_file_exists($ftp_conn=null, $file_path=null)
{
  static $_list=null;
  
  $dir_path = dirname($file_path);
  $dir_path = preg_replace('/\\\/', '/', $dir_path);  // for windows
  
  if(!isset($_list[$dir_path]))
  {
    $list = ftp_rawlist($ftp_conn, $dir_path);
    if(is_array($list))
    {
      foreach($list as $key => $item)
      {
        if(preg_match("/^([drwx\-]+)([\s]+)([0-9]+)([\s]+)([0-9]+)([\s]+)([a-zA-Z0-9\.]+)([\s]+)([0-9]+)([\s]+)([a-zA-Z]+)([\s ]+)([0-9]+)([\s]+)([0-9:]+)([\s]+)([a-zA-Z0-9\.\-\_ ]+)$/si", $item, $match))
        {
          unset($list[$key]);
          $list[$match[17]] = 1;
        }
      }
    }
    $_list[$dir_path] = $list;    // cache
  }
  else
  {
    $list = $_list[$dir_path];
  }
  
  return isset($list[basename($file_path)]);
}
?>


コマンドプロンプトからテストしてみる。

■動作テスト
$res = ftp_file_exists($ftp_conn, '/test_amb');
echo($res);
$res = ftp_file_exists($ftp_conn, '/test_amb');
echo($res);



■実行結果
> 1
> 1


実行結果からはわからないが二回目の問い合わせはキャッシュ内(static変数)で完結している。
もしこの関数をFTPクラスのメソッドとして実装するのであれば無理にstatic変数を使う理由もなくこの部分はメンバ変数にしてもよい。
メンバ変数にすると後述するがメリットもある。
もちろんstatic変数のままでもよい。




--
ついでにFTPクラス化の例をメモとして残しておく。
前回の記事に書いたftp_list_info()をFTPクラス内に実装した場合、同じように実行結果をメンバ変数に格納しておくことが可能。
http://xirasaya.com/?m=detail&hid=551

この場合、メンバ変数に格納した実行結果をftp_list_info()とftp_file_exits()の両方のメソッドから使いまわすことができるので若干お得になる。

■FTPクラス実装の例
<?php
Class FtpClass
{
  var $_list = null;
  
  /**
   * 指定ディレクトリ直下のファイル・ディレクトリの情報をリストで返却する
   * 
   * ※独自関数perm_2_octal()を関数内で使用
   */
  function ftp_list_info($ftp_conn=null, $dir_path=null)
  {
    $dir_path = dirname($file_path);
    $dir_path = preg_replace('/\\\/', '/', $dir_path);	// for windows
    if(!isset($this->_list[$dir_path]))
    {
      $items = ftp_rawlist($ftp_conn, $dir_path);
      if(is_array($items))
      {
        $buf=array();
        foreach($items as $item)
        {
          if(preg_match("/^([drwx\-]+)([\s]+)([0-9]+)([\s]+)([0-9]+)([\s]+)([a-zA-Z0-9\.]+)([\s]+)([0-9]+)([\s]+)([a-zA-Z]+)([\s ]+)([0-9]+)([\s]+)([0-9:]+)([\s]+)([a-zA-Z0-9\.\-\_ ]+)$/si", $item, $match))
          {
            if($match[3]!=='1' and ($match[17]==='.' or $match[17]==='..'))
            {
              // do nothing
            }
            else
            {
              $name = $match[17];
              $buf[$name]['name'] = $match[17];
              $buf[$name]['path'] = rtrim($dir_path, '/'). '/'. $match[17];
              $buf[$name]['size'] = $match[9];
              $buf[$name]['type'] = ($match[3]==='1')? 'file': 'directory';
              $buf[$name]['perm_symbol'] = $match[1];
              $buf[$name]['perm_octal'] = perm_2_octal($match[1]);
              $buf[$name]['owner'] = $match[5];
              $buf[$name]['group'] = $match[7];
              $buf[$name]['last_modified'] = date('Y/m/d H:i:s', strtotime($match[11]. " ". $match[13]. " ". $match[15]));
              $buf[$name]['mdtm'] = strtotime($match[11]. " ". $match[13]. " ". $match[15]);
            }
          }
        }
        $this->_list[$dir_path] = $buf;
      }
    }
    
    return isset($this->_list[$dir_path])? $this->_list[$dir_path]: array();
  }
  
  /**
   * FTP上のファイル・ディレクトリの存在を確認する
   *
   * ※メンバメソッド$this->ftp_list_info()を内部で使用
   */
  function ftp_file_exists($ftp_conn=null, $file_path=null)
  {
    if(!isset($this->_list[$dir_path]))
    {
      $list = $this->ftp_list_info($ftp_conn, $dir_path);
    }
    
    return isset($list[$dir_path][basename($file_path)]);
  }
}
function perm_2_octal($symbol=null)
{
  if(preg_match('/^[\-dbclps]([\-r])([\-w])([\-sxS])([\-r])([\-w])([\-sxS])([\-r])([\-w])([\-xtT])$/u', $symbol, $match))
  {
    $i = 0;
    if($match[1]=='r') $i |= 4 << 6;
    if($match[2]=='w') $i |= 2 << 6;
    if($match[3]=='x' or $match[3]=='s') $i |= 1 << 6;
    if($match[3]=='s' or $match[3]=='S') $i |= 4 << 9;
    if($match[4]=='r') $i |= 4 << 3;
    if($match[5]=='w') $i |= 2 << 3;
    if($match[6]=='x' or $match[6]=='s') $i |= 1 << 3;
    if($match[6]=='s' or $match[6]=='S') $i |= 2 << 9;
    if($match[7]=='r') $i |= 4;
    if($match[8]=='w') $i |= 2;
    if($match[9]=='x' or $match[9]=='t') $i |= 1;
    if($match[9]=='t' or $match[9]=='T') $i |= 1 << 9;
    
    return sprintf('%04o', $i);
  }
  
  return null;
}
?>


現実的にFTPクラスを実装した場合、メソッド呼び出し時にFTP接続状況などを調べてやる必要があり、
もし接続が切れていた場合は再接続するなどといった処理も必要となる。



あと上記のFTPクラスは単一のFTP接続先を前提としている組み方なので、複数の接続にも対応させる場合は、メンバ変数に接続先情報を格納するようにしておき、データの出し入れをする際にはその値を配列のキーとして利用するなど一工夫が必要となる。
以下に$this->ftp_list_info()を例に変更点をあげる。

■複数接続に対応したメンバ変数へのデータ格納・取得の例
// 格納の変更点
× $this->_list[$dir_path] = $buf;
↓
○ $this->_list[$this->host][$this->user][$dir_path] = $buf;

// 取得の変更点
× isset($this->_list[$dir_path])? $this->_list[$dir_path]: array();
↓
○ isset($this->_list[$this->host][$this->user][$dir_path])? $this->_list[$this->host][$this->user][$dir_path]: array();


最初に定義した関数ftp_file_exists()もstatic変数を内部で使っているので単一のFTP接続先を前提としている。
もしこちらも複数のFTP接続先に対応するのであればこちらも同じように接続先とユーザ名によって使い分けできるように第3引数を追加するか、あるいは第1引数で渡すFTP接続IDの部分をFTP接続情報の配列を渡すように変更するなど何かしらの修正が必要。
自分だったら第3引数に「cacheを使わない」、あるいは「cacheを更新する」みたいなフラグを渡せるように実装する。



-- 2017/10/30 一部修正。
ftp_list_info()の戻り値にキーmdtmを追加。












コメント投稿

名前 *
(10文字以内)
コメント *
(1000文字以内)