Make $wgMWLoggerDefaultSpi more expressive
authorBryan Davis <bd808@wikimedia.org>
Thu, 23 Oct 2014 03:53:14 +0000 (21:53 -0600)
committerBryan Davis <bd808@wikimedia.org>
Sat, 25 Oct 2014 22:00:06 +0000 (16:00 -0600)
Allow $wgMWLoggerDefaultSpi to specify a more expressive object creation
by introducing a new ObjectFactory class which can process an array of
instructions to call either an object constructor or a factory method
with an array of arguments. This allows removal of the
$wgMWLoggerMonologSpiConfig global variable in favor of configuration
using $wgMWLoggerDefaultSpi.

New classes introduced:
; ObjectFactory
: Construct objects from configuration instructions.

Change-Id: If56cce5dcb1ad5712e238d6e2dab809a351f79be

docs/mwlogger.txt
includes/AutoLoader.php
includes/DefaultSettings.php
includes/debug/logger/Logger.php
includes/debug/logger/NullSpi.php
includes/debug/logger/monolog/Spi.php
includes/libs/ObjectFactory.php [new file with mode: 0644]

index 9964e8b..5a3e249 100644 (file)
@@ -50,10 +50,8 @@ a more feature rich logging configuration.
 
 == Globals ==
 ; $wgMWLoggerDefaultSpi
-: Default service provider interface to use with MWLogger
-; $wgMWLoggerMonologSpiConfig
-: Configuration for MWLoggerMonologSpi describing how to configure the
-  Monolog logger instances.
+: Specification for creating the default service provider interface to use
+  with MWLogger
 
 [0]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
 [1]: https://github.com/Seldaek/monolog
index 6936570..25bf47d 100644 (file)
@@ -132,6 +132,7 @@ $wgAutoloadLocalClasses = array(
        'MWHookException' => 'includes/Hooks.php',
        'MWHttpRequest' => 'includes/HttpFunctions.php',
        'MWNamespace' => 'includes/MWNamespace.php',
+       'ObjectFactory' => 'includes/libs/ObjectFactory.php',
        'OutputPage' => 'includes/OutputPage.php',
        'PathRouter' => 'includes/PathRouter.php',
        'PathRouterPatternReplacer' => 'includes/PathRouter.php',
index af36a64..d828f38 100644 (file)
@@ -5218,38 +5218,21 @@ $wgDebugLogGroups = array();
 /**
  * Default service provider for creating MWLogger instances.
  *
- * This can either be the name of a class implementing the MWLoggerSpi
- * interface with a zero argument constructor or a callable that will return
- * an MWLoggerSpi instance. Alternately the MWLogger::registerProvider method
- * can be called to inject an MWLoggerSpi instance into MWLogger and bypass
- * the use of this configuration variable.
- *
- * @since 1.25
- * @var $wgMWLoggerDefaultSpi string|callable
- * @see MwLogger
- */
-$wgMWLoggerDefaultSpi = 'MWLoggerNullSpi';
-
-/**
- * Configuration for MWLoggerMonologSpi logger factory.
+ * The value should be an array suitable for use with
+ * ObjectFactory::getObjectFromSpec(). The created object is expected to
+ * implement the MWLoggerSpi interface. See ObjectFactory for additional
+ * details.
  *
- * Default configuration installs a null handler that will silently discard
- * all logging events.
+ * Alternately the MWLogger::registerProvider method can be called to inject
+ * an MWLoggerSpi instance into MWLogger and bypass the use of this
+ * configuration variable entirely.
  *
  * @since 1.25
- * @see MWLoggerMonologSpi
+ * @var array $wgMWLoggerDefaultSpi
+ * @see MwLogger
  */
-$wgMWLoggerMonologSpiConfig = array(
-       'loggers' => array(
-               '@default' => array(
-                       'handlers' => array( 'null' ),
-               ),
-       ),
-       'handlers' => array(
-               'null' => array(
-                       'class' => '\\Monolog\\Logger\\NullHandler',
-               ),
-       ),
+$wgMWLoggerDefaultSpi = array(
+       'class' => 'MWLoggerNullSpi',
 );
 
 /**
index f5dd1cf..7164bfa 100644 (file)
@@ -198,11 +198,9 @@ class MWLogger implements \Psr\Log\LoggerInterface {
        public static function getInstance( $channel ) {
                if ( self::$spi === null ) {
                        global $wgMWLoggerDefaultSpi;
-                       if ( is_callable( $wgMWLoggerDefaultSpi ) ) {
-                               $provider = $wgMWLoggerDefaultSpi();
-                       } else {
-                               $provider = new $wgMWLoggerDefaultSpi();
-                       }
+                       $provider = ObjectFactory::getObjectFromSpec(
+                               $wgMWLoggerDefaultSpi
+                       );
                        self::registerProvider( $provider );
                }
 
index 6c38c32..33304fc 100644 (file)
  * MWLogger service provider that creates \Psr\Log\NullLogger instances.
  * A NullLogger silently discards all log events sent to it.
  *
+ * Usage:
+ * @code
+ * $wgMWLoggerDefaultSpi = array(
+ *   'class' => 'MWLoggerNullSpi',
+ * );
+ * @endcode
+ *
  * @see MWLogger
  * @since 1.25
  * @author Bryan Davis <bd808@wikimedia.org>
index fc39b25..e514715 100644 (file)
  * for any channel that isn't explicitly named in the 'loggers' configuration
  * section.
  *
- * Configuration can be specified using the $wgMWLoggerMonologSpiConfig global
- * variable.
- *
- * Example:
+ * Configuration will most typically be provided in the $wgMWLoggerDefaultSpi
+ * global configuration variable used by MWLogger to construct its default SPI
+ * provider:
  * @code
- * $wgMWLoggerMonologSpiConfig = array(
- *     'loggers' => array(
- *         '@default' => array(
- *             'processors' => array( 'wiki', 'psr', 'pid', 'uid', 'web' ),
- *             'handlers'   => array( 'stream' ),
- *         ),
- *         'runJobs' => array(
- *             'processors' => array( 'wiki', 'psr', 'pid' ),
- *             'handlers'   => array( 'stream' ),
- *         )
- *     ),
- *     'processors' => array(
- *         'wiki' => array(
- *             'class' => 'MWLoggerMonologProcessor',
- *         ),
- *         'psr' => array(
- *             'class' => '\\Monolog\\Processor\\PsrLogMessageProcessor',
- *         ),
- *         'pid' => array(
- *             'class' => '\\Monolog\\Processor\\ProcessIdProcessor',
- *         ),
- *         'uid' => array(
- *             'class' => '\\Monolog\\Processor\\UidProcessor',
- *         ),
- *         'web' => array(
- *             'class' => '\\Monolog\\Processor\\WebProcessor',
- *         ),
- *     ),
- *     'handlers' => array(
- *         'stream' => array(
- *             'class'     => '\\Monolog\\Handler\\StreamHandler',
- *             'args'      => array( 'path/to/your.log' ),
- *             'formatter' => 'line',
- *         ),
- *         'redis' => array(
- *             'class'     => '\\Monolog\\Handler\\RedisHandler',
- *             'args'      => array( function() {
- *                     $redis = new Redis();
- *                     $redis->connect( '127.0.0.1', 6379 );
- *                     return $redis;
- *                 },
- *                 'logstash'
- *             ),
- *             'formatter' => 'logstash',
- *         ),
- *         'udp2log' => array(
- *             'class' => 'MWLoggerMonologHandler',
- *             'args' => array(
- *                 'udp://127.0.0.1:8420/mediawiki
- *             ),
- *             'formatter' => 'line',
- *         ),
- *     ),
- *     'formatters' => array(
- *         'line' => array(
- *             'class' => '\\Monolog\\Formatter\\LineFormatter',
- *          ),
- *          'logstash' => array(
- *              'class' => '\\Monolog\\Formatter\\LogstashFormatter',
- *              'args'  => array( 'mediawiki', php_uname( 'n' ), null, '', 1 ),
- *          ),
- *     ),
+ * $wgMWLoggerDefaultSpi = array(
+ *   'class' => 'MWLoggerMonologSpi',
+ *   'args' => array( array(
+ *       'loggers' => array(
+ *           '@default' => array(
+ *               'processors' => array( 'wiki', 'psr', 'pid', 'uid', 'web' ),
+ *               'handlers'   => array( 'stream' ),
+ *           ),
+ *           'runJobs' => array(
+ *               'processors' => array( 'wiki', 'psr', 'pid' ),
+ *               'handlers'   => array( 'stream' ),
+ *           )
+ *       ),
+ *       'processors' => array(
+ *           'wiki' => array(
+ *               'class' => 'MWLoggerMonologProcessor',
+ *           ),
+ *           'psr' => array(
+ *               'class' => '\\Monolog\\Processor\\PsrLogMessageProcessor',
+ *           ),
+ *           'pid' => array(
+ *               'class' => '\\Monolog\\Processor\\ProcessIdProcessor',
+ *           ),
+ *           'uid' => array(
+ *               'class' => '\\Monolog\\Processor\\UidProcessor',
+ *           ),
+ *           'web' => array(
+ *               'class' => '\\Monolog\\Processor\\WebProcessor',
+ *           ),
+ *       ),
+ *       'handlers' => array(
+ *           'stream' => array(
+ *               'class'     => '\\Monolog\\Handler\\StreamHandler',
+ *               'args'      => array( 'path/to/your.log' ),
+ *               'formatter' => 'line',
+ *           ),
+ *           'redis' => array(
+ *               'class'     => '\\Monolog\\Handler\\RedisHandler',
+ *               'args'      => array( function() {
+ *                       $redis = new Redis();
+ *                       $redis->connect( '127.0.0.1', 6379 );
+ *                       return $redis;
+ *                   },
+ *                   'logstash'
+ *               ),
+ *               'formatter' => 'logstash',
+ *           ),
+ *           'udp2log' => array(
+ *               'class' => 'MWLoggerMonologHandler',
+ *               'args' => array(
+ *                   'udp://127.0.0.1:8420/mediawiki
+ *               ),
+ *               'formatter' => 'line',
+ *           ),
+ *       ),
+ *       'formatters' => array(
+ *           'line' => array(
+ *               'class' => '\\Monolog\\Formatter\\LineFormatter',
+ *            ),
+ *            'logstash' => array(
+ *                'class' => '\\Monolog\\Formatter\\LogstashFormatter',
+ *                'args'  => array( 'mediawiki', php_uname( 'n' ), null, '', 1 ),
+ *            ),
+ *       ),
+ *   ) ),
  * );
  * @endcode
  *
@@ -119,14 +121,9 @@ class MWLoggerMonologSpi implements MWLoggerSpi {
 
 
        /**
-        * @param array $config Configuration data. Defaults to global
-        *     $wgMWLoggerMonologSpiConfig
+        * @param array $config Configuration data.
         */
-       public function __construct( $config = null ) {
-               if ( $config === null ) {
-                       global $wgMWLoggerMonologSpiConfig;
-                       $config = $wgMWLoggerMonologSpiConfig;
-               }
+       public function __construct( array $config ) {
                $this->config = $config;
                $this->reset();
        }
@@ -166,8 +163,8 @@ class MWLoggerMonologSpi implements MWLoggerSpi {
                                $this->config['loggers'][$channel] :
                                $this->config['loggers']['@default'];
 
-                               $monolog = $this->createLogger( $channel, $spec );
-                               $this->singletons['loggers'][$channel] = new MWLogger( $monolog );
+                       $monolog = $this->createLogger( $channel, $spec );
+                       $this->singletons['loggers'][$channel] = new MWLogger( $monolog );
                }
 
                return $this->singletons['loggers'][$channel];
@@ -206,7 +203,8 @@ class MWLoggerMonologSpi implements MWLoggerSpi {
        protected function getProcessor( $name ) {
                if ( !isset( $this->singletons['processors'][$name] ) ) {
                        $spec = $this->config['processors'][$name];
-                       $this->singletons['processors'][$name] = $this->instantiate( $spec );
+                       $processor = ObjectFactory::getObjectFromSpec( $spec );
+                       $this->singletons['processors'][$name] = $processor;
                }
                return $this->singletons['processors'][$name];
        }
@@ -220,7 +218,7 @@ class MWLoggerMonologSpi implements MWLoggerSpi {
        protected function getHandler( $name ) {
                if ( !isset( $this->singletons['handlers'][$name] ) ) {
                        $spec = $this->config['handlers'][$name];
-                       $handler = $this->instantiate( $spec );
+                       $handler = ObjectFactory::getObjectFromSpec( $spec );
                        $handler->setFormatter( $this->getFormatter( $spec['formatter'] ) );
                        $this->singletons['handlers'][$name] = $handler;
                }
@@ -236,44 +234,9 @@ class MWLoggerMonologSpi implements MWLoggerSpi {
        protected function getFormatter( $name ) {
                if ( !isset( $this->singletons['formatters'][$name] ) ) {
                        $spec = $this->config['formatters'][$name];
-                       $this->singletons['formatters'][$name] = $this->instantiate( $spec );
+                       $formatter = ObjectFactory::getObjectFromSpec( $spec );
+                       $this->singletons['formatters'][$name] = $formatter;
                }
                return $this->singletons['formatters'][$name];
        }
-
-
-       /**
-        * Instantiate the requested object.
-        *
-        * The specification array must contain a 'class' key with string value that
-        * specifies the class name to instantiate. It can optionally contain an
-        * 'args' key that provides constructor arguments.
-        *
-        * @param array $spec Object specification
-        * @return object
-        */
-       protected function instantiate( $spec ) {
-               $clazz = $spec['class'];
-               $args = isset( $spec['args'] ) ? $spec['args'] : array();
-               // If an argument is a callable, call it.
-               // This allows passing things such as a database connection to a logger.
-               $args = array_map( function ( $value ) {
-                               if ( is_callable( $value ) ) {
-                                       return $value();
-                               } else {
-                                       return $value;
-                               }
-                       }, $args );
-
-               if ( empty( $args ) ) {
-                       $obj = new $clazz();
-
-               } else {
-                       $ref = new ReflectionClass( $clazz );
-                       $obj = $ref->newInstanceArgs( $args );
-               }
-
-               return $obj;
-       }
-
 }
diff --git a/includes/libs/ObjectFactory.php b/includes/libs/ObjectFactory.php
new file mode 100644 (file)
index 0000000..ee696c3
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+/**
+ * @section LICENSE
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Construct objects from configuration instructions.
+ *
+ * @author Bryan Davis <bd808@wikimedia.org>
+ * @copyright © 2014 Bryan Davis and Wikimedia Foundation.
+ */
+class ObjectFactory {
+
+       /**
+        * Instantiate an object based on a specification array.
+        *
+        * The specification array must contain a 'class' key with string value
+        * that specifies the class name to instantiate or a 'factory' key with
+        * a callable (is_callable() === true). It can optionally contain
+        * an 'args' key that provides arguments to pass to the
+        * constructor/callable.
+        *
+        * Object construction using a specification having both 'class' and
+        * 'args' members will call the constructor of the class using
+        * ReflectionClass::newInstanceArgs. The use of ReflectionClass carries
+        * a performance penalty and should not be used to create large numbers of
+        * objects. If this is needed, consider introducing a factory method that
+        * can be called via call_user_func_array() instead.
+        *
+        * Values in the arguments collection which are Closure instances will be
+        * expanded by invoking them with no arguments before passing the
+        * resulting value on to the constructor/callable. This can be used to
+        * pass DatabaseBase instances or other live objects to the
+        * constructor/callable.
+        *
+        * @param array $spec Object specification
+        * @return object
+        * @throws InvalidArgumentException when object specification does not
+        * contain 'class' or 'factory' keys
+        * @throws ReflectionException when 'args' are supplied and 'class'
+        * constructor is non-public or non-existant
+        */
+       public static function getObjectFromSpec( $spec ) {
+               $args = isset( $spec['args'] ) ? $spec['args'] : array();
+
+               $args = array_map( function ( $value ) {
+                       if ( is_object( $value ) && $value instanceof Closure ) {
+                               // If an argument is a Closure, call it.
+                               return $value();
+                       } else {
+                               return $value;
+                       }
+               }, $args );
+
+               if ( isset( $spec['class'] ) ) {
+                       $clazz = $spec['class'];
+                       if ( !$args ) {
+                               $obj = new $clazz();
+                       } else {
+                               $ref = new ReflectionClass( $clazz );
+                               $obj = $ref->newInstanceArgs( $args );
+                       }
+               } elseif ( isset( $spec['factory'] ) ) {
+                       $obj = call_user_func_array( $spec['factory'], $args );
+               } else {
+                       throw new InvalidArgumentException(
+                               'Provided specification lacks both factory and class parameters.'
+                       );
+               }
+
+               return $obj;
+       }
+}