6
\$\begingroup\$

I'm implementing an is_dir() function to determine if a given item is a directory or a file for FTP/FTPS connections. Currently, I have two methods: one using PHP's FTP wrapper for the is_dir() function and another recommended in the help page for the ftp_chdir() function.

Here's the first one (protocol wrapper):

if ($items = ftp_nlist($this->attributes["link"], $path)) {
    $output = null;

    foreach ($items as $item) {
        if (is_dir("ftp://{$this->attributes["user"]}:{$this->attributes["password"]}@{$this->attributes["host"]}/{$item}")) {
            $output[] = $item;
        }
    } 

    $output[] = sprintf("%5.4fs", (microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"]));
    return $output;
}

Here's the second one (chdir implementation):

if ($items = ftp_nlist($this->attributes["link"], $path)) {
    $output = null;
    $current = ftp_pwd($this->attributes["link"]);

    foreach ($items as $item) {
        if (@ftp_chdir($this->attributes["link"], $item)) {
            ftp_chdir($this->attributes["link"], $current);
            $output[] = $item;
        }
    }

    $output[] = sprintf("%5.4fs", (microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"]));
    return $output;
}

The problem I find with these two implementations is they're quite slow (or, at least, I think they should be faster to be used within AJAX calls). Running these two functions and measuring their execution time with the last element in the returned array, these are their timed values (ran them 50 times to get an accurate enough time reading): 7.2312s for method A and 2.4534s for method B.

Which shows the ftp_chdir() implementation is almost 3x faster than using the FTP wrapper. Still... I have the feeling there may be room for improvement.

My development machine is Windows 7 x64 with Apache 2.4.4 (x86), PHP 5.4.13 (x86, thread-safe version) and MySQL 5.6.10 x64 (irrelevant in this case), always keeping up-to-date packages. The FTP server I ran these tests against is another Windows machine but a full-fledged production server (dedicated) on a data center.

Is there any other method to do the same but in a better (and/or faster) fashion?

\$\endgroup\$
8
  • \$\begingroup\$ Why do you iterate through all entries of the directory? \$\endgroup\$ – MarcDefiant Apr 1 '13 at 11:17
  • \$\begingroup\$ Because it's how the original ftp_chdir() implementation is done and, as ftp_nlist() gives you a list of all entries belonging to a given path, I thought that's the way to go. Is there a better method? \$\endgroup\$ – Julio María Meca Hansen Apr 1 '13 at 16:31
  • \$\begingroup\$ So you want to display a directory listing and additional information? Then you might consider using ftp_rawlist(), which basicly returns the output of ls -al from the current directory on the server. But note that the output may change depending on which OS or FTP server software the server uses. Someone on PHP.net implemented such a method already: php.net/manual/en/function.ftp-nlist.php#108501 \$\endgroup\$ – MarcDefiant Apr 2 '13 at 6:55
  • \$\begingroup\$ I already implemented a solution using a combination of mb_substr($item, 0, 1) for file/directory detection and $tmp = explode(" ", $item) and end($tmp) for link retrieval, but your link points to a much more developed and correct implementation. Thanks a lot! :) \$\endgroup\$ – Julio María Meca Hansen Apr 2 '13 at 8:56
  • 2
    \$\begingroup\$ Out of curiousity, why build somthing like this yourself instead of using something like Flysytem? \$\endgroup\$ – Potherca Jul 12 '15 at 23:02
1
\$\begingroup\$

In case someone is interested, here's a follow up to MarcDefiant's comment about using ftp_rawlist(). It's fast if you're already using it for getting the file list as well. Here's a snippet from the filebrowser.php in my GitHub filebrowser project. It's certainly not on the same level as Flysystem but I enjoyed working on it :).

public function isDir($path,$contents=NULL){
    $name = getPathName($path);
    if($contents === NULL){
        $path = getItemPath($path);
        $contents = $this->scandir($path);
    }
    foreach($contents as $content => $data){
        if($content == $name){
            if($data['type'] == 'directory'){
                return TRUE;
            }
            return FALSE;
        }
    }
    return FALSE;
}

public function scandir($path=NULL){
    if($path === NULL){
        $path = $this->Path;
    }
    if(!$path){
        $path = ".";
    }
    if(is_array($children = @ftp_rawlist($this->conn_id, $path))){
        $items = array();

        foreach($children as $name => $child){
            if(!$name){
                continue;
            }
            $chunks = preg_split("/\s+/", $child);
            list($item['rights'], $item['number'], $item['user'], $item['group'], $item['size'], $item['month'], $item['day'], $item['time']) = $chunks;
            $item['type'] = $chunks[0]{0} === 'd' ? 'directory' : 'file';
            array_splice($chunks, 0, 8);
            $items[implode(" ", $chunks)] = $item;
        }

        return $items;
    }
    return FALSE;
}

function getPathName($path){
    return substr($path,strrpos("/".$path,"/"));
}
\$\endgroup\$
1
  • \$\begingroup\$ Thank you OffTheBricks. Just a little improvment in your code : I replace $chunks = preg_split( '/\s+/', $child ); by $chunks = preg_split( '/\s+/', $child, 9 ); and array_splice($chunks, 0, 8); $items[implode(" ", $chunks)] = $item; by $name = array_pop( $chunks ); $items[$name] = $item; because I have some files with several consecutives spaces that was lost by the use of /s+ and implode( " "… ). \$\endgroup\$ – Thomas L'Excellent Jun 28 '17 at 10:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.