PHP FTP classes for connecting to and getting a file list from a Windows FTP server

Here's a couple of classes that I wrote to connect and retrieve an array of file paths, names, and sizes from a Windows FTP server. There is high npath/cyclomatic complexity in getFileList() but it is left that way for easy reading of code and for performance, as this method may run (recursively) thousands or more times per request, depending on how many files and directories exist.
<?php
 
/**
 * @file
 * TmFtp.php
 * 
 * FTP related functions.
 */
 
/**
 * Connects and disconnects to an FTP server.
 */
class TmFtpServer {
 
  public $servername;
  public $user;
  public $pass;
  public $path;
  protected $conn_id;
  protected $connected = FALSE;
 
  /**
   * Initializes variables.
   * 
   * @param string $name Server name.
   * @param string $user Username.
   * @param string $pass Password.
   * @param string $path Remote start path.
   */
  public function __construct($servername, $user, $pass, $path) {
    $this->servername = $servername;
    $this->user = $user;
    $this->pass = $pass;
    $this->path = $path;
  }
 
  /**
   * Logs in to FTP server and changes to a directory.
   */
  public function openConnection() {
    if (!$this->connected) {
      $this->conn_id = ftp_connect($this->servername);
      if ($this->conn_id !== FALSE // Connection successful?
          && ftp_login($this->conn_id, $this->user, $this->pass) // Logs in.
          && ftp_chdir($this->conn_id, $this->path)) { // Changes directory.
        $this->connected = TRUE; // Records success.
      }
    }
    return $this;
  }
 
  /**
   * Closes the connection.
   */
  public function closeConnection() {
    ftp_close($this->conn_id);
    $this->connected = FALSE;
    return $this;
  }
 
  /**
   * Gets the connection id.
   * 
   * @return resource An FTP Stream.
   */
  public function getConnID() {
    return $this->conn_id;
  }
 
  /**
   * Gets the connection status.
   * 
   * @return boolean TRUE if an FTP connetion is established. 
   */
  public function getConnectionStatus() {
    return $this->connected;
  }
 
}
 
/**
 * Retrieves a listing of files and directories on a Windows FTP server.
 */
class TmFtpServerWinFileListing {
 
  protected $server;
  protected $filelist = array();
  protected $directories_to_skip = array();
  protected $file_patterns_to_skip = array();
  protected $depth_reached = 0;
  protected $count = 0;
 
  /**
   * Retrieves a listing of files and directories on a Windows FTP server.
   * 
   * Example Usage:
   * <pre>
 
   * $servername = 'ftp.example.com';
   * $user = 'Joe';
   * $pass = 'abs123';
   * $depth_max = 2;
   * $path = '/starting/directory';
   * $show_directories = FALSE;
   * $directories_to_skip = array('/private', '/home');
   * $file_patterns_to_skip = array('.DS_Store');
   * 
   * $server = new TmFtpServer($servername, $user, $pass, $path);
   * $listing = new TmFtpServerWinFileListing($server);
   * $files = $listing->setDirectoriesToSkip($directories_to_skip)
   *     ->setFilePatternsToSkip($file_patterns_to_skip)
   *     ->getFileList($depth_max, $show_directories);
   * 
   * foreach ($files as $file) {
   *   $filepath = $file->path;
   *   $filename = $file->name;
   *   $filesize = $file->size;
   *   print "$filepath/$filename ($filesize)<br />\n";
   * }
 
   * </pre>
   */
  public function __construct(TmFtpServer $server) {
    $this->server = $server;
  }
 
  /**
   * Ensures FTP connection is closed.
   */
  public function __destruct() {
    if ($this->server->getConnectionStatus()) {
      $this->server->closeConnection();
    }
  }
 
 
  /**
   * Gets file list.
   *
   * @param int $depth_max Maximum depth to traverse. Set to 0 for unlimited.
   * @param boolean $show_directories Show directories in listing.
   * @param string $path The base directory.
   * @return array indexed array where each value is a path to a filename or directory.
   */
  public function getFileList($depth_max = 3, $show_directories = TRUE, $path = '') {
    $rawlist = $this->getRawList($path);
    // Gets current depth of path.
    $depth = substr_count($path, '/');
    if ($depth > $this->depth_reached) {
      $this->depth_reached = $depth;
    }
    // Opens the connection to the server if not already opened.
    foreach ($rawlist as $line) { // Loops through the directory listing.
      $item = preg_split('/[\s]+/', $line); // Puts the raw string into an array.
      $name = $this->getNameFromLineItem($item, $line); // Gets the filename.
      if ($item[2] == '<DIR>') { // Is a directory.
        if ($show_directories) { // Includes directories in listing.
          $file = new stdClass();
          $file->size = 0;
          $file->path = $path;
          $file->name = $name;
          $this->filelist[] = $file;
        }
        if (!$this->isSkipDir("$path/$name")) { // Directory is not marked to be skipped.
          if (!$depth_max || $depth < $depth_max) { // Only recurses if max_depth hasn't been reached.
            $this->getFileList($depth_max, $show_directories, "$path/$name"); // Recurses.
          }
        }
      } else if (!$this->isSkipFilePattern("$path/$name")) { // Is a file and it isn't marked to be skipped.
        // Adds to the filelist.
        $file = new stdClass();
        $file->size = (int) $item[2];
        $file->path = $path;
        $file->name = $name;
        $this->filelist[] = $file;
      }
      $this->count++;
      if ($this->count % 50 === 0) {
        error_log("$this->count lines processed");
      }
    }
    return $this->filelist;
  }
 
  /**
   * Gets depth; only useful after running getFileList().
   * 
   * @return int 
   */
  public function getDepthReached() {
    return $this->depth_reached;
  }
 
  /**
   * Gets and FTP Rawlist
   * 
   * @return array
   */
  public function getRawList(&$path) {
    // Sets the path on the first pass.
    if (!$path) {
      $path = $this->server->path;
    }
    $this->server->openConnection();
    $rawlist = ftp_rawlist($this->server->getConnID(), $path);
    $this->server->closeConnection();
    return $rawlist;
  }
 
  /**
   * Extracts name from ftp rawlist line item.
   */
  protected function getNameFromLineItem($item, $line) {
    $line = str_replace(array($item[0], $item[1], $item[2]), '', $line);
    return ltrim($line);
  }
 
  /**
   * Gets directories to skip.
   */
  public function getDirectoriesToSkip() {
    if (!$this->directories_to_skip) {
      $this->setDirectoriesToSkip();
    }
    return $this->directories_to_skip;
  }
 
  /**
   * Sets directories to skip. 
   */
  public function setDirectoriesToSkip($directories = array()) {
    $this->directories_to_skip = $directories;
    return $this;
  }
 
  /**
   * Checks to see if a directory is marked to be skipped.
   */
  protected function isSkipDir($path) {
    $directories_to_skip = $this->getDirectoriesToSkip();
    foreach ($directories_to_skip as $dir) {
      if (strpos($path, "/drupaldev$dir") === 0) {
        return TRUE;
      }
    }
    return FALSE;
  }
 
  /**
   * Gets file patterns to skip.
   */
  public function getFilePatternsToSkip() {
    if (!$this->file_patterns_to_skip) {
      $this->setFilePatternsToSkip();
    }
    return $this->file_patterns_to_skip;
  }
 
  /**
   * Sets file patterns to skip. 
   */
  public function setFilePatternsToSkip($file_patterns_to_skip = array()) {
    $this->file_patterns_to_skip = $file_patterns_to_skip;
    return $this;
  }
 
  /**
   * Checks to see if a file pattern is marked to be skipped.
   */
  protected function isSkipFilePattern($path) {
    $file_patterns_to_skip = $this->getFilePatternsToSkip();
    foreach ($file_patterns_to_skip as $pattern) {
      if (strstr($path, $pattern)) {
        return TRUE;
      }
    }
    return FALSE;
  }
 
}

Article Type

General