Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
| Total | |
0.00% |
0 / 1 |
|
0.00% |
0 / 3 |
CRAP | |
0.00% |
0 / 93 |
| InheritingGenerator | |
0.00% |
0 / 1 |
|
0.00% |
0 / 3 |
756 | |
0.00% |
0 / 93 |
| writeReferenceClass | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 9 |
|||
| generateDataGetterMethods | |
0.00% |
0 / 1 |
210 | |
0.00% |
0 / 55 |
|||
| generateHydratorMethod | |
0.00% |
0 / 1 |
156 | |
0.00% |
0 / 29 |
|||
| <?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\Definition\StateMachineDefinition; | |
| use Smalldb\StateMachine\InvalidArgumentException; | |
| use Smalldb\StateMachine\ReferenceDataSource\NotExistsException; | |
| use Smalldb\StateMachine\ReferenceInterface; | |
| use Smalldb\StateMachine\RuntimeException; | |
| use Smalldb\PhpFileWriter\PhpFileWriter; | |
| class InheritingGenerator extends AbstractGenerator | |
| { | |
| /** | |
| * Generate a new class implementing the missing methods in $sourceReferenceClassName. | |
| */ | |
| public function writeReferenceClass(PhpFileWriter $w, ReflectionClass $sourceClassReflection, StateMachineDefinition $definition): string | |
| { | |
| // Begin the Reference class | |
| $targetReferenceClassName = $this->beginReferenceClass($w, $sourceClassReflection); | |
| // Create methods | |
| $this->generateReferenceMethods($w, $definition); | |
| $this->generateIdMethods($w); | |
| $this->generateTransitionMethods($w, $definition, $sourceClassReflection); | |
| $this->generateDataGetterMethods($w, $definition, $sourceClassReflection); | |
| $this->generateGetMethod($w, $definition, $sourceClassReflection); | |
| $this->generateHydratorMethod($w, $definition, $sourceClassReflection); | |
| $w->endClass(); | |
| return $targetReferenceClassName; | |
| } | |
| private function generateDataGetterMethods(PhpFileWriter $w, StateMachineDefinition $definition, ReflectionClass $sourceClassReflection) | |
| { | |
| $w->writeln("/** @var bool */"); | |
| $w->writeln('private $dataLoaded = false;'); | |
| $w->beginMethod('invalidateCache', [], 'void'); | |
| { | |
| $w->writeln('$this->dataLoaded = false;'); | |
| $w->writeln('$this->dataSource->invalidateCache($this->getMachineId());'); | |
| } | |
| $w->endMethod(); | |
| $w->beginProtectedMethod('loadData', [], 'bool'); | |
| { | |
| $w->writeln("\$data = \$this->dataSource->loadData(\$this->getMachineId());"); | |
| $w->beginBlock("if (\$data !== null)"); | |
| { | |
| $w->writeln("static::hydrateFromArray(\$this, \$data);"); | |
| $w->writeln("\$this->dataLoaded = true;"); | |
| $w->writeln("return true;"); | |
| } | |
| $w->midBlock("else"); | |
| { | |
| $w->writeln("return false;"); | |
| } | |
| $w->endBlock(); | |
| } | |
| $w->endMethod(); | |
| $referenceInterfaceReflection = new ReflectionClass(ReferenceInterface::class); | |
| foreach ($sourceClassReflection->getMethods() as $method) { | |
| $methodName = $method->getName(); | |
| if (strncmp('get', $methodName, 3) === 0 && $method->isPublic() && !$w->hasMethod($methodName) && !$referenceInterfaceReflection->hasMethod($methodName)) { | |
| $w->beginMethodOverride($method, $argCall); | |
| $w->beginBlock("if (\$this->dataLoaded || \$this->loadData())"); | |
| { | |
| $w->writeln("return parent::$methodName(" . join(', ', $argCall) . ");"); | |
| } | |
| $w->midBlock("else"); | |
| { | |
| $w->writeln("throw new " . $w->useClass(NotExistsException::class) . "(\"Cannot load data in the Not Exists state.\");"); | |
| } | |
| $w->endBlock(); | |
| $w->endMethod(); | |
| } | |
| } | |
| // Implement state method | |
| if (!$w->hasMethod('getState') && ($stateMethod = $sourceClassReflection->getMethod('getState')) && $stateMethod->isAbstract()) { | |
| $w->beginMethod('getState', [], 'string'); | |
| { | |
| $notExists = $w->useClass(ReferenceInterface::class) . '::NOT_EXISTS'; | |
| $states = $definition->getStates(); | |
| switch (count($states)) { | |
| case 0: | |
| case 1: | |
| $w->writeln("return $notExists;"); | |
| break; | |
| case 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->writeln("return \$this->dataLoaded || \$this->loadData() ? (\$this->state ?? %s) : $notExists;", $theOtherState); | |
| break; | |
| default: | |
| $w->beginBlock("if (\$this->dataLoaded || \$this->loadData())"); | |
| { | |
| $w->beginBlock("if (\$this->state === null)"); | |
| { | |
| $w->writeln("throw new " . $w->useClass(RuntimeException::class) . "('Failed to load state machine state.');"); | |
| } | |
| $w->midBlock("else"); | |
| { | |
| $w->writeln("return \$this->state;"); | |
| } | |
| } | |
| $w->midBlock("else"); | |
| { | |
| $w->writeln("return $notExists;"); | |
| } | |
| $w->endBlock(); | |
| break; | |
| } | |
| } | |
| $w->endMethod(); | |
| } | |
| } | |
| private function generateHydratorMethod(PhpFileWriter $w, StateMachineDefinition $definition, ReflectionClass $sourceClassReflection): void | |
| { | |
| if ($sourceClassReflection->hasMethod('hydrateFromArray')) { | |
| throw new InvalidArgumentException('Method hydrateFromArray already defined in class ' . $sourceClassReflection->getName() . '.'); | |
| } | |
| $w->beginStaticMethod('hydrateFromArray', ['self $target', 'array $row'], 'void'); | |
| { | |
| foreach ($definition->getProperties() as $property) { | |
| $name = $property->getName(); | |
| $typehint = $property->getType(); | |
| // Fallback: Get a typehint from getter return type | |
| if ($typehint === null) { | |
| $getterName = 'get' . ucfirst($name); | |
| $returnType = $sourceClassReflection->hasMethod($getterName) | |
| ? $sourceClassReflection->getMethod($getterName)->getReturnType() | |
| : null; | |
| $typehint = $returnType ? $returnType->getName() : null; | |
| } | |
| // TODO: Support mapping of multiple SQL columns into a single machine property. | |
| // TODO: Support mapping of a single SQL column (e.g., JSON object) into multiple machine properties. | |
| // Convert value to fit the typehint | |
| switch ($typehint) { | |
| case 'int': | |
| case 'float': | |
| case 'bool': | |
| case 'string': | |
| $w->writeln("\$target->$name = isset(\$row[%s]) ? ($typehint) \$row[%s] : null;", $name, $name); | |
| break; | |
| default: | |
| if ($typehint && class_exists($typehint)) { | |
| $c = $w->useClass($typehint); | |
| $w->writeln("\$target->$name = (\$v = \$row[%s] ?? null) instanceof $c || \$v === null ? \$v : new $c(\$v);", $name); | |
| } else { | |
| $w->writeln("\$target->$name = \$row[%s] ?? null;", $name); | |
| } | |
| break; | |
| } | |
| } | |
| $w->writeln(); | |
| $w->writeln("\$target->state = isset(\$row['state']) ? (string) \$row['state'] : null;"); | |
| $w->writeln("\$target->dataLoaded = true;"); | |
| } | |
| $w->endMethod(); | |
| } | |
| } |