Problem/Motivation
We already inject Composer's autoloader into DrupalKernel
, yet drupal_classloader()
creates its own classloader with a hardcoded reference to Drupal's core/vendor/autolod.php
. Therefore it is not possible to use a different autoloader simply by passing a different one into DrupalKernel
.
Proposed resolution
Always use DrupalKernel
's autoloader. For this purpose a getClassLoader()
method is introduced on DrupalKernelInterface
. This is consumed by drupal_classloader_register()
. Alternatively, drupal_classloader_register()
could be replaced directly by a new method on the kernel or the already existing classloaderAddMultiplePsr4()
. In order to still support ApcClassLoader
for PHP 5.3 a setClassLoader()
method is introduced. This is (optionally) being called from index.php
directly now.
With these changes Drupal can be run with a completely custom classloader (as long as that classloader contains the right classes, obviously).
Remaining tasks
User interface changes
API changes
Comment | File | Size | Author |
---|---|---|---|
#20 | 2325197-20-remove-drupal-classloader.patch | 17.19 KB | tstoeckler |
Comments
Comment #1
tstoecklerSo I can install and run Drupal fine with this. I didn't try running tests, so let's see. I also checked that the
ApcClassLoader
stuff works, but I don't have PHP 5.3, so I could only verify that the correct classloader gets instantiated (it then fataled due to the missingapc
extension). So someone with PHP 5.3 would need to try this patch and uncomment the corresponding lines inindex.php
.Comment #2
dawehnerAdded a related issue, which uses a different approach.
Comment #3
larowlanWe don't support 5.3 for drupal 8, fwiw
Comment #4
tstoecklerOh right. Epic brainfail on my part :-)
So I really don't know why we ship with support for
ApcClassLoader
anymore, as that is pretty much PHP 5.3 only. I found a StackOverflow ticket which describes that it can be installed on PHP 5.4, but that seems rather complicated and not like something we need to support out of the box. And I couldn't get it to install at all on PHP 5.5.Will open a follow-up.
Regarding #2023325: Add interface for classloader so it can be cleanly swapped: Thanks for the reference. I had seen that issue at some point, but forgotten about it. I still think this makes sense, though. As can be seen in this patch a
ClassLoaderInterface
would be quite nice to have regardless.Comment #5
tstoecklerSo I misread http://stackoverflow.com/questions/9611676/is-apc-compatible-with-php-5-...
Apparently
apc
does work well on PHP 5.4. Still, there's not much point in using it (IMO) due toopcache
being included in PHP 5.5. Thus, opened #2325305: Remove support for ApcClassLoader nonetheless.Thoughts on the approach here, anyone? :-)
Comment #6
sunRemoving this static is welcome.
At the same time, this construct did ensure that the class loader is only constructed once and not reconstructed from scratch more than once in a single process/request.
However, given that this patch touches all instances, it looks like that situation did only occur in the
setUp()
of test base classes.Theoretically, constructing a new class loader for each test (method) is appropriate — however, a class loader is a native global state construct of PHP core.
We need to ensure that (1) we're not stacking a dozen of class loaders on top of each other, and (2) due to how Simpletest works, we theoretically need to restore the original class loader (of the parent site/test runner) after tearing down a test when restoring the original environment, since the test might have manipulated the class loader.
This appears to be the only instance outside of tests that reconstructs a new classloader in the same process.
We need to assign and inject the
$loader
from/core/install.php
intoinstall_drupal()
and pass it forward intoinstall_begin_request()
.Don't worry, we just need to change the signature of these two functions. I'd even suggest to inject it as the first argument, shifting the
$settings
/$install_state
arguments by one position.These two methods are not necessary and should be removed.
The class loader is exposed as synthetic service
'class_loader'
on the service container; seeDrupalKernel::attachSynthetic()
.There's not really a reason for why we cannot do this from within
DrupalKernel
, instead of having to hack index.php.Ideally as a separate method, so that alternative kernels are able to override it.
Alternatively, we could change the workflow and move this code directly into settings.php (commented out by default). To do so, we just need to forward the
$classloader
fromDrupalKernel
intoSettings::initialize()
. Just passing the argument is sufficient; the variable will be available in the local scope of settings.php.I'd be in favor of the latter approach, because it removes the indirection of the current settings variables, doesn't require to hack any front controllers, and allows you to use a different classloader/decorator.
Comment #7
tstoecklerThanks for the review!
Re 1. Composer itself keeps a static cache of it's loader. See
ComposerAutoloaderInitDrupal8::getLoader().
. I'm not sure if the addedrequire
's themselves are a performance penalty, but that's only without opcache enabled, unless I'm missing something. So I'd say, that's OK ?!Re 2. Yes, I had thought about that as well, but decided to pursue that in a follow-up, as it's going to break Drush. I'm not sure if it'll break the testbot, as they install Drupal through the UI IIRC, but yeah. Will do this now, though. I guess there's really not much difference whether we break Drush now or later.
Re 3. Thanks, that's a good tip! I had seen the 'class_loader' service but somehow didn't make the connection. Sounds great, I wasn't very pleased with those methods either.
Re 4. I like passing the classloader into
settings.php
will do that. Using settings.php is more inline with how we do things currently. I personally am of the opinion that people should own their front controllers and, thus, to me that's not hacking but as it stands, we do have index.php in the repo, so my point is pretty much moot sadly.Comment #8
sunre: 1) Interesting. Sorry, in that case, 2) is moot. And 1) can be ignored, since the behavior is effectively the same; the object is just retrieved from a different static. The additional requires are fine and not measurable (only a conditional file inclusion of a possibly non-existing file would present an issue).
Comment #9
Crell CreditAttribution: Crell commentedUnlike Symfony, Drupal generally views index.php as its own file, not a particular app instance's file. That is, this is technically telling people to hack core to enable to APC class loader.
That said, I'm not adverse to it. I think in the circumstances it's probably for the best, but we should be conscious of what we're doing here.
I haven't dug into the rest of the implementation details so I won't comment on them.
Comment #10
tstoecklerHere we go.
Fixed 2. from #6. I only know realize that you retrospectively declared that point moot (I previously thought you meant not doing something as it would break Drush would be moot). Do you want me to revert that part? I don't really care to be honest. I do think the change makes sense, but I don't care whether it's part of this issue, and also install.core.inc is all dusty and old anyway, so even if we don't end up changing it I don't really care.
Fixed 3. + 4.
4. should also fix the concern in #9 (although as stated in #7 I agree that having to "hack" index.php is not a bad thing, at least not inherently).
As you can see from the patch I couldn't get around leaving
DrupalKernel::setClassLoader()
in place, that would require a little bit more refactoring. Basically we would have to move the settings initialization out ofcreateFromRequest()
into an instance method. That makes sense in its own right, IMO, but again, not sure it should be this issue. If it is requested I will go for it, though.Also would love to hear what you think about the settings.php solution. It's a little bit more work than the
$class_loader = 'apc'
we had before, but it's a lot more flexible, and I don't think it's that bad.Let's see if this is still green.
Comment #12
sunAh. Oh. Interesting challenge. ;)
That said, your patch seemingly implements the possible solution of making
Settings::initialize()
accept&$class_loader
by reference, so the custom code in settings.php is able to replace the referenced variable itself (instead of the object reference) already.That looks fully correct to me. What else needs to be refactored to fully propagate the variable? Can't be much, so I think we should.
That was exactly my train of thought as well. The architectural concept of a string identifier indirection is artificially limited to a hard-coded set of string identifiers understood by Drupal core. By moving the code directly into settings.php, people can do whatever they want.
However, can we move the example code from within the PHPDoc comment into "more actual" PHP code that is commented out via
#
or/* ... */
, please?re: install.core.inc changes: Let's revert those for now, as they are not necessary.
Indeed, the initial installer invocation + front controller is pretty much one of the last major puzzle pieces of #1530756: [meta] Use a proper kernel for the installer
Comment #13
tstoecklerOK, reverted the installer changes.
Removing
setClassLoader()
as is was not possible because even though$class_loader
is being replaced fully (due to the reference),$kernel->classLoader
is not. I found a way to fix this which is only a very small refactoring ofDrupalKernel
. Let me hear what you think.Also removed all traces of
XcacheClassLoader
as that is currently incompatible with Drupal, as I found out during testing. See https://github.com/symfony/ClassLoader/pull/6Also fixed the comment syntax in
default.settings.php
, and some other minor issues.Since I apparently have you at hand here :-), @sun, do you have any thoughts on testing this? I didn't investigate this yet, but perhaps you have some upfront thoughts?
Edit: Sometimes I fail at life. Disregard the pull request above. Will submit a proper one and reference it here.
Comment #14
tstoecklerI opened an issue for now: https://github.com/symfony/symfony/issues/11733 . Not sure whether this is considered a feature request or a bug report and the Symfony contributing notes are a bit intimidating in that regard.
Comment #15
tstoecklerYeah, so the typehint was fairly pointless and also proof that our
apc
class-loading is completely borked currently.Comment #16
dawehnerUsing settings seems to be good enough approach. Still actual sites could still better control their front controller, but including this one
exception for logic inside settings.php feels okay.
Did we considered to just use DRUPAL_ROOT instead?
Just ensure that we don't accidentally enable APC support by default.
Now that the hash_salt could be manually set here, we could get rid of having it as part of settings but rather be usual configuration.
Instead use something like:
Is there any reason to make this methdo public? It seems rather pointless given that you would need createFromRequest anyway. At least there is no other external call to this function.
Comment #17
sunThe initial bootstrap can only be tested in a PHPUnit test that runs in a separate and cleanly isolated process, and which uses the virtual site environment infrastructure for booting a kernel from #2304461: KernelTestBaseTNG™ — otherwise, all affected classes are loaded already and the class loader isn't triggered.
I agree with @dawehner, let's make this method protected.
Also, the new exception is a behavior change, doesn't appear to be relevant or related to this issue.
In the affected test classes, we can use DRUPAL_ROOT. In all other non-test code that isn't deeply nested, I'd prefer to keep the relative include as in the patch.
Comment #18
tstoecklerA. Unless people feel strongly, I would like to avoid
DRUPAL_ROOT
whereever possible. There a bunch of cases in core where we can't avoid it currently, but here it's easy enough, and just to avoid a couple '..' (which are admittedly not that nice) is not worth it IMO. I think we should rather look into avoiding so many of those calls and if possible centralizing them to a certain place. Again, if you insistmin($dawehner, $sun) > $tstoeckler
:-), no hard feelings.B. I like not using settings for the APC prefix. I dug back and the usage of the hash salt dates all the way back to #1178246-34: Add Symfony2 HttpFoundation library to core, sadly without any explanation why this specific value was chosen. I don't see any reason why this should be any more random than a site-specific value, as you mention. Good idea!
C. Since the method is called "from the outside" I was pretty sure it was not possible to make
initializeSettings()
protected, but it in fact is. Either I'm weird, or PHP is weird... That also makes the Exception pointless, so removed that as well.Will provide updated patch probably on Monday, though :-/
Comment #19
sunHappy to help out.
I'd recommend to move the 'hash_salt' (APCu prefix) discussion into a separate issue, since that is an unrelated change in behavior, and needs more discussion, because we still need a unique value, so as to not clash with other Drupal installations that may run on the same machine/process/memory.
Comment #20
tstoecklerAwesome, thanks a lot. Two minor fix-ups:
Let's drop the typehint from the docs, as it's not correct and it's also not in the code (for that reason).
Since you replaced "wrap" with "decorate" above, let's also do that here.
RTBC anyone?
Comment #21
dawehnerdrupal_classloader_register is indeed just used in system_list() which requires a properly booted classloader already.
Comment #22
tstoecklerAwesome, thanks!
Added two draft change notices.
Comment #23
webchickI read the issue + the patch, but despite the thorough comments this is totally going to be a "close my eyes and commit it" type patch. ;) Since tests are passing I'm assuming either a) things aren't horribly broken, or b) since this touches the very guts of Drupal if anything IS broken it'll get found out pretty quick. ;)
I do have one concern before doing that, though, which is that catch was the creator of #2023325: Add interface for classloader so it can be cleanly swapped and since these two issues overlap (and since he's had a lot of very strong opinions about class loading in general), ideally he would be the one to review this. The problem with that is he's not back until next week, which would mean this sits here for a few days.
Tobias, since this is primarily blocking your "composer all the things" work (which dawehner showed a preview of in IRC and it looks awesome!), how urgent do you need this in this week? It's only a normal, so can it wait a few days for catch?
Comment #24
tstoecklerThanks for the feedback.
To provide some context for people following at home:
You can already pull in Drupal's core directory via Composer using https://github.com/tstoeckler/drupal-core/ (with or without this patch), if you want to control your own
index.php
, e.g. for using a different class loader, i.e. the one generated by Composer in your parent project and not the one shipped with Drupal core. But even if you do that, Drupal will not use Composer's generated auto loader, instead it hardcodedly uses the class loader it ships with (core/vendor/autoload.php
. Thus, what this patch enables is: Additionally pulling in other code via Composer and having that loaded during a Drupal bootstrap. The prime example for such "other code" would be a repository of utility classes you or your company maintains for various projects. Think of Ctools in D7, except that with all the refactoring for Drupal 8 and the power of Composer you don't need to make it a module but instead it's simply always there (TM) and you justuse
the provided classes when you need them.I'm doing exactly that both for a personal project as well as for a company project, so the sooner this goes in the awesomer, but we're not on a tight schedule (how could we with D8 ;-)), so a few days wouldn't hurt. Furthermore, I think it's a great idea to have @catch take a look at this (not that I wouldn't trust you to make an informed decision about this patch, though!).
Comment #25
webchickOk, great. Thanks so much for your understanding. Assigning to catch for a look-see. Sounds like a cool project though!
Comment #26
catchOK read through this and discussed briefly with tstoeckler . This removes the bug part of #2023325: Add interface for classloader so it can be cleanly swapped (so I've retitled it and changed it to a task) - which will allow for experimentation with more performant class loaders.
Patch itself looks fine, I'm not clear how useful the "hack index.php to change the location of autoload.php" use case will be if we stop checking in composer stuff to the repo, but it's not going to make that worse anyway.
Committed/pushed to 8.0.x, thanks!
Comment #29
znerol CreditAttribution: znerol commentedThe changes to
DrupalKernel
need to be replicated inTestKernel
: #2367557: Update TestKernel to match class loader changes in Drupal Kernel