Update Composer, update everything

This commit is contained in:
Oliver Davies 2018-11-23 12:29:20 +00:00
parent ea3e94409f
commit dda5c284b6
19527 changed files with 1135420 additions and 351004 deletions

3
vendor/symfony/config/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
vendor/
composer.lock
phpunit.xml

78
vendor/symfony/config/CHANGELOG.md vendored Normal file
View file

@ -0,0 +1,78 @@
CHANGELOG
=========
3.4.0
-----
* added `setDeprecated()` method to indicate a deprecated node
* added `XmlUtils::parse()` method to parse an XML string
* deprecated `ConfigCachePass`
3.3.0
-----
* added `ReflectionClassResource` class
* added second `$exists` constructor argument to `ClassExistenceResource`
* made `ClassExistenceResource` work with interfaces and traits
* added `ConfigCachePass` (originally in FrameworkBundle)
* added `castToArray()` helper to turn any config value into an array
3.0.0
-----
* removed `ReferenceDumper` class
* removed the `ResourceInterface::isFresh()` method
* removed `BCResourceInterfaceChecker` class
* removed `ResourceInterface::getResource()` method
2.8.0
-----
The edge case of defining just one value for nodes of type Enum is now allowed:
```php
$rootNode
->children()
->enumNode('variable')
->values(array('value'))
->end()
->end()
;
```
Before: `InvalidArgumentException` (variable must contain at least two
distinct elements).
After: the code will work as expected and it will restrict the values of the
`variable` option to just `value`.
* deprecated the `ResourceInterface::isFresh()` method. If you implement custom resource types and they
can be validated that way, make them implement the new `SelfCheckingResourceInterface`.
* deprecated the getResource() method in ResourceInterface. You can still call this method
on concrete classes implementing the interface, but it does not make sense at the interface
level as you need to know about the particular type of resource at hand to understand the
semantics of the returned value.
2.7.0
-----
* added `ConfigCacheInterface`, `ConfigCacheFactoryInterface` and a basic `ConfigCacheFactory`
implementation to delegate creation of ConfigCache instances
2.2.0
-----
* added `ArrayNodeDefinition::canBeEnabled()` and `ArrayNodeDefinition::canBeDisabled()`
to ease configuration when some sections are respectively disabled / enabled
by default.
* added a `normalizeKeys()` method for array nodes (to avoid key normalization)
* added numerical type handling for config definitions
* added convenience methods for optional configuration sections to `ArrayNodeDefinition`
* added a utils class for XML manipulations
2.1.0
-----
* added a way to add documentation on configuration
* implemented `Serializable` on resources
* `LoaderResolverInterface` is now used instead of `LoaderResolver` for type
hinting

62
vendor/symfony/config/ConfigCache.php vendored Normal file
View file

@ -0,0 +1,62 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
use Symfony\Component\Config\Resource\SelfCheckingResourceChecker;
/**
* ConfigCache caches arbitrary content in files on disk.
*
* When in debug mode, those metadata resources that implement
* \Symfony\Component\Config\Resource\SelfCheckingResourceInterface will
* be used to check cache freshness.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Matthias Pigulla <mp@webfactory.de>
*/
class ConfigCache extends ResourceCheckerConfigCache
{
private $debug;
/**
* @param string $file The absolute cache path
* @param bool $debug Whether debugging is enabled or not
*/
public function __construct($file, $debug)
{
$this->debug = (bool) $debug;
$checkers = array();
if (true === $this->debug) {
$checkers = array(new SelfCheckingResourceChecker());
}
parent::__construct($file, $checkers);
}
/**
* Checks if the cache is still fresh.
*
* This implementation always returns true when debug is off and the
* cache file exists.
*
* @return bool true if the cache is fresh, false otherwise
*/
public function isFresh()
{
if (!$this->debug && is_file($this->getPath())) {
return true;
}
return parent::isFresh();
}
}

View file

@ -0,0 +1,51 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
/**
* Basic implementation of ConfigCacheFactoryInterface that
* creates an instance of the default ConfigCache.
*
* This factory and/or cache <em>do not</em> support cache validation
* by means of ResourceChecker instances (that is, service-based).
*
* @author Matthias Pigulla <mp@webfactory.de>
*/
class ConfigCacheFactory implements ConfigCacheFactoryInterface
{
private $debug;
/**
* @param bool $debug The debug flag to pass to ConfigCache
*/
public function __construct($debug)
{
$this->debug = $debug;
}
/**
* {@inheritdoc}
*/
public function cache($file, $callback)
{
if (!\is_callable($callback)) {
throw new \InvalidArgumentException(sprintf('Invalid type for callback argument. Expected callable, but got "%s".', \gettype($callback)));
}
$cache = new ConfigCache($file, $this->debug);
if (!$cache->isFresh()) {
\call_user_func($callback, $cache);
}
return $cache;
}
}

View file

@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
/**
* Interface for a ConfigCache factory. This factory creates
* an instance of ConfigCacheInterface and initializes the
* cache if necessary.
*
* @author Matthias Pigulla <mp@webfactory.de>
*/
interface ConfigCacheFactoryInterface
{
/**
* Creates a cache instance and (re-)initializes it if necessary.
*
* @param string $file The absolute cache file path
* @param callable $callable The callable to be executed when the cache needs to be filled (i. e. is not fresh). The cache will be passed as the only parameter to this callback
*
* @return ConfigCacheInterface $configCache The cache instance
*/
public function cache($file, $callable);
}

View file

@ -0,0 +1,49 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
use Symfony\Component\Config\Resource\ResourceInterface;
/**
* Interface for ConfigCache.
*
* @author Matthias Pigulla <mp@webfactory.de>
*/
interface ConfigCacheInterface
{
/**
* Gets the cache file path.
*
* @return string The cache file path
*/
public function getPath();
/**
* Checks if the cache is still fresh.
*
* This check should take the metadata passed to the write() method into consideration.
*
* @return bool Whether the cache is still fresh
*/
public function isFresh();
/**
* Writes the given content into the cache file. Metadata will be stored
* independently and can be used to check cache freshness at a later time.
*
* @param string $content The content to write into the cache
* @param ResourceInterface[]|null $metadata An array of ResourceInterface instances
*
* @throws \RuntimeException When the cache file cannot be written
*/
public function write($content, array $metadata = null);
}

View file

@ -0,0 +1,380 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
use Symfony\Component\Config\Definition\Exception\UnsetKeyException;
/**
* Represents an Array node in the config tree.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ArrayNode extends BaseNode implements PrototypeNodeInterface
{
protected $xmlRemappings = array();
protected $children = array();
protected $allowFalse = false;
protected $allowNewKeys = true;
protected $addIfNotSet = false;
protected $performDeepMerging = true;
protected $ignoreExtraKeys = false;
protected $removeExtraKeys = true;
protected $normalizeKeys = true;
public function setNormalizeKeys($normalizeKeys)
{
$this->normalizeKeys = (bool) $normalizeKeys;
}
/**
* Normalizes keys between the different configuration formats.
*
* Namely, you mostly have foo_bar in YAML while you have foo-bar in XML.
* After running this method, all keys are normalized to foo_bar.
*
* If you have a mixed key like foo-bar_moo, it will not be altered.
* The key will also not be altered if the target key already exists.
*
* @param mixed $value
*
* @return array The value with normalized keys
*/
protected function preNormalize($value)
{
if (!$this->normalizeKeys || !\is_array($value)) {
return $value;
}
$normalized = array();
foreach ($value as $k => $v) {
if (false !== strpos($k, '-') && false === strpos($k, '_') && !array_key_exists($normalizedKey = str_replace('-', '_', $k), $value)) {
$normalized[$normalizedKey] = $v;
} else {
$normalized[$k] = $v;
}
}
return $normalized;
}
/**
* Retrieves the children of this node.
*
* @return array The children
*/
public function getChildren()
{
return $this->children;
}
/**
* Sets the xml remappings that should be performed.
*
* @param array $remappings An array of the form array(array(string, string))
*/
public function setXmlRemappings(array $remappings)
{
$this->xmlRemappings = $remappings;
}
/**
* Gets the xml remappings that should be performed.
*
* @return array $remappings an array of the form array(array(string, string))
*/
public function getXmlRemappings()
{
return $this->xmlRemappings;
}
/**
* Sets whether to add default values for this array if it has not been
* defined in any of the configuration files.
*
* @param bool $boolean
*/
public function setAddIfNotSet($boolean)
{
$this->addIfNotSet = (bool) $boolean;
}
/**
* Sets whether false is allowed as value indicating that the array should be unset.
*
* @param bool $allow
*/
public function setAllowFalse($allow)
{
$this->allowFalse = (bool) $allow;
}
/**
* Sets whether new keys can be defined in subsequent configurations.
*
* @param bool $allow
*/
public function setAllowNewKeys($allow)
{
$this->allowNewKeys = (bool) $allow;
}
/**
* Sets if deep merging should occur.
*
* @param bool $boolean
*/
public function setPerformDeepMerging($boolean)
{
$this->performDeepMerging = (bool) $boolean;
}
/**
* Whether extra keys should just be ignore without an exception.
*
* @param bool $boolean To allow extra keys
* @param bool $remove To remove extra keys
*/
public function setIgnoreExtraKeys($boolean, $remove = true)
{
$this->ignoreExtraKeys = (bool) $boolean;
$this->removeExtraKeys = $this->ignoreExtraKeys && $remove;
}
/**
* {@inheritdoc}
*/
public function setName($name)
{
$this->name = $name;
}
/**
* {@inheritdoc}
*/
public function hasDefaultValue()
{
return $this->addIfNotSet;
}
/**
* {@inheritdoc}
*/
public function getDefaultValue()
{
if (!$this->hasDefaultValue()) {
throw new \RuntimeException(sprintf('The node at path "%s" has no default value.', $this->getPath()));
}
$defaults = array();
foreach ($this->children as $name => $child) {
if ($child->hasDefaultValue()) {
$defaults[$name] = $child->getDefaultValue();
}
}
return $defaults;
}
/**
* Adds a child node.
*
* @throws \InvalidArgumentException when the child node has no name
* @throws \InvalidArgumentException when the child node's name is not unique
*/
public function addChild(NodeInterface $node)
{
$name = $node->getName();
if (!\strlen($name)) {
throw new \InvalidArgumentException('Child nodes must be named.');
}
if (isset($this->children[$name])) {
throw new \InvalidArgumentException(sprintf('A child node named "%s" already exists.', $name));
}
$this->children[$name] = $node;
}
/**
* Finalizes the value of this node.
*
* @param mixed $value
*
* @return mixed The finalised value
*
* @throws UnsetKeyException
* @throws InvalidConfigurationException if the node doesn't have enough children
*/
protected function finalizeValue($value)
{
if (false === $value) {
throw new UnsetKeyException(sprintf('Unsetting key for path "%s", value: %s', $this->getPath(), json_encode($value)));
}
foreach ($this->children as $name => $child) {
if (!array_key_exists($name, $value)) {
if ($child->isRequired()) {
$ex = new InvalidConfigurationException(sprintf('The child node "%s" at path "%s" must be configured.', $name, $this->getPath()));
$ex->setPath($this->getPath());
throw $ex;
}
if ($child->hasDefaultValue()) {
$value[$name] = $child->getDefaultValue();
}
continue;
}
if ($child->isDeprecated()) {
@trigger_error($child->getDeprecationMessage($name, $this->getPath()), E_USER_DEPRECATED);
}
try {
$value[$name] = $child->finalize($value[$name]);
} catch (UnsetKeyException $e) {
unset($value[$name]);
}
}
return $value;
}
/**
* Validates the type of the value.
*
* @param mixed $value
*
* @throws InvalidTypeException
*/
protected function validateType($value)
{
if (!\is_array($value) && (!$this->allowFalse || false !== $value)) {
$ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected array, but got %s', $this->getPath(), \gettype($value)));
if ($hint = $this->getInfo()) {
$ex->addHint($hint);
}
$ex->setPath($this->getPath());
throw $ex;
}
}
/**
* Normalizes the value.
*
* @param mixed $value The value to normalize
*
* @return mixed The normalized value
*
* @throws InvalidConfigurationException
*/
protected function normalizeValue($value)
{
if (false === $value) {
return $value;
}
$value = $this->remapXml($value);
$normalized = array();
foreach ($value as $name => $val) {
if (isset($this->children[$name])) {
$normalized[$name] = $this->children[$name]->normalize($val);
unset($value[$name]);
} elseif (!$this->removeExtraKeys) {
$normalized[$name] = $val;
}
}
// if extra fields are present, throw exception
if (\count($value) && !$this->ignoreExtraKeys) {
$ex = new InvalidConfigurationException(sprintf('Unrecognized option%s "%s" under "%s"', 1 === \count($value) ? '' : 's', implode(', ', array_keys($value)), $this->getPath()));
$ex->setPath($this->getPath());
throw $ex;
}
return $normalized;
}
/**
* Remaps multiple singular values to a single plural value.
*
* @param array $value The source values
*
* @return array The remapped values
*/
protected function remapXml($value)
{
foreach ($this->xmlRemappings as list($singular, $plural)) {
if (!isset($value[$singular])) {
continue;
}
$value[$plural] = Processor::normalizeConfig($value, $singular, $plural);
unset($value[$singular]);
}
return $value;
}
/**
* Merges values together.
*
* @param mixed $leftSide The left side to merge
* @param mixed $rightSide The right side to merge
*
* @return mixed The merged values
*
* @throws InvalidConfigurationException
* @throws \RuntimeException
*/
protected function mergeValues($leftSide, $rightSide)
{
if (false === $rightSide) {
// if this is still false after the last config has been merged the
// finalization pass will take care of removing this key entirely
return false;
}
if (false === $leftSide || !$this->performDeepMerging) {
return $rightSide;
}
foreach ($rightSide as $k => $v) {
// no conflict
if (!array_key_exists($k, $leftSide)) {
if (!$this->allowNewKeys) {
$ex = new InvalidConfigurationException(sprintf('You are not allowed to define new elements for path "%s". Please define all elements for this path in one config file. If you are trying to overwrite an element, make sure you redefine it with the same name.', $this->getPath()));
$ex->setPath($this->getPath());
throw $ex;
}
$leftSide[$k] = $v;
continue;
}
if (!isset($this->children[$k])) {
throw new \RuntimeException('merge() expects a normalized config array.');
}
$leftSide[$k] = $this->children[$k]->merge($leftSide[$k], $v);
}
return $leftSide;
}
}

View file

@ -0,0 +1,362 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\Exception;
use Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
/**
* The base node class.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
abstract class BaseNode implements NodeInterface
{
protected $name;
protected $parent;
protected $normalizationClosures = array();
protected $finalValidationClosures = array();
protected $allowOverwrite = true;
protected $required = false;
protected $deprecationMessage = null;
protected $equivalentValues = array();
protected $attributes = array();
/**
* @param string|null $name The name of the node
* @param NodeInterface|null $parent The parent of this node
*
* @throws \InvalidArgumentException if the name contains a period
*/
public function __construct($name, NodeInterface $parent = null)
{
if (false !== strpos($name = (string) $name, '.')) {
throw new \InvalidArgumentException('The name must not contain ".".');
}
$this->name = $name;
$this->parent = $parent;
}
public function setAttribute($key, $value)
{
$this->attributes[$key] = $value;
}
public function getAttribute($key, $default = null)
{
return isset($this->attributes[$key]) ? $this->attributes[$key] : $default;
}
public function hasAttribute($key)
{
return isset($this->attributes[$key]);
}
public function getAttributes()
{
return $this->attributes;
}
public function setAttributes(array $attributes)
{
$this->attributes = $attributes;
}
public function removeAttribute($key)
{
unset($this->attributes[$key]);
}
/**
* Sets an info message.
*
* @param string $info
*/
public function setInfo($info)
{
$this->setAttribute('info', $info);
}
/**
* Returns info message.
*
* @return string The info text
*/
public function getInfo()
{
return $this->getAttribute('info');
}
/**
* Sets the example configuration for this node.
*
* @param string|array $example
*/
public function setExample($example)
{
$this->setAttribute('example', $example);
}
/**
* Retrieves the example configuration for this node.
*
* @return string|array The example
*/
public function getExample()
{
return $this->getAttribute('example');
}
/**
* Adds an equivalent value.
*
* @param mixed $originalValue
* @param mixed $equivalentValue
*/
public function addEquivalentValue($originalValue, $equivalentValue)
{
$this->equivalentValues[] = array($originalValue, $equivalentValue);
}
/**
* Set this node as required.
*
* @param bool $boolean Required node
*/
public function setRequired($boolean)
{
$this->required = (bool) $boolean;
}
/**
* Sets this node as deprecated.
*
* You can use %node% and %path% placeholders in your message to display,
* respectively, the node name and its complete path.
*
* @param string|null $message Deprecated message
*/
public function setDeprecated($message)
{
$this->deprecationMessage = $message;
}
/**
* Sets if this node can be overridden.
*
* @param bool $allow
*/
public function setAllowOverwrite($allow)
{
$this->allowOverwrite = (bool) $allow;
}
/**
* Sets the closures used for normalization.
*
* @param \Closure[] $closures An array of Closures used for normalization
*/
public function setNormalizationClosures(array $closures)
{
$this->normalizationClosures = $closures;
}
/**
* Sets the closures used for final validation.
*
* @param \Closure[] $closures An array of Closures used for final validation
*/
public function setFinalValidationClosures(array $closures)
{
$this->finalValidationClosures = $closures;
}
/**
* {@inheritdoc}
*/
public function isRequired()
{
return $this->required;
}
/**
* Checks if this node is deprecated.
*
* @return bool
*/
public function isDeprecated()
{
return null !== $this->deprecationMessage;
}
/**
* Returns the deprecated message.
*
* @param string $node the configuration node name
* @param string $path the path of the node
*
* @return string
*/
public function getDeprecationMessage($node, $path)
{
return strtr($this->deprecationMessage, array('%node%' => $node, '%path%' => $path));
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->name;
}
/**
* {@inheritdoc}
*/
public function getPath()
{
$path = $this->name;
if (null !== $this->parent) {
$path = $this->parent->getPath().'.'.$path;
}
return $path;
}
/**
* {@inheritdoc}
*/
final public function merge($leftSide, $rightSide)
{
if (!$this->allowOverwrite) {
throw new ForbiddenOverwriteException(sprintf('Configuration path "%s" cannot be overwritten. You have to define all options for this path, and any of its sub-paths in one configuration section.', $this->getPath()));
}
$this->validateType($leftSide);
$this->validateType($rightSide);
return $this->mergeValues($leftSide, $rightSide);
}
/**
* {@inheritdoc}
*/
final public function normalize($value)
{
$value = $this->preNormalize($value);
// run custom normalization closures
foreach ($this->normalizationClosures as $closure) {
$value = $closure($value);
}
// replace value with their equivalent
foreach ($this->equivalentValues as $data) {
if ($data[0] === $value) {
$value = $data[1];
}
}
// validate type
$this->validateType($value);
// normalize value
return $this->normalizeValue($value);
}
/**
* Normalizes the value before any other normalization is applied.
*
* @param $value
*
* @return $value The normalized array value
*/
protected function preNormalize($value)
{
return $value;
}
/**
* Returns parent node for this node.
*
* @return NodeInterface|null
*/
public function getParent()
{
return $this->parent;
}
/**
* {@inheritdoc}
*/
final public function finalize($value)
{
$this->validateType($value);
$value = $this->finalizeValue($value);
// Perform validation on the final value if a closure has been set.
// The closure is also allowed to return another value.
foreach ($this->finalValidationClosures as $closure) {
try {
$value = $closure($value);
} catch (Exception $e) {
throw $e;
} catch (\Exception $e) {
throw new InvalidConfigurationException(sprintf('Invalid configuration for path "%s": %s', $this->getPath(), $e->getMessage()), $e->getCode(), $e);
}
}
return $value;
}
/**
* Validates the type of a Node.
*
* @param mixed $value The value to validate
*
* @throws InvalidTypeException when the value is invalid
*/
abstract protected function validateType($value);
/**
* Normalizes the value.
*
* @param mixed $value The value to normalize
*
* @return mixed The normalized value
*/
abstract protected function normalizeValue($value);
/**
* Merges two values together.
*
* @param mixed $leftSide
* @param mixed $rightSide
*
* @return mixed The merged value
*/
abstract protected function mergeValues($leftSide, $rightSide);
/**
* Finalizes a value.
*
* @param mixed $value The value to finalize
*
* @return mixed The finalized value
*/
abstract protected function finalizeValue($value);
}

View file

@ -0,0 +1,47 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
/**
* This node represents a Boolean value in the config tree.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class BooleanNode extends ScalarNode
{
/**
* {@inheritdoc}
*/
protected function validateType($value)
{
if (!\is_bool($value)) {
$ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected boolean, but got %s.', $this->getPath(), \gettype($value)));
if ($hint = $this->getInfo()) {
$ex->addHint($hint);
}
$ex->setPath($this->getPath());
throw $ex;
}
}
/**
* {@inheritdoc}
*/
protected function isValueEmpty($value)
{
// a boolean value cannot be empty
return false;
}
}

View file

@ -0,0 +1,522 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\ArrayNode;
use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
use Symfony\Component\Config\Definition\PrototypedArrayNode;
/**
* This class provides a fluent interface for defining an array node.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinitionInterface
{
protected $performDeepMerging = true;
protected $ignoreExtraKeys = false;
protected $removeExtraKeys = true;
protected $children = array();
protected $prototype;
protected $atLeastOne = false;
protected $allowNewKeys = true;
protected $key;
protected $removeKeyItem;
protected $addDefaults = false;
protected $addDefaultChildren = false;
protected $nodeBuilder;
protected $normalizeKeys = true;
/**
* {@inheritdoc}
*/
public function __construct($name, NodeParentInterface $parent = null)
{
parent::__construct($name, $parent);
$this->nullEquivalent = array();
$this->trueEquivalent = array();
}
/**
* {@inheritdoc}
*/
public function setBuilder(NodeBuilder $builder)
{
$this->nodeBuilder = $builder;
}
/**
* {@inheritdoc}
*/
public function children()
{
return $this->getNodeBuilder();
}
/**
* Sets a prototype for child nodes.
*
* @param string $type The type of node
*
* @return NodeDefinition
*/
public function prototype($type)
{
return $this->prototype = $this->getNodeBuilder()->node(null, $type)->setParent($this);
}
/**
* @return VariableNodeDefinition
*/
public function variablePrototype()
{
return $this->prototype('variable');
}
/**
* @return ScalarNodeDefinition
*/
public function scalarPrototype()
{
return $this->prototype('scalar');
}
/**
* @return BooleanNodeDefinition
*/
public function booleanPrototype()
{
return $this->prototype('boolean');
}
/**
* @return IntegerNodeDefinition
*/
public function integerPrototype()
{
return $this->prototype('integer');
}
/**
* @return FloatNodeDefinition
*/
public function floatPrototype()
{
return $this->prototype('float');
}
/**
* @return ArrayNodeDefinition
*/
public function arrayPrototype()
{
return $this->prototype('array');
}
/**
* @return EnumNodeDefinition
*/
public function enumPrototype()
{
return $this->prototype('enum');
}
/**
* Adds the default value if the node is not set in the configuration.
*
* This method is applicable to concrete nodes only (not to prototype nodes).
* If this function has been called and the node is not set during the finalization
* phase, it's default value will be derived from its children default values.
*
* @return $this
*/
public function addDefaultsIfNotSet()
{
$this->addDefaults = true;
return $this;
}
/**
* Adds children with a default value when none are defined.
*
* This method is applicable to prototype nodes only.
*
* @param int|string|array|null $children The number of children|The child name|The children names to be added
*
* @return $this
*/
public function addDefaultChildrenIfNoneSet($children = null)
{
$this->addDefaultChildren = $children;
return $this;
}
/**
* Requires the node to have at least one element.
*
* This method is applicable to prototype nodes only.
*
* @return $this
*/
public function requiresAtLeastOneElement()
{
$this->atLeastOne = true;
return $this;
}
/**
* Disallows adding news keys in a subsequent configuration.
*
* If used all keys have to be defined in the same configuration file.
*
* @return $this
*/
public function disallowNewKeysInSubsequentConfigs()
{
$this->allowNewKeys = false;
return $this;
}
/**
* Sets a normalization rule for XML configurations.
*
* @param string $singular The key to remap
* @param string $plural The plural of the key for irregular plurals
*
* @return $this
*/
public function fixXmlConfig($singular, $plural = null)
{
$this->normalization()->remap($singular, $plural);
return $this;
}
/**
* Sets the attribute which value is to be used as key.
*
* This is useful when you have an indexed array that should be an
* associative array. You can select an item from within the array
* to be the key of the particular item. For example, if "id" is the
* "key", then:
*
* array(
* array('id' => 'my_name', 'foo' => 'bar'),
* );
*
* becomes
*
* array(
* 'my_name' => array('foo' => 'bar'),
* );
*
* If you'd like "'id' => 'my_name'" to still be present in the resulting
* array, then you can set the second argument of this method to false.
*
* This method is applicable to prototype nodes only.
*
* @param string $name The name of the key
* @param bool $removeKeyItem Whether or not the key item should be removed
*
* @return $this
*/
public function useAttributeAsKey($name, $removeKeyItem = true)
{
$this->key = $name;
$this->removeKeyItem = $removeKeyItem;
return $this;
}
/**
* Sets whether the node can be unset.
*
* @param bool $allow
*
* @return $this
*/
public function canBeUnset($allow = true)
{
$this->merge()->allowUnset($allow);
return $this;
}
/**
* Adds an "enabled" boolean to enable the current section.
*
* By default, the section is disabled. If any configuration is specified then
* the node will be automatically enabled:
*
* enableableArrayNode: {enabled: true, ...} # The config is enabled & default values get overridden
* enableableArrayNode: ~ # The config is enabled & use the default values
* enableableArrayNode: true # The config is enabled & use the default values
* enableableArrayNode: {other: value, ...} # The config is enabled & default values get overridden
* enableableArrayNode: {enabled: false, ...} # The config is disabled
* enableableArrayNode: false # The config is disabled
*
* @return $this
*/
public function canBeEnabled()
{
$this
->addDefaultsIfNotSet()
->treatFalseLike(array('enabled' => false))
->treatTrueLike(array('enabled' => true))
->treatNullLike(array('enabled' => true))
->beforeNormalization()
->ifArray()
->then(function ($v) {
$v['enabled'] = isset($v['enabled']) ? $v['enabled'] : true;
return $v;
})
->end()
->children()
->booleanNode('enabled')
->defaultFalse()
;
return $this;
}
/**
* Adds an "enabled" boolean to enable the current section.
*
* By default, the section is enabled.
*
* @return $this
*/
public function canBeDisabled()
{
$this
->addDefaultsIfNotSet()
->treatFalseLike(array('enabled' => false))
->treatTrueLike(array('enabled' => true))
->treatNullLike(array('enabled' => true))
->children()
->booleanNode('enabled')
->defaultTrue()
;
return $this;
}
/**
* Disables the deep merging of the node.
*
* @return $this
*/
public function performNoDeepMerging()
{
$this->performDeepMerging = false;
return $this;
}
/**
* Allows extra config keys to be specified under an array without
* throwing an exception.
*
* Those config values are simply ignored and removed from the
* resulting array. This should be used only in special cases where
* you want to send an entire configuration array through a special
* tree that processes only part of the array.
*
* @param bool $remove Whether to remove the extra keys
*
* @return $this
*/
public function ignoreExtraKeys($remove = true)
{
$this->ignoreExtraKeys = true;
$this->removeExtraKeys = $remove;
return $this;
}
/**
* Sets key normalization.
*
* @param bool $bool Whether to enable key normalization
*
* @return $this
*/
public function normalizeKeys($bool)
{
$this->normalizeKeys = (bool) $bool;
return $this;
}
/**
* {@inheritdoc}
*/
public function append(NodeDefinition $node)
{
$this->children[$node->name] = $node->setParent($this);
return $this;
}
/**
* Returns a node builder to be used to add children and prototype.
*
* @return NodeBuilder The node builder
*/
protected function getNodeBuilder()
{
if (null === $this->nodeBuilder) {
$this->nodeBuilder = new NodeBuilder();
}
return $this->nodeBuilder->setParent($this);
}
/**
* {@inheritdoc}
*/
protected function createNode()
{
if (null === $this->prototype) {
$node = new ArrayNode($this->name, $this->parent);
$this->validateConcreteNode($node);
$node->setAddIfNotSet($this->addDefaults);
foreach ($this->children as $child) {
$child->parent = $node;
$node->addChild($child->getNode());
}
} else {
$node = new PrototypedArrayNode($this->name, $this->parent);
$this->validatePrototypeNode($node);
if (null !== $this->key) {
$node->setKeyAttribute($this->key, $this->removeKeyItem);
}
if (false === $this->allowEmptyValue) {
@trigger_error(sprintf('Using %s::cannotBeEmpty() at path "%s" has no effect, consider requiresAtLeastOneElement() instead. In 4.0 both methods will behave the same.', __CLASS__, $node->getPath()), E_USER_DEPRECATED);
}
if (true === $this->atLeastOne) {
$node->setMinNumberOfElements(1);
}
if ($this->default) {
$node->setDefaultValue($this->defaultValue);
}
if (false !== $this->addDefaultChildren) {
$node->setAddChildrenIfNoneSet($this->addDefaultChildren);
if ($this->prototype instanceof static && null === $this->prototype->prototype) {
$this->prototype->addDefaultsIfNotSet();
}
}
$this->prototype->parent = $node;
$node->setPrototype($this->prototype->getNode());
}
$node->setAllowNewKeys($this->allowNewKeys);
$node->addEquivalentValue(null, $this->nullEquivalent);
$node->addEquivalentValue(true, $this->trueEquivalent);
$node->addEquivalentValue(false, $this->falseEquivalent);
$node->setPerformDeepMerging($this->performDeepMerging);
$node->setRequired($this->required);
$node->setDeprecated($this->deprecationMessage);
$node->setIgnoreExtraKeys($this->ignoreExtraKeys, $this->removeExtraKeys);
$node->setNormalizeKeys($this->normalizeKeys);
if (null !== $this->normalization) {
$node->setNormalizationClosures($this->normalization->before);
$node->setXmlRemappings($this->normalization->remappings);
}
if (null !== $this->merge) {
$node->setAllowOverwrite($this->merge->allowOverwrite);
$node->setAllowFalse($this->merge->allowFalse);
}
if (null !== $this->validation) {
$node->setFinalValidationClosures($this->validation->rules);
}
return $node;
}
/**
* Validate the configuration of a concrete node.
*
* @throws InvalidDefinitionException
*/
protected function validateConcreteNode(ArrayNode $node)
{
$path = $node->getPath();
if (null !== $this->key) {
throw new InvalidDefinitionException(sprintf('->useAttributeAsKey() is not applicable to concrete nodes at path "%s"', $path));
}
if (false === $this->allowEmptyValue) {
@trigger_error(sprintf('->cannotBeEmpty() is not applicable to concrete nodes at path "%s". In 4.0 it will throw an exception.', $path), E_USER_DEPRECATED);
}
if (true === $this->atLeastOne) {
throw new InvalidDefinitionException(sprintf('->requiresAtLeastOneElement() is not applicable to concrete nodes at path "%s"', $path));
}
if ($this->default) {
throw new InvalidDefinitionException(sprintf('->defaultValue() is not applicable to concrete nodes at path "%s"', $path));
}
if (false !== $this->addDefaultChildren) {
throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() is not applicable to concrete nodes at path "%s"', $path));
}
}
/**
* Validate the configuration of a prototype node.
*
* @throws InvalidDefinitionException
*/
protected function validatePrototypeNode(PrototypedArrayNode $node)
{
$path = $node->getPath();
if ($this->addDefaults) {
throw new InvalidDefinitionException(sprintf('->addDefaultsIfNotSet() is not applicable to prototype nodes at path "%s"', $path));
}
if (false !== $this->addDefaultChildren) {
if ($this->default) {
throw new InvalidDefinitionException(sprintf('A default value and default children might not be used together at path "%s"', $path));
}
if (null !== $this->key && (null === $this->addDefaultChildren || \is_int($this->addDefaultChildren) && $this->addDefaultChildren > 0)) {
throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() should set default children names as ->useAttributeAsKey() is used at path "%s"', $path));
}
if (null === $this->key && (\is_string($this->addDefaultChildren) || \is_array($this->addDefaultChildren))) {
throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() might not set default children names as ->useAttributeAsKey() is not used at path "%s"', $path));
}
}
}
}

View file

@ -0,0 +1,53 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\BooleanNode;
use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
/**
* This class provides a fluent interface for defining a node.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class BooleanNodeDefinition extends ScalarNodeDefinition
{
/**
* {@inheritdoc}
*/
public function __construct($name, NodeParentInterface $parent = null)
{
parent::__construct($name, $parent);
$this->nullEquivalent = true;
}
/**
* Instantiate a Node.
*
* @return BooleanNode The node
*/
protected function instantiateNode()
{
return new BooleanNode($this->name, $this->parent);
}
/**
* {@inheritdoc}
*
* @throws InvalidDefinitionException
*/
public function cannotBeEmpty()
{
throw new InvalidDefinitionException('->cannotBeEmpty() is not applicable to BooleanNodeDefinition.');
}
}

View file

@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\EnumNode;
/**
* Enum Node Definition.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class EnumNodeDefinition extends ScalarNodeDefinition
{
private $values;
/**
* @return $this
*/
public function values(array $values)
{
$values = array_unique($values);
if (empty($values)) {
throw new \InvalidArgumentException('->values() must be called with at least one value.');
}
$this->values = $values;
return $this;
}
/**
* Instantiate a Node.
*
* @return EnumNode The node
*
* @throws \RuntimeException
*/
protected function instantiateNode()
{
if (null === $this->values) {
throw new \RuntimeException('You must call ->values() on enum nodes.');
}
return new EnumNode($this->name, $this->parent, $this->values);
}
}

View file

@ -0,0 +1,248 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\Exception\UnsetKeyException;
/**
* This class builds an if expression.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
* @author Christophe Coevoet <stof@notk.org>
*/
class ExprBuilder
{
protected $node;
public $ifPart;
public $thenPart;
public function __construct(NodeDefinition $node)
{
$this->node = $node;
}
/**
* Marks the expression as being always used.
*
* @return $this
*/
public function always(\Closure $then = null)
{
$this->ifPart = function ($v) { return true; };
if (null !== $then) {
$this->thenPart = $then;
}
return $this;
}
/**
* Sets a closure to use as tests.
*
* The default one tests if the value is true.
*
* @return $this
*/
public function ifTrue(\Closure $closure = null)
{
if (null === $closure) {
$closure = function ($v) { return true === $v; };
}
$this->ifPart = $closure;
return $this;
}
/**
* Tests if the value is a string.
*
* @return $this
*/
public function ifString()
{
$this->ifPart = function ($v) { return \is_string($v); };
return $this;
}
/**
* Tests if the value is null.
*
* @return $this
*/
public function ifNull()
{
$this->ifPart = function ($v) { return null === $v; };
return $this;
}
/**
* Tests if the value is empty.
*
* @return ExprBuilder
*/
public function ifEmpty()
{
$this->ifPart = function ($v) { return empty($v); };
return $this;
}
/**
* Tests if the value is an array.
*
* @return $this
*/
public function ifArray()
{
$this->ifPart = function ($v) { return \is_array($v); };
return $this;
}
/**
* Tests if the value is in an array.
*
* @return $this
*/
public function ifInArray(array $array)
{
$this->ifPart = function ($v) use ($array) { return \in_array($v, $array, true); };
return $this;
}
/**
* Tests if the value is not in an array.
*
* @return $this
*/
public function ifNotInArray(array $array)
{
$this->ifPart = function ($v) use ($array) { return !\in_array($v, $array, true); };
return $this;
}
/**
* Transforms variables of any type into an array.
*
* @return $this
*/
public function castToArray()
{
$this->ifPart = function ($v) { return !\is_array($v); };
$this->thenPart = function ($v) { return array($v); };
return $this;
}
/**
* Sets the closure to run if the test pass.
*
* @return $this
*/
public function then(\Closure $closure)
{
$this->thenPart = $closure;
return $this;
}
/**
* Sets a closure returning an empty array.
*
* @return $this
*/
public function thenEmptyArray()
{
$this->thenPart = function ($v) { return array(); };
return $this;
}
/**
* Sets a closure marking the value as invalid at validation time.
*
* if you want to add the value of the node in your message just use a %s placeholder.
*
* @param string $message
*
* @return $this
*
* @throws \InvalidArgumentException
*/
public function thenInvalid($message)
{
$this->thenPart = function ($v) use ($message) { throw new \InvalidArgumentException(sprintf($message, json_encode($v))); };
return $this;
}
/**
* Sets a closure unsetting this key of the array at validation time.
*
* @return $this
*
* @throws UnsetKeyException
*/
public function thenUnset()
{
$this->thenPart = function ($v) { throw new UnsetKeyException('Unsetting key'); };
return $this;
}
/**
* Returns the related node.
*
* @return NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition
*
* @throws \RuntimeException
*/
public function end()
{
if (null === $this->ifPart) {
throw new \RuntimeException('You must specify an if part.');
}
if (null === $this->thenPart) {
throw new \RuntimeException('You must specify a then part.');
}
return $this->node;
}
/**
* Builds the expressions.
*
* @param ExprBuilder[] $expressions An array of ExprBuilder instances to build
*
* @return array
*/
public static function buildExpressions(array $expressions)
{
foreach ($expressions as $k => $expr) {
if ($expr instanceof self) {
$if = $expr->ifPart;
$then = $expr->thenPart;
$expressions[$k] = function ($v) use ($if, $then) {
return $if($v) ? $then($v) : $v;
};
}
}
return $expressions;
}
}

View file

@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\FloatNode;
/**
* This class provides a fluent interface for defining a float node.
*
* @author Jeanmonod David <david.jeanmonod@gmail.com>
*/
class FloatNodeDefinition extends NumericNodeDefinition
{
/**
* Instantiates a Node.
*
* @return FloatNode The node
*/
protected function instantiateNode()
{
return new FloatNode($this->name, $this->parent, $this->min, $this->max);
}
}

View file

@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\IntegerNode;
/**
* This class provides a fluent interface for defining an integer node.
*
* @author Jeanmonod David <david.jeanmonod@gmail.com>
*/
class IntegerNodeDefinition extends NumericNodeDefinition
{
/**
* Instantiates a Node.
*
* @return IntegerNode The node
*/
protected function instantiateNode()
{
return new IntegerNode($this->name, $this->parent, $this->min, $this->max);
}
}

View file

@ -0,0 +1,67 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
/**
* This class builds merge conditions.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class MergeBuilder
{
protected $node;
public $allowFalse = false;
public $allowOverwrite = true;
public function __construct(NodeDefinition $node)
{
$this->node = $node;
}
/**
* Sets whether the node can be unset.
*
* @param bool $allow
*
* @return $this
*/
public function allowUnset($allow = true)
{
$this->allowFalse = $allow;
return $this;
}
/**
* Sets whether the node can be overwritten.
*
* @param bool $deny Whether the overwriting is forbidden or not
*
* @return $this
*/
public function denyOverwrite($deny = true)
{
$this->allowOverwrite = !$deny;
return $this;
}
/**
* Returns the related node.
*
* @return NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition
*/
public function end()
{
return $this->node;
}
}

View file

@ -0,0 +1,238 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
/**
* This class provides a fluent interface for building a node.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class NodeBuilder implements NodeParentInterface
{
protected $parent;
protected $nodeMapping;
public function __construct()
{
$this->nodeMapping = array(
'variable' => __NAMESPACE__.'\\VariableNodeDefinition',
'scalar' => __NAMESPACE__.'\\ScalarNodeDefinition',
'boolean' => __NAMESPACE__.'\\BooleanNodeDefinition',
'integer' => __NAMESPACE__.'\\IntegerNodeDefinition',
'float' => __NAMESPACE__.'\\FloatNodeDefinition',
'array' => __NAMESPACE__.'\\ArrayNodeDefinition',
'enum' => __NAMESPACE__.'\\EnumNodeDefinition',
);
}
/**
* Set the parent node.
*
* @return $this
*/
public function setParent(ParentNodeDefinitionInterface $parent = null)
{
$this->parent = $parent;
return $this;
}
/**
* Creates a child array node.
*
* @param string $name The name of the node
*
* @return ArrayNodeDefinition The child node
*/
public function arrayNode($name)
{
return $this->node($name, 'array');
}
/**
* Creates a child scalar node.
*
* @param string $name The name of the node
*
* @return ScalarNodeDefinition The child node
*/
public function scalarNode($name)
{
return $this->node($name, 'scalar');
}
/**
* Creates a child Boolean node.
*
* @param string $name The name of the node
*
* @return BooleanNodeDefinition The child node
*/
public function booleanNode($name)
{
return $this->node($name, 'boolean');
}
/**
* Creates a child integer node.
*
* @param string $name The name of the node
*
* @return IntegerNodeDefinition The child node
*/
public function integerNode($name)
{
return $this->node($name, 'integer');
}
/**
* Creates a child float node.
*
* @param string $name The name of the node
*
* @return FloatNodeDefinition The child node
*/
public function floatNode($name)
{
return $this->node($name, 'float');
}
/**
* Creates a child EnumNode.
*
* @param string $name
*
* @return EnumNodeDefinition
*/
public function enumNode($name)
{
return $this->node($name, 'enum');
}
/**
* Creates a child variable node.
*
* @param string $name The name of the node
*
* @return VariableNodeDefinition The builder of the child node
*/
public function variableNode($name)
{
return $this->node($name, 'variable');
}
/**
* Returns the parent node.
*
* @return ParentNodeDefinitionInterface|NodeDefinition The parent node
*/
public function end()
{
return $this->parent;
}
/**
* Creates a child node.
*
* @param string|null $name The name of the node
* @param string $type The type of the node
*
* @return NodeDefinition The child node
*
* @throws \RuntimeException When the node type is not registered
* @throws \RuntimeException When the node class is not found
*/
public function node($name, $type)
{
$class = $this->getNodeClass($type);
$node = new $class($name);
$this->append($node);
return $node;
}
/**
* Appends a node definition.
*
* Usage:
*
* $node = new ArrayNodeDefinition('name')
* ->children()
* ->scalarNode('foo')->end()
* ->scalarNode('baz')->end()
* ->append($this->getBarNodeDefinition())
* ->end()
* ;
*
* @return $this
*/
public function append(NodeDefinition $node)
{
if ($node instanceof ParentNodeDefinitionInterface) {
$builder = clone $this;
$builder->setParent(null);
$node->setBuilder($builder);
}
if (null !== $this->parent) {
$this->parent->append($node);
// Make this builder the node parent to allow for a fluid interface
$node->setParent($this);
}
return $this;
}
/**
* Adds or overrides a node Type.
*
* @param string $type The name of the type
* @param string $class The fully qualified name the node definition class
*
* @return $this
*/
public function setNodeClass($type, $class)
{
$this->nodeMapping[strtolower($type)] = $class;
return $this;
}
/**
* Returns the class name of the node definition.
*
* @param string $type The node type
*
* @return string The node definition class name
*
* @throws \RuntimeException When the node type is not registered
* @throws \RuntimeException When the node class is not found
*/
protected function getNodeClass($type)
{
$type = strtolower($type);
if (!isset($this->nodeMapping[$type])) {
throw new \RuntimeException(sprintf('The node type "%s" is not registered.', $type));
}
$class = $this->nodeMapping[$type];
if (!class_exists($class)) {
throw new \RuntimeException(sprintf('The node class "%s" does not exist.', $class));
}
return $class;
}
}

View file

@ -0,0 +1,353 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
use Symfony\Component\Config\Definition\NodeInterface;
/**
* This class provides a fluent interface for defining a node.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
abstract class NodeDefinition implements NodeParentInterface
{
protected $name;
protected $normalization;
protected $validation;
protected $defaultValue;
protected $default = false;
protected $required = false;
protected $deprecationMessage = null;
protected $merge;
protected $allowEmptyValue = true;
protected $nullEquivalent;
protected $trueEquivalent = true;
protected $falseEquivalent = false;
protected $parent;
protected $attributes = array();
/**
* @param string|null $name The name of the node
* @param NodeParentInterface|null $parent The parent
*/
public function __construct($name, NodeParentInterface $parent = null)
{
$this->parent = $parent;
$this->name = $name;
}
/**
* Sets the parent node.
*
* @return $this
*/
public function setParent(NodeParentInterface $parent)
{
$this->parent = $parent;
return $this;
}
/**
* Sets info message.
*
* @param string $info The info text
*
* @return $this
*/
public function info($info)
{
return $this->attribute('info', $info);
}
/**
* Sets example configuration.
*
* @param string|array $example
*
* @return $this
*/
public function example($example)
{
return $this->attribute('example', $example);
}
/**
* Sets an attribute on the node.
*
* @param string $key
* @param mixed $value
*
* @return $this
*/
public function attribute($key, $value)
{
$this->attributes[$key] = $value;
return $this;
}
/**
* Returns the parent node.
*
* @return NodeParentInterface|NodeBuilder|NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition|null The builder of the parent node
*/
public function end()
{
return $this->parent;
}
/**
* Creates the node.
*
* @param bool $forceRootNode Whether to force this node as the root node
*
* @return NodeInterface
*/
public function getNode($forceRootNode = false)
{
if ($forceRootNode) {
$this->parent = null;
}
if (null !== $this->normalization) {
$this->normalization->before = ExprBuilder::buildExpressions($this->normalization->before);
}
if (null !== $this->validation) {
$this->validation->rules = ExprBuilder::buildExpressions($this->validation->rules);
}
$node = $this->createNode();
$node->setAttributes($this->attributes);
return $node;
}
/**
* Sets the default value.
*
* @param mixed $value The default value
*
* @return $this
*/
public function defaultValue($value)
{
$this->default = true;
$this->defaultValue = $value;
return $this;
}
/**
* Sets the node as required.
*
* @return $this
*/
public function isRequired()
{
$this->required = true;
return $this;
}
/**
* Sets the node as deprecated.
*
* You can use %node% and %path% placeholders in your message to display,
* respectively, the node name and its complete path.
*
* @param string $message Deprecation message
*
* @return $this
*/
public function setDeprecated($message = 'The child node "%node%" at path "%path%" is deprecated.')
{
$this->deprecationMessage = $message;
return $this;
}
/**
* Sets the equivalent value used when the node contains null.
*
* @param mixed $value
*
* @return $this
*/
public function treatNullLike($value)
{
$this->nullEquivalent = $value;
return $this;
}
/**
* Sets the equivalent value used when the node contains true.
*
* @param mixed $value
*
* @return $this
*/
public function treatTrueLike($value)
{
$this->trueEquivalent = $value;
return $this;
}
/**
* Sets the equivalent value used when the node contains false.
*
* @param mixed $value
*
* @return $this
*/
public function treatFalseLike($value)
{
$this->falseEquivalent = $value;
return $this;
}
/**
* Sets null as the default value.
*
* @return $this
*/
public function defaultNull()
{
return $this->defaultValue(null);
}
/**
* Sets true as the default value.
*
* @return $this
*/
public function defaultTrue()
{
return $this->defaultValue(true);
}
/**
* Sets false as the default value.
*
* @return $this
*/
public function defaultFalse()
{
return $this->defaultValue(false);
}
/**
* Sets an expression to run before the normalization.
*
* @return ExprBuilder
*/
public function beforeNormalization()
{
return $this->normalization()->before();
}
/**
* Denies the node value being empty.
*
* @return $this
*/
public function cannotBeEmpty()
{
$this->allowEmptyValue = false;
return $this;
}
/**
* Sets an expression to run for the validation.
*
* The expression receives the value of the node and must return it. It can
* modify it.
* An exception should be thrown when the node is not valid.
*
* @return ExprBuilder
*/
public function validate()
{
return $this->validation()->rule();
}
/**
* Sets whether the node can be overwritten.
*
* @param bool $deny Whether the overwriting is forbidden or not
*
* @return $this
*/
public function cannotBeOverwritten($deny = true)
{
$this->merge()->denyOverwrite($deny);
return $this;
}
/**
* Gets the builder for validation rules.
*
* @return ValidationBuilder
*/
protected function validation()
{
if (null === $this->validation) {
$this->validation = new ValidationBuilder($this);
}
return $this->validation;
}
/**
* Gets the builder for merging rules.
*
* @return MergeBuilder
*/
protected function merge()
{
if (null === $this->merge) {
$this->merge = new MergeBuilder($this);
}
return $this->merge;
}
/**
* Gets the builder for normalization rules.
*
* @return NormalizationBuilder
*/
protected function normalization()
{
if (null === $this->normalization) {
$this->normalization = new NormalizationBuilder($this);
}
return $this->normalization;
}
/**
* Instantiate and configure the node according to this definition.
*
* @return NodeInterface $node The node instance
*
* @throws InvalidDefinitionException When the definition is invalid
*/
abstract protected function createNode();
}

View file

@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
/**
* An interface that must be implemented by all node parents.
*
* @author Victor Berchet <victor@suumit.com>
*/
interface NodeParentInterface
{
}

View file

@ -0,0 +1,60 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
/**
* This class builds normalization conditions.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class NormalizationBuilder
{
protected $node;
public $before = array();
public $remappings = array();
public function __construct(NodeDefinition $node)
{
$this->node = $node;
}
/**
* Registers a key to remap to its plural form.
*
* @param string $key The key to remap
* @param string $plural The plural of the key in case of irregular plural
*
* @return $this
*/
public function remap($key, $plural = null)
{
$this->remappings[] = array($key, null === $plural ? $key.'s' : $plural);
return $this;
}
/**
* Registers a closure to run before the normalization or an expression builder to build it if null is provided.
*
* @return ExprBuilder|$this
*/
public function before(\Closure $closure = null)
{
if (null !== $closure) {
$this->before[] = $closure;
return $this;
}
return $this->before[] = new ExprBuilder($this->node);
}
}

View file

@ -0,0 +1,73 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
/**
* Abstract class that contains common code of integer and float node definitions.
*
* @author David Jeanmonod <david.jeanmonod@gmail.com>
*/
abstract class NumericNodeDefinition extends ScalarNodeDefinition
{
protected $min;
protected $max;
/**
* Ensures that the value is smaller than the given reference.
*
* @param mixed $max
*
* @return $this
*
* @throws \InvalidArgumentException when the constraint is inconsistent
*/
public function max($max)
{
if (isset($this->min) && $this->min > $max) {
throw new \InvalidArgumentException(sprintf('You cannot define a max(%s) as you already have a min(%s)', $max, $this->min));
}
$this->max = $max;
return $this;
}
/**
* Ensures that the value is bigger than the given reference.
*
* @param mixed $min
*
* @return $this
*
* @throws \InvalidArgumentException when the constraint is inconsistent
*/
public function min($min)
{
if (isset($this->max) && $this->max < $min) {
throw new \InvalidArgumentException(sprintf('You cannot define a min(%s) as you already have a max(%s)', $min, $this->max));
}
$this->min = $min;
return $this;
}
/**
* {@inheritdoc}
*
* @throws InvalidDefinitionException
*/
public function cannotBeEmpty()
{
throw new InvalidDefinitionException('->cannotBeEmpty() is not applicable to NumericNodeDefinition.');
}
}

View file

@ -0,0 +1,49 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
/**
* An interface that must be implemented by nodes which can have children.
*
* @author Victor Berchet <victor@suumit.com>
*/
interface ParentNodeDefinitionInterface
{
/**
* Returns a builder to add children nodes.
*
* @return NodeBuilder
*/
public function children();
/**
* Appends a node definition.
*
* Usage:
*
* $node = $parentNode
* ->children()
* ->scalarNode('foo')->end()
* ->scalarNode('baz')->end()
* ->append($this->getBarNodeDefinition())
* ->end()
* ;
*
* @return $this
*/
public function append(NodeDefinition $node);
/**
* Sets a custom children builder.
*/
public function setBuilder(NodeBuilder $builder);
}

View file

@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\ScalarNode;
/**
* This class provides a fluent interface for defining a node.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ScalarNodeDefinition extends VariableNodeDefinition
{
/**
* Instantiate a Node.
*
* @return ScalarNode The node
*/
protected function instantiateNode()
{
return new ScalarNode($this->name, $this->parent);
}
}

View file

@ -0,0 +1,67 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\NodeInterface;
/**
* This is the entry class for building a config tree.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class TreeBuilder implements NodeParentInterface
{
protected $tree;
protected $root;
/**
* @deprecated since 3.4. To be removed in 4.0
*/
protected $builder;
/**
* Creates the root node.
*
* @param string $name The name of the root node
* @param string $type The type of the root node
* @param NodeBuilder $builder A custom node builder instance
*
* @return ArrayNodeDefinition|NodeDefinition The root node (as an ArrayNodeDefinition when the type is 'array')
*
* @throws \RuntimeException When the node type is not supported
*/
public function root($name, $type = 'array', NodeBuilder $builder = null)
{
$builder = $builder ?: new NodeBuilder();
return $this->root = $builder->node($name, $type)->setParent($this);
}
/**
* Builds the tree.
*
* @return NodeInterface
*
* @throws \RuntimeException
*/
public function buildTree()
{
if (null === $this->root) {
throw new \RuntimeException('The configuration tree has no root node.');
}
if (null !== $this->tree) {
return $this->tree;
}
return $this->tree = $this->root->getNode(true);
}
}

View file

@ -0,0 +1,44 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
/**
* This class builds validation conditions.
*
* @author Christophe Coevoet <stof@notk.org>
*/
class ValidationBuilder
{
protected $node;
public $rules = array();
public function __construct(NodeDefinition $node)
{
$this->node = $node;
}
/**
* Registers a closure to run as normalization or an expression builder to build it if null is provided.
*
* @return ExprBuilder|$this
*/
public function rule(\Closure $closure = null)
{
if (null !== $closure) {
$this->rules[] = $closure;
return $this;
}
return $this->rules[] = new ExprBuilder($this->node);
}
}

View file

@ -0,0 +1,65 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\VariableNode;
/**
* This class provides a fluent interface for defining a node.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class VariableNodeDefinition extends NodeDefinition
{
/**
* Instantiate a Node.
*
* @return VariableNode The node
*/
protected function instantiateNode()
{
return new VariableNode($this->name, $this->parent);
}
/**
* {@inheritdoc}
*/
protected function createNode()
{
$node = $this->instantiateNode();
if (null !== $this->normalization) {
$node->setNormalizationClosures($this->normalization->before);
}
if (null !== $this->merge) {
$node->setAllowOverwrite($this->merge->allowOverwrite);
}
if (true === $this->default) {
$node->setDefaultValue($this->defaultValue);
}
$node->setAllowEmptyValue($this->allowEmptyValue);
$node->addEquivalentValue(null, $this->nullEquivalent);
$node->addEquivalentValue(true, $this->trueEquivalent);
$node->addEquivalentValue(false, $this->falseEquivalent);
$node->setRequired($this->required);
$node->setDeprecated($this->deprecationMessage);
if (null !== $this->validation) {
$node->setFinalValidationClosures($this->validation->rules);
}
return $node;
}
}

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
/**
* Configuration interface.
*
* @author Victor Berchet <victor@suumit.com>
*/
interface ConfigurationInterface
{
/**
* Generates the configuration tree builder.
*
* @return \Symfony\Component\Config\Definition\Builder\TreeBuilder The tree builder
*/
public function getConfigTreeBuilder();
}

View file

@ -0,0 +1,311 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Dumper;
use Symfony\Component\Config\Definition\ArrayNode;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\EnumNode;
use Symfony\Component\Config\Definition\NodeInterface;
use Symfony\Component\Config\Definition\PrototypedArrayNode;
/**
* Dumps a XML reference configuration for the given configuration/node instance.
*
* @author Wouter J <waldio.webdesign@gmail.com>
*/
class XmlReferenceDumper
{
private $reference;
public function dump(ConfigurationInterface $configuration, $namespace = null)
{
return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree(), $namespace);
}
public function dumpNode(NodeInterface $node, $namespace = null)
{
$this->reference = '';
$this->writeNode($node, 0, true, $namespace);
$ref = $this->reference;
$this->reference = null;
return $ref;
}
/**
* @param NodeInterface $node
* @param int $depth
* @param bool $root If the node is the root node
* @param string $namespace The namespace of the node
*/
private function writeNode(NodeInterface $node, $depth = 0, $root = false, $namespace = null)
{
$rootName = ($root ? 'config' : $node->getName());
$rootNamespace = ($namespace ?: ($root ? 'http://example.org/schema/dic/'.$node->getName() : null));
// xml remapping
if ($node->getParent()) {
$remapping = array_filter($node->getParent()->getXmlRemappings(), function ($mapping) use ($rootName) {
return $rootName === $mapping[1];
});
if (\count($remapping)) {
list($singular) = current($remapping);
$rootName = $singular;
}
}
$rootName = str_replace('_', '-', $rootName);
$rootAttributes = array();
$rootAttributeComments = array();
$rootChildren = array();
$rootComments = array();
if ($node instanceof ArrayNode) {
$children = $node->getChildren();
// comments about the root node
if ($rootInfo = $node->getInfo()) {
$rootComments[] = $rootInfo;
}
if ($rootNamespace) {
$rootComments[] = 'Namespace: '.$rootNamespace;
}
// render prototyped nodes
if ($node instanceof PrototypedArrayNode) {
$prototype = $node->getPrototype();
$info = 'prototype';
if (null !== $prototype->getInfo()) {
$info .= ': '.$prototype->getInfo();
}
array_unshift($rootComments, $info);
if ($key = $node->getKeyAttribute()) {
$rootAttributes[$key] = str_replace('-', ' ', $rootName).' '.$key;
}
if ($prototype instanceof PrototypedArrayNode) {
$prototype->setName($key);
$children = array($key => $prototype);
} elseif ($prototype instanceof ArrayNode) {
$children = $prototype->getChildren();
} else {
if ($prototype->hasDefaultValue()) {
$prototypeValue = $prototype->getDefaultValue();
} else {
switch (\get_class($prototype)) {
case 'Symfony\Component\Config\Definition\ScalarNode':
$prototypeValue = 'scalar value';
break;
case 'Symfony\Component\Config\Definition\FloatNode':
case 'Symfony\Component\Config\Definition\IntegerNode':
$prototypeValue = 'numeric value';
break;
case 'Symfony\Component\Config\Definition\BooleanNode':
$prototypeValue = 'true|false';
break;
case 'Symfony\Component\Config\Definition\EnumNode':
$prototypeValue = implode('|', array_map('json_encode', $prototype->getValues()));
break;
default:
$prototypeValue = 'value';
}
}
}
}
// get attributes and elements
foreach ($children as $child) {
if (!$child instanceof ArrayNode) {
// get attributes
// metadata
$name = str_replace('_', '-', $child->getName());
$value = '%%%%not_defined%%%%'; // use a string which isn't used in the normal world
// comments
$comments = array();
if ($info = $child->getInfo()) {
$comments[] = $info;
}
if ($example = $child->getExample()) {
$comments[] = 'Example: '.$example;
}
if ($child->isRequired()) {
$comments[] = 'Required';
}
if ($child->isDeprecated()) {
$comments[] = sprintf('Deprecated (%s)', $child->getDeprecationMessage($child->getName(), $node->getPath()));
}
if ($child instanceof EnumNode) {
$comments[] = 'One of '.implode('; ', array_map('json_encode', $child->getValues()));
}
if (\count($comments)) {
$rootAttributeComments[$name] = implode(";\n", $comments);
}
// default values
if ($child->hasDefaultValue()) {
$value = $child->getDefaultValue();
}
// append attribute
$rootAttributes[$name] = $value;
} else {
// get elements
$rootChildren[] = $child;
}
}
}
// render comments
// root node comment
if (\count($rootComments)) {
foreach ($rootComments as $comment) {
$this->writeLine('<!-- '.$comment.' -->', $depth);
}
}
// attribute comments
if (\count($rootAttributeComments)) {
foreach ($rootAttributeComments as $attrName => $comment) {
$commentDepth = $depth + 4 + \strlen($attrName) + 2;
$commentLines = explode("\n", $comment);
$multiline = (\count($commentLines) > 1);
$comment = implode(PHP_EOL.str_repeat(' ', $commentDepth), $commentLines);
if ($multiline) {
$this->writeLine('<!--', $depth);
$this->writeLine($attrName.': '.$comment, $depth + 4);
$this->writeLine('-->', $depth);
} else {
$this->writeLine('<!-- '.$attrName.': '.$comment.' -->', $depth);
}
}
}
// render start tag + attributes
$rootIsVariablePrototype = isset($prototypeValue);
$rootIsEmptyTag = (0 === \count($rootChildren) && !$rootIsVariablePrototype);
$rootOpenTag = '<'.$rootName;
if (1 >= ($attributesCount = \count($rootAttributes))) {
if (1 === $attributesCount) {
$rootOpenTag .= sprintf(' %s="%s"', current(array_keys($rootAttributes)), $this->writeValue(current($rootAttributes)));
}
$rootOpenTag .= $rootIsEmptyTag ? ' />' : '>';
if ($rootIsVariablePrototype) {
$rootOpenTag .= $prototypeValue.'</'.$rootName.'>';
}
$this->writeLine($rootOpenTag, $depth);
} else {
$this->writeLine($rootOpenTag, $depth);
$i = 1;
foreach ($rootAttributes as $attrName => $attrValue) {
$attr = sprintf('%s="%s"', $attrName, $this->writeValue($attrValue));
$this->writeLine($attr, $depth + 4);
if ($attributesCount === $i++) {
$this->writeLine($rootIsEmptyTag ? '/>' : '>', $depth);
if ($rootIsVariablePrototype) {
$rootOpenTag .= $prototypeValue.'</'.$rootName.'>';
}
}
}
}
// render children tags
foreach ($rootChildren as $child) {
$this->writeLine('');
$this->writeNode($child, $depth + 4);
}
// render end tag
if (!$rootIsEmptyTag && !$rootIsVariablePrototype) {
$this->writeLine('');
$rootEndTag = '</'.$rootName.'>';
$this->writeLine($rootEndTag, $depth);
}
}
/**
* Outputs a single config reference line.
*
* @param string $text
* @param int $indent
*/
private function writeLine($text, $indent = 0)
{
$indent = \strlen($text) + $indent;
$format = '%'.$indent.'s';
$this->reference .= sprintf($format, $text).PHP_EOL;
}
/**
* Renders the string conversion of the value.
*
* @param mixed $value
*
* @return string
*/
private function writeValue($value)
{
if ('%%%%not_defined%%%%' === $value) {
return '';
}
if (\is_string($value) || is_numeric($value)) {
return $value;
}
if (false === $value) {
return 'false';
}
if (true === $value) {
return 'true';
}
if (null === $value) {
return 'null';
}
if (empty($value)) {
return '';
}
if (\is_array($value)) {
return implode(',', $value);
}
}
}

View file

@ -0,0 +1,256 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Dumper;
use Symfony\Component\Config\Definition\ArrayNode;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\EnumNode;
use Symfony\Component\Config\Definition\NodeInterface;
use Symfony\Component\Config\Definition\PrototypedArrayNode;
use Symfony\Component\Config\Definition\ScalarNode;
use Symfony\Component\Yaml\Inline;
/**
* Dumps a Yaml reference configuration for the given configuration/node instance.
*
* @author Kevin Bond <kevinbond@gmail.com>
*/
class YamlReferenceDumper
{
private $reference;
public function dump(ConfigurationInterface $configuration)
{
return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree());
}
public function dumpAtPath(ConfigurationInterface $configuration, $path)
{
$rootNode = $node = $configuration->getConfigTreeBuilder()->buildTree();
foreach (explode('.', $path) as $step) {
if (!$node instanceof ArrayNode) {
throw new \UnexpectedValueException(sprintf('Unable to find node at path "%s.%s"', $rootNode->getName(), $path));
}
/** @var NodeInterface[] $children */
$children = $node instanceof PrototypedArrayNode ? $this->getPrototypeChildren($node) : $node->getChildren();
foreach ($children as $child) {
if ($child->getName() === $step) {
$node = $child;
continue 2;
}
}
throw new \UnexpectedValueException(sprintf('Unable to find node at path "%s.%s"', $rootNode->getName(), $path));
}
return $this->dumpNode($node);
}
public function dumpNode(NodeInterface $node)
{
$this->reference = '';
$this->writeNode($node);
$ref = $this->reference;
$this->reference = null;
return $ref;
}
/**
* @param NodeInterface $node
* @param NodeInterface|null $parentNode
* @param int $depth
* @param bool $prototypedArray
*/
private function writeNode(NodeInterface $node, NodeInterface $parentNode = null, $depth = 0, $prototypedArray = false)
{
$comments = array();
$default = '';
$defaultArray = null;
$children = null;
$example = $node->getExample();
// defaults
if ($node instanceof ArrayNode) {
$children = $node->getChildren();
if ($node instanceof PrototypedArrayNode) {
$children = $this->getPrototypeChildren($node);
}
if (!$children) {
if ($node->hasDefaultValue() && \count($defaultArray = $node->getDefaultValue())) {
$default = '';
} elseif (!\is_array($example)) {
$default = '[]';
}
}
} elseif ($node instanceof EnumNode) {
$comments[] = 'One of '.implode('; ', array_map('json_encode', $node->getValues()));
$default = $node->hasDefaultValue() ? Inline::dump($node->getDefaultValue()) : '~';
} else {
$default = '~';
if ($node->hasDefaultValue()) {
$default = $node->getDefaultValue();
if (\is_array($default)) {
if (\count($defaultArray = $node->getDefaultValue())) {
$default = '';
} elseif (!\is_array($example)) {
$default = '[]';
}
} else {
$default = Inline::dump($default);
}
}
}
// required?
if ($node->isRequired()) {
$comments[] = 'Required';
}
// deprecated?
if ($node->isDeprecated()) {
$comments[] = sprintf('Deprecated (%s)', $node->getDeprecationMessage($node->getName(), $parentNode ? $parentNode->getPath() : $node->getPath()));
}
// example
if ($example && !\is_array($example)) {
$comments[] = 'Example: '.$example;
}
$default = '' != (string) $default ? ' '.$default : '';
$comments = \count($comments) ? '# '.implode(', ', $comments) : '';
$key = $prototypedArray ? '-' : $node->getName().':';
$text = rtrim(sprintf('%-21s%s %s', $key, $default, $comments), ' ');
if ($info = $node->getInfo()) {
$this->writeLine('');
// indenting multi-line info
$info = str_replace("\n", sprintf("\n%".($depth * 4).'s# ', ' '), $info);
$this->writeLine('# '.$info, $depth * 4);
}
$this->writeLine($text, $depth * 4);
// output defaults
if ($defaultArray) {
$this->writeLine('');
$message = \count($defaultArray) > 1 ? 'Defaults' : 'Default';
$this->writeLine('# '.$message.':', $depth * 4 + 4);
$this->writeArray($defaultArray, $depth + 1);
}
if (\is_array($example)) {
$this->writeLine('');
$message = \count($example) > 1 ? 'Examples' : 'Example';
$this->writeLine('# '.$message.':', $depth * 4 + 4);
$this->writeArray($example, $depth + 1);
}
if ($children) {
foreach ($children as $childNode) {
$this->writeNode($childNode, $node, $depth + 1, $node instanceof PrototypedArrayNode && !$node->getKeyAttribute());
}
}
}
/**
* Outputs a single config reference line.
*
* @param string $text
* @param int $indent
*/
private function writeLine($text, $indent = 0)
{
$indent = \strlen($text) + $indent;
$format = '%'.$indent.'s';
$this->reference .= sprintf($format, $text)."\n";
}
private function writeArray(array $array, $depth)
{
$isIndexed = array_values($array) === $array;
foreach ($array as $key => $value) {
if (\is_array($value)) {
$val = '';
} else {
$val = $value;
}
if ($isIndexed) {
$this->writeLine('- '.$val, $depth * 4);
} else {
$this->writeLine(sprintf('%-20s %s', $key.':', $val), $depth * 4);
}
if (\is_array($value)) {
$this->writeArray($value, $depth + 1);
}
}
}
/**
* @param PrototypedArrayNode $node
*
* @return array
*/
private function getPrototypeChildren(PrototypedArrayNode $node)
{
$prototype = $node->getPrototype();
$key = $node->getKeyAttribute();
// Do not expand prototype if it isn't an array node nor uses attribute as key
if (!$key && !$prototype instanceof ArrayNode) {
return $node->getChildren();
}
if ($prototype instanceof ArrayNode) {
$keyNode = new ArrayNode($key, $node);
$children = $prototype->getChildren();
if ($prototype instanceof PrototypedArrayNode && $prototype->getKeyAttribute()) {
$children = $this->getPrototypeChildren($prototype);
}
// add children
foreach ($children as $childNode) {
$keyNode->addChild($childNode);
}
} else {
$keyNode = new ScalarNode($key, $node);
}
$info = 'Prototype';
if (null !== $prototype->getInfo()) {
$info .= ': '.$prototype->getInfo();
}
$keyNode->setInfo($info);
return array($key => $keyNode);
}
}

View file

@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
/**
* Node which only allows a finite set of values.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class EnumNode extends ScalarNode
{
private $values;
public function __construct($name, NodeInterface $parent = null, array $values = array())
{
$values = array_unique($values);
if (empty($values)) {
throw new \InvalidArgumentException('$values must contain at least one element.');
}
parent::__construct($name, $parent);
$this->values = $values;
}
public function getValues()
{
return $this->values;
}
protected function finalizeValue($value)
{
$value = parent::finalizeValue($value);
if (!\in_array($value, $this->values, true)) {
$ex = new InvalidConfigurationException(sprintf('The value %s is not allowed for path "%s". Permissible values: %s', json_encode($value), $this->getPath(), implode(', ', array_map('json_encode', $this->values))));
$ex->setPath($this->getPath());
throw $ex;
}
return $value;
}
}

View file

@ -0,0 +1,22 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Exception;
/**
* This exception is thrown whenever the key of an array is not unique. This can
* only be the case if the configuration is coming from an XML file.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class DuplicateKeyException extends InvalidConfigurationException
{
}

View file

@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Exception;
/**
* Base exception for all configuration exceptions.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class Exception extends \RuntimeException
{
}

View file

@ -0,0 +1,22 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Exception;
/**
* This exception is thrown when a configuration path is overwritten from a
* subsequent configuration file, but the entry node specifically forbids this.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ForbiddenOverwriteException extends InvalidConfigurationException
{
}

View file

@ -0,0 +1,49 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Exception;
/**
* A very general exception which can be thrown whenever non of the more specific
* exceptions is suitable.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class InvalidConfigurationException extends Exception
{
private $path;
private $containsHints = false;
public function setPath($path)
{
$this->path = $path;
}
public function getPath()
{
return $this->path;
}
/**
* Adds extra information that is suffixed to the original exception message.
*
* @param string $hint
*/
public function addHint($hint)
{
if (!$this->containsHints) {
$this->message .= "\nHint: ".$hint;
$this->containsHints = true;
} else {
$this->message .= ', '.$hint;
}
}
}

View file

@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Exception;
/**
* Thrown when an error is detected in a node Definition.
*
* @author Victor Berchet <victor.berchet@suumit.com>
*/
class InvalidDefinitionException extends Exception
{
}

View file

@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Exception;
/**
* This exception is thrown if an invalid type is encountered.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class InvalidTypeException extends InvalidConfigurationException
{
}

View file

@ -0,0 +1,22 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Exception;
/**
* This exception is usually not encountered by the end-user, but only used
* internally to signal the parent scope to unset a key.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class UnsetKeyException extends Exception
{
}

View file

@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
/**
* This node represents a float value in the config tree.
*
* @author Jeanmonod David <david.jeanmonod@gmail.com>
*/
class FloatNode extends NumericNode
{
/**
* {@inheritdoc}
*/
protected function validateType($value)
{
// Integers are also accepted, we just cast them
if (\is_int($value)) {
$value = (float) $value;
}
if (!\is_float($value)) {
$ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected float, but got %s.', $this->getPath(), \gettype($value)));
if ($hint = $this->getInfo()) {
$ex->addHint($hint);
}
$ex->setPath($this->getPath());
throw $ex;
}
}
}

View file

@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
/**
* This node represents an integer value in the config tree.
*
* @author Jeanmonod David <david.jeanmonod@gmail.com>
*/
class IntegerNode extends NumericNode
{
/**
* {@inheritdoc}
*/
protected function validateType($value)
{
if (!\is_int($value)) {
$ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected int, but got %s.', $this->getPath(), \gettype($value)));
if ($hint = $this->getInfo()) {
$ex->addHint($hint);
}
$ex->setPath($this->getPath());
throw $ex;
}
}
}

View file

@ -0,0 +1,100 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
/**
* Common Interface among all nodes.
*
* In most cases, it is better to inherit from BaseNode instead of implementing
* this interface yourself.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface NodeInterface
{
/**
* Returns the name of the node.
*
* @return string The name of the node
*/
public function getName();
/**
* Returns the path of the node.
*
* @return string The node path
*/
public function getPath();
/**
* Returns true when the node is required.
*
* @return bool If the node is required
*/
public function isRequired();
/**
* Returns true when the node has a default value.
*
* @return bool If the node has a default value
*/
public function hasDefaultValue();
/**
* Returns the default value of the node.
*
* @return mixed The default value
*
* @throws \RuntimeException if the node has no default value
*/
public function getDefaultValue();
/**
* Normalizes a value.
*
* @param mixed $value The value to normalize
*
* @return mixed The normalized value
*
* @throws InvalidTypeException if the value type is invalid
*/
public function normalize($value);
/**
* Merges two values together.
*
* @param mixed $leftSide
* @param mixed $rightSide
*
* @return mixed The merged value
*
* @throws ForbiddenOverwriteException if the configuration path cannot be overwritten
* @throws InvalidTypeException if the value type is invalid
*/
public function merge($leftSide, $rightSide);
/**
* Finalizes a value.
*
* @param mixed $value The value to finalize
*
* @return mixed The finalized value
*
* @throws InvalidTypeException if the value type is invalid
* @throws InvalidConfigurationException if the value is invalid configuration
*/
public function finalize($value);
}

View file

@ -0,0 +1,64 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
/**
* This node represents a numeric value in the config tree.
*
* @author David Jeanmonod <david.jeanmonod@gmail.com>
*/
class NumericNode extends ScalarNode
{
protected $min;
protected $max;
public function __construct($name, NodeInterface $parent = null, $min = null, $max = null)
{
parent::__construct($name, $parent);
$this->min = $min;
$this->max = $max;
}
/**
* {@inheritdoc}
*/
protected function finalizeValue($value)
{
$value = parent::finalizeValue($value);
$errorMsg = null;
if (isset($this->min) && $value < $this->min) {
$errorMsg = sprintf('The value %s is too small for path "%s". Should be greater than or equal to %s', $value, $this->getPath(), $this->min);
}
if (isset($this->max) && $value > $this->max) {
$errorMsg = sprintf('The value %s is too big for path "%s". Should be less than or equal to %s', $value, $this->getPath(), $this->max);
}
if (isset($errorMsg)) {
$ex = new InvalidConfigurationException($errorMsg);
$ex->setPath($this->getPath());
throw $ex;
}
return $value;
}
/**
* {@inheritdoc}
*/
protected function isValueEmpty($value)
{
// a numeric value cannot be empty
return false;
}
}

View file

@ -0,0 +1,97 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
/**
* This class is the entry point for config normalization/merging/finalization.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class Processor
{
/**
* Processes an array of configurations.
*
* @param NodeInterface $configTree The node tree describing the configuration
* @param array $configs An array of configuration items to process
*
* @return array The processed configuration
*/
public function process(NodeInterface $configTree, array $configs)
{
$currentConfig = array();
foreach ($configs as $config) {
$config = $configTree->normalize($config);
$currentConfig = $configTree->merge($currentConfig, $config);
}
return $configTree->finalize($currentConfig);
}
/**
* Processes an array of configurations.
*
* @param ConfigurationInterface $configuration The configuration class
* @param array $configs An array of configuration items to process
*
* @return array The processed configuration
*/
public function processConfiguration(ConfigurationInterface $configuration, array $configs)
{
return $this->process($configuration->getConfigTreeBuilder()->buildTree(), $configs);
}
/**
* Normalizes a configuration entry.
*
* This method returns a normalize configuration array for a given key
* to remove the differences due to the original format (YAML and XML mainly).
*
* Here is an example.
*
* The configuration in XML:
*
* <twig:extension>twig.extension.foo</twig:extension>
* <twig:extension>twig.extension.bar</twig:extension>
*
* And the same configuration in YAML:
*
* extensions: ['twig.extension.foo', 'twig.extension.bar']
*
* @param array $config A config array
* @param string $key The key to normalize
* @param string $plural The plural form of the key if it is irregular
*
* @return array
*/
public static function normalizeConfig($config, $key, $plural = null)
{
if (null === $plural) {
$plural = $key.'s';
}
if (isset($config[$plural])) {
return $config[$plural];
}
if (isset($config[$key])) {
if (\is_string($config[$key]) || !\is_int(key($config[$key]))) {
// only one
return array($config[$key]);
}
return $config[$key];
}
return array();
}
}

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
/**
* This interface must be implemented by nodes which can be used as prototypes.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface PrototypeNodeInterface extends NodeInterface
{
/**
* Sets the name of the node.
*
* @param string $name The name of the node
*/
public function setName($name);
}

View file

@ -0,0 +1,377 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\DuplicateKeyException;
use Symfony\Component\Config\Definition\Exception\Exception;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Config\Definition\Exception\UnsetKeyException;
/**
* Represents a prototyped Array node in the config tree.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class PrototypedArrayNode extends ArrayNode
{
protected $prototype;
protected $keyAttribute;
protected $removeKeyAttribute = false;
protected $minNumberOfElements = 0;
protected $defaultValue = array();
protected $defaultChildren;
/**
* @var NodeInterface[] An array of the prototypes of the simplified value children
*/
private $valuePrototypes = array();
/**
* Sets the minimum number of elements that a prototype based node must
* contain. By default this is zero, meaning no elements.
*
* @param int $number
*/
public function setMinNumberOfElements($number)
{
$this->minNumberOfElements = $number;
}
/**
* Sets the attribute which value is to be used as key.
*
* This is useful when you have an indexed array that should be an
* associative array. You can select an item from within the array
* to be the key of the particular item. For example, if "id" is the
* "key", then:
*
* array(
* array('id' => 'my_name', 'foo' => 'bar'),
* );
*
* becomes
*
* array(
* 'my_name' => array('foo' => 'bar'),
* );
*
* If you'd like "'id' => 'my_name'" to still be present in the resulting
* array, then you can set the second argument of this method to false.
*
* @param string $attribute The name of the attribute which value is to be used as a key
* @param bool $remove Whether or not to remove the key
*/
public function setKeyAttribute($attribute, $remove = true)
{
$this->keyAttribute = $attribute;
$this->removeKeyAttribute = $remove;
}
/**
* Retrieves the name of the attribute which value should be used as key.
*
* @return string The name of the attribute
*/
public function getKeyAttribute()
{
return $this->keyAttribute;
}
/**
* Sets the default value of this node.
*
* @param string $value
*
* @throws \InvalidArgumentException if the default value is not an array
*/
public function setDefaultValue($value)
{
if (!\is_array($value)) {
throw new \InvalidArgumentException($this->getPath().': the default value of an array node has to be an array.');
}
$this->defaultValue = $value;
}
/**
* {@inheritdoc}
*/
public function hasDefaultValue()
{
return true;
}
/**
* Adds default children when none are set.
*
* @param int|string|array|null $children The number of children|The child name|The children names to be added
*/
public function setAddChildrenIfNoneSet($children = array('defaults'))
{
if (null === $children) {
$this->defaultChildren = array('defaults');
} else {
$this->defaultChildren = \is_int($children) && $children > 0 ? range(1, $children) : (array) $children;
}
}
/**
* {@inheritdoc}
*
* The default value could be either explicited or derived from the prototype
* default value.
*/
public function getDefaultValue()
{
if (null !== $this->defaultChildren) {
$default = $this->prototype->hasDefaultValue() ? $this->prototype->getDefaultValue() : array();
$defaults = array();
foreach (array_values($this->defaultChildren) as $i => $name) {
$defaults[null === $this->keyAttribute ? $i : $name] = $default;
}
return $defaults;
}
return $this->defaultValue;
}
/**
* Sets the node prototype.
*/
public function setPrototype(PrototypeNodeInterface $node)
{
$this->prototype = $node;
}
/**
* Retrieves the prototype.
*
* @return PrototypeNodeInterface The prototype
*/
public function getPrototype()
{
return $this->prototype;
}
/**
* Disable adding concrete children for prototyped nodes.
*
* @throws Exception
*/
public function addChild(NodeInterface $node)
{
throw new Exception('A prototyped array node can not have concrete children.');
}
/**
* Finalizes the value of this node.
*
* @param mixed $value
*
* @return mixed The finalized value
*
* @throws UnsetKeyException
* @throws InvalidConfigurationException if the node doesn't have enough children
*/
protected function finalizeValue($value)
{
if (false === $value) {
throw new UnsetKeyException(sprintf('Unsetting key for path "%s", value: %s', $this->getPath(), json_encode($value)));
}
foreach ($value as $k => $v) {
$prototype = $this->getPrototypeForChild($k);
try {
$value[$k] = $prototype->finalize($v);
} catch (UnsetKeyException $e) {
unset($value[$k]);
}
}
if (\count($value) < $this->minNumberOfElements) {
$ex = new InvalidConfigurationException(sprintf('The path "%s" should have at least %d element(s) defined.', $this->getPath(), $this->minNumberOfElements));
$ex->setPath($this->getPath());
throw $ex;
}
return $value;
}
/**
* Normalizes the value.
*
* @param mixed $value The value to normalize
*
* @return mixed The normalized value
*
* @throws InvalidConfigurationException
* @throws DuplicateKeyException
*/
protected function normalizeValue($value)
{
if (false === $value) {
return $value;
}
$value = $this->remapXml($value);
$isAssoc = array_keys($value) !== range(0, \count($value) - 1);
$normalized = array();
foreach ($value as $k => $v) {
if (null !== $this->keyAttribute && \is_array($v)) {
if (!isset($v[$this->keyAttribute]) && \is_int($k) && !$isAssoc) {
$ex = new InvalidConfigurationException(sprintf('The attribute "%s" must be set for path "%s".', $this->keyAttribute, $this->getPath()));
$ex->setPath($this->getPath());
throw $ex;
} elseif (isset($v[$this->keyAttribute])) {
$k = $v[$this->keyAttribute];
// remove the key attribute when required
if ($this->removeKeyAttribute) {
unset($v[$this->keyAttribute]);
}
// if only "value" is left
if (array_keys($v) === array('value')) {
$v = $v['value'];
if ($this->prototype instanceof ArrayNode && ($children = $this->prototype->getChildren()) && array_key_exists('value', $children)) {
$valuePrototype = current($this->valuePrototypes) ?: clone $children['value'];
$valuePrototype->parent = $this;
$originalClosures = $this->prototype->normalizationClosures;
if (\is_array($originalClosures)) {
$valuePrototypeClosures = $valuePrototype->normalizationClosures;
$valuePrototype->normalizationClosures = \is_array($valuePrototypeClosures) ? array_merge($originalClosures, $valuePrototypeClosures) : $originalClosures;
}
$this->valuePrototypes[$k] = $valuePrototype;
}
}
}
if (array_key_exists($k, $normalized)) {
$ex = new DuplicateKeyException(sprintf('Duplicate key "%s" for path "%s".', $k, $this->getPath()));
$ex->setPath($this->getPath());
throw $ex;
}
}
$prototype = $this->getPrototypeForChild($k);
if (null !== $this->keyAttribute || $isAssoc) {
$normalized[$k] = $prototype->normalize($v);
} else {
$normalized[] = $prototype->normalize($v);
}
}
return $normalized;
}
/**
* Merges values together.
*
* @param mixed $leftSide The left side to merge
* @param mixed $rightSide The right side to merge
*
* @return mixed The merged values
*
* @throws InvalidConfigurationException
* @throws \RuntimeException
*/
protected function mergeValues($leftSide, $rightSide)
{
if (false === $rightSide) {
// if this is still false after the last config has been merged the
// finalization pass will take care of removing this key entirely
return false;
}
if (false === $leftSide || !$this->performDeepMerging) {
return $rightSide;
}
foreach ($rightSide as $k => $v) {
// prototype, and key is irrelevant, so simply append the element
if (null === $this->keyAttribute) {
$leftSide[] = $v;
continue;
}
// no conflict
if (!array_key_exists($k, $leftSide)) {
if (!$this->allowNewKeys) {
$ex = new InvalidConfigurationException(sprintf('You are not allowed to define new elements for path "%s". Please define all elements for this path in one config file.', $this->getPath()));
$ex->setPath($this->getPath());
throw $ex;
}
$leftSide[$k] = $v;
continue;
}
$prototype = $this->getPrototypeForChild($k);
$leftSide[$k] = $prototype->merge($leftSide[$k], $v);
}
return $leftSide;
}
/**
* Returns a prototype for the child node that is associated to $key in the value array.
* For general child nodes, this will be $this->prototype.
* But if $this->removeKeyAttribute is true and there are only two keys in the child node:
* one is same as this->keyAttribute and the other is 'value', then the prototype will be different.
*
* For example, assume $this->keyAttribute is 'name' and the value array is as follows:
*
* array(
* array(
* 'name' => 'name001',
* 'value' => 'value001'
* )
* )
*
* Now, the key is 0 and the child node is:
*
* array(
* 'name' => 'name001',
* 'value' => 'value001'
* )
*
* When normalizing the value array, the 'name' element will removed from the child node
* and its value becomes the new key of the child node:
*
* array(
* 'name001' => array('value' => 'value001')
* )
*
* Now only 'value' element is left in the child node which can be further simplified into a string:
*
* array('name001' => 'value001')
*
* Now, the key becomes 'name001' and the child node becomes 'value001' and
* the prototype of child node 'name001' should be a ScalarNode instead of an ArrayNode instance.
*
* @param string $key The key of the child node
*
* @return mixed The prototype instance
*/
private function getPrototypeForChild($key)
{
$prototype = isset($this->valuePrototypes[$key]) ? $this->valuePrototypes[$key] : $this->prototype;
$prototype->setName($key);
return $prototype;
}
}

View file

@ -0,0 +1,53 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
/**
* This node represents a scalar value in the config tree.
*
* The following values are considered scalars:
* * booleans
* * strings
* * null
* * integers
* * floats
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ScalarNode extends VariableNode
{
/**
* {@inheritdoc}
*/
protected function validateType($value)
{
if (!is_scalar($value) && null !== $value) {
$ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected scalar, but got %s.', $this->getPath(), \gettype($value)));
if ($hint = $this->getInfo()) {
$ex->addHint($hint);
}
$ex->setPath($this->getPath());
throw $ex;
}
}
/**
* {@inheritdoc}
*/
protected function isValueEmpty($value)
{
return null === $value || '' === $value;
}
}

View file

@ -0,0 +1,128 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
/**
* This node represents a value of variable type in the config tree.
*
* This node is intended for values of arbitrary type.
* Any PHP type is accepted as a value.
*
* @author Jeremy Mikola <jmikola@gmail.com>
*/
class VariableNode extends BaseNode implements PrototypeNodeInterface
{
protected $defaultValueSet = false;
protected $defaultValue;
protected $allowEmptyValue = true;
public function setDefaultValue($value)
{
$this->defaultValueSet = true;
$this->defaultValue = $value;
}
/**
* {@inheritdoc}
*/
public function hasDefaultValue()
{
return $this->defaultValueSet;
}
/**
* {@inheritdoc}
*/
public function getDefaultValue()
{
$v = $this->defaultValue;
return $v instanceof \Closure ? $v() : $v;
}
/**
* Sets if this node is allowed to have an empty value.
*
* @param bool $boolean True if this entity will accept empty values
*/
public function setAllowEmptyValue($boolean)
{
$this->allowEmptyValue = (bool) $boolean;
}
/**
* {@inheritdoc}
*/
public function setName($name)
{
$this->name = $name;
}
/**
* {@inheritdoc}
*/
protected function validateType($value)
{
}
/**
* {@inheritdoc}
*/
protected function finalizeValue($value)
{
if (!$this->allowEmptyValue && $this->isValueEmpty($value)) {
$ex = new InvalidConfigurationException(sprintf('The path "%s" cannot contain an empty value, but got %s.', $this->getPath(), json_encode($value)));
if ($hint = $this->getInfo()) {
$ex->addHint($hint);
}
$ex->setPath($this->getPath());
throw $ex;
}
return $value;
}
/**
* {@inheritdoc}
*/
protected function normalizeValue($value)
{
return $value;
}
/**
* {@inheritdoc}
*/
protected function mergeValues($leftSide, $rightSide)
{
return $rightSide;
}
/**
* Evaluates if the given value is to be treated as empty.
*
* By default, PHP's empty() function is used to test for emptiness. This
* method may be overridden by subtypes to better match their understanding
* of empty data.
*
* @param mixed $value
*
* @return bool
*/
protected function isValueEmpty($value)
{
return empty($value);
}
}

View file

@ -0,0 +1,52 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\DependencyInjection;
@trigger_error(sprintf('The %s class is deprecated since Symfony 3.4 and will be removed in 4.0. Use tagged iterator arguments instead.', ConfigCachePass::class), E_USER_DEPRECATED);
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Adds services tagged config_cache.resource_checker to the config_cache_factory service, ordering them by priority.
*
* @author Matthias Pigulla <mp@webfactory.de>
* @author Benjamin Klotz <bk@webfactory.de>
*
* @deprecated since version 3.4, to be removed in 4.0. Use tagged iterator arguments instead.
*/
class ConfigCachePass implements CompilerPassInterface
{
use PriorityTaggedServiceTrait;
private $factoryServiceId;
private $resourceCheckerTag;
public function __construct($factoryServiceId = 'config_cache_factory', $resourceCheckerTag = 'config_cache.resource_checker')
{
$this->factoryServiceId = $factoryServiceId;
$this->resourceCheckerTag = $resourceCheckerTag;
}
public function process(ContainerBuilder $container)
{
$resourceCheckers = $this->findAndSortTaggedServices($this->resourceCheckerTag, $container);
if (empty($resourceCheckers)) {
return;
}
$container->getDefinition($this->factoryServiceId)->replaceArgument(0, new IteratorArgument($resourceCheckers));
}
}

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Exception;
/**
* Exception class for when a circular reference is detected when importing resources.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FileLoaderImportCircularReferenceException extends FileLoaderLoadException
{
public function __construct(array $resources, $code = null, $previous = null)
{
$message = sprintf('Circular reference detected in "%s" ("%s" > "%s").', $this->varToString($resources[0]), implode('" > "', $resources), $resources[0]);
\Exception::__construct($message, $code, $previous);
}
}

View file

@ -0,0 +1,109 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Exception;
/**
* Exception class for when a resource cannot be loaded or imported.
*
* @author Ryan Weaver <ryan@thatsquality.com>
*/
class FileLoaderLoadException extends \Exception
{
/**
* @param string $resource The resource that could not be imported
* @param string $sourceResource The original resource importing the new resource
* @param int $code The error code
* @param \Exception $previous A previous exception
* @param string $type The type of resource
*/
public function __construct($resource, $sourceResource = null, $code = null, $previous = null, $type = null)
{
$message = '';
if ($previous) {
// Include the previous exception, to help the user see what might be the underlying cause
// Trim the trailing period of the previous message. We only want 1 period remove so no rtrim...
if ('.' === substr($previous->getMessage(), -1)) {
$trimmedMessage = substr($previous->getMessage(), 0, -1);
$message .= sprintf('%s', $trimmedMessage).' in ';
} else {
$message .= sprintf('%s', $previous->getMessage()).' in ';
}
$message .= $resource.' ';
// show tweaked trace to complete the human readable sentence
if (null === $sourceResource) {
$message .= sprintf('(which is loaded in resource "%s")', $this->varToString($resource));
} else {
$message .= sprintf('(which is being imported from "%s")', $this->varToString($sourceResource));
}
$message .= '.';
// if there's no previous message, present it the default way
} elseif (null === $sourceResource) {
$message .= sprintf('Cannot load resource "%s".', $this->varToString($resource));
} else {
$message .= sprintf('Cannot import resource "%s" from "%s".', $this->varToString($resource), $this->varToString($sourceResource));
}
// Is the resource located inside a bundle?
if ('@' === $resource[0]) {
$parts = explode(\DIRECTORY_SEPARATOR, $resource);
$bundle = substr($parts[0], 1);
$message .= sprintf(' Make sure the "%s" bundle is correctly registered and loaded in the application kernel class.', $bundle);
$message .= sprintf(' If the bundle is registered, make sure the bundle path "%s" is not empty.', $resource);
} elseif (null !== $type) {
// maybe there is no loader for this specific type
if ('annotation' === $type) {
$message .= ' Make sure annotations are installed and enabled.';
} else {
$message .= sprintf(' Make sure there is a loader supporting the "%s" type.', $type);
}
}
parent::__construct($message, $code, $previous);
}
protected function varToString($var)
{
if (\is_object($var)) {
return sprintf('Object(%s)', \get_class($var));
}
if (\is_array($var)) {
$a = array();
foreach ($var as $k => $v) {
$a[] = sprintf('%s => %s', $k, $this->varToString($v));
}
return sprintf('Array(%s)', implode(', ', $a));
}
if (\is_resource($var)) {
return sprintf('Resource(%s)', get_resource_type($var));
}
if (null === $var) {
return 'null';
}
if (false === $var) {
return 'false';
}
if (true === $var) {
return 'true';
}
return (string) $var;
}
}

View file

@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Exception;
/**
* File locator exception if a file does not exist.
*
* @author Leo Feyer <https://github.com/leofeyer>
*/
class FileLocatorFileNotFoundException extends \InvalidArgumentException
{
private $paths;
public function __construct($message = '', $code = 0, $previous = null, array $paths = array())
{
parent::__construct($message, $code, $previous);
$this->paths = $paths;
}
public function getPaths()
{
return $this->paths;
}
}

98
vendor/symfony/config/FileLocator.php vendored Normal file
View file

@ -0,0 +1,98 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException;
/**
* FileLocator uses an array of pre-defined paths to find files.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FileLocator implements FileLocatorInterface
{
protected $paths;
/**
* @param string|array $paths A path or an array of paths where to look for resources
*/
public function __construct($paths = array())
{
$this->paths = (array) $paths;
}
/**
* {@inheritdoc}
*/
public function locate($name, $currentPath = null, $first = true)
{
if ('' == $name) {
throw new \InvalidArgumentException('An empty file name is not valid to be located.');
}
if ($this->isAbsolutePath($name)) {
if (!file_exists($name)) {
throw new FileLocatorFileNotFoundException(sprintf('The file "%s" does not exist.', $name), 0, null, array($name));
}
return $name;
}
$paths = $this->paths;
if (null !== $currentPath) {
array_unshift($paths, $currentPath);
}
$paths = array_unique($paths);
$filepaths = $notfound = array();
foreach ($paths as $path) {
if (@file_exists($file = $path.\DIRECTORY_SEPARATOR.$name)) {
if (true === $first) {
return $file;
}
$filepaths[] = $file;
} else {
$notfound[] = $file;
}
}
if (!$filepaths) {
throw new FileLocatorFileNotFoundException(sprintf('The file "%s" does not exist (in: %s).', $name, implode(', ', $paths)), 0, null, $notfound);
}
return $filepaths;
}
/**
* Returns whether the file path is an absolute path.
*
* @param string $file A file path
*
* @return bool
*/
private function isAbsolutePath($file)
{
if ('/' === $file[0] || '\\' === $file[0]
|| (\strlen($file) > 3 && ctype_alpha($file[0])
&& ':' === $file[1]
&& ('\\' === $file[2] || '/' === $file[2])
)
|| null !== parse_url($file, PHP_URL_SCHEME)
) {
return true;
}
return false;
}
}

View file

@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
interface FileLocatorInterface
{
/**
* Returns a full path for a given file name.
*
* @param string $name The file name to locate
* @param string|null $currentPath The current path
* @param bool $first Whether to return the first occurrence or an array of filenames
*
* @return string|array The full path to the file or an array of file paths
*
* @throws \InvalidArgumentException If $name is empty
* @throws FileLocatorFileNotFoundException If a file is not found
*/
public function locate($name, $currentPath = null, $first = true);
}

19
vendor/symfony/config/LICENSE vendored Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) 2004-2018 Fabien Potencier
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,50 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Loader;
use Symfony\Component\Config\Exception\FileLoaderLoadException;
/**
* DelegatingLoader delegates loading to other loaders using a loader resolver.
*
* This loader acts as an array of LoaderInterface objects - each having
* a chance to load a given resource (handled by the resolver)
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DelegatingLoader extends Loader
{
public function __construct(LoaderResolverInterface $resolver)
{
$this->resolver = $resolver;
}
/**
* {@inheritdoc}
*/
public function load($resource, $type = null)
{
if (false === $loader = $this->resolver->resolve($resource, $type)) {
throw new FileLoaderLoadException($resource, null, null, null, $type);
}
return $loader->load($resource, $type);
}
/**
* {@inheritdoc}
*/
public function supports($resource, $type = null)
{
return false !== $this->resolver->resolve($resource, $type);
}
}

View file

@ -0,0 +1,172 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Loader;
use Symfony\Component\Config\Exception\FileLoaderImportCircularReferenceException;
use Symfony\Component\Config\Exception\FileLoaderLoadException;
use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException;
use Symfony\Component\Config\FileLocatorInterface;
use Symfony\Component\Config\Resource\FileExistenceResource;
use Symfony\Component\Config\Resource\GlobResource;
/**
* FileLoader is the abstract class used by all built-in loaders that are file based.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class FileLoader extends Loader
{
protected static $loading = array();
protected $locator;
private $currentDir;
public function __construct(FileLocatorInterface $locator)
{
$this->locator = $locator;
}
/**
* Sets the current directory.
*
* @param string $dir
*/
public function setCurrentDir($dir)
{
$this->currentDir = $dir;
}
/**
* Returns the file locator used by this loader.
*
* @return FileLocatorInterface
*/
public function getLocator()
{
return $this->locator;
}
/**
* Imports a resource.
*
* @param mixed $resource A Resource
* @param string|null $type The resource type or null if unknown
* @param bool $ignoreErrors Whether to ignore import errors or not
* @param string|null $sourceResource The original resource importing the new resource
*
* @return mixed
*
* @throws FileLoaderLoadException
* @throws FileLoaderImportCircularReferenceException
* @throws FileLocatorFileNotFoundException
*/
public function import($resource, $type = null, $ignoreErrors = false, $sourceResource = null)
{
if (\is_string($resource) && \strlen($resource) !== $i = strcspn($resource, '*?{[')) {
$ret = array();
$isSubpath = 0 !== $i && false !== strpos(substr($resource, 0, $i), '/');
foreach ($this->glob($resource, false, $_, $ignoreErrors || !$isSubpath) as $path => $info) {
if (null !== $res = $this->doImport($path, $type, $ignoreErrors, $sourceResource)) {
$ret[] = $res;
}
$isSubpath = true;
}
if ($isSubpath) {
return isset($ret[1]) ? $ret : (isset($ret[0]) ? $ret[0] : null);
}
}
return $this->doImport($resource, $type, $ignoreErrors, $sourceResource);
}
/**
* @internal
*/
protected function glob($pattern, $recursive, &$resource = null, $ignoreErrors = false)
{
if (\strlen($pattern) === $i = strcspn($pattern, '*?{[')) {
$prefix = $pattern;
$pattern = '';
} elseif (0 === $i || false === strpos(substr($pattern, 0, $i), '/')) {
$prefix = '.';
$pattern = '/'.$pattern;
} else {
$prefix = \dirname(substr($pattern, 0, 1 + $i));
$pattern = substr($pattern, \strlen($prefix));
}
try {
$prefix = $this->locator->locate($prefix, $this->currentDir, true);
} catch (FileLocatorFileNotFoundException $e) {
if (!$ignoreErrors) {
throw $e;
}
$resource = array();
foreach ($e->getPaths() as $path) {
$resource[] = new FileExistenceResource($path);
}
return;
}
$resource = new GlobResource($prefix, $pattern, $recursive);
foreach ($resource as $path => $info) {
yield $path => $info;
}
}
private function doImport($resource, $type = null, $ignoreErrors = false, $sourceResource = null)
{
try {
$loader = $this->resolve($resource, $type);
if ($loader instanceof self && null !== $this->currentDir) {
$resource = $loader->getLocator()->locate($resource, $this->currentDir, false);
}
$resources = \is_array($resource) ? $resource : array($resource);
for ($i = 0; $i < $resourcesCount = \count($resources); ++$i) {
if (isset(self::$loading[$resources[$i]])) {
if ($i == $resourcesCount - 1) {
throw new FileLoaderImportCircularReferenceException(array_keys(self::$loading));
}
} else {
$resource = $resources[$i];
break;
}
}
self::$loading[$resource] = true;
try {
$ret = $loader->load($resource, $type);
} finally {
unset(self::$loading[$resource]);
}
return $ret;
} catch (FileLoaderImportCircularReferenceException $e) {
throw $e;
} catch (\Exception $e) {
if (!$ignoreErrors) {
// prevent embedded imports from nesting multiple exceptions
if ($e instanceof FileLoaderLoadException) {
throw $e;
}
throw new FileLoaderLoadException($resource, $sourceResource, null, $e, $type);
}
}
}
}

View file

@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Loader;
/**
* GlobFileLoader loads files from a glob pattern.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class GlobFileLoader extends FileLoader
{
/**
* {@inheritdoc}
*/
public function load($resource, $type = null)
{
return $this->import($resource);
}
/**
* {@inheritdoc}
*/
public function supports($resource, $type = null)
{
return 'glob' === $type;
}
}

78
vendor/symfony/config/Loader/Loader.php vendored Normal file
View file

@ -0,0 +1,78 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Loader;
use Symfony\Component\Config\Exception\FileLoaderLoadException;
/**
* Loader is the abstract class used by all built-in loaders.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class Loader implements LoaderInterface
{
protected $resolver;
/**
* {@inheritdoc}
*/
public function getResolver()
{
return $this->resolver;
}
/**
* {@inheritdoc}
*/
public function setResolver(LoaderResolverInterface $resolver)
{
$this->resolver = $resolver;
}
/**
* Imports a resource.
*
* @param mixed $resource A resource
* @param string|null $type The resource type or null if unknown
*
* @return mixed
*/
public function import($resource, $type = null)
{
return $this->resolve($resource, $type)->load($resource, $type);
}
/**
* Finds a loader able to load an imported resource.
*
* @param mixed $resource A resource
* @param string|null $type The resource type or null if unknown
*
* @return $this|LoaderInterface
*
* @throws FileLoaderLoadException If no loader is found
*/
public function resolve($resource, $type = null)
{
if ($this->supports($resource, $type)) {
return $this;
}
$loader = null === $this->resolver ? false : $this->resolver->resolve($resource, $type);
if (false === $loader) {
throw new FileLoaderLoadException($resource, null, null, null, $type);
}
return $loader;
}
}

View file

@ -0,0 +1,52 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Loader;
/**
* LoaderInterface is the interface implemented by all loader classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface LoaderInterface
{
/**
* Loads a resource.
*
* @param mixed $resource The resource
* @param string|null $type The resource type or null if unknown
*
* @throws \Exception If something went wrong
*/
public function load($resource, $type = null);
/**
* Returns whether this class supports the given resource.
*
* @param mixed $resource A resource
* @param string|null $type The resource type or null if unknown
*
* @return bool True if this class supports the given resource, false otherwise
*/
public function supports($resource, $type = null);
/**
* Gets the loader resolver.
*
* @return LoaderResolverInterface A LoaderResolverInterface instance
*/
public function getResolver();
/**
* Sets the loader resolver.
*/
public function setResolver(LoaderResolverInterface $resolver);
}

View file

@ -0,0 +1,68 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Loader;
/**
* LoaderResolver selects a loader for a given resource.
*
* A resource can be anything (e.g. a full path to a config file or a Closure).
* Each loader determines whether it can load a resource and how.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class LoaderResolver implements LoaderResolverInterface
{
/**
* @var LoaderInterface[] An array of LoaderInterface objects
*/
private $loaders = array();
/**
* @param LoaderInterface[] $loaders An array of loaders
*/
public function __construct(array $loaders = array())
{
foreach ($loaders as $loader) {
$this->addLoader($loader);
}
}
/**
* {@inheritdoc}
*/
public function resolve($resource, $type = null)
{
foreach ($this->loaders as $loader) {
if ($loader->supports($resource, $type)) {
return $loader;
}
}
return false;
}
public function addLoader(LoaderInterface $loader)
{
$this->loaders[] = $loader;
$loader->setResolver($this);
}
/**
* Returns the registered loaders.
*
* @return LoaderInterface[] An array of LoaderInterface instances
*/
public function getLoaders()
{
return $this->loaders;
}
}

View file

@ -0,0 +1,30 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Loader;
/**
* LoaderResolverInterface selects a loader for a given resource.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface LoaderResolverInterface
{
/**
* Returns a loader able to load the resource.
*
* @param mixed $resource A resource
* @param string|null $type The resource type or null if unknown
*
* @return LoaderInterface|false The loader or false if none is able to load the resource
*/
public function resolve($resource, $type = null);
}

15
vendor/symfony/config/README.md vendored Normal file
View file

@ -0,0 +1,15 @@
Config Component
================
The Config component provides several classes to help you find, load, combine,
autofill and validate configuration values of any kind, whatever their source
may be (YAML, XML, INI files, or for instance a database).
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/config/index.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)

View file

@ -0,0 +1,169 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Resource;
/**
* ClassExistenceResource represents a class existence.
* Freshness is only evaluated against resource existence.
*
* The resource must be a fully-qualified class name.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializable
{
private $resource;
private $exists;
private static $autoloadLevel = 0;
private static $autoloadedClass;
private static $existsCache = array();
/**
* @param string $resource The fully-qualified class name
* @param bool|null $exists Boolean when the existency check has already been done
*/
public function __construct($resource, $exists = null)
{
$this->resource = $resource;
if (null !== $exists) {
$this->exists = (bool) $exists;
}
}
/**
* {@inheritdoc}
*/
public function __toString()
{
return $this->resource;
}
/**
* @return string The file path to the resource
*/
public function getResource()
{
return $this->resource;
}
/**
* {@inheritdoc}
*
* @throws \ReflectionException when a parent class/interface/trait is not found
*/
public function isFresh($timestamp)
{
$loaded = class_exists($this->resource, false) || interface_exists($this->resource, false) || trait_exists($this->resource, false);
if (null !== $exists = &self::$existsCache[(int) (0 >= $timestamp)][$this->resource]) {
$exists = $exists || $loaded;
} elseif (!$exists = $loaded) {
if (!self::$autoloadLevel++) {
spl_autoload_register(__CLASS__.'::throwOnRequiredClass');
}
$autoloadedClass = self::$autoloadedClass;
self::$autoloadedClass = $this->resource;
try {
$exists = class_exists($this->resource) || interface_exists($this->resource, false) || trait_exists($this->resource, false);
} catch (\ReflectionException $e) {
if (0 >= $timestamp) {
unset(self::$existsCache[1][$this->resource]);
throw $e;
}
} finally {
self::$autoloadedClass = $autoloadedClass;
if (!--self::$autoloadLevel) {
spl_autoload_unregister(__CLASS__.'::throwOnRequiredClass');
}
}
}
if (null === $this->exists) {
$this->exists = $exists;
}
return $this->exists xor !$exists;
}
/**
* {@inheritdoc}
*/
public function serialize()
{
if (null === $this->exists) {
$this->isFresh(0);
}
return serialize(array($this->resource, $this->exists));
}
/**
* {@inheritdoc}
*/
public function unserialize($serialized)
{
list($this->resource, $this->exists) = unserialize($serialized);
}
/**
* @throws \ReflectionException When $class is not found and is required
*/
private static function throwOnRequiredClass($class)
{
if (self::$autoloadedClass === $class) {
return;
}
$e = new \ReflectionException("Class $class not found");
$trace = $e->getTrace();
$autoloadFrame = array(
'function' => 'spl_autoload_call',
'args' => array($class),
);
$i = 1 + array_search($autoloadFrame, $trace, true);
if (isset($trace[$i]['function']) && !isset($trace[$i]['class'])) {
switch ($trace[$i]['function']) {
case 'get_class_methods':
case 'get_class_vars':
case 'get_parent_class':
case 'is_a':
case 'is_subclass_of':
case 'class_exists':
case 'class_implements':
case 'class_parents':
case 'trait_exists':
case 'defined':
case 'interface_exists':
case 'method_exists':
case 'property_exists':
case 'is_callable':
return;
}
$props = array(
'file' => $trace[$i]['file'],
'line' => $trace[$i]['line'],
'trace' => \array_slice($trace, 1 + $i),
);
foreach ($props as $p => $v) {
$r = new \ReflectionProperty('Exception', $p);
$r->setAccessible(true);
$r->setValue($e, $v);
}
}
throw $e;
}
}

View file

@ -0,0 +1,78 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Resource;
/**
* ComposerResource tracks the PHP version and Composer dependencies.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class ComposerResource implements SelfCheckingResourceInterface, \Serializable
{
private $vendors;
private static $runtimeVendors;
public function __construct()
{
self::refresh();
$this->vendors = self::$runtimeVendors;
}
public function getVendors()
{
return array_keys($this->vendors);
}
/**
* {@inheritdoc}
*/
public function __toString()
{
return __CLASS__;
}
/**
* {@inheritdoc}
*/
public function isFresh($timestamp)
{
self::refresh();
return self::$runtimeVendors === $this->vendors;
}
public function serialize()
{
return serialize($this->vendors);
}
public function unserialize($serialized)
{
$this->vendors = unserialize($serialized);
}
private static function refresh()
{
self::$runtimeVendors = array();
foreach (get_declared_classes() as $class) {
if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) {
$r = new \ReflectionClass($class);
$v = \dirname(\dirname($r->getFileName()));
if (file_exists($v.'/composer/installed.json')) {
self::$runtimeVendors[$v] = @filemtime($v.'/composer/installed.json');
}
}
}
}
}

View file

@ -0,0 +1,116 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Resource;
/**
* DirectoryResource represents a resources stored in a subdirectory tree.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DirectoryResource implements SelfCheckingResourceInterface, \Serializable
{
private $resource;
private $pattern;
/**
* @param string $resource The file path to the resource
* @param string|null $pattern A pattern to restrict monitored files
*
* @throws \InvalidArgumentException
*/
public function __construct($resource, $pattern = null)
{
$this->resource = realpath($resource) ?: (file_exists($resource) ? $resource : false);
$this->pattern = $pattern;
if (false === $this->resource || !is_dir($this->resource)) {
throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist.', $resource));
}
}
/**
* {@inheritdoc}
*/
public function __toString()
{
return md5(serialize(array($this->resource, $this->pattern)));
}
/**
* @return string The file path to the resource
*/
public function getResource()
{
return $this->resource;
}
/**
* Returns the pattern to restrict monitored files.
*
* @return string|null
*/
public function getPattern()
{
return $this->pattern;
}
/**
* {@inheritdoc}
*/
public function isFresh($timestamp)
{
if (!is_dir($this->resource)) {
return false;
}
if ($timestamp < filemtime($this->resource)) {
return false;
}
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->resource), \RecursiveIteratorIterator::SELF_FIRST) as $file) {
// if regex filtering is enabled only check matching files
if ($this->pattern && $file->isFile() && !preg_match($this->pattern, $file->getBasename())) {
continue;
}
// always monitor directories for changes, except the .. entries
// (otherwise deleted files wouldn't get detected)
if ($file->isDir() && '/..' === substr($file, -3)) {
continue;
}
// for broken links
try {
$fileMTime = $file->getMTime();
} catch (\RuntimeException $e) {
continue;
}
// early return if a file's mtime exceeds the passed timestamp
if ($timestamp < $fileMTime) {
return false;
}
}
return true;
}
public function serialize()
{
return serialize(array($this->resource, $this->pattern));
}
public function unserialize($serialized)
{
list($this->resource, $this->pattern) = unserialize($serialized);
}
}

View file

@ -0,0 +1,76 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Resource;
/**
* FileExistenceResource represents a resource stored on the filesystem.
* Freshness is only evaluated against resource creation or deletion.
*
* The resource can be a file or a directory.
*
* @author Charles-Henri Bruyand <charleshenri.bruyand@gmail.com>
*/
class FileExistenceResource implements SelfCheckingResourceInterface, \Serializable
{
private $resource;
private $exists;
/**
* @param string $resource The file path to the resource
*/
public function __construct($resource)
{
$this->resource = (string) $resource;
$this->exists = file_exists($resource);
}
/**
* {@inheritdoc}
*/
public function __toString()
{
return $this->resource;
}
/**
* @return string The file path to the resource
*/
public function getResource()
{
return $this->resource;
}
/**
* {@inheritdoc}
*/
public function isFresh($timestamp)
{
return file_exists($this->resource) === $this->exists;
}
/**
* {@inheritdoc}
*/
public function serialize()
{
return serialize(array($this->resource, $this->exists));
}
/**
* {@inheritdoc}
*/
public function unserialize($serialized)
{
list($this->resource, $this->exists) = unserialize($serialized);
}
}

View file

@ -0,0 +1,75 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Resource;
/**
* FileResource represents a resource stored on the filesystem.
*
* The resource can be a file or a directory.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FileResource implements SelfCheckingResourceInterface, \Serializable
{
/**
* @var string|false
*/
private $resource;
/**
* @param string $resource The file path to the resource
*
* @throws \InvalidArgumentException
*/
public function __construct($resource)
{
$this->resource = realpath($resource) ?: (file_exists($resource) ? $resource : false);
if (false === $this->resource) {
throw new \InvalidArgumentException(sprintf('The file "%s" does not exist.', $resource));
}
}
/**
* {@inheritdoc}
*/
public function __toString()
{
return $this->resource;
}
/**
* @return string The canonicalized, absolute path to the resource
*/
public function getResource()
{
return $this->resource;
}
/**
* {@inheritdoc}
*/
public function isFresh($timestamp)
{
return false !== ($filemtime = @filemtime($this->resource)) && $filemtime <= $timestamp;
}
public function serialize()
{
return serialize($this->resource);
}
public function unserialize($serialized)
{
$this->resource = unserialize($serialized);
}
}

View file

@ -0,0 +1,151 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Resource;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\Glob;
/**
* GlobResource represents a set of resources stored on the filesystem.
*
* Only existence/removal is tracked (not mtimes.)
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class GlobResource implements \IteratorAggregate, SelfCheckingResourceInterface, \Serializable
{
private $prefix;
private $pattern;
private $recursive;
private $hash;
/**
* @param string $prefix A directory prefix
* @param string $pattern A glob pattern
* @param bool $recursive Whether directories should be scanned recursively or not
*
* @throws \InvalidArgumentException
*/
public function __construct($prefix, $pattern, $recursive)
{
$this->prefix = realpath($prefix) ?: (file_exists($prefix) ? $prefix : false);
$this->pattern = $pattern;
$this->recursive = $recursive;
if (false === $this->prefix) {
throw new \InvalidArgumentException(sprintf('The path "%s" does not exist.', $prefix));
}
}
public function getPrefix()
{
return $this->prefix;
}
/**
* {@inheritdoc}
*/
public function __toString()
{
return 'glob.'.$this->prefix.$this->pattern.(int) $this->recursive;
}
/**
* {@inheritdoc}
*/
public function isFresh($timestamp)
{
$hash = $this->computeHash();
if (null === $this->hash) {
$this->hash = $hash;
}
return $this->hash === $hash;
}
public function serialize()
{
if (null === $this->hash) {
$this->hash = $this->computeHash();
}
return serialize(array($this->prefix, $this->pattern, $this->recursive, $this->hash));
}
public function unserialize($serialized)
{
list($this->prefix, $this->pattern, $this->recursive, $this->hash) = unserialize($serialized);
}
public function getIterator()
{
if (!file_exists($this->prefix) || (!$this->recursive && '' === $this->pattern)) {
return;
}
if (0 !== strpos($this->prefix, 'phar://') && false === strpos($this->pattern, '/**/') && (\defined('GLOB_BRACE') || false === strpos($this->pattern, '{'))) {
foreach (glob($this->prefix.$this->pattern, \defined('GLOB_BRACE') ? GLOB_BRACE : 0) as $path) {
if ($this->recursive && is_dir($path)) {
$files = iterator_to_array(new \RecursiveIteratorIterator(
new \RecursiveCallbackFilterIterator(
new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS),
function (\SplFileInfo $file) { return '.' !== $file->getBasename()[0]; }
),
\RecursiveIteratorIterator::LEAVES_ONLY
));
uasort($files, function (\SplFileInfo $a, \SplFileInfo $b) {
return (string) $a > (string) $b ? 1 : -1;
});
foreach ($files as $path => $info) {
if ($info->isFile()) {
yield $path => $info;
}
}
} elseif (is_file($path)) {
yield $path => new \SplFileInfo($path);
}
}
return;
}
if (!class_exists(Finder::class)) {
throw new \LogicException(sprintf('Extended glob pattern "%s" cannot be used as the Finder component is not installed.', $this->pattern));
}
$finder = new Finder();
$regex = Glob::toRegex($this->pattern);
if ($this->recursive) {
$regex = substr_replace($regex, '(/|$)', -2, 1);
}
$prefixLen = \strlen($this->prefix);
foreach ($finder->followLinks()->sortByName()->in($this->prefix) as $path => $info) {
if (preg_match($regex, substr('\\' === \DIRECTORY_SEPARATOR ? str_replace('\\', '/', $path) : $path, $prefixLen)) && $info->isFile()) {
yield $path => $info;
}
}
}
private function computeHash()
{
$hash = hash_init('md5');
foreach ($this->getIterator() as $path => $info) {
hash_update($hash, $path."\n");
}
return hash_final($hash);
}
}

View file

@ -0,0 +1,200 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Resource;
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ReflectionClassResource implements SelfCheckingResourceInterface, \Serializable
{
private $files = array();
private $className;
private $classReflector;
private $excludedVendors = array();
private $hash;
public function __construct(\ReflectionClass $classReflector, $excludedVendors = array())
{
$this->className = $classReflector->name;
$this->classReflector = $classReflector;
$this->excludedVendors = $excludedVendors;
}
public function isFresh($timestamp)
{
if (null === $this->hash) {
$this->hash = $this->computeHash();
$this->loadFiles($this->classReflector);
}
foreach ($this->files as $file => $v) {
if (false === $filemtime = @filemtime($file)) {
return false;
}
if ($filemtime > $timestamp) {
return $this->hash === $this->computeHash();
}
}
return true;
}
public function __toString()
{
return 'reflection.'.$this->className;
}
public function serialize()
{
if (null === $this->hash) {
$this->hash = $this->computeHash();
$this->loadFiles($this->classReflector);
}
return serialize(array($this->files, $this->className, $this->hash));
}
public function unserialize($serialized)
{
list($this->files, $this->className, $this->hash) = unserialize($serialized);
}
private function loadFiles(\ReflectionClass $class)
{
foreach ($class->getInterfaces() as $v) {
$this->loadFiles($v);
}
do {
$file = $class->getFileName();
if (false !== $file && file_exists($file)) {
foreach ($this->excludedVendors as $vendor) {
if (0 === strpos($file, $vendor) && false !== strpbrk(substr($file, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) {
$file = false;
break;
}
}
if ($file) {
$this->files[$file] = null;
}
}
foreach ($class->getTraits() as $v) {
$this->loadFiles($v);
}
} while ($class = $class->getParentClass());
}
private function computeHash()
{
if (null === $this->classReflector) {
try {
$this->classReflector = new \ReflectionClass($this->className);
} catch (\ReflectionException $e) {
// the class does not exist anymore
return false;
}
}
$hash = hash_init('md5');
foreach ($this->generateSignature($this->classReflector) as $info) {
hash_update($hash, $info);
}
return hash_final($hash);
}
private function generateSignature(\ReflectionClass $class)
{
yield $class->getDocComment();
yield (int) $class->isFinal();
yield (int) $class->isAbstract();
if ($class->isTrait()) {
yield print_r(class_uses($class->name), true);
} else {
yield print_r(class_parents($class->name), true);
yield print_r(class_implements($class->name), true);
yield print_r($class->getConstants(), true);
}
if (!$class->isInterface()) {
$defaults = $class->getDefaultProperties();
foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED) as $p) {
yield $p->getDocComment().$p;
yield print_r($defaults[$p->name], true);
}
}
if (\defined('HHVM_VERSION')) {
foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $m) {
// workaround HHVM bug with variadics, see https://github.com/facebook/hhvm/issues/5762
yield preg_replace('/^ @@.*/m', '', new ReflectionMethodHhvmWrapper($m->class, $m->name));
}
} else {
foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $m) {
yield preg_replace('/^ @@.*/m', '', $m);
$defaults = array();
foreach ($m->getParameters() as $p) {
$defaults[$p->name] = $p->isDefaultValueAvailable() ? $p->getDefaultValue() : null;
}
yield print_r($defaults, true);
}
}
if ($class->isAbstract() || $class->isInterface() || $class->isTrait()) {
return;
}
if (interface_exists(EventSubscriberInterface::class, false) && $class->isSubclassOf(EventSubscriberInterface::class)) {
yield EventSubscriberInterface::class;
yield print_r(\call_user_func(array($class->name, 'getSubscribedEvents')), true);
}
if (interface_exists(ServiceSubscriberInterface::class, false) && $class->isSubclassOf(ServiceSubscriberInterface::class)) {
yield ServiceSubscriberInterface::class;
yield print_r(\call_user_func(array($class->name, 'getSubscribedServices')), true);
}
}
}
/**
* @internal
*/
class ReflectionMethodHhvmWrapper extends \ReflectionMethod
{
public function getParameters()
{
$params = array();
foreach (parent::getParameters() as $i => $p) {
$params[] = new ReflectionParameterHhvmWrapper(array($this->class, $this->name), $i);
}
return $params;
}
}
/**
* @internal
*/
class ReflectionParameterHhvmWrapper extends \ReflectionParameter
{
public function getDefaultValue()
{
return array($this->isVariadic(), $this->isDefaultValueAvailable() ? parent::getDefaultValue() : null);
}
}

View file

@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Resource;
/**
* ResourceInterface is the interface that must be implemented by all Resource classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface ResourceInterface
{
/**
* Returns a string representation of the Resource.
*
* This method is necessary to allow for resource de-duplication, for example by means
* of array_unique(). The string returned need not have a particular meaning, but has
* to be identical for different ResourceInterface instances referring to the same
* resource; and it should be unlikely to collide with that of other, unrelated
* resource instances.
*
* @return string A string representation unique to the underlying Resource
*/
public function __toString();
}

View file

@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Resource;
use Symfony\Component\Config\ResourceCheckerInterface;
/**
* Resource checker for instances of SelfCheckingResourceInterface.
*
* As these resources perform the actual check themselves, we can provide
* this class as a standard way of validating them.
*
* @author Matthias Pigulla <mp@webfactory.de>
*/
class SelfCheckingResourceChecker implements ResourceCheckerInterface
{
public function supports(ResourceInterface $metadata)
{
return $metadata instanceof SelfCheckingResourceInterface;
}
public function isFresh(ResourceInterface $resource, $timestamp)
{
/* @var SelfCheckingResourceInterface $resource */
return $resource->isFresh($timestamp);
}
}

View file

@ -0,0 +1,30 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Resource;
/**
* Interface for Resources that can check for freshness autonomously,
* without special support from external services.
*
* @author Matthias Pigulla <mp@webfactory.de>
*/
interface SelfCheckingResourceInterface extends ResourceInterface
{
/**
* Returns true if the resource has not been updated since the given timestamp.
*
* @param int $timestamp The last time the resource was loaded
*
* @return bool True if the resource has not been updated, false otherwise
*/
public function isFresh($timestamp);
}

View file

@ -0,0 +1,182 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
use Symfony\Component\Config\Resource\ResourceInterface;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem;
/**
* ResourceCheckerConfigCache uses instances of ResourceCheckerInterface
* to check whether cached data is still fresh.
*
* @author Matthias Pigulla <mp@webfactory.de>
*/
class ResourceCheckerConfigCache implements ConfigCacheInterface
{
/**
* @var string
*/
private $file;
/**
* @var iterable|ResourceCheckerInterface[]
*/
private $resourceCheckers;
/**
* @param string $file The absolute cache path
* @param iterable|ResourceCheckerInterface[] $resourceCheckers The ResourceCheckers to use for the freshness check
*/
public function __construct($file, $resourceCheckers = array())
{
$this->file = $file;
$this->resourceCheckers = $resourceCheckers;
}
/**
* {@inheritdoc}
*/
public function getPath()
{
return $this->file;
}
/**
* Checks if the cache is still fresh.
*
* This implementation will make a decision solely based on the ResourceCheckers
* passed in the constructor.
*
* The first ResourceChecker that supports a given resource is considered authoritative.
* Resources with no matching ResourceChecker will silently be ignored and considered fresh.
*
* @return bool true if the cache is fresh, false otherwise
*/
public function isFresh()
{
if (!is_file($this->file)) {
return false;
}
if ($this->resourceCheckers instanceof \Traversable && !$this->resourceCheckers instanceof \Countable) {
$this->resourceCheckers = iterator_to_array($this->resourceCheckers);
}
if (!\count($this->resourceCheckers)) {
return true; // shortcut - if we don't have any checkers we don't need to bother with the meta file at all
}
$metadata = $this->getMetaFile();
if (!is_file($metadata)) {
return false;
}
$meta = $this->safelyUnserialize($metadata);
if (false === $meta) {
return false;
}
$time = filemtime($this->file);
foreach ($meta as $resource) {
/* @var ResourceInterface $resource */
foreach ($this->resourceCheckers as $checker) {
if (!$checker->supports($resource)) {
continue; // next checker
}
if ($checker->isFresh($resource, $time)) {
break; // no need to further check this resource
}
return false; // cache is stale
}
// no suitable checker found, ignore this resource
}
return true;
}
/**
* Writes cache.
*
* @param string $content The content to write in the cache
* @param ResourceInterface[] $metadata An array of metadata
*
* @throws \RuntimeException When cache file can't be written
*/
public function write($content, array $metadata = null)
{
$mode = 0666;
$umask = umask();
$filesystem = new Filesystem();
$filesystem->dumpFile($this->file, $content);
try {
$filesystem->chmod($this->file, $mode, $umask);
} catch (IOException $e) {
// discard chmod failure (some filesystem may not support it)
}
if (null !== $metadata) {
$filesystem->dumpFile($this->getMetaFile(), serialize($metadata));
try {
$filesystem->chmod($this->getMetaFile(), $mode, $umask);
} catch (IOException $e) {
// discard chmod failure (some filesystem may not support it)
}
}
if (\function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN)) {
@opcache_invalidate($this->file, true);
}
}
/**
* Gets the meta file path.
*
* @return string The meta file path
*/
private function getMetaFile()
{
return $this->file.'.meta';
}
private function safelyUnserialize($file)
{
$e = null;
$meta = false;
$signalingException = new \UnexpectedValueException();
$prevUnserializeHandler = ini_set('unserialize_callback_func', '');
$prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = array()) use (&$prevErrorHandler, $signalingException) {
if (E_WARNING === $type && 'Class __PHP_Incomplete_Class has no unserializer' === $msg) {
throw $signalingException;
}
return $prevErrorHandler ? $prevErrorHandler($type, $msg, $file, $line, $context) : false;
});
try {
$meta = unserialize(file_get_contents($file));
} catch (\Error $e) {
} catch (\Exception $e) {
}
restore_error_handler();
ini_set('unserialize_callback_func', $prevUnserializeHandler);
if (null !== $e && $e !== $signalingException) {
throw $e;
}
return $meta;
}
}

View file

@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
/**
* A ConfigCacheFactory implementation that validates the
* cache with an arbitrary set of ResourceCheckers.
*
* @author Matthias Pigulla <mp@webfactory.de>
*/
class ResourceCheckerConfigCacheFactory implements ConfigCacheFactoryInterface
{
private $resourceCheckers = array();
/**
* @param iterable|ResourceCheckerInterface[] $resourceCheckers
*/
public function __construct($resourceCheckers = array())
{
$this->resourceCheckers = $resourceCheckers;
}
/**
* {@inheritdoc}
*/
public function cache($file, $callback)
{
if (!\is_callable($callback)) {
throw new \InvalidArgumentException(sprintf('Invalid type for callback argument. Expected callable, but got "%s".', \gettype($callback)));
}
$cache = new ResourceCheckerConfigCache($file, $this->resourceCheckers);
if (!$cache->isFresh()) {
\call_user_func($callback, $cache);
}
return $cache;
}
}

View file

@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
use Symfony\Component\Config\Resource\ResourceInterface;
/**
* Interface for ResourceCheckers.
*
* When a ResourceCheckerConfigCache instance is checked for freshness, all its associated
* metadata resources are passed to ResourceCheckers. The ResourceCheckers
* can then inspect the resources and decide whether the cache can be considered
* fresh or not.
*
* @author Matthias Pigulla <mp@webfactory.de>
* @author Benjamin Klotz <bk@webfactory.de>
*/
interface ResourceCheckerInterface
{
/**
* Queries the ResourceChecker whether it can validate a given
* resource or not.
*
* @param ResourceInterface $metadata The resource to be checked for freshness
*
* @return bool True if the ResourceChecker can handle this resource type, false if not
*/
public function supports(ResourceInterface $metadata);
/**
* Validates the resource.
*
* @param ResourceInterface $resource The resource to be validated
* @param int $timestamp The timestamp at which the cache associated with this resource was created
*
* @return bool True if the resource has not changed since the given timestamp, false otherwise
*/
public function isFresh(ResourceInterface $resource, $timestamp);
}

View file

@ -0,0 +1,29 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\ConfigCacheFactory;
class ConfigCacheFactoryTest extends TestCase
{
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Invalid type for callback argument. Expected callable, but got "object".
*/
public function testCacheWithInvalidCallback()
{
$cacheFactory = new ConfigCacheFactory(true);
$cacheFactory->cache('file', new \stdClass());
}
}

View file

@ -0,0 +1,99 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\ConfigCache;
use Symfony\Component\Config\Tests\Resource\ResourceStub;
class ConfigCacheTest extends TestCase
{
private $cacheFile = null;
protected function setUp()
{
$this->cacheFile = tempnam(sys_get_temp_dir(), 'config_');
}
protected function tearDown()
{
$files = array($this->cacheFile, $this->cacheFile.'.meta');
foreach ($files as $file) {
if (file_exists($file)) {
unlink($file);
}
}
}
/**
* @dataProvider debugModes
*/
public function testCacheIsNotValidIfNothingHasBeenCached($debug)
{
unlink($this->cacheFile); // remove tempnam() side effect
$cache = new ConfigCache($this->cacheFile, $debug);
$this->assertFalse($cache->isFresh());
}
public function testIsAlwaysFreshInProduction()
{
$staleResource = new ResourceStub();
$staleResource->setFresh(false);
$cache = new ConfigCache($this->cacheFile, false);
$cache->write('', array($staleResource));
$this->assertTrue($cache->isFresh());
}
/**
* @dataProvider debugModes
*/
public function testIsFreshWhenNoResourceProvided($debug)
{
$cache = new ConfigCache($this->cacheFile, $debug);
$cache->write('', array());
$this->assertTrue($cache->isFresh());
}
public function testFreshResourceInDebug()
{
$freshResource = new ResourceStub();
$freshResource->setFresh(true);
$cache = new ConfigCache($this->cacheFile, true);
$cache->write('', array($freshResource));
$this->assertTrue($cache->isFresh());
}
public function testStaleResourceInDebug()
{
$staleResource = new ResourceStub();
$staleResource->setFresh(false);
$cache = new ConfigCache($this->cacheFile, true);
$cache->write('', array($staleResource));
$this->assertFalse($cache->isFresh());
}
public function debugModes()
{
return array(
array(true),
array(false),
);
}
}

View file

@ -0,0 +1,251 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Tests\Definition;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\ArrayNode;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Config\Definition\ScalarNode;
class ArrayNodeTest extends TestCase
{
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidTypeException
*/
public function testNormalizeThrowsExceptionWhenFalseIsNotAllowed()
{
$node = new ArrayNode('root');
$node->normalize(false);
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
* @expectedExceptionMessage Unrecognized option "foo" under "root"
*/
public function testExceptionThrownOnUnrecognizedChild()
{
$node = new ArrayNode('root');
$node->normalize(array('foo' => 'bar'));
}
public function ignoreAndRemoveMatrixProvider()
{
$unrecognizedOptionException = new InvalidConfigurationException('Unrecognized option "foo" under "root"');
return array(
array(true, true, array(), 'no exception is thrown for an unrecognized child if the ignoreExtraKeys option is set to true'),
array(true, false, array('foo' => 'bar'), 'extra keys are not removed when ignoreExtraKeys second option is set to false'),
array(false, true, $unrecognizedOptionException),
array(false, false, $unrecognizedOptionException),
);
}
/**
* @dataProvider ignoreAndRemoveMatrixProvider
*/
public function testIgnoreAndRemoveBehaviors($ignore, $remove, $expected, $message = '')
{
if ($expected instanceof \Exception) {
if (method_exists($this, 'expectException')) {
$this->expectException(\get_class($expected));
$this->expectExceptionMessage($expected->getMessage());
} else {
$this->setExpectedException(\get_class($expected), $expected->getMessage());
}
}
$node = new ArrayNode('root');
$node->setIgnoreExtraKeys($ignore, $remove);
$result = $node->normalize(array('foo' => 'bar'));
$this->assertSame($expected, $result, $message);
}
/**
* @dataProvider getPreNormalizationTests
*/
public function testPreNormalize($denormalized, $normalized)
{
$node = new ArrayNode('foo');
$r = new \ReflectionMethod($node, 'preNormalize');
$r->setAccessible(true);
$this->assertSame($normalized, $r->invoke($node, $denormalized));
}
public function getPreNormalizationTests()
{
return array(
array(
array('foo-bar' => 'foo'),
array('foo_bar' => 'foo'),
),
array(
array('foo-bar_moo' => 'foo'),
array('foo-bar_moo' => 'foo'),
),
array(
array('anything-with-dash-and-no-underscore' => 'first', 'no_dash' => 'second'),
array('anything_with_dash_and_no_underscore' => 'first', 'no_dash' => 'second'),
),
array(
array('foo-bar' => null, 'foo_bar' => 'foo'),
array('foo-bar' => null, 'foo_bar' => 'foo'),
),
);
}
/**
* @dataProvider getZeroNamedNodeExamplesData
*/
public function testNodeNameCanBeZero($denormalized, $normalized)
{
$zeroNode = new ArrayNode(0);
$zeroNode->addChild(new ScalarNode('name'));
$fiveNode = new ArrayNode(5);
$fiveNode->addChild(new ScalarNode(0));
$fiveNode->addChild(new ScalarNode('new_key'));
$rootNode = new ArrayNode('root');
$rootNode->addChild($zeroNode);
$rootNode->addChild($fiveNode);
$rootNode->addChild(new ScalarNode('string_key'));
$r = new \ReflectionMethod($rootNode, 'normalizeValue');
$r->setAccessible(true);
$this->assertSame($normalized, $r->invoke($rootNode, $denormalized));
}
public function getZeroNamedNodeExamplesData()
{
return array(
array(
array(
0 => array(
'name' => 'something',
),
5 => array(
0 => 'this won\'t work too',
'new_key' => 'some other value',
),
'string_key' => 'just value',
),
array(
0 => array(
'name' => 'something',
),
5 => array(
0 => 'this won\'t work too',
'new_key' => 'some other value',
),
'string_key' => 'just value',
),
),
);
}
/**
* @dataProvider getPreNormalizedNormalizedOrderedData
*/
public function testChildrenOrderIsMaintainedOnNormalizeValue($prenormalized, $normalized)
{
$scalar1 = new ScalarNode('1');
$scalar2 = new ScalarNode('2');
$scalar3 = new ScalarNode('3');
$node = new ArrayNode('foo');
$node->addChild($scalar1);
$node->addChild($scalar3);
$node->addChild($scalar2);
$r = new \ReflectionMethod($node, 'normalizeValue');
$r->setAccessible(true);
$this->assertSame($normalized, $r->invoke($node, $prenormalized));
}
public function getPreNormalizedNormalizedOrderedData()
{
return array(
array(
array('2' => 'two', '1' => 'one', '3' => 'three'),
array('2' => 'two', '1' => 'one', '3' => 'three'),
),
);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Child nodes must be named.
*/
public function testAddChildEmptyName()
{
$node = new ArrayNode('root');
$childNode = new ArrayNode('');
$node->addChild($childNode);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage A child node named "foo" already exists.
*/
public function testAddChildNameAlreadyExists()
{
$node = new ArrayNode('root');
$childNode = new ArrayNode('foo');
$node->addChild($childNode);
$childNodeWithSameName = new ArrayNode('foo');
$node->addChild($childNodeWithSameName);
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage The node at path "foo" has no default value.
*/
public function testGetDefaultValueWithoutDefaultValue()
{
$node = new ArrayNode('foo');
$node->getDefaultValue();
}
public function testSetDeprecated()
{
$childNode = new ArrayNode('foo');
$childNode->setDeprecated('"%node%" is deprecated');
$this->assertTrue($childNode->isDeprecated());
$this->assertSame('"foo" is deprecated', $childNode->getDeprecationMessage($childNode->getName(), $childNode->getPath()));
$node = new ArrayNode('root');
$node->addChild($childNode);
$deprecationTriggered = false;
$deprecationHandler = function ($level, $message, $file, $line) use (&$prevErrorHandler, &$deprecationTriggered) {
if (E_USER_DEPRECATED === $level) {
return $deprecationTriggered = true;
}
return $prevErrorHandler ? $prevErrorHandler($level, $message, $file, $line) : false;
};
$prevErrorHandler = set_error_handler($deprecationHandler);
$node->finalize(array());
restore_error_handler();
$this->assertFalse($deprecationTriggered, '->finalize() should not trigger if the deprecated node is not set');
$prevErrorHandler = set_error_handler($deprecationHandler);
$node->finalize(array('foo' => array()));
restore_error_handler();
$this->assertTrue($deprecationTriggered, '->finalize() should trigger if the deprecated node is set');
}
}

View file

@ -0,0 +1,74 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Tests\Definition;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\BooleanNode;
class BooleanNodeTest extends TestCase
{
/**
* @dataProvider getValidValues
*/
public function testNormalize($value)
{
$node = new BooleanNode('test');
$this->assertSame($value, $node->normalize($value));
}
/**
* @dataProvider getValidValues
*
* @param bool $value
*/
public function testValidNonEmptyValues($value)
{
$node = new BooleanNode('test');
$node->setAllowEmptyValue(false);
$this->assertSame($value, $node->finalize($value));
}
public function getValidValues()
{
return array(
array(false),
array(true),
);
}
/**
* @dataProvider getInvalidValues
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidTypeException
*/
public function testNormalizeThrowsExceptionOnInvalidValues($value)
{
$node = new BooleanNode('test');
$node->normalize($value);
}
public function getInvalidValues()
{
return array(
array(null),
array(''),
array('foo'),
array(0),
array(1),
array(0.0),
array(0.1),
array(array()),
array(array('foo' => 'bar')),
array(new \stdClass()),
);
}
}

View file

@ -0,0 +1,347 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Tests\Definition\Builder;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\ScalarNodeDefinition;
use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
use Symfony\Component\Config\Definition\Processor;
class ArrayNodeDefinitionTest extends TestCase
{
public function testAppendingSomeNode()
{
$parent = new ArrayNodeDefinition('root');
$child = new ScalarNodeDefinition('child');
$parent
->children()
->scalarNode('foo')->end()
->scalarNode('bar')->end()
->end()
->append($child);
$this->assertCount(3, $this->getField($parent, 'children'));
$this->assertContains($child, $this->getField($parent, 'children'));
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidDefinitionException
* @dataProvider providePrototypeNodeSpecificCalls
*/
public function testPrototypeNodeSpecificOption($method, $args)
{
$node = new ArrayNodeDefinition('root');
\call_user_func_array(array($node, $method), $args);
$node->getNode();
}
public function providePrototypeNodeSpecificCalls()
{
return array(
array('defaultValue', array(array())),
array('addDefaultChildrenIfNoneSet', array()),
array('requiresAtLeastOneElement', array()),
array('useAttributeAsKey', array('foo')),
);
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidDefinitionException
*/
public function testConcreteNodeSpecificOption()
{
$node = new ArrayNodeDefinition('root');
$node
->addDefaultsIfNotSet()
->prototype('array')
;
$node->getNode();
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidDefinitionException
*/
public function testPrototypeNodesCantHaveADefaultValueWhenUsingDefaultChildren()
{
$node = new ArrayNodeDefinition('root');
$node
->defaultValue(array())
->addDefaultChildrenIfNoneSet('foo')
->prototype('array')
;
$node->getNode();
}
public function testPrototypedArrayNodeDefaultWhenUsingDefaultChildren()
{
$node = new ArrayNodeDefinition('root');
$node
->addDefaultChildrenIfNoneSet()
->prototype('array')
;
$tree = $node->getNode();
$this->assertEquals(array(array()), $tree->getDefaultValue());
}
/**
* @dataProvider providePrototypedArrayNodeDefaults
*/
public function testPrototypedArrayNodeDefault($args, $shouldThrowWhenUsingAttrAsKey, $shouldThrowWhenNotUsingAttrAsKey, $defaults)
{
$node = new ArrayNodeDefinition('root');
$node
->addDefaultChildrenIfNoneSet($args)
->prototype('array')
;
try {
$tree = $node->getNode();
$this->assertFalse($shouldThrowWhenNotUsingAttrAsKey);
$this->assertEquals($defaults, $tree->getDefaultValue());
} catch (InvalidDefinitionException $e) {
$this->assertTrue($shouldThrowWhenNotUsingAttrAsKey);
}
$node = new ArrayNodeDefinition('root');
$node
->useAttributeAsKey('attr')
->addDefaultChildrenIfNoneSet($args)
->prototype('array')
;
try {
$tree = $node->getNode();
$this->assertFalse($shouldThrowWhenUsingAttrAsKey);
$this->assertEquals($defaults, $tree->getDefaultValue());
} catch (InvalidDefinitionException $e) {
$this->assertTrue($shouldThrowWhenUsingAttrAsKey);
}
}
public function providePrototypedArrayNodeDefaults()
{
return array(
array(null, true, false, array(array())),
array(2, true, false, array(array(), array())),
array('2', false, true, array('2' => array())),
array('foo', false, true, array('foo' => array())),
array(array('foo'), false, true, array('foo' => array())),
array(array('foo', 'bar'), false, true, array('foo' => array(), 'bar' => array())),
);
}
public function testNestedPrototypedArrayNodes()
{
$nodeDefinition = new ArrayNodeDefinition('root');
$nodeDefinition
->addDefaultChildrenIfNoneSet()
->prototype('array')
->prototype('array')
;
$node = $nodeDefinition->getNode();
$this->assertInstanceOf('Symfony\Component\Config\Definition\PrototypedArrayNode', $node);
$this->assertInstanceOf('Symfony\Component\Config\Definition\PrototypedArrayNode', $node->getPrototype());
}
public function testEnabledNodeDefaults()
{
$node = new ArrayNodeDefinition('root');
$node
->canBeEnabled()
->children()
->scalarNode('foo')->defaultValue('bar')->end()
;
$this->assertEquals(array('enabled' => false, 'foo' => 'bar'), $node->getNode()->getDefaultValue());
}
/**
* @dataProvider getEnableableNodeFixtures
*/
public function testTrueEnableEnabledNode($expected, $config, $message)
{
$processor = new Processor();
$node = new ArrayNodeDefinition('root');
$node
->canBeEnabled()
->children()
->scalarNode('foo')->defaultValue('bar')->end()
;
$this->assertEquals(
$expected,
$processor->process($node->getNode(), $config),
$message
);
}
public function testCanBeDisabled()
{
$node = new ArrayNodeDefinition('root');
$node->canBeDisabled();
$this->assertTrue($this->getField($node, 'addDefaults'));
$this->assertEquals(array('enabled' => false), $this->getField($node, 'falseEquivalent'));
$this->assertEquals(array('enabled' => true), $this->getField($node, 'trueEquivalent'));
$this->assertEquals(array('enabled' => true), $this->getField($node, 'nullEquivalent'));
$nodeChildren = $this->getField($node, 'children');
$this->assertArrayHasKey('enabled', $nodeChildren);
$enabledNode = $nodeChildren['enabled'];
$this->assertTrue($this->getField($enabledNode, 'default'));
$this->assertTrue($this->getField($enabledNode, 'defaultValue'));
}
public function testIgnoreExtraKeys()
{
$node = new ArrayNodeDefinition('root');
$this->assertFalse($this->getField($node, 'ignoreExtraKeys'));
$result = $node->ignoreExtraKeys();
$this->assertEquals($node, $result);
$this->assertTrue($this->getField($node, 'ignoreExtraKeys'));
}
public function testNormalizeKeys()
{
$node = new ArrayNodeDefinition('root');
$this->assertTrue($this->getField($node, 'normalizeKeys'));
$result = $node->normalizeKeys(false);
$this->assertEquals($node, $result);
$this->assertFalse($this->getField($node, 'normalizeKeys'));
}
public function testPrototypeVariable()
{
$node = new ArrayNodeDefinition('root');
$this->assertEquals($node->prototype('variable'), $node->variablePrototype());
}
public function testPrototypeScalar()
{
$node = new ArrayNodeDefinition('root');
$this->assertEquals($node->prototype('scalar'), $node->scalarPrototype());
}
public function testPrototypeBoolean()
{
$node = new ArrayNodeDefinition('root');
$this->assertEquals($node->prototype('boolean'), $node->booleanPrototype());
}
public function testPrototypeInteger()
{
$node = new ArrayNodeDefinition('root');
$this->assertEquals($node->prototype('integer'), $node->integerPrototype());
}
public function testPrototypeFloat()
{
$node = new ArrayNodeDefinition('root');
$this->assertEquals($node->prototype('float'), $node->floatPrototype());
}
public function testPrototypeArray()
{
$node = new ArrayNodeDefinition('root');
$this->assertEquals($node->prototype('array'), $node->arrayPrototype());
}
public function testPrototypeEnum()
{
$node = new ArrayNodeDefinition('root');
$this->assertEquals($node->prototype('enum'), $node->enumPrototype());
}
public function getEnableableNodeFixtures()
{
return array(
array(array('enabled' => true, 'foo' => 'bar'), array(true), 'true enables an enableable node'),
array(array('enabled' => true, 'foo' => 'bar'), array(null), 'null enables an enableable node'),
array(array('enabled' => true, 'foo' => 'bar'), array(array('enabled' => true)), 'An enableable node can be enabled'),
array(array('enabled' => true, 'foo' => 'baz'), array(array('foo' => 'baz')), 'any configuration enables an enableable node'),
array(array('enabled' => false, 'foo' => 'baz'), array(array('foo' => 'baz', 'enabled' => false)), 'An enableable node can be disabled'),
array(array('enabled' => false, 'foo' => 'bar'), array(false), 'false disables an enableable node'),
);
}
public function testRequiresAtLeastOneElement()
{
$node = new ArrayNodeDefinition('root');
$node
->requiresAtLeastOneElement()
->integerPrototype();
$node->getNode()->finalize(array(1));
$this->addToAssertionCount(1);
}
/**
* @group legacy
* @expectedDeprecation Using Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition::cannotBeEmpty() at path "root" has no effect, consider requiresAtLeastOneElement() instead. In 4.0 both methods will behave the same.
*/
public function testCannotBeEmpty()
{
$node = new ArrayNodeDefinition('root');
$node
->cannotBeEmpty()
->integerPrototype();
$node->getNode()->finalize(array());
}
public function testSetDeprecated()
{
$node = new ArrayNodeDefinition('root');
$node
->children()
->arrayNode('foo')->setDeprecated('The "%path%" node is deprecated.')->end()
->end()
;
$deprecatedNode = $node->getNode()->getChildren()['foo'];
$this->assertTrue($deprecatedNode->isDeprecated());
$this->assertSame('The "root.foo" node is deprecated.', $deprecatedNode->getDeprecationMessage($deprecatedNode->getName(), $deprecatedNode->getPath()));
}
/**
* @group legacy
* @expectedDeprecation ->cannotBeEmpty() is not applicable to concrete nodes at path "root". In 4.0 it will throw an exception.
*/
public function testCannotBeEmptyOnConcreteNode()
{
$node = new ArrayNodeDefinition('root');
$node->cannotBeEmpty();
$node->getNode()->finalize(array());
}
protected function getField($object, $field)
{
$reflection = new \ReflectionProperty($object, $field);
$reflection->setAccessible(true);
return $reflection->getValue($object);
}
}

View file

@ -0,0 +1,39 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Tests\Definition\Builder;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\Builder\BooleanNodeDefinition;
class BooleanNodeDefinitionTest extends TestCase
{
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidDefinitionException
* @expectedExceptionMessage ->cannotBeEmpty() is not applicable to BooleanNodeDefinition.
*/
public function testCannotBeEmptyThrowsAnException()
{
$def = new BooleanNodeDefinition('foo');
$def->cannotBeEmpty();
}
public function testSetDeprecated()
{
$def = new BooleanNodeDefinition('foo');
$def->setDeprecated('The "%path%" node is deprecated.');
$node = $def->getNode();
$this->assertTrue($node->isDeprecated());
$this->assertSame('The "foo" node is deprecated.', $node->getDeprecationMessage($node->getName(), $node->getPath()));
}
}

View file

@ -0,0 +1,77 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Tests\Definition\Builder;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\Builder\EnumNodeDefinition;
class EnumNodeDefinitionTest extends TestCase
{
public function testWithOneValue()
{
$def = new EnumNodeDefinition('foo');
$def->values(array('foo'));
$node = $def->getNode();
$this->assertEquals(array('foo'), $node->getValues());
}
public function testWithOneDistinctValue()
{
$def = new EnumNodeDefinition('foo');
$def->values(array('foo', 'foo'));
$node = $def->getNode();
$this->assertEquals(array('foo'), $node->getValues());
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage You must call ->values() on enum nodes.
*/
public function testNoValuesPassed()
{
$def = new EnumNodeDefinition('foo');
$def->getNode();
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage ->values() must be called with at least one value.
*/
public function testWithNoValues()
{
$def = new EnumNodeDefinition('foo');
$def->values(array());
}
public function testGetNode()
{
$def = new EnumNodeDefinition('foo');
$def->values(array('foo', 'bar'));
$node = $def->getNode();
$this->assertEquals(array('foo', 'bar'), $node->getValues());
}
public function testSetDeprecated()
{
$def = new EnumNodeDefinition('foo');
$def->values(array('foo', 'bar'));
$def->setDeprecated('The "%path%" node is deprecated.');
$node = $def->getNode();
$this->assertTrue($node->isDeprecated());
$this->assertSame('The "foo" node is deprecated.', $def->getNode()->getDeprecationMessage($node->getName(), $node->getPath()));
}
}

View file

@ -0,0 +1,270 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Tests\Definition\Builder;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
class ExprBuilderTest extends TestCase
{
public function testAlwaysExpression()
{
$test = $this->getTestBuilder()
->always($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('new_value', $test);
}
public function testIfTrueExpression()
{
$test = $this->getTestBuilder()
->ifTrue()
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('new_value', $test, array('key' => true));
$test = $this->getTestBuilder()
->ifTrue(function ($v) { return true; })
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('new_value', $test);
$test = $this->getTestBuilder()
->ifTrue(function ($v) { return false; })
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('value', $test);
}
public function testIfStringExpression()
{
$test = $this->getTestBuilder()
->ifString()
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('new_value', $test);
$test = $this->getTestBuilder()
->ifString()
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs(45, $test, array('key' => 45));
}
public function testIfNullExpression()
{
$test = $this->getTestBuilder()
->ifNull()
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('new_value', $test, array('key' => null));
$test = $this->getTestBuilder()
->ifNull()
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('value', $test);
}
public function testIfEmptyExpression()
{
$test = $this->getTestBuilder()
->ifEmpty()
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('new_value', $test, array('key' => array()));
$test = $this->getTestBuilder()
->ifEmpty()
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('value', $test);
}
public function testIfArrayExpression()
{
$test = $this->getTestBuilder()
->ifArray()
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('new_value', $test, array('key' => array()));
$test = $this->getTestBuilder()
->ifArray()
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('value', $test);
}
public function testIfInArrayExpression()
{
$test = $this->getTestBuilder()
->ifInArray(array('foo', 'bar', 'value'))
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('new_value', $test);
$test = $this->getTestBuilder()
->ifInArray(array('foo', 'bar'))
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('value', $test);
}
public function testIfNotInArrayExpression()
{
$test = $this->getTestBuilder()
->ifNotInArray(array('foo', 'bar'))
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('new_value', $test);
$test = $this->getTestBuilder()
->ifNotInArray(array('foo', 'bar', 'value_from_config'))
->then($this->returnClosure('new_value'))
->end();
$this->assertFinalizedValueIs('new_value', $test);
}
public function testThenEmptyArrayExpression()
{
$test = $this->getTestBuilder()
->ifString()
->thenEmptyArray()
->end();
$this->assertFinalizedValueIs(array(), $test);
}
/**
* @dataProvider castToArrayValues
*/
public function testcastToArrayExpression($configValue, $expectedValue)
{
$test = $this->getTestBuilder()
->castToArray()
->end();
$this->assertFinalizedValueIs($expectedValue, $test, array('key' => $configValue));
}
public function castToArrayValues()
{
yield array('value', array('value'));
yield array(-3.14, array(-3.14));
yield array(null, array(null));
yield array(array('value'), array('value'));
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
*/
public function testThenInvalid()
{
$test = $this->getTestBuilder()
->ifString()
->thenInvalid('Invalid value')
->end();
$this->finalizeTestBuilder($test);
}
public function testThenUnsetExpression()
{
$test = $this->getTestBuilder()
->ifString()
->thenUnset()
->end();
$this->assertEquals(array(), $this->finalizeTestBuilder($test));
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage You must specify an if part.
*/
public function testEndIfPartNotSpecified()
{
$this->getTestBuilder()->end();
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage You must specify a then part.
*/
public function testEndThenPartNotSpecified()
{
$builder = $this->getTestBuilder();
$builder->ifPart = 'test';
$builder->end();
}
/**
* Create a test treebuilder with a variable node, and init the validation.
*
* @return TreeBuilder
*/
protected function getTestBuilder()
{
$builder = new TreeBuilder();
return $builder
->root('test')
->children()
->variableNode('key')
->validate()
;
}
/**
* Close the validation process and finalize with the given config.
*
* @param TreeBuilder $testBuilder The tree builder to finalize
* @param array $config The config you want to use for the finalization, if nothing provided
* a simple array('key'=>'value') will be used
*
* @return array The finalized config values
*/
protected function finalizeTestBuilder($testBuilder, $config = null)
{
return $testBuilder
->end()
->end()
->end()
->buildTree()
->finalize(null === $config ? array('key' => 'value') : $config)
;
}
/**
* Return a closure that will return the given value.
*
* @param mixed $val The value that the closure must return
*
* @return \Closure
*/
protected function returnClosure($val)
{
return function ($v) use ($val) {
return $val;
};
}
/**
* Assert that the given test builder, will return the given value.
*
* @param mixed $value The value to test
* @param TreeBuilder $treeBuilder The tree builder to finalize
* @param mixed $config The config values that new to be finalized
*/
protected function assertFinalizedValueIs($value, $treeBuilder, $config = null)
{
$this->assertEquals(array('key' => $value), $this->finalizeTestBuilder($treeBuilder, $config));
}
}

View file

@ -0,0 +1,95 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Tests\Definition\Builder;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\Builder\NodeBuilder as BaseNodeBuilder;
use Symfony\Component\Config\Definition\Builder\VariableNodeDefinition as BaseVariableNodeDefinition;
class NodeBuilderTest extends TestCase
{
/**
* @expectedException \RuntimeException
*/
public function testThrowsAnExceptionWhenTryingToCreateANonRegisteredNodeType()
{
$builder = new BaseNodeBuilder();
$builder->node('', 'foobar');
}
/**
* @expectedException \RuntimeException
*/
public function testThrowsAnExceptionWhenTheNodeClassIsNotFound()
{
$builder = new BaseNodeBuilder();
$builder
->setNodeClass('noclasstype', '\\foo\\bar\\noclass')
->node('', 'noclasstype');
}
public function testAddingANewNodeType()
{
$class = __NAMESPACE__.'\\SomeNodeDefinition';
$builder = new BaseNodeBuilder();
$node = $builder
->setNodeClass('newtype', $class)
->node('', 'newtype');
$this->assertInstanceOf($class, $node);
}
public function testOverridingAnExistingNodeType()
{
$class = __NAMESPACE__.'\\SomeNodeDefinition';
$builder = new BaseNodeBuilder();
$node = $builder
->setNodeClass('variable', $class)
->node('', 'variable');
$this->assertInstanceOf($class, $node);
}
public function testNodeTypesAreNotCaseSensitive()
{
$builder = new BaseNodeBuilder();
$node1 = $builder->node('', 'VaRiAbLe');
$node2 = $builder->node('', 'variable');
$this->assertInstanceOf(\get_class($node1), $node2);
$builder->setNodeClass('CuStOm', __NAMESPACE__.'\\SomeNodeDefinition');
$node1 = $builder->node('', 'CUSTOM');
$node2 = $builder->node('', 'custom');
$this->assertInstanceOf(\get_class($node1), $node2);
}
public function testNumericNodeCreation()
{
$builder = new BaseNodeBuilder();
$node = $builder->integerNode('foo')->min(3)->max(5);
$this->assertInstanceOf('Symfony\Component\Config\Definition\Builder\IntegerNodeDefinition', $node);
$node = $builder->floatNode('bar')->min(3.0)->max(5.0);
$this->assertInstanceOf('Symfony\Component\Config\Definition\Builder\FloatNodeDefinition', $node);
}
}
class SomeNodeDefinition extends BaseVariableNodeDefinition
{
}

View file

@ -0,0 +1,104 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Tests\Definition\Builder;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\Builder\FloatNodeDefinition;
use Symfony\Component\Config\Definition\Builder\IntegerNodeDefinition;
use Symfony\Component\Config\Definition\Builder\IntegerNodeDefinition as NumericNodeDefinition;
class NumericNodeDefinitionTest extends TestCase
{
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage You cannot define a min(4) as you already have a max(3)
*/
public function testIncoherentMinAssertion()
{
$def = new NumericNodeDefinition('foo');
$def->max(3)->min(4);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage You cannot define a max(2) as you already have a min(3)
*/
public function testIncoherentMaxAssertion()
{
$node = new NumericNodeDefinition('foo');
$node->min(3)->max(2);
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
* @expectedExceptionMessage The value 4 is too small for path "foo". Should be greater than or equal to 5
*/
public function testIntegerMinAssertion()
{
$def = new IntegerNodeDefinition('foo');
$def->min(5)->getNode()->finalize(4);
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
* @expectedExceptionMessage The value 4 is too big for path "foo". Should be less than or equal to 3
*/
public function testIntegerMaxAssertion()
{
$def = new IntegerNodeDefinition('foo');
$def->max(3)->getNode()->finalize(4);
}
public function testIntegerValidMinMaxAssertion()
{
$def = new IntegerNodeDefinition('foo');
$node = $def->min(3)->max(7)->getNode();
$this->assertEquals(4, $node->finalize(4));
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
* @expectedExceptionMessage The value 400 is too small for path "foo". Should be greater than or equal to 500
*/
public function testFloatMinAssertion()
{
$def = new FloatNodeDefinition('foo');
$def->min(5E2)->getNode()->finalize(4e2);
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
* @expectedExceptionMessage The value 4.3 is too big for path "foo". Should be less than or equal to 0.3
*/
public function testFloatMaxAssertion()
{
$def = new FloatNodeDefinition('foo');
$def->max(0.3)->getNode()->finalize(4.3);
}
public function testFloatValidMinMaxAssertion()
{
$def = new FloatNodeDefinition('foo');
$node = $def->min(3.0)->max(7e2)->getNode();
$this->assertEquals(4.5, $node->finalize(4.5));
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidDefinitionException
* @expectedExceptionMessage ->cannotBeEmpty() is not applicable to NumericNodeDefinition.
*/
public function testCannotBeEmptyThrowsAnException()
{
$def = new NumericNodeDefinition('foo');
$def->cannotBeEmpty();
}
}

View file

@ -0,0 +1,134 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Tests\Definition\Builder;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Tests\Fixtures\Builder\NodeBuilder as CustomNodeBuilder;
class TreeBuilderTest extends TestCase
{
public function testUsingACustomNodeBuilder()
{
$builder = new TreeBuilder();
$root = $builder->root('custom', 'array', new CustomNodeBuilder());
$nodeBuilder = $root->children();
$this->assertInstanceOf('Symfony\Component\Config\Tests\Fixtures\Builder\NodeBuilder', $nodeBuilder);
$nodeBuilder = $nodeBuilder->arrayNode('deeper')->children();
$this->assertInstanceOf('Symfony\Component\Config\Tests\Fixtures\Builder\NodeBuilder', $nodeBuilder);
}
public function testOverrideABuiltInNodeType()
{
$builder = new TreeBuilder();
$root = $builder->root('override', 'array', new CustomNodeBuilder());
$definition = $root->children()->variableNode('variable');
$this->assertInstanceOf('Symfony\Component\Config\Tests\Fixtures\Builder\VariableNodeDefinition', $definition);
}
public function testAddANodeType()
{
$builder = new TreeBuilder();
$root = $builder->root('override', 'array', new CustomNodeBuilder());
$definition = $root->children()->barNode('variable');
$this->assertInstanceOf('Symfony\Component\Config\Tests\Fixtures\Builder\BarNodeDefinition', $definition);
}
public function testCreateABuiltInNodeTypeWithACustomNodeBuilder()
{
$builder = new TreeBuilder();
$root = $builder->root('builtin', 'array', new CustomNodeBuilder());
$definition = $root->children()->booleanNode('boolean');
$this->assertInstanceOf('Symfony\Component\Config\Definition\Builder\BooleanNodeDefinition', $definition);
}
public function testPrototypedArrayNodeUseTheCustomNodeBuilder()
{
$builder = new TreeBuilder();
$root = $builder->root('override', 'array', new CustomNodeBuilder());
$root->prototype('bar')->end();
$this->assertInstanceOf('Symfony\Component\Config\Tests\Fixtures\BarNode', $root->getNode(true)->getPrototype());
}
public function testAnExtendedNodeBuilderGetsPropagatedToTheChildren()
{
$builder = new TreeBuilder();
$builder->root('propagation')
->children()
->setNodeClass('extended', 'Symfony\Component\Config\Definition\Builder\BooleanNodeDefinition')
->node('foo', 'extended')->end()
->arrayNode('child')
->children()
->node('foo', 'extended')
->end()
->end()
->end()
->end();
$node = $builder->buildTree();
$children = $node->getChildren();
$this->assertInstanceOf('Symfony\Component\Config\Definition\BooleanNode', $children['foo']);
$childChildren = $children['child']->getChildren();
$this->assertInstanceOf('Symfony\Component\Config\Definition\BooleanNode', $childChildren['foo']);
}
public function testDefinitionInfoGetsTransferredToNode()
{
$builder = new TreeBuilder();
$builder->root('test')->info('root info')
->children()
->node('child', 'variable')->info('child info')->defaultValue('default')
->end()
->end();
$tree = $builder->buildTree();
$children = $tree->getChildren();
$this->assertEquals('root info', $tree->getInfo());
$this->assertEquals('child info', $children['child']->getInfo());
}
public function testDefinitionExampleGetsTransferredToNode()
{
$builder = new TreeBuilder();
$builder->root('test')
->example(array('key' => 'value'))
->children()
->node('child', 'variable')->info('child info')->defaultValue('default')->example('example')
->end()
->end();
$tree = $builder->buildTree();
$children = $tree->getChildren();
$this->assertInternalType('array', $tree->getExample());
$this->assertEquals('example', $children['child']->getExample());
}
}

View file

@ -0,0 +1,114 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Tests\Definition\Dumper;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\Dumper\XmlReferenceDumper;
use Symfony\Component\Config\Tests\Fixtures\Configuration\ExampleConfiguration;
class XmlReferenceDumperTest extends TestCase
{
public function testDumper()
{
$configuration = new ExampleConfiguration();
$dumper = new XmlReferenceDumper();
$this->assertEquals($this->getConfigurationAsString(), $dumper->dump($configuration));
}
public function testNamespaceDumper()
{
$configuration = new ExampleConfiguration();
$dumper = new XmlReferenceDumper();
$this->assertEquals(str_replace('http://example.org/schema/dic/acme_root', 'http://symfony.com/schema/dic/symfony', $this->getConfigurationAsString()), $dumper->dump($configuration, 'http://symfony.com/schema/dic/symfony'));
}
private function getConfigurationAsString()
{
return str_replace("\n", PHP_EOL, <<<'EOL'
<!-- Namespace: http://example.org/schema/dic/acme_root -->
<!-- scalar-required: Required -->
<!-- scalar-deprecated: Deprecated (The child node "scalar_deprecated" at path "acme_root" is deprecated.) -->
<!-- scalar-deprecated-with-message: Deprecated (Deprecation custom message for "scalar_deprecated_with_message" at "acme_root") -->
<!-- enum-with-default: One of "this"; "that" -->
<!-- enum: One of "this"; "that" -->
<config
boolean="true"
scalar-empty=""
scalar-null="null"
scalar-true="true"
scalar-false="false"
scalar-default="default"
scalar-array-empty=""
scalar-array-defaults="elem1,elem2"
scalar-required=""
scalar-deprecated=""
scalar-deprecated-with-message=""
node-with-a-looong-name=""
enum-with-default="this"
enum=""
>
<!-- some info -->
<!--
child3: this is a long
multi-line info text
which should be indented;
Example: example setting
-->
<array
child1=""
child2=""
child3=""
/>
<!-- prototype -->
<scalar-prototyped>scalar value</scalar-prototyped>
<!-- prototype: Parameter name -->
<parameter name="parameter name">scalar value</parameter>
<!-- prototype -->
<connection
user=""
pass=""
/>
<!-- prototype -->
<cms-page page="cms page page">
<!-- prototype -->
<!-- title: Required -->
<!-- path: Required -->
<page
locale="page locale"
title=""
path=""
/>
</cms-page>
<!-- prototype -->
<pipou name="pipou name">
<!-- prototype -->
<name didou="" />
</pipou>
</config>
EOL
);
}
}

View file

@ -0,0 +1,143 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Tests\Definition\Dumper;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\Dumper\YamlReferenceDumper;
use Symfony\Component\Config\Tests\Fixtures\Configuration\ExampleConfiguration;
class YamlReferenceDumperTest extends TestCase
{
public function testDumper()
{
$configuration = new ExampleConfiguration();
$dumper = new YamlReferenceDumper();
$this->assertEquals($this->getConfigurationAsString(), $dumper->dump($configuration));
}
public function provideDumpAtPath()
{
return array(
'Regular node' => array('scalar_true', <<<EOL
scalar_true: true
EOL
),
'Array node' => array('array', <<<EOL
# some info
array:
child1: ~
child2: ~
# this is a long
# multi-line info text
# which should be indented
child3: ~ # Example: example setting
EOL
),
'Regular nested' => array('array.child2', <<<EOL
child2: ~
EOL
),
'Prototype' => array('cms_pages.page', <<<EOL
# Prototype
page:
# Prototype
locale:
title: ~ # Required
path: ~ # Required
EOL
),
'Nested prototype' => array('cms_pages.page.locale', <<<EOL
# Prototype
locale:
title: ~ # Required
path: ~ # Required
EOL
),
);
}
/**
* @dataProvider provideDumpAtPath
*/
public function testDumpAtPath($path, $expected)
{
$configuration = new ExampleConfiguration();
$dumper = new YamlReferenceDumper();
$this->assertSame(trim($expected), trim($dumper->dumpAtPath($configuration, $path)));
}
private function getConfigurationAsString()
{
return <<<'EOL'
acme_root:
boolean: true
scalar_empty: ~
scalar_null: null
scalar_true: true
scalar_false: false
scalar_default: default
scalar_array_empty: []
scalar_array_defaults:
# Defaults:
- elem1
- elem2
scalar_required: ~ # Required
scalar_deprecated: ~ # Deprecated (The child node "scalar_deprecated" at path "acme_root" is deprecated.)
scalar_deprecated_with_message: ~ # Deprecated (Deprecation custom message for "scalar_deprecated_with_message" at "acme_root")
node_with_a_looong_name: ~
enum_with_default: this # One of "this"; "that"
enum: ~ # One of "this"; "that"
# some info
array:
child1: ~
child2: ~
# this is a long
# multi-line info text
# which should be indented
child3: ~ # Example: example setting
scalar_prototyped: []
parameters:
# Prototype: Parameter name
name: ~
connections:
# Prototype
-
user: ~
pass: ~
cms_pages:
# Prototype
page:
# Prototype
locale:
title: ~ # Required
path: ~ # Required
pipou:
# Prototype
name: []
EOL;
}
}

View file

@ -0,0 +1,55 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Tests\Definition;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\EnumNode;
class EnumNodeTest extends TestCase
{
public function testFinalizeValue()
{
$node = new EnumNode('foo', null, array('foo', 'bar'));
$this->assertSame('foo', $node->finalize('foo'));
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage $values must contain at least one element.
*/
public function testConstructionWithNoValues()
{
new EnumNode('foo', null, array());
}
public function testConstructionWithOneValue()
{
$node = new EnumNode('foo', null, array('foo'));
$this->assertSame('foo', $node->finalize('foo'));
}
public function testConstructionWithOneDistinctValue()
{
$node = new EnumNode('foo', null, array('foo', 'foo'));
$this->assertSame('foo', $node->finalize('foo'));
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
* @expectedExceptionMessage The value "foobar" is not allowed for path "foo". Permissible values: "foo", "bar"
*/
public function testFinalizeWithInvalidValue()
{
$node = new EnumNode('foo', null, array('foo', 'bar'));
$node->finalize('foobar');
}
}

View file

@ -0,0 +1,74 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Tests\Definition;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\NodeInterface;
use Symfony\Component\Config\Definition\Processor;
class FinalizationTest extends TestCase
{
public function testUnsetKeyWithDeepHierarchy()
{
$tb = new TreeBuilder();
$tree = $tb
->root('config', 'array')
->children()
->node('level1', 'array')
->canBeUnset()
->children()
->node('level2', 'array')
->canBeUnset()
->children()
->node('somevalue', 'scalar')->end()
->node('anothervalue', 'scalar')->end()
->end()
->end()
->node('level1_scalar', 'scalar')->end()
->end()
->end()
->end()
->end()
->buildTree()
;
$a = array(
'level1' => array(
'level2' => array(
'somevalue' => 'foo',
'anothervalue' => 'bar',
),
'level1_scalar' => 'foo',
),
);
$b = array(
'level1' => array(
'level2' => false,
),
);
$this->assertEquals(array(
'level1' => array(
'level1_scalar' => 'foo',
),
), $this->process($tree, array($a, $b)));
}
protected function process(NodeInterface $tree, array $configs)
{
$processor = new Processor();
return $processor->process($tree, $configs);
}
}

View file

@ -0,0 +1,78 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Tests\Definition;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\FloatNode;
class FloatNodeTest extends TestCase
{
/**
* @dataProvider getValidValues
*/
public function testNormalize($value)
{
$node = new FloatNode('test');
$this->assertSame($value, $node->normalize($value));
}
/**
* @dataProvider getValidValues
*
* @param int $value
*/
public function testValidNonEmptyValues($value)
{
$node = new FloatNode('test');
$node->setAllowEmptyValue(false);
$this->assertSame($value, $node->finalize($value));
}
public function getValidValues()
{
return array(
array(1798.0),
array(-678.987),
array(12.56E45),
array(0.0),
// Integer are accepted too, they will be cast
array(17),
array(-10),
array(0),
);
}
/**
* @dataProvider getInvalidValues
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidTypeException
*/
public function testNormalizeThrowsExceptionOnInvalidValues($value)
{
$node = new FloatNode('test');
$node->normalize($value);
}
public function getInvalidValues()
{
return array(
array(null),
array(''),
array('foo'),
array(true),
array(false),
array(array()),
array(array('foo' => 'bar')),
array(new \stdClass()),
);
}
}

View file

@ -0,0 +1,75 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Tests\Definition;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\IntegerNode;
class IntegerNodeTest extends TestCase
{
/**
* @dataProvider getValidValues
*/
public function testNormalize($value)
{
$node = new IntegerNode('test');
$this->assertSame($value, $node->normalize($value));
}
/**
* @dataProvider getValidValues
*
* @param int $value
*/
public function testValidNonEmptyValues($value)
{
$node = new IntegerNode('test');
$node->setAllowEmptyValue(false);
$this->assertSame($value, $node->finalize($value));
}
public function getValidValues()
{
return array(
array(1798),
array(-678),
array(0),
);
}
/**
* @dataProvider getInvalidValues
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidTypeException
*/
public function testNormalizeThrowsExceptionOnInvalidValues($value)
{
$node = new IntegerNode('test');
$node->normalize($value);
}
public function getInvalidValues()
{
return array(
array(null),
array(''),
array('foo'),
array(true),
array(false),
array(0.0),
array(0.1),
array(array()),
array(array('foo' => 'bar')),
array(new \stdClass()),
);
}
}

View file

@ -0,0 +1,196 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Tests\Definition;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
class MergeTest extends TestCase
{
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException
*/
public function testForbiddenOverwrite()
{
$tb = new TreeBuilder();
$tree = $tb
->root('root', 'array')
->children()
->node('foo', 'scalar')
->cannotBeOverwritten()
->end()
->end()
->end()
->buildTree()
;
$a = array(
'foo' => 'bar',
);
$b = array(
'foo' => 'moo',
);
$tree->merge($a, $b);
}
public function testUnsetKey()
{
$tb = new TreeBuilder();
$tree = $tb
->root('root', 'array')
->children()
->node('foo', 'scalar')->end()
->node('bar', 'scalar')->end()
->node('unsettable', 'array')
->canBeUnset()
->children()
->node('foo', 'scalar')->end()
->node('bar', 'scalar')->end()
->end()
->end()
->node('unsetted', 'array')
->canBeUnset()
->prototype('scalar')->end()
->end()
->end()
->end()
->buildTree()
;
$a = array(
'foo' => 'bar',
'unsettable' => array(
'foo' => 'a',
'bar' => 'b',
),
'unsetted' => false,
);
$b = array(
'foo' => 'moo',
'bar' => 'b',
'unsettable' => false,
'unsetted' => array('a', 'b'),
);
$this->assertEquals(array(
'foo' => 'moo',
'bar' => 'b',
'unsettable' => false,
'unsetted' => array('a', 'b'),
), $tree->merge($a, $b));
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
*/
public function testDoesNotAllowNewKeysInSubsequentConfigs()
{
$tb = new TreeBuilder();
$tree = $tb
->root('config', 'array')
->children()
->node('test', 'array')
->disallowNewKeysInSubsequentConfigs()
->useAttributeAsKey('key')
->prototype('array')
->children()
->node('value', 'scalar')->end()
->end()
->end()
->end()
->end()
->end()
->buildTree();
$a = array(
'test' => array(
'a' => array('value' => 'foo'),
),
);
$b = array(
'test' => array(
'b' => array('value' => 'foo'),
),
);
$tree->merge($a, $b);
}
public function testPerformsNoDeepMerging()
{
$tb = new TreeBuilder();
$tree = $tb
->root('config', 'array')
->children()
->node('no_deep_merging', 'array')
->performNoDeepMerging()
->children()
->node('foo', 'scalar')->end()
->node('bar', 'scalar')->end()
->end()
->end()
->end()
->end()
->buildTree()
;
$a = array(
'no_deep_merging' => array(
'foo' => 'a',
'bar' => 'b',
),
);
$b = array(
'no_deep_merging' => array(
'c' => 'd',
),
);
$this->assertEquals(array(
'no_deep_merging' => array(
'c' => 'd',
),
), $tree->merge($a, $b));
}
public function testPrototypeWithoutAKeyAttribute()
{
$tb = new TreeBuilder();
$tree = $tb
->root('config', 'array')
->children()
->arrayNode('append_elements')
->prototype('scalar')->end()
->end()
->end()
->end()
->buildTree()
;
$a = array(
'append_elements' => array('a', 'b'),
);
$b = array(
'append_elements' => array('c', 'd'),
);
$this->assertEquals(array('append_elements' => array('a', 'b', 'c', 'd')), $tree->merge($a, $b));
}
}

View file

@ -0,0 +1,230 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Tests\Definition;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\NodeInterface;
class NormalizationTest extends TestCase
{
/**
* @dataProvider getEncoderTests
*/
public function testNormalizeEncoders($denormalized)
{
$tb = new TreeBuilder();
$tree = $tb
->root('root_name', 'array')
->fixXmlConfig('encoder')
->children()
->node('encoders', 'array')
->useAttributeAsKey('class')
->prototype('array')
->beforeNormalization()->ifString()->then(function ($v) { return array('algorithm' => $v); })->end()
->children()
->node('algorithm', 'scalar')->end()
->end()
->end()
->end()
->end()
->end()
->buildTree()
;
$normalized = array(
'encoders' => array(
'foo' => array('algorithm' => 'plaintext'),
),
);
$this->assertNormalized($tree, $denormalized, $normalized);
}
public function getEncoderTests()
{
$configs = array();
// XML
$configs[] = array(
'encoder' => array(
array('class' => 'foo', 'algorithm' => 'plaintext'),
),
);
// XML when only one element of this type
$configs[] = array(
'encoder' => array('class' => 'foo', 'algorithm' => 'plaintext'),
);
// YAML/PHP
$configs[] = array(
'encoders' => array(
array('class' => 'foo', 'algorithm' => 'plaintext'),
),
);
// YAML/PHP
$configs[] = array(
'encoders' => array(
'foo' => 'plaintext',
),
);
// YAML/PHP
$configs[] = array(
'encoders' => array(
'foo' => array('algorithm' => 'plaintext'),
),
);
return array_map(function ($v) {
return array($v);
}, $configs);
}
/**
* @dataProvider getAnonymousKeysTests
*/
public function testAnonymousKeysArray($denormalized)
{
$tb = new TreeBuilder();
$tree = $tb
->root('root', 'array')
->children()
->node('logout', 'array')
->fixXmlConfig('handler')
->children()
->node('handlers', 'array')
->prototype('scalar')->end()
->end()
->end()
->end()
->end()
->end()
->buildTree()
;
$normalized = array('logout' => array('handlers' => array('a', 'b', 'c')));
$this->assertNormalized($tree, $denormalized, $normalized);
}
public function getAnonymousKeysTests()
{
$configs = array();
$configs[] = array(
'logout' => array(
'handlers' => array('a', 'b', 'c'),
),
);
$configs[] = array(
'logout' => array(
'handler' => array('a', 'b', 'c'),
),
);
return array_map(function ($v) { return array($v); }, $configs);
}
/**
* @dataProvider getNumericKeysTests
*/
public function testNumericKeysAsAttributes($denormalized)
{
$normalized = array(
'thing' => array(42 => array('foo', 'bar'), 1337 => array('baz', 'qux')),
);
$this->assertNormalized($this->getNumericKeysTestTree(), $denormalized, $normalized);
}
public function getNumericKeysTests()
{
$configs = array();
$configs[] = array(
'thing' => array(
42 => array('foo', 'bar'), 1337 => array('baz', 'qux'),
),
);
$configs[] = array(
'thing' => array(
array('foo', 'bar', 'id' => 42), array('baz', 'qux', 'id' => 1337),
),
);
return array_map(function ($v) { return array($v); }, $configs);
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
* @expectedExceptionMessage The attribute "id" must be set for path "root.thing".
*/
public function testNonAssociativeArrayThrowsExceptionIfAttributeNotSet()
{
$denormalized = array(
'thing' => array(
array('foo', 'bar'), array('baz', 'qux'),
),
);
$this->assertNormalized($this->getNumericKeysTestTree(), $denormalized, array());
}
public function testAssociativeArrayPreserveKeys()
{
$tb = new TreeBuilder();
$tree = $tb
->root('root', 'array')
->prototype('array')
->children()
->node('foo', 'scalar')->end()
->end()
->end()
->end()
->buildTree()
;
$data = array('first' => array('foo' => 'bar'));
$this->assertNormalized($tree, $data, $data);
}
public static function assertNormalized(NodeInterface $tree, $denormalized, $normalized)
{
self::assertSame($normalized, $tree->normalize($denormalized));
}
private function getNumericKeysTestTree()
{
$tb = new TreeBuilder();
$tree = $tb
->root('root', 'array')
->children()
->node('thing', 'array')
->useAttributeAsKey('id')
->prototype('array')
->prototype('scalar')->end()
->end()
->end()
->end()
->end()
->buildTree()
;
return $tree;
}
}

View file

@ -0,0 +1,342 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Tests\Definition;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\ArrayNode;
use Symfony\Component\Config\Definition\PrototypedArrayNode;
use Symfony\Component\Config\Definition\ScalarNode;
use Symfony\Component\Config\Definition\VariableNode;
class PrototypedArrayNodeTest extends TestCase
{
public function testGetDefaultValueReturnsAnEmptyArrayForPrototypes()
{
$node = new PrototypedArrayNode('root');
$prototype = new ArrayNode(null, $node);
$node->setPrototype($prototype);
$this->assertEmpty($node->getDefaultValue());
}
public function testGetDefaultValueReturnsDefaultValueForPrototypes()
{
$node = new PrototypedArrayNode('root');
$prototype = new ArrayNode(null, $node);
$node->setPrototype($prototype);
$node->setDefaultValue(array('test'));
$this->assertEquals(array('test'), $node->getDefaultValue());
}
// a remapped key (e.g. "mapping" -> "mappings") should be unset after being used
public function testRemappedKeysAreUnset()
{
$node = new ArrayNode('root');
$mappingsNode = new PrototypedArrayNode('mappings');
$node->addChild($mappingsNode);
// each item under mappings is just a scalar
$prototype = new ScalarNode(null, $mappingsNode);
$mappingsNode->setPrototype($prototype);
$remappings = array();
$remappings[] = array('mapping', 'mappings');
$node->setXmlRemappings($remappings);
$normalized = $node->normalize(array('mapping' => array('foo', 'bar')));
$this->assertEquals(array('mappings' => array('foo', 'bar')), $normalized);
}
/**
* Tests that when a key attribute is mapped, that key is removed from the array.
*
* <things>
* <option id="option1" value="foo">
* <option id="option2" value="bar">
* </things>
*
* The above should finally be mapped to an array that looks like this
* (because "id" is the key attribute).
*
* array(
* 'things' => array(
* 'option1' => 'foo',
* 'option2' => 'bar',
* )
* )
*/
public function testMappedAttributeKeyIsRemoved()
{
$node = new PrototypedArrayNode('root');
$node->setKeyAttribute('id', true);
// each item under the root is an array, with one scalar item
$prototype = new ArrayNode(null, $node);
$prototype->addChild(new ScalarNode('foo'));
$node->setPrototype($prototype);
$children = array();
$children[] = array('id' => 'item_name', 'foo' => 'bar');
$normalized = $node->normalize($children);
$expected = array();
$expected['item_name'] = array('foo' => 'bar');
$this->assertEquals($expected, $normalized);
}
/**
* Tests the opposite of the testMappedAttributeKeyIsRemoved because
* the removal can be toggled with an option.
*/
public function testMappedAttributeKeyNotRemoved()
{
$node = new PrototypedArrayNode('root');
$node->setKeyAttribute('id', false);
// each item under the root is an array, with two scalar items
$prototype = new ArrayNode(null, $node);
$prototype->addChild(new ScalarNode('foo'));
$prototype->addChild(new ScalarNode('id')); // the key attribute will remain
$node->setPrototype($prototype);
$children = array();
$children[] = array('id' => 'item_name', 'foo' => 'bar');
$normalized = $node->normalize($children);
$expected = array();
$expected['item_name'] = array('id' => 'item_name', 'foo' => 'bar');
$this->assertEquals($expected, $normalized);
}
public function testAddDefaultChildren()
{
$node = $this->getPrototypeNodeWithDefaultChildren();
$node->setAddChildrenIfNoneSet();
$this->assertTrue($node->hasDefaultValue());
$this->assertEquals(array(array('foo' => 'bar')), $node->getDefaultValue());
$node = $this->getPrototypeNodeWithDefaultChildren();
$node->setKeyAttribute('foobar');
$node->setAddChildrenIfNoneSet();
$this->assertTrue($node->hasDefaultValue());
$this->assertEquals(array('defaults' => array('foo' => 'bar')), $node->getDefaultValue());
$node = $this->getPrototypeNodeWithDefaultChildren();
$node->setKeyAttribute('foobar');
$node->setAddChildrenIfNoneSet('defaultkey');
$this->assertTrue($node->hasDefaultValue());
$this->assertEquals(array('defaultkey' => array('foo' => 'bar')), $node->getDefaultValue());
$node = $this->getPrototypeNodeWithDefaultChildren();
$node->setKeyAttribute('foobar');
$node->setAddChildrenIfNoneSet(array('defaultkey'));
$this->assertTrue($node->hasDefaultValue());
$this->assertEquals(array('defaultkey' => array('foo' => 'bar')), $node->getDefaultValue());
$node = $this->getPrototypeNodeWithDefaultChildren();
$node->setKeyAttribute('foobar');
$node->setAddChildrenIfNoneSet(array('dk1', 'dk2'));
$this->assertTrue($node->hasDefaultValue());
$this->assertEquals(array('dk1' => array('foo' => 'bar'), 'dk2' => array('foo' => 'bar')), $node->getDefaultValue());
$node = $this->getPrototypeNodeWithDefaultChildren();
$node->setAddChildrenIfNoneSet(array(5, 6));
$this->assertTrue($node->hasDefaultValue());
$this->assertEquals(array(0 => array('foo' => 'bar'), 1 => array('foo' => 'bar')), $node->getDefaultValue());
$node = $this->getPrototypeNodeWithDefaultChildren();
$node->setAddChildrenIfNoneSet(2);
$this->assertTrue($node->hasDefaultValue());
$this->assertEquals(array(array('foo' => 'bar'), array('foo' => 'bar')), $node->getDefaultValue());
}
public function testDefaultChildrenWinsOverDefaultValue()
{
$node = $this->getPrototypeNodeWithDefaultChildren();
$node->setAddChildrenIfNoneSet();
$node->setDefaultValue(array('bar' => 'foo'));
$this->assertTrue($node->hasDefaultValue());
$this->assertEquals(array(array('foo' => 'bar')), $node->getDefaultValue());
}
protected function getPrototypeNodeWithDefaultChildren()
{
$node = new PrototypedArrayNode('root');
$prototype = new ArrayNode(null, $node);
$child = new ScalarNode('foo');
$child->setDefaultValue('bar');
$prototype->addChild($child);
$prototype->setAddIfNotSet(true);
$node->setPrototype($prototype);
return $node;
}
/**
* Tests that when a key attribute is mapped, that key is removed from the array.
* And if only 'value' element is left in the array, it will replace its wrapper array.
*
* <things>
* <option id="option1" value="value1">
* </things>
*
* The above should finally be mapped to an array that looks like this
* (because "id" is the key attribute).
*
* array(
* 'things' => array(
* 'option1' => 'value1'
* )
* )
*
* It's also possible to mix 'value-only' and 'non-value-only' elements in the array.
*
* <things>
* <option id="option1" value="value1">
* <option id="option2" value="value2" foo="foo2">
* </things>
*
* The above should finally be mapped to an array as follows
*
* array(
* 'things' => array(
* 'option1' => 'value1',
* 'option2' => array(
* 'value' => 'value2',
* 'foo' => 'foo2'
* )
* )
* )
*
* The 'value' element can also be ArrayNode:
*
* <things>
* <option id="option1">
* <value>
* <foo>foo1</foo>
* <bar>bar1</bar>
* </value>
* </option>
* </things>
*
* The above should be finally be mapped to an array as follows
*
* array(
* 'things' => array(
* 'option1' => array(
* 'foo' => 'foo1',
* 'bar' => 'bar1'
* )
* )
* )
*
* If using VariableNode for value node, it's also possible to mix different types of value nodes:
*
* <things>
* <option id="option1">
* <value>
* <foo>foo1</foo>
* <bar>bar1</bar>
* </value>
* </option>
* <option id="option2" value="value2">
* </things>
*
* The above should be finally mapped to an array as follows
*
* array(
* 'things' => array(
* 'option1' => array(
* 'foo' => 'foo1',
* 'bar' => 'bar1'
* ),
* 'option2' => 'value2'
* )
* )
*
*
* @dataProvider getDataForKeyRemovedLeftValueOnly
*/
public function testMappedAttributeKeyIsRemovedLeftValueOnly($value, $children, $expected)
{
$node = new PrototypedArrayNode('root');
$node->setKeyAttribute('id', true);
// each item under the root is an array, with one scalar item
$prototype = new ArrayNode(null, $node);
$prototype->addChild(new ScalarNode('id'));
$prototype->addChild(new ScalarNode('foo'));
$prototype->addChild($value);
$node->setPrototype($prototype);
$normalized = $node->normalize($children);
$this->assertEquals($expected, $normalized);
}
public function getDataForKeyRemovedLeftValueOnly()
{
$scalarValue = new ScalarNode('value');
$arrayValue = new ArrayNode('value');
$arrayValue->addChild(new ScalarNode('foo'));
$arrayValue->addChild(new ScalarNode('bar'));
$variableValue = new VariableNode('value');
return array(
array(
$scalarValue,
array(
array('id' => 'option1', 'value' => 'value1'),
),
array('option1' => 'value1'),
),
array(
$scalarValue,
array(
array('id' => 'option1', 'value' => 'value1'),
array('id' => 'option2', 'value' => 'value2', 'foo' => 'foo2'),
),
array(
'option1' => 'value1',
'option2' => array('value' => 'value2', 'foo' => 'foo2'),
),
),
array(
$arrayValue,
array(
array(
'id' => 'option1',
'value' => array('foo' => 'foo1', 'bar' => 'bar1'),
),
),
array(
'option1' => array('foo' => 'foo1', 'bar' => 'bar1'),
),
),
array($variableValue,
array(
array(
'id' => 'option1', 'value' => array('foo' => 'foo1', 'bar' => 'bar1'),
),
array('id' => 'option2', 'value' => 'value2'),
),
array(
'option1' => array('foo' => 'foo1', 'bar' => 'bar1'),
'option2' => 'value2',
),
),
);
}
}

View file

@ -0,0 +1,169 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Tests\Definition;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\ArrayNode;
use Symfony\Component\Config\Definition\ScalarNode;
class ScalarNodeTest extends TestCase
{
/**
* @dataProvider getValidValues
*/
public function testNormalize($value)
{
$node = new ScalarNode('test');
$this->assertSame($value, $node->normalize($value));
}
public function getValidValues()
{
return array(
array(false),
array(true),
array(null),
array(''),
array('foo'),
array(0),
array(1),
array(0.0),
array(0.1),
);
}
public function testSetDeprecated()
{
$childNode = new ScalarNode('foo');
$childNode->setDeprecated('"%node%" is deprecated');
$this->assertTrue($childNode->isDeprecated());
$this->assertSame('"foo" is deprecated', $childNode->getDeprecationMessage($childNode->getName(), $childNode->getPath()));
$node = new ArrayNode('root');
$node->addChild($childNode);
$deprecationTriggered = 0;
$deprecationHandler = function ($level, $message, $file, $line) use (&$prevErrorHandler, &$deprecationTriggered) {
if (E_USER_DEPRECATED === $level) {
return ++$deprecationTriggered;
}
return $prevErrorHandler ? $prevErrorHandler($level, $message, $file, $line) : false;
};
$prevErrorHandler = set_error_handler($deprecationHandler);
$node->finalize(array());
restore_error_handler();
$this->assertSame(0, $deprecationTriggered, '->finalize() should not trigger if the deprecated node is not set');
$prevErrorHandler = set_error_handler($deprecationHandler);
$node->finalize(array('foo' => ''));
restore_error_handler();
$this->assertSame(1, $deprecationTriggered, '->finalize() should trigger if the deprecated node is set');
}
/**
* @dataProvider getInvalidValues
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidTypeException
*/
public function testNormalizeThrowsExceptionOnInvalidValues($value)
{
$node = new ScalarNode('test');
$node->normalize($value);
}
public function getInvalidValues()
{
return array(
array(array()),
array(array('foo' => 'bar')),
array(new \stdClass()),
);
}
public function testNormalizeThrowsExceptionWithoutHint()
{
$node = new ScalarNode('test');
if (method_exists($this, 'expectException')) {
$this->expectException('Symfony\Component\Config\Definition\Exception\InvalidTypeException');
$this->expectExceptionMessage('Invalid type for path "test". Expected scalar, but got array.');
} else {
$this->setExpectedException('Symfony\Component\Config\Definition\Exception\InvalidTypeException', 'Invalid type for path "test". Expected scalar, but got array.');
}
$node->normalize(array());
}
public function testNormalizeThrowsExceptionWithErrorMessage()
{
$node = new ScalarNode('test');
$node->setInfo('"the test value"');
if (method_exists($this, 'expectException')) {
$this->expectException('Symfony\Component\Config\Definition\Exception\InvalidTypeException');
$this->expectExceptionMessage("Invalid type for path \"test\". Expected scalar, but got array.\nHint: \"the test value\"");
} else {
$this->setExpectedException('Symfony\Component\Config\Definition\Exception\InvalidTypeException', "Invalid type for path \"test\". Expected scalar, but got array.\nHint: \"the test value\"");
}
$node->normalize(array());
}
/**
* @dataProvider getValidNonEmptyValues
*
* @param mixed $value
*/
public function testValidNonEmptyValues($value)
{
$node = new ScalarNode('test');
$node->setAllowEmptyValue(false);
$this->assertSame($value, $node->finalize($value));
}
public function getValidNonEmptyValues()
{
return array(
array(false),
array(true),
array('foo'),
array(0),
array(1),
array(0.0),
array(0.1),
);
}
/**
* @dataProvider getEmptyValues
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
*
* @param mixed $value
*/
public function testNotAllowedEmptyValuesThrowException($value)
{
$node = new ScalarNode('test');
$node->setAllowEmptyValue(false);
$node->finalize($value);
}
public function getEmptyValues()
{
return array(
array(null),
array(''),
);
}
}

View file

@ -0,0 +1,59 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Tests\DependencyInjection;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\DependencyInjection\ConfigCachePass;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* @group legacy
*/
class ConfigCachePassTest extends TestCase
{
public function testThatCheckersAreProcessedInPriorityOrder()
{
$container = new ContainerBuilder();
$definition = $container->register('config_cache_factory')->addArgument(null);
$container->register('checker_2')->addTag('config_cache.resource_checker', array('priority' => 100));
$container->register('checker_1')->addTag('config_cache.resource_checker', array('priority' => 200));
$container->register('checker_3')->addTag('config_cache.resource_checker');
$pass = new ConfigCachePass();
$pass->process($container);
$expected = new IteratorArgument(array(
new Reference('checker_1'),
new Reference('checker_2'),
new Reference('checker_3'),
));
$this->assertEquals($expected, $definition->getArgument(0));
}
public function testThatCheckersCanBeMissing()
{
$container = new ContainerBuilder();
$definitionsBefore = \count($container->getDefinitions());
$aliasesBefore = \count($container->getAliases());
$pass = new ConfigCachePass();
$pass->process($container);
// the container is untouched (i.e. no new definitions or aliases)
$this->assertCount($definitionsBefore, $container->getDefinitions());
$this->assertCount($aliasesBefore, $container->getAliases());
}
}

View file

@ -0,0 +1,98 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Tests\Exception;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Exception\FileLoaderLoadException;
class FileLoaderLoadExceptionTest extends TestCase
{
public function testMessageCannotLoadResource()
{
$exception = new FileLoaderLoadException('resource', null);
$this->assertEquals('Cannot load resource "resource".', $exception->getMessage());
}
public function testMessageCannotLoadResourceWithType()
{
$exception = new FileLoaderLoadException('resource', null, null, null, 'foobar');
$this->assertEquals('Cannot load resource "resource". Make sure there is a loader supporting the "foobar" type.', $exception->getMessage());
}
public function testMessageCannotLoadResourceWithAnnotationType()
{
$exception = new FileLoaderLoadException('resource', null, null, null, 'annotation');
$this->assertEquals('Cannot load resource "resource". Make sure annotations are installed and enabled.', $exception->getMessage());
}
public function testMessageCannotImportResourceFromSource()
{
$exception = new FileLoaderLoadException('resource', 'sourceResource');
$this->assertEquals('Cannot import resource "resource" from "sourceResource".', $exception->getMessage());
}
public function testMessageCannotImportBundleResource()
{
$exception = new FileLoaderLoadException('@resource', 'sourceResource');
$this->assertEquals(
'Cannot import resource "@resource" from "sourceResource". '.
'Make sure the "resource" bundle is correctly registered and loaded in the application kernel class. '.
'If the bundle is registered, make sure the bundle path "@resource" is not empty.',
$exception->getMessage()
);
}
public function testMessageHasPreviousErrorWithDotAndUnableToLoad()
{
$exception = new FileLoaderLoadException(
'resource',
null,
null,
new \Exception('There was a previous error with an ending dot.')
);
$this->assertEquals(
'There was a previous error with an ending dot in resource (which is loaded in resource "resource").',
$exception->getMessage()
);
}
public function testMessageHasPreviousErrorWithoutDotAndUnableToLoad()
{
$exception = new FileLoaderLoadException(
'resource',
null,
null,
new \Exception('There was a previous error with no ending dot')
);
$this->assertEquals(
'There was a previous error with no ending dot in resource (which is loaded in resource "resource").',
$exception->getMessage()
);
}
public function testMessageHasPreviousErrorAndUnableToLoadBundle()
{
$exception = new FileLoaderLoadException(
'@resource',
null,
null,
new \Exception('There was a previous error with an ending dot.')
);
$this->assertEquals(
'There was a previous error with an ending dot in @resource '.
'(which is loaded in resource "@resource"). '.
'Make sure the "resource" bundle is correctly registered and loaded in the application kernel class. '.
'If the bundle is registered, make sure the bundle path "@resource" is not empty.',
$exception->getMessage()
);
}
}

View file

@ -0,0 +1,120 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\FileLocator;
class FileLocatorTest extends TestCase
{
/**
* @dataProvider getIsAbsolutePathTests
*/
public function testIsAbsolutePath($path)
{
$loader = new FileLocator(array());
$r = new \ReflectionObject($loader);
$m = $r->getMethod('isAbsolutePath');
$m->setAccessible(true);
$this->assertTrue($m->invoke($loader, $path), '->isAbsolutePath() returns true for an absolute path');
}
public function getIsAbsolutePathTests()
{
return array(
array('/foo.xml'),
array('c:\\\\foo.xml'),
array('c:/foo.xml'),
array('\\server\\foo.xml'),
array('https://server/foo.xml'),
array('phar://server/foo.xml'),
);
}
public function testLocate()
{
$loader = new FileLocator(__DIR__.'/Fixtures');
$this->assertEquals(
__DIR__.\DIRECTORY_SEPARATOR.'FileLocatorTest.php',
$loader->locate('FileLocatorTest.php', __DIR__),
'->locate() returns the absolute filename if the file exists in the given path'
);
$this->assertEquals(
__DIR__.'/Fixtures'.\DIRECTORY_SEPARATOR.'foo.xml',
$loader->locate('foo.xml', __DIR__),
'->locate() returns the absolute filename if the file exists in one of the paths given in the constructor'
);
$this->assertEquals(
__DIR__.'/Fixtures'.\DIRECTORY_SEPARATOR.'foo.xml',
$loader->locate(__DIR__.'/Fixtures'.\DIRECTORY_SEPARATOR.'foo.xml', __DIR__),
'->locate() returns the absolute filename if the file exists in one of the paths given in the constructor'
);
$loader = new FileLocator(array(__DIR__.'/Fixtures', __DIR__.'/Fixtures/Again'));
$this->assertEquals(
array(__DIR__.'/Fixtures'.\DIRECTORY_SEPARATOR.'foo.xml', __DIR__.'/Fixtures/Again'.\DIRECTORY_SEPARATOR.'foo.xml'),
$loader->locate('foo.xml', __DIR__, false),
'->locate() returns an array of absolute filenames'
);
$this->assertEquals(
array(__DIR__.'/Fixtures'.\DIRECTORY_SEPARATOR.'foo.xml', __DIR__.'/Fixtures/Again'.\DIRECTORY_SEPARATOR.'foo.xml'),
$loader->locate('foo.xml', __DIR__.'/Fixtures', false),
'->locate() returns an array of absolute filenames'
);
$loader = new FileLocator(__DIR__.'/Fixtures/Again');
$this->assertEquals(
array(__DIR__.'/Fixtures'.\DIRECTORY_SEPARATOR.'foo.xml', __DIR__.'/Fixtures/Again'.\DIRECTORY_SEPARATOR.'foo.xml'),
$loader->locate('foo.xml', __DIR__.'/Fixtures', false),
'->locate() returns an array of absolute filenames'
);
}
/**
* @expectedException \Symfony\Component\Config\Exception\FileLocatorFileNotFoundException
* @expectedExceptionMessage The file "foobar.xml" does not exist
*/
public function testLocateThrowsAnExceptionIfTheFileDoesNotExists()
{
$loader = new FileLocator(array(__DIR__.'/Fixtures'));
$loader->locate('foobar.xml', __DIR__);
}
/**
* @expectedException \Symfony\Component\Config\Exception\FileLocatorFileNotFoundException
*/
public function testLocateThrowsAnExceptionIfTheFileDoesNotExistsInAbsolutePath()
{
$loader = new FileLocator(array(__DIR__.'/Fixtures'));
$loader->locate(__DIR__.'/Fixtures/foobar.xml', __DIR__);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage An empty file name is not valid to be located.
*/
public function testLocateEmpty()
{
$loader = new FileLocator(array(__DIR__.'/Fixtures'));
$loader->locate(null, __DIR__);
}
}

View file

View file

@ -0,0 +1,7 @@
<?php
namespace Symfony\Component\Config\Tests\Fixtures;
class BadParent extends MissingParent
{
}

Some files were not shown because too many files have changed in this diff Show more