wol-soft/php-json-schema-model-generator

View on GitHub
src/Utils/RenderHelper.php

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
<?php

declare(strict_types = 1);

namespace PHPModelGenerator\Utils;

use PHPModelGenerator\Model\GeneratorConfiguration;
use PHPModelGenerator\Model\Property\PropertyInterface;
use PHPModelGenerator\Model\Schema;
use PHPModelGenerator\Model\Validator\ExtractedMethodValidator;
use PHPModelGenerator\Model\Validator\PropertyTemplateValidator;
use PHPModelGenerator\Model\Validator\PropertyValidatorInterface;

/**
 * Class RenderHelper
 *
 * @package PHPModelGenerator\Utils
 */
class RenderHelper
{
    public function __construct(protected GeneratorConfiguration $generatorConfiguration) {}

    public function ucfirst(string $value): string
    {
        return ucfirst($value);
    }

    public function isNull(mixed $value): bool
    {
        return $value === null;
    }

    public function getSimpleClassName(string $fqcn): string
    {
        $parts = explode('\\', $fqcn);

        return end($parts);
    }

    public function joinClassNames(array $fqcns): string
    {
        return join(', ', array_map([$this, 'getSimpleClassName'], $fqcns));
    }

    /**
     * Resolve all associated decorators of a property
     */
    public function resolvePropertyDecorator(PropertyInterface $property, bool $nestedProperty = false): string
    {
        if (!$property->getDecorators()) {
            return '';
        }

        return '$value = ' . $property->resolveDecorator('$value', $nestedProperty) . ';';
    }

    /**
     * Generate code to handle a validation error
     */
    public function validationError(PropertyValidatorInterface $validator): string
    {
        $exceptionConstructor = sprintf(
            'new \%s($value ?? null, ...%s)',
            $validator->getExceptionClass(),
            preg_replace('/\'&(\$\w+)\'/i', '$1', var_export($validator->getExceptionParams(), true)),
        );

        if ($this->generatorConfiguration->collectErrors()) {
            return "\$this->_errorRegistry->addError($exceptionConstructor);";
        }

        return "throw $exceptionConstructor;";
    }

    /**
     * check if the property may contain/accept null
     * - if the property is required the property may never contain null (if it's a null property null is already
     *   contained in the proprety type hints)
     * - if the output type is requested null may be contained (if the property was not set)
     *   if implicitNull is enabled null may be set for the property
     * - except the property contains a default value and implicit null is disabled. in this case null is not
     *   possible
     */
    public function isPropertyNullable(PropertyInterface $property, bool $outputType = false): bool
    {
        return !$property->isRequired()
            && ($outputType || $this->generatorConfiguration->isImplicitNullAllowed())
            && !($property->getDefaultValue() !== null && !$this->generatorConfiguration->isImplicitNullAllowed());
    }

    public function getType(PropertyInterface $property, bool $outputType = false): string
    {
        $type = $property->getType($outputType);

        if (!$type) {
            return '';
        }

        $nullable = ($type->isNullable() ?? $this->isPropertyNullable($property, $outputType)) ? '?' : '';

        return "$nullable{$type->getName()}";
    }

    public function getTypeHintAnnotation(PropertyInterface $property, bool $outputType = false): string
    {
        $typeHint = $property->getTypeHint($outputType);
        $hasDefinedNullability = ($type = $property->getType($outputType)) && $type->isNullable() !== null;

        if ((($hasDefinedNullability && $type->isNullable())
                || (!$hasDefinedNullability && $this->isPropertyNullable($property, $outputType))
            ) && !strstr($typeHint, 'mixed')
        ) {
            $typeHint = "$typeHint|null";
        }

        return implode('|', array_unique(explode('|', $typeHint)));
    }

    public function renderValidator(PropertyValidatorInterface $validator, Schema $schema): string
    {
        // scoping of the validator might be required as validators from a composition might be transferred to a
        // different schema
        if ($validator instanceof PropertyTemplateValidator) {
            $validator->setScope($schema);
        }

        if (!$validator instanceof ExtractedMethodValidator) {
            return "
{$validator->getValidatorSetUp()}
if ({$validator->getCheck()}) {
    {$this->validationError($validator)}
}
";
        }

        if (!$schema->hasMethod($validator->getExtractedMethodName())) {
            $schema->addMethod($validator->getExtractedMethodName(), $validator->getMethod());
        }

        return "\$this->{$validator->getExtractedMethodName()}(\$value, \$modelData);";
    }

    public function renderMethods(Schema $schema): string
    {
        $renderedMethods = '';

        // don't change to a foreach loop as the render process of a method might add additional methods
        for ($i = 0; $i < count($schema->getMethods()); $i++) {
            $renderedMethods .= $schema->getMethods()[array_keys($schema->getMethods())[$i]]->getCode() . "\n\n";
        }

        return $renderedMethods;
    }

    public function isMutableBaseValidator(GeneratorConfiguration $generatorConfiguration, bool $isBaseValidator): bool
    {
        return !$generatorConfiguration->isImmutable() && $isBaseValidator;
    }

    public static function varExportArray(array $values): string
    {
        return preg_replace('(\d+\s=>)', '', var_export($values, true));
    }
}