From c2d733af643079625d0a96cfdb89a9919c117139 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Sat, 23 Jan 2016 00:36:09 +0100 Subject: [PATCH 1/5] [AstGenerator] Init the Component --- composer.json | 3 +- src/Symfony/Component/AstGenerator/.gitignore | 3 + .../AstGenerator/AstGeneratorChain.php | 66 ++++++++++++++ .../AstGenerator/AstGeneratorInterface.php | 39 ++++++++ .../Exception/MissingContextException.php | 7 ++ src/Symfony/Component/AstGenerator/LICENSE | 19 ++++ src/Symfony/Component/AstGenerator/README.md | 8 ++ .../Tests/AstGeneratorChainTest.php | 88 +++++++++++++++++++ .../Tests/UniqueVariableScopeTest.php | 31 +++++++ .../AstGenerator/UniqueVariableScope.php | 40 +++++++++ .../Component/AstGenerator/composer.json | 38 ++++++++ .../Component/AstGenerator/phpunit.xml.dist | 28 ++++++ 12 files changed, 369 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/AstGenerator/.gitignore create mode 100644 src/Symfony/Component/AstGenerator/AstGeneratorChain.php create mode 100644 src/Symfony/Component/AstGenerator/AstGeneratorInterface.php create mode 100644 src/Symfony/Component/AstGenerator/Exception/MissingContextException.php create mode 100644 src/Symfony/Component/AstGenerator/LICENSE create mode 100644 src/Symfony/Component/AstGenerator/README.md create mode 100644 src/Symfony/Component/AstGenerator/Tests/AstGeneratorChainTest.php create mode 100644 src/Symfony/Component/AstGenerator/Tests/UniqueVariableScopeTest.php create mode 100644 src/Symfony/Component/AstGenerator/UniqueVariableScope.php create mode 100644 src/Symfony/Component/AstGenerator/composer.json create mode 100644 src/Symfony/Component/AstGenerator/phpunit.xml.dist diff --git a/composer.json b/composer.json index 1dececdeeaf37..c4a02d56cd14b 100644 --- a/composer.json +++ b/composer.json @@ -88,7 +88,8 @@ "egulias/email-validator": "~1.2,>=1.2.8|~2.0", "symfony/polyfill-apcu": "~1.1", "symfony/security-acl": "~2.8|~3.0", - "phpdocumentor/reflection-docblock": "^3.0" + "phpdocumentor/reflection-docblock": "^3.0", + "nikic/PHP-Parser": "~2.0" }, "conflict": { "phpdocumentor/reflection-docblock": "<3.0", diff --git a/src/Symfony/Component/AstGenerator/.gitignore b/src/Symfony/Component/AstGenerator/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Component/AstGenerator/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Component/AstGenerator/AstGeneratorChain.php b/src/Symfony/Component/AstGenerator/AstGeneratorChain.php new file mode 100644 index 0000000000000..7efb980f6f288 --- /dev/null +++ b/src/Symfony/Component/AstGenerator/AstGeneratorChain.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AstGenerator; + +/** + * Generator delegating the generation to a chain of generators. + * + * @author Joel Wurtz + */ +class AstGeneratorChain implements AstGeneratorInterface +{ + /** @var AstGeneratorInterface[] A list of generators */ + protected $generators; + + /** @var bool Whether the generation must return as soon as possible or use all generators, default to false */ + protected $returnOnFirst; + + public function __construct(array $generators = array(), $returnOnFirst = false) + { + $this->generators = $generators; + $this->returnOnFirst = $returnOnFirst; + } + + /** + * {@inheritdoc} + */ + public function generate($object, array $context = array()) + { + $nodes = array(); + + foreach ($this->generators as $generator) { + if ($generator instanceof AstGeneratorInterface && $generator->supportsGeneration($object)) { + $nodes = array_merge($nodes, $generator->generate($object, $context)); + + if ($this->returnOnFirst) { + return $nodes; + } + } + } + + return $nodes; + } + + /** + * {@inheritdoc} + */ + public function supportsGeneration($object) + { + foreach ($this->generators as $generator) { + if ($generator instanceof AstGeneratorInterface && $generator->supportsGeneration($object)) { + return true; + } + } + + return false; + } +} diff --git a/src/Symfony/Component/AstGenerator/AstGeneratorInterface.php b/src/Symfony/Component/AstGenerator/AstGeneratorInterface.php new file mode 100644 index 0000000000000..30abb726aacec --- /dev/null +++ b/src/Symfony/Component/AstGenerator/AstGeneratorInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AstGenerator; + +/** + * An AstGeneratorInterface is a contract to transform an object into an AST. + * + * @author Joel Wurtz + */ +interface AstGeneratorInterface +{ + /** + * Generate an object into an AST given a specific context. + * + * @param mixed $object Object to generate AST from + * @param array $context Context for the generator + * + * @return \PhpParser\Node[] An array of statements (AST Node) + */ + public function generate($object, array $context = array()); + + /** + * Check whether the given object is supported for generation by this generator. + * + * @param mixed $object Object to generate AST from + * + * @return bool + */ + public function supportsGeneration($object); +} diff --git a/src/Symfony/Component/AstGenerator/Exception/MissingContextException.php b/src/Symfony/Component/AstGenerator/Exception/MissingContextException.php new file mode 100644 index 0000000000000..1edae8bb1e89c --- /dev/null +++ b/src/Symfony/Component/AstGenerator/Exception/MissingContextException.php @@ -0,0 +1,7 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AstGenerator\Tests; + +use Symfony\Component\AstGenerator\AstGeneratorChain; +use Symfony\Component\AstGenerator\AstGeneratorInterface; + +class AstGeneratorChainTest extends \PHPUnit_Framework_TestCase +{ + public function testEmpty() + { + $generator = new AstGeneratorChain(); + + $this->assertFalse($generator->supportsGeneration('dummy')); + $this->assertEmpty($generator->generate('dummy')); + } + + public function testSupports() + { + $generatorSub = $this->getGeneratorMock(true, array('ast')); + + $generator = new AstGeneratorChain(array($generatorSub)); + $this->assertTrue($generator->supportsGeneration('dummy')); + $this->assertEquals(array('ast'), $generator->generate('dummy')); + } + + public function testMultiSupports() + { + $generatorSub1 = $this->getGeneratorMock(true, array('ast1')); + $generatorSub2 = $this->getGeneratorMock(true, array('ast2')); + + $generator = new AstGeneratorChain(array($generatorSub1, $generatorSub2)); + $this->assertTrue($generator->supportsGeneration('dummy')); + $this->assertEquals(array('ast1', 'ast2'), $generator->generate('dummy')); + } + + public function testPartialSupports() + { + $generatorSub1 = $this->getGeneratorMock(true, array('ast1')); + $generatorSub2 = $this->getGeneratorMock(false); + + $generator = new AstGeneratorChain(array($generatorSub1, $generatorSub2)); + $this->assertTrue($generator->supportsGeneration('dummy')); + $this->assertEquals(array('ast1'), $generator->generate('dummy')); + } + + public function testMultiSupportsWithFirstReturn() + { + $generatorSub1 = $this->getGeneratorMock(true, array('ast1')); + $generatorSub2 = $this->getGeneratorMock(true, array('ast2')); + + $generator = new AstGeneratorChain(array($generatorSub1, $generatorSub2), true); + $this->assertTrue($generator->supportsGeneration('dummy')); + $this->assertEquals(array('ast1'), $generator->generate('dummy')); + } + + private function getGeneratorMock($support, $return = null) + { + $generatorSub = $this->getMockBuilder(AstGeneratorInterface::class)->getMock(); + $generatorSub + ->expects($this->any()) + ->method('supportsGeneration') + ->with('dummy') + ->willReturn($support); + if (null === $return) { + $generatorSub + ->expects($this->never()) + ->method('generate'); + } else { + $generatorSub + ->expects($this->any()) + ->method('generate') + ->with('dummy', array()) + ->willReturn($return); + } + + return $generatorSub; + } +} diff --git a/src/Symfony/Component/AstGenerator/Tests/UniqueVariableScopeTest.php b/src/Symfony/Component/AstGenerator/Tests/UniqueVariableScopeTest.php new file mode 100644 index 0000000000000..f46c5e4e5070d --- /dev/null +++ b/src/Symfony/Component/AstGenerator/Tests/UniqueVariableScopeTest.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AstGenerator\Tests; + +use Symfony\Component\AstGenerator\UniqueVariableScope; + +class UniqueVariableScopeTest extends \PHPUnit_Framework_TestCase +{ + public function testUniqueVariable() + { + $uniqueVariableScope = new UniqueVariableScope(); + + $name = $uniqueVariableScope->getUniqueName('name'); + $this->assertEquals('name', $name); + + $name = $uniqueVariableScope->getUniqueName('name'); + $this->assertEquals('name_1', $name); + + $name = $uniqueVariableScope->getUniqueName('name'); + $this->assertEquals('name_2', $name); + } +} diff --git a/src/Symfony/Component/AstGenerator/UniqueVariableScope.php b/src/Symfony/Component/AstGenerator/UniqueVariableScope.php new file mode 100644 index 0000000000000..eed95c9a0c64a --- /dev/null +++ b/src/Symfony/Component/AstGenerator/UniqueVariableScope.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AstGenerator; + +/** + * Allow to get a unique variable name for a scope (like a method). + */ +class UniqueVariableScope +{ + private $registry = array(); + + /** + * Return an unique name for a variable. + * + * @param string $name Name of the variable + * + * @return string if not found return the $name given, if not return the name suffixed with a number + */ + public function getUniqueName($name) + { + if (!isset($this->registry[$name])) { + $this->registry[$name] = 0; + + return $name; + } + + ++$this->registry[$name]; + + return sprintf('%s_%s', $name, $this->registry[$name]); + } +} diff --git a/src/Symfony/Component/AstGenerator/composer.json b/src/Symfony/Component/AstGenerator/composer.json new file mode 100644 index 0000000000000..a0889cd91d11e --- /dev/null +++ b/src/Symfony/Component/AstGenerator/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/ast-generator", + "type": "library", + "description": "Symfony component to generate AST from and to various others components", + "keywords": ["symfony", "ast", "generator"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Joel Wurtz", + "email": "joel.wurtz@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.5.9", + "nikic/php-parser": "~2.0", + "symfony/property-info": "~2.8|~3.0" + }, + "require-dev": { + "symfony/serializer": "~2.8|~3.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\AstGenerator\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + } +} diff --git a/src/Symfony/Component/AstGenerator/phpunit.xml.dist b/src/Symfony/Component/AstGenerator/phpunit.xml.dist new file mode 100644 index 0000000000000..5484c4319c9e4 --- /dev/null +++ b/src/Symfony/Component/AstGenerator/phpunit.xml.dist @@ -0,0 +1,28 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + ./vendor + + + + From 8e89d349b29281ee8883ca281a50828990a6071f Mon Sep 17 00:00:00 2001 From: Ener-Getick Date: Sat, 6 Aug 2016 16:12:03 +0200 Subject: [PATCH 2/5] [AstGenerator] Create an util class to instantiate common nodes --- .../Component/AstGenerator/Util/AstHelper.php | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 src/Symfony/Component/AstGenerator/Util/AstHelper.php diff --git a/src/Symfony/Component/AstGenerator/Util/AstHelper.php b/src/Symfony/Component/AstGenerator/Util/AstHelper.php new file mode 100644 index 0000000000000..c349902c1a963 --- /dev/null +++ b/src/Symfony/Component/AstGenerator/Util/AstHelper.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AstGenerator\Util; + +use PhpParser\Node; +use PhpParser\Node\Expr; +use PhpParser\Node\Name; +use PhpParser\Node\Scalar; +use PhpParser\ParserFactory; +use PhpParser\PrettyPrinter\Standard; + +/** + * @internal + */ +final class AstHelper +{ + private static $parser; + + /** + * @param Node[] $stmts + * + * @return string the php code + */ + public static function dump(array $stmts) + { + $printer = new Standard(); + + return $printer->prettyPrintFile($stmts); + } + + /** + * Transforms php code into an ast node. + * + * @param string $code the php code + * + * @return Node[] + */ + public static function raw($code) + { + $code = "create(ParserFactory::ONLY_PHP5); + } + + return self::$parser->parse($code); + } + + /** + * Transforms a php value into an AST node. + * + * @param null|bool|int|float|string|array $value + * + * @return Expr + */ + public static function value($value) + { + if (is_null($value)) { + return new Expr\ConstFetch( + new Name('null') + ); + } elseif (is_bool($value)) { + return new Expr\ConstFetch( + new Name($value ? 'true' : 'false') + ); + } elseif (is_int($value)) { + return new Scalar\LNumber($value); + } elseif (is_float($value)) { + return new Scalar\DNumber($value); + } elseif (is_string($value)) { + return new Scalar\String_($value); + } elseif (is_array($value)) { + $items = array(); + $lastKey = -1; + foreach ($value as $itemKey => $itemValue) { + // for consecutive, numeric keys don't generate keys + if (null !== $lastKey && ++$lastKey === $itemKey) { + $items[] = new Expr\ArrayItem( + self::value($itemValue) + ); + } else { + $lastKey = null; + $items[] = new Expr\ArrayItem( + self::value($itemValue), + self::value($itemKey) + ); + } + } + + return new Expr\Array_($items); + } else { + throw new \LogicException('Invalid value'); + } + } + + private function __construct() + { + } +} From 0b61e4c6766316b6e2f1628487196d9178ae2275 Mon Sep 17 00:00:00 2001 From: Ener-Getick Date: Sat, 6 Aug 2016 15:30:44 +0200 Subject: [PATCH 3/5] [Routing] Convert the PhpMatcherDumper to an AstGeneratorInterface --- .../Matcher/Dumper/PhpMatcherDumper.php | 40 +----- .../Matcher/Generator/PhpMatcherGenerator.php | 133 ++++++++++++++++++ 2 files changed, 138 insertions(+), 35 deletions(-) create mode 100644 src/Symfony/Component/Routing/Matcher/Generator/PhpMatcherGenerator.php diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php index aa7df8a78b5ba..c7e786c7ffeb6 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php @@ -11,8 +11,10 @@ namespace Symfony\Component\Routing\Matcher\Dumper; +use Symfony\Component\AstGenerator\Util\AstHelper; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Matcher\Generator\PhpMatcherGenerator; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; @@ -31,6 +33,7 @@ class PhpMatcherDumper extends MatcherDumper * @var ExpressionFunctionProviderInterface[] */ private $expressionLanguageProviders = array(); + private $generator; /** * Dumps a set of routes to a PHP class. @@ -46,42 +49,9 @@ class PhpMatcherDumper extends MatcherDumper */ public function dump(array $options = array()) { - $options = array_replace(array( - 'class' => 'ProjectUrlMatcher', - 'base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', - ), $options); + $generator = new PhpMatcherGenerator(); - // trailing slash support is only enabled if we know how to redirect the user - $interfaces = class_implements($options['base_class']); - $supportsRedirections = isset($interfaces['Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcherInterface']); - - return <<context = \$context; - } - -{$this->generateMatchMethod($supportsRedirections)} -} - -EOF; + return AstHelper::dump($generator->generate($this->getRoutes(), $options)); } public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) diff --git a/src/Symfony/Component/Routing/Matcher/Generator/PhpMatcherGenerator.php b/src/Symfony/Component/Routing/Matcher/Generator/PhpMatcherGenerator.php new file mode 100644 index 0000000000000..e0b8ec6cd4e27 --- /dev/null +++ b/src/Symfony/Component/Routing/Matcher/Generator/PhpMatcherGenerator.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Generator; + +use PhpParser\BuilderFactory; +use PhpParser\Comment; +use PhpParser\Node\Arg; +use PhpParser\Node\Name; +use PhpParser\Node\Param; +use PhpParser\Node\Stmt; +use PhpParser\Node\Expr; +use PhpParser\Node\Scalar; +use Symfony\Component\AstGenerator\AstGeneratorInterface; +use Symfony\Component\AstGenerator\Util\AstHelper; +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\RequestContext; + +/** + * @author Guilhem N. + */ +class PhpMatcherGenerator implements AstGeneratorInterface +{ + private $expressionLanguage; + + /** + * @var ExpressionFunctionProviderInterface[] + */ + private $expressionLanguageProviders = array(); + private $factory; + + public function __construct() + { + $this->factory = new BuilderFactory(); + } + + /** + * {@inheritdoc} + */ + public function generate($object, array $context = array()) + { + $context = array_replace(array( + 'class' => 'ProjectUrlMatcher', + 'base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', + ), $context); + + // trailing slash support is only enabled if we know how to redirect the user + $interfaces = class_implements($context['base_class']); + $supportsRedirections = isset($interfaces['Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcherInterface']); + + return array($this->generateClass($object, $context)->getNode()); + } + + /** + * {@inheritdoc} + */ + public function supportsGeneration($object) + { + return is_string($object) && class_exists($object); + } + + private function generateClass($object, array $context) + { + $docComment = <<factory->class($context['class']) + ->setDocComment($docComment) + ->extend($context['base_class']) + ->addStmt($this->factory->property('context')->makePrivate()) + ->addStmt( + $this->factory->method('__construct') + ->makePublic() + ->addParam($this->factory->param('context')->setTypeHint(RequestContext::class)) + ->addStmt( + // $this->context = $context + new Expr\Assign( + new Expr\PropertyFetch(new Expr\Variable('this'), 'context'), + new Expr\Variable('context') + ) + ) + ) + ->addStmt($this->generateMatchMethod($object, $context)); + } + + private function generateMatchMethod($object, array $context) + { + $method = $this->factory + ->method('match') + ->makePublic() + ->addParam($this->factory->param('pathinfo')) + ->addStmt(new Expr\Assign(new Expr\Variable('allow'), AstHelper::value(array()))) + ->addStmt(new Expr\Assign( + new Expr\Variable('pathinfo'), + new Expr\FuncCall(new Name('rawurldecode'), array( + new Arg(new Expr\Variable('pathinfo')), + )) + )) + ->addStmt(new Expr\Assign(new Expr\Variable('context'), new Expr\PropertyFetch(new Expr\Variable('this'), 'context'))) + ->addStmt(new Expr\Assign(new Expr\Variable('request'), new Expr\PropertyFetch(new Expr\Variable('this'), 'request'))); + + $method->addStmt(new Stmt\Throw_(new Expr\Ternary( + new Expr\BinaryOp\Smaller( + new Scalar\LNumber(0), + new Expr\FuncCall(new Name('count'), array(new Arg(new Expr\Variable('allow')))) + ), + new Expr\New_(new Name(MethodNotAllowedException::class), array( + new Arg(new Expr\FuncCall(new Name('array_unique'), array( + new Arg(new Expr\Variable('allow')), + ))), + )), + new Expr\New_(new Name(ResourceNotFoundException::class)) + ))); + + return $method; + } +} From d258187fb26d3c98ec3155bc99adf7af28e3ebb9 Mon Sep 17 00:00:00 2001 From: Ener-Getick Date: Sat, 6 Aug 2016 17:17:52 +0200 Subject: [PATCH 4/5] [Routing] Convert the PhpGeneratorDumper to an AstGeneratorInterface --- .../AstGenerator/UrlGeneratorGenerator.php | 167 ++++++++++++++++++ .../Generator/Dumper/PhpGeneratorDumper.php | 90 +--------- 2 files changed, 173 insertions(+), 84 deletions(-) create mode 100644 src/Symfony/Component/Routing/Generator/AstGenerator/UrlGeneratorGenerator.php diff --git a/src/Symfony/Component/Routing/Generator/AstGenerator/UrlGeneratorGenerator.php b/src/Symfony/Component/Routing/Generator/AstGenerator/UrlGeneratorGenerator.php new file mode 100644 index 0000000000000..bf2619d8dbf14 --- /dev/null +++ b/src/Symfony/Component/Routing/Generator/AstGenerator/UrlGeneratorGenerator.php @@ -0,0 +1,167 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator\AstGenerator; + +use PhpParser\BuilderFactory; +use PhpParser\Comment; +use PhpParser\Node\Name; +use PhpParser\Node\Param; +use PhpParser\Node\Stmt; +use PhpParser\Node\Expr; +use Psr\Log\LoggerInterface; +use Symfony\Component\AstGenerator\AstGeneratorInterface; +use Symfony\Component\AstGenerator\Util\AstHelper; +use Symfony\Component\Routing\Exception\RouteNotFoundException; +use Symfony\Component\Routing\Generator\UrlGenerator; +use Symfony\Component\Routing\RequestContext; + +/** + * @author Guilhem N. + */ +class UrlGeneratorGenerator implements AstGeneratorInterface +{ + public function __construct() + { + $this->factory = new BuilderFactory(); + } + + /** + * {@inheritdoc} + */ + public function generate($object, array $context = array()) + { + $context = array_replace(array( + 'class' => 'ProjectUrlGenerator', + 'base_class' => UrlGenerator::class, + ), $context); + + return array($this->generateClass($object, $context)->getNode()); + } + + /** + * {@inheritdoc} + */ + public function supportsGeneration($object) + { + return is_string($object) && class_exists($object); + } + + private function generateClass($object, array $context) + { + $docComment = <<factory->class($context['class']) + ->setDocComment($docComment) + ->extend($context['base_class']) + ->addStmt($this->factory->property('declaredRoutes')->makePrivate()->makeStatic()) + ->addStmt($this->generateConstructor($object, $context)) + ->addStmt($this->generateGenerateMethod($object, $context)); + } + + private function generateConstructor($object, array $context) + { + $constructor = $this->factory->method('__construct') + ->makePublic() + ->addParam($this->factory->param('context')->setTypeHint(RequestContext::class)) + ->addParam($this->factory->param('logger')->setTypeHint(LoggerInterface::class)->setDefault(null)); + + $code = <<<'EOF' +$this->context = $context; +$this->logger = $logger; +EOF; + foreach (AstHelper::raw($code) as $stmt) { + $constructor->addStmt($stmt); + } + + $constructor->addStmt(new Stmt\If_( + new Expr\BinaryOp\Equal( + AstHelper::value(null), + new Expr\StaticPropertyFetch(new Name('self'), 'declaredRoutes') + ), + array( + 'stmts' => array( + new Expr\Assign( + new Expr\StaticPropertyFetch(new Name('self'), 'declaredRoutes'), + $this->generateDeclaredRoutes($object, $context) + ), + ), + ) + )); + + return $constructor; + } + + /** + * Generates an AST node representing an array of defined routes + * together with the routes properties (e.g. requirements). + * + * @return Expr\Array_ + */ + private function generateDeclaredRoutes($object, array $context) + { + $routes = array(); + foreach ($object->all() as $name => $route) { + $compiledRoute = $route->compile(); + + $properties = array(); + $properties[] = $compiledRoute->getVariables(); + $properties[] = $route->getDefaults(); + $properties[] = $route->getRequirements(); + $properties[] = $compiledRoute->getTokens(); + $properties[] = $compiledRoute->getHostTokens(); + $properties[] = $route->getSchemes(); + + $routes[$name] = $properties; + } + + return AstHelper::value($routes); + } + + /** + * Generates an AST node representing the `generate` method that implements the UrlGeneratorInterface. + * + * @return string PHP code + */ + private function generateGenerateMethod() + { + $generateMethod = $this->factory + ->method('generate') + ->makePublic() + ->addParam($this->factory->param('name')) + ->addParam($this->factory->param('parameters')->setDefault(array())) + ->addParam($this->factory->param('referenceType')->setDefault(UrlGenerator::ABSOLUTE_PATH)); + + $exception = RouteNotFoundException::class; + $code = <<doGenerate(\$variables, \$defaults, \$requirements, \$tokens, \$parameters, \$name, \$referenceType, \$hostTokens, \$requiredSchemes); +EOF; + + foreach (AstHelper::raw($code) as $stmt) { + $generateMethod->addStmt($stmt); + } + + return $generateMethod; + } +} diff --git a/src/Symfony/Component/Routing/Generator/Dumper/PhpGeneratorDumper.php b/src/Symfony/Component/Routing/Generator/Dumper/PhpGeneratorDumper.php index 9fd35ea112559..cc48ff05b39d3 100644 --- a/src/Symfony/Component/Routing/Generator/Dumper/PhpGeneratorDumper.php +++ b/src/Symfony/Component/Routing/Generator/Dumper/PhpGeneratorDumper.php @@ -11,6 +11,9 @@ namespace Symfony\Component\Routing\Generator\Dumper; +use Symfony\Component\AstGenerator\Util\AstHelper; +use Symfony\Component\Routing\Generator\AstGenerator\UrlGeneratorGenerator; + /** * PhpGeneratorDumper creates a PHP class able to generate URLs for a given set of routes. * @@ -33,91 +36,10 @@ class PhpGeneratorDumper extends GeneratorDumper */ public function dump(array $options = array()) { - $options = array_merge(array( - 'class' => 'ProjectUrlGenerator', - 'base_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', - ), $options); - - return <<context = \$context; - \$this->logger = \$logger; - if (null === self::\$declaredRoutes) { - self::\$declaredRoutes = {$this->generateDeclaredRoutes()}; - } - } - -{$this->generateGenerateMethod()} -} - -EOF; - } - - /** - * Generates PHP code representing an array of defined routes - * together with the routes properties (e.g. requirements). - * - * @return string PHP code - */ - private function generateDeclaredRoutes() - { - $routes = "array(\n"; - foreach ($this->getRoutes()->all() as $name => $route) { - $compiledRoute = $route->compile(); - - $properties = array(); - $properties[] = $compiledRoute->getVariables(); - $properties[] = $route->getDefaults(); - $properties[] = $route->getRequirements(); - $properties[] = $compiledRoute->getTokens(); - $properties[] = $compiledRoute->getHostTokens(); - $properties[] = $route->getSchemes(); + $generator = new UrlGeneratorGenerator(); - $routes .= sprintf(" '%s' => %s,\n", $name, str_replace("\n", '', var_export($properties, true))); - } - $routes .= ' )'; - - return $routes; - } + $code = AstHelper::dump($generator->generate($this->getRoutes(), $options)); - /** - * Generates PHP code representing the `generate` method that implements the UrlGeneratorInterface. - * - * @return string PHP code - */ - private function generateGenerateMethod() - { - return <<<'EOF' - public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH) - { - if (!isset(self::$declaredRoutes[$name])) { - throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name)); - } - - list($variables, $defaults, $requirements, $tokens, $hostTokens, $requiredSchemes) = self::$declaredRoutes[$name]; - - return $this->doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, $requiredSchemes); - } -EOF; + return $code; } } From 791a9554e87355798c3c223a57b235c9e65e33f3 Mon Sep 17 00:00:00 2001 From: Ener-Getick Date: Sun, 7 Aug 2016 11:54:08 +0200 Subject: [PATCH 5/5] Focus on GeneratorAstGenerator --- .../{AstGenerator => Ast}/.gitignore | 0 src/Symfony/Component/Ast/AstDumper.php | 34 +++++ .../Component/{AstGenerator => Ast}/LICENSE | 0 src/Symfony/Component/Ast/NodeList.php | 49 +++++++ .../Component/{AstGenerator => Ast}/README.md | 0 .../{AstGenerator => Ast}/Util/AstHelper.php | 2 +- .../{AstGenerator => Ast}/composer.json | 16 +-- .../{AstGenerator => Ast}/phpunit.xml.dist | 0 .../AstGenerator/AstGeneratorChain.php | 66 --------- .../AstGenerator/AstGeneratorInterface.php | 39 ----- .../Exception/MissingContextException.php | 7 - .../Tests/AstGeneratorChainTest.php | 88 ------------ .../Tests/UniqueVariableScopeTest.php | 31 ---- .../AstGenerator/UniqueVariableScope.php | 40 ------ ...enerator.php => GeneratorAstGenerator.php} | 46 +++--- .../GeneratorAstGeneratorInterface.php | 37 +++++ .../Generator/Dumper/PhpGeneratorDumper.php | 11 +- .../Matcher/Dumper/PhpMatcherDumper.php | 40 +++++- .../Matcher/Generator/PhpMatcherGenerator.php | 133 ------------------ src/Symfony/Component/Routing/composer.json | 1 + 20 files changed, 188 insertions(+), 452 deletions(-) rename src/Symfony/Component/{AstGenerator => Ast}/.gitignore (100%) create mode 100644 src/Symfony/Component/Ast/AstDumper.php rename src/Symfony/Component/{AstGenerator => Ast}/LICENSE (100%) create mode 100644 src/Symfony/Component/Ast/NodeList.php rename src/Symfony/Component/{AstGenerator => Ast}/README.md (100%) rename src/Symfony/Component/{AstGenerator => Ast}/Util/AstHelper.php (98%) rename src/Symfony/Component/{AstGenerator => Ast}/composer.json (56%) rename src/Symfony/Component/{AstGenerator => Ast}/phpunit.xml.dist (100%) delete mode 100644 src/Symfony/Component/AstGenerator/AstGeneratorChain.php delete mode 100644 src/Symfony/Component/AstGenerator/AstGeneratorInterface.php delete mode 100644 src/Symfony/Component/AstGenerator/Exception/MissingContextException.php delete mode 100644 src/Symfony/Component/AstGenerator/Tests/AstGeneratorChainTest.php delete mode 100644 src/Symfony/Component/AstGenerator/Tests/UniqueVariableScopeTest.php delete mode 100644 src/Symfony/Component/AstGenerator/UniqueVariableScope.php rename src/Symfony/Component/Routing/Generator/AstGenerator/{UrlGeneratorGenerator.php => GeneratorAstGenerator.php} (78%) create mode 100644 src/Symfony/Component/Routing/Generator/AstGenerator/GeneratorAstGeneratorInterface.php delete mode 100644 src/Symfony/Component/Routing/Matcher/Generator/PhpMatcherGenerator.php diff --git a/src/Symfony/Component/AstGenerator/.gitignore b/src/Symfony/Component/Ast/.gitignore similarity index 100% rename from src/Symfony/Component/AstGenerator/.gitignore rename to src/Symfony/Component/Ast/.gitignore diff --git a/src/Symfony/Component/Ast/AstDumper.php b/src/Symfony/Component/Ast/AstDumper.php new file mode 100644 index 0000000000000..af5bd39d564a3 --- /dev/null +++ b/src/Symfony/Component/Ast/AstDumper.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Ast; + +use PhpParser\PrettyPrinterAbstract; +use PhpParser\PrettyPrinter\Standard; + +final class AstDumper +{ + private $printer; + + public function __construct(PrettyPrinterAbstract $printer = null) + { + if (null === $printer) { + $printer = new Standard(); + } + + $this->printer = $printer; + } + + public function dump(NodeList $nodeList) + { + return $this->printer->prettyPrintFile($nodeList->getNodes()); + } +} diff --git a/src/Symfony/Component/AstGenerator/LICENSE b/src/Symfony/Component/Ast/LICENSE similarity index 100% rename from src/Symfony/Component/AstGenerator/LICENSE rename to src/Symfony/Component/Ast/LICENSE diff --git a/src/Symfony/Component/Ast/NodeList.php b/src/Symfony/Component/Ast/NodeList.php new file mode 100644 index 0000000000000..9f8e08dc7f9a8 --- /dev/null +++ b/src/Symfony/Component/Ast/NodeList.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Ast; + +use PhpParser\Node; + +final class NodeList +{ + private $nodes = array(); + + /** + * @param Node[] $nodes + */ + public function __construct(array $nodes) + { + foreach ($nodes as $node) { + $this->addNode($node); + } + } + + /** + * @return Node[] + */ + public function getNodes() + { + return $this->nodes; + } + + public function addNode(Node $node) + { + $this->nodes[] = $node; + } + + public function append(NodeList $nodeList) + { + foreach ($nodeList->getNodes() as $node) { + $this->addNode($node); + } + } +} diff --git a/src/Symfony/Component/AstGenerator/README.md b/src/Symfony/Component/Ast/README.md similarity index 100% rename from src/Symfony/Component/AstGenerator/README.md rename to src/Symfony/Component/Ast/README.md diff --git a/src/Symfony/Component/AstGenerator/Util/AstHelper.php b/src/Symfony/Component/Ast/Util/AstHelper.php similarity index 98% rename from src/Symfony/Component/AstGenerator/Util/AstHelper.php rename to src/Symfony/Component/Ast/Util/AstHelper.php index c349902c1a963..32c0148f90efc 100644 --- a/src/Symfony/Component/AstGenerator/Util/AstHelper.php +++ b/src/Symfony/Component/Ast/Util/AstHelper.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\AstGenerator\Util; +namespace Symfony\Component\Ast\Util; use PhpParser\Node; use PhpParser\Node\Expr; diff --git a/src/Symfony/Component/AstGenerator/composer.json b/src/Symfony/Component/Ast/composer.json similarity index 56% rename from src/Symfony/Component/AstGenerator/composer.json rename to src/Symfony/Component/Ast/composer.json index a0889cd91d11e..fc8dbc2b0d8d2 100644 --- a/src/Symfony/Component/AstGenerator/composer.json +++ b/src/Symfony/Component/Ast/composer.json @@ -1,8 +1,8 @@ { - "name": "symfony/ast-generator", + "name": "symfony/ast", "type": "library", - "description": "Symfony component to generate AST from and to various others components", - "keywords": ["symfony", "ast", "generator"], + "description": "Symfony AST Component", + "keywords": ["symfony", "ast"], "homepage": "https://symfony.com", "license": "MIT", "authors": [ @@ -17,14 +17,10 @@ ], "require": { "php": ">=5.5.9", - "nikic/php-parser": "~2.0", - "symfony/property-info": "~2.8|~3.0" - }, - "require-dev": { - "symfony/serializer": "~2.8|~3.0" + "nikic/php-parser": "~2.0" }, "autoload": { - "psr-4": { "Symfony\\Component\\AstGenerator\\": "" }, + "psr-4": { "Symfony\\Component\\Ast\\": "" }, "exclude-from-classmap": [ "/Tests/" ] @@ -32,7 +28,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "3.2-dev" } } } diff --git a/src/Symfony/Component/AstGenerator/phpunit.xml.dist b/src/Symfony/Component/Ast/phpunit.xml.dist similarity index 100% rename from src/Symfony/Component/AstGenerator/phpunit.xml.dist rename to src/Symfony/Component/Ast/phpunit.xml.dist diff --git a/src/Symfony/Component/AstGenerator/AstGeneratorChain.php b/src/Symfony/Component/AstGenerator/AstGeneratorChain.php deleted file mode 100644 index 7efb980f6f288..0000000000000 --- a/src/Symfony/Component/AstGenerator/AstGeneratorChain.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\AstGenerator; - -/** - * Generator delegating the generation to a chain of generators. - * - * @author Joel Wurtz - */ -class AstGeneratorChain implements AstGeneratorInterface -{ - /** @var AstGeneratorInterface[] A list of generators */ - protected $generators; - - /** @var bool Whether the generation must return as soon as possible or use all generators, default to false */ - protected $returnOnFirst; - - public function __construct(array $generators = array(), $returnOnFirst = false) - { - $this->generators = $generators; - $this->returnOnFirst = $returnOnFirst; - } - - /** - * {@inheritdoc} - */ - public function generate($object, array $context = array()) - { - $nodes = array(); - - foreach ($this->generators as $generator) { - if ($generator instanceof AstGeneratorInterface && $generator->supportsGeneration($object)) { - $nodes = array_merge($nodes, $generator->generate($object, $context)); - - if ($this->returnOnFirst) { - return $nodes; - } - } - } - - return $nodes; - } - - /** - * {@inheritdoc} - */ - public function supportsGeneration($object) - { - foreach ($this->generators as $generator) { - if ($generator instanceof AstGeneratorInterface && $generator->supportsGeneration($object)) { - return true; - } - } - - return false; - } -} diff --git a/src/Symfony/Component/AstGenerator/AstGeneratorInterface.php b/src/Symfony/Component/AstGenerator/AstGeneratorInterface.php deleted file mode 100644 index 30abb726aacec..0000000000000 --- a/src/Symfony/Component/AstGenerator/AstGeneratorInterface.php +++ /dev/null @@ -1,39 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\AstGenerator; - -/** - * An AstGeneratorInterface is a contract to transform an object into an AST. - * - * @author Joel Wurtz - */ -interface AstGeneratorInterface -{ - /** - * Generate an object into an AST given a specific context. - * - * @param mixed $object Object to generate AST from - * @param array $context Context for the generator - * - * @return \PhpParser\Node[] An array of statements (AST Node) - */ - public function generate($object, array $context = array()); - - /** - * Check whether the given object is supported for generation by this generator. - * - * @param mixed $object Object to generate AST from - * - * @return bool - */ - public function supportsGeneration($object); -} diff --git a/src/Symfony/Component/AstGenerator/Exception/MissingContextException.php b/src/Symfony/Component/AstGenerator/Exception/MissingContextException.php deleted file mode 100644 index 1edae8bb1e89c..0000000000000 --- a/src/Symfony/Component/AstGenerator/Exception/MissingContextException.php +++ /dev/null @@ -1,7 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\AstGenerator\Tests; - -use Symfony\Component\AstGenerator\AstGeneratorChain; -use Symfony\Component\AstGenerator\AstGeneratorInterface; - -class AstGeneratorChainTest extends \PHPUnit_Framework_TestCase -{ - public function testEmpty() - { - $generator = new AstGeneratorChain(); - - $this->assertFalse($generator->supportsGeneration('dummy')); - $this->assertEmpty($generator->generate('dummy')); - } - - public function testSupports() - { - $generatorSub = $this->getGeneratorMock(true, array('ast')); - - $generator = new AstGeneratorChain(array($generatorSub)); - $this->assertTrue($generator->supportsGeneration('dummy')); - $this->assertEquals(array('ast'), $generator->generate('dummy')); - } - - public function testMultiSupports() - { - $generatorSub1 = $this->getGeneratorMock(true, array('ast1')); - $generatorSub2 = $this->getGeneratorMock(true, array('ast2')); - - $generator = new AstGeneratorChain(array($generatorSub1, $generatorSub2)); - $this->assertTrue($generator->supportsGeneration('dummy')); - $this->assertEquals(array('ast1', 'ast2'), $generator->generate('dummy')); - } - - public function testPartialSupports() - { - $generatorSub1 = $this->getGeneratorMock(true, array('ast1')); - $generatorSub2 = $this->getGeneratorMock(false); - - $generator = new AstGeneratorChain(array($generatorSub1, $generatorSub2)); - $this->assertTrue($generator->supportsGeneration('dummy')); - $this->assertEquals(array('ast1'), $generator->generate('dummy')); - } - - public function testMultiSupportsWithFirstReturn() - { - $generatorSub1 = $this->getGeneratorMock(true, array('ast1')); - $generatorSub2 = $this->getGeneratorMock(true, array('ast2')); - - $generator = new AstGeneratorChain(array($generatorSub1, $generatorSub2), true); - $this->assertTrue($generator->supportsGeneration('dummy')); - $this->assertEquals(array('ast1'), $generator->generate('dummy')); - } - - private function getGeneratorMock($support, $return = null) - { - $generatorSub = $this->getMockBuilder(AstGeneratorInterface::class)->getMock(); - $generatorSub - ->expects($this->any()) - ->method('supportsGeneration') - ->with('dummy') - ->willReturn($support); - if (null === $return) { - $generatorSub - ->expects($this->never()) - ->method('generate'); - } else { - $generatorSub - ->expects($this->any()) - ->method('generate') - ->with('dummy', array()) - ->willReturn($return); - } - - return $generatorSub; - } -} diff --git a/src/Symfony/Component/AstGenerator/Tests/UniqueVariableScopeTest.php b/src/Symfony/Component/AstGenerator/Tests/UniqueVariableScopeTest.php deleted file mode 100644 index f46c5e4e5070d..0000000000000 --- a/src/Symfony/Component/AstGenerator/Tests/UniqueVariableScopeTest.php +++ /dev/null @@ -1,31 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\AstGenerator\Tests; - -use Symfony\Component\AstGenerator\UniqueVariableScope; - -class UniqueVariableScopeTest extends \PHPUnit_Framework_TestCase -{ - public function testUniqueVariable() - { - $uniqueVariableScope = new UniqueVariableScope(); - - $name = $uniqueVariableScope->getUniqueName('name'); - $this->assertEquals('name', $name); - - $name = $uniqueVariableScope->getUniqueName('name'); - $this->assertEquals('name_1', $name); - - $name = $uniqueVariableScope->getUniqueName('name'); - $this->assertEquals('name_2', $name); - } -} diff --git a/src/Symfony/Component/AstGenerator/UniqueVariableScope.php b/src/Symfony/Component/AstGenerator/UniqueVariableScope.php deleted file mode 100644 index eed95c9a0c64a..0000000000000 --- a/src/Symfony/Component/AstGenerator/UniqueVariableScope.php +++ /dev/null @@ -1,40 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\AstGenerator; - -/** - * Allow to get a unique variable name for a scope (like a method). - */ -class UniqueVariableScope -{ - private $registry = array(); - - /** - * Return an unique name for a variable. - * - * @param string $name Name of the variable - * - * @return string if not found return the $name given, if not return the name suffixed with a number - */ - public function getUniqueName($name) - { - if (!isset($this->registry[$name])) { - $this->registry[$name] = 0; - - return $name; - } - - ++$this->registry[$name]; - - return sprintf('%s_%s', $name, $this->registry[$name]); - } -} diff --git a/src/Symfony/Component/Routing/Generator/AstGenerator/UrlGeneratorGenerator.php b/src/Symfony/Component/Routing/Generator/AstGenerator/GeneratorAstGenerator.php similarity index 78% rename from src/Symfony/Component/Routing/Generator/AstGenerator/UrlGeneratorGenerator.php rename to src/Symfony/Component/Routing/Generator/AstGenerator/GeneratorAstGenerator.php index bf2619d8dbf14..10fcc33186f8e 100644 --- a/src/Symfony/Component/Routing/Generator/AstGenerator/UrlGeneratorGenerator.php +++ b/src/Symfony/Component/Routing/Generator/AstGenerator/GeneratorAstGenerator.php @@ -18,63 +18,57 @@ use PhpParser\Node\Stmt; use PhpParser\Node\Expr; use Psr\Log\LoggerInterface; -use Symfony\Component\AstGenerator\AstGeneratorInterface; -use Symfony\Component\AstGenerator\Util\AstHelper; +use Symfony\Component\Ast\NodeList; +use Symfony\Component\Ast\Util\AstHelper; use Symfony\Component\Routing\Exception\RouteNotFoundException; use Symfony\Component\Routing\Generator\UrlGenerator; use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\RouteCollection; /** * @author Guilhem N. */ -class UrlGeneratorGenerator implements AstGeneratorInterface +final class GeneratorAstGenerator implements GeneratorAstGeneratorInterface { - public function __construct() + public function __construct(RouteCollection $routes) { + $this->routes = $routes; $this->factory = new BuilderFactory(); } /** * {@inheritdoc} */ - public function generate($object, array $context = array()) + public function generate(array $options = array()) { - $context = array_replace(array( + $options = array_replace(array( 'class' => 'ProjectUrlGenerator', 'base_class' => UrlGenerator::class, - ), $context); + ), $options); - return array($this->generateClass($object, $context)->getNode()); + return new NodeList(array($this->generateClass($options)->getNode())); } - /** - * {@inheritdoc} - */ - public function supportsGeneration($object) - { - return is_string($object) && class_exists($object); - } - - private function generateClass($object, array $context) + private function generateClass(array $options) { $docComment = <<factory->class($context['class']) + return $this->factory->class($options['class']) ->setDocComment($docComment) - ->extend($context['base_class']) + ->extend($options['base_class']) ->addStmt($this->factory->property('declaredRoutes')->makePrivate()->makeStatic()) - ->addStmt($this->generateConstructor($object, $context)) - ->addStmt($this->generateGenerateMethod($object, $context)); + ->addStmt($this->generateConstructor()) + ->addStmt($this->generateGenerateMethod()); } - private function generateConstructor($object, array $context) + private function generateConstructor() { $constructor = $this->factory->method('__construct') ->makePublic() @@ -98,7 +92,7 @@ private function generateConstructor($object, array $context) 'stmts' => array( new Expr\Assign( new Expr\StaticPropertyFetch(new Name('self'), 'declaredRoutes'), - $this->generateDeclaredRoutes($object, $context) + $this->generateDeclaredRoutes() ), ), ) @@ -113,10 +107,10 @@ private function generateConstructor($object, array $context) * * @return Expr\Array_ */ - private function generateDeclaredRoutes($object, array $context) + private function generateDeclaredRoutes() { $routes = array(); - foreach ($object->all() as $name => $route) { + foreach ($this->routes->all() as $name => $route) { $compiledRoute = $route->compile(); $properties = array(); diff --git a/src/Symfony/Component/Routing/Generator/AstGenerator/GeneratorAstGeneratorInterface.php b/src/Symfony/Component/Routing/Generator/AstGenerator/GeneratorAstGeneratorInterface.php new file mode 100644 index 0000000000000..725b5b4b23e4a --- /dev/null +++ b/src/Symfony/Component/Routing/Generator/AstGenerator/GeneratorAstGeneratorInterface.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator\AstGenerator; + +use PhpParser\Node\Name; +use PhpParser\Node\Param; +use Symfony\Component\Ast\NodeList; + +/** + * @author Guilhem N. + */ +interface GeneratorAstGeneratorInterface +{ + /** + * Dumps a set of routes to an ast representation that + * can then be used to generate a URL of such a route. + * + * Available options: + * + * * class: The class name + * * base_class: The base class name + * + * @param array $options An array of options + * + * @return NodeList + */ + public function generate(array $options = array()); +} diff --git a/src/Symfony/Component/Routing/Generator/Dumper/PhpGeneratorDumper.php b/src/Symfony/Component/Routing/Generator/Dumper/PhpGeneratorDumper.php index cc48ff05b39d3..f49f3bc6d6669 100644 --- a/src/Symfony/Component/Routing/Generator/Dumper/PhpGeneratorDumper.php +++ b/src/Symfony/Component/Routing/Generator/Dumper/PhpGeneratorDumper.php @@ -11,8 +11,8 @@ namespace Symfony\Component\Routing\Generator\Dumper; -use Symfony\Component\AstGenerator\Util\AstHelper; -use Symfony\Component\Routing\Generator\AstGenerator\UrlGeneratorGenerator; +use Symfony\Component\Ast\AstDumper; +use Symfony\Component\Routing\Generator\AstGenerator\GeneratorAstGenerator; /** * PhpGeneratorDumper creates a PHP class able to generate URLs for a given set of routes. @@ -36,10 +36,9 @@ class PhpGeneratorDumper extends GeneratorDumper */ public function dump(array $options = array()) { - $generator = new UrlGeneratorGenerator(); + $dumper = new AstDumper(); + $generator = new GeneratorAstGenerator($this->getRoutes()); - $code = AstHelper::dump($generator->generate($this->getRoutes(), $options)); - - return $code; + return $dumper->dump($generator->generate($options)); } } diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php index c7e786c7ffeb6..aa7df8a78b5ba 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php @@ -11,10 +11,8 @@ namespace Symfony\Component\Routing\Matcher\Dumper; -use Symfony\Component\AstGenerator\Util\AstHelper; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; -use Symfony\Component\Routing\Matcher\Generator\PhpMatcherGenerator; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; @@ -33,7 +31,6 @@ class PhpMatcherDumper extends MatcherDumper * @var ExpressionFunctionProviderInterface[] */ private $expressionLanguageProviders = array(); - private $generator; /** * Dumps a set of routes to a PHP class. @@ -49,9 +46,42 @@ class PhpMatcherDumper extends MatcherDumper */ public function dump(array $options = array()) { - $generator = new PhpMatcherGenerator(); + $options = array_replace(array( + 'class' => 'ProjectUrlMatcher', + 'base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', + ), $options); - return AstHelper::dump($generator->generate($this->getRoutes(), $options)); + // trailing slash support is only enabled if we know how to redirect the user + $interfaces = class_implements($options['base_class']); + $supportsRedirections = isset($interfaces['Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcherInterface']); + + return <<context = \$context; + } + +{$this->generateMatchMethod($supportsRedirections)} +} + +EOF; } public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) diff --git a/src/Symfony/Component/Routing/Matcher/Generator/PhpMatcherGenerator.php b/src/Symfony/Component/Routing/Matcher/Generator/PhpMatcherGenerator.php deleted file mode 100644 index e0b8ec6cd4e27..0000000000000 --- a/src/Symfony/Component/Routing/Matcher/Generator/PhpMatcherGenerator.php +++ /dev/null @@ -1,133 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Routing\Matcher\Generator; - -use PhpParser\BuilderFactory; -use PhpParser\Comment; -use PhpParser\Node\Arg; -use PhpParser\Node\Name; -use PhpParser\Node\Param; -use PhpParser\Node\Stmt; -use PhpParser\Node\Expr; -use PhpParser\Node\Scalar; -use Symfony\Component\AstGenerator\AstGeneratorInterface; -use Symfony\Component\AstGenerator\Util\AstHelper; -use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; -use Symfony\Component\Routing\Exception\MethodNotAllowedException; -use Symfony\Component\Routing\Exception\ResourceNotFoundException; -use Symfony\Component\Routing\RequestContext; - -/** - * @author Guilhem N. - */ -class PhpMatcherGenerator implements AstGeneratorInterface -{ - private $expressionLanguage; - - /** - * @var ExpressionFunctionProviderInterface[] - */ - private $expressionLanguageProviders = array(); - private $factory; - - public function __construct() - { - $this->factory = new BuilderFactory(); - } - - /** - * {@inheritdoc} - */ - public function generate($object, array $context = array()) - { - $context = array_replace(array( - 'class' => 'ProjectUrlMatcher', - 'base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', - ), $context); - - // trailing slash support is only enabled if we know how to redirect the user - $interfaces = class_implements($context['base_class']); - $supportsRedirections = isset($interfaces['Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcherInterface']); - - return array($this->generateClass($object, $context)->getNode()); - } - - /** - * {@inheritdoc} - */ - public function supportsGeneration($object) - { - return is_string($object) && class_exists($object); - } - - private function generateClass($object, array $context) - { - $docComment = <<factory->class($context['class']) - ->setDocComment($docComment) - ->extend($context['base_class']) - ->addStmt($this->factory->property('context')->makePrivate()) - ->addStmt( - $this->factory->method('__construct') - ->makePublic() - ->addParam($this->factory->param('context')->setTypeHint(RequestContext::class)) - ->addStmt( - // $this->context = $context - new Expr\Assign( - new Expr\PropertyFetch(new Expr\Variable('this'), 'context'), - new Expr\Variable('context') - ) - ) - ) - ->addStmt($this->generateMatchMethod($object, $context)); - } - - private function generateMatchMethod($object, array $context) - { - $method = $this->factory - ->method('match') - ->makePublic() - ->addParam($this->factory->param('pathinfo')) - ->addStmt(new Expr\Assign(new Expr\Variable('allow'), AstHelper::value(array()))) - ->addStmt(new Expr\Assign( - new Expr\Variable('pathinfo'), - new Expr\FuncCall(new Name('rawurldecode'), array( - new Arg(new Expr\Variable('pathinfo')), - )) - )) - ->addStmt(new Expr\Assign(new Expr\Variable('context'), new Expr\PropertyFetch(new Expr\Variable('this'), 'context'))) - ->addStmt(new Expr\Assign(new Expr\Variable('request'), new Expr\PropertyFetch(new Expr\Variable('this'), 'request'))); - - $method->addStmt(new Stmt\Throw_(new Expr\Ternary( - new Expr\BinaryOp\Smaller( - new Scalar\LNumber(0), - new Expr\FuncCall(new Name('count'), array(new Arg(new Expr\Variable('allow')))) - ), - new Expr\New_(new Name(MethodNotAllowedException::class), array( - new Arg(new Expr\FuncCall(new Name('array_unique'), array( - new Arg(new Expr\Variable('allow')), - ))), - )), - new Expr\New_(new Name(ResourceNotFoundException::class)) - ))); - - return $method; - } -} diff --git a/src/Symfony/Component/Routing/composer.json b/src/Symfony/Component/Routing/composer.json index 156bbecb2a3bd..fb59944ae46b6 100644 --- a/src/Symfony/Component/Routing/composer.json +++ b/src/Symfony/Component/Routing/composer.json @@ -23,6 +23,7 @@ "symfony/http-foundation": "~2.8|~3.0", "symfony/yaml": "~2.8|~3.0", "symfony/expression-language": "~2.8|~3.0", + "symfony/ast": "~3.2", "doctrine/annotations": "~1.0", "doctrine/common": "~2.2", "psr/log": "~1.0"