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();
}
}

View File

@@ -40,7 +40,7 @@ class Flare
protected ContextProviderDetector $contextDetector;
protected $previousExceptionHandler = null;
protected mixed $previousExceptionHandler = null;
/** @var null|callable */
protected $previousErrorHandler = null;
@@ -211,15 +211,16 @@ class Flare
public function registerExceptionHandler(): self
{
/** @phpstan-ignore-next-line */
$this->previousExceptionHandler = set_exception_handler([$this, 'handleException']);
return $this;
}
public function registerErrorHandler(): self
public function registerErrorHandler(?int $errorLevels = null): self
{
$this->previousErrorHandler = set_error_handler([$this, 'handleError']);
$this->previousErrorHandler = $errorLevels
? set_error_handler([$this, 'handleError'], $errorLevels)
: set_error_handler([$this, 'handleError']);
return $this;
}
@@ -309,7 +310,7 @@ class Flare
return $this;
}
public function report(Throwable $throwable, callable $callback = null, Report $report = null): ?Report
public function report(Throwable $throwable, callable $callback = null, Report $report = null, ?bool $handled = null): ?Report
{
if (! $this->shouldSendReport($throwable)) {
return null;
@@ -317,6 +318,10 @@ class Flare
$report ??= $this->createReport($throwable);
if ($handled) {
$report->handled();
}
if (! is_null($callback)) {
call_user_func($callback, $report);
}
@@ -328,6 +333,11 @@ class Flare
return $report;
}
public function reportHandled(Throwable $throwable): ?Report
{
return $this->report($throwable, null, null, true);
}
protected function shouldSendReport(Throwable $throwable): bool
{
if (isset($this->reportErrorLevels) && $throwable instanceof Error) {
@@ -449,6 +459,7 @@ class Flare
: $singleMiddleware;
}, $this->middleware);
$report = (new Pipeline())
->send($report)
->through($middleware)

View File

@@ -65,6 +65,8 @@ class Report
public static ?string $fakeTrackingUuid = null;
protected ?bool $handled = null;
/** @param array<class-string<ArgumentReducer>|ArgumentReducer>|ArgumentReducers|null $argumentReducers */
public static function createForThrowable(
Throwable $throwable,
@@ -300,6 +302,13 @@ class Report
return array_merge_recursive_distinct($context, $this->userProvidedContext);
}
public function handled(?bool $handled = true): self
{
$this->handled = $handled;
return $this;
}
protected function exceptionContext(Throwable $throwable): self
{
if ($throwable instanceof ProvidesFlareContext) {
@@ -376,6 +385,7 @@ class Report
'application_path' => $this->applicationPath,
'application_version' => $this->applicationVersion,
'tracking_uuid' => $this->trackingUuid,
'handled' => $this->handled,
];
}

View File

@@ -56,6 +56,8 @@ For Symfony apps, go to [symfony-ignition-bundle](https://github.com/spatie/symf
For Drupal 10+ websites, use the [Ignition module](https://www.drupal.org/project/ignition).
For OpenMage websites, use the [Ignition module](https://github.com/empiricompany/openmage_ignition).
For all other PHP projects, install the package via composer:
```bash

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -238,14 +238,14 @@ class Ignition
return $this;
}
public function register(): self
public function register(?int $errorLevels = null): self
{
error_reporting(-1);
error_reporting($errorLevels ?? -1);
/** @phpstan-ignore-next-line */
set_error_handler([$this, 'renderError']);
$errorLevels
? set_error_handler([$this, 'renderError'], $errorLevels)
: set_error_handler([$this, 'renderError']);
/** @phpstan-ignore-next-line */
set_exception_handler([$this, 'handleException']);
return $this;

View File

@@ -2,6 +2,37 @@
All notable changes to `laravel-html` will be documented in this file.
## 3.8.0 - 2024-04-24
### What's Changed
* Add autocomplete attribute helper to the form element by @raveren in https://github.com/spatie/laravel-html/pull/221
* Added support for Htmlable contents in BaseElement by @hemmesdev in https://github.com/spatie/laravel-html/pull/215
* Register Service Provider in Laravel 11 by @gqrdev in https://github.com/spatie/laravel-html/pull/224
* Add name attribute to form element by @bskl in https://github.com/spatie/laravel-html/pull/223
### New Contributors
* @hemmesdev made their first contribution in https://github.com/spatie/laravel-html/pull/215
* @gqrdev made their first contribution in https://github.com/spatie/laravel-html/pull/224
**Full Changelog**: https://github.com/spatie/laravel-html/compare/3.7.0...3.8.0
## 3.7.0 - 2024-03-23
### What's Changed
* Fix return value in docs in element-methods.md by @raveren in https://github.com/spatie/laravel-html/pull/218
* Add autocomplete attribute helper to input, select and textarea by @raveren in https://github.com/spatie/laravel-html/pull/219
* Fix link with version in documentation by @fey in https://github.com/spatie/laravel-html/pull/217
### New Contributors
* @raveren made their first contribution in https://github.com/spatie/laravel-html/pull/218
* @fey made their first contribution in https://github.com/spatie/laravel-html/pull/217
**Full Changelog**: https://github.com/spatie/laravel-html/compare/3.6.0...3.7.0
## 3.6.0 - 2024-03-08
### What's Changed

View File

@@ -169,6 +169,17 @@ abstract class BaseElement implements Htmlable, HtmlElement
return $this->attribute("data-{$name}", $value);
}
/**
* @param string $attribute
* @param string|null $value
*
* @return static
*/
public function aria($attribute, $value = null)
{
return $this->attribute("aria-{$attribute}", $value);
}
/**
* @param \Spatie\Html\HtmlElement|string|iterable|int|float|null $children
* @param callable|null $mapper
@@ -465,6 +476,8 @@ abstract class BaseElement implements Htmlable, HtmlElement
{
if ($children instanceof HtmlElement) {
$children = [$children];
} elseif ($children instanceof Htmlable) {
$children = $children->toHtml();
}
$children = Collection::make($children);

View File

@@ -3,10 +3,14 @@
namespace Spatie\Html\Elements;
use Spatie\Html\BaseElement;
use Spatie\Html\Elements\Attributes\Autocomplete;
use Spatie\Html\Elements\Attributes\Name;
use Spatie\Html\Elements\Attributes\Target;
class Form extends BaseElement
{
use Autocomplete;
use Name;
use Target;
protected $tag = 'form';

View File

@@ -4,6 +4,7 @@ namespace Spatie\Html;
use BackedEnum;
use DateTimeImmutable;
use Exception;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
@@ -271,8 +272,9 @@ class Html
*/
public function fieldset($legend = null)
{
return $legend ?
Fieldset::create()->legend($legend) : Fieldset::create();
return $legend
? Fieldset::create()->legend($legend)
: Fieldset::create();
}
/**
@@ -389,9 +391,9 @@ class Html
public function number($name = null, $value = null, $min = null, $max = null, $step = null)
{
return $this->input('number', $name, $value)
->attributeIfNotNull($min, 'min', $min)
->attributeIfNotNull($max, 'max', $max)
->attributeIfNotNull($step, 'step', $step);
->attributeIfNotNull($min, 'min', $min)
->attributeIfNotNull($max, 'max', $max)
->attributeIfNotNull($step, 'step', $step);
}
/**
@@ -658,7 +660,7 @@ class Html
$date = new DateTimeImmutable($value);
return $date->format($format);
} catch (\Exception $e) {
} catch (Exception $e) {
return $value;
}
}
@@ -672,7 +674,7 @@ class Html
protected function getEnumValue($value)
{
return $value instanceof BackedEnum
? $value->value
: $value->name;
? $value->value
: $value->name;
}
}

View File

@@ -22,8 +22,8 @@
"ext-json": "*",
"ext-mbstring": "*",
"illuminate/support": "^10.0|^11.0",
"spatie/flare-client-php": "^1.3.5",
"spatie/ignition": "^1.13.2",
"spatie/flare-client-php": "^1.5",
"spatie/ignition": "^1.14",
"symfony/console": "^6.2.3|^7.0",
"symfony/var-dumper": "^6.2.3|^7.0"
},
@@ -31,11 +31,11 @@
"livewire/livewire": "^2.11|^3.3.5",
"mockery/mockery": "^1.5.1",
"openai-php/client": "^0.8.1",
"orchestra/testbench": "^8.0|^9.0",
"pestphp/pest": "^2.30",
"phpstan/extension-installer": "^1.2",
"orchestra/testbench": "8.22.3|^9.0",
"pestphp/pest": "^2.34",
"phpstan/extension-installer": "^1.3.1",
"phpstan/phpstan-deprecation-rules": "^1.1.1",
"phpstan/phpstan-phpunit": "^1.3.3",
"phpstan/phpstan-phpunit": "^1.3.16",
"vlucas/phpdotenv": "^5.5"
},
"suggest": {

View File

@@ -6,6 +6,7 @@ use Spatie\FlareClient\FlareMiddleware\CensorRequestBodyFields;
use Spatie\FlareClient\FlareMiddleware\CensorRequestHeaders;
use Spatie\LaravelIgnition\FlareMiddleware\AddDumps;
use Spatie\LaravelIgnition\FlareMiddleware\AddEnvironmentInformation;
use Spatie\LaravelIgnition\FlareMiddleware\AddExceptionHandledStatus;
use Spatie\LaravelIgnition\FlareMiddleware\AddExceptionInformation;
use Spatie\LaravelIgnition\FlareMiddleware\AddJobs;
use Spatie\LaravelIgnition\FlareMiddleware\AddLogs;
@@ -55,6 +56,7 @@ return [
'max_chained_job_reporting_depth' => 5,
],
AddContext::class,
AddExceptionHandledStatus::class,
CensorRequestBodyFields::class => [
'censor_fields' => [
'password',
@@ -70,7 +72,7 @@ return [
'X-CSRF-TOKEN',
'X-XSRF-TOKEN',
]
]
],
],
/*

View File

@@ -6,6 +6,7 @@ use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Livewire\LivewireManager;
use Livewire\Mechanisms\ComponentRegistry;
class LaravelLivewireRequestContextProvider extends LaravelRequestContextProvider
{
@@ -37,9 +38,29 @@ class LaravelLivewireRequestContextProvider extends LaravelRequestContextProvide
return $properties;
}
/** @return array<string, mixed> */
/** @return array<int, mixed> */
protected function getLivewireInformation(): array
{
if ($this->request->has('components')) {
$data = [];
foreach ($this->request->get('components') as $component) {
$snapshot = json_decode($component['snapshot'], true);
$class = app(ComponentRegistry::class)->getClass($snapshot['memo']['name']);
$data[] = [
'component_class' => $class ?? null,
'data' => $snapshot['data'],
'memo' => $snapshot['memo'],
'updates' => $this->resolveUpdates($component['updates']),
'calls' => $component['calls'],
];
}
return $data;
}
/** @phpstan-ignore-next-line */
$componentId = $this->request->input('fingerprint.id');
@@ -56,12 +77,20 @@ class LaravelLivewireRequestContextProvider extends LaravelRequestContextProvide
$componentClass = null;
}
/** @phpstan-ignore-next-line */
$updates = $this->request->input('updates') ?? [];
/** @phpstan-ignore-next-line */
$updates = $this->request->input('updates') ?? [];
return [
'component_class' => $componentClass,
'component_alias' => $componentAlias,
'component_id' => $componentId,
'data' => $this->resolveData(),
'updates' => $this->resolveUpdates(),
[
'component_class' => $componentClass,
'component_alias' => $componentAlias,
'component_id' => $componentId,
'data' => $this->resolveData(),
'updates' => $this->resolveUpdates($updates),
],
];
}
@@ -86,7 +115,7 @@ class LaravelLivewireRequestContextProvider extends LaravelRequestContextProvide
}
/** @return array<string, mixed> */
protected function resolveUpdates(): array
protected function resolveUpdates(array $updates): array
{
/** @phpstan-ignore-next-line */
$updates = $this->request->input('updates') ?? [];

View File

@@ -13,7 +13,7 @@ class AddContext implements FlareMiddleware
public function handle(Report $report, Closure $next)
{
if (! class_exists(Repository::class)) {
return $report;
return $next($report);
}
$allContext = Context::all();
@@ -22,6 +22,6 @@ class AddContext implements FlareMiddleware
$report->group('laravel_context', $allContext);
}
return $report;
return $next($report);
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace Spatie\LaravelIgnition\FlareMiddleware;
use Closure;
use Spatie\Backtrace\Backtrace;
use Spatie\FlareClient\FlareMiddleware\FlareMiddleware;
use Spatie\FlareClient\Report;
use Throwable;
class AddExceptionHandledStatus implements FlareMiddleware
{
public function handle(Report $report, Closure $next)
{
$frames = Backtrace::create()->limit(40)->frames();
$frameCount = count($frames);
try {
foreach ($frames as $i => $frame) {
// Check first frame, probably Illuminate\Foundation\Exceptions\Handler::report()
// Next frame should be: Illuminate/Foundation/helpers.php::report()
if ($frame->method !== 'report') {
continue;
}
if ($frame->class === null) {
continue;
}
if ($i === $frameCount - 1) {
continue;
}
if ($frames[$i + 1]->class !== null) {
continue;
}
if ($frames[$i + 1]->method !== 'report') {
continue;
}
$report->handled();
break;
}
} catch (Throwable) {
// Do nothing
}
return $next($report);
}
}

View File

@@ -2,7 +2,6 @@
namespace Spatie\LaravelIgnition\Views;
use Exception;
use Illuminate\Contracts\View\Engine;
use Illuminate\Foundation\Application;
use Illuminate\Support\Arr;
@@ -19,7 +18,9 @@ use Throwable;
class ViewExceptionMapper
{
protected Engine $compilerEngine;
protected BladeSourceMapCompiler $bladeSourceMapCompiler;
protected array $knownPaths;
public function __construct(BladeSourceMapCompiler $bladeSourceMapCompiler)
@@ -80,15 +81,27 @@ class ViewExceptionMapper
protected function modifyViewsInTrace(IgnitionViewException $exception): void
{
$viewIndex = null;
$trace = Collection::make($exception->getPrevious()->getTrace())
->map(function ($trace) {
->map(function ($trace, $index) use (&$viewIndex) {
if ($originalPath = $this->findCompiledView(Arr::get($trace, 'file', ''))) {
$trace['file'] = $originalPath;
$trace['line'] = $this->getBladeLineNumber($trace['file'], $trace['line']);
if ($viewIndex === null) {
$viewIndex = $index;
}
}
return $trace;
})->toArray();
})
->when(
$viewIndex !== null && str_ends_with($exception->getFile(), '.blade.php'),
fn (Collection $trace) => $trace->slice($viewIndex + 1) // Remove all traces before the view
)
->toArray();
$traceProperty = new ReflectionProperty('Exception', 'trace');
$traceProperty->setAccessible(true);