Skip to content

Commit

Permalink
Merge pull request #31: Fix Client helpers bugs and add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
roxblnfk authored Dec 5, 2023
2 parents 11d4d52 + 1c7ff81 commit fab397d
Show file tree
Hide file tree
Showing 11 changed files with 291 additions and 53 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"php-http/message": "^1.15",
"psr/http-message": "^1.1 || ^2",
"symfony/console": "^5.4 || ^6 || ^7",
"symfony/var-dumper": "^6 || ^7"
"symfony/var-dumper": "^6.3 || ^7"
},
"require-dev": {
"dereuromark/composer-prefer-lowest": "^0.1.10",
Expand Down
3 changes: 3 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
colors="true"
processIsolation="false"
executionOrder="random"
failOnRisky="true"
failOnWarning="true"
stopOnFailure="false"
stopOnError="false"
stderr="true"
Expand All @@ -22,6 +24,7 @@
</include>
<exclude>
<directory>tests</directory>
<directory>src/Test</directory>
</exclude>
</source>
</phpunit>
1 change: 1 addition & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<ignoreFiles>
<directory name="vendor"/>
<directory name="src/Test"/>
<directory name="tests"/>
</ignoreFiles>
</projectFiles>
</psalm>
71 changes: 41 additions & 30 deletions src/Client/TrapHandle.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use Buggregator\Trap\Client\TrapHandle\Counter;
use Buggregator\Trap\Client\TrapHandle\Dumper as VarDumper;
use Buggregator\Trap\Client\TrapHandle\StackTrace;
use Buggregator\Trap\Client\TrapHandle\StaticState;
use Symfony\Component\VarDumper\Caster\TraceStub;

/**
Expand All @@ -18,6 +18,7 @@ final class TrapHandle
private int $times = 0;
private string $timesCounterKey = '';
private int $depth = 0;
private StaticState $staticState;

public static function fromArray(array $array): self
{
Expand Down Expand Up @@ -68,8 +69,8 @@ public function times(int $times, bool $fullStack = false): self
$this->times = $times;
$this->timesCounterKey = \sha1(\serialize(
$fullStack
? StackTrace::stackTrace()
: StackTrace::stackTrace()[0]
? $this->staticState->stackTrace
: $this->staticState->stackTrace[0]
));
return $this;
}
Expand All @@ -89,42 +90,52 @@ public function __destruct()

private function sendDump(): void
{
// Set default values if not set
if (!isset($_SERVER['VAR_DUMPER_FORMAT'], $_SERVER['VAR_DUMPER_SERVER'])) {
$_SERVER['VAR_DUMPER_FORMAT'] = 'server';
// todo use the config file in the future
$_SERVER['VAR_DUMPER_SERVER'] = '127.0.0.1:9912';
}
$staticState = StaticState::getValue();
// todo resolve race condition with fibers
StaticState::setState($this->staticState);

try {
// Set default values if not set
if (!isset($_SERVER['VAR_DUMPER_FORMAT'], $_SERVER['VAR_DUMPER_SERVER'])) {
$_SERVER['VAR_DUMPER_FORMAT'] = 'server';
// todo use the config file in the future
$_SERVER['VAR_DUMPER_SERVER'] = '127.0.0.1:9912';
}

// If there are no values - stack trace
if ($this->values === []) {
VarDumper::dump([
'cwd' => \getcwd(),
'trace' => new TraceStub((StackTrace::stackTrace(\getcwd()))),
], depth: $this->depth);
return;
}
// If there are no values - stack trace
if ($this->values === []) {
VarDumper::dump([
'cwd' => \getcwd(),
// todo StackTrace::stackTrace(\getcwd()) - add CWD
'trace' => new TraceStub($this->staticState->stackTrace),
], depth: $this->depth);
return;
}

// Dump single value
if (\array_keys($this->values) === [0]) {
VarDumper::dump($this->values[0], depth: $this->depth);
return;
}
// Dump single value
if (\array_keys($this->values) === [0]) {
VarDumper::dump($this->values[0], depth: $this->depth);
return;
}

// Dump sequence of values
/**
* @var string|int $key
* @var mixed $value
*/
foreach ($this->values as $key => $value) {
/** @psalm-suppress TooManyArguments */
VarDumper::dump($value, label: $key, depth: $this->depth);
// Dump sequence of values
/**
* @var string|int $key
* @var mixed $value
*/
foreach ($this->values as $key => $value) {
/** @psalm-suppress TooManyArguments */
VarDumper::dump($value, label: $key, depth: $this->depth);
}
} finally {
StaticState::setState($staticState);
}
}

private function __construct(
private array $values,
) {
$this->staticState = StaticState::new();
}

private function haveToSend(): bool
Expand Down
4 changes: 3 additions & 1 deletion src/Client/TrapHandle/ContextProvider/Source.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Buggregator\Trap\Client\TrapHandle\ContextProvider;

use Buggregator\Trap\Client\TrapHandle\StackTrace;
use Buggregator\Trap\Client\TrapHandle\StaticState;
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface;
Expand Down Expand Up @@ -43,7 +44,8 @@ public function __construct(string $charset = null, string $projectDir = null, F

public function getContext(): ?array
{
$trace = StackTrace::stackTrace((string)$this->projectDir);
\assert(StaticState::getValue() !== null);
$trace = StaticState::getValue()->stackTraceWithObjects;

$file = $trace[0]['file'];
$line = $trace[0]['line'];
Expand Down
38 changes: 28 additions & 10 deletions src/Client/TrapHandle/Dumper.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
use Symfony\Component\VarDumper\Caster\ReflectionCaster;
use Symfony\Component\VarDumper\Cloner\DumperInterface;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
use Symfony\Component\VarDumper\Dumper\ContextProvider\CliContextProvider;
use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface;
use Symfony\Component\VarDumper\Dumper\ContextProvider\RequestContextProvider;
use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider;
use Symfony\Component\VarDumper\Dumper\ContextualizedDumper;
use Symfony\Component\VarDumper\Dumper\DataDumperInterface;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
use Symfony\Component\VarDumper\Dumper\ServerDumper;

Expand All @@ -26,7 +28,7 @@
final class Dumper
{
/** @var null|Closure(mixed, string|null, int): mixed */
private static ?Closure $handler;
private static ?Closure $handler = null;

public static function dump(mixed $var, string|int|null $label = null, int $depth = 0): mixed
{
Expand All @@ -42,6 +44,30 @@ public static function setHandler(callable $callable = null): ?Closure
return ([$callable, self::$handler] = [self::$handler, $callable === null ? null : $callable(...)])[0];
}

/**
* @return Closure(mixed, string|null, int): mixed
*/
public static function setDumper(?DataDumperInterface $dumper = null): Closure
{
if ($dumper === null) {
return self::registerHandler();
}

$cloner = new VarCloner();
/** @psalm-suppress InvalidArgument */
$cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO);

return self::$handler = static function (mixed $var, string|null $label = null, int $depth = 0)
use ($cloner, $dumper): ?string {
$var = $cloner->cloneVar($var);

$label === null or $var = $var->withContext(['label' => $label]);
$depth > 0 and $var = $var->withMaxDepth($depth);

return $dumper->dump($var);
};
}

/**
* @return Closure(mixed, string|null, int): mixed
*
Expand Down Expand Up @@ -75,15 +101,7 @@ private static function registerHandler(): Closure
$dumper = new ContextualizedDumper($dumper, [new SourceContextProvider()]);
}

return self::$handler = static function (mixed $var, string|null $label = null, int $depth = 0)
use ($cloner, $dumper): ?string {
$var = $cloner->cloneVar($var);

$label === null or $var = $var->withContext(['label' => $label]);
$depth > 0 and $var = $var->withMaxDepth($depth);

return $dumper->dump($var);
};
return self::setDumper($dumper);
}

/**
Expand Down
43 changes: 32 additions & 11 deletions src/Client/TrapHandle/StackTrace.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,48 @@

namespace Buggregator\Trap\Client\TrapHandle;

/**
*
* @psalm-type StackTraceWithObjects = list<array{
* function?: string,
* line?: int,
* file?: string,
* class?: class-string,
* type?: string,
* object?: object,
* args?: list<mixed>
* }>
* @psalm-type SimpleStackTrace = list<array{
* function?: string,
* line?: int,
* file?: string,
* class?: class-string,
* type?: string,
* args?: list<mixed>
* }>
*/
final class StackTrace
{
/**
* Returns a backtrace as an array.
* Removes the internal frames and the first next frames after them.
*
* @param string $baseDir Base directory for relative paths
* @return list<array{
* function?: string,
* line?: int,
* file?: string,
* class?: class-string,
* object?: object,
* type?: string,
* args?: list<mixed>
* }>
* @param bool $provideObjects Whether to provide objects in the stack trace
*
* @return ($provideObjects is true ? StackTraceWithObjects : SimpleStackTrace)
*/
public static function stackTrace(string $baseDir = ''): array
public static function stackTrace(string $baseDir = '', bool $provideObjects = false): array
{
$dir = $baseDir . \DIRECTORY_SEPARATOR;
$cwdLen = \strlen($dir);
$stack = [];
$internal = false;
foreach (\debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS) as $frame) {
foreach (
\debug_backtrace(
($provideObjects ? \DEBUG_BACKTRACE_PROVIDE_OBJECT : 0) | \DEBUG_BACKTRACE_IGNORE_ARGS
) as $frame
) {
if (\str_starts_with($frame['class'] ?? '', 'Buggregator\\Trap\\Client\\')) {
$internal = true;
$stack = [];
Expand Down
53 changes: 53 additions & 0 deletions src/Client/TrapHandle/StaticState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

namespace Buggregator\Trap\Client\TrapHandle;

/**
* @internal
* @psalm-internal Buggregator\Trap\Client
* @psalm-import-type SimpleStackTrace from StackTrace
* @psalm-import-type StackTraceWithObjects from StackTrace
*/
final class StaticState
{
/**
* @param SimpleStackTrace $stackTrace Simple stack trace without arguments and objects.
* @param StackTraceWithObjects $stackTraceWithObjects Stack trace without arguments but with objects.
*/
private function __construct(
public array $stackTrace = [],
public array $stackTraceWithObjects = [],
) {
}

private static ?StaticState $value = null;

/**
* @param SimpleStackTrace|null $stackTrace
* @param StackTraceWithObjects|null $stackTraceWithObjects
*/
public static function new(
array $stackTrace = null,
array $stackTraceWithObjects = null,
): self
{
$new = new self(
$stackTrace ?? StackTrace::stackTrace(provideObjects: false),
$stackTraceWithObjects ?? StackTrace::stackTrace(provideObjects: true),
);
self::setState($new);
return $new;
}

public static function setState(?self $state): void
{
self::$value = $state;
}

public static function getValue(): ?StaticState
{
return self::$value;
}
}
47 changes: 47 additions & 0 deletions tests/Unit/Client/Base.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

namespace Buggregator\Trap\Tests\Unit\Client;

use Buggregator\Trap\Client\TrapHandle\Dumper;
use Closure;
use PHPUnit\Framework\TestCase;
use Symfony\Component\VarDumper\Caster\ReflectionCaster;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\DataDumperInterface;

class Base extends TestCase
{
protected static Data $lastData;
protected static ?Closure $handler = null;

protected function setUp(): void
{
$cloner = new VarCloner();
/** @psalm-suppress InvalidArgument */
$cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO);
$dumper = $this->getMockBuilder(DataDumperInterface::class)
->getMock();
$dumper->expects($this->once())
->method('dump')
->with(
$this->callback(static function (Data $data): bool {
static::$lastData = $data;
return true;
})
)
->willReturnArgument(1);

Dumper::setDumper($dumper);

parent::setUp();
}

protected function tearDown(): void
{
Dumper::setDumper(null);
parent::tearDown();
}
}
Loading

0 comments on commit fab397d

Please sign in to comment.