Skip to content

Commit

Permalink
Implement central file provider
Browse files Browse the repository at this point in the history
  • Loading branch information
DZunke committed Apr 17, 2024
1 parent 41761f7 commit 40b3fb1
Show file tree
Hide file tree
Showing 10 changed files with 283 additions and 23 deletions.
5 changes: 4 additions & 1 deletion panaly
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
9 changes: 9 additions & 0 deletions panaly.dist.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
12 changes: 3 additions & 9 deletions src/Configuration/ConfigurationFileLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
8 changes: 8 additions & 0 deletions src/Configuration/RuntimeConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -17,6 +18,7 @@
class RuntimeConfiguration
{
private EventDispatcherInterface $eventDispatcher;
private FileProvider $fileProvider;

/** @var list<Plugin> */
private array $loadedPlugins = [];
Expand All @@ -30,13 +32,19 @@ class RuntimeConfiguration
public function __construct()
{
$this->eventDispatcher = new EventDispatcher();
$this->fileProvider = new FileProvider();
}

public function getEventDispatcher(): EventDispatcherInterface
{
return $this->eventDispatcher;
}

public function getFileProvider(): FileProvider
{
return $this->fileProvider;
}

public function addPlugin(Plugin $plugin): void
{
$this->loadedPlugins[] = $plugin;
Expand Down
80 changes: 80 additions & 0 deletions src/Provider/FileProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

declare(strict_types=1);

namespace Panaly\Provider;

use Panaly\Provider\FileProvider\InvalidFileAccess;

use function file_exists;
use function file_get_contents;
use function file_put_contents;
use function is_dir;
use function is_readable;
use function unlink;

/**
* The class delivers a centralized toolset to work with files. Through the Runtime it will also be available
* to plugins to be utilized - but because of a static storage it can also be instantiated as often as needed.
* A centralized usage can be beneficial to a longer running job with identical files opened by multiple methods.
* In this case every running process would access the filesystem and bring a file to the memory while this could
* be done once.
* It has to be ensured that during the process read and write should be handled with this process because the write
* process will overwrite the cached content which would otherwise be invalid.
* Another advantage of this provider class is that the error handling is centralized and must not be implement again
* and again.
*/
class FileProvider
{
/** @var array<string, string> */
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);
}
}
30 changes: 30 additions & 0 deletions src/Provider/FileProvider/InvalidFileAccess.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Panaly\Provider\FileProvider;

use RuntimeException;

final class InvalidFileAccess extends RuntimeException
{
public static function fileIsADirectory(string $file): InvalidFileAccess
{
return new self('The provided file path "' . $file . '" is a directory.');
}

public static function fileNotAccessible(string $file): InvalidFileAccess
{
return new self('The provided file "' . $file . '" is not accessible or does not exist.');
}

public static function fileNotReadable(string $file): InvalidFileAccess
{
return new self('The provided file "' . $file . '" is not readable or does not exist.');
}

public static function onlyProvidedFilesAreRemovable(string $file): InvalidFileAccess
{
return new self('The provided file "' . $file . '" was not read by the provider and can so not be removed.');
}
}
26 changes: 18 additions & 8 deletions tests/Collector/CollectorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,34 @@
use Panaly\Configuration\PluginLoader;
use Panaly\Configuration\RuntimeConfiguration;
use Panaly\Result\Result;
use Panaly\Test\Double\MemoryFileProvider;
use PHPUnit\Framework\TestCase;

class CollectorTest extends TestCase
{
protected function tearDown(): void
{
(new MemoryFileProvider())->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);
Expand All @@ -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();
Expand Down
25 changes: 20 additions & 5 deletions tests/Configuration/ConfigurationFileLoaderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
50 changes: 50 additions & 0 deletions tests/Double/MemoryFileProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

namespace Panaly\Test\Double;

use Panaly\Provider\FileProvider;

use function array_key_exists;
use function file_exists;
use function file_get_contents;

class MemoryFileProvider extends FileProvider
{
public function write(string $path, string $content): void
{
self::$files[$path] = $content;
}

public function remove(string $path): void
{
unset(self::$files[$path]);
}

public function isFile(string $path): bool
{
return array_key_exists($path, self::$files);
}

public function isDirectory(string $path): bool
{
return false;
}

public function addFixture(string $file, string|null $content = null): void
{
if ($content === null && file_exists($file)) {
self::$files[$file] = (string) file_get_contents($file);

return;
}

self::$files[$file] = $content ?? '';
}

public function reset(): void
{
self::$files = [];
}
}
Loading

0 comments on commit 40b3fb1

Please sign in to comment.