Skip to content

[PoC] [ValueExporter] extract HttpKernel/DataCollector util to its own component #18450

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 12 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/Symfony/Component/ValueExporter/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
composer.lock
phpunit.xml
vendor/
7 changes: 7 additions & 0 deletions src/Symfony/Component/ValueExporter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CHANGELOG
=========

3.2.0
-----

* introducing the component
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\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 <jules@heahprod.com>
*/
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));
}
}
128 changes: 128 additions & 0 deletions src/Symfony/Component/ValueExporter/Exporter/AbstractValueExporter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\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 <jules@heahprod.com>
*/
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];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same with the list thing if you decide to change it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, my first draw used list. I end up using that notation because I find it more readable, especially when casting to int :)
Since the priority in the value used to sort with arsort I want to be sure some '10' string doesn't mess with the order, do you think that's only paranoia 😨?

} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could actually do some unpacking here list($formatter, $priority) = $formatter;. I don't really think the int cast is necessary

$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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\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 <jules@heahprod.com>
*/
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);
}
103 changes: 103 additions & 0 deletions src/Symfony/Component/ValueExporter/Exporter/ValueToStringExporter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\ValueExporter\Exporter;

use Symfony\Component\ValueExporter\Formatter\StringFormatterInterface;

/**
* @author Fabien Potencier <fabien@symfony.com>
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Quentin Schuler <qschuler@neosyne.com>
* @author Jules Pietri <jules@heahprod.com>
*/
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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\ValueExporter\Formatter;

/**
* Returns a string representation of a string or array callable.
*
* @author Jules Pietri <jules@heahprod.com>
*/
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);
}
}
Loading