Quoting sun from #1224666: [meta] Unofficial Drupal 8 Framework initiative.
For the "move stuff into classes" and "convert include files into classes" goals, we could create a whole bunch of Novice issues, once we've defined their architecture.
Very infrequently used stuff like Flood, Locking, etc are trivial targets. Simplest architecture would be a class for each with static functions. Then, convert all callers from whatever_*() to Whatever::*(). Part of the architecture discussion should be whether there should be a namespace prefix for the classes; e.g., CoreWhatever.
Might make sense to open an architectural discussion issue for that?
Here's the issue. I'm deliberately not posting an issue summary yet since I want to put my own biased opinions below.
Comments
Comment #1
catchI'm not too keen on directly calling static methods on a class without any kind of factory, if we want to refactor the class later internally (to be pluggable or whatever), it means we definitely have to change calls to those methods around core.
Factories get around this issue, but we don't have a convention for factories in core yet first. If we can move towards having one for factories, then a lot of other things could fall into place.
Off the top of my head here's a rundown of patterns I can think of in D7:
Procedural factory
(not sure if there is a better term for this).
db_query()
I like this because db_query()->fetchField() etc. is pretty natural. Also it was quite easy to change simple db_query() from D6 to db_query() in D7 (I think Dave Reid even had a forwards compatible pattern for placeholders at some point).
Procedural wrapper around a procedural factory or direct class instantiation
An example would be entity_load()
or cache_get()
In these cases there is also a factory (_entity_get_controller() or _cache_get_object() but it is not exposed as a public API.
Instead the result of this pattern is that users never see the class, they just run procedural functions and get whatever the return of the class method is back. This makes sense if we wanted to backport some classes to D7 (or for Drupal 7 contrib backports - http://drupal.org/project/cache_backport for Drupal 6 takes advantage of this), but it's something we should move away from in D8 IMO.
The entity system we have other things to worry about so I won't discuss that one.
For caching, I would love to have a cache() function that returns the class and then we do:
$cache = cache($bin);
$cache->get($cid);
$cache->set($cid, $data);
Or something similar to this.
I have a patch for the lock framework at #1225404: Modernize lock acquisition to RAII. This is using procedural factory, no wrapper - so lock()->acquire($name);
In this case the lock() function itself lives in bootstrap.inc (and is a couple of lines), that way the lock system is never explicitly initialized during bootstrap, nor is it autoloaded. That pattern only works for extremely low level systems (although it may be worth trying to avoid autoload for some of them).
bonus pattern
While it's not in core that I know of, another pattern, and one that chx mentioned, would be to have the factory be a static method on a class.
The static method just return an instance of the class we actually want. A big advantage of this is that the factory itself can be autoloaded. There are other pure OOP variations on this pattern as well.
That might look like
Which still looks pretty natural to me.
There may be some classes that we do not want any kind of static cache for (we need it for db_query(), cache etc. since they have instantiation costs for each instance). Maybe flood control comes into this, not sure. But the ability to avoid static caching classes sometimes will help keep memory cost down a little bit at least so would be good to incorporate.
Comment #2
sunHm. I understand the examples are just examples, but all of them are targeting more complex subsystems than the simple things I thought of. I.e., cache, db, etc are complex things that are statically tracking state and stuff.
Our risk is to make zero progress, because of epic debates about a "perfect pluggable architecture". The current functions are not pluggable currently, so why even think of making them pluggable now?
As a very first baby step, I really thought of straightforward conversion of procedural functions into (static) class methods only, and moving the class into a separate include file (if not the case already). That gains us auto-loading of stuff that's currently loaded but not used, and also clarifies what stuff we actually have.
OLD: includes/common.inc:
NEW: includes/flood.inc:
Note: Flood might be a bad example. Based on what I see, Flood should be a module.
Comment #3
lars toomre commented@sun, could you elaborate on why you said Flood should be a module? I am confused about what is now procedural code in common.inc being moved into static class methods vis-a-vis a module. Is it because of possible response to hook_* calls?
Comment #4
Crell commentedI do not believe that simply replacing the first underscore in a function with a class name and static method (#2) is a good idea. While that does get us some lazy-loading, that is *all* it gets us. I agree that we should not immediately shoot for perfect, but we should also not shoot for "trivial hack". :-) That does not offer any sort of testability improvement at all; it also means we cannot separate out code to different files at all, since a class must be entirely contained within a single file.
At minimum, we should use an instantiated object with at least a trivial factory. That will help us see how and where something is actually used. If the object tracks very little data of its own, then the actual performance cost of creating the object should be fairly small. The existence of an object is not that much more than any other ZVal, I believe.
Comment #5
plachsubscribe
Comment #6
ralt commentedsub.
Comment #7
dixon_I agree with Crell in #4. We should try to capture the most basic logic in a factory class that can be lazy loaded and then the rest in methods that makes sense.
The following is not strictly related to this issue; but if some sort of pluggability is needed for the "thing", one could do as I proposed in #1252486: Low level UUID API in core. There I basically have an interface, a simple factory class and some very basic implementations. The factory class is of course possible to lazy-load and it decides on the implementation to use by using
variable_get('thing_class'). It's basically similar to the cache thing, but used a lazy-loadable factory class instead of procedural functions.I know that this might be jumping ahead of a proper plugin system, but at least it's a simple pattern we potentially could move forward on now.
just my two 2 cents...
Edit: wrong issue link
Comment #8
gábor hojtsysub
Comment #9
mfer commentedA few thoughts....
The one thing that comes to mind is the DX (within the Drupal community who is not typically accustomed to classes and namespaces). For example, I could just imagine the difficulty of someone implementing:
From a DX standpoint it might be easier to have something that makes this easier while still autoloading:
In this example cache() is in a file (maybe something akin to common.inc that includes the use statement and wraps the static method calls to make it an easier transition.
Fun note: it seems the \ in namespaces does not play nice with our php filer.
Comment #10
Crell commentedI agree with mfer that factories are in general a good thing, as are interfaces. However, I disagree that static methods make good factories in general. Their only advantage is that they will self-lazy-load. However, they are then still not truly separating the "creation behavior" from the "being behavior" (the factory from the object being created).
I'm fine in general with a utility function to wrap a factory call if it is easier. The database layer uses that a lot. However, we should still emphasize dependency injection over direct calls for all of the testability reasons we've mentioned before.
Comment #11
catchI prefer utility functions for syntax. However for example in #1225404: Modernize lock acquisition to RAII there is not really anywhere to put the lock() function in core except for bootstrap.inc. This massively privileges core over contrib APIs (if we want to allow APIs that aren't only provided by always loaded modules).
Comment #12
catchA factory method on a class, that instantiates an object of a different class, doesn't seem to break that at all for me. Or what do you mean?
Comment #13
nicl commentedsub (interested in helping with the novice issues once this is agreed).
ps. if there are any other pressing novice issues (or more complicated issues which you can guide on) send me a message.
Comment #14
xandeadx commentedsubscr
Comment #15
geerlingguy commentedSub.
Comment #16
yched commentedSub. This will need to concern Field API at some point.
Comment #17
Mark Theunissen commentedSub
Comment #18
baldwinlouie commentedsub
Comment #19
tarmstrong commentedDrupal's kind of bad at failing hard when you give functions bad data. I'm willing to be convinced otherwise, but that's my impression :). Would it make sense to do things like the following in order to do more automatic sanity checking?
I consider this better DX because you find out exactly why you've done something wrong very quickly. Thoughts?
Comment #20
Crell commentedtarmstrong: Our coding standards already encourage interface-based type specification, but never, ever class-based. (It's more flexible that way.)
catch: I meant a static factory method on the parent class. Eg,
Entity::load('node', $nid); // returns instance of Node, which extends EntityThat's what I was arguing against.
If you're already using a separate class anyway for the factory (which is good), I don't see why you wouldn't just instantiate it.
Comment #21
boombatower commentedsub
Comment #22
shawnb commentedsub
Comment #23
Anonymous (not verified) commentedsub
Comment #24
Anonymous (not verified) commentedsub
Comment #25
mhrabovcin commentedsub
Comment #26
mfer commentedIn response to something Crell said in #10 about factories as static methods on classes...
There are a number of benefits for classes as static methods on classes:
Most of these don't hit the default use cases but they are definitely nice to have for people pushing the limits of Drupal.
Comment #27
Crell commentedVs. a purely function factory, everything in #26 is true. However, all of those benefits apply to an object factory as well, which is what I was arguing for over a static class factory in most cases.
Comment #28
catchis not nearly as nice as either:
though in terms of syntax.
Comment #29
fietserwinsub
Comment #30
Crell commentedFrom #10:
Those are convenience wrappers only, and until/unless we have a proper services locator system I am OK with them. That doesn't mean they contain any real functionality.
Comment #31
catchI'm going to try to summarize what I think the three options are:
Pros:
We already use this pattern.
It's very convenient, concise and readable.
Cons:
The utility function has to live in a separate, always loaded file and can't be bundled with the class structure.
Factory class.
Pros:
The factory class itself can be autoloaded and bundled with the interface etc.
It might not end up as ugly as my example.
Cons:
Not something we really use in core (maybe Queue?)
Verbose.
You have to instantiate at least two classes to get anything done.
Pros:
The class can be autoloaded.
Still compact/readable.
Cons:
This syntax doesn't work for very simple systems like lock or cache. You'd have to do something like:
Which is redundant typing compared to:
Let's look at the disadvantages of utility functions - it is only really autoloading.
If the utility function is for low level stuff, we need to bundle that in bootstrap.inc or common.inc. That's a tiny amount of code weight compared to some of the other stuff in common.inc like our good friend format_rss_channel() and similar.
If the utility function is for module level stuff, it'll need to go in
the module. Again no great loss.
The only issue I can see is if people want to use very low level stuff with a utility function, for something core doesn't provide a utility or interface for at all. We don't let people do that now until you get to hook_boot() or do something ugly in settings.php. We might also have third party libraries that use different factories - but we could provide our own factory or consider using more than one pattern if something is used very rarely.
Also if we wanted to change this at any point, we could switch to whatever new pattern internally and keep the utility functions right up until the last minute, that's not so much the case for the other two.
With all that in mind, I think I'm pretty much in favour of utility functions at this point, and have a section in bootstrap.inc and/or common.inc for them.
Comment #32
Crell commentedI think we're saying the same thing, with the caveat that the utility function should be dumb wrappers around an underlying OO factory (one of the other two). That way if you really do need to use such a system before that file loads it's plenty easy to do, and the code weight of loading a bunch of utility functions in common.inc or bootstrap.inc is minimal.
Comment #33
podaroksubscribe
Comment #34
catchHmm. I can agree with #32 but I'd be concerned if we moved for example check_plain() to a class method, then had a procedural utility function, factory method/class, then implementing class to instantiate to run it. For most of the things we're discussing this won't be an issue though.
Comment #35
jherencia commentedComment #36
fagosubscribe
Comment #37
jeff veit commentedsubscribe
Comment #38
jeff veit commentedsubscribe
Comment #39
jide commentedsub.
Comment #40
xtfer commentedUtility functions, as described in #31 + #32, seem to be the simplest approach to this problem, with the minimum of overhead for updates and reasonable DX. Its close to what occurs in a lot of procedural stuff in Drupal anyway, and easy to switch implementations on.
The static method is possibly slower than the others anyway, as I discovered while hacking away at http://drupal.org/node/1177246.
That probably solves the Factory Method part of this discussion. What other aspects are there to this issue?
Comment #41
Paul Simard commentedsub.
Just my $.02 from one of the newest kids on the block...
Have we decided exactly what core's purpose is? Versus the modules' purposes?
I was reading the online documentation for Apache the other day, and something written there struck me as profound. I can't seem to find it at the moment, but it went something like this.
The entire purpose of Apache (or any web server) is to accept requests, rewrite them just a bit, then locate and serve the requested resource. --paraphrased from Apache.org documentation
That's it!
Shouldn't we be trying to bring that same simplicity to Drupal's core? For both the UX and the DX of the experience, Drupal has an enormous community of expertise from the oldest tyros to the youngest prodigies.
I believe in the KISS principle... Keep It Simple, Silly.
Bringing that degree of simplicity to the user and developer experience is going to require a much greater degree of complexity inside the black box. But, IMO, that is what Drupal needs, I'm coming to believe. I also believe that to pursue this is a task of years and enormous labor from the community as a whole. At the end, though, I see a product that defines CMS, industry-wide, that can build a basic blog site for the newest of us, as well as a globe-spanning enterprise system, as well as anything in between.
I don't see this as an issue for Drupal 8.x, sorry to say. More in line with Drupal 10.x or 11.x. I'm thinking that on the day Drupal 8.x is released, the basic feature set of Drupal 9.x will be fixed, and discussions for what is needed for Drupal 10.x begin.
Sorry about that... the $0.02 grew to $0.25. Inflation occurs everywhere. :)
Paul
Comment #42
Paul Simard commentedA proposed aphorism:
The principle of Conservation of Complexity states:
Complexity cannot be created or destroyed. It can only be hidden or exposed.
-- Unknown
My vote goes toward hiding the complexity from the outside world, and exposing the simplicity to the user and developer community.
Comment #43
xtfer commented@Paul_Simard This is a well-worn discussion but this issue is not the right place for it. Catch has a good write up on the current state with a number of links you should investigate if you want to contribute.
http://ca.tchpole.net/node/4
Comment #44
Coornail commentedSub
Comment #45
Paul Simard commented@xtfer Thanks for the link. Very interesting read. No more mention here. Sorry.
Comment #46
donquixote commentedCrell #27
I completely support Crell on this static vs function vs object thing.
Static methods are so tempting for many developers, that we will/would quickly see plenty of static methods replacing regular functions, for obscure reasons. I would call this the "class as namespace" anti-pattern, and I would really like Drupal to avoid falling into this trap.
So, for my taste, either regular functions, or class instances, whatever does make the most sense in a particular situation. (both for factories, and other things).
Comment #47
donquixote commentedClass names with module prefix.
I think we need a naming scheme for classes that reflects Drupal's separation of code into modules.
Unlike some typical PHP frameworks and packages out there, a Drupal codebase is not an arbitrarily nested directory structure, so let's not try to mimick those frameworks' naming patterns 1:1.
Within one module, there is usually not too deep of a nested filesystem structure, so it would be ok if all class files live within the same folder.
We should aim for something that is safe against nameclashes, and where the class name reveals which module it belongs to.
Still, the class name should reveal that this thing is a class, not a function. So a little bit of ucfirst / camelcase should be there.
So far in my own modules I used a CamelCase class name, prefixed by the (lowercase) module name with underscores.
So, "view" would become "views_View". More general, some_module_SomeClass.
In some cases I add lowercased suffixes, to add descriptive attributes to the name, but that's a matter of taste.
I think having the original module name being part of the class name is preferable to a distorted, camel-cased version of the module name.
I would appreciate if we can agree on something in this direction.
Comment #48
donquixote commentedcatch #31:
Having to create the factory in-place as a "new Db('query')" kind of defeats the purpose, imo.
So, "drupal_db('query')", instead of "new Db('query')".
I think this only becomes interesting if either
- you get the $db object injected "from above", or
- you retrieve it from a retrieval function such as "$db = drupal_db()", or
- you retrieve it from a retrieval object, which again was injected "from above", or
- your example code is actually the "from above" level, and you are going to pass the $db object around.
I think, as long as we don't have any fancy injection mechanics in place, we should stick with the regular-function factory / retrieval function.
Comment #49
Crell commentedRe #47, we're going to have a PSR-0-compatible autoloader in core in Drupal 8. I don't know yet how we'll leverage that in modules, but I am hopeful that we will be able to use that somehow rather than Crazy_Long_Class_Names_That_Violate_Coding_Standards.
Comment #50
donquixote commentedAnyone following here should also be following there,
#1290658: Move all module-provided classes to PHP namespaces (PSR-0 or similar), and autoload them without the registry [policy, no patch]
Comment #51
sunI think we're done with this issue.
Comment #52
donquixote commentedThis is definitely fixed for D8.
For D7, I think the current naming conventions for classes are quite lame.
We cannot count on namespaces, but I would be happy for something where the module name is suggested as a prefix.
(as supported in xautoload)
So, should we have a better convention for D7? Or just stick with the rather stupid one we have now?
Comment #53
catchClass names for Drupal 7 aren't going to change since APIs are frozen. Moving back to fixed.
Comment #54
donquixote commentedI was thinking more of new classes, mostly in contrib.
But I guess it's fine, just leave the convention as it is, and contrib can just ignore the convention and prefix things if they prefer to do so.