🔧
This commit is contained in:
2
vendor/spatie/laravel-ignition/README.md
vendored
2
vendor/spatie/laravel-ignition/README.md
vendored
@@ -4,7 +4,7 @@
|
||||

|
||||
[](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.
|
||||
[Ignition](https://flareapp.io/docs/ignition-for-laravel/introduction) is a beautiful and customizable error page for 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.
|
||||
|
||||
|
||||
18
vendor/spatie/laravel-ignition/composer.json
vendored
18
vendor/spatie/laravel-ignition/composer.json
vendored
@@ -21,21 +21,20 @@
|
||||
"ext-curl": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"illuminate/support": "^10.0|^11.0",
|
||||
"spatie/flare-client-php": "^1.3.5",
|
||||
"spatie/ignition": "^1.9",
|
||||
"illuminate/support": "^10.0|^11.0|^12.0",
|
||||
"spatie/ignition": "^1.15",
|
||||
"symfony/console": "^6.2.3|^7.0",
|
||||
"symfony/var-dumper": "^6.2.3|^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"livewire/livewire": "^2.11|^3.3.5",
|
||||
"mockery/mockery": "^1.5.1",
|
||||
"openai-php/client": "^0.8.1",
|
||||
"orchestra/testbench": "^8.0|^9.0",
|
||||
"pestphp/pest": "^2.30",
|
||||
"phpstan/extension-installer": "^1.2",
|
||||
"phpstan/phpstan-deprecation-rules": "^1.1.1",
|
||||
"phpstan/phpstan-phpunit": "^1.3.3",
|
||||
"openai-php/client": "^0.8.1|^0.10",
|
||||
"orchestra/testbench": "8.22.3|^9.0|^10.0",
|
||||
"pestphp/pest": "^2.34|^3.7",
|
||||
"phpstan/extension-installer": "^1.3.1",
|
||||
"phpstan/phpstan-deprecation-rules": "^1.1.1|^2.0",
|
||||
"phpstan/phpstan-phpunit": "^1.3.16|^2.0",
|
||||
"vlucas/phpdotenv": "^5.5"
|
||||
},
|
||||
"suggest": {
|
||||
@@ -73,7 +72,6 @@
|
||||
"Spatie\\LaravelIgnition\\Tests\\": "tests"
|
||||
}
|
||||
},
|
||||
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"scripts": {
|
||||
|
||||
11
vendor/spatie/laravel-ignition/config/flare.php
vendored
11
vendor/spatie/laravel-ignition/config/flare.php
vendored
@@ -6,10 +6,12 @@ use Spatie\FlareClient\FlareMiddleware\CensorRequestBodyFields;
|
||||
use Spatie\FlareClient\FlareMiddleware\CensorRequestHeaders;
|
||||
use Spatie\LaravelIgnition\FlareMiddleware\AddDumps;
|
||||
use Spatie\LaravelIgnition\FlareMiddleware\AddEnvironmentInformation;
|
||||
use Spatie\LaravelIgnition\FlareMiddleware\AddExceptionHandledStatus;
|
||||
use Spatie\LaravelIgnition\FlareMiddleware\AddExceptionInformation;
|
||||
use Spatie\LaravelIgnition\FlareMiddleware\AddJobs;
|
||||
use Spatie\LaravelIgnition\FlareMiddleware\AddLogs;
|
||||
use Spatie\LaravelIgnition\FlareMiddleware\AddQueries;
|
||||
use Spatie\LaravelIgnition\FlareMiddleware\AddContext;
|
||||
use Spatie\LaravelIgnition\FlareMiddleware\AddNotifierName;
|
||||
|
||||
return [
|
||||
@@ -53,6 +55,8 @@ return [
|
||||
AddJobs::class => [
|
||||
'max_chained_job_reporting_depth' => 5,
|
||||
],
|
||||
AddContext::class,
|
||||
AddExceptionHandledStatus::class,
|
||||
CensorRequestBodyFields::class => [
|
||||
'censor_fields' => [
|
||||
'password',
|
||||
@@ -62,8 +66,13 @@ return [
|
||||
CensorRequestHeaders::class => [
|
||||
'headers' => [
|
||||
'API-KEY',
|
||||
'Authorization',
|
||||
'Cookie',
|
||||
'Set-Cookie',
|
||||
'X-CSRF-TOKEN',
|
||||
'X-XSRF-TOKEN',
|
||||
]
|
||||
]
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|
||||
@@ -24,6 +24,8 @@ use Spatie\LaravelIgnition\Solutions\SolutionProviders\UnknownValidationSolution
|
||||
use Spatie\LaravelIgnition\Solutions\SolutionProviders\ViewNotFoundSolutionProvider;
|
||||
use Spatie\LaravelIgnition\Solutions\SolutionProviders\OpenAiSolutionProvider;
|
||||
use Spatie\LaravelIgnition\Solutions\SolutionProviders\SailNetworkSolutionProvider;
|
||||
use Spatie\LaravelIgnition\Solutions\SolutionProviders\UnknownMariadbCollationSolutionProvider;
|
||||
use Spatie\LaravelIgnition\Solutions\SolutionProviders\UnknownMysql8CollationSolutionProvider;
|
||||
|
||||
return [
|
||||
|
||||
@@ -118,6 +120,8 @@ return [
|
||||
GenericLaravelExceptionSolutionProvider::class,
|
||||
OpenAiSolutionProvider::class,
|
||||
SailNetworkSolutionProvider::class,
|
||||
UnknownMysql8CollationSolutionProvider::class,
|
||||
UnknownMariadbCollationSolutionProvider::class,
|
||||
],
|
||||
|
||||
/*
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace DummyNamespace;
|
||||
|
||||
use Spatie\Ignition\Contracts\RunnableSolution;
|
||||
use Spatie\ErrorSolutions\Contracts\RunnableSolution;
|
||||
|
||||
class DummyClass implements RunnableSolution
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace DummyNamespace;
|
||||
|
||||
use Spatie\Ignition\Contracts\HasSolutionsForThrowable;
|
||||
use Spatie\ErrorSolutions\Contracts\HasSolutionsForThrowable;
|
||||
use Throwable;
|
||||
|
||||
class DummyClass implements HasSolutionsForThrowable
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace DummyNamespace;
|
||||
|
||||
use Spatie\Ignition\Contracts\Solution;
|
||||
use Spatie\ErrorSolutions\Contracts\Solution;
|
||||
|
||||
class DummyClass implements Solution
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Arr;
|
||||
use Livewire\LivewireManager;
|
||||
use Livewire\Mechanisms\ComponentRegistry;
|
||||
|
||||
class LaravelLivewireRequestContextProvider extends LaravelRequestContextProvider
|
||||
{
|
||||
@@ -37,9 +38,29 @@ class LaravelLivewireRequestContextProvider extends LaravelRequestContextProvide
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
/** @return array<int, mixed> */
|
||||
protected function getLivewireInformation(): array
|
||||
{
|
||||
if ($this->request->has('components')) {
|
||||
$data = [];
|
||||
|
||||
foreach ($this->request->get('components') as $component) {
|
||||
$snapshot = json_decode($component['snapshot'], true);
|
||||
|
||||
$class = app(ComponentRegistry::class)->getClass($snapshot['memo']['name']);
|
||||
|
||||
$data[] = [
|
||||
'component_class' => $class ?? null,
|
||||
'data' => $snapshot['data'],
|
||||
'memo' => $snapshot['memo'],
|
||||
'updates' => $this->resolveUpdates($component['updates']),
|
||||
'calls' => $component['calls'],
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
$componentId = $this->request->input('fingerprint.id');
|
||||
|
||||
@@ -56,12 +77,20 @@ class LaravelLivewireRequestContextProvider extends LaravelRequestContextProvide
|
||||
$componentClass = null;
|
||||
}
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
$updates = $this->request->input('updates') ?? [];
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
$updates = $this->request->input('updates') ?? [];
|
||||
|
||||
return [
|
||||
'component_class' => $componentClass,
|
||||
'component_alias' => $componentAlias,
|
||||
'component_id' => $componentId,
|
||||
'data' => $this->resolveData(),
|
||||
'updates' => $this->resolveUpdates(),
|
||||
[
|
||||
'component_class' => $componentClass,
|
||||
'component_alias' => $componentAlias,
|
||||
'component_id' => $componentId,
|
||||
'data' => $this->resolveData(),
|
||||
'updates' => $this->resolveUpdates($updates),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -86,7 +115,7 @@ class LaravelLivewireRequestContextProvider extends LaravelRequestContextProvide
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
protected function resolveUpdates(): array
|
||||
protected function resolveUpdates(array $updates): array
|
||||
{
|
||||
/** @phpstan-ignore-next-line */
|
||||
$updates = $this->request->input('updates') ?? [];
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace Spatie\LaravelIgnition\Exceptions;
|
||||
|
||||
use Spatie\Ignition\Contracts\BaseSolution;
|
||||
use Spatie\Ignition\Contracts\ProvidesSolution;
|
||||
use Spatie\Ignition\Contracts\Solution;
|
||||
use Spatie\ErrorSolutions\Contracts\BaseSolution;
|
||||
use Spatie\ErrorSolutions\Contracts\ProvidesSolution;
|
||||
use Spatie\ErrorSolutions\Contracts\Solution;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
|
||||
class CannotExecuteSolutionForNonLocalIp extends HttpException implements ProvidesSolution
|
||||
|
||||
@@ -4,9 +4,9 @@ namespace Spatie\LaravelIgnition\Exceptions;
|
||||
|
||||
use Exception;
|
||||
use Monolog\Level;
|
||||
use Spatie\Ignition\Contracts\BaseSolution;
|
||||
use Spatie\Ignition\Contracts\ProvidesSolution;
|
||||
use Spatie\Ignition\Contracts\Solution;
|
||||
use Spatie\ErrorSolutions\Contracts\BaseSolution;
|
||||
use Spatie\ErrorSolutions\Contracts\ProvidesSolution;
|
||||
use Spatie\ErrorSolutions\Contracts\Solution;
|
||||
|
||||
class InvalidConfig extends Exception implements ProvidesSolution
|
||||
{
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Spatie\LaravelIgnition\Exceptions;
|
||||
|
||||
use Spatie\Ignition\Contracts\ProvidesSolution;
|
||||
use Spatie\Ignition\Contracts\Solution;
|
||||
use Spatie\ErrorSolutions\Contracts\ProvidesSolution;
|
||||
use Spatie\ErrorSolutions\Contracts\Solution;
|
||||
|
||||
class ViewExceptionWithSolution extends ViewException implements ProvidesSolution
|
||||
{
|
||||
|
||||
27
vendor/spatie/laravel-ignition/src/FlareMiddleware/AddContext.php
vendored
Normal file
27
vendor/spatie/laravel-ignition/src/FlareMiddleware/AddContext.php
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\LaravelIgnition\FlareMiddleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Log\Context\Repository;
|
||||
use Illuminate\Support\Facades\Context;
|
||||
use Spatie\FlareClient\FlareMiddleware\FlareMiddleware;
|
||||
use Spatie\FlareClient\Report;
|
||||
|
||||
class AddContext implements FlareMiddleware
|
||||
{
|
||||
public function handle(Report $report, Closure $next)
|
||||
{
|
||||
if (! class_exists(Repository::class)) {
|
||||
return $next($report);
|
||||
}
|
||||
|
||||
$allContext = Context::all();
|
||||
|
||||
if (count($allContext)) {
|
||||
$report->group('laravel_context', $allContext);
|
||||
}
|
||||
|
||||
return $next($report);
|
||||
}
|
||||
}
|
||||
53
vendor/spatie/laravel-ignition/src/FlareMiddleware/AddExceptionHandledStatus.php
vendored
Normal file
53
vendor/spatie/laravel-ignition/src/FlareMiddleware/AddExceptionHandledStatus.php
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\LaravelIgnition\FlareMiddleware;
|
||||
|
||||
use Closure;
|
||||
use Spatie\Backtrace\Backtrace;
|
||||
use Spatie\FlareClient\FlareMiddleware\FlareMiddleware;
|
||||
use Spatie\FlareClient\Report;
|
||||
use Throwable;
|
||||
|
||||
class AddExceptionHandledStatus implements FlareMiddleware
|
||||
{
|
||||
public function handle(Report $report, Closure $next)
|
||||
{
|
||||
$frames = Backtrace::create()->limit(40)->frames();
|
||||
$frameCount = count($frames);
|
||||
|
||||
try {
|
||||
foreach ($frames as $i => $frame) {
|
||||
// Check first frame, probably Illuminate\Foundation\Exceptions\Handler::report()
|
||||
// Next frame should be: Illuminate/Foundation/helpers.php::report()
|
||||
|
||||
if ($frame->method !== 'report') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($frame->class === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($i === $frameCount - 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($frames[$i + 1]->class !== null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($frames[$i + 1]->method !== 'report') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$report->handled();
|
||||
|
||||
break;
|
||||
}
|
||||
} catch (Throwable) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
return $next($report);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace Spatie\LaravelIgnition\Http\Controllers;
|
||||
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Spatie\Ignition\Contracts\SolutionProviderRepository;
|
||||
use Spatie\ErrorSolutions\Contracts\SolutionProviderRepository;
|
||||
use Spatie\LaravelIgnition\Exceptions\CannotExecuteSolutionForNonLocalIp;
|
||||
use Spatie\LaravelIgnition\Http\Requests\ExecuteSolutionRequest;
|
||||
use Spatie\LaravelIgnition\Support\RunnableSolutionsGuard;
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
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;
|
||||
use Spatie\ErrorSolutions\Contracts\RunnableSolution;
|
||||
use Spatie\ErrorSolutions\Contracts\Solution;
|
||||
use Spatie\ErrorSolutions\Contracts\SolutionProviderRepository;
|
||||
|
||||
class ExecuteSolutionRequest extends FormRequest
|
||||
{
|
||||
|
||||
@@ -14,12 +14,13 @@ use Laravel\Octane\Events\TaskReceived;
|
||||
use Laravel\Octane\Events\TickReceived;
|
||||
use Monolog\Level;
|
||||
use Monolog\Logger;
|
||||
use Spatie\ErrorSolutions\Contracts\SolutionProviderRepository as SolutionProviderRepositoryContract;
|
||||
use Spatie\ErrorSolutions\SolutionProviderRepository;
|
||||
use Spatie\FlareClient\Flare;
|
||||
use Spatie\FlareClient\FlareMiddleware\AddSolutions;
|
||||
use Spatie\Ignition\Config\FileConfigManager;
|
||||
use Spatie\Ignition\Config\IgnitionConfig;
|
||||
use Spatie\Ignition\Contracts\ConfigManager;
|
||||
use Spatie\Ignition\Contracts\SolutionProviderRepository as SolutionProviderRepositoryContract;
|
||||
use Spatie\Ignition\Ignition;
|
||||
use Spatie\LaravelIgnition\Commands\SolutionMakeCommand;
|
||||
use Spatie\LaravelIgnition\Commands\SolutionProviderMakeCommand;
|
||||
@@ -34,7 +35,6 @@ 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;
|
||||
@@ -145,8 +145,7 @@ class IgnitionServiceProvider extends ServiceProvider
|
||||
|
||||
$this->app->singleton(
|
||||
Ignition::class,
|
||||
fn () => (new Ignition())
|
||||
->applicationPath(base_path())
|
||||
fn () => (new Ignition($this->app->make(Flare::class)))->applicationPath(base_path())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use Symfony\Component\VarDumper\Dumper\HtmlDumper as BaseHtmlDumper;
|
||||
|
||||
class HtmlDumper extends BaseHtmlDumper
|
||||
{
|
||||
public function __construct($output = null, string $charset = null, int $flags = 0)
|
||||
public function __construct($output = null, ?string $charset = null, int $flags = 0)
|
||||
{
|
||||
parent::__construct($output, $charset, $flags);
|
||||
|
||||
|
||||
@@ -16,9 +16,11 @@ class MultiDumpHandler
|
||||
}
|
||||
}
|
||||
|
||||
public function addHandler(callable $callable = null): self
|
||||
public function addHandler(?callable $callable = null): self
|
||||
{
|
||||
$this->handlers[] = $callable;
|
||||
if ($callable) {
|
||||
$this->handlers[] = $callable;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -72,16 +72,16 @@ class JobRecorder
|
||||
foreach ($payload as $key => $value) {
|
||||
if (! in_array($key, ['job', 'data', 'displayName'])) {
|
||||
$properties[$key] = $value;
|
||||
|
||||
if (is_string($payload['data'])) {
|
||||
try {
|
||||
$properties['data'] = json_decode($payload['data'], true, 512, JSON_THROW_ON_ERROR);
|
||||
} catch (Exception $exception) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (is_string($payload['data'])) {
|
||||
$properties['data'] = json_decode($payload['data'], true, 512, JSON_THROW_ON_ERROR);
|
||||
}
|
||||
} catch (Exception $exception) {
|
||||
}
|
||||
|
||||
if ($pushedAt = DateTime::createFromFormat('U.u', $payload->get('pushedAt', ''))) {
|
||||
$properties['pushedAt'] = $pushedAt->format(DATE_ATOM);
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace Spatie\LaravelIgnition\Renderers;
|
||||
|
||||
use Spatie\ErrorSolutions\Contracts\SolutionProviderRepository;
|
||||
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;
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<?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()];
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
<?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",
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
<?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') === '';
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
<?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',
|
||||
]),];
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
<?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()];
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<?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')];
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
<?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`?'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
<?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.'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?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',
|
||||
]),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<?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')];
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
<?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,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
<?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,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Spatie\LaravelIgnition\Solutions\SolutionTransformers;
|
||||
|
||||
use Spatie\Ignition\Contracts\RunnableSolution;
|
||||
use Spatie\Ignition\Solutions\SolutionTransformer;
|
||||
use Spatie\ErrorSolutions\Contracts\RunnableSolution;
|
||||
use Spatie\ErrorSolutions\Solutions\SolutionTransformer;
|
||||
use Spatie\LaravelIgnition\Http\Controllers\ExecuteSolutionController;
|
||||
use Throwable;
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?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 [];
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<?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',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
<?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.';
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
<?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 [];
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace Spatie\LaravelIgnition\Views;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Contracts\View\Engine;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Support\Arr;
|
||||
@@ -11,7 +10,7 @@ use Illuminate\View\Engines\PhpEngine;
|
||||
use Illuminate\View\ViewException;
|
||||
use ReflectionClass;
|
||||
use ReflectionProperty;
|
||||
use Spatie\Ignition\Contracts\ProvidesSolution;
|
||||
use Spatie\ErrorSolutions\Contracts\ProvidesSolution;
|
||||
use Spatie\LaravelIgnition\Exceptions\ViewException as IgnitionViewException;
|
||||
use Spatie\LaravelIgnition\Exceptions\ViewExceptionWithSolution;
|
||||
use Throwable;
|
||||
@@ -19,7 +18,9 @@ use Throwable;
|
||||
class ViewExceptionMapper
|
||||
{
|
||||
protected Engine $compilerEngine;
|
||||
|
||||
protected BladeSourceMapCompiler $bladeSourceMapCompiler;
|
||||
|
||||
protected array $knownPaths;
|
||||
|
||||
public function __construct(BladeSourceMapCompiler $bladeSourceMapCompiler)
|
||||
@@ -80,15 +81,27 @@ class ViewExceptionMapper
|
||||
|
||||
protected function modifyViewsInTrace(IgnitionViewException $exception): void
|
||||
{
|
||||
$viewIndex = null;
|
||||
|
||||
$trace = Collection::make($exception->getPrevious()->getTrace())
|
||||
->map(function ($trace) {
|
||||
->map(function ($trace, $index) use (&$viewIndex) {
|
||||
if ($originalPath = $this->findCompiledView(Arr::get($trace, 'file', ''))) {
|
||||
|
||||
$trace['file'] = $originalPath;
|
||||
$trace['line'] = $this->getBladeLineNumber($trace['file'], $trace['line']);
|
||||
|
||||
if ($viewIndex === null) {
|
||||
$viewIndex = $index;
|
||||
}
|
||||
}
|
||||
|
||||
return $trace;
|
||||
})->toArray();
|
||||
})
|
||||
->when(
|
||||
$viewIndex !== null && str_ends_with($exception->getFile(), '.blade.php'),
|
||||
fn (Collection $trace) => $trace->slice($viewIndex + 1) // Remove all traces before the view
|
||||
)
|
||||
->toArray();
|
||||
|
||||
$traceProperty = new ReflectionProperty('Exception', 'trace');
|
||||
$traceProperty->setAccessible(true);
|
||||
|
||||
Reference in New Issue
Block a user