-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[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
Closed
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
4d7c89b
[ValueExporter] extracted ValueExporter in its own component
HeahDude afadb5b
[ValueExporter] added CallableToStringFormatter
HeahDude 4eeb800
[ValueExporter] added abstract ExpandedFormatter
HeahDude 845aea1
[ValueExporter] added TraversableToStringFormatter
HeahDude 438b845
[ValueExporter] added EntityToStringFormatter
HeahDude eb1a34d
[ValueExporter] added priority to formatters
HeahDude 0e066a1
[ValueExporter] made ExpandedFormatter a trait
HeahDude 677c3c2
[ValueExporter] fixed class name's late static bind in AbstractValueE…
HeahDude 8f63b07
[ValueExporter] added support for formatters FQCN instead of instances
HeahDude a83684c
[ValueExporter] added support for objects implementing __toString()
HeahDude ab119f0
[ValueExporter] tweaked CallableToStringFormatter
HeahDude 826e8fb
[ValueExporter] fixed some doc blocks in AbstractValueExporter
HeahDude File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
composer.lock | ||
phpunit.xml | ||
vendor/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
CHANGELOG | ||
========= | ||
|
||
3.2.0 | ||
----- | ||
|
||
* introducing the component |
31 changes: 31 additions & 0 deletions
31
src/Symfony/Component/ValueExporter/Exception/InvalidFormatterException.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
128
src/Symfony/Component/ValueExporter/Exporter/AbstractValueExporter.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]; | ||
} else { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could actually do some unpacking here |
||
$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; | ||
} | ||
} |
52 changes: 52 additions & 0 deletions
52
src/Symfony/Component/ValueExporter/Exporter/ValueExporterInterface.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
103
src/Symfony/Component/ValueExporter/Exporter/ValueToStringExporter.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
54 changes: 54 additions & 0 deletions
54
src/Symfony/Component/ValueExporter/Formatter/CallableToStringFormatter.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 😨?