One Hat Cyber Team
Your IP:
216.73.216.102
Server IP:
198.54.114.155
Server:
Linux server71.web-hosting.com 4.18.0-513.18.1.lve.el8.x86_64 #1 SMP Thu Feb 22 12:55:50 UTC 2024 x86_64
Server Software:
LiteSpeed
PHP Version:
5.6.40
Create File
|
Create Folder
Execute
Dir :
~
/
home
/
fluxyjvi
/
www
/
assets
/
images
/
View File Name :
laravel-ignition.tar
src/helpers.php 0000644 00000000773 15107340472 0007520 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/Renderers/IgnitionExceptionRenderer.php 0000644 00000001005 15107340472 0015122 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/Renderers/ErrorPageRenderer.php 0000644 00000002746 15107340472 0013366 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Renderers; use Spatie\FlareClient\Flare; use Spatie\Ignition\Config\IgnitionConfig; use Spatie\Ignition\Contracts\SolutionProviderRepository; 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/Exceptions/ViewExceptionWithSolution.php 0000644 00000000713 15107340472 0015353 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Exceptions; use Spatie\Ignition\Contracts\ProvidesSolution; use Spatie\Ignition\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/ViewException.php 0000644 00000002307 15107340472 0012763 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/CannotExecuteSolutionForNonLocalIp.php 0000644 00000001500 15107340472 0017054 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Exceptions; use Spatie\Ignition\Contracts\BaseSolution; use Spatie\Ignition\Contracts\ProvidesSolution; use Spatie\Ignition\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/Exceptions/InvalidConfig.php 0000644 00000001676 15107340472 0012716 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Exceptions; use Exception; use Monolog\Level; use Spatie\Ignition\Contracts\BaseSolution; use Spatie\Ignition\Contracts\ProvidesSolution; use Spatie\Ignition\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/Facades/Flare.php 0000644 00000001246 15107340472 0010431 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/Commands/SolutionMakeCommand.php 0000644 00000001554 15107340472 0013526 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/TestCommand.php 0000644 00000011250 15107340472 0012025 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 15107340472 0015227 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/Commands/stubs/runnable-solution.stub 0000644 00000001272 15107340472 0014620 0 ustar 00 <?php namespace DummyNamespace; use Spatie\Ignition\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 00000000552 15107340472 0013014 0 ustar 00 <?php namespace DummyNamespace; use Spatie\Ignition\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 00000000534 15107340473 0014645 0 ustar 00 <?php namespace DummyNamespace; use Spatie\Ignition\Contracts\HasSolutionsForThrowable; use Throwable; class DummyClass implements HasSolutionsForThrowable { public function canSolve(Throwable $throwable): bool { return false; } public function getSolutions(Throwable $throwable): array { return []; } } src/Recorders/QueryRecorder/Query.php 0000644 00000003157 15107340473 0013706 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 15107340473 0015375 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/DumpRecorder/HtmlDumper.php 0000644 00000001363 15107340473 0014457 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 { protected $dumpHeader = ''; 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/DumpHandler.php 0000644 00000000704 15107340473 0014577 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); } } src/Recorders/DumpRecorder/MultiDumpHandler.php 0000644 00000000765 15107340473 0015621 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/Dump.php 0000644 00000001421 15107340473 0013276 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/DumpRecorder.php 0000644 00000007424 15107340473 0014775 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/JobRecorder/JobRecorder.php 0000644 00000011600 15107340473 0014376 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; } } 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/LogMessage.php 0000644 00000002364 15107340473 0014242 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/LogRecorder/LogRecorder.php 0000644 00000003730 15107340473 0014421 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/ignition-routes.php 0000644 00000001413 15107340473 0011206 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/Support/SentReports.php 0000644 00000002172 15107340473 0012016 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/LaravelDocumentationLinkFinder.php 0000644 00000004114 15107340473 0015612 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/LaravelVersion.php 0000644 00000000264 15107340473 0012462 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Support; class LaravelVersion { public static function major(): string { return explode('.', app()->version())[0]; } } src/Support/StringComparator.php 0000644 00000003041 15107340473 0013020 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Support; use Illuminate\Support\Collection; class StringComparator { /** * @param array<int|string, string> $strings * @param string $input * @param int $sensitivity * * @return string|null */ public static function findClosestMatch(array $strings, string $input, int $sensitivity = 4): ?string { $closestDistance = -1; $closestMatch = null; foreach ($strings as $string) { $levenshteinDistance = levenshtein($input, $string); if ($levenshteinDistance === 0) { $closestMatch = $string; $closestDistance = 0; break; } if ($levenshteinDistance <= $closestDistance || $closestDistance < 0) { $closestMatch = $string; $closestDistance = $levenshteinDistance; } } if ($closestDistance <= $sensitivity) { return $closestMatch; } return null; } /** * @param array<int, string> $strings * @param string $input * * @return string|null */ public static function findSimilarText(array $strings, string $input): ?string { if (empty($strings)) { return null; } return Collection::make($strings) ->sortByDesc(function (string $string) use ($input) { similar_text($input, $string, $percentage); return $percentage; }) ->first(); } } src/Support/Composer/FakeComposer.php 0000644 00000000674 15107340473 0013700 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Support\Composer; class FakeComposer implements Composer { /** @return array<string, mixed> */ public function getClassMap(): array { return []; } /** @return array<string, mixed> */ public function getPrefixes(): array { return []; } /** @return array<string, mixed> */ public function getPrefixesPsr4(): array { return []; } } src/Support/Composer/Composer.php 0000644 00000000516 15107340473 0013104 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Support\Composer; interface Composer { /** @return array<string, mixed> */ public function getClassMap(): array; /** @return array<string, mixed> */ public function getPrefixes(): array; /** @return array<string, mixed> */ public function getPrefixesPsr4(): array; } src/Support/Composer/ComposerClassMap.php 0000644 00000007355 15107340473 0014540 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Support\Composer; use function app_path; use function base_path; use Illuminate\Support\Str; use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\SplFileInfo; class ComposerClassMap { /** @var \Spatie\LaravelIgnition\Support\Composer\Composer */ protected object $composer; protected string $basePath; public function __construct(?string $autoloaderPath = null) { $autoloaderPath = $autoloaderPath ?? base_path('/vendor/autoload.php'); $this->composer = file_exists($autoloaderPath) ? require $autoloaderPath : new FakeComposer(); $this->basePath = app_path(); } /** @return array<string, string> */ public function listClasses(): array { $classes = $this->composer->getClassMap(); return array_merge($classes, $this->listClassesInPsrMaps()); } public function searchClassMap(string $missingClass): ?string { foreach ($this->composer->getClassMap() as $fqcn => $file) { $basename = basename($file, '.php'); if ($basename === $missingClass) { return $fqcn; } } return null; } /** @return array<string, mixed> */ public function listClassesInPsrMaps(): array { // TODO: This is incorrect. Doesnt list all fqcns. Need to parse namespace? e.g. App\LoginController is wrong $prefixes = array_merge( $this->composer->getPrefixes(), $this->composer->getPrefixesPsr4() ); $classes = []; foreach ($prefixes as $namespace => $directories) { foreach ($directories as $directory) { if (file_exists($directory)) { $files = (new Finder) ->in($directory) ->files() ->name('*.php'); foreach ($files as $file) { if ($file instanceof SplFileInfo) { $fqcn = $this->getFullyQualifiedClassNameFromFile($namespace, $file); $classes[$fqcn] = $file->getRelativePathname(); } } } } } return $classes; } public function searchPsrMaps(string $missingClass): ?string { $prefixes = array_merge( $this->composer->getPrefixes(), $this->composer->getPrefixesPsr4() ); foreach ($prefixes as $namespace => $directories) { foreach ($directories as $directory) { if (file_exists($directory)) { $files = (new Finder) ->in($directory) ->files() ->name('*.php'); foreach ($files as $file) { if ($file instanceof SplFileInfo) { $basename = basename($file->getRelativePathname(), '.php'); if ($basename === $missingClass) { return $namespace . basename($file->getRelativePathname(), '.php'); } } } } } } return null; } protected function getFullyQualifiedClassNameFromFile(string $rootNamespace, SplFileInfo $file): string { $class = trim(str_replace($this->basePath, '', (string)$file->getRealPath()), DIRECTORY_SEPARATOR); $class = str_replace( [DIRECTORY_SEPARATOR, 'App\\'], ['\\', app()->getNamespace()], ucfirst(Str::replaceLast('.php', '', $class)) ); return $rootNamespace . $class; } } src/Support/RunnableSolutionsGuard.php 0000644 00000002053 15107340473 0014175 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/Support/LivewireComponentParser.php 0000644 00000005735 15107340473 0014364 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Support; use Illuminate\Support\Collection; use Illuminate\Support\Str; use Livewire\LivewireManager; use ReflectionClass; use ReflectionMethod; use ReflectionProperty; class LivewireComponentParser { protected string $componentClass; protected ReflectionClass $reflectionClass; public static function create(string $componentAlias): self { return new self($componentAlias); } public function __construct(protected string $componentAlias) { $this->componentClass = app(LivewireManager::class)->getClass($this->componentAlias); $this->reflectionClass = new ReflectionClass($this->componentClass); } public function getComponentClass(): string { return $this->componentClass; } public function getPropertyNamesLike(string $similar): Collection { $properties = collect($this->reflectionClass->getProperties(ReflectionProperty::IS_PUBLIC)) // @phpstan-ignore-next-line ->reject(fn (ReflectionProperty $reflectionProperty) => $reflectionProperty->class !== $this->reflectionClass->name) ->map(fn (ReflectionProperty $reflectionProperty) => $reflectionProperty->name); $computedProperties = collect($this->reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC)) // @phpstan-ignore-next-line ->reject(fn (ReflectionMethod $reflectionMethod) => $reflectionMethod->class !== $this->reflectionClass->name) ->filter(fn (ReflectionMethod $reflectionMethod) => str_starts_with($reflectionMethod->name, 'get') && str_ends_with($reflectionMethod->name, 'Property')) ->map(fn (ReflectionMethod $reflectionMethod) => lcfirst(Str::of($reflectionMethod->name)->after('get')->before('Property'))); return $this->filterItemsBySimilarity( $properties->merge($computedProperties), $similar ); } public function getMethodNamesLike(string $similar): Collection { $methods = collect($this->reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC)) // @phpstan-ignore-next-line ->reject(fn (ReflectionMethod $reflectionMethod) => $reflectionMethod->class !== $this->reflectionClass->name) ->map(fn (ReflectionMethod $reflectionMethod) => $reflectionMethod->name); return $this->filterItemsBySimilarity($methods, $similar); } protected function filterItemsBySimilarity(Collection $items, string $similar): Collection { return $items ->map(function (string $name) use ($similar) { similar_text($similar, $name, $percentage); return ['match' => $percentage, 'value' => $name]; }) ->sortByDesc('match') ->filter(function (array $item) { return $item['match'] > 40; }) ->map(function (array $item) { return $item['value']; }) ->values(); } } src/Support/FlareLogHandler.php 0000644 00000005423 15107340473 0012521 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/Solutions/LivewireDiscoverSolution.php 0000644 00000002353 15107340473 0015074 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions; use Livewire\LivewireComponentsFinder; use Spatie\Ignition\Contracts\RunnableSolution; class LivewireDiscoverSolution implements RunnableSolution { protected string $customTitle; public function __construct(string $customTitle = '') { $this->customTitle = $customTitle; } public function getSolutionTitle(): string { return $this->customTitle; } public function getSolutionDescription(): string { return 'You might have forgotten to discover your Livewire components.'; } public function getDocumentationLinks(): array { return [ 'Livewire: Artisan Commands' => 'https://laravel-livewire.com/docs/2.x/artisan-commands', ]; } public function getRunParameters(): array { return []; } public function getSolutionActionDescription(): string { return 'You can discover your Livewire components using `php artisan livewire:discover`.'; } public function getRunButtonText(): string { return 'Run livewire:discover'; } public function run(array $parameters = []): void { app(LivewireComponentsFinder::class)->build(); } } src/Solutions/SuggestUsingCorrectDbNameSolution.php 0000644 00000001460 15107340473 0016625 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions; use Spatie\Ignition\Contracts\Solution; class SuggestUsingCorrectDbNameSolution implements Solution { public function getSolutionTitle(): string { return 'Database name seems incorrect'; } public function getSolutionDescription(): string { $defaultDatabaseName = env('DB_DATABASE'); return "You're using the default database name `$defaultDatabaseName`. This database does not exist.\n\nEdit the `.env` file and use the correct database name in the `DB_DATABASE` key."; } /** @return array<string, string> */ public function getDocumentationLinks(): array { return [ 'Database: Getting Started docs' => 'https://laravel.com/docs/master/database#configuration', ]; } } src/Solutions/MakeViewVariableOptionalSolution.php 0000644 00000007362 15107340473 0016500 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions; use Illuminate\Support\Facades\Blade; use Illuminate\Support\Str; use Spatie\Ignition\Contracts\RunnableSolution; class MakeViewVariableOptionalSolution implements RunnableSolution { protected ?string $variableName; protected ?string $viewFile; public function __construct(string $variableName = null, string $viewFile = null) { $this->variableName = $variableName; $this->viewFile = $viewFile; } public function getSolutionTitle(): string { return "$$this->variableName is undefined"; } public function getDocumentationLinks(): array { return []; } public function getSolutionActionDescription(): string { $output = [ 'Make the variable optional in the blade template.', "Replace `{{ $$this->variableName }}` with `{{ $$this->variableName ?? '' }}`", ]; return implode(PHP_EOL, $output); } public function getRunButtonText(): string { return 'Make variable optional'; } public function getSolutionDescription(): string { return ''; } public function getRunParameters(): array { return [ 'variableName' => $this->variableName, 'viewFile' => $this->viewFile, ]; } /** * @param array<string, mixed> $parameters * * @return bool */ public function isRunnable(array $parameters = []): bool { return $this->makeOptional($this->getRunParameters()) !== false; } /** * @param array<string, string> $parameters * * @return void */ public function run(array $parameters = []): void { $output = $this->makeOptional($parameters); if ($output !== false) { file_put_contents($parameters['viewFile'], $output); } } protected function isSafePath(string $path): bool { if (! Str::startsWith($path, ['/', './'])) { return false; } if (! Str::endsWith($path, '.blade.php')) { return false; } return true; } /** * @param array<string, string> $parameters * * @return bool|string */ public function makeOptional(array $parameters = []): bool|string { if (! $this->isSafePath($parameters['viewFile'])) { return false; } $originalContents = (string)file_get_contents($parameters['viewFile']); $newContents = str_replace('$'.$parameters['variableName'], '$'.$parameters['variableName']." ?? ''", $originalContents); $originalTokens = token_get_all(Blade::compileString($originalContents)); $newTokens = token_get_all(Blade::compileString($newContents)); $expectedTokens = $this->generateExpectedTokens($originalTokens, $parameters['variableName']); if ($expectedTokens !== $newTokens) { return false; } return $newContents; } /** * @param array<int, mixed> $originalTokens * @param string $variableName * * @return array<int, mixed> */ protected function generateExpectedTokens(array $originalTokens, string $variableName): array { $expectedTokens = []; foreach ($originalTokens as $token) { $expectedTokens[] = $token; if ($token[0] === T_VARIABLE && $token[1] === '$'.$variableName) { $expectedTokens[] = [T_WHITESPACE, ' ', $token[2]]; $expectedTokens[] = [T_COALESCE, '??', $token[2]]; $expectedTokens[] = [T_WHITESPACE, ' ', $token[2]]; $expectedTokens[] = [T_CONSTANT_ENCAPSED_STRING, "''", $token[2]]; } } return $expectedTokens; } } src/Solutions/SolutionProviders/UnknownValidationSolutionProvider.php 0000644 00000004734 15107340473 0022513 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions\SolutionProviders; use BadMethodCallException; use Illuminate\Support\Collection; use Illuminate\Support\Str; use Illuminate\Validation\Validator; use ReflectionClass; use ReflectionMethod; use Spatie\Ignition\Contracts\BaseSolution; use Spatie\Ignition\Contracts\HasSolutionsForThrowable; use Spatie\LaravelIgnition\Support\StringComparator; use Throwable; class UnknownValidationSolutionProvider implements HasSolutionsForThrowable { protected const REGEX = '/Illuminate\\\\Validation\\\\Validator::(?P<method>validate(?!(Attribute|UsingCustomRule))[A-Z][a-zA-Z]+)/m'; public function canSolve(Throwable $throwable): bool { if (! $throwable instanceof BadMethodCallException) { return false; } return ! is_null($this->getMethodFromExceptionMessage($throwable->getMessage())); } public function getSolutions(Throwable $throwable): array { return [ BaseSolution::create() ->setSolutionTitle('Unknown Validation Rule') ->setSolutionDescription($this->getSolutionDescription($throwable)), ]; } protected function getSolutionDescription(Throwable $throwable): string { $method = (string)$this->getMethodFromExceptionMessage($throwable->getMessage()); $possibleMethod = StringComparator::findSimilarText( $this->getAvailableMethods()->toArray(), $method ); if (empty($possibleMethod)) { return ''; } $rule = Str::snake(str_replace('validate', '', $possibleMethod)); return "Did you mean `{$rule}` ?"; } protected function getMethodFromExceptionMessage(string $message): ?string { if (! preg_match(self::REGEX, $message, $matches)) { return null; } return $matches['method']; } protected function getAvailableMethods(): Collection { $class = new ReflectionClass(Validator::class); $extensions = Collection::make((app('validator')->make([], []))->extensions) ->keys() ->map(fn (string $extension) => 'validate'.Str::studly($extension)); return Collection::make($class->getMethods()) ->filter(fn (ReflectionMethod $method) => preg_match('/(validate(?!(Attribute|UsingCustomRule))[A-Z][a-zA-Z]+)/', $method->name)) ->map(fn (ReflectionMethod $method) => $method->name) ->merge($extensions); } } src/Solutions/SolutionProviders/MissingViteManifestSolutionProvider.php 0000644 00000003451 15107340473 0022764 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions\SolutionProviders; use Illuminate\Support\Str; use Spatie\Ignition\Contracts\BaseSolution; use Spatie\Ignition\Contracts\HasSolutionsForThrowable; use Spatie\Ignition\Contracts\Solution; use Throwable; class MissingViteManifestSolutionProvider implements HasSolutionsForThrowable { /** @var array<string, string> */ protected array $links = [ 'Asset bundling with Vite' => 'https://laravel.com/docs/9.x/vite#running-vite', ]; public function canSolve(Throwable $throwable): bool { return Str::startsWith($throwable->getMessage(), 'Vite manifest not found'); } public function getSolutions(Throwable $throwable): array { return [ $this->getSolution(), ]; } public function getSolution(): Solution { /** @var string */ $baseCommand = collect([ 'pnpm-lock.yaml' => 'pnpm', 'yarn.lock' => 'yarn', ])->first(fn ($_, $lockfile) => file_exists(base_path($lockfile)), 'npm run'); return app()->environment('local') ? $this->getLocalSolution($baseCommand) : $this->getProductionSolution($baseCommand); } protected function getLocalSolution(string $baseCommand): Solution { return BaseSolution::create('Start the development server') ->setSolutionDescription("Run `{$baseCommand} dev` in your terminal and refresh the page.") ->setDocumentationLinks($this->links); } protected function getProductionSolution(string $baseCommand): Solution { return BaseSolution::create('Build the production assets') ->setSolutionDescription("Run `{$baseCommand} build` in your deployment script.") ->setDocumentationLinks($this->links); } } src/Solutions/SolutionProviders/LazyLoadingViolationSolutionProvider.php 0000644 00000002573 15107340473 0023142 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions\SolutionProviders; use Illuminate\Database\LazyLoadingViolationException; use Spatie\Ignition\Contracts\BaseSolution; use Spatie\Ignition\Contracts\HasSolutionsForThrowable; use Spatie\LaravelIgnition\Support\LaravelVersion; use Throwable; class LazyLoadingViolationSolutionProvider implements HasSolutionsForThrowable { public function canSolve(Throwable $throwable): bool { if ($throwable instanceof LazyLoadingViolationException) { return true; } if (! $previous = $throwable->getPrevious()) { return false; } return $previous instanceof LazyLoadingViolationException; } public function getSolutions(Throwable $throwable): array { $majorVersion = LaravelVersion::major(); return [BaseSolution::create( 'Lazy loading was disabled to detect N+1 problems' ) ->setSolutionDescription( 'Either avoid lazy loading the relation or allow lazy loading.' ) ->setDocumentationLinks([ 'Read the docs on preventing lazy loading' => "https://laravel.com/docs/{$majorVersion}.x/eloquent-relationships#preventing-lazy-loading", 'Watch a video on how to deal with the N+1 problem' => 'https://www.youtube.com/watch?v=ZE7KBeraVpc', ]),]; } } src/Solutions/SolutionProviders/RouteNotDefinedSolutionProvider.php 0000644 00000003166 15107340473 0022075 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions\SolutionProviders; use Illuminate\Support\Facades\Route; use Spatie\Ignition\Contracts\BaseSolution; use Spatie\Ignition\Contracts\HasSolutionsForThrowable; use Spatie\LaravelIgnition\Support\StringComparator; use Symfony\Component\Routing\Exception\RouteNotFoundException; use Throwable; class RouteNotDefinedSolutionProvider implements HasSolutionsForThrowable { protected const REGEX = '/Route \[(.*)\] not defined/m'; public function canSolve(Throwable $throwable): bool { if (! $throwable instanceof RouteNotFoundException) { return false; } return (bool)preg_match(self::REGEX, $throwable->getMessage(), $matches); } public function getSolutions(Throwable $throwable): array { preg_match(self::REGEX, $throwable->getMessage(), $matches); $missingRoute = $matches[1] ?? ''; $suggestedRoute = $this->findRelatedRoute($missingRoute); if ($suggestedRoute) { return [ BaseSolution::create("{$missingRoute} was not defined.") ->setSolutionDescription("Did you mean `{$suggestedRoute}`?"), ]; } return [ BaseSolution::create("{$missingRoute} was not defined.") ->setSolutionDescription('Are you sure that the route is defined'), ]; } protected function findRelatedRoute(string $missingRoute): ?string { Route::getRoutes()->refreshNameLookups(); return StringComparator::findClosestMatch(array_keys(Route::getRoutes()->getRoutesByName()), $missingRoute); } } src/Solutions/SolutionProviders/MissingAppKeySolutionProvider.php 0000644 00000001252 15107340473 0021554 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions\SolutionProviders; use RuntimeException; use Spatie\Ignition\Contracts\HasSolutionsForThrowable; use Spatie\LaravelIgnition\Solutions\GenerateAppKeySolution; use Throwable; class MissingAppKeySolutionProvider implements HasSolutionsForThrowable { public function canSolve(Throwable $throwable): bool { if (! $throwable instanceof RuntimeException) { return false; } return $throwable->getMessage() === 'No application encryption key has been specified.'; } public function getSolutions(Throwable $throwable): array { return [new GenerateAppKeySolution()]; } } src/Solutions/SolutionProviders/MissingImportSolutionProvider.php 0000644 00000002646 15107340473 0021645 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions\SolutionProviders; use Spatie\Ignition\Contracts\HasSolutionsForThrowable; use Spatie\LaravelIgnition\Solutions\SuggestImportSolution; use Spatie\LaravelIgnition\Support\Composer\ComposerClassMap; use Throwable; class MissingImportSolutionProvider implements HasSolutionsForThrowable { protected ?string $foundClass; protected ComposerClassMap $composerClassMap; public function canSolve(Throwable $throwable): bool { $pattern = '/Class \"([^\s]+)\" not found/m'; if (! preg_match($pattern, $throwable->getMessage(), $matches)) { return false; } $class = $matches[1]; $this->composerClassMap = new ComposerClassMap(); $this->search($class); return ! is_null($this->foundClass); } /** * @param \Throwable $throwable * * @return array<int, SuggestImportSolution> */ public function getSolutions(Throwable $throwable): array { if (is_null($this->foundClass)) { return []; } return [new SuggestImportSolution($this->foundClass)]; } protected function search(string $missingClass): void { $this->foundClass = $this->composerClassMap->searchClassMap($missingClass); if (is_null($this->foundClass)) { $this->foundClass = $this->composerClassMap->searchPsrMaps($missingClass); } } } src/Solutions/SolutionProviders/RunningLaravelDuskInProductionProvider.php 0000644 00000002122 15107340473 0023405 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions\SolutionProviders; use Exception; use Spatie\Ignition\Contracts\BaseSolution; use Spatie\Ignition\Contracts\HasSolutionsForThrowable; use Throwable; class RunningLaravelDuskInProductionProvider implements HasSolutionsForThrowable { public function canSolve(Throwable $throwable): bool { if (! $throwable instanceof Exception) { return false; } return $throwable->getMessage() === 'It is unsafe to run Dusk in production.'; } public function getSolutions(Throwable $throwable): array { return [ BaseSolution::create() ->setSolutionTitle('Laravel Dusk should not be run in production.') ->setSolutionDescription('Install the dependencies with the `--no-dev` flag.'), BaseSolution::create() ->setSolutionTitle('Laravel Dusk can be run in other environments.') ->setSolutionDescription('Consider setting the `APP_ENV` to something other than `production` like `local` for example.'), ]; } } src/Solutions/SolutionProviders/UndefinedViewVariableSolutionProvider.php 0000644 00000006760 15107340473 0023244 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions\SolutionProviders; use Spatie\Ignition\Contracts\BaseSolution; use Spatie\Ignition\Contracts\HasSolutionsForThrowable; use Spatie\Ignition\Contracts\Solution; use Spatie\LaravelIgnition\Exceptions\ViewException; use Spatie\LaravelIgnition\Solutions\MakeViewVariableOptionalSolution; use Spatie\LaravelIgnition\Solutions\SuggestCorrectVariableNameSolution; use Throwable; class UndefinedViewVariableSolutionProvider implements HasSolutionsForThrowable { protected string $variableName; protected string $viewFile; public function canSolve(Throwable $throwable): bool { if (! $throwable instanceof ViewException) { return false; } return $this->getNameAndView($throwable) !== null; } public function getSolutions(Throwable $throwable): array { $solutions = []; /** @phpstan-ignore-next-line */ extract($this->getNameAndView($throwable)); if (! isset($variableName)) { return []; } if (isset($viewFile)) { /** @phpstan-ignore-next-line */ $solutions = $this->findCorrectVariableSolutions($throwable, $variableName, $viewFile); $solutions[] = $this->findOptionalVariableSolution($variableName, $viewFile); } return $solutions; } /** * @param \Spatie\LaravelIgnition\Exceptions\ViewException $throwable * @param string $variableName * @param string $viewFile * * @return array<int, \Spatie\Ignition\Contracts\Solution> */ protected function findCorrectVariableSolutions( ViewException $throwable, string $variableName, string $viewFile ): array { return collect($throwable->getViewData()) ->map(function ($value, $key) use ($variableName) { similar_text($variableName, $key, $percentage); return ['match' => $percentage, 'value' => $value]; }) ->sortByDesc('match') ->filter(fn ($var) => $var['match'] > 40) ->keys() ->map(fn ($suggestion) => new SuggestCorrectVariableNameSolution($variableName, $viewFile, $suggestion)) ->map(function ($solution) { return $solution->isRunnable() ? $solution : BaseSolution::create($solution->getSolutionTitle()) ->setSolutionDescription($solution->getSolutionDescription()); }) ->toArray(); } protected function findOptionalVariableSolution(string $variableName, string $viewFile): Solution { $optionalSolution = new MakeViewVariableOptionalSolution($variableName, $viewFile); return $optionalSolution->isRunnable() ? $optionalSolution : BaseSolution::create($optionalSolution->getSolutionTitle()) ->setSolutionDescription($optionalSolution->getSolutionDescription()); } /** * @param \Throwable $throwable * * @return array<string, string>|null */ protected function getNameAndView(Throwable $throwable): ?array { $pattern = '/Undefined variable:? (.*?) \(View: (.*?)\)/'; preg_match($pattern, $throwable->getMessage(), $matches); if (count($matches) === 3) { [, $variableName, $viewFile] = $matches; $variableName = ltrim($variableName, '$'); return compact('variableName', 'viewFile'); } return null; } } src/Solutions/SolutionProviders/SolutionProviderRepository.php 0000644 00000006143 15107340473 0021214 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions\SolutionProviders; use Illuminate\Support\Collection; use Spatie\Ignition\Contracts\HasSolutionsForThrowable; use Spatie\Ignition\Contracts\ProvidesSolution; use Spatie\Ignition\Contracts\Solution; use Spatie\Ignition\Contracts\SolutionProviderRepository as SolutionProviderRepositoryContract; use Throwable; class SolutionProviderRepository implements SolutionProviderRepositoryContract { /** * @param array<int, ProvidesSolution> $solutionProviders */ protected Collection $solutionProviders; /** * @param array<int, ProvidesSolution> $solutionProviders */ public function __construct(array $solutionProviders = []) { $this->solutionProviders = Collection::make($solutionProviders); } public function registerSolutionProvider(string $solutionProviderClass): SolutionProviderRepositoryContract { $this->solutionProviders->push($solutionProviderClass); return $this; } public function registerSolutionProviders(array $solutionProviderClasses): SolutionProviderRepositoryContract { $this->solutionProviders = $this->solutionProviders->merge($solutionProviderClasses); return $this; } public function getSolutionsForThrowable(Throwable $throwable): array { $solutions = []; if ($throwable instanceof Solution) { $solutions[] = $throwable; } if ($throwable instanceof ProvidesSolution) { $solutions[] = $throwable->getSolution(); } /** @phpstan-ignore-next-line */ $providedSolutions = $this->solutionProviders ->filter(function (string $solutionClass) { if (! in_array(HasSolutionsForThrowable::class, class_implements($solutionClass) ?: [])) { return false; } if (in_array($solutionClass, config('ignition.ignored_solution_providers', []))) { return false; } return true; }) ->map(fn (string $solutionClass) => app($solutionClass)) ->filter(function (HasSolutionsForThrowable $solutionProvider) use ($throwable) { try { return $solutionProvider->canSolve($throwable); } catch (Throwable $e) { return false; } }) ->map(function (HasSolutionsForThrowable $solutionProvider) use ($throwable) { try { return $solutionProvider->getSolutions($throwable); } catch (Throwable $e) { return []; } }) ->flatten() ->toArray(); return array_merge($solutions, $providedSolutions); } public function getSolutionForClass(string $solutionClass): ?Solution { if (! class_exists($solutionClass)) { return null; } if (! in_array(Solution::class, class_implements($solutionClass) ?: [])) { return null; } return app($solutionClass); } } src/Solutions/SolutionProviders/ViewNotFoundSolutionProvider.php 0000644 00000007224 15107340473 0021425 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions\SolutionProviders; use Illuminate\Support\Arr; use Illuminate\Support\Facades\View; use InvalidArgumentException; use Spatie\Ignition\Contracts\BaseSolution; use Spatie\Ignition\Contracts\HasSolutionsForThrowable; use Spatie\LaravelIgnition\Exceptions\ViewException; use Spatie\LaravelIgnition\Support\StringComparator; use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\SplFileInfo; use Throwable; class ViewNotFoundSolutionProvider implements HasSolutionsForThrowable { protected const REGEX = '/View \[(.*)\] not found/m'; public function canSolve(Throwable $throwable): bool { if (! $throwable instanceof InvalidArgumentException && ! $throwable instanceof ViewException) { return false; } return (bool)preg_match(self::REGEX, $throwable->getMessage(), $matches); } public function getSolutions(Throwable $throwable): array { preg_match(self::REGEX, $throwable->getMessage(), $matches); $missingView = $matches[1] ?? null; $suggestedView = $this->findRelatedView($missingView); if ($suggestedView) { return [ BaseSolution::create() ->setSolutionTitle("{$missingView} was not found.") ->setSolutionDescription("Did you mean `{$suggestedView}`?"), ]; } return [ BaseSolution::create() ->setSolutionTitle("{$missingView} was not found.") ->setSolutionDescription('Are you sure the view exists and is a `.blade.php` file?'), ]; } protected function findRelatedView(string $missingView): ?string { $views = $this->getAllViews(); return StringComparator::findClosestMatch($views, $missingView); } /** @return array<int, string> */ protected function getAllViews(): array { /** @var \Illuminate\View\FileViewFinder $fileViewFinder */ $fileViewFinder = View::getFinder(); $extensions = $fileViewFinder->getExtensions(); $viewsForHints = collect($fileViewFinder->getHints()) ->flatMap(function ($paths, string $namespace) use ($extensions) { $paths = Arr::wrap($paths); return collect($paths) ->flatMap(fn (string $path) => $this->getViewsInPath($path, $extensions)) ->map(fn (string $view) => "{$namespace}::{$view}") ->toArray(); }); $viewsForViewPaths = collect($fileViewFinder->getPaths()) ->flatMap(fn (string $path) => $this->getViewsInPath($path, $extensions)); return $viewsForHints->merge($viewsForViewPaths)->toArray(); } /** * @param string $path * @param array<int, string> $extensions * * @return array<int, string> */ protected function getViewsInPath(string $path, array $extensions): array { $filePatterns = array_map(fn (string $extension) => "*.{$extension}", $extensions); $extensionsWithDots = array_map(fn (string $extension) => ".{$extension}", $extensions); $files = (new Finder()) ->in($path) ->files(); foreach ($filePatterns as $filePattern) { $files->name($filePattern); } $views = []; foreach ($files as $file) { if ($file instanceof SplFileInfo) { $view = $file->getRelativePathname(); $view = str_replace($extensionsWithDots, '', $view); $view = str_replace('/', '.', $view); $views[] = $view; } } return $views; } } src/Solutions/SolutionProviders/SailNetworkSolutionProvider.php 0000644 00000002017 15107340473 0021273 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions\SolutionProviders; use Spatie\Ignition\Contracts\BaseSolution; use Spatie\Ignition\Contracts\HasSolutionsForThrowable; use Throwable; class SailNetworkSolutionProvider implements HasSolutionsForThrowable { public function canSolve(Throwable $throwable): bool { return app()->runningInConsole() && str_contains($throwable->getMessage(), 'php_network_getaddresses') && file_exists(base_path('vendor/bin/sail')) && file_exists(base_path('docker-compose.yml')) && env('LARAVEL_SAIL') === null; } public function getSolutions(Throwable $throwable): array { return [ BaseSolution::create('Network address not found') ->setSolutionDescription('Did you mean to use `sail artisan`?') ->setDocumentationLinks([ 'Sail: Executing Artisan Commands' => 'https://laravel.com/docs/sail#executing-artisan-commands', ]), ]; } } src/Solutions/SolutionProviders/InvalidRouteActionSolutionProvider.php 0000644 00000005623 15107340474 0022603 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions\SolutionProviders; use Illuminate\Support\Str; use Spatie\Ignition\Contracts\BaseSolution; use Spatie\Ignition\Contracts\HasSolutionsForThrowable; use Spatie\LaravelIgnition\Support\Composer\ComposerClassMap; use Spatie\LaravelIgnition\Support\StringComparator; use Throwable; use UnexpectedValueException; class InvalidRouteActionSolutionProvider implements HasSolutionsForThrowable { protected const REGEX = '/\[([a-zA-Z\\\\]+)\]/m'; public function canSolve(Throwable $throwable): bool { if (! $throwable instanceof UnexpectedValueException) { return false; } if (! preg_match(self::REGEX, $throwable->getMessage(), $matches)) { return false; } return Str::startsWith($throwable->getMessage(), 'Invalid route action: '); } public function getSolutions(Throwable $throwable): array { preg_match(self::REGEX, $throwable->getMessage(), $matches); $invalidController = $matches[1] ?? null; $suggestedController = $this->findRelatedController($invalidController); if ($suggestedController === $invalidController) { return [ BaseSolution::create("`{$invalidController}` is not invokable.") ->setSolutionDescription("The controller class `{$invalidController}` is not invokable. Did you forget to add the `__invoke` method or is the controller's method missing in your routes file?"), ]; } if ($suggestedController) { return [ BaseSolution::create("`{$invalidController}` was not found.") ->setSolutionDescription("Controller class `{$invalidController}` for one of your routes was not found. Did you mean `{$suggestedController}`?"), ]; } return [ BaseSolution::create("`{$invalidController}` was not found.") ->setSolutionDescription("Controller class `{$invalidController}` for one of your routes was not found. Are you sure this controller exists and is imported correctly?"), ]; } protected function findRelatedController(string $invalidController): ?string { $composerClassMap = app(ComposerClassMap::class); $controllers = collect($composerClassMap->listClasses()) ->filter(function (string $file, string $fqcn) { return Str::endsWith($fqcn, 'Controller'); }) ->mapWithKeys(function (string $file, string $fqcn) { return [$fqcn => class_basename($fqcn)]; }) ->toArray(); $basenameMatch = StringComparator::findClosestMatch($controllers, $invalidController, 4); $controllers = array_flip($controllers); $fqcnMatch = StringComparator::findClosestMatch($controllers, $invalidController, 4); return $fqcnMatch ?? $basenameMatch; } } src/Solutions/SolutionProviders/GenericLaravelExceptionSolutionProvider.php 0000644 00000003427 15107340474 0023602 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions\SolutionProviders; use Illuminate\Broadcasting\BroadcastException; use Spatie\Ignition\Contracts\BaseSolution; use Spatie\Ignition\Contracts\HasSolutionsForThrowable; use Spatie\LaravelIgnition\Support\LaravelVersion; use Throwable; class GenericLaravelExceptionSolutionProvider implements HasSolutionsForThrowable { public function canSolve(Throwable $throwable): bool { return ! is_null($this->getSolutionTexts($throwable)); } public function getSolutions(Throwable $throwable): array { if (! $texts = $this->getSolutionTexts($throwable)) { return []; } $solution = BaseSolution::create($texts['title']) ->setSolutionDescription($texts['description']) ->setDocumentationLinks($texts['links']); return ([$solution]); } /** * @param \Throwable $throwable * * @return array<string, mixed>|null */ protected function getSolutionTexts(Throwable $throwable) : ?array { foreach ($this->getSupportedExceptions() as $supportedClass => $texts) { if ($throwable instanceof $supportedClass) { return $texts; } } return null; } /** @return array<string, mixed> */ protected function getSupportedExceptions(): array { $majorVersion = LaravelVersion::major(); return [ BroadcastException::class => [ 'title' => 'Here are some links that might help solve this problem', 'description' => '', 'links' => [ 'Laravel docs on authentication' => "https://laravel.com/docs/{$majorVersion}.x/authentication", ], ], ]; } } src/Solutions/SolutionProviders/UndefinedLivewirePropertySolutionProvider.php 0000644 00000003213 15107340474 0024206 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions\SolutionProviders; use Livewire\Exceptions\PropertyNotFoundException; use Spatie\Ignition\Contracts\HasSolutionsForThrowable; use Spatie\LaravelIgnition\Solutions\SuggestLivewirePropertyNameSolution; use Spatie\LaravelIgnition\Support\LivewireComponentParser; use Throwable; class UndefinedLivewirePropertySolutionProvider implements HasSolutionsForThrowable { public function canSolve(Throwable $throwable): bool { return $throwable instanceof PropertyNotFoundException; } public function getSolutions(Throwable $throwable): array { ['variable' => $variable, 'component' => $component] = $this->getMethodAndComponent($throwable); if ($variable === null || $component === null) { return []; } $parsed = LivewireComponentParser::create($component); return $parsed->getPropertyNamesLike($variable) ->map(function (string $suggested) use ($parsed, $variable) { return new SuggestLivewirePropertyNameSolution( $variable, $parsed->getComponentClass(), '$'.$suggested ); }) ->toArray(); } /** * @param \Throwable $throwable * * @return array<string, string|null> */ protected function getMethodAndComponent(Throwable $throwable): array { preg_match_all('/\[([\d\w\-_\$]*)\]/m', $throwable->getMessage(), $matches, PREG_SET_ORDER, 0); return [ 'variable' => $matches[0][1] ?? null, 'component' => $matches[1][1] ?? null, ]; } } src/Solutions/SolutionProviders/DefaultDbNameSolutionProvider.php 0000644 00000001620 15107340474 0021464 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions\SolutionProviders; use Illuminate\Database\QueryException; use Spatie\Ignition\Contracts\HasSolutionsForThrowable; use Spatie\LaravelIgnition\Solutions\SuggestUsingCorrectDbNameSolution; use Throwable; class DefaultDbNameSolutionProvider implements HasSolutionsForThrowable { const MYSQL_UNKNOWN_DATABASE_CODE = 1049; public function canSolve(Throwable $throwable): bool { if (! $throwable instanceof QueryException) { return false; } if ($throwable->getCode() !== self::MYSQL_UNKNOWN_DATABASE_CODE) { return false; } if (! in_array(env('DB_DATABASE'), ['homestead', 'laravel'])) { return false; } return true; } public function getSolutions(Throwable $throwable): array { return [new SuggestUsingCorrectDbNameSolution()]; } } src/Solutions/SolutionProviders/MissingMixManifestSolutionProvider.php 0000644 00000001307 15107340474 0022611 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions\SolutionProviders; use Illuminate\Support\Str; use Spatie\Ignition\Contracts\BaseSolution; use Spatie\Ignition\Contracts\HasSolutionsForThrowable; use Throwable; class MissingMixManifestSolutionProvider implements HasSolutionsForThrowable { public function canSolve(Throwable $throwable): bool { return Str::startsWith($throwable->getMessage(), 'Mix manifest not found'); } public function getSolutions(Throwable $throwable): array { return [ BaseSolution::create('Missing Mix Manifest File') ->setSolutionDescription('Did you forget to run `npm install && npm run dev`?'), ]; } } src/Solutions/SolutionProviders/TableNotFoundSolutionProvider.php 0000644 00000001730 15107340474 0021537 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions\SolutionProviders; use Illuminate\Database\QueryException; use Spatie\Ignition\Contracts\HasSolutionsForThrowable; use Spatie\LaravelIgnition\Solutions\RunMigrationsSolution; use Throwable; class TableNotFoundSolutionProvider implements HasSolutionsForThrowable { /** * See https://dev.mysql.com/doc/refman/8.0/en/server-error-reference.html#error_er_bad_table_error. */ const MYSQL_BAD_TABLE_CODE = '42S02'; public function canSolve(Throwable $throwable): bool { if (! $throwable instanceof QueryException) { return false; } return $this->isBadTableErrorCode($throwable->getCode()); } protected function isBadTableErrorCode(string $code): bool { return $code === static::MYSQL_BAD_TABLE_CODE; } public function getSolutions(Throwable $throwable): array { return [new RunMigrationsSolution('A table was not found')]; } } src/Solutions/SolutionProviders/MissingColumnSolutionProvider.php 0000644 00000001731 15107340474 0021623 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions\SolutionProviders; use Illuminate\Database\QueryException; use Spatie\Ignition\Contracts\HasSolutionsForThrowable; use Spatie\LaravelIgnition\Solutions\RunMigrationsSolution; use Throwable; class MissingColumnSolutionProvider implements HasSolutionsForThrowable { /** * See https://dev.mysql.com/doc/refman/8.0/en/server-error-reference.html#error_er_bad_field_error. */ const MYSQL_BAD_FIELD_CODE = '42S22'; public function canSolve(Throwable $throwable): bool { if (! $throwable instanceof QueryException) { return false; } return $this->isBadTableErrorCode($throwable->getCode()); } protected function isBadTableErrorCode(string $code): bool { return $code === static::MYSQL_BAD_FIELD_CODE; } public function getSolutions(Throwable $throwable): array { return [new RunMigrationsSolution('A column was not found')]; } } src/Solutions/SolutionProviders/OpenAiSolutionProvider.php 0000644 00000002102 15107340474 0020200 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions\SolutionProviders; use Illuminate\Support\Str; use OpenAI\Client; use Spatie\Ignition\Contracts\HasSolutionsForThrowable; use Spatie\Ignition\Solutions\OpenAi\OpenAiSolutionProvider as BaseOpenAiSolutionProvider; use Throwable; class OpenAiSolutionProvider implements HasSolutionsForThrowable { public function canSolve(Throwable $throwable): bool { if (! class_exists(Client::class)) { return false; } if (config('ignition.open_ai_key') === null) { return false; } return true; } public function getSolutions(Throwable $throwable): array { $solutionProvider = new BaseOpenAiSolutionProvider( openAiKey: config('ignition.open_ai_key'), cache: cache()->store(config('cache.default')), cacheTtlInSeconds: 60, applicationType: 'Laravel ' . Str::before(app()->version(), '.'), applicationPath: base_path(), ); return $solutionProvider->getSolutions($throwable); } } src/Solutions/SolutionProviders/MissingLivewireComponentSolutionProvider.php 0000644 00000002116 15107340474 0024035 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions\SolutionProviders; use Livewire\Exceptions\ComponentNotFoundException; use Livewire\LivewireComponentsFinder; use Spatie\Ignition\Contracts\HasSolutionsForThrowable; use Spatie\LaravelIgnition\Solutions\LivewireDiscoverSolution; use Throwable; class MissingLivewireComponentSolutionProvider implements HasSolutionsForThrowable { public function canSolve(Throwable $throwable): bool { if (! $this->livewireIsInstalled()) { return false; } if (! $throwable instanceof ComponentNotFoundException) { return false; } return true; } public function getSolutions(Throwable $throwable): array { return [new LivewireDiscoverSolution('A Livewire component was not found')]; } public function livewireIsInstalled(): bool { if (! class_exists(ComponentNotFoundException::class)) { return false; } if (! class_exists(LivewireComponentsFinder::class)) { return false; } return true; } } src/Solutions/SolutionProviders/IncorrectValetDbCredentialsSolutionProvider.php 0000644 00000003142 15107340474 0024402 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions\SolutionProviders; use Illuminate\Database\QueryException; use Spatie\Ignition\Contracts\HasSolutionsForThrowable; use Spatie\LaravelIgnition\Solutions\UseDefaultValetDbCredentialsSolution; use Throwable; class IncorrectValetDbCredentialsSolutionProvider implements HasSolutionsForThrowable { const MYSQL_ACCESS_DENIED_CODE = 1045; public function canSolve(Throwable $throwable): bool { if (PHP_OS !== 'Darwin') { return false; } if (! $throwable instanceof QueryException) { return false; } if (! $this->isAccessDeniedCode($throwable->getCode())) { return false; } if (! $this->envFileExists()) { return false; } if (! $this->isValetInstalled()) { return false; } if ($this->usingCorrectDefaultCredentials()) { return false; } return true; } public function getSolutions(Throwable $throwable): array { return [new UseDefaultValetDbCredentialsSolution()]; } protected function envFileExists(): bool { return file_exists(base_path('.env')); } protected function isAccessDeniedCode(string $code): bool { return $code === static::MYSQL_ACCESS_DENIED_CODE; } protected function isValetInstalled(): bool { return file_exists('/usr/local/bin/valet'); } protected function usingCorrectDefaultCredentials(): bool { return env('DB_USERNAME') === 'root' && env('DB_PASSWORD') === ''; } } src/Solutions/SolutionProviders/UndefinedLivewireMethodSolutionProvider.php 0000644 00000003115 15107340474 0023603 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions\SolutionProviders; use Livewire\Exceptions\MethodNotFoundException; use Spatie\Ignition\Contracts\HasSolutionsForThrowable; use Spatie\LaravelIgnition\Solutions\SuggestLivewireMethodNameSolution; use Spatie\LaravelIgnition\Support\LivewireComponentParser; use Throwable; class UndefinedLivewireMethodSolutionProvider implements HasSolutionsForThrowable { public function canSolve(Throwable $throwable): bool { return $throwable instanceof MethodNotFoundException; } public function getSolutions(Throwable $throwable): array { ['methodName' => $methodName, 'component' => $component] = $this->getMethodAndComponent($throwable); if ($methodName === null || $component === null) { return []; } $parsed = LivewireComponentParser::create($component); return $parsed->getMethodNamesLike($methodName) ->map(function (string $suggested) use ($parsed, $methodName) { return new SuggestLivewireMethodNameSolution( $methodName, $parsed->getComponentClass(), $suggested ); }) ->toArray(); } /** @return array<string, string|null> */ protected function getMethodAndComponent(Throwable $throwable): array { preg_match_all('/\[([\d\w\-_]*)\]/m', $throwable->getMessage(), $matches, PREG_SET_ORDER); return [ 'methodName' => $matches[0][1] ?? null, 'component' => $matches[1][1] ?? null, ]; } } src/Solutions/SuggestImportSolution.php 0000644 00000001154 15107340475 0014423 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions; use Spatie\Ignition\Contracts\Solution; class SuggestImportSolution implements Solution { protected string $class; public function __construct(string $class = '') { $this->class = $class; } public function getSolutionTitle(): string { return 'A class import is missing'; } public function getSolutionDescription(): string { return 'You have a missing class import. Try importing this class: `'.$this->class.'`.'; } public function getDocumentationLinks(): array { return []; } } src/Solutions/GenerateAppKeySolution.php 0000644 00000002000 15107340475 0014442 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions; use Illuminate\Support\Facades\Artisan; use Spatie\Ignition\Contracts\RunnableSolution; class GenerateAppKeySolution implements RunnableSolution { public function getSolutionTitle(): string { return 'Your app key is missing'; } public function getDocumentationLinks(): array { return [ 'Laravel installation' => 'https://laravel.com/docs/master/installation#configuration', ]; } public function getSolutionActionDescription(): string { return 'Generate your application encryption key using `php artisan key:generate`.'; } public function getRunButtonText(): string { return 'Generate app key'; } public function getSolutionDescription(): string { return ''; } public function getRunParameters(): array { return []; } public function run(array $parameters = []): void { Artisan::call('key:generate'); } } src/Solutions/SuggestLivewireMethodNameSolution.php 0000644 00000001370 15107340475 0016701 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions; use Spatie\Ignition\Contracts\Solution; class SuggestLivewireMethodNameSolution implements Solution { public function __construct( protected string $methodName, protected string $componentClass, protected string $suggested ) { } public function getSolutionTitle(): string { return "Possible typo `{$this->componentClass}::{$this->methodName}`"; } public function getDocumentationLinks(): array { return []; } public function getSolutionDescription(): string { return "Did you mean `{$this->componentClass}::{$this->suggested}`?"; } public function isRunnable(): bool { return false; } } src/Solutions/RunMigrationsSolution.php 0000644 00000002305 15107340475 0014407 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions; use Illuminate\Support\Facades\Artisan; use Spatie\Ignition\Contracts\RunnableSolution; class RunMigrationsSolution implements RunnableSolution { protected string $customTitle; public function __construct(string $customTitle = '') { $this->customTitle = $customTitle; } public function getSolutionTitle(): string { return $this->customTitle; } public function getSolutionDescription(): string { return 'You might have forgotten to run your database migrations.'; } public function getDocumentationLinks(): array { return [ 'Database: Running Migrations docs' => 'https://laravel.com/docs/master/migrations#running-migrations', ]; } public function getRunParameters(): array { return []; } public function getSolutionActionDescription(): string { return 'You can try to run your migrations using `php artisan migrate`.'; } public function getRunButtonText(): string { return 'Run migrations'; } public function run(array $parameters = []): void { Artisan::call('migrate'); } } src/Solutions/SolutionTransformers/LaravelSolutionTransformer.php 0000644 00000003306 15107340475 0021643 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions\SolutionTransformers; use Spatie\Ignition\Contracts\RunnableSolution; use Spatie\Ignition\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/Solutions/UseDefaultValetDbCredentialsSolution.php 0000644 00000003150 15107340475 0017266 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions; use Illuminate\Support\Str; use Spatie\Ignition\Contracts\RunnableSolution; class UseDefaultValetDbCredentialsSolution implements RunnableSolution { public function getSolutionActionDescription(): string { return 'Pressing the button will change `DB_USER` and `DB_PASSWORD` in your `.env` file.'; } public function getRunButtonText(): string { return 'Use default Valet credentials'; } public function getSolutionTitle(): string { return 'Could not connect to database'; } public function run(array $parameters = []): void { if (! file_exists(base_path('.env'))) { return; } $this->ensureLineExists('DB_USERNAME', 'root'); $this->ensureLineExists('DB_PASSWORD', ''); } protected function ensureLineExists(string $key, string $value): void { $envPath = base_path('.env'); $envLines = array_map(fn (string $envLine) => Str::startsWith($envLine, $key) ? "{$key}={$value}".PHP_EOL : $envLine, file($envPath) ?: []); file_put_contents($envPath, implode('', $envLines)); } public function getRunParameters(): array { return []; } public function getDocumentationLinks(): array { return [ 'Valet documentation' => 'https://laravel.com/docs/master/valet', ]; } public function getSolutionDescription(): string { return 'You seem to be using Valet, but the .env file does not contain the right default database credentials.'; } } src/Solutions/SuggestCorrectVariableNameSolution.php 0000644 00000001605 15107340475 0017022 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions; use Spatie\Ignition\Contracts\Solution; class SuggestCorrectVariableNameSolution implements Solution { protected ?string $variableName; protected ?string $viewFile; protected ?string $suggested; public function __construct(string $variableName = null, string $viewFile = null, string $suggested = null) { $this->variableName = $variableName; $this->viewFile = $viewFile; $this->suggested = $suggested; } public function getSolutionTitle(): string { return 'Possible typo $'.$this->variableName; } public function getDocumentationLinks(): array { return []; } public function getSolutionDescription(): string { return "Did you mean `$$this->suggested`?"; } public function isRunnable(): bool { return false; } } src/Solutions/SuggestLivewirePropertyNameSolution.php 0000644 00000001311 15107340475 0017300 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Solutions; use Spatie\Ignition\Contracts\Solution; class SuggestLivewirePropertyNameSolution implements Solution { public function __construct( protected string $variableName, protected string $componentClass, protected string $suggested, ) { } public function getSolutionTitle(): string { return "Possible typo {$this->variableName}"; } public function getDocumentationLinks(): array { return []; } public function getSolutionDescription(): string { return "Did you mean `$this->suggested`?"; } public function isRunnable(): bool { return false; } } src/ContextProviders/LaravelLivewireRequestContextProvider.php 0000644 00000005370 15107340475 0021127 0 ustar 00 <?php namespace Spatie\LaravelIgnition\ContextProviders; use Exception; use Illuminate\Http\Request; use Illuminate\Support\Arr; use Livewire\LivewireManager; 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<string, mixed> */ protected function getLivewireInformation(): array { /** @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; } return [ 'component_class' => $componentClass, 'component_alias' => $componentAlias, 'component_id' => $componentId, 'data' => $this->resolveData(), 'updates' => $this->resolveUpdates(), ]; } /** @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 { /** @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/LaravelRequestContextProvider.php 0000644 00000005314 15107340475 0017416 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/LaravelConsoleContextProvider.php 0000644 00000000272 15107340476 0017367 0 ustar 00 <?php namespace Spatie\LaravelIgnition\ContextProviders; use Spatie\FlareClient\Context\ConsoleContextProvider; class LaravelConsoleContextProvider extends ConsoleContextProvider { } src/ContextProviders/LaravelContextProviderDetector.php 0000644 00000001636 15107340476 0017543 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/error_log 0000644 00000002600 15107340476 0007255 0 ustar 00 [18-Nov-2025 10:46:54 UTC] PHP Fatal error: Uncaught Error: Class "Illuminate\Support\Facades\Route" not found in /home/fluxyjvi/public_html/project/vendor/spatie/laravel-ignition/src/ignition-routes.php:9 Stack trace: #0 {main} thrown in /home/fluxyjvi/public_html/project/vendor/spatie/laravel-ignition/src/ignition-routes.php on line 9 [18-Nov-2025 20:11:46 UTC] PHP Fatal error: Uncaught Error: Class "Illuminate\Support\Facades\Route" not found in /home/fluxyjvi/public_html/project/vendor/spatie/laravel-ignition/src/ignition-routes.php:9 Stack trace: #0 {main} thrown in /home/fluxyjvi/public_html/project/vendor/spatie/laravel-ignition/src/ignition-routes.php on line 9 [18-Nov-2025 22:40:18 UTC] PHP Fatal error: Uncaught Error: Class "Illuminate\Support\ServiceProvider" not found in /home/fluxyjvi/public_html/project/vendor/spatie/laravel-ignition/src/IgnitionServiceProvider.php:42 Stack trace: #0 {main} thrown in /home/fluxyjvi/public_html/project/vendor/spatie/laravel-ignition/src/IgnitionServiceProvider.php on line 42 [19-Nov-2025 05:43:07 UTC] PHP Fatal error: Uncaught Error: Class "Illuminate\Support\ServiceProvider" not found in /home/fluxyjvi/public_html/project/vendor/spatie/laravel-ignition/src/IgnitionServiceProvider.php:42 Stack trace: #0 {main} thrown in /home/fluxyjvi/public_html/project/vendor/spatie/laravel-ignition/src/IgnitionServiceProvider.php on line 42 src/Views/ViewExceptionMapper.php 0000644 00000013732 15107340476 0013114 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Views; use Exception; 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\Ignition\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 { $trace = Collection::make($exception->getPrevious()->getTrace()) ->map(function ($trace) { if ($originalPath = $this->findCompiledView(Arr::get($trace, 'file', ''))) { $trace['file'] = $originalPath; $trace['line'] = $this->getBladeLineNumber($trace['file'], $trace['line']); } return $trace; })->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 15107340476 0013474 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/IgnitionServiceProvider.php 0000644 00000026762 15107340476 0012704 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\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\Contracts\SolutionProviderRepository as SolutionProviderRepositoryContract; 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\Solutions\SolutionProviders\SolutionProviderRepository; 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()) ->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/ArgumentReducers/ModelArgumentReducer.php 0000644 00000001351 15107340477 0015410 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/ArgumentReducers/CollectionArgumentReducer.php 0000644 00000001170 15107340477 0016442 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/FlareMiddleware/AddQueries.php 0000644 00000000754 15107340477 0013137 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 15107340477 0014100 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/AddExceptionInformation.php 0000644 00000002717 15107340477 0015667 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/FlareMiddleware/AddDumps.php 0000644 00000001107 15107340477 0012603 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 15107340477 0012423 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/AddEnvironmentInformation.php 0000644 00000001333 15107340477 0016226 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/AddJobs.php 0000644 00000001117 15107340500 0012374 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/Http/Controllers/HealthCheckController.php 0000644 00000000767 15107340500 0015505 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 00000002645 15107340500 0016476 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Http\Controllers; use Illuminate\Foundation\Validation\ValidatesRequests; use Spatie\Ignition\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 15107340500 0015701 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/ExecuteSolutionRequest.php 0000644 00000001747 15107340500 0015312 0 ustar 00 <?php namespace Spatie\LaravelIgnition\Http\Requests; use Illuminate\Foundation\Http\FormRequest; use Spatie\Ignition\Contracts\RunnableSolution; use Spatie\Ignition\Contracts\Solution; use Spatie\Ignition\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/Http/Requests/UpdateConfigRequest.php 0000644 00000000654 15107340500 0014517 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/Middleware/RunnableSolutionsEnabled.php 0000644 00000000532 15107340500 0015774 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); } } README.md 0000644 00000005503 15107340500 0006021 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. composer.json 0000644 00000005203 15107340501 0007262 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", "spatie/flare-client-php": "^1.3.5", "spatie/ignition": "^1.9", "symfony/console": "^6.2.3", "symfony/var-dumper": "^6.2.3" }, "require-dev": { "livewire/livewire": "^2.11", "mockery/mockery": "^1.5.1", "openai-php/client": "^0.3.4", "orchestra/testbench": "^8.0", "pestphp/pest": "^1.22.3", "phpstan/extension-installer": "^1.2", "phpstan/phpstan-deprecation-rules": "^1.1.1", "phpstan/phpstan-phpunit": "^1.3.3", "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 } }, "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" } } LICENSE.md 0000644 00000002075 15107340501 0006150 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. config/flare.php 0000644 00000005061 15107340501 0007611 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\AddExceptionInformation; use Spatie\LaravelIgnition\FlareMiddleware\AddJobs; use Spatie\LaravelIgnition\FlareMiddleware\AddLogs; use Spatie\LaravelIgnition\FlareMiddleware\AddQueries; 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, ], CensorRequestBodyFields::class => [ 'censor_fields' => [ 'password', 'password_confirmation', ], ], CensorRequestHeaders::class => [ 'headers' => [ 'API-KEY', ] ] ], /* |-------------------------------------------------------------------------- | 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, ]; config/ignition.php 0000644 00000027054 15107340501 0010346 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; 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 |-------------------------------------------------------------------------- | | You may specify a list of solution providers (as fully qualified class | names) that should be loaded. Ignition will ignore these classes | and possible solutions provided by them will never be displayed. | */ '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, ], /* |-------------------------------------------------------------------------- | 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/error_log 0000644 00000002354 15107340501 0007726 0 ustar 00 [18-Nov-2025 13:58:36 UTC] PHP Fatal error: Uncaught Error: Call to undefined function env() in /home/fluxyjvi/public_html/project/vendor/spatie/laravel-ignition/config/flare.php:28 Stack trace: #0 {main} thrown in /home/fluxyjvi/public_html/project/vendor/spatie/laravel-ignition/config/flare.php on line 28 [18-Nov-2025 13:59:31 UTC] PHP Fatal error: Uncaught Error: Call to undefined function env() in /home/fluxyjvi/public_html/project/vendor/spatie/laravel-ignition/config/ignition.php:43 Stack trace: #0 {main} thrown in /home/fluxyjvi/public_html/project/vendor/spatie/laravel-ignition/config/ignition.php on line 43 [18-Nov-2025 22:56:18 UTC] PHP Fatal error: Uncaught Error: Call to undefined function env() in /home/fluxyjvi/public_html/project/vendor/spatie/laravel-ignition/config/ignition.php:43 Stack trace: #0 {main} thrown in /home/fluxyjvi/public_html/project/vendor/spatie/laravel-ignition/config/ignition.php on line 43 [18-Nov-2025 23:01:37 UTC] PHP Fatal error: Uncaught Error: Call to undefined function env() in /home/fluxyjvi/public_html/project/vendor/spatie/laravel-ignition/config/flare.php:28 Stack trace: #0 {main} thrown in /home/fluxyjvi/public_html/project/vendor/spatie/laravel-ignition/config/flare.php on line 28