Файловый менеджер - Редактировать - /home/easybachat/hisabat365.com/4a7891/laravel-ignition.tar
Ðазад
src/Renderers/ErrorPageRenderer.php 0000644 00000002754 15060171630 0013361 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Renderers; use Spatie\ErrorSolutions\Contracts\SolutionProviderRepository; use Spatie\FlareClient\Flare; use Spatie\Ignition\Config\IgnitionConfig; use Spatie\Ignition\Ignition; use Spatie\LaravelIgnition\ContextProviders\LaravelContextProviderDetector; use Spatie\LaravelIgnition\Solutions\SolutionTransformers\LaravelSolutionTransformer; use Spatie\LaravelIgnition\Support\LaravelDocumentationLinkFinder; use Throwable; class ErrorPageRenderer { public function render(Throwable $throwable): void { $viteJsAutoRefresh = ''; if (class_exists('Illuminate\Foundation\Vite')) { $vite = app(\Illuminate\Foundation\Vite::class); if (is_file($vite->hotFile())) { $viteJsAutoRefresh = $vite->__invoke([]); } } app(Ignition::class) ->resolveDocumentationLink( fn (Throwable $throwable) => (new LaravelDocumentationLinkFinder())->findLinkForThrowable($throwable) ) ->setFlare(app(Flare::class)) ->setConfig(app(IgnitionConfig::class)) ->setSolutionProviderRepository(app(SolutionProviderRepository::class)) ->setContextProviderDetector(new LaravelContextProviderDetector()) ->setSolutionTransformerClass(LaravelSolutionTransformer::class) ->applicationPath(base_path()) ->addCustomHtmlToHead($viteJsAutoRefresh) ->renderException($throwable); } } src/Renderers/IgnitionExceptionRenderer.php 0000644 00000001005 15060171630 0015116 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Renderers; use Illuminate\Contracts\Foundation\ExceptionRenderer; class IgnitionExceptionRenderer implements ExceptionRenderer { protected ErrorPageRenderer $errorPageHandler; public function __construct(ErrorPageRenderer $errorPageHandler) { $this->errorPageHandler = $errorPageHandler; } public function render($throwable) { ob_start(); $this->errorPageHandler->render($throwable); return ob_get_clean(); } } src/ArgumentReducers/CollectionArgumentReducer.php 0000644 00000001170 15060171630 0016431 0 ustar 00 <?php namespace Spatie\LaravelIgnition\ArgumentReducers; use Illuminate\Support\Collection; use Spatie\Backtrace\Arguments\ReducedArgument\ReducedArgumentContract; use Spatie\Backtrace\Arguments\ReducedArgument\UnReducedArgument; use Spatie\Backtrace\Arguments\Reducers\ArrayArgumentReducer; class CollectionArgumentReducer extends ArrayArgumentReducer { public function execute(mixed $argument): ReducedArgumentContract { if (! $argument instanceof Collection) { return UnReducedArgument::create(); } return $this->reduceArgument($argument->toArray(), get_class($argument)); } } src/ArgumentReducers/ModelArgumentReducer.php 0000644 00000001351 15060171630 0015377 0 ustar 00 <?php namespace Spatie\LaravelIgnition\ArgumentReducers; use Illuminate\Database\Eloquent\Model; use Spatie\Backtrace\Arguments\ReducedArgument\ReducedArgument; use Spatie\Backtrace\Arguments\ReducedArgument\ReducedArgumentContract; use Spatie\Backtrace\Arguments\ReducedArgument\UnReducedArgument; use Spatie\Backtrace\Arguments\Reducers\ArgumentReducer; class ModelArgumentReducer implements ArgumentReducer { public function execute(mixed $argument): ReducedArgumentContract { if (! $argument instanceof Model) { return UnReducedArgument::create(); } return new ReducedArgument( "{$argument->getKeyName()}:{$argument->getKey()}", get_class($argument) ); } } src/Views/ViewExceptionMapper.php 0000644 00000014565 15060171630 0013111 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Views; use Illuminate\Contracts\View\Engine; use Illuminate\Foundation\Application; use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\View\Engines\PhpEngine; use Illuminate\View\ViewException; use ReflectionClass; use ReflectionProperty; use Spatie\ErrorSolutions\Contracts\ProvidesSolution; use Spatie\LaravelIgnition\Exceptions\ViewException as IgnitionViewException; use Spatie\LaravelIgnition\Exceptions\ViewExceptionWithSolution; use Throwable; class ViewExceptionMapper { protected Engine $compilerEngine; protected BladeSourceMapCompiler $bladeSourceMapCompiler; protected array $knownPaths; public function __construct(BladeSourceMapCompiler $bladeSourceMapCompiler) { $resolver = app('view.engine.resolver'); $this->compilerEngine = $resolver->resolve('blade'); $this->bladeSourceMapCompiler = $bladeSourceMapCompiler; } public function map(ViewException $viewException): IgnitionViewException { $baseException = $this->getRealException($viewException); if ($baseException instanceof IgnitionViewException) { return $baseException; } preg_match('/\(View: (?P<path>.*?)\)/', $viewException->getMessage(), $matches); $compiledViewPath = $matches['path']; $exception = $this->createException($baseException); if ($baseException instanceof ProvidesSolution) { /** @var ViewExceptionWithSolution $exception */ $exception->setSolution($baseException->getSolution()); } $this->modifyViewsInTrace($exception); $exception->setView($compiledViewPath); $exception->setViewData($this->getViewData($exception)); return $exception; } protected function createException(Throwable $baseException): IgnitionViewException { $viewExceptionClass = $baseException instanceof ProvidesSolution ? ViewExceptionWithSolution::class : IgnitionViewException::class; $viewFile = $this->findCompiledView($baseException->getFile()); $file = $viewFile ?? $baseException->getFile(); $line = $viewFile ? $this->getBladeLineNumber($file, $baseException->getLine()) : $baseException->getLine(); return new $viewExceptionClass( $baseException->getMessage(), 0, 1, $file, $line, $baseException ); } protected function modifyViewsInTrace(IgnitionViewException $exception): void { $viewIndex = null; $trace = Collection::make($exception->getPrevious()->getTrace()) ->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; }) ->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); $traceProperty->setValue($exception, $trace); } /** * Look at the previous exceptions to find the original exception. * This is usually the first Exception that is not a ViewException. */ protected function getRealException(Throwable $exception): Throwable { $rootException = $exception->getPrevious() ?? $exception; while ($rootException instanceof ViewException && $rootException->getPrevious()) { $rootException = $rootException->getPrevious(); } return $rootException; } protected function findCompiledView(string $compiledPath): ?string { $this->knownPaths ??= $this->getKnownPaths(); return $this->knownPaths[$compiledPath] ?? null; } protected function getKnownPaths(): array { $compilerEngineReflection = new ReflectionClass($this->compilerEngine); if (! $compilerEngineReflection->hasProperty('lastCompiled') && $compilerEngineReflection->hasProperty('engine')) { $compilerEngine = $compilerEngineReflection->getProperty('engine'); $compilerEngine->setAccessible(true); $compilerEngine = $compilerEngine->getValue($this->compilerEngine); $lastCompiled = new ReflectionProperty($compilerEngine, 'lastCompiled'); $lastCompiled->setAccessible(true); $lastCompiled = $lastCompiled->getValue($compilerEngine); } else { $lastCompiled = $compilerEngineReflection->getProperty('lastCompiled'); $lastCompiled->setAccessible(true); $lastCompiled = $lastCompiled->getValue($this->compilerEngine); } $knownPaths = []; foreach ($lastCompiled as $lastCompiledPath) { $compiledPath = $this->compilerEngine->getCompiler()->getCompiledPath($lastCompiledPath); $knownPaths[realpath($compiledPath ?? $lastCompiledPath)] = realpath($lastCompiledPath); } return $knownPaths; } protected function getBladeLineNumber(string $view, int $compiledLineNumber): int { return $this->bladeSourceMapCompiler->detectLineNumber($view, $compiledLineNumber); } protected function getViewData(Throwable $exception): array { foreach ($exception->getTrace() as $frame) { if (Arr::get($frame, 'class') === PhpEngine::class) { $data = Arr::get($frame, 'args.1', []); return $this->filterViewData($data); } } return []; } protected function filterViewData(array $data): array { // By default, Laravel views get two data keys: // __env and app. We try to filter them out. return array_filter($data, function ($value, $key) { if ($key === 'app') { return ! $value instanceof Application; } return $key !== '__env'; }, ARRAY_FILTER_USE_BOTH); } } src/Views/BladeSourceMapCompiler.php 0000644 00000010660 15060171630 0013464 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Views; use Illuminate\View\Compilers\BladeCompiler; use Throwable; class BladeSourceMapCompiler { protected BladeCompiler $bladeCompiler; public function __construct() { $this->bladeCompiler = app('blade.compiler'); } public function detectLineNumber(string $filename, int $compiledLineNumber): int { $map = $this->compileSourcemap((string)file_get_contents($filename)); return $this->findClosestLineNumberMapping($map, $compiledLineNumber); } protected function compileSourcemap(string $value): string { try { $value = $this->addEchoLineNumbers($value); $value = $this->addStatementLineNumbers($value); $value = $this->addBladeComponentLineNumbers($value); $value = $this->bladeCompiler->compileString($value); return $this->trimEmptyLines($value); } catch (Throwable $e) { report($e); return $value; } } protected function addEchoLineNumbers(string $value): string { $echoPairs = [['{{', '}}'], ['{{{', '}}}'], ['{!!', '!!}']]; foreach ($echoPairs as $pair) { // Matches {{ $value }}, {!! $value !!} and {{{ $value }}} depending on $pair $pattern = sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $pair[0], $pair[1]); if (preg_match_all($pattern, $value, $matches, PREG_OFFSET_CAPTURE)) { foreach (array_reverse($matches[0]) as $match) { $position = mb_strlen(substr($value, 0, $match[1])); $value = $this->insertLineNumberAtPosition($position, $value); } } } return $value; } protected function addStatementLineNumbers(string $value): string { // Matches @bladeStatements() like @if, @component(...), @etc; $shouldInsertLineNumbers = preg_match_all( '/\B@(@?\w+(?:::\w+)?)([ \t]*)(\( ( (?>[^()]+) | (?3) )* \))?/x', $value, $matches, PREG_OFFSET_CAPTURE ); if ($shouldInsertLineNumbers) { foreach (array_reverse($matches[0]) as $match) { $position = mb_strlen(substr($value, 0, $match[1])); $value = $this->insertLineNumberAtPosition($position, $value); } } return $value; } protected function addBladeComponentLineNumbers(string $value): string { // Matches the start of `<x-blade-component` $shouldInsertLineNumbers = preg_match_all( '/<\s*x[-:]([\w\-:.]*)/mx', $value, $matches, PREG_OFFSET_CAPTURE ); if ($shouldInsertLineNumbers) { foreach (array_reverse($matches[0]) as $match) { $position = mb_strlen(substr($value, 0, $match[1])); $value = $this->insertLineNumberAtPosition($position, $value); } } return $value; } protected function insertLineNumberAtPosition(int $position, string $value): string { $before = mb_substr($value, 0, $position); $lineNumber = count(explode("\n", $before)); return mb_substr($value, 0, $position)."|---LINE:{$lineNumber}---|".mb_substr($value, $position); } protected function trimEmptyLines(string $value): string { $value = preg_replace('/^\|---LINE:([0-9]+)---\|$/m', '', $value); return ltrim((string)$value, PHP_EOL); } protected function findClosestLineNumberMapping(string $map, int $compiledLineNumber): int { $map = explode("\n", $map); // Max 20 lines between compiled and source line number. // Blade components can span multiple lines and the compiled line number is often // a couple lines below the source-mapped `<x-component>` code. $maxDistance = 20; $pattern = '/\|---LINE:(?P<line>[0-9]+)---\|/m'; $lineNumberToCheck = $compiledLineNumber - 1; while (true) { if ($lineNumberToCheck < $compiledLineNumber - $maxDistance) { // Something wrong. Return the $compiledLineNumber (unless it's out of range) return min($compiledLineNumber, count($map)); } if (preg_match($pattern, $map[$lineNumberToCheck] ?? '', $matches)) { return (int)$matches['line']; } $lineNumberToCheck--; } } } src/Support/FlareLogHandler.php 0000644 00000005423 15060171630 0012514 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Support; use InvalidArgumentException; use Monolog\Handler\AbstractProcessingHandler; use Monolog\Level; use Monolog\Logger; use Monolog\LogRecord; use Spatie\FlareClient\Flare; use Spatie\FlareClient\Report; use Throwable; class FlareLogHandler extends AbstractProcessingHandler { protected Flare $flare; protected SentReports $sentReports; protected int $minimumReportLogLevel; public function __construct(Flare $flare, SentReports $sentReports, $level = Level::Debug, $bubble = true) { $this->flare = $flare; $this->minimumReportLogLevel = Level::Error->value; $this->sentReports = $sentReports; parent::__construct($level, $bubble); } public function setMinimumReportLogLevel(int $level): void { if (! in_array($level, Level::VALUES)) { throw new InvalidArgumentException('The given minimum log level is not supported.'); } $this->minimumReportLogLevel = $level; } protected function write(LogRecord $record): void { if (! $this->shouldReport($record->toArray())) { return; } if ($this->hasException($record->toArray())) { $report = $this->flare->report($record['context']['exception']); if ($report) { $this->sentReports->add($report); } return; } if (config('flare.send_logs_as_events')) { if ($this->hasValidLogLevel($record->toArray())) { $this->flare->reportMessage( $record['message'], 'Log ' . Logger::toMonologLevel($record['level'])->getName(), function (Report $flareReport) use ($record) { foreach ($record['context'] as $key => $value) { $flareReport->context($key, $value); } } ); } } } /** * @param array<string, mixed> $report * * @return bool */ protected function shouldReport(array $report): bool { if (! config('flare.key')) { return false; } return $this->hasException($report) || $this->hasValidLogLevel($report); } /** * @param array<string, mixed> $report * * @return bool */ protected function hasException(array $report): bool { $context = $report['context']; return isset($context['exception']) && $context['exception'] instanceof Throwable; } /** * @param array<string, mixed> $report * * @return bool */ protected function hasValidLogLevel(array $report): bool { return $report['level'] >= $this->minimumReportLogLevel; } } src/Support/LaravelVersion.php 0000644 00000000264 15060171630 0012455 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Support; class LaravelVersion { public static function major(): string { return explode('.', app()->version())[0]; } } src/Support/LaravelDocumentationLinkFinder.php 0000644 00000004114 15060171630 0015605 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Support; use Illuminate\Support\Collection; use Illuminate\Support\Str; use Spatie\LaravelIgnition\Exceptions\ViewException; use Throwable; class LaravelDocumentationLinkFinder { public function findLinkForThrowable(Throwable $throwable): ?string { if ($throwable instanceof ViewException) { $throwable = $throwable->getPrevious(); } $majorVersion = LaravelVersion::major(); if (str_contains($throwable->getMessage(), Collection::class)) { return "https://laravel.com/docs/{$majorVersion}.x/collections#available-methods"; } $type = $this->getType($throwable); if (! $type) { return null; } return match ($type) { 'Auth' => "https://laravel.com/docs/{$majorVersion}.x/authentication", 'Broadcasting' => "https://laravel.com/docs/{$majorVersion}.x/broadcasting", 'Container' => "https://laravel.com/docs/{$majorVersion}.x/container", 'Database' => "https://laravel.com/docs/{$majorVersion}.x/eloquent", 'Pagination' => "https://laravel.com/docs/{$majorVersion}.x/pagination", 'Queue' => "https://laravel.com/docs/{$majorVersion}.x/queues", 'Routing' => "https://laravel.com/docs/{$majorVersion}.x/routing", 'Session' => "https://laravel.com/docs/{$majorVersion}.x/session", 'Validation' => "https://laravel.com/docs/{$majorVersion}.x/validation", 'View' => "https://laravel.com/docs/{$majorVersion}.x/views", default => null, }; } protected function getType(?Throwable $throwable): ?string { if (! $throwable) { return null; } if (str_contains($throwable::class, 'Illuminate')) { return Str::between($throwable::class, 'Illuminate\\', '\\'); } if (str_contains($throwable->getMessage(), 'Illuminate')) { return explode('\\', Str::between($throwable->getMessage(), 'Illuminate\\', '\\'))[0]; } return null; } } src/Support/SentReports.php 0000644 00000002172 15060171630 0012011 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Support; use Illuminate\Support\Arr; use Spatie\FlareClient\Report; class SentReports { /** @var array<int, Report> */ protected array $reports = []; public function add(Report $report): self { $this->reports[] = $report; return $this; } /** @return array<int, Report> */ public function all(): array { return $this->reports; } /** @return array<int, string> */ public function uuids(): array { return array_map(fn (Report $report) => $report->trackingUuid(), $this->reports); } /** @return array<int, string> */ public function urls(): array { return array_map(function (string $trackingUuid) { return "https://flareapp.io/tracked-occurrence/{$trackingUuid}"; }, $this->uuids()); } public function latestUuid(): ?string { return Arr::last($this->reports)?->trackingUuid(); } public function latestUrl(): ?string { return Arr::last($this->urls()); } public function clear(): void { $this->reports = []; } } src/Support/RunnableSolutionsGuard.php 0000644 00000002053 15060171630 0014170 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Support; class RunnableSolutionsGuard { /** * Check if runnable solutions are allowed based on the current * environment and config. * * @return bool */ public static function check(): bool { if (! config('app.debug')) { // Never run solutions in when debug mode is not enabled. return false; } if (config('ignition.enable_runnable_solutions') !== null) { // Allow enabling or disabling runnable solutions regardless of environment // if the IGNITION_ENABLE_RUNNABLE_SOLUTIONS env var is explicitly set. return config('ignition.enable_runnable_solutions'); } if (! app()->environment('local') && ! app()->environment('development')) { // Never run solutions on non-local environments. This avoids exposing // applications that are somehow APP_ENV=production with APP_DEBUG=true. return false; } return config('app.debug'); } } src/IgnitionServiceProvider.php 0000644 00000026746 15060171630 0012676 0 ustar 00 <?php namespace Spatie\LaravelIgnition; use Exception; use Illuminate\Contracts\Debug\ExceptionHandler; use Illuminate\Foundation\Application; use Illuminate\Support\Facades\Log; use Illuminate\Support\ServiceProvider; use Illuminate\View\ViewException; use Laravel\Octane\Events\RequestReceived; use Laravel\Octane\Events\RequestTerminated; use Laravel\Octane\Events\TaskReceived; use Laravel\Octane\Events\TickReceived; use Monolog\Level; use Monolog\Logger; use Spatie\ErrorSolutions\Contracts\SolutionProviderRepository as SolutionProviderRepositoryContract; use Spatie\ErrorSolutions\SolutionProviderRepository; use Spatie\FlareClient\Flare; use Spatie\FlareClient\FlareMiddleware\AddSolutions; use Spatie\Ignition\Config\FileConfigManager; use Spatie\Ignition\Config\IgnitionConfig; use Spatie\Ignition\Contracts\ConfigManager; use Spatie\Ignition\Ignition; use Spatie\LaravelIgnition\Commands\SolutionMakeCommand; use Spatie\LaravelIgnition\Commands\SolutionProviderMakeCommand; use Spatie\LaravelIgnition\Commands\TestCommand; use Spatie\LaravelIgnition\ContextProviders\LaravelContextProviderDetector; use Spatie\LaravelIgnition\Exceptions\InvalidConfig; use Spatie\LaravelIgnition\FlareMiddleware\AddJobs; use Spatie\LaravelIgnition\FlareMiddleware\AddLogs; use Spatie\LaravelIgnition\FlareMiddleware\AddQueries; use Spatie\LaravelIgnition\Recorders\DumpRecorder\DumpRecorder; use Spatie\LaravelIgnition\Recorders\JobRecorder\JobRecorder; use Spatie\LaravelIgnition\Recorders\LogRecorder\LogRecorder; use Spatie\LaravelIgnition\Recorders\QueryRecorder\QueryRecorder; use Spatie\LaravelIgnition\Renderers\IgnitionExceptionRenderer; use Spatie\LaravelIgnition\Support\FlareLogHandler; use Spatie\LaravelIgnition\Support\SentReports; use Spatie\LaravelIgnition\Views\ViewExceptionMapper; class IgnitionServiceProvider extends ServiceProvider { public function register(): void { $this->registerConfig(); $this->registerFlare(); $this->registerIgnition(); $this->registerRenderer(); $this->registerRecorders(); $this->registerLogHandler(); } public function boot() { if ($this->app->runningInConsole()) { $this->registerCommands(); $this->publishConfigs(); } $this->registerRoutes(); $this->configureTinker(); $this->configureOctane(); $this->registerViewExceptionMapper(); $this->startRecorders(); $this->configureQueue(); } protected function registerConfig(): void { $this->mergeConfigFrom(__DIR__ . '/../config/flare.php', 'flare'); $this->mergeConfigFrom(__DIR__ . '/../config/ignition.php', 'ignition'); } protected function registerCommands(): void { if ($this->app['config']->get('flare.key')) { $this->commands([ TestCommand::class, ]); } if ($this->app['config']->get('ignition.register_commands')) { $this->commands([ SolutionMakeCommand::class, SolutionProviderMakeCommand::class, ]); } } protected function publishConfigs(): void { $this->publishes([ __DIR__ . '/../config/ignition.php' => config_path('ignition.php'), ], 'ignition-config'); $this->publishes([ __DIR__ . '/../config/flare.php' => config_path('flare.php'), ], 'flare-config'); } protected function registerRenderer(): void { $this->app->bind( 'Illuminate\Contracts\Foundation\ExceptionRenderer', fn (Application $app) => $app->make(IgnitionExceptionRenderer::class) ); } protected function registerFlare(): void { $this->app->singleton(Flare::class, function () { return Flare::make() ->setApiToken(config('flare.key') ?? '') ->setBaseUrl(config('flare.base_url', 'https://flareapp.io/api')) ->applicationPath(base_path()) ->setStage(app()->environment()) ->setContextProviderDetector(new LaravelContextProviderDetector()) ->registerMiddleware($this->getFlareMiddleware()) ->registerMiddleware(new AddSolutions(new SolutionProviderRepository($this->getSolutionProviders()))) ->argumentReducers(config('ignition.argument_reducers', [])) ->withStackFrameArguments(config('ignition.with_stack_frame_arguments', true)); }); $this->app->singleton(SentReports::class); } protected function registerIgnition(): void { $this->app->singleton( ConfigManager::class, fn () => new FileConfigManager(config('ignition.settings_file_path', '')) ); $ignitionConfig = (new IgnitionConfig()) ->merge(config('ignition', [])) ->loadConfigFile(); $solutionProviders = $this->getSolutionProviders(); $solutionProviderRepository = new SolutionProviderRepository($solutionProviders); $this->app->singleton(IgnitionConfig::class, fn () => $ignitionConfig); $this->app->singleton(SolutionProviderRepositoryContract::class, fn () => $solutionProviderRepository); $this->app->singleton( Ignition::class, fn () => (new Ignition($this->app->make(Flare::class)))->applicationPath(base_path()) ); } protected function registerRecorders(): void { $this->app->singleton(DumpRecorder::class); $this->app->singleton(LogRecorder::class, function (Application $app): LogRecorder { return new LogRecorder( $app, config()->get('flare.flare_middleware.' . AddLogs::class . '.maximum_number_of_collected_logs') ); }); $this->app->singleton( QueryRecorder::class, function (Application $app): QueryRecorder { return new QueryRecorder( $app, config('flare.flare_middleware.' . AddQueries::class . '.report_query_bindings', true), config('flare.flare_middleware.' . AddQueries::class . '.maximum_number_of_collected_queries', 200) ); } ); $this->app->singleton(JobRecorder::class, function (Application $app): JobRecorder { return new JobRecorder( $app, config('flare.flare_middleware.' . AddJobs::class . '.max_chained_job_reporting_depth', 5) ); }); } public function configureTinker(): void { if ($this->app->runningInConsole()) { if (isset($_SERVER['argv']) && ['artisan', 'tinker'] === $_SERVER['argv']) { app(Flare::class)->sendReportsImmediately(); } } } protected function configureOctane(): void { if (isset($_SERVER['LARAVEL_OCTANE'])) { $this->setupOctane(); } } protected function registerViewExceptionMapper(): void { $handler = $this->app->make(ExceptionHandler::class); if (! method_exists($handler, 'map')) { return; } $handler->map(function (ViewException $viewException) { return $this->app->make(ViewExceptionMapper::class)->map($viewException); }); } protected function registerRoutes(): void { $this->loadRoutesFrom(realpath(__DIR__ . '/ignition-routes.php')); } protected function registerLogHandler(): void { $this->app->singleton('flare.logger', function ($app) { $handler = new FlareLogHandler( $app->make(Flare::class), $app->make(SentReports::class), ); $logLevelString = config('logging.channels.flare.level', 'error'); $logLevel = $this->getLogLevel($logLevelString); $handler->setMinimumReportLogLevel($logLevel); return tap( new Logger('Flare'), fn (Logger $logger) => $logger->pushHandler($handler) ); }); Log::extend('flare', fn ($app) => $app['flare.logger']); } protected function startRecorders(): void { foreach ($this->app->config['ignition.recorders'] ?? [] as $recorder) { $this->app->make($recorder)->start(); } } protected function configureQueue(): void { if (! $this->app->bound('queue')) { return; } $queue = $this->app->get('queue'); // Reset before executing a queue job to make sure the job's log/query/dump recorders are empty. // When using a sync queue this also reports the queued reports from previous exceptions. $queue->before(function () { $this->resetFlareAndLaravelIgnition(); app(Flare::class)->sendReportsImmediately(); }); // Send queued reports (and reset) after executing a queue job. $queue->after(function () { $this->resetFlareAndLaravelIgnition(); }); // Note: the $queue->looping() event can't be used because it's not triggered on Vapor } protected function getLogLevel(string $logLevelString): int { try { $logLevel = Level::fromName($logLevelString); } catch (Exception $exception) { $logLevel = null; } if (! $logLevel) { throw InvalidConfig::invalidLogLevel($logLevelString); } return $logLevel->value; } protected function getFlareMiddleware(): array { return collect(config('flare.flare_middleware')) ->map(function ($value, $key) { if (is_string($key)) { $middlewareClass = $key; $parameters = $value ?? []; } else { $middlewareClass = $value; $parameters = []; } return new $middlewareClass(...array_values($parameters)); }) ->values() ->toArray(); } protected function getSolutionProviders(): array { return collect(config('ignition.solution_providers')) ->reject( fn (string $class) => in_array($class, config('ignition.ignored_solution_providers')) ) ->toArray(); } protected function setupOctane(): void { $this->app['events']->listen(RequestReceived::class, function () { $this->resetFlareAndLaravelIgnition(); }); $this->app['events']->listen(TaskReceived::class, function () { $this->resetFlareAndLaravelIgnition(); }); $this->app['events']->listen(TickReceived::class, function () { $this->resetFlareAndLaravelIgnition(); }); $this->app['events']->listen(RequestTerminated::class, function () { $this->resetFlareAndLaravelIgnition(); }); } protected function resetFlareAndLaravelIgnition(): void { $this->app->get(SentReports::class)->clear(); $this->app->get(Ignition::class)->reset(); if (config('flare.flare_middleware.' . AddLogs::class)) { $this->app->make(LogRecorder::class)->reset(); } if (config('flare.flare_middleware.' . AddQueries::class)) { $this->app->make(QueryRecorder::class)->reset(); } if (config('flare.flare_middleware.' . AddJobs::class)) { $this->app->make(JobRecorder::class)->reset(); } $this->app->make(DumpRecorder::class)->reset(); } } src/ignition-routes.php 0000644 00000001413 15060171630 0011201 0 ustar 00 <?php use Illuminate\Support\Facades\Route; use Spatie\LaravelIgnition\Http\Controllers\ExecuteSolutionController; use Spatie\LaravelIgnition\Http\Controllers\HealthCheckController; use Spatie\LaravelIgnition\Http\Controllers\UpdateConfigController; use Spatie\LaravelIgnition\Http\Middleware\RunnableSolutionsEnabled; Route::group([ 'as' => 'ignition.', 'prefix' => config('ignition.housekeeping_endpoint_prefix'), 'middleware' => [RunnableSolutionsEnabled::class], ], function () { Route::get('health-check', HealthCheckController::class)->name('healthCheck'); Route::post('execute-solution', ExecuteSolutionController::class) ->name('executeSolution'); Route::post('update-config', UpdateConfigController::class)->name('updateConfig'); }); src/Commands/SolutionMakeCommand.php 0000644 00000001554 15060171630 0013522 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Commands; use Illuminate\Console\GeneratorCommand; use Symfony\Component\Console\Input\InputOption; class SolutionMakeCommand extends GeneratorCommand { protected $name = 'ignition:make-solution'; protected $description = 'Create a new custom Ignition solution class'; protected $type = 'Solution'; protected function getStub(): string { return $this->option('runnable') ? __DIR__.'/stubs/runnable-solution.stub' : __DIR__.'/stubs/solution.stub'; } protected function getDefaultNamespace($rootNamespace) { return "{$rootNamespace}\\Solutions"; } /** @return array<int, mixed> */ protected function getOptions(): array { return [ ['runnable', null, InputOption::VALUE_NONE, 'Create runnable solution'], ]; } } src/Commands/stubs/runnable-solution.stub 0000644 00000001300 15060171630 0014604 0 ustar 00 <?php namespace DummyNamespace; use Spatie\ErrorSolutions\Contracts\RunnableSolution; class DummyClass implements RunnableSolution { public function getSolutionTitle(): string { return ''; } public function getDocumentationLinks(): array { return []; } public function getSolutionActionDescription(): string { return ''; } public function getRunButtonText(): string { return ''; } public function getSolutionDescription(): string { return ''; } public function getRunParameters(): array { return []; } public function run(array $parameters = []) { // } } src/Commands/stubs/solution.stub 0000644 00000000560 15060171630 0013007 0 ustar 00 <?php namespace DummyNamespace; use Spatie\ErrorSolutions\Contracts\Solution; class DummyClass implements Solution { public function getSolutionTitle(): string { return ''; } public function getSolutionDescription(): string { return ''; } public function getDocumentationLinks(): array { return []; } } src/Commands/stubs/solution-provider.stub 0000644 00000000542 15060171630 0014637 0 ustar 00 <?php namespace DummyNamespace; use Spatie\ErrorSolutions\Contracts\HasSolutionsForThrowable; use Throwable; class DummyClass implements HasSolutionsForThrowable { public function canSolve(Throwable $throwable): bool { return false; } public function getSolutions(Throwable $throwable): array { return []; } } src/Commands/TestCommand.php 0000644 00000011250 15060171630 0012021 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Commands; use Composer\InstalledVersions; use Exception; use Illuminate\Config\Repository; use Illuminate\Console\Command; use Illuminate\Log\LogManager; use Spatie\FlareClient\Flare; use Spatie\FlareClient\Http\Exceptions\BadResponseCode; class TestCommand extends Command { protected $signature = 'flare:test'; protected $description = 'Send a test notification to Flare'; protected Repository $config; public function handle(Repository $config): void { $this->config = $config; $this->checkFlareKey(); if (app()->make('log') instanceof LogManager) { $this->checkFlareLogger(); } $this->sendTestException(); } protected function checkFlareKey(): self { $message = empty($this->config->get('flare.key')) ? '❌ Flare key not specified. Make sure you specify a value in the `key` key of the `flare` config file.' : '✅ Flare key specified'; $this->info($message); return $this; } public function checkFlareLogger(): self { $defaultLogChannel = $this->config->get('logging.default'); $activeStack = $this->config->get("logging.channels.{$defaultLogChannel}"); if (is_null($activeStack)) { $this->info("❌ The default logging channel `{$defaultLogChannel}` is not configured in the `logging` config file"); } if (! isset($activeStack['channels']) || ! in_array('flare', $activeStack['channels'])) { $this->info("❌ The logging channel `{$defaultLogChannel}` does not contain the 'flare' channel"); } if (is_null($this->config->get('logging.channels.flare'))) { $this->info('❌ There is no logging channel named `flare` in the `logging` config file'); } if ($this->config->get('logging.channels.flare.driver') !== 'flare') { $this->info('❌ The `flare` logging channel defined in the `logging` config file is not set to `flare`.'); } if ($this->config->get('ignition.with_stack_frame_arguments') && ini_get('zend.exception_ignore_args')) { $this->info('⚠️ The `zend.exception_ignore_args` php ini setting is enabled. This will prevent Flare from showing stack trace arguments.'); } $this->info('✅ The Flare logging driver was configured correctly.'); return $this; } protected function sendTestException(): void { $testException = new Exception('This is an exception to test if the integration with Flare works.'); try { app(Flare::class)->sendTestReport($testException); $this->info(''); } catch (Exception $exception) { $this->warn('❌ We were unable to send an exception to Flare. '); if ($exception instanceof BadResponseCode) { $this->info(''); $message = 'Unknown error'; $body = $exception->response->getBody(); if (is_array($body) && isset($body['message'])) { $message = $body['message']; } $this->warn("{$exception->response->getHttpResponseCode()} - {$message}"); } else { $this->warn($exception->getMessage()); } $this->warn('Make sure that your key is correct and that you have a valid subscription.'); $this->info(''); $this->info('For more info visit the docs on https://flareapp.io/docs/ignition-for-laravel/introduction'); $this->info('You can see the status page of Flare at https://status.flareapp.io'); $this->info('Flare support can be reached at support@flareapp.io'); $this->line(''); $this->line('Extra info'); $this->table([], [ ['Platform', PHP_OS], ['PHP', phpversion()], ['Laravel', app()->version()], ['spatie/ignition', InstalledVersions::getVersion('spatie/ignition')], ['spatie/laravel-ignition', InstalledVersions::getVersion('spatie/laravel-ignition')], ['spatie/flare-client-php', InstalledVersions::getVersion('spatie/flare-client-php')], /** @phpstan-ignore-next-line */ ['Curl', curl_version()['version'] ?? 'Unknown'], /** @phpstan-ignore-next-line */ ['SSL', curl_version()['ssl_version'] ?? 'Unknown'], ]); if ($this->output->isVerbose()) { throw $exception; } return; } $this->info('We tried to send an exception to Flare. Please check if it arrived!'); } } src/Commands/SolutionProviderMakeCommand.php 0000644 00000001103 15060171630 0015223 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Commands; use Illuminate\Console\GeneratorCommand; class SolutionProviderMakeCommand extends GeneratorCommand { protected $name = 'ignition:make-solution-provider'; protected $description = 'Create a new custom Ignition solution provider class'; protected $type = 'Solution Provider'; protected function getStub(): string { return __DIR__.'/stubs/solution-provider.stub'; } protected function getDefaultNamespace($rootNamespace) { return "{$rootNamespace}\\SolutionProviders"; } } src/FlareMiddleware/AddContext.php 0000644 00000001162 15060171630 0013127 0 ustar 00 <?php namespace Spatie\LaravelIgnition\FlareMiddleware; use Closure; use Illuminate\Log\Context\Repository; use Illuminate\Support\Facades\Context; use Spatie\FlareClient\FlareMiddleware\FlareMiddleware; use Spatie\FlareClient\Report; class AddContext implements FlareMiddleware { public function handle(Report $report, Closure $next) { if (! class_exists(Repository::class)) { return $next($report); } $allContext = Context::all(); if (count($allContext)) { $report->group('laravel_context', $allContext); } return $next($report); } } src/FlareMiddleware/AddExceptionHandledStatus.php 0000644 00000002517 15060171630 0016132 0 ustar 00 <?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); } } src/FlareMiddleware/AddEnvironmentInformation.php 0000644 00000001333 15060171630 0016215 0 ustar 00 <?php namespace Spatie\LaravelIgnition\FlareMiddleware; use Closure; use Spatie\FlareClient\FlareMiddleware\FlareMiddleware; use Spatie\FlareClient\Report; class AddEnvironmentInformation implements FlareMiddleware { public function handle(Report $report, Closure $next) { $report->frameworkVersion(app()->version()); $report->group('env', [ 'laravel_version' => app()->version(), 'laravel_locale' => app()->getLocale(), 'laravel_config_cached' => app()->configurationIsCached(), 'app_debug' => config('app.debug'), 'app_env' => config('app.env'), 'php_version' => phpversion(), ]); return $next($report); } } src/FlareMiddleware/AddDumps.php 0000644 00000001107 15060171630 0012572 0 ustar 00 <?php namespace Spatie\LaravelIgnition\FlareMiddleware; use Closure; use Spatie\FlareClient\FlareMiddleware\FlareMiddleware; use Spatie\FlareClient\Report; use Spatie\LaravelIgnition\Recorders\DumpRecorder\DumpRecorder; class AddDumps implements FlareMiddleware { protected DumpRecorder $dumpRecorder; public function __construct() { $this->dumpRecorder = app(DumpRecorder::class); } public function handle(Report $report, Closure $next) { $report->group('dumps', $this->dumpRecorder->getDumps()); return $next($report); } } src/FlareMiddleware/AddLogs.php 0000644 00000001057 15060171630 0012412 0 ustar 00 <?php namespace Spatie\LaravelIgnition\FlareMiddleware; use Spatie\FlareClient\FlareMiddleware\FlareMiddleware; use Spatie\FlareClient\Report; use Spatie\LaravelIgnition\Recorders\LogRecorder\LogRecorder; class AddLogs implements FlareMiddleware { protected LogRecorder $logRecorder; public function __construct() { $this->logRecorder = app(LogRecorder::class); } public function handle(Report $report, $next) { $report->group('logs', $this->logRecorder->getLogMessages()); return $next($report); } } src/FlareMiddleware/AddQueries.php 0000644 00000000754 15060171630 0013126 0 ustar 00 <?php namespace Spatie\LaravelIgnition\FlareMiddleware; use Spatie\FlareClient\Report; use Spatie\LaravelIgnition\Recorders\QueryRecorder\QueryRecorder; class AddQueries { protected QueryRecorder $queryRecorder; public function __construct() { $this->queryRecorder = app(QueryRecorder::class); } public function handle(Report $report, $next) { $report->group('queries', $this->queryRecorder->getQueries()); return $next($report); } } src/FlareMiddleware/AddNotifierName.php 0000644 00000000617 15060171630 0014067 0 ustar 00 <?php namespace Spatie\LaravelIgnition\FlareMiddleware; use Spatie\FlareClient\FlareMiddleware\FlareMiddleware; use Spatie\FlareClient\Report; class AddNotifierName implements FlareMiddleware { public const NOTIFIER_NAME = 'Laravel Client'; public function handle(Report $report, $next) { $report->notifierName(static::NOTIFIER_NAME); return $next($report); } } src/FlareMiddleware/AddJobs.php 0000644 00000001117 15060171630 0012400 0 ustar 00 <?php namespace Spatie\LaravelIgnition\FlareMiddleware; use Spatie\FlareClient\FlareMiddleware\FlareMiddleware; use Spatie\FlareClient\Report; use Spatie\LaravelIgnition\Recorders\JobRecorder\JobRecorder; class AddJobs implements FlareMiddleware { protected JobRecorder $jobRecorder; public function __construct() { $this->jobRecorder = app(JobRecorder::class); } public function handle(Report $report, $next) { if ($job = $this->jobRecorder->getJob()) { $report->group('job', $job); } return $next($report); } } src/FlareMiddleware/AddExceptionInformation.php 0000644 00000002717 15060171630 0015656 0 ustar 00 <?php namespace Spatie\LaravelIgnition\FlareMiddleware; use Illuminate\Database\QueryException; use Spatie\FlareClient\Contracts\ProvidesFlareContext; use Spatie\FlareClient\FlareMiddleware\FlareMiddleware; use Spatie\FlareClient\Report; class AddExceptionInformation implements FlareMiddleware { public function handle(Report $report, $next) { $throwable = $report->getThrowable(); $this->addUserDefinedContext($report); if (! $throwable instanceof QueryException) { return $next($report); } $report->group('exception', [ 'raw_sql' => $throwable->getSql(), ]); return $next($report); } private function addUserDefinedContext(Report $report): void { $throwable = $report->getThrowable(); if ($throwable === null) { return; } if ($throwable instanceof ProvidesFlareContext) { // ProvidesFlareContext writes directly to context groups and is handled in the flare-client-php package. return; } if (! method_exists($throwable, 'context')) { return; } $context = $throwable->context(); if (! is_array($context)) { return; } $exceptionContextGroup = []; foreach ($context as $key => $value) { $exceptionContextGroup[$key] = $value; } $report->group('exception', $exceptionContextGroup); } } src/Http/Middleware/RunnableSolutionsEnabled.php 0000644 00000000532 15060171630 0016000 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Http\Middleware; use Closure; use Spatie\LaravelIgnition\Support\RunnableSolutionsGuard; class RunnableSolutionsEnabled { public function handle($request, Closure $next) { if (! RunnableSolutionsGuard::check()) { abort(404); } return $next($request); } } src/Http/Controllers/HealthCheckController.php 0000644 00000000767 15060171630 0015511 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Http\Controllers; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Str; class HealthCheckController { public function __invoke() { return [ 'can_execute_commands' => $this->canExecuteCommands(), ]; } protected function canExecuteCommands(): bool { Artisan::call('help', ['--version']); $output = Artisan::output(); return Str::contains($output, app()->version()); } } src/Http/Controllers/ExecuteSolutionController.php 0000644 00000002653 15060171630 0016501 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Http\Controllers; use Illuminate\Foundation\Validation\ValidatesRequests; use Spatie\ErrorSolutions\Contracts\SolutionProviderRepository; use Spatie\LaravelIgnition\Exceptions\CannotExecuteSolutionForNonLocalIp; use Spatie\LaravelIgnition\Http\Requests\ExecuteSolutionRequest; use Spatie\LaravelIgnition\Support\RunnableSolutionsGuard; class ExecuteSolutionController { use ValidatesRequests; public function __invoke( ExecuteSolutionRequest $request, SolutionProviderRepository $solutionProviderRepository ) { $this ->ensureRunnableSolutionsEnabled() ->ensureLocalRequest(); $solution = $request->getRunnableSolution(); $solution->run($request->get('parameters', [])); return response()->noContent(); } public function ensureRunnableSolutionsEnabled(): self { // Should already be checked in middleware but we want to be 100% certain. abort_unless(RunnableSolutionsGuard::check(), 400); return $this; } public function ensureLocalRequest(): self { $ipIsPublic = filter_var( request()->ip(), FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ); if ($ipIsPublic) { throw CannotExecuteSolutionForNonLocalIp::make(); } return $this; } } src/Http/Controllers/UpdateConfigController.php 0000644 00000000605 15060171630 0015705 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Http\Controllers; use Spatie\Ignition\Config\IgnitionConfig; use Spatie\LaravelIgnition\Http\Requests\UpdateConfigRequest; class UpdateConfigController { public function __invoke(UpdateConfigRequest $request) { $result = (new IgnitionConfig())->saveValues($request->validated()); return response()->json($result); } } src/Http/Requests/UpdateConfigRequest.php 0000644 00000000654 15060171630 0014523 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Http\Requests; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; class UpdateConfigRequest extends FormRequest { public function rules(): array { return [ 'theme' => ['required', Rule::in(['light', 'dark', 'auto'])], 'editor' => ['required'], 'hide_solutions' => ['required', 'boolean'], ]; } } src/Http/Requests/ExecuteSolutionRequest.php 0000644 00000001771 15060171630 0015313 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Http\Requests; use Illuminate\Foundation\Http\FormRequest; use Spatie\ErrorSolutions\Contracts\RunnableSolution; use Spatie\ErrorSolutions\Contracts\Solution; use Spatie\ErrorSolutions\Contracts\SolutionProviderRepository; class ExecuteSolutionRequest extends FormRequest { public function rules(): array { return [ 'solution' => 'required', 'parameters' => 'array', ]; } public function getSolution(): Solution { $solution = app(SolutionProviderRepository::class) ->getSolutionForClass($this->get('solution')); abort_if(is_null($solution), 404, 'Solution could not be found'); return $solution; } public function getRunnableSolution(): RunnableSolution { $solution = $this->getSolution(); if (! $solution instanceof RunnableSolution) { abort(404, 'Runnable solution could not be found'); } return $solution; } } src/Facades/Flare.php 0000644 00000001246 15060171630 0010425 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Facades; use Illuminate\Support\Facades\Facade; use Spatie\LaravelIgnition\Support\SentReports; /** * @method static void glow(string $name, string $messageLevel = \Spatie\FlareClient\Enums\MessageLevels::INFO, array $metaData = []) * @method static void context($key, $value) * @method static void group(string $groupName, array $properties) * * @see \Spatie\FlareClient\Flare */ class Flare extends Facade { protected static function getFacadeAccessor() { return \Spatie\FlareClient\Flare::class; } public static function sentReports(): SentReports { return app(SentReports::class); } } src/Solutions/SolutionTransformers/LaravelSolutionTransformer.php 0000644 00000003322 15060171630 0021632 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions\SolutionTransformers; use Spatie\ErrorSolutions\Contracts\RunnableSolution; use Spatie\ErrorSolutions\Solutions\SolutionTransformer; use Spatie\LaravelIgnition\Http\Controllers\ExecuteSolutionController; use Throwable; class LaravelSolutionTransformer extends SolutionTransformer { /** @return array<string|mixed> */ public function toArray(): array { $baseProperties = parent::toArray(); if (! $this->isRunnable()) { return $baseProperties; } /** @var RunnableSolution $solution Type shenanigans */ $solution = $this->solution; $runnableProperties = [ 'is_runnable' => true, 'action_description' => $solution->getSolutionActionDescription(), 'run_button_text' => $solution->getRunButtonText(), 'execute_endpoint' => $this->executeEndpoint(), 'run_parameters' => $solution->getRunParameters(), ]; return array_merge($baseProperties, $runnableProperties); } protected function isRunnable(): bool { if (! $this->solution instanceof RunnableSolution) { return false; } if (! $this->executeEndpoint()) { return false; } return true; } protected function executeEndpoint(): ?string { try { // The action class needs to be prefixed with a `\` to Laravel from trying // to add its own global namespace from RouteServiceProvider::$namespace. return action('\\'.ExecuteSolutionController::class); } catch (Throwable $exception) { report($exception); return null; } } } src/ContextProviders/LaravelRequestContextProvider.php 0000644 00000005314 15060171630 0017407 0 ustar 00 <?php namespace Spatie\LaravelIgnition\ContextProviders; use Illuminate\Database\Eloquent\Model; use Illuminate\Http\Request as LaravelRequest; use Spatie\FlareClient\Context\RequestContextProvider; use Symfony\Component\HttpFoundation\Request as SymphonyRequest; use Throwable; class LaravelRequestContextProvider extends RequestContextProvider { protected LaravelRequest|SymphonyRequest|null $request; public function __construct(LaravelRequest $request) { $this->request = $request; } /** @return null|array<string, mixed> */ public function getUser(): array|null { try { /** @var object|null $user */ /** @phpstan-ignore-next-line */ $user = $this->request?->user(); if (! $user) { return null; } } catch (Throwable) { return null; } try { if (method_exists($user, 'toFlare')) { return $user->toFlare(); } if (method_exists($user, 'toArray')) { return $user->toArray(); } } catch (Throwable $e) { return null; } return null; } /** @return null|array<string, mixed> */ public function getRoute(): array|null { /** * @phpstan-ignore-next-line * @var \Illuminate\Routing\Route|null $route */ $route = $this->request->route(); if (! $route) { return null; } return [ 'route' => $route->getName(), 'routeParameters' => $this->getRouteParameters(), 'controllerAction' => $route->getActionName(), 'middleware' => array_values($route->gatherMiddleware() ?? []), ]; } /** @return array<int, mixed> */ protected function getRouteParameters(): array { try { /** @phpstan-ignore-next-line */ return collect(optional($this->request->route())->parameters ?? []) ->map(fn ($parameter) => $parameter instanceof Model ? $parameter->withoutRelations() : $parameter) ->map(function ($parameter) { return method_exists($parameter, 'toFlare') ? $parameter->toFlare() : $parameter; }) ->toArray(); } catch (Throwable) { return []; } } /** @return array<int, mixed> */ public function toArray(): array { $properties = parent::toArray(); if ($route = $this->getRoute()) { $properties['route'] = $route; } if ($user = $this->getUser()) { $properties['user'] = $user; } return $properties; } } src/ContextProviders/LaravelLivewireRequestContextProvider.php 0000644 00000007352 15060171630 0021122 0 ustar 00 <?php namespace Spatie\LaravelIgnition\ContextProviders; use Exception; use Illuminate\Http\Request; use Illuminate\Support\Arr; use Livewire\LivewireManager; use Livewire\Mechanisms\ComponentRegistry; class LaravelLivewireRequestContextProvider extends LaravelRequestContextProvider { public function __construct( Request $request, protected LivewireManager $livewireManager ) { parent::__construct($request); } /** @return array<string, string> */ public function getRequest(): array { $properties = parent::getRequest(); $properties['method'] = $this->livewireManager->originalMethod(); $properties['url'] = $this->livewireManager->originalUrl(); return $properties; } /** @return array<int|string, mixed> */ public function toArray(): array { $properties = parent::toArray(); $properties['livewire'] = $this->getLivewireInformation(); return $properties; } /** @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'); /** @phpstan-ignore-next-line */ $componentAlias = $this->request->input('fingerprint.name'); if ($componentAlias === null) { return []; } try { $componentClass = $this->livewireManager->getClass($componentAlias); } catch (Exception $e) { $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($updates), ], ]; } /** @return array<string, mixed> */ protected function resolveData(): array { /** @phpstan-ignore-next-line */ $data = $this->request->input('serverMemo.data') ?? []; /** @phpstan-ignore-next-line */ $dataMeta = $this->request->input('serverMemo.dataMeta') ?? []; foreach ($dataMeta['modelCollections'] ?? [] as $key => $value) { $data[$key] = array_merge($data[$key] ?? [], $value); } foreach ($dataMeta['models'] ?? [] as $key => $value) { $data[$key] = array_merge($data[$key] ?? [], $value); } return $data; } /** @return array<string, mixed> */ protected function resolveUpdates(array $updates): array { /** @phpstan-ignore-next-line */ $updates = $this->request->input('updates') ?? []; return array_map(function (array $update) { $update['payload'] = Arr::except($update['payload'] ?? [], ['id']); return $update; }, $updates); } } src/ContextProviders/LaravelConsoleContextProvider.php 0000644 00000000272 15060171630 0017357 0 ustar 00 <?php namespace Spatie\LaravelIgnition\ContextProviders; use Spatie\FlareClient\Context\ConsoleContextProvider; class LaravelConsoleContextProvider extends ConsoleContextProvider { } src/ContextProviders/LaravelContextProviderDetector.php 0000644 00000001636 15060171630 0017533 0 ustar 00 <?php namespace Spatie\LaravelIgnition\ContextProviders; use Illuminate\Http\Request; use Livewire\LivewireManager; use Spatie\FlareClient\Context\ContextProvider; use Spatie\FlareClient\Context\ContextProviderDetector; class LaravelContextProviderDetector implements ContextProviderDetector { public function detectCurrentContext(): ContextProvider { if (app()->runningInConsole()) { return new LaravelConsoleContextProvider($_SERVER['argv'] ?? []); } $request = app(Request::class); if ($this->isRunningLiveWire($request)) { return new LaravelLivewireRequestContextProvider($request, app(LivewireManager::class)); } return new LaravelRequestContextProvider($request); } protected function isRunningLiveWire(Request $request): bool { return $request->hasHeader('x-livewire') && $request->hasHeader('referer'); } } src/helpers.php 0000644 00000000773 15060171630 0007514 0 ustar 00 <?php use Spatie\LaravelIgnition\Renderers\ErrorPageRenderer; if (! function_exists('ddd')) { function ddd() { $args = func_get_args(); if (count($args) === 0) { throw new Exception('You should pass at least 1 argument to `ddd`'); } call_user_func_array('dump', $args); $renderer = app()->make(ErrorPageRenderer::class); $exception = new Exception('Dump, Die, Debug'); $renderer->render($exception); die(); } } src/Exceptions/ViewException.php 0000644 00000002307 15060171630 0012757 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Exceptions; use ErrorException; use Spatie\FlareClient\Contracts\ProvidesFlareContext; use Spatie\LaravelIgnition\Recorders\DumpRecorder\HtmlDumper; class ViewException extends ErrorException implements ProvidesFlareContext { /** @var array<string, mixed> */ protected array $viewData = []; protected string $view = ''; /** * @param array<string, mixed> $data * * @return void */ public function setViewData(array $data): void { $this->viewData = $data; } /** @return array<string, mixed> */ public function getViewData(): array { return $this->viewData; } public function setView(string $path): void { $this->view = $path; } protected function dumpViewData(mixed $variable): string { return (new HtmlDumper())->dumpVariable($variable); } /** @return array<string, mixed> */ public function context(): array { $context = [ 'view' => [ 'view' => $this->view, ], ]; $context['view']['data'] = array_map([$this, 'dumpViewData'], $this->viewData); return $context; } } src/Exceptions/InvalidConfig.php 0000644 00000001720 15060171630 0012700 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Exceptions; use Exception; use Monolog\Level; use Spatie\ErrorSolutions\Contracts\BaseSolution; use Spatie\ErrorSolutions\Contracts\ProvidesSolution; use Spatie\ErrorSolutions\Contracts\Solution; class InvalidConfig extends Exception implements ProvidesSolution { public static function invalidLogLevel(string $logLevel): self { return new self("Invalid log level `{$logLevel}` specified."); } public function getSolution(): Solution { $validLogLevels = array_map( fn (string $level) => strtolower($level), array_keys(Level::VALUES) ); $validLogLevelsString = implode(',', $validLogLevels); return BaseSolution::create() ->setSolutionTitle('You provided an invalid log level') ->setSolutionDescription("Please change the log level in your `config/logging.php` file. Valid log levels are {$validLogLevelsString}."); } } src/Exceptions/ViewExceptionWithSolution.php 0000644 00000000727 15060171630 0015354 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Exceptions; use Spatie\ErrorSolutions\Contracts\ProvidesSolution; use Spatie\ErrorSolutions\Contracts\Solution; class ViewExceptionWithSolution extends ViewException implements ProvidesSolution { protected Solution $solution; public function setSolution(Solution $solution): void { $this->solution = $solution; } public function getSolution(): Solution { return $this->solution; } } src/Exceptions/CannotExecuteSolutionForNonLocalIp.php 0000644 00000001522 15060171630 0017054 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Exceptions; use Spatie\ErrorSolutions\Contracts\BaseSolution; use Spatie\ErrorSolutions\Contracts\ProvidesSolution; use Spatie\ErrorSolutions\Contracts\Solution; use Symfony\Component\HttpKernel\Exception\HttpException; class CannotExecuteSolutionForNonLocalIp extends HttpException implements ProvidesSolution { public static function make(): self { return new self(403, 'Solutions cannot be run from your current IP address.'); } public function getSolution(): Solution { return BaseSolution::create() ->setSolutionTitle('Checking your environment settings') ->setSolutionDescription("Solutions can only be executed by requests from a local IP address. Keep in mind that `APP_DEBUG` should set to false on any production environment."); } } src/Recorders/QueryRecorder/Query.php 0000644 00000003157 15060171630 0013701 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Recorders\QueryRecorder; use Illuminate\Database\Events\QueryExecuted; class Query { protected string $sql; protected float $time; protected string $connectionName; /** @var array<string, string>|null */ protected ?array $bindings; protected float $microtime; public static function fromQueryExecutedEvent(QueryExecuted $queryExecuted, bool $reportBindings = false): self { return new self( $queryExecuted->sql, $queryExecuted->time, /** @phpstan-ignore-next-line */ $queryExecuted->connectionName ?? '', $reportBindings ? $queryExecuted->bindings : null ); } /** * @param string $sql * @param float $time * @param string $connectionName * @param array<string, string>|null $bindings * @param float|null $microtime */ protected function __construct( string $sql, float $time, string $connectionName, ?array $bindings = null, ?float $microtime = null ) { $this->sql = $sql; $this->time = $time; $this->connectionName = $connectionName; $this->bindings = $bindings; $this->microtime = $microtime ?? microtime(true); } /** * @return array<string, mixed> */ public function toArray(): array { return [ 'sql' => $this->sql, 'time' => $this->time, 'connection_name' => $this->connectionName, 'bindings' => $this->bindings, 'microtime' => $this->microtime, ]; } } src/Recorders/QueryRecorder/QueryRecorder.php 0000644 00000003662 15060171630 0015370 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Recorders\QueryRecorder; use Illuminate\Contracts\Foundation\Application; use Illuminate\Database\Events\QueryExecuted; class QueryRecorder { /** @var \Spatie\LaravelIgnition\Recorders\QueryRecorder\Query[] */ protected array $queries = []; protected Application $app; protected bool $reportBindings = true; protected ?int $maxQueries; public function __construct( Application $app, bool $reportBindings = true, ?int $maxQueries = 200 ) { $this->app = $app; $this->reportBindings = $reportBindings; $this->maxQueries = $maxQueries; } public function start(): self { /** @phpstan-ignore-next-line */ $this->app['events']->listen(QueryExecuted::class, [$this, 'record']); return $this; } public function record(QueryExecuted $queryExecuted): void { $this->queries[] = Query::fromQueryExecutedEvent($queryExecuted, $this->reportBindings); if (is_int($this->maxQueries)) { $this->queries = array_slice($this->queries, -$this->maxQueries); } } /** * @return array<int, array<string, mixed>> */ public function getQueries(): array { $queries = []; foreach ($this->queries as $query) { $queries[] = $query->toArray(); } return $queries; } public function reset(): void { $this->queries = []; } public function getReportBindings(): bool { return $this->reportBindings; } public function setReportBindings(bool $reportBindings): self { $this->reportBindings = $reportBindings; return $this; } public function getMaxQueries(): ?int { return $this->maxQueries; } public function setMaxQueries(?int $maxQueries): self { $this->maxQueries = $maxQueries; return $this; } } src/Recorders/JobRecorder/JobRecorder.php 0000644 00000012144 15060171630 0014375 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Recorders\JobRecorder; use DateTime; use Error; use Exception; use Illuminate\Contracts\Encryption\Encrypter; use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\Queue\Job; use Illuminate\Queue\CallQueuedClosure; use Illuminate\Queue\Events\JobExceptionOccurred; use Illuminate\Queue\Jobs\RedisJob; use Illuminate\Support\Str; use ReflectionClass; use ReflectionProperty; use RuntimeException; class JobRecorder { protected ?Job $job = null; public function __construct( protected Application $app, protected int $maxChainedJobReportingDepth = 5, ) { } public function start(): self { /** @phpstan-ignore-next-line */ $this->app['events']->listen(JobExceptionOccurred::class, [$this, 'record']); return $this; } public function record(JobExceptionOccurred $event): void { $this->job = $event->job; } /** * @return array<string, mixed>|null */ public function getJob(): ?array { if ($this->job === null) { return null; } return array_merge( $this->getJobProperties(), [ 'name' => $this->job->resolveName(), 'connection' => $this->job->getConnectionName(), 'queue' => $this->job->getQueue(), ] ); } public function reset(): void { $this->job = null; } protected function getJobProperties(): array { $payload = collect($this->resolveJobPayload()); $properties = []; foreach ($payload as $key => $value) { if (! in_array($key, ['job', 'data', 'displayName'])) { $properties[$key] = $value; } } try { if (is_string($payload['data'])) { $properties['data'] = json_decode($payload['data'], true, 512, JSON_THROW_ON_ERROR); } } catch (Exception $exception) { } if ($pushedAt = DateTime::createFromFormat('U.u', $payload->get('pushedAt', ''))) { $properties['pushedAt'] = $pushedAt->format(DATE_ATOM); } try { $properties['data'] = $this->resolveCommandProperties( $this->resolveObjectFromCommand($payload['data']['command']), $this->maxChainedJobReportingDepth ); } catch (Exception $exception) { } return $properties; } protected function resolveJobPayload(): array { if (! $this->job instanceof RedisJob) { return $this->job->payload(); } try { return json_decode($this->job->getReservedJob(), true, 512, JSON_THROW_ON_ERROR); } catch (Exception $e) { return $this->job->payload(); } } protected function resolveCommandProperties(object $command, int $maxChainDepth): array { $propertiesToIgnore = ['job', 'closure']; $properties = collect((new ReflectionClass($command))->getProperties()) ->reject(function (ReflectionProperty $property) use ($propertiesToIgnore) { return in_array($property->name, $propertiesToIgnore); }) ->mapWithKeys(function (ReflectionProperty $property) use ($command) { try { $property->setAccessible(true); return [$property->name => $property->getValue($command)]; } catch (Error $error) { return [$property->name => 'uninitialized']; } }); if ($properties->has('chained')) { $properties['chained'] = $this->resolveJobChain($properties->get('chained'), $maxChainDepth); } return $properties->all(); } /** * @param array<string, mixed> $chainedCommands * @param int $maxDepth * * @return array */ protected function resolveJobChain(array $chainedCommands, int $maxDepth): array { if ($maxDepth === 0) { return ['Ignition stopped recording jobs after this point since the max chain depth was reached']; } return array_map( function (string $command) use ($maxDepth) { $commandObject = $this->resolveObjectFromCommand($command); return [ 'name' => $commandObject instanceof CallQueuedClosure ? $commandObject->displayName() : get_class($commandObject), 'data' => $this->resolveCommandProperties($commandObject, $maxDepth - 1), ]; }, $chainedCommands ); } // Taken from Illuminate\Queue\CallQueuedHandler protected function resolveObjectFromCommand(string $command): object { if (Str::startsWith($command, 'O:')) { return unserialize($command); } if ($this->app->bound(Encrypter::class)) { /** @phpstan-ignore-next-line */ return unserialize($this->app[Encrypter::class]->decrypt($command)); } throw new RuntimeException('Unable to extract job payload.'); } } src/Recorders/LogRecorder/LogRecorder.php 0000644 00000003730 15060171630 0014414 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Recorders\LogRecorder; use Illuminate\Contracts\Foundation\Application; use Illuminate\Log\Events\MessageLogged; use Throwable; class LogRecorder { /** @var \Spatie\LaravelIgnition\Recorders\LogRecorder\LogMessage[] */ protected array $logMessages = []; protected Application $app; protected ?int $maxLogs; public function __construct(Application $app, ?int $maxLogs = null) { $this->app = $app; $this->maxLogs = $maxLogs; } public function start(): self { /** @phpstan-ignore-next-line */ $this->app['events']->listen(MessageLogged::class, [$this, 'record']); return $this; } public function record(MessageLogged $event): void { if ($this->shouldIgnore($event)) { return; } $this->logMessages[] = LogMessage::fromMessageLoggedEvent($event); if (is_int($this->maxLogs)) { $this->logMessages = array_slice($this->logMessages, -$this->maxLogs); } } /** @return array<array<int,string>> */ public function getLogMessages(): array { return $this->toArray(); } /** @return array<int, mixed> */ public function toArray(): array { $logMessages = []; foreach ($this->logMessages as $log) { $logMessages[] = $log->toArray(); } return $logMessages; } protected function shouldIgnore(mixed $event): bool { if (! isset($event->context['exception'])) { return false; } if (! $event->context['exception'] instanceof Throwable) { return false; } return true; } public function reset(): void { $this->logMessages = []; } public function getMaxLogs(): ?int { return $this->maxLogs; } public function setMaxLogs(?int $maxLogs): self { $this->maxLogs = $maxLogs; return $this; } } src/Recorders/LogRecorder/LogMessage.php 0000644 00000002364 15060171630 0014235 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Recorders\LogRecorder; use Illuminate\Log\Events\MessageLogged; class LogMessage { protected ?string $message; protected string $level; /** @var array<string, string> */ protected array $context = []; protected ?float $microtime; /** * @param string|null $message * @param string $level * @param array<string, string> $context * @param float|null $microtime */ public function __construct( ?string $message, string $level, array $context = [], ?float $microtime = null ) { $this->message = $message; $this->level = $level; $this->context = $context; $this->microtime = $microtime ?? microtime(true); } public static function fromMessageLoggedEvent(MessageLogged $event): self { return new self( $event->message, $event->level, $event->context ); } /** @return array<string, mixed> */ public function toArray(): array { return [ 'message' => $this->message, 'level' => $this->level, 'context' => $this->context, 'microtime' => $this->microtime, ]; } } src/Recorders/DumpRecorder/HtmlDumper.php 0000644 00000001622 15060171630 0014450 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Recorders\DumpRecorder; use Symfony\Component\VarDumper\Cloner\Data; use Symfony\Component\VarDumper\Cloner\VarCloner; use Symfony\Component\VarDumper\Dumper\HtmlDumper as BaseHtmlDumper; class HtmlDumper extends BaseHtmlDumper { public function __construct($output = null, string $charset = null, int $flags = 0) { parent::__construct($output, $charset, $flags); $this->setDumpHeader(''); } public function dumpVariable($variable): string { $cloner = new VarCloner(); $clonedData = $cloner->cloneVar($variable)->withMaxDepth(3); return $this->dump($clonedData); } public function dump(Data $data, $output = null, array $extraDisplayOptions = []): string { return (string)parent::dump($data, true, [ 'maxDepth' => 3, 'maxStringLength' => 160, ]); } } src/Recorders/DumpRecorder/DumpRecorder.php 0000644 00000007424 15060171630 0014770 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Recorders\DumpRecorder; use Illuminate\Contracts\Foundation\Application; use Illuminate\Support\Arr; use ReflectionMethod; use ReflectionProperty; use Symfony\Component\VarDumper\Cloner\Data; use Symfony\Component\VarDumper\VarDumper; class DumpRecorder { /** @var array<array<int,mixed>> */ protected array $dumps = []; protected Application $app; protected static bool $registeredHandler = false; public function __construct(Application $app) { $this->app = $app; } public function start(): self { $multiDumpHandler = new MultiDumpHandler(); $this->app->singleton(MultiDumpHandler::class, fn () => $multiDumpHandler); if (! self::$registeredHandler) { static::$registeredHandler = true; $this->ensureOriginalHandlerExists(); $originalHandler = VarDumper::setHandler(fn ($dumpedVariable) => $multiDumpHandler->dump($dumpedVariable)); $multiDumpHandler?->addHandler($originalHandler); $multiDumpHandler->addHandler(fn ($var) => (new DumpHandler($this))->dump($var)); } return $this; } public function record(Data $data): void { $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 11); $sourceFrame = $this->findSourceFrame($backtrace); $file = (string) Arr::get($sourceFrame, 'file'); $lineNumber = (int) Arr::get($sourceFrame, 'line'); $htmlDump = (new HtmlDumper())->dump($data); $this->dumps[] = new Dump($htmlDump, $file, $lineNumber); } public function getDumps(): array { return $this->toArray(); } public function reset() { $this->dumps = []; } public function toArray(): array { $dumps = []; foreach ($this->dumps as $dump) { $dumps[] = $dump->toArray(); } return $dumps; } /* * Only the `VarDumper` knows how to create the orignal HTML or CLI VarDumper. * Using reflection and the private VarDumper::register() method we can force it * to create and register a new VarDumper::$handler before we'll overwrite it. * Of course, we only need to do this if there isn't a registered VarDumper::$handler. * * @throws \ReflectionException */ protected function ensureOriginalHandlerExists(): void { $reflectionProperty = new ReflectionProperty(VarDumper::class, 'handler'); $reflectionProperty->setAccessible(true); $handler = $reflectionProperty->getValue(); if (! $handler) { // No handler registered yet, so we'll force VarDumper to create one. $reflectionMethod = new ReflectionMethod(VarDumper::class, 'register'); $reflectionMethod->setAccessible(true); $reflectionMethod->invoke(null); } } /** * Find the first meaningful stack frame that is not the `DumpRecorder` itself. * * @template T of array{class?: class-string, function?: string, line?: int, file?: string} * * @param array<T> $stacktrace * * @return null|T */ protected function findSourceFrame(array $stacktrace): ?array { $seenVarDumper = false; foreach ($stacktrace as $frame) { // Keep looping until we're past the VarDumper::dump() call in Symfony's helper functions file. if (Arr::get($frame, 'class') === VarDumper::class && Arr::get($frame, 'function') === 'dump') { $seenVarDumper = true; continue; } if (! $seenVarDumper) { continue; } // Return the next frame in the stack after the VarDumper::dump() call: return $frame; } return null; } } src/Recorders/DumpRecorder/Dump.php 0000644 00000001421 15060171630 0013271 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Recorders\DumpRecorder; class Dump { protected string $htmlDump; protected ?string $file; protected ?int $lineNumber; protected float $microtime; public function __construct(string $htmlDump, ?string $file, ?int $lineNumber, ?float $microtime = null) { $this->htmlDump = $htmlDump; $this->file = $file; $this->lineNumber = $lineNumber; $this->microtime = $microtime ?? microtime(true); } /** @return array<string, mixed> */ public function toArray(): array { return [ 'html_dump' => $this->htmlDump, 'file' => $this->file, 'line_number' => $this->lineNumber, 'microtime' => $this->microtime, ]; } } src/Recorders/DumpRecorder/MultiDumpHandler.php 0000644 00000000765 15060171630 0015614 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Recorders\DumpRecorder; class MultiDumpHandler { /** @var array<int, callable|null> */ protected array $handlers = []; public function dump(mixed $value): void { foreach ($this->handlers as $handler) { if ($handler) { $handler($value); } } } public function addHandler(callable $callable = null): self { $this->handlers[] = $callable; return $this; } } src/Recorders/DumpRecorder/DumpHandler.php 0000644 00000000704 15060171630 0014572 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Recorders\DumpRecorder; use Symfony\Component\VarDumper\Cloner\VarCloner; class DumpHandler { protected DumpRecorder $dumpRecorder; public function __construct(DumpRecorder $dumpRecorder) { $this->dumpRecorder = $dumpRecorder; } public function dump(mixed $value): void { $data = (new VarCloner)->cloneVar($value); $this->dumpRecorder->record($data); } } LICENSE.md 0000644 00000002075 15060171630 0006153 0 ustar 00 The MIT License (MIT) Copyright (c) Spatie <info@spatie.be> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. composer.json 0000644 00000005237 15060171630 0007274 0 ustar 00 { "name": "spatie/laravel-ignition", "description": "A beautiful error page for Laravel applications.", "keywords": [ "error", "page", "laravel", "flare" ], "authors": [ { "name": "Spatie", "email": "info@spatie.be", "role": "Developer" } ], "homepage": "https://flareapp.io/ignition", "license": "MIT", "require": { "php": "^8.1", "ext-curl": "*", "ext-json": "*", "ext-mbstring": "*", "illuminate/support": "^10.0|^11.0", "spatie/ignition": "^1.15", "symfony/console": "^6.2.3|^7.0", "symfony/var-dumper": "^6.2.3|^7.0" }, "require-dev": { "livewire/livewire": "^2.11|^3.3.5", "mockery/mockery": "^1.5.1", "openai-php/client": "^0.8.1", "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.16", "vlucas/phpdotenv": "^5.5" }, "suggest": { "openai-php/client": "Require get solutions from OpenAI", "psr/simple-cache-implementation": "Needed to cache solutions from OpenAI" }, "config": { "sort-packages": true, "allow-plugins": { "phpstan/extension-installer": true, "pestphp/pest-plugin": true, "php-http/discovery": false } }, "extra": { "laravel": { "providers": [ "Spatie\\LaravelIgnition\\IgnitionServiceProvider" ], "aliases": { "Flare": "Spatie\\LaravelIgnition\\Facades\\Flare" } } }, "autoload": { "psr-4": { "Spatie\\LaravelIgnition\\": "src" }, "files": [ "src/helpers.php" ] }, "autoload-dev": { "psr-4": { "Spatie\\LaravelIgnition\\Tests\\": "tests" } }, "minimum-stability": "dev", "prefer-stable": true, "scripts": { "analyse": "vendor/bin/phpstan analyse", "baseline": "vendor/bin/phpstan --generate-baseline", "format": "vendor/bin/php-cs-fixer fix --allow-risky=yes", "test": "vendor/bin/pest", "test-coverage": "vendor/bin/phpunit --coverage-html coverage" }, "support": { "issues": "https://github.com/spatie/laravel-ignition/issues", "forum": "https://twitter.com/flareappio", "source": "https://github.com/spatie/laravel-ignition", "docs": "https://flareapp.io/docs/ignition-for-laravel/introduction" } } config/ignition.php 0000644 00000027404 15060171630 0010350 0 ustar 00 <?php use Spatie\Ignition\Solutions\SolutionProviders\BadMethodCallSolutionProvider; use Spatie\Ignition\Solutions\SolutionProviders\MergeConflictSolutionProvider; use Spatie\Ignition\Solutions\SolutionProviders\UndefinedPropertySolutionProvider; use Spatie\LaravelIgnition\Recorders\DumpRecorder\DumpRecorder; use Spatie\LaravelIgnition\Recorders\JobRecorder\JobRecorder; use Spatie\LaravelIgnition\Recorders\LogRecorder\LogRecorder; use Spatie\LaravelIgnition\Recorders\QueryRecorder\QueryRecorder; use Spatie\LaravelIgnition\Solutions\SolutionProviders\DefaultDbNameSolutionProvider; use Spatie\LaravelIgnition\Solutions\SolutionProviders\GenericLaravelExceptionSolutionProvider; use Spatie\LaravelIgnition\Solutions\SolutionProviders\IncorrectValetDbCredentialsSolutionProvider; use Spatie\LaravelIgnition\Solutions\SolutionProviders\InvalidRouteActionSolutionProvider; use Spatie\LaravelIgnition\Solutions\SolutionProviders\MissingAppKeySolutionProvider; use Spatie\LaravelIgnition\Solutions\SolutionProviders\MissingColumnSolutionProvider; use Spatie\LaravelIgnition\Solutions\SolutionProviders\MissingImportSolutionProvider; use Spatie\LaravelIgnition\Solutions\SolutionProviders\MissingLivewireComponentSolutionProvider; use Spatie\LaravelIgnition\Solutions\SolutionProviders\MissingMixManifestSolutionProvider; use Spatie\LaravelIgnition\Solutions\SolutionProviders\MissingViteManifestSolutionProvider; use Spatie\LaravelIgnition\Solutions\SolutionProviders\RunningLaravelDuskInProductionProvider; use Spatie\LaravelIgnition\Solutions\SolutionProviders\TableNotFoundSolutionProvider; use Spatie\LaravelIgnition\Solutions\SolutionProviders\UndefinedViewVariableSolutionProvider; use Spatie\LaravelIgnition\Solutions\SolutionProviders\UnknownValidationSolutionProvider; use Spatie\LaravelIgnition\Solutions\SolutionProviders\ViewNotFoundSolutionProvider; use Spatie\LaravelIgnition\Solutions\SolutionProviders\OpenAiSolutionProvider; use Spatie\LaravelIgnition\Solutions\SolutionProviders\SailNetworkSolutionProvider; use Spatie\LaravelIgnition\Solutions\SolutionProviders\UnknownMariadbCollationSolutionProvider; use Spatie\LaravelIgnition\Solutions\SolutionProviders\UnknownMysql8CollationSolutionProvider; return [ /* |-------------------------------------------------------------------------- | Editor |-------------------------------------------------------------------------- | | Choose your preferred editor to use when clicking any edit button. | | Supported: "phpstorm", "vscode", "vscode-insiders", "textmate", "emacs", | "sublime", "atom", "nova", "macvim", "idea", "netbeans", | "xdebug", "phpstorm-remote" | */ 'editor' => env('IGNITION_EDITOR', 'phpstorm'), /* |-------------------------------------------------------------------------- | Theme |-------------------------------------------------------------------------- | | Here you may specify which theme Ignition should use. | | Supported: "light", "dark", "auto" | */ 'theme' => env('IGNITION_THEME', 'auto'), /* |-------------------------------------------------------------------------- | Sharing |-------------------------------------------------------------------------- | | You can share local errors with colleagues or others around the world. | Sharing is completely free and doesn't require an account on Flare. | | If necessary, you can completely disable sharing below. | */ 'enable_share_button' => env('IGNITION_SHARING_ENABLED', true), /* |-------------------------------------------------------------------------- | Register Ignition commands |-------------------------------------------------------------------------- | | Ignition comes with an additional make command that lets you create | new solution classes more easily. To keep your default Laravel | installation clean, this command is not registered by default. | | You can enable the command registration below. | */ 'register_commands' => env('REGISTER_IGNITION_COMMANDS', false), /* |-------------------------------------------------------------------------- | Solution Providers |-------------------------------------------------------------------------- | | List of solution providers that should be loaded. You may specify additional | providers as fully qualified class names. | */ 'solution_providers' => [ // from spatie/ignition BadMethodCallSolutionProvider::class, MergeConflictSolutionProvider::class, UndefinedPropertySolutionProvider::class, // from spatie/laravel-ignition IncorrectValetDbCredentialsSolutionProvider::class, MissingAppKeySolutionProvider::class, DefaultDbNameSolutionProvider::class, TableNotFoundSolutionProvider::class, MissingImportSolutionProvider::class, InvalidRouteActionSolutionProvider::class, ViewNotFoundSolutionProvider::class, RunningLaravelDuskInProductionProvider::class, MissingColumnSolutionProvider::class, UnknownValidationSolutionProvider::class, MissingMixManifestSolutionProvider::class, MissingViteManifestSolutionProvider::class, MissingLivewireComponentSolutionProvider::class, UndefinedViewVariableSolutionProvider::class, GenericLaravelExceptionSolutionProvider::class, OpenAiSolutionProvider::class, SailNetworkSolutionProvider::class, UnknownMysql8CollationSolutionProvider::class, UnknownMariadbCollationSolutionProvider::class, ], /* |-------------------------------------------------------------------------- | Ignored Solution Providers |-------------------------------------------------------------------------- | | You may specify a list of solution providers (as fully qualified class | names) that shouldn't be loaded. Ignition will ignore these classes | and possible solutions provided by them will never be displayed. | */ 'ignored_solution_providers' => [ ], /* |-------------------------------------------------------------------------- | Runnable Solutions |-------------------------------------------------------------------------- | | Some solutions that Ignition displays are runnable and can perform | various tasks. By default, runnable solutions are only enabled when your | app has debug mode enabled and the environment is `local` or | `development`. | | Using the `IGNITION_ENABLE_RUNNABLE_SOLUTIONS` environment variable, you | can override this behaviour and enable or disable runnable solutions | regardless of the application's environment. | | Default: env('IGNITION_ENABLE_RUNNABLE_SOLUTIONS') | */ 'enable_runnable_solutions' => env('IGNITION_ENABLE_RUNNABLE_SOLUTIONS'), /* |-------------------------------------------------------------------------- | Remote Path Mapping |-------------------------------------------------------------------------- | | If you are using a remote dev server, like Laravel Homestead, Docker, or | even a remote VPS, it will be necessary to specify your path mapping. | | Leaving one, or both of these, empty or null will not trigger the remote | URL changes and Ignition will treat your editor links as local files. | | "remote_sites_path" is an absolute base path for your sites or projects | in Homestead, Vagrant, Docker, or another remote development server. | | Example value: "/home/vagrant/Code" | | "local_sites_path" is an absolute base path for your sites or projects | on your local computer where your IDE or code editor is running on. | | Example values: "/Users/<name>/Code", "C:\Users\<name>\Documents\Code" | */ 'remote_sites_path' => env('IGNITION_REMOTE_SITES_PATH', base_path()), 'local_sites_path' => env('IGNITION_LOCAL_SITES_PATH', ''), /* |-------------------------------------------------------------------------- | Housekeeping Endpoint Prefix |-------------------------------------------------------------------------- | | Ignition registers a couple of routes when it is enabled. Below you may | specify a route prefix that will be used to host all internal links. | */ 'housekeeping_endpoint_prefix' => '_ignition', /* |-------------------------------------------------------------------------- | Settings File |-------------------------------------------------------------------------- | | Ignition allows you to save your settings to a specific global file. | | If no path is specified, a file with settings will be saved to the user's | home directory. The directory depends on the OS and its settings but it's | typically `~/.ignition.json`. In this case, the settings will be applied | to all of your projects where Ignition is used and the path is not | specified. | | However, if you want to store your settings on a project basis, or you | want to keep them in another directory, you can specify a path where | the settings file will be saved. The path should be an existing directory | with correct write access. | For example, create a new `ignition` folder in the storage directory and | use `storage_path('ignition')` as the `settings_file_path`. | | Default value: '' (empty string) */ 'settings_file_path' => '', /* |-------------------------------------------------------------------------- | Recorders |-------------------------------------------------------------------------- | | Ignition registers a couple of recorders when it is enabled. Below you may | specify a recorders will be used to record specific events. | */ 'recorders' => [ DumpRecorder::class, JobRecorder::class, LogRecorder::class, QueryRecorder::class, ], /* * When a key is set, we'll send your exceptions to Open AI to generate a solution */ 'open_ai_key' => env('IGNITION_OPEN_AI_KEY'), /* |-------------------------------------------------------------------------- | Include arguments |-------------------------------------------------------------------------- | | Ignition show you stack traces of exceptions with the arguments that were | passed to each method. This feature can be disabled here. | */ 'with_stack_frame_arguments' => true, /* |-------------------------------------------------------------------------- | Argument reducers |-------------------------------------------------------------------------- | | Ignition show you stack traces of exceptions with the arguments that were | passed to each method. To make these variables more readable, you can | specify a list of classes here which summarize the variables. | */ 'argument_reducers' => [ \Spatie\Backtrace\Arguments\Reducers\BaseTypeArgumentReducer::class, \Spatie\Backtrace\Arguments\Reducers\ArrayArgumentReducer::class, \Spatie\Backtrace\Arguments\Reducers\StdClassArgumentReducer::class, \Spatie\Backtrace\Arguments\Reducers\EnumArgumentReducer::class, \Spatie\Backtrace\Arguments\Reducers\ClosureArgumentReducer::class, \Spatie\Backtrace\Arguments\Reducers\DateTimeArgumentReducer::class, \Spatie\Backtrace\Arguments\Reducers\DateTimeZoneArgumentReducer::class, \Spatie\Backtrace\Arguments\Reducers\SymphonyRequestArgumentReducer::class, \Spatie\LaravelIgnition\ArgumentReducers\ModelArgumentReducer::class, \Spatie\LaravelIgnition\ArgumentReducers\CollectionArgumentReducer::class, \Spatie\Backtrace\Arguments\Reducers\StringableArgumentReducer::class, ], ]; config/flare.php 0000644 00000005615 15060171630 0007621 0 ustar 00 <?php use Spatie\FlareClient\FlareMiddleware\AddGitInformation; use Spatie\FlareClient\FlareMiddleware\RemoveRequestIp; 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; use Spatie\LaravelIgnition\FlareMiddleware\AddQueries; use Spatie\LaravelIgnition\FlareMiddleware\AddContext; use Spatie\LaravelIgnition\FlareMiddleware\AddNotifierName; return [ /* | |-------------------------------------------------------------------------- | Flare API key |-------------------------------------------------------------------------- | | Specify Flare's API key below to enable error reporting to the service. | | More info: https://flareapp.io/docs/general/projects | */ 'key' => env('FLARE_KEY'), /* |-------------------------------------------------------------------------- | Middleware |-------------------------------------------------------------------------- | | These middleware will modify the contents of the report sent to Flare. | */ 'flare_middleware' => [ RemoveRequestIp::class, AddGitInformation::class, AddNotifierName::class, AddEnvironmentInformation::class, AddExceptionInformation::class, AddDumps::class, AddLogs::class => [ 'maximum_number_of_collected_logs' => 200, ], AddQueries::class => [ 'maximum_number_of_collected_queries' => 200, 'report_query_bindings' => true, ], AddJobs::class => [ 'max_chained_job_reporting_depth' => 5, ], AddContext::class, AddExceptionHandledStatus::class, CensorRequestBodyFields::class => [ 'censor_fields' => [ 'password', 'password_confirmation', ], ], CensorRequestHeaders::class => [ 'headers' => [ 'API-KEY', 'Authorization', 'Cookie', 'Set-Cookie', 'X-CSRF-TOKEN', 'X-XSRF-TOKEN', ] ], ], /* |-------------------------------------------------------------------------- | Reporting log statements |-------------------------------------------------------------------------- | | If this setting is `false` log statements won't be sent as events to Flare, | no matter which error level you specified in the Flare log channel. | */ 'send_logs_as_events' => true, ]; README.md 0000644 00000005503 15060171630 0006025 0 ustar 00 # Ignition: a beautiful error page for Laravel apps [](https://packagist.org/packages/spatie/laravel-ignition)  [](https://packagist.org/packages/spatie/laravel-ignition) [Ignition](https://flareapp.io/docs/ignition-for-laravel/introduction) is a beautiful and customizable error page for Laravel applications. It is the default error page for new Laravel applications. It also allows to publicly share your errors on [Flare](https://flareapp.io). If configured with a valid Flare API key, your errors in production applications will be tracked, and you'll get notified when they happen. `spatie/laravel-ignition` works for Laravel 8 and 9 applications running on PHP 8.0 and above. Looking for Ignition for Laravel 5.x, 6.x or 7.x or old PHP versions? `facade/ignition` is still compatible.  ## Are you a visual learner? In [this video on YouTube](https://youtu.be/LEY0N0Bteew?t=739), you'll see a demo of all of the features. Do know more about the design decisions we made, read [this blog post](https://freek.dev/2168-ignition-the-most-beautiful-error-page-for-laravel-and-php-got-a-major-redesign). ## Official Documentation The official documentation for Ignition can be found on the [Flare website](https://flareapp.io/docs/ignition/introducing-ignition/overview). ## Support us [<img src="https://github-ads.s3.eu-central-1.amazonaws.com/laravel-ignition.jpg?t=1" width="419px" />](https://spatie.be/github-ad-click/laravel-ignition) We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). ### Changelog Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. ## Contributing Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. ## Security Vulnerabilities Please review [our security policy](../../security/policy) on how to report security vulnerabilities. ## Credits - [Spatie](https://spatie.be) - [All Contributors](../../contributors) ## License The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
| ver. 1.4 |
Github
|
.
| PHP 8.2.29 | Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ñтраницы: 0 |
proxy
|
phpinfo
|
ÐаÑтройка