Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@mnapoli
Copy link
Member

@mnapoli mnapoli commented Jun 11, 2017

Factory definitions are now dumped into the compiled container!

This follows #494 where all definitions but factories, decorators and wildcards were compiled. Decorators and wildcards are still not compiled.

Compiling factories means also compiling closures. That was actually much more doable than I thought, I've used Roave/BetterReflection (thanks @asgrim and @Ocramius!)

The main downside of the whole pull request is the number of dependencies that BetterReflection brings:

  • Installing symfony/polyfill-util (v1.4.0) Loading from cache
  • Installing symfony/polyfill-php56 (v1.4.0) Loading from cache
  • Installing nikic/php-parser (v2.1.1) Loading from cache
  • Installing jeremeamia/superclosure (2.3.0) Loading from cache
  • Installing zendframework/zend-eventmanager (3.1.0) Loading from cache
  • Installing zendframework/zend-code (3.1.0) Loading from cache
  • Installing phpdocumentor/reflection-common (1.0) Loading from cache
  • Installing phpdocumentor/type-resolver (0.2.1) Loading from cache
  • Installing phpdocumentor/reflection-docblock (2.0.5) Loading from cache
  • Installing roave/better-reflection (1.2.0) Loading from cache

Maybe we could get on with superclosure only. Or else make compiling closures optional (only compile them if BetterReflection is installed…). Also I've seen that there is a v2.0 of BetterReflection in development but it requires 7.1, I have no idea for how long the v1 will be supported (e.g. will it support PHP 7.2). PHP-DI could upgrade to 7.1 at the end of this year but that's not for sure yet.

Performance improvements: -20% on the factory.php benchmark, profile, which is very good for a micro-benchmark IMO. I'll test all that in larger scenarios for the complete 6.0 release but it's safe to say this PR is an improvement.

There are also a lot of micro/not-so-micro optimisations possible here in the future, especially because there are a lot of parameter-guessing done at runtime (you can inject dependencies into the factories and PHP-DI figures it out with type-hints). https://github.com/PHP-DI/Invoker could be compiled in the future (optionally), and simpler scenarios (classic closures) could be much more optimized than that. Work for later! :)

Because of closures, some scenarios are explicitly not supported:

  • you should not use $this inside closures
  • you should not import variables inside the closure using the use keyword, like in function () use ($foo) { ...
  • you should not define multiple closures on the same line

But those scenarios don't make sense in container factories written in array config so that's good!

@mnapoli mnapoli added this to the 6.0 milestone Jun 11, 2017
@mnapoli mnapoli force-pushed the compile-factories branch 2 times, most recently from d8c56b0 to 6ec16df Compare June 11, 2017 19:34
Factory definitions are now dumped into the compiled container!

This follows #494 where all definitions but factories, decorators and wildcards were compiled. Decorators and wildcards are still not compiled.

Compiling factories means also compiling closures. That was actually much more doable than I thought, I've used [Roave/BetterReflection](https://github.com/Roave/BetterReflection) (thanks asgrim and ocramius!)

The main downside of the whole pull request is the number of dependencies that BetterReflection brings:

  - Installing symfony/polyfill-util (v1.4.0) Loading from cache
  - Installing symfony/polyfill-php56 (v1.4.0) Loading from cache
  - Installing nikic/php-parser (v2.1.1) Loading from cache
  - Installing jeremeamia/superclosure (2.3.0) Loading from cache
  - Installing zendframework/zend-eventmanager (3.1.0) Loading from cache
  - Installing zendframework/zend-code (3.1.0) Loading from cache
  - Installing phpdocumentor/reflection-common (1.0) Loading from cache
  - Installing phpdocumentor/type-resolver (0.2.1) Loading from cache
  - Installing phpdocumentor/reflection-docblock (2.0.5) Loading from cache
  - Installing roave/better-reflection (1.2.0) Loading from cache

Maybe we could get on with superclosure only. Or else make compiling closures optional (only compile them if BetterReflection is installed…). Also I've seen that there is a v2.0 of BetterReflection in development [but it requires 7.1](https://github.com/Roave/BetterReflection#changes-in-br-20), I have no idea for how long the v1 will be supported (e.g. will it support PHP 7.2). PHP-DI could upgrade to 7.1 at the end of this year but that's not for sure yet.

Performance improvements: -20% on the factory.php benchmark, [profile](https://blackfire.io/profiles/compare/6692ca3e-521a-49df-96c6-1bf9833c8209/graph), which is very good for a micro-benchmark IMO. I'll test all that in larger scenarios for the complete 6.0 release but it's safe to say this PR is an improvement.

There are also a lot of micro/not-so-micro optimisations possible here in the future, especially because there are a lot of parameter-guessing done at runtime (you can inject dependencies into the factories and PHP-DI figures it out with type-hints). https://github.com/PHP-DI/Invoker could be compiled in the future (optionally), and simpler scenarios (classic closures) could be much more optimized than that. Work for later! :)

Because of closures, some scenarios are explicitly not supported:

- you should not use `$this` inside closures
- you should not import variables inside the closure using the `use` keyword, like in `function () use ($foo) { ...`
- you should not define multiple closures on the same line

But those scenarios don't make sense in container factories written in array config so that's good!
Copy link

@Ocramius Ocramius left a comment

Choose a reason for hiding this comment

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

Good work! Unsure about the dependencies - it is indeed a large dependency graph, but I also don't have a good solution to fix that, sorry :-\

composer.json Outdated
},
"config": {
"platform": {
"php": "7.0"

Choose a reason for hiding this comment

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

uhmmmmmmmm

Copy link
Member Author

Choose a reason for hiding this comment

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

What was I trying to do 🤔

protected function resolveFactory($callable, $entryName, array $extraParameters = [])
{
// Initialize the factory resolver
if (! $this->factoryInvoker) {

Choose a reason for hiding this comment

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

Make this a private getter

Copy link
Member Author

Choose a reason for hiding this comment

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

break;
case $definition instanceof FactoryDefinition:
$value = $definition->getCallable();

Choose a reason for hiding this comment

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

Method should not be static

Choose a reason for hiding this comment

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

Variable name does not reflect its meaning: this is not a name, it's a bool

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure I should check for the staticness, PHP already throws a warning at compile time and it still works so I don't think PHP-DI should choose to make this stricter.

src/Compiler.php Outdated
$value = $definition->getCallable();

// Custom error message to help debugging
$isInvokableClassName = is_string($value) && class_exists($value) && method_exists($value, '__invoke');

Choose a reason for hiding this comment

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

You should really start using named constructors for exceptions :-P

Copy link
Member Author

Choose a reason for hiding this comment

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

I do for common ones but this error message is very specific to that line so moving it elsewhere would just spread logic around (cohesion ftw)

src/Compiler.php Outdated
if ($value instanceof \Closure) {
try {
$reflection = ReflectionFunction::createFromClosure($value);
} catch (TwoClosuresOneLine $e) {

Choose a reason for hiding this comment

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

Do you really want to catch this? Also, make sure to re-throw the previous exception if you do.

Copy link
Member Author

Choose a reason for hiding this comment

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

I want to hide the implementation details of the closure compiler, but good catch on forwarding the previous exception.


return "[\n$value ]";
}

Choose a reason for hiding this comment

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

Move to private method

src/Compiler.php Outdated
$ast = $reflection->getAst();

// Force all closures to be static (add the `static` keyword), i.e. they can't use
// $this, which makes sense since their code is copied into another class.

Choose a reason for hiding this comment

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

You are using the API from PHP-Parser here, so you should add that to your composer.json too


if ($this->compileToFile) {
$containerClass = (new Compiler)->compile($source, $this->compileToFile);
$autowiringEnabled = $this->useAutowiring || $this->useAnnotations;

Choose a reason for hiding this comment

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

Variable is redundant

@Ocramius
Copy link

I have no idea for how long the v1 will be supported (e.g. will it support PHP 7.2). PHP-DI could upgrade to 7.1 at the end of this year but that's not for sure yet.

You'll get security support, no worries :-)

Copy link
Member Author

@mnapoli mnapoli left a comment

Choose a reason for hiding this comment

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

You'll get security support, no worries :-)

Nice to know!

Thanks for the review!

composer.json Outdated
},
"config": {
"platform": {
"php": "7.0"
Copy link
Member Author

Choose a reason for hiding this comment

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

What was I trying to do 🤔

protected function resolveFactory($callable, $entryName, array $extraParameters = [])
{
// Initialize the factory resolver
if (! $this->factoryInvoker) {
Copy link
Member Author

Choose a reason for hiding this comment

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

src/Compiler.php Outdated
if ($value instanceof \Closure) {
try {
$reflection = ReflectionFunction::createFromClosure($value);
} catch (TwoClosuresOneLine $e) {
Copy link
Member Author

Choose a reason for hiding this comment

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

I want to hide the implementation details of the closure compiler, but good catch on forwarding the previous exception.

break;
case $definition instanceof FactoryDefinition:
$value = $definition->getCallable();

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure I should check for the staticness, PHP already throws a warning at compile time and it still works so I don't think PHP-DI should choose to make this stricter.

src/Compiler.php Outdated
$value = $definition->getCallable();

// Custom error message to help debugging
$isInvokableClassName = is_string($value) && class_exists($value) && method_exists($value, '__invoke');
Copy link
Member Author

Choose a reason for hiding this comment

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

I do for common ones but this error message is very specific to that line so moving it elsewhere would just spread logic around (cohesion ftw)

mnapoli added 2 commits June 30, 2017 23:07
# Conflicts:
#	src/Compiler.php
#	src/ContainerBuilder.php
#	tests/IntegrationTest/CompiledContainerTest.php
@mnapoli
Copy link
Member Author

mnapoli commented Jun 30, 2017

Less Composer dependencies are now installed:

  • Installing symfony/polyfill-util (v1.4.0): Loading from cache
  • Installing symfony/polyfill-php56 (v1.4.0): Loading from cache
  • Installing nikic/php-parser (v3.0.6): Loading from cache
  • Installing jeremeamia/superclosure (2.3.0): Loading from cache

@Ocramius
Copy link

Ocramius commented Jul 2, 2017

@mnapoli would it be a good idea to have the factory compiling split out and handled as a dev dependency by consumer projects? Just wondering/rambling, not necessarily feasible or relevant.

@mnapoli
Copy link
Member Author

mnapoli commented Jul 2, 2017

That could be a good idea indeed (considering the whole compilation step to make sense only in dev env, not prod) but I'd like to keep the whole thing simple (simply calling a method on the ContainerBuilder to compile the container is nice). The 4 extra dependencies are OK I think.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants