This commit is contained in:
2025-05-12 14:25:25 +02:00
parent ab2db755ef
commit 9e378ca2b7
2719 changed files with 46505 additions and 60181 deletions

View File

@@ -62,16 +62,17 @@ A `Spatie\Backtrace\Frame` has these properties:
- `arguments`: the arguments used for this frame. Will be `null` if `withArguments` was not used.
- `class`: the class name for this frame. Will be `null` if the frame concerns a function.
- `method`: the method used in this frame
- `object`: the object when the frame is in an object context (method call, closure bound to object, arrow function which captured `$this`, etc.). Will be `null` if `withObject` was not used.
- `applicationFrame`: contains `true` is this frame belongs to your application, and `false` if it belongs to a file in
the vendor directory
### Collecting arguments
### Collecting arguments and objects
For performance reasons, the frames of the back trace will not contain the arguments of the called functions. If you
want to add those use the `withArguments` method.
For performance reasons, the frames of the back trace will not contain the arguments of the called functions and the
object. If you want to add those, use the `withArguments` and `withObject` methods.
```php
$backtrace = Spatie\Backtrace\Backtrace::create()->withArguments();
$backtrace = Spatie\Backtrace\Backtrace::create()->withArguments()->withObject();
```
#### Reducing arguments
@@ -123,6 +124,13 @@ frame is an application frame, or a vendor frame. Here's an example using a Lara
```php
$backtrace = Spatie\Backtrace\Backtrace::create()->applicationPath(base_path());
```
### Removing the application path from the file name
You can use `trimFilePaths` to remove the base path of your app from the file. This will only work if you use it in conjunction with the `applicationPath` method re above. Here's an example using a Laravel specific function. This will ensure the Frame has the trimmedFilePath property set.
```php
$backtrace = Backtrace::create()->applicationPath(base_path())->trimFilePaths());
```
### Getting a certain part of a trace
@@ -168,7 +176,10 @@ Here's how you can get a backtrace for a throwable.
$frames = Spatie\Backtrace\Backtrace::createForThrowable($throwable)
```
Because we will use the backtrace that is already available the throwable, the frames will always contain the arguments used.
Because we will use the backtrace that is already available in the throwable, the frames will contain the arguments used
in the backtrace as long as the `zend.exception_ignore_args` INI option is disabled (set to `0`) *before* the throwable
is thrown. On the other hand, objects will never be included in the backtrace.
[More information](https://www.php.net/manual/en/throwable.gettrace.php#129087).
## Testing

View File

@@ -1,12 +1,11 @@
{
"name": "spatie/backtrace",
"description": "A better backtrace",
"license": "MIT",
"keywords": [
"spatie",
"backtrace"
],
"homepage": "https://github.com/spatie/backtrace",
"license": "MIT",
"authors": [
{
"name": "Freek Van de Herten",
@@ -15,15 +14,29 @@
"role": "Developer"
}
],
"homepage": "https://github.com/spatie/backtrace",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/spatie"
},
{
"type": "other",
"url": "https://spatie.be/open-source/support-us"
}
],
"require": {
"php": "^7.3|^8.0"
"php": "^7.3 || ^8.0"
},
"require-dev": {
"ext-json": "*",
"phpunit/phpunit": "^9.3",
"spatie/phpunit-snapshot-assertions": "^4.2",
"symfony/var-dumper": "^5.1"
"laravel/serializable-closure": "^1.3 || ^2.0",
"phpunit/phpunit": "^9.3 || ^11.4.3",
"spatie/phpunit-snapshot-assertions": "^4.2 || ^5.1.6",
"symfony/var-dumper": "^5.1 || ^6.0 || ^7.0"
},
"minimum-stability": "dev",
"prefer-stable": true,
"autoload": {
"psr-4": {
"Spatie\\Backtrace\\": "src"
@@ -34,25 +47,13 @@
"Spatie\\Backtrace\\Tests\\": "tests"
}
},
"scripts": {
"psalm": "vendor/bin/psalm",
"test": "vendor/bin/phpunit",
"test-coverage": "vendor/bin/phpunit --coverage-html coverage",
"format": "vendor/bin/php-cs-fixer fix --allow-risky=yes"
},
"config": {
"sort-packages": true
},
"minimum-stability": "dev",
"prefer-stable": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/spatie"
},
{
"type": "other",
"url": "https://spatie.be/open-source/support-us"
}
]
"scripts": {
"format": "vendor/bin/php-cs-fixer fix --allow-risky=yes",
"psalm": "vendor/bin/psalm",
"test": "vendor/bin/phpunit",
"test-coverage": "vendor/bin/phpunit --coverage-html coverage"
}
}

View File

@@ -10,7 +10,7 @@ class MinimalArrayArgumentReducer implements ArgumentReducer
{
public function execute($argument): ReducedArgumentContract
{
if(! is_array($argument)) {
if (! is_array($argument)) {
return UnReducedArgument::create();
}

View File

@@ -11,7 +11,7 @@ class SymphonyRequestArgumentReducer implements ArgumentReducer
{
public function execute($argument): ReducedArgumentContract
{
if(! $argument instanceof Request) {
if (! $argument instanceof Request) {
return UnReducedArgument::create();
}

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;
@@ -22,6 +23,9 @@ class Backtrace
/** @var bool */
protected $withObject = false;
/** @var bool */
protected $trimFilePaths = false;
/** @var string|null */
protected $applicationPath;
@@ -76,9 +80,9 @@ class Backtrace
return $this;
}
public function withObject(): self
public function withObject(bool $withObject = true): self
{
$this->withObject = true;
$this->withObject = $withObject;
return $this;
}
@@ -90,6 +94,13 @@ class Backtrace
return $this;
}
public function trimFilePaths(): self
{
$this->trimFilePaths = true;
return $this;
}
public function offset(int $offset): self
{
$this->offset = $offset;
@@ -138,13 +149,16 @@ class Backtrace
return $this->throwable->getTrace();
}
$options = null;
// Omit arguments and object
$options = DEBUG_BACKTRACE_IGNORE_ARGS;
if (! $this->withArguments) {
$options = $options | DEBUG_BACKTRACE_IGNORE_ARGS;
// Populate arguments
if ($this->withArguments) {
$options = 0;
}
if ($this->withObject()) {
// Populate object
if ($this->withObject) {
$options = $options | DEBUG_BACKTRACE_PROVIDE_OBJECT;
}
@@ -171,15 +185,34 @@ 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;
}
if ($this->trimFilePaths && $this->applicationPath) {
$trimmedFilePath = str_replace($this->applicationPath, '', $currentFile);
}
$frame = new Frame(
$currentFile,
$currentLine,
$arguments,
$rawFrame['function'] ?? null,
$rawFrame['class'] ?? null,
$this->isApplicationFrame($currentFile)
$rawFrame['object'] ?? null,
$this->isApplicationFrame($currentFile),
$textSnippet,
$trimmedFilePath ?? null,
);
$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;
@@ -21,7 +21,7 @@ class File
return $this->file->key() + 1;
}
public function getLine(int $lineNumber = null): string
public function getLine(?int $lineNumber = null): string
{
if (is_null($lineNumber)) {
return $this->getNextLine();

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,11 +2,20 @@
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 */
public $file;
/** @var string|null */
public $trimmedFilePath;
/** @var int */
public $lineNumber;
@@ -22,16 +31,27 @@ class Frame
/** @var string|null */
public $class;
/** @var object|null */
public $object;
/** @var string|null */
protected $textSnippet;
public function __construct(
string $file,
int $lineNumber,
?array $arguments,
string $method = null,
string $class = null,
bool $isApplicationFrame = false
?string $method = null,
?string $class = null,
?object $object = null,
bool $isApplicationFrame = false,
?string $textSnippet = null,
?string $trimmedFilePath = null
) {
$this->file = $file;
$this->trimmedFilePath = $trimmedFilePath;
$this->lineNumber = $lineNumber;
$this->arguments = $arguments;
@@ -40,7 +60,11 @@ class Frame
$this->class = $class;
$this->object = $object;
$this->applicationFrame = $isApplicationFrame;
$this->textSnippet = $textSnippet;
}
public function getSnippet(int $lineCount): array
@@ -48,7 +72,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 +80,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 +94,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();
}
}