diff --git a/src/Exception/CircularReferenceException.php b/src/Exception/CircularReferenceException.php index b363860f..268f4414 100644 --- a/src/Exception/CircularReferenceException.php +++ b/src/Exception/CircularReferenceException.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - namespace AutoMapper\Exception; /** diff --git a/src/Exception/CompileException.php b/src/Exception/CompileException.php index f57e3cd6..ebde8551 100644 --- a/src/Exception/CompileException.php +++ b/src/Exception/CompileException.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - namespace AutoMapper\Exception; /** diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php new file mode 100644 index 00000000..2adacb52 --- /dev/null +++ b/src/Exception/InvalidArgumentException.php @@ -0,0 +1,12 @@ + + */ +final class InvalidArgumentException extends \InvalidArgumentException +{ +} diff --git a/src/Exception/InvalidMappingException.php b/src/Exception/InvalidMappingException.php index db3dfdcc..071260ba 100644 --- a/src/Exception/InvalidMappingException.php +++ b/src/Exception/InvalidMappingException.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - namespace AutoMapper\Exception; /** diff --git a/src/Exception/LogicException.php b/src/Exception/LogicException.php new file mode 100644 index 00000000..183ec959 --- /dev/null +++ b/src/Exception/LogicException.php @@ -0,0 +1,12 @@ + + */ +final class LogicException extends \LogicException +{ +} diff --git a/src/Exception/NoMappingFoundException.php b/src/Exception/NoMappingFoundException.php index ba8db22c..a581cc9c 100644 --- a/src/Exception/NoMappingFoundException.php +++ b/src/Exception/NoMappingFoundException.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - namespace AutoMapper\Exception; /** diff --git a/src/Extractor/AstExtractor.php b/src/Extractor/AstExtractor.php new file mode 100644 index 00000000..d7d58bb4 --- /dev/null +++ b/src/Extractor/AstExtractor.php @@ -0,0 +1,103 @@ + + * + * @internal + */ +final readonly class AstExtractor +{ + private Parser $parser; + + public function __construct(?Parser $parser = null) + { + $this->parser = $parser ?? (new ParserFactory())->create(ParserFactory::PREFER_PHP7); + } + + /** + * Extracts the code of the given method from a given class, and wraps it inside a closure, in order to inject it + * in the generated mappers. + * + * @param class-string $class + * @param Arg[] $inputParameters + */ + public function extract(string $class, string $method, array $inputParameters): Expr + { + $fileName = (new \ReflectionClass($class))->getFileName(); + if (false === $fileName) { + throw new RuntimeException("You cannot extract code from \"{$class}\" class."); + } + $fileContents = file_get_contents($fileName); + if (false === $fileContents) { + throw new RuntimeException("File \"{$fileName}\" for \"{$class}\" couldn't be read."); + } + + $statements = $this->parser->parse($fileContents); + if (null === $statements) { + throw new RuntimeException("Couldn't parse file \"{$fileName}\" for class \"{$class}\"."); + } + + $namespaceStatement = self::findUnique(Stmt\Namespace_::class, $statements, $fileName); + /** @var Stmt\Class_ $classStatement */ + $classStatement = self::findUnique(Stmt\Class_::class, $namespaceStatement->stmts, $fileName); + + $classMethod = $classStatement->getMethod($method) ?? throw new LogicException("Cannot find method \"{$method}()\" in class \"{$class}\"."); + + if (\count($inputParameters) !== \count($classMethod->getParams())) { + throw new InvalidArgumentException("Input parameters and method parameters in class \"{$class}\" do not match."); + } + + foreach ($classMethod->getParams() as $key => $parameter) { + if ($inputParameters[$key]->value->name !== $parameter->var->name) { + $parameterName = \is_string($parameter->var->name) ? $parameter->var->name : 'N/A'; + throw new InvalidArgumentException("Method parameter \"{$parameterName}\" does not match type \"{$inputParameters[$key]->getType()}\" from input parameter \"{$inputParameters[$key]->name}\" in \"{$class}::{$method}\" method."); + } + } + + $closureParameters = []; + foreach ($classMethod->getParams() as $parameter) { + $closureParameters[] = new Param(new Expr\Variable($parameter->var->name), type: $parameter->type->name); + } + + return new Expr\FuncCall( + new Expr\Closure(['stmts' => $classMethod->stmts, 'params' => $closureParameters]), + $inputParameters, + ); + } + + /** + * @template T of Stmt + * + * @param class-string $searchedStatementClass + * @param Stmt[] $statements + * + * @return T + */ + private static function findUnique(string $searchedStatementClass, array $statements, string $fileName): Stmt + { + $foundStatements = array_filter( + $statements, + static fn (Stmt $statement): bool => $statement instanceof $searchedStatementClass, + ); + + if (\count($foundStatements) > 1) { + throw new InvalidArgumentException("Multiple \"{$searchedStatementClass}\" found in file \"{$fileName}\"."); + } + + return array_values($foundStatements)[0] ?? throw new InvalidArgumentException("No \"{$searchedStatementClass}\" found in file \"{$fileName}\"."); + } +} diff --git a/tests/Extractor/AstExtractorTest.php b/tests/Extractor/AstExtractorTest.php new file mode 100644 index 00000000..d39f14fb --- /dev/null +++ b/tests/Extractor/AstExtractorTest.php @@ -0,0 +1,33 @@ + + */ +class AstExtractorTest extends TestCase +{ + public function testExtractSimpleMethod(): void + { + $extractor = new AstExtractor(); + $extractedMethod = $extractor->extract(FooCustomMapper::class, 'transform', [new Arg(new Variable('object'))]); + + $this->assertEquals(<<bar = 'Hello World!'; + } + return \$object; +})(\$object) +PHP, (new Standard())->prettyPrint([$extractedMethod])); + } +} diff --git a/tests/Extractor/Fixtures/Foo.php b/tests/Extractor/Fixtures/Foo.php new file mode 100644 index 00000000..c3910968 --- /dev/null +++ b/tests/Extractor/Fixtures/Foo.php @@ -0,0 +1,11 @@ +bar = 'Hello World!'; + } + + return $object; + } +}