Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ If you perform a mbin upgrade (eg. `git pull`), be aware to _always_ execute the
./bin/post-upgrade
```

And when needed also execute: `sudo redis-cli FLUSHDB` to get rid of Redis cache issues. And reload the PHP FPM service if you have OPCache enabled.
And when needed also execute: `./bin/console mbin:cache:clear` to get rid of Redis cache issues. And reload the PHP FPM service if you have OPCache enabled.

## Docker Upgrade

Expand All @@ -19,7 +19,5 @@ And when needed also execute: `sudo redis-cli FLUSHDB` to get rid of Redis cache

```bash
$ docker compose exec php bin/console cache:clear
$ docker compose exec redis redis-cli
> auth REDIS_PASSWORD
> FLUSHDB
$ docker compose exec php bin/console mbin:cache:clear
```
2 changes: 1 addition & 1 deletion bin/post-upgrade
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,6 @@ if [ "$answer" != "${answer#[Yy]}" ]; then
fi

echo -e "INFO: Upgrade successfully completed!\n"
echo "INFO: You might want to clear your Redis/Valkey cache (redis-cli FLUSHDB).
echo "INFO: You might want to clear your Redis/Valkey cache (./bin/console mbin:cache:clear).
If you have OPCache enabled also reload your PHP FPM service, to clear the PHP cache."
fi
5 changes: 5 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,11 @@ services:
event: kernel.controller_arguments,
}

# Commands
App\Command\ClearCachesCommand:
arguments:
$redis: '@cache.default_redis_provider'

# Monolog handlers
log_filter_handler:
class: App\Service\MonologFilterHandler
Expand Down
2 changes: 1 addition & 1 deletion docs/02-admin/04-running-mbin/03-upgrades.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ If you perform a Mbin upgrade (eg. `git pull`), be aware to _always_ execute the

### Clear Cache

And when needed also execute: `sudo redis-cli FLUSHDB` to get rid of Redis/KeyDB cache issues. And reload the PHP FPM service if you have OPCache enabled.
And when needed also execute: `./bin/console mbin:cache:clear` to get rid of Redis/KeyDB cache issues. And reload the PHP FPM service if you have OPCache enabled.

## Docker

Expand Down
20 changes: 20 additions & 0 deletions docs/02-admin/04-running-mbin/05-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,26 @@ Usage:
php bin/console mbin:users:remove-marked-for-deletion
```

### Clear caches

This command allows you to delete cached data according to the passed parameters.

The following caches can be deleted (separate them by space in the command):
- `redis`: clear all keys in Redis / Valkey, except the stored Symfony sessions
- `images`: delete all cached thumbnail image files (files in the `cache` dir of the public file storage)

Usage:

```bash
php bin/console mbin:cache:clear [-s|--show] [--dry-run] [chaches]
```

If no `caches` are passed, the default is `redis`.

Options:
- `-s`|`--show`: show everything what is being deleted
- `--dry-run`: don't delete anything

### Messengers-Failed-Remove-All

> [!NOTE]
Expand Down
131 changes: 131 additions & 0 deletions src/Command/ClearCachesCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php
declare(strict_types=1);

namespace App\Command;

use League\Flysystem\Filesystem;
use League\Flysystem\StorageAttributes;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

#[AsCommand(
name: 'mbin:cache:clear',
description: 'Clear a selection of caches.',
)]
class ClearCachesCommand extends Command
{
private const array SUPPORTED_CACHES = [
'redis',
'images',
];

private const string REDIS_SESSION_PREFIX = 'sf_s';

private bool $verbose = false;
private bool $dryRun = false;
private SymfonyStyle $io;

public function __construct(
private readonly Filesystem $files,
private readonly \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis,
) {
parent::__construct();
}

protected function configure(): void
{
$this
->setDescription('Clear a selection of caches.')
->addOption('dry-run', null, InputOption::VALUE_NONE, 'Dry run, don\'t delete anything.')
->addOption('show', 's', InputOption::VALUE_NONE, 'Show everything what is being deleted.')
->addArgument('caches', InputOption::VALUE_OPTIONAL, 'Caches to clear, separated by space. Allowed values are: '.implode(', ', self::SUPPORTED_CACHES), ['redis']);
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->io = new SymfonyStyle($input, $output);
$this->verbose = $input->getOption('show');
$this->dryRun = $input->getOption('dry-run');

$toClear = $input->getArgument('caches');
if ($diff = array_diff($toClear, self::SUPPORTED_CACHES)) {
$this->io->error('The following cache types are not supported: '.implode(' ', $diff));
return 1;
}
if ([] === $toClear) {
$toClear = ['redis'];
}

$success = true;
foreach ($toClear as $cache) {
switch ($cache) {
case 'redis':
$success &= $this->clearRedis();
break;
case 'images':
$success &= $this->clearImages();
break;
}
}

if ($success) {
$this->io->success('All requested caches have been cleared.');
return 0;
} else {
$this->io->error('Not all requested caches could be cleared.');
return 2;
}
}

private function clearRedis(): bool
{
$this->io->section('Clearing Redis');

try {
$keys = $this->redis->keys('*');
$keys = array_filter($keys, fn ($key) => !str_starts_with($key, self::REDIS_SESSION_PREFIX));

if ($this->verbose) {
$this->io->text('Keys to delete: '.implode(', ', $keys));
}
if (!$this->dryRun) {
$this->redis->del($keys);
}
} catch (\Throwable $e) {
$this->io->error(\get_class($e).': '.$e->getMessage());
return false;
}

return true;
}

private function clearImages(): bool
{
$this->io->section('Clearing images');

try {
$cacheFiles = $this->files->listContents('cache', true);
foreach ($cacheFiles as $file) {
/** @var StorageAttributes $file */
if (!$file->isFile()) {
continue;
}
if ($this->verbose) {
$this->io->text('Deleting '.$file->path());
}
if (!$this->dryRun) {
$this->files->delete($file->path());
}
}
} catch (\Throwable $e) {
$this->io->error(\get_class($e).': '.$e->getMessage());
return false;
}

return true;
}
}
Loading