Skip to content

Compile recursively all references found during compilation#566

Merged
mnapoli merged 11 commits intomasterfrom
compile-recursively
Jan 14, 2018
Merged

Compile recursively all references found during compilation#566
mnapoli merged 11 commits intomasterfrom
compile-recursively

Conversation

@mnapoli
Copy link
Copy Markdown
Member

@mnapoli mnapoli commented Jan 9, 2018

Work in progress

Fixes #565

This pull request includes #564 to allow testing will all the performance improvements.

TODO:

  • Recursively compile definitions
  • Tests

…rmances

This restores some kind of cache like there was in PHP-DI 4 and 5 (which was removed during v6's development). However this is not a full restore, this new cache is very simple and targeted at specific use cases.

Related to #543 and #547.

Compiling the container is the most efficient solution, but it has some limits. The following cases are not optimized:

- autowired (or annotated) classes that are not declared in the configuration
- wildcard definitions
- usage of `Container::make()` or `Container::injectOn()` (because those are not using the compiled code)

If you make heavy use of those features, and if it slows down your application you can enable the caching system. The cache will ensure annotations or the reflection is not read again on every request.

The cache relies on APCu directly because it is the only cache system that makes sense (very fast to write and read). Other caches are not good options, this is why PHP-DI does not use PSR-6 or PSR-16.

To enable the cache:

```php
$containerBuilder = new \DI\ContainerBuilder();
if (/* is production */) {
    $containerBuilder->enableDefinitionCache();
}
```

To be clear: compilation should be used first. This cache is only for specific use cases.
Since the container is compiled we will store much less entries (definitions) in the cache. So it makes sense to separate each entry because there should not be a lot of entries. That will prevent cache stamped.
@amakhrov
Copy link
Copy Markdown

amakhrov commented Jan 9, 2018

@mnapoli thanks for the quick turnaround!

  • Looks like compilation is still not fully recursive. It compiles only root definitions and direct dependencies of those. However, dependencies of dependencies (and so on) are not compiled. As a result the performance gain is rather low, not comparable to using cache.
  • I have an issue with non-instantiable classes. What is a proper way to define them in config? My use case:
    • The project uses Zend Framework 1 (I know... :) ). ZF1-Bridge is used to integrate PHP-DI into it.
    • In this scenario the DI container does not instantiate a class - instead it injects (using Container::injectOn()) dependencies via private properties into an already created instance.
    • For testing I just disabled the assertClassIsInstantiable() check in the compiler. As a result, the definition for abstract classes appear in the CompiledContainer (which is not a big deal, frankly, as it's never expected to be called - just some extra junk in the compiled file).
    • Maybe a better approach would be to add a directive for the config like DI\autowire()->disableConstructor() or DI\inject()->autowire() - which one could use instead of DI\autowire()

@mnapoli
Copy link
Copy Markdown
Member Author

mnapoli commented Jan 10, 2018

However, dependencies of dependencies (and so on) are not compiled.

I'll look into that (this was a quick POC so I didn't check in details yet), thanks for the feedback.

For non-instantiable classes if you use injectOn() then it doesn't matter if the class is compiled or not (because compiled definitions are only used for get()). Everything that is non-instantiable doesn't make sense to compile.

Now if I understand you get exceptions at the compilation because of these? Maybe we could skip compiling those definitions? (without errors)

@amakhrov
Copy link
Copy Markdown

Now if I understand you get exceptions at the compilation because of these? Maybe we could skip compiling those definitions? (without errors)

I can't skip those. These are my entry point I list in the php-di config. If I skip those, I have nothing to compile. Or I will have to list all 1st-level dependencies in the config - which is much harder to maintain then the list of entry points.
Well, I mean I don't really need to have those compiled (yet it could still be helpful, so that @Inject annotations are not parsed again every time) - but it's the most reasonable way for defining the root for the recursive compilation.

@mnapoli
Copy link
Copy Markdown
Member Author

mnapoli commented Jan 14, 2018

This PR is ready.

@amakhrov why are you adding abstract classes to your config? I'm guessing since you mention the ZF-Bridge it's about controllers: you shouldn't add the abstract controller class in the config. Instead you should add all the controllers if you want their dependencies to be compiled. That way you will not get an error because of the assertClassIsInstantiable() call.

I still believe the config should not contain an abstract class because it makes no sense, so the exception should stay IMO.

@mnapoli mnapoli merged commit d891004 into master Jan 14, 2018
@mnapoli mnapoli deleted the compile-recursively branch January 14, 2018 09:06
@amakhrov
Copy link
Copy Markdown

amakhrov commented Jan 14, 2018

@mnapoli I don't add abstract controllers to the config - I add my real controllers.
However, they apparently inherit from the abstract one (provided by ZF1). And the abstract controller's constructor has the following signature:

public function __construct(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response, array $invokeArgs = array())

So you see - the abstract controller has abstract dependencies.
In ZF1 you're not supposed to redefine the base constructor - so my real controllers inherit the same constructor with abstract dependencies.

Now I add the real controller to the config.
PHP-DI Compiler properly compiles it, because it's not an abstract class.
But then, with the new 'recursive compilation' feature it also tries to compile those abstract dependencies - that's where I get the error from the Compiler.

@holtkamp
Copy link
Copy Markdown
Contributor

holtkamp commented Jan 15, 2018

But then, with the new 'recursive compilation' feature it also tries to compile those abstract dependencies - that's where I get the error from the Compiler.

So if I understand correctly, currently ZF1 controllers "should" / can not be added to the PHP-DI configuration to prevent them from being compiled (which results in an error)?

I also get errors with the recursive compilation approach for entries that are not part of my configuration (Entities):

"Entry "Project\Domain\Entity\EntityXYZ" cannot be compiled: the class is not instantiable

Is there any way to debug this / find out which configuration entry is causing this exactly?

For now I disabled compilation, the re-added cache ensures performance remains at a good level.

@mnapoli should an issue be opened for this?

@mnapoli
Copy link
Copy Markdown
Member Author

mnapoli commented Jan 18, 2018

@holtkamp yes please. Could it be a dependency of one of your service? Because the only explanation I see is that this is caused by the recursive compilation.

We could ignore errors in definitions during compilation, not sure if that's the best though 🤔 Let's discuss that in the new issue.

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