samsonframework/generator

View on GitHub
src/ClassGenerator.php

Summary

Maintainability
A
2 hrs
Test Coverage
<?php declare(strict_types=1);
/**
 * Created by Vitaly Iegorov <egorov@samsonos.com>.
 * on 03.09.16 at 09:58
 */
namespace samsonframework\generator;

use samsonframework\generator\exception\ClassNameNotFoundException;

/**
 * Class generator class.
 *
 * @author Vitaly Egorov <egorov@samsonos.com>
 */
class ClassGenerator extends AbstractGenerator
{
    use AbstractFinalTrait;

    /** OOP public visibility */
    const VISIBILITY_PUBLIC = 'public';

    /** OOP protected visibility */
    const VISIBILITY_PROTECTED = 'protected';

    /** OOP private visibility */
    const VISIBILITY_PRIVATE = 'private';

    /** @var string Class name */
    protected $className;

    /** @var string Parent class name */
    protected $parentClassName;

    /** @var string Class namespace */
    protected $namespace;

    /** @var array Collection of class uses */
    protected $uses = [];

    /** @var array Collection of class interfaces */
    protected $interfaces = [];

    /** @var array Collection of class used traits */
    protected $traits = [];

    /** @var string Multi-line file description */
    protected $fileDescription;

    /**
     * ClassGenerator constructor.
     *
     * @param string            $className Class name
     * @param AbstractGenerator $parent    Parent generator
     */
    public function __construct(string $className = null, AbstractGenerator $parent = null)
    {
        $this->className = $className;

        parent::__construct($parent);
    }

    /**
     * Set class file description.
     *
     * @param array $description Collection of class file description lines
     *
     * @return ClassGenerator
     */
    public function defDescription(array $description): ClassGenerator
    {
        $commentsGenerator = new CommentsGenerator($this);
        foreach ($description as $line) {
            $commentsGenerator->defLine($line);
        }

        $this->fileDescription = $commentsGenerator->code();

        return $this;
    }

    /**
     * Set class namespace.
     *
     * @param string $namespace
     *
     * @return ClassGenerator
     */
    public function defNamespace(string $namespace): ClassGenerator
    {
        $this->namespace = $namespace;

        return $this;
    }

    /**
     * Set class name.
     *
     * @param string $className
     *
     * @return ClassGenerator
     */
    public function defName(string $className): ClassGenerator
    {
        $this->className = $className;

        return $this;
    }

    /**
     * Set class use.
     *
     * @param string $use   Use class name
     * @param string $alias Use class name
     *
     * @return ClassGenerator
     */
    public function defUse(string $use, string $alias = null): ClassGenerator
    {
        // Store the alias of class
        if ($alias) {
            $this->uses[$alias] = $use;
        } else {
            $this->uses[] = $use;
        }

        return $this;
    }

    /**
     * Set parent class.
     *
     * @param string $className Parent class name
     *
     * @return ClassGenerator
     */
    public function defExtends(string $className): ClassGenerator
    {
        $this->parentClassName = $className;

        return $this;
    }

    /**
     * Set implements interfaces.
     *
     * @param string $interfaceName Interface name
     *
     * @return ClassGenerator
     */
    public function defImplements(string $interfaceName): ClassGenerator
    {
        $this->interfaces[] = $interfaceName;

        return $this;
    }

    /**
     * Set class trait use.
     *
     * @param string $trait Trait class name
     *
     * @return ClassGenerator
     */
    public function defTrait(string $trait): ClassGenerator
    {
        $this->traits[] = $trait;

        return $this;
    }

    /**
     * Set protected class property.
     *
     * @param string $name        Property name
     * @param string $type        Property type
     * @param mixed  $value       Property value
     * @param string $description Property description
     *
     * @return PropertyGenerator
     */
    public function defProtectedProperty(string $name, string $type, $value, string $description = null): PropertyGenerator
    {
        return $this->defProperty($name, $type, $value, $description)->defProtected();
    }

    /**
     * Set class property.
     *
     * @param string $name        Property name
     * @param string $type        Property type
     * @param mixed  $value       Property value
     * @param string $description Property description
     *
     * @return PropertyGenerator
     */
    public function defProperty(string $name, string $type, $value = null, string $description = null): PropertyGenerator
    {
        return (new PropertyGenerator($name, $value, $this))
            ->setIndentation($this->indentation)
            ->increaseIndentation()
            ->defComment()
            ->defVar($type, $description)
            ->end();
    }

    /**
     * Set protected static class property.
     *
     * @param string $name        Property name
     * @param string $type        Property type
     * @param mixed  $value       Property value
     * @param string $description Property description
     *
     * @return PropertyGenerator
     */
    public function defProtectedStaticProperty(string $name, string $type, $value, string $description = null): PropertyGenerator
    {
        return $this->defStaticProperty($name, $type, $value, $description)->defProtected();
    }

    /**
     * Set static class property.
     *
     * @param string $name        Property name
     * @param string $type        Property type
     * @param mixed  $value       Property value
     * @param string $description Property description
     *
     * @return PropertyGenerator
     */
    public function defStaticProperty(string $name, string $type, $value, string $description = null): PropertyGenerator
    {
        return $this->defProperty($name, $type, $value, $description)->defStatic();
    }

    /**
     * Set protected class method.
     *
     * @param string $name Method name
     *
     * @return MethodGenerator
     */
    public function defProtectedMethod(string $name): MethodGenerator
    {
        return $this->defMethod($name)->defProtected();
    }

    /**
     * Set public class method.
     *
     * @param string $name Method name
     *
     * @return MethodGenerator
     */
    public function defMethod(string $name): MethodGenerator
    {
        return (new MethodGenerator($name, $this))->setIndentation($this->indentation)->increaseIndentation();
    }

    /**
     * Set protected static class method.
     *
     * @param string $name Method name
     *
     * @return MethodGenerator
     */
    public function defProtectedStaticMethod(string $name): MethodGenerator
    {
        return $this->defStaticMethod($name)->defProtected();
    }

    /**
     * Set public static class method.
     *
     * @param string $name Method name
     *
     * @return MethodGenerator
     */
    public function defStaticMethod(string $name): MethodGenerator
    {
        return $this->defMethod($name)->defStatic();
    }

    /**
     * Set class constant.
     *
     * @param string $name
     * @param mixed  $value
     * @param string $type
     * @param string $description
     *
     * @return $this|ClassConstantGenerator
     */
    public function defConstant(string $name, $value, string $type, string $description): ClassConstantGenerator
    {
        return (new ClassConstantGenerator($name, $value, $this))
            ->setIndentation($this->indentation)
            ->increaseIndentation()
            ->defComment()
            ->defLine($type . ' ' . $description)
            ->end();
    }

    /**
     * @inheritdoc
     * @throws \InvalidArgumentException
     * @throws ClassNameNotFoundException
     */
    public function code(): string
    {
        if (!$this->className) {
            throw new ClassNameNotFoundException('Class name should be defined');
        }

        $formattedCode = $this->buildNamespaceCode();
        $formattedCode = $this->buildFileDescriptionCode($formattedCode);
        $formattedCode = $this->buildUsesCode($formattedCode);
        $formattedCode = $this->buildNestedCode(CommentsGenerator::class, $formattedCode);

        // Add previously generated code
        $formattedCode[] = $this->buildDefinition();
        $formattedCode[] = '{';

        $indentationString = $this->indentation($this->indentation);
        $innerIndentation = $this->indentation(1);

        $formattedCode = $this->buildTraitsCode($formattedCode, $innerIndentation);
        $formattedCode = $this->buildNestedCode(ClassConstantGenerator::class, $formattedCode);
        $formattedCode = $this->buildNestedCode(PropertyGenerator::class, $formattedCode);
        $formattedCode = $this->buildNestedCode(MethodGenerator::class, $formattedCode);

        // Close class and add one empty line
        $formattedCode[] = '}' . "\n";

        return implode("\n" . $indentationString, $formattedCode);
    }

    protected function buildNamespaceCode(array $formattedCode = []): array
    {
        if ($this->namespace === null) {
            throw new \InvalidArgumentException('Class namespace should be defined');
        }

        $formattedCode[] = 'namespace ' . $this->namespace . ';';

        // One empty line after namespace
        $formattedCode[] = '';

        return $formattedCode;
    }

    protected function buildFileDescriptionCode(array $formattedCode): array
    {
        // Prepend file description if present
        if ($this->fileDescription !== null) {
            array_unshift($formattedCode, $this->fileDescription);
        }

        return $formattedCode;
    }

    protected function buildUsesCode(array $formattedCode): array
    {
        // Add uses
        foreach ($this->uses as $alias => $use) {
            $formattedCode[] = 'use ' . $use . (is_string($alias) ? ' as ' . $alias : '') . ';';
        }

        // One empty line after uses if we have them
        if (count($this->uses)) {
            $formattedCode[] = '';
        }

        return $formattedCode;
    }

    /**
     * Build class definition.
     *
     * @return string Function definition
     */
    protected function buildDefinition()
    {
        return ($this->isFinal ? 'final ' : '') .
            ($this->isAbstract ? 'abstract ' : '') .
            'class ' .
            $this->className .
            ($this->parentClassName ? ' extends ' . $this->parentClassName : '') .
            (count($this->interfaces) ? rtrim(' implements ' . implode(', ', $this->interfaces), ', ') : '');
    }

    /**
     * Build traits definition code.
     *
     * @param array  $formattedCode    Collection of code
     * @param string $innerIndentation Inner indentation
     *
     * @return array Collection of code with trait uses
     */
    protected function buildTraitsCode(array $formattedCode, string $innerIndentation): array
    {
        // Add traits
        foreach ($this->traits as $trait) {
            $formattedCode[] = $innerIndentation . 'use ' . $trait . ';';
        }

        // One empty line after traits if we have them
        if (count($this->traits)) {
            $formattedCode[] = '';
        }

        return $formattedCode;
    }

    /**
     * Get generated class name.
     *
     * @return string Generated class name
     */
    public function getClassName(): string
    {
        return $this->className;
    }

    /**
     * Get generated class namespace.
     *
     * @return string Generated class namespace
     */
    public function getNamespace(): string
    {
        return $this->namespace;
    }
}