Skip to content

Commit

Permalink
Add option to ignore fatal erorrs in scanned services
Browse files Browse the repository at this point in the history
* add caching option
* refactor
* update readme
  • Loading branch information
jfilla committed Mar 31, 2020
1 parent cd4dfce commit dbab10a
Show file tree
Hide file tree
Showing 16 changed files with 432 additions and 56 deletions.
27 changes: 27 additions & 0 deletions README.md
Expand Up @@ -58,3 +58,30 @@ will generate from [class](tests/DIServiceAnnotationTests/Services/Nested/Exampl
- `tags: string[]` – list of tags to be used with the service in generated config

For configuration options see [Configuration properties](src/DIServiceAnnotation/Configuration.php#L7).

### Configuration option

#### Required

* `sourceDirectory: string` – location of services
* `outputFile: string` – output file for registered services

#### Optional

* `setMask: string` – mask for file locator - default `*.php`
* `setFileMapping: array` – map for splitting configs by namespace

```php
$configuration->setFileMapping([
'RootNamespace\Namespace1' => 'config1.neon',
'RootNamespace\Namespace2' => 'config2.neon',
]);
```
* `setInjectGenerator: Inject` – set custom generator for injects
* `setFactoryGenerator: Factory` – set custom generator for factories
* `setComponentFactory: Component` – set custom generator for components
* `setRegenerate: bool` – regenerate all generated code each run - default `false`
* `enableFileValidation` – check each file for fatal errors before reading annotation, skip file on error
* `autoloadFile: string` – file for class autoloading, e.g. `vendor/autoload.php`
* `tempDir: string` – enable cache, directory for cache file, only changed files are validated

6 changes: 6 additions & 0 deletions cli/validate-class.php
@@ -0,0 +1,6 @@
<?php declare(strict_types = 1);

use Wavevision\DIServiceAnnotation\ClassValidator;

require_once $argv[1];
exit((new ClassValidator())->validate($argv[2]));
69 changes: 69 additions & 0 deletions src/DIServiceAnnotation/Cache.php
@@ -0,0 +1,69 @@
<?php declare(strict_types = 1);

namespace Wavevision\DIServiceAnnotation;

use Nette\SmartObject;
use Nette\Utils\FileSystem;
use Wavevision\Utils\Path;

class Cache
{

public const FILE = '.di-service-annotation.cache';

use SmartObject;

private bool $enabled;

/**
* @var array<mixed>
*/
private array $cache;

private string $cacheFile;

public function __construct(Configuration $configuration)
{
$tempDir = $configuration->getTempDir();
$this->enabled = $tempDir !== null;
if ($this->enabled) {
$this->cacheFile = Path::join($tempDir, self::FILE);
if (is_file($this->cacheFile)) {
$this->cache = unserialize(FileSystem::read($this->cacheFile));
} else {
$this->cache = [];
}
}
}

/**
* @return mixed
*/
public function get(string $key)
{
if ($this->enabled) {
if (isset($this->cache[$key])) {
return $this->cache[$key];
}
}
return null;
}

/**
* @param array<mixed> $value
*/
public function set(string $key, array $value): void
{
if ($this->enabled) {
$this->cache[$key] = $value;
}
}

public function flush(): void
{
if ($this->enabled) {
FileSystem::write($this->cacheFile, serialize($this->cache));
}
}

}
19 changes: 19 additions & 0 deletions src/DIServiceAnnotation/ClassValidator.php
@@ -0,0 +1,19 @@
<?php declare(strict_types = 1);

namespace Wavevision\DIServiceAnnotation;

use Nette\SmartObject;
use ReflectionClass;

class ClassValidator
{

use SmartObject;

public function validate(string $class): int
{
new ReflectionClass($class);
return 0;
}

}
86 changes: 82 additions & 4 deletions src/DIServiceAnnotation/Configuration.php
Expand Up @@ -50,16 +50,28 @@ final class Configuration

private Component $componentFactory;

public function __construct(string $sourceDirectory, string $outputFile, bool $regenerate = false)
private ?string $autoloadFile;

private Output $output;

private ?string $tempDir;

private bool $regenerate;

public function __construct(string $sourceDirectory, string $outputFile)
{
$this->sourceDirectory = $sourceDirectory;
$this->outputFile = $outputFile;
$this->mask = '*.php';
$this->fileMapping = [];
$templates = Path::create(__DIR__, 'Generators', 'templates');
$this->injectGenerator = new DefaultInject('Inject%s', $templates->string('inject.txt'), $regenerate);
$this->factoryGenerator = new DefaultFactory('%sFactory', $templates->string('factory.txt'), $regenerate);
$this->componentFactory = new DefaultComponent('%sComponent', $templates->string('component.txt'), $regenerate);
$this->autoloadFile = null;
$this->output = new ConsoleOutput();
$this->tempDir = null;
$this->regenerate = false;
$this->injectGenerator = new DefaultInject($this, 'Inject%s', $templates->string('inject.txt'));
$this->factoryGenerator = new DefaultFactory($this, '%sFactory', $templates->string('factory.txt'));
$this->componentFactory = new DefaultComponent($this, '%sComponent', $templates->string('component.txt'));
}

public function getSourceDirectory(): string
Expand Down Expand Up @@ -154,4 +166,70 @@ public function setComponentFactory(Component $componentFactory)
return $this;
}

public function getAutoloadFile(): ?string
{
return $this->autoloadFile;
}

/**
* @return static
*/
public function enableFileValidation(string $autoloadFile, ?string $tempDir = null)
{
$this->setAutoloadFile($autoloadFile);
$this->setTempDir($tempDir);
return $this;
}

public function getOutput(): Output
{
return $this->output;
}

/**
* @return static
*/
public function setOutput(Output $output)
{
$this->output = $output;
return $this;
}

public function getTempDir(): ?string
{
return $this->tempDir;
}

public function getRegenerate(): bool
{
return $this->regenerate;
}

/**
* @return static
*/
public function setRegenerate(bool $regenerate)
{
$this->regenerate = $regenerate;
return $this;
}

/**
* @return static
*/
public function setAutoloadFile(?string $autoloadFile)
{
$this->autoloadFile = $autoloadFile;
return $this;
}

/**
* @return static
*/
public function setTempDir(?string $tempDir)
{
$this->tempDir = $tempDir;
return $this;
}

}
17 changes: 17 additions & 0 deletions src/DIServiceAnnotation/ConsoleOutput.php
@@ -0,0 +1,17 @@
<?php declare(strict_types = 1);

namespace Wavevision\DIServiceAnnotation;

use Nette\SmartObject;

class ConsoleOutput implements Output
{

use SmartObject;

public function writeln(string $string): void
{
echo $string . "\n";
}

}
2 changes: 2 additions & 0 deletions src/DIServiceAnnotation/DIService.php
Expand Up @@ -2,6 +2,8 @@

namespace Wavevision\DIServiceAnnotation;

//phpcs:disable SlevomatCodingStandard.Variables.UnusedVariable.UnusedVariable

/**
* @Annotation
* @Target("CLASS")
Expand Down
14 changes: 12 additions & 2 deletions src/DIServiceAnnotation/ExtractServices.php
Expand Up @@ -94,20 +94,30 @@ private function saveFile(array $services, string $outputFile): void
*/
private function findServices(): array
{
$fileValidator = new FileValidator($this->configuration);
$services = [];
/** @var SplFileInfo $file */
foreach (Finder::findFiles($this->configuration->getMask())->from(
$this->configuration->getSourceDirectory()
) as $file) {
$tokenizerResult = $this->tokenizer->getStructureNameFromFile($file->getPathname(), [T_CLASS, T_INTERFACE]);
$pathname = $file->getPathname();
$tokenizerResult = $this->tokenizer->getStructureNameFromFile($pathname, [T_CLASS, T_INTERFACE]);
if ($tokenizerResult !== null) {
$className = $tokenizerResult->getFullyQualifiedName();
if ($fileValidator->containsErrors($pathname, $className)) {
continue;
}
$serviceAnnotation = $this->getAnnotation($className);
if ($serviceAnnotation !== null) {
$services[$className] = new Service($serviceAnnotation, $tokenizerResult, $file);
$service = new Service($serviceAnnotation, $tokenizerResult, $file);
$this->configuration->getOutput()->writeln(
"Processing annotation in class: $className"
);
$services[$className] = $service;
}
}
}
$fileValidator->flushCache();
return $services;
}

Expand Down
69 changes: 69 additions & 0 deletions src/DIServiceAnnotation/FileValidator.php
@@ -0,0 +1,69 @@
<?php declare(strict_types = 1);

namespace Wavevision\DIServiceAnnotation;

use Nette\SmartObject;
use Wavevision\Utils\ExternalProgram\Executor;
use Wavevision\Utils\Path;

class FileValidator
{

use SmartObject;

private Configuration $configuration;

private Cache $cache;

public function __construct(Configuration $configuration)
{
$this->cache = new Cache($configuration);
$this->configuration = $configuration;
}

public function containsErrors(string $pathname, string $className): bool
{
if ($autoload = $this->configuration->getAutoloadFile()) {
$hash = md5_file($pathname);
$cacheItem = $this->cache->get($pathname);
if ($cacheItem !== null && $cacheItem['hash'] === $hash) {
if ($cacheItem['error']) {
$this->configuration->getOutput()->writeln(
"Loading from cache: Fatal error in class $className"
);
return true;
}
} else {
$classValidator = Path::join(__DIR__, '..', '..', 'cli', 'validate-class.php');
$result = Executor::executeUnchecked("php '$classValidator' '$autoload' '$className'");
if ($result->getReturnValue() !== 0) {
$this->configuration->getOutput()->writeln(
"Fatal error in class $className\n" . $result->getOutputAsString()
);
$this->cache->set(
$pathname,
[
'hash' => $hash,
'error' => true,
]
);
return true;
}
}
$this->cache->set(
$pathname,
[
'hash' => $hash,
'error' => false,
]
);
}
return false;
}

public function flushCache(): void
{
$this->cache->flush();
}

}

0 comments on commit dbab10a

Please sign in to comment.