php-kafka/php-avro-schema-generator

View on GitHub
src/Converter/PhpClassConverter.php

Summary

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

declare(strict_types=1);

namespace PhpKafka\PhpAvroSchemaGenerator\Converter;

use PhpKafka\PhpAvroSchemaGenerator\Avro\Avro;
use PhpKafka\PhpAvroSchemaGenerator\Parser\ClassParserInterface;
use PhpKafka\PhpAvroSchemaGenerator\PhpClass\PhpClass;
use PhpKafka\PhpAvroSchemaGenerator\PhpClass\PhpClassInterface;
use PhpKafka\PhpAvroSchemaGenerator\PhpClass\PhpClassProperty;
use PhpKafka\PhpAvroSchemaGenerator\PhpClass\PhpClassPropertyInterface;

final class PhpClassConverter implements PhpClassConverterInterface
{
    private ClassParserInterface $parser;

    /**
     * @var array<string,int>
     */
    private array $singleTypesToSkip = [
        'null' => 1,
        'object' => 1,
        'callable' => 1,
        'resource' => 1,
        'mixed' => 1
    ];

    /**
     * @var array<string,int>
     */
    private array $unionTypesToSkip = [
        'object' => 1,
        'callable' => 1,
        'resource' => 1,
        'mixed' => 1
    ];

    /**
     * @param ClassParserInterface $parser
     */
    public function __construct(ClassParserInterface $parser)
    {
        $this->parser = $parser;
    }


    public function convert(string $phpClass): ?PhpClassInterface
    {
        $this->parser->setCode($phpClass);

        if (null === $this->parser->getClassName()) {
            return null;
        }

        $convertedProperties = $this->getConvertedProperties($this->parser->getProperties());

        return new PhpClass(
            $this->parser->getClassName(),
            $this->parser->getNamespace(),
            $phpClass,
            $convertedProperties
        );
    }

    /**
     * @param PhpClassPropertyInterface[] $properties
     * @return PhpClassPropertyInterface[]
     */
    private function getConvertedProperties(array $properties): array
    {
        $convertedProperties = [];
        foreach ($properties as $property) {
            if (false === is_string($property->getPropertyType())) {
                continue;
            }

            $convertedType = $this->getConvertedType($property->getPropertyType());

            if (null === $convertedType) {
                continue;
            }

            $convertedProperties[] = new PhpClassProperty(
                $property->getPropertyName(),
                $convertedType,
                $property->getPropertyDefault(),
                $property->getPropertyDoc(),
                $property->getPropertyLogicalType()
            );
        }

        return $convertedProperties;
    }

    /**
     * @param string $type
     * @return string|string[]|null
     */
    private function getConvertedType(string $type)
    {
        $types = explode('|', $type);

        if (1 === count($types)) {
            return $this->getFullTypeName($type);
        }

        return $this->getConvertedUnionType($types);
    }

    private function getFullTypeName(string $type, bool $isUnionType = false): ?string
    {

        if (true === isset(Avro::MAPPED_TYPES[$type])) {
            $type = Avro::MAPPED_TYPES[$type];
        }

        if (
            (false === $isUnionType && true === isset($this->singleTypesToSkip[$type]))
            || (true === $isUnionType && true === isset($this->unionTypesToSkip[$type]))
        ) {
            return null;
        }

        if (true === isset(Avro::BASIC_TYPES[$type])) {
            return $type;
        }

        $usedClasses = $this->parser->getUsedClasses();

        if (true === isset($usedClasses[$type])) {
            return $this->convertNamespace($usedClasses[$type]);
        }

        if (null !== $this->parser->getNamespace()) {
            return $this->convertNamespace($this->parser->getNamespace() . '\\' . $type);
        }

        return $type;
    }

    /**
     * @param string[] $types
     * @return array<int,mixed>
     */
    private function getConvertedUnionType(array $types): array
    {
        $convertedUnionType = [];

        foreach ($types as $type) {
            if (false === $this->isArrayType($type) && null !== $formattedType = $this->getFullTypeName($type, true)) {
                $convertedUnionType[] = $formattedType;
            }
        }

        $arrayType = $this->getArrayType($types);

        if (0 !== count($convertedUnionType) && [] !== $arrayType) {
            $convertedUnionType[] = $arrayType;
        } elseif (0 === count($convertedUnionType) && [] !== $arrayType) {
            return $arrayType;
        }

        return $convertedUnionType;
    }

    /**
     * @param string[] $types
     * @return string[]
     */
    private function getArrayType(array $types): array
    {
        $itemPrefix = '[';
        $itemSuffix = ']';

        $arrayTypes = $this->getArrayTypes($types);

        if (0 === count($arrayTypes)) {
            return [];
        }

        $arrayTypes = $this->getCleanedArrayTypes($arrayTypes);

        if (0 === count($arrayTypes)) {
            $arrayTypes[] = 'string';
        }

        if (1 === count($arrayTypes)) {
            $itemPrefix = '';
            $itemSuffix = '';
        }

        return [
            'type' => 'array',
            'items' => $itemPrefix . implode(',', $arrayTypes) . $itemSuffix
        ];
    }

    /**
     * @param string[] $types
     * @return string[]
     */
    private function getArrayTypes(array $types): array
    {
        $arrayTypes = [];

        foreach ($types as $type) {
            if (true === $this->isArrayType($type)) {
                $arrayTypes[] = $type;
            }
        }

        return $arrayTypes;
    }

    /**
     * @param string[] $arrayTypes
     * @return string[]
     */
    private function getCleanedArrayTypes(array $arrayTypes): array
    {
        foreach ($arrayTypes as $idx => $arrayType) {
            if ('array' === $arrayType) {
                unset($arrayTypes[$idx]);
                continue;
            }

            $cleanedType = str_replace('[]', '', $arrayType);

            if (null === $this->getFullTypeName($cleanedType)) {
                unset($arrayTypes[$idx]);
                continue;
            }

            $arrayTypes[$idx] = $this->getFullTypeName($cleanedType);
        }

        return $arrayTypes;
    }

    private function isArrayType(string $type): bool
    {
        if ('array' === $type || str_contains($type, '[]')) {
            return true;
        }

        return false;
    }

    private function convertNamespace(string $namespace): string
    {
        return str_replace('\\', '.', $namespace);
    }
}