65

I currently have a script which allows me to output the list of files inside the same directory.

The output shows the names, and then I used filemtime() function to show the date when the file was modified.

How will I sort the output to show the latest modified file?

This is what I have for now:

if ($handle = opendir('.')) {
    while (false !== ($file = readdir($handle))) {
        if ($file != "." && $file != "..") {
            $lastModified = date('F d Y, H:i:s', filemtime($file));
            if(strlen($file)-strpos($file, ".swf") == 4) {
                echo "$file - $lastModified";
            }
        }
    }
    closedir($handle);
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
sasori
  • 5,249
  • 16
  • 86
  • 138

6 Answers6

185

This would get all files in path/to/files with an .swf extension into an array and then sort that array by the file's mtime

$files = glob('path/to/files/*.swf');
usort($files, function($a, $b) {
    return filemtime($b) - filemtime($a);
});

The above uses an Lambda function and requires PHP 5.3. Prior to 5.3, you would do

usort($files, create_function('$a,$b', 'return filemtime($b)-filemtime($a);'));

If you don't want to use an anonymous function, you can just as well define the callback as a regular function and pass the function name to usort instead.

With the resulting array, you would then iterate over the files like this:

foreach($files as $file){
    printf('<tr><td><input type="checkbox" name="box[]"></td>
            <td><a href="%1$s" target="_blank">%1$s</a></td>
            <td>%2$s</td></tr>', 
            $file, // or basename($file) for just the filename w\out path
            date('F d Y, H:i:s', filemtime($file)));
}

Note that because you already called filemtime when sorting the files, there is no additional cost when calling it again in the foreach loop due to the stat cache.

Walf
  • 8,535
  • 2
  • 44
  • 59
Gordon
  • 312,688
  • 75
  • 539
  • 559
  • 2
    -1 You don't utilize his code and usort is the worst sort function to use in that case. But hell, all vote for you because it looks fancy. *-* – elias Apr 19 '10 at 12:08
  • 47
    @elias No offense, but I find it rather ridiculous to downvote a perfectly valid answer for the reasons you gave. Talking about readability your code takes much longer to figure out than mine. I'd call it Spaghetti code. And if you are unhappy about usort being oh so terrible, than file a complaint with PHP's bugtracker. – Gordon Apr 19 '10 at 12:14
  • Granted this question is aeons old, I'm about to make use `usort()` right now; pray, what is the problem with `usort()`? (Relating to @elias comment above.) – Ifedi Okonkwo Jun 10 '15 at 09:23
  • 3
    @IfediOkonkwo I don't know elias particular concerns, but some people dont like `usort` because sorting via the closure adds some internal function call overhead, e.g. it's slower than other sorts. In practise you want to profile your test before jumping to conclusions though. In most cases, the overhead does not have a relevant impact. – Gordon Jun 10 '15 at 10:48
  • Well, if you need to sort by something non-trivial, then you probably DO need to use a callback anyway. And in this case of sorting by `filemtime`, that's about the only option: I did indeed come up against the key overwriting problem evident in elias' answer. Thanks @Gordon for your time and attention. – Ifedi Okonkwo Jun 10 '15 at 11:21
  • 6
    It might be worth noting that the Lambda function above sorts with the youngest file first (i.e. date ascending). If you want the oldest file first, simply switch the `<` for `>`. – Coder Oct 21 '16 at 14:26
  • Would you mind updating the answer with PHP 7.4 version (arrow functions) and adding a warning about deprecated `create_function()`? – Dharman Mar 01 '20 at 14:13
  • This reads the modified time for each file multiple times, the more files there are to sort, the more times each will be read, exponentially degrading performance. – Walf Feb 23 '21 at 04:31
  • 1
    @Walf see the [notes in the PHP Manual](https://www.php.net/manual/en/function.filemtime.php): The results of this function are cached. See `clearstatcache()` for more details. – Gordon Feb 23 '21 at 15:46
20

You need to put the files into an array in order to sort and find the last modified file.

$files = array();
if ($handle = opendir('.')) {
    while (false !== ($file = readdir($handle))) {
        if ($file != "." && $file != "..") {
           $files[filemtime($file)] = $file;
        }
    }
    closedir($handle);

    // sort
    ksort($files);
    // find the last modification
    $reallyLastModified = end($files);

    foreach($files as $file) {
        $lastModified = date('F d Y, H:i:s',filemtime($file));
        if(strlen($file)-strpos($file,".swf")== 4){
           if ($file == $reallyLastModified) {
             // do stuff for the real last modified file
           }
           echo "<tr><td><input type=\"checkbox\" name=\"box[]\"></td><td><a href=\"$file\" target=\"_blank\">$file</a></td><td>$lastModified</td></tr>";
        }
    }
}

Not tested, but that's how to do it.

Nisse Engström
  • 4,738
  • 23
  • 27
  • 42
elias
  • 1,481
  • 7
  • 13
  • 28
    @sasori note that this solution will not work properly when you've got files with the same mtime, as it will overwrite the previous filename for that mtime. – Gordon Apr 19 '10 at 14:34
  • 4
    Exactly Gordon, I wish there would have been a way to BUMP comments to be more visible as I have spent an hour digging on what was wrong.. Apparently, the idea behind the above code is correct, but this bug makes it absolutely useless. – Ruslan Abuzant Aug 14 '12 at 17:57
  • Edited the code to make the key unique (by repeatedly adding one second). It'll confuse things slightly when some files have one-second-different (but not identical) mtimes, but that won't matter in most cases. – Steve Almond Feb 20 '14 at 11:37
  • 2
    Only change I'd make is: `$filetime = filemtime("$path/$file");` `while (isset($files[$filetime])) { $filetime++; }` `$files[$filetime] = $file;` – Nicholas Blasgen Feb 13 '15 at 20:11
  • 3
    To correct this answer, you could use the file path as array key and the date as value, so there would be no duplicate keys. And if I understand well, to sort the values without changing the keys you must use asort (). – Dudaskank Jun 17 '15 at 20:51
  • yeah, definitely put the read directory into some $path and then `opendir($path)` and do `filemtime($path.DIRECTORY_SEPARATOR.$file)`. this is not a very complete answer – user151496 May 06 '19 at 12:57
  • Yes as Dudaskank says. $files[$file] = filemtime($path.$file); then asort($files); – dave.zap May 30 '19 at 03:34
  • This answer is NOT reliable. If the last mod time of two files are identical, then you will lose earlier files to later files in the associative array. Arrays cannot have duplicate keys in the same level. Instead, the mod times should be the values and the filenames should be the keys. – mickmackusa Jan 01 '22 at 03:35
12

Here is an example that uses the RecursiveDirectoryIterator class. It's a convenient way to iterate recursively over file system.

$output = array();
foreach( new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator( 'path', FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS ) ) as $value ) {
        if ( $value->isFile() ) {
            $output[] = array( $value->getMTime(), $value->getRealPath() );
        }
}

usort ( $output, function( $a, $b ) {
    return $a[0] > $b[0];
});
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Danijel
  • 12,408
  • 5
  • 38
  • 54
  • 2
    people need to adopt OOP approach in PHP in order to make it competitive to other languages. – Raheel Apr 06 '15 at 17:00
  • 6
    @RaheelKhan Why? Use OOP when it solves problems easier, don't use OOP when it's more complicated. OOP doesn't make anything better, nor your code, nor a language. It just allows certain problems to solve more efficiently (what I don't think is the case here). – Lorenz Meyer Jun 19 '15 at 11:23
  • +1 for the `>`; I didn't understand why other answers failed as far as my code's concerned, but yours' succeded. Just the right compare operator: `>` instead of `<`. – 1111161171159459134 Dec 18 '15 at 21:25
  • 2
    While you don't really need to use `RecursiveDirectoryIterator` for this problem (esp. if you don't want recursive results), this the only answer here that uses `usort` with any kind of caching. Kind of appalled that Gordon's answer has such massive traction here; you definitely do not want to be doing a filesystem lookup inside a comparator function. `filemtime` is expensive due to the filesystem lookups, so if I did have to do it, I'd rather do it once per file (like in this answer) than maybe thousands of times. Not to mention, `DirectoryIterator::getMTime` is efficient to begin with. – Andrew Apr 11 '16 at 09:31
3
$files = array_diff(scandir($dir,SCANDIR_SORT_DESCENDING), array('..', '.'));
print_r($files);
barbsan
  • 3,418
  • 11
  • 21
  • 28
Wynn
  • 183
  • 1
  • 2
  • 1
    `SCANDIR_SORT_*` constants have nothing to do with file dates. This will only work if the filenames are prefixed with ISO dates, whose lexical sort is already chronological. – Walf Feb 23 '21 at 04:34
1

I use your exact proposed code with only some few additional lines. The idea is more or less the same of the one proposed by elias, but in this solution there cannot be conflicts on the keys since each file in the directory has a different filename and so adding it to the key solves the conflicts.

The first part of the key is the datetime string formatted in a manner such that I can lexicographically compare two of them.

if ($handle = opendir('.')) {
    $result = array();
    while (false !== ($file = readdir($handle))) {
        if ($file != "." && $file != "..") {
            $lastModified = date('F d Y, H:i:s', filemtime($file));
            if(strlen($file)-strpos($file, ".swf") == 4) {
                $result [date('Y-m-d H:i:s', filemtime($file)).$file] =
                    "<tr><td><input type=\"checkbox\" name=\"box[]\"></td><td><a href=\"$file\" target=\"_blank\">$file</a></td><td>$lastModified</td></tr>";
            }
        }
    }
    closedir($handle);
    krsort($result);
    echo implode('', $result);
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Roberto Trani
  • 1,217
  • 11
  • 14
0

For anyone who is desperately focused on micro-optimizing performance (minimizing the total number of iterated function calls), you can store the file modification time as the first element and the filename as the second element of an array of rows.

Once this is set up, you can simply call sort() and the algorithm will compare by the mod times first, then break ties using the unique filenames.

Code: (Demo)

$result = [];
foreach (glob('path/to/files/*.swf') as $file) {
    $result[] = [filemtime($file), $file];
}
sort($result);
var_export($result);

*note: if you don't want the path prepended to the filename ($file), then use chdir('path/to/files') to move into the targeted directory before calling glob().

Of course, using the stock-standard sort() means the you are locked into ASC for all elements in all rows. As well, rsort() would sort by all columns in DESC order. These outcomes will not satisfy in some circumstances. I suppose you could hack the mod time sorting to be the opposite of the filename alphabetizing by storing the mod time as a negative integer. ...I'm digressing.

mickmackusa
  • 43,625
  • 12
  • 83
  • 136