reinfi/zf-optimized-servicemanager

View on GitHub
src/Service/ClassBuilderService.php

Summary

Maintainability
C
7 hrs
Test Coverage
<?php

namespace Reinfi\OptimizedServiceManager\Service;

use Nette\PhpGenerator\ClassType;
use Nette\PhpGenerator\Method;
use Nette\PhpGenerator\PhpNamespace;
use Zend\ServiceManager\ServiceManager;

/**
 * @package Reinfi\OptimizedServiceManager\Service
 */
class ClassBuilderService
{
    /**
     * @param string $namespace
     *
     * @return PhpNamespace
     */
    public function buildNamespace(string $namespace): PhpNamespace
    {
        return new PhpNamespace($namespace);
    }

    /**
     * @param PhpNamespace $namespace
     * @param string       $className
     *
     * @return ClassType
     */
    public function buildClass(
        PhpNamespace $namespace,
        string $className
    ): ClassType {
        return $namespace->addClass($className);
    }

    /**
     * @param ClassType $class
     */
    public function addConstructor(ClassType $class)
    {
        $constructor = $class
            ->addMethod('__construct')
            ->setVisibility('public');

        $constructor
            ->addBody('parent::__construct();')
            ->addBody('')
            ->addBody('foreach ($this->registeredInitializers as $initializer):')
            ->addBody('    $this->addInitializer($initializer);')
            ->addBody('endforeach;')
            ->addBody('')
            ->addBody('$this->setService(\'' . ServiceManager::class . '\', $this);');
    }

    /**
     * @param ClassType $class
     * @param Options   $options
     */
    public function addOverwriteMethods(ClassType $class, Options $options)
    {
        $this->addGetMethod($class, $options);
        $this->addHasMethod($class);
        $this->overwriteCanonicalizeName($class, $options);
        $this->overwriteCanCreateFromAbstractFactory($class);
        $this->overwriteSetFactory($class);
        $this->overwriteSetInvokableClass($class);
    }

    /**
     * @param ClassType $class
     * @param Options   $options
     *
     * @return Method
     */
    private function addGetMethod(ClassType $class, Options $options): Method
    {
        $getMethod = $class->addMethod('get')
            ->setVisibility('public')
            ->addComment('@inheritdoc');

        $getMethod->addParameter('name');
        $getMethod->addParameter('usePeeringServiceManagers', true);

        $getMethod
            ->addBody('$instance = null;')
            ->addBody('')
            ->addBody('if (isset($this->instances[$name])):')
            ->addBody('    return $this->instances[$name];')
            ->addBody('endif;')
            ->addBody('')
            ->addBody('if (isset($this->mappings[$name])):')
            ->addBody('    $instance = $this->{$this->mappings[$name]}();')
            ->addBody('endif;')
            ->addBody('')
            ->addBody('if ($instance === null):')
            ->addBody('    $instance = parent::get($name, $usePeeringServiceManagers);')
            ->addBody('endif;')
            ->addBody('')
            ->addBody('if ($instance === null):')
            ->addBody('    return $instance;')
            ->addBody('endif;');

        if ($options->isWithInitializers()) {
            $getMethod
                ->addBody('foreach ($this->initializers as $initializer):')
                ->addBody('    if ($initializer instanceof \Zend\ServiceManager\InitializerInterface):')
                ->addBody('        $initializer->initialize($instance, $this);')
                ->addBody('    else:')
                ->addBody('        call_user_func($initializer, $instance, $this);')
                ->addBody('    endif;')
                ->addBody('    endforeach;')
                ->addBody('')
            ;
        }

        $getMethod
            ->addBody('if (isset($this->shared[$name]) && $this->shared[$name]):')
            ->addBody('    $this->instances[$name] = $instance;')
            ->addBody('endif;')
            ->addBody('')
            ->addBody('return $instance;');

        return $getMethod;
    }

    /**
     * @param ClassType $class
     *
     * @return Method
     */
    private function addHasMethod(ClassType $class): Method
    {
        $hasMethod = $class->addMethod('has')
            ->setVisibility('public')
            ->addComment('@inheritdoc');

        $hasMethod->addParameter('name');
        $hasMethod->addParameter('checkAbstractFactories', true);
        $hasMethod->addParameter('usePeeringServiceManagers', true);

        $hasMethod
            ->addBody('if (is_array($name)):')
            ->addBody('    list(, $name) = $name;')
            ->addBody('endif;')
            ->addBody('')
            ->addBody('if (isset($this->mappings[$name])):')
            ->addBody('    return true;')
            ->addBody('endif;')
            ->addBody('')
            ->addBody(
            'return parent::has($name, $checkAbstractFactories, $usePeeringServiceManagers);'
            );

        return $hasMethod;
    }

    /**
     * @param ClassType $class
     * @param Options   $options
     *
     * @return void
     */
    private function overwriteCanonicalizeName(ClassType $class, Options $options)
    {
        if ($options->isCanonicalizeNames()) {
            return;
        }

        $method = $class->addMethod('canonicalizeName')
            ->setVisibility('protected')
            ->addComment('@inheritdoc');

        $method->addParameter('name');

        $method->addBody('return $name;');
    }

    /**
     * @param ClassType $class
     *
     * @return Method
     */
    private function overwriteCanCreateFromAbstractFactory(ClassType $class): Method
    {
        $class->addProperty('initializedAbstractFactories', false);

        $method = $class->addMethod('canCreateFromAbstractFactory')
                        ->setVisibility('public')
                        ->addComment('@inheritdoc');

        $method->addParameter('cName');
        $method->addParameter('rName');

        $method
            ->addBody('if ($this->initializedAbstractFactories === false):')
            ->addBody('    foreach ($this->registeredAbstractFactories as $factory):')
            ->addBody('        $this->addAbstractFactory($factory);')
            ->addBody('    endforeach;')
            ->addBody('    $this->initializedAbstractFactories = true;')
            ->addBody('endif;')
            ->addBody('')
            ->addBody('return parent::canCreateFromAbstractFactory($cName, $rName);');

        return $method;
    }

    /**
     * @param ClassType $class
     *
     * @return Method
     */
    private function overwriteSetFactory(ClassType $class): Method
    {
        $method = $class->addMethod('setFactory')
            ->setVisibility('public')
            ->addComment('@inheritdoc');

        $method->addParameter('name');
        $method->addParameter('factory');
        $method->addParameter('shared')->setDefaultValue(null);

        $method
            ->addBody('if (isset($this->mappings[$name])):')
            ->addBody('    return $this;')
            ->addBody('endif;')
            ->addBody('')
            ->addBody('if (isset($this->factories[$name])):')
            ->addBody('    return $this;')
            ->addBody('endif;')
            ->addBody('')
            ->addBody('return parent::setFactory($name, $factory, $shared);');

        return $method;
    }

    /**
     * @param ClassType $class
     *
     * @return Method
     */
    private function overwriteSetInvokableClass(ClassType $class): Method
    {
        $method = $class->addMethod('setInvokableClass')
            ->setVisibility('public')
            ->addComment('@inheritdoc');

        $method->addParameter('name');
        $method->addParameter('invokableClass');
        $method->addParameter('shared')->setDefaultValue(null);

        $method
            ->addBody('if (isset($this->mappings[$name])):')
            ->addBody('    return $this;')
            ->addBody('endif;')
            ->addBody('')
            ->addBody('if (isset($this->invokables[$name])):')
            ->addBody('    return $this;')
            ->addBody('endif;')
            ->addBody('')
            ->addBody('return parent::setInvokableClass($name, $invokableClass, $shared);');

        return $method;
    }
}