Skip to content

Conversation

@lohanidamodar
Copy link
Contributor

@lohanidamodar lohanidamodar commented Jan 15, 2026

We switched delete() to scandir() instead of getFiles() because getFiles() relied on glob() patterns that could miss hidden entries (dotfiles, multi-dot names, false on no matches), leaving residual files and causing rmdir to fail with “Directory not empty.” scandir() enumerates all entries (including hidden) in one call, we filter only ./.., and ensure recursive deletion clears the directory before rmdir.

Summary by CodeRabbit

  • Bug Fixes

    • Improved local storage directory deletion to properly handle hidden files and edge cases.
    • Enhanced error handling for directory operations.
  • Tests

    • Added test coverage for recursive directory deletion with hidden files.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 15, 2026

Walkthrough

The pull request refactors the recursive directory deletion logic in Local::delete() by replacing getFiles($path) with scandir($path) to enumerate directory entries. Error handling is added to return false if scandir fails. The method now iterates over entries (excluding "." and ".."), recursively deletes each, and calls rmdir($path) to remove the empty directory after traversal. Non-directory paths retain existing behavior using unlink. A new test verifies that recursive deletion properly removes directories containing hidden files.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: replacing getFiles() with scandir() in the recursive delete logic to properly handle hidden files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

🧹 Recent nitpick comments
src/Storage/Device/Local.php (1)

373-392: Consider applying the same fix to deletePath().

The deletePath() method still uses getFiles() internally (line 381), which relies on glob() patterns. According to the PR rationale, glob() can miss certain hidden entries. For consistency and to fully resolve the hidden file issue across all deletion methods, consider refactoring deletePath() to use scandir() as well, or delegate to delete($path, true).


📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 32b6259 and 0a0cc1d.

📒 Files selected for processing (2)
  • src/Storage/Device/Local.php
  • tests/Storage/Device/LocalTest.php
🧰 Additional context used
🧬 Code graph analysis (2)
tests/Storage/Device/LocalTest.php (1)
src/Storage/Device/Local.php (5)
  • getPath (63-66)
  • createDirectory (452-461)
  • write (288-297)
  • delete (338-365)
  • exists (400-403)
src/Storage/Device/Local.php (2)
src/Storage/Device.php (1)
  • delete (192-192)
src/Storage/Device/S3.php (1)
  • delete (527-538)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build & Unit
🔇 Additional comments (3)
src/Storage/Device/Local.php (2)

338-365: LGTM! The refactored delete logic correctly handles hidden files.

The change from getFiles() (which uses glob()) to scandir() properly addresses the hidden file issue since scandir() returns all entries including dotfiles. The implementation is clean:

  1. Error handling for scandir() failure
  2. Proper filtering of . and ..
  3. Early return on recursive deletion failure
  4. rmdir() called only after contents are cleared

One edge case to note: if delete() is called with $recursive=false on a directory, the method will fall through to line 364 and return false (since is_file() and is_link() are both false for directories). This is reasonable behavior.


340-361: Verify symlink-to-directory behavior.

When $path is a symlink pointing to a directory, is_dir($path) returns true (PHP follows the symlink). This means the code will enter the recursive block and delete the contents of the linked directory rather than just removing the symlink itself.

If the intent is to only remove the symlink without affecting the target directory's contents, consider checking is_link() first:

Suggested approach if symlinks should be unlinked directly
 public function delete(string $path, bool $recursive = false): bool
 {
+    if (\is_link($path)) {
+        return \unlink($path);
+    }
+
     if (\is_dir($path) && $recursive) {
         $entries = \scandir($path);
         // ...
     }
-
-    if (\is_file($path) || \is_link($path)) {
+    if (\is_file($path)) {
         return \unlink($path);
     }
tests/Storage/Device/LocalTest.php (1)

110-120: Good test coverage for the fix.

The test correctly validates that recursive deletion handles hidden files. It creates both a hidden file (.hidden) and a visible file, then verifies the entire directory is removed.

One minor consideration: if the test fails partway through (e.g., delete() returns false), the test directory and files may remain. Consider adding cleanup in tearDown() or wrapping with a try/finally block. However, since this is a test for a fix that should work, this is low priority.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@lohanidamodar lohanidamodar merged commit b992b1b into main Jan 15, 2026
15 of 16 checks passed
@lohanidamodar lohanidamodar deleted the fix-recursive-deletes branch January 15, 2026 01:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants