src/Model/RenderJob.php
<?php
declare(strict_types = 1);
namespace PHPModelGenerator\Model;
use PHPMicroTemplate\Exception\PHPMicroTemplateException;
use PHPMicroTemplate\Render;
use PHPModelGenerator\Exception\FileSystemException;
use PHPModelGenerator\Exception\RenderException;
use PHPModelGenerator\Exception\ValidationException;
use PHPModelGenerator\Model\Validator\AbstractComposedPropertyValidator;
use PHPModelGenerator\SchemaProcessor\Hook\SchemaHookResolver;
use PHPModelGenerator\SchemaProcessor\PostProcessor\PostProcessor;
use PHPModelGenerator\Utils\RenderHelper;
/**
* Class RenderJob
*
* @package PHPModelGenerator\Model
*/
class RenderJob
{
/**
* Create a new class render job
*
* @param string $fileName The file name
* @param string $classPath The relative path of the class for namespace generation
* @param string $className The class name
* @param Schema $schema The Schema object which holds properties and validators
*/
public function __construct(
protected string $fileName,
protected string $classPath,
protected string $className,
protected Schema $schema,
) {}
/**
* @param PostProcessor[] $postProcessors
*/
public function postProcess(array $postProcessors, GeneratorConfiguration $generatorConfiguration): void
{
foreach ($postProcessors as $postProcessor) {
$postProcessor->process($this->schema, $generatorConfiguration);
}
}
/**
* Execute the render job and render the class
*
* @throws FileSystemException
* @throws RenderException
*/
public function render(GeneratorConfiguration $generatorConfiguration): void
{
$this->generateModelDirectory();
$class = $this->renderClass($generatorConfiguration);
if (file_exists($this->fileName)) {
throw new FileSystemException("File {$this->fileName} already exists. Make sure object IDs are unique.");
}
if (!file_put_contents($this->fileName, $class)) {
// @codeCoverageIgnoreStart
throw new FileSystemException("Can't write class $this->classPath\\$this->className.");
// @codeCoverageIgnoreEnd
}
if ($generatorConfiguration->isOutputEnabled()) {
echo sprintf(
"Rendered class %s\n",
join(
'\\',
array_filter([$generatorConfiguration->getNamespacePrefix(), $this->classPath, $this->className]),
)
);
}
}
/**
* Generate the directory structure for saving a generated class
*
* @throws FileSystemException
*/
protected function generateModelDirectory(): void
{
$destination = dirname($this->fileName);
if (!is_dir($destination) && !mkdir($destination, 0777, true)) {
throw new FileSystemException("Can't create path $destination");
}
}
/**
* Render a class. Returns the php code of the class
*
* @throws RenderException
*/
protected function renderClass(GeneratorConfiguration $generatorConfiguration): string
{
$namespace = trim(join('\\', [$generatorConfiguration->getNamespacePrefix(), $this->classPath]), '\\');
try {
$class = (new Render(__DIR__ . '/../Templates/'))->renderTemplate(
'Model.phptpl',
[
'namespace' => $namespace,
'use' => $this->getUseForSchema($generatorConfiguration, $namespace),
'class' => $this->className,
'schema' => $this->schema,
'schemaHookResolver' => new SchemaHookResolver($this->schema),
'generatorConfiguration' => $generatorConfiguration,
'viewHelper' => new RenderHelper($generatorConfiguration),
// one hack a day keeps the problems away. Make true literal available for the templating. Easy fix
'true' => true,
'baseValidatorsWithoutCompositions' => array_filter(
$this->schema->getBaseValidators(),
static fn($validator): bool => !is_a($validator, AbstractComposedPropertyValidator::class),
),
],
);
} catch (PHPMicroTemplateException $exception) {
throw new RenderException("Can't render class $this->classPath\\$this->className", 0, $exception);
}
return $class;
}
/**
* @return string[]
*/
protected function getUseForSchema(GeneratorConfiguration $generatorConfiguration, string $namespace): array
{
$use = array_unique(
array_merge(
$this->schema->getUsedClasses(),
$generatorConfiguration->collectErrors()
? [$generatorConfiguration->getErrorRegistryClass()]
: [ValidationException::class],
)
);
// filter out non-compound uses and uses which link to the current namespace
$use = array_filter($use, static fn($classPath): bool =>
strstr(trim(str_replace("$namespace", '', $classPath), '\\'), '\\') ||
(!strstr($classPath, '\\') && !empty($namespace)),
);
return $use;
}
}