maj+ header
This commit is contained in:
+84
-46
@@ -123,7 +123,7 @@ abstract class BlueprintForm implements \ArrayAccess, ExportInterface
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function load()
|
||||
public function load($extends = null)
|
||||
{
|
||||
// Only load and extend blueprint if it has not yet been loaded.
|
||||
if (empty($this->items) && $this->filename) {
|
||||
@@ -131,7 +131,7 @@ abstract class BlueprintForm implements \ArrayAccess, ExportInterface
|
||||
$files = $this->getFiles($this->filename);
|
||||
|
||||
// Load and extend blueprints.
|
||||
$data = $this->doLoad($files);
|
||||
$data = $this->doLoad($files, $extends);
|
||||
|
||||
$this->items = (array) array_shift($data);
|
||||
|
||||
@@ -301,9 +301,9 @@ abstract class BlueprintForm implements \ArrayAccess, ExportInterface
|
||||
$current = $current[$field];
|
||||
$fields = false;
|
||||
|
||||
} elseif (isset($current['.' . $field])) {
|
||||
} elseif (isset($current[$index = '.' . $field])) {
|
||||
$parts[] = array_shift($path);
|
||||
$current = $current['.' . $field];
|
||||
$current = $current[$index];
|
||||
$fields = false;
|
||||
|
||||
} else {
|
||||
@@ -332,18 +332,22 @@ abstract class BlueprintForm implements \ArrayAccess, ExportInterface
|
||||
$head = array_pop($head_stack);
|
||||
unset($bref_stack[key($bref_stack)]);
|
||||
|
||||
foreach (array_keys($head) as $key) {
|
||||
if (!empty($key) && ($key[0] === '@' || $key[strlen($key) - 1] === '@')) {
|
||||
$list = explode('-', trim($key, '@'), 2);
|
||||
foreach ($head as $key => $value) {
|
||||
if (strpos($key, '@') !== false) {
|
||||
// Remove @ from the start and the end. Key syntax `import@2` is supported to allow multiple operations of the same type.
|
||||
$list = explode('-', preg_replace('/^(@*)?([^@]+)(@\d*)?$/', '\2', $key), 2);
|
||||
$action = array_shift($list);
|
||||
if ($action === 'unset' || $action === 'replace') {
|
||||
$property = array_shift($list);
|
||||
if (!$property) {
|
||||
$bref = ['unset@' => true];
|
||||
} else {
|
||||
unset($bref[$property]);
|
||||
}
|
||||
continue;
|
||||
$property = array_shift($list);
|
||||
|
||||
switch ($action) {
|
||||
case 'unset':
|
||||
case 'replace':
|
||||
if (!$property) {
|
||||
$bref = ['unset@' => true];
|
||||
} else {
|
||||
unset($bref[$property]);
|
||||
}
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
if (isset($key, $bref[$key]) && is_array($bref[$key]) && is_array($head[$key])) {
|
||||
@@ -374,10 +378,10 @@ abstract class BlueprintForm implements \ArrayAccess, ExportInterface
|
||||
if ($field && isset($item['type'])) {
|
||||
$item['name'] = $key;
|
||||
}
|
||||
|
||||
// Handle special instructions in the form.
|
||||
if (!empty($key) && ($key[0] === '@' || $key[strlen($key) - 1] === '@')) {
|
||||
$list = explode('-', trim($key, '@'), 2);
|
||||
if (strpos($key, '@') !== false) {
|
||||
// Remove @ from the start and the end. Key syntax `import@2` is supported to allow multiple operations of the same type.
|
||||
$list = explode('-', preg_replace('/^(@*)?([^@]+)(@\d*)?$/', '\2', $key), 2);
|
||||
$action = array_shift($list);
|
||||
$property = array_shift($list);
|
||||
|
||||
@@ -389,8 +393,8 @@ abstract class BlueprintForm implements \ArrayAccess, ExportInterface
|
||||
}
|
||||
break;
|
||||
case 'import':
|
||||
$this->doImport($item, $path);
|
||||
unset($items[$key]);
|
||||
$this->doImport($item, $path);
|
||||
break;
|
||||
case 'ordering':
|
||||
$ordering = $item;
|
||||
@@ -412,6 +416,7 @@ abstract class BlueprintForm implements \ArrayAccess, ExportInterface
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($item);
|
||||
|
||||
if ($order) {
|
||||
// Reorder fields if needed.
|
||||
@@ -423,38 +428,67 @@ abstract class BlueprintForm implements \ArrayAccess, ExportInterface
|
||||
|
||||
/**
|
||||
* @param array|string $value
|
||||
* @param array $path
|
||||
* @return array|null
|
||||
*/
|
||||
protected function doImport(&$value, array &$path)
|
||||
protected function loadImport($value)
|
||||
{
|
||||
$type = !is_string($value) ? !isset($value['type']) ? null : $value['type'] : $value;
|
||||
|
||||
$files = $this->getFiles($type, isset($value['context']) ? $value['context'] : null);
|
||||
|
||||
if (!$files) {
|
||||
return;
|
||||
$type = !is_string($value) ? (!isset($value['type']) ? null : $value['type']) : $value;
|
||||
$field = 'form';
|
||||
if ($type && strpos($type, ':') !== false) {
|
||||
list ($type, $field) = explode(':', $type, 2);
|
||||
}
|
||||
|
||||
/** @var BlueprintForm $blueprint */
|
||||
$blueprint = new static($files);
|
||||
$blueprint->setContext($this->context)->setOverrides($this->overrides)->load();
|
||||
if (!$type && !$field) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$name = implode('/', $path);
|
||||
if ($type) {
|
||||
$files = $this->getFiles($type, isset($value['context']) ? $value['context'] : null);
|
||||
|
||||
$this->embed($name, $blueprint->form(), '/', false);
|
||||
if (!$files) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var BlueprintForm $blueprint */
|
||||
$blueprint = new static($files);
|
||||
$blueprint->setContext($this->context)->setOverrides($this->overrides)->load();
|
||||
} else {
|
||||
$blueprint = $this;
|
||||
}
|
||||
|
||||
$import = $blueprint->get($field);
|
||||
|
||||
return is_array($import) ? $import : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string $value
|
||||
* @param array $path
|
||||
*/
|
||||
protected function doImport($value, array &$path)
|
||||
{
|
||||
$imported = $this->loadImport($value);
|
||||
|
||||
if ($imported) {
|
||||
$this->deepInit($imported, $path);
|
||||
$name = implode('/', $path);
|
||||
$this->embed($name, $imported, '/', false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function that handles loading extended blueprints.
|
||||
*
|
||||
* @param array $files
|
||||
* @param string|array|null $extends
|
||||
* @return array
|
||||
*/
|
||||
protected function doLoad(array $files)
|
||||
protected function doLoad(array $files, $extends = null)
|
||||
{
|
||||
$filename = array_shift($files);
|
||||
$content = $this->loadFile($filename);
|
||||
|
||||
$key = null;
|
||||
if (isset($content['extends@'])) {
|
||||
$key = 'extends@';
|
||||
} elseif (isset($content['@extends'])) {
|
||||
@@ -463,12 +497,12 @@ abstract class BlueprintForm implements \ArrayAccess, ExportInterface
|
||||
$key = '@extends@';
|
||||
}
|
||||
|
||||
$data = isset($key) ? $this->doExtend($filename, $files, (array) $content[$key]) : [];
|
||||
$override = (bool)$extends;
|
||||
$extends = (array)($key && !$extends ? $content[$key] : $extends);
|
||||
|
||||
if (isset($key)) {
|
||||
unset($content[$key]);
|
||||
}
|
||||
unset($content['extends@'], $content['@extends'], $content['@extends@']);
|
||||
|
||||
$data = $extends ? $this->doExtend($filename, $files, $extends, $override) : [];
|
||||
$data[] = $content;
|
||||
|
||||
return $data;
|
||||
@@ -482,17 +516,16 @@ abstract class BlueprintForm implements \ArrayAccess, ExportInterface
|
||||
* @param array $extends
|
||||
* @return array
|
||||
*/
|
||||
protected function doExtend($filename, array $parents, array $extends)
|
||||
protected function doExtend($filename, array $parents, array $extends, $override = false)
|
||||
{
|
||||
if (is_string(key($extends))) {
|
||||
$extends = [$extends];
|
||||
}
|
||||
|
||||
$data = [];
|
||||
$data = [[]];
|
||||
foreach ($extends as $value) {
|
||||
// Accept array of type and context or a string.
|
||||
$type = !is_string($value)
|
||||
? !isset($value['type']) ? null : $value['type'] : $value;
|
||||
$type = !is_string($value) ? (!isset($value['type']) ? null : $value['type']) : $value;
|
||||
|
||||
if (!$type) {
|
||||
continue;
|
||||
@@ -507,6 +540,10 @@ abstract class BlueprintForm implements \ArrayAccess, ExportInterface
|
||||
} else {
|
||||
$files = $this->getFiles($type, isset($value['context']) ? $value['context'] : null);
|
||||
|
||||
if ($override && !$files) {
|
||||
throw new \RuntimeException("Blueprint '{$type}' missing for '{$filename}'");
|
||||
}
|
||||
|
||||
// Detect extend loops.
|
||||
if ($files && array_intersect($files, $parents)) {
|
||||
// Let's check if user really meant extends@: parent@.
|
||||
@@ -525,11 +562,12 @@ abstract class BlueprintForm implements \ArrayAccess, ExportInterface
|
||||
}
|
||||
|
||||
if ($files) {
|
||||
$data = array_merge($data, $this->doLoad($files));
|
||||
$data[] = $this->doLoad($files);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
// TODO: In PHP 5.6+ use array_merge(...$data);
|
||||
return call_user_func_array('array_merge', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -545,14 +583,14 @@ abstract class BlueprintForm implements \ArrayAccess, ExportInterface
|
||||
|
||||
foreach ($keys as $item => $ordering) {
|
||||
if ((string)(int) $ordering === (string) $ordering) {
|
||||
$location = array_search($item, $reordered);
|
||||
$location = array_search($item, $reordered, true);
|
||||
$rel = array_splice($reordered, $location, 1);
|
||||
array_splice($reordered, $ordering, 0, $rel);
|
||||
|
||||
} elseif (isset($items[$ordering])) {
|
||||
$location = array_search($item, $reordered);
|
||||
$location = array_search($item, $reordered, true);
|
||||
$rel = array_splice($reordered, $location, 1);
|
||||
$location = array_search($ordering, $reordered);
|
||||
$location = array_search($ordering, $reordered, true);
|
||||
array_splice($reordered, $location + 1, 0, $rel);
|
||||
}
|
||||
}
|
||||
|
||||
+11
-12
@@ -127,7 +127,7 @@ class BlueprintSchema
|
||||
*/
|
||||
public function get($name, $default = null, $separator = '.')
|
||||
{
|
||||
$name = $separator != '.' ? strtr($name, $separator, '.') : $name;
|
||||
$name = $separator !== '.' ? strtr($name, $separator, '.') : $name;
|
||||
|
||||
return isset($this->items[$name]) ? $this->items[$name] : $default;
|
||||
}
|
||||
@@ -143,7 +143,7 @@ class BlueprintSchema
|
||||
*/
|
||||
public function set($name, $value, $separator = '.')
|
||||
{
|
||||
$name = $separator != '.' ? strtr($name, $separator, '.') : $name;
|
||||
$name = $separator !== '.' ? strtr($name, $separator, '.') : $name;
|
||||
|
||||
$this->items[$name] = $value;
|
||||
$this->addProperty($name);
|
||||
@@ -215,7 +215,7 @@ class BlueprintSchema
|
||||
$this->rules = array_merge($this->rules, $value['rules']);
|
||||
}
|
||||
|
||||
$name = $separator != '.' ? strtr($name, $separator, '.') : $name;
|
||||
$name = $separator !== '.' ? strtr($name, $separator, '.') : $name;
|
||||
|
||||
if (isset($value['form'])) {
|
||||
$form = array_diff_key($value['form'], ['fields' => 1, 'field' => 1]);
|
||||
@@ -326,7 +326,7 @@ class BlueprintSchema
|
||||
// Check if the form cannot have extra fields.
|
||||
if (isset($rules[''])) {
|
||||
$rule = $this->items[''];
|
||||
if (isset($rule['type']) && $rule['type'] != '_root') {
|
||||
if (isset($rule['type']) && $rule['type'] !== '_root') {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -343,7 +343,7 @@ class BlueprintSchema
|
||||
*/
|
||||
protected function getPropertyRecursion($property, $nested)
|
||||
{
|
||||
if (!isset($property['type']) || empty($nested) || !is_array($nested)) {
|
||||
if (empty($nested) || !is_array($nested) || !isset($property['type'])) {
|
||||
return $property;
|
||||
}
|
||||
|
||||
@@ -443,9 +443,8 @@ class BlueprintSchema
|
||||
$val = isset($rules[$key]) ? $rules[$key] : null;
|
||||
$rule = is_string($val) ? $this->items[$val] : null;
|
||||
|
||||
if (!empty($rule['type']) && $rule['type'][0] === '_'
|
||||
|| (array_key_exists($key, $data1) && is_array($data1[$key]) && is_array($field) && is_array($val) && !isset($val['*']))
|
||||
) {
|
||||
if ((array_key_exists($key, $data1) && is_array($data1[$key]) && is_array($field) && is_array($val) && !isset($val['*']))
|
||||
|| (!empty($rule['type']) && $rule['type'][0] === '_')) {
|
||||
// Array has been defined in blueprints and is not a collection of items.
|
||||
$data1[$key] = $this->mergeArrays($data1[$key], $field, $val);
|
||||
} else {
|
||||
@@ -557,7 +556,7 @@ class BlueprintSchema
|
||||
protected function getFieldKey($key, $prefix, $parent)
|
||||
{
|
||||
// Set name from the array key.
|
||||
if ($key && $key[0] == '.') {
|
||||
if ($key && $key[0] === '.') {
|
||||
return ($parent ?: rtrim($prefix, '.')) . $key;
|
||||
}
|
||||
|
||||
@@ -668,7 +667,7 @@ class BlueprintSchema
|
||||
// Item has been defined in blueprints.
|
||||
} elseif (is_array($field) && is_array($val)) {
|
||||
// Array has been defined in blueprints.
|
||||
$array += $this->ExtraArray($field, $val, $prefix . $key . '.');
|
||||
$array += $this->extraArray($field, $val, $prefix . $key . '.');
|
||||
} else {
|
||||
// Undefined/extra item.
|
||||
$array[$prefix.$key] = $field;
|
||||
@@ -693,7 +692,7 @@ class BlueprintSchema
|
||||
$params = [];
|
||||
}
|
||||
|
||||
$list = preg_split('/::/', $function, 2);
|
||||
$list = explode('::', $function, 2);
|
||||
$f = array_pop($list);
|
||||
$o = array_pop($list);
|
||||
if (!$o) {
|
||||
@@ -708,7 +707,7 @@ class BlueprintSchema
|
||||
|
||||
// If function returns a value,
|
||||
if (isset($data)) {
|
||||
if (isset($field[$property]) && is_array($field[$property]) && is_array($data)) {
|
||||
if (is_array($data) && isset($field[$property]) && is_array($field[$property])) {
|
||||
// Combine field and @data-field together.
|
||||
$field[$property] += $data;
|
||||
} else {
|
||||
|
||||
+34
-4
@@ -1,3 +1,33 @@
|
||||
# v1.4.2
|
||||
## 08/08/2018
|
||||
|
||||
1. [](#new)
|
||||
* Added `UniformResourceLocator::clearCache()` to allow resource cache to be cleared
|
||||
* Added `$extends` parameter to `BlueprintForm::load()` to override `extends@`
|
||||
1. [](#improved)
|
||||
* Improved messages in `Stream` exceptions
|
||||
1. [](#bugfix)
|
||||
* Fixed bugs when using `mkdir()`, `rmdir()`, `rename()` or creating new files with URIs
|
||||
|
||||
# v1.4.1
|
||||
## 06/20/2018
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed a bug in blueprint extend and embed
|
||||
|
||||
# v1.4.0
|
||||
## 06/13/2018
|
||||
|
||||
1. [](#new)
|
||||
* `BlueprintForm`: Implemented support for multiple `import@`s and partial `import@`s (#17)
|
||||
1. [](#improved)
|
||||
* `YamlFile`: Added support for `@data` without quoting it (fixes issues with Symfony 3.4 if `compat=true`)
|
||||
* `YamlFile`: Added compatibility mode which falls back to Symfony YAML 2.8.38 if parsing with newer version fails
|
||||
* `YamlFile`: Make `compat` and `native` settings global, enable `native` setting by default
|
||||
* General code cleanup, some optimizations
|
||||
1. [](#bugfix)
|
||||
* `Session`: Removed broken request counter
|
||||
|
||||
# v1.3.9
|
||||
## 10/08/2017
|
||||
|
||||
@@ -41,7 +71,7 @@
|
||||
* Fixed `IniFile::content()` should not fail if file doesn't exist
|
||||
* Session: Protection against invalid session cookie name throwing exception
|
||||
* Session: Do not destroy session on CLI
|
||||
* BlueprintSchema: Fixed warning when field list is not what was expected
|
||||
* BlueprintSchema: Fixed warning when field list is not what was expected
|
||||
|
||||
# v1.3.3
|
||||
## 10/06/2016
|
||||
@@ -68,9 +98,9 @@
|
||||
* Add new function UniformResourceLocator::fillCache()
|
||||
1. [](#bugfix)
|
||||
* Fix collections support in BluprintSchema::extra()
|
||||
* Fix exception in stream wrapper when scheme is not defined in locator
|
||||
* Fix exception in stream wrapper when scheme is not defined in locator
|
||||
* Prevent UniformResourceLocator from resolving paths outside of defined scheme paths (#8)
|
||||
* Fix breaking YAML files which start with three dashes (#5)
|
||||
* Fix breaking YAML files which start with three dashes (#5)
|
||||
|
||||
# v1.3.0
|
||||
## 03/07/2016
|
||||
@@ -96,7 +126,7 @@
|
||||
## 10/24/2015
|
||||
|
||||
1. [](#new)
|
||||
* **Backwards compatibility break**: Blueprints class needs to be initialized with `init()` if blueprints contain `@data-*` fields
|
||||
* **Backwards compatibility break**: Blueprints class needs to be initialized with `init()` if blueprints contain `@data-*` fields
|
||||
* Renamed NestedArrayAccess::remove() into NestedArrayAccess::undef() to avoid name clashes
|
||||
|
||||
# v1.1.4
|
||||
|
||||
@@ -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 RocketTheme\Toolbox\Compat\Yaml\Exception;
|
||||
|
||||
/**
|
||||
* Exception interface for all exceptions thrown by the component.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
interface ExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?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 RocketTheme\Toolbox\Compat\Yaml\Exception;
|
||||
|
||||
/**
|
||||
* Exception class thrown when an error occurs during parsing.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class ParseException extends RuntimeException
|
||||
{
|
||||
private $parsedFile;
|
||||
private $parsedLine;
|
||||
private $snippet;
|
||||
private $rawMessage;
|
||||
|
||||
/**
|
||||
* @param string $message The error message
|
||||
* @param int $parsedLine The line where the error occurred
|
||||
* @param string|null $snippet The snippet of code near the problem
|
||||
* @param string|null $parsedFile The file name where the error occurred
|
||||
* @param \Exception|null $previous The previous exception
|
||||
*/
|
||||
public function __construct($message, $parsedLine = -1, $snippet = null, $parsedFile = null, \Exception $previous = null)
|
||||
{
|
||||
$this->parsedFile = $parsedFile;
|
||||
$this->parsedLine = $parsedLine;
|
||||
$this->snippet = $snippet;
|
||||
$this->rawMessage = $message;
|
||||
|
||||
$this->updateRepr();
|
||||
|
||||
parent::__construct($this->message, 0, $previous);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the snippet of code near the error.
|
||||
*
|
||||
* @return string The snippet of code
|
||||
*/
|
||||
public function getSnippet()
|
||||
{
|
||||
return $this->snippet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the snippet of code near the error.
|
||||
*
|
||||
* @param string $snippet The code snippet
|
||||
*/
|
||||
public function setSnippet($snippet)
|
||||
{
|
||||
$this->snippet = $snippet;
|
||||
|
||||
$this->updateRepr();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the filename where the error occurred.
|
||||
*
|
||||
* This method returns null if a string is parsed.
|
||||
*
|
||||
* @return string The filename
|
||||
*/
|
||||
public function getParsedFile()
|
||||
{
|
||||
return $this->parsedFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the filename where the error occurred.
|
||||
*
|
||||
* @param string $parsedFile The filename
|
||||
*/
|
||||
public function setParsedFile($parsedFile)
|
||||
{
|
||||
$this->parsedFile = $parsedFile;
|
||||
|
||||
$this->updateRepr();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the line where the error occurred.
|
||||
*
|
||||
* @return int The file line
|
||||
*/
|
||||
public function getParsedLine()
|
||||
{
|
||||
return $this->parsedLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the line where the error occurred.
|
||||
*
|
||||
* @param int $parsedLine The file line
|
||||
*/
|
||||
public function setParsedLine($parsedLine)
|
||||
{
|
||||
$this->parsedLine = $parsedLine;
|
||||
|
||||
$this->updateRepr();
|
||||
}
|
||||
|
||||
private function updateRepr()
|
||||
{
|
||||
$this->message = $this->rawMessage;
|
||||
|
||||
$dot = false;
|
||||
if ('.' === substr($this->message, -1)) {
|
||||
$this->message = substr($this->message, 0, -1);
|
||||
$dot = true;
|
||||
}
|
||||
|
||||
if (null !== $this->parsedFile) {
|
||||
if (\PHP_VERSION_ID >= 50400) {
|
||||
$jsonOptions = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
|
||||
} else {
|
||||
$jsonOptions = 0;
|
||||
}
|
||||
$this->message .= sprintf(' in %s', json_encode($this->parsedFile, $jsonOptions));
|
||||
}
|
||||
|
||||
if ($this->parsedLine >= 0) {
|
||||
$this->message .= sprintf(' at line %d', $this->parsedLine);
|
||||
}
|
||||
|
||||
if ($this->snippet) {
|
||||
$this->message .= sprintf(' (near "%s")', $this->snippet);
|
||||
}
|
||||
|
||||
if ($dot) {
|
||||
$this->message .= '.';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 RocketTheme\Toolbox\Compat\Yaml\Exception;
|
||||
|
||||
/**
|
||||
* Exception class thrown when an error occurs during parsing.
|
||||
*
|
||||
* @author Romain Neutron <imprec@gmail.com>
|
||||
*/
|
||||
class RuntimeException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,501 @@
|
||||
<?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 RocketTheme\Toolbox\Compat\Yaml;
|
||||
|
||||
use RocketTheme\Toolbox\Compat\Yaml\Exception\ParseException;
|
||||
|
||||
/**
|
||||
* Inline implements a YAML parser/dumper for the YAML inline syntax.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Inline
|
||||
{
|
||||
const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')';
|
||||
|
||||
private static $exceptionOnInvalidType = false;
|
||||
private static $objectSupport = false;
|
||||
private static $objectForMap = false;
|
||||
|
||||
/**
|
||||
* Converts a YAML string to a PHP value.
|
||||
*
|
||||
* @param string $value A YAML string
|
||||
* @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
|
||||
* @param bool $objectSupport True if object support is enabled, false otherwise
|
||||
* @param bool $objectForMap True if maps should return a stdClass instead of array()
|
||||
* @param array $references Mapping of variable names to values
|
||||
*
|
||||
* @return mixed A PHP value
|
||||
*
|
||||
* @throws ParseException
|
||||
*/
|
||||
public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false, $references = array())
|
||||
{
|
||||
self::$exceptionOnInvalidType = $exceptionOnInvalidType;
|
||||
self::$objectSupport = $objectSupport;
|
||||
self::$objectForMap = $objectForMap;
|
||||
|
||||
$value = trim($value);
|
||||
|
||||
if ('' === $value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) {
|
||||
$mbEncoding = mb_internal_encoding();
|
||||
mb_internal_encoding('ASCII');
|
||||
}
|
||||
|
||||
$i = 0;
|
||||
switch ($value[0]) {
|
||||
case '[':
|
||||
$result = self::parseSequence($value, $i, $references);
|
||||
++$i;
|
||||
break;
|
||||
case '{':
|
||||
$result = self::parseMapping($value, $i, $references);
|
||||
++$i;
|
||||
break;
|
||||
default:
|
||||
$result = self::parseScalar($value, null, array('"', "'"), $i, true, $references);
|
||||
}
|
||||
|
||||
// some comments are allowed at the end
|
||||
if (preg_replace('/\s+#.*$/A', '', substr($value, $i))) {
|
||||
throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)));
|
||||
}
|
||||
|
||||
if (isset($mbEncoding)) {
|
||||
mb_internal_encoding($mbEncoding);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if given array is hash or just normal indexed array.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param array $value The PHP array to check
|
||||
*
|
||||
* @return bool true if value is hash array, false otherwise
|
||||
*/
|
||||
public static function isHash(array $value)
|
||||
{
|
||||
$expectedKey = 0;
|
||||
|
||||
foreach ($value as $key => $val) {
|
||||
if ($key !== $expectedKey++) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a YAML scalar.
|
||||
*
|
||||
* @param string $scalar
|
||||
* @param string[] $delimiters
|
||||
* @param string[] $stringDelimiters
|
||||
* @param int &$i
|
||||
* @param bool $evaluate
|
||||
* @param array $references
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws ParseException When malformed inline YAML string is parsed
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true, $references = array())
|
||||
{
|
||||
if (in_array($scalar[$i], $stringDelimiters)) {
|
||||
// quoted scalar
|
||||
$output = self::parseQuotedScalar($scalar, $i);
|
||||
|
||||
if (null !== $delimiters) {
|
||||
$tmp = ltrim(substr($scalar, $i), ' ');
|
||||
if (!in_array($tmp[0], $delimiters)) {
|
||||
throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// "normal" string
|
||||
if (!$delimiters) {
|
||||
$output = substr($scalar, $i);
|
||||
$i += strlen($output);
|
||||
|
||||
// remove comments
|
||||
if (Parser::preg_match('/[ \t]+#/', $output, $match, PREG_OFFSET_CAPTURE)) {
|
||||
$output = substr($output, 0, $match[0][1]);
|
||||
}
|
||||
} elseif (Parser::preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) {
|
||||
$output = $match[1];
|
||||
$i += strlen($output);
|
||||
} else {
|
||||
throw new ParseException(sprintf('Malformed inline YAML string: %s.', $scalar));
|
||||
}
|
||||
|
||||
// a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >)
|
||||
if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0])) {
|
||||
@trigger_error(sprintf('Not quoting the scalar "%s" starting with "%s" is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.', $output, $output[0]), E_USER_DEPRECATED);
|
||||
|
||||
// to be thrown in 3.0
|
||||
// throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0]));
|
||||
}
|
||||
|
||||
if ($evaluate) {
|
||||
$output = self::evaluateScalar($output, $references);
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a YAML quoted scalar.
|
||||
*
|
||||
* @param string $scalar
|
||||
* @param int &$i
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws ParseException When malformed inline YAML string is parsed
|
||||
*/
|
||||
private static function parseQuotedScalar($scalar, &$i)
|
||||
{
|
||||
if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) {
|
||||
throw new ParseException(sprintf('Malformed inline YAML string: %s.', substr($scalar, $i)));
|
||||
}
|
||||
|
||||
$output = substr($match[0], 1, strlen($match[0]) - 2);
|
||||
|
||||
$unescaper = new Unescaper();
|
||||
if ('"' == $scalar[$i]) {
|
||||
$output = $unescaper->unescapeDoubleQuotedString($output);
|
||||
} else {
|
||||
$output = $unescaper->unescapeSingleQuotedString($output);
|
||||
}
|
||||
|
||||
$i += strlen($match[0]);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a YAML sequence.
|
||||
*
|
||||
* @param string $sequence
|
||||
* @param int &$i
|
||||
* @param array $references
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws ParseException When malformed inline YAML string is parsed
|
||||
*/
|
||||
private static function parseSequence($sequence, &$i = 0, $references = array())
|
||||
{
|
||||
$output = array();
|
||||
$len = strlen($sequence);
|
||||
++$i;
|
||||
|
||||
// [foo, bar, ...]
|
||||
while ($i < $len) {
|
||||
switch ($sequence[$i]) {
|
||||
case '[':
|
||||
// nested sequence
|
||||
$output[] = self::parseSequence($sequence, $i, $references);
|
||||
break;
|
||||
case '{':
|
||||
// nested mapping
|
||||
$output[] = self::parseMapping($sequence, $i, $references);
|
||||
break;
|
||||
case ']':
|
||||
return $output;
|
||||
case ',':
|
||||
case ' ':
|
||||
break;
|
||||
default:
|
||||
$isQuoted = in_array($sequence[$i], array('"', "'"));
|
||||
$value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i, true, $references);
|
||||
|
||||
// the value can be an array if a reference has been resolved to an array var
|
||||
if (!is_array($value) && !$isQuoted && false !== strpos($value, ': ')) {
|
||||
// embedded mapping?
|
||||
try {
|
||||
$pos = 0;
|
||||
$value = self::parseMapping('{'.$value.'}', $pos, $references);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
// no, it's not
|
||||
}
|
||||
}
|
||||
|
||||
$output[] = $value;
|
||||
|
||||
--$i;
|
||||
}
|
||||
|
||||
++$i;
|
||||
}
|
||||
|
||||
throw new ParseException(sprintf('Malformed inline YAML string: %s.', $sequence));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a YAML mapping.
|
||||
*
|
||||
* @param string $mapping
|
||||
* @param int &$i
|
||||
* @param array $references
|
||||
*
|
||||
* @return array|\stdClass
|
||||
*
|
||||
* @throws ParseException When malformed inline YAML string is parsed
|
||||
*/
|
||||
private static function parseMapping($mapping, &$i = 0, $references = array())
|
||||
{
|
||||
$output = array();
|
||||
$len = strlen($mapping);
|
||||
++$i;
|
||||
$allowOverwrite = false;
|
||||
|
||||
// {foo: bar, bar:foo, ...}
|
||||
while ($i < $len) {
|
||||
switch ($mapping[$i]) {
|
||||
case ' ':
|
||||
case ',':
|
||||
++$i;
|
||||
continue 2;
|
||||
case '}':
|
||||
if (self::$objectForMap) {
|
||||
return (object) $output;
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
// key
|
||||
$key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false);
|
||||
|
||||
if ('<<' === $key) {
|
||||
$allowOverwrite = true;
|
||||
}
|
||||
|
||||
// value
|
||||
$done = false;
|
||||
|
||||
while ($i < $len) {
|
||||
switch ($mapping[$i]) {
|
||||
case '[':
|
||||
// nested sequence
|
||||
$value = self::parseSequence($mapping, $i, $references);
|
||||
// Spec: Keys MUST be unique; first one wins.
|
||||
// Parser cannot abort this mapping earlier, since lines
|
||||
// are processed sequentially.
|
||||
// But overwriting is allowed when a merge node is used in current block.
|
||||
if ('<<' === $key) {
|
||||
foreach ($value as $parsedValue) {
|
||||
$output += $parsedValue;
|
||||
}
|
||||
} elseif ($allowOverwrite || !isset($output[$key])) {
|
||||
$output[$key] = $value;
|
||||
}
|
||||
$done = true;
|
||||
break;
|
||||
case '{':
|
||||
// nested mapping
|
||||
$value = self::parseMapping($mapping, $i, $references);
|
||||
// Spec: Keys MUST be unique; first one wins.
|
||||
// Parser cannot abort this mapping earlier, since lines
|
||||
// are processed sequentially.
|
||||
// But overwriting is allowed when a merge node is used in current block.
|
||||
if ('<<' === $key) {
|
||||
$output += $value;
|
||||
} elseif ($allowOverwrite || !isset($output[$key])) {
|
||||
$output[$key] = $value;
|
||||
}
|
||||
$done = true;
|
||||
break;
|
||||
case ':':
|
||||
case ' ':
|
||||
break;
|
||||
default:
|
||||
$value = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i, true, $references);
|
||||
// Spec: Keys MUST be unique; first one wins.
|
||||
// Parser cannot abort this mapping earlier, since lines
|
||||
// are processed sequentially.
|
||||
// But overwriting is allowed when a merge node is used in current block.
|
||||
if ('<<' === $key) {
|
||||
$output += $value;
|
||||
} elseif ($allowOverwrite || !isset($output[$key])) {
|
||||
$output[$key] = $value;
|
||||
}
|
||||
$done = true;
|
||||
--$i;
|
||||
}
|
||||
|
||||
++$i;
|
||||
|
||||
if ($done) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new ParseException(sprintf('Malformed inline YAML string: %s.', $mapping));
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates scalars and replaces magic values.
|
||||
*
|
||||
* @param string $scalar
|
||||
* @param array $references
|
||||
*
|
||||
* @return mixed The evaluated YAML string
|
||||
*
|
||||
* @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved
|
||||
*/
|
||||
private static function evaluateScalar($scalar, $references = array())
|
||||
{
|
||||
$scalar = trim($scalar);
|
||||
$scalarLower = strtolower($scalar);
|
||||
|
||||
if (0 === strpos($scalar, '*')) {
|
||||
if (false !== $pos = strpos($scalar, '#')) {
|
||||
$value = substr($scalar, 1, $pos - 2);
|
||||
} else {
|
||||
$value = substr($scalar, 1);
|
||||
}
|
||||
|
||||
// an unquoted *
|
||||
if (false === $value || '' === $value) {
|
||||
throw new ParseException('A reference must contain at least one character.');
|
||||
}
|
||||
|
||||
if (!array_key_exists($value, $references)) {
|
||||
throw new ParseException(sprintf('Reference "%s" does not exist.', $value));
|
||||
}
|
||||
|
||||
return $references[$value];
|
||||
}
|
||||
|
||||
switch (true) {
|
||||
case 'null' === $scalarLower:
|
||||
case '' === $scalar:
|
||||
case '~' === $scalar:
|
||||
return;
|
||||
case 'true' === $scalarLower:
|
||||
return true;
|
||||
case 'false' === $scalarLower:
|
||||
return false;
|
||||
// Optimise for returning strings.
|
||||
case '+' === $scalar[0] || '-' === $scalar[0] || '.' === $scalar[0] || '!' === $scalar[0] || is_numeric($scalar[0]):
|
||||
switch (true) {
|
||||
case 0 === strpos($scalar, '!str'):
|
||||
return (string) substr($scalar, 5);
|
||||
case 0 === strpos($scalar, '! '):
|
||||
return (int) self::parseScalar(substr($scalar, 2));
|
||||
case 0 === strpos($scalar, '!php/object:'):
|
||||
if (self::$objectSupport) {
|
||||
return unserialize(substr($scalar, 12));
|
||||
}
|
||||
|
||||
if (self::$exceptionOnInvalidType) {
|
||||
throw new ParseException('Object support when parsing a YAML file has been disabled.');
|
||||
}
|
||||
|
||||
return;
|
||||
case 0 === strpos($scalar, '!!php/object:'):
|
||||
if (self::$objectSupport) {
|
||||
return unserialize(substr($scalar, 13));
|
||||
}
|
||||
|
||||
if (self::$exceptionOnInvalidType) {
|
||||
throw new ParseException('Object support when parsing a YAML file has been disabled.');
|
||||
}
|
||||
|
||||
return;
|
||||
case 0 === strpos($scalar, '!!float '):
|
||||
return (float) substr($scalar, 8);
|
||||
case ctype_digit($scalar):
|
||||
$raw = $scalar;
|
||||
$cast = (int) $scalar;
|
||||
|
||||
return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw);
|
||||
case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)):
|
||||
$raw = $scalar;
|
||||
$cast = (int) $scalar;
|
||||
|
||||
return '0' == $scalar[1] ? octdec($scalar) : (((string) $raw === (string) $cast) ? $cast : $raw);
|
||||
case is_numeric($scalar):
|
||||
case Parser::preg_match(self::getHexRegex(), $scalar):
|
||||
return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar;
|
||||
case '.inf' === $scalarLower:
|
||||
case '.nan' === $scalarLower:
|
||||
return -log(0);
|
||||
case '-.inf' === $scalarLower:
|
||||
return log(0);
|
||||
case Parser::preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar):
|
||||
return (float) str_replace(',', '', $scalar);
|
||||
case Parser::preg_match(self::getTimestampRegex(), $scalar):
|
||||
$timeZone = date_default_timezone_get();
|
||||
date_default_timezone_set('UTC');
|
||||
$time = strtotime($scalar);
|
||||
date_default_timezone_set($timeZone);
|
||||
|
||||
return $time;
|
||||
}
|
||||
// no break
|
||||
default:
|
||||
return (string) $scalar;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a regex that matches a YAML date.
|
||||
*
|
||||
* @return string The regular expression
|
||||
*
|
||||
* @see http://www.yaml.org/spec/1.2/spec.html#id2761573
|
||||
*/
|
||||
private static function getTimestampRegex()
|
||||
{
|
||||
return <<<EOF
|
||||
~^
|
||||
(?P<year>[0-9][0-9][0-9][0-9])
|
||||
-(?P<month>[0-9][0-9]?)
|
||||
-(?P<day>[0-9][0-9]?)
|
||||
(?:(?:[Tt]|[ \t]+)
|
||||
(?P<hour>[0-9][0-9]?)
|
||||
:(?P<minute>[0-9][0-9])
|
||||
:(?P<second>[0-9][0-9])
|
||||
(?:\.(?P<fraction>[0-9]*))?
|
||||
(?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
|
||||
(?::(?P<tz_minute>[0-9][0-9]))?))?)?
|
||||
$~x
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a regex that matches a YAML number in hexadecimal notation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function getHexRegex()
|
||||
{
|
||||
return '~^0x[0-9a-f]++$~i';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,852 @@
|
||||
<?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 RocketTheme\Toolbox\Compat\Yaml;
|
||||
|
||||
use RocketTheme\Toolbox\Compat\Yaml\Exception\ParseException;
|
||||
|
||||
/**
|
||||
* Parser parses YAML strings to convert them to PHP arrays.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Parser
|
||||
{
|
||||
const BLOCK_SCALAR_HEADER_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?';
|
||||
// BC - wrongly named
|
||||
const FOLDED_SCALAR_PATTERN = self::BLOCK_SCALAR_HEADER_PATTERN;
|
||||
|
||||
private $offset = 0;
|
||||
private $totalNumberOfLines;
|
||||
private $lines = array();
|
||||
private $currentLineNb = -1;
|
||||
private $currentLine = '';
|
||||
private $refs = array();
|
||||
private $skippedLineNumbers = array();
|
||||
private $locallySkippedLineNumbers = array();
|
||||
|
||||
/**
|
||||
* @param int $offset The offset of YAML document (used for line numbers in error messages)
|
||||
* @param int|null $totalNumberOfLines The overall number of lines being parsed
|
||||
* @param int[] $skippedLineNumbers Number of comment lines that have been skipped by the parser
|
||||
*/
|
||||
public function __construct($offset = 0, $totalNumberOfLines = null, array $skippedLineNumbers = array())
|
||||
{
|
||||
$this->offset = $offset;
|
||||
$this->totalNumberOfLines = $totalNumberOfLines;
|
||||
$this->skippedLineNumbers = $skippedLineNumbers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a YAML string to a PHP value.
|
||||
*
|
||||
* @param string $value A YAML string
|
||||
* @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
|
||||
* @param bool $objectSupport True if object support is enabled, false otherwise
|
||||
* @param bool $objectForMap True if maps should return a stdClass instead of array()
|
||||
*
|
||||
* @return mixed A PHP value
|
||||
*
|
||||
* @throws ParseException If the YAML is not valid
|
||||
*/
|
||||
public function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false)
|
||||
{
|
||||
if (false === preg_match('//u', $value)) {
|
||||
throw new ParseException('The YAML value does not appear to be valid UTF-8.');
|
||||
}
|
||||
|
||||
$this->refs = array();
|
||||
|
||||
$mbEncoding = null;
|
||||
$e = null;
|
||||
$data = null;
|
||||
|
||||
if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) {
|
||||
$mbEncoding = mb_internal_encoding();
|
||||
mb_internal_encoding('UTF-8');
|
||||
}
|
||||
|
||||
try {
|
||||
$data = $this->doParse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
|
||||
if (null !== $mbEncoding) {
|
||||
mb_internal_encoding($mbEncoding);
|
||||
}
|
||||
|
||||
$this->lines = array();
|
||||
$this->currentLine = '';
|
||||
$this->refs = array();
|
||||
$this->skippedLineNumbers = array();
|
||||
$this->locallySkippedLineNumbers = array();
|
||||
|
||||
if (null !== $e) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function doParse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false)
|
||||
{
|
||||
$this->currentLineNb = -1;
|
||||
$this->currentLine = '';
|
||||
$value = $this->cleanup($value);
|
||||
$this->lines = explode("\n", $value);
|
||||
$this->locallySkippedLineNumbers = array();
|
||||
|
||||
if (null === $this->totalNumberOfLines) {
|
||||
$this->totalNumberOfLines = count($this->lines);
|
||||
}
|
||||
|
||||
$data = array();
|
||||
$context = null;
|
||||
$allowOverwrite = false;
|
||||
|
||||
while ($this->moveToNextLine()) {
|
||||
if ($this->isCurrentLineEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// tab?
|
||||
if ("\t" === $this->currentLine[0]) {
|
||||
throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
|
||||
}
|
||||
|
||||
$isRef = $mergeNode = false;
|
||||
if (self::preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+))?$#u', rtrim($this->currentLine), $values)) {
|
||||
if ($context && 'mapping' == $context) {
|
||||
throw new ParseException('You cannot define a sequence item when in a mapping', $this->getRealCurrentLineNb() + 1, $this->currentLine);
|
||||
}
|
||||
$context = 'sequence';
|
||||
|
||||
if (isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
|
||||
$isRef = $matches['ref'];
|
||||
$values['value'] = $matches['value'];
|
||||
}
|
||||
|
||||
// array
|
||||
if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
|
||||
$data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, $objectSupport, $objectForMap);
|
||||
} else {
|
||||
if (isset($values['leadspaces'])
|
||||
&& self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+))?$#u', rtrim($values['value']), $matches)
|
||||
) {
|
||||
// this is a compact notation element, add to next block and parse
|
||||
$block = $values['value'];
|
||||
if ($this->isNextLineIndented()) {
|
||||
$block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + strlen($values['leadspaces']) + 1);
|
||||
}
|
||||
|
||||
$data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $exceptionOnInvalidType, $objectSupport, $objectForMap);
|
||||
} else {
|
||||
$data[] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap, $context);
|
||||
}
|
||||
}
|
||||
if ($isRef) {
|
||||
$this->refs[$isRef] = end($data);
|
||||
}
|
||||
} elseif (
|
||||
self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\[\{].*?) *\:(\s+(?P<value>.+))?$#u', rtrim($this->currentLine), $values)
|
||||
&& (false === strpos($values['key'], ' #') || in_array($values['key'][0], array('"', "'")))
|
||||
) {
|
||||
if ($context && 'sequence' == $context) {
|
||||
throw new ParseException('You cannot define a mapping item when in a sequence', $this->currentLineNb + 1, $this->currentLine);
|
||||
}
|
||||
$context = 'mapping';
|
||||
|
||||
// force correct settings
|
||||
Inline::parse(null, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs);
|
||||
try {
|
||||
$key = Inline::parseScalar($values['key']);
|
||||
} catch (ParseException $e) {
|
||||
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
|
||||
$e->setSnippet($this->currentLine);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// Convert float keys to strings, to avoid being converted to integers by PHP
|
||||
if (is_float($key)) {
|
||||
$key = (string) $key;
|
||||
}
|
||||
|
||||
if ('<<' === $key && (!isset($values['value']) || !self::preg_match('#^&(?P<ref>[^ ]+)#u', $values['value'], $refMatches))) {
|
||||
$mergeNode = true;
|
||||
$allowOverwrite = true;
|
||||
if (isset($values['value']) && 0 === strpos($values['value'], '*')) {
|
||||
$refName = substr($values['value'], 1);
|
||||
if (!array_key_exists($refName, $this->refs)) {
|
||||
throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine);
|
||||
}
|
||||
|
||||
$refValue = $this->refs[$refName];
|
||||
|
||||
if (!is_array($refValue)) {
|
||||
throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
|
||||
}
|
||||
|
||||
$data += $refValue; // array union
|
||||
} else {
|
||||
if (isset($values['value']) && '' !== $values['value']) {
|
||||
$value = $values['value'];
|
||||
} else {
|
||||
$value = $this->getNextEmbedBlock();
|
||||
}
|
||||
$parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $exceptionOnInvalidType, $objectSupport, $objectForMap);
|
||||
|
||||
if (!is_array($parsed)) {
|
||||
throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
|
||||
}
|
||||
|
||||
if (isset($parsed[0])) {
|
||||
// If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes
|
||||
// and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier
|
||||
// in the sequence override keys specified in later mapping nodes.
|
||||
foreach ($parsed as $parsedItem) {
|
||||
if (!is_array($parsedItem)) {
|
||||
throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem);
|
||||
}
|
||||
|
||||
$data += $parsedItem; // array union
|
||||
}
|
||||
} else {
|
||||
// If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the
|
||||
// current mapping, unless the key already exists in it.
|
||||
$data += $parsed; // array union
|
||||
}
|
||||
}
|
||||
} elseif ('<<' !== $key && isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
|
||||
$isRef = $matches['ref'];
|
||||
$values['value'] = $matches['value'];
|
||||
}
|
||||
|
||||
if ($mergeNode) {
|
||||
// Merge keys
|
||||
} elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#') || '<<' === $key) {
|
||||
// hash
|
||||
// if next line is less indented or equal, then it means that the current value is null
|
||||
if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) {
|
||||
// Spec: Keys MUST be unique; first one wins.
|
||||
// But overwriting is allowed when a merge node is used in current block.
|
||||
if ($allowOverwrite || !isset($data[$key])) {
|
||||
$data[$key] = null;
|
||||
}
|
||||
} else {
|
||||
$value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport, $objectForMap);
|
||||
|
||||
if ('<<' === $key) {
|
||||
$this->refs[$refMatches['ref']] = $value;
|
||||
$data += $value;
|
||||
} elseif ($allowOverwrite || !isset($data[$key])) {
|
||||
// Spec: Keys MUST be unique; first one wins.
|
||||
// But overwriting is allowed when a merge node is used in current block.
|
||||
$data[$key] = $value;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$value = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap, $context);
|
||||
// Spec: Keys MUST be unique; first one wins.
|
||||
// But overwriting is allowed when a merge node is used in current block.
|
||||
if ($allowOverwrite || !isset($data[$key])) {
|
||||
$data[$key] = $value;
|
||||
}
|
||||
}
|
||||
if ($isRef) {
|
||||
$this->refs[$isRef] = $data[$key];
|
||||
}
|
||||
} else {
|
||||
// multiple documents are not supported
|
||||
if ('---' === $this->currentLine) {
|
||||
throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine);
|
||||
}
|
||||
|
||||
// 1-liner optionally followed by newline(s)
|
||||
if (is_string($value) && $this->lines[0] === trim($value)) {
|
||||
try {
|
||||
$value = Inline::parse($this->lines[0], $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs);
|
||||
} catch (ParseException $e) {
|
||||
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
|
||||
$e->setSnippet($this->currentLine);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
|
||||
}
|
||||
}
|
||||
|
||||
if ($objectForMap && !is_object($data) && 'mapping' === $context) {
|
||||
$object = new \stdClass();
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
$object->$key = $value;
|
||||
}
|
||||
|
||||
$data = $object;
|
||||
}
|
||||
|
||||
return empty($data) ? null : $data;
|
||||
}
|
||||
|
||||
private function parseBlock($offset, $yaml, $exceptionOnInvalidType, $objectSupport, $objectForMap)
|
||||
{
|
||||
$skippedLineNumbers = $this->skippedLineNumbers;
|
||||
|
||||
foreach ($this->locallySkippedLineNumbers as $lineNumber) {
|
||||
if ($lineNumber < $offset) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$skippedLineNumbers[] = $lineNumber;
|
||||
}
|
||||
|
||||
$parser = new self($offset, $this->totalNumberOfLines, $skippedLineNumbers);
|
||||
$parser->refs = &$this->refs;
|
||||
|
||||
return $parser->doParse($yaml, $exceptionOnInvalidType, $objectSupport, $objectForMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current line number (takes the offset into account).
|
||||
*
|
||||
* @return int The current line number
|
||||
*/
|
||||
private function getRealCurrentLineNb()
|
||||
{
|
||||
$realCurrentLineNumber = $this->currentLineNb + $this->offset;
|
||||
|
||||
foreach ($this->skippedLineNumbers as $skippedLineNumber) {
|
||||
if ($skippedLineNumber > $realCurrentLineNumber) {
|
||||
break;
|
||||
}
|
||||
|
||||
++$realCurrentLineNumber;
|
||||
}
|
||||
|
||||
return $realCurrentLineNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current line indentation.
|
||||
*
|
||||
* @return int The current line indentation
|
||||
*/
|
||||
private function getCurrentLineIndentation()
|
||||
{
|
||||
return strlen($this->currentLine) - strlen(ltrim($this->currentLine, ' '));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next embed block of YAML.
|
||||
*
|
||||
* @param int $indentation The indent level at which the block is to be read, or null for default
|
||||
* @param bool $inSequence True if the enclosing data structure is a sequence
|
||||
*
|
||||
* @return string A YAML string
|
||||
*
|
||||
* @throws ParseException When indentation problem are detected
|
||||
*/
|
||||
private function getNextEmbedBlock($indentation = null, $inSequence = false)
|
||||
{
|
||||
$oldLineIndentation = $this->getCurrentLineIndentation();
|
||||
$blockScalarIndentations = array();
|
||||
|
||||
if ($this->isBlockScalarHeader()) {
|
||||
$blockScalarIndentations[] = $this->getCurrentLineIndentation();
|
||||
}
|
||||
|
||||
if (!$this->moveToNextLine()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null === $indentation) {
|
||||
$newIndent = $this->getCurrentLineIndentation();
|
||||
|
||||
$unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem();
|
||||
|
||||
if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) {
|
||||
throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
|
||||
}
|
||||
} else {
|
||||
$newIndent = $indentation;
|
||||
}
|
||||
|
||||
$data = array();
|
||||
if ($this->getCurrentLineIndentation() >= $newIndent) {
|
||||
$data[] = substr($this->currentLine, $newIndent);
|
||||
} else {
|
||||
$this->moveToPreviousLine();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) {
|
||||
// the previous line contained a dash but no item content, this line is a sequence item with the same indentation
|
||||
// and therefore no nested list or mapping
|
||||
$this->moveToPreviousLine();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$isItUnindentedCollection = $this->isStringUnIndentedCollectionItem();
|
||||
|
||||
if (empty($blockScalarIndentations) && $this->isBlockScalarHeader()) {
|
||||
$blockScalarIndentations[] = $this->getCurrentLineIndentation();
|
||||
}
|
||||
|
||||
$previousLineIndentation = $this->getCurrentLineIndentation();
|
||||
|
||||
while ($this->moveToNextLine()) {
|
||||
$indent = $this->getCurrentLineIndentation();
|
||||
|
||||
// terminate all block scalars that are more indented than the current line
|
||||
if (!empty($blockScalarIndentations) && $indent < $previousLineIndentation && '' !== trim($this->currentLine)) {
|
||||
foreach ($blockScalarIndentations as $key => $blockScalarIndentation) {
|
||||
if ($blockScalarIndentation >= $this->getCurrentLineIndentation()) {
|
||||
unset($blockScalarIndentations[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($blockScalarIndentations) && !$this->isCurrentLineComment() && $this->isBlockScalarHeader()) {
|
||||
$blockScalarIndentations[] = $this->getCurrentLineIndentation();
|
||||
}
|
||||
|
||||
$previousLineIndentation = $indent;
|
||||
|
||||
if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) {
|
||||
$this->moveToPreviousLine();
|
||||
break;
|
||||
}
|
||||
|
||||
if ($this->isCurrentLineBlank()) {
|
||||
$data[] = substr($this->currentLine, $newIndent);
|
||||
continue;
|
||||
}
|
||||
|
||||
// we ignore "comment" lines only when we are not inside a scalar block
|
||||
if (empty($blockScalarIndentations) && $this->isCurrentLineComment()) {
|
||||
// remember ignored comment lines (they are used later in nested
|
||||
// parser calls to determine real line numbers)
|
||||
//
|
||||
// CAUTION: beware to not populate the global property here as it
|
||||
// will otherwise influence the getRealCurrentLineNb() call here
|
||||
// for consecutive comment lines and subsequent embedded blocks
|
||||
$this->locallySkippedLineNumbers[] = $this->getRealCurrentLineNb();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($indent >= $newIndent) {
|
||||
$data[] = substr($this->currentLine, $newIndent);
|
||||
} elseif (0 == $indent) {
|
||||
$this->moveToPreviousLine();
|
||||
|
||||
break;
|
||||
} else {
|
||||
throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
|
||||
}
|
||||
}
|
||||
|
||||
return implode("\n", $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the parser to the next line.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function moveToNextLine()
|
||||
{
|
||||
if ($this->currentLineNb >= count($this->lines) - 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->currentLine = $this->lines[++$this->currentLineNb];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the parser to the previous line.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function moveToPreviousLine()
|
||||
{
|
||||
if ($this->currentLineNb < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->currentLine = $this->lines[--$this->currentLineNb];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a YAML value.
|
||||
*
|
||||
* @param string $value A YAML value
|
||||
* @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise
|
||||
* @param bool $objectSupport True if object support is enabled, false otherwise
|
||||
* @param bool $objectForMap True if maps should return a stdClass instead of array()
|
||||
* @param string $context The parser context (either sequence or mapping)
|
||||
*
|
||||
* @return mixed A PHP value
|
||||
*
|
||||
* @throws ParseException When reference does not exist
|
||||
*/
|
||||
private function parseValue($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $context)
|
||||
{
|
||||
if (0 === strpos($value, '*')) {
|
||||
if (false !== $pos = strpos($value, '#')) {
|
||||
$value = substr($value, 1, $pos - 2);
|
||||
} else {
|
||||
$value = substr($value, 1);
|
||||
}
|
||||
|
||||
if (!array_key_exists($value, $this->refs)) {
|
||||
throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine);
|
||||
}
|
||||
|
||||
return $this->refs[$value];
|
||||
}
|
||||
|
||||
if (self::preg_match('/^'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) {
|
||||
$modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : '';
|
||||
|
||||
return $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), (int) abs($modifiers));
|
||||
}
|
||||
|
||||
try {
|
||||
$parsedValue = Inline::parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs);
|
||||
|
||||
if ('mapping' === $context && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) {
|
||||
@trigger_error(sprintf('Using a colon in the unquoted mapping value "%s" in line %d is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.', $value, $this->getRealCurrentLineNb() + 1), E_USER_DEPRECATED);
|
||||
|
||||
// to be thrown in 3.0
|
||||
// throw new ParseException('A colon cannot be used in an unquoted mapping value.');
|
||||
}
|
||||
|
||||
return $parsedValue;
|
||||
} catch (ParseException $e) {
|
||||
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
|
||||
$e->setSnippet($this->currentLine);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a block scalar.
|
||||
*
|
||||
* @param string $style The style indicator that was used to begin this block scalar (| or >)
|
||||
* @param string $chomping The chomping indicator that was used to begin this block scalar (+ or -)
|
||||
* @param int $indentation The indentation indicator that was used to begin this block scalar
|
||||
*
|
||||
* @return string The text value
|
||||
*/
|
||||
private function parseBlockScalar($style, $chomping = '', $indentation = 0)
|
||||
{
|
||||
$notEOF = $this->moveToNextLine();
|
||||
if (!$notEOF) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$isCurrentLineBlank = $this->isCurrentLineBlank();
|
||||
$blockLines = array();
|
||||
|
||||
// leading blank lines are consumed before determining indentation
|
||||
while ($notEOF && $isCurrentLineBlank) {
|
||||
// newline only if not EOF
|
||||
if ($notEOF = $this->moveToNextLine()) {
|
||||
$blockLines[] = '';
|
||||
$isCurrentLineBlank = $this->isCurrentLineBlank();
|
||||
}
|
||||
}
|
||||
|
||||
// determine indentation if not specified
|
||||
if (0 === $indentation) {
|
||||
if (self::preg_match('/^ +/', $this->currentLine, $matches)) {
|
||||
$indentation = strlen($matches[0]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($indentation > 0) {
|
||||
$pattern = sprintf('/^ {%d}(.*)$/', $indentation);
|
||||
|
||||
while (
|
||||
$notEOF && (
|
||||
$isCurrentLineBlank ||
|
||||
self::preg_match($pattern, $this->currentLine, $matches)
|
||||
)
|
||||
) {
|
||||
if ($isCurrentLineBlank && strlen($this->currentLine) > $indentation) {
|
||||
$blockLines[] = substr($this->currentLine, $indentation);
|
||||
} elseif ($isCurrentLineBlank) {
|
||||
$blockLines[] = '';
|
||||
} else {
|
||||
$blockLines[] = $matches[1];
|
||||
}
|
||||
|
||||
// newline only if not EOF
|
||||
if ($notEOF = $this->moveToNextLine()) {
|
||||
$isCurrentLineBlank = $this->isCurrentLineBlank();
|
||||
}
|
||||
}
|
||||
} elseif ($notEOF) {
|
||||
$blockLines[] = '';
|
||||
}
|
||||
|
||||
if ($notEOF) {
|
||||
$blockLines[] = '';
|
||||
$this->moveToPreviousLine();
|
||||
} elseif (!$notEOF && !$this->isCurrentLineLastLineInDocument()) {
|
||||
$blockLines[] = '';
|
||||
}
|
||||
|
||||
// folded style
|
||||
if ('>' === $style) {
|
||||
$text = '';
|
||||
$previousLineIndented = false;
|
||||
$previousLineBlank = false;
|
||||
|
||||
for ($i = 0, $blockLinesCount = count($blockLines); $i < $blockLinesCount; ++$i) {
|
||||
if ('' === $blockLines[$i]) {
|
||||
$text .= "\n";
|
||||
$previousLineIndented = false;
|
||||
$previousLineBlank = true;
|
||||
} elseif (' ' === $blockLines[$i][0]) {
|
||||
$text .= "\n".$blockLines[$i];
|
||||
$previousLineIndented = true;
|
||||
$previousLineBlank = false;
|
||||
} elseif ($previousLineIndented) {
|
||||
$text .= "\n".$blockLines[$i];
|
||||
$previousLineIndented = false;
|
||||
$previousLineBlank = false;
|
||||
} elseif ($previousLineBlank || 0 === $i) {
|
||||
$text .= $blockLines[$i];
|
||||
$previousLineIndented = false;
|
||||
$previousLineBlank = false;
|
||||
} else {
|
||||
$text .= ' '.$blockLines[$i];
|
||||
$previousLineIndented = false;
|
||||
$previousLineBlank = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$text = implode("\n", $blockLines);
|
||||
}
|
||||
|
||||
// deal with trailing newlines
|
||||
if ('' === $chomping) {
|
||||
$text = preg_replace('/\n+$/', "\n", $text);
|
||||
} elseif ('-' === $chomping) {
|
||||
$text = preg_replace('/\n+$/', '', $text);
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the next line is indented.
|
||||
*
|
||||
* @return bool Returns true if the next line is indented, false otherwise
|
||||
*/
|
||||
private function isNextLineIndented()
|
||||
{
|
||||
$currentIndentation = $this->getCurrentLineIndentation();
|
||||
$EOF = !$this->moveToNextLine();
|
||||
|
||||
while (!$EOF && $this->isCurrentLineEmpty()) {
|
||||
$EOF = !$this->moveToNextLine();
|
||||
}
|
||||
|
||||
if ($EOF) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$ret = $this->getCurrentLineIndentation() > $currentIndentation;
|
||||
|
||||
$this->moveToPreviousLine();
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current line is blank or if it is a comment line.
|
||||
*
|
||||
* @return bool Returns true if the current line is empty or if it is a comment line, false otherwise
|
||||
*/
|
||||
private function isCurrentLineEmpty()
|
||||
{
|
||||
return $this->isCurrentLineBlank() || $this->isCurrentLineComment();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current line is blank.
|
||||
*
|
||||
* @return bool Returns true if the current line is blank, false otherwise
|
||||
*/
|
||||
private function isCurrentLineBlank()
|
||||
{
|
||||
return '' == trim($this->currentLine, ' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current line is a comment line.
|
||||
*
|
||||
* @return bool Returns true if the current line is a comment line, false otherwise
|
||||
*/
|
||||
private function isCurrentLineComment()
|
||||
{
|
||||
//checking explicitly the first char of the trim is faster than loops or strpos
|
||||
$ltrimmedLine = ltrim($this->currentLine, ' ');
|
||||
|
||||
return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0];
|
||||
}
|
||||
|
||||
private function isCurrentLineLastLineInDocument()
|
||||
{
|
||||
return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanups a YAML string to be parsed.
|
||||
*
|
||||
* @param string $value The input YAML string
|
||||
*
|
||||
* @return string A cleaned up YAML string
|
||||
*/
|
||||
private function cleanup($value)
|
||||
{
|
||||
$value = str_replace(array("\r\n", "\r"), "\n", $value);
|
||||
|
||||
// strip YAML header
|
||||
$count = 0;
|
||||
$value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count);
|
||||
$this->offset += $count;
|
||||
|
||||
// remove leading comments
|
||||
$trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count);
|
||||
if (1 == $count) {
|
||||
// items have been removed, update the offset
|
||||
$this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
|
||||
$value = $trimmedValue;
|
||||
}
|
||||
|
||||
// remove start of the document marker (---)
|
||||
$trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count);
|
||||
if (1 == $count) {
|
||||
// items have been removed, update the offset
|
||||
$this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
|
||||
$value = $trimmedValue;
|
||||
|
||||
// remove end of the document marker (...)
|
||||
$value = preg_replace('#\.\.\.\s*$#', '', $value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the next line starts unindented collection.
|
||||
*
|
||||
* @return bool Returns true if the next line starts unindented collection, false otherwise
|
||||
*/
|
||||
private function isNextLineUnIndentedCollection()
|
||||
{
|
||||
$currentIndentation = $this->getCurrentLineIndentation();
|
||||
$notEOF = $this->moveToNextLine();
|
||||
|
||||
while ($notEOF && $this->isCurrentLineEmpty()) {
|
||||
$notEOF = $this->moveToNextLine();
|
||||
}
|
||||
|
||||
if (false === $notEOF) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$ret = $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem();
|
||||
|
||||
$this->moveToPreviousLine();
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the string is un-indented collection item.
|
||||
*
|
||||
* @return bool Returns true if the string is un-indented collection item, false otherwise
|
||||
*/
|
||||
private function isStringUnIndentedCollectionItem()
|
||||
{
|
||||
return '-' === rtrim($this->currentLine) || 0 === strpos($this->currentLine, '- ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether or not the current line is the header of a block scalar.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isBlockScalarHeader()
|
||||
{
|
||||
return (bool) self::preg_match('~'.self::BLOCK_SCALAR_HEADER_PATTERN.'$~', $this->currentLine);
|
||||
}
|
||||
|
||||
/**
|
||||
* A local wrapper for `preg_match` which will throw a ParseException if there
|
||||
* is an internal error in the PCRE engine.
|
||||
*
|
||||
* This avoids us needing to check for "false" every time PCRE is used
|
||||
* in the YAML engine
|
||||
*
|
||||
* @throws ParseException on a PCRE internal error
|
||||
*
|
||||
* @see preg_last_error()
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function preg_match($pattern, $subject, &$matches = null, $flags = 0, $offset = 0)
|
||||
{
|
||||
if (false === $ret = preg_match($pattern, $subject, $matches, $flags, $offset)) {
|
||||
switch (preg_last_error()) {
|
||||
case PREG_INTERNAL_ERROR:
|
||||
$error = 'Internal PCRE error.';
|
||||
break;
|
||||
case PREG_BACKTRACK_LIMIT_ERROR:
|
||||
$error = 'pcre.backtrack_limit reached.';
|
||||
break;
|
||||
case PREG_RECURSION_LIMIT_ERROR:
|
||||
$error = 'pcre.recursion_limit reached.';
|
||||
break;
|
||||
case PREG_BAD_UTF8_ERROR:
|
||||
$error = 'Malformed UTF-8 data.';
|
||||
break;
|
||||
case PREG_BAD_UTF8_OFFSET_ERROR:
|
||||
$error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.';
|
||||
break;
|
||||
default:
|
||||
$error = 'Error.';
|
||||
}
|
||||
|
||||
throw new ParseException($error);
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
<?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 RocketTheme\Toolbox\Compat\Yaml;
|
||||
|
||||
/**
|
||||
* Unescaper encapsulates unescaping rules for single and double-quoted
|
||||
* YAML strings.
|
||||
*
|
||||
* @author Matthew Lewinski <matthew@lewinski.org>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Unescaper
|
||||
{
|
||||
/**
|
||||
* Parser and Inline assume UTF-8 encoding, so escaped Unicode characters
|
||||
* must be converted to that encoding.
|
||||
*
|
||||
* @deprecated since version 2.5, to be removed in 3.0
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const ENCODING = 'UTF-8';
|
||||
|
||||
/**
|
||||
* Regex fragment that matches an escaped character in a double quoted string.
|
||||
*/
|
||||
const REGEX_ESCAPED_CHARACTER = '\\\\(x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8}|.)';
|
||||
|
||||
/**
|
||||
* Unescapes a single quoted string.
|
||||
*
|
||||
* @param string $value A single quoted string
|
||||
*
|
||||
* @return string The unescaped string
|
||||
*/
|
||||
public function unescapeSingleQuotedString($value)
|
||||
{
|
||||
return str_replace('\'\'', '\'', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unescapes a double quoted string.
|
||||
*
|
||||
* @param string $value A double quoted string
|
||||
*
|
||||
* @return string The unescaped string
|
||||
*/
|
||||
public function unescapeDoubleQuotedString($value)
|
||||
{
|
||||
$self = $this;
|
||||
$callback = function ($match) use ($self) {
|
||||
return $self->unescapeCharacter($match[0]);
|
||||
};
|
||||
|
||||
// evaluate the string
|
||||
return preg_replace_callback('/'.self::REGEX_ESCAPED_CHARACTER.'/u', $callback, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unescapes a character that was found in a double-quoted string.
|
||||
*
|
||||
* @param string $value An escaped character
|
||||
*
|
||||
* @return string The unescaped character
|
||||
*
|
||||
* @internal This method is public to be usable as callback. It should not
|
||||
* be used in user code. Should be changed in 3.0.
|
||||
*/
|
||||
public function unescapeCharacter($value)
|
||||
{
|
||||
switch ($value[1]) {
|
||||
case '0':
|
||||
return "\x0";
|
||||
case 'a':
|
||||
return "\x7";
|
||||
case 'b':
|
||||
return "\x8";
|
||||
case 't':
|
||||
return "\t";
|
||||
case "\t":
|
||||
return "\t";
|
||||
case 'n':
|
||||
return "\n";
|
||||
case 'v':
|
||||
return "\xB";
|
||||
case 'f':
|
||||
return "\xC";
|
||||
case 'r':
|
||||
return "\r";
|
||||
case 'e':
|
||||
return "\x1B";
|
||||
case ' ':
|
||||
return ' ';
|
||||
case '"':
|
||||
return '"';
|
||||
case '/':
|
||||
return '/';
|
||||
case '\\':
|
||||
return '\\';
|
||||
case 'N':
|
||||
// U+0085 NEXT LINE
|
||||
return "\xC2\x85";
|
||||
case '_':
|
||||
// U+00A0 NO-BREAK SPACE
|
||||
return "\xC2\xA0";
|
||||
case 'L':
|
||||
// U+2028 LINE SEPARATOR
|
||||
return "\xE2\x80\xA8";
|
||||
case 'P':
|
||||
// U+2029 PARAGRAPH SEPARATOR
|
||||
return "\xE2\x80\xA9";
|
||||
case 'x':
|
||||
return self::utf8chr(hexdec(substr($value, 2, 2)));
|
||||
case 'u':
|
||||
return self::utf8chr(hexdec(substr($value, 2, 4)));
|
||||
case 'U':
|
||||
return self::utf8chr(hexdec(substr($value, 2, 8)));
|
||||
default:
|
||||
@trigger_error('Not escaping a backslash in a double-quoted string is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.', E_USER_DEPRECATED);
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the UTF-8 character for the given code point.
|
||||
*
|
||||
* @param int $c The unicode code point
|
||||
*
|
||||
* @return string The corresponding UTF-8 character
|
||||
*/
|
||||
private static function utf8chr($c)
|
||||
{
|
||||
if (0x80 > $c %= 0x200000) {
|
||||
return chr($c);
|
||||
}
|
||||
if (0x800 > $c) {
|
||||
return chr(0xC0 | $c >> 6).chr(0x80 | $c & 0x3F);
|
||||
}
|
||||
if (0x10000 > $c) {
|
||||
return chr(0xE0 | $c >> 12).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F);
|
||||
}
|
||||
|
||||
return chr(0xF0 | $c >> 18).chr(0x80 | $c >> 12 & 0x3F).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F);
|
||||
}
|
||||
}
|
||||
@@ -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 RocketTheme\Toolbox\Compat\Yaml;
|
||||
|
||||
use RocketTheme\Toolbox\Compat\Yaml\Exception\ParseException;
|
||||
|
||||
/**
|
||||
* Yaml offers convenience methods to load and dump YAML.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Yaml
|
||||
{
|
||||
/**
|
||||
* Parses YAML into a PHP value.
|
||||
*
|
||||
* Usage:
|
||||
* <code>
|
||||
* $array = Yaml::parse(file_get_contents('config.yml'));
|
||||
* print_r($array);
|
||||
* </code>
|
||||
*
|
||||
* As this method accepts both plain strings and file names as an input,
|
||||
* you must validate the input before calling this method. Passing a file
|
||||
* as an input is a deprecated feature and will be removed in 3.0.
|
||||
*
|
||||
* Note: the ability to pass file names to the Yaml::parse method is deprecated since version 2.2 and will be removed in 3.0. Pass the YAML contents of the file instead.
|
||||
*
|
||||
* @param string $input Path to a YAML file or a string containing YAML
|
||||
* @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise
|
||||
* @param bool $objectSupport True if object support is enabled, false otherwise
|
||||
* @param bool $objectForMap True if maps should return a stdClass instead of array()
|
||||
*
|
||||
* @return mixed The YAML converted to a PHP value
|
||||
*
|
||||
* @throws ParseException If the YAML is not valid
|
||||
*/
|
||||
public static function parse($input, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false)
|
||||
{
|
||||
// if input is a file, process it
|
||||
$file = '';
|
||||
if (false === strpos($input, "\n") && is_file($input)) {
|
||||
@trigger_error('The ability to pass file names to the '.__METHOD__.' method is deprecated since version 2.2 and will be removed in 3.0. Pass the YAML contents of the file instead.', E_USER_DEPRECATED);
|
||||
|
||||
if (false === is_readable($input)) {
|
||||
throw new ParseException(sprintf('Unable to parse "%s" as the file is not readable.', $input));
|
||||
}
|
||||
|
||||
$file = $input;
|
||||
$input = file_get_contents($file);
|
||||
}
|
||||
|
||||
$yaml = new Parser();
|
||||
|
||||
try {
|
||||
return $yaml->parse($input, $exceptionOnInvalidType, $objectSupport, $objectForMap);
|
||||
} catch (ParseException $e) {
|
||||
if ($file) {
|
||||
$e->setParsedFile($file);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -20,5 +20,5 @@ class Event extends BaseEvent implements \ArrayAccess
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $items = array();
|
||||
protected $items = [];
|
||||
}
|
||||
|
||||
+5
-6
@@ -48,7 +48,7 @@ class File implements FileInterface
|
||||
/**
|
||||
* @var array|File[]
|
||||
*/
|
||||
static protected $instances = array();
|
||||
static protected $instances = [];
|
||||
|
||||
/**
|
||||
* Get file instance.
|
||||
@@ -97,8 +97,6 @@ class File implements FileInterface
|
||||
|
||||
/**
|
||||
* Prevent constructor from being used.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function __construct()
|
||||
{
|
||||
@@ -106,8 +104,6 @@ class File implements FileInterface
|
||||
|
||||
/**
|
||||
* Prevent cloning.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function __clone()
|
||||
{
|
||||
@@ -224,7 +220,7 @@ class File implements FileInterface
|
||||
public function unlock()
|
||||
{
|
||||
if (!$this->handle) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if ($this->locked) {
|
||||
flock($this->handle, LOCK_UN);
|
||||
@@ -232,6 +228,8 @@ class File implements FileInterface
|
||||
}
|
||||
fclose($this->handle);
|
||||
$this->handle = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -282,6 +280,7 @@ class File implements FileInterface
|
||||
*
|
||||
* @param mixed $var
|
||||
* @return string|array
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function content($var = null)
|
||||
{
|
||||
|
||||
+4
-4
@@ -18,7 +18,7 @@ class IniFile extends File
|
||||
/**
|
||||
* @var array|File[]
|
||||
*/
|
||||
static protected $instances = array();
|
||||
static protected $instances = [];
|
||||
|
||||
/**
|
||||
* Check contents and make sure it is in correct format.
|
||||
@@ -65,12 +65,12 @@ class IniFile extends File
|
||||
*/
|
||||
protected function decode($var)
|
||||
{
|
||||
$var = file_exists($this->filename) ? @parse_ini_file($this->filename) : [];
|
||||
$decoded = file_exists($this->filename) ? @parse_ini_file($this->filename) : [];
|
||||
|
||||
if ($var === false) {
|
||||
if ($decoded === false) {
|
||||
throw new \RuntimeException("Decoding file '{$this->filename}' failed'");
|
||||
}
|
||||
|
||||
return $var;
|
||||
return $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ class JsonFile extends File
|
||||
/**
|
||||
* @var array|File[]
|
||||
*/
|
||||
static protected $instances = array();
|
||||
static protected $instances = [];
|
||||
|
||||
/**
|
||||
* Check contents and make sure it is in correct format.
|
||||
|
||||
+3
-3
@@ -13,7 +13,7 @@ class LogFile extends File
|
||||
/**
|
||||
* @var array|File[]
|
||||
*/
|
||||
static protected $instances = array();
|
||||
static protected $instances = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@@ -41,11 +41,11 @@ class LogFile extends File
|
||||
*
|
||||
* @param string $var
|
||||
* @return string|void
|
||||
* @throws \Exception
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
protected function encode($var)
|
||||
{
|
||||
throw new \Exception('Saving log file is forbidden.');
|
||||
throw new \BadMethodCallException('Saving log file is forbidden.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+15
-11
@@ -1,7 +1,9 @@
|
||||
<?php
|
||||
namespace RocketTheme\Toolbox\File;
|
||||
|
||||
use \Symfony\Component\Yaml\Yaml as YamlParser;
|
||||
use Symfony\Component\Yaml\Exception\ParseException;
|
||||
use Symfony\Component\Yaml\Yaml as YamlParser;
|
||||
use RocketTheme\Toolbox\Compat\Yaml\Yaml as FallbackYamlParser;
|
||||
|
||||
/**
|
||||
* Implements Markdown File reader.
|
||||
@@ -20,7 +22,7 @@ class MarkdownFile extends File
|
||||
/**
|
||||
* @var array|File[]
|
||||
*/
|
||||
static protected $instances = array();
|
||||
static protected $instances = [];
|
||||
|
||||
/**
|
||||
* Get/set file header.
|
||||
@@ -136,26 +138,28 @@ class MarkdownFile extends File
|
||||
// Parse header.
|
||||
preg_match($frontmatter_regex, ltrim($var), $m);
|
||||
if(!empty($m)) {
|
||||
$content['frontmatter'] = preg_replace("/\n\t/", "\n ", $m[1]);
|
||||
// Normalize frontmatter.
|
||||
$content['frontmatter'] = $frontmatter = preg_replace("/\n\t/", "\n ", $m[1]);
|
||||
|
||||
// Try native PECL YAML PHP extension first if available.
|
||||
if ($this->setting('native') && function_exists('yaml_parse')) {
|
||||
$data = $content['frontmatter'];
|
||||
if ($this->setting('compat', true)) {
|
||||
// Fix illegal @ start character.
|
||||
$data = preg_replace('/ (@[\w\.\-]*)/', " '\${1}'", $data);
|
||||
}
|
||||
|
||||
// Safely decode YAML.
|
||||
$saved = @ini_get('yaml.decode_php');
|
||||
@ini_set('yaml.decode_php', 0);
|
||||
$content['header'] = @yaml_parse("---\n" . $data . "\n...");
|
||||
$content['header'] = @yaml_parse("---\n" . $frontmatter . "\n...");
|
||||
@ini_set('yaml.decode_php', $saved);
|
||||
}
|
||||
|
||||
if ($content['header'] === false) {
|
||||
// YAML hasn't been parsed yet (error or extension isn't available). Fall back to Symfony parser.
|
||||
$content['header'] = (array) YamlParser::parse($content['frontmatter']);
|
||||
try {
|
||||
$content['header'] = (array) YamlParser::parse($frontmatter);
|
||||
} catch (ParseException $e) {
|
||||
if (!$this->setting('compat', true)) {
|
||||
throw $e;
|
||||
}
|
||||
$content['header'] = (array) FallbackYamlParser::parse($frontmatter);
|
||||
}
|
||||
}
|
||||
$content['markdown'] = $m[2];
|
||||
} else {
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ class MoFile extends File
|
||||
/**
|
||||
* @var array|File[]
|
||||
*/
|
||||
static protected $instances = array();
|
||||
static protected $instances = [];
|
||||
|
||||
/**
|
||||
* File can never be written.
|
||||
|
||||
+7
-8
@@ -18,7 +18,7 @@ class PhpFile extends File
|
||||
/**
|
||||
* @var array|File[]
|
||||
*/
|
||||
static protected $instances = array();
|
||||
static protected $instances = [];
|
||||
|
||||
/**
|
||||
* Saves PHP file and invalidates opcache.
|
||||
@@ -43,7 +43,7 @@ class PhpFile extends File
|
||||
/**
|
||||
* Check contents and make sure it is in correct format.
|
||||
*
|
||||
* @param array $var
|
||||
* @param array|object $var
|
||||
* @return array
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
@@ -75,20 +75,20 @@ class PhpFile extends File
|
||||
* @param array $a The array to get as a string.
|
||||
* @param int $level Used internally to indent rows.
|
||||
*
|
||||
* @return array
|
||||
* @return string
|
||||
*/
|
||||
protected function encodeArray(array $a, $level = 0)
|
||||
{
|
||||
$r = [];
|
||||
foreach ($a as $k => $v) {
|
||||
if (is_array($v) || is_object($v)) {
|
||||
$r[] = var_export($k, true) . " => " . $this->encodeArray((array) $v, $level + 1);
|
||||
$r[] = var_export($k, true) . ' => ' . $this->encodeArray((array) $v, $level + 1);
|
||||
} else {
|
||||
$r[] = var_export($k, true) . " => " . var_export($v, true);
|
||||
$r[] = var_export($k, true) . ' => ' . var_export($v, true);
|
||||
}
|
||||
}
|
||||
|
||||
$space = str_repeat(" ", $level);
|
||||
$space = str_repeat(' ', $level);
|
||||
return "[\n {$space}" . implode(",\n {$space}", $r) . "\n{$space}]";
|
||||
}
|
||||
|
||||
@@ -100,7 +100,6 @@ class PhpFile extends File
|
||||
*/
|
||||
protected function decode($var)
|
||||
{
|
||||
$var = (array) include $this->filename;
|
||||
return $var;
|
||||
return (array) include $this->filename;
|
||||
}
|
||||
}
|
||||
|
||||
+71
-15
@@ -3,7 +3,8 @@ namespace RocketTheme\Toolbox\File;
|
||||
|
||||
use Symfony\Component\Yaml\Exception\DumpException;
|
||||
use Symfony\Component\Yaml\Exception\ParseException;
|
||||
use \Symfony\Component\Yaml\Yaml as YamlParser;
|
||||
use Symfony\Component\Yaml\Yaml as YamlParser;
|
||||
use RocketTheme\Toolbox\Compat\Yaml\Yaml as FallbackYamlParser;
|
||||
|
||||
/**
|
||||
* Implements YAML File reader.
|
||||
@@ -17,7 +18,27 @@ class YamlFile extends File
|
||||
/**
|
||||
* @var array|File[]
|
||||
*/
|
||||
static protected $instances = array();
|
||||
static protected $instances = [];
|
||||
|
||||
static protected $globalSettings = [
|
||||
'compat' => true,
|
||||
'native' => true
|
||||
];
|
||||
|
||||
/**
|
||||
* Set/get settings.
|
||||
*
|
||||
* @param array $settings
|
||||
* @return array
|
||||
*/
|
||||
public static function globalSettings(array $settings = null)
|
||||
{
|
||||
if ($settings !== null) {
|
||||
static::$globalSettings = $settings;
|
||||
}
|
||||
|
||||
return static::$globalSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@@ -29,6 +50,38 @@ class YamlFile extends File
|
||||
$this->extension = '.yaml';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set/get settings.
|
||||
*
|
||||
* @param array $settings
|
||||
* @return array
|
||||
*/
|
||||
public function settings(array $settings = null)
|
||||
{
|
||||
if ($settings !== null) {
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
return $this->settings + static::$globalSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get setting.
|
||||
*
|
||||
* @param string $setting
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
*/
|
||||
public function setting($setting, $default = null)
|
||||
{
|
||||
$value = parent::setting($setting);
|
||||
if (null === $value) {
|
||||
$value = isset(static::$globalSettings[$setting]) ? static::$globalSettings[$setting] : $default;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check contents and make sure it is in correct format.
|
||||
*
|
||||
@@ -43,7 +96,7 @@ class YamlFile extends File
|
||||
/**
|
||||
* Encode contents into RAW string.
|
||||
*
|
||||
* @param string $var
|
||||
* @param array $var
|
||||
* @return string
|
||||
* @throws DumpException
|
||||
*/
|
||||
@@ -61,24 +114,27 @@ class YamlFile extends File
|
||||
*/
|
||||
protected function decode($var)
|
||||
{
|
||||
$data = false;
|
||||
|
||||
// Try native PECL YAML PHP extension first if available.
|
||||
if ($this->setting('native') && function_exists('yaml_parse')) {
|
||||
if ($this->setting('compat', true)) {
|
||||
// Fix illegal @ start character.
|
||||
$data = preg_replace('/ (@[\w\.\-]*)/', " '\${1}'", $var);
|
||||
} else {
|
||||
$data = $var;
|
||||
}
|
||||
|
||||
if ($this->setting('native', true) && function_exists('yaml_parse')) {
|
||||
// Safely decode YAML.
|
||||
$saved = @ini_get('yaml.decode_php');
|
||||
@ini_set('yaml.decode_php', 0);
|
||||
$data = @yaml_parse($data);
|
||||
$data = @yaml_parse($var);
|
||||
@ini_set('yaml.decode_php', $saved);
|
||||
|
||||
if ($data !== false) {
|
||||
return (array) $data;
|
||||
}
|
||||
}
|
||||
|
||||
return $data !== false ? $data : (array) YamlParser::parse($var);
|
||||
try {
|
||||
return (array) YamlParser::parse($var);
|
||||
} catch (ParseException $e) {
|
||||
if ($this->setting('compat', true)) {
|
||||
return (array) FallbackYamlParser::parse($var);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+52
-13
@@ -19,7 +19,7 @@ class UniformResourceLocator implements ResourceLocatorInterface
|
||||
public $base;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* @var array[]
|
||||
*/
|
||||
protected $schemes = [];
|
||||
|
||||
@@ -102,11 +102,11 @@ class UniformResourceLocator implements ResourceLocatorInterface
|
||||
foreach((array) $paths as $path) {
|
||||
if (is_array($path)) {
|
||||
// Support stream lookup in ['theme', 'path/to'] format.
|
||||
if (count($path) != 2 || !is_string($path[0]) || !is_string($path[1])) {
|
||||
if (count($path) !== 2 || !is_string($path[0]) || !is_string($path[1])) {
|
||||
throw new \BadMethodCallException('Invalid stream path given.');
|
||||
}
|
||||
$list[] = $path;
|
||||
} elseif (strstr($path, '://')) {
|
||||
} elseif (false !== strpos($path, '://')) {
|
||||
// Support stream lookup in 'theme://path/to' format.
|
||||
$stream = explode('://', $path, 2);
|
||||
$stream[1] = trim($stream[1], '/');
|
||||
@@ -231,9 +231,9 @@ class UniformResourceLocator implements ResourceLocatorInterface
|
||||
if (!is_string($uri)) {
|
||||
if ($throwException) {
|
||||
throw new \BadMethodCallException('Invalid parameter $uri.');
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$uri = preg_replace('|\\\|u', '/', $uri);
|
||||
@@ -252,9 +252,9 @@ class UniformResourceLocator implements ResourceLocatorInterface
|
||||
if ($part === null || $part === '' || (!$list && strpos($part, ':'))) {
|
||||
if ($throwException) {
|
||||
throw new \BadMethodCallException('Invalid parameter $uri.');
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
} elseif (($i && $part === '') || $part === '.') {
|
||||
continue;
|
||||
@@ -321,12 +321,13 @@ class UniformResourceLocator implements ResourceLocatorInterface
|
||||
{
|
||||
$uris = array_unique($uris);
|
||||
|
||||
$list = [];
|
||||
$lists = [[]];
|
||||
foreach ($uris as $uri) {
|
||||
$list = array_merge($list, $this->findResources($uri, $absolute, $all));
|
||||
$lists[] = $this->findResources($uri, $absolute, $all);
|
||||
}
|
||||
|
||||
return $list;
|
||||
// TODO: In PHP 5.6+ use array_merge(...$list);
|
||||
return call_user_func_array('array_merge', $lists);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -345,17 +346,47 @@ class UniformResourceLocator implements ResourceLocatorInterface
|
||||
$iterator = new \RecursiveIteratorIterator($this->getRecursiveIterator($uri), \RecursiveIteratorIterator::SELF_FIRST);
|
||||
|
||||
/** @var UniformResourceIterator $uri */
|
||||
foreach ($iterator as $uri) {
|
||||
$key = $uri->getUrl() . '@010';
|
||||
$this->cache[$key] = $uri->getPathname();
|
||||
foreach ($iterator as $item) {
|
||||
$key = $item->getUrl() . '@010';
|
||||
$this->cache[$key] = $item->getPathname();
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset locator cache.
|
||||
*
|
||||
* @param string $uri
|
||||
* @return $this
|
||||
*/
|
||||
public function clearCache($uri = null)
|
||||
{
|
||||
if ($uri) {
|
||||
$this->clearCached($uri, true, true, true);
|
||||
$this->clearCached($uri, true, true, false);
|
||||
$this->clearCached($uri, true, false, true);
|
||||
$this->clearCached($uri, true, false, false);
|
||||
$this->clearCached($uri, false, true, true);
|
||||
$this->clearCached($uri, false, true, false);
|
||||
$this->clearCached($uri, false, false, true);
|
||||
$this->clearCached($uri, false, false, false);
|
||||
} else {
|
||||
$this->cache = [];
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $uri
|
||||
* @param bool $array
|
||||
* @param bool $absolute
|
||||
* @param bool $all
|
||||
* @return array|string|bool
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
protected function findCached($uri, $array, $absolute, $all)
|
||||
{
|
||||
// Local caching: make sure that the function gets only called at once for each file.
|
||||
@@ -379,6 +410,14 @@ class UniformResourceLocator implements ResourceLocatorInterface
|
||||
return $this->cache[$key];
|
||||
}
|
||||
|
||||
protected function clearCached($uri, $array, $absolute, $all)
|
||||
{
|
||||
// Local caching: make sure that the function gets only called at once for each file.
|
||||
$key = $uri .'@'. (int) $array . (int) $absolute . (int) $all;
|
||||
|
||||
unset($this->cache[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $scheme
|
||||
* @param string $file
|
||||
|
||||
+5
-5
@@ -13,7 +13,7 @@ class Message
|
||||
/**
|
||||
* @var array|string[]
|
||||
*/
|
||||
protected $messages = array();
|
||||
protected $messages = [];
|
||||
|
||||
/**
|
||||
* Add message to the queue.
|
||||
@@ -25,11 +25,11 @@ class Message
|
||||
public function add($message, $scope = 'default')
|
||||
{
|
||||
$key = md5($scope.'~'.$message);
|
||||
$message = array('message' => $message, 'scope' => $scope);
|
||||
$item = ['message' => $message, 'scope' => $scope];
|
||||
|
||||
// don't add duplicates
|
||||
if (!array_key_exists($key, $this->messages)) {
|
||||
$this->messages[$key] = $message;
|
||||
$this->messages[$key] = $item;
|
||||
}
|
||||
|
||||
return $this;
|
||||
@@ -47,7 +47,7 @@ class Message
|
||||
$this->messages = array();
|
||||
} else {
|
||||
foreach ($this->messages as $key => $message) {
|
||||
if ($message['scope'] == $scope) {
|
||||
if ($message['scope'] === $scope) {
|
||||
unset($this->messages[$key]);
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,7 @@ class Message
|
||||
|
||||
$messages = array();
|
||||
foreach ($this->messages as $message) {
|
||||
if ($message['scope'] == $scope) {
|
||||
if ($message['scope'] === $scope) {
|
||||
$messages[] = $message;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-12
@@ -31,12 +31,11 @@ class Session implements \IteratorAggregate
|
||||
{
|
||||
// Session is a singleton.
|
||||
if (isset(self::$instance)) {
|
||||
throw new \RuntimeException("Session has already been initialized.", 500);
|
||||
throw new \RuntimeException('Session has already been initialized.', 500);
|
||||
}
|
||||
|
||||
// Destroy any existing sessions started with session.auto_start
|
||||
if ($this->isSessionStarted())
|
||||
{
|
||||
if ($this->isSessionStarted()) {
|
||||
session_unset();
|
||||
session_destroy();
|
||||
}
|
||||
@@ -52,12 +51,6 @@ class Session implements \IteratorAggregate
|
||||
register_shutdown_function([$this, 'close']);
|
||||
session_cache_limiter('nocache');
|
||||
|
||||
if (isset($this->count)) {
|
||||
$this->count++;
|
||||
} else {
|
||||
$this->count = 1;
|
||||
}
|
||||
|
||||
self::$instance = $this;
|
||||
}
|
||||
|
||||
@@ -69,7 +62,7 @@ class Session implements \IteratorAggregate
|
||||
*/
|
||||
public function instance()
|
||||
{
|
||||
if (!isset(self::$instance)) {
|
||||
if (null === self::$instance) {
|
||||
throw new \RuntimeException("Session hasn't been initialized.", 500);
|
||||
}
|
||||
|
||||
@@ -101,7 +94,7 @@ class Session implements \IteratorAggregate
|
||||
/**
|
||||
* Get session ID
|
||||
*
|
||||
* @return string Session ID
|
||||
* @return string|null Session ID
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
@@ -126,7 +119,7 @@ class Session implements \IteratorAggregate
|
||||
/**
|
||||
* Get session name
|
||||
*
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
|
||||
@@ -21,17 +21,23 @@ class ReadOnlyStream extends Stream implements StreamInterface
|
||||
{
|
||||
if (!in_array($mode, ['r', 'rb', 'rt'])) {
|
||||
if ($options & STREAM_REPORT_ERRORS) {
|
||||
trigger_error('stream_open() write modes not supported for read-only stream wrappers', E_USER_WARNING);
|
||||
trigger_error(sprintf('stream_open() write modes not supported for %s', $uri), E_USER_WARNING);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$path = $this->getPath($uri);
|
||||
|
||||
if (!$path) {
|
||||
if ($options & STREAM_REPORT_ERRORS) {
|
||||
trigger_error(sprintf('stream_open(): path for %s does not exist', $uri), E_USER_WARNING);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->uri = $uri;
|
||||
$this->handle = ($options & STREAM_REPORT_ERRORS) ? fopen($path, $mode) : @fopen($path, $mode);
|
||||
|
||||
return (bool) $this->handle;
|
||||
@@ -40,39 +46,49 @@ class ReadOnlyStream extends Stream implements StreamInterface
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
// Disallow exclusive lock or non-blocking lock requests
|
||||
if (!in_array($operation, [LOCK_SH, LOCK_UN, LOCK_SH | LOCK_NB])) {
|
||||
if (!in_array($operation, [LOCK_SH, LOCK_UN, LOCK_SH | LOCK_NB], true)) {
|
||||
trigger_error(
|
||||
'stream_lock() exclusive lock operations not supported for read-only stream wrappers',
|
||||
sprintf('stream_lock() exclusive lock operations not supported for %s', $this->uri),
|
||||
E_USER_WARNING
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return flock($this->handle, $operation);
|
||||
}
|
||||
|
||||
public function stream_metadata($uri, $option, $value)
|
||||
{
|
||||
if ($option !== STREAM_META_TOUCH) {
|
||||
throw new \BadMethodCallException(sprintf('stream_metadata() not supported for %s', $uri));
|
||||
}
|
||||
|
||||
return parent::stream_metadata($uri, $option, $value);
|
||||
}
|
||||
|
||||
public function stream_write($data)
|
||||
{
|
||||
throw new \BadMethodCallException('stream_write() not supported for read-only stream wrappers');
|
||||
throw new \BadMethodCallException(sprintf('stream_write() not supported for %s', $this->uri));
|
||||
}
|
||||
|
||||
public function unlink($uri)
|
||||
{
|
||||
throw new \BadMethodCallException('unlink() not supported for read-only stream wrappers');
|
||||
throw new \BadMethodCallException(sprintf('unlink() not supported for %s', $uri));
|
||||
}
|
||||
|
||||
public function rename($from_uri, $to_uri)
|
||||
{
|
||||
throw new \BadMethodCallException('rename() not supported for read-only stream wrappers');
|
||||
throw new \BadMethodCallException(sprintf('rename() not supported for %s', $from_uri));
|
||||
}
|
||||
|
||||
public function mkdir($uri, $mode, $options)
|
||||
{
|
||||
throw new \BadMethodCallException('mkdir() not supported for read-only stream wrappers');
|
||||
throw new \BadMethodCallException(sprintf('mkdir() not supported for %s', $uri));
|
||||
}
|
||||
|
||||
public function rmdir($uri, $options)
|
||||
{
|
||||
throw new \BadMethodCallException('rmdir() not supported for read-only stream wrappers');
|
||||
throw new \BadMethodCallException(sprintf('rmdir() not supported for %s', $uri));
|
||||
}
|
||||
}
|
||||
|
||||
+76
-22
@@ -2,6 +2,7 @@
|
||||
namespace RocketTheme\Toolbox\StreamWrapper;
|
||||
|
||||
use RocketTheme\Toolbox\ResourceLocator\ResourceLocatorInterface;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
/**
|
||||
* Implements Read/Write Streams.
|
||||
@@ -12,6 +13,11 @@ use RocketTheme\Toolbox\ResourceLocator\ResourceLocatorInterface;
|
||||
*/
|
||||
class Stream implements StreamInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $uri;
|
||||
|
||||
/**
|
||||
* A generic resource handle.
|
||||
*
|
||||
@@ -20,7 +26,7 @@ class Stream implements StreamInterface
|
||||
protected $handle = null;
|
||||
|
||||
/**
|
||||
* @var ResourceLocatorInterface
|
||||
* @var ResourceLocatorInterface|UniformResourceLocator
|
||||
*/
|
||||
protected static $locator;
|
||||
|
||||
@@ -37,11 +43,20 @@ class Stream implements StreamInterface
|
||||
$path = $this->getPath($uri, $mode);
|
||||
|
||||
if (!$path) {
|
||||
if ($options & STREAM_REPORT_ERRORS) {
|
||||
trigger_error(sprintf('stream_open(): path for %s does not exist', $uri), E_USER_WARNING);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->uri = $uri;
|
||||
$this->handle = ($options & STREAM_REPORT_ERRORS) ? fopen($path, $mode) : @fopen($path, $mode);
|
||||
|
||||
if (!in_array($mode, ['r', 'rb', 'rt']) && static::$locator instanceof UniformResourceLocator) {
|
||||
static::$locator->clearCache($this->uri);
|
||||
}
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
@@ -52,7 +67,7 @@ class Stream implements StreamInterface
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
if (in_array($operation, [LOCK_SH, LOCK_EX, LOCK_UN, LOCK_NB])) {
|
||||
if (\in_array($operation, [LOCK_SH, LOCK_EX, LOCK_UN, LOCK_NB], true)) {
|
||||
return flock($this->handle, $operation);
|
||||
}
|
||||
|
||||
@@ -61,21 +76,24 @@ class Stream implements StreamInterface
|
||||
|
||||
public function stream_metadata($uri, $option, $value)
|
||||
{
|
||||
switch ($option) {
|
||||
case STREAM_META_TOUCH:
|
||||
list ($time, $atime) = $value;
|
||||
return touch($uri, $time, $atime);
|
||||
$path = $this->findPath($uri);
|
||||
if ($path) {
|
||||
switch ($option) {
|
||||
case STREAM_META_TOUCH:
|
||||
list ($time, $atime) = $value;
|
||||
return touch($path, $time, $atime);
|
||||
|
||||
case STREAM_META_OWNER_NAME:
|
||||
case STREAM_META_OWNER:
|
||||
return chown($uri, $value);
|
||||
case STREAM_META_OWNER_NAME:
|
||||
case STREAM_META_OWNER:
|
||||
return chown($path, $value);
|
||||
|
||||
case STREAM_META_GROUP_NAME:
|
||||
case STREAM_META_GROUP:
|
||||
return chgrp($uri, $value);
|
||||
case STREAM_META_GROUP_NAME:
|
||||
case STREAM_META_GROUP:
|
||||
return chgrp($path, $value);
|
||||
|
||||
case STREAM_META_ACCESS:
|
||||
return chmod($uri, $value);
|
||||
case STREAM_META_ACCESS:
|
||||
return chmod($path, $value);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -131,24 +149,37 @@ class Stream implements StreamInterface
|
||||
public function rename($fromUri, $toUri)
|
||||
{
|
||||
$fromPath = $this->getPath($fromUri);
|
||||
$toPath = $this->getPath($toUri);
|
||||
$toPath = $this->getPath($toUri, 'w');
|
||||
|
||||
if (!($fromPath && $toPath)) {
|
||||
if (!$fromPath || !$toPath) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (static::$locator instanceof UniformResourceLocator) {
|
||||
static::$locator->clearCache($fromUri);
|
||||
static::$locator->clearCache($toUri);
|
||||
}
|
||||
|
||||
return rename($fromPath, $toPath);
|
||||
}
|
||||
|
||||
public function mkdir($uri, $mode, $options)
|
||||
{
|
||||
$recursive = (bool) ($options & STREAM_MKDIR_RECURSIVE);
|
||||
$path = $this->getPath($uri, $recursive ? $mode : null);
|
||||
$path = $this->getPath($uri, $recursive ? 'd' : 'w');
|
||||
|
||||
if (!$path) {
|
||||
if ($options & STREAM_REPORT_ERRORS) {
|
||||
trigger_error(sprintf('mkdir(): Could not create directory for %s', $uri), E_USER_WARNING);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (static::$locator instanceof UniformResourceLocator) {
|
||||
static::$locator->clearCache($uri);
|
||||
}
|
||||
|
||||
return ($options & STREAM_REPORT_ERRORS) ? mkdir($path, $mode, $recursive) : @mkdir($path, $mode, $recursive);
|
||||
}
|
||||
|
||||
@@ -157,9 +188,17 @@ class Stream implements StreamInterface
|
||||
$path = $this->getPath($uri);
|
||||
|
||||
if (!$path) {
|
||||
if ($options & STREAM_REPORT_ERRORS) {
|
||||
trigger_error(sprintf('rmdir(): Directory not found for %s', $uri), E_USER_WARNING);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (static::$locator instanceof UniformResourceLocator) {
|
||||
static::$locator->clearCache($uri);
|
||||
}
|
||||
|
||||
return ($options & STREAM_REPORT_ERRORS) ? rmdir($path) : @rmdir($path);
|
||||
}
|
||||
|
||||
@@ -172,7 +211,7 @@ class Stream implements StreamInterface
|
||||
}
|
||||
|
||||
// Suppress warnings if requested or if the file or directory does not
|
||||
// exist. This is consistent with PHP's plain filesystem stream wrapper.
|
||||
// exist. This is consistent with PHPs plain filesystem stream wrapper.
|
||||
return ($flags & STREAM_URL_STAT_QUIET || !file_exists($path)) ? @stat($path) : stat($path);
|
||||
}
|
||||
|
||||
@@ -184,6 +223,7 @@ class Stream implements StreamInterface
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->uri = $uri;
|
||||
$this->handle = opendir($path);
|
||||
|
||||
return (bool) $this->handle;
|
||||
@@ -210,26 +250,40 @@ class Stream implements StreamInterface
|
||||
|
||||
protected function getPath($uri, $mode = null)
|
||||
{
|
||||
if ($mode === null) {
|
||||
$mode = 'r';
|
||||
}
|
||||
|
||||
$path = $this->findPath($uri);
|
||||
|
||||
if ($mode == null || !$path || file_exists($path)) {
|
||||
if ($path && file_exists($path)) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
if ($mode[0] == 'r') {
|
||||
if ($mode[0] === 'r') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We are either opening a file or creating directory.
|
||||
list($scheme, $target) = explode('://', $uri, 2);
|
||||
|
||||
$path = $this->findPath($scheme . '://' . dirname($target));
|
||||
if ($target === '') {
|
||||
return false;
|
||||
}
|
||||
$target = explode('/', $target);
|
||||
$filename = [];
|
||||
|
||||
do {
|
||||
$filename[] = array_pop($target);
|
||||
|
||||
$path = $this->findPath($scheme . '://' . implode('/', $target));
|
||||
} while ($target && !$path);
|
||||
|
||||
if (!$path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $path . '/' . basename($uri);
|
||||
return $path . '/' . implode('/', array_reverse($filename));
|
||||
}
|
||||
|
||||
protected function findPath($uri)
|
||||
|
||||
@@ -12,6 +12,11 @@ class StreamBuilder
|
||||
*/
|
||||
protected $items = [];
|
||||
|
||||
/**
|
||||
* StreamBuilder constructor.
|
||||
* @param StreamInterface[] $items
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct(array $items = [])
|
||||
{
|
||||
foreach ($items as $scheme => $handler) {
|
||||
@@ -20,15 +25,15 @@ class StreamBuilder
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $scheme
|
||||
* @param $handler
|
||||
* @param string $scheme
|
||||
* @param StreamInterface $handler
|
||||
* @return $this
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function add($scheme, $handler)
|
||||
{
|
||||
if (isset($this->items[$scheme])) {
|
||||
if ($handler == $this->items[$scheme]) {
|
||||
if ($handler === $this->items[$scheme]) {
|
||||
return $this;
|
||||
}
|
||||
throw new \InvalidArgumentException("Stream '{$scheme}' has already been initialized.");
|
||||
@@ -48,7 +53,7 @@ class StreamBuilder
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $scheme
|
||||
* @param string $scheme
|
||||
* @return $this
|
||||
*/
|
||||
public function remove($scheme)
|
||||
@@ -70,7 +75,7 @@ class StreamBuilder
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $scheme
|
||||
* @param string $scheme
|
||||
* @return bool
|
||||
*/
|
||||
public function isStream($scheme)
|
||||
@@ -79,8 +84,8 @@ class StreamBuilder
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $scheme
|
||||
* @return null
|
||||
* @param string $scheme
|
||||
* @return StreamInterface|null
|
||||
*/
|
||||
public function getStreamType($scheme)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user