From a5c14b5721ea319d197a680d570779fa9320f336 Mon Sep 17 00:00:00 2001 From: Peter Elmered Date: Thu, 1 Aug 2024 15:00:22 +0200 Subject: [PATCH 1/9] Performance optimizations for Backups check --- src/Checks/Checks/BackupsCheck.php | 82 +++++++++++++++++++++--------- src/Support/BackupFile.php | 18 ++++++- 2 files changed, 76 insertions(+), 24 deletions(-) diff --git a/src/Checks/Checks/BackupsCheck.php b/src/Checks/Checks/BackupsCheck.php index 0405f73c..26427768 100644 --- a/src/Checks/Checks/BackupsCheck.php +++ b/src/Checks/Checks/BackupsCheck.php @@ -22,8 +22,12 @@ class BackupsCheck extends Check protected ?Carbon $oldestShouldHaveBeenMadeAfter = null; + protected string $parseModifiedUsing = 'Y-m-d_H-i-s'; + protected int $minimumSizeInMegabytes = 0; + protected bool $onlyCheckSizeOnFirstAndLast = false; + protected ?int $minimumNumberOfBackups = null; protected ?int $maximumNumberOfBackups = null; @@ -42,6 +46,20 @@ public function onDisk($disk) return $this; } + public function parseModifiedFormat($parseModifiedFormat = 'Y-m-d_H-i-s'): self + { + $this->parseModifiedUsing = $parseModifiedFormat; + + return $this; + } + + public function onDisk($disk) + { + $this->disk = Storage::disk($disk); + + return $this; + } + public function youngestBackShouldHaveBeenMadeBefore(Carbon $date): self { $this->youngestShouldHaveBeenMadeBefore = $date; @@ -56,13 +74,20 @@ public function oldestBackShouldHaveBeenMadeAfter(Carbon $date): self return $this; } - public function atLeastSizeInMb(int $minimumSizeInMegabytes): self + public function atLeastSizeInMb(int $minimumSizeInMegabytes, $onlyCheckFirstAndLast = false): self { $this->minimumSizeInMegabytes = $minimumSizeInMegabytes; return $this; } + public function onlyCheckSizeOnFirstAndLast($onlyCheckSizeOnFirstAndLast = true): self + { + $this->onlyCheckSizeOnFirstAndLast = $onlyCheckSizeOnFirstAndLast; + + return $this; + } + public function numberOfBackups(?int $min = null, ?int $max = null): self { $this->minimumNumberOfBackups = $min; @@ -81,16 +106,9 @@ public function run(): Result $eligableBackups = $files ->map(function (string $path) { - return new BackupFile($path, $this->disk); - }) - ->filter(function (BackupFile $file) { - return $file->size() >= $this->minimumSizeInMegabytes * 1024 * 1024; + return new BackupFile($path, $this->disk, $this->parseModifiedUsing); }); - if ($eligableBackups->isEmpty()) { - return Result::make()->failed('No backups found that are large enough'); - } - if ($this->minimumNumberOfBackups) { if ($eligableBackups->count() < $this->minimumNumberOfBackups) { return Result::make()->failed('Not enough backups found'); @@ -103,48 +121,66 @@ public function run(): Result } } + $youngestBackup = $this->getYoungestBackup($eligableBackups); + if ($this->youngestShouldHaveBeenMadeBefore) { - if ($this->youngestBackupIsToolOld($eligableBackups)) { + if (!$youngestBackup || $this->youngestBackupIsToolOld($youngestBackup)) { return Result::make() ->failed('Youngest backup was too old'); } } + $oldestBackup = $this->getOldestBackup($eligableBackups); + if ($this->oldestShouldHaveBeenMadeAfter) { - if ($this->oldestBackupIsTooYoung($eligableBackups)) { + if (!$oldestBackup || $this->oldestBackupIsTooYoung($eligableBackups)) { return Result::make() ->failed('Oldest backup was too young'); } } + if ($eligableBackups->isEmpty()) { + return Result::make()->failed('Backups are not large enough'); + } + + if ($this->onlyCheckSizeOnFirstAndLast) { + $eligableBackups = collect([$youngestBackup, $oldestBackup]); + } + + if ( + $eligableBackups->filter(function (BackupFile $file) { + return $file->size() >= $this->minimumSizeInMegabytes * 1024 * 1024; + })->isEmpty() + ) { + return Result::make()->failed('Backups are not large enough'); + } + return Result::make()->ok(); } - /** - * @param Collection $backups - */ - protected function youngestBackupIsToolOld(Collection $backups): bool + protected function getYoungestBackup(Collection $backups): ?BackupFile { - /** @var SymfonyFile|null $youngestBackup */ - $youngestBackup = $backups + return $backups ->sortByDesc(fn (BackupFile $file) => $file->lastModified()) ->first(); + } + protected function youngestBackupIsToolOld(BackupFile $youngestBackup): bool + { $threshold = $this->youngestShouldHaveBeenMadeBefore->getTimestamp(); return $youngestBackup->lastModified() <= $threshold; } - /** - * @param Collection $backups - */ - protected function oldestBackupIsTooYoung(Collection $backups): bool + protected function getOldestBackup(Collection $backups): ?BackupFile { - /** @var SymfonyFile|null $oldestBackup */ - $oldestBackup = $backups + return $backups ->sortBy(fn (BackupFile $file) => $file->lastModified()) ->first(); + } + protected function oldestBackupIsTooYoung(BackupFile $oldestBackup): bool + { $threshold = $this->oldestShouldHaveBeenMadeAfter->getTimestamp(); return $oldestBackup->lastModified() >= $threshold; diff --git a/src/Support/BackupFile.php b/src/Support/BackupFile.php index f769b7d9..db3fc08b 100644 --- a/src/Support/BackupFile.php +++ b/src/Support/BackupFile.php @@ -2,7 +2,10 @@ namespace Spatie\Health\Support; +use Carbon\Exceptions\InvalidFormatException; use Illuminate\Contracts\Filesystem\Filesystem; +use Illuminate\Support\Carbon; +use Illuminate\Support\Str; use Symfony\Component\HttpFoundation\File\File as SymfonyFile; class BackupFile @@ -12,6 +15,7 @@ class BackupFile public function __construct( protected string $path, protected ?Filesystem $disk = null, + protected ?string $parseModifiedUsing = null, ) { if (! $disk) { $this->file = new SymfonyFile($path); @@ -28,8 +32,20 @@ public function size(): int return $this->file ? $this->file->getSize() : $this->disk->size($this->path); } - public function lastModified(): int + public function lastModified(): ?int { + if ($this->parseModifiedUsing) { + $filename = Str::before($this->path, '.'); + + $format = 'Y-m-d_H-i-s'; + + try { + return Carbon::createFromFormat($format, $filename)->timestamp; + } catch (InvalidFormatException $e) { + return null; + } + } + return $this->file ? $this->file->getMTime() : $this->disk->lastModified($this->path); } } From 999f3ba75cb4a4726ab17568ee6df8e4e428cfd0 Mon Sep 17 00:00:00 2001 From: Peter Elmered Date: Thu, 1 Aug 2024 15:04:06 +0200 Subject: [PATCH 2/9] Fix merge issue --- src/Checks/Checks/BackupsCheck.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Checks/Checks/BackupsCheck.php b/src/Checks/Checks/BackupsCheck.php index 26427768..7359efdb 100644 --- a/src/Checks/Checks/BackupsCheck.php +++ b/src/Checks/Checks/BackupsCheck.php @@ -53,13 +53,6 @@ public function parseModifiedFormat($parseModifiedFormat = 'Y-m-d_H-i-s'): self return $this; } - public function onDisk($disk) - { - $this->disk = Storage::disk($disk); - - return $this; - } - public function youngestBackShouldHaveBeenMadeBefore(Carbon $date): self { $this->youngestShouldHaveBeenMadeBefore = $date; From ab08ae7afe0a05cfbedbea66c894d8f941d1b2e5 Mon Sep 17 00:00:00 2001 From: Peter Elmered Date: Thu, 1 Aug 2024 15:10:04 +0200 Subject: [PATCH 3/9] Fix parseModifiedUsing default value --- src/Checks/Checks/BackupsCheck.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Checks/Checks/BackupsCheck.php b/src/Checks/Checks/BackupsCheck.php index 7359efdb..28c8ec8e 100644 --- a/src/Checks/Checks/BackupsCheck.php +++ b/src/Checks/Checks/BackupsCheck.php @@ -22,7 +22,7 @@ class BackupsCheck extends Check protected ?Carbon $oldestShouldHaveBeenMadeAfter = null; - protected string $parseModifiedUsing = 'Y-m-d_H-i-s'; + protected ?string $parseModifiedUsing = null; protected int $minimumSizeInMegabytes = 0; @@ -126,7 +126,7 @@ public function run(): Result $oldestBackup = $this->getOldestBackup($eligableBackups); if ($this->oldestShouldHaveBeenMadeAfter) { - if (!$oldestBackup || $this->oldestBackupIsTooYoung($eligableBackups)) { + if (!$oldestBackup || $this->oldestBackupIsTooYoung($oldestBackup)) { return Result::make() ->failed('Oldest backup was too young'); } From dce4245365f7f4627c59fb1169fcd33f85ad6694 Mon Sep 17 00:00:00 2001 From: Peter Elmered Date: Thu, 1 Aug 2024 15:22:23 +0200 Subject: [PATCH 4/9] Fix: use configured date format and strip directories from filename --- src/Support/BackupFile.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Support/BackupFile.php b/src/Support/BackupFile.php index db3fc08b..02881500 100644 --- a/src/Support/BackupFile.php +++ b/src/Support/BackupFile.php @@ -35,12 +35,10 @@ public function size(): int public function lastModified(): ?int { if ($this->parseModifiedUsing) { - $filename = Str::before($this->path, '.'); - - $format = 'Y-m-d_H-i-s'; + $filename = Str::of($this->path)->afterLast('/')->before('.'); try { - return Carbon::createFromFormat($format, $filename)->timestamp; + return Carbon::createFromFormat($this->parseModifiedUsing, $filename)->timestamp; } catch (InvalidFormatException $e) { return null; } From 87e7e1e6d628a00a1d99cdf58f26187775a0726a Mon Sep 17 00:00:00 2001 From: Peter Elmered Date: Thu, 1 Aug 2024 15:53:16 +0200 Subject: [PATCH 5/9] Add tests --- tests/Checks/BackupsCheckTest.php | 67 +++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/tests/Checks/BackupsCheckTest.php b/tests/Checks/BackupsCheckTest.php index 00cf2dcb..b2fa2c7b 100644 --- a/tests/Checks/BackupsCheckTest.php +++ b/tests/Checks/BackupsCheckTest.php @@ -1,9 +1,11 @@ run(); expect($result)->status->toBe(Status::failed()); }); + +it('can parse modified time from file name', function ($format) { + Storage::fake('backups'); + + $now = now(); + Storage::disk('backups')->put('backups/'.$now->format($format).'.zip', 'content'); + + $result1 = $this->backupsCheck + ->onDisk('backups') + ->locatedAt('backups') + ->parseModifiedFormat($format) + ->oldestBackShouldHaveBeenMadeAfter($now->subMinutes(5)) + ->run(); + + testTime()->addMinutes(6); + + $backupFile = new BackupFile('backups/'.$now->format($format).'.zip', Storage::disk('backups'), $format); + + expect($backupFile->lastModified())->toBe($now->timestamp); + + $result2 = $this->backupsCheck + ->onDisk('backups') + ->locatedAt('backups') + ->parseModifiedFormat($format) + ->oldestBackShouldHaveBeenMadeAfter(now()->subMinutes(5)) + ->run(); + + expect($result1)->status->toBe(Status::failed()) + ->and($result2)->status->toBe(Status::ok()); + + testTime()->addMinutes(2); + + $result = $this->backupsCheck + ->locatedAt($this->temporaryDirectory->path('*.zip')) + ->oldestBackShouldHaveBeenMadeAfter(now()->subMinutes(5)) + ->run(); + expect($result)->status->toBe(Status::failed()); +})->with([ + ['Y-m-d_H-i-s'], + ['Ymd_His'], + ['YmdHis'], + ['\B\a\c\k\u\p_Ymd_His'], +]); + +it('can check the size of only the first and last backup files', function () { + $now = now()->startOfMinute(); + + addTestFile($this->temporaryDirectory->path('hey1.zip'), date: $now, sizeInMb: 5); + addTestFile($this->temporaryDirectory->path('hey2.zip'), date: $now->addMinutes(10), sizeInMb: 10); + addTestFile($this->temporaryDirectory->path('hey3.zip'), date: $now->addMinutes(20), sizeInMb: 5); + + $result1 = $this->backupsCheck + ->locatedAt($this->temporaryDirectory->path('*.zip')) + ->atLeastSizeInMb(9) + ->run(); + + $result2 = $this->backupsCheck + ->locatedAt($this->temporaryDirectory->path('*.zip')) + ->atLeastSizeInMb(9) + ->onlyCheckSizeOnFirstAndLast() + ->run(); + + expect($result1)->status->toBe(Status::ok()) + ->and($result2)->status->toBe(Status::failed()); +}); From 652730945b35cee01490101f0beaffccc749f98a Mon Sep 17 00:00:00 2001 From: Peter Elmered Date: Fri, 2 Aug 2024 15:15:37 +0200 Subject: [PATCH 6/9] Add docs for using filesystem disks and performance optimizations --- docs/available-checks/backups.md | 38 ++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/available-checks/backups.md b/docs/available-checks/backups.md index c6907a33..456c1f98 100644 --- a/docs/available-checks/backups.md +++ b/docs/available-checks/backups.md @@ -86,3 +86,41 @@ Health::checks([ ->atLeastSizeInMb(20), ]); ``` + +### Check backups on external filesystems + +You can use the `onDisk` method to specify any disk you have configured in Laravel. +This is useful when the backups are stored on an external filesystem. + +```php +use Spatie\Health\Facades\Health; +use Spatie\Health\Checks\Checks\BackupsCheck; + +Health::checks([ + BackupsCheck::new() + ->onDisk('backups') + ->locatedAt('backups'), +]); +``` + +Checking backup files on external filesystems can be slow if you have a lot of backup files. +* You can use the `parseModifiedFormat` method to get the modified date of the file from the name instead of reaching out to the file and read its metadata. This strips out the file folder and file extension and uses the remaining string to parse the date with `Carbon::createFromFormat`. +* You can also limit the size check to only the first and last backup files by using the `onlyCheckSizeOnFirstAndLast` method. Otherwise the check needs to reach out to all files and check the file sizes. + +These two things can speed up the check of ~200 files on an S3 bucket from about 30 seconds to about 1 second. + +```php +use Spatie\Health\Facades\Health; +use Spatie\Health\Checks\Checks\BackupsCheck; + +Health::checks([ + BackupsCheck::new() + ->onDisk('backups') + ->parseModifiedFormat('Y-m-d_H-i-s'), + ->atLeastSizeInMb(20), + ->onlyCheckSizeOnFirstAndLast() +]); +``` + +For files that contains more than just the date you can use something like parseModifiedFormat('\b\a\c\k\u\p_Ymd_His') +which would parse a file with the name similar to `backup_20240101_120000.sql.zip`. From 22761f14ddc8555ec8cf5be0d9904b972c5de474 Mon Sep 17 00:00:00 2001 From: Peter Elmered Date: Fri, 2 Aug 2024 15:16:43 +0200 Subject: [PATCH 7/9] Refactor/simplify BackupsCheck + add metadata --- src/Checks/Checks/BackupsCheck.php | 93 +++++++++++++++--------------- src/Checks/Result.php | 7 +++ 2 files changed, 54 insertions(+), 46 deletions(-) diff --git a/src/Checks/Checks/BackupsCheck.php b/src/Checks/Checks/BackupsCheck.php index 28c8ec8e..2fda45b7 100644 --- a/src/Checks/Checks/BackupsCheck.php +++ b/src/Checks/Checks/BackupsCheck.php @@ -48,7 +48,7 @@ public function onDisk($disk) public function parseModifiedFormat($parseModifiedFormat = 'Y-m-d_H-i-s'): self { - $this->parseModifiedUsing = $parseModifiedFormat; + $this->parseModifiedUsing = $parseModifiedFormat; return $this; } @@ -91,64 +91,65 @@ public function numberOfBackups(?int $min = null, ?int $max = null): self public function run(): Result { - $files = collect($this->disk ? $files = $this->disk->files($this->locatedAt) : File::glob($this->locatedAt)); + $eligibleBackups = $this->getBackupFiles(); - if ($files->isEmpty()) { - return Result::make()->failed('No backups found'); - } + $backupCount = $eligibleBackups->count(); - $eligableBackups = $files - ->map(function (string $path) { - return new BackupFile($path, $this->disk, $this->parseModifiedUsing); - }); + $result = Result::make()->meta([ + 'minimum_size' => $this->minimumSizeInMegabytes.'MB', + 'backup_count' => $backupCount, + ]); - if ($this->minimumNumberOfBackups) { - if ($eligableBackups->count() < $this->minimumNumberOfBackups) { - return Result::make()->failed('Not enough backups found'); - } + if ($backupCount === 0) { + return $result->failed('No backups found'); } - if ($this->maximumNumberOfBackups) { - if ($eligableBackups->count() > $this->maximumNumberOfBackups) { - return Result::make()->failed('Too many backups found'); - } + if ($this->minimumNumberOfBackups && $backupCount < $this->minimumNumberOfBackups) { + return $result->failed('Not enough backups found'); } - $youngestBackup = $this->getYoungestBackup($eligableBackups); - - if ($this->youngestShouldHaveBeenMadeBefore) { - if (!$youngestBackup || $this->youngestBackupIsToolOld($youngestBackup)) { - return Result::make() - ->failed('Youngest backup was too old'); - } + if ($this->maximumNumberOfBackups && $backupCount > $this->maximumNumberOfBackups) { + return $result->failed('Too many backups found'); } - $oldestBackup = $this->getOldestBackup($eligableBackups); + $youngestBackup = $this->getYoungestBackup($eligibleBackups); + $oldestBackup = $this->getOldestBackup($eligibleBackups); - if ($this->oldestShouldHaveBeenMadeAfter) { - if (!$oldestBackup || $this->oldestBackupIsTooYoung($oldestBackup)) { - return Result::make() - ->failed('Oldest backup was too young'); - } - } + $result->appendMeta([ + 'youngest_backup' => Carbon::createFromTimestamp($youngestBackup?->lastModified())->toDateTimeString(), + 'oldest_backup' => Carbon::createFromTimestamp($oldestBackup?->lastModified())->toDateTimeString(), + ]); - if ($eligableBackups->isEmpty()) { - return Result::make()->failed('Backups are not large enough'); + if ($this->youngestShouldHaveBeenMadeBefore && $this->youngestBackupIsToolOld($youngestBackup)) { + return $result->failed('The youngest backup was too old'); } - if ($this->onlyCheckSizeOnFirstAndLast) { - $eligableBackups = collect([$youngestBackup, $oldestBackup]); + if ($this->oldestShouldHaveBeenMadeAfter && $this->oldestBackupIsTooYoung($oldestBackup)) { + return $result->failed('The oldest backup was too young'); } - if ( - $eligableBackups->filter(function (BackupFile $file) { - return $file->size() >= $this->minimumSizeInMegabytes * 1024 * 1024; - })->isEmpty() - ) { - return Result::make()->failed('Backups are not large enough'); + $backupsToCheckSizeOn = $this->onlyCheckSizeOnFirstAndLast + ? collect([$youngestBackup, $oldestBackup]) + : $eligibleBackups; + + if ($backupsToCheckSizeOn->filter( + fn(BackupFile $file) => $file->size() >= $this->minimumSizeInMegabytes * 1024 * 1024 + )->isEmpty()) { + return $result->failed('Backups are not large enough'); } - return Result::make()->ok(); + return $result->ok(); + } + + protected function getBackupFiles(): Collection + { + return collect( + $this->disk + ? $this->disk->files($this->locatedAt) + : File::glob($this->locatedAt) + )->map(function (string $path) { + return new BackupFile($path, $this->disk, $this->parseModifiedUsing); + }); } protected function getYoungestBackup(Collection $backups): ?BackupFile @@ -158,11 +159,11 @@ protected function getYoungestBackup(Collection $backups): ?BackupFile ->first(); } - protected function youngestBackupIsToolOld(BackupFile $youngestBackup): bool + protected function youngestBackupIsToolOld(?BackupFile $youngestBackup): bool { $threshold = $this->youngestShouldHaveBeenMadeBefore->getTimestamp(); - return $youngestBackup->lastModified() <= $threshold; + return !$youngestBackup || $youngestBackup->lastModified() <= $threshold; } protected function getOldestBackup(Collection $backups): ?BackupFile @@ -172,10 +173,10 @@ protected function getOldestBackup(Collection $backups): ?BackupFile ->first(); } - protected function oldestBackupIsTooYoung(BackupFile $oldestBackup): bool + protected function oldestBackupIsTooYoung(?BackupFile $oldestBackup): bool { $threshold = $this->oldestShouldHaveBeenMadeAfter->getTimestamp(); - return $oldestBackup->lastModified() >= $threshold; + return !$oldestBackup || $oldestBackup->lastModified() >= $threshold; } } diff --git a/src/Checks/Result.php b/src/Checks/Result.php index 18e2eda3..c4f586a2 100644 --- a/src/Checks/Result.php +++ b/src/Checks/Result.php @@ -103,6 +103,13 @@ public function meta(array $meta): self return $this; } + public function appendMeta($meta): self + { + $this->meta = array_merge($this->meta, $meta); + + return $this; + } + public function endedAt(CarbonInterface $carbon): self { $this->ended_at = $carbon; From 2fe68f8791199e1af3a21af4b53ad085129d7835 Mon Sep 17 00:00:00 2001 From: Peter Elmered Date: Fri, 2 Aug 2024 15:42:51 +0200 Subject: [PATCH 8/9] Add return type for BackupsCheck->onDisk --- src/Checks/Checks/BackupsCheck.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Checks/Checks/BackupsCheck.php b/src/Checks/Checks/BackupsCheck.php index 2fda45b7..15c843d7 100644 --- a/src/Checks/Checks/BackupsCheck.php +++ b/src/Checks/Checks/BackupsCheck.php @@ -39,7 +39,7 @@ public function locatedAt(string $globPath): self return $this; } - public function onDisk($disk) + public function onDisk($disk): static { $this->disk = Storage::disk($disk); From 832c90b86bc3418c1d60c9ac493f612c999af7ed Mon Sep 17 00:00:00 2001 From: Peter Elmered Date: Fri, 2 Aug 2024 15:55:02 +0200 Subject: [PATCH 9/9] Minor code improvements --- src/Checks/Checks/BackupsCheck.php | 27 ++++++++++++++++++--------- src/Support/BackupFile.php | 2 +- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/Checks/Checks/BackupsCheck.php b/src/Checks/Checks/BackupsCheck.php index 15c843d7..9637778a 100644 --- a/src/Checks/Checks/BackupsCheck.php +++ b/src/Checks/Checks/BackupsCheck.php @@ -39,14 +39,14 @@ public function locatedAt(string $globPath): self return $this; } - public function onDisk($disk): static + public function onDisk(string $disk): static { $this->disk = Storage::disk($disk); return $this; } - public function parseModifiedFormat($parseModifiedFormat = 'Y-m-d_H-i-s'): self + public function parseModifiedFormat(string $parseModifiedFormat = 'Y-m-d_H-i-s'): self { $this->parseModifiedUsing = $parseModifiedFormat; @@ -67,14 +67,15 @@ public function oldestBackShouldHaveBeenMadeAfter(Carbon $date): self return $this; } - public function atLeastSizeInMb(int $minimumSizeInMegabytes, $onlyCheckFirstAndLast = false): self + public function atLeastSizeInMb(int $minimumSizeInMegabytes, bool $onlyCheckFirstAndLast = false): self { $this->minimumSizeInMegabytes = $minimumSizeInMegabytes; + $this->onlyCheckSizeOnFirstAndLast = $onlyCheckFirstAndLast; return $this; } - public function onlyCheckSizeOnFirstAndLast($onlyCheckSizeOnFirstAndLast = true): self + public function onlyCheckSizeOnFirstAndLast(bool $onlyCheckSizeOnFirstAndLast = true): self { $this->onlyCheckSizeOnFirstAndLast = $onlyCheckSizeOnFirstAndLast; @@ -116,15 +117,15 @@ public function run(): Result $oldestBackup = $this->getOldestBackup($eligibleBackups); $result->appendMeta([ - 'youngest_backup' => Carbon::createFromTimestamp($youngestBackup?->lastModified())->toDateTimeString(), - 'oldest_backup' => Carbon::createFromTimestamp($oldestBackup?->lastModified())->toDateTimeString(), + 'youngest_backup' => $youngestBackup ? Carbon::createFromTimestamp($youngestBackup->lastModified())->toDateTimeString() : null, + 'oldest_backup' => $oldestBackup ? Carbon::createFromTimestamp($oldestBackup->lastModified())->toDateTimeString() : null, ]); - if ($this->youngestShouldHaveBeenMadeBefore && $this->youngestBackupIsToolOld($youngestBackup)) { + if ($this->youngestBackupIsToolOld($youngestBackup)) { return $result->failed('The youngest backup was too old'); } - if ($this->oldestShouldHaveBeenMadeAfter && $this->oldestBackupIsTooYoung($oldestBackup)) { + if ($this->oldestBackupIsTooYoung($oldestBackup)) { return $result->failed('The oldest backup was too young'); } @@ -146,7 +147,7 @@ protected function getBackupFiles(): Collection return collect( $this->disk ? $this->disk->files($this->locatedAt) - : File::glob($this->locatedAt) + : File::glob($this->locatedAt ?? '') )->map(function (string $path) { return new BackupFile($path, $this->disk, $this->parseModifiedUsing); }); @@ -161,6 +162,10 @@ protected function getYoungestBackup(Collection $backups): ?BackupFile protected function youngestBackupIsToolOld(?BackupFile $youngestBackup): bool { + if ($this->youngestShouldHaveBeenMadeBefore === null) { + return false; + } + $threshold = $this->youngestShouldHaveBeenMadeBefore->getTimestamp(); return !$youngestBackup || $youngestBackup->lastModified() <= $threshold; @@ -175,6 +180,10 @@ protected function getOldestBackup(Collection $backups): ?BackupFile } protected function oldestBackupIsTooYoung(?BackupFile $oldestBackup): bool { + if ($this->oldestShouldHaveBeenMadeAfter === null) { + return false; + } + $threshold = $this->oldestShouldHaveBeenMadeAfter->getTimestamp(); return !$oldestBackup || $oldestBackup->lastModified() >= $threshold; diff --git a/src/Support/BackupFile.php b/src/Support/BackupFile.php index 02881500..bd7af1b2 100644 --- a/src/Support/BackupFile.php +++ b/src/Support/BackupFile.php @@ -38,7 +38,7 @@ public function lastModified(): ?int $filename = Str::of($this->path)->afterLast('/')->before('.'); try { - return Carbon::createFromFormat($this->parseModifiedUsing, $filename)->timestamp; + return (int) Carbon::createFromFormat($this->parseModifiedUsing, $filename)->timestamp; } catch (InvalidFormatException $e) { return null; }