Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: cache flush safety & cache flush command #118

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
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: 4 additions & 2 deletions bin/peck
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

declare(strict_types=1);

use Peck\Console\Commands\Cache\ClearCommand;
use Peck\Console\Commands\CheckCommand;
use Symfony\Component\Console\Application;

Expand All @@ -22,9 +23,10 @@ $application = new Application(
'0.1.1',
);

$application->add(
$application->addCommands([
new CheckCommand,
);
new ClearCommand
]);

$application->setDefaultCommand('check');

Expand Down
60 changes: 60 additions & 0 deletions src/Console/Commands/Cache/ClearCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace Peck\Console\Commands\Cache;

use Exception;
use Peck\Plugins\Cache;
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;

/**
* @internal
*/
#[AsCommand(name: 'cache:clear', description: 'Clears cached data.')]
final class ClearCommand extends Command
{
/**
* Configures the command.
*/
protected function configure(): void
{
$this->addOption('directory', 'd', InputOption::VALUE_OPTIONAL, 'Cache directory');
$this->addOption('prefix', 'p', InputOption::VALUE_OPTIONAL, 'Cache file prefix');
}

/**
* Executes the command.
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln('<info>Clearing cache...</info>');

$directory = $input->getOption('directory');
$prefix = $input->getOption('prefix');

$prefix = match (is_string($prefix)) {
true => trim($prefix),
default => Cache::CACHE_PREFIX,
};

if (is_string($directory) && ! is_dir($directory)) {
$output->writeln('<error>The specified cache directory does not exist.</error>');

return Command::FAILURE;
}

match (is_string($directory)) {
true => Cache::create($directory, $prefix)->flush(),
default => Cache::default()->flush(),
};

$output->writeln('<info>Cache successfully cleared!</info>');

return Command::SUCCESS;
}
}
20 changes: 16 additions & 4 deletions src/Plugins/Cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@

final readonly class Cache
{
/**
* The cache prefix.
*/
public const string CACHE_PREFIX = 'peck_';

/**
* The version of the cache.
*/
Expand All @@ -18,6 +23,7 @@
*/
public function __construct(
private string $cacheDirectory,
private string $cachePrefix = self::CACHE_PREFIX,
) {
//
}
Expand All @@ -27,9 +33,15 @@ public function __construct(
*/
public static function default(): self
{
$basePath = __DIR__.'/../../';
return self::create(__DIR__.'/../../.peck.cache');
}

$cache = new self("{$basePath}/.peck.cache");
/**
* Creates a new instance of Cache.
*/
public static function create(string $cacheDirectory, string $cachePrefix = self::CACHE_PREFIX): self
{
$cache = new self($cacheDirectory, $cachePrefix);

$cache->get('__internal_version') === self::VERSION ?: $cache->flush();

Expand Down Expand Up @@ -99,15 +111,15 @@ public function getCacheFile(string $key): string
*/
public function getCacheKey(string $key): string
{
return md5($key);
return $this->cachePrefix.md5($key);
}

/**
* Flushes the cache.
*/
public function flush(): void
{
if (is_array($files = glob("{$this->cacheDirectory}/*"))) {
if (is_array($files = glob("{$this->cacheDirectory}/{$this->cachePrefix}*"))) {
foreach ($files as $file) {
@unlink($file);
}
Expand Down
2 changes: 1 addition & 1 deletion tests/.pest/snapshots/Console/OutputTest/it_may_pass.snap
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
........................................
...........................................

PASS No misspellings found in your project.

Expand Down
150 changes: 150 additions & 0 deletions tests/Integration/Cache/ClearCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
<?php

declare(strict_types=1);

use Peck\Console\Commands\Cache\ClearCommand;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;

it('clears default cache directory', function (): void {
$application = new Application;

$application->add(new ClearCommand);

$command = $application->find('cache:clear');

$commandTester = new CommandTester($command);

$commandTester->execute([]);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit unsure about this, as this "interacts with"/changes the files outside the tests directory.
But I guess it is the only way to test this.


$output = $commandTester->getDisplay();

expect(trim($output))->toContain('Clearing cache...');
expect(trim($output))->toContain('Cache successfully cleared!');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should also check if the files where deleted? Or do we leave that to the unit test of the Cache class?

});

it('clears custom cache directory', function (): void {

$application = new Application;

$application->add(new ClearCommand);

$command = $application->find('cache:clear');

$commandTester = new CommandTester($command);

if (! is_dir('/tmp/.peck.cache')) {
@mkdir('/tmp/.peck.cache');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't you check if this directory was created?

If it was not created there is a permission problem. I am unsure how the test behaves in that scenario, when we ignore the errors.

}

$commandTester->execute([
'--directory' => '/tmp/.peck.cache',
]);

$output = $commandTester->getDisplay();

expect(trim($output))->toContain('Clearing cache...');
expect(trim($output))->toContain('Cache successfully cleared!');

@unlink('/tmp/.peck.cache');
});

it('throws an exception when the specified directory does not exist', function (): void {
$application = new Application;

$application->add(new ClearCommand);

$command = $application->find('cache:clear');

$commandTester = new CommandTester($command);

$commandTester->execute([
'--directory' => '/tmp/peck.cache',
]);

$output = $commandTester->getDisplay();

expect(trim($output))->toContain('The specified cache directory does not exist.');
});

it('only deletes cached files from custom cache directory', function (): void {
$application = new Application;

$application->add(new ClearCommand);

$command = $application->find('cache:clear');

$commandTester = new CommandTester($command);

if (! is_dir('/tmp/peck_custom')) {
@mkdir('/tmp/peck_custom');
}

file_put_contents('/tmp/peck_custom/peck_1', 'test');
file_put_contents('/tmp/peck_custom/peck_2', 'test');
file_put_contents('/tmp/peck_custom/not_a_cached_file', 'test');

$commandTester->execute([
'--directory' => '/tmp/peck_custom',
]);

expect(count(glob('/tmp/peck_custom/*')))->toBe(1);

array_map('unlink', array_filter((array) glob('/tmp/peck_custom/*')));
});

it('deletes all files from cache directory based on custom prefix', function (): void {
$application = new Application;

$application->add(new ClearCommand);

$command = $application->find('cache:clear');

$commandTester = new CommandTester($command);

if (! is_dir('/tmp/peck_custom')) {
@mkdir('/tmp/peck_custom');
}

file_put_contents('/tmp/peck_custom/peck_1', 'test');
file_put_contents('/tmp/peck_custom/peck_2', 'test');
file_put_contents('/tmp/peck_custom/pecker_1', 'test');
file_put_contents('/tmp/peck_custom/pecker_2', 'test');

$commandTester->execute([
'--directory' => '/tmp/peck_custom',
'--prefix' => 'pecker_',
]);

expect(count(glob('/tmp/peck_custom/*')))->toBe(2);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would also pass if the --prefix was ignored and the default was used (as the number of files for both prefixes are the same). If possible also assert the file names.


array_map('unlink', array_filter((array) glob('/tmp/peck_custom/*')));
});

it('deletes all cache if prefix is empty', function (): void {
$application = new Application;

$application->add(new ClearCommand);

$command = $application->find('cache:clear');

$commandTester = new CommandTester($command);

if (! is_dir('/tmp/peck_custom')) {
@mkdir('/tmp/peck_custom');
}

file_put_contents('/tmp/peck_custom/peck_1', 'test');
file_put_contents('/tmp/peck_custom/peck_2', 'test');
file_put_contents('/tmp/peck_custom/pecker_1', 'test');
file_put_contents('/tmp/peck_custom/pecker_2', 'test');

$commandTester->execute([
'--directory' => '/tmp/peck_custom',
'--prefix' => '',
]);

expect(glob('/tmp/peck_custom/*'))->toBeEmpty();

array_map('unlink', array_filter((array) glob('/tmp/peck_custom/*')));
});
Loading