Skip to content

Commit

Permalink
Add support for handlers where constructor params are objects.
Browse files Browse the repository at this point in the history
This is achieved by making the ClassLoader recursive.

As a side effect it is necessary to convert constructor params to camelCase
to support non PSR-2 compliant constructors (e.g. Raven_Client for RavenHandler).

A couple of examples have been added for Raven and Redis.
  • Loading branch information
dmnc committed Aug 12, 2015
1 parent 02b1051 commit 4e5c5ae
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 14 deletions.
22 changes: 22 additions & 0 deletions examples/dependency_config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
version: 1

disable_existing_loggers: false

handlers:
sentry:
class: Monolog\Handler\RavenHandler
level: DEBUG
raven_client:
class: Raven_Client
options_or_dsn: https://something:[email protected]/1
redis:
class: Monolog\Handler\RedisHandler
level: DEBUG
key: cascade
redis:
class: Redis
connect: ['127.0.0.1', 6379]
loggers:
dependency:
handlers: [sentry, redis]
13 changes: 13 additions & 0 deletions examples/dependency_logger.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php
require_once(realpath(__DIR__.'/../vendor/autoload.php'));

use Cascade\Cascade;

// For these to work you will need php-redis and raven/raven

// You will want to update this file with a valid dsn
$loggerConfigFile = realpath(__DIR__.'/dependency_config.yml');

Cascade::fileConfig($loggerConfigFile);
Cascade::getLogger('dependency')->info('Well, that works!');
Cascade::getLogger('dependency')->error('Maybe not...');
21 changes: 21 additions & 0 deletions src/Config/Loader/ClassLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,25 @@ protected function setClass()
unset($this->rawOptions['class']);
}

/**
* Recursively loads objects into any of the rawOptions that represent
* a class
*
* @author Dom Morgan <[email protected]>
*/
protected function loadChildClasses()
{
foreach ($this->rawOptions as &$option) {
if (is_array($option)
&& array_key_exists('class', $option)
&& class_exists($option['class'])
) {
$classLoader = new ClassLoader($option);
$option = $classLoader->load();
}
}
}

/**
* Return option values indexed by name using camelCased keys
*
Expand Down Expand Up @@ -163,6 +182,8 @@ private function resolveOptions()
*/
public function load()
{
$this->loadChildClasses();

list($constructorResolvedOptions, $extraResolvedOptions) = $this->resolveOptions();
$instance = $this->reflected->newInstanceArgs($constructorResolvedOptions);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
namespace Cascade\Config\Loader\ClassLoader\Resolver;

use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;

/**
* Constructor Resolver. Pull args from the contructor and set up an option
Expand Down Expand Up @@ -55,15 +56,19 @@ public function __construct(\ReflectionClass $reflected)
/**
* Fetches constructor args (array of ReflectionParameter) from the reflected class
* and set them as an associative array
*
* Convert the parameter names to camelCase for classes that have contructor
* params defined in snake_case for consistency with the options
*/
public function initConstructorArgs()
{
$constructor = $this->reflected->getConstructor();
$nameConverter = new CamelCaseToSnakeCaseNameConverter();

if (!is_null($constructor)) {
// Index parameters by their names
foreach ($constructor->getParameters() as $param) {
$this->constructorArgs[$param->getName()] = $param;
$this->constructorArgs[$nameConverter->denormalize($param->getName())] = $param;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
use Cascade\Config\Loader\ClassLoader\Resolver\ConstructorResolver;
use Cascade\Tests\Fixtures\SampleClass;

use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;

/**
* Class ConstructorResolverTest
*
Expand Down Expand Up @@ -74,13 +76,17 @@ public function testConstructor()

/**
* Test that constructor args were pulled properly
*
* Notie that we need to deuplicate the CamelCase conversion here for old
* fashioned classes
*/
public function testInitConstructorArgs()
{
$expectedConstructorArgs = array();
$nameConverter = new CamelCaseToSnakeCaseNameConverter();

foreach ($this->getConstructorArgs() as $param) {
$expectedConstructorArgs[$param->getName()] = $param;
$expectedConstructorArgs[$nameConverter->denormalize($param->getName())] = $param;
}
$this->assertEquals($expectedConstructorArgs, $this->resolver->getConstructorArgs());
}
Expand All @@ -91,12 +97,13 @@ public function testInitConstructorArgs()
public function testHashToArgsArray()
{
$this->assertEquals(
array('someValue', 'hello', 'there'),
array('someValue', 'hello', 'there', 'slither'),
$this->resolver->hashToArgsArray(
array( // Not properly ordered on purpose
'optionalB' => 'there',
'optionalA' => 'hello',
'mandatory' => 'someValue'
'optionalB' => 'there',
'optionalA' => 'hello',
'optionalSnake' => 'slither',
'mandatory' => 'someValue',
)
)
);
Expand All @@ -113,22 +120,23 @@ public function optionsProvider()
{
return array(
array(
array('someValue', 'hello', 'there'), // Expected resolved options
array('someValue', 'hello', 'there', 'slither'), // Expected resolved options
array( // Options (order should not matter, part of resolution)
'optionalB' => 'there',
'optionalA' => 'hello',
'mandatory' => 'someValue'
'optionalB' => 'there',
'optionalA' => 'hello',
'mandatory' => 'someValue',
'optionalSnake' => 'slither',
)
),
array(
array('someValue', 'hello', 'BBB'),
array('someValue', 'hello', 'BBB', 'snake'),
array(
'mandatory' => 'someValue',
'optionalA' => 'hello'
'optionalA' => 'hello',
)
),
array(
array('someValue', 'AAA', 'BBB'),
array('someValue', 'AAA', 'BBB', 'snake'),
array('mandatory' => 'someValue')
)
);
Expand Down
26 changes: 26 additions & 0 deletions tests/Config/Loader/ClassLoaderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

use Cascade\Config\Loader\ClassLoader;
use Cascade\Tests\Fixtures\SampleClass;
use Cascade\Tests\Fixtures\DependentClass;

/**
* Class ClassLoaderTest
Expand Down Expand Up @@ -145,4 +146,29 @@ public function testLoad()

$this->assertEquals($expectedInstance, $instance);
}

/**
* Test a nested class to load
*
* @author Dom Morgan <[email protected]>
*/
public function testLoadDependency()
{
$options = array(
'class' => 'Cascade\Tests\Fixtures\DependentClass',
'dependency' => [
'class' => 'Cascade\Tests\Fixtures\SampleClass',
'mandatory' => 'someValue',
]
);

$loader = new ClassLoader($options);
$instance = $loader->load();

$expectedInstance = new DependentClass(
new SampleClass('someValue')
);

$this->assertEquals($expectedInstance, $instance);
}
}
47 changes: 47 additions & 0 deletions tests/Fixtures/DependentClass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php
/**
* This file is part of the Monolog Cascade package.
*
* (c) Raphael Antonmattei <[email protected]>
* (c) The Orchard
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Cascade\Tests\Fixtures;

/**
* Class SampleClass
*
* @author Raphael Antonmattei <[email protected]>
*/
class DependentClass
{
/**
* An object dependency
* @var Cascade\Tests\Fixtures\SampleClass
*/
private $dependency;

/**
* Constructor
*
* @param mixed $mandatory Some mandatory param
* @param string $optionalA Some optional param
*/
public function __construct(
SampleClass $dependency
) {
$this->setDependency($dependency);
}

/**
* Set the object dependency
*
* @param Cascade\Tests\Fixtures\SampleClass $dependency Some value
*/
public function setDependency($dependency)
{
$this->dependency = $dependency;
}
}
3 changes: 2 additions & 1 deletion tests/Fixtures/SampleClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ class SampleClass
public function __construct(
$mandatory,
$optionalA = 'AAA',
$optionalB = 'BBB'
$optionalB = 'BBB',
$optional_snake = 'snake'
) {
$this->setMandatory($mandatory);
}
Expand Down

0 comments on commit 4e5c5ae

Please sign in to comment.