Hello,
A thousand chats have been done about the topic, and once again today I found myself in the need of a good autoloader I had to code.
For once, I decided to patch directly Drupal 8 and test it with my own autoloader: it works! It can be used at early pre-bootstrap phases, even before Drupal has started its bootstrap.
It support both map-based class list, and PSR-0 autoloading based on a series of namespace/dirpath couples that defines the namespaces.
This will allow modules to use it "as-is" as the only thing needed for them to use it is to declare their path directly once the database has been bootstrapped (not already done). This will allow to use module defined classes and API without loading the module file, and directly from the early bootstrap phases.
In term of performances, I didn't made any measures yet, but there are good chances it is more efficient than the legacy core one. It may also be more scalable if namespaces are widely used.
Using namespaces instead a map-based autoloader will also allow any core subsystem to be used before database connection has been configured/bootstrapped, because most of the core components have a fixed path (in oposition to modules). This also help for making those subsystems PSR-0 compatible themselves since the autoloader already work with that.
I choosed a singleton pattern (introducing a global state object) because Drupal is a global-state software. It can be changed easily, this will be done once the core will be more OOP thanks to Crell.
Right now, here is my patch, working at install time, module enable time, normal browse time, without any errors in my error log.
By doing this patch, I also removed a lot of useless code, and some bits of code indirection (and the separation between class and interfaces madness) from the file bootstrap.inc.
Some references:
#1240138: Adopt PSR-0 namespace convention for core framework classes
| Comment | File | Size | Author |
|---|---|---|---|
| #9 | core-autoloader-map-and-namespace-8.x-3.patch | 14.93 KB | pounard |
| #6 | core-autoloader-map-and-namespace-8.x-2.patch | 14.96 KB | pounard |
| core-autoloader-map-and-namespace-8.x.patch | 7.13 KB | pounard |
Comments
Comment #1
pounardComment #3
pounardIf that matters, it works gracefully where I tested it. I can't guess why test log fails here, it seems unable to proceed to installation (but why?).
Comment #4
damien tournoud commentedProbably because this file is missing?
Comment #5
pounardYes it is, don't know why git diff didn't took it, it's added. Git still has some parts of mystery for me.
Comment #6
pounardgit add -Nmagic. Attached new patch.Comment #7
pounardComment #9
pounardLet's go for another one.
Comment #10
Crell commentedActually, I see no reason for us to write our own autoloader. It looks like we'll be adopting some Symfony2 components, and they have a perfectly good autoloader available already that we can use. In fact they have a couple, including one that is map based. There's no reason for us to duplicate code here needlessly.
We've been discussing that over here: #1241190: Possible approaches to bootstrap/front loading of classes
At the moment I'm just waiting on a green light from Dries to write a patch to bring in both Symfony's HTTP library and autoloader. I'm tempted to won't-fix this.
(Also Drupal being global-state-based software is a critical design flaw that we should eliminate with extreme prejudice as soon as possible, not encourage.)
Comment #11
pounardI know what you mean, just fed of rewriting this each module I do, at least it's something, and it works.
Isn't it what I said?
It's true that the Symphony UniversalClassLoader seems both simple and fully featured, it would totally fit here instead of the one I wrote. Nevertheless, the rest of the patch, meaninig getting rid of boostrap.inc registry related insanity would remain true as a first step of patching core to use it.
Comment #12
Crell commentedI believe the Symfony autoloaders include a map-based one, if you feed it a map. So that would change the approach here at the very least. Also, just because Drupal does too much global state right now doesn't mean we should continue to do so "until that gets fixed". If anything, start fixing it sooner rather than later. Don't wait for me. :-)
I note that in the patch it's doing a complete load of the entire registry table. While that is a simpler implementation, it's also far less performant because that's... not a small table. That would be a huge memory hit with yet another massive in-memory array, which is something we need to have less of, not more. Our memory situation is already atrocious.
If you can, I'd have a look at the Symfony map autoloader and see if there's a way we can feed data from the registry into that. (Subclass it, maybe? Not sure.) That way at least we can reduce the amount of code we have on the way to simplifying the autoload code. (At least for now we're not dealing with namespaces in modules; where we'll end up with that later I do not know.)
Comment #13
pounardAs I said before, I surely do agree about the global state removal. The actual code cannot allow people not using global state: changing this would require huge architectural changes way heavier than just adding an autoloader class. I assume that you as a community leader in charge of this topic and as a proud OOP advocate would not let me do it without your approval. This is another issue, it doesn't change for a bit what an autoloader looks like.
If that matters, in my opinion, the autoloader is probably the last thing of all that can remain global state. When you load code you load it for the full page request (or if you're in CLI, for ad vitam eternam): you don't unload code or modifiy code at runtime. The autoloader is more compile-related than business-related and basically, if it is done well, you don't really need to have more than one, and you surely not do need to remove it from the memory (may be some edge cases would, but right now I don't see where). And if it's registered pre-bootstrap, this means that systems like Drush or SimpleTest would just be able to register their own instead: core would not care and it would not need any patching or such.
Whatever is going to be awesome Drupal 8, I surely don't think that having a global state autoloader is a blocker at all for moving forward and continue to remove global state elsewhere. Beside of core itself for registering its namespaces, I really don't think any other pieces of code cares about the autoloader (except when modules needs third-party libraries, that definitely should be handled by the core at some point) nobody sane will use it directly if there are strong conventions set in core for class naming.
This choice is not easy to make I admit: I did reading the various current threads: I assume that core code is going PSR-0 in the near future, at least most of it. This means the registry table will get thiner and thiner over time until it reaches a point where, ideally, we would not need it at all.
Using the registry table here is for compatiblity, but assuming it will disapear, I won't make optimizations about it: it would complexify a lot the code for something that will not exist anymore before the next real release.
I believe that for making a Drupal 7 backport, we will need it anyway because it's part of the actual core API. Therefor this particular map handling (and not generic map handling) will need to be written, not as an architectural proposal but as a backward compatibility fix: this means it doesn't fit in my proposal or at least needs to be evicted from the global design.
The registry table is insanity: it forces the autoloader to rely on database (what?!). Using only PSR-0 code will definitely get rid of this registry, for ever (I it would make me happy). Furthermore, by definitely being free of the registry table, we could make the autoloader being fully working really earlier in the bootstrap process, potentially enabling it for cache, lock and session backends.
I agree with you: it is in direct relation with the 2 last paragraphs I wrote above. I think that whatever is Symphony one or mine the code arround will still remain the same.
It's probably do-able, as a solution I would tend to create a specific object class for registry management, that would encapsulate this complexity elsewhere, maybe by prepending it before the global autoloader and fixing the registry stuff at runtime, populating the cache entry so that, on next hit, it would be filled up for the autoloader. In all cases, I would leave the global autoloader alone and keep him generic for the reasons I explained upper, and I would write this as an opaque component, fully removable on which others should not rely for anything else that backward compatibility.
Even more: I'd probably mark it as deprecated and load it conditionnaly using a variable which would default to true (may be we could see that as the big kernel lock removal in the linux kernel: while it was not get rid of completely, people could remove it manually from the kernel config if they were sure they were using no modules using it).
This comment is probably the most interresting one: using PSR-0, modules and probably third-party libraries we need namespaces. In that regard both the autoloader I wrote and the Symphony UniversalClassLoader seems to manage very well namespacing and having different base directory for each namespace.
Having modules benefit from a generic autoloader without modifying it seems to me relatively easy, there are a lot of ways to achieve this and I don't see any one difficult.
One of the way I see would be adding a "namespace" entry into modules .info file, then at drupal_load() time I would probably register the namespace into the global autoloader (at this particular time, we have the module full path and we can add this call at no cost). This could even be a bit better by adding the namespace information into system table for denormalization and micro optimization reasons.
Another way could be using the module name with a bit of transformation (something like some_module becomming SomeModule) and ending up by registering it at drupal_load() time also.
Both ways here are just suggestions but seems to be efficient and Drupal 7 backward compatible: and most importantly keeps the autoloader generic.
In conclusion, I think that (but this remains an opinion) that the autoloader should not be specialized or subclassed except if we really need it. Drupal should know of the autoloader and be able to use it, even if they are more than one chained into the autoloader stack. Core would register everything it needs to it while the autoloader shouldn't care about Drupal and remain generic. I'd maybe make only one exception for Drupal 7 registry insanity compat, because there is here no clean way to achieve the same feature easily.
I will try doing some more code as you suggested, for fun.
Comment #14
sunI hope I'm not mistaken, but it appears to me that this is entirely obsolete, as we're using Symfony's universal PSR-0 class loader now.
Comment #15
pounardYes you're right! Good catch, I forgotten this issue for years.