alevikzs/mapper

View on GitHub
src/ClassParser.php

Summary

Maintainability
A
1 hr
Test Coverage
<?php

declare(strict_types = 1);

namespace Mapper;

use \Reflection\ClassUseStatements as ReflectionClass;
use \ReflectionMethod;

/**
 * Class ClassParser
 * @package Mapper
 */
class ClassParser {

    /**
     * @const string
     */
    const PARAM_SETTER_PATTERN = '/@param\s+(.+)/';

    /**
     * @var string
     */
    private $class;

    /**
     * @var ReflectionClass
     */
    private $reflectionClass;

    /**
     * @param string $class
     */
    public function __construct(string $class) {
        $this->setClass($class);
    }

    /**
     * @return string
     */
    public function getClass() {
        return $this->class;
    }

    /**
     * @param string $class
     * @return ClassParser
     */
    public function setClass(string $class): ClassParser {
        $this->class = $class;

        return $this->setReflectionClass(new ReflectionClass($class));
    }

    /**
     * @return ReflectionClass
     */
    protected function getReflectionClass(): ReflectionClass {
        return $this->reflectionClass;
    }

    /**
     * @param ReflectionClass $reflectionClass
     * @return ClassParser
     */
    private function setReflectionClass(ReflectionClass $reflectionClass): ClassParser {
        $this->reflectionClass = $reflectionClass;

        return $this;
    }

    /**
     * @return ClassFields
     */
    public function getClassFields(): ClassFields {
        $methods = $this->getReflectionClass()
            ->getMethods();

        $classFields = new ClassFields();

        foreach ($methods as $method) {
            if ($this->isSetterMethod($method)) {
                $classFields->add($this->createClassField($method));
            }
        }

        return $classFields;
    }

    /**
     * @param ReflectionMethod $method
     * @return bool
     */
    private function isSetterMethod(ReflectionMethod $method): bool {
        return strpos($method->name, 'set') !== false;
    }

    /**
     * @param ReflectionMethod $method
     * @return ClassField
     */
    private function createClassField(ReflectionMethod $method): ClassField {
        $classField = new ClassField(
            $method->name,
            lcfirst(str_replace('set', '', $method->name))
        );

        $this->setupClassFieldFromAnnotation($classField, $method);

        if (empty($classField->getType())) {
            $this->setupClassFieldFromSignature($classField, $method);
        }

        return $classField;
    }

    /**
     * @param ClassField $classField
     * @param ReflectionMethod $method
     * @return ClassParser
     */
    private function setupClassFieldFromAnnotation(ClassField $classField, ReflectionMethod $method): ClassParser {
        preg_match(self::PARAM_SETTER_PATTERN, $method->getDocComment(), $paramTypeAndVariable);

        if (isset($paramTypeAndVariable[1])) {
            $types = preg_split(
                '/\s+/',
                $paramTypeAndVariable[1],
                $limit = 3,
                PREG_SPLIT_DELIM_CAPTURE
            );

            foreach ($types as $type) {
                if ($type[0] !== '$') {
                    if (strpos($type, '[]') !== false) {
                        $classField->setIsSequential();

                        $type = str_replace('[]', '', $type);
                    }

                    $this->setupClassFieldType($classField, $type);

                    break;
                }
            }
        }

        return $this;
    }

    /**
     * @param ClassField $classField
     * @param ReflectionMethod $method
     * @return ClassParser
     */
    private function setupClassFieldFromSignature(ClassField $classField, ReflectionMethod $method): ClassParser {
        $parameters = $method->getParameters();

        if (isset($parameters[0])) {
            $type = (string) $parameters[0]->getType();

            $classField->setType($type);
        }

        return $this;
    }

    /**
     * @param ClassField $classField
     * @param string $type
     * @return ClassParser
     */
    private function setupClassFieldType(ClassField $classField, string $type): ClassParser {
        if ($this->typeIsNotStandard($type)) {
            $fullClassName = $this->getReflectionClass()
                ->getUseStatements()
                ->getFullClassName($type);

            if ($fullClassName) {
                $type = $fullClassName;
            } else {
                $type = "{$this->getReflectionClass()->getNamespaceName()}\\$type";
            }
        }

        $classField->setType($type);

        return $this;
    }

    /**
     * @param string $type
     * @return bool
     */
    private function typeIsNotStandard(string $type): bool {
        return !in_array($type, array('int', 'string', 'float', 'bool', 'array'));
    }

}