[wp-trac] [WordPress Trac] #37699: Death to Globals Episode #1: A Registry, A Pattern
WordPress Trac
noreply at wordpress.org
Sat Sep 3 18:58:48 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 ChriCo):
Howdy.
as i see, @schlessera found enough valid arguments for this "discussion"
above, so we can continue the work on a integration of a ServiceContainer
into WordPress.
Since we're also working on composer-integration (#36335), we should maybe
think - even it's still far far away and no PHP > 5.2 - about the
integration of some PSR-"standards". The PSR-group has currently a
proposal for the integration of Containers (https://github.com/php-fig
/fig-standards/blob/master/proposed/container.md), which should be at
least known and respected when developing something similar for WordPress.
Based on the topics above there should also be discussed how the
ServiceContainer should be work.
== 0. Naming ==
The Container should be called {{{WP_Service_Container}}} and be placed
into its own subfolder called {{{service}}}. This - based on my notes
above - follows not directly the PSR-standards but in future we ''maybe''
switch to namespaces which leads us to PSR-4 (https://github.com/php-fig
/fig-standards/blob/master/accepted/PSR-4-autoloader.md) and to
{{{WP\Service\Container}}} --> {{{/service/Container.php}}}. ''Note:'' i
just want to keep - here - the possibility to improve even further in
future.. ;)
== 1. API Design ==
It needs to be discussed how the API should look like.
=== WP_Service_Container ===
This seems to be the major class which has basic implementation of
{{{get}}}, {{{set}}}, {{{has}}}. It needs to be discussed what..
- {{{get}}} returns/throws if something is accessed which is not there.
- how to implemented {{{protected}}} values which are not overwriteable
and how to handle setting a protected value.
- how to implemented creation of new instances --> e.g. {{{factory}}} -
see Pimple http://pimple.sensiolabs.org/#defining-factory-services.
- what should be extended/implemented to the class (such as
{{{ArrayAccess}}})
- naming rules of the {{$key}} such as "prefix every internal thing with
the "vendor"-name and seperate it with dot's. Configurations are called
e.G. {{{wp.config.db}}}) and classes are called e.G. {{{wp.db}}})
- LazyLoading of instances when they are first accessed via {{{set}}}
- ...
Short example of usage:
{{{#!php
<?php
$c = new WP_Service_Container();
// ---------------
// set-get-has
$c->set( 'foo', 'bar' );
echo $c->get( 'foo' ); // bar
var_dump(
$c->has( 'foo' ),
$c->has( 'lorum ipsum' )
); // TRUE, FALSE
// ---------------
// protected
$c->protected( 'foo', 'bar' );
$c->set( 'foo', 'b..arrrrr i\'m a pirate' ); // '''not''' possible -
exception or at least return FALSE;
echo $c->get( 'foo' ); // bar
// factory
// just an example, since we're not able to use closures we maybe need to
build something around it.
// $key, $class_name (instance of class name is the returned value), $args
$c->factory( 'wp.db', 'wpdb', array( 'db-user', 'db-pass', 'db-name', 'db-
host' ) );
$wpdb1 = $c->get( 'wp.db' ); // complete new instance of wpdb
$wpdb2 = $c->get( 'wp.db' ); // complete new instance of wpdb
}}}
=== Configurations ===
The ServiceContainer on its own is a nice thing. But one step further we
can think about the generalization of configurations (such as database-
credentials, debug modes, ... stuff from {{{wp-config.php}}} and
{{{define}}}). The configurations should be "protected" (not changeable)
and grouped by "type" such as "db", "debug", ...
Short example to give you a quick view on it:
{{{#!php
<?php
// get somehow the service container..
// and set the database configuration as "protected" to disallow
overwriting the values.
$service_container->protected(
'wp.config.db',
array(
'user' => 'db-user',
'pass' => 'db-pass',
'name' => 'db-name',
'host' => 'localhost'
)
);
// later on in code when setting up the wpdb
$conf = $service_container->get( 'wp.config.db' );
$service_container->protected(
'wp.db',
new wpdb(
$conf[ 'user' ],
$conf[ 'pass' ],
$conf[ 'name' ],
$conf[ 'host' ]
)
);
}}}
== 2. $GLOBALS vs. Singleton vs. instance-holding function vs. hook ==
Since we're hardly depending on some {{{$GLOBALS}}} in every single
situation, its maybe not the best choice to add the ServiceContainer to
the globals to remove some of them. On the other hand, if we don't rely on
a global available class, it's highly possible, that the ServiceContainer
will contain static methods and implement some Singleton-Pattern.
=== v1 - global ===
But it to global..than its accessible to everyone and overwritable too..
{{{#!php
<?php
$GLOBALS[ 'wp_service_container' ] = new WP_Service_Container();
// ...later on
$GLOBALS[ 'wp_service_container' ]->set( $key, $value );
$value = $GLOBALS[ 'wp_service_container' ]->get( $key );
}}}
=== v2 - singleton ===
Singleton, no global variable required..but everything is static. Not the
best choice here since mocking in UnitTests could be getting much more
difficulty.
{{{#!php
<?php
WP_Service_Container::get_instance();
// ...later on
WP_Service_Container::set( $key, $value );
$value = WP_Service_Container::get( $key );
}}}
=== v3 - instance holding function ===
The intance-holding function is a mix of "global"-accessible and
singleton, but no static methods/variables required. At least worth to
discuss about it.
{{{#!php
<?php
function wp_service_container( ) {
static $instance;
if ( $instance === NULL ) {
$instance = new WP_Service_Container();
}
return $instance;
}
// ...later on
wp_service_container()->set( $key, $value );
$value = wp_service_container()->get( $key );
}}}
=== v4 - hook ===
The hook seems to be another valid solution here, since we're not having
any global variable and its accessible on a very specific point for
everyone. The main problem here is, that the complete core has to rely on
this single hook to get everything which is stored into the container. So
everything which needs access to a {{{$value}}} needs to use this hook,
which means we could maybe run into "priority"-problems.
{{{#!php
do_action( 'wp_service_container', new WP_Service_Container() );
// ...later on
add_action( 'wp_service_container', function(
WP_Service_Container_Interface $container ) {
$container->set( $key, $value );
$value = $container->get( $key );
} );
}}}
This needs to be discussed since we're still relying heavily on global
variables in every single template. I would not implement a Singleton-
class and/or something with static methods/variables here, since we're
also having a lot of disadvantages with it. A good way could be v3, since
we can mock the function in unit tests and access it easily everywhere.
But could also be a main disadvantage, if the container is always
accessible.
----
For now i think thats enough input. We should define some basics and
afterwards we can continue with more features such es ConfigBuilder's
(e.G. {{{WP_Service_ConfigBuilder_PluginConfig}}} whic parses the Plugin
file headers:
{{{#!php
<?php
$service_container->set( 'my-plugin.config', new
WP_Service_ConfigBuilder_PluginConfig( __FILE__ ) );
$config = $service_container->get( 'my-plugin.config' );
echo $config[ 'textdomain' ]; // yay, the textdomain!
}}}
Have a nice weekend!
--
Ticket URL: <https://core.trac.wordpress.org/ticket/37699#comment:73>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform
More information about the wp-trac
mailing list