From a914f0293b82a208556135ca6228eec408688170 Mon Sep 17 00:00:00 2001 From: Giuseppe Criscione <18699708+giuscris@users.noreply.github.com> Date: Sun, 8 Mar 2026 14:02:50 +0100 Subject: [PATCH 1/2] Refactor `Backupper` and `Updater` as services --- formwork/src/Backup/Backupper.php | 26 +++++++++++--- formwork/src/Cms/App.php | 8 +++++ formwork/src/Commands/BackupCommand.php | 14 ++------ formwork/src/Commands/UpdatesCommand.php | 36 ++++--------------- .../Panel/Controllers/BackupController.php | 3 +- .../src/Panel/Controllers/ToolsController.php | 4 +-- .../Panel/Controllers/UpdatesController.php | 3 +- .../Services/Loaders/PanelServiceLoader.php | 4 --- formwork/src/Updater/Updater.php | 19 +++++----- 9 files changed, 51 insertions(+), 66 deletions(-) diff --git a/formwork/src/Backup/Backupper.php b/formwork/src/Backup/Backupper.php index 068c1279b..cceb5be20 100644 --- a/formwork/src/Backup/Backupper.php +++ b/formwork/src/Backup/Backupper.php @@ -3,8 +3,11 @@ namespace Formwork\Backup; use Formwork\Backup\Utils\ZipErrors; +use Formwork\Cms\App; use Formwork\Exceptions\TranslatedException; +use Formwork\Http\Request; use Formwork\Utils\FileSystem; +use Formwork\Utils\Str; use ZipArchive; final class Backupper @@ -12,12 +15,13 @@ final class Backupper /** * Date format used in backup archive name */ - private const string DATE_FORMAT = 'YmdHis'; + private const string DATE_FORMAT = 'Ymd-His'; /** - * @param array $options + * @param array $options */ public function __construct( + private Request $request, private array $options, ) {} @@ -26,7 +30,7 @@ public function __construct( * * @return string Backup archive file path */ - public function backup(): string + public function backup(?string $name = null, ?string $hostname = null): string { $previousMaxExecutionTime = ini_set('max_execution_time', $this->options['maxExecutionTime']); @@ -37,9 +41,21 @@ public function backup(): string FileSystem::createDirectory($this->options['path'], recursive: true); } - $name = sprintf('%s-%s-%s.zip', str_replace([' ', '.'], '-', $this->options['hostname'] ?? 'unknown-host'), $this->options['name'], date(self::DATE_FORMAT)); + $date = date(self::DATE_FORMAT); - $destination = FileSystem::joinPaths($path, $name); + $suffix = "-{$date}.zip"; + + $name = Str::interpolate($name ?? $this->options['name'], [ + 'hostname' => str_replace('.', '-', $hostname ?? $this->options['hostname'] ?? $this->request->host() ?? 'unknown-host'), + 'site' => Str::slug(App::instance()->site()->title() ?? 'unknown-site'), + 'context' => PHP_SAPI === 'cli' ? 'cli' : 'web', + 'version' => App::VERSION, + 'random' => FileSystem::randomName(), + ]); + + $filename = rtrim(substr($name, 0, 75 - strlen($suffix)), '-_') . $suffix; + + $destination = FileSystem::joinPaths($path, $filename); $zipArchive = new ZipArchive(); diff --git a/formwork/src/Cms/App.php b/formwork/src/Cms/App.php index 3ee973139..70c8bb9eb 100644 --- a/formwork/src/Cms/App.php +++ b/formwork/src/Cms/App.php @@ -6,6 +6,7 @@ use ErrorException; use Formwork\Assets\Assets; use Formwork\Authentication\Authenticator; +use Formwork\Backup\Backupper; use Formwork\Cache\AbstractCache; use Formwork\Cache\FilesCache; use Formwork\Cms\Events\ExceptionThrownEvent; @@ -50,6 +51,7 @@ use Formwork\Templates\Templates; use Formwork\Traits\SingletonClass; use Formwork\Translations\Translations; +use Formwork\Updater\Updater; use Formwork\Users\UserFactory; use Formwork\Users\Users; use Formwork\Utils\Str; @@ -360,6 +362,12 @@ private function loadServices(Container $container): void $container->define(Plugins::class) ->loader(PluginsServiceLoader::class) ->alias('plugins'); + + $container->define(Backupper::class) + ->parameter('options', fn(Config $config) => $config->get('system.backup')); + + $container->define(Updater::class) + ->parameter('options', fn(Config $config) => $config->get('system.updates')); } /** diff --git a/formwork/src/Commands/BackupCommand.php b/formwork/src/Commands/BackupCommand.php index f678e2e75..14134ee77 100644 --- a/formwork/src/Commands/BackupCommand.php +++ b/formwork/src/Commands/BackupCommand.php @@ -104,8 +104,8 @@ public function make(array $argv = []): void { $this->climate->out('Creating backup... this may take a while depending on the size of your installation.'); /** @var string $hostname */ - $hostname = $this->climate->arguments->get('hostname') ?: null; - $file = $this->getBackupper($hostname)->backup(); + $hostname = $this->climate->arguments->get('hostname') ?: (gethostname() ?: 'local-cli'); + $file = $this->app->getService(Backupper::class)->backup(hostname: $hostname); $this->climate->br()->out(sprintf('Backup created: %s', $file)); } @@ -116,7 +116,7 @@ public function make(array $argv = []): void */ public function list(array $argv = []): void { - $backups = $this->getBackupper()->getBackups(); + $backups = $this->app->getService(Backupper::class)->getBackups(); if (count($backups) === 0) { $this->climate->green('No backups found.'); return; @@ -131,14 +131,6 @@ public function list(array $argv = []): void } } - /** - * Get Backupper instance - */ - private function getBackupper(?string $hostname = null): Backupper - { - return new Backupper([...$this->app->config()->get('system.backup'), 'hostname' => $hostname ?? (gethostname() ?: 'local-cli')]); - } - /** * @param list $argv */ diff --git a/formwork/src/Commands/UpdatesCommand.php b/formwork/src/Commands/UpdatesCommand.php index c7eac5134..7f09507e9 100644 --- a/formwork/src/Commands/UpdatesCommand.php +++ b/formwork/src/Commands/UpdatesCommand.php @@ -117,10 +117,10 @@ public function check(array $argv = []): void { $force = $this->climate->arguments->defined('force'); - $updater = $this->getUpdater(['force' => $force]); + $updater = $this->app->getService(Updater::class); try { - $upToDate = $updater->checkUpdates(); + $upToDate = $updater->checkUpdates($force); } catch (RuntimeException $e) { $this->climate->error("Cannot check for updates: {$e->getMessage()}"); exit(1); @@ -158,14 +158,10 @@ public function update(array $argv = []): void $backup = !$this->climate->arguments->defined('no-backup'); $preferDist = !$this->climate->arguments->defined('no-prefer-dist'); $cleanup = !$this->climate->arguments->defined('no-cleanup'); - $updater = $this->getUpdater([ - 'force' => $force, - 'preferDistAssets' => $preferDist, - 'cleanupAfterInstall' => $cleanup, - ]); + $updater = $this->app->getService(Updater::class); try { - $upToDate = $updater->checkUpdates(); + $upToDate = $updater->checkUpdates(force: $force, preferDistAssets: $preferDist); } catch (RuntimeException $e) { $this->climate->error("Cannot check for updates: {$e->getMessage()}"); exit(1); @@ -184,9 +180,9 @@ public function update(array $argv = []): void if ($backup) { $this->climate->out('Creating backup before update... this may take a while depending on the size of your installation and site.'); - $backupper = $this->getBackupper(); try { - $backupper->backup(); + $this->app->getService(Backupper::class) + ->backup(hostname: gethostname() ?: 'local-cli'); } catch (RuntimeException $e) { $this->climate->error("Cannot make backup: {$e->getMessage()}"); exit(1); @@ -196,7 +192,7 @@ public function update(array $argv = []): void try { $this->climate->out("Updating Formwork to {$release['tag']}..."); - $updater->update(); + $updater->update(force: $force, preferDistAssets: $preferDist, cleanupAfterInstall: $cleanup); } catch (RuntimeException $e) { $this->climate->error("Cannot install updates: {$e->getMessage()}"); exit(1); @@ -212,24 +208,6 @@ public function update(array $argv = []): void $this->climate->out("Formwork has been updated successfully to {$release['tag']}."); } - /** - * Get Updater instance - * - * @param array $config - */ - private function getUpdater(array $config): Updater - { - return new Updater([...$this->app->config()->get('system.updates'), ...$config], App::instance()); - } - - /** - * Get Backupper instance - */ - private function getBackupper(): Backupper - { - return new Backupper([...$this->app->config()->get('system.backup'), 'hostname' => gethostname() ?: 'local-cli']); - } - /** * @param list $argv */ diff --git a/formwork/src/Panel/Controllers/BackupController.php b/formwork/src/Panel/Controllers/BackupController.php index 59ef8c74e..c924234c6 100644 --- a/formwork/src/Panel/Controllers/BackupController.php +++ b/formwork/src/Panel/Controllers/BackupController.php @@ -18,13 +18,12 @@ final class BackupController extends AbstractController /** * Backup@make action */ - public function make(): JsonResponse|Response + public function make(Backupper $backupper): JsonResponse|Response { if (!$this->hasPermission('panel.backup.make')) { return $this->forward(ErrorsController::class, 'forbidden'); } - $backupper = $backupper = new Backupper([...$this->config->get('system.backup'), 'hostname' => $this->request->host()]); try { $file = $backupper->backup(); } catch (TranslatedException $e) { diff --git a/formwork/src/Panel/Controllers/ToolsController.php b/formwork/src/Panel/Controllers/ToolsController.php index 8ce2284ce..b1a7f9a6f 100644 --- a/formwork/src/Panel/Controllers/ToolsController.php +++ b/formwork/src/Panel/Controllers/ToolsController.php @@ -34,14 +34,12 @@ public function index(): Response /** * Tools@backups action */ - public function backups(): Response + public function backups(Backupper $backupper): Response { if (!$this->hasPermission('panel.tools.backups')) { return $this->forward(ErrorsController::class, 'forbidden'); } - $backupper = new Backupper([...$this->config->get('system.backup'), 'hostname' => $this->request->host()]); - $backups = Arr::map($backupper->getBackups(), fn(string $path, int $timestamp): array => [ 'name' => basename($path), 'encodedName' => rawurlencode(base64_encode(basename($path))), diff --git a/formwork/src/Panel/Controllers/UpdatesController.php b/formwork/src/Panel/Controllers/UpdatesController.php index 096b2e8ba..689bf2075 100644 --- a/formwork/src/Panel/Controllers/UpdatesController.php +++ b/formwork/src/Panel/Controllers/UpdatesController.php @@ -43,14 +43,13 @@ public function check(Updater $updater): JsonResponse|Response /** * Updates@update action */ - public function update(Updater $updater, AbstractCache $cache): JsonResponse|Response + public function update(Updater $updater, Backupper $backupper, AbstractCache $cache): JsonResponse|Response { if (!$this->hasPermission('panel.updates.update')) { return $this->forward(ErrorsController::class, 'forbidden'); } if ($this->config->get('system.updates.backupBefore')) { - $backupper = new Backupper([...$this->config->get('system.backup'), 'hostname' => $this->request->host()]); try { $backupper->backup(); } catch (TranslatedException) { diff --git a/formwork/src/Services/Loaders/PanelServiceLoader.php b/formwork/src/Services/Loaders/PanelServiceLoader.php index 6b5f577da..e0962956d 100644 --- a/formwork/src/Services/Loaders/PanelServiceLoader.php +++ b/formwork/src/Services/Loaders/PanelServiceLoader.php @@ -20,7 +20,6 @@ use Formwork\Services\Container; use Formwork\Services\ResolutionAwareServiceLoaderInterface; use Formwork\Translations\Translations; -use Formwork\Updater\Updater; use Formwork\Utils\FileSystem; use Formwork\View\ViewFactory; @@ -56,9 +55,6 @@ public function load(Container $container): Panel $container->resolve(RateLimiter::class); } - $container->define(Updater::class) - ->parameter('options', $this->config->get('system.updates')); - if ($this->config->has('system.panel.sessionTimeout')) { trigger_error('The "system.panel.sessionTimeout" configuration option (in minutes) is deprecated since Formwork 2.3.0. Use "system.session.duration" (in seconds) instead.', E_USER_DEPRECATED); $this->request->session()->setDuration($this->config->get('system.panel.sessionTimeout') * 60); diff --git a/formwork/src/Updater/Updater.php b/formwork/src/Updater/Updater.php index 6723c492a..bbd99adfc 100644 --- a/formwork/src/Updater/Updater.php +++ b/formwork/src/Updater/Updater.php @@ -67,7 +67,6 @@ final class Updater */ public function __construct( private array $options, - private App $app, ) { $this->registry = new Registry($this->options['registryFile']); @@ -83,10 +82,10 @@ public function __construct( * * @return bool Whether updates are found or not */ - public function checkUpdates(): bool + public function checkUpdates(?bool $force = null, ?bool $preferDistAssets = null): bool { if ( - !$this->options['force'] + !($force ?? $this->options['force']) && $this->registry->has('currentRelease') && $this->registry->get('currentRelease') === App::VERSION && $this->registry->has('lastCheck') && time() - $this->registry->get('lastCheck') < $this->options['time'] ) { @@ -94,7 +93,7 @@ public function checkUpdates(): bool return $this->registry->get('upToDate'); } - $this->loadRelease(); + $this->loadRelease($preferDistAssets ?? $this->options['preferDistAssets']); $this->registry->set('lastCheck', time()); $this->registry->set('currentRelease', App::VERSION); @@ -128,9 +127,9 @@ public function checkUpdates(): bool * * @return bool|null Whether Formwork was updated or not */ - public function update(): ?bool + public function update(?bool $force = null, ?bool $preferDistAssets = null, ?bool $cleanupAfterInstall = null): ?bool { - $this->checkUpdates(); + $this->checkUpdates($force, $preferDistAssets); if ($this->registry->get('upToDate')) { return null; @@ -178,7 +177,7 @@ public function update(): ?bool FileSystem::delete($this->options['tempFile']); - if ($this->options['cleanupAfterInstall']) { + if ($cleanupAfterInstall ?? $this->options['cleanupAfterInstall']) { $deletableFiles = $this->findDeletableFiles($installedFiles); foreach ($deletableFiles as $deletableFile) { FileSystem::delete($deletableFile); @@ -208,7 +207,7 @@ public function latestRelease(): ?array /** * Load latest release data */ - private function loadRelease(): void + private function loadRelease(bool $preferDistAssets = true): void { if (isset($this->release)) { return; @@ -233,7 +232,7 @@ private function loadRelease(): void 'archive' => $data['zipball_url'], ]; - if ($this->options['preferDistAssets'] && !empty($data['assets'])) { + if ($preferDistAssets && !empty($data['assets'])) { $assetName = "formwork-{$data['tag_name']}.zip"; $key = array_search($assetName, array_column($data['assets'], 'name'), true); @@ -266,7 +265,7 @@ private function getReleaseArchiveEtag(): string */ private function isVersionInstallable(string $version): bool { - $semVer = SemVer::fromString($this->app::VERSION); + $semVer = SemVer::fromString($this->registry->get('currentRelease')); $new = SemVer::fromString($version); return !$new->isPrerelease() && $semVer->compareWith($new, '!=') && $semVer->compareWith($new, '^'); } From 014308b56b1ab765f5cd096030cabfbb3c89a041 Mon Sep 17 00:00:00 2001 From: Giuseppe Criscione <18699708+giuscris@users.noreply.github.com> Date: Sun, 8 Mar 2026 14:11:22 +0100 Subject: [PATCH 2/2] Update `backup.name` option with new naming options --- formwork/config/system.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/formwork/config/system.yaml b/formwork/config/system.yaml index 301cc14b4..724b96374 100644 --- a/formwork/config/system.yaml +++ b/formwork/config/system.yaml @@ -6,7 +6,7 @@ authentication: backup: path: '${%ROOT_PATH%}/backup' - name: formwork-backup + name: '{{hostname}}-formwork-backup' maxExecutionTime: 180 maxFiles: 10 ignore: