From 40b3fb11283797c1afc6010efbc123b2aed2614e Mon Sep 17 00:00:00 2001 From: Denis Zunke Date: Wed, 17 Apr 2024 20:56:11 +0200 Subject: [PATCH] Implement central file provider --- panaly | 5 +- panaly.dist.yaml | 9 +++ src/Configuration/ConfigurationFileLoader.php | 12 +-- src/Configuration/RuntimeConfiguration.php | 8 ++ src/Provider/FileProvider.php | 80 +++++++++++++++++++ .../FileProvider/InvalidFileAccess.php | 30 +++++++ tests/Collector/CollectorTest.php | 26 ++++-- .../ConfigurationFileLoaderTest.php | 25 ++++-- tests/Double/MemoryFileProvider.php | 50 ++++++++++++ tests/Provider/FileProviderTest.php | 61 ++++++++++++++ 10 files changed, 283 insertions(+), 23 deletions(-) create mode 100644 src/Provider/FileProvider.php create mode 100644 src/Provider/FileProvider/InvalidFileAccess.php create mode 100644 tests/Double/MemoryFileProvider.php create mode 100644 tests/Provider/FileProviderTest.php diff --git a/panaly b/panaly index e214504..1294bcc 100755 --- a/panaly +++ b/panaly @@ -34,7 +34,10 @@ include $_composer_autoload_path ?? __DIR__ . '/vendor/autoload.php'; $io->title('Project Analyzer'); $runtimeConfiguration = new Configuration\RuntimeConfiguration(); - $configurationFile = (new Configuration\ConfigurationFileLoader())->loadFromFile($input->getOption('config')); + $configurationFile = (new Configuration\ConfigurationFileLoader())->loadFromFile( + $runtimeConfiguration->getFileProvider(), + $input->getOption('config') + ); // The configuration file is parsed, so allow to change the configuration when there is any need for it, before the plugins are loaded $runtimeConfiguration->getEventDispatcher()->dispatch($event = new ConfigurationLoaded($configurationFile)); diff --git a/panaly.dist.yaml b/panaly.dist.yaml index fd22859..8f6fb08 100644 --- a/panaly.dist.yaml +++ b/panaly.dist.yaml @@ -25,6 +25,15 @@ groups: - tests names: - "*.php" + largest_php_files: + title: Largest PHP Files + metric: largest_files + amount: 5 + paths: + - src + - tests + names: + - "*.php" storage: json-timeline-storage: diff --git a/src/Configuration/ConfigurationFileLoader.php b/src/Configuration/ConfigurationFileLoader.php index fe9b876..7bfccf7 100644 --- a/src/Configuration/ConfigurationFileLoader.php +++ b/src/Configuration/ConfigurationFileLoader.php @@ -5,22 +5,16 @@ namespace Panaly\Configuration; use Panaly\Configuration\Exception\InvalidConfigurationFile; +use Panaly\Provider\FileProvider; use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Yaml; -use function is_file; -use function is_readable; - class ConfigurationFileLoader { - public function loadFromFile(string $filePath): ConfigurationFile + public function loadFromFile(FileProvider $fileProvider, string $filePath): ConfigurationFile { - if (! is_file($filePath) || ! is_readable($filePath)) { - throw InvalidConfigurationFile::fileNotFound($filePath); - } - try { - $fileContent = Yaml::parseFile($filePath); + $fileContent = Yaml::parse($fileProvider->read($filePath)); } catch (ParseException $e) { throw InvalidConfigurationFile::fileContentNotValidYaml($filePath, $e); } diff --git a/src/Configuration/RuntimeConfiguration.php b/src/Configuration/RuntimeConfiguration.php index bcb3395..55b26f7 100644 --- a/src/Configuration/RuntimeConfiguration.php +++ b/src/Configuration/RuntimeConfiguration.php @@ -9,6 +9,7 @@ use Panaly\Plugin\Plugin\Metric; use Panaly\Plugin\Plugin\Reporting; use Panaly\Plugin\Plugin\Storage; +use Panaly\Provider\FileProvider; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -17,6 +18,7 @@ class RuntimeConfiguration { private EventDispatcherInterface $eventDispatcher; + private FileProvider $fileProvider; /** @var list */ private array $loadedPlugins = []; @@ -30,6 +32,7 @@ class RuntimeConfiguration public function __construct() { $this->eventDispatcher = new EventDispatcher(); + $this->fileProvider = new FileProvider(); } public function getEventDispatcher(): EventDispatcherInterface @@ -37,6 +40,11 @@ public function getEventDispatcher(): EventDispatcherInterface return $this->eventDispatcher; } + public function getFileProvider(): FileProvider + { + return $this->fileProvider; + } + public function addPlugin(Plugin $plugin): void { $this->loadedPlugins[] = $plugin; diff --git a/src/Provider/FileProvider.php b/src/Provider/FileProvider.php new file mode 100644 index 0000000..a27c294 --- /dev/null +++ b/src/Provider/FileProvider.php @@ -0,0 +1,80 @@ + */ + protected static array $files = []; + + public function read(string $path): string + { + if (isset(self::$files[$path])) { + return self::$files[$path]; + } + + if ($this->isDirectory($path)) { + throw InvalidFileAccess::fileIsADirectory($path); + } + + if (! $this->isFile($path)) { + throw InvalidFileAccess::fileNotAccessible($path); + } + + $fileContent = @file_get_contents($path); + if ($fileContent === false) { + throw InvalidFileAccess::fileNotReadable($path); + } + + return self::$files[$path] = $fileContent; + } + + public function write(string $path, string $content): void + { + self::$files[$path] = $content; + + file_put_contents($path, $content); + } + + public function remove(string $path): void + { + if (! isset(self::$files[$path])) { + throw InvalidFileAccess::onlyProvidedFilesAreRemovable($path); + } + + unset(self::$files[$path]); + @unlink($path); + } + + public function isFile(string $path): bool + { + return file_exists($path) && is_readable($path); + } + + public function isDirectory(string $path): bool + { + return is_dir($path) && is_readable($path); + } +} diff --git a/src/Provider/FileProvider/InvalidFileAccess.php b/src/Provider/FileProvider/InvalidFileAccess.php new file mode 100644 index 0000000..e0af13b --- /dev/null +++ b/src/Provider/FileProvider/InvalidFileAccess.php @@ -0,0 +1,30 @@ +reset(); + } + public function testCollectingWithoutAnyMetricsGiveEmptyResult(): void { - $collectionResult = $this->getResultFromConfigFile( - __DIR__ . '/../Fixtures/valid-config-without-metrics.yaml', - ); + $fixtureFile = __DIR__ . '/../Fixtures/valid-config-without-metrics.yaml'; + $memoryFileProvider = new MemoryFileProvider(); + $memoryFileProvider->addFixture($fixtureFile); + + $collectionResult = $this->getResultFromConfigFile($memoryFileProvider, $fixtureFile); self::assertCount(0, $collectionResult->getGroups()); } public function testCollectingMetricsWithResults(): void { - $collectionResult = $this->getResultFromConfigFile( - __DIR__ . '/../Fixtures/valid-config.yaml', - ); + $fixtureFile = __DIR__ . '/../Fixtures/valid-config.yaml'; + $memoryFileProvider = new MemoryFileProvider(); + $memoryFileProvider->addFixture($fixtureFile); + + $collectionResult = $this->getResultFromConfigFile($memoryFileProvider, $fixtureFile); $groups = $collectionResult->getGroups(); self::assertCount(1, $groups); @@ -42,10 +52,10 @@ public function testCollectingMetricsWithResults(): void self::assertSame(12, $metrics[1]->value->compute()); } - private function getResultFromConfigFile(string $file): Result + private function getResultFromConfigFile(MemoryFileProvider $fileProvider, string $file): Result { $runtimeConfiguration = new RuntimeConfiguration(); - $configurationFile = (new ConfigurationFileLoader())->loadFromFile($file); + $configurationFile = (new ConfigurationFileLoader())->loadFromFile($fileProvider, $file); (new PluginLoader())->load($configurationFile, $runtimeConfiguration); return (new Collector($configurationFile, $runtimeConfiguration))->collect(); diff --git a/tests/Configuration/ConfigurationFileLoaderTest.php b/tests/Configuration/ConfigurationFileLoaderTest.php index 2846dd7..6ffb5ba 100644 --- a/tests/Configuration/ConfigurationFileLoaderTest.php +++ b/tests/Configuration/ConfigurationFileLoaderTest.php @@ -6,35 +6,50 @@ use Panaly\Configuration\ConfigurationFileLoader; use Panaly\Configuration\Exception\InvalidConfigurationFile; +use Panaly\Provider\FileProvider; +use Panaly\Test\Double\MemoryFileProvider; use Panaly\Test\Fixtures\Plugin\TestPlugin; use PHPUnit\Framework\TestCase; class ConfigurationFileLoaderTest extends TestCase { + protected function tearDown(): void + { + (new MemoryFileProvider())->reset(); + } + public function testThatLoadingANonExistingFileWillNotWork(): void { - $this->expectException(InvalidConfigurationFile::class); - $this->expectExceptionMessage('The config file "not-existing-yet.yml" is not readable or does not exists!'); + $this->expectException(FileProvider\InvalidFileAccess::class); + $this->expectExceptionMessage('The provided file "not-existing-yet.yml" is not accessible or does not exist.'); $loader = new ConfigurationFileLoader(); - $loader->loadFromFile('not-existing-yet.yml'); + $loader->loadFromFile(new MemoryFileProvider(), 'not-existing-yet.yml'); } public function testThatLoadingAnInvalidConfigurationFileWillThrowAnException(): void { + $memoryFileProvider = new MemoryFileProvider(); + $file = __DIR__ . '/../Fixtures/invalid-config.yaml'; + $memoryFileProvider->addFixture($file); $this->expectException(InvalidConfigurationFile::class); $this->expectExceptionMessage('The configuration file "' . $file . '" does not contain valid Yaml content.'); $loader = new ConfigurationFileLoader(); - $loader->loadFromFile($file); + $loader->loadFromFile($memoryFileProvider, $file); } public function testThatLoadingAValidConfigurationFileWillWork(): void { + $validConfigFile = __DIR__ . '/../Fixtures/valid-config.yaml'; + + $memoryFileProvider = new MemoryFileProvider(); + $memoryFileProvider->addFixture($validConfigFile); + $loader = new ConfigurationFileLoader(); - $configuration = $loader->loadFromFile(__DIR__ . '/../Fixtures/valid-config.yaml'); + $configuration = $loader->loadFromFile(new MemoryFileProvider(), $validConfigFile); self::assertCount(1, $configuration->plugins); self::assertSame(TestPlugin::class, $configuration->plugins[0]->class); diff --git a/tests/Double/MemoryFileProvider.php b/tests/Double/MemoryFileProvider.php new file mode 100644 index 0000000..83e4df7 --- /dev/null +++ b/tests/Double/MemoryFileProvider.php @@ -0,0 +1,50 @@ +expectException(FileProvider\InvalidFileAccess::class); + $this->expectExceptionMessage(sprintf('The provided file path "%s" is a directory.', __DIR__)); + + (new FileProvider())->read(__DIR__); + } + + public function testThatReadingAFileWillDeliverContent(): void + { + $content = (new FileProvider())->read(__FILE__); + + self::assertStringEqualsFile( + __FILE__, + $content, + 'The provider did not return the content of "' . __FILE__ . '"', + ); + } + + public function testThatRemovingAFileThatWasNotReadIsNotPossible(): void + { + $this->expectException(FileProvider\InvalidFileAccess::class); + $this->expectExceptionMessage( + 'The provided file "foo.txt" was not read by the provider and can so not be removed', + ); + + (new FileProvider())->remove('foo.txt'); + } + + public function testReadWriteRemoveWorkflowIsFullyWorking(): void + { + file_put_contents('foo.txt', 'foo'); // Create temporary file + + self::assertSame('foo', (new FileProvider())->read('foo.txt')); + (new FileProvider())->write('foo.txt', 'bar'); + self::assertSame('bar', (new FileProvider())->read('foo.txt')); + + // Manually remove the file - reader should work? + @unlink('foo.txt'); + self::assertSame('bar', (new FileProvider())->read('foo.txt')); + + (new FileProvider())->remove('foo.txt'); + + self::assertFileDoesNotExist('foo.txt'); + } +}