61

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?

mickmackusa
  • 43,625
  • 12
  • 83
  • 136
cole
  • 1,001
  • 2
  • 11
  • 13
  • 2
    Did you find this? (Just did a Google search on your question title): http://www.webdeveloper.com/forum/showthread.php?t=188670 – John Sep 24 '08 at 01:58

6 Answers6

101

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.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Jay
  • 41,768
  • 14
  • 66
  • 83
  • 16
    Worked beautifully. I wanted the reverse order, so I swapped $a with $b in the function definition field. Thanks Jay! – AVProgrammer Jan 11 '12 at 22:53
  • 7
    This code is accessing the filesystem every time a comparison is made (several times for each file). Depending where your filesystem is, that could be very slow. Also, if any of the files is written to during the sort, then the changing file times could lead to bizarre sorting results, depending on the algorithm used by usort. I would recommend the [other answer](http://stackoverflow.com/a/3298787/706054), which avoids all these problems. – Matt Jun 27 '13 at 18:43
  • 5
    If you are using PHP 5.3.0 or newer a native anonymous function should be used instead. http://php.net/create_function – xd6_ Jan 29 '15 at 14:33
  • 1
    This is slow because `filemtime` is called many times. [This answer](https://stackoverflow.com/a/70155591/1422096) solves this problem, as well as probably @AlfEaton's solution, as mentioned by Matt's comment. – Basj Dec 16 '21 at 15:05
47
<?php
$items = glob('*', GLOB_NOSORT);
array_multisort(array_map('filemtime', $items), SORT_NUMERIC, SORT_DESC, $items);
Alf Eaton
  • 5,226
  • 4
  • 45
  • 50
  • 1
    It makes no sense to pass the result of `array_map` as an argument meant to be passed by reference. You will sort it but then what? You no longer have it. – Okonomiyaki3000 Apr 15 '14 at 05:47
  • 1
    The result of `array_map` is used to sort the `$items` array, which is also passed by reference. – Alf Eaton Apr 15 '14 at 20:34
  • 2
    I see. I guess this will work, a similar approach is even documented on the `array_multisort` page of php.net. I think there are more appropriate functions for this task but I'll take back my downvote. Or I would if I could... sorry. – Okonomiyaki3000 Apr 16 '14 at 05:18
  • Tested and doesn't sort correctly in real life despite it looks like a sexy oneliner. – Viktor Joras Jun 25 '18 at 07:17
  • @ViktorJoras Can you post a link to your test code? – Alf Eaton Jul 13 '18 at 13:22
  • 1
    I'd probably use this technique because this way `filemtime()` is never called more than once on a given file. I'd UV this answer, but I have a hard policy that prevents me UVing answers that are unexplained. – mickmackusa Oct 12 '20 at 00:44
36

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.

Community
  • 1
  • 1
fusion3k
  • 11,568
  • 4
  • 25
  • 47
  • 6
    Since it's 2019 now, this should be the accepted answer. PHP 7 no longer allows for "create_function".. so the best solution, across the board, is to use the anonymous function (or a reference) – Matt Kenefick Jan 09 '19 at 16:07
7

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.

Dharman
  • 30,962
  • 25
  • 85
  • 135
5

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 );
Dharman
  • 30,962
  • 25
  • 85
  • 135
Sebastian
  • 178
  • 1
  • 7
5

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']) );
Dharman
  • 30,962
  • 25
  • 85
  • 135
Stefano
  • 224
  • 4
  • 7