Add pear modules, mail and net_smtp via composer (#93)

Add pear modules, mail and net_smtp via composer, remove php 5.6 build due to phpunit 6
This commit is contained in:
Thilina Hasantha
2018-01-08 23:13:43 +01:00
committed by GitHub
parent 359e3f8382
commit e7792e7d79
2349 changed files with 117270 additions and 83170 deletions

View File

@@ -0,0 +1,15 @@
# This file is for unifying the coding style for different editors and IDEs
# editorconfig.org
root = true
[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[**.php]
indent_style = space
indent_size = 4

View File

@@ -0,0 +1,3 @@
.DS_Store
vendor
build

View File

@@ -0,0 +1,38 @@
language: php
branches:
# Only test the master branch and SemVer tags.
only:
- master
- /^[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+.*$/
matrix:
fast_finish: true
include:
- php: 7.2
env: 'SCENARIO=symfony4 HIGHEST_LOWEST="update"'
- php: 7.1
env: 'SCENARIO=symfony4'
- php: 7.0.11
env: 'HIGHEST_LOWEST="update"'
- php: 7.0.11
- php: 5.6
- php: 5.5
- php: 5.4
env: 'SCENARIO=symfony2 HIGHEST_LOWEST="update --prefer-lowest'
sudo: false
cache:
directories:
- vendor
- $HOME/.composer/cache
install:
- 'composer scenario "${SCENARIO}" "${HIGHEST_LOWEST-install}"'
script:
- composer test
after_success:
- travis_retry php vendor/bin/coveralls -v

View File

@@ -0,0 +1,30 @@
# Changelog
### 1.0.6 10/17/2017
* Add a 'Config::combine()' method for importing without overwriting.
* Factor out ArrayUtil as a reusable utility class.
### 1.0.4 10/16/2017
* BUGFIX: Go back to injecting boolean options only if their value is 'true'.
### 1.0.3 10/04/2017
* Add an EnvConfig utility class.
* BUGFIX: Fix bug in envKey calculation: it was missing its prefix.
* BUGFIX: Export must always return something.
* BUGFIX: Pass reference array through to Expander class.
### 1.0.2 09/16/2017
* BUGFIX: Allow global boolean options to have either `true` or `false` initial values.
### 1.0.1 07/28/2017
* Inject default values into InputOption objects when 'help' command executed.
### 1.0.0 06/28/2017
* Initial release

View File

@@ -0,0 +1,31 @@
# Contributing to Consolidation
Thank you for your interest in contributing to the Consolidation effort! Consolidation aims to provide reusable, loosely-coupled components useful for building command-line tools. Consolidation is built on top of Symfony Console, but aims to separate the tool from the implementation details of Symfony.
Here are some of the guidelines you should follow to make the most of your efforts:
## Code Style Guidelines
Consolidation adheres to the [PSR-2 Coding Style Guide](http://www.php-fig.org/psr/psr-2/) for PHP code.
## Pull Request Guidelines
Every pull request is run through:
- phpcs -n --standard=PSR2 src
- phpunit
- [Scrutinizer](https://scrutinizer-ci.com/g/consolidation/config/)
It is easy to run the unit tests and code sniffer locally; just run:
- composer cs
To run the code beautifier, which will fix many of the problems reported by phpcs:
- composer cbf
These two commands (`composer cs` and `composer cbf`) are defined in the `scripts` section of [composer.json](composer.json).
After submitting a pull request, please examine the Scrutinizer report. It is not required to fix all Scrutinizer issues; you may ignore recommendations that you disagree with. The spacing patches produced by Scrutinizer do not conform to PSR2 standards, and therefore should never be applied. DocBlock patches may be applied at your discression. Things that Scrutinizer identifies as a bug nearly always need to be addressed.
Pull requests must pass phpcs and phpunit in order to be merged; ideally, new functionality will also include new unit tests.

View File

@@ -0,0 +1,8 @@
Copyright (c) 2017 Consolidation Developers
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,210 @@
# Consolidation\Config
Manage configuration for a commandline tool.
[![Travis CI](https://travis-ci.org/consolidation/config.svg?branch=master)](https://travis-ci.org/consolidation/config)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/consolidation/config/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/consolidation/config/?branch=master)
[![Coverage Status](https://coveralls.io/repos/github/consolidation/config/badge.svg?branch=master)](https://coveralls.io/github/consolidation/config?branch=master)
[![License](https://poser.pugx.org/consolidation/config/license)](https://packagist.org/packages/consolidation/config)
This component is designed to provide the components needed to manage configuration options from different sources, including:
- Commandline options
- Configuration files
- Alias files (special configuration files that identify a specific target site)
- Default values (provided by command)
Symfony Console is used to provide the framework for the commandline tool, and the Symfony Configuration component is used to load and merge configuration files. This project provides the glue that binds the components together in an easy-to-use package.
If your goal is to be able to quickly write configurable commandline tools, you might want to consider using [Robo as a Framework](https://robo.li/framework), as the work for setting up this component is already done in that project. Consolidation/Config may be used with any Symfony Console application, though.
## Component Status
In use in Robo (1.x), Terminus (1.x) and Drush (9.x).
## Motivation
Provide a simple Config class that can be injected where needed to provide configuration values in non-command classes, and make configuration settings a no-op for command classes by automatically initializing the Input object from configuration as needed.
## Configuration File Usage
Configuration files are simple hierarchical yaml files.
### Providing Command Options
Command options are defined by creating an entry for each command name under the `command:` section of the configuration file. The options for the command should be defined within an `options:` section. For example, to set a configuration value `red` for the option `--color` in the `example` command:
```
command:
example:
options:
color: red
```
If a command name contains a `:`, then each section of the command name defines another level of heirarchy in the command option configuration. For example, to set a configuration value `George` for the option `--name` of the command `my:foo`:
```
command:
my:
foo:
options:
name: George
```
Furthermore, every level of the command name heirarchy may contain options. For example, to define a configuration value for the option `--dir` for any command that begins with `my:`:
```
command:
my:
options:
dir: '/base/path'
foo:
options:
name: George
bar:
options:
priority: high
```
### Providing Global Options
If your Symfony Console application defines global options, like so (from a method in an extension of the Application class):
```
$this->getDefinition()
->addOption(
new InputOption('--simulate', null, InputOption::VALUE_NONE, 'Run in simulated mode (show what would have happened).')
);
```
Default values for global options can then be declared in the global options section:
```
options:
simulate: false
```
If this is done, then global option values set on the command line will be used to alter the value of the configuration item at runtime. For example, `$config->get('options.simulate')` will return `false` when the `--simulate` global option is not used, and will return `true` when it is.
See the section "Set Up Command Option Configuration Injection", below, for instructions on how to enable this setup.
### Configuration Value Substitution
It is possible to define values in a configuration file that will be substituted in wherever used. For example:
```
common:
path: '/shared/path'
command:
my:
options:
dir: '${common.path}'
foo:
options:
name: George
```
[grasmash/yaml-expander](https://github.com/grasmash/expander) is used to provide this capability.
## API Usage
The easiest way to utilize the capabilities of this project is to use [Robo as a framework](https://robo.li/framework) to create your commandline tools. Using Robo is optional, though, as this project will work with any Symfony Console application.
### Load Configuration Files with Provided Loader
Consolidation/config includes a built-in yaml loader / processor. To use it directly, use a YamlConfigLoader to load each of your configuration files, and a ConfigProcessor to merge them together. Then, export the result from the configuration processor, and import it into a Config object.
```
use Consolidation\Config\Config;
use Consolidation\Config\YamlConfigLoader;
use Consolidation\Config\ConfigProcessor;
$config = new Config();
$loader = new YamlConfigLoader();
$processor = new ConfigProcessor();
$processor->extend($loader->load('defaults.yml'));
$processor->extend($loader->load('myconf.yml'));
$config->import($processor->export());
```
### Set Up Command Option Configuration Injection
The command option configuration feature described above in the section `Providing Command Options` is provided via a configuration injection class. All that you need to do to use this feature as attach this object to your Symfony Console application's event dispatcher:
```
$application = new Symfony\Component\Console\Application($name, $version);
$configInjector = new \Consolidation\Config\Inject\ConfigForCommand($config);
$configInjector->setApplication($application);
$eventDispatcher = new \Symfony\Component\EventDispatcher\EventDispatcher();
$eventDispatcher->addSubscriber($configInjector);
$application->setDispatcher($eventDispatcher);
```
### Get Configuration Values
If you have a configuration file that looks like this:
```
a:
b:
c: foo
```
Then you can fetch the value of the configuration option `c` via:
```
$value = $config->get('a.b.c');
```
[dflydev/dot-access-data](https://github.com/dflydev/dot-access-data) is leveraged to provide this capability.
### Configuration Overlays
Optionally, you may use the ConfigOverlay class to combine multiple configuration objects implamenting ConfigInterface into a single, prioritized configuration object. It is not necessary to use a configuration overlay; if your only goal is to merge configuration from multiple files, you may follow the example above to extend a processor with multiple configuration files, and then import the result into a single configuration object. This will cause newer configuration items to overwrite any existing values stored under the same key.
A configuration overlay can achieve the same end result without overwriting any config values. The advantage of doing this is that different configuration overlays could be used to create separate "views" on different collections of configuration. A configuration overlay is also useful if you wish to temporarily override some configuration values, and then put things back the way they were by removing the overlay.
```
use Consolidation\Config\Config;
use Consolidation\Config\YamlConfigLoader;
use Consolidation\Config\ConfigProcessor;
use Consolidation\Config\Util\ConfigOverlay;
$config1 = new Config();
$config2 = new Config();
$loader = new YamlConfigLoader();
$processor = new ConfigProcessor();
$processor->extend($loader->load('c1.yml'));
$config1->import($processor->export());
$processor = new ConfigProcessor();
$processor->extend($loader->load('c2.yml'));
$config2->import($processor->export());
$configOverlay = (new ConfigOverlay())
->addContext('one', $config1)
->addContext('two', $config2);
$value = $configOverlay->get('key');
$configOverlay->removeContext('two');
$value = $configOverlay->get('key');
```
The first call to `$configOverlay->get('key')`, above, will return the value from `key` in `$config2`, if it exists, or from `$config1` otherwise. The second call to the same function, after `$config2` is removed, will only consider configuration values stored in `$config1`.
## External Examples
### Load Configuration Files with Symfony/Config
The [Symfony Config](http://symfony.com/doc/current/components/config.html) component provides the capability to locate configuration file, load them from either YAML or XML sources, and validate that they match a certain defined schema. Classes to find configuration files are also available.
If these features are needed, the results from `Symfony\Component\Config\Definition\Processor::processConfiguration()` may be provided directly to the `Consolidation\Config\Config::import()` method.
### Use Configuration to Call Setter Methods
[Robo](https://robo.li) provides a facility for configuration files to [define default values for task setter methods](http://robo.li/getting-started/#configuration-for-task-settings). This is done via the `ConfigForSetters::apply()` method.
```
$taskClass = static::configClassIdentifier($taskClass);
$configurationApplier = new \Consolidation\Config\Inject\ConfigForSetters($this->getConfig(), $taskClass, 'task.');
$configurationApplier->apply($task, 'settings');
```
The `configClassIdentifier` method converts `\`-separated class and namespace names into `.`-separated identifiers; it is provided by ConfigAwareTrait:
```
protected static function configClassIdentifier($classname)
{
$configIdentifier = strtr($classname, '\\', '.');
$configIdentifier = preg_replace('#^(.*\.Task\.|\.)#', '', $configIdentifier);
return $configIdentifier;
}
```
A similar pattern may be used in other applications that may wish to inject values into objects using their setter methods.
## Comparison to Existing Solutions
Drush has an existing procedural mechanism for loading configuration values from multiple files, and overlaying the results in priority order. Command-specific options from configuration files and site aliases may also be applied.

View File

@@ -0,0 +1,68 @@
{
"name": "consolidation/config",
"description": "Provide configuration services for a commandline tool.",
"license": "MIT",
"authors": [
{
"name": "Greg Anderson",
"email": "greg.1.anderson@greenknowe.org"
}
],
"autoload":{
"psr-4":{
"Consolidation\\Config\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Consolidation\\TestUtils\\": "tests/src"
}
},
"require": {
"php": ">=5.4.0",
"dflydev/dot-access-data": "^1.1.0",
"grasmash/expander": "^1"
},
"require-dev": {
"phpunit/phpunit": "^4",
"greg-1-anderson/composer-test-scenarios": "^1",
"symfony/console": "^2.5|^3|^4",
"symfony/yaml": "^2.8.11|^3|^4",
"satooshi/php-coveralls": "^1.0",
"squizlabs/php_codesniffer": "2.*"
},
"suggest": {
"symfony/yaml": "Required to use Consolidation\\Config\\Loader\\YamlConfigLoader"
},
"config": {
"optimize-autoloader": true,
"sort-packages": true,
"platform": {
"php": "5.6"
}
},
"scripts": {
"cs": "phpcs --standard=PSR2 -n src",
"cbf": "phpcbf --standard=PSR2 -n src",
"unit": "SHELL_INTERACTIVE=true phpunit --colors=always",
"lint": [
"find src -name '*.php' -print0 | xargs -0 -n1 php -l",
"find tests/src -name '*.php' -print0 | xargs -0 -n1 php -l"
],
"test": [
"@lint",
"@unit",
"@cs"
],
"scenario": "scenarios/install",
"post-update-cmd": [
"create-scenario symfony4 'symfony/console:^4.0'",
"create-scenario symfony2 'symfony/console:^2.8' --platform-php '5.4' --no-lockfile"
]
},
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
}
}

1967
lib/composer/vendor/consolidation/config/composer.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,19 @@
<phpunit bootstrap="vendor/autoload.php" colors="true">
<testsuites>
<testsuite name="config">
<directory prefix="" suffix="Test.php">tests</directory>
</testsuite>
</testsuites>
<logging>
<!--
<log type="coverage-html" target="build/logs/coverage" lowUpperBound="35"
highLowerBound="70"/>
-->
<log type="coverage-clover" target="build/logs/clover.xml"/>
</logging>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src</directory>
</whitelist>
</filter>
</phpunit>

View File

@@ -0,0 +1,28 @@
#!/bin/bash
SCENARIO=$1
ACTION=${2-install}
dir=scenarios/${SCENARIO}
if [ -z "$SCENARIO" ] ; then
SCENARIO=default
dir=.
fi
if [ ! -d "$dir" ] ; then
echo "Requested scenario '${SCENARIO}' does not exist."
exit 1
fi
echo
echo "::"
echo ":: Switch to ${SCENARIO} scenario"
echo "::"
echo
set -ex
composer -n validate --working-dir=$dir --no-check-all --ansi
composer -n --working-dir=$dir ${ACTION} --prefer-dist --no-scripts
composer -n --working-dir=$dir info

View File

@@ -0,0 +1,2 @@
vendor
composer.lock

View File

@@ -0,0 +1,70 @@
{
"name": "consolidation/config",
"description": "Provide configuration services for a commandline tool.",
"license": "MIT",
"authors": [
{
"name": "Greg Anderson",
"email": "greg.1.anderson@greenknowe.org"
}
],
"autoload":{
"psr-4":{
"Consolidation\\Config\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Consolidation\\TestUtils\\": "tests/src"
}
},
"require": {
"php": ">=5.4.0",
"dflydev/dot-access-data": "^1.1.0",
"grasmash/expander": "^1"
},
"require-dev": {
"greg-1-anderson/composer-test-scenarios": "^1",
"phpunit/phpunit": "^4",
"satooshi/php-coveralls": "^1.0",
"squizlabs/php_codesniffer": "2.*",
"symfony/console": "^2.8",
"symfony/yaml": "^2.8.11|^3|^4"
},
"suggest": {
"symfony/yaml": "Required to use Consolidation\\Config\\Loader\\YamlConfigLoader"
},
"config": {
"optimize-autoloader": true,
"sort-packages": true,
"platform": {
"php": "5.4"
},
"vendor-dir": "../../vendor"
},
"scripts": {
"cs": "phpcs --standard=PSR2 -n src",
"cbf": "phpcbf --standard=PSR2 -n src",
"unit": "SHELL_INTERACTIVE=true phpunit --colors=always",
"lint": [
"find src -name '*.php' -print0 | xargs -0 -n1 php -l",
"find tests/src -name '*.php' -print0 | xargs -0 -n1 php -l"
],
"test": [
"@lint",
"@unit",
"@cs"
],
"scenario": "scenarios/install",
"post-update-cmd": [
"create-scenario symfony4 'symfony/console:^4.0'",
"create-scenario symfony2 'symfony/console:^2.8' --platform-php '5.4' --no-lockfile"
]
},
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"minimum-stability": "stable"
}

View File

@@ -0,0 +1 @@
../../src

View File

@@ -0,0 +1 @@
../../tests

View File

@@ -0,0 +1 @@
vendor

View File

@@ -0,0 +1,70 @@
{
"name": "consolidation/config",
"description": "Provide configuration services for a commandline tool.",
"license": "MIT",
"authors": [
{
"name": "Greg Anderson",
"email": "greg.1.anderson@greenknowe.org"
}
],
"autoload":{
"psr-4":{
"Consolidation\\Config\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Consolidation\\TestUtils\\": "tests/src"
}
},
"require": {
"php": ">=5.4.0",
"dflydev/dot-access-data": "^1.1.0",
"grasmash/expander": "^1"
},
"require-dev": {
"greg-1-anderson/composer-test-scenarios": "^1",
"phpunit/phpunit": "^4",
"satooshi/php-coveralls": "^1.0",
"squizlabs/php_codesniffer": "2.*",
"symfony/console": "^4.0",
"symfony/yaml": "^2.8.11|^3|^4"
},
"suggest": {
"symfony/yaml": "Required to use Consolidation\\Config\\Loader\\YamlConfigLoader"
},
"config": {
"optimize-autoloader": true,
"sort-packages": true,
"platform": {
},
"vendor-dir": "../../vendor"
},
"scripts": {
"cs": "phpcs --standard=PSR2 -n src",
"cbf": "phpcbf --standard=PSR2 -n src",
"unit": "SHELL_INTERACTIVE=true phpunit --colors=always",
"lint": [
"find src -name '*.php' -print0 | xargs -0 -n1 php -l",
"find tests/src -name '*.php' -print0 | xargs -0 -n1 php -l"
],
"test": [
"@lint",
"@unit",
"@cs"
],
"scenario": "scenarios/install",
"post-update-cmd": [
"create-scenario symfony4 'symfony/console:^4.0'",
"create-scenario symfony2 'symfony/console:^2.8' --platform-php '5.4' --no-lockfile"
]
},
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"minimum-stability": "stable"
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
../../src

View File

@@ -0,0 +1 @@
../../tests

View File

@@ -0,0 +1,157 @@
<?php
namespace Consolidation\Config;
use Dflydev\DotAccessData\Data;
class Config implements ConfigInterface
{
/**
* @var Data
*/
protected $config;
/**
* TODO: make this private in 2.0 to prevent being saved as an array
* Making private now breaks backward compatibility
*
* @var Data
*/
protected $defaults;
/**
* Create a new configuration object, and initialize it with
* the provided nested array containing configuration data.
*
* @param array $data - Config data to store
*/
public function __construct(array $data = null)
{
$this->config = new Data($data);
$this->setDefaults(new Data());
}
/**
* {@inheritdoc}
*/
public function has($key)
{
return ($this->config->has($key));
}
/**
* {@inheritdoc}
*/
public function get($key, $defaultFallback = null)
{
if ($this->has($key)) {
return $this->config->get($key);
}
return $this->getDefault($key, $defaultFallback);
}
/**
* {@inheritdoc}
*/
public function set($key, $value)
{
$this->config->set($key, $value);
return $this;
}
/**
* {@inheritdoc}
*/
public function import($data)
{
return $this->replace($data);
}
/**
* {@inheritdoc}
*/
public function replace($data)
{
$this->config = new Data($data);
return $this;
}
/**
* {@inheritdoc}
*/
public function combine($data)
{
if (!empty($data)) {
$this->config->import($data, true);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function export()
{
return $this->config->export();
}
/**
* {@inheritdoc}
*/
public function hasDefault($key)
{
return $this->getDefaults()->has($key);
}
/**
* {@inheritdoc}
*/
public function getDefault($key, $defaultFallback = null)
{
return $this->hasDefault($key) ? $this->getDefaults()->get($key) : $defaultFallback;
}
/**
* {@inheritdoc}
*/
public function setDefault($key, $value)
{
$this->getDefaults()->set($key, $value);
return $this;
}
/**
* Return the class $defaults property and ensure it's a Data object
* TODO: remove Data object validation in 2.0
*
* @return Data
*/
protected function getDefaults()
{
// Ensure $this->defaults is a Data object (not an array)
if (!$this->defaults instanceof Data) {
$this->setDefaults($this->defaults);
}
return $this->defaults;
}
/**
* Sets the $defaults class parameter
* TODO: remove support for array in 2.0 as this would currently break backward compatibility
*
* @param Data|array $defaults
*
* @throws \Exception
*/
protected function setDefaults($defaults)
{
if (is_array($defaults)) {
$this->defaults = new Data($defaults);
} elseif ($defaults instanceof Data) {
$this->defaults = $defaults;
} else {
throw new \Exception("Unknown type provided for \$defaults");
}
}
}

View File

@@ -0,0 +1,105 @@
<?php
namespace Consolidation\Config;
interface ConfigInterface
{
/**
* Determine if a non-default config value exists.
*/
public function has($key);
/**
* Fetch a configuration value
*
* @param string $key Which config item to look up
* @param string|null $defaultFallback Fallback default value to use when
* configuration object has neither a value nor a default. Use is
* discouraged; use default context in ConfigOverlay, or provide defaults
* using a config processor.
*
* @return mixed
*/
public function get($key, $defaultFallback = null);
/**
* Set a config value
*
* @param string $key
* @param mixed $value
*
* @return $this
*/
public function set($key, $value);
/**
* Import configuration from the provided nexted array, replacing whatever
* was here previously. No processing is done on the provided data.
*
* @deprecated Use 'replace'. Dflydev\DotAccessData\Data::import() merges, which is confusing, since this method replaces.
*
* @param array $data
* @return Config
*/
public function import($data);
/**
* Load configuration from the provided nexted array, replacing whatever
* was here previously. No processing is done on the provided data.
*
* TODO: This will become a required method in version 2.0. Adding now
* would break clients that implement ConfigInterface.
*
* @param array $data
* @return Config
*/
// public function replace($data);
/**
* Import configuration from the provided nexted array, merging with whatever
* was here previously. No processing is done on the provided data.
* Any data provided to the combine() method will overwrite existing data
* with the same key.
*
* TODO: This will become a required method in version 2.0. Adding now
* would break clients that implement ConfigInterface.
*
* @param array $data
* @return Config
*/
// public function combine($data);
/**
* Export all configuration as a nested array.
*/
public function export();
/**
* Return the default value for a given configuration item.
*
* @param string $key
*
* @return mixed
*/
public function hasDefault($key);
/**
* Return the default value for a given configuration item.
*
* @param string $key
* @param mixed $defaultFallback
*
* @return mixed
*/
public function getDefault($key, $defaultFallback = null);
/**
* Set the default value for a configuration setting. This allows us to
* set defaults either before or after more specific configuration values
* are loaded. Keeping defaults separate from current settings also
* allows us to determine when a setting has been overridden.
*
* @param string $key
* @param string $value
*/
public function setDefault($key, $value);
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Consolidation\Config;
interface GlobalOptionDefaultValuesInterface
{
/**
* Return an associative array of option-key => default-value
*/
public function getGlobalOptionDefaultValues();
}

View File

@@ -0,0 +1,127 @@
<?php
namespace Consolidation\Config\Inject;
use Consolidation\Config\ConfigInterface;
use Consolidation\Config\Util\ConfigFallback;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\InputOption;
class ConfigForCommand implements EventSubscriberInterface
{
protected $config;
protected $application;
public function __construct(ConfigInterface $config)
{
$this->config = $config;
}
public function setApplication(Application $application)
{
$this->application = $application;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [ConsoleEvents::COMMAND => 'injectConfiguration'];
}
/**
* Before a Console command runs, inject configuration settings
* for this command into the default value of the options of
* this command.
*
* @param \Symfony\Component\Console\Event\ConsoleCommandEvent $event
*/
public function injectConfiguration(ConsoleCommandEvent $event)
{
$command = $event->getCommand();
$this->injectConfigurationForGlobalOptions($event->getInput());
$this->injectConfigurationForCommand($command, $event->getInput());
$targetOfHelpCommand = $this->getHelpCommandTarget($command, $event->getInput());
if ($targetOfHelpCommand) {
$this->injectConfigurationForCommand($targetOfHelpCommand, $event->getInput());
}
}
protected function injectConfigurationForGlobalOptions($input)
{
if (!$this->application) {
return;
}
$configGroup = new ConfigFallback($this->config, 'options');
$definition = $this->application->getDefinition();
$options = $definition->getOptions();
return $this->injectConfigGroupIntoOptions($configGroup, $options, $input);
}
protected function injectConfigurationForCommand($command, $input)
{
$commandName = $command->getName();
$commandName = str_replace(':', '.', $commandName);
$configGroup = new ConfigFallback($this->config, $commandName, 'command.', '.options.');
$definition = $command->getDefinition();
$options = $definition->getOptions();
return $this->injectConfigGroupIntoOptions($configGroup, $options, $input);
}
protected function injectConfigGroupIntoOptions($configGroup, $options, $input)
{
foreach ($options as $option => $inputOption) {
$key = str_replace('.', '-', $option);
$value = $configGroup->get($key);
if ($value !== null) {
if (is_bool($value) && ($value == true)) {
$input->setOption($key, $value);
} elseif ($inputOption->acceptValue()) {
$inputOption->setDefault($value);
}
}
}
}
protected function getHelpCommandTarget($command, $input)
{
if (($command->getName() != 'help') || (!isset($this->application))) {
return false;
}
$this->fixInputForSymfony2($command, $input);
// Symfony Console helpfully swaps 'command_name' and 'command'
// depending on whether the user entered `help foo` or `--help foo`.
// One of these is always `help`, and the other is the command we
// are actually interested in.
$nameOfCommandToDescribe = $input->getArgument('command_name');
if ($nameOfCommandToDescribe == 'help') {
$nameOfCommandToDescribe = $input->getArgument('command');
}
return $this->application->find($nameOfCommandToDescribe);
}
protected function fixInputForSymfony2($command, $input)
{
// Symfony 3.x prepares $input for us; Symfony 2.x, on the other
// hand, passes it in prior to binding with the command definition,
// so we have to go to a little extra work. It may be inadvisable
// to do these steps for commands other than 'help'.
if (!$input->hasArgument('command_name')) {
$command->ignoreValidationErrors();
$command->mergeApplicationDefinition();
$input->bind($command->getDefinition());
}
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Consolidation\Config\Inject;
use Consolidation\Config\Util\ConfigMerge;
/**
* Given an object that contains configuration methods, inject any
* configuration found in the configuration file.
*
* The proper use for this method is to call setter methods of the
* provided object. Using configuration to call methods that do work
* is an abuse of this mechanism.
*/
class ConfigForSetters
{
protected $config;
public function __construct($config, $group, $prefix = '', $postfix = '')
{
if (!empty($group) && empty($postfix)) {
$postfix = '.';
}
$this->config = new ConfigMerge($config, $group, $prefix, $postfix);
}
public function apply($object, $configurationKey)
{
$settings = $this->config->get($configurationKey);
foreach ($settings as $setterMethod => $args) {
$fn = [$object, $setterMethod];
if (is_callable($fn)) {
$result = call_user_func_array($fn, (array)$args);
// We require that $fn must only be used with setter methods.
// Setter methods are required to always return $this so that
// they may be chained. We will therefore throw an exception
// for any setter that returns something else.
if ($result != $object) {
$methodDescription = get_class($object) . "::$setterMethod";
$propertyDescription = $this->config->describe($configurationKey);
throw new \Exception("$methodDescription did not return '\$this' when processing $propertyDescription.");
}
}
}
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Consolidation\Config\Loader;
/**
* Load configuration files.
*/
abstract class ConfigLoader implements ConfigLoaderInterface
{
protected $config = [];
protected $source = '';
public function getSourceName()
{
return $this->source;
}
protected function setSourceName($source)
{
$this->source = $source;
return $this;
}
public function export()
{
return $this->config;
}
public function keys()
{
return array_keys($this->config);
}
abstract public function load($path);
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Consolidation\Config\Loader;
/**
* Load configuration files, and fill in any property values that
* need to be expanded.
*/
interface ConfigLoaderInterface
{
/**
* Convert loaded configuration into a simple php nested array.
*
* @return array
*/
public function export();
/**
* Return the top-level keys in the exported data.
*
* @return array
*/
public function keys();
/**
* Return a symbolic name for this configuration loader instance.
*/
public function getSourceName();
}

View File

@@ -0,0 +1,167 @@
<?php
namespace Consolidation\Config\Loader;
use Grasmash\Expander\Expander;
use Consolidation\Config\Util\ArrayUtil;
/**
* The config processor combines multiple configuration
* files together, and processes them as necessary.
*/
class ConfigProcessor
{
protected $processedConfig = [];
protected $unprocessedConfig = [];
protected $expander;
public function __construct($expander = null)
{
$this->expander = $expander ?: new Expander();
}
/**
* Extend the configuration to be processed with the
* configuration provided by the specified loader.
*
* @param ConfigLoaderInterface $loader
*/
public function extend(ConfigLoaderInterface $loader)
{
return $this->addFromSource($loader->export(), $loader->getSourceName());
}
/**
* Extend the configuration to be processed with
* the provided nested array.
*
* @param array $data
*/
public function add($data)
{
$this->unprocessedConfig[] = $data;
return $this;
}
/**
* Extend the configuration to be processed with
* the provided nested array. Also record the name
* of the data source, if applicable.
*
* @param array $data
* @param string $source
*/
protected function addFromSource($data, $source = '')
{
if (empty($source)) {
return $this->add($data);
}
$this->unprocessedConfig[$source] = $data;
return $this;
}
/**
* Process all of the configuration that has been collected,
* and return a nested array.
*
* @return array
*/
public function export($referenceArray = [])
{
if (!empty($this->unprocessedConfig)) {
$this->processedConfig = $this->process(
$this->processedConfig,
$this->fetchUnprocessed(),
$referenceArray
);
}
return $this->processedConfig;
}
/**
* To aid in debugging: return the source of each configuration item.
* n.b. Must call this function *before* export and save the result
* if persistence is desired.
*/
public function sources()
{
$sources = [];
foreach ($this->unprocessedConfig as $sourceName => $config) {
if (!empty($sourceName)) {
$configSources = ArrayUtil::fillRecursive($config, $sourceName);
$sources = ArrayUtil::mergeRecursiveDistinct($sources, $configSources);
}
}
return $sources;
}
/**
* Get the configuration to be processed, and clear out the
* 'unprocessed' list.
*
* @return array
*/
protected function fetchUnprocessed()
{
$toBeProcessed = $this->unprocessedConfig;
$this->unprocessedConfig = [];
return $toBeProcessed;
}
/**
* Use a map-reduce to evaluate the items to be processed,
* and merge them into the processed array.
*
* @param array $processed
* @param array $toBeProcessed
* @return array
*/
protected function process(array $processed, array $toBeProcessed, $referenceArray = [])
{
$toBeReduced = array_map([$this, 'preprocess'], $toBeProcessed);
$reduced = array_reduce($toBeReduced, [$this, 'reduceOne'], $processed);
return $this->evaluate($reduced, $referenceArray);
}
/**
* Process a single configuration file from the 'to be processed'
* list. By default this is a no-op. Override this method to
* provide any desired configuration preprocessing, e.g. dot-notation
* expansion of the configuration keys, etc.
*
* @param array $config
* @return array
*/
protected function preprocess(array $config)
{
return $config;
}
/**
* Evaluate one item in the 'to be evaluated' list, and then
* merge it into the processed configuration (the 'carry').
*
* @param array $processed
* @param array $config
* @return array
*/
protected function reduceOne(array $processed, array $config)
{
return ArrayUtil::mergeRecursiveDistinct($processed, $config);
}
/**
* Evaluate one configuration item.
*
* @param array $processed
* @param array $config
* @return array
*/
protected function evaluate(array $config, $referenceArray = [])
{
return $this->expander->expandArrayProperties(
$config,
$referenceArray
);
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Consolidation\Config\Loader;
use Symfony\Component\Yaml\Yaml;
/**
* Load configuration files, and fill in any property values that
* need to be expanded.
*/
class YamlConfigLoader extends ConfigLoader
{
public function load($path)
{
$this->setSourceName($path);
// We silently skip any nonexistent config files, so that
// clients may simply `load` all of their candidates.
if (!file_exists($path)) {
$this->config = [];
return $this;
}
$this->config = (array) Yaml::parse(file_get_contents($path));
return $this;
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace Consolidation\Config\Util;
/**
* Useful array utilities.
*/
class ArrayUtil
{
/**
* Merges arrays recursively while preserving.
*
* @param array $array1
* @param array $array2
*
* @return array
*
* @see http://php.net/manual/en/function.array-merge-recursive.php#92195
* @see https://github.com/grasmash/bolt/blob/robo-rebase/src/Robo/Common/ArrayManipulator.php#L22
*/
public static function mergeRecursiveDistinct(
array &$array1,
array &$array2
) {
$merged = $array1;
foreach ($array2 as $key => &$value) {
$merged[$key] = self::mergeRecursiveValue($merged, $key, $value);
}
return $merged;
}
/**
* Process the value in an mergeRecursiveDistinct - make a recursive
* call if needed.
*/
protected static function mergeRecursiveValue(&$merged, $key, $value)
{
if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
return self::mergeRecursiveDistinct($merged[$key], $value);
}
return $value;
}
/**
* Fills all of the leaf-node values of a nested array with the
* provided replacement value.
*/
public static function fillRecursive(array $data, $fill)
{
$result = [];
foreach ($data as $key => $value) {
$result[$key] = $fill;
if (self::isAssociative($value)) {
$result[$key] = self::fillRecursive($value, $fill);
}
}
return $result;
}
/**
* Return true if the provided parameter is an array, and at least
* one key is non-numeric.
*/
public static function isAssociative($testArray)
{
if (!is_array($testArray)) {
return false;
}
foreach (array_keys($testArray) as $key) {
if (!is_numeric($key)) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Consolidation\Config\Util;
/**
* Fetch a configuration value from a configuration group. If the
* desired configuration value is not found in the most specific
* group named, keep stepping up to the next parent group until a
* value is located.
*
* Given the following constructor inputs:
* - $prefix = "command."
* - $group = "foo.bar.baz"
* - $postfix = ".options."
* Then the `get` method will then consider, in order:
* - command.foo.bar.baz.options
* - command.foo.bar.options
* - command.foo.options
* If any of these contain an option for "$key", then return its value.
*/
class ConfigFallback extends ConfigGroup
{
/**
* @inheritdoc
*/
public function get($key)
{
return $this->getWithFallback($key, $this->group, $this->prefix, $this->postfix);
}
/**
* Fetch an option value from a given key, or, if that specific key does
* not contain a value, then consult various fallback options until a
* value is found.
*
*/
protected function getWithFallback($key, $group, $prefix = '', $postfix = '.')
{
$configKey = "{$prefix}{$group}${postfix}{$key}";
if ($this->config->has($configKey)) {
return $this->config->get($configKey);
}
if ($this->config->hasDefault($configKey)) {
return $this->config->getDefault($configKey);
}
$moreGeneralGroupname = $this->moreGeneralGroupName($group);
if ($moreGeneralGroupname) {
return $this->getWithFallback($key, $moreGeneralGroupname, $prefix, $postfix);
}
return null;
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace Consolidation\Config\Util;
/**
* Fetch a configuration value from a configuration group. If the
* desired configuration value is not found in the most specific
* group named, keep stepping up to the next parent group until a
* value is located.
*
* Given the following constructor inputs:
* - $prefix = "command."
* - $group = "foo.bar.baz"
* - $postfix = ".options."
* Then the `get` method will then consider, in order:
* - command.foo.bar.baz.options
* - command.foo.bar.options
* - command.foo.options
* If any of these contain an option for "$key", then return its value.
*/
abstract class ConfigGroup
{
protected $config;
protected $group;
protected $prefix;
protected $postfix;
public function __construct($config, $group, $prefix = '', $postfix = '.')
{
$this->config = $config;
$this->group = $group;
$this->prefix = $prefix;
$this->postfix = $postfix;
}
/**
* Return a description of the configuration group (with prefix and postfix).
*/
public function describe($property)
{
return $this->prefix . $this->group . $this->postfix . $property;
}
/**
* Get the requested configuration key from the most specific configuration
* group that contains it.
*/
abstract public function get($key);
/**
* Given a group name, such as "foo.bar.baz", return the next configuration
* group in the fallback hierarchy, e.g. "foo.bar".
*/
protected function moreGeneralGroupName($group)
{
$result = preg_replace('#\.[^.]*$#', '', $group);
if ($result != $group) {
return $result;
}
return false;
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Consolidation\Config\Util;
/**
* Works like 'getWithFallback', but merges results from all applicable
* groups. Settings from most specific group take precedence.
*/
class ConfigMerge extends ConfigGroup
{
/**
* @inheritdoc
*/
public function get($key)
{
return $this->getWithMerge($key, $this->group, $this->prefix, $this->postfix);
}
/**
* Merge available configuration from each configuration group.
*/
public function getWithMerge($key, $group, $prefix = '', $postfix = '.')
{
$configKey = "{$prefix}{$group}${postfix}{$key}";
$result = $this->config->get($configKey, []);
if (!is_array($result)) {
throw new \UnexpectedValueException($configKey . ' must be a list of settings to apply.');
}
$moreGeneralGroupname = $this->moreGeneralGroupName($group);
if ($moreGeneralGroupname) {
$result += $this->getWithMerge($key, $moreGeneralGroupname, $prefix, $postfix);
}
return $result;
}
}

View File

@@ -0,0 +1,203 @@
<?php
namespace Consolidation\Config\Util;
use Consolidation\Config\Config;
use Consolidation\Config\ConfigInterface;
/**
* Overlay different configuration objects that implement ConfigInterface
* to make a priority-based, merged configuration object.
*
* Note that using a ConfigOverlay hides the defaults stored in each
* individual configuration context. When using overlays, always call
* getDefault / setDefault on the ConfigOverlay object itself.
*/
class ConfigOverlay implements ConfigInterface
{
protected $contexts = [];
const DEFAULT_CONTEXT = 'default';
const PROCESS_CONTEXT = 'process';
public function __construct()
{
$this->contexts[self::DEFAULT_CONTEXT] = new Config();
$this->contexts[self::PROCESS_CONTEXT] = new Config();
}
/**
* Add a named configuration object to the configuration overlay.
* Configuration objects added LAST have HIGHEST priority, with the
* exception of the fact that the process context always has the
* highest priority.
*
* If a context has already been added, its priority will not change.
*/
public function addContext($name, ConfigInterface $config)
{
$process = $this->contexts[self::PROCESS_CONTEXT];
unset($this->contexts[self::PROCESS_CONTEXT]);
$this->contexts[$name] = $config;
$this->contexts[self::PROCESS_CONTEXT] = $process;
return $this;
}
/**
* Add a placeholder context that will be prioritized higher than
* existing contexts. This is done to ensure that contexts added
* later will maintain a higher priority if the placeholder context
* is later relaced with a different configuration set via addContext().
*
* @param string $name
* @return $this
*/
public function addPlaceholder($name)
{
return $this->addContext($name, new Config());
}
/**
* Increase the priority of the named context such that it is higher
* in priority than any existing context except for the 'process'
* context.
*
* @param string $name
* @return $this
*/
public function increasePriority($name)
{
$config = $this->getContext($name);
unset($this->contexts[$name]);
return $this->addContext($name, $config);
}
public function hasContext($name)
{
return isset($this->contexts[$name]);
}
public function getContext($name)
{
if ($this->hasContext($name)) {
return $this->contexts[$name];
}
return new Config();
}
public function removeContext($name)
{
unset($this->contexts[$name]);
}
/**
* Determine if a non-default config value exists.
*/
public function findContext($key)
{
foreach (array_reverse($this->contexts) as $name => $config) {
if ($config->has($key)) {
return $config;
}
}
return false;
}
/**
* @inheritdoc
*/
public function has($key)
{
return $this->findContext($key) != false;
}
/**
* @inheritdoc
*/
public function get($key, $default = null)
{
$context = $this->findContext($key);
if ($context) {
return $context->get($key, $default);
}
return $default;
}
/**
* @inheritdoc
*/
public function set($key, $value)
{
$this->contexts[self::PROCESS_CONTEXT]->set($key, $value);
return $this;
}
/**
* @inheritdoc
*/
public function import($data)
{
$this->unsupported(__FUNCTION__);
}
/**
* @inheritdoc
*/
public function replace($data)
{
$this->unsupported(__FUNCTION__);
}
/**
* @inheritdoc
*/
public function combine($data)
{
$this->unsupported(__FUNCTION__);
}
/**
* @inheritdoc
*/
protected function unsupported($fn)
{
throw new \Exception("The method '$fn' is not supported for the ConfigOverlay class.");
}
/**
* @inheritdoc
*/
public function export()
{
$export = [];
foreach ($this->contexts as $name => $config) {
$export = array_merge_recursive($export, $config->export());
}
return $export;
}
/**
* @inheritdoc
*/
public function hasDefault($key)
{
return $this->contexts[self::DEFAULT_CONTEXT]->has($key);
}
/**
* @inheritdoc
*/
public function getDefault($key, $default = null)
{
return $this->contexts[self::DEFAULT_CONTEXT]->get($key, $default);
}
/**
* @inheritdoc
*/
public function setDefault($key, $value)
{
$this->contexts[self::DEFAULT_CONTEXT]->set($key, $value);
return $this;
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace Consolidation\Config\Util;
use Consolidation\Config\Config;
use Consolidation\Config\ConfigInterface;
/**
* Provide a configuration object that fetches values from environment
* variables.
*/
class EnvConfig implements ConfigInterface
{
/** @var string */
protected $prefix;
/**
* EnvConfig constructor
*
* @param $prefix The string to appear before every environment
* variable key. For example, if the prefix is 'MYAPP_', then
* the key 'foo.bar' will be fetched from the environment variable
* MYAPP_FOO_BAR.
*/
public function __construct($prefix)
{
// Ensure that the prefix is always uppercase, and always
// ends with a '_', regardless of the form the caller provided.
$this->prefix = strtoupper(rtrim($prefix, '_')) . '_';
}
/**
* @inheritdoc
*/
public function has($key)
{
return $this->get($key) !== null;
}
/**
* @inheritdoc
*/
public function get($key, $defaultFallback = null)
{
$envKey = $this->prefix . strtoupper(strtr($key, '.-', '__'));
$envKey = str_replace($this->prefix . $this->prefix, $this->prefix, $envKey);
return getenv($envKey) ?: $defaultFallback;
}
/**
* @inheritdoc
*/
public function set($key, $value)
{
throw new \Exception('Cannot call "set" on environmental configuration.');
}
/**
* @inheritdoc
*/
public function import($data)
{
// no-op
}
/**
* @inheritdoc
*/
public function export()
{
return [];
}
/**
* @inheritdoc
*/
public function hasDefault($key)
{
return false;
}
/**
* @inheritdoc
*/
public function getDefault($key, $defaultFallback = null)
{
return $defaultFallback;
}
/**
* @inheritdoc
*/
public function setDefault($key, $value)
{
throw new \Exception('Cannot call "setDefault" on environmental configuration.');
}
}

View File

@@ -0,0 +1,130 @@
<?php
namespace Consolidation\Config\Inject;
use Consolidation\Config\Config;
use Consolidation\TestUtils\MyFooCommand;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\OutputInterface;
class ConfigForCommandTest extends \PHPUnit_Framework_TestCase
{
protected $config;
protected function setUp()
{
$data = [
// Global options
'options' => [
'global' => 'from-config',
],
// Define some configuration settings for the options for
// the commands my:foo and my:bar.
'command' => [
'my' => [
// commands.my.options.* apply to all my:* commands.
'options' => [
'dir' => '/etc/common',
'priority' => 'normal',
],
'foo' => [
// commands.my.foo.options.* apply only to the my:foo command.
'options' => [
'name' => 'baz',
],
],
],
],
];
$this->config = new Config($data);
}
public function testInjection()
{
$command = new MyFooCommand();
$input = new StringInput('my:foo');
list($status, $output) = $this->runCommandViaApplication($command, $input);
$expectedOutput = <<< EOT
Enter my:foo
dir: /etc/common
name: baz
other: fish
EOT;
$this->assertEquals(0, $status);
$this->assertEquals($expectedOutput, $output);
}
public function testInjectionWithOverride()
{
$command = new MyFooCommand();
$input = new StringInput('my:foo --name=Fred');
list($status, $output) = $this->runCommandViaApplication($command, $input);
$expectedOutput = <<< EOT
Enter my:foo
dir: /etc/common
name: Fred
other: fish
EOT;
$this->assertEquals(0, $status);
$this->assertEquals($expectedOutput, $output);
}
public function testHelpDefaultInjection()
{
$command = new MyFooCommand();
$input = new StringInput('help my:foo');
list($status, $output) = $this->runCommandViaApplication($command, $input);
$expectedOutput = <<< EOT
What is the name of the thing we are naming [default: "baz"]
EOT;
$this->assertEquals(0, $status);
$this->assertContains($expectedOutput, $output);
$expectedOutput = <<< EOT
A certain global option. [default: "from-config"]
EOT;
$this->assertContains($expectedOutput, $output);
}
protected function runCommandViaApplication($command, $input)
{
$application = new Application('TestApplication', '0.0.0');
$application->getDefinition()
->addOption(
new InputOption('--global', null, InputOption::VALUE_REQUIRED, 'A certain global option.', 'hardcoded')
);
$output = new BufferedOutput();
$configInjector = new ConfigForCommand($this->config);
$configInjector->setApplication($application);
$eventDispatcher = new \Symfony\Component\EventDispatcher\EventDispatcher();
$eventDispatcher->addSubscriber($configInjector);
$application->setDispatcher($eventDispatcher);
$application->setAutoExit(false);
$application->add($command);
$statusCode = $application->run($input, $output);
$commandOutput = trim($output->fetch());
return [$statusCode, $commandOutput];
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace Consolidation\Config\Inject;
use Consolidation\Config\Config;
use Consolidation\TestUtils\ApplyConfigTestTarget;
class ConfigForSettersTest extends \PHPUnit_Framework_TestCase
{
public function testApplyConfig()
{
$data = [
// Define some configuration settings for the configuration
// of some task \My\Tasks\Operations\Frobulate.
'task' => [
'Operations' => [
// task.Operations.settings apply to all tasks in
// any *.Tass.Operations namespace.
'settings' => [
'dir' => '/base/dir',
],
'Frobulate' => [
// task.Operations.Frobulate.settings applies only
// the Frobulate task.
'settings' => [
'dir' => '/override/dir',
],
],
],
],
];
$config = new Config($data);
$applicator = new ConfigForSetters($config, 'Operations.Frobulate', 'task.');
$testTarget = new ApplyConfigTestTarget();
$applicator->apply($testTarget, 'settings');
$this->assertEquals('/override/dir', $testTarget->getDir());
$this->assertEquals(null, $testTarget->getBad());
}
public function testApplyBadConfig()
{
$data = [
// Define some configuration settings for the configuration
// of some task \My\Tasks\Operations\Frobulate.
'task' => [
'Operations' => [
// task.Operations.settings apply to all tasks in
// any *.Tass.Operations namespace.
'settings' => [
'dir' => '/base/dir',
],
'Frobulate' => [
// task.Operations.Frobulate.settings applies only
// the Frobulate task.
'settings' => [
'bad' => 'fire truck',
],
],
],
],
];
$config = new Config($data);
$applicator = new ConfigForSetters($config, 'Operations.Frobulate', 'task.');
$testTarget = new ApplyConfigTestTarget();
$exceptionMessage = '';
try
{
$applicator->apply($testTarget, 'settings');
}
catch (\Exception $e)
{
$exceptionMessage = $e->getMessage();
}
// We would prefer it if bad methods were never called; unfortunately,
// declaring the return type of a method cannot be done in a reliable
// way (via reflection) until php 7, so we allow these methods to be
// called for now.
$this->assertEquals('fire truck', $testTarget->getBad());
$this->assertEquals('Consolidation\\TestUtils\\ApplyConfigTestTarget::bad did not return \'$this\' when processing task.Operations.Frobulate.settings.', $exceptionMessage);
}
}

View File

@@ -0,0 +1,91 @@
<?php
namespace Consolidation\Config\Util;
use Consolidation\Config\Config;
class ConfigGroupTest extends \PHPUnit_Framework_TestCase
{
protected $config;
protected function setUp()
{
$data = [
// Define some configuration settings for the options for
// the commands my:foo and my:bar.
'command' => [
'my' => [
// commands.my.options.* apply to all my:* commands.
'options' => [
'path' => '/etc/common',
'priority' => 'normal',
],
'foo' => [
// commands.my.foo.options.* apply only to the my:foo command.
'options' => [
'name' => 'baz',
],
],
'bar' => [
// Similarly, commands.my.bar.options is for the my:bar command.
'options' => [
'priority' => 'high',
],
],
],
],
// Define some configuration settings for the configuration
// of some task \My\Tasks\Operations\Frobulate.
'task' => [
'Operations' => [
// task.Operations.settings apply to all tasks in
// any *.Tass.Operations namespace.
'settings' => [
'dir' => '/base/dir',
],
'Frobulate' => [
// task.Operations.Frobulate.settings applies only
// the Frobulate task.
'settings' => [
'object' => 'fire truck',
],
],
],
],
];
$this->config = new Config($data);
}
public function testDotNotation()
{
// Test the test
$this->assertEquals('baz', $this->config->get('command.my.foo.options.name'));
}
public function testFallback()
{
$fooFallback = new ConfigFallback($this->config, 'my.foo', 'command.', '.options.');
$barFallback = new ConfigFallback($this->config, 'my.bar', 'command.', '.options.');
$this->assertEquals(null, $barFallback->get('name'));
$this->assertEquals('baz', $fooFallback->get('name'));
$this->assertEquals('high', $barFallback->get('priority'));
$this->assertEquals('normal', $fooFallback->get('priority'));
$this->assertEquals('/etc/common', $barFallback->get('path'));
$this->assertEquals('/etc/common', $fooFallback->get('path'));
}
public function testMerge()
{
$frobulateMerge = new ConfigMerge($this->config, 'Operations.Frobulate', 'task.');
$settings = $frobulateMerge->get('settings');
$this->assertEquals('fire truck', $settings['object']);
$this->assertEquals('/base/dir', $settings['dir']);
$keys = array_keys($settings);
sort($keys);
$this->assertEquals('dir,object', implode(',', $keys));
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Consolidation\Config\Loader;
class ConfigLoaderTest extends \PHPUnit_Framework_TestCase
{
public function testConfigLoader()
{
$loader = new YamlConfigLoader();
// Assert that our test data exists (test the test)
$path = __DIR__ . '/data/config-1.yml';
$this->assertTrue(file_exists($path));
$loader->load($path);
$configFile = basename($loader->getSourceName());
$this->assertEquals('config-1.yml', $configFile);
// Make sure that the data we loaded contained the expected keys
$keys = $loader->keys();
sort($keys);
$keysString = implode(',', $keys);
$this->assertEquals('c,m', $keysString);
$configData = $loader->export();
$this->assertEquals('foo', $configData['c']);
$this->assertEquals('1', $configData['m'][0]);
}
}

View File

@@ -0,0 +1,168 @@
<?php
namespace Consolidation\Config\Util;
use Consolidation\Config\Config;
use Consolidation\Config\Loader\ConfigProcessor;
use Consolidation\Config\Loader\YamlConfigLoader;
class ConfigOverlayTest extends \PHPUnit_Framework_TestCase
{
protected $overlay;
protected function setUp()
{
$aliasContext = new Config();
$aliasContext->import([
'hidden-by-a' => 'alias hidden-by-a',
'hidden-by-process' => 'alias hidden-by-process',
'options' =>[
'a-a' => 'alias-a',
],
'command' => [
'foo' => [
'bar' => [
'command' => [
'options' => [
'a-b' => 'alias-b',
],
],
],
],
],
]);
$configFileContext = new Config();
$configFileContext->import([
'hidden-by-cf' => 'config-file hidden-by-cf',
'hidden-by-a' => 'config-file hidden-by-a',
'hidden-by-process' => 'config-file hidden-by-process',
'options' =>[
'cf-a' => 'config-file-a',
],
'command' => [
'foo' => [
'bar' => [
'command' => [
'options' => [
'cf-b' => 'config-file-b',
],
],
],
],
],
]);
$this->overlay = new ConfigOverlay();
$this->overlay->set('hidden-by-process', 'process-h');
$this->overlay->addContext('cf', $configFileContext);
$this->overlay->addContext('a', $aliasContext);
$this->overlay->setDefault('df-a', 'default');
$this->overlay->setDefault('hidden-by-a', 'default hidden-by-a');
$this->overlay->setDefault('hidden-by-cf', 'default hidden-by-cf');
$this->overlay->setDefault('hidden-by-process', 'default hidden-by-process');
}
public function testGetPriority()
{
$this->assertEquals('process-h', $this->overlay->get('hidden-by-process'));
$this->assertEquals('config-file hidden-by-cf', $this->overlay->get('hidden-by-cf'));
$this->assertEquals('alias hidden-by-a', $this->overlay->get('hidden-by-a'));
}
public function testDefault()
{
$this->assertEquals('alias-a', $this->overlay->get('options.a-a'));
$this->assertEquals('alias-a', $this->overlay->get('options.a-a', 'ignored'));
$this->assertEquals('default', $this->overlay->getDefault('df-a', 'ignored'));
$this->assertEquals('nsv', $this->overlay->getDefault('a-a', 'nsv'));
$this->overlay->setDefault('df-a', 'new value');
$this->assertEquals('new value', $this->overlay->getDefault('df-a', 'ignored'));
}
public function testExport()
{
$data = $this->overlay->export();
$this->assertEquals('config-file-a', $data['options']['cf-a']);
$this->assertEquals('alias-a', $data['options']['a-a']);
}
/**
* @expectedException Exception
*/
public function testImport()
{
$data = $this->overlay->import(['a' => 'value']);
}
public function testMaintainPriority()
{
// Get and re-add the 'cf' context. Its priority should not change.
$configFileContext = $this->overlay->getContext('cf');
$this->overlay->addContext('cf', $configFileContext);
// These asserts are the same as in testGetPriority
$this->assertEquals('process-h', $this->overlay->get('hidden-by-process'));
$this->assertEquals('config-file hidden-by-cf', $this->overlay->get('hidden-by-cf'));
$this->assertEquals('alias hidden-by-a', $this->overlay->get('hidden-by-a'));
}
public function testChangePriority()
{
// Increase the priority of the 'cf' context. Now, it should have a higher
// priority than the 'alias' context, but should still have a lower
// priority than the 'process' context.
$this->overlay->increasePriority('cf');
// These asserts are the same as in testGetPriority
$this->assertEquals('process-h', $this->overlay->get('hidden-by-process'));
$this->assertEquals('config-file hidden-by-cf', $this->overlay->get('hidden-by-cf'));
// This one has changed: the config-file value is now found instead
// of the alias value.
$this->assertEquals('config-file hidden-by-a', $this->overlay->get('hidden-by-a'));
}
public function testPlaceholder()
{
$this->overlay->addPlaceholder('lower');
$higherContext = new Config();
$higherContext->import(['priority-test' => 'higher']);
$lowerContext = new Config();
$lowerContext->import(['priority-test' => 'lower']);
// Usually 'lower' would have the highest priority, since it is
// added last. However, our earlier call to 'addPlaceholder' reserves
// a spot for it, so the 'higher' context will end up with a higher
// priority.
$this->overlay->addContext('higher', $higherContext);
$this->overlay->addContext('lower', $lowerContext);
$this->assertEquals('higher', $this->overlay->get('priority-test', 'neither'));
// Test to see that we can change the value of the 'higher' context,
// and the change will be reflected in the overlay.
$higherContext->set('priority-test', 'changed');
$this->assertEquals('changed', $this->overlay->get('priority-test', 'neither'));
// Test to see that the 'process' context still has the highest priority.
$this->overlay->set('priority-test', 'process');
$higherContext->set('priority-test', 'ignored');
$this->assertEquals('process', $this->overlay->get('priority-test', 'neither'));
}
public function testDoesNotHave()
{
$context = $this->overlay->getContext('no-such-context');
$data = $context->export();
$this->assertEquals('[]', json_encode($data));
$this->assertTrue(!$this->overlay->has('no-such-key'));
$this->assertTrue(!$this->overlay->hasDefault('no-such-default'));
$this->assertEquals('no-such-value', $this->overlay->get('no-such-key', 'no-such-value'));
}
}

View File

@@ -0,0 +1,152 @@
<?php
namespace Consolidation\Config\Loader;
use Consolidation\TestUtils\TestLoader;
class ConfigProcessorTest extends \PHPUnit_Framework_TestCase
{
public function testConfigProcessorAdd()
{
$config1 = [
'c' => 'foo',
'm' => [1],
];
$config2 = [
'b' => '${c}bar',
'm' => [2],
];
$config3 = [
'a' => '${b}baz',
'm' => [3],
];
$processor = new ConfigProcessor();
$processor->add($config1);
$processor->add($config2);
$processor->add($config3);
$data = $processor->export();
$this->assertEquals('foo', $data['c']);
$this->assertEquals('foobar', $data['b']);
$this->assertEquals('foobarbaz', $data['a']);
}
public function processorForConfigMergeTest($provideSourceNames)
{
$config1 = [
'm' => [
'x' => 'x-1',
'y' => [
'r' => 'r-1',
's' => 's-1',
't' => 't-1',
],
'z' => 'z-1',
],
];
$config2 = [
'm' => [
'w' => 'w-2',
'y' => [
'q' => 'q-2',
's' => 's-2',
],
'z' => 'z-2',
],
];
$config3 = [
'm' => [
'v' => 'v-3',
'y' => [
't' => 't-3',
'u' => 'u-3',
],
'z' => 'z-3',
],
];
$processor = new ConfigProcessor();
$testLoader = new TestLoader();
$testLoader->set($config1);
$testLoader->setSourceName($provideSourceNames ? 'c-1' : '');
$processor->extend($testLoader);
$testLoader->set($config2);
$testLoader->setSourceName($provideSourceNames ? 'c-2' : '');
$processor->extend($testLoader);
$testLoader->set($config3);
$testLoader->setSourceName($provideSourceNames ? 'c-3' : '');
$processor->extend($testLoader);
return $processor;
}
public function testConfigProcessorMergeAssociative()
{
$processor = $this->processorForConfigMergeTest(false);
$data = $processor->export();
$this->assertEquals('{"m":{"x":"x-1","y":{"r":"r-1","s":"s-2","t":"t-3","q":"q-2","u":"u-3"},"z":"z-3","w":"w-2","v":"v-3"}}', json_encode($data));
}
public function testConfigProcessorMergeAssociativeWithSourceNames()
{
$processor = $this->processorForConfigMergeTest(true);
$sources = $processor->sources();
$data = $processor->export();
$this->assertEquals('{"m":{"x":"x-1","y":{"r":"r-1","s":"s-2","t":"t-3","q":"q-2","u":"u-3"},"z":"z-3","w":"w-2","v":"v-3"}}', json_encode($data));
$this->assertEquals('c-1', $sources['m']['x']);
$this->assertEquals('c-1', $sources['m']['y']['r']);
$this->assertEquals('c-2', $sources['m']['w']);
$this->assertEquals('c-2', $sources['m']['y']['s']);
$this->assertEquals('c-3', $sources['m']['z']);
$this->assertEquals('c-3', $sources['m']['y']['u']);
}
public function testConfiProcessorSources()
{
$processor = new ConfigProcessor();
$loader = new YamlConfigLoader();
$processor->extend($loader->load(__DIR__ . '/data/config-1.yml'));
$processor->extend($loader->load(__DIR__ . '/data/config-2.yml'));
$processor->extend($loader->load(__DIR__ . '/data/config-3.yml'));
$sources = $processor->sources();
$data = $processor->export();
$this->assertEquals('foo', $data['c']);
$this->assertEquals('foobar', $data['b']);
$this->assertEquals('foobarbaz', $data['a']);
$this->assertEquals('3', $data['m'][0]);
$this->assertEquals( __DIR__ . '/data/config-3.yml', $sources['a']);
$this->assertEquals( __DIR__ . '/data/config-2.yml', $sources['b']);
$this->assertEquals( __DIR__ . '/data/config-1.yml', $sources['c']);
$this->assertEquals( __DIR__ . '/data/config-3.yml', $sources['m']);
}
public function testConfiProcessorSourcesLoadInReverseOrder()
{
$processor = new ConfigProcessor();
$loader = new YamlConfigLoader();
$processor->extend($loader->load(__DIR__ . '/data/config-3.yml'));
$processor->extend($loader->load(__DIR__ . '/data/config-2.yml'));
$processor->extend($loader->load(__DIR__ . '/data/config-1.yml'));
$sources = $processor->sources();
$data = $processor->export();
$this->assertEquals('foo', $data['c']);
$this->assertEquals('foobar', $data['b']);
$this->assertEquals('foobarbaz', $data['a']);
$this->assertEquals('1', $data['m'][0]);
$this->assertEquals( __DIR__ . '/data/config-3.yml', $sources['a']);
$this->assertEquals( __DIR__ . '/data/config-2.yml', $sources['b']);
$this->assertEquals( __DIR__ . '/data/config-1.yml', $sources['c']);
$this->assertEquals( __DIR__ . '/data/config-1.yml', $sources['m']);
}
}

View File

@@ -0,0 +1,140 @@
<?php
namespace Consolidation\Config;
use Consolidation\Config\Loader\ConfigProcessor;
use Consolidation\Config\Loader\YamlConfigLoader;
class ConfigTest extends \PHPUnit_Framework_TestCase
{
public function testSetters()
{
// Pointless tests just to ensure everything is covered.
$config = new Config();
$config->set('foo', 'bar');
$data = $config->export();
$this->assertEquals('{"foo":"bar"}', json_encode($data));
}
public function testCombine()
{
// Pointless tests just to ensure everything is covered.
$config = new Config();
$config->set('foo', 'bar');
$config->set('baz', 'boz');
$config2 = new Config();
$config2->set('foo', 'fu');
$config2->set('new', 'blue');
$config->combine($config2->export());
$this->assertEquals('fu', $config->get('foo'));
$this->assertEquals('boz', $config->get('baz'));
$this->assertEquals('blue', $config->get('new'));
}
public function testDefault()
{
$data = [
'a' => 'foo',
'b' => 'bar',
'c' => 'boz',
];
$foo = ["foo" => "bar"];
$config = new Config($data);
$config->setDefault('c', 'other');
$config->setDefault('d', 'other');
$config->setDefault('f', $foo);
$this->assertEquals('foo', $config->get('a'));
$this->assertEquals('boz', $config->get('c'));
$this->assertEquals('other', $config->get('d'));
$this->assertEquals('other', $config->getDefault('c'));
$this->assertEquals('', $config->get('e'));
$this->assertEquals('bar', $config->get('f.foo'));
$this->assertEquals('{"foo":"bar"}', json_encode($config->get('f')));
}
public function testDefaultsArray()
{
$data = ['a' => 'foo', 'b' => 'bar', 'c' => 'boz',];
$defaults = ['d' => 'foo', 'e' => 'bar', 'f' => 'boz',];
// Create reflection class to test private methods
$configClass = new \ReflectionClass("Consolidation\Config\Config");
// $defaults
$defaultsProperty = $configClass->getProperty("defaults");
$defaultsProperty->setAccessible(true);
// $getDefaults
$getDefaultsMethod = $configClass->getMethod("getDefaults");
$getDefaultsMethod->setAccessible(true);
// Test the config class
$config = new Config($data);
// Set $config::defaults to an array to test getter and setter
$defaultsProperty->setValue($config, $defaults);
$this->assertTrue(is_array($defaultsProperty->getValue($config)));
$this->assertInstanceOf('Dflydev\DotAccessData\Data',
$getDefaultsMethod->invoke($config));
// Set $config::defaults to a string to test exception
$defaultsProperty->setValue($config, "foo.bar");
$this->setExpectedException("Exception");
$getDefaultsMethod->invoke($config);
}
public function testConfigurationWithCrossFileReferences()
{
$config = new Config();
$processor = new ConfigProcessor();
$loader = new YamlConfigLoader();
$processor->extend($loader->load(__DIR__ . '/data/config-1.yml'));
$processor->extend($loader->load(__DIR__ . '/data/config-2.yml'));
$processor->extend($loader->load(__DIR__ . '/data/config-3.yml'));
// Does not fail if configuration file cannot be found
$processor->extend($loader->load(__DIR__ . '/data/no-such-file.yml'));
// We must capture the sources before exporting, as export
// dumps this information.
$sources = $processor->sources();
$config->import($processor->export());
$this->assertEquals(implode(',', $config->get('m')), '3');
$this->assertEquals($config->get('a'), 'foobarbaz');
$this->assertEquals($sources['a'], __DIR__ . '/data/config-3.yml');
$this->assertEquals($sources['b'], __DIR__ . '/data/config-2.yml');
$this->assertEquals($sources['c'], __DIR__ . '/data/config-1.yml');
}
public function testConfigurationWithReverseOrderCrossFileReferences()
{
$config = new Config();
$processor = new ConfigProcessor();
$loader = new YamlConfigLoader();
$processor->extend($loader->load(__DIR__ . '/data/config-3.yml'));
$processor->extend($loader->load(__DIR__ . '/data/config-2.yml'));
$processor->extend($loader->load(__DIR__ . '/data/config-1.yml'));
$sources = $processor->sources();
$config->import($processor->export());
$this->assertEquals(implode(',', $config->get('m')), '1');
if (strpos($config->get('a'), '$') !== false) {
throw new \PHPUnit_Framework_SkippedTestError(
'Evaluation of cross-file references in reverse order not supported.'
);
}
$this->assertEquals($config->get('a'), 'foobarbaz');
$this->assertEquals($sources['a'], __DIR__ . '/data/config-3.yml');
$this->assertEquals($sources['b'], __DIR__ . '/data/config-2.yml');
$this->assertEquals($sources['c'], __DIR__ . '/data/config-1.yml');
}
}

View File

@@ -0,0 +1,3 @@
c: foo
m:
- 1

View File

@@ -0,0 +1,3 @@
b: ${c}bar
m:
- 2

View File

@@ -0,0 +1,3 @@
a: ${b}baz
m:
- 3

View File

@@ -0,0 +1,23 @@
#!/bin/bash
SCENARIO=$1
ACTION=${2-install}
dir=dependencies/${SCENARIO}
if [ -z "$SCENARIO" ] ; then
SCENARIO=default
dir=.
fi
if [ ! -d "$dir" ] ; then
echo "Requested scenario '${SCENARIO}' does not exist."
exit 1
fi
echo "Switch to ${SCENARIO} scenario"
set -ex
composer -n --working-dir=$dir ${ACTION} --prefer-dist --no-scripts
composer -n --working-dir=$dir info

View File

@@ -0,0 +1,66 @@
#!/bin/bash
#
# This script is called automatically on every `composer update`.
# See "post-update-cmd" in the "scripts" section of composer.json.
#
# This script will create a derived composer.json / composer.lock
# pair for every test scenario. Test scenarios are defined in the
# "scenarios" file, which should be customized to suit the needs
# of the project.
#
SELF_DIRNAME="`dirname -- "$0"`"
source ${SELF_DIRNAME}/scenarios
echo
echo "::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"
echo "::"
echo ":: Update dependencies for the following scenarios:"
echo "::"
echo ":: ${SCENARIOS}"
echo "::"
echo "::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"
echo
set -ex
for SCENARIO in ${SCENARIOS} ; do
dir=dependencies/${SCENARIO}
# Define indirect variable names
stability_variable="stability_${SCENARIO}"
requirement_variable="requirement_${SCENARIO}"
platform_php_variable="platform_php_${SCENARIO}"
echo "### Create $dir/composer.json for ${SCENARIO} scenario"
mkdir -p $dir
cp composer.json $dir
# Then set our own platform php version if applicable (otherwise unset it)
composer -n --working-dir=$dir config platform.php "${!platform_php_variable---unset}"
# Temporarily set our vendor directory to 'vendor'
composer -n --working-dir=$dir config vendor-dir vendor
# Set an appropriate minimum stability for this version of Symfony
composer -n --working-dir=$dir config minimum-stability "${!stability_variable-stable}"
# Add a constraint to limit the Symfony version
composer -n --working-dir=$dir require --dev --no-update "${!requirement_variable}"
# Create the composer.lock file. Ignore the vendor directory created.
composer -n --working-dir=$dir update --no-scripts
# Set the vendor directory to its final desired location.
composer -n --working-dir=$dir config vendor-dir '../../vendor'
# The 'autoload' section specifies directory paths that are relative
# to the composer.json file. We will drop in some symlinks so that
# these paths will resolve as if the composer.json were in the root.
for target in $AUTOLOAD_DIRECTORIES ; do
ln -s -f ../../$target $dir
done
done

View File

@@ -0,0 +1,12 @@
#!/bin/bash
SCENARIOS="symfony2 symfony3 symfony4"
AUTOLOAD_DIRECTORIES='src tests'
platform_php_symfony2='5.4'
platform_php_symfony3='5.6'
requirement_symfony2='symfony/console:^2.8'
requirement_symfony3='symfony/console:^3'
requirement_symfony4='symfony/console:^4'

View File

@@ -0,0 +1,43 @@
<?php
namespace Consolidation\TestUtils;
class ApplyConfigTestTarget
{
protected $dir;
protected $value;
/**
* A proper setter for the 'dir' property
*/
public function dir($dir)
{
$this->dir = $dir;
return $this;
}
/**
* A getter for the 'dir' property that we will use to
* determine if the setter was called.
*/
public function getDir()
{
return $this->dir;
}
/**
* A bad setter that does not return $this.
*/
public function bad($value)
{
$this->value = $value;
}
/**
* A getter for the bad setter.
*/
public function getBad()
{
return $this->value;
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Consolidation\TestUtils;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
class MyFooCommand extends Command
{
protected function configure()
{
$this
->setName('my:foo')
->setDescription('My foo command.')
->setHelp('This command tests command option injection by echoing its options')
->addOption(
'other',
null,
InputOption::VALUE_REQUIRED,
'Some other option',
'fish'
)
->addOption(
'name',
null,
InputOption::VALUE_REQUIRED,
'What is the name of the thing we are naming',
'George'
)
->addOption(
'dir',
null,
InputOption::VALUE_REQUIRED,
'What is the base directory to use for this command',
'/default/path'
);
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->writeln('Enter my:foo');
$output->writeln('dir: ' . $input->getOption('dir'));
$output->writeln('name: ' . $input->getOption('name'));
$output->writeln('other: ' . $input->getOption('other'));
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Consolidation\TestUtils;
use Consolidation\Config\Loader\ConfigLoaderInterface;
class TestLoader implements ConfigLoaderInterface
{
protected $data;
protected $sourceName;
public function set($data)
{
$this->data = $data;
}
public function setSourceName($name)
{
$this->sourceName = $name;
}
public function export()
{
return $this->data;
}
public function keys()
{
return array_keys($this->data);
}
public function getSourceName()
{
return $this->sourceName;
}
}