Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
87.50% covered (warning)
87.50%
7 / 8
CRAP
97.44% covered (success)
97.44%
76 / 78
AbstractGenerator
0.00% covered (danger)
0.00%
0 / 1
87.50% covered (warning)
87.50%
7 / 8
23
97.44% covered (success)
97.44%
76 / 78
 generateReferenceClass
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
5 / 5
 writeReferenceClass
n/a
0 / 0
1
n/a
0 / 0
 createPhpFileWriter
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
7 / 7
 beginReferenceClass
0.00% covered (danger)
0.00%
0 / 1
2.02
81.82% covered (warning)
81.82%
9 / 11
 generateTransitionMethods
100.00% covered (success)
100.00%
1 / 1
6
100.00% covered (success)
100.00%
18 / 18
 generateReferenceMethods
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 generateIdMethods
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
8 / 8
 generateFallbackExistsStateFunction
100.00% covered (success)
100.00%
1 / 1
7
100.00% covered (success)
100.00%
15 / 15
 generateGetMethod
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
10 / 10
<?php declare(strict_types = 1);
/*
 * Copyright (c) 2019, Josef Kufner  <josef@kufner.cz>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
namespace Smalldb\StateMachine\ClassGenerator\ReferenceClassGenerator;
use ReflectionClass;
use Smalldb\StateMachine\ClassGenerator\AbstractClassGenerator;
use Smalldb\StateMachine\ClassGenerator\ReflectionException;
use Smalldb\StateMachine\Definition\StateMachineDefinition;
use Smalldb\StateMachine\InvalidArgumentException;
use Smalldb\StateMachine\ReferenceInterface;
use Smalldb\StateMachine\ReferenceTrait;
use Smalldb\PhpFileWriter\PhpFileWriter;
abstract class AbstractGenerator extends AbstractClassGenerator
{
    final public function generateReferenceClass(string $sourceReferenceClassName, StateMachineDefinition $definition): string
    {
        try {
            $sourceClassReflection = new ReflectionClass($sourceReferenceClassName);
            $w = $this->createPhpFileWriter($sourceClassReflection);
            $targetReferenceClassName = $this->writeReferenceClass($w, $sourceClassReflection, $definition);
            $this->getClassGenerator()->addGeneratedClass($targetReferenceClassName, $w->getPhpCode());
            return $targetReferenceClassName;
        }
        // @codeCoverageIgnoreStart
        catch (\ReflectionException $ex) {
            throw new ReflectionException("Failed to generate Smalldb reference class: " . $definition->getMachineType(), 0, $ex);
        }
        // @codeCoverageIgnoreEnd
    }
    abstract protected function writeReferenceClass(PhpFileWriter $w, ReflectionClass $sourceClassReflection, StateMachineDefinition $definition): string;
    protected function createPhpFileWriter(ReflectionClass $sourceClassReflection): PhpFileWriter
    {
        $targetNamespace = $this->getClassGenerator()->getClassNamespace();
        $targetShortClassName = $sourceClassReflection->getShortName();
        // Setup the writer
        $w = new PhpFileWriter();
        $w->setFileHeader(get_class($this));
        $w->setNamespace($targetNamespace);
        $w->setClassName($targetShortClassName);
        return $w;
    }
    protected function beginReferenceClass(PhpFileWriter $w, ReflectionClass $sourceClassReflection, array $implements = []): string
    {
        $targetNamespace = $this->getClassGenerator()->getClassNamespace();
        $targetShortClassName = strtr($sourceClassReflection->getName(), ['\\' =>'__']);
        $targetReferenceClassName = $targetNamespace . '\\' . $targetShortClassName;
        // Add parent class/interface
        if ($sourceClassReflection->isInterface()) {
            $extends = null;
            $implements[] = $w->useClass($sourceClassReflection->getName());
        } else {
            $extends = $w->useClass($sourceClassReflection->getName());
            $implements[] = $w->useClass(ReferenceInterface::class);
        }
        // Create the class
        $w->beginClass($targetShortClassName, $extends, $implements);
        $w->writeln('use ' . $w->useClass(ReferenceTrait::class) . ';');
        return $targetReferenceClassName;
    }
    protected function generateTransitionMethods(PhpFileWriter $w, StateMachineDefinition $definition,
        ReflectionClass $sourceClassReflection): void
    {
        foreach ($definition->getActions() as $action) {
            $methodName = $action->getName();
            if ($sourceClassReflection->hasMethod($methodName)) {
                $methodReflection = $sourceClassReflection->getMethod($methodName);
                if ($methodReflection->isAbstract()) {
                    $argMethod = [];
                    $argCall = [];
                    foreach ($methodReflection->getParameters() as $param) {
                        $argMethod[] = $w->getParamAsCode($param);
                        $argCall[] = '$' . $param->name;
                    }
                    $w->beginMethod($methodName, $argMethod);
                    $argCallStr = empty($argCall) ? '' : ', ' . join(', ', $argCall);
                    $w->writeln('return $this->invokeTransition(%s' . $argCallStr . ');', $methodName);
                    $w->endMethod();
                }
            } else {
                $w->beginMethod($methodName, ['...$args']);
                $w->writeln('return $this->invokeTransition(%s, ...$args);', $methodName);
                $w->endMethod();
            }
        }
    }
    protected function generateReferenceMethods(PhpFileWriter $w, StateMachineDefinition $definition): void
    {
        $w->beginMethod('getMachineType', [], 'string');
        $w->writeln('return %s;', $definition->getMachineType());
        $w->endMethod();
    }
    protected function generateIdMethods(PhpFileWriter $w)
    {
        $w->writeln('private $machineId = null;');
        $w->beginMethod('getMachineId', [], '');
        {
            $w->writeln('return $this->machineId;');
        }
        $w->endMethod();
        $w->beginProtectedMethod('setMachineId', ['$machineId'], 'void');
        {
            $w->writeln('$this->machineId = $machineId;');
        }
        $w->endMethod();
    }
    protected function generateFallbackExistsStateFunction(PhpFileWriter $w, ReflectionClass $sourceClassReflection,
        StateMachineDefinition $definition, string $canLoadDataCondition)
    {
        if (!$w->hasMethod('getState') && ($stateMethod = $sourceClassReflection->getMethod('getState')) && $stateMethod->isAbstract()) {
            $states = $definition->getStates();
            if (count($states) === 2) {
                // There are two states: NOT_EXISTS and EXISTS. If there are any data, it EXISTS.
                $theOtherState = null;
                foreach ($states as $state) {
                    if ($state->getName() !== ReferenceInterface::NOT_EXISTS) {
                        $theOtherState = $state->getName();
                        break;
                    }
                }
                $w->beginMethod('getState', [], 'string');
                {
                    $w->writeln("return ($canLoadDataCondition) ? %s : " . $w->useClass(ReferenceInterface::class) . "::NOT_EXISTS;", $theOtherState);
                }
                $w->endMethod();
            } else {
                $w->beginMethod('getState', [], 'string');
                {
                    $w->writeln("return " . $w->useClass(ReferenceInterface::class) . "::NOT_EXISTS;");
                }
                $w->endMethod();
            }
        }
    }
    protected function generateGetMethod(PhpFileWriter $w, StateMachineDefinition $definition, ReflectionClass $sourceClassReflection)
    {
        $w->beginMethod("get", ["string \$propertyName"]);
        {
            $w->beginBlock("switch(\$propertyName)");
            {
                foreach ($definition->getProperties() as $p) {
                    $pName = $p->getName();
                    // TODO: Get the getter properly.
                    $getter = 'get' . ucfirst($pName);
                    $w->writeln("case %s: return \$this->$getter();", $pName);
                }
                $w->writeln("default: throw new " . $w->useClass(InvalidArgumentException::class) . "(\"Undefined property: \$propertyName\");");
            }
            $w->endBlock();
        }
        $w->endMethod();
    }
}