diff --git a/src/Symfony/Component/ValueExporter/.gitignore b/src/Symfony/Component/ValueExporter/.gitignore new file mode 100644 index 0000000000000..5414c2c655e72 --- /dev/null +++ b/src/Symfony/Component/ValueExporter/.gitignore @@ -0,0 +1,3 @@ +composer.lock +phpunit.xml +vendor/ diff --git a/src/Symfony/Component/ValueExporter/CHANGELOG.md b/src/Symfony/Component/ValueExporter/CHANGELOG.md new file mode 100644 index 0000000000000..58eb13c207df2 --- /dev/null +++ b/src/Symfony/Component/ValueExporter/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +3.2.0 +----- + + * introducing the component diff --git a/src/Symfony/Component/ValueExporter/Exception/InvalidFormatterException.php b/src/Symfony/Component/ValueExporter/Exception/InvalidFormatterException.php new file mode 100644 index 0000000000000..a8ae16aa43680 --- /dev/null +++ b/src/Symfony/Component/ValueExporter/Exception/InvalidFormatterException.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ValueExporter\Exception; + +/** + * Thrown when a {@link \Symfony\Component\ValueExporter\Formatter\FormatterInterface} + * is not supported by the {@link \Symfony\Component\ValueExporter\Exporter\ValueExporterInterface}. + * + * @author Jules Pietri + */ +class InvalidFormatterException extends \InvalidArgumentException +{ + /** + * @param string $formatterClass The invalid formatter class + * @param string $exporterClass The exporter class + * @param string $expectedInterface The expected formatter interface + */ + public function __construct($formatterClass, $exporterClass, $expectedInterface) + { + parent::__construct(sprintf('The exporter "%s" expects formatters implementing "%", but was given "%s" class.', $exporterClass, $expectedInterface, $formatterClass)); + } +} diff --git a/src/Symfony/Component/ValueExporter/Exporter/AbstractValueExporter.php b/src/Symfony/Component/ValueExporter/Exporter/AbstractValueExporter.php new file mode 100644 index 0000000000000..11c0bffef0da4 --- /dev/null +++ b/src/Symfony/Component/ValueExporter/Exporter/AbstractValueExporter.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ValueExporter\Exporter; + +use Symfony\Component\ValueExporter\Exception\InvalidFormatterException; +use Symfony\Component\ValueExporter\Formatter\ExpandedFormatterTrait; +use Symfony\Component\ValueExporter\Formatter\FormatterInterface; + +/** + * ValueExporterInterface implementations export PHP values. + * + * @author Jules Pietri + */ +abstract class AbstractValueExporter implements ValueExporterInterface +{ + /** + * @var int + */ + protected $depth; + /** + * @var bool + */ + protected $expand; + + /** + * The supported formatter interface. + * + * @var string + */ + protected $formatterInterface = FormatterInterface::class; + + /** + * An array indexed by formatter FQCN with a corresponding priority as value. + * + * @var int[] + */ + private $formatters = array(); + + /** + * An array of formatters instances sorted by priority or null. + * + * @var FormatterInterface[]|null + */ + private $sortedFormatters; + + /** + * An array of cached formatters instances by their FQCN. + * + * @var FormatterInterface[] + */ + private $cachedFormatters = array(); + + /** + * Takes {@link FormatterInterface} FQCN as arguments. + * + * They will be called in the given order. + * Alternatively, instead of a class, you can pass an array with + * a class and its priority {@see self::addFormatters}. + */ + final public function __construct() + { + $this->addFormatters(func_get_args()); + } + + /** + * {@inheritdoc} + */ + final public function addFormatters(array $formatters) + { + $this->sortedFormatters = null; + + foreach ($formatters as $formatter) { + if (is_array($formatter)) { + $priority = (int) $formatter[1]; + $formatterClass = $formatter[0]; + } else { + $priority = 0; + $formatterClass = $formatter; + } + + if (!in_array($this->formatterInterface, class_implements($formatterClass), true)) { + throw new InvalidFormatterException($formatterClass, static::class, $this->formatterInterface); + } + + // Using the class as key prevents duplicate and allows to + // dynamically change the priority + $this->formatters[$formatterClass] = $priority; + } + } + + /** + * @return FormatterInterface[] + */ + final protected function formatters() + { + if (null === $this->sortedFormatters) { + arsort($this->formatters); + + foreach (array_keys($this->formatters) as $formatterClass) { + if (isset($this->cachedFormatters[$formatterClass])) { + $this->sortedFormatters[] = $this->cachedFormatters[$formatterClass]; + + continue; + } + + $formatter = new $formatterClass(); + + if (in_array(ExpandedFormatterTrait::class, class_uses($formatterClass), true)) { + /* @var ExpandedFormatterTrait $formatter */ + $formatter->setExporter($this); + } + + $this->sortedFormatters[] = $this->cachedFormatters[$formatterClass] = $formatter; + } + } + + return $this->sortedFormatters; + } +} diff --git a/src/Symfony/Component/ValueExporter/Exporter/ValueExporterInterface.php b/src/Symfony/Component/ValueExporter/Exporter/ValueExporterInterface.php new file mode 100644 index 0000000000000..cdc9275a72637 --- /dev/null +++ b/src/Symfony/Component/ValueExporter/Exporter/ValueExporterInterface.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ValueExporter\Exporter; + +use Symfony\Component\ValueExporter\Exception\InvalidFormatterException; +use Symfony\Component\ValueExporter\Exporter; +use Symfony\Component\ValueExporter\Formatter\FormatterInterface; + +/** + * ValueExporterInterface implementations export PHP values. + * + * An implementation can rely on {@link FormatterInterface} implementations + * to handle specific types of value. + * + * @author Jules Pietri + */ +interface ValueExporterInterface +{ + /** + * Exports a PHP value. + * + * ValueExporter instance should always deal with array or \Traversable + * values first in order to handle depth and expand arguments. + * + * Usually you don't need to define the depth but it will be incremented + * in recursive calls. When expand is false any expandable values such as + * arrays or objects should be inline in their exported representation. + * + * @param mixed $value The PHP value to export + * @param int $depth The level of indentation + * @param bool $expand Whether to inline or expand nested values + */ + public function exportValue($value, $depth = 1, $expand = false); + + /** + * Adds {@link FormatterInterface} that will be called by priority. + * + * @param (FormatterInterface|array)[] $formatters + * + * @throws InvalidFormatterException If the exporter does not support a given formatter + */ + public function addFormatters(array $formatters); +} diff --git a/src/Symfony/Component/ValueExporter/Exporter/ValueToStringExporter.php b/src/Symfony/Component/ValueExporter/Exporter/ValueToStringExporter.php new file mode 100644 index 0000000000000..480c6bf7a5abb --- /dev/null +++ b/src/Symfony/Component/ValueExporter/Exporter/ValueToStringExporter.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ValueExporter\Exporter; + +use Symfony\Component\ValueExporter\Formatter\StringFormatterInterface; + +/** + * @author Fabien Potencier + * @author Bernhard Schussek + * @author Quentin Schuler + * @author Jules Pietri + */ +class ValueToStringExporter extends AbstractValueExporter +{ + protected $formatterInterface = StringFormatterInterface::class; + + public function exportValue($value, $depth = 1, $expand = false) + { + // Use set properties for recursive calls + $depth = null === $this->depth ? $depth : $this->depth; + $expand = null === $this->expand ? $expand : $this->expand; + // Arrays have to be handled first to deal with nested level and depth, + // this implementation intentionally ignores \Traversable values. + // Therefor, \Traversable instances might be treated as objects unless + // implementing a {@link StringFormatterInterface} and passing it to + // the exporter in order to support them. + if (is_array($value) && !is_callable($value)) { + if (empty($value)) { + return 'array()'; + } + $indent = str_repeat(' ', $depth); + + $a = array(); + foreach ($value as $k => $v) { + if (is_array($v) && !empty($v)) { + $this->expand = true; + $this->depth = $depth + 1; + } + $a[] = sprintf('%s => %s', is_string($k) ? sprintf("'%s'", $k) : $k, $this->exportValue($v)); + $this->depth = null; + $this->expand = null; + } + if ($expand) { + return sprintf("array(\n%s%s\n%s)", $indent, implode(sprintf(", \n%s", $indent), $a), str_repeat(' ', $depth - 1)); + } + + $s = sprintf('array(%s)', implode(', ', $a)); + + if (80 > strlen($s)) { + return $s; + } + + return sprintf("array(\n%s%s\n)", $indent, implode(sprintf(",\n%s", $indent), $a)); + } + // Not an array, test each formatter + foreach ($this->formatters() as $formatter) { + /** @var StringFormatterInterface $formatter */ + if ($formatter->supports($value)) { + return $formatter->formatToString($value); + } + } + // Fallback on default + if (is_object($value)) { + if (method_exists($value, '__toString')) { + return sprintf('object(%s) "%s"', get_class($value), $value); + } + + return sprintf('object(%s)', get_class($value)); + } + if (is_resource($value)) { + return sprintf('resource(%s#%d)', get_resource_type($value), $value); + } + if (is_float($value)) { + return sprintf('(float) %s', $value); + } + if (is_int($value)) { + return sprintf('(int) %d', $value); + } + if (is_string($value)) { + return sprintf('"%s"', $value); + } + if (null === $value) { + return 'null'; + } + if (false === $value) { + return 'false'; + } + if (true === $value) { + return 'true'; + } + + return (string) $value; + } +} diff --git a/src/Symfony/Component/ValueExporter/Formatter/CallableToStringFormatter.php b/src/Symfony/Component/ValueExporter/Formatter/CallableToStringFormatter.php new file mode 100644 index 0000000000000..8240919bf01eb --- /dev/null +++ b/src/Symfony/Component/ValueExporter/Formatter/CallableToStringFormatter.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ValueExporter\Formatter; + +/** + * Returns a string representation of a string or array callable. + * + * @author Jules Pietri + */ +class CallableToStringFormatter implements StringFormatterInterface +{ + /** + * {@inheritdoc} + */ + public function supports($value) + { + return is_callable($value) && !$value instanceof \Closure; + } + + /** + * {@inheritdoc} + */ + public function formatToString($value) + { + if (is_string($value)) { + return sprintf('(function) "%s"', $value); + } + + $caller = is_object($value) ? get_class($value) : (is_object($value[0]) ? get_class($value[0]) : $value[0]); + if (is_object($value) || (is_object($value[0]) && isset($value[1]) && '__invoke' === $value[1])) { + return sprintf('(invokable) "%s"', $caller); + } + + $method = $value[1]; + if (false !== $cut = strpos($method, $caller)) { + $method = substr($method, $cut); + } + + if ((new \ReflectionMethod($caller, $method))->isStatic()) { + return sprintf('(static) "%s::%s"', $caller, $method); + } + + return sprintf('(callable) "%s::%s"', $caller, $method); + } +} diff --git a/src/Symfony/Component/ValueExporter/Formatter/DateTimeToStringFormatter.php b/src/Symfony/Component/ValueExporter/Formatter/DateTimeToStringFormatter.php new file mode 100644 index 0000000000000..efa4b0b6ccc26 --- /dev/null +++ b/src/Symfony/Component/ValueExporter/Formatter/DateTimeToStringFormatter.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ValueExporter\Formatter; + +/** + * Returns a string representation of a DateTimeInterface instance. + * + * Based on the contribution by @scuben (https://github.com/scuben) + * https://github.com/symfony/symfony/commit/a1762fb65423dc94d69c5fb6abaed37f2ad576e6 + * + * @author Jules Pietri + */ +class DateTimeToStringFormatter implements StringFormatterInterface +{ + /** + * {@inheritdoc} + */ + public function supports($value) + { + return $value instanceof \DateTimeInterface; + } + + /** + * {@inheritdoc} + */ + public function formatToString($value) + { + return sprintf('object(%s) - %s', get_class($value), $value->format(\DateTime::ISO8601)); + } +} diff --git a/src/Symfony/Component/ValueExporter/Formatter/EntityToStringFormatter.php b/src/Symfony/Component/ValueExporter/Formatter/EntityToStringFormatter.php new file mode 100644 index 0000000000000..162fc2fe0dd22 --- /dev/null +++ b/src/Symfony/Component/ValueExporter/Formatter/EntityToStringFormatter.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ValueExporter\Formatter; + +/** + * Returns a string representation of a DateTimeInterface instance. + * + * Based on the contribution by @scuben (https://github.com/scuben) + * https://github.com/symfony/symfony/commit/a1762fb65423dc94d69c5fb6abaed37f2ad576e6 + * + * @author Jules Pietri + */ +class EntityToStringFormatter implements StringFormatterInterface +{ + /** + * {@inheritdoc} + */ + public function supports($value) + { + return is_object($value) + && !$value instanceof \Closure + && (isset($value->id) || is_callable(array($value, 'id')) || is_callable(array($value, 'getId'))) + ; + } + + /** + * {@inheritdoc} + */ + public function formatToString($value) + { + $id = isset($value->id) ? $value->id : (is_callable(array($value, 'id')) ? $value->id() : $value->getId()); + + if (method_exists($value, '__toString')) { + return sprintf('entity:%s(%s) "%s"', $id, get_class($value), $value); + } + + return sprintf('entity:%s(%s)', $id, get_class($value)); + } +} diff --git a/src/Symfony/Component/ValueExporter/Formatter/ExpandedFormatterTrait.php b/src/Symfony/Component/ValueExporter/Formatter/ExpandedFormatterTrait.php new file mode 100644 index 0000000000000..24816b818d25d --- /dev/null +++ b/src/Symfony/Component/ValueExporter/Formatter/ExpandedFormatterTrait.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ValueExporter\Formatter; + +use Symfony\Component\ValueExporter\Exporter\ValueExporterInterface; + +/** + * ExpandedFormatter. + * + * A trait holding the {@link ValueExporterInterface} to export nested values. + * + * @author Jules Pietri + */ +trait ExpandedFormatterTrait +{ + /** + * @var ValueExporterInterface + */ + private $exporter; + + /** + * Sets the exporter to call on nested values. + * + * @param ValueExporterInterface $exporter The exporter + */ + final public function setExporter(ValueExporterInterface $exporter) + { + $this->exporter = $exporter; + } + + /** + * @param mixed $value The nested value to export + * + * @return mixed The exported nested value + */ + final protected function export($value) + { + return $this->exporter->exportValue($value); + } +} diff --git a/src/Symfony/Component/ValueExporter/Formatter/FormatterInterface.php b/src/Symfony/Component/ValueExporter/Formatter/FormatterInterface.php new file mode 100644 index 0000000000000..e32504905b05a --- /dev/null +++ b/src/Symfony/Component/ValueExporter/Formatter/FormatterInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ValueExporter\Formatter; + +/** + * FormatterInterface. + * + * Returns a formatted representation of (a) supported type(s) of PHP value. + * + * @author Jules Pietri + */ +interface FormatterInterface +{ + /** + * Returns whether the formatter can format the type(s) of the given value. + * + * @param mixed $value The given value to format + * + * @return bool Whether the given value can be formatted + */ + public function supports($value); +} diff --git a/src/Symfony/Component/ValueExporter/Formatter/PhpIncompleteClassToStringFormatter.php b/src/Symfony/Component/ValueExporter/Formatter/PhpIncompleteClassToStringFormatter.php new file mode 100644 index 0000000000000..b3ba602d1bed5 --- /dev/null +++ b/src/Symfony/Component/ValueExporter/Formatter/PhpIncompleteClassToStringFormatter.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ValueExporter\Formatter; + +/** + * Returns a string representation of a __PHP_Incomplete_Class instance. + * + * @author Yonel Ceruto González + * @author Jules Pietri + */ +class PhpIncompleteClassToStringFormatter implements StringFormatterInterface +{ + /** + * {@inheritdoc} + */ + public function supports($value) + { + return $value instanceof \__PHP_Incomplete_Class; + } + + /** + * {@inheritdoc} + */ + public function formatToString($value) + { + return sprintf('__PHP_Incomplete_Class(%s)', $this->getClassNameFromIncomplete($value)); + } + + private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value) + { + $array = new \ArrayObject($value); + + return $array['__PHP_Incomplete_Class_Name']; + } +} diff --git a/src/Symfony/Component/ValueExporter/Formatter/StringFormatterInterface.php b/src/Symfony/Component/ValueExporter/Formatter/StringFormatterInterface.php new file mode 100644 index 0000000000000..8c6fa260e98a3 --- /dev/null +++ b/src/Symfony/Component/ValueExporter/Formatter/StringFormatterInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ValueExporter\Formatter; + +/** + * StringFormatter. + * + * Returns a string representation of a given value. + * + * @author Jules Pietri + */ +interface StringFormatterInterface extends FormatterInterface +{ + /** + * Returns a given value formatted to string. + * + * @param mixed $value The given value to format to string + * + * @return string A string representation of the given value + */ + public function formatToString($value); +} diff --git a/src/Symfony/Component/ValueExporter/Formatter/TraversableToStringFormatter.php b/src/Symfony/Component/ValueExporter/Formatter/TraversableToStringFormatter.php new file mode 100644 index 0000000000000..db45b5d193637 --- /dev/null +++ b/src/Symfony/Component/ValueExporter/Formatter/TraversableToStringFormatter.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ValueExporter\Formatter; + +/** + * Returns a string representation of an instance implementing \Traversable. + * + * @author Jules Pietri + */ +class TraversableToStringFormatter implements StringFormatterInterface +{ + use ExpandedFormatterTrait; + + /** + * {@inheritdoc} + */ + public function supports($value) + { + return $value instanceof \Traversable; + } + + /** + * {@inheritdoc} + */ + public function formatToString($value) + { + $nested = array(); + foreach ($value as $k => $v) { + $nested[] = sprintf('%s => %s', is_string($k) ? sprintf("'%s'", $k) : $k, $this->export($v)); + } + + return sprintf("Traversable:\"%s\"(\n %s\n)", get_class($value), implode(",\n ", $nested)); + } +} diff --git a/src/Symfony/Component/ValueExporter/LICENSE.txt b/src/Symfony/Component/ValueExporter/LICENSE.txt new file mode 100644 index 0000000000000..0564c5a9b7f1f --- /dev/null +++ b/src/Symfony/Component/ValueExporter/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2016 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/ValueExporter/README.md b/src/Symfony/Component/ValueExporter/README.md new file mode 100644 index 0000000000000..cde83ef943a37 --- /dev/null +++ b/src/Symfony/Component/ValueExporter/README.md @@ -0,0 +1,16 @@ +ValueExporter Component +======================= + +The ValueExporter component provides mechanisms to export any arbitrary +PHP variable in a desired format. Built on top, it provides a `to_string()` +function that you can safely use instead of casting `(string) $value`, finely +represented thanks to formatters. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/value_exporter/introduction.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Component/ValueExporter/Resources/functions/to_string.php b/src/Symfony/Component/ValueExporter/Resources/functions/to_string.php new file mode 100644 index 0000000000000..367fca038e77b --- /dev/null +++ b/src/Symfony/Component/ValueExporter/Resources/functions/to_string.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\ValueExporter\ValueExporter; + +if (!function_exists('to_string')) { + /** + * @author Nicolas Grekas + * @author Jules Pietri + */ + function to_string($value, $depth = 1, $expand = false) + { + return ValueExporter::export($value, $depth, $expand); + } +} diff --git a/src/Symfony/Component/ValueExporter/Tests/Fixtures/Entity.php b/src/Symfony/Component/ValueExporter/Tests/Fixtures/Entity.php new file mode 100644 index 0000000000000..06c38e984552a --- /dev/null +++ b/src/Symfony/Component/ValueExporter/Tests/Fixtures/Entity.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ValueExporter\Tests\Fixtures; + +/** + * Entity with an id getter. + * + * @author Jules Pietri + */ +class Entity +{ + private $id; + + public function __construct($id) + { + $this->id = $id; + } + + public function getId() + { + return $this->id; + } +} diff --git a/src/Symfony/Component/ValueExporter/Tests/Fixtures/EntityImplementingToString.php b/src/Symfony/Component/ValueExporter/Tests/Fixtures/EntityImplementingToString.php new file mode 100644 index 0000000000000..370981ce0f73c --- /dev/null +++ b/src/Symfony/Component/ValueExporter/Tests/Fixtures/EntityImplementingToString.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ValueExporter\Tests\Fixtures; + +/** + * Entity with an id getter. + * + * @author Jules Pietri + */ +class EntityImplementingToString +{ + public $id; + private $name; + + public function __construct($id, $name) + { + $this->id = $id; + $this->name = $name; + } + + public function __toString() + { + return (string) $this->name; + } +} diff --git a/src/Symfony/Component/ValueExporter/Tests/Fixtures/ObjectImplementingToString.php b/src/Symfony/Component/ValueExporter/Tests/Fixtures/ObjectImplementingToString.php new file mode 100644 index 0000000000000..9455301fd5c90 --- /dev/null +++ b/src/Symfony/Component/ValueExporter/Tests/Fixtures/ObjectImplementingToString.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ValueExporter\Tests\Fixtures; + +/** + * Entity with an id getter. + * + * @author Jules Pietri + */ +class ObjectImplementingToString +{ + private $name; + + public function __construct($name) + { + $this->name = $name; + } + + public function __toString() + { + return (string) $this->name; + } +} diff --git a/src/Symfony/Component/ValueExporter/Tests/Fixtures/PublicEntity.php b/src/Symfony/Component/ValueExporter/Tests/Fixtures/PublicEntity.php new file mode 100644 index 0000000000000..310dd32b716a6 --- /dev/null +++ b/src/Symfony/Component/ValueExporter/Tests/Fixtures/PublicEntity.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ValueExporter\Tests\Fixtures; + +/** + * Entity with a public id. + * + * @author Jules Pietri + */ +class PublicEntity +{ + public $id; + + public function __construct($id) + { + $this->id = $id; + } +} diff --git a/src/Symfony/Component/ValueExporter/Tests/Fixtures/TraversableInstance.php b/src/Symfony/Component/ValueExporter/Tests/Fixtures/TraversableInstance.php new file mode 100644 index 0000000000000..6a3bfc32a2aed --- /dev/null +++ b/src/Symfony/Component/ValueExporter/Tests/Fixtures/TraversableInstance.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ValueExporter\Tests\Fixtures; + +/** + * TraversableInstance. + * + * @author Jules Pietri + */ +class TraversableInstance implements \IteratorAggregate +{ + public $property1 = 'value1'; + public $property2 = 'value2'; + + /** + * {@inheritdoc} + */ + public function getIterator() + { + return new \ArrayIterator($this); + } +} diff --git a/src/Symfony/Component/ValueExporter/Tests/ValueExporterTest.php b/src/Symfony/Component/ValueExporter/Tests/ValueExporterTest.php new file mode 100644 index 0000000000000..ec544c6000112 --- /dev/null +++ b/src/Symfony/Component/ValueExporter/Tests/ValueExporterTest.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ValueExporter\Tests; + +use Symfony\Component\ValueExporter\Formatter\TraversableToStringFormatter; +use Symfony\Component\ValueExporter\Tests\Fixtures\Entity; +use Symfony\Component\ValueExporter\Tests\Fixtures\EntityImplementingToString; +use Symfony\Component\ValueExporter\Tests\Fixtures\ObjectImplementingToString; +use Symfony\Component\ValueExporter\Tests\Fixtures\PublicEntity; +use Symfony\Component\ValueExporter\Tests\Fixtures\TraversableInstance; +use Symfony\Component\ValueExporter\ValueExporter; + +class ValueExporterTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider valueProvider + */ + public function testExportValue($value, $string) + { + $this->assertSame($string, ValueExporter::export($value)); + } + + /** + * @dataProvider valueProvider + */ + public function testToStringFunctionWrapper($value, $string) + { + $this->assertSame($string, to_string($value)); + } + + public function testExportValueExpanded() + { + $value = array( + array(ValueExporter::class, 'export'), + ); + + $exportedValue = << (static) "Symfony\Component\ValueExporter\ValueExporter::export" +) +EOT; + + $this->assertSame($exportedValue, ValueExporter::export($value, 1, true)); + } + + public function testExportTraversable() + { + ValueExporter::addFormatters(array(TraversableToStringFormatter::class)); + + $value = new TraversableInstance(); + $exportedValue = << "value1", + 'property2' => "value2" +) +EOT; + + $this->assertSame($exportedValue, ValueExporter::export($value)); + } + + public function valueProvider() + { + $foo = new \__PHP_Incomplete_Class(); + $array = new \ArrayObject($foo); + $array['__PHP_Incomplete_Class_Name'] = 'AppBundle/Foo'; + + return array( + 'null' => array(null, 'null'), + 'true' => array(true, 'true'), + 'false' => array(false, 'false'), + 'int' => array(4, '(int) 4'), + 'float' => array(4.5, '(float) 4.5'), + 'string' => array('test', '"test"'), + 'empty array' => array(array(), 'array()'), + 'numeric array' => array( + array(0 => null, 1 => true, 2 => 1, 3 => '2', 4 => new \stdClass()), + 'array(0 => null, 1 => true, 2 => (int) 1, 3 => "2", 4 => object(stdClass))', + ), + 'mixed keys array' => array( + array(0 => 0, '1' => 'un', 'key' => 4.5), + 'array(0 => (int) 0, 1 => "un", \'key\' => (float) 4.5)', + ), + 'object implementing to string' => array( + new ObjectImplementingToString('test'), + 'object(Symfony\Component\ValueExporter\Tests\Fixtures\ObjectImplementingToString) "test"', + ), + 'closure' => array(function() {}, 'object(Closure)'), + 'callable string' => array('strlen', '(function) "strlen"'), + 'callable array' => array( + array($this, 'testExportValue'), + '(callable) "Symfony\Component\ValueExporter\Tests\ValueExporterTest::testExportValue"', + ), + 'invokable object' => array($this, '(invokable) "Symfony\Component\ValueExporter\Tests\ValueExporterTest"'), + 'invokable object as array' => array(array($this, '__invoke'), '(invokable) "Symfony\Component\ValueExporter\Tests\ValueExporterTest"'), + 'datetime' => array( + new \DateTime('2014-06-10 07:35:40', new \DateTimeZone('UTC')), + 'object(DateTime) - 2014-06-10T07:35:40+0000', + ), + 'datetime immutable' => array( + new \DateTimeImmutable('2014-06-10 07:35:40', new \DateTimeZone('UTC')), + 'object(DateTimeImmutable) - 2014-06-10T07:35:40+0000', + ), + 'php incomplete class' => array($foo, '__PHP_Incomplete_Class(AppBundle/Foo)'), + 'entity' => array(new Entity(23), 'entity:23(Symfony\Component\ValueExporter\Tests\Fixtures\Entity)'), + 'public entity' => array(new PublicEntity(23), 'entity:23(Symfony\Component\ValueExporter\Tests\Fixtures\PublicEntity)'), + 'entity implementing to string' => array( + new EntityImplementingToString(23, 'test'), + 'entity:23(Symfony\Component\ValueExporter\Tests\Fixtures\EntityImplementingToString) "test"', + ), + ); + } + + public function __invoke() + { + return 'TEST'; + } +} diff --git a/src/Symfony/Component/ValueExporter/ValueExporter.php b/src/Symfony/Component/ValueExporter/ValueExporter.php new file mode 100644 index 0000000000000..974a4e8a0ebec --- /dev/null +++ b/src/Symfony/Component/ValueExporter/ValueExporter.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ValueExporter; + +use Symfony\Component\ValueExporter\Exporter\ValueExporterInterface; +use Symfony\Component\ValueExporter\Exporter\ValueToStringExporter; +use Symfony\Component\ValueExporter\Formatter\CallableToStringFormatter; +use Symfony\Component\ValueExporter\Formatter\DateTimeToStringFormatter; +use Symfony\Component\ValueExporter\Formatter\EntityToStringFormatter; +use Symfony\Component\ValueExporter\Formatter\FormatterInterface; +use Symfony\Component\ValueExporter\Formatter\PhpIncompleteClassToStringFormatter; + +// Load the global to_string() function +require_once __DIR__.'/Resources/functions/to_string.php'; + +/** + * @author Nicolas Grekas + * @author Jules Pietri + */ +class ValueExporter +{ + private static $handler; + private static $exporter; + private static $formatters = array(); + + public static function export($value, $depth = 1, $expand = false) + { + if (null === self::$handler) { + $exporter = self::$exporter ?: new ValueToStringExporter( + CallableToStringFormatter::class, + DateTimeToStringFormatter::class, + EntityToStringFormatter::class, + PhpIncompleteClassToStringFormatter::class + ); + $exporter->addFormatters(self::$formatters); + // Clear formatters + self::$formatters = array(); + self::$handler = function ($value, $depth = 1, $expand = false) use ($exporter) { + return $exporter->exportValue($value, $depth, $expand); + }; + } + + return call_user_func(self::$handler, $value, $depth, $expand); + } + + public static function setHandler(callable $callable = null) + { + $prevHandler = self::$handler; + self::$handler = $callable; + + return $prevHandler; + } + + /** + * Sets a new {@link ValueExporterInterface} instance as exporter. + * + * @param ValueExporterInterface $exporter The exporter instance + */ + public static function setExporter(ValueExporterInterface $exporter) + { + self::$handler = null; + self::$exporter = $exporter; + self::$formatters = array(); + } + + /** + * Adds {@link FormatterInterface} to the {@link ValueExporterInterface}. + * + * You can simple pass an instance or an array with the instance and the priority: + * + * + * ValueExporter::addFormatters(array( + * new AcmeFormatter, + * array(new AcmeOtherFormatter(), 10) + * ); + * + * + * @param mixed $formatters An array of FormatterInterface instances and/or + * arrays holding an instance and its priority + */ + public static function addFormatters($formatters) + { + self::$handler = null; + foreach ($formatters as $formatter) { + self::$formatters[] = $formatter; + } + } +} diff --git a/src/Symfony/Component/ValueExporter/composer.json b/src/Symfony/Component/ValueExporter/composer.json new file mode 100644 index 0000000000000..48a6b310ecf79 --- /dev/null +++ b/src/Symfony/Component/ValueExporter/composer.json @@ -0,0 +1,49 @@ +{ + "name": "symfony/value-exporter", + "type": "library", + "description": "Symfony mechanism for formatting PHP variables", + "keywords": ["export", "php values", "logs"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Jules Pietri", + "email": "jules@heahprod.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.5.9" + }, + "suggest": { + "symfony/var-dumper": "To dump PHP values" + }, + "autoload": { + "files": [ "Resources/functions/to_string.php" ], + "psr-4": { "Symfony\\Component\\ValueExporter\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + } +} diff --git a/src/Symfony/Component/ValueExporter/phpunit.xml.dist b/src/Symfony/Component/ValueExporter/phpunit.xml.dist new file mode 100644 index 0000000000000..4e1bb0a660cd4 --- /dev/null +++ b/src/Symfony/Component/ValueExporter/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy