feature #28422 [VarExporter] throw component-specific exceptions (nicolas-grekas)

This PR was merged into the 4.2-dev branch.

Discussion
----------

[VarExporter] throw component-specific exceptions

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

This makes "serializing/unserializing" with the component diverge a bit from native serialize/unserialize (wich can throw plain "Exception" instances), but I think we should still do it.

Commits
-------

2c444927bc [VarExporter] throw component-specific exceptions
This commit is contained in:
Nicolas Grekas 2018-09-10 11:27:28 +02:00
commit deae538245
8 changed files with 81 additions and 15 deletions

View File

@ -0,0 +1,20 @@
<?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\VarExporter\Exception;
class ClassNotFoundException extends \Exception implements ExceptionInterface
{
public function __construct(string $class, \Throwable $previous = null)
{
parent::__construct(sprintf('Class "%s" not found.', $class), 0, $previous);
}
}

View File

@ -0,0 +1,16 @@
<?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\VarExporter\Exception;
interface ExceptionInterface extends \Throwable
{
}

View File

@ -0,0 +1,20 @@
<?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\VarExporter\Exception;
class NotInstantiableTypeException extends \Exception implements ExceptionInterface
{
public function __construct(string $type)
{
parent::__construct(sprintf('Type "%s" is not instantiable.', $type));
}
}

View File

@ -11,6 +11,8 @@
namespace Symfony\Component\VarExporter\Internal;
use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
@ -31,14 +33,14 @@ class Exporter
*
* @return int
*
* @throws \Exception When a value cannot be serialized
* @throws NotInstantiableTypeException When a value cannot be serialized
*/
public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount, &$valuesAreStatic)
{
$refs = $values;
foreach ($values as $k => $value) {
if (\is_resource($value)) {
throw new \Exception(sprintf("Serialization of '%s' resource is not allowed", \get_resource_type($value)));
throw new NotInstantiableTypeException(\get_resource_type($value).' resource');
}
$refs[$k] = $objectsPool;
@ -77,9 +79,12 @@ class Exporter
$arrayValue = (array) $value;
if (!isset(Registry::$reflectors[$class])) {
// Might throw Exception("Serialization of '...' is not allowed")
Registry::getClassReflector($class);
serialize(Registry::$prototypes[$class]);
try {
serialize(Registry::$prototypes[$class]);
} catch (\Exception $e) {
throw new NotInstantiableTypeException($class, $e);
}
if (\method_exists($class, '__sleep')) {
Registry::getClassReflector($class, Registry::$instantiableWithoutConstructor[$class], Registry::$cloneable[$class]);
}

View File

@ -11,6 +11,9 @@
namespace Symfony\Component\VarExporter\Internal;
use Symfony\Component\VarExporter\Exception\ClassNotFoundException;
use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
@ -37,9 +40,7 @@ class Registry
try {
foreach ($serializables as $k => $v) {
if (false === $objects[$k] = unserialize($v)) {
throw new \Exception(error_get_last()['message'] ?? 'unserialize(): unknown error');
}
$objects[$k] = unserialize($v);
}
} finally {
ini_set('unserialize_callback_func', $unserializeCallback);
@ -64,6 +65,9 @@ class Registry
public static function getClassReflector($class, $instantiableWithoutConstructor = false, $cloneable = null)
{
if (!\class_exists($class)) {
throw new ClassNotFoundException($class);
}
$reflector = new \ReflectionClass($class);
if (self::$instantiableWithoutConstructor[$class] = $instantiableWithoutConstructor || !$reflector->isFinal()) {
@ -77,14 +81,14 @@ class Registry
if ('C:' === $proto && !$reflector->getMethod('unserialize')->isInternal()) {
$proto = null;
} elseif (false === $proto = @unserialize($proto.\strlen($class).':"'.$class.'":0:{}')) {
throw new \Exception(sprintf("Serialization of '%s' is not allowed", $class));
throw new NotInstantiableTypeException($class);
}
}
}
if (null === self::$cloneable[$class] = $cloneable) {
if (($proto instanceof \Reflector || $proto instanceof \ReflectionGenerator || $proto instanceof \ReflectionType || $proto instanceof \IteratorIterator || $proto instanceof \RecursiveIteratorIterator) && (!$proto instanceof \Serializable && !\method_exists($proto, '__wakeup'))) {
throw new \Exception(sprintf("Serialization of '%s' is not allowed", $class));
throw new NotInstantiableTypeException($class);
}
self::$cloneable[$class] = $reflector->isCloneable() && !$reflector->hasMethod('__clone');

View File

@ -16,7 +16,7 @@ It also provides a few improvements over `var_export()`/`serialize()`:
* the output is PSR-2 compatible;
* the output can be re-indented without messing up with `\r` or `\n` in the data
* missing classes throw a `ReflectionException` instead of being unserialized to
* missing classes throw a `ClassNotFoundException` instead of being unserialized to
`PHP_Incomplete_Class` objects;
* references involving `SplObjectStorage`, `ArrayObject` or `ArrayIterator`
instances are preserved;

View File

@ -21,8 +21,8 @@ class VarExporterTest extends TestCase
use VarDumperTestTrait;
/**
* @expectedException \ReflectionException
* @expectedExceptionMessage Class SomeNotExistingClass does not exist
* @expectedException \Symfony\Component\VarExporter\Exception\ClassNotFoundException
* @expectedExceptionMessage Class "SomeNotExistingClass" not found.
*/
public function testPhpIncompleteClassesAreForbidden()
{
@ -36,8 +36,8 @@ class VarExporterTest extends TestCase
/**
* @dataProvider provideFailingSerialization
* @expectedException \Exception
* @expectedExceptionMessageRegexp Serialization of '.*' is not allowed
* @expectedException \Symfony\Component\VarExporter\Exception\NotInstantiableTypeException
* @expectedExceptionMessageRegexp Type ".*" is not instantiable.
*/
public function testFailingSerialization($value)
{

View File

@ -11,6 +11,7 @@
namespace Symfony\Component\VarExporter;
use Symfony\Component\VarExporter\Exception\ExceptionInterface;
use Symfony\Component\VarExporter\Internal\Exporter;
use Symfony\Component\VarExporter\Internal\Hydrator;
use Symfony\Component\VarExporter\Internal\Registry;
@ -36,7 +37,7 @@ final class VarExporter
*
* @return string The value exported as PHP code
*
* @throws \Exception When the provided value cannot be serialized
* @throws ExceptionInterface When the provided value cannot be serialized
*/
public static function export($value, bool &$isStaticValue = null): string
{