diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 63a8f070..2e52031d 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -17,7 +17,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - php: [8.3, 8.2, 8.1] + php: [8.3, 8.2] laravel: [11.*, 10.*] stability: [prefer-lowest, prefer-stable] carbon: [^2.63] @@ -26,9 +26,6 @@ jobs: testbench: 8.* - laravel: 11.* testbench: 9.* - exclude: - - laravel: 11.* - php: 8.1 name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} diff --git a/.gitignore b/.gitignore index f3ae3118..bf66b2e0 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ composer.lock vendor .phpunit.result.cache .php-cs-fixer.cache +.phpunit.cache diff --git a/README.md b/README.md index 5c4b84d9..7af5325d 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ We highly appreciate you sending us a postcard from your hometown, mentioning wh ## Installation and usage -This package requires PHP 8.0 and Laravel 8.0 or higher. +This package requires PHP 8.2 and Laravel 10.0 or higher. You'll find installation instructions and full documentation on https://spatie.be/docs/laravel-backup. ## Using an older version of PHP / Laravel? diff --git a/UPGRADING.md b/UPGRADING.md index ff835644..18332c8d 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,6 +1,7 @@ ## From v8 to v9 -- (All keys in the config file are still snake_cased. Rename any camelCased keys to their snake_cased counterparts) +- Ensure your config/backup.php file is in sync with the latest settings. You can copy paste the missing defaults from this [config file](https://github.com/spatie/laravel-backup/blob/main/config/backup.php). +- All keys in the config file are still snake_cased. Rename any camelCased keys to their snake_cased counterparts ## From v7 to v8 diff --git a/composer.json b/composer.json index 173939cf..ba7e9803 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-zip": "^1.14.0", "illuminate/console": "^10.10.0|^11.0", "illuminate/contracts": "^10.10.0|^11.0", @@ -32,10 +32,10 @@ "spatie/laravel-signal-aware-command": "^1.2|^2.0", "spatie/temporary-directory": "^2.0", "symfony/console": "^6.0|^7.0", - "symfony/finder": "^6.0|^7.0" + "symfony/finder": "^6.0|^7.0", + "ext-pcntl": "*" }, "require-dev": { - "ext-pcntl": "*", "composer-runtime-api": "^2.0", "larastan/larastan": "^2.7.0", "laravel/slack-notification-channel": "^2.5|^3.0", @@ -45,7 +45,8 @@ "pestphp/pest": "^1.20|^2.0", "phpstan/extension-installer": "^1.1", "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-phpunit": "^1.1" + "phpstan/phpstan-phpunit": "^1.1", + "rector/rector": "^1.1" }, "autoload": { "psr-4": { @@ -61,9 +62,11 @@ } }, "scripts": { - "test": "vendor/bin/pest", + "test": "vendor/bin/pest --compact", "format": "vendor/bin/php-cs-fixer fix --allow-risky=yes", - "analyse": "vendor/bin/phpstan analyse" + "analyse": "vendor/bin/phpstan analyse", + "rector": "vendor/bin/rector --dry-run", + "baseline": "./vendor/bin/phpstan analyse --generate-baseline --memory-limit=2G" }, "suggest": { "laravel/slack-notification-channel": "Required for sending notifications via Slack" diff --git a/docs/requirements.md b/docs/requirements.md index 0e4775be..846a52fc 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -3,7 +3,7 @@ title: Requirements weight: 3 --- -This backup package requires **PHP 8.0**, with the [ZIP module](http://php.net/manual/en/book.zip.php) and **Laravel 9.0 or higher**. It's not compatible with Windows servers. +This backup package requires **PHP 8.2**, with the [ZIP module](http://php.net/manual/en/book.zip.php) and **Laravel 9.0 or higher**. It's not compatible with Windows servers. If you are using an older version of Laravel, take a look at one of the previous versions of this package. diff --git a/docs/sending-notifications/customizing-the-notifiable.md b/docs/sending-notifications/customizing-the-notifiable.md index c3b21a82..c3dc1859 100644 --- a/docs/sending-notifications/customizing-the-notifiable.md +++ b/docs/sending-notifications/customizing-the-notifiable.md @@ -20,7 +20,7 @@ class BackupNotifiable extends Notifiable { public function routeNotificationForAnotherNotificationChannel() { - return config('backup.notifications.another_notification_channel.property'); + return $this->config()->notifications->another_notification_channel->property; } } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 364905f7..80e7aa86 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,2 +1,51 @@ parameters: ignoreErrors: + - + message: "#^Parameter \\#1 \\$callback of method Illuminate\\\\Support\\\\Collection\\\\:\\:filter\\(\\) expects \\(callable\\(Spatie\\\\Backup\\\\BackupDestination\\\\Backup, int\\)\\: bool\\)\\|null, Closure\\(string\\)\\: bool given\\.$#" + count: 1 + path: src/BackupDestination/BackupCollection.php + + - + message: "#^Parameter \\#1 \\$callback of method Illuminate\\\\Support\\\\Collection\\\\:\\:map\\(\\) expects callable\\(Spatie\\\\Backup\\\\BackupDestination\\\\Backup, int\\)\\: Spatie\\\\Backup\\\\BackupDestination\\\\Backup, Closure\\(string\\)\\: Spatie\\\\Backup\\\\BackupDestination\\\\Backup given\\.$#" + count: 1 + path: src/BackupDestination/BackupCollection.php + + - + message: "#^Parameter \\#1 \\$callback of method Illuminate\\\\Support\\\\Collection\\\\:\\:map\\(\\) expects callable\\(int, int\\)\\: string, Closure\\(string\\)\\: non\\-falsy\\-string given\\.$#" + count: 1 + path: src/Exceptions/CannotCreateDbDumper.php + + - + message: "#^Method Spatie\\\\Backup\\\\Notifications\\\\Channels\\\\Discord\\\\DiscordMessage\\:\\:toArray\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: src/Notifications/Channels/Discord/DiscordMessage.php + + - + message: "#^Method Spatie\\\\Backup\\\\Tasks\\\\Backup\\\\FileSelection\\:\\:sanitize\\(\\) return type with generic class Illuminate\\\\Support\\\\Collection does not specify its types\\: TKey, TValue$#" + count: 1 + path: src/Tasks/Backup/FileSelection.php + + - + message: "#^Method Spatie\\\\Backup\\\\Tasks\\\\Backup\\\\Manifest\\:\\:files\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: src/Tasks/Backup/Manifest.php + + - + message: "#^Method Spatie\\\\Backup\\\\Tasks\\\\Backup\\\\Manifest\\:\\:files\\(\\) return type has no value type specified in iterable type array\\|Generator\\.$#" + count: 1 + path: src/Tasks/Backup/Manifest.php + + - + message: "#^Method Spatie\\\\Backup\\\\Tasks\\\\Backup\\\\Zip\\:\\:add\\(\\) has parameter \\$files with no value type specified in iterable type iterable\\.$#" + count: 1 + path: src/Tasks/Backup/Zip.php + + - + message: "#^Parameter \\#1 \\$callback of method Illuminate\\\\Support\\\\Collection\\\\:\\:each\\(\\) expects callable\\(Spatie\\\\Backup\\\\BackupDestination\\\\Backup, int\\)\\: mixed, Closure\\(Spatie\\\\Backup\\\\BackupDestination\\\\BackupCollection\\)\\: void given\\.$#" + count: 1 + path: src/Tasks/Cleanup/Strategies/DefaultStrategy.php + + - + message: "#^Parameter \\#1 \\$callback of method Illuminate\\\\Support\\\\Collection\\<\\(int\\|string\\),Spatie\\\\Backup\\\\Config\\\\MonitoredBackupConfig\\>\\:\\:flatMap\\(\\) expects callable\\(Spatie\\\\Backup\\\\Config\\\\MonitoredBackupConfig, int\\|string\\)\\: \\(array\\\\|Illuminate\\\\Support\\\\Collection\\\\), Closure\\(array\\)\\: Illuminate\\\\Support\\\\Collection\\ given\\.$#" + count: 1 + path: src/Tasks/Monitor/BackupDestinationStatusFactory.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 2670010c..c5770235 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -2,14 +2,14 @@ includes: - phpstan-baseline.neon parameters: - level: 5 + level: 6 paths: - src - config tmpDir: build/phpstan checkOctaneCompatibility: true checkModelProperties: true - checkMissingIterableValueType: false + treatPhpDocTypesAsCertain: false ignoreErrors: - '#Unsafe usage of new static#' diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 92ed9fe9..6f21f52f 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,10 +1,15 @@ - - - - src/ - - + tests @@ -13,4 +18,9 @@ + + + src/ + + diff --git a/rector.php b/rector.php new file mode 100644 index 00000000..836677e8 --- /dev/null +++ b/rector.php @@ -0,0 +1,38 @@ +withPaths(['config', 'resources', 'src']) + ->withPhpSets(php81: true) + ->withPreparedSets(deadCode: true, codingStyle: true, typeDeclarations: true) + ->withSkip([ + ReturnNeverTypeRector::class, + OptionalParametersAfterRequiredRector::class, + ClosureToArrowFunctionRector::class, + FlipTypeControlToUseExclusiveTypeRector::class, + ExplicitBoolCompareRector::class, + EncapsedStringsToSprintfRector::class, + StaticClosureRector::class, + StaticArrowFunctionRector::class, + UseIncrementAssignRector::class, + PostIncDecToPreIncDecRector::class, + NullableCompareToNullRector::class, + AddArrowFunctionReturnTypeRector::class, + AddClosureVoidReturnTypeWhereNoReturnRector::class, + ]); diff --git a/resources/lang/fr/notifications.php b/resources/lang/fr/notifications.php index 2ae49765..ad60a5c9 100644 --- a/resources/lang/fr/notifications.php +++ b/resources/lang/fr/notifications.php @@ -1,10 +1,10 @@ 'Message de l\'exception : :message', - 'exception_trace' => 'Trace de l\'exception : :trace', - 'exception_message_title' => 'Message de l\'exception', - 'exception_trace_title' => 'Trace de l\'exception', + 'exception_message' => "Message de l'exception : :message", + 'exception_trace' => "Trace de l'exception : :trace", + 'exception_message_title' => "Message de l'exception", + 'exception_trace_title' => "Trace de l'exception", 'backup_failed_subject' => 'Échec de la sauvegarde de :application_name', 'backup_failed_body' => 'Important : Une erreur est survenue lors de la sauvegarde de :application_name', @@ -27,14 +27,14 @@ 'unhealthy_backup_found_subject' => 'Important : Les sauvegardes pour :application_name sont corrompues', 'unhealthy_backup_found_subject_title' => 'Important : Les sauvegardes pour :application_name sont corrompues. :problem', 'unhealthy_backup_found_body' => 'Les sauvegardes pour :application_name sur le disque :disk_name sont corrompues.', - 'unhealthy_backup_found_not_reachable' => 'La destination de la sauvegarde n\'est pas accessible. :error', - 'unhealthy_backup_found_empty' => 'Il n\'y a aucune sauvegarde pour cette application.', + 'unhealthy_backup_found_not_reachable' => "La destination de la sauvegarde n'est pas accessible. :error", + 'unhealthy_backup_found_empty' => "Il n'y a aucune sauvegarde pour cette application.", 'unhealthy_backup_found_old' => 'La dernière sauvegarde du :date est considérée trop vieille.', 'unhealthy_backup_found_unknown' => 'Désolé, une raison exacte ne peut être déterminée.', 'unhealthy_backup_found_full' => 'Les sauvegardes utilisent trop d\'espace disque. L\'utilisation actuelle est de :disk_usage alors que la limite autorisée est de :disk_limit.', 'no_backups_info' => 'Aucune sauvegarde n\'a encore été effectuée', - 'application_name' => 'Nom de l\'application', + 'application_name' => "Nom de l'application", 'backup_name' => 'Nom de la sauvegarde', 'disk' => 'Disque', 'newest_backup_size' => 'Taille de la sauvegarde la plus récente', diff --git a/resources/lang/it/notifications.php b/resources/lang/it/notifications.php index 94fe1415..e96618d0 100644 --- a/resources/lang/it/notifications.php +++ b/resources/lang/it/notifications.php @@ -1,10 +1,10 @@ 'Messaggio dell\'eccezione: :message', - 'exception_trace' => 'Traccia dell\'eccezione: :trace', - 'exception_message_title' => 'Messaggio dell\'eccezione', - 'exception_trace_title' => 'Traccia dell\'eccezione', + 'exception_message' => "Messaggio dell'eccezione: :message", + 'exception_trace' => "Traccia dell'eccezione: :trace", + 'exception_message_title' => "Messaggio dell'eccezione", + 'exception_trace_title' => "Traccia dell'eccezione", 'backup_failed_subject' => 'Fallito il backup di :application_name', 'backup_failed_body' => 'Importante: Si è verificato un errore durante il backup di :application_name', @@ -34,7 +34,7 @@ 'unhealthy_backup_found_full' => 'I backup utilizzano troppa memoria. L\'utilizzo corrente è :disk_usage che è superiore al limite consentito di :disk_limit.', 'no_backups_info' => 'Non sono stati ancora effettuati backup', - 'application_name' => 'Nome dell\'applicazione', + 'application_name' => "Nome dell'applicazione", 'backup_name' => 'Nome di backup', 'disk' => 'Disco', 'newest_backup_size' => 'Dimensione backup più recente', diff --git a/src/BackupDestination/Backup.php b/src/BackupDestination/Backup.php index b4b4c0f7..6a0d6d31 100644 --- a/src/BackupDestination/Backup.php +++ b/src/BackupDestination/Backup.php @@ -66,6 +66,7 @@ public function sizeInBytes(): float return $this->size; } + /** @return resource */ public function stream() { return throw_unless( diff --git a/src/BackupDestination/BackupCollection.php b/src/BackupDestination/BackupCollection.php index 514c1bfa..b3564010 100644 --- a/src/BackupDestination/BackupCollection.php +++ b/src/BackupDestination/BackupCollection.php @@ -6,15 +6,17 @@ use Illuminate\Support\Collection; use Spatie\Backup\Helpers\File; +/** @extends Collection */ class BackupCollection extends Collection { protected ?float $sizeCache = null; + /** @param array $files */ public static function createFromFiles(?FileSystem $disk, array $files): self { return (new static($files)) ->filter(fn (string $path) => (new File())->isZipFile($disk, $path)) - ->map(fn (string $path) => new Backup($disk, $path)) + ->map(fn (string $path): \Spatie\Backup\BackupDestination\Backup => new Backup($disk, $path)) ->sortByDesc(fn (Backup $backup) => $backup->date()->timestamp) ->values(); } diff --git a/src/BackupDestination/BackupDestination.php b/src/BackupDestination/BackupDestination.php index 9c689f6a..ec047c98 100644 --- a/src/BackupDestination/BackupDestination.php +++ b/src/BackupDestination/BackupDestination.php @@ -10,24 +10,11 @@ class BackupDestination { - protected ?Filesystem $disk; - - protected string $diskName; - - protected string $backupName; - public ?Exception $connectionError = null; protected ?BackupCollection $backupCollectionCache = null; - public function __construct(Filesystem $disk = null, string $backupName, string $diskName) - { - $this->disk = $disk; - - $this->diskName = $diskName; - - $this->backupName = $backupName; - } + public function __construct(protected ?Filesystem $disk, protected string $backupName, protected string $diskName) {} public function disk(): Filesystem { @@ -49,7 +36,7 @@ public function filesystemType(): string $filesystemType = last(explode('\\', $adapterClass)); - return strtolower($filesystemType); + return strtolower((string) $filesystemType); } public static function create(string $diskName, string $backupName): self @@ -125,6 +112,7 @@ public function connectionError(): Exception return $this->connectionError; } + /** @return array */ public function getDiskOptions(): array { return config("filesystems.disks.{$this->diskName()}.backup_options") ?? []; diff --git a/src/BackupDestination/BackupDestinationFactory.php b/src/BackupDestination/BackupDestinationFactory.php index 3212470a..af48085b 100644 --- a/src/BackupDestination/BackupDestinationFactory.php +++ b/src/BackupDestination/BackupDestinationFactory.php @@ -3,12 +3,16 @@ namespace Spatie\Backup\BackupDestination; use Illuminate\Support\Collection; +use Spatie\Backup\Config\Config; class BackupDestinationFactory { - public static function createFromArray(array $config): Collection + /** + * @return Collection + */ + public static function createFromArray(Config $config): Collection { - return collect($config['destination']['disks']) - ->map(fn ($filesystemName) => BackupDestination::create($filesystemName, $config['name'])); + return collect($config->backup->destination->disks) + ->map(fn (string $filesystemName) => BackupDestination::create($filesystemName, $config->backup->name)); } } diff --git a/src/BackupServiceProvider.php b/src/BackupServiceProvider.php index 64e4c663..037c5464 100644 --- a/src/BackupServiceProvider.php +++ b/src/BackupServiceProvider.php @@ -9,6 +9,7 @@ use Spatie\Backup\Commands\CleanupCommand; use Spatie\Backup\Commands\ListCommand; use Spatie\Backup\Commands\MonitorCommand; +use Spatie\Backup\Config\Config; use Spatie\Backup\Events\BackupZipWasCreated; use Spatie\Backup\Helpers\ConsoleOutput; use Spatie\Backup\Listeners\EncryptBackupArchive; @@ -34,7 +35,7 @@ public function configurePackage(Package $package): void ]); } - public function packageBooted() + public function packageBooted(): void { $this->app['events']->subscribe(EventHandler::class); @@ -43,19 +44,23 @@ public function packageBooted() } } - public function packageRegistered() + public function packageRegistered(): void { $this->app->singleton(ConsoleOutput::class); $this->app->bind(CleanupStrategy::class, config('backup.cleanup.strategy')); $this->registerDiscordChannel(); + + $this->app->scoped(Config::class, function (): Config { + return Config::fromArray(config('backup')); + }); } - protected function registerDiscordChannel() + protected function registerDiscordChannel(): void { Notification::resolved(function (ChannelManager $service) { - $service->extend('discord', function ($app) { + $service->extend('discord', function ($app): DiscordChannel { return new DiscordChannel(); }); }); diff --git a/src/Commands/BackupCommand.php b/src/Commands/BackupCommand.php index 6dba3b7c..3ddeb225 100644 --- a/src/Commands/BackupCommand.php +++ b/src/Commands/BackupCommand.php @@ -4,6 +4,7 @@ use Exception; use Illuminate\Contracts\Console\Isolatable; +use Spatie\Backup\Config\Config; use Spatie\Backup\Events\BackupHasFailed; use Spatie\Backup\Exceptions\BackupFailed; use Spatie\Backup\Exceptions\InvalidCommand; @@ -18,6 +19,11 @@ class BackupCommand extends BaseCommand implements Isolatable protected $description = 'Run the backup.'; + public function __construct(protected Config $config) + { + parent::__construct(); + } + public function handle(): int { consoleOutput()->comment($this->currentTry > 1 ? sprintf('Attempt n°%d...', $this->currentTry) : 'Starting backup...'); @@ -31,11 +37,12 @@ public function handle(): int try { $this->guardAgainstInvalidOptions(); - $backupJob = BackupJobFactory::createFromArray(config('backup')); + $backupJob = BackupJobFactory::createFromConfig($this->config); if ($this->option('only-db')) { $backupJob->dontBackupFilesystem(); } + if ($this->option('db-name')) { $backupJob->onlyDbName($this->option('db-name')); } @@ -94,7 +101,7 @@ public function handle(): int } } - protected function guardAgainstInvalidOptions() + protected function guardAgainstInvalidOptions(): void { if (! $this->option('only-db')) { return; diff --git a/src/Commands/BaseCommand.php b/src/Commands/BaseCommand.php index c12ad4f5..b05aaf56 100644 --- a/src/Commands/BaseCommand.php +++ b/src/Commands/BaseCommand.php @@ -10,6 +10,7 @@ abstract class BaseCommand extends SignalAwareCommand { + /** @var array */ protected array $handlesSignals = []; public function __construct() @@ -30,9 +31,10 @@ public function run(InputInterface $input, OutputInterface $output): int protected function runningInConsole(): bool { - return in_array(php_sapi_name(), ['cli', 'phpdbg']); + return in_array(PHP_SAPI, ['cli', 'phpdbg']); } + /** @return array */ public function getSubscribedSignals(): array { return $this->handlesSignals; diff --git a/src/Commands/CleanupCommand.php b/src/Commands/CleanupCommand.php index d770f07e..be4b5332 100644 --- a/src/Commands/CleanupCommand.php +++ b/src/Commands/CleanupCommand.php @@ -5,6 +5,7 @@ use Exception; use Illuminate\Contracts\Console\Isolatable; use Spatie\Backup\BackupDestination\BackupDestinationFactory; +use Spatie\Backup\Config\Config; use Spatie\Backup\Events\CleanupHasFailed; use Spatie\Backup\Tasks\Cleanup\CleanupJob; use Spatie\Backup\Tasks\Cleanup\CleanupStrategy; @@ -20,13 +21,11 @@ class CleanupCommand extends BaseCommand implements Isolatable /** @var string */ protected $description = 'Remove all backups older than specified number of days in config.'; - protected CleanupStrategy $strategy; - - public function __construct(CleanupStrategy $strategy) - { + public function __construct( + protected CleanupStrategy $strategy, + protected Config $config, + ) { parent::__construct(); - - $this->strategy = $strategy; } public function handle(): int @@ -38,9 +37,7 @@ public function handle(): int $this->setTries('cleanup'); try { - $config = config('backup'); - - $backupDestinations = BackupDestinationFactory::createFromArray($config['backup']); + $backupDestinations = BackupDestinationFactory::createFromArray($this->config); $cleanupJob = new CleanupJob($backupDestinations, $this->strategy, $disableNotifications); diff --git a/src/Commands/ListCommand.php b/src/Commands/ListCommand.php index ac9cada7..7e226eeb 100644 --- a/src/Commands/ListCommand.php +++ b/src/Commands/ListCommand.php @@ -4,6 +4,7 @@ use Illuminate\Support\Collection; use Spatie\Backup\BackupDestination\Backup; +use Spatie\Backup\Config\Config; use Spatie\Backup\Helpers\Format; use Spatie\Backup\Helpers\RightAlignedTableStyle; use Spatie\Backup\Tasks\Monitor\BackupDestinationStatus; @@ -17,24 +18,28 @@ class ListCommand extends BaseCommand /** @var string */ protected $description = 'Display a list of all backups.'; - public function handle(): int + public function __construct(protected Config $config) { - if (config()->has('backup.monitorBackups')) { - $this->warn("Warning! Your config file still uses the old monitorBackups key. Update it to monitor_backups."); - } + parent::__construct(); + } - $statuses = BackupDestinationStatusFactory::createForMonitorConfig(config('backup.monitor_backups')); + public function handle(): int + { + $statuses = BackupDestinationStatusFactory::createForMonitorConfig($this->config->monitoredBackups); $this->displayOverview($statuses)->displayFailures($statuses); return static::SUCCESS; } - protected function displayOverview(Collection $backupDestinationStatuses) + /** + * @param Collection $backupDestinationStatuses + */ + protected function displayOverview(Collection $backupDestinationStatuses): static { $headers = ['Name', 'Disk', 'Reachable', 'Healthy', '# of backups', 'Newest backup', 'Used storage']; - $rows = $backupDestinationStatuses->map(function (BackupDestinationStatus $backupDestinationStatus) { + $rows = $backupDestinationStatuses->map(function (BackupDestinationStatus $backupDestinationStatus): array { return $this->convertToRow($backupDestinationStatus); }); @@ -46,6 +51,7 @@ protected function displayOverview(Collection $backupDestinationStatuses) return $this; } + /** @return array{0: string, 1: string, 2: string, disk: string, amount: int, newest: string, usedStorage: string} */ public function convertToRow(BackupDestinationStatus $backupDestinationStatus): array { $destination = $backupDestinationStatus->backupDestination(); @@ -73,13 +79,14 @@ public function convertToRow(BackupDestinationStatus $backupDestinationStatus): return $row; } - protected function displayFailures(Collection $backupDestinationStatuses) + /** @param Collection $backupDestinationStatuses */ + protected function displayFailures(Collection $backupDestinationStatuses): static { $failed = $backupDestinationStatuses - ->filter(function (BackupDestinationStatus $backupDestinationStatus) { + ->filter(function (BackupDestinationStatus $backupDestinationStatus): bool { return $backupDestinationStatus->getHealthCheckFailure() !== null; }) - ->map(function (BackupDestinationStatus $backupDestinationStatus) { + ->map(function (BackupDestinationStatus $backupDestinationStatus): array { return [ $backupDestinationStatus->backupDestination()->backupName(), $backupDestinationStatus->backupDestination()->diskName(), @@ -98,7 +105,7 @@ protected function displayFailures(Collection $backupDestinationStatuses) return $this; } - protected function getFormattedBackupDate(Backup $backup = null) + protected function getFormattedBackupDate(?Backup $backup = null): string { return is_null($backup) ? 'No backups present' diff --git a/src/Commands/MonitorCommand.php b/src/Commands/MonitorCommand.php index f68040bf..d0adb34a 100644 --- a/src/Commands/MonitorCommand.php +++ b/src/Commands/MonitorCommand.php @@ -3,6 +3,7 @@ namespace Spatie\Backup\Commands; use Illuminate\Contracts\Console\Isolatable; +use Spatie\Backup\Config\Config; use Spatie\Backup\Events\HealthyBackupWasFound; use Spatie\Backup\Events\UnhealthyBackupWasFound; use Spatie\Backup\Tasks\Monitor\BackupDestinationStatusFactory; @@ -15,15 +16,16 @@ class MonitorCommand extends BaseCommand implements Isolatable /** @var string */ protected $description = 'Monitor the health of all backups.'; - public function handle(): int + public function __construct(protected Config $config) { - if (config()->has('backup.monitorBackups')) { - $this->warn("Warning! Your config file still uses the old monitorBackups key. Update it to monitor_backups."); - } + parent::__construct(); + } + public function handle(): int + { $hasError = false; - $statuses = BackupDestinationStatusFactory::createForMonitorConfig(config('backup.monitor_backups')); + $statuses = BackupDestinationStatusFactory::createForMonitorConfig($this->config->monitoredBackups); foreach ($statuses as $backupDestinationStatus) { $backupName = $backupDestinationStatus->backupDestination()->backupName(); diff --git a/src/Config/BackupConfig.php b/src/Config/BackupConfig.php new file mode 100644 index 00000000..dc0791e8 --- /dev/null +++ b/src/Config/BackupConfig.php @@ -0,0 +1,51 @@ +tries < 1) { + throw InvalidConfig::integerMustBePositive('tries'); + } + } + + /** @param array $data */ + public static function fromArray(array $data): self + { + $monitoredBackups = $data['monitored_backups'] ?? $data['monitorBackups'] ?? null; + + return new self( + name: $data['name'], + source: SourceConfig::fromArray($data['source']), + databaseDumpCompressor: $data['database_dump_compressor'] ?? null, + databaseDumpFileTimestampFormat: $data['database_dump_file_timestamp_format'] ?? null, + databaseDumpFilenameBase: $data['database_dump_filename_base'], + databaseDumpFileExtension: $data['database_dump_file_extension'], + destination: DestinationConfig::fromArray($data['destination']), + temporaryDirectory: $data['temporary_directory'] ?? null, + password: $data['password'], + encryption: $data['encryption'], + tries: $data['tries'], + retryDelay: $data['retry_delay'], + monitoredBackups: $monitoredBackups ? MonitoredBackupsConfig::fromArray($monitoredBackups) : null, + ); + } +} diff --git a/src/Config/CleanupConfig.php b/src/Config/CleanupConfig.php new file mode 100644 index 00000000..31a072e2 --- /dev/null +++ b/src/Config/CleanupConfig.php @@ -0,0 +1,39 @@ + $strategy + */ + protected function __construct( + public string $strategy, + public StrategyConfig $defaultStrategy, + public int $tries, + public int $retryDelay, + ) { + if ($this->tries < 1) { + throw InvalidConfig::integerMustBePositive('cleanup tries'); + } + + if (! class_exists($this->strategy)) { + throw InvalidConfig::invalidStrategy($this->strategy); + } + } + + /** @param array $data */ + public static function fromArray(array $data): self + { + return new self( + strategy: $data['strategy'], + defaultStrategy: StrategyConfig::fromArray($data['default_strategy']), + tries: $data['tries'], + retryDelay: $data['retry_delay'], + ); + } +} diff --git a/src/Config/Config.php b/src/Config/Config.php new file mode 100644 index 00000000..78f9787b --- /dev/null +++ b/src/Config/Config.php @@ -0,0 +1,34 @@ +scoped(Config::class, function (): \Spatie\Backup\Config\Config { + return self::fromArray(config('backup')); + }); + } + + /** @param array $data */ + public static function fromArray(array $data): self + { + return new self( + backup: BackupConfig::fromArray($data['backup']), + notifications: NotificationsConfig::fromArray($data['notifications']), + monitoredBackups: MonitoredBackupsConfig::fromArray($data['monitor_backups']), + cleanup: CleanupConfig::fromArray($data['cleanup']), + ); + } +} diff --git a/src/Config/DestinationConfig.php b/src/Config/DestinationConfig.php new file mode 100644 index 00000000..9894f727 --- /dev/null +++ b/src/Config/DestinationConfig.php @@ -0,0 +1,39 @@ + $compressionLevel + * @param array $disks + */ + protected function __construct( + public int $compressionMethod, + public int $compressionLevel, + public string $filenamePrefix, + public array $disks, + ) { + if ($compressionLevel > 9) { + throw InvalidConfig::integerMustBeBetween('compression_level', 0, 9); + } + + if ($compressionLevel < 0) { + throw InvalidConfig::integerMustBeBetween('compression_level', 0, 9); + } + } + + /** @param array $data */ + public static function fromArray(array $data): self + { + return new self( + compressionMethod: $data['compression_method'], + compressionLevel: $data['compression_level'], + filenamePrefix: $data['filename_prefix'], + disks: $data['disks'], + ); + } +} diff --git a/src/Config/MonitoredBackupConfig.php b/src/Config/MonitoredBackupConfig.php new file mode 100644 index 00000000..e24d5c40 --- /dev/null +++ b/src/Config/MonitoredBackupConfig.php @@ -0,0 +1,29 @@ + $disks + * @param array, int> $healthChecks + */ + protected function __construct( + public string $name, + public array $disks, + public array $healthChecks, + ) {} + + /** @param array $data */ + public static function fromArray(array $data): self + { + return new self( + name: $data['name'], + disks: $data['disks'], + healthChecks: $data['health_checks'], + ); + } +} diff --git a/src/Config/MonitoredBackupsConfig.php b/src/Config/MonitoredBackupsConfig.php new file mode 100644 index 00000000..7962247c --- /dev/null +++ b/src/Config/MonitoredBackupsConfig.php @@ -0,0 +1,25 @@ + $monitorBackups + */ + protected function __construct( + public array $monitorBackups, + ) {} + + /** @param array $data */ + public static function fromArray(array $data): self + { + return new self( + monitorBackups: collect($data) + ->map(fn (array $monitoredBackup) => MonitoredBackupConfig::fromArray($monitoredBackup)) + ->toArray(), + ); + } +} diff --git a/src/Config/NotificationDiscordConfig.php b/src/Config/NotificationDiscordConfig.php new file mode 100644 index 00000000..adbb3dc7 --- /dev/null +++ b/src/Config/NotificationDiscordConfig.php @@ -0,0 +1,24 @@ + $data */ + public static function fromArray(array $data): self + { + return new self( + webhookUrl: $data['webhook_url'], + username: $data['username'], + avatar_url: $data['avatar_url'], + ); + } +} diff --git a/src/Config/NotificationMailConfig.php b/src/Config/NotificationMailConfig.php new file mode 100644 index 00000000..65c04231 --- /dev/null +++ b/src/Config/NotificationMailConfig.php @@ -0,0 +1,31 @@ + $data + * + * @throws InvalidConfig + */ + public static function fromArray(array $data): self + { + if (! filter_var($data['to'], FILTER_VALIDATE_EMAIL)) { + throw InvalidConfig::invalidEmail($data['to']); + } + + return new self( + to: $data['to'], + from: NotificationMailSenderConfig::fromArray($data['from'] ?? []), + ); + } +} diff --git a/src/Config/NotificationMailSenderConfig.php b/src/Config/NotificationMailSenderConfig.php new file mode 100644 index 00000000..0e674551 --- /dev/null +++ b/src/Config/NotificationMailSenderConfig.php @@ -0,0 +1,33 @@ + $data */ + public static function fromArray(array $data): self + { + $address = $data['from']['address'] ?? config('mail.from.address'); + + if ($address === null) { + throw InvalidConfig::missingSender(); + } + + if ($address && ! filter_var($address, FILTER_VALIDATE_EMAIL)) { + throw InvalidConfig::invalidEmail($address); + } + + return new self( + address: $address, + name: $data['from']['name'] ?? config('mail.from.name'), + ); + } +} diff --git a/src/Config/NotificationSlackConfig.php b/src/Config/NotificationSlackConfig.php new file mode 100644 index 00000000..407ce415 --- /dev/null +++ b/src/Config/NotificationSlackConfig.php @@ -0,0 +1,26 @@ + $data */ + public static function fromArray(array $data): self + { + return new self( + webhookUrl: $data['webhook_url'], + channel: $data['channel'], + username: $data['username'], + icon: $data['icon'], + ); + } +} diff --git a/src/Config/NotificationsConfig.php b/src/Config/NotificationsConfig.php new file mode 100644 index 00000000..b2045e99 --- /dev/null +++ b/src/Config/NotificationsConfig.php @@ -0,0 +1,39 @@ +, array> $notifications + * @param class-string $notifiable + */ + protected function __construct( + public array $notifications, + public string $notifiable, + public NotificationMailConfig $mail, + public NotificationSlackConfig $slack, + public NotificationDiscordConfig $discord, + ) { + if (! class_exists($this->notifiable)) { + throw InvalidConfig::invalidStrategy($this->notifiable); + } + } + + /** @param array $data */ + public static function fromArray(array $data): self + { + return new self( + notifications: $data['notifications'], + notifiable: $data['notifiable'], + mail: NotificationMailConfig::fromArray($data['mail']), + slack: NotificationSlackConfig::fromArray($data['slack']), + discord: NotificationDiscordConfig::fromArray($data['discord']), + ); + } +} diff --git a/src/Config/SourceConfig.php b/src/Config/SourceConfig.php new file mode 100644 index 00000000..ce03aeaa --- /dev/null +++ b/src/Config/SourceConfig.php @@ -0,0 +1,25 @@ + $databases + */ + protected function __construct( + public SourceFilesConfig $files, + public array $databases, + ) {} + + /** @param array $data */ + public static function fromArray(array $data): self + { + return new self( + files: SourceFilesConfig::fromArray($data['files']), + databases: $data['databases'], + ); + } +} diff --git a/src/Config/SourceFilesConfig.php b/src/Config/SourceFilesConfig.php new file mode 100644 index 00000000..3cef60d6 --- /dev/null +++ b/src/Config/SourceFilesConfig.php @@ -0,0 +1,32 @@ + $include + * @param array $exclude + */ + protected function __construct( + public array $include, + public array $exclude, + public bool $followLinks, + public bool $ignoreUnreadableDirectories, + public ?string $relativePath, + ) {} + + /** @param array $data */ + public static function fromArray(array $data): self + { + return new self( + include: $data['include'], + exclude: $data['exclude'], + followLinks: $data['follow_links'] ?? false, + ignoreUnreadableDirectories: $data['ignore_unreadable_directories'] ?? false, + relativePath: $data['relative_path'], + ); + } +} diff --git a/src/Config/StrategyConfig.php b/src/Config/StrategyConfig.php new file mode 100644 index 00000000..cf7802b5 --- /dev/null +++ b/src/Config/StrategyConfig.php @@ -0,0 +1,30 @@ + $data */ + public static function fromArray(array $data): self + { + return new self( + keepAllBackupsForDays: $data['keep_all_backups_for_days'], + keepDailyBackupsForDays: $data['keep_daily_backups_for_days'], + keepWeeklyBackupsForWeeks: $data['keep_weekly_backups_for_weeks'], + keepMonthlyBackupsForMonths: $data['keep_monthly_backups_for_months'], + keepYearlyBackupsForYears: $data['keep_yearly_backups_for_years'], + deleteOldestBackupsWhenUsingMoreMegabytesThan: $data['delete_oldest_backups_when_using_more_megabytes_than'], + ); + } +} diff --git a/src/Events/BackupHasFailed.php b/src/Events/BackupHasFailed.php index 66454cf8..8bf68229 100644 --- a/src/Events/BackupHasFailed.php +++ b/src/Events/BackupHasFailed.php @@ -10,6 +10,5 @@ class BackupHasFailed public function __construct( public Exception $exception, public ?BackupDestination $backupDestination = null, - ) { - } + ) {} } diff --git a/src/Events/BackupManifestWasCreated.php b/src/Events/BackupManifestWasCreated.php index 5d45aa2f..3cb8aec0 100644 --- a/src/Events/BackupManifestWasCreated.php +++ b/src/Events/BackupManifestWasCreated.php @@ -8,6 +8,5 @@ class BackupManifestWasCreated { public function __construct( public Manifest $manifest, - ) { - } + ) {} } diff --git a/src/Events/BackupWasSuccessful.php b/src/Events/BackupWasSuccessful.php index de686fa6..8b562be2 100644 --- a/src/Events/BackupWasSuccessful.php +++ b/src/Events/BackupWasSuccessful.php @@ -8,6 +8,5 @@ class BackupWasSuccessful { public function __construct( public BackupDestination $backupDestination, - ) { - } + ) {} } diff --git a/src/Events/BackupZipWasCreated.php b/src/Events/BackupZipWasCreated.php index 211730d9..4106311f 100644 --- a/src/Events/BackupZipWasCreated.php +++ b/src/Events/BackupZipWasCreated.php @@ -6,6 +6,5 @@ class BackupZipWasCreated { public function __construct( public string $pathToZip, - ) { - } + ) {} } diff --git a/src/Events/CleanupHasFailed.php b/src/Events/CleanupHasFailed.php index 709cb299..6d97ecc5 100644 --- a/src/Events/CleanupHasFailed.php +++ b/src/Events/CleanupHasFailed.php @@ -10,6 +10,5 @@ class CleanupHasFailed public function __construct( public Exception $exception, public ?BackupDestination $backupDestination = null, - ) { - } + ) {} } diff --git a/src/Events/CleanupWasSuccessful.php b/src/Events/CleanupWasSuccessful.php index 4306284c..a9bd8ebe 100644 --- a/src/Events/CleanupWasSuccessful.php +++ b/src/Events/CleanupWasSuccessful.php @@ -8,6 +8,5 @@ class CleanupWasSuccessful { public function __construct( public BackupDestination $backupDestination, - ) { - } + ) {} } diff --git a/src/Events/DumpingDatabase.php b/src/Events/DumpingDatabase.php index a66afe89..98faf17f 100644 --- a/src/Events/DumpingDatabase.php +++ b/src/Events/DumpingDatabase.php @@ -8,6 +8,5 @@ class DumpingDatabase { public function __construct( public DbDumper $dbDumper - ) { - } + ) {} } diff --git a/src/Events/HealthyBackupWasFound.php b/src/Events/HealthyBackupWasFound.php index 75d89cfa..0d76759c 100644 --- a/src/Events/HealthyBackupWasFound.php +++ b/src/Events/HealthyBackupWasFound.php @@ -8,6 +8,5 @@ class HealthyBackupWasFound { public function __construct( public BackupDestinationStatus $backupDestinationStatus, - ) { - } + ) {} } diff --git a/src/Events/UnhealthyBackupWasFound.php b/src/Events/UnhealthyBackupWasFound.php index 77d698e7..503945b1 100644 --- a/src/Events/UnhealthyBackupWasFound.php +++ b/src/Events/UnhealthyBackupWasFound.php @@ -8,6 +8,5 @@ class UnhealthyBackupWasFound { public function __construct( public BackupDestinationStatus $backupDestinationStatus - ) { - } + ) {} } diff --git a/src/Exceptions/CannotCreateDbDumper.php b/src/Exceptions/CannotCreateDbDumper.php index dca0e6bf..5fabe436 100644 --- a/src/Exceptions/CannotCreateDbDumper.php +++ b/src/Exceptions/CannotCreateDbDumper.php @@ -6,12 +6,14 @@ class CannotCreateDbDumper extends Exception { - public static function unsupportedDriver(string $driver): self + public static function unsupportedDriver(string $driver): static { - $supportedDrivers = collect(config("database.connections"))->keys(); + /** @var array $supportedDrivers */ + $supportedDrivers = config('database.connections'); - $formattedSupportedDrivers = $supportedDrivers - ->map(fn (string $supportedDriver) => "`$supportedDriver`") + $formattedSupportedDrivers = collect($supportedDrivers) + ->keys() + ->map(fn (string $supportedDriver) => "`{$supportedDriver}`") ->join(glue: ', ', finalGlue: ' or '); return new static("Cannot create a dumper for db driver `{$driver}`. Use {$formattedSupportedDrivers}."); diff --git a/src/Exceptions/InvalidConfig.php b/src/Exceptions/InvalidConfig.php new file mode 100644 index 00000000..53f8c2f8 --- /dev/null +++ b/src/Exceptions/InvalidConfig.php @@ -0,0 +1,33 @@ +command = $command; } - public function __call(string $method, array $arguments) + /** @param array $arguments */ + public function __call(string $method, array $arguments): void { $consoleOutput = app(static::class); diff --git a/src/Helpers/File.php b/src/Helpers/File.php index c1f6b2d8..0c33b3d1 100644 --- a/src/Helpers/File.php +++ b/src/Helpers/File.php @@ -7,6 +7,7 @@ class File { + /** @var array */ protected static array $allowedMimeTypes = [ 'application/zip', 'application/x-zip', @@ -32,7 +33,7 @@ protected function hasAllowedMimeType(?Filesystem $disk, string $path): bool return in_array($this->mimeType($disk, $path), self::$allowedMimeTypes); } - protected function mimeType(?Filesystem $disk, string $path): bool | string + protected function mimeType(?Filesystem $disk, string $path): bool|string { try { if ($disk && method_exists($disk, 'mimeType')) { diff --git a/src/Helpers/Format.php b/src/Helpers/Format.php index 07903a95..49a6ed19 100644 --- a/src/Helpers/Format.php +++ b/src/Helpers/Format.php @@ -13,6 +13,7 @@ public static function humanReadableSize(float $sizeInBytes): string if ($sizeInBytes === 0.0) { return '0 '.$units[1]; } + for ($i = 0; $sizeInBytes > 1024; $i++) { $sizeInBytes /= 1024; } diff --git a/src/Listeners/EncryptBackupArchive.php b/src/Listeners/EncryptBackupArchive.php index 35d62b1d..230aa700 100644 --- a/src/Listeners/EncryptBackupArchive.php +++ b/src/Listeners/EncryptBackupArchive.php @@ -57,7 +57,7 @@ protected static function getAlgorithm(): ?int $encryption = config('backup.backup.encryption'); if ($encryption === 'default') { - $encryption = defined("\ZipArchive::EM_AES_256") + $encryption = defined(\ZipArchive::class.'::EM_AES_256') ? ZipArchive::EM_AES_256 : null; } diff --git a/src/Notifications/BaseNotification.php b/src/Notifications/BaseNotification.php index 9a509e9f..77e0dfbc 100644 --- a/src/Notifications/BaseNotification.php +++ b/src/Notifications/BaseNotification.php @@ -5,17 +5,24 @@ use Illuminate\Notifications\Notification; use Illuminate\Support\Collection; use Spatie\Backup\BackupDestination\BackupDestination; +use Spatie\Backup\Config\Config; use Spatie\Backup\Helpers\Format; abstract class BaseNotification extends Notification { + /** @return array */ public function via(): array { - $notificationChannels = config('backup.notifications.notifications.'.static::class); + $notificationChannels = $this->config()->notifications->notifications[static::class]; return array_filter($notificationChannels); } + public function config(): Config + { + return app(Config::class); + } + public function applicationName(): string { $name = config('app.name') ?? config('app.url') ?? 'Laravel'; @@ -34,6 +41,7 @@ public function diskName(): string return $this->backupDestination()->diskName(); } + /** @return Collection */ protected function backupDestinationProperties(): Collection { $backupDestination = $this->backupDestination(); diff --git a/src/Notifications/Channels/Discord/DiscordChannel.php b/src/Notifications/Channels/Discord/DiscordChannel.php index 6571e12d..93c32230 100644 --- a/src/Notifications/Channels/Discord/DiscordChannel.php +++ b/src/Notifications/Channels/Discord/DiscordChannel.php @@ -4,10 +4,11 @@ use Illuminate\Notifications\Notification; use Illuminate\Support\Facades\Http; +use Spatie\Backup\Notifications\Notifiable; class DiscordChannel { - public function send($notifiable, Notification $notification): void + public function send(Notifiable $notifiable, Notification $notification): void { $discordMessage = $notification->toDiscord(); // @phpstan-ignore-line diff --git a/src/Notifications/Channels/Discord/DiscordMessage.php b/src/Notifications/Channels/Discord/DiscordMessage.php index 8854aef2..1d865eeb 100644 --- a/src/Notifications/Channels/Discord/DiscordMessage.php +++ b/src/Notifications/Channels/Discord/DiscordMessage.php @@ -7,7 +7,9 @@ class DiscordMessage { public const COLOR_SUCCESS = '0b6623'; + public const COLOR_WARNING = 'fD6a02'; + public const COLOR_ERROR = 'e32929'; protected string $username = 'Laravel Backup'; @@ -18,6 +20,7 @@ class DiscordMessage protected string $description = ''; + /** @var array */ protected array $fields = []; protected ?string $timestamp = null; @@ -28,7 +31,7 @@ class DiscordMessage protected string $url = ''; - public function from(string $username, string $avatarUrl = null): self + public function from(string $username, ?string $avatarUrl = null): self { $this->username = $username; @@ -46,7 +49,7 @@ public function url(string $url): self return $this; } - public function title($title): self + public function title(string $title): self { $this->title = $title; @@ -95,6 +98,7 @@ public function error(): self return $this; } + /** @param array $fields */ public function fields(array $fields, bool $inline = true): self { foreach ($fields as $label => $value) { @@ -120,7 +124,7 @@ public function toArray(): array 'type' => 'rich', 'description' => $this->description, 'fields' => $this->fields, - 'color' => hexdec($this->color), + 'color' => hexdec((string) $this->color), 'footer' => [ 'text' => $this->footer ?? '', ], diff --git a/src/Notifications/EventHandler.php b/src/Notifications/EventHandler.php index 5feb46e0..6289238a 100644 --- a/src/Notifications/EventHandler.php +++ b/src/Notifications/EventHandler.php @@ -17,8 +17,7 @@ class EventHandler { public function __construct( protected Repository $config - ) { - } + ) {} public function subscribe(Dispatcher $events): void { @@ -31,18 +30,21 @@ public function subscribe(Dispatcher $events): void }); } - protected function determineNotifiable() + protected function determineNotifiable(): Notifiable { $notifiableClass = $this->config->get('backup.notifications.notifiable'); return app($notifiableClass); } - protected function determineNotification($event): Notification + protected function determineNotification(object $event): Notification { - $lookingForNotificationClass = class_basename($event) . "Notification"; + $lookingForNotificationClass = class_basename($event).'Notification'; + + /** @var array> $notificationClasses */ + $notificationClasses = $this->config->get('backup.notifications.notifications'); - $notificationClass = collect($this->config->get('backup.notifications.notifications')) + $notificationClass = collect($notificationClasses) ->keys() ->first(fn (string $notificationClass) => class_basename($notificationClass) === $lookingForNotificationClass); @@ -53,6 +55,7 @@ protected function determineNotification($event): Notification return new $notificationClass($event); } + /** @return array */ protected function allBackupEventClasses(): array { return [ diff --git a/src/Notifications/Notifiable.php b/src/Notifications/Notifiable.php index f20549a2..1321c168 100644 --- a/src/Notifications/Notifiable.php +++ b/src/Notifications/Notifiable.php @@ -3,28 +3,35 @@ namespace Spatie\Backup\Notifications; use Illuminate\Notifications\Notifiable as NotifiableTrait; +use Spatie\Backup\Config\Config; class Notifiable { use NotifiableTrait; - public function routeNotificationForMail(): string | array + /** @return string|array{int, string} */ + public function routeNotificationForMail(): string|array { - return config('backup.notifications.mail.to'); + return $this->config()->notifications->mail->to; } public function routeNotificationForSlack(): string { - return config('backup.notifications.slack.webhook_url'); + return $this->config()->notifications->slack->webhookUrl; } public function routeNotificationForDiscord(): string { - return config('backup.notifications.discord.webhook_url'); + return $this->config()->notifications->discord->webhookUrl; } public function getKey(): int { return 1; } + + protected function config(): Config + { + return app(Config::class); + } } diff --git a/src/Notifications/Notifications/BackupHasFailedNotification.php b/src/Notifications/Notifications/BackupHasFailedNotification.php index 64d99bd8..ba768d3f 100644 --- a/src/Notifications/Notifications/BackupHasFailedNotification.php +++ b/src/Notifications/Notifications/BackupHasFailedNotification.php @@ -13,20 +13,19 @@ class BackupHasFailedNotification extends BaseNotification { public function __construct( public BackupHasFailed $event, - ) { - } + ) {} public function toMail(): MailMessage { $mailMessage = (new MailMessage()) ->error() - ->from(config('backup.notifications.mail.from.address', config('mail.from.address')), config('backup.notifications.mail.from.name', config('mail.from.name'))) + ->from($this->config()->notifications->mail->from->address, $this->config()->notifications->mail->from->name) ->subject(trans('backup::notifications.backup_failed_subject', ['application_name' => $this->applicationName()])) ->line(trans('backup::notifications.backup_failed_body', ['application_name' => $this->applicationName()])) ->line(trans('backup::notifications.exception_message', ['message' => $this->event->exception->getMessage()])) ->line(trans('backup::notifications.exception_trace', ['trace' => $this->event->exception->getTraceAsString()])); - $this->backupDestinationProperties()->each(fn ($value, $name) => $mailMessage->line("{$name}: $value")); + $this->backupDestinationProperties()->each(fn ($value, $name) => $mailMessage->line("{$name}: {$value}")); return $mailMessage; } @@ -35,8 +34,8 @@ public function toSlack(): SlackMessage { return (new SlackMessage()) ->error() - ->from(config('backup.notifications.slack.username'), config('backup.notifications.slack.icon')) - ->to(config('backup.notifications.slack.channel')) + ->from($this->config()->notifications->slack->username, $this->config()->notifications->slack->icon) + ->to($this->config()->notifications->slack->channel) ->content(trans('backup::notifications.backup_failed_subject', ['application_name' => $this->applicationName()])) ->attachment(function (SlackAttachment $attachment) { $attachment @@ -57,7 +56,7 @@ public function toDiscord(): DiscordMessage { return (new DiscordMessage()) ->error() - ->from(config('backup.notifications.discord.username'), config('backup.notifications.discord.avatar_url')) + ->from($this->config()->notifications->discord->username, $this->config()->notifications->discord->avatar_url) ->title(trans('backup::notifications.backup_failed_subject', ['application_name' => $this->applicationName()])) ->fields([ trans('backup::notifications.exception_message_title') => $this->event->exception->getMessage(), diff --git a/src/Notifications/Notifications/BackupWasSuccessfulNotification.php b/src/Notifications/Notifications/BackupWasSuccessfulNotification.php index 013bafc9..c101a074 100644 --- a/src/Notifications/Notifications/BackupWasSuccessfulNotification.php +++ b/src/Notifications/Notifications/BackupWasSuccessfulNotification.php @@ -13,18 +13,17 @@ class BackupWasSuccessfulNotification extends BaseNotification { public function __construct( public BackupWasSuccessful $event, - ) { - } + ) {} public function toMail(): MailMessage { $mailMessage = (new MailMessage()) - ->from(config('backup.notifications.mail.from.address', config('mail.from.address')), config('backup.notifications.mail.from.name', config('mail.from.name'))) + ->from($this->config()->notifications->mail->from->address, $this->config()->notifications->mail->from->name) ->subject(trans('backup::notifications.backup_successful_subject', ['application_name' => $this->applicationName()])) ->line(trans('backup::notifications.backup_successful_body', ['application_name' => $this->applicationName(), 'disk_name' => $this->diskName()])); $this->backupDestinationProperties()->each(function ($value, $name) use ($mailMessage) { - $mailMessage->line("{$name}: $value"); + $mailMessage->line("{$name}: {$value}"); }); return $mailMessage; @@ -34,8 +33,8 @@ public function toSlack(): SlackMessage { return (new SlackMessage()) ->success() - ->from(config('backup.notifications.slack.username'), config('backup.notifications.slack.icon')) - ->to(config('backup.notifications.slack.channel')) + ->from($this->config()->notifications->slack->username, $this->config()->notifications->slack->icon) + ->to($this->config()->notifications->slack->channel) ->content(trans('backup::notifications.backup_successful_subject_title')) ->attachment(function (SlackAttachment $attachment) { $attachment->fields($this->backupDestinationProperties()->toArray()); @@ -46,7 +45,7 @@ public function toDiscord(): DiscordMessage { return (new DiscordMessage()) ->success() - ->from(config('backup.notifications.discord.username'), config('backup.notifications.discord.avatar_url')) + ->from($this->config()->notifications->discord->username, $this->config()->notifications->discord->avatar_url) ->title(trans('backup::notifications.backup_successful_subject_title')) ->fields($this->backupDestinationProperties()->toArray()); } diff --git a/src/Notifications/Notifications/CleanupHasFailedNotification.php b/src/Notifications/Notifications/CleanupHasFailedNotification.php index 42cf2383..74f3081c 100644 --- a/src/Notifications/Notifications/CleanupHasFailedNotification.php +++ b/src/Notifications/Notifications/CleanupHasFailedNotification.php @@ -13,21 +13,20 @@ class CleanupHasFailedNotification extends BaseNotification { public function __construct( public CleanupHasFailed $event, - ) { - } + ) {} public function toMail(): MailMessage { $mailMessage = (new MailMessage()) ->error() - ->from(config('backup.notifications.mail.from.address', config('mail.from.address')), config('backup.notifications.mail.from.name', config('mail.from.name'))) + ->from($this->config()->notifications->mail->from->address, $this->config()->notifications->mail->from->name) ->subject(trans('backup::notifications.cleanup_failed_subject', ['application_name' => $this->applicationName()])) ->line(trans('backup::notifications.cleanup_failed_body', ['application_name' => $this->applicationName()])) ->line(trans('backup::notifications.exception_message', ['message' => $this->event->exception->getMessage()])) ->line(trans('backup::notifications.exception_trace', ['trace' => $this->event->exception->getTraceAsString()])); $this->backupDestinationProperties()->each(function ($value, $name) use ($mailMessage) { - $mailMessage->line("{$name}: $value"); + $mailMessage->line("{$name}: {$value}"); }); return $mailMessage; @@ -37,8 +36,8 @@ public function toSlack(): SlackMessage { return (new SlackMessage()) ->error() - ->from(config('backup.notifications.slack.username'), config('backup.notifications.slack.icon')) - ->to(config('backup.notifications.slack.channel')) + ->from($this->config()->notifications->slack->username, $this->config()->notifications->slack->icon) + ->to($this->config()->notifications->slack->channel) ->content(trans('backup::notifications.cleanup_failed_subject', ['application_name' => $this->applicationName()])) ->attachment(function (SlackAttachment $attachment) { $attachment @@ -59,7 +58,7 @@ public function toDiscord(): DiscordMessage { return (new DiscordMessage()) ->error() - ->from(config('backup.notifications.discord.username'), config('backup.notifications.discord.avatar_url')) + ->from($this->config()->notifications->discord->username, $this->config()->notifications->discord->avatar_url) ->title( trans('backup::notifications.cleanup_failed_subject', ['application_name' => $this->applicationName()]) )->fields([ diff --git a/src/Notifications/Notifications/CleanupWasSuccessfulNotification.php b/src/Notifications/Notifications/CleanupWasSuccessfulNotification.php index b554be0d..04b4cb07 100644 --- a/src/Notifications/Notifications/CleanupWasSuccessfulNotification.php +++ b/src/Notifications/Notifications/CleanupWasSuccessfulNotification.php @@ -13,18 +13,17 @@ class CleanupWasSuccessfulNotification extends BaseNotification { public function __construct( public CleanupWasSuccessful $event, - ) { - } + ) {} public function toMail(): MailMessage { $mailMessage = (new MailMessage()) - ->from(config('backup.notifications.mail.from.address', config('mail.from.address')), config('backup.notifications.mail.from.name', config('mail.from.name'))) + ->from($this->config()->notifications->mail->from->address, $this->config()->notifications->mail->from->name) ->subject(trans('backup::notifications.cleanup_successful_subject', ['application_name' => $this->applicationName()])) ->line(trans('backup::notifications.cleanup_successful_body', ['application_name' => $this->applicationName(), 'disk_name' => $this->diskName()])); $this->backupDestinationProperties()->each(function ($value, $name) use ($mailMessage) { - $mailMessage->line("{$name}: $value"); + $mailMessage->line("{$name}: {$value}"); }); return $mailMessage; @@ -34,8 +33,8 @@ public function toSlack(): SlackMessage { return (new SlackMessage()) ->success() - ->from(config('backup.notifications.slack.username'), config('backup.notifications.slack.icon')) - ->to(config('backup.notifications.slack.channel')) + ->from($this->config()->notifications->slack->username, $this->config()->notifications->slack->icon) + ->to($this->config()->notifications->slack->channel) ->content(trans('backup::notifications.cleanup_successful_subject_title')) ->attachment(function (SlackAttachment $attachment) { $attachment->fields($this->backupDestinationProperties()->toArray()); @@ -46,7 +45,7 @@ public function toDiscord(): DiscordMessage { return (new DiscordMessage()) ->success() - ->from(config('backup.notifications.discord.username'), config('backup.notifications.discord.avatar_url')) + ->from($this->config()->notifications->discord->username, $this->config()->notifications->discord->avatar_url) ->title(trans('backup::notifications.cleanup_successful_subject_title')) ->fields($this->backupDestinationProperties()->toArray()); } diff --git a/src/Notifications/Notifications/HealthyBackupWasFoundNotification.php b/src/Notifications/Notifications/HealthyBackupWasFoundNotification.php index 6ed4a9e8..7135e861 100644 --- a/src/Notifications/Notifications/HealthyBackupWasFoundNotification.php +++ b/src/Notifications/Notifications/HealthyBackupWasFoundNotification.php @@ -13,18 +13,17 @@ class HealthyBackupWasFoundNotification extends BaseNotification { public function __construct( public HealthyBackupWasFound $event, - ) { - } + ) {} public function toMail(): MailMessage { $mailMessage = (new MailMessage()) - ->from(config('backup.notifications.mail.from.address', config('mail.from.address')), config('backup.notifications.mail.from.name', config('mail.from.name'))) + ->from($this->config()->notifications->mail->from->address, $this->config()->notifications->mail->from->name) ->subject(trans('backup::notifications.healthy_backup_found_subject', ['application_name' => $this->applicationName(), 'disk_name' => $this->diskName()])) ->line(trans('backup::notifications.healthy_backup_found_body', ['application_name' => $this->applicationName()])); $this->backupDestinationProperties()->each(function ($value, $name) use ($mailMessage) { - $mailMessage->line("{$name}: $value"); + $mailMessage->line("{$name}: {$value}"); }); return $mailMessage; @@ -34,8 +33,8 @@ public function toSlack(): SlackMessage { return (new SlackMessage()) ->success() - ->from(config('backup.notifications.slack.username'), config('backup.notifications.slack.icon')) - ->to(config('backup.notifications.slack.channel')) + ->from($this->config()->notifications->slack->username, $this->config()->notifications->slack->icon) + ->to($this->config()->notifications->slack->channel) ->content(trans('backup::notifications.healthy_backup_found_subject_title', ['application_name' => $this->applicationName()])) ->attachment(function (SlackAttachment $attachment) { $attachment->fields($this->backupDestinationProperties()->toArray()); @@ -46,10 +45,10 @@ public function toDiscord(): DiscordMessage { return (new DiscordMessage()) ->success() - ->from(config('backup.notifications.discord.username'), config('backup.notifications.discord.avatar_url')) + ->from($this->config()->notifications->discord->username, $this->config()->notifications->discord->avatar_url) ->title( trans('backup::notifications.healthy_backup_found_subject_title', [ - 'application_name' => $this->applicationName(), + 'application_name' => $this->applicationName(), ]) )->fields($this->backupDestinationProperties()->toArray()); } diff --git a/src/Notifications/Notifications/UnhealthyBackupWasFoundNotification.php b/src/Notifications/Notifications/UnhealthyBackupWasFoundNotification.php index 4e7570f8..da3840ec 100644 --- a/src/Notifications/Notifications/UnhealthyBackupWasFoundNotification.php +++ b/src/Notifications/Notifications/UnhealthyBackupWasFoundNotification.php @@ -14,20 +14,19 @@ class UnhealthyBackupWasFoundNotification extends BaseNotification { public function __construct( public UnhealthyBackupWasFound $event, - ) { - } + ) {} public function toMail(): MailMessage { $mailMessage = (new MailMessage()) ->error() - ->from(config('backup.notifications.mail.from.address', config('mail.from.address')), config('backup.notifications.mail.from.name', config('mail.from.name'))) + ->from($this->config()->notifications->mail->from->address, $this->config()->notifications->mail->from->name) ->subject(trans('backup::notifications.unhealthy_backup_found_subject', ['application_name' => $this->applicationName()])) ->line(trans('backup::notifications.unhealthy_backup_found_body', ['application_name' => $this->applicationName(), 'disk_name' => $this->diskName()])) ->line($this->problemDescription()); $this->backupDestinationProperties()->each(function ($value, $name) use ($mailMessage) { - $mailMessage->line("{$name}: $value"); + $mailMessage->line("{$name}: {$value}"); }); if ($this->failure()->wasUnexpected()) { @@ -44,8 +43,8 @@ public function toSlack(): SlackMessage { $slackMessage = (new SlackMessage()) ->error() - ->from(config('backup.notifications.slack.username'), config('backup.notifications.slack.icon')) - ->to(config('backup.notifications.slack.channel')) + ->from($this->config()->notifications->slack->username, $this->config()->notifications->slack->icon) + ->to($this->config()->notifications->slack->channel) ->content(trans('backup::notifications.unhealthy_backup_found_subject_title', ['application_name' => $this->applicationName(), 'problem' => $this->problemDescription()])) ->attachment(function (SlackAttachment $attachment) { $attachment->fields($this->backupDestinationProperties()->toArray()); @@ -77,7 +76,7 @@ public function toDiscord(): DiscordMessage { $discordMessage = (new DiscordMessage()) ->error() - ->from(config('backup.notifications.discord.username'), config('backup.notifications.discord.avatar_url')) + ->from($this->config()->notifications->discord->username, $this->config()->notifications->discord->avatar_url) ->title( trans('backup::notifications.unhealthy_backup_found_subject_title', [ 'application_name' => $this->applicationName(), diff --git a/src/Support/Data.php b/src/Support/Data.php new file mode 100644 index 00000000..c8f474a1 --- /dev/null +++ b/src/Support/Data.php @@ -0,0 +1,30 @@ + */ +class Data implements Arrayable +{ + public function toArray(): array + { + $array = []; + $reflectionClass = new ReflectionClass($this); + + foreach ($reflectionClass->getProperties(ReflectionProperty::IS_PUBLIC) as $property) { + $name = $property->getName(); + $value = $this->$name; + + if ($value instanceof Arrayable) { + $array[$name] = $value->toArray(); + } else { + $array[$name] = $value; + } + } + + return $array; + } +} diff --git a/src/Tasks/Backup/BackupJob.php b/src/Tasks/Backup/BackupJob.php index 952cb57e..ad3c0453 100644 --- a/src/Tasks/Backup/BackupJob.php +++ b/src/Tasks/Backup/BackupJob.php @@ -8,6 +8,7 @@ use Illuminate\Console\Command; use Illuminate\Support\Collection; use Spatie\Backup\BackupDestination\BackupDestination; +use Spatie\Backup\Config\Config; use Spatie\Backup\Events\BackupManifestWasCreated; use Spatie\Backup\Events\BackupWasSuccessful; use Spatie\Backup\Events\BackupZipWasCreated; @@ -27,8 +28,10 @@ class BackupJob protected FileSelection $fileSelection; + /** @var Collection */ protected Collection $dbDumpers; + /** @var Collection */ protected Collection $backupDestinations; protected string $filename; @@ -39,7 +42,7 @@ class BackupJob protected bool $signals = true; - public function __construct() + public function __construct(protected Config $config) { $this ->dontBackupFilesystem() @@ -56,6 +59,7 @@ public function dontBackupFilesystem(): self return $this; } + /** @param array $allowedDbNames */ public function onlyDbName(array $allowedDbNames): self { $this->dbDumpers = $this->dbDumpers->filter( @@ -100,6 +104,10 @@ public function setFileSelection(FileSelection $fileSelection): self return $this; } + /** + * @param Collection $dbDumpers + * @return $this + */ public function setDbDumpers(Collection $dbDumpers): self { $this->dbDumpers = $dbDumpers; @@ -127,6 +135,7 @@ public function onlyBackupTo(string $diskName): self return $this; } + /** @param Collection $backupDestinations */ public function setBackupDestinations(Collection $backupDestinations): self { $this->backupDestinations = $backupDestinations; @@ -134,12 +143,10 @@ public function setBackupDestinations(Collection $backupDestinations): self return $this; } - /** - * @throws Exception - */ + /** @throws Exception */ public function run(): void { - $temporaryDirectoryPath = config('backup.backup.temporary_directory') ?? storage_path('app/backup-temp'); + $temporaryDirectoryPath = $this->config->backup->temporaryDirectory ?? storage_path('app/backup-temp'); $this->temporaryDirectory = (new TemporaryDirectory($temporaryDirectoryPath)) ->name('temp') @@ -170,7 +177,7 @@ public function run(): void $this->copyToBackupDestinations($zipFile); } catch (Exception $exception) { - consoleOutput()->error("Backup failed because: {$exception->getMessage()}." . PHP_EOL . $exception->getTraceAsString()); + consoleOutput()->error("Backup failed because: {$exception->getMessage()}.".PHP_EOL.$exception->getTraceAsString()); $this->temporaryDirectory->delete(); @@ -206,12 +213,13 @@ public function filesToBeBackedUp(): Generator return $this->fileSelection->selectedFiles(); } + /** @return array */ protected function directoriesUsedByBackupJob(): array { return $this->backupDestinations ->filter(fn (BackupDestination $backupDestination) => $backupDestination->filesystemType() === 'localfilesystemadapter') ->map( - fn (BackupDestination $backupDestination) => $backupDestination->disk()->path('') . $backupDestination->backupName() + fn (BackupDestination $backupDestination) => $backupDestination->disk()->path('').$backupDestination->backupName() ) ->each(fn (string $backupDestinationDirectory) => $this->fileSelection->excludeFilesFrom($backupDestinationDirectory)) ->push($this->temporaryDirectory->path()) @@ -222,7 +230,7 @@ protected function createZipContainingEveryFileInManifest(Manifest $manifest): s { consoleOutput()->info("Zipping {$manifest->count()} files and directories..."); - $pathToZip = $this->temporaryDirectory->path(config('backup.backup.destination.filename_prefix') . $this->filename); + $pathToZip = $this->temporaryDirectory->path($this->config->backup->destination->filenamePrefix.$this->filename); $zip = Zip::createForManifest($manifest, $pathToZip); @@ -241,43 +249,44 @@ protected function createZipContainingEveryFileInManifest(Manifest $manifest): s * Dumps the databases to the given directory. * Returns an array with paths to the dump files. * - * @return array + * @return array */ protected function dumpDatabases(): array { return $this->dbDumpers - ->map(function (DbDumper $dbDumper, $key) { + ->map(function (DbDumper $dbDumper, string $key): string { consoleOutput()->info("Dumping database {$dbDumper->getDbName()}..."); - $dbType = mb_strtolower(basename(str_replace('\\', '/', get_class($dbDumper)))); - + $dbType = mb_strtolower(basename(str_replace('\\', '/', $dbDumper::class))); - if (config('backup.backup.database_dump_filename_base') === 'connection') { + if ($this->config->backup->databaseDumpFilenameBase === 'connection') { $dbName = $key; } elseif ($dbDumper instanceof Sqlite) { - $dbName = $key . '-database'; + $dbName = $key.'-database'; } else { $dbName = $dbDumper->getDbName(); } $timeStamp = ''; - if ($timeStampFormat = config('backup.backup.database_dump_file_timestamp_format')) { - $timeStamp = '-' . Carbon::now()->format($timeStampFormat); + + if ($timeStampFormat = $this->config->backup->databaseDumpFileTimestampFormat) { + $timeStamp = '-'.Carbon::now()->format($timeStampFormat); } $fileName = "{$dbType}-{$dbName}{$timeStamp}.{$this->getExtension($dbDumper)}"; + // @todo is this still relevant or undocumented? if (config('backup.backup.gzip_database_dump')) { $dbDumper->useCompressor(new GzipCompressor()); - $fileName .= '.' . $dbDumper->getCompressorExtension(); + $fileName .= '.'.$dbDumper->getCompressorExtension(); } - if ($compressor = config('backup.backup.database_dump_compressor')) { + if ($compressor = $this->config->backup->databaseDumpCompressor) { $dbDumper->useCompressor(new $compressor()); - $fileName .= '.' . $dbDumper->getCompressorExtension(); + $fileName .= '.'.$dbDumper->getCompressorExtension(); } - $temporaryFilePath = $this->temporaryDirectory->path('db-dumps' . DIRECTORY_SEPARATOR . $fileName); + $temporaryFilePath = $this->temporaryDirectory->path('db-dumps'.DIRECTORY_SEPARATOR.$fileName); event(new DumpingDatabase($dbDumper)); @@ -315,7 +324,7 @@ protected function copyToBackupDestinations(string $path): void }); } - protected function sendNotification($notification): void + protected function sendNotification(object|string $notification): void { if ($this->sendNotifications) { rescue( @@ -327,7 +336,7 @@ protected function sendNotification($notification): void protected function getExtension(DbDumper $dbDumper): string { - if ($extension = config('backup.backup.database_dump_file_extension')) { + if ($extension = $this->config->backup->databaseDumpFileExtension) { return $extension; } diff --git a/src/Tasks/Backup/BackupJobFactory.php b/src/Tasks/Backup/BackupJobFactory.php index f9cfddfe..a047f6e0 100644 --- a/src/Tasks/Backup/BackupJobFactory.php +++ b/src/Tasks/Backup/BackupJobFactory.php @@ -2,32 +2,38 @@ namespace Spatie\Backup\Tasks\Backup; -use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Spatie\Backup\BackupDestination\BackupDestinationFactory; +use Spatie\Backup\Config\Config; +use Spatie\Backup\Config\SourceFilesConfig; +use Spatie\DbDumper\DbDumper; class BackupJobFactory { - public static function createFromArray(array $config): BackupJob + public static function createFromConfig(Config $config): BackupJob { - return (new BackupJob()) - ->setFileSelection(static::createFileSelection($config['backup']['source']['files'])) - ->setDbDumpers(static::createDbDumpers($config['backup']['source']['databases'])) - ->setBackupDestinations(BackupDestinationFactory::createFromArray($config['backup'])); + return (new BackupJob($config)) + ->setFileSelection(static::createFileSelection($config->backup->source->files)) + ->setDbDumpers(static::createDbDumpers($config->backup->source->databases)) + ->setBackupDestinations(BackupDestinationFactory::createFromArray($config)); } - protected static function createFileSelection(array $sourceFiles): FileSelection + protected static function createFileSelection(SourceFilesConfig $sourceFiles): FileSelection { - return FileSelection::create($sourceFiles['include']) - ->excludeFilesFrom($sourceFiles['exclude']) - ->shouldFollowLinks(isset($sourceFiles['follow_links']) && $sourceFiles['follow_links']) - ->shouldIgnoreUnreadableDirs(Arr::get($sourceFiles, 'ignore_unreadable_directories', false)); + return FileSelection::create($sourceFiles->include) + ->excludeFilesFrom($sourceFiles->exclude) + ->shouldFollowLinks($sourceFiles->followLinks) + ->shouldIgnoreUnreadableDirs($sourceFiles->ignoreUnreadableDirectories); } + /** + * @param array $dbConnectionNames + * @return Collection + */ protected static function createDbDumpers(array $dbConnectionNames): Collection { return collect($dbConnectionNames)->mapWithKeys( - fn (string $dbConnectionName) => [$dbConnectionName => DbDumperFactory::createFromConnection($dbConnectionName)] + fn (string $dbConnectionName): array => [$dbConnectionName => DbDumperFactory::createFromConnection($dbConnectionName)] ); } } diff --git a/src/Tasks/Backup/DbDumperFactory.php b/src/Tasks/Backup/DbDumperFactory.php index d62ff378..f879a790 100644 --- a/src/Tasks/Backup/DbDumperFactory.php +++ b/src/Tasks/Backup/DbDumperFactory.php @@ -15,6 +15,7 @@ class DbDumperFactory { + /** @var array */ protected static array $custom = []; public static function createFromConnection(string $dbConnectionName): DbDumper @@ -69,12 +70,12 @@ public static function createFromConnection(string $dbConnectionName): DbDumper return $dbDumper; } - public static function extend(string $driver, callable $callback) + public static function extend(string $driver, callable $callback): void { static::$custom[$driver] = $callback; } - protected static function forDriver($dbDriver): DbDumper + protected static function forDriver(string $dbDriver): DbDumper { $driver = strtolower($dbDriver); @@ -91,6 +92,7 @@ protected static function forDriver($dbDriver): DbDumper }; } + /** @param array> $dumpConfiguration */ protected static function processExtraDumpParameters(array $dumpConfiguration, DbDumper $dbDumper): DbDumper { collect($dumpConfiguration)->each(function ($configValue, $configName) use ($dbDumper) { @@ -107,7 +109,7 @@ protected static function processExtraDumpParameters(array $dumpConfiguration, D return $dbDumper; } - protected static function callMethodOnDumper(DbDumper $dbDumper, string $methodName, $methodValue): DbDumper + protected static function callMethodOnDumper(DbDumper $dbDumper, string $methodName, mixed $methodValue): DbDumper { if (! $methodValue) { $dbDumper->$methodName(); diff --git a/src/Tasks/Backup/FileSelection.php b/src/Tasks/Backup/FileSelection.php index 93a03958..9daae78b 100644 --- a/src/Tasks/Backup/FileSelection.php +++ b/src/Tasks/Backup/FileSelection.php @@ -9,27 +9,32 @@ class FileSelection { + /** @var Collection */ protected Collection $includeFilesAndDirectories; + /** @var Collection */ protected Collection $excludeFilesAndDirectories; protected bool $shouldFollowLinks = false; protected bool $shouldIgnoreUnreadableDirs = false; - public static function create(array | string $includeFilesAndDirectories = []): self + /** @param array|string $includeFilesAndDirectories */ + public static function create(array|string $includeFilesAndDirectories = []): static { return new static($includeFilesAndDirectories); } - public function __construct(array | string $includeFilesAndDirectories = []) + /** @param array|string $includeFilesAndDirectories */ + public function __construct(array|string $includeFilesAndDirectories = []) { $this->includeFilesAndDirectories = collect($includeFilesAndDirectories); $this->excludeFilesAndDirectories = collect(); } - public function excludeFilesFrom(array | string $excludeFilesAndDirectories): self + /** @param array|string $excludeFilesAndDirectories */ + public function excludeFilesFrom(array|string $excludeFilesAndDirectories): self { $this->excludeFilesAndDirectories = $this->excludeFilesAndDirectories->merge($this->sanitize($excludeFilesAndDirectories)); @@ -50,7 +55,8 @@ public function shouldIgnoreUnreadableDirs(bool $ignoreUnreadableDirs): self return $this; } - public function selectedFiles(): Generator | array + /** @return Generator|array */ + public function selectedFiles(): Generator|array { if ($this->includeFilesAndDirectories->isEmpty()) { return []; @@ -72,7 +78,7 @@ public function selectedFiles(): Generator | array yield $includedFile; } - if (! count($this->includedDirectories())) { + if ($this->includedDirectories() === []) { return []; } @@ -87,26 +93,29 @@ public function selectedFiles(): Generator | array } } + /** @return array */ protected function includedFiles(): array { return $this ->includeFilesAndDirectories - ->filter(fn ($path) => is_file($path))->toArray(); + ->filter(fn (string $path) => is_file($path))->toArray(); } + /** @return array */ protected function includedDirectories(): array { return $this ->includeFilesAndDirectories - ->reject(fn ($path) => is_file($path))->toArray(); + ->reject(fn (string $path) => is_file($path))->toArray(); } protected function shouldExclude(string $path): bool { $path = realpath($path); if (is_dir($path)) { - $path .= DIRECTORY_SEPARATOR ; + $path .= DIRECTORY_SEPARATOR; } + foreach ($this->excludeFilesAndDirectories as $excludedPath) { if (Str::startsWith($path, $excludedPath.(is_dir($excludedPath) ? DIRECTORY_SEPARATOR : ''))) { if ($path != $excludedPath && is_file($excludedPath)) { @@ -120,15 +129,19 @@ protected function shouldExclude(string $path): bool return false; } - protected function sanitize(string | array $paths): Collection + /** + * @param string|array $paths + */ + protected function sanitize(string|array $paths): Collection { return collect($paths) - ->reject(fn ($path) => $path === '') - ->flatMap(fn ($path) => $this->getMatchingPaths($path)) - ->map(fn ($path) => realpath($path)) + ->reject(fn (string $path) => $path === '') + ->flatMap(fn (string $path) => $this->getMatchingPaths($path)) + ->map(fn (string $path) => realpath($path)) ->reject(fn ($path) => $path === false); } + /** @return array */ protected function getMatchingPaths(string $path): array { if ($this->canUseGlobBrace($path)) { @@ -140,6 +153,6 @@ protected function getMatchingPaths(string $path): array protected function canUseGlobBrace(string $path): bool { - return strpos($path, '*') !== false && defined('GLOB_BRACE'); + return str_contains($path, '*') && defined('GLOB_BRACE'); } } diff --git a/src/Tasks/Backup/Manifest.php b/src/Tasks/Backup/Manifest.php index 69894cfe..538dc4a8 100644 --- a/src/Tasks/Backup/Manifest.php +++ b/src/Tasks/Backup/Manifest.php @@ -8,18 +8,14 @@ class Manifest implements Countable { - protected string $manifestPath; - public static function create(string $manifestPath): self { return new static($manifestPath); } - public function __construct(string $manifestPath) + public function __construct(protected string $manifestPath) { - $this->manifestPath = $manifestPath; - - touch($manifestPath); + touch($this->manifestPath); } public function path(): string @@ -27,7 +23,10 @@ public function path(): string return $this->manifestPath; } - public function addFiles(array | string | Generator $filePaths): self + /** + * @param Generator|string|array $filePaths + */ + public function addFiles(array|string|Generator $filePaths): self { if (is_string($filePaths)) { $filePaths = [$filePaths]; @@ -42,7 +41,7 @@ public function addFiles(array | string | Generator $filePaths): self return $this; } - public function files(): Generator | array + public function files(): Generator|array { $file = new SplFileObject($this->path()); diff --git a/src/Tasks/Backup/Zip.php b/src/Tasks/Backup/Zip.php index 3dbcbf1f..41181481 100644 --- a/src/Tasks/Backup/Zip.php +++ b/src/Tasks/Backup/Zip.php @@ -3,6 +3,7 @@ namespace Spatie\Backup\Tasks\Backup; use Illuminate\Support\Str; +use Spatie\Backup\Config\Config; use Spatie\Backup\Helpers\Format; use ZipArchive; @@ -12,12 +13,23 @@ class Zip protected int $fileCount = 0; - protected string $pathToZip; + protected Config $config; + + public function __construct(protected string $pathToZip) + { + $this->zipFile = new ZipArchive(); + $this->config = app(Config::class); + + $this->open(); + } public static function createForManifest(Manifest $manifest, string $pathToZip): self { - $relativePath = config('backup.backup.source.files.relative_path') ? - rtrim(config('backup.backup.source.files.relative_path'), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : false; + $config = app(Config::class); + + $relativePath = $config->backup->source->files->relativePath + ? rtrim($config->backup->source->files->relativePath, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR + : false; $zip = new static($pathToZip); @@ -32,11 +44,11 @@ public static function createForManifest(Manifest $manifest, string $pathToZip): return $zip; } - protected static function determineNameOfFileInZip(string $pathToFile, string $pathToZip, string $relativePath) + protected static function determineNameOfFileInZip(string $pathToFile, string $pathToZip, string $relativePath): string { - $fileDirectory = pathinfo($pathToFile, PATHINFO_DIRNAME) . DIRECTORY_SEPARATOR; + $fileDirectory = pathinfo($pathToFile, PATHINFO_DIRNAME).DIRECTORY_SEPARATOR; - $zipDirectory = pathinfo($pathToZip, PATHINFO_DIRNAME) . DIRECTORY_SEPARATOR; + $zipDirectory = pathinfo($pathToZip, PATHINFO_DIRNAME).DIRECTORY_SEPARATOR; if (Str::startsWith($fileDirectory, $zipDirectory)) { return substr($pathToFile, strlen($zipDirectory)); @@ -49,15 +61,6 @@ protected static function determineNameOfFileInZip(string $pathToFile, string $p return $pathToFile; } - public function __construct(string $pathToZip) - { - $this->zipFile = new ZipArchive(); - - $this->pathToZip = $pathToZip; - - $this->open(); - } - public function path(): string { return $this->pathToZip; @@ -87,7 +90,7 @@ public function close(): void $this->zipFile->close(); } - public function add(string | iterable $files, string $nameInZip = null): self + public function add(string|iterable $files, ?string $nameInZip = null): self { if (is_array($files)) { $nameInZip = null; @@ -97,8 +100,8 @@ public function add(string | iterable $files, string $nameInZip = null): self $files = [$files]; } - $compressionMethod = config('backup.backup.destination.compression_method', null); - $compressionLevel = config('backup.backup.destination.compression_level', 9); + $compressionMethod = $this->config->backup->destination->compressionMethod; + $compressionLevel = $this->config->backup->destination->compressionLevel; foreach ($files as $file) { if (is_dir($file)) { @@ -106,16 +109,15 @@ public function add(string | iterable $files, string $nameInZip = null): self } if (is_file($file)) { - $this->zipFile->addFile($file, ltrim($nameInZip, DIRECTORY_SEPARATOR)); - - if (is_int($compressionMethod)) { - $this->zipFile->setCompressionName( - ltrim($nameInZip ?: $file, DIRECTORY_SEPARATOR), - $compressionMethod, - $compressionLevel - ); - } + $this->zipFile->addFile($file, ltrim((string) $nameInZip, DIRECTORY_SEPARATOR)); + + $this->zipFile->setCompressionName( + ltrim($nameInZip ?: $file, DIRECTORY_SEPARATOR), + $compressionMethod, + $compressionLevel + ); } + $this->fileCount++; } diff --git a/src/Tasks/Cleanup/CleanupJob.php b/src/Tasks/Cleanup/CleanupJob.php index d327cda5..a8734eda 100644 --- a/src/Tasks/Cleanup/CleanupJob.php +++ b/src/Tasks/Cleanup/CleanupJob.php @@ -11,21 +11,14 @@ class CleanupJob { - protected Collection $backupDestinations; - - protected CleanupStrategy $strategy; - protected bool $sendNotifications = true; + /** @param Collection $backupDestinations */ public function __construct( - Collection $backupDestinations, - CleanupStrategy $strategy, + protected Collection $backupDestinations, + protected CleanupStrategy $strategy, bool $disableNotifications = false, ) { - $this->backupDestinations = $backupDestinations; - - $this->strategy = $strategy; - $this->sendNotifications = ! $disableNotifications; } @@ -57,7 +50,7 @@ public function run(): void }); } - protected function sendNotification($notification): void + protected function sendNotification(string|object $notification): void { if ($this->sendNotifications) { rescue( diff --git a/src/Tasks/Cleanup/CleanupStrategy.php b/src/Tasks/Cleanup/CleanupStrategy.php index 49bf3438..6a85c751 100644 --- a/src/Tasks/Cleanup/CleanupStrategy.php +++ b/src/Tasks/Cleanup/CleanupStrategy.php @@ -12,10 +12,9 @@ abstract class CleanupStrategy public function __construct( protected Repository $config, - ) { - } + ) {} - abstract public function deleteOldBackups(BackupCollection $backups); + abstract public function deleteOldBackups(BackupCollection $backups): void; public function setBackupDestination(BackupDestination $backupDestination): self { diff --git a/src/Tasks/Cleanup/Period.php b/src/Tasks/Cleanup/Period.php index 6019413e..d00bcb32 100644 --- a/src/Tasks/Cleanup/Period.php +++ b/src/Tasks/Cleanup/Period.php @@ -9,8 +9,7 @@ class Period public function __construct( protected Carbon $startDate, protected Carbon $endDate - ) { - } + ) {} public function startDate(): Carbon { diff --git a/src/Tasks/Cleanup/Strategies/DefaultStrategy.php b/src/Tasks/Cleanup/Strategies/DefaultStrategy.php index 81152de4..d4ba5c17 100644 --- a/src/Tasks/Cleanup/Strategies/DefaultStrategy.php +++ b/src/Tasks/Cleanup/Strategies/DefaultStrategy.php @@ -13,14 +13,14 @@ class DefaultStrategy extends CleanupStrategy { protected ?Backup $newestBackup = null; - public function deleteOldBackups(BackupCollection $backups) + public function deleteOldBackups(BackupCollection $backups): void { // Don't ever delete the newest backup. $this->newestBackup = $backups->shift(); $dateRanges = $this->calculateDateRanges(); - /** @var Collection<(string|BackupCollection)> */ + /** @var Collection $backupsPerPeriod */ $backupsPerPeriod = $dateRanges->map(function (Period $period) use ($backups) { return $backups ->filter(fn (Backup $backup) => $backup->date()->between($period->startDate(), $period->endDate())); @@ -38,6 +38,7 @@ public function deleteOldBackups(BackupCollection $backups) $this->removeOldBackupsUntilUsingLessThanMaximumStorage($backups); } + /** @return Collection */ protected function calculateDateRanges(): Collection { $config = $this->config->get('backup.cleanup.default_strategy'); @@ -70,15 +71,16 @@ protected function calculateDateRanges(): Collection return collect(compact('daily', 'weekly', 'monthly', 'yearly')); } - protected function groupByDateFormat(Collection $backups, string $dateFormat): Collection + protected function groupByDateFormat(BackupCollection $backups, string $dateFormat): BackupCollection { return $backups->groupBy(fn (Backup $backup) => $backup->date()->format($dateFormat)); } - protected function removeBackupsForAllPeriodsExceptOne(Collection $backupsPerPeriod) + /** @param Collection $backupsPerPeriod */ + protected function removeBackupsForAllPeriodsExceptOne(Collection $backupsPerPeriod): void { $backupsPerPeriod->each(function (Collection $groupedBackupsByDateProperty, string $periodName) { - $groupedBackupsByDateProperty->each(function (Collection $group) { + $groupedBackupsByDateProperty->each(function (BackupCollection $group) { $group->shift(); $group->each(fn (Backup $backup) => $backup->delete()); @@ -86,14 +88,14 @@ protected function removeBackupsForAllPeriodsExceptOne(Collection $backupsPerPer }); } - protected function removeBackupsOlderThan(Carbon $endDate, BackupCollection $backups) + protected function removeBackupsOlderThan(Carbon $endDate, BackupCollection $backups): void { $backups ->filter(fn (Backup $backup) => $backup->exists() && $backup->date()->lt($endDate)) ->each(fn (Backup $backup) => $backup->delete()); } - protected function removeOldBackupsUntilUsingLessThanMaximumStorage(BackupCollection $backups) + protected function removeOldBackupsUntilUsingLessThanMaximumStorage(BackupCollection $backups): void { if (! $this->shouldRemoveOldestBackup($backups)) { return; diff --git a/src/Tasks/Monitor/BackupDestinationStatus.php b/src/Tasks/Monitor/BackupDestinationStatus.php index e63e8900..b79ee544 100644 --- a/src/Tasks/Monitor/BackupDestinationStatus.php +++ b/src/Tasks/Monitor/BackupDestinationStatus.php @@ -11,18 +11,18 @@ class BackupDestinationStatus { protected ?HealthCheckFailure $healthCheckFailure = null; + /** @param array $healthChecks */ public function __construct( protected BackupDestination $backupDestination, protected array $healthChecks = [] - ) { - } + ) {} public function backupDestination(): BackupDestination { return $this->backupDestination; } - public function check(HealthCheck $check): bool | HealthCheckFailure + public function check(HealthCheck $check): bool|HealthCheckFailure { try { $check->checkHealth($this->backupDestination()); @@ -33,6 +33,7 @@ public function check(HealthCheck $check): bool | HealthCheckFailure return true; } + /** @return Collection */ public function getHealthChecks(): Collection { return collect($this->healthChecks)->prepend(new IsReachable()); diff --git a/src/Tasks/Monitor/BackupDestinationStatusFactory.php b/src/Tasks/Monitor/BackupDestinationStatusFactory.php index 061b3132..42c0c6ec 100644 --- a/src/Tasks/Monitor/BackupDestinationStatusFactory.php +++ b/src/Tasks/Monitor/BackupDestinationStatusFactory.php @@ -2,34 +2,45 @@ namespace Spatie\Backup\Tasks\Monitor; -use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Spatie\Backup\BackupDestination\BackupDestination; +use Spatie\Backup\Config\MonitoredBackupsConfig; class BackupDestinationStatusFactory { - public static function createForMonitorConfig(array $monitorConfiguration): Collection + /** + * @return Collection + */ + public static function createForMonitorConfig(MonitoredBackupsConfig $monitorConfiguration): Collection { - return collect($monitorConfiguration) + return collect($monitorConfiguration->monitorBackups) ->flatMap(fn (array $monitorProperties) => self::createForSingleMonitor($monitorProperties)) - ->sortBy(fn (BackupDestinationStatus $backupDestinationStatus) => $backupDestinationStatus->backupDestination()->backupName() . '-' . + ->sortBy(fn (BackupDestinationStatus $backupDestinationStatus) => $backupDestinationStatus->backupDestination()->backupName().'-'. $backupDestinationStatus->backupDestination()->diskName()); } + /** + * @param array{name: string, disks: array, healthChecks: array>} $monitorConfig + * @return Collection + */ public static function createForSingleMonitor(array $monitorConfig): Collection { return collect($monitorConfig['disks']) - ->map(function ($diskName) use ($monitorConfig) { + ->map(function ($diskName) use ($monitorConfig): \Spatie\Backup\Tasks\Monitor\BackupDestinationStatus { $backupDestination = BackupDestination::create($diskName, $monitorConfig['name']); return new BackupDestinationStatus($backupDestination, static::buildHealthChecks($monitorConfig)); }); } - protected static function buildHealthChecks($monitorConfig): array + /** + * @param array{name: string, disks: array, healthChecks: array>} $monitorConfig + * @return array + */ + protected static function buildHealthChecks(array $monitorConfig): array { - return collect(Arr::get($monitorConfig, 'health_checks')) - ->map(function ($options, $class) { + return collect($monitorConfig['healthChecks']) + ->map(function ($options, $class): \Spatie\Backup\Tasks\Monitor\HealthCheck { if (is_int($class)) { $class = $options; $options = []; @@ -39,7 +50,8 @@ protected static function buildHealthChecks($monitorConfig): array })->toArray(); } - protected static function buildHealthCheck(string $class, string | array $options): HealthCheck + /** @param string|array $options */ + protected static function buildHealthCheck(string $class, string|array $options): HealthCheck { if (! is_array($options)) { return new $class($options); diff --git a/src/Tasks/Monitor/HealthCheck.php b/src/Tasks/Monitor/HealthCheck.php index 6d04f141..499c12e5 100644 --- a/src/Tasks/Monitor/HealthCheck.php +++ b/src/Tasks/Monitor/HealthCheck.php @@ -8,7 +8,7 @@ abstract class HealthCheck { - abstract public function checkHealth(BackupDestination $backupDestination); + abstract public function checkHealth(BackupDestination $backupDestination): void; public function name(): string { diff --git a/src/Tasks/Monitor/HealthCheckFailure.php b/src/Tasks/Monitor/HealthCheckFailure.php index ca97fa6a..94d1b0ce 100644 --- a/src/Tasks/Monitor/HealthCheckFailure.php +++ b/src/Tasks/Monitor/HealthCheckFailure.php @@ -10,8 +10,7 @@ class HealthCheckFailure public function __construct( protected HealthCheck $healthCheck, protected Exception $exception - ) { - } + ) {} public function healthCheck(): HealthCheck { diff --git a/src/Tasks/Monitor/HealthChecks/MaximumAgeInDays.php b/src/Tasks/Monitor/HealthChecks/MaximumAgeInDays.php index c25d97bd..0bd33824 100644 --- a/src/Tasks/Monitor/HealthChecks/MaximumAgeInDays.php +++ b/src/Tasks/Monitor/HealthChecks/MaximumAgeInDays.php @@ -10,8 +10,7 @@ class MaximumAgeInDays extends HealthCheck { public function __construct( protected int $days = 1 - ) { - } + ) {} public function checkHealth(BackupDestination $backupDestination): void { diff --git a/src/Tasks/Monitor/HealthChecks/MaximumStorageInMegabytes.php b/src/Tasks/Monitor/HealthChecks/MaximumStorageInMegabytes.php index 2335808a..9a119ed3 100644 --- a/src/Tasks/Monitor/HealthChecks/MaximumStorageInMegabytes.php +++ b/src/Tasks/Monitor/HealthChecks/MaximumStorageInMegabytes.php @@ -10,8 +10,7 @@ class MaximumStorageInMegabytes extends HealthCheck { public function __construct( protected int $maximumSizeInMegaBytes = 5000 - ) { - } + ) {} public function checkHealth(BackupDestination $backupDestination): void { diff --git a/src/Traits/Retryable.php b/src/Traits/Retryable.php index b0ef0fb6..f255a983 100644 --- a/src/Traits/Retryable.php +++ b/src/Traits/Retryable.php @@ -10,7 +10,7 @@ trait Retryable protected int $currentTry = 1; - protected function shouldRetry() + protected function shouldRetry(): bool { if ($this->tries <= 1) { return false; @@ -19,29 +19,37 @@ protected function shouldRetry() return $this->currentTry < $this->tries; } - protected function hasRetryDelay(string $type) + protected function hasRetryDelay(string $type): bool { return ! empty($this->getRetryDelay($type)); } - protected function sleepFor(int $seconds = 0) + protected function sleepFor(int $seconds = 0): void { Sleep::for($seconds)->seconds(); } - protected function setTries(string $type) + protected function setTries(string $type): void { if ($this->option('tries')) { - $this->tries = (int)$this->option('tries'); + $this->tries = (int) $this->option('tries'); return; } - $this->tries = (int)config('backup.' . $type . '.tries', 1); + $this->tries = match ($type) { + 'backup' => $this->config->backup->tries, + 'cleanup' => $this->config->cleanup->tries, + default => 1, + }; } - protected function getRetryDelay(string $type) + protected function getRetryDelay(string $type): int { - return (int)config('backup.' . $type . '.retry_delay', 0); + return match ($type) { + 'backup' => $this->config->backup->retryDelay, + 'cleanup' => $this->config->cleanup->retryDelay, + default => 0, + }; } } diff --git a/tests/BackupDestination/BackupTest.php b/tests/BackupDestination/BackupTest.php index 4dd6bf4d..2b48c1c5 100644 --- a/tests/BackupDestination/BackupTest.php +++ b/tests/BackupDestination/BackupTest.php @@ -6,6 +6,7 @@ use Mockery as m; use Spatie\Backup\BackupDestination\Backup; use Spatie\Backup\BackupDestination\BackupDestinationFactory; +use Spatie\Backup\Config\Config; use Spatie\Backup\Exceptions\InvalidBackupFile; it('can determine the disk of the backup', function () { @@ -62,7 +63,7 @@ }); it('can determine its size', function () { - $backup = getBackupForFile('test.zip', 0, 'this backup has content'); + $backup = getBackupForFile('test.zip', 0); $fileSize = floatval(Storage::disk('local')->size('mysite.com/test.zip')); @@ -72,7 +73,7 @@ }); it('can determine its size even after it has been deleted', function () { - $backup = getBackupForFile('test.zip', 0, 'this backup has content'); + $backup = getBackupForFile('test.zip', 0); $backup->delete(); @@ -92,7 +93,9 @@ 's3-test-backup', ]); - $backupDestination = BackupDestinationFactory::createFromArray(config('backup.backup'))->first(); + $config = Config::fromArray(config('backup')); + + $backupDestination = BackupDestinationFactory::createFromArray($config)->first(); expect($backupDestination->getDiskOptions())->toEqual(['StorageClass' => 'COLD']); }); @@ -107,13 +110,15 @@ 'local', ]); - $backupDestination = BackupDestinationFactory::createFromArray(config('backup.backup'))->first(); + $config = Config::fromArray(config('backup')); + + $backupDestination = BackupDestinationFactory::createFromArray($config)->first(); expect($backupDestination->getDiskOptions())->toBe([]); }); it('need a float type size', function () { - $backup = getBackupForFile('test.zip', 0, 'this backup has content'); + $backup = getBackupForFile('test.zip', 0); expect($backup->sizeInBytes())->toBeFloat(); }); diff --git a/tests/Commands/BackupCommandTest.php b/tests/Commands/BackupCommandTest.php index 446af30b..0c59d72b 100644 --- a/tests/Commands/BackupCommandTest.php +++ b/tests/Commands/BackupCommandTest.php @@ -102,6 +102,7 @@ foreach (range(0, $zip->numFiles - 1) as $i) { $zipFiles[] = $zip->statIndex($i)['name']; } + $zip->close(); sort($testFiles); sort($zipFiles); @@ -121,9 +122,10 @@ if ($zip->numFiles) { $zipFile = $zip->statIndex(0)['name']; } + $zip->close(); - expect($zipFile)->toStartWith(ltrim($this->getStubDirectory(), DIRECTORY_SEPARATOR)); + expect($zipFile)->toStartWith(ltrim((string) $this->getStubDirectory(), DIRECTORY_SEPARATOR)); }); it('excludes the temporary directory from the backup', function () { @@ -132,6 +134,7 @@ if (! file_exists($tempDirectoryPath)) { mkdir($tempDirectoryPath, 0777, true); } + touch($tempDirectoryPath.DIRECTORY_SEPARATOR.'testing-file-temp.txt'); $this->artisan('backup:run --only-files')->assertExitCode(0); @@ -367,8 +370,10 @@ $zip = new ZipArchive(); $zip->open(Storage::disk('local')->path($this->expectedZipPath)); + expect($zip->numFiles)->toBe(1); expect($zip->statIndex(0)['encryption_method'])->toBe(ZipArchive::EM_AES_256); + $zip->close(); Event::assertNotDispatched(BackupZipWasCreated::class); @@ -382,34 +387,42 @@ $zip = new ZipArchive(); $zip->open(Storage::disk('local')->path($this->expectedZipPath)); + expect($zip->numFiles)->toBe(1); expect($zip->statIndex(0)['comp_method'])->toBe(ZipArchive::CM_DEFLATE); - $zip->close(); + $zip->close(); // check no compression with ZipArchive::CM_STORE method config()->set('backup.backup.destination.compression_method', ZipArchive::CM_STORE); config()->set('backup.backup.destination.compression_level', 0); + \Spatie\Backup\Config\Config::rebind(); + $this->artisan('backup:run --only-db')->assertExitCode(0); $zip = new ZipArchive(); $zip->open(Storage::disk('local')->path($this->expectedZipPath)); + expect($zip->numFiles)->toBe(1); expect($zip->statIndex(0)['comp_method'])->toBe(ZipArchive::CM_STORE); - $zip->close(); + $zip->close(); // check ZipArchive::CM_DEFLATE method with custom compression level config()->set('backup.backup.destination.compression_method', ZipArchive::CM_DEFLATE); config()->set('backup.backup.destination.compression_level', 2); + \Spatie\Backup\Config\Config::rebind(); + $this->artisan('backup:run --only-db')->assertExitCode(0); $zip = new ZipArchive(); $zip->open(Storage::disk('local')->path($this->expectedZipPath)); + expect($zip->numFiles)->toBe(1); expect($zip->statIndex(0)['comp_method'])->toBe(ZipArchive::CM_DEFLATE); + $zip->close(); }); diff --git a/tests/Commands/CleanupCommandTest.php b/tests/Commands/CleanupCommandTest.php index 1e717e4c..4ff893e1 100644 --- a/tests/Commands/CleanupCommandTest.php +++ b/tests/Commands/CleanupCommandTest.php @@ -35,7 +35,7 @@ return [ $this->createFileOnDisk('local', "mysite/test_{$date->format('Ymd')}_first.zip", $date), - $this->createFileOnDisk('local', "mysite/test_{$date->format('Ymd')}_second.zip", $date->addHour(2)), + $this->createFileOnDisk('local', "mysite/test_{$date->format('Ymd')}_second.zip", $date->addHours(2)), ]; })->partition(function (string $backupPath) { return in_array($backupPath, [ diff --git a/tests/Commands/ListCommandTest.php b/tests/Commands/ListCommandTest.php index 35657b57..a5b67f6e 100644 --- a/tests/Commands/ListCommandTest.php +++ b/tests/Commands/ListCommandTest.php @@ -7,13 +7,3 @@ $this->artisan('backup:list')->assertExitCode(0); }); - -it('warns_the_user_about_the_old_style_config_keys', function () { - $this->artisan('backup:list') - ->assertSuccessful(); - - config(['backup.monitorBackups' => config('backup.monitor_backups')]); - - $this->artisan('backup:list') - ->expectsOutput("Warning! Your config file still uses the old monitorBackups key. Update it to monitor_backups."); -}); diff --git a/tests/Commands/MonitorCommandTest.php b/tests/Commands/MonitorCommandTest.php index a43b7c32..f768f0dc 100644 --- a/tests/Commands/MonitorCommandTest.php +++ b/tests/Commands/MonitorCommandTest.php @@ -2,19 +2,6 @@ namespace Spatie\Backup\Tests\Commands; -use Spatie\Backup\Tests\TestCase; - -class MonitorCommandTest extends TestCase -{ - /** @test */ - public function it_warns_the_user_about_the_old_style_config_keys() - { - $this->artisan('backup:monitor') - ->assertSuccessful(); - - config(['backup.monitorBackups' => config('backup.monitor_backups')]); - - $this->artisan('backup:monitor') - ->expectsOutput("Warning! Your config file still uses the old monitorBackups key. Update it to monitor_backups."); - } -} +it('can run the monitor command', function () { + $this->artisan('backup:monitor')->assertExitCode(0); +}); diff --git a/tests/DbDumperFactoryTest.php b/tests/DbDumperFactoryTest.php index d93c82ee..d45c4158 100644 --- a/tests/DbDumperFactoryTest.php +++ b/tests/DbDumperFactoryTest.php @@ -174,7 +174,6 @@ expect(DbDumperFactory::createFromConnection('mysql'))->toBeInstanceOf(MongoDb::class); }); - function getDumpCommand(): string { $dumpFile = ''; diff --git a/tests/FileSelectionTest.php b/tests/FileSelectionTest.php index 4c5b9eeb..db75ff40 100644 --- a/tests/FileSelectionTest.php +++ b/tests/FileSelectionTest.php @@ -37,7 +37,7 @@ it('can exclude files from a given subdirectory', function () { $fileSelection = (new FileSelection($this->sourceDirectory)) - ->excludeFilesFrom("{$this->sourceDirectory}/directory1"); + ->excludeFilesFrom("{$this->sourceDirectory}/directory1"); $testFiles = getTestFiles([ '.dot', @@ -62,8 +62,8 @@ it('can exclude files with wildcards from a given subdirectory', function () { $fileSelection = (new FileSelection($this->sourceDirectory)) ->excludeFilesFrom(getTestFiles([ - "*/file1.txt", - "*/directory1", + '*/file1.txt', + '*/directory1', ])); $testFiles = getTestFiles([ @@ -160,7 +160,6 @@ expect($fileSelection)->toBeInstanceOf(FileSelection::class); }); - function assertSameArrayContent($expected, $actual, $message = '') { test()->assertCount(count($expected), array_intersect($expected, $actual), $message); diff --git a/tests/FormatTest.php b/tests/FormatTest.php index dd4bb4e0..0f61ed97 100644 --- a/tests/FormatTest.php +++ b/tests/FormatTest.php @@ -1,7 +1,6 @@ startOfDay()); expect(Format::ageInDays(Carbon::now()->subSeconds(5)))->toEqual('0.00 (5 seconds ago)'); - expect(Format::ageInDays(Carbon::now()->subHour(1)))->toEqual('0.04 (1 hour ago)'); - expect(Format::ageInDays(Carbon::now()->subHour(1)->subDay(1)))->toEqual('1.04 (1 day ago)'); - expect(Format::ageInDays(Carbon::now()->subHour(1)->subMonths(1)))->toEqual('30.04 (4 weeks ago)'); + expect(Format::ageInDays(Carbon::now()->subHour()))->toEqual('0.04 (1 hour ago)'); + expect(Format::ageInDays(Carbon::now()->subHour()->subDay()))->toEqual('1.04 (1 day ago)'); + expect(Format::ageInDays(Carbon::now()->subHour()->subMonths(1)))->toEqual('30.04 (4 weeks ago)'); }); diff --git a/tests/Listeners/EncryptBackupArchiveTest.php b/tests/Listeners/EncryptBackupArchiveTest.php index 9ea73acc..756c22d4 100644 --- a/tests/Listeners/EncryptBackupArchiveTest.php +++ b/tests/Listeners/EncryptBackupArchiveTest.php @@ -23,7 +23,7 @@ }); /** - * @param int $algorithm + * @param int $algorithm */ it('encrypts archive with password', function (int $algorithm) { config()->set('backup.backup.encryption', $algorithm); diff --git a/tests/ManifestTest.php b/tests/ManifestTest.php index 52f372b7..85ab2f86 100644 --- a/tests/ManifestTest.php +++ b/tests/ManifestTest.php @@ -67,7 +67,6 @@ } }); - function getManifestTestFiles(): array { return collect(range(1, 3)) diff --git a/tests/Notifications/EventHandlerTest.php b/tests/Notifications/EventHandlerTest.php index b001bc39..bcfb1aee 100644 --- a/tests/Notifications/EventHandlerTest.php +++ b/tests/Notifications/EventHandlerTest.php @@ -2,9 +2,10 @@ use Illuminate\Support\Facades\Notification; use Spatie\Backup\BackupDestination\BackupDestinationFactory; +use Spatie\Backup\Config\Config; use Spatie\Backup\Events\BackupHasFailed; use Spatie\Backup\Notifications\Notifiable; -use Spatie\Backup\Notifications\Notifications\BackupHasFailedNotification as BackupHasFailedNotification; +use Spatie\Backup\Notifications\Notifications\BackupHasFailedNotification; beforeEach(function () { Notification::fake(); @@ -49,11 +50,13 @@ Notification::assertSentTimes(BackupHasFailedNotification::class, 1); }); -function fireBackupHasFailedEvent() +function fireBackupHasFailedEvent(): void { $exception = new Exception('Dummy exception'); - $backupDestination = BackupDestinationFactory::createFromArray(config('backup.backup'))->first(); + $config = Config::fromArray(config('backup')); + + $backupDestination = BackupDestinationFactory::createFromArray($config)->first(); event(new BackupHasFailed($exception, $backupDestination)); } diff --git a/tests/Pest.php b/tests/Pest.php index 0819fb64..43d20332 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -1,9 +1,9 @@ in(__DIR__); expect()->extend('hasItemContaining', function (string $searchString) { diff --git a/tests/TestCase.php b/tests/TestCase.php index 27f4ce42..d7e5f45b 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -19,9 +19,7 @@ abstract class TestCase extends Orchestra { /** - * @param \Illuminate\Foundation\Application $app - * - * @return array + * @param \Illuminate\Foundation\Application $app */ protected function getPackageProviders($app): array { @@ -31,7 +29,7 @@ protected function getPackageProviders($app): array } /** - * @param \Illuminate\Foundation\Application $app + * @param \Illuminate\Foundation\Application $app */ protected function getEnvironmentSetUp($app) { @@ -57,7 +55,7 @@ protected function getEnvironmentSetUp($app) Storage::fake('secondLocal'); } - protected function setUpDatabase(Application $app) + protected function setUpDatabase(Application $app): void { touch($this->getTempDirectory().'/database.sqlite'); @@ -74,7 +72,7 @@ protected function seeInConsoleOutput(string $expectedText): void $this->assertStringContainsString( $expectedText, $consoleOutput, - "Did not see `{$expectedText}` in console output: `$consoleOutput`" + "Did not see `{$expectedText}` in console output: `{$consoleOutput}`" ); } @@ -85,7 +83,7 @@ protected function doNotSeeInConsoleOutput(string $unexpectedText): void $this->assertNotContains( $unexpectedText, $consoleOutput, - "Did not expect to see `{$unexpectedText}` in console output: `$consoleOutput`" + "Did not expect to see `{$unexpectedText}` in console output: `{$consoleOutput}`" ); } @@ -116,7 +114,7 @@ protected function fileExistsInZip(string $diskName, string $zipPath, string $fi return false; } - protected function assertExactPathExistsInZip(string $diskName, string $zipPath, string $fullPath) + protected function assertExactPathExistsInZip(string $diskName, string $zipPath, string $fullPath): void { $this->assertTrue( $this->exactPathExistsInZip($diskName, $zipPath, $fullPath), @@ -206,7 +204,7 @@ public function getTempDirectory(?string $file = null): string return __DIR__.'/temp'.($file ? '/'.$file : ''); } - public function initializeTempDirectory() + public function initializeTempDirectory(): void { $this->initializeDirectory($this->getTempDirectory()); } @@ -236,7 +234,7 @@ public function fakeBackup(): self return $this; } - public function makeHealthCheckFail(Exception $customException = null): self + public function makeHealthCheckFail(?Exception $customException = null): self { FakeFailingHealthCheck::$reason = $customException; diff --git a/tests/TestSupport/FakeFailingHealthCheck.php b/tests/TestSupport/FakeFailingHealthCheck.php index 7f96a5f6..d0be35a4 100644 --- a/tests/TestSupport/FakeFailingHealthCheck.php +++ b/tests/TestSupport/FakeFailingHealthCheck.php @@ -10,7 +10,7 @@ class FakeFailingHealthCheck extends HealthCheck { public static $reason; - public function checkHealth(BackupDestination $backupDestination) + public function checkHealth(BackupDestination $backupDestination): void { throw (static::$reason ?: new Exception('dummy exception message')); }