cole
cole

Reputation: 1021

glob() - sort array of files by last modified datetime stamp

I'm trying to display an array of files in order of date (last modified).

I have done this buy looping through the array and sorting it into another array, but is there an easier (more efficient) way to do this?

Upvotes: 63

Views: 52421

Answers (6)

Sebastian
Sebastian

Reputation: 189

This can be done with a better performance. The usort() in the accepted answer will call filemtime() a lot of times. PHP uses quicksort algorithm which has an average performance of 1.39*n*lg(n). The algorithm calls filemtime() twice per comparison, so we will have about 28 calls for 10 directory entries, 556 calls for 100 entries, 8340 calls for 1000 entries etc. The following piece of code works good for me and has a great performance:

exec ( stripos ( PHP_OS, 'WIN' ) === 0 ? 'dir /B /O-D *.*' : 'ls -td1 *.*' , $myarray );

Upvotes: 6

Dharman
Dharman

Reputation: 33242

Since PHP 7.4 the best solution is to use custom sort with arrow function:

usort($myarray, fn($a, $b) => filemtime($a) - filemtime($b));

You can also use the spaceship operator which works for all kinds of comparisons and not just on integer ones. It won't make any difference in this case, but it's a good practice to use it in all sorting operations.

usort($myarray, fn($a, $b) => filemtime($a) <=> filemtime($b));

If you want to sort in reversed order you can negate the condition:

usort($myarray, fn($a, $b) => -(filemtime($a) - filemtime($b)));
// or 
usort($myarray, fn($a, $b) => -(filemtime($a) <=> filemtime($b)));

Note that calling filemtime() repetitively is bad for performance. Please apply memoization to improve the performance.

Upvotes: 7

Stefano
Stefano

Reputation: 307

Year 2020 - If you care about performance, consider not to use glob()!

If you want scan a lot of files in a folder without special wildcards, rulesets, or any exec(),

I suggest scandir(), or readdir().

glob() is a lot slower, on Windows it's even slower.


quote by: aalfiann

why glob seems slower in this benchmark? because glob will do recursive into sub dir if you write like this "mydir/*".

just make sure there is no any sub dir to make glob fast.

"mydir/*.jpg" is faster because glob will not try to get files inside sub dir.


benchmark: glob() vs scandir()

http://www.spudsdesign.com/benchmark/index.php?t=dir2 (external)


discussion: readdir() vs scandir()

readdir vs scandir (stackoverflow)


readdir() or scandir() combined with these, for pretty neat performance.

PHP 7.4

usort( $myarray, function( $a, $b ) { return filemtime($a) - filemtime($b); } );

source: https://stackoverflow.com/a/60476123/3626361

PHP 5.3.0 and newer

usort($myarray, fn($a, $b) => filemtime($a) - filemtime($b));

source: https://stackoverflow.com/a/35925596/3626361


if you wanna go even deeper the rabbit hole:

The DirectoryIterator


https://www.php.net/manual/en/class.directoryiterator.php

https://www.php.net/manual/en/directoryiterator.construct.php (read the comments!)

http://paulyg.github.io/blog/2014/06/03/directoryiterator-vs-filesystemiterator.html

Difference between DirectoryIterator and FileSystemIterator


Last but not least, my Demo!

<?php
function files_attachment_list($id, $sort_by_date = false, $allowed_extensions = ['png', 'jpg', 'jpeg', 'gif', 'doc', 'docx', 'pdf', 'zip', 'rar', '7z'])
{
    if (empty($id) or !is_dir(sprintf('files/%s/', $id))) {
        return false;
    }
    $out = [];
    foreach (new DirectoryIterator(sprintf('files/%s/', $id)) as $file) {
        if ($file->isFile() == false || !in_array($file->getExtension(), $allowed_extensions)) {
            continue;
        }

        $datetime = new DateTime();
        $datetime->setTimestamp($file->getMTime());
        $out[] = [
            'title' => $file->getFilename(),
            'size' => human_filesize($file->getSize()),
            'modified' => $datetime->format('Y-m-d H:i:s'),
            'extension' => $file->getExtension(),
            'url' => $file->getPathname()
        ];
    }

    $sort_by_date && usort($out, function ($a, $b) {
        return $a['modified'] > $b['modified'];
    });

    return $out;
}

function human_filesize($bytes, $decimals = 2)
{
    $sz = 'BKMGTP';
    $factor = floor((strlen($bytes) - 1) / 3);
    return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor];
}

// returns a file info array from path like '/files/123/*.extensions'
// extensions = 'png', 'jpg', 'jpeg', 'gif', 'doc', 'docx', 'pdf', 'zip', 'rar', '7z'
// OS specific sorting
print_r( files_attachment_list(123) );

// returns a file info array from the folder '/files/456/*.extensions'
// extensions = 'txt', 'zip'
// sorting by modified date (newest first)
print_r( files_attachment_list(456, true, ['txt','zip']) );

Upvotes: 5

Jay
Jay

Reputation: 42632

Warning create_function() has been DEPRECATED as of PHP 7.2.0. Relying on this function is highly discouraged.

For the sake of posterity, in case the forum post linked in the accepted answer is lost or unclear to some, the relevant code needed is:

<?php

$myarray = glob("*.*");
usort($myarray, create_function('$a,$b', 'return filemtime($a) - filemtime($b);'));

?>

Tested this on my system and verified it does sort by file mtime as desired. I used a similar approach (written in Python) for determining the last updated files on my website as well.

Upvotes: 101

fusion3k
fusion3k

Reputation: 11689

This solution is same as accepted answer, updated with anonymous function1:

$myarray = glob("*.*");

usort( $myarray, function( $a, $b ) { return filemtime($a) - filemtime($b); } );

1 Anonymous functions have been introduced in PHP in 2010. Original answer is dated 2008.

Upvotes: 38

Alf Eaton
Alf Eaton

Reputation: 5463

<?php
$items = glob('*', GLOB_NOSORT);
array_multisort(array_map('filemtime', $items), SORT_NUMERIC, SORT_DESC, $items);

Upvotes: 48

Related Questions