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
AutoloadGenerator refactoring - strategy talk #2460
Comments
|
I closed the other two PR to clean up the state. Let's discuss here it's a better idea to get a clear plan of action first.
|
|
A new idea about the dumpers / plugins: namespace Composer\Autoload\Generator\Plugin;
class Psr4 {
function generate($build) {
$snippet = <<<EOT
\$loader->addPsr4(..);
EOT;
$build->addAutoloaderInitSnippet($snippet);
$php = <<<EOT
return array(..);
EOT;
$build->addFile('autoload_psr4.php', $php);
}
}Putting it together: $build = new AutoloadGeneratorBuild();
// Collect the information.
foreach ($plugins as $plugin) {
$plugin->generate($build);
}
// Create the files.
$build->dump();We could even extend this for package parsing: namespace Composer\Autoload\Generator\Plugin;
class Psr4 {
protected $psr4 = array();
function addPackage($dir, $package) {
$this->psr4[..] = ..;
}
function generate($build) {
// Generate the snippet for AutoloaderInit.
$snippet = <<<EOT
\$loader->addPsr4(..);
EOT;
$build->addAutoloaderInitSnippet($snippet);
// Generate the autoload_psr4.php file.
$export = var_export($this->psr4, TRUE);
$php = <<<EOT
return $export;
EOT;
$build->addFile('autoload_psr4.php', $php);
}
}Putting it together: // Parse package data
foreach ($packages as list($dir, $package)) {
foreach ($plugins as $plugin) {
$plugin->addPackage($dir, $package);
}
}
$build = new AutoloadGeneratorBuild();
// Collect the information.
foreach ($plugins as $plugin) {
$plugin->generate($build);
}
// Create the files.
$build->dump(); |
|
One thing to remember here that I dont' really see in your above idea is that we need two modes of operation ideally:
|
|
Does AutoloadGenerator currently do both of that? It does not look like it.. |
|
It sort of does via createLoader + buildPackageMap + parseAutoloads, but that's exactly the point: right now it's broken and way harder than it should be to achieve full functionality (classmap notably is missing) and it's even too hard to use the parts that work. I'd like the new architecture to take this into account so that we don't fuck up again, now that we know the requirements :) |
|
Ok, so..
So, similar to above: // Feed package information into the plugins.
foreach ($packages as list($dir, $package)) {
foreach ($plugins as $plugin) {
$plugin->addPackage($dir, $package);
}
}
$loader = new ClassLoader();
// Let plugins register their stuff to the class loader.
foreach ($plugins as $plugin) {
$plugin->initClassLoader($loader);
}Or, if we only have one new package: // We need fresh plugins, to avoid registering the old stuff again.
$plugins = $this->createPlugins();
// Feed package information into the plugins. This time only for the new package.
foreach ($plugins as $plugin) {
$plugin->addPackage($newPackageDir, $newPackage);
}
$loader = $this->getClassLoader();
// Let plugins register their stuff to the class loader.
foreach ($plugins as $plugin) {
$plugin->initClassLoader($loader);
} |
|
How about having less state and something like: // Reuse old stateless plugins
$plugins = $this->createPlugins();
$loader = $this->getClassLoader();
// Let plugins register their stuff to the class loader.
foreach ($plugins as $plugin) {
$plugin->initClassLoader($loader, $newPackageDir, $newPackage);
} |
|
This means having more local state in methods in the plugin, and those Also this is about making parts of the plugin easier to reuse. Maybe we should try both attempts and see which looks better. 2014/1/7 Jordi Boggiano notifications@github.com
|
|
Yeah maybe you are right. We could just instantiate the plugins needed |
|
I am making quite some progress! For parseAutoloadsType(), psr-0 and psr-4 use the unsorted package map, but Could you explain the sort criterion? 2014/1/7 Jordi Boggiano notifications@github.com
|
|
The sorting is done to make sure package dependencies work fine, because with files autoload it could be that later packages depend on others to be loaded already. Similarly with classmap packages defined later should be able to override classes or such thing. In any case you should try to preserve that sort algo as is, and maybe you can even use it for psr-4/psr-0 but there we might need to reverse the order in which we go through the array then to keep BC. Anyway I believe there are tests for at least some of this so let's hope they catch anything bad you'd do :) |
|
The PR is now in a state where I am quite happy with it. I leave the rebase to some later time. |
|
What do you think about the overall direction? |
|
Just not really worth it anymore IMO, the code is pretty stable and changing rarely, no huge benefit in refactoring with possibility of adding regressions. |
I still would like to refactor the AutoloadGenerator. It is so messy and huge. But I want to wait until PSR-4 lands, #2459
Previous attempts:
Now, before I give this another try, I would like to discuss some strategy.
I made some strategical decisions in the previous attempts, and would like to know which of these you agree with and which you don't.
1. Introduce separate classes for different parts of the generated autoload stuff.
One for PSR-0, one for PSR-4, one for classmap, one for include paths, etc. In the second PR I called this "aspects", but discussion showed this was a stupid and misleading choice of word. Maybe "Plugin" is better?
Each of these classes can do two things: Dump a file, and return a php snippet for the ComposerAutoloaderInit::getLoader() method. This can be defined in an interface they all implement.
In the second PR this used to be in the same method, but we can easily split this up to be two separate methods.
One question is which data to provide to those "plugins". The main algorithm should just iterate over all registered plugins and give each the chance to dump a file and add a php snippet. Here they should all receive the same parameters. However, they could have different constructor parameters.
2. Turn some variables into protected object properties
Some of the variables in AutoloadGenerator are passed around all over the place. E.g. $basePath, $vendorPath, $targetDir, $appDirBaseCode, stuff like that.
I generally believe in reducing the visibility of state and variables as much as possible. An object or method that does not need a given variable, should not get its hand on it. However in this case I don't think that all the passing around really helps anyone.
It would be easier if at least some of the variables were somehow bundled in an object. This can either be the main object where all the work happens, so you would do $this->vendorPath.
Or it could be a service object that is passed around, to provide everyone with the paths. This could also take care of the calculation of relative paths from one base to another.
3. Have an object that represents the process
Atm the AutoloadGenerator itself does not remember anything about the generation process. All the information about the generation process is passed in to the dump() method, and will be forgotten afterwards.
I am suggesting a different approach: An object that receives all the information on construction time, and that only lives as long as the generation process.
This object could either contain all the generation logic, or it could just be a helper to be passed around.
We should not change the interface of AutoloadGenerator, so this new object would be something else, instantiated from AutoloadGenerator->dump().
4. LazyEvaluationContainer similar to a DIC
This was an idea I used in the second PR, that I also like to use "at home" in other projects.
It is meant for a situation where you want to calculate a number of values with a dependency graph. You want to make sure that each value is only calculated once, and you want to make sure that values are calculated in the correct order so that if B depends on A, then A will be calculated before B. The values are all part of the same instance of the problem, they will all be recalculated if the instance parameters change.
Technically the solution I use works very much like a compiled DIC like in e.g. Symfony2.
I am still not sure this is an idea that can be generally recommended, e.g. @igorw had some valid concerns. I also asked on stackoverflow to get some useful feedback, but not sure I asked the right question.
The text was updated successfully, but these errors were encountered: