* 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
*
/**
- * @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();
}
$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];
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];
}
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;
}
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;
- }
-
}
--- /dev/null
+<?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;
+ }
+}