[wp-trac] [WordPress Trac] #37699: Death to Globals Episode #1: A Registry, A Pattern
WordPress Trac
noreply at wordpress.org
Wed Aug 17 22:02:29 UTC 2016
#37699: Death to Globals Episode #1: A Registry, A Pattern
----------------------------+------------------
Reporter: wonderboymusic | Owner:
Type: enhancement | Status: new
Priority: normal | Milestone: 4.7
Component: General | Version:
Severity: normal | Resolution:
Keywords: | Focuses:
----------------------------+------------------
Comment (by schlessera):
Hell yes, it would be awesome to get rid of the globals.
I'm fully aware that this is just a very rough first pass to get the
discussions started, so I won't delve too much into technical details. I'd
like to share some early observations, though, based on what you posted so
far.
'''1. Patch is huge'''
The patch is very unwieldy when the goal is to discuss basic concepts
first. I think it would be more practical to limit patches on the actual
"Registry", and one or two use cases until the general direction is clear.
'''2. Existing pattern'''
What you call "Registry" here is commonly called a "Service Locator",
which is a known pattern. The Service Locator is responsible for letting
major subsystems interact with each other, by providing instances to each
subsystem when requested.
'''3. Still global'''
The way you've implemented this Registry/Service Locator here means that
you've replaced one global with another global. When we start to put more
subsystems into that Service Locator, the number of globals will of course
reduce, but it cannot be completely brought down this way. A reference to
`WP::get()` means that the method `get()` is called on the static
(=global) `WP` "instance". I put "instance" in brackets here, because it
has not technically been instantiated (through the `new` keyword), but it
has state, and this state is made globally accessible.
'''4. Using WP as object'''
Relying on the `WP` class name as the Service Locator is not ideal, as a
future version of WordPress (PHP 5.3+) would most likely have `WP` as the
root namespace. That future version would probably offer something like
`WP\Service::get( 'wpdb' );`, so something like `WP_Service::get( 'wpdb'
);` would be preferable. Keeping this as future-proof and flexible as
possible should be a priority.
'''5. Dependency injection'''
WordPress is mostly procedural, but the places in the code where we do
already have classes should try to start using dependency injection.
As an example, consider the `WP_Comment_Query` class, for which you
proposed something like the following:
{{{#!php
class WP_Comment_Query {
protected $dbh;
public function __construct( $query = '' ) {
$this->dbh = WP::get( 'wpdb' );
// [...]
}
}
}}}
There's a missed opportunity there to start using dependency injection to
make it easier to do unit tests. Even worse: right now, with the globals,
the unit tests can still set the global to a mock DB. With the above code,
there's no proper way of injecting a mock DB for testing anymore.
So, while we cannot yet have a proper injector decide what to inject in
what context, we can at least let the constructor take an injected
dependency, and provide a "Poka-yoke" for as long as we are not able to do
real injection. This would look something like this:
{{{#!php
class WP_Comment_Query {
protected $dbh;
public function __construct( $query = '', $wpdb = null ) {
$this->dbh = null !== $wpdb ? $wpdb : WP_service::get( 'wpdb' );
// [...]
}
}
}}}
Improvements would be using the short-form ternary (PHP 5.3+) and
typehinting against an interface. However, please, '''do not typehint
against the WPDB class''', this defeats the whole point. This would just
very tightly couple the class to the exact WPDB implementation, and any
dependency injection would just be of esthetic nature.
With the above constructor, the injected dependency is optional, so
existing code will still work, but the unit tests can now inject a mocked
WPDB instance into the constructor for real unit tests.
'''6. What about the interface'''
As mentioned under 5., typehinting against an interface would be a clear
improvement. Let's imagine we had a `WPDB_Interface` and examine what
would happen if we use it together with the idea of a Service Locator.
First of all, any code making DB operations would need to be coded against
that interface, not against the actual implementation. For code that does
not using any typehinting or instance-checking, this should not make a
difference.
For our example code above, we would now have something like this:
{{{#!php
class WP_Comment_Query {
/** @var WPDB_Interface */
protected $dbh;
public function __construct( $query = '', WPDB_Interface $wpdb = null )
{
$this->dbh = null !== $wpdb ? $wpdb : WP_Service::get(
WPDB_Interface );
// [...]
}
}
}}}
We have two changes here:
A. The constructor argument is type-hinted. This means that, would we
instantiate the `WP_Comment_Query` through a real Injector, the Injector
could by itself figure out that it needs to pass the instance of the
current DB handler into that constructor. So in that (wishful thinking)
scenario, neither the `WP_Comment_Query` class, nor its surrounding code
would need to know anything about the DB stack at all. `WP_Comment_Query`
needs something that behaves like a WPDB, and it can just assume that when
it gets instantiated, what it needs is automagically available within its
constructor.
B. The poka-yoke fallback that uses the Service Locator does not request
an arbitrary string identifier, it requests an object that implements the
interface it needs. The Service Locator, which should know more about the
current environment than the `WP_Comment_Query` class, will then decide
what exact object to pass around. Some objects, like the WPDB here, are
globally shared, so everyone gets a reference to the same instance. Others
might be freshly instantiated for each request, like a `WP_Query`. The
Service Locator will deal with this behind the scenes, the ones requesting
such interfaces should not need to care.
Hope this all makes sense, and can't wait to see where this ticket will
take us... :)
--
Ticket URL: <https://core.trac.wordpress.org/ticket/37699#comment:1>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform
More information about the wp-trac
mailing list