-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,15 +2,6 @@ | |
|
||
declare(strict_types=1); | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <[email protected]> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace AutoMapper\Exception; | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,15 +2,6 @@ | |
|
||
declare(strict_types=1); | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <[email protected]> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace AutoMapper\Exception; | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace AutoMapper\Exception; | ||
|
||
/** | ||
* @author Baptiste Leduc <[email protected]> | ||
*/ | ||
final class InvalidArgumentException extends \InvalidArgumentException | ||
{ | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,15 +2,6 @@ | |
|
||
declare(strict_types=1); | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <[email protected]> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace AutoMapper\Exception; | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace AutoMapper\Exception; | ||
|
||
/** | ||
* @author Baptiste Leduc <[email protected]> | ||
*/ | ||
final class LogicException extends \LogicException | ||
{ | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,15 +2,6 @@ | |
|
||
declare(strict_types=1); | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <[email protected]> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace AutoMapper\Exception; | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace AutoMapper\Extractor; | ||
|
||
use AutoMapper\Exception\InvalidArgumentException; | ||
use AutoMapper\Exception\LogicException; | ||
use AutoMapper\Exception\RuntimeException; | ||
use PhpParser\Node\Arg; | ||
use PhpParser\Node\Expr; | ||
use PhpParser\Node\Param; | ||
use PhpParser\Node\Stmt; | ||
use PhpParser\Parser; | ||
use PhpParser\ParserFactory; | ||
|
||
/** | ||
* @author Nicolas Philippe <[email protected]> | ||
* | ||
* @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) { | ||
Check failure on line 65 in src/Extractor/AstExtractor.php GitHub Actions / phpstan
Check failure on line 65 in src/Extractor/AstExtractor.php GitHub Actions / phpstan
Check failure on line 65 in src/Extractor/AstExtractor.php GitHub Actions / phpstan
|
||
$parameterName = \is_string($parameter->var->name) ? $parameter->var->name : 'N/A'; | ||
Check failure on line 66 in src/Extractor/AstExtractor.php GitHub Actions / phpstan
|
||
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); | ||
Check failure on line 73 in src/Extractor/AstExtractor.php GitHub Actions / phpstan
Check failure on line 73 in src/Extractor/AstExtractor.php GitHub Actions / phpstan
Check failure on line 73 in src/Extractor/AstExtractor.php GitHub Actions / phpstan
|
||
} | ||
|
||
return new Expr\FuncCall( | ||
new Expr\Closure(['stmts' => $classMethod->stmts, 'params' => $closureParameters]), | ||
$inputParameters, | ||
); | ||
} | ||
|
||
/** | ||
* @template T of Stmt | ||
* | ||
* @param class-string<T> $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}\"."); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace AutoMapper\Tests\Extractor; | ||
|
||
use AutoMapper\Extractor\AstExtractor; | ||
use AutoMapper\Tests\Extractor\Fixtures\FooCustomMapper; | ||
use PhpParser\Node\Arg; | ||
use PhpParser\Node\Expr\Variable; | ||
use PhpParser\PrettyPrinter\Standard; | ||
use PHPUnit\Framework\TestCase; | ||
|
||
/** | ||
* @author Baptiste Leduc <[email protected]> | ||
*/ | ||
class AstExtractorTest extends TestCase | ||
{ | ||
public function testExtractSimpleMethod(): void | ||
{ | ||
$extractor = new AstExtractor(); | ||
$extractedMethod = $extractor->extract(FooCustomMapper::class, 'transform', [new Arg(new Variable('object'))]); | ||
|
||
$this->assertEquals(<<<PHP | ||
(function (mixed \$object) { | ||
if (\$object instanceof Foo) { | ||
\$object->bar = 'Hello World!'; | ||
} | ||
return \$object; | ||
})(\$object) | ||
PHP, (new Standard())->prettyPrint([$extractedMethod])); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace AutoMapper\Tests\Extractor\Fixtures; | ||
|
||
class Foo | ||
{ | ||
public string $bar; | ||
public string $baz; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace AutoMapper\Tests\Extractor\Fixtures; | ||
|
||
class FooCustomMapper | ||
{ | ||
public function transform(mixed $object): mixed | ||
{ | ||
if ($object instanceof Foo) { | ||
$object->bar = 'Hello World!'; | ||
} | ||
|
||
return $object; | ||
} | ||
} |