Skip to content

[RFC][Ast][Routing] Use Ast to generate the UrlGenerator #19555

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from

Conversation

GuilhemN
Copy link
Contributor

@GuilhemN GuilhemN commented Aug 6, 2016

Q A
Branch? "master"
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? no
Fixed tickets
License MIT
Doc PR

This PR applies the bases proposed in #17516 to the Routing component.
Generating a Normalizer is complicated (i think that's worth it but that's not ideal to introduce a new component) so I decided to try to generate the cached matcher/generator of the routing component with AST.
I created an util class to easily convert simple php code to an ast node (convenient when you have a part of your code which never changes dynamically).
I didn't finish this PR because I'm waiting for the core team approval.

The main advantages of using ast I see:

  • Coding standards are managed by the code generator
  • It's easier to generate complex code
  • The user can optimize/update and do whatever he wants on the ast without dirty hacks

ping @joelwurtz

@carsonbot carsonbot added Status: Needs Review RFC RFC = Request For Comments (proposals about features that you want to be discussed) Routing Feature labels Aug 6, 2016
* @return Expr
*/
public static function value($value)
{
Copy link
Contributor

@joelwurtz joelwurtz Aug 6, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not directly convert the value to ast with the parser, by using var_export (as all the logic is already handled) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean passing the raw value to the Nodes directly ? This is managed by the Builders but not by the Exprs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

@GuilhemN GuilhemN Aug 6, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure it would support well every edge cases such as non-associative array (var_export always prints the keys if i'm not wrong).
BTW this part of the code is extracted from nikic's library.

*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
class AstGeneratorChain implements AstGeneratorInterface
Copy link
Contributor

@unkind unkind Aug 6, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this class is open for inheritance (not marked as final)?
Same question about UrlGeneratorGenerator, PhpMatcherGenerator, UniqueVariableScope.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this class and UniqueVariableScope i agree it would make sense even if there aren't that much final classes in symfony.
But for the routing generators i would like to allow the user to easily hook into them and it could pass by inheritance.

Copy link
Contributor

@unkind unkind Aug 6, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But for the routing generators i would like to allow the user to easily hook into them and it could pass by inheritance.

How? Those classes have only private members and 2 public methods. And those 2 public methods can be easily changed with composition if developer really needs it.
This is weird Symfony internal rule: to keep members private by default, but in the same time do not require final: there is no point to open them for inheritance then.

If you find valid reason to extend the classes, you can provide sane extension point(s) instead of inheritance: strategies, events, hooks, etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now they are private but it may change. But you're right the class should be final as long as there are no extensions possible, i'll change ASAP.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now they are private but it may change.

Sure, but instead of changing private properties to protected, you probably can introduce better extension point.

Copy link
Contributor

@ro0NL ro0NL Aug 6, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thx. I wasnt really opinionated on it, yet. https://ocramius.github.io/blog/when-to-declare-classes-final/ is also a nice read. Anyway, it looks good practice to me.. not sure if it's common enough (yet) for symfony though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ener-Getick then maybe it would be better to lock them (final and @internal) first no?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once you fix it, our comments will disappear :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@theofidry i'm using my phone tonight :P

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good thing, you shouldn't work a saturday evening anyway ;)

@theofidry
Copy link
Contributor

@Ener-Getick looking at it most of it comes from #17516 and then you apply it to the routing component. To keep things smaller, wouldn't it be better to finish #17516 rather?

@GuilhemN
Copy link
Contributor Author

GuilhemN commented Aug 6, 2016

@theofidry the component itself is more or less ready but it isn't especially useful if we don't use it in symfony.
And generating normalizers is complicated as the serializer provides many features so i thought it would be better to do something smaller as a first step.

@theofidry
Copy link
Contributor

the component itself is more or less ready but it isn't especially useful if we don't use it in symfony

Then #17516 could be merged somehow no? It would need to be locked first (finals and @internal), but polishing the component will come with more usage (like this PR).

But I'm saying that to simplify this PR, if you feel it's not needed it's alright.

@GuilhemN
Copy link
Contributor Author

GuilhemN commented Aug 6, 2016

Some parts of #17516 aren't ready to be merged and would make sf tests fail so i don't think it would be accepted, even tagged as @internal

->addStmt($this->generateMatchMethod($object, $context));
}

private function generateMatchMethod($object, array $context)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both arguments are unused.

@joelwurtz
Copy link
Contributor

joelwurtz commented Aug 7, 2016

While the AstGeneratorInterface make sense for generating normalizer and types (as we have different kinds of objects and metadata), it does not make sense here like @unkind said, as we always have the same input.

Given this, the ASTGenerator Component does not add a value in this context (just use php-parser).

I have begin the work on the AstGeneratorComponent for the purpose of generating better and faster normalizer like in #17516

Then the idea comes to also apply AST generation to components already generating code (like the router or the DIC).

Seeing this pr from @Ener-Getick and comments made me realise that we don't need to do an ASTGenerator Component, but instead separate the generation of each component into itself.
(So the AstGeneratorInterface will be renamed and include in the Serializer Component).

However this does not mean we should stop working on ASTGenerator Component, instead we must renaming it to ASTComponent which will provides utility class that works on a AST (a.k.a. NodeList):

  • Debug AST Generation
  • AST Diff (useful for debug)
  • AST Optimization
  • Helper class ?

Also we should provide a unified way of writing AST into the differents components (not speaking about interface, but more about the choices and being consistent of the way of doing across components)

@GuilhemN
Copy link
Contributor Author

GuilhemN commented Aug 7, 2016

@joelwurtz I agree the ast component would indeed be more valuable like that.
It could provide a printer compliant to symfony practices and util classes allowing to easily generate code.
I will do that ASAP :)

@joelwurtz
Copy link
Contributor

No need for a printer there is already php cs fixer :)

@GuilhemN
Copy link
Contributor Author

GuilhemN commented Aug 7, 2016

@joelwurtz it's not part of symfony and won't be applied to the code cached anyway so i think it makes sense to have our own printer (based on the one included in PHP-Parser).
At least, we should have one wrapping the default printer to support NodeList imo:

class AstDumper
{
    public function __construct(PrettyPrinterAbstract $basePrinter = null);
    public function dump(NodeList $nodes);
}

@GuilhemN GuilhemN changed the title [RFC][AstGenerator][Routing] Convert the PhpMatcherDumper to an AstGeneratorInterface [RFC][Ast][Routing] Use Ast to generate the UrlGenerator Aug 7, 2016
/**
* {@inheritdoc}
*/
public function generate(array $options = array())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ironically, I'd swap options and RouteCollection.

We have RouteCollection and we want to generate AST for this route collection. It looks natural to pass it as an argument of the generate($routeCollection) method, doesn't it?

Copy link
Contributor Author

@GuilhemN GuilhemN Aug 7, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reused the current signature. I have no strong opinion about passing the route collection but it looks weird to me to not know the name of the class that will be generated (most of the time the caller doesn't instantiate the class it calls).

Copy link
Contributor

@unkind unkind Aug 7, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By analogy, why do you pass NodeList in the dump method instead of constructor in example above (AstDumper)? Does it look weird that you don't know printer parameters that will be used?

You can pass options as 2nd and 3rd arguments if you think you need to pass them explicitly:

public function generate(
    RouteCollection $routeCollection,
    string $class = 'ProjectUrlGenerator',
    string $baseClass = UrlGenerator::class
) 

If you take a look on your interface below, it just says that it generates AST for routes, but it wonders me which routes description is talking about?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me this dumper can be used to print different files (you can use the same config to print them without combining them) this is why i think the ast should be passed in the dump method.

About the generate method that's true, i only have to find how it is used exactly in the framework to ensure we can prefer explicitness without creating issues.

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Aug 9, 2016

I don't see much value in using an AST-based code generator here.
The resulting code will be the same and it adds complexity (thus harder to contribute/fix) for an already solved problem.
I tend to agree with @joelwurtz in that if the envisioned ast generator is targeted at generating optimized normalizers, it should be in the Serializer component directly.
We don't need a generic code generator component IMHO.
Thus 👎

@GuilhemN
Copy link
Contributor Author

GuilhemN commented Aug 9, 2016

@nicolas-grekas i was thinking allowing the user to hook into the process and eventually change the generate method behavior but maybe that shouldn't be allowed (the user should create his own generator).
BTW we can decrease this code complexity by parsing a template with PHP-parser. We would still be able to benefit of a code optimization (or rewriting for supporting features of newer php versions) without the complexity but in that case we will have to create a component managing this optimization part.

So maybe after all that's better to begin with something where ast brings a clear improvement and then we will see if it should be extanded to other components or not.
I'm closing this one for now and will maybe reopen it if #17516 is well accepted.

@GuilhemN GuilhemN closed this Aug 9, 2016
@GuilhemN GuilhemN deleted the DUMPERGENERATOR branch August 9, 2016 15:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature RFC RFC = Request For Comments (proposals about features that you want to be discussed) Routing Status: Needs Review
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants