├── .coveralls.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── examples ├── dependency_config.yml ├── dependency_logger.php ├── logger_config.json ├── logger_config.yml ├── multiple_loggers.php └── single_logger.php ├── phpunit.xml.dist ├── src ├── Cascade.php ├── Config.php ├── Config │ ├── ConfigLoader.php │ └── Loader │ │ ├── ClassLoader.php │ │ ├── ClassLoader │ │ ├── FormatterLoader.php │ │ ├── HandlerLoader.php │ │ ├── LoggerLoader.php │ │ ├── ProcessorLoader.php │ │ └── Resolver │ │ │ ├── ConstructorResolver.php │ │ │ └── ExtraOptionsResolver.php │ │ ├── FileLoader │ │ ├── FileLoaderAbstract.php │ │ ├── Json.php │ │ ├── PhpArray.php │ │ └── Yaml.php │ │ └── PhpArray.php └── Util.php └── tests ├── CascadeTest.php ├── Config ├── ConfigLoaderTest.php └── Loader │ ├── ClassLoader │ ├── FormatterLoaderTest.php │ ├── HandlerLoaderTest.php │ ├── LoggerLoaderTest.php │ ├── ProcessorLoaderTest.php │ └── Resolver │ │ ├── ConstructorResolverTest.php │ │ └── ExtraOptionsResolverTest.php │ ├── ClassLoaderTest.php │ ├── FileLoader │ ├── FileLoaderAbstractTest.php │ ├── JsonTest.php │ ├── PhpArrayTest.php │ └── YamlTest.php │ └── PhpArrayTest.php ├── ConfigTest.php ├── Fixtures.php ├── Fixtures ├── DependentClass.php ├── SampleClass.php ├── fixture_config.json ├── fixture_config.php ├── fixture_config.yml ├── fixture_invalid_config.php ├── fixture_sample.json └── fixture_sample.yml └── UtilTest.php /.coveralls.yml: -------------------------------------------------------------------------------- 1 | coverage_clover: clover.xml 2 | json_path: coveralls-upload.json 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /composer.lock 3 | /composer.phar 4 | /phpunit.xml 5 | /vendor 6 | /.idea 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | env: 4 | global: 5 | - COVERALLS=0 6 | - PHPCS=0 7 | 8 | matrix: 9 | include: 10 | - php: 5.3 11 | dist: precise 12 | env: COMPOSER_MEMORY_LIMIT=-1 13 | - php: 5.4 14 | dist: trusty 15 | - php: 5.5 16 | dist: trusty 17 | - php: 5.6 18 | env: COVERALLS=1 PHPCS=1 19 | - php: 7 20 | 21 | allow_failures: 22 | # allow failure for Php < 5.6 23 | - php: 5.3 24 | - php: 5.4 25 | - php: 5.5 26 | fast_finish: true 27 | 28 | install: 29 | composer install --prefer-source 30 | 31 | script: 32 | - sh -c "if [ '$COVERALLS' = '1' ]; then ./vendor/bin/phpunit --coverage-clover clover.xml ; else ./vendor/bin/phpunit ; fi" 33 | - sh -c "if [ '$PHPCS' = '1' ]; then vendor/bin/phpcs -p --extensions=php --standard=PSR2 ./src ./tests ; fi" 34 | 35 | after_script: 36 | - sh -c "if [ '$COVERALLS' = '1' ]; then vendor/bin/coveralls -vvv ; fi" 37 | 38 | 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Raphaël Antonmattei 4 | Copyright (c) 2015 The Orchard 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Monolog Cascade [![Build Status](https://travis-ci.org/theorchard/monolog-cascade.svg?branch=master)](https://travis-ci.org/theorchard/monolog-cascade) [![Coverage Status](https://coveralls.io/repos/theorchard/monolog-cascade/badge.svg?branch=master)](https://coveralls.io/r/theorchard/monolog-cascade?branch=master) 2 | =============== 3 | 4 | What is Monolog Cascade? 5 | ------------------------ 6 | 7 | Monolog Cascade is a [Monolog](https://github.com/Seldaek/monolog) extension that allows you to set up and configure multiple loggers and handlers from a single config file. 8 | 9 | It's been inspired by the [`logging.config`](https://docs.python.org/3.4/library/logging.config.html?highlight=fileconfig#module-logging.config) Python module. 10 | 11 | 12 | ------------ 13 | 14 | 15 | Installation 16 | ------------ 17 | 18 | Add `monolog-cascade` as a requirement in your `composer.json` file or run 19 | ```sh 20 | $ composer require theorchard/monolog-cascade 21 | ``` 22 | 23 | Note: Monolog Cascade requires PHP 5.3.9 or higher. 24 | 25 | Usage 26 | ----- 27 | 28 | ```php 29 | info('Well, that works!'); 44 | Cascade::getLogger('myLogger')->error('Maybe not...'); 45 | ``` 46 | 47 | Configuring your loggers 48 | ------------------------ 49 | Monolog Cascade supports the following config formats: 50 | - Yaml 51 | - JSON 52 | - PHP File returning Array 53 | - PHP Array 54 | 55 | ### Configuration structure 56 | Here is a sample Yaml config file: 57 | ```yaml 58 | 59 | formatters: 60 | dashed: 61 | class: Monolog\Formatter\LineFormatter 62 | format: "%datetime%-%channel%.%level_name% - %message%\n" 63 | handlers: 64 | console: 65 | class: Monolog\Handler\StreamHandler 66 | level: DEBUG 67 | formatter: dashed 68 | processors: [memory_processor] 69 | stream: php://stdout 70 | info_file_handler: 71 | class: Monolog\Handler\StreamHandler 72 | level: INFO 73 | formatter: dashed 74 | stream: ./example_info.log 75 | processors: 76 | web_processor: 77 | class: Monolog\Processor\WebProcessor 78 | memory_processor: 79 | class: Monolog\Processor\MemoryUsageProcessor 80 | loggers: 81 | myLogger: 82 | handlers: [console, info_file_handler] 83 | processors: [web_processor] 84 | ``` 85 | 86 | Here is a sample PHP config file: 87 | ```php 88 | 1, 92 | 93 | 'formatters' => array( 94 | 'spaced' => array( 95 | 'format' => "%datetime% %channel%.%level_name% %message%\n", 96 | 'include_stacktraces' => true 97 | ), 98 | 'dashed' => array( 99 | 'format' => "%datetime%-%channel%.%level_name% - %message%\n" 100 | ), 101 | ), 102 | 'handlers' => array( 103 | 'console' => array( 104 | 'class' => 'Monolog\Handler\StreamHandler', 105 | 'level' => 'DEBUG', 106 | 'formatter' => 'spaced', 107 | 'stream' => 'php://stdout' 108 | ), 109 | 110 | 'info_file_handler' => array( 111 | 'class' => 'Monolog\Handler\StreamHandler', 112 | 'level' => 'INFO', 113 | 'formatter' => 'dashed', 114 | 'stream' => './demo_info.log' 115 | ), 116 | 117 | 'error_file_handler' => array( 118 | 'class' => 'Monolog\Handler\StreamHandler', 119 | 'level' => 'ERROR', 120 | 'stream' => './demo_error.log', 121 | 'formatter' => 'spaced' 122 | ) 123 | ), 124 | 'processors' => array( 125 | 'tag_processor' => array( 126 | 'class' => 'Monolog\Processor\TagProcessor' 127 | ) 128 | ), 129 | 'loggers' => array( 130 | 'my_logger' => array( 131 | 'handlers' => array('console', 'info_file_handler') 132 | ) 133 | ) 134 | ); 135 | ``` 136 | 137 | More information on how the Cascade config parser loads and reads the parameters: 138 | 139 | Only the `loggers` key is required. If `formatters` and/or `handlers` are ommitted, Monolog's default will be used. `processors` is optional and if ommitted, no processors will be used. (See the "Optional Keys" section further below). 140 | 141 | Other keys are optional and would be interpreted as described below: 142 | 143 | - **_formatters_** - the derived associative array (from the Yaml or JSON) in which each key is the formatter identifier holds keys/values to configure your formatters. 144 | The only _reserved_ key is `class` and it should contain the classname of the formatter you would like to use. Other parameters will be interpreted as constructor parameters for that class and passed in when the formatter object is instanced by the Cascade config loader.
145 | If some parameters are not present in the constructor, they will be treated as extra parameters and Cascade will try to interpret them should they match any custom handler functions that are able to use them. (see [Extra Parameters](#user-content-extra-parameters-other-than-constructors) section below)
146 | 147 | If `class` is not provided Cascade will default to `Monolog\Formatter\LineFormatter` 148 | 149 | - **_handlers_** - the derived associative array (from the Yaml or JSON) in which each key is the handler identifier holds keys/values to configure your handlers.
The following keys are _reserved_: 150 | - `class` (optional): classname of the handler you would like to use 151 | - `formatter` (optional): formatter identifier that you have defined 152 | - `processors` (optional): array of processor identifiers that you have defined 153 | - `handlers` (optional): array of handler identifiers that you have defined 154 | - `handler` (optional): single handler identifier that you have defined 155 | 156 | Other parameters will be interpreted as constructor parameters for that Handler class and passed in when the handler object is instantiated by the Cascade config loader.
157 | If some parameters are not present in the constructor, they will be interpreted as extra parameters and Cascade will try to interpret them should they match any custom handler functions that are able to use them. (see [Extra Parameters](#user-content-extra-parameters-other-than-constructors) section below) 158 | 159 | If class is not provided Cascade will default to `Monolog\Handler\StreamHandler` 160 | 161 | - **_processors_** - the derived associative array (from the Yaml or JSON) in which each key is the processor identifier holds keys/values to configure your processors.
The following key is _reserved_: 162 | - `class` (required): classname of the processor you would like to use 163 | 164 | - **_loggers_** - the derived array (from the Yaml or JSON) in which each key is the logger identifier may contain only a `handlers` key and/or a `processors` key. You can decide what handler(s) and/or processor(s) you would like your logger to use. 165 | 166 | **Note**: If you would like to use objects as parameters for your handlers, you can pass a class name (using the `class` option) with the corresponding arguments just like you would configure your handler. Cascade recursively instantiates and loads those objects as it parses the config file. See [this sample config file](https://github.com/theorchard/monolog-cascade/blob/master/examples/dependency_config.yml). 167 | 168 | #### Parameter case 169 | You can use either _underscored_ or _camelCased_ style in your config files, it does not matter. However, it is important that they match the names of the arguments from the constructor method. 170 | 171 | ```php 172 | public function __construct($level = Logger::ERROR, $bubble = true, $appName = null) 173 | ``` 174 | 175 | Using a Yaml file: 176 | ```yaml 177 | level: ERROR, 178 | bubble: true, 179 | app_name: "some app that I wrote" 180 | ``` 181 | 182 | Cascade will _camelCase_ all the names of your parameters internally prior to be passed to the constructors. 183 | 184 | #### Optional keys 185 | `formatters`, `handlers` and `processors` keys are optional. If ommitted Cascade will default to Monolog's default formatter and handler: `Monolog\Formatter\LineFormatter` and `Monolog\Handler\StreamHandler` to `stderr`. If `processors` is ommitted, your logger(s) won't use any. 186 | 187 | #### Default parameters 188 | If a constructor method provides default value(s) in their declaration, Cascade will look it up and identify those parameters as optional with their default values. It can therefore be ommitted in your config file. 189 | 190 | #### Order of sections and params 191 | Order of the sections within the config file has no impact as long as they are formatted properly. 192 |
Order of parameters does not matter either. 193 | 194 | #### Extra parameters (other than constructor's) 195 | You may want to have your Formatters and/or Handlers consume values other than via the constructor. Some methods may be called to do additional set up when configuring your loggers. Cascade interprets those extra params 3 different ways and will try do so in that order: 196 | 197 | 1. _Instance method_ 198 |
Your Formatter or Handler has a defined method that takes a param as input. In that case you can write it as follow in your config file: 199 | 200 | ```yaml 201 | formatters: 202 | spaced: 203 | class: Monolog\Formatter\LineFormatter 204 | format: "%datetime% %channel%.%level_name% %message%\n" 205 | include_stacktraces: true 206 | ``` 207 | In this example, the `LineFormatter` class has an `includeStacktraces` method that takes a boolean. This method will be called upon instantiation.
208 | 209 | 2. _Public member_ 210 |
Your Formatter or Handler has a public member that can be set. 211 | 212 | ```yaml 213 | formatters: 214 | spaced: 215 | class: Monolog\Formatter\SomeFormatter 216 | some_public_member: "some value" 217 | ``` 218 | In this example, the public member will be set to the passed in value upon instantiation.
219 | 220 | 3. _Custom handler function_ 221 |
See `FormatterLoader::initExtraOptionsHandlers` and `HandlerLoader::initExtraOptionsHandlers`. Those methods hold closures that can call instance methods if needed. The closure takes the instance and the parameter value as input. 222 | 223 | ```php 224 | self::$extraOptionHandlers = array( 225 | 'Monolog\Formatter\LineFormatter' => array( 226 | 'includeStacktraces' => function ($instance, $include) { 227 | $instance->includeStacktraces($include); 228 | } 229 | ) 230 | ); 231 | ``` 232 | You can add handlers at runtime if needed. (i.e. if you write your logger handler for instance) 233 | 234 | Running Tests 235 | ------------- 236 | 237 | Just run Phpunit: 238 | ```sh 239 | $ phpunit tests/ 240 | ``` 241 | 242 | Contributing 243 | ------------ 244 | 245 | This extension is open source. Feel free to contribute and send a pull request! 246 | 247 | Make sure your code follows the [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) standards, is documented and has unit tests. 248 | 249 | 250 | What's next? 251 | ------------ 252 | - add support for `.ini` config files 253 | - add support for namespaced Loggers with message propagation (through handler inheritance) so children loggers log messages using parent's handlers 254 | - add more custom function handlers to cover all the possible options of the current Monolog Formatters and Handlers 255 | - ~~add support for Processors (DONE)~~ 256 | - ~~add support for DB/Store and other handlers requiring injection into the constructor ([issue #30](https://github.com/theorchard/monolog-cascade/issues/30)) (DONE)~~ 257 | - other suggestions? 258 | 259 | 260 | Symfony Users 261 | ------------- 262 | You may want to use [MonologBundle](https://github.com/symfony/MonologBundle) as it integrates directly with your favorite framework. 263 | 264 | 265 | Under The Hood 266 | -------------- 267 | Here is a [Medium post](https://medium.com/orchard-technology/enhancing-monolog-699efff1051d#.dw6qu1c2p) if you want to know more about the implementation. 268 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "theorchard/monolog-cascade", 3 | "license": "MIT", 4 | "type": "library", 5 | "description": "Monolog extension to configure multiple loggers in the blink of an eye and access them from anywhere", 6 | "keywords": ["monolog", "cascade", "monolog extension", "monolog plugin", "monolog utils", "logger", "log"], 7 | "authors": [ 8 | { 9 | "name": "Raphaël Antonmattei", 10 | "email": "rantonmattei@theorchard.com", 11 | "homepage": "https://github.com/rantonmattei" 12 | } 13 | ], 14 | "homepage": "https://github.com/theorchard/monolog-cascade", 15 | "require": { 16 | "php": ">=7.2", 17 | "symfony/config": "^2.7 || ^3.0 || ^4.0 || ^5.0", 18 | "symfony/options-resolver": "^2.7 || ^3.0 || ^4.0 || ^5.0", 19 | "symfony/serializer": "^2.7 || ^3.0 || ^4.0 || ^5.0", 20 | "symfony/yaml": "^2.7 || ^3.0 || ^4.0 || ^5.0", 21 | "monolog/monolog": "^1.13 || ^2.0" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "Cascade\\": "src/" 26 | } 27 | }, 28 | "autoload-dev": { 29 | "psr-4": { 30 | "Cascade\\Tests\\": "tests/" 31 | } 32 | }, 33 | "require-dev": { 34 | "mikey179/vfsstream": "^1.6", 35 | "phpunit/phpcov": "^6.0", 36 | "phpunit/phpunit": "^8.5", 37 | "php-coveralls/php-coveralls": "^1.0", 38 | "squizlabs/php_codesniffer": "^2.5" 39 | }, 40 | "prefer-stable": true, 41 | "extra": { 42 | "branch-alias": { 43 | "dev-master": "0.5.x-dev" 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/dependency_config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 1 3 | 4 | disable_existing_loggers: false 5 | 6 | handlers: 7 | sentry: 8 | class: Monolog\Handler\RavenHandler 9 | level: DEBUG 10 | raven_client: 11 | class: Raven_Client 12 | options_or_dsn: https://something:dummy@getsentry.com/1 13 | redis: 14 | class: Monolog\Handler\RedisHandler 15 | level: DEBUG 16 | key: cascade 17 | redis: 18 | class: Redis 19 | connect: ['127.0.0.1', 6379] 20 | loggers: 21 | dependency: 22 | handlers: [sentry, redis] 23 | -------------------------------------------------------------------------------- /examples/dependency_logger.php: -------------------------------------------------------------------------------- 1 | info('Well, that works!'); 13 | Cascade::getLogger('dependency')->error('Maybe not...'); 14 | -------------------------------------------------------------------------------- /examples/logger_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "disable_existing_loggers": false, 4 | "formatters": { 5 | "spaced": { 6 | "class": "Monolog\\Formatter\\LineFormatter", 7 | "format": "%datetime% %channel%.%level_name% %message%\n", 8 | "include_stacktraces": true 9 | }, 10 | "dashed": { 11 | "format": "%datetime%-%channel%.%level_name% - %message%\n" 12 | } 13 | }, 14 | "handlers": { 15 | "console": { 16 | "class": "Monolog\\Handler\\StreamHandler", 17 | "level": "DEBUG", 18 | "formatter": "spaced", 19 | "stream": "php://stdout" 20 | }, 21 | "info_file_handler": { 22 | "class": "Monolog\\Handler\\StreamHandler", 23 | "level": "INFO", 24 | "formatter": "dashed", 25 | "stream": "./example_info.log" 26 | }, 27 | "error_file_handler": { 28 | "class": "Monolog\\Handler\\StreamHandler", 29 | "level": "ERROR", 30 | "formatter": "dashed", 31 | "stream": "./example_error.log" 32 | } 33 | }, 34 | "loggers": { 35 | "loggerA": { 36 | "handlers": [ 37 | "console", 38 | "info_file_handler" 39 | ] 40 | }, 41 | "loggerB": { 42 | "handlers": [ 43 | "console", 44 | "error_file_handler" 45 | ] 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /examples/logger_config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 1 3 | 4 | disable_existing_loggers: false 5 | 6 | formatters: 7 | spaced: 8 | # class is not needed here as LineFormatter is the default Formatter when none is provided 9 | class: Monolog\Formatter\LineFormatter 10 | format: "%datetime% %channel%.%level_name% %message%\n" 11 | include_stacktraces: true 12 | dashed: 13 | format: "%datetime%-%channel%.%level_name% - %message%\n" 14 | 15 | processors: 16 | web_processor: 17 | class: Monolog\Processor\WebProcessor 18 | 19 | handlers: 20 | console: 21 | class: Monolog\Handler\StreamHandler 22 | level: DEBUG 23 | formatter: spaced 24 | stream: php://stdout 25 | info_file_handler: 26 | class: Monolog\Handler\StreamHandler 27 | level: INFO 28 | formatter: dashed 29 | stream: ./example_info.log 30 | error_file_handler: 31 | class: Monolog\Handler\StreamHandler 32 | level: ERROR 33 | formatter: dashed 34 | stream: ./example_error.log 35 | 36 | loggers: 37 | loggerA: 38 | handlers: [console, info_file_handler] 39 | loggerB: 40 | handlers: [console, error_file_handler] 41 | processors: [web_processor] 42 | -------------------------------------------------------------------------------- /examples/multiple_loggers.php: -------------------------------------------------------------------------------- 1 | info('Well, that works!'); 13 | Cascade::getLogger('loggerB')->error('Maybe not...'); 14 | 15 | // This should log into 2 different log files depending on the level: 'example_info.log' and 'example_error.log' -------------------------------------------------------------------------------- /examples/single_logger.php: -------------------------------------------------------------------------------- 1 | pushHandler(new Monolog\Handler\StreamHandler('php://stdout')); 8 | $logger->info('Hellooooo World!'); 9 | 10 | // you should see the following in the stdout: 11 | // [YYYY-mm-dd hh:mm:ss] some_logger.INFO: Hellooooo World! 12 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | tests/ 7 | 8 | 9 | 10 | 11 | 12 | src/ 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Cascade.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade; 12 | 13 | use Monolog\Handler\HandlerInterface; 14 | use Monolog\Logger; 15 | use Monolog\Registry; 16 | 17 | use Cascade\Config\ConfigLoader; 18 | 19 | /** 20 | * Module class that manages Monolog Logger object 21 | * @see Logger 22 | * @see Registry 23 | * 24 | * @author Raphael Antonmattei 25 | */ 26 | class Cascade 27 | { 28 | /** 29 | * Config class that holds options for all registered loggers 30 | * This is optional, you can set up your loggers programmatically 31 | * @var Config 32 | */ 33 | protected static $config = null; 34 | 35 | /** 36 | * Create a new Logger object and push it to the registry 37 | * @see Logger::__construct 38 | * 39 | * @throws \InvalidArgumentException if no name is given 40 | * 41 | * @param string $name The logging channel 42 | * @param HandlerInterface[] $handlers Optional stack of handlers, the first 43 | * one in the array is called first, etc. 44 | * @param callable[] $processors Optional array of processors 45 | * 46 | * @return Logger Newly created Logger 47 | */ 48 | public static function createLogger( 49 | $name, 50 | array $handlers = array(), 51 | array $processors = array() 52 | ) { 53 | 54 | if (empty($name)) { 55 | throw new \InvalidArgumentException('Logger name is required.'); 56 | } 57 | 58 | $logger = new Logger($name, $handlers, $processors); 59 | Registry::addLogger($logger); 60 | 61 | return $logger; 62 | } 63 | 64 | /** 65 | * Get a Logger instance by name. Creates a new one if a Logger with the 66 | * provided name does not exist 67 | * 68 | * @param string $name Name of the requested Logger instance 69 | * 70 | * @return Logger Requested instance of Logger or new instance 71 | */ 72 | public static function getLogger($name) 73 | { 74 | return Registry::hasLogger($name) ? Registry::getInstance($name) : self::createLogger($name); 75 | } 76 | 77 | /** 78 | * Alias of getLogger 79 | * @see getLogger 80 | * 81 | * @param string $name Name of the requested Logger instance 82 | * 83 | * @return Logger Requested instance of Logger or new instance 84 | */ 85 | public static function logger($name) 86 | { 87 | return self::getLogger($name); 88 | } 89 | 90 | /** 91 | * Checks if a logger with given name already exists. 92 | * 93 | * @param string $name Name of the requested Logger instance 94 | * 95 | * @return bool true - Logger already exists; false - Logger does not exist 96 | */ 97 | public static function hasLogger($name) 98 | { 99 | return Registry::hasLogger($name); 100 | } 101 | 102 | /** 103 | * Return the config options 104 | * 105 | * @return Config Array with configuration options 106 | */ 107 | public static function getConfig() 108 | { 109 | return self::$config; 110 | } 111 | 112 | /** 113 | * Load configuration options from a file, a JSON or Yaml string or an array. 114 | * 115 | * @param string|array $resource Path to config file or configuration as string or array 116 | */ 117 | public static function fileConfig($resource) 118 | { 119 | self::$config = new Config($resource, new ConfigLoader()); 120 | self::$config->load(); 121 | self::$config->configure(); 122 | } 123 | 124 | /** 125 | * Load configuration options from a JSON or Yaml string. Alias of fileConfig. 126 | * @see fileConfig 127 | * 128 | * @param string $configString Configuration in string form 129 | */ 130 | public static function loadConfigFromString($configString) 131 | { 132 | self::fileConfig($configString); 133 | } 134 | 135 | /** 136 | * Load configuration options from an array. Alias of fileConfig. 137 | * @see fileConfig 138 | * 139 | * @param array $configArray Configuration in array form 140 | */ 141 | public static function loadConfigFromArray($configArray) 142 | { 143 | self::fileConfig($configArray); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/Config.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade; 12 | 13 | use Monolog; 14 | 15 | use Cascade\Config\ConfigLoader; 16 | use Cascade\Config\Loader\ClassLoader\FormatterLoader; 17 | use Cascade\Config\Loader\ClassLoader\HandlerLoader; 18 | use Cascade\Config\Loader\ClassLoader\LoggerLoader; 19 | use Cascade\Config\Loader\ClassLoader\ProcessorLoader; 20 | 21 | /** 22 | * Config class that takes a config resource (file, JSON, Yaml, etc.) and configure Loggers with 23 | * all the required options (Formatters, Handlers, etc.) 24 | * 25 | * @author Raphael Antonmattei 26 | */ 27 | class Config 28 | { 29 | /** 30 | * Input from user. This is either a file path, a string or an array 31 | * @var string|array 32 | */ 33 | protected $input = null; 34 | 35 | /** 36 | * Array of logger configuration options: (logger attributes, formatters, handlers, etc.) 37 | * @var array 38 | */ 39 | protected $options = array(); 40 | 41 | /** 42 | * Array of Formatter objects 43 | * @var Monolog\Formatter\FormatterInterface[] 44 | */ 45 | protected $formatters = array(); 46 | 47 | /** 48 | * Array of Handler objects 49 | * @var Monolog\Handler\HandlerInterface[] 50 | */ 51 | protected $handlers = array(); 52 | 53 | /** 54 | * Array of Processor objects 55 | * @var callable[] 56 | */ 57 | protected $processors = array(); 58 | 59 | /** 60 | * Array of logger objects 61 | * @var Monolog\Logger[] 62 | */ 63 | protected $loggers = array(); 64 | 65 | /** 66 | * Config loader 67 | * @var ConfigLoader 68 | */ 69 | protected $loader = null; 70 | 71 | /** 72 | * Instantiate a Config object 73 | * 74 | * @param string|array $input User input 75 | * @param ConfigLoader $loader Config loader object 76 | */ 77 | public function __construct($input, ConfigLoader $loader) 78 | { 79 | $this->input = $input; 80 | $this->loader = $loader; 81 | } 82 | 83 | /** 84 | * Load config options into the options array using the injected loader 85 | */ 86 | public function load() 87 | { 88 | $this->options = $this->loader->load($this->input); 89 | } 90 | 91 | /** 92 | * Configure and register Logger(s) according to the options passed in 93 | */ 94 | public function configure() 95 | { 96 | if (!isset($this->options['disable_existing_loggers'])) { 97 | // We disable any existing loggers by default 98 | $this->options['disable_existing_loggers'] = true; 99 | } 100 | 101 | if ($this->options['disable_existing_loggers']) { 102 | Monolog\Registry::clear(); 103 | } 104 | 105 | if (isset($this->options['formatters'])) { 106 | $this->configureFormatters($this->options['formatters']); 107 | } 108 | 109 | if (isset($this->options['processors'])) { 110 | $this->configureProcessors($this->options['processors']); 111 | } 112 | 113 | if (isset($this->options['handlers'])) { 114 | $this->configureHandlers($this->options['handlers']); 115 | } 116 | 117 | if (isset($this->options['loggers'])) { 118 | $this->configureLoggers($this->options['loggers']); 119 | } else { 120 | throw new \RuntimeException( 121 | 'Cannot configure loggers. No logger configuration options provided.' 122 | ); 123 | } 124 | } 125 | 126 | /** 127 | * Configure the formatters 128 | * 129 | * @param array $formatters Array of formatter options 130 | */ 131 | protected function configureFormatters(array $formatters = array()) 132 | { 133 | foreach ($formatters as $formatterId => $formatterOptions) { 134 | $formatterLoader = new FormatterLoader($formatterOptions); 135 | $this->formatters[$formatterId] = $formatterLoader->load(); 136 | } 137 | } 138 | 139 | /** 140 | * Configure the handlers 141 | * 142 | * @param array $handlers Array of handler options 143 | */ 144 | protected function configureHandlers(array $handlers) 145 | { 146 | foreach ($handlers as $handlerId => $handlerOptions) { 147 | $handlerLoader = new HandlerLoader($handlerOptions, $this->formatters, $this->processors, $this->handlers); 148 | $this->handlers[$handlerId] = $handlerLoader->load(); 149 | } 150 | } 151 | 152 | /** 153 | * Configure the processors 154 | * 155 | * @param array $processors Array of processor options 156 | */ 157 | protected function configureProcessors(array $processors) 158 | { 159 | foreach ($processors as $processorName => $processorOptions) { 160 | $processorLoader = new ProcessorLoader($processorOptions, $this->processors); 161 | $this->processors[$processorName] = $processorLoader->load(); 162 | } 163 | } 164 | 165 | /** 166 | * Configure the loggers 167 | * 168 | * @param array $loggers Array of logger options 169 | */ 170 | protected function configureLoggers(array $loggers) 171 | { 172 | foreach ($loggers as $loggerName => $loggerOptions) { 173 | $loggerLoader = new LoggerLoader($loggerName, $loggerOptions, $this->handlers, $this->processors); 174 | $this->loggers[$loggerName] = $loggerLoader->load(); 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/Config/ConfigLoader.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Config; 12 | 13 | use Symfony\Component\Config\FileLocator; 14 | use Symfony\Component\Config\Loader\DelegatingLoader; 15 | use Symfony\Component\Config\Loader\LoaderResolver; 16 | 17 | use Cascade\Config\Loader\FileLoader\Json as JsonLoader; 18 | use Cascade\Config\Loader\FileLoader\PhpArray as ArrayFromFileLoader; 19 | use Cascade\Config\Loader\FileLoader\Yaml as YamlLoader; 20 | use Cascade\Config\Loader\PhpArray as ArrayLoader; 21 | 22 | /** 23 | * Loader class that loads Yaml, JSON and array from various resources (file, php array, string) 24 | * @see DelegatingLoader 25 | * 26 | * @author Raphael Antonmattei 27 | */ 28 | class ConfigLoader extends DelegatingLoader 29 | { 30 | /** 31 | * Locator 32 | * @var FileLocator 33 | */ 34 | protected $locator = null; 35 | 36 | /** 37 | * Instantiate a Loader object 38 | * @todo have the locator passed to the constructor so we can load more than one file 39 | */ 40 | public function __construct() 41 | { 42 | $this->locator = new FileLocator(); 43 | 44 | $loaderResolver = new LoaderResolver(array( 45 | // Do not change that order, it does matter as the resolver returns the first loader 46 | // that meets the requirements of the "supports" method for each of those loaders 47 | new ArrayLoader(), 48 | new ArrayFromFileLoader($this->locator), 49 | new JsonLoader($this->locator), 50 | new YamlLoader($this->locator) 51 | )); 52 | 53 | parent::__construct($loaderResolver); 54 | } 55 | 56 | /** 57 | * Loads a configuration resource: file, array, string 58 | * 59 | * @param mixed $resource Resource to load 60 | * @param string|null $type Not used 61 | * 62 | * @return array Array of config options 63 | */ 64 | public function load($resource, $type = null) 65 | { 66 | return parent::load($resource); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Config/Loader/ClassLoader.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Config\Loader; 12 | 13 | use Cascade\Config\Loader\ClassLoader\Resolver\ConstructorResolver; 14 | use Cascade\Config\Loader\ClassLoader\Resolver\ExtraOptionsResolver; 15 | use Cascade\Util; 16 | 17 | /** 18 | * Class Loader. Instantiate an object given a set of options. The option might look like: 19 | * array( 20 | * 'class' => 'Some\Class' 21 | * 'some_contruct_param' => 'abc', 22 | * 'some_param' => 'def', 23 | * 'some_other_param' => 'sdsad' 24 | * ) 25 | * 26 | * Some of them are applicable to the constructor, other are applicable to other handlers. 27 | * For the latter you need to make sure there is a handler defined for that option 28 | * 29 | * @author Raphael Antonmattei 30 | * @author Dom Morgan 31 | */ 32 | class ClassLoader 33 | { 34 | /** 35 | * Default class to use if none is provided in the option array 36 | */ 37 | const DEFAULT_CLASS = '\stdClass'; 38 | 39 | /** 40 | * Array of Closures indexed by class. 41 | * array( 42 | * '\Full\Absolute\Namespace\ClassName' => array( 43 | * 'myOption' => Closure 44 | * ), ... 45 | * ) 46 | * @var array 47 | */ 48 | public static $extraOptionHandlers = array(); 49 | 50 | /** 51 | * Name of the class you want to load 52 | * @var String 53 | */ 54 | public $class = null; 55 | 56 | /** 57 | * Reflected object of the class passed in 58 | * @var \ReflectionClass 59 | */ 60 | protected $reflected = null; 61 | 62 | /** 63 | * Array of options. This is a raw copy of the options passed in. 64 | * @var array 65 | */ 66 | protected $rawOptions = array(); 67 | 68 | /** 69 | * Constructor 70 | * 71 | * @param array $options Array of options 72 | * The option array might look like: 73 | * array( 74 | * 'class' => 'Some\Class', 75 | * 'some_contruct_param' => 'abc', 76 | * 'some_param' => 'def', 77 | * 'some_other_param' => 'sdsad' 78 | * ) 79 | */ 80 | public function __construct(array $options) 81 | { 82 | $this->rawOptions = self::optionsToCamelCase($options); 83 | $this->setClass(); 84 | $this->reflected = new \ReflectionClass($this->class); 85 | } 86 | 87 | /** 88 | * Set the class you want to load from the raw option array 89 | */ 90 | protected function setClass() 91 | { 92 | if (!isset($this->rawOptions['class'])) { 93 | $this->rawOptions['class'] = static::DEFAULT_CLASS; 94 | } 95 | 96 | $this->class = $this->rawOptions['class']; 97 | unset($this->rawOptions['class']); 98 | } 99 | 100 | /** 101 | * Recursively loads objects into any of the rawOptions that represent 102 | * a class 103 | */ 104 | protected function loadChildClasses() 105 | { 106 | foreach ($this->rawOptions as &$option) { 107 | if (is_array($option) 108 | && array_key_exists('class', $option) 109 | && class_exists($option['class']) 110 | ) { 111 | $classLoader = new ClassLoader($option); 112 | $option = $classLoader->load(); 113 | } 114 | } 115 | } 116 | 117 | /** 118 | * Return option values indexed by name using camelCased keys 119 | * 120 | * @param array $options Array of options 121 | * 122 | * @return mixed[] Array of options indexed by (camelCased) name 123 | */ 124 | public static function optionsToCamelCase(array $options) 125 | { 126 | $optionsByName = array(); 127 | 128 | if (count($options)) { 129 | foreach ($options as $name => $value) { 130 | $optionsByName[Util::snakeToCamelCase($name)] = $value; 131 | } 132 | } 133 | 134 | return $optionsByName; 135 | } 136 | 137 | /** 138 | * Resolve options and returns them into 2 buckets: 139 | * - constructor options and 140 | * - extra options 141 | * Extra options are those that are not in the contructor. The constructor arguments determine 142 | * what goes into which bucket 143 | * 144 | * @return array Array of constructorOptions and extraOptions 145 | */ 146 | private function resolveOptions() 147 | { 148 | $constructorResolver = new ConstructorResolver($this->reflected); 149 | 150 | // Contructor options are only the ones matching the contructor args' names 151 | $constructorOptions = array_intersect_key( 152 | $this->rawOptions, 153 | $constructorResolver->getConstructorArgs() 154 | ); 155 | 156 | // Extra options are everything else than contructor options 157 | $extraOptions = array_diff_key( 158 | $this->rawOptions, 159 | $constructorOptions 160 | ); 161 | 162 | $extraOptionsResolver = new ExtraOptionsResolver( 163 | $this->reflected, 164 | array_keys($extraOptions) 165 | ); 166 | 167 | return array( 168 | $constructorResolver->resolve($constructorOptions), 169 | $extraOptionsResolver->resolve($extraOptions, $this) 170 | ); 171 | } 172 | 173 | /** 174 | * Instantiate the reflected object using the parsed contructor args and set 175 | * extra options if any 176 | * 177 | * @return mixed Instance of the reflected object 178 | */ 179 | public function load() 180 | { 181 | $this->loadChildClasses(); 182 | 183 | list($constructorResolvedOptions, $extraResolvedOptions) = $this->resolveOptions(); 184 | $instance = $this->reflected->newInstanceArgs($constructorResolvedOptions); 185 | 186 | $this->loadExtraOptions($extraResolvedOptions, $instance); 187 | 188 | return $instance; 189 | } 190 | 191 | /** 192 | * Indicates whether or not an option is supported by the loader 193 | * 194 | * @param string $extraOptionName Option name 195 | * 196 | * @return boolean Whether or not an option is supported by the loader 197 | */ 198 | public function canHandle($extraOptionName) 199 | { 200 | return 201 | isset(self::$extraOptionHandlers['*'][$extraOptionName]) || 202 | isset(self::$extraOptionHandlers[$this->class][$extraOptionName]); 203 | } 204 | 205 | /** 206 | * Get the corresponding handler for a given option 207 | * 208 | * @param string $extraOptionName Option name 209 | * 210 | * @return \Closure|null Corresponding Closure object or null if not found 211 | */ 212 | public function getExtraOptionsHandler($extraOptionName) 213 | { 214 | // Check extraOption handlers that are valid for all classes 215 | if (isset(self::$extraOptionHandlers['*'][$extraOptionName])) { 216 | return self::$extraOptionHandlers['*'][$extraOptionName]; 217 | } 218 | 219 | // Check extraOption handlers that are valid for the given class 220 | if (isset(self::$extraOptionHandlers[$this->class][$extraOptionName])) { 221 | return self::$extraOptionHandlers[$this->class][$extraOptionName]; 222 | } 223 | 224 | return null; 225 | } 226 | 227 | /** 228 | * Set extra options if any were requested 229 | * 230 | * @param array $extraOptions Array of extra options (key => value) 231 | * @param mixed $instance Instance you want to set options for 232 | */ 233 | public function loadExtraOptions($extraOptions, $instance) 234 | { 235 | foreach ($extraOptions as $name => $value) { 236 | if ($this->reflected->hasMethod($name)) { 237 | // There is a method to handle this option 238 | call_user_func_array( 239 | array($instance, $name), 240 | is_array($value) ? $value : array($value) 241 | ); 242 | continue; 243 | } 244 | if ($this->reflected->hasProperty($name) && 245 | $this->reflected->getProperty($name)->isPublic() 246 | ) { 247 | // There is a public member we can set for this option 248 | $instance->$name = $value; 249 | continue; 250 | } 251 | 252 | if ($this->canHandle($name)) { 253 | // There is a custom handler for that option 254 | $closure = $this->getExtraOptionsHandler($name); 255 | $closure($instance, $value); 256 | } 257 | } 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/Config/Loader/ClassLoader/FormatterLoader.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Config\Loader\ClassLoader; 12 | 13 | use Monolog; 14 | 15 | use Cascade\Config\Loader\ClassLoader; 16 | 17 | /** 18 | * Formatter Loader. Loads the Formatter options, validate them and instantiates 19 | * a Formatter object (implementing Monolog\Formatter\FormatterInterface) with all 20 | * the corresponding options 21 | * @see ClassLoader 22 | * 23 | * @author Raphael Antonmattei 24 | */ 25 | class FormatterLoader extends ClassLoader 26 | { 27 | /** 28 | * Default formatter class to use if none is provided in the option array 29 | */ 30 | const DEFAULT_CLASS = 'Monolog\Formatter\LineFormatter'; 31 | 32 | /** 33 | * Constructor 34 | * @see ClassLoader::__construct 35 | * @see Monolog\Formatter classes for formatter options 36 | * 37 | * @param array $formatterOptions Formatter options 38 | */ 39 | public function __construct(array $formatterOptions) 40 | { 41 | parent::__construct($formatterOptions); 42 | 43 | self::initExtraOptionsHandlers(); 44 | } 45 | 46 | /** 47 | * Loads the closures as option handlers. Add handlers to this function if 48 | * you want to support additional custom options. 49 | * 50 | * The syntax is the following: 51 | * array( 52 | * '\Full\Absolute\Namespace\ClassName' => array( 53 | * 'myOption' => Closure 54 | * ), ... 55 | * ) 56 | * 57 | * You can use the '*' wildcard if you want to set up an option for all 58 | * Formatter classes 59 | * 60 | * @todo add handlers to handle extra options for all known Monolog formatters 61 | */ 62 | public static function initExtraOptionsHandlers() 63 | { 64 | self::$extraOptionHandlers = array( 65 | 'Monolog\Formatter\LineFormatter' => array( 66 | 'includeStacktraces' => function (Monolog\Formatter\LineFormatter $instance, $include) { 67 | $instance->includeStacktraces($include); 68 | } 69 | ) 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Config/Loader/ClassLoader/HandlerLoader.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Config\Loader\ClassLoader; 12 | 13 | use Monolog\Formatter\FormatterInterface; 14 | use Monolog\Handler\HandlerInterface; 15 | use Monolog\Handler\LogglyHandler; 16 | 17 | use Cascade\Config\Loader\ClassLoader; 18 | 19 | /** 20 | * Handler Loader. Loads the Handler options, validate them and instantiates 21 | * a Handler object (implementing Monolog\Handler\HandlerInterface) with all 22 | * the corresponding options 23 | * @see ClassLoader 24 | * 25 | * @author Raphael Antonmattei 26 | */ 27 | class HandlerLoader extends ClassLoader 28 | { 29 | /** 30 | * Default handler class to use if none is provided in the option array 31 | */ 32 | const DEFAULT_CLASS = 'Monolog\Handler\StreamHandler'; 33 | 34 | /** 35 | * Constructor 36 | * @see ClassLoader::__construct 37 | * @see \Monolog\Handler classes for handler options 38 | * 39 | * @param array $handlerOptions Handler options 40 | * @param FormatterInterface[] $formatters Array of formatter to pick from 41 | * @param callable[] $processors Array of processors to pick from 42 | * @param callable[] $handlers Array of handlers to pick from 43 | */ 44 | public function __construct( 45 | array &$handlerOptions, 46 | array $formatters = array(), 47 | array $processors = array(), 48 | array $handlers = array() 49 | ) { 50 | $this->populateFormatters($handlerOptions, $formatters); 51 | $this->populateProcessors($handlerOptions, $processors); 52 | $this->populateHandlers($handlerOptions, $handlers); 53 | parent::__construct($handlerOptions); 54 | 55 | self::initExtraOptionsHandlers(); 56 | } 57 | 58 | /** 59 | * Replace the formatter name in the option array with the corresponding object from the 60 | * formatter array passed in if it exists. 61 | * 62 | * If no formatter is specified in the options, Monolog will use its default formatter for the 63 | * handler 64 | * 65 | * @throws \InvalidArgumentException 66 | * 67 | * @param array &$handlerOptions Handler options 68 | * @param FormatterInterface[] $formatters Array of formatter to pick from 69 | */ 70 | private function populateFormatters(array &$handlerOptions, array $formatters) 71 | { 72 | if (isset($handlerOptions['formatter'])) { 73 | if (isset($formatters[$handlerOptions['formatter']])) { 74 | $handlerOptions['formatter'] = $formatters[$handlerOptions['formatter']]; 75 | } else { 76 | throw new \InvalidArgumentException( 77 | sprintf( 78 | 'Formatter %s not found in the configured formatters', 79 | $handlerOptions['formatter'] 80 | ) 81 | ); 82 | } 83 | } 84 | } 85 | 86 | /** 87 | * Replace the processors in the option array with the corresponding callable from the 88 | * array of loaded and callable processors, if it exists. 89 | * 90 | * @throws \InvalidArgumentException 91 | * 92 | * @param array &$handlerOptions Handler options 93 | * @param callable[] $processors Array of processors to pick from 94 | */ 95 | private function populateProcessors(array &$handlerOptions, array $processors) 96 | { 97 | $processorArray = array(); 98 | 99 | if (isset($handlerOptions['processors'])) { 100 | foreach ($handlerOptions['processors'] as $processorId) { 101 | if (isset($processors[$processorId])) { 102 | $processorArray[] = $processors[$processorId]; 103 | } else { 104 | throw new \InvalidArgumentException( 105 | sprintf( 106 | 'Cannot add processor "%s" to the handler. Processor not found.', 107 | $processorId 108 | ) 109 | ); 110 | } 111 | } 112 | 113 | $handlerOptions['processors'] = $processorArray; 114 | } 115 | } 116 | 117 | /** 118 | * Replace the handler or handlers in the option array with the corresponding callable(s) from the 119 | * array of loaded and callable handlers, if they exist. 120 | * 121 | * @throws \InvalidArgumentException 122 | * 123 | * @param array &$handlerOptions Handler options 124 | * @param callable[] $handlers Array of handlers to pick from 125 | */ 126 | private function populateHandlers(array &$handlerOptions, array $handlers) 127 | { 128 | $handlerArray = array(); 129 | 130 | if (isset($handlerOptions['handlers'])) { 131 | foreach ($handlerOptions['handlers'] as $handlerId) { 132 | if (isset($handlers[$handlerId])) { 133 | $handlerArray[] = $handlers[$handlerId]; 134 | } else { 135 | throw new \InvalidArgumentException( 136 | sprintf( 137 | 'Cannot add handler "%s" to the handler. Handler not found.', 138 | $handlerId 139 | ) 140 | ); 141 | } 142 | } 143 | 144 | $handlerOptions['handlers'] = $handlerArray; 145 | } 146 | 147 | if (isset($handlerOptions['handler'])) { 148 | $handlerId = $handlerOptions['handler']; 149 | 150 | if (isset($handlers[$handlerId])) { 151 | $handlerOptions['handler'] = $handlers[$handlerId]; 152 | } else { 153 | throw new \InvalidArgumentException( 154 | sprintf( 155 | 'Cannot add handler "%s" to the handler. Handler not found.', 156 | $handlerId 157 | ) 158 | ); 159 | } 160 | } 161 | } 162 | 163 | /** 164 | * Loads the closures as option handlers. Add handlers to this function if 165 | * you want to support additional custom options. 166 | * 167 | * The syntax is the following: 168 | * array( 169 | * '\Full\Absolute\Namespace\ClassName' => array( 170 | * 'myOption' => Closure 171 | * ), ... 172 | * ) 173 | * 174 | * You can use the '*' wildcard if you want to set up an option for all 175 | * Handler classes 176 | */ 177 | public static function initExtraOptionsHandlers() 178 | { 179 | self::$extraOptionHandlers = array( 180 | '*' => array( 181 | 'formatter' => function (HandlerInterface $instance, FormatterInterface $formatter) { 182 | $instance->setFormatter($formatter); 183 | }, 184 | 'processors' => function (HandlerInterface $instance, array $processors) { 185 | // We need to reverse the array because Monolog "pushes" processors to top of the stack 186 | foreach (array_reverse($processors) as $processor) { 187 | $instance->pushProcessor($processor); 188 | } 189 | } 190 | ), 191 | 'Monolog\Handler\LogglyHandler' => array( 192 | 'tags' => function (LogglyHandler $instance, $tags) { 193 | $instance->setTag($tags); 194 | } 195 | ) 196 | ); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/Config/Loader/ClassLoader/LoggerLoader.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Config\Loader\ClassLoader; 12 | 13 | use Cascade\Cascade; 14 | 15 | use Monolog; 16 | 17 | /** 18 | * Logger Loader. Instantiate a Logger and set passed in handlers and processors if any 19 | * @see ClassLoader 20 | * 21 | * @author Raphael Antonmattei 22 | */ 23 | class LoggerLoader 24 | { 25 | /** 26 | * Array of options 27 | * @var array 28 | */ 29 | protected $loggerOptions = array(); 30 | 31 | /** 32 | * Array of handlers 33 | * @var Monolog\Handler\HandlerInterface[] 34 | */ 35 | protected $handlers = array(); 36 | 37 | /** 38 | * Array of processors 39 | * @var callable[] 40 | */ 41 | protected $processors = array(); 42 | 43 | /** 44 | * Logger 45 | * @var Monolog\Logger 46 | */ 47 | protected $logger = null; 48 | 49 | /** 50 | * Constructor 51 | * 52 | * @param string $loggerName Name of the logger 53 | * @param array $loggerOptions Array of logger options 54 | * @param Monolog\Handler\HandlerInterface[] $handlers Array of Monolog handlers 55 | * @param callable[] $processors Array of processors 56 | */ 57 | public function __construct( 58 | $loggerName, 59 | array $loggerOptions = array(), 60 | array $handlers = array(), 61 | array $processors = array() 62 | ) { 63 | $this->loggerOptions = $loggerOptions; 64 | $this->handlers = $handlers; 65 | $this->processors = $processors; 66 | 67 | // This instantiates a Logger object and set it to the Registry 68 | $this->logger = Cascade::getLogger($loggerName); 69 | } 70 | 71 | /** 72 | * Resolve handlers for that Logger (if any provided) against an array of previously set 73 | * up handlers. Returns an array of valid handlers. 74 | * 75 | * @throws \InvalidArgumentException if a requested handler is not available in $handlers 76 | * 77 | * @param array $loggerOptions Array of logger options 78 | * @param Monolog\Handler\HandlerInterface[] $handlers Available Handlers to resolve against 79 | * 80 | * @return Monolog\Handler\HandlerInterface[] Array of Monolog handlers 81 | */ 82 | public function resolveHandlers(array $loggerOptions, array $handlers) 83 | { 84 | $handlerArray = array(); 85 | 86 | if (isset($loggerOptions['handlers'])) { 87 | // If handlers have been specified and, they do exist in the provided handlers array 88 | // We return an array of handler objects 89 | foreach ($loggerOptions['handlers'] as $handlerId) { 90 | if (isset($handlers[$handlerId])) { 91 | $handlerArray[] = $handlers[$handlerId]; 92 | } else { 93 | throw new \InvalidArgumentException( 94 | sprintf( 95 | 'Cannot add handler "%s" to the logger "%s". Handler not found.', 96 | $handlerId, 97 | $this->logger->getName() 98 | ) 99 | ); 100 | } 101 | } 102 | } 103 | 104 | // If nothing is set there is nothing to resolve, Handlers will be Monolog's default 105 | 106 | return $handlerArray; 107 | } 108 | 109 | /** 110 | * Resolve processors for that Logger (if any provided) against an array of previously set 111 | * up processors. 112 | * 113 | * @throws \InvalidArgumentException if a requested processor is not available in $processors 114 | * 115 | * @param array $loggerOptions Array of logger options 116 | * @param callable[] $processors Available Processors to resolve against 117 | * 118 | * @return callable[] Array of Monolog processors 119 | */ 120 | public function resolveProcessors(array $loggerOptions, $processors) 121 | { 122 | $processorArray = array(); 123 | 124 | if (isset($loggerOptions['processors'])) { 125 | // If processors have been specified and, they do exist in the provided processors array 126 | // We return an array of processor objects 127 | foreach ($loggerOptions['processors'] as $processorId) { 128 | if (isset($processors[$processorId])) { 129 | $processorArray[] = $processors[$processorId]; 130 | } else { 131 | throw new \InvalidArgumentException( 132 | sprintf( 133 | 'Cannot add processor "%s" to the logger "%s". Processor not found.', 134 | $processorId, 135 | $this->logger->getName() 136 | ) 137 | ); 138 | } 139 | } 140 | } 141 | 142 | // If nothing is set there is nothing to resolve, Processors will be Monolog's default 143 | 144 | return $processorArray; 145 | } 146 | 147 | /** 148 | * Add handlers to the Logger 149 | * 150 | * @param Monolog\Handler\HandlerInterface[] Array of Monolog handlers 151 | */ 152 | private function addHandlers(array $handlers) 153 | { 154 | // We need to reverse the array because Monolog "pushes" handlers to top of the stack 155 | foreach (array_reverse($handlers) as $handler) { 156 | $this->logger->pushHandler($handler); 157 | } 158 | } 159 | 160 | /** 161 | * Add processors to the Logger 162 | * 163 | * @param callable[] Array of Monolog processors 164 | */ 165 | private function addProcessors(array $processors) 166 | { 167 | // We need to reverse the array because Monolog "pushes" processors to top of the stack 168 | foreach (array_reverse($processors) as $processor) { 169 | $this->logger->pushProcessor($processor); 170 | } 171 | } 172 | 173 | /** 174 | * Return the instantiated Logger object based on its name 175 | * 176 | * @return Monolog\Logger Logger object 177 | */ 178 | public function load() 179 | { 180 | $this->addHandlers($this->resolveHandlers($this->loggerOptions, $this->handlers)); 181 | $this->addProcessors($this->resolveProcessors($this->loggerOptions, $this->processors)); 182 | 183 | return $this->logger; 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/Config/Loader/ClassLoader/ProcessorLoader.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Config\Loader\ClassLoader; 12 | 13 | use Cascade\Config\Loader\ClassLoader; 14 | 15 | use Monolog; 16 | 17 | /** 18 | * Processor Loader. Loads the Processor options, validate them and instantiates 19 | * a Processor object (implementing Monolog\Processor\ProcessorInterface) with all 20 | * the corresponding options 21 | * @see ClassLoader 22 | * 23 | * @author Kate Burdon 24 | * @author Raphael Antonmattei 25 | */ 26 | class ProcessorLoader extends ClassLoader 27 | { 28 | /** 29 | * Constructor 30 | * @see ClassLoader::__construct 31 | * @see Monolog\Handler classes for handler options 32 | * 33 | * @param array $processorOptions Processor options 34 | * @param Monolog\Processor\ProcessorInterface[] $processors Array of processors to pick from 35 | */ 36 | public function __construct(array &$processorOptions, array $processors = array()) 37 | { 38 | parent::__construct($processorOptions); 39 | 40 | // @todo add additional options later? Is the "tags" option needed in this implementation? 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Config/Loader/ClassLoader/Resolver/ConstructorResolver.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Config\Loader\ClassLoader\Resolver; 12 | 13 | use Cascade\Util; 14 | use Symfony\Component\OptionsResolver\OptionsResolver; 15 | 16 | /** 17 | * Constructor Resolver. Pull args from the contructor and set up an option 18 | * resolver against args requirements 19 | * 20 | * @author Raphael Antonmattei 21 | */ 22 | class ConstructorResolver 23 | { 24 | /** 25 | * Reflection class for which you want to resolve constructor options 26 | * @var \ReflectionClass 27 | */ 28 | protected $reflected = null; 29 | 30 | /** 31 | * Registry of resolvers 32 | * @var array 33 | */ 34 | private static $resolvers = array(); 35 | 36 | /** 37 | * Associative array of contructor args to resolve against 38 | * @var \ReflectionParameter[] 39 | */ 40 | protected $constructorArgs = array(); 41 | 42 | /** 43 | * Contructor 44 | * 45 | * @param \ReflectionClass $reflected Reflection class for which you want to resolve 46 | * constructor options 47 | */ 48 | public function __construct(\ReflectionClass $reflected) 49 | { 50 | $this->reflected = $reflected; 51 | $this->initConstructorArgs(); 52 | } 53 | 54 | /** 55 | * Fetches constructor args (array of ReflectionParameter) from the reflected class 56 | * and set them as an associative array 57 | * 58 | * Convert the parameter names to camelCase for classes that have contructor 59 | * params defined in snake_case for consistency with the options 60 | */ 61 | public function initConstructorArgs() 62 | { 63 | $constructor = $this->reflected->getConstructor(); 64 | 65 | if (!is_null($constructor)) { 66 | // Index parameters by their names 67 | foreach ($constructor->getParameters() as $param) { 68 | $name = Util::snakeToCamelCase($param->getName()); 69 | $this->constructorArgs[$name] = $param; 70 | } 71 | } 72 | } 73 | 74 | /** 75 | * Returns the contructor args as an associative array 76 | * 77 | * @return array Contructor args 78 | */ 79 | public function getConstructorArgs() 80 | { 81 | return $this->constructorArgs; 82 | } 83 | 84 | /** 85 | * Returns the reflected object 86 | * 87 | * @return \ReflectionClass 88 | */ 89 | public function getReflected() 90 | { 91 | return $this->reflected; 92 | } 93 | 94 | /** 95 | * Configure options for the provided OptionResolver to match contructor args requirements 96 | * 97 | * @param OptionsResolver $optionsResolver OptionResolver to configure 98 | */ 99 | protected function configureOptions(OptionsResolver $optionsResolver) 100 | { 101 | foreach ($this->constructorArgs as $name => $param) { 102 | if ($param->isOptional() && $param->isDefaultValueAvailable()) { 103 | $optionsResolver->setDefault($name, $param->getDefaultValue()); 104 | } else { 105 | $optionsResolver->setRequired($name); 106 | } 107 | } 108 | } 109 | 110 | /** 111 | * Loops through constructor args and buid an ordered array of args using 112 | * the option values passed in. We assume the passed in array has been resolved already. 113 | * i.e. That the arg name has an entry in the option array. 114 | * 115 | * @param array $hashOfOptions Array of options 116 | * 117 | * @return array Array of ordered args 118 | */ 119 | public function hashToArgsArray($hashOfOptions) 120 | { 121 | $optionsArray = new \SplFixedArray(count($hashOfOptions)); 122 | 123 | foreach ($this->constructorArgs as $name => $param) { 124 | $optionsArray[$param->getPosition()] = $hashOfOptions[$name]; 125 | } 126 | 127 | return $optionsArray->toArray(); 128 | } 129 | 130 | /** 131 | * Resolve options against constructor args 132 | * 133 | * @param array $options Array of option values. Expected array looks like: 134 | * array( 135 | * 'someParam' => 'def', 136 | * 'someOtherParam' => 'sdsad' 137 | * ) 138 | * 139 | * @return array Array of resolved ordered args 140 | */ 141 | public function resolve(array $options) 142 | { 143 | $reflectedClassName = $this->reflected->getName(); 144 | 145 | // We check if that constructor has been configured before and is in the registry 146 | if (!isset(self::$resolvers[$reflectedClassName])) { 147 | self::$resolvers[$reflectedClassName] = new OptionsResolver(); 148 | 149 | $this->configureOptions(self::$resolvers[$reflectedClassName]); 150 | } 151 | 152 | return $this->hashToArgsArray( 153 | self::$resolvers[$reflectedClassName]->resolve($options) 154 | ); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/Config/Loader/ClassLoader/Resolver/ExtraOptionsResolver.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Config\Loader\ClassLoader\Resolver; 12 | 13 | use Symfony\Component\OptionsResolver\OptionsResolver; 14 | 15 | use Cascade\Config\Loader\ClassLoader; 16 | 17 | /** 18 | * Extra options resolver. Set up an option resolver for the passed in params and 19 | * apply validation rules if any 20 | * 21 | * @author Raphael Antonmattei 22 | */ 23 | class ExtraOptionsResolver 24 | { 25 | /** 26 | * Reflection class for which you want to resolve extra options 27 | * @var \ReflectionClass 28 | */ 29 | protected $reflected = null; 30 | 31 | /** 32 | * Registry of resolvers 33 | * @var OptionsResolver[] 34 | */ 35 | private static $resolvers = array(); 36 | 37 | /** 38 | * Associative array of parameters to resolve against 39 | * @var array 40 | */ 41 | protected $params = array(); 42 | 43 | /** 44 | * Constructor 45 | * 46 | * @param \ReflectionClass $reflected Reflection class for which you want to resolve 47 | * extra options 48 | * @param array $params Associative array of extra parameters we want to resolve against 49 | */ 50 | public function __construct(\ReflectionClass $reflected, array $params = array()) 51 | { 52 | $this->reflected = $reflected; 53 | $this->setParams($params); 54 | } 55 | 56 | /** 57 | * Set the parameters we want to resolve against 58 | * 59 | * @param array $params Associative array of extra parameters we want to resolve against 60 | */ 61 | public function setParams(array $params = array()) 62 | { 63 | $this->params = $params; 64 | } 65 | 66 | /** 67 | * Get the parameters we want to resolve against 68 | * 69 | * @return array $params Associative array of parameters 70 | */ 71 | public function getParams() 72 | { 73 | return $this->params; 74 | } 75 | 76 | /** 77 | * Returns the reflected object 78 | * 79 | * @return \ReflectionClass 80 | */ 81 | public function getReflected() 82 | { 83 | return $this->reflected; 84 | } 85 | 86 | /** 87 | * Generate a unique hash based on the keys of the extra params 88 | * 89 | * @param array $params: array of parameters 90 | * 91 | * @return string Unique MD5 hash 92 | */ 93 | public static function generateParamsHashKey($params) 94 | { 95 | return md5(serialize($params)); 96 | } 97 | 98 | /** 99 | * Configure options for the provided OptionResolver to match extra params requirements 100 | * 101 | * @param OptionsResolver $resolver OptionResolver to configure 102 | * @param ClassLoader|null $classLoader Optional class loader if you want to use custom 103 | * handlers for some of the extra options 104 | */ 105 | protected function configureOptions(OptionsResolver $resolver, ClassLoader $classLoader = null) 106 | { 107 | foreach ($this->params as $name) { 108 | if ($this->reflected->hasMethod($name)) { 109 | // There is a method to handle this option 110 | $resolver->setDefined($name); 111 | continue; 112 | } 113 | if ($this->reflected->hasProperty($name) && 114 | $this->reflected->getProperty($name)->isPublic() 115 | ) { 116 | // There is a public member we can set to handle this option 117 | $resolver->setDefined($name); 118 | continue; 119 | } 120 | 121 | // Option that cannot be handled by a regular setter but 122 | // requires specific pre-processing and/or handling to be set 123 | // e.g. like LogglyHandler::addTag for instance 124 | if (!is_null($classLoader) && $classLoader->canHandle($name)) { 125 | $resolver->setDefined($name); 126 | } 127 | } 128 | } 129 | 130 | /** 131 | * Resolve options against extra params requirements 132 | * 133 | * @param array $options Array of option values 134 | * @param ClassLoader|null $classLoader Optional class loader if you want to use custom 135 | * handlers to resolve the extra options 136 | * 137 | * @return array Array of resolved options 138 | */ 139 | public function resolve($options, ClassLoader $classLoader = null) 140 | { 141 | $hashKey = self::generateParamsHashKey($this->params); 142 | 143 | // Was configureOptions() executed before for this class? 144 | if (!isset(self::$resolvers[$hashKey])) { 145 | self::$resolvers[$hashKey] = new OptionsResolver(); 146 | $this->configureOptions(self::$resolvers[$hashKey], $classLoader); 147 | } 148 | 149 | return self::$resolvers[$hashKey]->resolve($options); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/Config/Loader/FileLoader/FileLoaderAbstract.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Config\Loader\FileLoader; 12 | 13 | use Symfony\Component\Config\Loader\FileLoader; 14 | 15 | /** 16 | * Abstract class that reads input from various sources: file, string or array 17 | * 18 | * @author Raphael Antonmattei 19 | */ 20 | abstract class FileLoaderAbstract extends FileLoader 21 | { 22 | public static $validExtensions = array(); 23 | 24 | /** 25 | * Read from a file or string 26 | * 27 | * @throws \RuntimeException if the file is not readable 28 | * 29 | * @param string $input Filepath or string 30 | * 31 | * @return string Return a string from read file or directly from $input 32 | */ 33 | public function readFrom($input) 34 | { 35 | if ($this->isFile($input)) { 36 | if (is_readable($input) === false) { 37 | throw new \RuntimeException( 38 | sprintf('Unable to parse "%s" as the file is not readable.', $input) 39 | ); 40 | } 41 | 42 | // $input is a filepath, so we load that file 43 | $input = file_get_contents($input); 44 | } 45 | 46 | return trim($input); 47 | } 48 | 49 | /** 50 | * Test if a given resource is a file name or a file path 51 | * 52 | * @param string $resource Plain string or file path 53 | * 54 | * @return boolean Whether or not the resource is a file 55 | */ 56 | public function isFile($resource) 57 | { 58 | return (strpos($resource, "\n") === false) && is_file($resource); 59 | } 60 | 61 | /** 62 | * Validate a file extension against a list of provided valid extensions 63 | * 64 | * @param string $filepath file path of the file we want to check 65 | * 66 | * @return boolean Whether or not the extension is valid 67 | */ 68 | public function validateExtension($filepath) 69 | { 70 | return in_array(pathinfo($filepath, PATHINFO_EXTENSION), static::$validExtensions, true); 71 | } 72 | 73 | /** 74 | * Return a section of an array based on the key passed in 75 | * 76 | * @param array $array Array we want the section from 77 | * @param string $section Section name (key) 78 | * 79 | * @return array|mixed Return the section of an array or just a value 80 | */ 81 | public function getSectionOf($array, $section = '') 82 | { 83 | if (!empty($section) && array_key_exists($section, $array)) { 84 | return $array[$section]; 85 | } 86 | 87 | return $array; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Config/Loader/FileLoader/Json.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Config\Loader\FileLoader; 12 | 13 | /** 14 | * JSON loader class. It can load a JSON string or a Yaml file 15 | * @see FileLoaderAbstract 16 | * 17 | * @author Raphael Antonmattei 18 | */ 19 | class Json extends FileLoaderAbstract 20 | { 21 | /** 22 | * Valid file extensions for this loader 23 | * @var array 24 | */ 25 | public static $validExtensions = array('json'); 26 | 27 | /** 28 | * Load a JSON string/file 29 | * 30 | * @param string $resource JSON string or file path to a JSON file 31 | * @param string|null $type Not used. 32 | * 33 | * @return array Array containing data from the parsed JSON string or file 34 | */ 35 | public function load($resource, $type = null) 36 | { 37 | return json_decode($this->readFrom($resource), true); 38 | } 39 | 40 | /** 41 | * Determine whether a given string is supposed to be a Json string 42 | * This is a very simplified validation to avoid calling 43 | * json_decode (which is much more expensive). If the json is invalid, it will throw an 44 | * exception when we actually load it. 45 | * 46 | * @param string $string String to evaluate 47 | * 48 | * @return boolean Whether or not the passed string is meant to be a JSON string 49 | */ 50 | private function isJson($string) 51 | { 52 | return ( 53 | !empty($string) && 54 | ($string[0] === '[' || $string[0] === '{') 55 | ); 56 | } 57 | 58 | /** 59 | * Return whether or not the passed in resource is supported by this loader 60 | * 61 | * @param string $resource Plain string or filepath 62 | * @param string $type Not used 63 | * 64 | * @return boolean Whether or not the passed in resource is supported by this loader 65 | */ 66 | public function supports($resource, $type = null) 67 | { 68 | if (is_string($resource)) { 69 | if ($this->isFile($resource)) { 70 | return $this->validateExtension($resource); 71 | } else { 72 | return $this->isJson($resource); 73 | } 74 | } 75 | 76 | return false; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Config/Loader/FileLoader/PhpArray.php: -------------------------------------------------------------------------------- 1 | isFile($resource) && $this->validateExtension($resource); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Config/Loader/FileLoader/Yaml.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Config\Loader\FileLoader; 12 | 13 | use Symfony\Component\Yaml\Yaml as YamlParser; 14 | 15 | /** 16 | * Yaml loader class. It can load a Yaml string or a Yaml file 17 | * @see FileLoaderAbstract 18 | * 19 | * @author Raphael Antonmattei 20 | */ 21 | class Yaml extends FileLoaderAbstract 22 | { 23 | /** 24 | * Valid file extensions for this loader 25 | * @var array 26 | */ 27 | public static $validExtensions = array( 28 | 'yaml', // official extension 29 | 'yml' // but everybody uses that one 30 | ); 31 | 32 | /** 33 | * Load a Yaml string/file 34 | * 35 | * @param string $resource Yaml string or file path to a Yaml file 36 | * @param string|null $type Not used 37 | * 38 | * @return array Array containing data from the parse Yaml string or file 39 | */ 40 | public function load($resource, $type = null) 41 | { 42 | return YamlParser::parse($this->readFrom($resource)); 43 | } 44 | 45 | /** 46 | * Return whether or not the resource passed in is supported by this loader 47 | * /!\ This does not validate Yaml content. The parser will raise an exception in that case 48 | * 49 | * @param string $resource Plain string or filepath 50 | * @param string $type Not used 51 | * 52 | * @return boolean Whether or not the passed in resrouce is supported by this loader 53 | */ 54 | public function supports($resource, $type = null) 55 | { 56 | if (!is_string($resource)) { 57 | return false; 58 | } 59 | 60 | if ($this->isFile($resource)) { 61 | return $this->validateExtension($resource); 62 | } 63 | 64 | return true; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Config/Loader/PhpArray.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Config\Loader; 12 | 13 | use Symfony\Component\Config\Loader\Loader; 14 | 15 | /** 16 | * Array loader class. It loads a php array 17 | * @see Loader 18 | * 19 | * @author Raphael Antonmattei 20 | */ 21 | class PhpArray extends Loader 22 | { 23 | /** 24 | * Loads an array 25 | * 26 | * @param array $array Array to load 27 | * @param string|null $type Not used 28 | * 29 | * @return array The passed in array 30 | */ 31 | public function load($array, $type = null) 32 | { 33 | return $array; 34 | } 35 | 36 | /** 37 | * Return whether or not the passed in resource is supported by this loader 38 | * 39 | * @param string $resource Plain string or filepath 40 | * @param string|null $type Not used 41 | * 42 | * @return boolean Whether or not the passed in resource is supported by this loader 43 | */ 44 | public function supports($resource, $type = null) 45 | { 46 | return is_array($resource); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Util.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Tests; 12 | 13 | use Monolog\Logger; 14 | use Monolog\Registry; 15 | 16 | use Cascade\Cascade; 17 | use PHPUnit\Framework\TestCase; 18 | 19 | /** 20 | * Class CascadeTest 21 | * 22 | * @author Raphael Antonmattei 23 | */ 24 | class CascadeTest extends TestCase 25 | { 26 | public function teardown(): void 27 | { 28 | Registry::clear(); 29 | parent::teardown(); 30 | } 31 | 32 | public function testCreateLogger() 33 | { 34 | $logger = Cascade::createLogger('test'); 35 | 36 | $this->assertTrue($logger instanceof Logger); 37 | $this->assertEquals('test', $logger->getName()); 38 | $this->assertTrue(Registry::hasLogger('test')); 39 | } 40 | 41 | public function testRegistry() 42 | { 43 | // Creates the logger and push it to the registry 44 | $logger = Cascade::logger('test'); 45 | 46 | // We should get the logger from the registry this time 47 | $logger2 = Cascade::logger('test'); 48 | $this->assertSame($logger, $logger2); 49 | } 50 | 51 | public function testRegistryWithInvalidName() 52 | { 53 | $this->expectException(\InvalidArgumentException::class); 54 | 55 | Cascade::getLogger(null); 56 | } 57 | 58 | public function testFileConfig() 59 | { 60 | $filePath = Fixtures::getPhpArrayConfigFile(); 61 | Cascade::fileConfig($filePath); 62 | $this->assertInstanceOf('Cascade\Config', Cascade::getConfig()); 63 | } 64 | 65 | public function testLoadConfigFromArray() 66 | { 67 | $options = Fixtures::getPhpArrayConfig(); 68 | Cascade::loadConfigFromArray($options); 69 | $this->assertInstanceOf('Cascade\Config', Cascade::getConfig()); 70 | } 71 | 72 | public function testLoadConfigFromStringWithJson() 73 | { 74 | $jsonConfig = Fixtures::getJsonConfig(); 75 | Cascade::loadConfigFromString($jsonConfig); 76 | $this->assertInstanceOf('Cascade\Config', Cascade::getConfig()); 77 | } 78 | 79 | public function testLoadConfigFromStringWithYaml() 80 | { 81 | $yamlConfig = Fixtures::getYamlConfig(); 82 | Cascade::loadConfigFromString($yamlConfig); 83 | $this->assertInstanceOf('Cascade\Config', Cascade::getConfig()); 84 | } 85 | 86 | public function testHasLogger() 87 | { 88 | // implicitly create logger "existing" 89 | Cascade::logger('existing'); 90 | $this->assertFalse(Cascade::hasLogger('not_existing')); 91 | $this->assertTrue(Cascade::hasLogger('existing')); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /tests/Config/ConfigLoaderTest.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Tests\Config; 12 | 13 | use Cascade\Config\ConfigLoader; 14 | use Cascade\Tests\Fixtures; 15 | use PHPUnit\Framework\TestCase; 16 | 17 | /** 18 | * Class ConfigLoaderTest 19 | * 20 | * @author Raphael Antonmattei 21 | */ 22 | class ConfigLoaderTest extends TestCase 23 | { 24 | /** 25 | * Loader to test against 26 | * @var ConfigLoader 27 | */ 28 | protected $loader = null; 29 | 30 | public function setUp(): void 31 | { 32 | parent::setup(); 33 | $this->loader = new ConfigLoader(); 34 | } 35 | 36 | public function tearDown(): void 37 | { 38 | $this->loader = null; 39 | parent::tearDown(); 40 | } 41 | 42 | public function testLoader() 43 | { 44 | $this->assertInstanceOf( 45 | 'Symfony\Component\Config\Loader\DelegatingLoader', 46 | $this->loader 47 | ); 48 | 49 | $this->assertInstanceOf( 50 | 'Symfony\Component\Config\Loader\LoaderResolver', 51 | $this->loader->getResolver() 52 | ); 53 | 54 | $configLoaders = $this->loader->getResolver()->getLoaders(); 55 | $this->assertCount(4, $configLoaders); 56 | 57 | // Checking the order of thr loaders 58 | $this->assertInstanceOf( 59 | 'Cascade\Config\Loader\PhpArray', 60 | $configLoaders[0] 61 | ); 62 | $this->assertInstanceOf( 63 | 'Cascade\Config\Loader\FileLoader\PhpArray', 64 | $configLoaders[1] 65 | ); 66 | $this->assertInstanceOf( 67 | 'Cascade\Config\Loader\FileLoader\Json', 68 | $configLoaders[2] 69 | ); 70 | $this->assertInstanceOf( 71 | 'Cascade\Config\Loader\FileLoader\Yaml', 72 | $configLoaders[3] 73 | ); 74 | } 75 | 76 | public function testLoad() 77 | { 78 | $json = Fixtures::getSampleJsonString(); 79 | $this->assertEquals(json_decode($json, true), $this->loader->load($json)); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /tests/Config/Loader/ClassLoader/FormatterLoaderTest.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Tests\Config\Loader\ClassLoader; 12 | 13 | use Cascade\Config\Loader\ClassLoader\FormatterLoader; 14 | use PHPUnit\Framework\TestCase; 15 | 16 | /** 17 | * Class FormatterLoaderTest 18 | * 19 | * @author Raphael Antonmattei 20 | */ 21 | class FormatterLoaderTest extends TestCase 22 | { 23 | /** 24 | * Set up function 25 | */ 26 | public function setUp(): void 27 | { 28 | parent::setUp(); 29 | new FormatterLoader(array()); 30 | } 31 | 32 | /** 33 | * Tear down function 34 | */ 35 | public function tearDown(): void 36 | { 37 | FormatterLoader::$extraOptionHandlers = array(); 38 | parent::tearDown(); 39 | } 40 | 41 | /** 42 | * Check if the handler exists for a given class and option 43 | * Also checks that it a callable and return it 44 | * 45 | * @param string $class Class name the handler applies to 46 | * @param string $optionName Option name 47 | * @return \Closure Closure 48 | * @throws \Exception 49 | */ 50 | private function getHandler($class, $optionName) 51 | { 52 | if (isset(FormatterLoader::$extraOptionHandlers[$class][$optionName])) { 53 | // Get the closure 54 | $closure = FormatterLoader::$extraOptionHandlers[$class][$optionName]; 55 | 56 | $this->assertTrue(is_callable($closure)); 57 | 58 | return $closure; 59 | } else { 60 | throw new \Exception( 61 | sprintf( 62 | 'Handler %s is not defined for class %s', 63 | $optionName, 64 | $class 65 | ) 66 | ); 67 | } 68 | } 69 | 70 | /** 71 | * Tests that calling the given Closure will trigger a method call with the given param 72 | * in the given class 73 | * 74 | * @param string $class Class name 75 | * @param string $methodName Method name 76 | * @param mixed $methodArg Parameter passed to the closure 77 | * @param \Closure $closure Closure to call 78 | */ 79 | private function doTestMethodCalledInHandler($class, $methodName, $methodArg, \Closure $closure) 80 | { 81 | // Setup mock and expectations 82 | $mock = $this->getMockBuilder($class) 83 | ->setMethods(array($methodName)) 84 | ->getMock(); 85 | 86 | $mock->expects($this->once()) 87 | ->method($methodName) 88 | ->with($methodArg); 89 | 90 | // Calling the handler 91 | $closure($mock, $methodArg); 92 | } 93 | 94 | 95 | /** 96 | * Test that handlers exist 97 | */ 98 | public function testHandlersExist() 99 | { 100 | $this->assertTrue(count(FormatterLoader::$extraOptionHandlers) > 0); 101 | } 102 | 103 | /** 104 | * Data provider for testHandlers 105 | * /!\ Important note: just add values to this array if you need to test a newly added handler 106 | * If one of your handlers calls more than one method you can add more than one entries 107 | * 108 | * @return array of array of args for testHandlers 109 | */ 110 | public function handlerParamsProvider() 111 | { 112 | return array( 113 | array( 114 | 'Monolog\Formatter\LineFormatter', // Class name 115 | 'includeStacktraces', // Option name 116 | true, // Option test value 117 | 'includeStacktraces' // Name of the method called by your handler 118 | ) 119 | ); 120 | } 121 | 122 | /** 123 | * Test the extra option handlers 124 | * @see doTestMethodCalledInHandler 125 | * 126 | * @param string $class Class name 127 | * @param string $optionName Option name 128 | * @param mixed $optionValue Option value 129 | * @param string $calledMethodName Expected called method name 130 | * @dataProvider handlerParamsProvider 131 | */ 132 | public function testHandlers($class, $optionName, $optionValue, $calledMethodName) 133 | { 134 | // Test if handler exists and return it 135 | $closure = $this->getHandler($class, $optionName); 136 | 137 | $this->doTestMethodCalledInHandler($class, $calledMethodName, $optionValue, $closure); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /tests/Config/Loader/ClassLoader/HandlerLoaderTest.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Tests\Config\Loader\ClassLoader; 12 | 13 | use Monolog\Formatter\LineFormatter; 14 | 15 | use Cascade\Config\Loader\ClassLoader\HandlerLoader; 16 | use PHPUnit\Framework\TestCase; 17 | 18 | /** 19 | * Class HandlerLoaderTest 20 | * 21 | * @author Raphael Antonmattei 22 | */ 23 | class HandlerLoaderTest extends TestCase 24 | { 25 | public function testHandlerLoader() 26 | { 27 | $dummyClosure = function () { 28 | // Empty function 29 | }; 30 | $original = $options = array( 31 | 'class' => 'Monolog\Handler\TestHandler', 32 | 'level' => 'DEBUG', 33 | 'formatter' => 'test_formatter', 34 | 'processors' => array('test_processor_1', 'test_processor_2') 35 | ); 36 | $formatters = array('test_formatter' => new LineFormatter()); 37 | $processors = array( 38 | 'test_processor_1' => $dummyClosure, 39 | 'test_processor_2' => $dummyClosure 40 | ); 41 | $loader = new HandlerLoader($options, $formatters, $processors); 42 | 43 | $this->assertNotEquals($original, $options); 44 | $this->assertSame($formatters['test_formatter'], $options['formatter']); 45 | $this->assertSame($processors['test_processor_1'], $options['processors'][0]); 46 | $this->assertSame($processors['test_processor_2'], $options['processors'][1]); 47 | } 48 | 49 | public function testHandlerLoaderWithNoOptions() 50 | { 51 | $original = $options = array(); 52 | $loader = new HandlerLoader($options); 53 | 54 | $this->assertEquals($original, $options); 55 | } 56 | 57 | public function testHandlerLoaderWithInvalidFormatter() 58 | { 59 | $this->expectException(\InvalidArgumentException::class); 60 | 61 | $options = array( 62 | 'formatter' => 'test_formatter' 63 | ); 64 | 65 | $formatters = array('test_formatterXYZ' => new LineFormatter()); 66 | $loader = new HandlerLoader($options, $formatters); 67 | } 68 | 69 | public function testHandlerLoaderWithInvalidProcessor() 70 | { 71 | $this->expectException(\InvalidArgumentException::class); 72 | 73 | $dummyClosure = function () { 74 | // Empty function 75 | }; 76 | $options = array( 77 | 'processors' => array('test_processor_1') 78 | ); 79 | 80 | $formatters = array(); 81 | $processors = array('test_processorXYZ' => $dummyClosure); 82 | $loader = new HandlerLoader($options, $formatters, $processors); 83 | } 84 | 85 | public function testHandlerLoaderWithInvalidHandler() 86 | { 87 | $this->expectException(\InvalidArgumentException::class); 88 | 89 | $dummyClosure = function () { 90 | // Empty function 91 | }; 92 | $options = array( 93 | 'handler' => 'test_handler' 94 | ); 95 | 96 | $formatters = array(); 97 | $processors = array(); 98 | $handlers = array('test_handlerXYZ' => $dummyClosure); 99 | $loader = new HandlerLoader($options, $formatters, $processors, $handlers); 100 | } 101 | 102 | public function testHandlerLoaderWithInvalidHandlers() 103 | { 104 | $this->expectException(\InvalidArgumentException::class); 105 | 106 | $dummyClosure = function () { 107 | // Empty function 108 | }; 109 | $options = array( 110 | 'handlers' => array('test_handler_1', 'test_handler_2') 111 | ); 112 | 113 | $formatters = array(); 114 | $processors = array(); 115 | $handlers = array( 116 | 'test_handler_1' => $dummyClosure, 117 | 'test_handlerXYZ' => $dummyClosure 118 | ); 119 | $loader = new HandlerLoader($options, $formatters, $processors, $handlers); 120 | } 121 | 122 | /** 123 | * Check if the handler exists for a given class and option 124 | * Also checks that it a callable and return it 125 | * 126 | * @param string $class Class name the handler applies to 127 | * @param string $optionName Option name 128 | * @return \Closure Closure 129 | * @throws \Exception 130 | */ 131 | private function getHandler($class, $optionName) 132 | { 133 | if (isset(HandlerLoader::$extraOptionHandlers[$class][$optionName])) { 134 | // Get the closure 135 | $closure = HandlerLoader::$extraOptionHandlers[$class][$optionName]; 136 | 137 | $this->assertTrue(is_callable($closure)); 138 | 139 | return $closure; 140 | } else { 141 | throw new \Exception( 142 | sprintf( 143 | 'Custom handler %s is not defined for class %s', 144 | $optionName, 145 | $class 146 | ) 147 | ); 148 | } 149 | } 150 | 151 | /** 152 | * Tests that calling the given Closure will trigger a method call with the given param 153 | * in the given class 154 | * 155 | * @param string $class Class name 156 | * @param string $methodName Method name 157 | * @param mixed $methodArg Parameter passed to the closure 158 | * @param \Closure $closure Closure to call 159 | */ 160 | private function doTestMethodCalledInHandler($class, $methodName, $methodArg, \Closure $closure) 161 | { 162 | // Setup mock and expectations 163 | $mock = $this->getMockBuilder($class) 164 | ->disableOriginalConstructor() 165 | ->setMethods(array($methodName)) 166 | ->getMock(); 167 | 168 | $mock->expects($this->once()) 169 | ->method($methodName) 170 | ->with($methodArg); 171 | 172 | // Calling the handler 173 | $closure($mock, $methodArg); 174 | } 175 | 176 | 177 | /** 178 | * Test that handlers exist 179 | */ 180 | public function testHandlersExist() 181 | { 182 | $options = array(); 183 | new HandlerLoader($options); 184 | $this->assertTrue(count(HandlerLoader::$extraOptionHandlers) > 0); 185 | } 186 | 187 | /** 188 | * Data provider for testHandlers 189 | * /!\ Important note: 190 | * Just add values to this array if you need to test a newly added handler 191 | * 192 | * If one of your handlers calls more than one method you can add more than one entries 193 | * 194 | * @return array of array of args for testHandlers 195 | */ 196 | public function handlerParamsProvider() 197 | { 198 | return array( 199 | array( 200 | '*', // Class name 201 | 'formatter', // Option name 202 | new LineFormatter(), // Option test value 203 | 'setFormatter' // Name of the method called by your handler 204 | ), 205 | array( 206 | 'Monolog\Handler\LogglyHandler', // Class name 207 | 'tags', // Option name 208 | array('some_tag'), // Option test value 209 | 'setTag' // Name of the method called by your handler 210 | ) 211 | ); 212 | } 213 | 214 | /** 215 | * Test the extra option handlers 216 | * 217 | * @param string $class Class name 218 | * @param string $optionName Option name 219 | * @param mixed $optionValue Option value 220 | * @param string $calledMethodName Expected called method name 221 | * @dataProvider handlerParamsProvider 222 | */ 223 | public function testHandlers($class, $optionName, $optionValue, $calledMethodName) 224 | { 225 | $options = array(); 226 | new HandlerLoader($options); 227 | // Test if handler exists and return it 228 | $closure = $this->getHandler($class, $optionName); 229 | 230 | if ($class == '*') { 231 | $class = 'Monolog\Handler\TestHandler'; 232 | } 233 | 234 | $this->doTestMethodCalledInHandler($class, $calledMethodName, $optionValue, $closure); 235 | } 236 | 237 | /** 238 | * Test extra option processor handler 239 | */ 240 | public function testHandlerForProcessor() 241 | { 242 | $options = array(); 243 | 244 | $mockProcessor1 = function($record) { 245 | $record['extra']['dummy'] = 'Hello world 1!'; 246 | return $record; 247 | }; 248 | $mockProcessor2 = function($record) { 249 | $record['extra']['dummy'] = 'Hello world 1!'; 250 | return $record; 251 | }; 252 | $processorsArray = array($mockProcessor1, $mockProcessor2); 253 | 254 | // Setup mock and expectations 255 | $mockHandler = $this->getMockBuilder('Monolog\Handler\TestHandler') 256 | ->disableOriginalConstructor() 257 | ->setMethods(array('pushProcessor')) 258 | ->getMock(); 259 | 260 | $mockHandler->expects($this->exactly(sizeof($processorsArray))) 261 | ->method('pushProcessor') 262 | ->withConsecutive(array($mockProcessor2), array($mockProcessor1)); 263 | 264 | new HandlerLoader($options); 265 | $closure = $this->getHandler('*', 'processors'); 266 | $closure($mockHandler, $processorsArray); 267 | } 268 | 269 | public function testReplacesHandlerNamesInOptionsArrayWithLoadedCallable() 270 | { 271 | $options = array( 272 | 'handlers' => array( 273 | 'foo', 274 | 'bar', 275 | ), 276 | 'handler' => 'baz' 277 | ); 278 | 279 | $formatters = array(); 280 | $processors = array(); 281 | $handlers = array( 282 | 'foo' => function () { 283 | return 'foo'; 284 | }, 285 | 'bar' => function () { 286 | return 'bar'; 287 | }, 288 | 'baz' => function () { 289 | return 'baz'; 290 | }, 291 | ); 292 | 293 | $loader = new HandlerLoader($options, $formatters, $processors, $handlers); 294 | 295 | $this->assertSame($handlers['foo'], $options['handlers'][0]); 296 | $this->assertSame($handlers['bar'], $options['handlers'][1]); 297 | $this->assertSame($handlers['baz'], $options['handler']); 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /tests/Config/Loader/ClassLoader/LoggerLoaderTest.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Tests\Config\Loader\ClassLoader; 12 | 13 | use Monolog\Handler\TestHandler; 14 | use Monolog\Logger; 15 | use Monolog\Registry; 16 | 17 | use Cascade\Config\Loader\ClassLoader\LoggerLoader; 18 | use PHPUnit\Framework\TestCase; 19 | 20 | /** 21 | * Class LoggerLoaderTest 22 | * 23 | * @author Raphael Antonmattei 24 | */ 25 | class LoggerLoaderTest extends TestCase 26 | { 27 | /** 28 | * Tear down function 29 | */ 30 | public function tearDown(): void 31 | { 32 | parent::tearDown(); 33 | Registry::clear(); 34 | } 35 | 36 | public function testConstructor() 37 | { 38 | $loader = new LoggerLoader('testLogger'); 39 | 40 | $this->assertTrue(Registry::hasLogger('testLogger')); 41 | } 42 | 43 | public function testResolveHandlers() 44 | { 45 | $options = array( 46 | 'handlers' => array('test_handler_1', 'test_handler_2') 47 | ); 48 | $handlers = array( 49 | 'test_handler_1' => new TestHandler(), 50 | 'test_handler_2' => new TestHandler() 51 | ); 52 | $loader = new LoggerLoader('testLogger', $options, $handlers); 53 | 54 | $this->assertEquals( 55 | array_values($handlers), 56 | $loader->resolveHandlers($options, $handlers) 57 | ); 58 | } 59 | 60 | public function testResolveHandlersWithMismatch() 61 | { 62 | $this->expectException(\InvalidArgumentException::class); 63 | 64 | $options = array( 65 | 'handlers' => array('unexisting_handler', 'test_handler_2') 66 | ); 67 | $handlers = array( 68 | 'test_handler_1' => new TestHandler(), 69 | 'test_handler_2' => new TestHandler() 70 | ); 71 | $loader = new LoggerLoader('testLogger', $options, $handlers); 72 | 73 | // This should throw an InvalidArgumentException 74 | $loader->resolveHandlers($options, $handlers); 75 | } 76 | 77 | public function testResolveProcessors() 78 | { 79 | $dummyClosure = function () { 80 | // Empty function 81 | }; 82 | $options = array( 83 | 'processors' => array('test_processor_1', 'test_processor_2') 84 | ); 85 | $processors = array( 86 | 'test_processor_1' => $dummyClosure, 87 | 'test_processor_2' => $dummyClosure 88 | ); 89 | 90 | $loader = new LoggerLoader('testLogger', $options, array(), $processors); 91 | 92 | $this->assertEquals( 93 | array_values($processors), 94 | $loader->resolveProcessors($options, $processors) 95 | ); 96 | } 97 | 98 | public function testResolveProcessorsWithMismatch() 99 | { 100 | $this->expectException(\InvalidArgumentException::class); 101 | 102 | $dummyClosure = function () { 103 | // Empty function 104 | }; 105 | $options = array( 106 | 'processors' => array('unexisting_processor', 'test_processor_2') 107 | ); 108 | $processors = array( 109 | 'test_processor_1' => $dummyClosure, 110 | 'test_processor_2' => $dummyClosure 111 | ); 112 | 113 | $loader = new LoggerLoader('testLogger', $options, array(), $processors); 114 | 115 | // This should throw an InvalidArgumentException 116 | $loader->resolveProcessors($options, $processors); 117 | } 118 | 119 | public function testLoad() 120 | { 121 | $options = array( 122 | 'handlers' => array('test_handler_1', 'test_handler_2'), 123 | 'processors' => array('test_processor_1', 'test_processor_2') 124 | ); 125 | $handlers = array( 126 | 'test_handler_1' => new TestHandler(), 127 | 'test_handler_2' => new TestHandler() 128 | ); 129 | $dummyClosure = function () { 130 | // Empty function 131 | }; 132 | $processors = array( 133 | 'test_processor_1' => $dummyClosure, 134 | 'test_processor_2' => $dummyClosure 135 | ); 136 | 137 | $loader = new LoggerLoader('testLogger', $options, $handlers, $processors); 138 | $logger = $loader->load(); 139 | 140 | $this->assertTrue($logger instanceof Logger); 141 | $this->assertEquals(array_values($handlers), $logger->getHandlers()); 142 | $this->assertEquals(array_values($processors), $logger->getProcessors()); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /tests/Config/Loader/ClassLoader/ProcessorLoaderTest.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Tests\Config\Loader\ClassLoader; 12 | 13 | use Monolog\Processor\WebProcessor; 14 | 15 | use Cascade\Config\Loader\ClassLoader\ProcessorLoader; 16 | use PHPUnit\Framework\TestCase; 17 | 18 | /** 19 | * Class ProcessorLoaderTest 20 | * 21 | * @author Kate Burdon 22 | */ 23 | class ProcessorLoaderTest extends TestCase 24 | { 25 | public function testProcessorLoader() 26 | { 27 | $options = array( 28 | 'class' => 'Monolog\Processor\WebProcessor' 29 | ); 30 | $processors = array(new WebProcessor()); 31 | $loader = new ProcessorLoader($options, $processors); 32 | 33 | $this->assertEquals($loader->class, $options['class']); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Config/Loader/ClassLoader/Resolver/ConstructorResolverTest.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Tests\Config\Loader\ClassLoader\Resolver; 12 | 13 | use Cascade\Util; 14 | use Cascade\Config\Loader\ClassLoader\Resolver\ConstructorResolver; 15 | 16 | use PHPUnit\Framework\TestCase; 17 | use Symfony; 18 | use Symfony\Component\OptionsResolver\Exception\MissingOptionsException; 19 | use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException; 20 | 21 | /** 22 | * Class ConstructorResolverTest 23 | * 24 | * @author Raphael Antonmattei 25 | */ 26 | class ConstructorResolverTest extends TestCase 27 | { 28 | /** 29 | * Reflection class for which you want to resolve extra options 30 | * @var \ReflectionClass 31 | */ 32 | protected $reflected = null; 33 | 34 | /** 35 | * Constructor Resolver 36 | * @var ConstructorResolver 37 | */ 38 | protected $resolver = null; 39 | 40 | /** 41 | * Set up function 42 | */ 43 | public function setUp(): void 44 | { 45 | $this->class = 'Cascade\Tests\Fixtures\SampleClass'; 46 | $this->resolver = new ConstructorResolver(new \ReflectionClass($this->class)); 47 | parent::setUp(); 48 | } 49 | 50 | /** 51 | * Tear down function 52 | */ 53 | public function tearDown(): void 54 | { 55 | $this->resolver = null; 56 | $this->class = null; 57 | parent::tearDown(); 58 | } 59 | 60 | /** 61 | * Return the contructor args of the reflected class 62 | * 63 | * @return \ReflectionParameter[] array of params 64 | */ 65 | protected function getConstructorArgs() 66 | { 67 | return $this->resolver->getReflected()->getConstructor()->getParameters(); 68 | } 69 | 70 | /** 71 | * Test the resolver contructor 72 | */ 73 | public function testConstructor() 74 | { 75 | $this->assertEquals($this->class, $this->resolver->getReflected()->getName()); 76 | } 77 | 78 | /** 79 | * Test that constructor args were pulled properly 80 | * 81 | * Note that we need to deuplicate the CamelCase conversion here for old 82 | * fashioned classes 83 | */ 84 | public function testInitConstructorArgs() 85 | { 86 | $expectedConstructorArgs = array(); 87 | 88 | foreach ($this->getConstructorArgs() as $param) { 89 | $expectedConstructorArgs[Util::snakeToCamelCase($param->getName())] = $param; 90 | } 91 | $this->assertEquals($expectedConstructorArgs, $this->resolver->getConstructorArgs()); 92 | } 93 | 94 | /** 95 | * Test the hashToArgsArray function 96 | */ 97 | public function testHashToArgsArray() 98 | { 99 | $this->assertEquals( 100 | array('someValue', 'hello', 'there', 'slither'), 101 | $this->resolver->hashToArgsArray( 102 | array( // Not properly ordered on purpose 103 | 'optionalB' => 'there', 104 | 'optionalA' => 'hello', 105 | 'optionalSnake' => 'slither', 106 | 'mandatory' => 'someValue', 107 | ) 108 | ) 109 | ); 110 | } 111 | 112 | /** 113 | * Data provider for testResolve 114 | * 115 | * The order of the input options does not matter and is somewhat random. The resolution 116 | * should reconcile those options and match them up with the contructor param position 117 | * 118 | * @return array of arrays with expected resolved values and options used as input 119 | */ 120 | public function optionsProvider() 121 | { 122 | return array( 123 | array( 124 | array('someValue', 'hello', 'there', 'slither'), // Expected resolved options 125 | array( // Options (order should not matter, part of resolution) 126 | 'optionalB' => 'there', 127 | 'optionalA' => 'hello', 128 | 'mandatory' => 'someValue', 129 | 'optionalSnake' => 'slither', 130 | ) 131 | ), 132 | array( 133 | array('someValue', 'hello', 'BBB', 'snake'), 134 | array( 135 | 'mandatory' => 'someValue', 136 | 'optionalA' => 'hello', 137 | ) 138 | ), 139 | array( 140 | array('someValue', 'AAA', 'BBB', 'snake'), 141 | array('mandatory' => 'someValue') 142 | ) 143 | ); 144 | } 145 | 146 | /** 147 | * Test resolving with valid options 148 | * 149 | * @param array $expectedResolvedOptions Array of expected resolved options 150 | * (i.e. parsed and validated) 151 | * @param array $options Array of raw options 152 | * @dataProvider optionsProvider 153 | */ 154 | public function testResolve(array $expectedResolvedOptions, array $options) 155 | { 156 | $this->assertEquals($expectedResolvedOptions, $this->resolver->resolve($options)); 157 | } 158 | 159 | /** 160 | * Data provider for testResolveWithInvalidOptions. 161 | * 162 | * The order of the input options does not matter and is somewhat random. The resolution 163 | * should reconcile those options and match them up with the contructor param position 164 | * 165 | * @return array of arrays with expected resolved values and options used as input 166 | */ 167 | public function missingOptionsProvider() 168 | { 169 | return array( 170 | array( 171 | array( // No values 172 | ), 173 | array( // Missing a mandatory value 174 | 'optionalB' => 'BBB' 175 | ), 176 | array( // Still missing a mandatory value 177 | 'optionalB' => 'there', 178 | 'optionalA' => 'hello' 179 | ) 180 | ) 181 | ); 182 | } 183 | 184 | /** 185 | * Test resolving with missing/incomplete options. It should throw an exception. 186 | * 187 | * @param array $incompleteOptions Array of invalid options 188 | * @dataProvider missingOptionsProvider 189 | */ 190 | public function testResolveWithMissingOptions(array $incompleteOptions) 191 | { 192 | $this->expectException(MissingOptionsException::class); 193 | 194 | $this->resolver->resolve($incompleteOptions); 195 | } 196 | 197 | /** 198 | * Data provider for testResolveWithInvalidOptions 199 | * 200 | * The order of the input options does not matter and is somewhat random. The resolution 201 | * should reconcile those options and match them up with the contructor param position 202 | * 203 | * @return array of arrays with expected resolved values and options used as input 204 | */ 205 | public function invalidOptionsProvider() 206 | { 207 | return array( 208 | array( 209 | array('ABC'), 210 | array( // All invalid 211 | 'someInvalidOptionA' => 'abc', 212 | 'someInvalidOptionB' => 'def' 213 | ), 214 | array( // Some invalid 215 | 'optionalB' => 'there', 216 | 'optionalA' => 'hello', 217 | 'mandatory' => 'dsadsa', 218 | 'additionalInvalid' => 'some unknow param' 219 | ) 220 | ) 221 | ); 222 | } 223 | 224 | /** 225 | * Test resolving with invalid options. It should throw an exception. 226 | * 227 | * @param array $invalidOptions Array of invalid options 228 | * @dataProvider invalidOptionsProvider 229 | */ 230 | public function testResolveWithInvalidOptions($invalidOptions) 231 | { 232 | $this->expectException(UndefinedOptionsException::class); 233 | 234 | $this->resolver->resolve($invalidOptions); 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /tests/Config/Loader/ClassLoader/Resolver/ExtraOptionsResolverTest.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Tests\Config\Loader\ClassLoader\Resolver; 12 | 13 | use Cascade\Config\Loader\ClassLoader\Resolver\ExtraOptionsResolver; 14 | 15 | use PHPUnit\Framework\TestCase; 16 | use Symfony; 17 | use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException; 18 | 19 | /** 20 | * Class ExtraOptionsResolverTest 21 | * 22 | * @author Raphael Antonmattei 23 | */ 24 | class ExtraOptionsResolverTest extends TestCase 25 | { 26 | /** 27 | * Reflection class for which you want to resolve extra options 28 | * @var \ReflectionClass 29 | */ 30 | protected $reflected = null; 31 | 32 | /** 33 | * ExtraOptions Resolver 34 | * @var ExtraOptionsResolver 35 | */ 36 | protected $resolver = null; 37 | 38 | /** 39 | * Set up function 40 | */ 41 | public function setUp(): void 42 | { 43 | $this->class = 'Cascade\Tests\Fixtures\SampleClass'; 44 | $this->params = array('optionalA', 'optionalB'); 45 | $this->resolver = new ExtraOptionsResolver( 46 | new \ReflectionClass($this->class), 47 | $this->params 48 | ); 49 | parent::setUp(); 50 | } 51 | 52 | /** 53 | * Tear down function 54 | */ 55 | public function tearDown(): void 56 | { 57 | $this->resolver = null; 58 | $this->class = null; 59 | parent::tearDown(); 60 | } 61 | 62 | /** 63 | * Test the hsah key generation 64 | */ 65 | public function testGenerateParamsHashKey() 66 | { 67 | $a = array('optionA', 'optionB', 'optionC'); 68 | $b = array('optionA', 'optionB', 'optionC'); 69 | 70 | $this->assertEquals( 71 | ExtraOptionsResolver::generateParamsHashKey($a), 72 | ExtraOptionsResolver::generateParamsHashKey($b) 73 | ); 74 | } 75 | 76 | /** 77 | * Test the resolver contructor 78 | */ 79 | public function testConstructor() 80 | { 81 | $this->assertEquals($this->class, $this->resolver->getReflected()->getName()); 82 | $this->assertEquals($this->params, $this->resolver->getParams()); 83 | } 84 | 85 | /** 86 | * Test resolving with valid options 87 | */ 88 | public function testResolve() 89 | { 90 | $this->assertEquals( 91 | array_combine($this->params, array('hello', 'there')), 92 | $this->resolver->resolve(array('optionalB' => 'there', 'optionalA' => 'hello')) 93 | ); 94 | 95 | // Resolve an empty array (edge case) 96 | $this->assertEquals(array(), $this->resolver->resolve(array())); 97 | } 98 | 99 | /** 100 | * Data provider for testResolveWithInvalidOptions 101 | * 102 | * The order of the input options does not matter and is somewhat random. The resolution 103 | * should reconcile those options and match them up with the closure param position 104 | * 105 | * @return array of arrays with expected resolved values and options used as input 106 | */ 107 | public function optionsProvider() 108 | { 109 | return array( 110 | array( 111 | array('optionalA', 'optionalB', 'mandatory'), 112 | $this->getMockBuilder('Cascade\Config\Loader\ClassLoader') 113 | ->disableOriginalConstructor() 114 | ->getMock()->method('canHandle') 115 | ->willReturn(true) 116 | ) 117 | ); 118 | } 119 | 120 | /** 121 | * Test resolving with valid options 122 | */ 123 | public function testResolveWithCustomOptionHandler() 124 | { 125 | $this->params = array('optionalA', 'optionalB', 'mandatory'); 126 | $this->resolver = new ExtraOptionsResolver( 127 | new \ReflectionClass($this->class), 128 | $this->params 129 | ); 130 | 131 | // Create a stub for the SomeClass class. 132 | $stub = $this->getMockBuilder('Cascade\Config\Loader\ClassLoader') 133 | ->disableOriginalConstructor() 134 | ->getMock(); 135 | 136 | $stub->method('canHandle') 137 | ->willReturn(true); 138 | 139 | // Resolve an empty array (edge case) 140 | $this->assertEquals(array('mandatory' => 'abc'), $this->resolver->resolve(array('mandatory' => 'abc'), $stub)); 141 | } 142 | 143 | /** 144 | * Data provider for testResolveWithInvalidOptions 145 | * 146 | * The order of the input options does not matter and is somewhat random. The resolution 147 | * should reconcile those options and match them up with the closure param position 148 | * 149 | * @return array of arrays with expected resolved values and options used as input 150 | */ 151 | public function invalidOptionsProvider() 152 | { 153 | return array( 154 | array( 155 | array( // Some invalid 156 | 'optionalB' => 'there', 157 | 'optionalA' => 'hello', 158 | 'additionalInvalid' => 'some unknow param' 159 | ), 160 | array( // All invalid 161 | 'someInvalidOptionA' => 'abc', 162 | 'someInvalidOptionB' => 'def' 163 | ) 164 | ) 165 | ); 166 | } 167 | 168 | /** 169 | * Test resolving with invalid options. It should throw an exception. 170 | * 171 | * @param array $invalidOptions Array of invalid options 172 | * @dataProvider invalidOptionsProvider 173 | */ 174 | public function testResolveWithInvalidOptions($invalidOptions) 175 | { 176 | $this->expectException(UndefinedOptionsException::class); 177 | 178 | $this->resolver->resolve($invalidOptions); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /tests/Config/Loader/ClassLoaderTest.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Tests\Config\Loader; 12 | 13 | use Cascade\Config\Loader\ClassLoader; 14 | use Cascade\Tests\Fixtures\DependentClass; 15 | use Cascade\Tests\Fixtures\SampleClass; 16 | use PHPUnit\Framework\TestCase; 17 | 18 | /** 19 | * Class ClassLoaderTest 20 | * 21 | * @author Raphael Antonmattei 22 | * @author Dom Morgan 23 | */ 24 | class ClassLoaderTest extends TestCase 25 | { 26 | /** 27 | * Set up function 28 | */ 29 | public function setUp(): void 30 | { 31 | parent::setUp(); 32 | } 33 | 34 | /** 35 | * Tear down function 36 | */ 37 | public function tearDown(): void 38 | { 39 | ClassLoader::$extraOptionHandlers = array(); 40 | parent::tearDown(); 41 | } 42 | 43 | /** 44 | * Provides options with and without a class param 45 | * @return array of args 46 | */ 47 | public function dataFortestSetClass() 48 | { 49 | return array( 50 | array( 51 | array( 52 | 'class' => 'Cascade\Tests\Fixtures\SampleClass', 53 | 'some_param' => 'abc' 54 | ), 55 | 'Cascade\Tests\Fixtures\SampleClass' 56 | ), 57 | array( 58 | array( 59 | 'some_param' => 'abc' 60 | ), 61 | '\stdClass' 62 | ) 63 | ); 64 | } 65 | 66 | /** 67 | * Testing the setClass method 68 | * 69 | * @param array $options Array of options 70 | * @param string $expectedClass Expected classname of the instantiated object 71 | * @dataProvider dataFortestSetClass 72 | */ 73 | public function testSetClass($options, $expectedClass) 74 | { 75 | $loader = new ClassLoader($options); 76 | 77 | $this->assertEquals($expectedClass, $loader->class); 78 | } 79 | 80 | public function testOptionsToCamelCase() 81 | { 82 | $array = array('hello_there' => 'Hello', 'bye_bye' => 'Bye'); 83 | 84 | $this->assertEquals( 85 | array('helloThere' => 'Hello', 'byeBye' => 'Bye'), 86 | ClassLoader::optionsToCamelCase($array) 87 | ); 88 | } 89 | 90 | public function testGetExtraOptionsHandler() 91 | { 92 | ClassLoader::$extraOptionHandlers = array( 93 | '*' => array( 94 | 'hello' => function ($instance, $value) { 95 | $instance->setHello(strtoupper($value)); 96 | } 97 | ), 98 | 'Cascade\Tests\Fixtures\SampleClass' => array( 99 | 'there' => function ($instance, $value) { 100 | $instance->setThere(strtoupper($value).'!!!'); 101 | } 102 | ) 103 | ); 104 | 105 | $loader = new ClassLoader(array()); 106 | $existingHandler = $loader->getExtraOptionsHandler('hello'); 107 | $this->assertNotNull($existingHandler); 108 | $this->assertTrue(is_callable($existingHandler)); 109 | 110 | $this->assertNull($loader->getExtraOptionsHandler('nohandler')); 111 | } 112 | 113 | public function testLoad() 114 | { 115 | $options = array( 116 | 'class' => 'Cascade\Tests\Fixtures\SampleClass', 117 | 'mandatory' => 'someValue', 118 | 'optional_X' => 'testing some stuff', 119 | 'optional_Y' => 'testing other stuff', 120 | 'hello' => 'hello', 121 | 'there' => 'there', 122 | ); 123 | 124 | ClassLoader::$extraOptionHandlers = array( 125 | '*' => array( 126 | 'hello' => function ($instance, $value) { 127 | $instance->setHello(strtoupper($value)); 128 | } 129 | ), 130 | 'Cascade\Tests\Fixtures\SampleClass' => array( 131 | 'there' => function ($instance, $value) { 132 | $instance->setThere(strtoupper($value).'!!!'); 133 | } 134 | ) 135 | ); 136 | 137 | $loader = new ClassLoader($options); 138 | $instance = $loader->load(); 139 | 140 | $expectedInstance = new SampleClass('someValue'); 141 | $expectedInstance->optionalX('testing some stuff'); 142 | $expectedInstance->optionalY = 'testing other stuff'; 143 | $expectedInstance->setHello('HELLO'); 144 | $expectedInstance->setThere('THERE!!!'); 145 | 146 | $this->assertEquals($expectedInstance, $instance); 147 | } 148 | 149 | /** 150 | * Test a nested class to load 151 | */ 152 | public function testLoadDependency() 153 | { 154 | $options = array( 155 | 'class' => 'Cascade\Tests\Fixtures\DependentClass', 156 | 'dependency' => array( 157 | 'class' => 'Cascade\Tests\Fixtures\SampleClass', 158 | 'mandatory' => 'someValue', 159 | ) 160 | ); 161 | 162 | $loader = new ClassLoader($options); 163 | $instance = $loader->load(); 164 | 165 | $expectedInstance = new DependentClass( 166 | new SampleClass('someValue') 167 | ); 168 | 169 | $this->assertEquals($expectedInstance, $instance); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /tests/Config/Loader/FileLoader/FileLoaderAbstractTest.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Tests\Config\Loader\FileLoader; 12 | 13 | use PHPUnit\Framework\MockObject\MockObject; 14 | use PHPUnit\Framework\TestCase; 15 | use Symfony\Component\Config\FileLocator; 16 | use Symfony\Component\Config\FileLocatorInterface; 17 | use org\bovigo\vfs\vfsStream; 18 | 19 | use Cascade\Tests\Fixtures; 20 | 21 | /** 22 | * Class FileLoaderAbstractTest 23 | * 24 | * @author Raphael Antonmattei 25 | */ 26 | class FileLoaderAbstractTest extends TestCase 27 | { 28 | /** 29 | * Mock of extending Cascade\Config\Loader\FileLoader\FileLoaderAbstract 30 | * @var MockObject 31 | */ 32 | protected $mock = null; 33 | 34 | public function setUp(): void 35 | { 36 | parent::setUp(); 37 | 38 | $fileLocatorMock = $this->getMockBuilder('Symfony\Component\Config\FileLocatorInterface') 39 | ->getMock(); 40 | 41 | $this->mock = $this->getMockForAbstractClass( 42 | 'Cascade\Config\Loader\FileLoader\FileLoaderAbstract', 43 | array($fileLocatorMock), 44 | 'FileLoaderAbstractMockClass' // mock class name 45 | ); 46 | 47 | // Setting valid extensions for tests 48 | \FileLoaderAbstractMockClass::$validExtensions = array('test', 'php'); 49 | } 50 | 51 | public function tearDown(): void 52 | { 53 | $this->mock = null; 54 | parent::tearDown(); 55 | } 56 | 57 | /** 58 | * Test loading config from a valid file 59 | */ 60 | public function testReadFrom() 61 | { 62 | $this->assertEquals( 63 | Fixtures::getSampleYamlString(), 64 | $this->mock->readFrom(Fixtures::getSampleYamlFile()) 65 | ); 66 | } 67 | 68 | /** 69 | * Test loading config from a valid file 70 | */ 71 | public function testLoadFileFromString() 72 | { 73 | $this->assertEquals( 74 | trim(Fixtures::getSampleString()), 75 | $this->mock->readFrom(Fixtures::getSampleString()) 76 | ); 77 | } 78 | 79 | /** 80 | * Data provider for testGetSectionOf 81 | * 82 | * @return array array with original value, section and expected value 83 | */ 84 | public function extensionsDataProvider() 85 | { 86 | return array( 87 | array(true, 'hello/world.test'), 88 | array(true, 'hello/world.php'), 89 | array(false, 'hello/world.jpeg'), 90 | array(false, 'hello/world'), 91 | array(false, '') 92 | ); 93 | } 94 | 95 | /** 96 | * Test validating the extension 97 | * 98 | * @param boolean $expected Expected boolean value 99 | * @param string $filepath Filepath to validate 100 | * @dataProvider extensionsDataProvider 101 | */ 102 | public function testValidateExtension($expected, $filepath) 103 | { 104 | if ($expected) { 105 | $this->assertTrue($this->mock->validateExtension($filepath)); 106 | } else { 107 | $this->assertFalse($this->mock->validateExtension($filepath)); 108 | } 109 | } 110 | 111 | /** 112 | * Data provider for testGetSectionOf 113 | * 114 | * @return array array wit original value, section and expected value 115 | */ 116 | public function arrayDataProvider() 117 | { 118 | return array( 119 | array( 120 | array( 121 | 'a' => array('aa' => 'AA', 'ab' => 'AB'), 122 | 'b' => array('ba' => 'BA', 'bb' => 'BB') 123 | ), 124 | 'b', 125 | array('ba' => 'BA', 'bb' => 'BB') 126 | ), 127 | array( 128 | array('a' => 'A', 'b' => 'B'), 129 | 'c', 130 | array('a' => 'A', 'b' => 'B'), 131 | ), 132 | array( 133 | array('a' => 'A', 'b' => 'B'), 134 | '', 135 | array('a' => 'A', 'b' => 'B'), 136 | ) 137 | ); 138 | } 139 | 140 | /** 141 | * Test the getSectionOf function 142 | * 143 | * @param array $array Array of options 144 | * @param string $section Section key 145 | * @param array $expected Expected array for the given section 146 | * @dataProvider arrayDataProvider 147 | */ 148 | public function testGetSectionOf(array $array, $section, array $expected) 149 | { 150 | $this->assertSame($expected, $this->mock->getSectionOf($array, $section)); 151 | } 152 | 153 | /** 154 | * Test loading an invalid file 155 | */ 156 | public function testloadFileFromInvalidFile() 157 | { 158 | $this->expectException(\RuntimeException::class); 159 | 160 | // mocking the file system from a 'config_dir' base dir 161 | $root = vfsStream::setup('config_dir'); 162 | 163 | // Adding an unreadable file (chmod 0000) 164 | vfsStream::newFile('config.yml', 0000) 165 | ->withContent( 166 | "---\n". 167 | "hidden_config: true" 168 | )->at($root); 169 | 170 | // This will throw an exception because the file is not readable 171 | $this->mock->readFrom(vfsStream::url('config_dir/config.yml')); 172 | 173 | stream_wrapper_unregister(vfsStream::SCHEME); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /tests/Config/Loader/FileLoader/JsonTest.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Tests\Config\Loader\FileLoader; 12 | 13 | use Cascade\Tests\Fixtures; 14 | use PHPUnit\Framework\MockObject\MockBuilder; 15 | use PHPUnit\Framework\TestCase; 16 | 17 | /** 18 | * Class JsonTest 19 | * 20 | * @author Raphael Antonmattei 21 | */ 22 | class JsonTest extends TestCase 23 | { 24 | /** 25 | * JSON loader mock builder 26 | * @var MockBuilder 27 | */ 28 | protected $jsonLoader = null; 29 | 30 | public function setUp(): void 31 | { 32 | parent::setUp(); 33 | 34 | $fileLocatorMock = $this->getMockBuilder('Symfony\Component\Config\FileLocatorInterface') 35 | ->getMock(); 36 | 37 | $this->jsonLoader = $this->getMockBuilder( 38 | 'Cascade\Config\Loader\FileLoader\Json' 39 | ) 40 | ->setConstructorArgs(array($fileLocatorMock)) 41 | ->setMethods(array('readFrom', 'isFile', 'validateExtension')) 42 | ->getMock(); 43 | } 44 | 45 | public function tearDown(): void 46 | { 47 | $this->jsonLoader = null; 48 | parent::tearDown(); 49 | } 50 | 51 | /** 52 | * Test loading a JSON string 53 | */ 54 | public function testLoad() 55 | { 56 | $json = Fixtures::getSampleJsonString(); 57 | 58 | $this->jsonLoader->expects($this->once()) 59 | ->method('readFrom') 60 | ->willReturn($json); 61 | 62 | $this->assertEquals( 63 | json_decode($json, true), 64 | $this->jsonLoader->load($json) 65 | ); 66 | } 67 | 68 | /** 69 | * Data provider for testSupportsWithInvalidResource 70 | * 71 | * @return array array non-string values 72 | */ 73 | public function notStringDataProvider() 74 | { 75 | return array( 76 | array(array()), 77 | array(true), 78 | array(123), 79 | array(123.456), 80 | array(null), 81 | array(new \stdClass), 82 | array(function () { 83 | }) 84 | ); 85 | } 86 | 87 | /** 88 | * Test loading resources supported by the JsonLoader 89 | * 90 | * @param mixed $invalidResource Invalid resource value 91 | * @dataProvider notStringDataProvider 92 | */ 93 | public function testSupportsWithInvalidResource($invalidResource) 94 | { 95 | $this->assertFalse($this->jsonLoader->supports($invalidResource)); 96 | } 97 | 98 | /** 99 | * Test loading a JSON string 100 | */ 101 | public function testSupportsWithJsonString() 102 | { 103 | $this->jsonLoader->expects($this->once()) 104 | ->method('isFile') 105 | ->willReturn(false); 106 | 107 | $json = Fixtures::getSampleJsonString(); 108 | 109 | $this->assertTrue($this->jsonLoader->supports($json)); 110 | } 111 | 112 | /** 113 | * Test loading a JSON file 114 | * Note that this function tests isJson with a valid Json string 115 | */ 116 | public function testSupportsWithJsonFile() 117 | { 118 | $this->jsonLoader->expects($this->once()) 119 | ->method('isFile') 120 | ->willReturn(true); 121 | 122 | $this->jsonLoader->expects($this->once()) 123 | ->method('validateExtension') 124 | ->willReturn(true); 125 | 126 | $jsonFile = Fixtures::getSampleJsonFile(); 127 | 128 | $this->assertTrue($this->jsonLoader->supports($jsonFile)); 129 | } 130 | 131 | /** 132 | * Test isJson method with invalid JSON string. 133 | * Valid scenario is tested by the method above 134 | */ 135 | public function testSupportsWithNonJsonString() 136 | { 137 | $this->jsonLoader->expects($this->once()) 138 | ->method('isFile') 139 | ->willReturn(false); 140 | 141 | $someString = Fixtures::getSampleString(); 142 | 143 | $this->assertFalse($this->jsonLoader->supports($someString)); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /tests/Config/Loader/FileLoader/PhpArrayTest.php: -------------------------------------------------------------------------------- 1 | loader = new ArrayLoader(new FileLocator()); 28 | } 29 | 30 | protected function tearDown(): void 31 | { 32 | $this->loader = null; 33 | } 34 | 35 | public function testSupportsPhpFile() 36 | { 37 | $this->assertTrue($this->loader->supports(__DIR__.'/../../../Fixtures/fixture_config.php')); 38 | } 39 | 40 | public function testDoesNotSupportNonPhpFiles() 41 | { 42 | $this->assertFalse($this->loader->supports('foo')); 43 | $this->assertFalse($this->loader->supports(__DIR__.'/../../../Fixtures/fixture_config.json')); 44 | } 45 | 46 | public function testThrowsExceptionWhenLoadingFileIfDoesNotReturnValidPhpArray() 47 | { 48 | $this->expectException(\InvalidArgumentException::class); 49 | 50 | $this->loader->load(__DIR__.'/../../../Fixtures/fixture_invalid_config.php'); 51 | } 52 | 53 | public function testLoadsPhpArrayConfigFromFile() 54 | { 55 | $this->assertSame( 56 | include __DIR__.'/../../../Fixtures/fixture_config.php', 57 | $this->loader->load(__DIR__.'/../../../Fixtures/fixture_config.php') 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/Config/Loader/FileLoader/YamlTest.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Tests\Config\Loader\FileLoader; 12 | 13 | use PHPUnit\Framework\MockObject\MockBuilder; 14 | use PHPUnit\Framework\TestCase; 15 | use Symfony\Component\Yaml\Yaml as YamlParser; 16 | 17 | use Cascade\Tests\Fixtures; 18 | 19 | /** 20 | * Class YamlTest 21 | * 22 | * @author Raphael Antonmattei 23 | */ 24 | class YamlTest extends TestCase 25 | { 26 | /** 27 | * Yaml loader mock builder 28 | * @var MockBuilder 29 | */ 30 | protected $yamlLoader = null; 31 | 32 | public function setUp(): void 33 | { 34 | parent::setUp(); 35 | 36 | $fileLocatorMock = $this->getMockBuilder('Symfony\Component\Config\FileLocatorInterface') 37 | ->getMock(); 38 | 39 | $this->yamlLoader = $this->getMockBuilder( 40 | 'Cascade\Config\Loader\FileLoader\Yaml' 41 | ) 42 | ->setConstructorArgs(array($fileLocatorMock)) 43 | ->setMethods(array('readFrom', 'isFile', 'validateExtension')) 44 | ->getMock(); 45 | } 46 | 47 | public function tearDown(): void 48 | { 49 | $this->yamlLoader = null; 50 | parent::tearDown(); 51 | } 52 | 53 | /** 54 | * Test loading a Yaml string 55 | */ 56 | public function testLoad() 57 | { 58 | $yaml = Fixtures::getSampleYamlString(); 59 | 60 | $this->yamlLoader->expects($this->once()) 61 | ->method('readFrom') 62 | ->willReturn($yaml); 63 | 64 | $this->assertEquals( 65 | YamlParser::parse($yaml), 66 | $this->yamlLoader->load($yaml) 67 | ); 68 | } 69 | 70 | /** 71 | * Data provider for testSupportsWithInvalidResource 72 | * @return array array non-string values 73 | */ 74 | public function notStringDataProvider() 75 | { 76 | return array( 77 | array(array()), 78 | array(true), 79 | array(123), 80 | array(123.456), 81 | array(null), 82 | array(new \stdClass), 83 | // array(function () { 84 | // }) 85 | // cannot test Closure type because of PhpUnit 86 | // @see https://github.com/sebastianbergmann/phpunit/issues/451 87 | ); 88 | } 89 | 90 | /** 91 | * Test loading resources supported by the YamlLoader 92 | * 93 | * @param mixed $invalidResource Invalid resource value 94 | * @dataProvider notStringDataProvider 95 | */ 96 | public function testSupportsWithInvalidResource($invalidResource) 97 | { 98 | $this->assertFalse($this->yamlLoader->supports($invalidResource)); 99 | } 100 | 101 | /** 102 | * Test loading a Yaml string 103 | */ 104 | public function testSupportsWithYamlString() 105 | { 106 | $this->yamlLoader->expects($this->once()) 107 | ->method('isFile') 108 | ->willReturn(false); 109 | 110 | $yaml = Fixtures::getSampleYamlString(); 111 | 112 | $this->assertTrue($this->yamlLoader->supports($yaml)); 113 | } 114 | 115 | /** 116 | * Test loading a Yaml file 117 | */ 118 | public function testSupportsWithYamlFile() 119 | { 120 | $this->yamlLoader->expects($this->once()) 121 | ->method('isFile') 122 | ->willReturn(true); 123 | 124 | $this->yamlLoader->expects($this->once()) 125 | ->method('validateExtension') 126 | ->willReturn(true); 127 | 128 | $yamlFile = Fixtures::getSampleYamlFile(); 129 | 130 | $this->assertTrue($this->yamlLoader->supports($yamlFile)); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /tests/Config/Loader/PhpArrayTest.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Tests\Config\Loader; 12 | 13 | use Cascade\Config\Loader\PhpArray as ArrayLoader; 14 | use Cascade\Tests\Fixtures; 15 | use PHPUnit\Framework\TestCase; 16 | 17 | /** 18 | * Class PhpArrayTest 19 | * 20 | * @author Raphael Antonmattei 21 | */ 22 | class PhpArrayTest extends TestCase 23 | { 24 | /** 25 | * Array loader object 26 | * @var ArrayLoader 27 | */ 28 | protected $arrayLoader = null; 29 | 30 | public function setUp(): void 31 | { 32 | parent::setUp(); 33 | 34 | $this->arrayLoader = new ArrayLoader(); 35 | } 36 | 37 | public function tearDown(): void 38 | { 39 | $this->arrayLoader = null; 40 | parent::tearDown(); 41 | } 42 | 43 | /** 44 | * Test loading a Php array 45 | */ 46 | public function testLoad() 47 | { 48 | $array = Fixtures::getSamplePhpArray(); 49 | $this->assertSame($array, $this->arrayLoader->load($array)); 50 | } 51 | 52 | /** 53 | * Data provider for testSupportsWithInvalidResource 54 | * 55 | * @return array array of non-array values 56 | */ 57 | public function notStringDataProvider() 58 | { 59 | return array( 60 | array('Some string'), 61 | array(true), 62 | array(123), 63 | array(123.456), 64 | array(null), 65 | array(new \stdClass), 66 | array(function () { 67 | }) 68 | ); 69 | } 70 | 71 | /** 72 | * Test loading resources supported by the YamlLoader 73 | * 74 | * @param mixed $invalidResource Invalid resource value 75 | * @dataProvider notStringDataProvider 76 | */ 77 | public function testSupportsWithInvalidResource($invalidResource) 78 | { 79 | $this->assertFalse($this->arrayLoader->supports($invalidResource)); 80 | } 81 | 82 | /** 83 | * Test supports with a valid array 84 | */ 85 | public function testSupports() 86 | { 87 | $array = Fixtures::getSamplePhpArray(); 88 | $this->assertTrue($this->arrayLoader->supports($array)); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /tests/ConfigTest.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Tests; 12 | 13 | use Monolog\Registry; 14 | 15 | use Cascade\Config; 16 | use Cascade\Tests\Fixtures; 17 | use PHPUnit\Framework\TestCase; 18 | 19 | /** 20 | * Class ConfigTest 21 | * 22 | * @author Raphael Antonmattei 23 | */ 24 | class ConfigTest extends TestCase 25 | { 26 | /** 27 | * Testing contructor and load functions 28 | */ 29 | public function testLoad() 30 | { 31 | $mock = $this->getMockBuilder('Cascade\Config\ConfigLoader') 32 | ->disableOriginalConstructor() 33 | ->setMethods(array('load')) 34 | ->getMock(); 35 | 36 | $array = Fixtures::getSamplePhpArray(); 37 | 38 | $mock->expects($this->once()) 39 | ->method('load') 40 | ->willReturn($array); 41 | 42 | $config = new Config(array(''), $mock); 43 | $config->load(); 44 | } 45 | 46 | public function testConfigure() 47 | { 48 | $options = Fixtures::getPhpArrayConfig(); 49 | 50 | // Mocking the ConfigLoader with the load method 51 | $configLoader = $this->getMockBuilder('Cascade\Config\ConfigLoader') 52 | ->disableOriginalConstructor() 53 | ->setMethods(array('load')) 54 | ->getMock(); 55 | 56 | $configLoader->method('load')->willReturn($options); 57 | 58 | // Mocking the config object and set expectations for the configure methods 59 | $config = $this->getMockBuilder('Cascade\Config') 60 | ->setConstructorArgs(array($options, $configLoader)) 61 | ->setMethods(array( 62 | 'configureFormatters', 63 | 'configureProcessors', 64 | 'configureHandlers', 65 | 'configureLoggers' 66 | )) 67 | ->getMock(); 68 | 69 | $config->expects($this->once())->method('configureFormatters'); 70 | $config->expects($this->once())->method('configureProcessors'); 71 | $config->expects($this->once())->method('configureHandlers'); 72 | $config->expects($this->once())->method('configureLoggers'); 73 | 74 | $config->load(); 75 | $config->configure(); 76 | } 77 | 78 | /** 79 | * Test configure throwing an exception due to missing 'loggers' key 80 | */ 81 | public function testConfigureWithNoLoggers() 82 | { 83 | $this->expectException(\RuntimeException::class); 84 | 85 | $options = array(); 86 | 87 | // Mocking the ConfigLoader with the load method 88 | $configLoader = $this->getMockBuilder('Cascade\Config\ConfigLoader') 89 | ->disableOriginalConstructor() 90 | ->setMethods(array('load')) 91 | ->getMock(); 92 | 93 | $configLoader->method('load')->willReturn($options); 94 | 95 | // Mocking the config object 96 | $config = $this->getMockBuilder('Cascade\Config') 97 | ->setConstructorArgs(array($options, $configLoader)) 98 | ->setMethods(null) 99 | ->getMock(); 100 | 101 | $config->load(); 102 | 103 | // This should trigger an exception because there is no 'loggers' key in 104 | // the options passed in 105 | $config->configure(); 106 | } 107 | 108 | public function testLoggersConfigured() 109 | { 110 | $options = Fixtures::getPhpArrayConfig(); 111 | 112 | // Mocking the ConfigLoader with the load method 113 | $configLoader = $this->getMockBuilder('Cascade\Config\ConfigLoader') 114 | ->disableOriginalConstructor() 115 | ->setMethods(array('load')) 116 | ->getMock(); 117 | 118 | $configLoader->method('load')->willReturn($options); 119 | 120 | $config = new Config($options, $configLoader); 121 | 122 | $config->load(); 123 | $config->configure(); 124 | 125 | $this->assertTrue(Registry::hasLogger('my_logger')); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /tests/Fixtures.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Tests; 12 | 13 | class Fixtures 14 | { 15 | /** 16 | * Return the fixture directory 17 | * 18 | * @return string Fixture directory 19 | */ 20 | public static function fixtureDir() 21 | { 22 | return realpath(__DIR__.'/Fixtures'); 23 | } 24 | 25 | /** 26 | * Return a path to a non existing file 27 | * 28 | * @return string Wrong file path 29 | */ 30 | public static function getInvalidFile() 31 | { 32 | return 'some/non/existing/file.txt'; 33 | } 34 | 35 | /** 36 | * Return the fixture Yaml config file 37 | * 38 | * @return string Path to yaml config file 39 | */ 40 | public static function getYamlConfigFile() 41 | { 42 | return self::fixtureDir().'/fixture_config.yml'; 43 | } 44 | 45 | /** 46 | * Return a config as Yaml 47 | * 48 | * @return string Yaml config 49 | */ 50 | public static function getYamlConfig() 51 | { 52 | return file_get_contents(self::fixtureDir().'/fixture_config.yml'); 53 | } 54 | 55 | /** 56 | * Return the fixture sample Yaml file 57 | * 58 | * @return string Path to a sample yaml file 59 | */ 60 | public static function getSampleYamlFile() 61 | { 62 | return self::fixtureDir().'/fixture_sample.yml'; 63 | } 64 | 65 | /** 66 | * Return the fixture sample Yaml string 67 | * 68 | * @return string Sample yaml string 69 | */ 70 | public static function getSampleYamlString() 71 | { 72 | return trim( 73 | '---'."\n". 74 | 'greeting: "hello"'."\n". 75 | 'to: "you"'."\n" 76 | ); 77 | } 78 | 79 | /** 80 | * Return the fixture JSON config file 81 | * 82 | * @return string Path to JSON config file 83 | */ 84 | public static function getJsonConfigFile() 85 | { 86 | return self::fixtureDir().'/fixture_config.json'; 87 | } 88 | 89 | /** 90 | * Return a config as JSON 91 | * 92 | * @return string JSON config 93 | */ 94 | public static function getJsonConfig() 95 | { 96 | return file_get_contents(self::fixtureDir().'/fixture_config.json'); 97 | } 98 | 99 | /** 100 | * Return the fixture sample JSON file 101 | * 102 | * @return string Path to a sample JSON file 103 | */ 104 | public static function getSampleJsonFile() 105 | { 106 | return self::fixtureDir().'/fixture_sample.json'; 107 | } 108 | 109 | /** 110 | * Return the fixture sample JSON string 111 | * 112 | * @return string Sample JSON string 113 | */ 114 | public static function getSampleJsonString() 115 | { 116 | return trim( 117 | '{'."\n". 118 | ' "greeting": "hello",'."\n". 119 | ' "to": "you"'."\n". 120 | '}'."\n" 121 | ); 122 | } 123 | 124 | /** 125 | * Return a sample string 126 | * 127 | * @return string Sample string 128 | */ 129 | public static function getSampleString() 130 | { 131 | return " some string with new \n\n lines and white spaces \n\n"; 132 | } 133 | 134 | /** 135 | * Return the fixture PHP array config file 136 | * 137 | * @return string Path to PHP array config file 138 | */ 139 | public static function getPhpArrayConfigFile() 140 | { 141 | return self::fixtureDir().'/fixture_config.php'; 142 | } 143 | 144 | /** 145 | * Return a config array 146 | * 147 | * @return array Config array 148 | */ 149 | public static function getPhpArrayConfig() 150 | { 151 | return require self::fixtureDir().'/fixture_config.php'; 152 | } 153 | 154 | /** 155 | * Return a sample array 156 | * 157 | * @return array Sample array 158 | */ 159 | public static function getSamplePhpArray() 160 | { 161 | return array( 162 | 'greeting' => 'hello', 163 | 'to' => 'you' 164 | ); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /tests/Fixtures/DependentClass.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Tests\Fixtures; 12 | 13 | /** 14 | * Class DependentClass 15 | * 16 | * @author Raphael Antonmattei 17 | * @author Dom Morgan 18 | */ 19 | class DependentClass 20 | { 21 | /** 22 | * An object dependency 23 | * @var SampleClass 24 | */ 25 | private $dependency; 26 | 27 | /** 28 | * Constructor 29 | * 30 | * @param SampleClass $dependency Some sample object 31 | */ 32 | public function __construct(SampleClass $dependency) 33 | { 34 | $this->setDependency($dependency); 35 | } 36 | 37 | /** 38 | * Set the object dependency 39 | * 40 | * @param SampleClass $dependency Some sample object 41 | */ 42 | public function setDependency(SampleClass $dependency) 43 | { 44 | $this->dependency = $dependency; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/Fixtures/SampleClass.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | namespace Cascade\Tests\Fixtures; 12 | 13 | /** 14 | * Class SampleClass 15 | * 16 | * @author Raphael Antonmattei 17 | */ 18 | class SampleClass 19 | { 20 | /** 21 | * Some mandatory member 22 | * @var mixed 23 | */ 24 | private $mandatory; 25 | 26 | /** 27 | * Optional member A 28 | * @var mixed 29 | */ 30 | private $optionalA; 31 | 32 | /** 33 | * Optional member A 34 | * @var mixed 35 | */ 36 | public $optionalB; 37 | 38 | /** 39 | * Optional member X 40 | * @var mixed 41 | */ 42 | private $optionalX; 43 | 44 | /** 45 | * Optional member Y 46 | * @var mixed 47 | */ 48 | public $optionalY; 49 | 50 | /** 51 | * Hello member 52 | * @var mixed 53 | */ 54 | private $hello; 55 | 56 | /** 57 | * There member 58 | * @var mixed 59 | */ 60 | private $there; 61 | 62 | /** 63 | * Constructor 64 | * 65 | * @param mixed $mandatory Some mandatory param 66 | * @param string $optionalA Some optional param 67 | * @param string $optionalB Some other optional param 68 | * @param string $optional_snake Some optional snake param 69 | */ 70 | public function __construct( 71 | $mandatory, 72 | $optionalA = 'AAA', 73 | $optionalB = 'BBB', 74 | $optional_snake = 'snake' 75 | ) { 76 | $this->setMandatory($mandatory); 77 | } 78 | 79 | /** 80 | * Set the mandatory property 81 | * 82 | * @param mixed $mandatory Some value 83 | */ 84 | public function setMandatory($mandatory) 85 | { 86 | $this->mandatory = $mandatory; 87 | } 88 | 89 | /** 90 | * Function that sets the optionalA member 91 | * 92 | * @param mixed $value Some value 93 | */ 94 | public function optionalA($value) 95 | { 96 | $this->optionalA = $value; 97 | } 98 | 99 | /** 100 | * Function that sets the optionalX member 101 | * 102 | * @param mixed $value Some value 103 | */ 104 | public function optionalX($value) 105 | { 106 | $this->optionalX = $value; 107 | } 108 | 109 | /** 110 | * Function that sets the hello member 111 | * 112 | * @param mixed $value Some value 113 | */ 114 | public function setHello($value) 115 | { 116 | $this->hello = $value; 117 | } 118 | 119 | /** 120 | * Function that sets the there member 121 | * 122 | * @param mixed $value Some value 123 | */ 124 | public function setThere($value) 125 | { 126 | $this->there = $value; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /tests/Fixtures/fixture_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "disable_existing_loggers": false, 4 | "formatters": { 5 | "simple": { 6 | "class": "Monolog\\Formatter\\LineFormatter", 7 | "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 8 | } 9 | }, 10 | "handlers": { 11 | "console": { 12 | "class": "Monolog\\Handler\\StreamHandler", 13 | "level": "DEBUG", 14 | "formatter": "simple", 15 | "stream": "php://stdout" 16 | }, 17 | "info_file_handler": { 18 | "class": "Monolog\\Handler\\StreamHandler", 19 | "level": "INFO", 20 | "formatter": "simple", 21 | "stream": "./info.log" 22 | }, 23 | "error_file_handler": { 24 | "class": "Monolog\\Handler\\StreamHandler", 25 | "level": "ERROR", 26 | "formatter": "simple", 27 | "stream": "./error.log" 28 | } 29 | }, 30 | "processors": { 31 | "tag_processor": { 32 | "class": "Monolog\\Processor\\TagProcessor" 33 | } 34 | }, 35 | "loggers": { 36 | "my_logger": { 37 | "level": "ERROR", 38 | "handlers": [ 39 | "console" 40 | ], 41 | "propagate": "no" 42 | } 43 | }, 44 | "root": { 45 | "level": "INFO", 46 | "handlers": [ 47 | "console", 48 | "info_file_handler", 49 | "error_file_handler" 50 | ] 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/Fixtures/fixture_config.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) The Orchard 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | return array( 12 | 'version' => 1, 13 | 14 | 'formatters' => array( 15 | 'spaced' => array( 16 | 'format' => "%datetime% %channel%.%level_name% %message%\n", 17 | 'include_stacktraces' => true 18 | ), 19 | 'dashed' => array( 20 | 'format' => "%datetime%-%channel%.%level_name% - %message%\n" 21 | ), 22 | ), 23 | 'handlers' => array( 24 | 'console' => array( 25 | 'class' => 'Monolog\Handler\StreamHandler', 26 | 'level' => 'DEBUG', 27 | 'formatter' => 'spaced', 28 | 'stream' => 'php://stdout' 29 | ), 30 | 31 | 'info_file_handler' => array( 32 | 'class' => 'Monolog\Handler\StreamHandler', 33 | 'level' => 'INFO', 34 | 'formatter' => 'dashed', 35 | 'stream' => './demo_info.log' 36 | ), 37 | 38 | 'error_file_handler' => array( 39 | 'class' => 'Monolog\Handler\StreamHandler', 40 | 'level' => 'ERROR', 41 | 'stream' => './demo_error.log', 42 | 'formatter' => 'spaced' 43 | ), 44 | 45 | 'group_handler' => array( 46 | 'class' => 'Monolog\Handler\GroupHandler', 47 | 'handlers' => array( 48 | 'console', 49 | 'info_file_handler', 50 | ), 51 | ), 52 | 53 | 'fingers_crossed_handler' => array( 54 | 'class' => 'Monolog\Handler\FingersCrossedHandler', 55 | 'handler' => 'group_handler', 56 | ), 57 | ), 58 | 'processors' => array( 59 | 'tag_processor' => array( 60 | 'class' => 'Monolog\Processor\TagProcessor' 61 | ) 62 | ), 63 | 'loggers' => array( 64 | 'my_logger' => array( 65 | 'handlers' => array('console', 'info_file_handler') 66 | ) 67 | ) 68 | ); 69 | -------------------------------------------------------------------------------- /tests/Fixtures/fixture_config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 1 3 | 4 | disable_existing_loggers: false 5 | 6 | formatters: 7 | spaced: 8 | format: "%datetime% %channel%.%level_name% %message%\n" 9 | include_stacktraces: true 10 | dashed: 11 | format: "%datetime%-%channel%.%level_name% - %message%\n" 12 | 13 | processors: 14 | tag_processor: 15 | class: Monolog\Processor\TagProcessor 16 | 17 | handlers: 18 | console: 19 | class: Monolog\Handler\StreamHandler 20 | level: DEBUG 21 | formatter: spaced 22 | stream: php://stdout 23 | 24 | info_file_handler: 25 | class: Monolog\Handler\StreamHandler 26 | level: INFO 27 | formatter: dashed 28 | stream: ./demo_info.log 29 | 30 | error_file_handler: 31 | class: Monolog\Handler\StreamHandler 32 | level: ERROR 33 | stream: ./demo_error.log 34 | formatter: spaced 35 | 36 | loggers: 37 | my_logger: 38 | handlers: [console, info_file_handler] 39 | -------------------------------------------------------------------------------- /tests/Fixtures/fixture_invalid_config.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class UtilTest extends TestCase 14 | { 15 | public function testSnakeToCamelCase() 16 | { 17 | // non-strings 18 | $this->assertSame(null, Util::snakeToCamelCase(null)); 19 | $this->assertSame(null, Util::snakeToCamelCase(array())); 20 | $this->assertSame(null, Util::snakeToCamelCase(1)); 21 | 22 | // strings 23 | $this->assertSame('', Util::snakeToCamelCase('')); 24 | $this->assertSame('foo', Util::snakeToCamelCase('foo')); 25 | $this->assertSame('fooBar', Util::snakeToCamelCase('foo_bar')); 26 | $this->assertSame('fooBarBaz', Util::snakeToCamelCase('foo_bar_baz')); 27 | 28 | // weird strings 29 | $this->assertSame('_', Util::snakeToCamelCase('_')); 30 | $this->assertSame('__', Util::snakeToCamelCase('___')); 31 | $this->assertSame('_ _', Util::snakeToCamelCase('_ _')); 32 | $this->assertSame('x_', Util::snakeToCamelCase('X__')); 33 | $this->assertSame('_X', Util::snakeToCamelCase('__X')); 34 | } 35 | } 36 | --------------------------------------------------------------------------------