Fix serialization issue of InvalidTag

This commit is contained in:
Jaapio
2020-01-16 09:38:58 +01:00
parent f256f0b49e
commit ce65c06bb6
2 changed files with 100 additions and 0 deletions
+45
View File
@@ -4,7 +4,10 @@ declare(strict_types=1);
namespace phpDocumentor\Reflection\DocBlock\Tags;
use Closure;
use phpDocumentor\Reflection\DocBlock\Tag;
use ReflectionClass;
use ReflectionFunction;
use Throwable;
/**
@@ -56,12 +59,54 @@ final class InvalidTag implements Tag
public function withError(Throwable $exception) : self
{
$this->flattenExceptionBacktrace($exception);
$tag = new self($this->name, $this->body);
$tag->throwable = $exception;
return $tag;
}
/**
* Removes all complex types from backtrace
*
* Not all objects are serializable. So we need to remove them from the
* stored exception to be sure that we do not break existing library usage.
*/
private function flattenExceptionBacktrace(Throwable $exception) : void
{
$traceProperty = (new ReflectionClass('Exception'))->getProperty('trace');
$traceProperty->setAccessible(true);
$flatten = static function (&$value) {
if ($value instanceof Closure) {
$closureReflection = new ReflectionFunction($value);
$value = sprintf(
'(Closure at %s:%s)',
$closureReflection->getFileName(),
$closureReflection->getStartLine()
);
} elseif (is_object($value)) {
$value = sprintf('object(%s)', get_class($value));
} elseif (is_resource($value)) {
$value = sprintf('resource(%s)', get_resource_type($value));
}
};
do {
$trace = array_map(
static function($call) use ($flatten) {
array_walk_recursive($call['args'], $flatten);
return $call;
},
$exception->getTrace()
);
$traceProperty->setValue($exception, $trace);
} while ($exception = $exception->getPrevious());
$traceProperty->setAccessible(false);
}
public function render(?Formatter $formatter = null) : string
{
if ($formatter === null) {
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace phpDocumentor\Reflection\DocBlock\Tags;
use Exception;
use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
/**
@@ -28,6 +29,7 @@ final class InvalidTagTest extends TestCase
/**
* @covers ::withError
* @covers ::__toString
*/
public function testCreationWithError() : void
{
@@ -36,6 +38,59 @@ final class InvalidTagTest extends TestCase
self::assertSame('name', $tag->getName());
self::assertSame('@name Body', $tag->render());
self::assertSame('Body', (string)$tag);
self::assertSame($exception, $tag->getException());
}
public function testCreationWithErrorContainingClosure() : void
{
try {
$this->throwExceptionFromClosureWithClosureArgument();
} catch (Exception $e) {
$parentException = new Exception('test', 0, $e);
$tag = InvalidTag::create('Body', 'name')->withError($parentException);
self::assertSame('name', $tag->getName());
self::assertSame('@name Body', $tag->render());
self::assertSame($parentException, $tag->getException());
self::assertStringStartsWith('(Closure at', $tag->getException()->getPrevious()->getTrace()[0]['args'][0]);
self::assertStringContainsString(__FILE__, $tag->getException()->getPrevious()->getTrace()[0]['args'][0]);
self::assertEquals($parentException, unserialize(serialize($parentException)));
}
}
private function throwExceptionFromClosureWithClosureArgument()
{
$function = function() {
throw new InvalidArgumentException();
};
$function($function);
}
public function testCreationWithErrorContainingResource()
{
try {
$this->throwExceptionWithResourceArgument();
} catch (Exception $e) {
$parentException = new Exception('test', 0, $e);
$tag = InvalidTag::create('Body', 'name')->withError($parentException);
self::assertSame('name', $tag->getName());
self::assertSame('@name Body', $tag->render());
self::assertSame($parentException, $tag->getException());
self::assertStringStartsWith(
'resource(stream)',
$tag->getException()->getPrevious()->getTrace()[0]['args'][0])
;
self::assertEquals($parentException, unserialize(serialize($parentException)));
}
}
private function throwExceptionWithResourceArgument()
{
$function = function() {
throw new InvalidArgumentException();
};
$function(fopen(__FILE__, 'r'));
}
}