This commit is contained in:
Xes
2025-08-14 22:41:49 +02:00
parent 2de81ccc46
commit 8ce45119b6
39774 changed files with 4309466 additions and 0 deletions

View File

@@ -0,0 +1,189 @@
# Changelog
All notable changes to this project will be documented in this file, in reverse chronological order by release.
## 3.3.0 - 2019-06-08
### Added
- [#54](https://github.com/zendframework/zend-config/pull/54) adds support for PHP 7.3.
- [#58](https://github.com/zendframework/zend-config/pull/58) adds
`$processSections` to INI reader, allowing control over whether sections
should be parsed or not
- [#63](https://github.com/zendframework/zend-config/pull/63) adds .yml to
Zend\Config\Factory as an alternative extension for yaml
### Changed
- Nothing.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- Nothing.
## 3.2.0 - 2018-04-24
### Added
- [#47](https://github.com/zendframework/zend-config/pull/47) adds `Zend\Config\Writer\JavaProperties`, a complement to
`Zend\Config\Reader\JavaProperties`, for writing JavaProperties files from configuration. The writer supports
specifying an alternate key/value delimiter (the default is ":") via the constructor.
- [#46](https://github.com/zendframework/zend-config/pull/46) adds a constructor option to the JavaProperties reader to allow
users to indicate keys and values from the configuration should be trimmed of whitespace:
```php
$reader = new JavaProperties(
JavaProperties::DELIMITER_DEFAULT, // or ":"
JavaProperties::WHITESPACE_TRIM, // or true; default is false
);
```
- [#45](https://github.com/zendframework/zend-config/pull/45) adds the ability to specify an alternate key/value delimiter to
the JavaProperties config reader via the constructor: `$reader = new JavaProperties("=");`.
- [#42](https://github.com/zendframework/zend-config/pull/42) adds support for PHP 7.1 and 7.2.
### Changed
- Nothing.
### Deprecated
- Nothing.
### Removed
- [#42](https://github.com/zendframework/zend-config/pull/42) removes support for HHVM.
### Fixed
- Nothing.
## 3.1.0 - 2017-02-22
### Added
- [#37](https://github.com/zendframework/zend-config/pull/37) adds a new method,
`enableKeyProcessing()`, and constructor argument, `$enableKeyProcessing =
false`, to each of the `Token` and `Constant` processors. These allow enabling
processing of tokens and/or constants encountered in configuration key values.
- [#37](https://github.com/zendframework/zend-config/pull/37) adds the ability
for the `Constant` processor to process class constants, including the
`::class` pseudo-constant.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- Nothing.
## 3.0.0 - 2017-02-16
### Added
- [#36](https://github.com/zendframework/zend-config/pull/36) adds support for
[PSR-11](http://www.php-fig.org/psr/psr-11/).
- [#36](https://github.com/zendframework/zend-config/pull/36) adds the class
`Zend\Config\StandaloneReaderPluginManager` for managing config reader plugins.
This implementation implements the PSR-11 `ContainerInterface`, and uses a
hard-coded list of reader plugins.
- [#36](https://github.com/zendframework/zend-config/pull/36) adds the class
`Zend\Config\StandaloneWriterPluginManager` for managing config writer plugins.
This implementation implements the PSR-11 `ContainerInterface`, and uses a
hard-coded list of writer plugins.
### Changes
- [#36](https://github.com/zendframework/zend-config/pull/36) updates the
`Zend\Config\Factory::getReaderPluginManager()` method to lazy-load a
`StandaloneReaderPluginManager` by default, instead of a
`ReaderPluginManager`, allowing usage out-of-the-box without requiring
zend-servicemanager.
- [#36](https://github.com/zendframework/zend-config/pull/36) updates the
`Zend\Config\Factory::setReaderPluginManager()` method to typehint against
`Psr\Container\ContainerInterface` instead of `ReaderPluginManager`. If you
were extending and overriding that method, you will need to update your
signature.
- [#36](https://github.com/zendframework/zend-config/pull/36) updates the
`Zend\Config\Factory::getWriterPluginManager()` method to lazy-load a
`StandaloneWriterPluginManager` by default, instead of a
`WriterPluginManager`, allowing usage out-of-the-box without requiring
zend-servicemanager.
- [#36](https://github.com/zendframework/zend-config/pull/36) updates the
`Zend\Config\Factory::setWriterPluginManager()` method to typehint against
`Psr\Container\ContainerInterface` instead of `WriterPluginManager`. If you
were extending and overriding that method, you will need to update your
signature.
### Deprecated
- Nothing.
### Removed
- [#36](https://github.com/zendframework/zend-config/pull/36) removes usage of
zend-json as a JSON de/serializer in the JSON writer and reader; the
component now requires ext/json is installed to use these features.
### Fixed
- Nothing.
## 2.6.0 - 2016-02-04
### Added
- [#6](https://github.com/zendframework/zend-config/pull/6) adds the ability for
the `PhpArray` writer to optionally translate strings that evaluate to known
classes to `ClassName::class` syntax; the feature works for both keys and
values.
- [#21](https://github.com/zendframework/zend-config/pull/21) adds revised
documentation, and publishes it to https://zendframework.github.io/zend-config/
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- [#8](https://github.com/zendframework/zend-config/pull/8),
[#18](https://github.com/zendframework/zend-config/pull/18), and
[#20](https://github.com/zendframework/zend-config/pull/20) update the
code base to make it forwards-compatible with the v3.0 versions of
zend-stdlib and zend-servicemanager. Primarily, this involved:
- Updating the `AbstractConfigFactory` to implement the new methods in the
v3 `AbstractFactoryInterface` definition, and updating the v2 methods to
proxy to those.
- Updating `ReaderPluginManager` and `WriterPluginManager` to follow the
changes to `AbstractPluginManager`. In particular, instead of defining
invokables, they now define a combination of aliases and factories (using
the new `InvokableFactory`); additionally, they each now implement both
`validatePlugin()` from v2 and `validate()` from v3.
- Pinning to stable versions of already updated components.
- Selectively omitting zend-i18n-reliant tests when testing against
zend-servicemanager v3.

View File

@@ -0,0 +1,27 @@
Copyright (c) 2005-2018, Zend Technologies USA, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
- Neither the name of Zend Technologies USA, Inc. nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,12 @@
# zend-config
[![Build Status](https://secure.travis-ci.org/zendframework/zend-config.svg?branch=master)](https://secure.travis-ci.org/zendframework/zend-config)
[![Coverage Status](https://coveralls.io/repos/github/zendframework/zend-config/badge.svg?branch=master)](https://coveralls.io/github/zendframework/zend-config?branch=master)
zend-config is designed to simplify access to configuration data within
applications. It provides a nested object property-based user interface for
accessing this configuration data within application code. The configuration
data may come from a variety of media supporting hierarchical data storage.
- File issues at https://github.com/zendframework/zend-config/issues
- Documentation is at https://docs.zendframework.com/zend-config/

View File

@@ -0,0 +1,71 @@
{
"name": "zendframework/zend-config",
"description": "provides a nested object property based user interface for accessing this configuration data within application code",
"license": "BSD-3-Clause",
"keywords": [
"zf",
"zendframework",
"config"
],
"support": {
"docs": "https://docs.zendframework.com/zend-config/",
"issues": "https://github.com/zendframework/zend-config/issues",
"source": "https://github.com/zendframework/zend-config",
"rss": "https://github.com/zendframework/zend-config/releases.atom",
"chat": "https://zendframework-slack.herokuapp.com",
"forum": "https://discourse.zendframework.com/c/questions/components"
},
"require": {
"php": "^5.6 || ^7.0",
"ext-json": "*",
"zendframework/zend-stdlib": "^2.7.7 || ^3.1",
"psr/container": "^1.0"
},
"require-dev": {
"malukenho/docheader": "^0.1.6",
"phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2",
"zendframework/zend-coding-standard": "~1.0.0",
"zendframework/zend-filter": "^2.7.2",
"zendframework/zend-i18n": "^2.7.4",
"zendframework/zend-servicemanager": "^2.7.8 || ^3.3"
},
"conflict": {
"container-interop/container-interop": "<1.2.0"
},
"suggest": {
"zendframework/zend-filter": "^2.7.2; install if you want to use the Filter processor",
"zendframework/zend-i18n": "^2.7.4; install if you want to use the Translator processor",
"zendframework/zend-servicemanager": "^2.7.8 || ^3.3; if you need an extensible plugin manager for use with the Config Factory"
},
"autoload": {
"psr-4": {
"Zend\\Config\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"ZendTest\\Config\\": "test/"
}
},
"config": {
"sort-packages": true
},
"extra": {
"branch-alias": {
"dev-master": "3.3.x-dev",
"dev-develop": "3.4.x-dev"
}
},
"scripts": {
"check": [
"@license-check",
"@cs-check",
"@test"
],
"cs-check": "phpcs",
"cs-fix": "phpcbf",
"test": "phpunit --colors=always",
"test-coverage": "phpunit --colors=always --coverage-clover clover.xml",
"license-check": "docheader check src/"
}
}

View File

@@ -0,0 +1,197 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config;
use Interop\Container\ContainerInterface;
use Traversable;
use Zend\ServiceManager\AbstractFactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
/**
* Class AbstractConfigFactory
*/
class AbstractConfigFactory implements AbstractFactoryInterface
{
/**
* @var array
*/
protected $configs = [];
/**
* @var string[]
*/
protected $defaultPatterns = [
'#config[\._-](.*)$#i',
'#^(.*)[\\\\\._-]config$#i'
];
/**
* @var string[]
*/
protected $patterns;
/**
* Determine if we can create a service with name (SM v2)
*
* @param ServiceLocatorInterface $serviceLocator
* @param $name
* @param $requestedName
* @return bool
*/
public function canCreateServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName)
{
return $this->canCreate($serviceLocator, $requestedName);
}
/**
* Determine if we can create a service (SM v3)
*
* @param ContainerInterface $container
* @param string $requestedName
* @return bool
*/
public function canCreate(ContainerInterface $container, $requestedName)
{
if (isset($this->configs[$requestedName])) {
return true;
}
if (! $container->has('Config')) {
return false;
}
$key = $this->match($requestedName);
if (null === $key) {
return false;
}
$config = $container->get('Config');
return isset($config[$key]);
}
/**
* Create service with name (SM v2)
*
* @param ServiceLocatorInterface $serviceLocator
* @param string $name
* @param string $requestedName
* @return string|mixed|array
*/
public function createServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName)
{
return $this($serviceLocator, $requestedName);
}
/**
* Create service with name (SM v3)
*
* @param ContainerInterface $container
* @param string $requestedName
* @param array $options
* @return string|mixed|array
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
if (isset($this->configs[$requestedName])) {
return $this->configs[$requestedName];
}
$key = $this->match($requestedName);
if (isset($this->configs[$key])) {
$this->configs[$requestedName] = $this->configs[$key];
return $this->configs[$key];
}
$config = $container->get('Config');
$this->configs[$requestedName] = $this->configs[$key] = $config[$key];
return $config[$key];
}
/**
* @param string $pattern
* @return self
* @throws Exception\InvalidArgumentException
*/
public function addPattern($pattern)
{
if (! is_string($pattern)) {
throw new Exception\InvalidArgumentException('pattern must be string');
}
$patterns = $this->getPatterns();
array_unshift($patterns, $pattern);
$this->setPatterns($patterns);
return $this;
}
/**
* @param array|Traversable $patterns
* @return self
* @throws Exception\InvalidArgumentException
*/
public function addPatterns($patterns)
{
if ($patterns instanceof Traversable) {
$patterns = iterator_to_array($patterns);
}
if (! is_array($patterns)) {
throw new Exception\InvalidArgumentException("patterns must be array or Traversable");
}
foreach ($patterns as $pattern) {
$this->addPattern($pattern);
}
return $this;
}
/**
* @param array|Traversable $patterns
* @return self
* @throws \InvalidArgumentException
*/
public function setPatterns($patterns)
{
if ($patterns instanceof Traversable) {
$patterns = iterator_to_array($patterns);
}
if (! is_array($patterns)) {
throw new \InvalidArgumentException("patterns must be array or Traversable");
}
$this->patterns = $patterns;
return $this;
}
/**
* @return array
*/
public function getPatterns()
{
if (null === $this->patterns) {
$this->setPatterns($this->defaultPatterns);
}
return $this->patterns;
}
/**
* @param string $requestedName
* @return null|string
*/
protected function match($requestedName)
{
foreach ($this->getPatterns() as $pattern) {
if (preg_match($pattern, $requestedName, $matches)) {
return $matches[1];
}
}
return null;
}
}

View File

@@ -0,0 +1,384 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config;
use ArrayAccess;
use Countable;
use Iterator;
/**
* Provides a property based interface to an array.
* The data are read-only unless $allowModifications is set to true
* on construction.
*
* Implements Countable, Iterator and ArrayAccess
* to facilitate easy access to the data.
*/
class Config implements Countable, Iterator, ArrayAccess
{
/**
* Whether modifications to configuration data are allowed.
*
* @var bool
*/
protected $allowModifications;
/**
* Data within the configuration.
*
* @var array
*/
protected $data = [];
/**
* Used when unsetting values during iteration to ensure we do not skip
* the next element.
*
* @var bool
*/
protected $skipNextIteration;
/**
* Constructor.
*
* Data is read-only unless $allowModifications is set to true
* on construction.
*
* @param array $array
* @param bool $allowModifications
*/
public function __construct(array $array, $allowModifications = false)
{
$this->allowModifications = (bool) $allowModifications;
foreach ($array as $key => $value) {
if (is_array($value)) {
$this->data[$key] = new static($value, $this->allowModifications);
} else {
$this->data[$key] = $value;
}
}
}
/**
* Retrieve a value and return $default if there is no element set.
*
* @param string $name
* @param mixed $default
* @return mixed
*/
public function get($name, $default = null)
{
if (array_key_exists($name, $this->data)) {
return $this->data[$name];
}
return $default;
}
/**
* Magic function so that $obj->value will work.
*
* @param string $name
* @return mixed
*/
public function __get($name)
{
return $this->get($name);
}
/**
* Set a value in the config.
*
* Only allow setting of a property if $allowModifications was set to true
* on construction. Otherwise, throw an exception.
*
* @param string $name
* @param mixed $value
* @return void
* @throws Exception\RuntimeException
*/
public function __set($name, $value)
{
if ($this->allowModifications) {
if (is_array($value)) {
$value = new static($value, true);
}
if (null === $name) {
$this->data[] = $value;
} else {
$this->data[$name] = $value;
}
} else {
throw new Exception\RuntimeException('Config is read only');
}
}
/**
* Deep clone of this instance to ensure that nested Zend\Configs are also
* cloned.
*
* @return void
*/
public function __clone()
{
$array = [];
foreach ($this->data as $key => $value) {
if ($value instanceof self) {
$array[$key] = clone $value;
} else {
$array[$key] = $value;
}
}
$this->data = $array;
}
/**
* Return an associative array of the stored data.
*
* @return array
*/
public function toArray()
{
$array = [];
$data = $this->data;
/** @var self $value */
foreach ($data as $key => $value) {
if ($value instanceof self) {
$array[$key] = $value->toArray();
} else {
$array[$key] = $value;
}
}
return $array;
}
/**
* isset() overloading
*
* @param string $name
* @return bool
*/
public function __isset($name)
{
return isset($this->data[$name]);
}
/**
* unset() overloading
*
* @param string $name
* @return void
* @throws Exception\InvalidArgumentException
*/
public function __unset($name)
{
if (! $this->allowModifications) {
throw new Exception\InvalidArgumentException('Config is read only');
} elseif (isset($this->data[$name])) {
unset($this->data[$name]);
$this->skipNextIteration = true;
}
}
/**
* count(): defined by Countable interface.
*
* @see Countable::count()
* @return int
*/
public function count()
{
return count($this->data);
}
/**
* current(): defined by Iterator interface.
*
* @see Iterator::current()
* @return mixed
*/
public function current()
{
$this->skipNextIteration = false;
return current($this->data);
}
/**
* key(): defined by Iterator interface.
*
* @see Iterator::key()
* @return mixed
*/
public function key()
{
return key($this->data);
}
/**
* next(): defined by Iterator interface.
*
* @see Iterator::next()
* @return void
*/
public function next()
{
if ($this->skipNextIteration) {
$this->skipNextIteration = false;
return;
}
next($this->data);
}
/**
* rewind(): defined by Iterator interface.
*
* @see Iterator::rewind()
* @return void
*/
public function rewind()
{
$this->skipNextIteration = false;
reset($this->data);
}
/**
* valid(): defined by Iterator interface.
*
* @see Iterator::valid()
* @return bool
*/
public function valid()
{
return ($this->key() !== null);
}
/**
* offsetExists(): defined by ArrayAccess interface.
*
* @see ArrayAccess::offsetExists()
* @param mixed $offset
* @return bool
*/
public function offsetExists($offset)
{
return $this->__isset($offset);
}
/**
* offsetGet(): defined by ArrayAccess interface.
*
* @see ArrayAccess::offsetGet()
* @param mixed $offset
* @return mixed
*/
public function offsetGet($offset)
{
return $this->__get($offset);
}
/**
* offsetSet(): defined by ArrayAccess interface.
*
* @see ArrayAccess::offsetSet()
* @param mixed $offset
* @param mixed $value
* @return void
*/
public function offsetSet($offset, $value)
{
$this->__set($offset, $value);
}
/**
* offsetUnset(): defined by ArrayAccess interface.
*
* @see ArrayAccess::offsetUnset()
* @param mixed $offset
* @return void
*/
public function offsetUnset($offset)
{
$this->__unset($offset);
}
/**
* Merge another Config with this one.
*
* For duplicate keys, the following will be performed:
* - Nested Configs will be recursively merged.
* - Items in $merge with INTEGER keys will be appended.
* - Items in $merge with STRING keys will overwrite current values.
*
* @param Config $merge
* @return self
*/
public function merge(Config $merge)
{
/** @var Config $value */
foreach ($merge as $key => $value) {
if (array_key_exists($key, $this->data)) {
if (is_int($key)) {
$this->data[] = $value;
} elseif ($value instanceof self && $this->data[$key] instanceof self) {
$this->data[$key]->merge($value);
} else {
if ($value instanceof self) {
$this->data[$key] = new static($value->toArray(), $this->allowModifications);
} else {
$this->data[$key] = $value;
}
}
} else {
if ($value instanceof self) {
$this->data[$key] = new static($value->toArray(), $this->allowModifications);
} else {
$this->data[$key] = $value;
}
}
}
return $this;
}
/**
* Prevent any more modifications being made to this instance.
*
* Useful after merge() has been used to merge multiple Config objects
* into one object which should then not be modified again.
*
* @return void
*/
public function setReadOnly()
{
$this->allowModifications = false;
/** @var Config $value */
foreach ($this->data as $value) {
if ($value instanceof self) {
$value->setReadOnly();
}
}
}
/**
* Returns whether this Config object is read only or not.
*
* @return bool
*/
public function isReadOnly()
{
return ! $this->allowModifications;
}
}

View File

@@ -0,0 +1,12 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config\Exception;
interface ExceptionInterface
{
}

View File

@@ -0,0 +1,12 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config\Exception;
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}

View File

@@ -0,0 +1,15 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config\Exception;
use Psr\Container\NotFoundExceptionInterface;
class PluginNotFoundException extends RuntimeException implements
NotFoundExceptionInterface
{
}

View File

@@ -0,0 +1,12 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config\Exception;
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}

View File

@@ -0,0 +1,12 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2018 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config\Exception;
class UnprocessableConfigException extends RuntimeException
{
}

View File

@@ -0,0 +1,306 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config;
use Psr\Container\ContainerInterface;
use Zend\Stdlib\ArrayUtils;
class Factory
{
/**
* Plugin manager for loading readers
*
* @var null|ContainerInterface
*/
public static $readers = null;
/**
* Plugin manager for loading writers
*
* @var null|ContainerInterface
*/
public static $writers = null;
/**
* Registered config file extensions.
* key is extension, value is reader instance or plugin name
*
* @var array
*/
protected static $extensions = [
'ini' => 'ini',
'json' => 'json',
'xml' => 'xml',
'yaml' => 'yaml',
'yml' => 'yaml',
'properties' => 'javaproperties',
];
/**
* Register config file extensions for writing
* key is extension, value is writer instance or plugin name
*
* @var array
*/
protected static $writerExtensions = [
'php' => 'php',
'ini' => 'ini',
'json' => 'json',
'xml' => 'xml',
'yaml' => 'yaml',
'yml' => 'yaml',
];
/**
* Read a config from a file.
*
* @param string $filename
* @param bool $returnConfigObject
* @param bool $useIncludePath
* @return array|Config
* @throws Exception\InvalidArgumentException
* @throws Exception\RuntimeException
*/
public static function fromFile($filename, $returnConfigObject = false, $useIncludePath = false)
{
$filepath = $filename;
if (! file_exists($filename)) {
if (! $useIncludePath) {
throw new Exception\RuntimeException(sprintf(
'Filename "%s" cannot be found relative to the working directory',
$filename
));
}
$fromIncludePath = stream_resolve_include_path($filename);
if (! $fromIncludePath) {
throw new Exception\RuntimeException(sprintf(
'Filename "%s" cannot be found relative to the working directory or the include_path ("%s")',
$filename,
get_include_path()
));
}
$filepath = $fromIncludePath;
}
$pathinfo = pathinfo($filepath);
if (! isset($pathinfo['extension'])) {
throw new Exception\RuntimeException(sprintf(
'Filename "%s" is missing an extension and cannot be auto-detected',
$filename
));
}
$extension = strtolower($pathinfo['extension']);
if ($extension === 'php') {
if (! is_file($filepath) || ! is_readable($filepath)) {
throw new Exception\RuntimeException(sprintf(
"File '%s' doesn't exist or not readable",
$filename
));
}
$config = include $filepath;
} elseif (isset(static::$extensions[$extension])) {
$reader = static::$extensions[$extension];
if (! $reader instanceof Reader\ReaderInterface) {
$reader = static::getReaderPluginManager()->get($reader);
static::$extensions[$extension] = $reader;
}
/* @var Reader\ReaderInterface $reader */
$config = $reader->fromFile($filepath);
} else {
throw new Exception\RuntimeException(sprintf(
'Unsupported config file extension: .%s',
$pathinfo['extension']
));
}
return ($returnConfigObject) ? new Config($config) : $config;
}
/**
* Read configuration from multiple files and merge them.
*
* @param array $files
* @param bool $returnConfigObject
* @param bool $useIncludePath
* @return array|Config
*/
public static function fromFiles(array $files, $returnConfigObject = false, $useIncludePath = false)
{
$config = [];
foreach ($files as $file) {
$config = ArrayUtils::merge($config, static::fromFile($file, false, $useIncludePath));
}
return ($returnConfigObject) ? new Config($config) : $config;
}
/**
* Writes a config to a file
*
* @param string $filename
* @param array|Config $config
* @return bool TRUE on success | FALSE on failure
* @throws Exception\RuntimeException
* @throws Exception\InvalidArgumentException
*/
public static function toFile($filename, $config)
{
if ((is_object($config) && ! ($config instanceof Config))
|| (! is_object($config) && ! is_array($config))
) {
throw new Exception\InvalidArgumentException(
__METHOD__." \$config should be an array or instance of Zend\\Config\\Config"
);
}
$extension = substr(strrchr($filename, '.'), 1);
$directory = dirname($filename);
if (! is_dir($directory)) {
throw new Exception\RuntimeException(
"Directory '{$directory}' does not exists!"
);
}
if (! is_writable($directory)) {
throw new Exception\RuntimeException(
"Cannot write in directory '{$directory}'"
);
}
if (! isset(static::$writerExtensions[$extension])) {
throw new Exception\RuntimeException(
"Unsupported config file extension: '.{$extension}' for writing."
);
}
$writer = static::$writerExtensions[$extension];
if (($writer instanceof Writer\AbstractWriter) === false) {
$writer = self::getWriterPluginManager()->get($writer);
static::$writerExtensions[$extension] = $writer;
}
if (is_object($config)) {
$config = $config->toArray();
}
$content = $writer->processConfig($config);
return (bool) (file_put_contents($filename, $content) !== false);
}
/**
* Set reader plugin manager
*
* @param ContainerInterface $readers
* @return void
*/
public static function setReaderPluginManager(ContainerInterface $readers)
{
static::$readers = $readers;
}
/**
* Get the reader plugin manager.
*
* If none is available, registers and returns a
* StandaloneReaderPluginManager instance by default.
*
* @return ContainerInterface
*/
public static function getReaderPluginManager()
{
if (static::$readers === null) {
static::$readers = new StandaloneReaderPluginManager();
}
return static::$readers;
}
/**
* Set writer plugin manager
*
* @param ContainerInterface $writers
* @return void
*/
public static function setWriterPluginManager(ContainerInterface $writers)
{
static::$writers = $writers;
}
/**
* Get the writer plugin manager.
*
* If none is available, registers and returns a
* StandaloneWriterPluginManager instance by default.
*
* @return ContainerInterface
*/
public static function getWriterPluginManager()
{
if (static::$writers === null) {
static::$writers = new StandaloneWriterPluginManager();
}
return static::$writers;
}
/**
* Set config reader for file extension
*
* @param string $extension
* @param string|Reader\ReaderInterface $reader
* @throws Exception\InvalidArgumentException
* @return void
*/
public static function registerReader($extension, $reader)
{
$extension = strtolower($extension);
if (! is_string($reader) && ! $reader instanceof Reader\ReaderInterface) {
throw new Exception\InvalidArgumentException(sprintf(
'Reader should be plugin name, class name or ' .
'instance of %s\Reader\ReaderInterface; received "%s"',
__NAMESPACE__,
(is_object($reader) ? get_class($reader) : gettype($reader))
));
}
static::$extensions[$extension] = $reader;
}
/**
* Set config writer for file extension
*
* @param string $extension
* @param string|Writer\AbstractWriter $writer
* @throws Exception\InvalidArgumentException
* @return void
*/
public static function registerWriter($extension, $writer)
{
$extension = strtolower($extension);
if (! is_string($writer) && ! $writer instanceof Writer\AbstractWriter) {
throw new Exception\InvalidArgumentException(sprintf(
'Writer should be plugin name, class name or ' .
'instance of %s\Writer\AbstractWriter; received "%s"',
__NAMESPACE__,
(is_object($writer) ? get_class($writer) : gettype($writer))
));
}
static::$writerExtensions[$extension] = $writer;
}
}

View File

@@ -0,0 +1,130 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config\Processor;
class Constant extends Token implements ProcessorInterface
{
/**
* Replace only user-defined tokens
*
* @var bool
*/
protected $userOnly = true;
/**
* Constant Processor walks through a Config structure and replaces all
* PHP constants with their respective values
*
* @param bool $userOnly True to process only user-defined constants,
* false to process all PHP constants; defaults to true.
* @param string $prefix Optional prefix
* @param string $suffix Optional suffix
* @param bool $enableKeyProcessing Whether or not to enable processing of
* constant values in configuration keys; defaults to false.
* @return \Zend\Config\Processor\Constant
*/
public function __construct($userOnly = true, $prefix = '', $suffix = '', $enableKeyProcessing = false)
{
$this->setUserOnly((bool) $userOnly);
$this->setPrefix((string) $prefix);
$this->setSuffix((string) $suffix);
if (true === $enableKeyProcessing) {
$this->enableKeyProcessing();
}
$this->loadConstants();
}
/**
* @return bool
*/
public function getUserOnly()
{
return $this->userOnly;
}
/**
* Should we use only user-defined constants?
*
* @param bool $userOnly
* @return self
*/
public function setUserOnly($userOnly)
{
$this->userOnly = (bool) $userOnly;
return $this;
}
/**
* Load all currently defined constants into parser.
*
* @return void
*/
public function loadConstants()
{
if ($this->userOnly) {
$constants = get_defined_constants(true);
$constants = isset($constants['user']) ? $constants['user'] : [];
$this->setTokens($constants);
} else {
$this->setTokens(get_defined_constants());
}
}
/**
* Get current token registry.
* @return array
*/
public function getTokens()
{
return $this->tokens;
}
/**
* Override processing of individual value.
*
* If the value is a string and evaluates to a class constant, returns
* the class constant value; otherwise, delegates to the parent.
*
* @param mixed $value
* @param array $replacements
* @return mixed
*/
protected function doProcess($value, array $replacements)
{
if (! is_string($value)) {
return parent::doProcess($value, $replacements);
}
if (false === strpos($value, '::')) {
return parent::doProcess($value, $replacements);
}
// Handle class constants
if (defined($value)) {
return constant($value);
}
// Handle ::class notation
if (! preg_match('/::class$/i', $value)) {
return parent::doProcess($value, $replacements);
}
$class = substr($value, 0, -7);
if (class_exists($class)) {
return $class;
}
// While we've matched ::class, the class does not exist, and PHP will
// raise an error if you try to define a constant using that notation.
// As such, we have something that cannot possibly be a constant, so we
// can safely return the value verbatim.
return $value;
}
}

View File

@@ -0,0 +1,86 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config\Processor;
use Zend\Config\Config;
use Zend\Config\Exception;
use Zend\Filter\FilterInterface as ZendFilter;
class Filter implements ProcessorInterface
{
/**
* @var ZendFilter
*/
protected $filter;
/**
* Filter all config values using the supplied Zend\Filter
*
* @param ZendFilter $filter
*/
public function __construct(ZendFilter $filter)
{
$this->setFilter($filter);
}
/**
* @param ZendFilter $filter
* @return self
*/
public function setFilter(ZendFilter $filter)
{
$this->filter = $filter;
return $this;
}
/**
* @return ZendFilter
*/
public function getFilter()
{
return $this->filter;
}
/**
* Process
*
* @param Config $config
* @return Config
* @throws Exception\InvalidArgumentException
*/
public function process(Config $config)
{
if ($config->isReadOnly()) {
throw new Exception\InvalidArgumentException('Cannot process config because it is read-only');
}
/**
* Walk through config and replace values
*/
foreach ($config as $key => $val) {
if ($val instanceof Config) {
$this->process($val);
} else {
$config->$key = $this->filter->filter($val);
}
}
return $config;
}
/**
* Process a single value
*
* @param mixed $value
* @return mixed
*/
public function processValue($value)
{
return $this->filter->filter($value);
}
}

View File

@@ -0,0 +1,29 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config\Processor;
use Zend\Config\Config;
interface ProcessorInterface
{
/**
* Process the whole Config structure and recursively parse all its values.
*
* @param Config $value
* @return Config
*/
public function process(Config $value);
/**
* Process a single value
*
* @param mixed $value
* @return mixed
*/
public function processValue($value);
}

View File

@@ -0,0 +1,50 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config\Processor;
use Zend\Config\Config;
use Zend\Config\Exception;
use Zend\Stdlib\PriorityQueue;
class Queue extends PriorityQueue implements ProcessorInterface
{
/**
* Process the whole config structure with each parser in the queue.
*
* @param Config $config
* @return Config
* @throws Exception\InvalidArgumentException
*/
public function process(Config $config)
{
if ($config->isReadOnly()) {
throw new Exception\InvalidArgumentException('Cannot process config because it is read-only');
}
foreach ($this as $parser) {
/** @var $parser ProcessorInterface */
$parser->process($config);
}
}
/**
* Process a single value
*
* @param mixed $value
* @return mixed
*/
public function processValue($value)
{
foreach ($this as $parser) {
/** @var $parser ProcessorInterface */
$value = $parser->processValue($value);
}
return $value;
}
}

View File

@@ -0,0 +1,298 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config\Processor;
use Traversable;
use Zend\Config\Config;
use Zend\Config\Exception;
class Token implements ProcessorInterface
{
/**
* Token prefix.
*
* @var string
*/
protected $prefix = '';
/**
* Whether or not to process keys as well as values.
*
* @var bool
*/
protected $processKeys = false;
/**
* Token suffix.
*
* @var string
*/
protected $suffix = '';
/**
* The registry of tokens
*
* @var array
*/
protected $tokens = [];
/**
* Replacement map
*
* @var array
*/
protected $map = null;
/**
* Token Processor walks through a Config structure and replaces all
* occurrences of tokens with supplied values.
*
* @param array|Config|Traversable $tokens Associative array of TOKEN =>
* value to replace it with
* @param string $prefix
* @param string $suffix
* @param bool $enableKeyProcessing Whether or not to enable processing of
* token values in configuration keys; defaults to false.
*/
public function __construct($tokens = [], $prefix = '', $suffix = '', $enableKeyProcessing = false)
{
$this->setTokens($tokens);
$this->setPrefix((string) $prefix);
$this->setSuffix((string) $suffix);
if (true === $enableKeyProcessing) {
$this->enableKeyProcessing();
}
}
/**
* @param string $prefix
* @return self
*/
public function setPrefix($prefix)
{
// reset map
$this->map = null;
$this->prefix = $prefix;
return $this;
}
/**
* @return string
*/
public function getPrefix()
{
return $this->prefix;
}
/**
* @param string $suffix
* @return self
*/
public function setSuffix($suffix)
{
// reset map
$this->map = null;
$this->suffix = $suffix;
return $this;
}
/**
* @return string
*/
public function getSuffix()
{
return $this->suffix;
}
/**
* Set token registry.
*
* @param array|Config|Traversable $tokens Associative array of TOKEN =>
* value to replace it with
* @return self
* @throws Exception\InvalidArgumentException
*/
public function setTokens($tokens)
{
if (is_array($tokens)) {
$this->tokens = $tokens;
} elseif ($tokens instanceof Config) {
$this->tokens = $tokens->toArray();
} elseif ($tokens instanceof Traversable) {
$this->tokens = [];
foreach ($tokens as $key => $val) {
$this->tokens[$key] = $val;
}
} else {
throw new Exception\InvalidArgumentException('Cannot use ' . gettype($tokens) . ' as token registry.');
}
// reset map
$this->map = null;
return $this;
}
/**
* Get current token registry.
*
* @return array
*/
public function getTokens()
{
return $this->tokens;
}
/**
* Add new token.
*
* @param string $token
* @param mixed $value
* @return self
* @throws Exception\InvalidArgumentException
*/
public function addToken($token, $value)
{
if (! is_scalar($token)) {
throw new Exception\InvalidArgumentException('Cannot use ' . gettype($token) . ' as token name.');
}
$this->tokens[$token] = $value;
// reset map
$this->map = null;
return $this;
}
/**
* Add new token.
*
* @param string $token
* @param mixed $value
* @return self
*/
public function setToken($token, $value)
{
return $this->addToken($token, $value);
}
/**
* Enable processing keys as well as values.
*
* @return void
*/
public function enableKeyProcessing()
{
$this->processKeys = true;
}
/**
* Build replacement map
*
* @return array
*/
protected function buildMap()
{
if (null === $this->map) {
if (! $this->suffix && ! $this->prefix) {
$this->map = $this->tokens;
} else {
$this->map = [];
foreach ($this->tokens as $token => $value) {
$this->map[$this->prefix . $token . $this->suffix] = $value;
}
}
foreach (array_keys($this->map) as $key) {
if (empty($key)) {
unset($this->map[$key]);
}
}
}
return $this->map;
}
/**
* Process
*
* @param Config $config
* @return Config
* @throws Exception\InvalidArgumentException
*/
public function process(Config $config)
{
return $this->doProcess($config, $this->buildMap());
}
/**
* Process a single value
*
* @param $value
* @return mixed
*/
public function processValue($value)
{
return $this->doProcess($value, $this->buildMap());
}
/**
* Applies replacement map to the given value by modifying the value itself
*
* @param mixed $value
* @param array $replacements
*
* @return mixed
*
* @throws Exception\InvalidArgumentException if the provided value is a read-only {@see Config}
*/
protected function doProcess($value, array $replacements)
{
if ($value instanceof Config) {
if ($value->isReadOnly()) {
throw new Exception\InvalidArgumentException('Cannot process config because it is read-only');
}
foreach ($value as $key => $val) {
$newKey = $this->processKeys ? $this->doProcess($key, $replacements) : $key;
$value->$newKey = $this->doProcess($val, $replacements);
// If the processed key differs from the original, remove the original
if ($newKey !== $key) {
unset($value->$key);
}
}
return $value;
}
if ($value instanceof Traversable || is_array($value)) {
foreach ($value as & $val) {
$val = $this->doProcess($val, $replacements);
}
return $value;
}
if (! is_string($value) && (is_bool($value) || is_numeric($value))) {
$stringVal = (string) $value;
$changedVal = strtr($stringVal, $this->map);
// replace the value only if a string replacement occurred
if ($changedVal !== $stringVal) {
return $changedVal;
}
return $value;
}
return strtr((string) $value, $this->map);
}
}

View File

@@ -0,0 +1,137 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config\Processor;
use Zend\Config\Config;
use Zend\Config\Exception;
use Zend\I18n\Translator\Translator as ZendTranslator;
class Translator implements ProcessorInterface
{
/**
* @var ZendTranslator
*/
protected $translator;
/**
* @var string|null
*/
protected $locale = null;
/**
* @var string
*/
protected $textDomain = 'default';
/**
* Translator uses the supplied Zend\I18n\Translator\Translator to find
* and translate language strings in config.
*
* @param ZendTranslator $translator
* @param string $textDomain
* @param string|null $locale
*/
public function __construct(ZendTranslator $translator, $textDomain = 'default', $locale = null)
{
$this->setTranslator($translator);
$this->setTextDomain($textDomain);
$this->setLocale($locale);
}
/**
* @param ZendTranslator $translator
* @return self
*/
public function setTranslator(ZendTranslator $translator)
{
$this->translator = $translator;
return $this;
}
/**
* @return ZendTranslator
*/
public function getTranslator()
{
return $this->translator;
}
/**
* @param string|null $locale
* @return self
*/
public function setLocale($locale)
{
$this->locale = $locale;
return $this;
}
/**
* @return string|null
*/
public function getLocale()
{
return $this->locale;
}
/**
* @param string $textDomain
* @return self
*/
public function setTextDomain($textDomain)
{
$this->textDomain = $textDomain;
return $this;
}
/**
* @return string
*/
public function getTextDomain()
{
return $this->textDomain;
}
/**
* Process
*
* @param Config $config
* @return Config
* @throws Exception\InvalidArgumentException
*/
public function process(Config $config)
{
if ($config->isReadOnly()) {
throw new Exception\InvalidArgumentException('Cannot process config because it is read-only');
}
/**
* Walk through config and replace values
*/
foreach ($config as $key => $val) {
if ($val instanceof Config) {
$this->process($val);
} else {
$config->{$key} = $this->translator->translate($val, $this->textDomain, $this->locale);
}
}
return $config;
}
/**
* Process a single value
*
* @param $value
* @return string
*/
public function processValue($value)
{
return $this->translator->translate($value, $this->textDomain, $this->locale);
}
}

View File

@@ -0,0 +1,261 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2005-2019 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config\Reader;
use Zend\Config\Exception;
/**
* INI config reader.
*/
class Ini implements ReaderInterface
{
/**
* Separator for nesting levels of configuration data identifiers.
*
* @var string
*/
protected $nestSeparator = '.';
/**
* Directory of the file to process.
*
* @var string
*/
protected $directory;
/**
* Flag which determines whether sections are processed or not.
*
* @see https://www.php.net/parse_ini_file
* @var bool
*/
protected $processSections = true;
/**
* Set nest separator.
*
* @param string $separator
* @return self
*/
public function setNestSeparator($separator)
{
$this->nestSeparator = $separator;
return $this;
}
/**
* Get nest separator.
*
* @return string
*/
public function getNestSeparator()
{
return $this->nestSeparator;
}
/**
* Marks whether sections should be processed.
* When sections are not processed,section names are stripped and section
* values are merged
*
* @see https://www.php.net/parse_ini_file
* @param bool $processSections
* @return $this
*/
public function setProcessSections($processSections)
{
$this->processSections = (bool) $processSections;
return $this;
}
/**
* Get if sections should be processed
* When sections are not processed,section names are stripped and section
* values are merged
*
* @see https://www.php.net/parse_ini_file
* @return bool
*/
public function getProcessSections()
{
return $this->processSections;
}
/**
* fromFile(): defined by Reader interface.
*
* @see ReaderInterface::fromFile()
* @param string $filename
* @return array
* @throws Exception\RuntimeException
*/
public function fromFile($filename)
{
if (! is_file($filename) || ! is_readable($filename)) {
throw new Exception\RuntimeException(sprintf(
"File '%s' doesn't exist or not readable",
$filename
));
}
$this->directory = dirname($filename);
set_error_handler(
function ($error, $message = '') use ($filename) {
throw new Exception\RuntimeException(
sprintf('Error reading INI file "%s": %s', $filename, $message),
$error
);
},
E_WARNING
);
$ini = parse_ini_file($filename, $this->getProcessSections());
restore_error_handler();
return $this->process($ini);
}
/**
* fromString(): defined by Reader interface.
*
* @param string $string
* @return array|bool
* @throws Exception\RuntimeException
*/
public function fromString($string)
{
if (empty($string)) {
return [];
}
$this->directory = null;
set_error_handler(
function ($error, $message = '') {
throw new Exception\RuntimeException(
sprintf('Error reading INI string: %s', $message),
$error
);
},
E_WARNING
);
$ini = parse_ini_string($string, $this->getProcessSections());
restore_error_handler();
return $this->process($ini);
}
/**
* Process data from the parsed ini file.
*
* @param array $data
* @return array
*/
protected function process(array $data)
{
$config = [];
foreach ($data as $section => $value) {
if (is_array($value)) {
if (strpos($section, $this->nestSeparator) !== false) {
$sections = explode($this->nestSeparator, $section);
$config = array_merge_recursive($config, $this->buildNestedSection($sections, $value));
} else {
$config[$section] = $this->processSection($value);
}
} else {
$this->processKey($section, $value, $config);
}
}
return $config;
}
/**
* Process a nested section
*
* @param array $sections
* @param mixed $value
* @return array
*/
private function buildNestedSection($sections, $value)
{
if (! $sections) {
return $this->processSection($value);
}
$nestedSection = [];
$first = array_shift($sections);
$nestedSection[$first] = $this->buildNestedSection($sections, $value);
return $nestedSection;
}
/**
* Process a section.
*
* @param array $section
* @return array
*/
protected function processSection(array $section)
{
$config = [];
foreach ($section as $key => $value) {
$this->processKey($key, $value, $config);
}
return $config;
}
/**
* Process a key.
*
* @param string $key
* @param string $value
* @param array $config
* @return array
* @throws Exception\RuntimeException
*/
protected function processKey($key, $value, array &$config)
{
if (strpos($key, $this->nestSeparator) !== false) {
$pieces = explode($this->nestSeparator, $key, 2);
if ($pieces[0] === '' || $pieces[1] === '') {
throw new Exception\RuntimeException(sprintf('Invalid key "%s"', $key));
}
if (! isset($config[$pieces[0]])) {
if ($pieces[0] === '0' && ! empty($config)) {
$config = [$pieces[0] => $config];
} else {
$config[$pieces[0]] = [];
}
} elseif (! is_array($config[$pieces[0]])) {
throw new Exception\RuntimeException(
sprintf('Cannot create sub-key for "%s", as key already exists', $pieces[0])
);
}
$this->processKey($pieces[1], $value, $config[$pieces[0]]);
} else {
if ($key === '@include') {
if ($this->directory === null) {
throw new Exception\RuntimeException('Cannot process @include statement for a string config');
}
$reader = clone $this;
$include = $reader->fromFile($this->directory . '/' . $value);
$config = array_replace_recursive($config, $include);
} else {
$config[$key] = $value;
}
}
}
}

View File

@@ -0,0 +1,179 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2005-2018 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config\Reader;
use Zend\Config\Exception;
/**
* Java-style properties config reader.
*/
class JavaProperties implements ReaderInterface
{
const DELIMITER_DEFAULT = ':';
const WHITESPACE_TRIM = true;
const WHITESPACE_KEEP = false;
/**
* Directory of the Java-style properties file
*
* @var string
*/
protected $directory;
/**
* Delimiter for key/value pairs.
*/
private $delimiter;
/*
* Whether or not to trim whitespace from discovered keys and values.
*
* @var bool
*/
private $trimWhitespace;
/**
* @param string $delimiter Delimiter to use for key/value pairs; defaults
* to self::DELIMITER_DEFAULT (':')
* @param bool $trimWhitespace
* @throws Exception\InvalidArgumentException for invalid $delimiter values.
*/
public function __construct($delimiter = self::DELIMITER_DEFAULT, $trimWhitespace = self::WHITESPACE_KEEP)
{
if (! is_string($delimiter) || '' === $delimiter) {
throw new Exception\InvalidArgumentException(sprintf(
'Invalid delimiter of type "%s"; must be a non-empty string',
is_object($delimiter) ? get_class($delimiter) : gettype($delimiter)
));
}
$this->delimiter = $delimiter;
$this->trimWhitespace = (bool) $trimWhitespace;
}
/**
* fromFile(): defined by Reader interface.
*
* @see ReaderInterface::fromFile()
* @param string $filename
* @return array
* @throws Exception\RuntimeException if the file cannot be read
*/
public function fromFile($filename)
{
if (! is_file($filename) || ! is_readable($filename)) {
throw new Exception\RuntimeException(sprintf(
"File '%s' doesn't exist or not readable",
$filename
));
}
$this->directory = dirname($filename);
$config = $this->parse(file_get_contents($filename));
return $this->process($config);
}
/**
* fromString(): defined by Reader interface.
*
* @see ReaderInterface::fromString()
* @param string $string
* @return array
* @throws Exception\RuntimeException if an @include key is found
*/
public function fromString($string)
{
if (empty($string)) {
return [];
}
$this->directory = null;
$config = $this->parse($string);
return $this->process($config);
}
/**
* Process the array for @include
*
* @param array $data
* @return array
* @throws Exception\RuntimeException if an @include key is found
*/
protected function process(array $data)
{
foreach ($data as $key => $value) {
if (trim($key) === '@include') {
if ($this->directory === null) {
throw new Exception\RuntimeException('Cannot process @include statement for a string');
}
$reader = clone $this;
unset($data[$key]);
$data = array_replace_recursive($data, $reader->fromFile($this->directory . '/' . $value));
}
}
return $data;
}
/**
* Parse Java-style properties string
*
* @todo Support use of the equals sign "key=value" as key-value delimiter
* @todo Ignore whitespace that precedes text past the first line of multiline values
*
* @param string $string
* @return array
*/
protected function parse($string)
{
$delimiter = $this->delimiter;
$delimLength = strlen($delimiter);
$result = [];
$lines = explode("\n", $string);
$key = '';
$isWaitingOtherLine = false;
foreach ($lines as $i => $line) {
// Ignore empty lines and commented lines
if (empty($line)
|| (! $isWaitingOtherLine && strpos($line, "#") === 0)
|| (! $isWaitingOtherLine && strpos($line, "!") === 0)
) {
continue;
}
// Add a new key-value pair or append value to a previous pair
if (! $isWaitingOtherLine) {
$key = substr($line, 0, strpos($line, $delimiter));
$value = substr($line, strpos($line, $delimiter) + $delimLength, strlen($line));
} else {
$value .= $line;
}
// Check if ends with single '\' (indicating another line is expected)
if (strrpos($value, "\\") === strlen($value) - strlen("\\")) {
$value = substr($value, 0, -1);
$isWaitingOtherLine = true;
} else {
$isWaitingOtherLine = false;
}
$key = $this->trimWhitespace ? trim($key) : $key;
$value = $this->trimWhitespace && ! $isWaitingOtherLine
? trim($value)
: $value;
$result[$key] = stripslashes($value);
unset($lines[$i]);
}
return $result;
}
}

View File

@@ -0,0 +1,125 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config\Reader;
use Zend\Config\Exception;
/**
* JSON config reader.
*/
class Json implements ReaderInterface
{
/**
* Directory of the JSON file
*
* @var string
*/
protected $directory;
/**
* fromFile(): defined by Reader interface.
*
* @see ReaderInterface::fromFile()
* @param string $filename
* @return array
* @throws Exception\RuntimeException
*/
public function fromFile($filename)
{
if (! is_file($filename) || ! is_readable($filename)) {
throw new Exception\RuntimeException(sprintf(
"File '%s' doesn't exist or not readable",
$filename
));
}
$this->directory = dirname($filename);
$config = $this->decode(file_get_contents($filename));
return $this->process($config);
}
/**
* fromString(): defined by Reader interface.
*
* @see ReaderInterface::fromString()
* @param string $string
* @return array|bool
* @throws Exception\RuntimeException
*/
public function fromString($string)
{
if (empty($string)) {
return [];
}
$this->directory = null;
$config = $this->decode($string);
return $this->process($config);
}
/**
* Process the array for @include
*
* @param array $data
* @return array
* @throws Exception\RuntimeException
*/
protected function process(array $data)
{
foreach ($data as $key => $value) {
if (is_array($value)) {
$data[$key] = $this->process($value);
}
if (trim($key) === '@include') {
if ($this->directory === null) {
throw new Exception\RuntimeException('Cannot process @include statement for a JSON string');
}
$reader = clone $this;
unset($data[$key]);
$data = array_replace_recursive($data, $reader->fromFile($this->directory . '/' . $value));
}
}
return $data;
}
/**
* Decode JSON configuration.
*
* Determines if ext/json is present, and, if so, uses that to decode the
* configuration. Otherwise, it uses zend-json, and, if that is missing,
* raises an exception indicating inability to decode.
*
* @param string $data
* @return array
* @throws Exception\RuntimeException for any decoding errors.
*/
private function decode($data)
{
$config = json_decode($data, true);
if (null !== $config && ! is_array($config)) {
throw new Exception\RuntimeException(
'Invalid JSON configuration; did not return an array or object'
);
}
if (null !== $config) {
return $config;
}
if (JSON_ERROR_NONE === json_last_error()) {
return $config;
}
throw new Exception\RuntimeException(json_last_error_msg());
}
}

View File

@@ -0,0 +1,27 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config\Reader;
interface ReaderInterface
{
/**
* Read from a file and create an array
*
* @param string $filename
* @return array
*/
public function fromFile($filename);
/**
* Read from a string and create an array
*
* @param string $string
* @return array|bool
*/
public function fromString($string);
}

View File

@@ -0,0 +1,201 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config\Reader;
use XMLReader;
use Zend\Config\Exception;
/**
* XML config reader.
*/
class Xml implements ReaderInterface
{
/**
* XML Reader instance.
*
* @var XMLReader
*/
protected $reader;
/**
* Directory of the file to process.
*
* @var string
*/
protected $directory;
/**
* Nodes to handle as plain text.
*
* @var array
*/
protected $textNodes = [
XMLReader::TEXT,
XMLReader::CDATA,
XMLReader::WHITESPACE,
XMLReader::SIGNIFICANT_WHITESPACE
];
/**
* fromFile(): defined by Reader interface.
*
* @see ReaderInterface::fromFile()
* @param string $filename
* @return array
* @throws Exception\RuntimeException
*/
public function fromFile($filename)
{
if (! is_file($filename) || ! is_readable($filename)) {
throw new Exception\RuntimeException(sprintf(
"File '%s' doesn't exist or not readable",
$filename
));
}
$this->reader = new XMLReader();
$this->reader->open($filename, null, LIBXML_XINCLUDE);
$this->directory = dirname($filename);
set_error_handler(
function ($error, $message = '') use ($filename) {
throw new Exception\RuntimeException(
sprintf('Error reading XML file "%s": %s', $filename, $message),
$error
);
},
E_WARNING
);
$return = $this->process();
restore_error_handler();
$this->reader->close();
return $return;
}
/**
* fromString(): defined by Reader interface.
*
* @see ReaderInterface::fromString()
* @param string $string
* @return array|bool
* @throws Exception\RuntimeException
*/
public function fromString($string)
{
if (empty($string)) {
return [];
}
$this->reader = new XMLReader();
$this->reader->XML($string, null, LIBXML_XINCLUDE);
$this->directory = null;
set_error_handler(
function ($error, $message = '') {
throw new Exception\RuntimeException(
sprintf('Error reading XML string: %s', $message),
$error
);
},
E_WARNING
);
$return = $this->process();
restore_error_handler();
$this->reader->close();
return $return;
}
/**
* Process data from the created XMLReader.
*
* @return array
*/
protected function process()
{
return $this->processNextElement();
}
/**
* Process the next inner element.
*
* @return mixed
*/
protected function processNextElement()
{
$children = [];
$text = '';
while ($this->reader->read()) {
if ($this->reader->nodeType === XMLReader::ELEMENT) {
if ($this->reader->depth === 0) {
return $this->processNextElement();
}
$attributes = $this->getAttributes();
$name = $this->reader->name;
if ($this->reader->isEmptyElement) {
$child = [];
} else {
$child = $this->processNextElement();
}
if ($attributes) {
if (is_string($child)) {
$child = ['_' => $child];
}
if (! is_array($child)) {
$child = [];
}
$child = array_merge($child, $attributes);
}
if (isset($children[$name])) {
if (! is_array($children[$name]) || ! array_key_exists(0, $children[$name])) {
$children[$name] = [$children[$name]];
}
$children[$name][] = $child;
} else {
$children[$name] = $child;
}
} elseif ($this->reader->nodeType === XMLReader::END_ELEMENT) {
break;
} elseif (in_array($this->reader->nodeType, $this->textNodes)) {
$text .= $this->reader->value;
}
}
return $children ?: $text;
}
/**
* Get all attributes on the current node.
*
* @return array
*/
protected function getAttributes()
{
$attributes = [];
if ($this->reader->hasAttributes) {
while ($this->reader->moveToNextAttribute()) {
$attributes[$this->reader->localName] = $this->reader->value;
}
$this->reader->moveToElement();
}
return $attributes;
}
}

View File

@@ -0,0 +1,157 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config\Reader;
use Zend\Config\Exception;
/**
* YAML config reader.
*/
class Yaml implements ReaderInterface
{
/**
* Directory of the YAML file
*
* @var string
*/
protected $directory;
/**
* YAML decoder callback
*
* @var callable
*/
protected $yamlDecoder;
/**
* Constructor
*
* @param callable $yamlDecoder
*/
public function __construct($yamlDecoder = null)
{
if ($yamlDecoder !== null) {
$this->setYamlDecoder($yamlDecoder);
} else {
if (function_exists('yaml_parse')) {
$this->setYamlDecoder('yaml_parse');
}
}
}
/**
* Set callback for decoding YAML
*
* @param string|callable $yamlDecoder the decoder to set
* @return self
* @throws Exception\RuntimeException
*/
public function setYamlDecoder($yamlDecoder)
{
if (! is_callable($yamlDecoder)) {
throw new Exception\RuntimeException(
'Invalid parameter to setYamlDecoder() - must be callable'
);
}
$this->yamlDecoder = $yamlDecoder;
return $this;
}
/**
* Get callback for decoding YAML
*
* @return callable
*/
public function getYamlDecoder()
{
return $this->yamlDecoder;
}
/**
* fromFile(): defined by Reader interface.
*
* @see ReaderInterface::fromFile()
* @param string $filename
* @return array
* @throws Exception\RuntimeException
*/
public function fromFile($filename)
{
if (! is_file($filename) || ! is_readable($filename)) {
throw new Exception\RuntimeException(sprintf(
"File '%s' doesn't exist or not readable",
$filename
));
}
if (null === $this->getYamlDecoder()) {
throw new Exception\RuntimeException("You didn't specify a Yaml callback decoder");
}
$this->directory = dirname($filename);
$config = call_user_func($this->getYamlDecoder(), file_get_contents($filename));
if (null === $config) {
throw new Exception\RuntimeException("Error parsing YAML data");
}
return $this->process($config);
}
/**
* fromString(): defined by Reader interface.
*
* @see ReaderInterface::fromString()
* @param string $string
* @return array|bool
* @throws Exception\RuntimeException
*/
public function fromString($string)
{
if (null === $this->getYamlDecoder()) {
throw new Exception\RuntimeException("You didn't specify a Yaml callback decoder");
}
if (empty($string)) {
return [];
}
$this->directory = null;
$config = call_user_func($this->getYamlDecoder(), $string);
if (null === $config) {
throw new Exception\RuntimeException("Error parsing YAML data");
}
return $this->process($config);
}
/**
* Process the array for @include
*
* @param array $data
* @return array
* @throws Exception\RuntimeException
*/
protected function process(array $data)
{
foreach ($data as $key => $value) {
if (is_array($value)) {
$data[$key] = $this->process($value);
}
if (trim($key) === '@include') {
if ($this->directory === null) {
throw new Exception\RuntimeException('Cannot process @include statement for a json string');
}
$reader = clone $this;
unset($data[$key]);
$data = array_replace_recursive($data, $reader->fromFile($this->directory . '/' . $value));
}
}
return $data;
}
}

View File

@@ -0,0 +1,91 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\AbstractPluginManager;
use Zend\ServiceManager\Exception\InvalidServiceException;
use Zend\ServiceManager\Factory\InvokableFactory;
class ReaderPluginManager extends AbstractPluginManager
{
protected $instanceOf = Reader\ReaderInterface::class;
protected $aliases = [
'ini' => Reader\Ini::class,
'Ini' => Reader\Ini::class,
'json' => Reader\Json::class,
'Json' => Reader\Json::class,
'xml' => Reader\Xml::class,
'Xml' => Reader\Xml::class,
'yaml' => Reader\Yaml::class,
'Yaml' => Reader\Yaml::class,
'javaproperties' => Reader\JavaProperties::class,
'javaProperties' => Reader\JavaProperties::class,
'JavaProperties' => Reader\JavaProperties::class,
];
protected $factories = [
Reader\Ini::class => InvokableFactory::class,
Reader\Json::class => InvokableFactory::class,
Reader\Xml::class => InvokableFactory::class,
Reader\Yaml::class => InvokableFactory::class,
Reader\JavaProperties::class => InvokableFactory::class,
// Legacy (v2) due to alias resolution; canonical form of resolved
// alias is used to look up the factory, while the non-normalized
// resolved alias is used as the requested name passed to the factory.
'zendconfigreaderini' => InvokableFactory::class,
'zendconfigreaderjson' => InvokableFactory::class,
'zendconfigreaderxml' => InvokableFactory::class,
'zendconfigreaderyaml' => InvokableFactory::class,
'zendconfigreaderjavaproperties' => InvokableFactory::class,
];
/**
* Validate the plugin is of the expected type (v3).
*
* Validates against `$instanceOf`.
*
* @param mixed $instance
* @throws InvalidServiceException
*/
public function validate($instance)
{
if (! $instance instanceof $this->instanceOf) {
throw new InvalidServiceException(sprintf(
'%s can only create instances of %s; %s is invalid',
get_class($this),
$this->instanceOf,
(is_object($instance) ? get_class($instance) : gettype($instance))
));
}
}
/**
* Validate the plugin is of the expected type (v2).
*
* Proxies to `validate()`.
*
* @param mixed $instance
* @throws Exception\InvalidArgumentException
*/
public function validatePlugin($instance)
{
try {
$this->validate($instance);
} catch (InvalidServiceException $e) {
throw new Exception\InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
}
public function __construct(ContainerInterface $container, array $config = [])
{
$config = array_merge_recursive(['aliases' => $this->aliases], $config);
parent::__construct($container, $config);
}
}

View File

@@ -0,0 +1,55 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config;
use Psr\Container\ContainerInterface;
class StandaloneReaderPluginManager implements ContainerInterface
{
private $knownPlugins = [
'ini' => Reader\Ini::class,
'json' => Reader\Json::class,
'xml' => Reader\Xml::class,
'yaml' => Reader\Yaml::class,
'javaproperties' => Reader\JavaProperties::class,
];
/**
* @param string $plugin
* @return bool
*/
public function has($plugin)
{
if (in_array($plugin, array_values($this->knownPlugins), true)) {
return true;
}
return in_array(strtolower($plugin), array_keys($this->knownPlugins), true);
}
/**
* @param string $plugin
* @return Reader\ReaderInterface
* @throws Exception\PluginNotFoundException
*/
public function get($plugin)
{
if (! $this->has($plugin)) {
throw new Exception\PluginNotFoundException(sprintf(
'Config reader plugin by name %s not found',
$plugin
));
}
if (! class_exists($plugin)) {
$plugin = $this->knownPlugins[strtolower($plugin)];
}
return new $plugin();
}
}

View File

@@ -0,0 +1,57 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config;
use Psr\Container\ContainerInterface;
class StandaloneWriterPluginManager implements ContainerInterface
{
private $knownPlugins = [
'ini' => Writer\Ini::class,
'javaproperties' => Writer\JavaProperties::class,
'json' => Writer\Json::class,
'php' => Writer\PhpArray::class,
'phparray' => Writer\PhpArray::class,
'xml' => Writer\Xml::class,
'yaml' => Writer\Yaml::class,
];
/**
* @param string $plugin
* @return bool
*/
public function has($plugin)
{
if (in_array($plugin, array_values($this->knownPlugins), true)) {
return true;
}
return in_array(strtolower($plugin), array_keys($this->knownPlugins), true);
}
/**
* @param string $plugin
* @return Reader\ReaderInterface
* @throws Exception\PluginNotFoundException
*/
public function get($plugin)
{
if (! $this->has($plugin)) {
throw new Exception\PluginNotFoundException(sprintf(
'Config writer plugin by name %s not found',
$plugin
));
}
if (! class_exists($plugin)) {
$plugin = $this->knownPlugins[strtolower($plugin)];
}
return new $plugin();
}
}

View File

@@ -0,0 +1,82 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config\Writer;
use Traversable;
use Zend\Config\Exception;
use Zend\Stdlib\ArrayUtils;
abstract class AbstractWriter implements WriterInterface
{
/**
* toFile(): defined by Writer interface.
*
* @see WriterInterface::toFile()
* @param string $filename
* @param mixed $config
* @param bool $exclusiveLock
* @return void
* @throws Exception\InvalidArgumentException
* @throws Exception\RuntimeException
*/
public function toFile($filename, $config, $exclusiveLock = true)
{
if (empty($filename)) {
throw new Exception\InvalidArgumentException('No file name specified');
}
$flags = 0;
if ($exclusiveLock) {
$flags |= LOCK_EX;
}
set_error_handler(
function ($error, $message = '') use ($filename) {
throw new Exception\RuntimeException(
sprintf('Error writing to "%s": %s', $filename, $message),
$error
);
},
E_WARNING
);
try {
file_put_contents($filename, $this->toString($config), $flags);
} catch (\Exception $e) {
restore_error_handler();
throw $e;
}
restore_error_handler();
}
/**
* toString(): defined by Writer interface.
*
* @see WriterInterface::toString()
* @param mixed $config
* @return string
* @throws Exception\InvalidArgumentException
*/
public function toString($config)
{
if ($config instanceof Traversable) {
$config = ArrayUtils::iteratorToArray($config);
} elseif (! is_array($config)) {
throw new Exception\InvalidArgumentException(__METHOD__ . ' expects an array or Traversable config');
}
return $this->processConfig($config);
}
/**
* @param array $config
* @return string
*/
abstract protected function processConfig(array $config);
}

View File

@@ -0,0 +1,185 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config\Writer;
use Zend\Config\Exception;
class Ini extends AbstractWriter
{
/**
* Separator for nesting levels of configuration data identifiers.
*
* @var string
*/
protected $nestSeparator = '.';
/**
* If true the INI string is rendered in the global namespace without
* sections.
*
* @var bool
*/
protected $renderWithoutSections = false;
/**
* Set nest separator.
*
* @param string $separator
* @return self
*/
public function setNestSeparator($separator)
{
$this->nestSeparator = $separator;
return $this;
}
/**
* Get nest separator.
*
* @return string
*/
public function getNestSeparator()
{
return $this->nestSeparator;
}
/**
* Set if rendering should occur without sections or not.
*
* If set to true, the INI file is rendered without sections completely
* into the global namespace of the INI file.
*
* @param bool $withoutSections
* @return self
*/
public function setRenderWithoutSectionsFlags($withoutSections)
{
$this->renderWithoutSections = (bool) $withoutSections;
return $this;
}
/**
* Return whether the writer should render without sections.
*
* @return bool
*/
public function shouldRenderWithoutSections()
{
return $this->renderWithoutSections;
}
/**
* processConfig(): defined by AbstractWriter.
*
* @param array $config
* @return string
*/
public function processConfig(array $config)
{
$iniString = '';
if ($this->shouldRenderWithoutSections()) {
$iniString .= $this->addBranch($config);
} else {
$config = $this->sortRootElements($config);
foreach ($config as $sectionName => $data) {
if (! is_array($data)) {
$iniString .= $sectionName
. ' = '
. $this->prepareValue($data)
. "\n";
} else {
$iniString .= '[' . $sectionName . ']' . "\n"
. $this->addBranch($data)
. "\n";
}
}
}
return $iniString;
}
/**
* Add a branch to an INI string recursively.
*
* @param array $config
* @param array $parents
* @return string
*/
protected function addBranch(array $config, $parents = [])
{
$iniString = '';
foreach ($config as $key => $value) {
$group = array_merge($parents, [$key]);
if (is_array($value)) {
$iniString .= $this->addBranch($value, $group);
} else {
$iniString .= implode($this->nestSeparator, $group)
. ' = '
. $this->prepareValue($value)
. "\n";
}
}
return $iniString;
}
/**
* Prepare a value for INI.
*
* @param mixed $value
* @return string
* @throws Exception\RuntimeException
*/
protected function prepareValue($value)
{
if (is_int($value) || is_float($value)) {
return $value;
}
if (is_bool($value)) {
return ($value ? 'true' : 'false');
}
if (false === strpos($value, '"')) {
return '"' . $value . '"';
}
throw new Exception\RuntimeException('Value can not contain double quotes');
}
/**
* Root elements that are not assigned to any section needs to be on the
* top of config.
*
* @param array $config
* @return array
*/
protected function sortRootElements(array $config)
{
$sections = [];
// Remove sections from config array.
foreach ($config as $key => $value) {
if (is_array($value)) {
$sections[$key] = $value;
unset($config[$key]);
}
}
// Read sections to the end.
foreach ($sections as $key => $value) {
$config[$key] = $value;
}
return $config;
}
}

View File

@@ -0,0 +1,72 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2018 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config\Writer;
use Zend\Config\Exception;
class JavaProperties extends AbstractWriter
{
const DELIMITER_DEFAULT = ':';
/**
* Delimiter for key/value pairs.
*/
private $delimiter;
/**
* @param string $delimiter Delimiter to use for key/value pairs; defaults
* to self::DELIMITER_DEFAULT (':')
* @throws Exception\InvalidArgumentException for invalid $delimiter values.
*/
public function __construct($delimiter = self::DELIMITER_DEFAULT)
{
if (! is_string($delimiter) || '' === $delimiter) {
throw new Exception\InvalidArgumentException(sprintf(
'Invalid delimiter of type "%s"; must be a non-empty string',
is_object($delimiter) ? get_class($delimiter) : gettype($delimiter)
));
}
$this->delimiter = $delimiter;
}
/**
* processConfig(): defined by AbstractWriter.
*
* @param array $config
* @return string
* @throws Exception\UnprocessableConfigException for non-scalar values in
* the $config array.
*/
public function processConfig(array $config)
{
$string = '';
foreach ($config as $key => $value) {
if (! is_scalar($value)) {
throw new Exception\UnprocessableConfigException(sprintf(
'%s configuration writer can only process scalar values; received "%s" for key "%s"',
__CLASS__,
is_object($value) ? get_class($value) : gettype($value),
$key
));
}
$value = (null === $value) ? '' : $value;
$string .= sprintf(
"%s%s%s\n",
$key,
$this->delimiter,
$value
);
}
return $string;
}
}

View File

@@ -0,0 +1,31 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config\Writer;
use Zend\Config\Exception;
class Json extends AbstractWriter
{
/**
* processConfig(): defined by AbstractWriter.
*
* @param array $config
* @return string
* @throws Exception\RuntimeException if encoding errors occur.
*/
public function processConfig(array $config)
{
$serialized = json_encode($config, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
if (false === $serialized) {
throw new Exception\RuntimeException(json_last_error_msg());
}
return $serialized;
}
}

View File

@@ -0,0 +1,236 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config\Writer;
use Zend\Config\Exception;
class PhpArray extends AbstractWriter
{
/**
* @var string
*/
const INDENT_STRING = ' ';
/**
* @var bool
*/
protected $useBracketArraySyntax = false;
/**
* @var bool
*/
protected $useClassNameScalars = false;
/**
* processConfig(): defined by AbstractWriter.
*
* @param array $config
* @return string
*/
public function processConfig(array $config)
{
$arraySyntax = [
'open' => $this->useBracketArraySyntax ? '[' : 'array(',
'close' => $this->useBracketArraySyntax ? ']' : ')'
];
return "<?php\n" .
"return " . $arraySyntax['open'] . "\n" . $this->processIndented($config, $arraySyntax) .
$arraySyntax['close'] . ";\n";
}
/**
* Sets whether or not to use the PHP 5.4+ "[]" array syntax.
*
* @param bool $value
* @return self
*/
public function setUseBracketArraySyntax($value)
{
$this->useBracketArraySyntax = $value;
return $this;
}
/**
* Sets whether or not to render resolvable FQN strings as scalars, using PHP 5.5+ class-keyword
*
* @param boolean $value
* @return self
*/
public function setUseClassNameScalars($value)
{
$this->useClassNameScalars = $value;
return $this;
}
/**
* @return boolean
*/
public function getUseClassNameScalars()
{
return $this->useClassNameScalars;
}
/**
* toFile(): defined by Writer interface.
*
* @see WriterInterface::toFile()
* @param string $filename
* @param mixed $config
* @param bool $exclusiveLock
* @return void
* @throws Exception\InvalidArgumentException
* @throws Exception\RuntimeException
*/
public function toFile($filename, $config, $exclusiveLock = true)
{
if (empty($filename)) {
throw new Exception\InvalidArgumentException('No file name specified');
}
$flags = 0;
if ($exclusiveLock) {
$flags |= LOCK_EX;
}
set_error_handler(
function ($error, $message = '') use ($filename) {
throw new Exception\RuntimeException(
sprintf('Error writing to "%s": %s', $filename, $message),
$error
);
},
E_WARNING
);
try {
// for Windows, paths are escaped.
$dirname = str_replace('\\', '\\\\', dirname($filename));
$string = $this->toString($config);
$string = str_replace("'" . $dirname, "__DIR__ . '", $string);
file_put_contents($filename, $string, $flags);
} catch (\Exception $e) {
restore_error_handler();
throw $e;
}
restore_error_handler();
}
/**
* Recursively processes a PHP config array structure into a readable format.
*
* @param array $config
* @param array $arraySyntax
* @param int $indentLevel
* @return string
*/
protected function processIndented(array $config, array $arraySyntax, &$indentLevel = 1)
{
$arrayString = "";
foreach ($config as $key => $value) {
$arrayString .= str_repeat(self::INDENT_STRING, $indentLevel);
$arrayString .= (is_int($key) ? $key : $this->processStringKey($key)) . ' => ';
if (is_array($value)) {
if ($value === []) {
$arrayString .= $arraySyntax['open'] . $arraySyntax['close'] . ",\n";
} else {
$indentLevel++;
$arrayString .= $arraySyntax['open'] . "\n"
. $this->processIndented($value, $arraySyntax, $indentLevel)
. str_repeat(self::INDENT_STRING, --$indentLevel) . $arraySyntax['close'] . ",\n";
}
} elseif (is_object($value)) {
$arrayString .= var_export($value, true) . ",\n";
} elseif (is_string($value)) {
$arrayString .= $this->processStringValue($value) . ",\n";
} elseif (is_bool($value)) {
$arrayString .= ($value ? 'true' : 'false') . ",\n";
} elseif ($value === null) {
$arrayString .= "null,\n";
} else {
$arrayString .= $value . ",\n";
}
}
return $arrayString;
}
/**
* Process a string configuration value
*
* @param string $value
* @return string
*/
protected function processStringValue($value)
{
if ($this->useClassNameScalars && false !== ($fqnValue = $this->fqnStringToClassNameScalar($value))) {
return $fqnValue;
}
return var_export($value, true);
}
/**
* Process a string configuration key
*
* @param string $key
* @return string
*/
protected function processStringKey($key)
{
if ($this->useClassNameScalars && false !== ($fqnKey = $this->fqnStringToClassNameScalar($key))) {
return $fqnKey;
}
return "'" . addslashes($key) . "'";
}
/**
* Attempts to convert a FQN string to class name scalar.
* Returns false if string is not a valid FQN or can not be resolved to an existing name.
*
* @param string $string
* @return bool|string
*/
protected function fqnStringToClassNameScalar($string)
{
if (strlen($string) < 1) {
return false;
}
if ($string[0] !== '\\') {
$string = '\\' . $string;
}
if ($this->checkStringIsFqn($string)) {
return $string . '::class';
}
return false;
}
/**
* Check whether a string represents a resolvable FQCN
*
* @param string $string
* @return bool
*/
protected function checkStringIsFqn($string)
{
if (! preg_match('/^(?:\x5c[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)+$/', $string)) {
return false;
}
return class_exists($string) || interface_exists($string) || trait_exists($string);
}
}

View File

@@ -0,0 +1,29 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config\Writer;
interface WriterInterface
{
/**
* Write a config object to a file.
*
* @param string $filename
* @param mixed $config
* @param bool $exclusiveLock
* @return void
*/
public function toFile($filename, $config, $exclusiveLock = true);
/**
* Write a config object to a string.
*
* @param mixed $config
* @return string
*/
public function toString($config);
}

View File

@@ -0,0 +1,89 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config\Writer;
use XMLWriter;
use Zend\Config\Exception;
class Xml extends AbstractWriter
{
/**
* processConfig(): defined by AbstractWriter.
*
* @param array $config
* @return string
*/
public function processConfig(array $config)
{
$writer = new XMLWriter();
$writer->openMemory();
$writer->setIndent(true);
$writer->setIndentString(str_repeat(' ', 4));
$writer->startDocument('1.0', 'UTF-8');
$writer->startElement('zend-config');
foreach ($config as $sectionName => $data) {
if (! is_array($data)) {
$writer->writeElement($sectionName, (string) $data);
} else {
$this->addBranch($sectionName, $data, $writer);
}
}
$writer->endElement();
$writer->endDocument();
return $writer->outputMemory();
}
/**
* Add a branch to an XML object recursively.
*
* @param string $branchName
* @param array $config
* @param XMLWriter $writer
* @return void
* @throws Exception\RuntimeException
*/
protected function addBranch($branchName, array $config, XMLWriter $writer)
{
$branchType = null;
foreach ($config as $key => $value) {
if ($branchType === null) {
if (is_numeric($key)) {
$branchType = 'numeric';
} else {
$writer->startElement($branchName);
$branchType = 'string';
}
} elseif ($branchType !== (is_numeric($key) ? 'numeric' : 'string')) {
throw new Exception\RuntimeException('Mixing of string and numeric keys is not allowed');
}
if ($branchType === 'numeric') {
if (is_array($value)) {
$this->addBranch($branchName, $value, $writer);
} else {
$writer->writeElement($branchName, (string) $value);
}
} else {
if (is_array($value)) {
$this->addBranch($key, $value, $writer);
} else {
$writer->writeElement($key, (string) $value);
}
}
}
if ($branchType === 'string') {
$writer->endElement();
}
}
}

View File

@@ -0,0 +1,83 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config\Writer;
use Zend\Config\Exception;
class Yaml extends AbstractWriter
{
/**
* YAML encoder callback
*
* @var callable
*/
protected $yamlEncoder;
/**
* Constructor
*
* @param callable|string|null $yamlEncoder
*/
public function __construct($yamlEncoder = null)
{
if ($yamlEncoder !== null) {
$this->setYamlEncoder($yamlEncoder);
} else {
if (function_exists('yaml_emit')) {
$this->setYamlEncoder('yaml_emit');
}
}
}
/**
* Get callback for decoding YAML
*
* @return callable
*/
public function getYamlEncoder()
{
return $this->yamlEncoder;
}
/**
* Set callback for decoding YAML
*
* @param callable $yamlEncoder the decoder to set
* @return self
* @throws Exception\InvalidArgumentException
*/
public function setYamlEncoder($yamlEncoder)
{
if (! is_callable($yamlEncoder)) {
throw new Exception\InvalidArgumentException('Invalid parameter to setYamlEncoder() - must be callable');
}
$this->yamlEncoder = $yamlEncoder;
return $this;
}
/**
* processConfig(): defined by AbstractWriter.
*
* @param array $config
* @return string
* @throws Exception\RuntimeException
*/
public function processConfig(array $config)
{
if (null === $this->getYamlEncoder()) {
throw new Exception\RuntimeException("You didn't specify a Yaml callback encoder");
}
$config = call_user_func($this->getYamlEncoder(), $config);
if (null === $config) {
throw new Exception\RuntimeException("Error generating YAML data");
}
return $config;
}
}

View File

@@ -0,0 +1,97 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Config;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\AbstractPluginManager;
use Zend\ServiceManager\Exception\InvalidServiceException;
use Zend\ServiceManager\Factory\InvokableFactory;
class WriterPluginManager extends AbstractPluginManager
{
protected $instanceOf = Writer\AbstractWriter::class;
protected $aliases = [
'ini' => Writer\Ini::class,
'Ini' => Writer\Ini::class,
'json' => Writer\Json::class,
'Json' => Writer\Json::class,
'php' => Writer\PhpArray::class,
'phparray' => Writer\PhpArray::class,
'phpArray' => Writer\PhpArray::class,
'PhpArray' => Writer\PhpArray::class,
'yaml' => Writer\Yaml::class,
'Yaml' => Writer\Yaml::class,
'xml' => Writer\Xml::class,
'Xml' => Writer\Xml::class,
'javaproperties' => Writer\JavaProperties::class,
'javaProperties' => Writer\JavaProperties::class,
'JavaProperties' => Writer\JavaProperties::class,
];
protected $factories = [
Writer\Ini::class => InvokableFactory::class,
Writer\JavaProperties::class => InvokableFactory::class,
Writer\Json::class => InvokableFactory::class,
Writer\PhpArray::class => InvokableFactory::class,
Writer\Yaml::class => InvokableFactory::class,
Writer\Xml::class => InvokableFactory::class,
// Legacy (v2) due to alias resolution; canonical form of resolved
// alias is used to look up the factory, while the non-normalized
// resolved alias is used as the requested name passed to the factory.
'zendconfigwriterini' => InvokableFactory::class,
'zendconfigwriterjavaproperties' => InvokableFactory::class,
'zendconfigwriterjson' => InvokableFactory::class,
'zendconfigwriterphparray' => InvokableFactory::class,
'zendconfigwriteryaml' => InvokableFactory::class,
'zendconfigwriterxml' => InvokableFactory::class,
];
/**
* Validate the plugin is of the expected type (v3).
*
* Validates against `$instanceOf`.
*
* @param mixed $instance
* @throws InvalidServiceException
*/
public function validate($instance)
{
if (! $instance instanceof $this->instanceOf) {
throw new InvalidServiceException(sprintf(
'%s can only create instances of %s; %s is invalid',
get_class($this),
$this->instanceOf,
(is_object($instance) ? get_class($instance) : gettype($instance))
));
}
}
/**
* Validate the plugin is of the expected type (v2).
*
* Proxies to `validate()`.
*
* @param mixed $instance
* @throws Exception\InvalidArgumentException
*/
public function validatePlugin($instance)
{
try {
$this->validate($instance);
} catch (InvalidServiceException $e) {
throw new Exception\InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
}
public function __construct(ContainerInterface $container, array $config = [])
{
$config = array_merge_recursive(['aliases' => $this->aliases], $config);
parent::__construct($container, $config);
}
}