This commit is contained in:
Sampanna Rimal
2024-09-04 12:22:04 +05:45
parent 53c0140f58
commit 82fab174dc
203 changed files with 4255 additions and 1343 deletions

View File

@ -21,6 +21,7 @@
"require-dev": {
"ext-json": "*",
"phpunit/phpunit": "^9.3",
"laravel/serializable-closure": "^1.3",
"spatie/phpunit-snapshot-assertions": "^4.2",
"symfony/var-dumper": "^5.1"
},

View File

@ -3,6 +3,7 @@
namespace Spatie\Backtrace;
use Closure;
use Laravel\SerializableClosure\Support\ClosureStream;
use Spatie\Backtrace\Arguments\ArgumentReducers;
use Spatie\Backtrace\Arguments\ReduceArgumentsAction;
use Spatie\Backtrace\Arguments\Reducers\ArgumentReducer;
@ -171,15 +172,29 @@ class Backtrace
$reduceArgumentsAction = new ReduceArgumentsAction($this->resolveArgumentReducers());
foreach ($rawFrames as $rawFrame) {
$frames[] = new Frame(
$textSnippet = null;
if (
class_exists(ClosureStream::class)
&& substr($currentFile, 0, strlen(ClosureStream::STREAM_PROTO)) === ClosureStream::STREAM_PROTO
) {
$textSnippet = $currentFile;
$currentFile = ClosureStream::STREAM_PROTO.'://function()';
$currentLine -= 1;
}
$frame = new Frame(
$currentFile,
$currentLine,
$arguments,
$rawFrame['function'] ?? null,
$rawFrame['class'] ?? null,
$this->isApplicationFrame($currentFile)
$this->isApplicationFrame($currentFile),
$textSnippet
);
$frames[] = $frame;
$arguments = $this->withArguments
? $rawFrame['args'] ?? null
: null;

View File

@ -1,6 +1,6 @@
<?php
namespace Spatie\Backtrace;
namespace Spatie\Backtrace\CodeSnippets;
use RuntimeException;
@ -26,27 +26,21 @@ class CodeSnippet
return $this;
}
public function get(string $fileName): array
public function get(SnippetProvider $provider): array
{
if (! file_exists($fileName)) {
return [];
}
try {
$file = new File($fileName);
[$startLineNumber, $endLineNumber] = $this->getBounds($file->numberOfLines());
[$startLineNumber, $endLineNumber] = $this->getBounds($provider->numberOfLines());
$code = [];
$line = $file->getLine($startLineNumber);
$line = $provider->getLine($startLineNumber);
$currentLineNumber = $startLineNumber;
while ($currentLineNumber <= $endLineNumber) {
$code[$currentLineNumber] = rtrim(substr($line, 0, 250));
$line = $file->getNextLine();
$line = $provider->getNextLine();
$currentLineNumber++;
}
@ -56,9 +50,9 @@ class CodeSnippet
}
}
public function getAsString(string $fileName): string
public function getAsString(SnippetProvider $provider): string
{
$snippet = $this->get($fileName);
$snippet = $this->get($provider);
$snippetStrings = array_map(function (string $line, string $number) {
return "{$number} {$line}";

View File

@ -1,10 +1,10 @@
<?php
namespace Spatie\Backtrace;
namespace Spatie\Backtrace\CodeSnippets;
use SplFileObject;
class File
class FileSnippetProvider implements SnippetProvider
{
/** @var \SplFileObject */
protected $file;

View File

@ -0,0 +1,67 @@
<?php
namespace Spatie\Backtrace\CodeSnippets;
class LaravelSerializableClosureSnippetProvider implements SnippetProvider
{
/** @var array<string> */
protected $lines;
/** @var int */
protected $counter = 0;
public function __construct(string $snippet)
{
$this->lines = preg_split("/\r\n|\n|\r/", $snippet);
$this->cleanupLines();
}
public function numberOfLines(): int
{
return count($this->lines);
}
public function getLine(int $lineNumber = null): string
{
if (is_null($lineNumber)) {
return $this->getNextLine();
}
$this->counter = $lineNumber - 1;
return $this->lines[$lineNumber - 1];
}
public function getNextLine(): string
{
$this->counter++;
if ($this->counter >= count($this->lines)) {
return '';
}
return $this->lines[$this->counter];
}
protected function cleanupLines(): void
{
$spacesOrTabsToRemove = PHP_INT_MAX;
for ($i = 1; $i < count($this->lines); $i++) {
if (empty($this->lines[$i])) {
continue;
}
$spacesOrTabsToRemove = min(strspn($this->lines[$i], " \t"), $spacesOrTabsToRemove);
}
if ($spacesOrTabsToRemove === PHP_INT_MAX) {
$spacesOrTabsToRemove = 0;
}
for ($i = 1; $i < count($this->lines); $i++) {
$this->lines[$i] = substr($this->lines[$i], $spacesOrTabsToRemove);
}
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Spatie\Backtrace\CodeSnippets;
class NullSnippetProvider implements SnippetProvider
{
public function numberOfLines(): int
{
return 1;
}
public function getLine(int $lineNumber = null): string
{
return $this->getNextLine();
}
public function getNextLine(): string
{
return "File not found for code snippet";
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace Spatie\Backtrace\CodeSnippets;
interface SnippetProvider
{
public function numberOfLines(): int;
public function getLine(int $lineNumber = null): string;
public function getNextLine(): string;
}

View File

@ -2,6 +2,12 @@
namespace Spatie\Backtrace;
use Spatie\Backtrace\CodeSnippets\CodeSnippet;
use Spatie\Backtrace\CodeSnippets\FileSnippetProvider;
use Spatie\Backtrace\CodeSnippets\LaravelSerializableClosureSnippetProvider;
use Spatie\Backtrace\CodeSnippets\NullSnippetProvider;
use Spatie\Backtrace\CodeSnippets\SnippetProvider;
class Frame
{
/** @var string */
@ -22,13 +28,17 @@ class Frame
/** @var string|null */
public $class;
/** @var string|null */
protected $textSnippet;
public function __construct(
string $file,
int $lineNumber,
?array $arguments,
string $method = null,
string $class = null,
bool $isApplicationFrame = false
bool $isApplicationFrame = false,
?string $textSnippet = null
) {
$this->file = $file;
@ -41,6 +51,8 @@ class Frame
$this->class = $class;
$this->applicationFrame = $isApplicationFrame;
$this->textSnippet = $textSnippet;
}
public function getSnippet(int $lineCount): array
@ -48,7 +60,7 @@ class Frame
return (new CodeSnippet())
->surroundingLine($this->lineNumber)
->snippetLineCount($lineCount)
->get($this->file);
->get($this->getCodeSnippetProvider());
}
public function getSnippetAsString(int $lineCount): string
@ -56,7 +68,7 @@ class Frame
return (new CodeSnippet())
->surroundingLine($this->lineNumber)
->snippetLineCount($lineCount)
->getAsString($this->file);
->getAsString($this->getCodeSnippetProvider());
}
public function getSnippetProperties(int $lineCount): array
@ -70,4 +82,17 @@ class Frame
];
}, array_keys($snippet));
}
protected function getCodeSnippetProvider(): SnippetProvider
{
if($this->textSnippet) {
return new LaravelSerializableClosureSnippetProvider($this->textSnippet);
}
if(file_exists($this->file)) {
return new FileSnippetProvider($this->file);
}
return new NullSnippetProvider();
}
}