upgraded dependencies

This commit is contained in:
RafficMohammed
2023-01-08 01:59:16 +05:30
parent 51056e3aad
commit f9ae387337
6895 changed files with 133617 additions and 178680 deletions

View File

@@ -1,4 +0,0 @@
{
"presets": ["@babel/preset-env", "@babel/preset-typescript"],
"plugins": ["@babel/transform-runtime", "@babel/plugin-syntax-dynamic-import"]
}

View File

@@ -1,100 +0,0 @@
name: Run tests
on:
push:
pull_request:
schedule:
- cron: '0 0 * * *'
jobs:
php-tests:
runs-on: ${{ matrix.os }}
strategy:
matrix:
php: [8.0, 7.4, 7.3, 7.2]
laravel: [6.*, 5.8.*, 5.7.*, 5.6.*, 5.5.*]
dependency-version: [prefer-lowest, prefer-stable]
os: [ubuntu-latest, windows-latest]
include:
- laravel: 6.*
testbench: 4.*
- laravel: 5.8.*
testbench: 3.8.*
- laravel: 5.7.*
testbench: 3.7.*
- laravel: 5.6.*
testbench: 3.6.*
- laravel: 5.5.*
testbench: 3.5.*
exclude:
- laravel: 5.8.*
php: 8.0
- laravel: 5.7.*
php: 8.0
- laravel: 5.6.*
php: 8.0
- laravel: 5.5.*
php: 8.0
- laravel: 5.7.*
php: 7.4
- laravel: 5.6.*
php: 7.4
- laravel: 5.5.*
php: 7.4
name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} - ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, gd, fileinfo
coverage: none
- name: Install dependencies
run: |
composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest
- name: Execute tests
run: vendor/bin/phpunit
- name: Send Slack notification
uses: 8398a7/action-slack@v2
if: ${{ failure() && !! env.SLACK_WEBHOOK }}
with:
status: ${{ job.status }}
author_name: ${{ github.actor }}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
js-tests:
runs-on: ubuntu-latest
name: JavaScript tests
steps:
- name: Checkout code
uses: actions/checkout@v1
- name: Install dependencies
run: yarn install --non-interactive
- name: Execute tests
run: yarn run jest
- name: Send Slack notification
uses: 8398a7/action-slack@v2
if: ${{ failure() && !! env.SLACK_WEBHOOK }}
with:
status: ${{ job.status }}
author_name: ${{ github.actor }}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

40
vendor/facade/ignition/.php_cs.php vendored Normal file
View File

@@ -0,0 +1,40 @@
<?php
$finder = Symfony\Component\Finder\Finder::create()
->in([
__DIR__ . '/src',
__DIR__ . '/tests',
])
->name('*.php')
->notName('*.blade.php')
->ignoreDotFiles(true)
->ignoreVCS(true);
return (new PhpCsFixer\Config())
->setRules([
'@PSR12' => true,
'array_syntax' => ['syntax' => 'short'],
'ordered_imports' => ['sort_algorithm' => 'alpha'],
'no_unused_imports' => true,
'not_operator_with_successor_space' => true,
'trailing_comma_in_multiline' => true,
'phpdoc_scalar' => true,
'unary_operator_spaces' => true,
'binary_operator_spaces' => true,
'blank_line_before_statement' => [
'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'],
],
'phpdoc_single_line_var_spacing' => true,
'phpdoc_var_without_name' => true,
'class_attributes_separation' => [
'elements' => [
'method' => 'one',
],
],
'method_argument_space' => [
'on_multiline' => 'ensure_fully_multiline',
'keep_multiple_spaces_after_comma' => true,
],
'single_trait_insert_per_statement' => true,
])
->setFinder($finder);

View File

@@ -1 +0,0 @@
resources/compiled

View File

@@ -1,6 +0,0 @@
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"tabWidth": 4
}

View File

@@ -1,8 +0,0 @@
preset: laravel
disabled:
- single_class_element_per_statement
finder:
not-name:
- "GitConflictController.php"

View File

@@ -2,30 +2,322 @@
All notable changes to `ignition` will be documented in this file
## 1.18.0 - 2021-08-02
## 2.17.5 - 2022-02-23
- disable executing solutions on non-local environments or from non-local IP addresses on version 1.x (#404)
## What's Changed
## 1.17.0
- fix solutions section padding by @faissaloux in https://github.com/facade/ignition/pull/433
- Bump markdown-it from 9.1.0 to 12.3.2 by @dependabot in https://github.com/facade/ignition/pull/446
- Bump ajv from 6.10.2 to 6.12.6 by @dependabot in https://github.com/facade/ignition/pull/448
- Fix E_NOTICE when requesting invalid script by @cweiske in https://github.com/facade/ignition/pull/449
- add extra editors (#389)
## New Contributors
## 1.16.5 - 2021-02-14
- @faissaloux made their first contribution in https://github.com/facade/ignition/pull/433
- @cweiske made their first contribution in https://github.com/facade/ignition/pull/449
- fix CVE-2021-3129 for facade/ignition 1.16.x (Laravel 6) (#351)
**Full Changelog**: https://github.com/facade/ignition/compare/2.17.4...2.17.5
## 1.16.4 - 2021-02-13
## 2.17.4 - 2021-12-27
do not use, tagged on the wrong branch
- fix bug where uninitialized property within a job could break Ignition
## 1.16.3 - 2020-07-13
## 2.17.3 - 2021-12-23
- do not use missing package solution provider by default (closes #179)
- allow filtering route parameters using a `toFlare` method
## 1.16.2 - 2020-07-12
## 2.17.2 - 2021-11-29
## What's Changed
- Allow overflow-x on solutions with unbreakable words by @willemvb in https://github.com/facade/ignition/pull/431
**Full Changelog**: https://github.com/facade/ignition/compare/2.17.1...2.17.2
## 2.17.2 - 2021-11-29
- scroll overflow on solutions
## 2.17.1 - 2021-11-25
- streamline Livewire solutions
## 2.17.0 - 2021-11-24
- improve recording of Livewire data
## 2.16.1 - 2021-11-16
- allow sending of unbinded sql queries to Flare
## 2.16.0 - 2021-10-28
- improve recording data from jobs (#416)
## 2.15.0 - 2021-10-11
- improve output of flare:test
## 2.14.1 - 2021-10-08
- update base URL for Flare
## 2.14.0 - 2021-10-01
- add support for VScode WSL + SSH remote (#420)
## 2.13.1 - 2021-09-13
- fix namespace of `SentReports` in facade
## 2.13.0 - 2021-09-13
- add tracking uuid (#418)
## 2.12.1 - 2021-09-08
- add support for VS Codium editor (#417)
## 2.12.0 - 2021-08-24
- add support for collecting information about jobs (#412)
## 2.11.4 - 2021-08-16
- use npm ci instead of install (#411)
## 2.11.3 - 2021-08-16
- fix issues with circular dependencies in model route parameters (#408)
- remove notice about dirty git state in context
- wrap `AddGitInformation` middleware in try-catch
## 2.11.2 - 2021-07-20
- fix issues introduced in 2.11.1 (#403)
## 2.11.1 - 2021-07-20
- fix sending queued reports on Laravel Vapor queues (#398)
## 2.11.0 - 2021-07-12
- prepare Laravel 9 support
- remove filp/whoops dependency
- update front-end dependencies
## 2.10.2 - 2021-06-11
- fix typo in config/flare.php (#395)
## 2.10.1 - 2021-06-03
- fix memory leaks in Octane (#393)
## 2.10.0 - 2021-06-03
- add a solution for lazy loading violations (#392)
## 2.9.0 - 2021-05-05
- add Xdebug format links for editor (#383)
## 2.8.4 - 2021-04-29
- avoid making call to Flare when no API key is specified
## 2.8.3 - 2021-04-09
- support Octane (#379)
## 2.8.2 - 2021-04-08
- censor passwords by default (#377)
## 2.8.1 - 2021-04-08
- add `censor_request_body_fields` default config option
## 2.8.0 - 2021-04-08
- add `censor_request_body_fields` config option
## 2.7.0 - 2021-03-30
- adds a debug warning when having debug enabled on a non-local environment (#366)
## 2.6.1 - 2021-03-30
- Disable executing solutions on non-local environments or from non-local IP addresses (#364)
## 2.6.0 - 2021-03-24
- add extra output to test command when executing verbosely
## 2.5.14 - 2021-03-03
- fix ignition not working when there is no argv
## 2.5.13 - 2021-02-16
- remove custom grouping
## 2.5.12 - 2021-02-15
- fix wrong config usage (#354)
## 2.5.11 - 2021-02-05
- fix memory leaks caused by log and query recorder (#344)
## 2.5.10 - 2021-02-02
- fix tinker logs not being sent to Flare
## 2.5.9 - 2021-01-26
- fix logged context not being sent to Flare
## 2.5.8 - 2020-12-29
- fix double `$` on PHP 8 (#338)
## 2.5.7 - 2020-12-29
- fix for breaking change in highlight.js (fixes 2.5.5)
## 2.5.6 - 2020-12-29
- revert to compiled js of 2.5.3
## 2.5.5 - 2020-12-29
- added compiled js of previous release
## 2.5.4 - 2020-12-29
- added support for Nova text editor (#343)
## 2.5.3 - 2020-12-08
- Use Livewire compatible compiler engine when using Livewire (#340)
## 2.5.2 - 2020-11-14
- fix `MakeViewVariableOptionalSolution` to disallow stream wrappers and files that do not end in ".blade.php" (#334)
## 2.5.1 - 2020-11-13
- add support for LiveWire component urls
## 2.5.0 - 2020-10-27
- add PHP 8.0-dev support
- remove unnecessary `scrivo/highlight.php` dependency
## 2.4.2 - 2021-03-08
- fix `MakeViewVariableOptionalSolution` to disallow stream wrappers and files that do not end in .blade.php (#356)
## 2.4.1 - 2020-10-14
- fix copy casing
## 2.4.0 - 2020-10-14
- add livewire component discovery solution
## 2.3.8 - 2020-10-02
- Address Missing Mix Manifest Error (#317)
## 2.3.7 - 2020-09-06
- add loading state on share button (#309)
- compatibility fix for L8
## 2.3.6 - 2020-08-10
- possible security vulnerability: bump elliptic version (#300)
- possible XSS vulnerability: escape characters in stacktrace and exception title
## 2.3.5 - 2020-08-01
- catch exception in detectLineNumber for not existing blade files (#299)
## 2.3.4 - 2020-07-27
- fix an error that would throw a blank page when using third party extensions
## 2.3.3 -2020-07-14
- fix all psalm related issues
## 2.3.2 - 2020-07-14
- properly bind singleton (#291)
## 2.3.1 - 2020-07-13
- improve db name solution (#289)
## 2.3.0 - 2020-07-13
- allow override of Dumper via `$_SERVER variable` (#271)
- make DumpHandler instance manually in DumpRecorder (#286)
- only setup queues when queue is available (#287)
## 2.2.0 - 2020-07-13
- add `ignition:make:solution-provider` command
## 2.1.0 - 2020-07-13
- add "Undefined Property" solution (#264)
## 2.0.10 - 2020-07-13
- correctly detect dump location from ddd (#216)
## 2.0.9 - 2020-07-13
- use application contract instead of concrete class (#243)
## 2.0.8 - 2020-07-12
- do not render solution title tag for empty titles
## 2.0.7 - 2020-06-07
- Fix `DefaultDbNameSolutionProvider` (#277)
## 2.0.6 - 2020-06-01
- remove ability to fix variable names
## 2.0.5 - 2020-05-29
- blacklist certain variable names when fixing variable names
## 2.0.4 - 2020-05-18
- handle exceptions in case the request doesn't have a user (#274)
## 2.0.3 - 2020-04-07
- support Laravel 8
## 2.0.2 - 2020-03-18
- fix execute solution route not defined (#265)
## 2.0.0 - 2020-02-02
- adds support for Laravel 7
- drop support for Laravel 6 and below
- git information won't be collected by default anymore (if you need this set `collect_git_information` to `true` in the `flare` config file)
- `MissingPackageSolutionProvider` was added to the `ignored_solution_providers` because it potentially could be slow.
## 1.16.0 - 2020-01-21
- add named routes (#197)
@@ -40,7 +332,7 @@ do not use, tagged on the wrong branch
## 1.13.1 - 2020-01-02
- Remove external reference for icons (#134)
- Remove external reference for icons (#134)
## 1.13.0 - 2019-11-27
@@ -59,224 +351,224 @@ do not use, tagged on the wrong branch
## 1.11.2 - 2019-10-13
- simplify default Laravel installation (#198)
- simplify default Laravel installation (#198)
## 1.11.1 - 2019-10-08
- add conditional line number (#182)
- add conditional line number (#182)
## 1.11.0 - 2019-10-08
- add better error messages for missing validation rules (#125)
- add better error messages for missing validation rules (#125)
## 1.10.0 - 2019-10-07
- Add `ignition:make-solution` command
- Add default for query binding option (Fixes #183)
- Add `ignition:make-solution` command
- Add default for query binding option (Fixes #183)
## 1.9.2 - 2019-10-04
- Fix service provider registration (Fixes #177)
- Fix service provider registration (Fixes #177)
## 1.9.1 - 2019-10-01
- collapse vendor frames on windows fix (#176)
- collapse vendor frames on windows fix (#176)
## 1.9.0 - 2019-09-27
- add ability to send logs to flare
- add `ddd` function
- add ability to send logs to flare
- add `ddd` function
## 1.8.4 - 2019-09-27
- Resolve configuration from the injected app instead of the helper ([#168](https://github.com/facade/ignition/pull/168))
- Resolve configuration from the injected app instead of the helper ([#168](https://github.com/facade/ignition/pull/168))
## 1.8.3 - 2019-09-25
- Remove `select-none` from error message
- Change line clamp behaviour for longer error messages
- Remove `select-none` from error message
- Change line clamp behaviour for longer error messages
## 1.8.2 - 2019-09-20
- fix for `TypeError: Cannot set property 'highlightState' of undefined`
- fix for `TypeError: Cannot set property 'highlightState' of undefined`
## 1.8.1 - 2019-09-20
- Revert javascript assets via URL - Fixes #161
- Revert javascript assets via URL - Fixes #161
## 1.8.0 - 2019-09-18
- added solution for running Laravel Dusk in production ([#121](https://github.com/facade/ignition/pull/121))
- Automatically fix blade variable typos and optional variables ([#38](https://github.com/facade/ignition/pull/38))
- added solution for running Laravel Dusk in production ([#121](https://github.com/facade/ignition/pull/121))
- Automatically fix blade variable typos and optional variables ([#38](https://github.com/facade/ignition/pull/38))
## 1.7.1 - 2019-09-18
- Use url helper to generate housekeeping endpoints
- Use url helper to generate housekeeping endpoints
## 1.7.0 - 2019-09-18
- Add the ability to define a query collector max value ([#153](https://github.com/facade/ignition/pull/153))
- Add the ability to define a query collector max value ([#153](https://github.com/facade/ignition/pull/153))
## 1.6.10 - 2019-09-18
- fix `__invoke` method name in solution ([#151](https://github.com/facade/ignition/pull/151))
- fix `__invoke` method name in solution ([#151](https://github.com/facade/ignition/pull/151))
## 1.6.9 - 2019-09-18
- Add noscript trace information - fixes [#146](https://github.com/facade/ignition/issues/146)
- Add noscript trace information - fixes [#146](https://github.com/facade/ignition/issues/146)
## 1.6.8 - 2019-09-18
- Use javascript content type for asset response - fixes [#149](https://github.com/facade/ignition/issues/149)
- Use javascript content type for asset response - fixes [#149](https://github.com/facade/ignition/issues/149)
## 1.6.7 - 2019-09-18
- Load javascript assets via URL. Fixes [#16](https://github.com/facade/ignition/issues/16)
- Load javascript assets via URL. Fixes [#16](https://github.com/facade/ignition/issues/16)
## 1.6.6 - 2019-09-16
- Prevent undefined index exception in `TestCommand`
- Prevent undefined index exception in `TestCommand`
## 1.6.5 - 2019-09-13
- Ignore invalid characters in JSON encoding. Fixes [#138](https://github.com/facade/ignition/issues/138)
- Ignore invalid characters in JSON encoding. Fixes [#138](https://github.com/facade/ignition/issues/138)
## 1.6.4 - 2019-09-13
- add no-index on error page
- add no-index on error page
## 1.6.3 - 2019-09-12
- Fix `RouteNotDefinedSolutionProvider` in Laravel 5
- Fix `RouteNotDefinedSolutionProvider` in Laravel 5
## 1.6.2 - 2019-09-12
- updated publishing tag from default config
- updated publishing tag from default config
## 1.6.1 - 2019-09-12
- Resolve configuration from the injected application instead of the helper - Fixes [#131](https://github.com/facade/ignition/issues/131)
- Resolve configuration from the injected application instead of the helper - Fixes [#131](https://github.com/facade/ignition/issues/131)
## 1.6.0 - 2019-09-09
- add `RouteNotDefined` solution provider ([#113](https://github.com/facade/ignition/pull/113))
- add `RouteNotDefined` solution provider ([#113](https://github.com/facade/ignition/pull/113))
## 1.5.0 - 2019-09-09
- suggest running migrations when a column is missing ([#83](https://github.com/facade/ignition/pull/83))
- suggest running migrations when a column is missing ([#83](https://github.com/facade/ignition/pull/83))
## 1.4.19 - 2019-09-09
- Remove quotation from git commit url ([#89](https://github.com/facade/ignition/pull/89))
- Remove quotation from git commit url ([#89](https://github.com/facade/ignition/pull/89))
## 1.4.18 - 2019-09-09
- Fix open_basedir restriction when looking up config file. Fixes ([#120](https://github.com/facade/ignition/pull/120))
- Fix open_basedir restriction when looking up config file. Fixes ([#120](https://github.com/facade/ignition/pull/120))
## 1.4.17 - 2019-09-06
- Remove Inter, Operator from font stack. Fixes [#74](https://github.com/facade/ignition/issues/74)
- Remove Inter, Operator from font stack. Fixes [#74](https://github.com/facade/ignition/issues/74)
## 1.4.15 - 2019-09-05
- Use previous exception trace for view exceptions. Fixes [#107](https://github.com/facade/ignition/issues/107)
- Use previous exception trace for view exceptions. Fixes [#107](https://github.com/facade/ignition/issues/107)
## 1.4.14 - 2019-09-05
- Use DIRECTORY_SEPARATOR to fix an issue with blade view lookups in Windows
- Use DIRECTORY_SEPARATOR to fix an issue with blade view lookups in Windows
## 1.4.13 - 2019-09-05
- Use Laravel style comments
- Use Laravel style comments
## 1.4.12 - 2019-09-04
- Use a middleware to protect ignition routes ([#93](https://github.com/facade/ignition/pull/93))
- Use a middleware to protect ignition routes ([#93](https://github.com/facade/ignition/pull/93))
## 1.4.11 - 2019-09-04
- Use exception line number as fallbacks for view errors
- Use exception line number as fallbacks for view errors
## 1.4.10 - 2019-09-04
- Wrap solution provider lookup in a try-catch block
- Wrap solution provider lookup in a try-catch block
## 1.4.9 - 2019-09-04
- Lookup the first exception when linking to Telescope
- Lookup the first exception when linking to Telescope
## 1.4.8 - 2019-09-04
- pass an empty string to query if no connection name is available - fixes [#86](https://github.com/facade/ignition/issues/86)
- pass an empty string to query if no connection name is available - fixes [#86](https://github.com/facade/ignition/issues/86)
## 1.4.7 - 2019-09-04
- Match whoops minimum version constraint with Laravel 6
- Match whoops minimum version constraint with Laravel 6
## 1.4.6 - 2019-09-04
- Use empty array for default ignored solution providers
- Use empty array for default ignored solution providers
## 1.4.5 - 2019-09-03
- fix for new Laravel 6 installs
- fix for new Laravel 6 installs
## 1.4.4 - 2019-09-03
- Suggest default database name in Laravel 6
- Add void return type to FlareHandler::write()
- Suggest default database name in Laravel 6
- Add void return type to FlareHandler::write()
## 1.4.3 - 2019-09-03
- allow monolog v2
- allow monolog v2
## 1.4.2 - 2019-09-03
- style fixes
- style fixes
## 1.4.1 - 2019-09-03
- Change `remote-sites-path` and `local-sites-path` config keys to us snake case
- Change `remote-sites-path` and `local-sites-path` config keys to us snake case
## 1.4.0 - 2019-09-03
- add `enable_runnable_solutions` key to config file
- add `enable_runnable_solutions` key to config file
## 1.3.0 - 2019-09-02
- add `MergeConflictSolutionProvider`
- add `MergeConflictSolutionProvider`
## 1.2.0 - 2019-09-02
- add `ignored_solution_providers` key to config file
- add `ignored_solution_providers` key to config file
## 1.1.1 - 2019-09-02
- Fixed context tab crash when not using git ([#24](https://github.com/facade/ignition/issues/24))
- Fixed context tab crash when not using git ([#24](https://github.com/facade/ignition/issues/24))
## 1.1.0 - 2019-09-02
- Fixed an error that removed the ability to register custom blade directives.
- Fixed an error that prevented solution execution in Laravel 5.5 and 5.6
- The "Share" button can now be disabled in the configuration file
- Fixes an error when trying to log `null` values
- Fixed an error that removed the ability to register custom blade directives.
- Fixed an error that prevented solution execution in Laravel 5.5 and 5.6
- The "Share" button can now be disabled in the configuration file
- Fixes an error when trying to log `null` values
## 1.0.4 - 2019-09-02
- Check if the authenticated user has a `toArray` method available, before collecting user data
- Check if the authenticated user has a `toArray` method available, before collecting user data
## 1.0.3 - 2019-09-02
- Corrected invalid link in config file
- Corrected invalid link in config file
## 1.0.2 - 2019-09-02
- Fixed an error in the `DefaultDbNameSolutionProvider` that could cause an infinite loop in Laravel < 5.6.28
- Fixed an error in the `DefaultDbNameSolutionProvider` that could cause an infinite loop in Laravel < 5.6.28
## 1.0.1 - 2019-08-31
- add support for L5.5 & 5.6 ([#21](https://github.com/facade/ignition/pull/21))
- add support for L5.5 & 5.6 ([#21](https://github.com/facade/ignition/pull/21))
## 1.0.0 - 2019-08-30
- initial release
- initial release

View File

@@ -1,14 +1,17 @@
# Ignition: a beautiful error page for Laravel apps
[![Latest Version on Packagist](https://img.shields.io/packagist/v/facade/ignition.svg?style=flat-square)](https://packagist.org/packages/facade/ignition)
![GitHub Workflow Status](https://img.shields.io/github/workflow/status/facade/ignition/run-php-tests?label=Tests)
[![Quality Score](https://img.shields.io/scrutinizer/g/facade/ignition.svg?style=flat-square)](https://scrutinizer-ci.com/g/facade/ignition)
[![StyleCI](https://github.styleci.io/repos/204472210/shield?branch=master)](https://github.styleci.io/repos/204472210)
![Tests](https://github.com/facade/ignition/workflows/Run%20tests/badge.svg)
[![Total Downloads](https://img.shields.io/packagist/dt/facade/ignition.svg?style=flat-square)](https://packagist.org/packages/facade/ignition)
[Ignition](https://flareapp.io/docs/ignition-for-laravel/introduction) is a beautiful and customizable error page for Laravel applications running on Laravel 5.5 and newer. It is the default error page for all Laravel 6 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 running on Laravel 5.5 up Laravel 8. It is the default error page for all Laravel 6 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.
![Screenshot of ignition](https://facade.github.io/ignition/screenshot.png)
## Using Laravel 8 or above?
If you're on Laravel 8 or above, you can switch to [spatie/laravel-ignition](https://github.com/spatie/laravel-ignition), which is a drop-in replacement.
Replace `facade/ignition` with `"spatie/laravel-ignition": "^1.0"` in your application's `composer.json` file.
Going forward, we'll only add security fixes to facade/ignition and highly encourage you to switch to spatie/laravel-ignition.
## Official Documentation

3
vendor/facade/ignition/SECURITY.md vendored Normal file
View File

@@ -0,0 +1,3 @@
# Security Policy
For security related problems, please don't use the public issue tracker, but mail info@spatie.be.

View File

@@ -10,31 +10,33 @@
"homepage": "https://github.com/facade/ignition",
"license": "MIT",
"require": {
"php": "^7.1|^8.0",
"php": "^7.2.5|^8.0",
"ext-json": "*",
"ext-mbstring": "*",
"facade/flare-client-php": "^1.3",
"facade/ignition-contracts": "^1.0",
"filp/whoops": "^2.4",
"illuminate/support": "~5.5.0 || ~5.6.0 || ~5.7.0 || ~5.8.0 || ^6.0",
"monolog/monolog": "^1.12 || ^2.0",
"scrivo/highlight.php": "^9.15",
"symfony/console": "^3.4 || ^4.0",
"symfony/var-dumper": "^3.4 || ^4.0"
"facade/flare-client-php": "^1.9.1",
"facade/ignition-contracts": "^1.0.2",
"illuminate/support": "^7.0|^8.0",
"monolog/monolog": "^2.0",
"symfony/console": "^5.0",
"symfony/var-dumper": "^5.0",
"ext-curl": "*"
},
"require-dev": {
"mockery/mockery": "~1.3.3|^1.4.2",
"orchestra/testbench": "^3.5 || ^3.6 || ^3.7 || ^3.8 || ^4.0"
"friendsofphp/php-cs-fixer": "^2.14",
"livewire/livewire": "^2.4",
"mockery/mockery": "^1.3",
"orchestra/testbench": "^5.0|^6.0",
"psalm/plugin-laravel": "^1.2"
},
"suggest": {
"laravel/telescope": "^2.0"
"laravel/telescope": "^3.1"
},
"config": {
"sort-packages": true
},
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
"dev-master": "2.x-dev"
},
"laravel": {
"providers": [
@@ -61,9 +63,8 @@
"minimum-stability": "dev",
"prefer-stable": true,
"scripts": {
"format": [
"vendor/bin/php-cs-fixer fix"
],
"psalm": "vendor/bin/psalm",
"format": "vendor/bin/php-cs-fixer fix --allow-risky=yes",
"test": "vendor/bin/phpunit",
"test-coverage": "vendor/bin/phpunit --coverage-html coverage"
},

View File

@@ -26,12 +26,15 @@ return [
'reporting' => [
'anonymize_ips' => true,
'collect_git_information' => true,
'collect_git_information' => false,
'report_queries' => true,
'maximum_number_of_collected_queries' => 200,
'report_query_bindings' => true,
'report_view_data' => true,
'grouping_type' => null,
'report_logs' => true,
'maximum_number_of_collected_logs' => 200,
'censor_request_body_fields' => ['password'],
],
/*
@@ -39,10 +42,21 @@ return [
| Reporting Log statements
|--------------------------------------------------------------------------
|
| If this setting is `false` log statements won't be send as events to Flare,
| 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,
/*
|--------------------------------------------------------------------------
| Censor request body fields
|--------------------------------------------------------------------------
|
| These fields will be censored from your request when sent to Flare.
|
*/
'censor_request_body_fields' => ['password'],
];

View File

@@ -9,7 +9,7 @@ return [
|
| Choose your preferred editor to use when clicking any edit button.
|
| Supported: "phpstorm", "vscode", "vscode-insiders", "textmate", "emacs",
| Supported: "phpstorm", "vscode", "vscode-insiders", "vscodium", "textmate", "emacs",
| "sublime", "atom", "nova", "macvim", "idea", "netbeans",
| "xdebug"
|
@@ -70,7 +70,7 @@ return [
*/
'ignored_solution_providers' => [
Facade\Ignition\SolutionProviders\MissingPackageSolutionProvider::class,
\Facade\Ignition\SolutionProviders\MissingPackageSolutionProvider::class,
],
/*

View File

@@ -7,9 +7,9 @@
},
"dependencies": {
"git-url-parse": "^11.1.2",
"highlight.js": "^9.15.6",
"lodash": "^4.17.4",
"markdown-it": "^9.0.1",
"highlight.js": "^10.4.1",
"lodash": "^4.17.21",
"markdown-it": "^12.3.2",
"md5": "^2.2.1",
"sql-formatter": "^2.3.3"
},

View File

@@ -1,29 +0,0 @@
const purgecss = require('@fullhuman/postcss-purgecss');
module.exports = {
plugins: [
require('postcss-import'),
require('tailwindcss')('./tailwind.config.js'),
require('postcss-preset-env')(),
process.env.NODE_ENV === 'production'
? purgecss({
content: [
'./resources/js/**/*.js',
'./resources/js/**/*.vue',
'./resources/views/errorPage.php',
],
extractors: [
{
extractor: class {
static extract(content) {
return content.match(/[a-zA-Z0-9-:_/]+/g) || [];
}
},
extensions: ['js', 'php', 'vue'],
},
],
whitelistPatterns: [/hljs/, /sf-dump/, /theme-dark/, /theme-auto/],
})
: '',
],
};

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="3.17.1@8f211792d813e4dc89f04ed372785ce93b902fd1">
<file src="src/IgnitionServiceProvider.php">
<UndefinedInterfaceMethod occurrences="7">
<code>$this-&gt;app</code>
<code>$this-&gt;app</code>
<code>$this-&gt;app</code>
<code>$this-&gt;app</code>
<code>$this-&gt;app</code>
<code>$this-&gt;app</code>
<code>$this-&gt;app</code>
</UndefinedInterfaceMethod>
</file>
<file src="src/LogRecorder/LogRecorder.php">
<UndefinedInterfaceMethod occurrences="1">
<code>$this-&gt;app</code>
</UndefinedInterfaceMethod>
</file>
<file src="src/QueryRecorder/QueryRecorder.php">
<UndefinedInterfaceMethod occurrences="3">
<code>$this-&gt;app</code>
<code>$this-&gt;app</code>
<code>$this-&gt;app</code>
</UndefinedInterfaceMethod>
</file>
<file src="src/SolutionProviders/MissingLivewireComponentSolutionProvider.php">
<UndefinedClass occurrences="1">
<code>ComponentNotFoundException</code>
</UndefinedClass>
</file>
<file src="src/SolutionProviders/UnknownValidationSolutionProvider.php">
<UndefinedClass occurrences="1">
<code>app('validator')</code>
</UndefinedClass>
</file>
<file src="src/Solutions/LivewireDiscoverSolution.php">
<UndefinedClass occurrences="1">
<code>LivewireComponentsFinder</code>
</UndefinedClass>
</file>
<file src="src/Views/Engines/CompilerEngine.php">
<ParamNameMismatch occurrences="1">
<code>$baseException</code>
</ParamNameMismatch>
</file>
<file src="src/Views/Engines/PhpEngine.php">
<ParamNameMismatch occurrences="1">
<code>$baseException</code>
</ParamNameMismatch>
</file>
</files>

47
vendor/facade/ignition/psalm.xml vendored Normal file
View File

@@ -0,0 +1,47 @@
<?xml version="1.0"?>
<psalm
errorLevel="4"
findUnusedVariablesAndParams="true"
resolveFromConfigFile="true"
useDocblockPropertyTypes="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
errorBaseline="psalm-baseline.xml"
>
<projectFiles>
<directory name="src"/>
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
<issueHandlers>
<UndefinedInterfaceMethod>
<errorLevel type="suppress">
<file name="src/Solutions/SolutionTransformer.php" />
</errorLevel>
</UndefinedInterfaceMethod>
<ForbiddenCode>
<errorLevel type="suppress">
<file name="src/SolutionProviders/MergeConflictSolutionProvider.php" />
</errorLevel>
</ForbiddenCode>
<InvalidCast>
<errorLevel type="suppress">
<file name="src/DumpRecorder/DumpRecorder.php" />
</errorLevel>
</InvalidCast>
<UndefinedClass>
<errorLevel type="suppress">
<file name="src/ErrorPage/ErrorPageViewModel.php" />
<file name="src/ErrorPage/IgnitionExceptionRenderer.php" />
<file name="src/IgnitionServiceProvider.php" />
</errorLevel>
</UndefinedClass>
</issueHandlers>
<plugins>
<pluginClass class="Psalm\LaravelPlugin\Plugin"/>
</plugins>
</psalm>

File diff suppressed because one or more lines are too long

View File

@@ -36,8 +36,10 @@
'shareEndpoint' => $shareEndpoint,
'defaultTab' => $defaultTab,
'defaultTabProps' => $defaultTabProps,
'appEnv' => $appEnv,
'appDebug' => $appDebug,
])
?>
?>;
window.tabs = <?=$tabs?>;
</script>

View File

@@ -150,10 +150,11 @@ class ShareReportAction
'context',
'logs',
'dumps',
'exception',
];
return Collection::make($contextItems)
->reject(function ($value, $group) use ($predefinedContextItemGroups) {
->reject(function ($_value, $group) use ($predefinedContextItemGroups) {
return in_array($group, $predefinedContextItemGroups);
})
->keys()

View File

@@ -0,0 +1,50 @@
<?php
namespace Facade\Ignition\Commands;
use Illuminate\Console\GeneratorCommand;
class SolutionProviderMakeCommand extends GeneratorCommand
{
/**
* The console command name.
*
* @var string
*/
protected $name = 'ignition:make-solution-provider';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new custom Ignition solution provider class';
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'Solution Provider';
/**
* Get the stub file for the generator.
*
* @return string
*/
protected function getStub()
{
return __DIR__.'/stubs/solution-provider.stub';
}
/**
* Get the default namespace for the class.
*
* @param string $rootNamespace
* @return string
*/
protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace.'\SolutionProviders';
}
}

View File

@@ -2,7 +2,10 @@
namespace Facade\Ignition\Commands;
use Composer\InstalledVersions;
use Exception;
use Facade\FlareClient\Flare;
use Facade\FlareClient\Http\Exceptions\BadResponseCode;
use Illuminate\Config\Repository;
use Illuminate\Console\Command;
use Illuminate\Log\LogManager;
@@ -72,10 +75,47 @@ class TestCommand extends Command
$testException = new Exception('This is an exception to test if the integration with Flare works.');
try {
app('flare.client')->sendTestReport($testException);
$this->info(PHP_EOL);
app(Flare::class)->sendTestReport($testException);
$this->info('');
} catch (Exception $exception) {
$this->warn('❌ We were unable to send an exception to Flare. Make sure that your key is correct and that you have a valid subscription. '.PHP_EOL.PHP_EOL.'For more info visit the docs on installing Flare in a Laravel project: https://flareapp.io/docs/ignition-for-laravel/introduction');
$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()],
['facade/ignition', InstalledVersions::getVersion('facade/ignition')],
['facade/flare-client-php', InstalledVersions::getVersion('facade/flare-client-php')],
['Curl', curl_version()['version']],
['SSL', curl_version()['ssl_version']],
]);
if ($this->output->isVerbose()) {
throw $exception;
}
return;
}

View File

@@ -0,0 +1,18 @@
<?php
namespace DummyNamespace;
use Facade\IgnitionContracts\HasSolutionsForThrowable;
class DummyClass implements HasSolutionsForThrowable
{
public function canSolve(): bool
{
return false;
}
public function getSolutions(): array
{
return [];
}
}

View File

@@ -5,6 +5,7 @@ namespace Facade\Ignition\Context;
use Facade\FlareClient\Context\ContextDetectorInterface;
use Facade\FlareClient\Context\ContextInterface;
use Illuminate\Http\Request;
use Livewire\LivewireManager;
class LaravelContextDetector implements ContextDetectorInterface
{
@@ -14,6 +15,17 @@ class LaravelContextDetector implements ContextDetectorInterface
return new LaravelConsoleContext($_SERVER['argv'] ?? []);
}
return new LaravelRequestContext(app(Request::class));
$request = app(Request::class);
if ($this->isRunningLiveWire($request)) {
return new LivewireRequestContext($request, app(LivewireManager::class));
}
return new LaravelRequestContext($request);
}
protected function isRunningLiveWire(Request $request)
{
return $request->hasHeader('x-livewire') && $request->hasHeader('referer');
}
}

View File

@@ -3,7 +3,9 @@
namespace Facade\Ignition\Context;
use Facade\FlareClient\Context\RequestContext;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Throwable;
class LaravelRequestContext extends RequestContext
{
@@ -17,9 +19,13 @@ class LaravelRequestContext extends RequestContext
public function getUser(): array
{
$user = $this->request->user();
try {
$user = $this->request->user();
if (! $user) {
if (! $user) {
return [];
}
} catch (Throwable $e) {
return [];
}
@@ -31,7 +37,7 @@ class LaravelRequestContext extends RequestContext
if (method_exists($user, 'toArray')) {
return $user->toArray();
}
} catch (\Throwable $e) {
} catch (Throwable $e) {
return [];
}
@@ -53,8 +59,15 @@ class LaravelRequestContext extends RequestContext
protected function getRouteParameters(): array
{
try {
return collect(optional($this->request->route())->parameters ?? [])->toArray();
} catch (\Throwable $e) {
return collect(optional($this->request->route())->parameters ?? [])
->map(function ($parameter) {
return $parameter instanceof Model ? $parameter->withoutRelations() : $parameter;
})
->map(function ($parameter) {
return method_exists($parameter, 'toFlare') ? $parameter->toFlare() : $parameter;
})
->toArray();
} catch (Throwable $e) {
return [];
}
}

View File

@@ -0,0 +1,94 @@
<?php
namespace Facade\Ignition\Context;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Livewire\LivewireManager;
class LivewireRequestContext extends LaravelRequestContext
{
/** @var \Livewire\LivewireManager */
protected $livewireManager;
public function __construct(
Request $request,
LivewireManager $livewireManager
) {
parent::__construct($request);
$this->livewireManager = $livewireManager;
}
public function getRequest(): array
{
$properties = parent::getRequest();
$properties['method'] = $this->livewireManager->originalMethod();
$properties['url'] = $this->livewireManager->originalUrl();
return $properties;
}
public function toArray(): array
{
$properties = parent::toArray();
$properties['livewire'] = $this->getLiveWireInformation();
return $properties;
}
protected function getLiveWireInformation(): array
{
$componentId = $this->request->input('fingerprint.id');
$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(),
];
}
protected function resolveData(): array
{
$data = $this->request->input('serverMemo.data') ?? [];
$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;
}
protected function resolveUpdates()
{
$updates = $this->request->input('updates') ?? [];
return array_map(function (array $update) {
$update['payload'] = Arr::except($update['payload'] ?? [], ['id']);
return $update;
}, $updates);
}
}

View File

@@ -6,7 +6,7 @@ use Symfony\Component\VarDumper\Cloner\VarCloner;
class DumpHandler
{
/** @var \Facade\Flare\DumpRecorder\DumpRecorder */
/** @var \Facade\Ignition\DumpRecorder\DumpRecorder */
protected $dumpRecorder;
public function __construct(DumpRecorder $dumpRecorder)
@@ -16,7 +16,7 @@ class DumpHandler
public function dump($value)
{
$data = (new VarCloner)->cloneVar($value);
$data = (new VarCloner())->cloneVar($value);
$this->dumpRecorder->record($data);
}

View File

@@ -2,7 +2,7 @@
namespace Facade\Ignition\DumpRecorder;
use Illuminate\Foundation\Application;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Arr;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Cloner\VarCloner;
@@ -14,7 +14,7 @@ class DumpRecorder
{
protected $dumps = [];
/** @var \Illuminate\Foundation\Application */
/** @var \Illuminate\Contracts\Foundation\Application */
protected $app;
public function __construct(Application $app)
@@ -26,7 +26,9 @@ class DumpRecorder
{
$multiDumpHandler = new MultiDumpHandler();
$this->app->singleton(MultiDumpHandler::class, $multiDumpHandler);
$this->app->singleton(MultiDumpHandler::class, function () use ($multiDumpHandler) {
return $multiDumpHandler;
});
$previousHandler = VarDumper::setHandler(function ($var) use ($multiDumpHandler) {
$multiDumpHandler->dump($var);
@@ -39,7 +41,7 @@ class DumpRecorder
}
$multiDumpHandler->addHandler(function ($var) {
$this->app->make(DumpHandler::class)->dump($var);
(new DumpHandler($this))->dump($var);
});
return $this;
@@ -47,9 +49,14 @@ class DumpRecorder
public function record(Data $data)
{
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 7);
$file = Arr::get($backtrace, '6.file');
$lineNumber = Arr::get($backtrace, '6.line');
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 8);
$file = (string)Arr::get($backtrace, '6.file');
$lineNumber = (int)Arr::get($backtrace, '6.line');
if (! Arr::exists($backtrace, '7.class') && (string)Arr::get($backtrace, '7.function') === 'ddd') {
$file = (string)Arr::get($backtrace, '7.file');
$lineNumber = (int)Arr::get($backtrace, '7.line');
}
$htmlDump = (new HtmlDumper())->dump($data);
@@ -80,10 +87,26 @@ class DumpRecorder
protected function getDefaultHandler()
{
return function ($value) {
$data = (new VarCloner)->cloneVar($value);
$data = (new VarCloner())->cloneVar($value);
$dumper = in_array(PHP_SAPI, ['cli', 'phpdbg']) ? new CliDumper : new BaseHtmlDumper;
$dumper->dump($data);
$this->getDumper()->dump($data);
};
}
protected function getDumper()
{
if (isset($_SERVER['VAR_DUMPER_FORMAT'])) {
if ($_SERVER['VAR_DUMPER_FORMAT'] === 'html') {
return new BaseHtmlDumper();
}
return new CliDumper();
}
if (in_array(PHP_SAPI, ['cli', 'phpdbg']) && ! isset($_SERVER['LARAVEL_OCTANE'])) {
return new CliDumper() ;
}
return new BaseHtmlDumper();
}
}

View File

@@ -21,7 +21,7 @@ class HtmlDumper extends BaseHtmlDumper
public function dump(Data $data, $output = null, array $extraDisplayOptions = []): string
{
return parent::dump($data, true, [
return (string)parent::dump($data, true, [
'maxDepth' => 3,
'maxStringLength' => 160,
]);

View File

@@ -2,6 +2,7 @@
namespace Facade\Ignition\ErrorPage;
use Facade\FlareClient\Flare;
use Facade\FlareClient\Report;
use Facade\Ignition\IgnitionConfig;
use Facade\IgnitionContracts\SolutionProviderRepository;
@@ -13,7 +14,7 @@ class ErrorPageHandler
/** @var \Facade\Ignition\IgnitionConfig */
protected $ignitionConfig;
/** @var \Facade\Ignition\Facades\Flare */
/** @var \Facade\FlareClient\Flare */
protected $flareClient;
/** @var \Facade\Ignition\ErrorPage\Renderer */
@@ -28,7 +29,7 @@ class ErrorPageHandler
Renderer $renderer,
SolutionProviderRepository $solutionProviderRepository
) {
$this->flareClient = $app->make('flare.client');
$this->flareClient = $app->make(Flare::class);
$this->ignitionConfig = $ignitionConfig;
$this->renderer = $renderer;
$this->solutionProviderRepository = $solutionProviderRepository;

View File

@@ -34,6 +34,12 @@ class ErrorPageViewModel implements Arrayable
/** @var array */
protected $defaultTabProps = [];
/** @var string */
protected $appEnv;
/** @var bool */
protected $appDebug;
public function __construct(?Throwable $throwable, IgnitionConfig $ignitionConfig, Report $report, array $solutions)
{
$this->throwable = $throwable;
@@ -43,6 +49,9 @@ class ErrorPageViewModel implements Arrayable
$this->report = $report;
$this->solutions = $solutions;
$this->appEnv = config('app.env');
$this->appDebug = config('app.debug');
}
public function throwableString(): string
@@ -51,7 +60,7 @@ class ErrorPageViewModel implements Arrayable
return '';
}
return sprintf(
$throwableString = sprintf(
"%s: %s in file %s on line %d\n\n%s\n",
get_class($this->throwable),
$this->throwable->getMessage(),
@@ -59,6 +68,8 @@ class ErrorPageViewModel implements Arrayable
$this->throwable->getLine(),
$this->report->getThrowable()->getTraceAsString()
);
return htmlspecialchars($throwableString);
}
public function telescopeUrl(): ?string
@@ -90,7 +101,9 @@ class ErrorPageViewModel implements Arrayable
public function title(): string
{
return "🧨 {$this->report->getMessage()}";
$message = htmlspecialchars($this->report->getMessage());
return "🧨 {$message}";
}
public function config(): array
@@ -126,13 +139,9 @@ class ErrorPageViewModel implements Arrayable
public function jsonEncode($data): string
{
$jsonOptions = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT;
$jsonOptions = JSON_PARTIAL_OUTPUT_ON_ERROR | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT;
if (version_compare(phpversion(), '7.2', '>=')) {
return json_encode($data, JSON_PARTIAL_OUTPUT_ON_ERROR | $jsonOptions);
}
return json_encode($data, JSON_PARTIAL_OUTPUT_ON_ERROR | $jsonOptions);
return json_encode($data, $jsonOptions);
}
public function getAssetContents(string $asset): string
@@ -184,6 +193,8 @@ class ErrorPageViewModel implements Arrayable
'getAssetContents' => Closure::fromCallable([$this, 'getAssetContents']),
'defaultTab' => $this->defaultTab,
'defaultTabProps' => $this->defaultTabProps,
'appEnv' => $this->appEnv,
'appDebug' => $this->appDebug,
];
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Facade\Ignition\ErrorPage;
use Illuminate\Contracts\Foundation\ExceptionRenderer;
/** @psalm-suppress UndefinedClass */
class IgnitionExceptionRenderer implements ExceptionRenderer
{
/** @var \Facade\Ignition\ErrorPage\ErrorPageHandler */
protected $errorPageHandler;
public function __construct(ErrorPageHandler $errorPageHandler)
{
$this->errorPageHandler = $errorPageHandler;
}
public function render($throwable)
{
ob_start();
$this->errorPageHandler->handle($throwable);
return ob_get_clean();
}
}

View File

@@ -22,7 +22,7 @@ class Renderer
$viewFile = "{$this->viewPath}/{$viewName}.php";
try {
extract((array) $_data, EXTR_OVERWRITE);
extract($_data, EXTR_OVERWRITE);
include $viewFile;
} catch (Exception $exception) {

View File

@@ -2,6 +2,7 @@
namespace Facade\Ignition\Facades;
use Facade\Ignition\Support\SentReports;
use Illuminate\Support\Facades\Facade;
/**
@@ -17,6 +18,11 @@ class Flare extends Facade
{
protected static function getFacadeAccessor()
{
return 'flare.client';
return \Facade\FlareClient\Flare::class;
}
public static function sentReports(): SentReports
{
return app(SentReports::class);
}
}

View File

@@ -11,7 +11,8 @@ class StyleController
{
return response(
file_get_contents(Ignition::styles()[$request->style]),
200, ['Content-Type' => 'text/css']
200,
['Content-Type' => 'text/css']
);
}
}

View File

@@ -24,6 +24,7 @@ class ExecuteSolutionRequest extends FormRequest
abort_if(is_null($solution), 404, 'Solution could not be found');
/** @var Solution */
return $solution;
}

View File

@@ -2,12 +2,16 @@
namespace Facade\Ignition;
use Exception;
use Facade\FlareClient\Api;
use Facade\FlareClient\Flare;
use Facade\FlareClient\Http\Client;
use Facade\Ignition\Commands\SolutionMakeCommand;
use Facade\Ignition\Commands\SolutionProviderMakeCommand;
use Facade\Ignition\Commands\TestCommand;
use Facade\Ignition\Context\LaravelContextDetector;
use Facade\Ignition\DumpRecorder\DumpRecorder;
use Facade\Ignition\ErrorPage\IgnitionExceptionRenderer;
use Facade\Ignition\ErrorPage\IgnitionWhoopsHandler;
use Facade\Ignition\ErrorPage\Renderer;
use Facade\Ignition\Exceptions\InvalidConfig;
@@ -18,32 +22,41 @@ use Facade\Ignition\Http\Controllers\ShareReportController;
use Facade\Ignition\Http\Controllers\StyleController;
use Facade\Ignition\Http\Middleware\IgnitionConfigValueEnabled;
use Facade\Ignition\Http\Middleware\IgnitionEnabled;
use Facade\Ignition\JobRecorder\JobRecorder;
use Facade\Ignition\Logger\FlareHandler;
use Facade\Ignition\LogRecorder\LogRecorder;
use Facade\Ignition\Middleware\AddDumps;
use Facade\Ignition\Middleware\AddEnvironmentInformation;
use Facade\Ignition\Middleware\AddExceptionInformation;
use Facade\Ignition\Middleware\AddGitInformation;
use Facade\Ignition\Middleware\AddJobInformation;
use Facade\Ignition\Middleware\AddLogs;
use Facade\Ignition\Middleware\AddQueries;
use Facade\Ignition\Middleware\AddSolutions;
use Facade\Ignition\Middleware\CustomizeGrouping;
use Facade\Ignition\Middleware\SetNotifierName;
use Facade\Ignition\QueryRecorder\QueryRecorder;
use Facade\Ignition\SolutionProviders\BadMethodCallSolutionProvider;
use Facade\Ignition\SolutionProviders\DefaultDbNameSolutionProvider;
use Facade\Ignition\SolutionProviders\IncorrectValetDbCredentialsSolutionProvider;
use Facade\Ignition\SolutionProviders\InvalidRouteActionSolutionProvider;
use Facade\Ignition\SolutionProviders\LazyLoadingViolationSolutionProvider;
use Facade\Ignition\SolutionProviders\MergeConflictSolutionProvider;
use Facade\Ignition\SolutionProviders\MissingAppKeySolutionProvider;
use Facade\Ignition\SolutionProviders\MissingColumnSolutionProvider;
use Facade\Ignition\SolutionProviders\MissingImportSolutionProvider;
use Facade\Ignition\SolutionProviders\MissingLivewireComponentSolutionProvider;
use Facade\Ignition\SolutionProviders\MissingMixManifestSolutionProvider;
use Facade\Ignition\SolutionProviders\MissingPackageSolutionProvider;
use Facade\Ignition\SolutionProviders\RunningLaravelDuskInProductionProvider;
use Facade\Ignition\SolutionProviders\SolutionProviderRepository;
use Facade\Ignition\SolutionProviders\TableNotFoundSolutionProvider;
use Facade\Ignition\SolutionProviders\UndefinedLivewireMethodSolutionProvider;
use Facade\Ignition\SolutionProviders\UndefinedLivewirePropertySolutionProvider;
use Facade\Ignition\SolutionProviders\UndefinedPropertySolutionProvider;
use Facade\Ignition\SolutionProviders\UndefinedVariableSolutionProvider;
use Facade\Ignition\SolutionProviders\UnknownValidationSolutionProvider;
use Facade\Ignition\SolutionProviders\ViewNotFoundSolutionProvider;
use Facade\Ignition\Support\SentReports;
use Facade\Ignition\Views\Engines\CompilerEngine;
use Facade\Ignition\Views\Engines\PhpEngine;
use Facade\IgnitionContracts\SolutionProviderRepository as SolutionProviderRepositoryContract;
@@ -57,9 +70,12 @@ use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
use Illuminate\View\Engines\CompilerEngine as LaravelCompilerEngine;
use Illuminate\View\Engines\PhpEngine as LaravelPhpEngine;
use Laravel\Octane\Events\RequestReceived;
use Laravel\Octane\Events\TaskReceived;
use Laravel\Octane\Events\TickReceived;
use Livewire\CompilerEngineForIgnition;
use Monolog\Logger;
use Throwable;
use Whoops\Handler\HandlerInterface;
class IgnitionServiceProvider extends ServiceProvider
{
@@ -73,17 +89,36 @@ class IgnitionServiceProvider extends ServiceProvider
$this->publishes([
__DIR__.'/../config/ignition.php' => config_path('ignition.php'),
], 'ignition-config');
if (isset($_SERVER['argv']) && ['artisan', 'tinker'] === $_SERVER['argv']) {
Api::sendReportsInBatches(false);
}
$this->app->make(JobRecorder::class)->register();
}
$this
->registerViewEngines()
->registerHousekeepingRoutes()
->registerLogHandler()
->registerCommands()
->setupQueue($this->app->queue);
->registerCommands();
if ($this->app->bound('queue')) {
$this->setupQueue($this->app->get('queue'));
}
if (isset($_SERVER['LARAVEL_OCTANE'])) {
$this->setupOctane();
}
if (config('flare.reporting.report_logs', true)) {
$this->app->make(LogRecorder::class)->register();
}
if (config('flare.reporting.report_queries', true)) {
$this->app->make(QueryRecorder::class)->register();
}
$this->app->make(QueryRecorder::class)->register();
$this->app->make(LogRecorder::class)->register();
$this->app->make(DumpRecorder::class)->register();
}
@@ -94,21 +129,27 @@ class IgnitionServiceProvider extends ServiceProvider
$this
->registerSolutionProviderRepository()
->registerRenderer()
->registerExceptionRenderer()
->registerWhoopsHandler()
->registerIgnitionConfig()
->registerFlare()
->registerLogRecorder()
->registerDumpCollector();
->registerDumpCollector()
->registerJobRecorder();
if (config('flare.reporting.report_queries')) {
if (config('flare.reporting.report_logs', true)) {
$this->registerLogRecorder();
}
if (config('flare.reporting.report_queries', true)) {
$this->registerQueryRecorder();
}
if (config('flare.reporting.anonymize_ips')) {
$this->app->get('flare.client')->anonymizeIp();
$this->app->get(Flare::class)->anonymizeIp();
}
$this->app->get(Flare::class)->censorRequestBodyFields(config('flare.reporting.censor_request_body_fields', ['password']));
$this->registerBuiltInMiddleware();
}
@@ -119,10 +160,14 @@ class IgnitionServiceProvider extends ServiceProvider
}
$this->app->make('view.engine.resolver')->register('php', function () {
return new PhpEngine();
return new PhpEngine($this->app['files']);
});
$this->app->make('view.engine.resolver')->register('blade', function () {
if (class_exists(CompilerEngineForIgnition::class)) {
return new CompilerEngineForIgnition($this->app['blade.compiler']);
}
return new CompilerEngine($this->app['blade.compiler']);
});
@@ -168,7 +213,7 @@ class IgnitionServiceProvider extends ServiceProvider
return $this;
}
protected function registerExceptionRenderer()
protected function registerRenderer()
{
$this->app->bind(Renderer::class, function () {
return new Renderer(__DIR__.'/../resources/views/');
@@ -177,11 +222,19 @@ class IgnitionServiceProvider extends ServiceProvider
return $this;
}
protected function registerWhoopsHandler()
protected function registerExceptionRenderer()
{
$this->app->bind(HandlerInterface::class, function (Application $app) {
return $app->make(IgnitionWhoopsHandler::class);
});
if (interface_exists(\Whoops\Handler\HandlerInterface::class)) {
$this->app->bind(\Whoops\Handler\HandlerInterface::class, function (Application $app) {
return $app->make(IgnitionWhoopsHandler::class);
});
}
if (interface_exists(\Illuminate\Contracts\Foundation\ExceptionRenderer::class)) {
$this->app->bind(\Illuminate\Contracts\Foundation\ExceptionRenderer::class, function (Application $app) {
return $app->make(IgnitionExceptionRenderer::class);
});
}
return $this;
}
@@ -211,29 +264,32 @@ class IgnitionServiceProvider extends ServiceProvider
return new Client(
config('flare.key'),
config('flare.secret'),
config('flare.base_url', 'https://flareapp.io/api')
config('flare.base_url', 'https://reporting.flareapp.io/api')
);
});
$this->app->singleton(SentReports::class);
$this->app->alias('flare.http', Client::class);
$this->app->singleton('flare.client', function () {
$client = new Flare($this->app->get('flare.http'), new LaravelContextDetector, $this->app);
$this->app->singleton(Flare::class, function () {
$client = new Flare($this->app->get('flare.http'), new LaravelContextDetector(), $this->app);
$client->applicationPath(base_path());
$client->stage(config('app.env'));
return $client;
});
$this->app->alias('flare.client', Flare::class);
return $this;
}
protected function registerLogHandler()
{
$this->app->singleton('flare.logger', function ($app) {
$handler = new FlareHandler($app->make('flare.client'));
$handler = new FlareHandler(
$app->make(Flare::class),
$app->make(SentReports::class)
);
$logLevelString = config('logging.channels.flare.level', 'error');
@@ -269,13 +325,14 @@ class IgnitionServiceProvider extends ServiceProvider
return $logLevel;
}
protected function registerLogRecorder()
protected function registerLogRecorder(): self
{
$logCollector = $this->app->make(LogRecorder::class);
$this->app->singleton(LogRecorder::class);
$this->app->instance(LogRecorder::class, $logCollector);
$this->app->singleton(LogRecorder::class, function (Application $app): LogRecorder {
return new LogRecorder(
$app,
$app->get('config')->get('flare.reporting.maximum_number_of_collected_logs')
);
});
return $this;
}
@@ -291,10 +348,22 @@ class IgnitionServiceProvider extends ServiceProvider
return $this;
}
protected function registerJobRecorder()
{
if (! $this->app->runningInConsole()) {
return $this;
}
$this->app->singleton(JobRecorder::class);
return $this;
}
protected function registerCommands()
{
$this->app->bind('command.flare:test', TestCommand::class);
$this->app->bind('command.make:solution', SolutionMakeCommand::class);
$this->app->bind('command.make:solution-provider', SolutionProviderMakeCommand::class);
if ($this->app['config']->get('flare.key')) {
$this->commands(['command.flare:test']);
@@ -302,46 +371,60 @@ class IgnitionServiceProvider extends ServiceProvider
if ($this->app['config']->get('ignition.register_commands', false)) {
$this->commands(['command.make:solution']);
$this->commands(['command.make:solution-provider']);
}
return $this;
}
protected function registerQueryRecorder()
protected function registerQueryRecorder(): self
{
$queryCollector = $this->app->make(QueryRecorder::class);
$this->app->singleton(QueryRecorder::class);
$this->app->instance(QueryRecorder::class, $queryCollector);
$this->app->singleton(QueryRecorder::class, function (Application $app): QueryRecorder {
return new QueryRecorder(
$app,
$app->get('config')->get('flare.reporting.report_query_bindings'),
$app->get('config')->get('flare.reporting.maximum_number_of_collected_queries')
);
});
return $this;
}
protected function registerBuiltInMiddleware()
{
$middleware = collect([
$middlewares = [
SetNotifierName::class,
AddEnvironmentInformation::class,
AddLogs::class,
AddDumps::class,
AddQueries::class,
AddSolutions::class,
])
->map(function (string $middlewareClass) {
return $this->app->make($middlewareClass);
});
AddExceptionInformation::class,
];
if (config('flare.reporting.report_logs', true)) {
$middlewares[] = AddLogs::class;
}
$middlewares[] = AddDumps::class;
if (config('flare.reporting.report_queries', true)) {
$middlewares[] = AddQueries::class;
}
$middlewares[] = AddSolutions::class;
if ($this->app->runningInConsole()) {
$middlewares[] = AddJobInformation::class;
}
$middleware = collect($middlewares)
->map(function (string $middlewareClass) {
return $this->app->make($middlewareClass);
});
if (config('flare.reporting.collect_git_information')) {
$middleware[] = (new AddGitInformation());
}
if (! is_null(config('flare.reporting.grouping_type'))) {
$middleware[] = new CustomizeGrouping(config('flare.reporting.grouping_type'));
}
foreach ($middleware as $singleMiddleware) {
$this->app->get('flare.client')->registerMiddleware($singleMiddleware);
$this->app->get(Flare::class)->registerMiddleware($singleMiddleware);
}
return $this;
@@ -364,6 +447,12 @@ class IgnitionServiceProvider extends ServiceProvider
RunningLaravelDuskInProductionProvider::class,
MissingColumnSolutionProvider::class,
UnknownValidationSolutionProvider::class,
UndefinedLivewireMethodSolutionProvider::class,
UndefinedLivewirePropertySolutionProvider::class,
UndefinedPropertySolutionProvider::class,
MissingMixManifestSolutionProvider::class,
MissingLivewireComponentSolutionProvider::class,
LazyLoadingViolationSolutionProvider::class,
];
}
@@ -416,18 +505,55 @@ class IgnitionServiceProvider extends ServiceProvider
return null;
}
protected function resetFlare()
{
$this->app->get(SentReports::class)->clear();
$this->app->get(Flare::class)->reset();
if (config('flare.reporting.report_logs', true)) {
$this->app->make(LogRecorder::class)->reset();
}
if (config('flare.reporting.report_queries', true)) {
$this->app->make(QueryRecorder::class)->reset();
}
if ($this->app->runningInConsole()) {
$this->app->make(JobRecorder::class)->reset();
}
$this->app->make(DumpRecorder::class)->reset();
}
protected function setupQueue(QueueManager $queue)
{
$queue->looping(function () {
$this->app->get('flare.client')->reset();
// 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->resetFlare();
});
if (config('flare.reporting.report_queries')) {
$this->app->make(QueryRecorder::class)->reset();
}
// Send queued reports (and reset) after executing a queue job.
$queue->after(function () {
$this->resetFlare();
});
$this->app->make(LogRecorder::class)->reset();
// Note: the $queue->looping() event can't be used because it's not triggered on Vapor
}
$this->app->make(DumpRecorder::class)->reset();
/** @psalm-suppress UndefinedClass */
protected function setupOctane()
{
$this->app['events']->listen(RequestReceived::class, function () {
$this->resetFlare();
});
$this->app['events']->listen(TaskReceived::class, function () {
$this->resetFlare();
});
$this->app['events']->listen(TickReceived::class, function () {
$this->resetFlare();
});
}
}

View File

@@ -0,0 +1,167 @@
<?php
namespace Facade\Ignition\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
{
/** @var \Illuminate\Contracts\Foundation\Application */
protected $app;
/** @var \Illuminate\Contracts\Queue\Job|null */
protected $job = null;
public function __construct(Application $app)
{
$this->app = $app;
}
public function register(): self
{
$this->app['events']->listen(JobExceptionOccurred::class, [$this, 'record']);
return $this;
}
public function record(JobExceptionOccurred $event): void
{
$this->job = $event->job;
}
public function getJob(): ?Job
{
return $this->job;
}
public function reset(): void
{
$this->job = null;
}
public function toArray(): array
{
if ($this->job === null) {
return [];
}
return array_merge(
$this->getJobProperties(),
[
'name' => $this->job->resolveName(),
'connection' => $this->job->getConnectionName(),
'queue' => $this->job->getQueue(),
]
);
}
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']),
config('ignition.max_chained_job_reporting_depth', 5)
);
} 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();
}
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)) {
return unserialize($this->app[Encrypter::class]->decrypt($command));
}
throw new RuntimeException('Unable to extract job payload.');
}
}

View File

@@ -2,21 +2,25 @@
namespace Facade\Ignition\LogRecorder;
use Exception;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Log\Events\MessageLogged;
use Throwable;
class LogRecorder
{
/** @var \Facade\Flare\LogRecorder\LogMessage[] */
/** @var \Facade\Ignition\LogRecorder\LogMessage[] */
protected $logMessages = [];
/** @var \Illuminate\Contracts\Foundation\Application */
protected $app;
public function __construct(Application $app)
/** @var int|null */
private $maxLogs;
public function __construct(Application $app, ?int $maxLogs = null)
{
$this->app = $app;
$this->maxLogs = $maxLogs;
}
public function register(): self
@@ -33,6 +37,10 @@ class LogRecorder
}
$this->logMessages[] = LogMessage::fromMessageLoggedEvent($event);
if (is_int($this->maxLogs)) {
$this->logMessages = array_slice($this->logMessages, -$this->maxLogs);
}
}
public function getLogMessages(): array
@@ -57,7 +65,7 @@ class LogRecorder
return false;
}
if (! $event->context['exception'] instanceof Exception) {
if (! $event->context['exception'] instanceof Throwable) {
return false;
}
@@ -68,4 +76,16 @@ class LogRecorder
{
$this->logMessages = [];
}
public function getMaxLogs(): ?int
{
return $this->maxLogs;
}
public function setMaxLogs(?int $maxLogs): self
{
$this->maxLogs = $maxLogs;
return $this;
}
}

View File

@@ -3,7 +3,9 @@
namespace Facade\Ignition\Logger;
use Facade\FlareClient\Flare;
use Facade\FlareClient\Report;
use Facade\Ignition\Ignition;
use Facade\Ignition\Support\SentReports;
use Facade\Ignition\Tabs\Tab;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Logger;
@@ -14,12 +16,17 @@ class FlareHandler extends AbstractProcessingHandler
/** @var \Facade\FlareClient\Flare */
protected $flare;
/** @var \Facade\Ignition\Support\SentReports */
protected $sentReports;
protected $minimumReportLogLevel = Logger::ERROR;
public function __construct(Flare $flare, $level = Logger::DEBUG, $bubble = true)
public function __construct(Flare $flare, SentReports $sentReports, $level = Logger::DEBUG, $bubble = true)
{
$this->flare = $flare;
$this->sentReports = $sentReports;
parent::__construct($level, $bubble);
}
@@ -32,35 +39,51 @@ class FlareHandler extends AbstractProcessingHandler
$this->minimumReportLogLevel = $level;
}
protected function write(array $report): void
protected function write(array $record): void
{
if (! $this->shouldReport($report)) {
if (! $this->shouldReport($record)) {
return;
}
if ($this->hasException($report)) {
if ($this->hasException($record)) {
/** @var Throwable $throwable */
$throwable = $report['context']['exception'];
$throwable = $record['context']['exception'];
collect(Ignition::$tabs)
->each(function (Tab $tab) use ($throwable) {
$tab->beforeRenderingErrorPage($this->flare, $throwable);
});
$this->flare->report($report['context']['exception']);
$report = $this->flare->report($record['context']['exception']);
if ($report) {
$this->sentReports->add($report);
}
return;
}
if (config('flare.send_logs_as_events')) {
if ($this->hasValidLogLevel($report)) {
$this->flare->reportMessage($report['message'], 'Log '.Logger::getLevelName($report['level']));
if ($this->hasValidLogLevel($record)) {
$this->flare->reportMessage(
$record['message'],
'Log ' . Logger::getLevelName($record['level']),
function (Report $flareReport) use ($record) {
foreach ($record['context'] as $key => $value) {
$flareReport->context($key, $value);
}
}
);
}
}
}
protected function shouldReport(array $report): bool
{
if (! config('flare.key')) {
return false;
}
return $this->hasException($report) || $this->hasValidLogLevel($report);
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Facade\Ignition\Middleware;
use Facade\FlareClient\Report;
use Illuminate\Database\QueryException;
class AddExceptionInformation
{
public function handle(Report $report, $next)
{
$throwable = $report->getThrowable();
if (! $throwable instanceof QueryException) {
return $next($report);
}
$report->group('exception', [
'raw_sql' => $throwable->getSql(),
]);
return $next($report);
}
}

View File

@@ -3,19 +3,23 @@
namespace Facade\Ignition\Middleware;
use Facade\FlareClient\Report;
use ReflectionClass;
use Symfony\Component\Process\Exception\RuntimeException;
use Symfony\Component\Process\Process;
class AddGitInformation
{
public function handle(Report $report, $next)
{
$report->group('git', [
'hash' => $this->hash(),
'message' => $this->message(),
'tag' => $this->tag(),
'remote' => $this->remote(),
'isDirty' => ! $this->isClean(),
]);
try {
$report->group('git', [
'hash' => $this->hash(),
'message' => $this->message(),
'tag' => $this->tag(),
'remote' => $this->remote(),
]);
} catch (RuntimeException $exception) {
}
return $next($report);
}
@@ -40,14 +44,9 @@ class AddGitInformation
return $this->command('git config --get remote.origin.url');
}
public function isClean(): bool
{
return empty($this->command('git status -s'));
}
protected function command($command)
{
$process = (new \ReflectionClass(Process::class))->hasMethod('fromShellCommandline')
$process = (new ReflectionClass(Process::class))->hasMethod('fromShellCommandline')
? Process::fromShellCommandline($command, base_path())
: new Process($command, base_path());

View File

@@ -0,0 +1,26 @@
<?php
namespace Facade\Ignition\Middleware;
use Facade\FlareClient\Report;
use Facade\Ignition\JobRecorder\JobRecorder;
class AddJobInformation
{
/** @var \Facade\Ignition\JobRecorder\JobRecorder */
protected $jobRecorder;
public function __construct(JobRecorder $jobRecorder)
{
$this->jobRecorder = $jobRecorder;
}
public function handle(Report $report, $next)
{
if ($this->jobRecorder->getJob()) {
$report->group('job', $this->jobRecorder->toArray());
}
return $next($report);
}
}

View File

@@ -1,27 +0,0 @@
<?php
namespace Facade\Ignition\Middleware;
use Facade\FlareClient\Enums\GroupingTypes;
use Facade\FlareClient\Report;
class CustomizeGrouping
{
protected $groupingType;
public function __construct($groupingType)
{
$this->groupingType = $groupingType;
}
public function handle(Report $report, $next)
{
$report->groupByTopFrame();
if ($this->groupingType === GroupingTypes::EXCEPTION) {
$report->groupByException();
}
return $next($report);
}
}

View File

@@ -6,7 +6,7 @@ use Facade\FlareClient\Report;
class SetNotifierName
{
const NOTIFIER_NAME = 'Laravel Client';
public const NOTIFIER_NAME = 'Laravel Client';
public function handle(Report $report, $next)
{

View File

@@ -13,9 +13,20 @@ class QueryRecorder
/** @var \Illuminate\Contracts\Foundation\Application */
protected $app;
public function __construct(Application $app)
{
/** @var bool */
private $reportBindings;
/** @var int|null */
private $maxQueries;
public function __construct(
Application $app,
bool $reportBindings = true,
?int $maxQueries = null
) {
$this->app = $app;
$this->reportBindings = $reportBindings;
$this->maxQueries = $maxQueries;
}
public function register()
@@ -27,13 +38,11 @@ class QueryRecorder
public function record(QueryExecuted $queryExecuted)
{
$maximumQueries = $this->app['config']->get('flare.reporting.maximum_number_of_collected_queries', 200);
$this->queries[] = Query::fromQueryExecutedEvent($queryExecuted, $this->reportBindings);
$reportBindings = $this->app['config']->get('flare.reporting.report_query_bindings', true);
$this->queries[] = Query::fromQueryExecutedEvent($queryExecuted, $reportBindings);
$this->queries = array_slice($this->queries, $maximumQueries * -1, $maximumQueries);
if (is_int($this->maxQueries)) {
$this->queries = array_slice($this->queries, -$this->maxQueries);
}
}
public function getQueries(): array
@@ -51,4 +60,28 @@ class QueryRecorder
{
$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;
}
}

View File

@@ -4,31 +4,32 @@ namespace Facade\Ignition\SolutionProviders;
use Facade\Ignition\Solutions\SuggestUsingCorrectDbNameSolution;
use Facade\IgnitionContracts\HasSolutionsForThrowable;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\QueryException;
use Throwable;
class DefaultDbNameSolutionProvider implements HasSolutionsForThrowable
{
public const MYSQL_UNKNOWN_DATABASE_CODE = 1049;
public function canSolve(Throwable $throwable): bool
{
if ($this->canTryDatabaseConnection()) {
try {
DB::connection()->select('SELECT 1');
} catch (\Exception $e) {
return in_array(env('DB_DATABASE'), ['homestead', 'laravel']);
}
if (! $throwable instanceof QueryException) {
return false;
}
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()];
}
protected function canTryDatabaseConnection()
{
return version_compare(app()->version(), '5.6.28', '>');
}
}

View File

@@ -9,11 +9,11 @@ use Throwable;
class IncorrectValetDbCredentialsSolutionProvider implements HasSolutionsForThrowable
{
const MYSQL_ACCESS_DENIED_CODE = 1045;
public const MYSQL_ACCESS_DENIED_CODE = 1045;
public function canSolve(Throwable $throwable): bool
{
if (! PHP_OS === 'Darwin') {
if (PHP_OS !== 'Darwin') {
return false;
}

View File

@@ -60,10 +60,10 @@ class InvalidRouteActionSolutionProvider implements HasSolutionsForThrowable
$composerClassMap = app(ComposerClassMap::class);
$controllers = collect($composerClassMap->listClasses())
->filter(function (string $file, string $fqcn) {
->filter(function (string $_file, string $fqcn) {
return Str::endsWith($fqcn, 'Controller');
})
->mapWithKeys(function (string $file, string $fqcn) {
->mapWithKeys(function (string $_file, string $fqcn) {
return [$fqcn => class_basename($fqcn)];
})
->toArray();

View File

@@ -0,0 +1,41 @@
<?php
namespace Facade\Ignition\SolutionProviders;
use Facade\Ignition\Support\LaravelVersion;
use Facade\IgnitionContracts\BaseSolution;
use Facade\IgnitionContracts\HasSolutionsForThrowable;
use Illuminate\Database\LazyLoadingViolationException;
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',
]),];
}
}

View File

@@ -6,14 +6,13 @@ use Facade\IgnitionContracts\BaseSolution;
use Facade\IgnitionContracts\HasSolutionsForThrowable;
use Illuminate\Support\Str;
use ParseError;
use Symfony\Component\Debug\Exception\FatalThrowableError;
use Throwable;
class MergeConflictSolutionProvider implements HasSolutionsForThrowable
{
public function canSolve(Throwable $throwable): bool
{
if (! ($throwable instanceof FatalThrowableError || $throwable instanceof ParseError)) {
if (! ($throwable instanceof ParseError)) {
return false;
}
@@ -48,11 +47,11 @@ class MergeConflictSolutionProvider implements HasSolutionsForThrowable
];
}
private function getCurrentBranch(string $directory): string
protected function getCurrentBranch(string $directory): string
{
$branch = "'".trim(shell_exec("cd ${directory}; git branch | grep \\* | cut -d ' ' -f2"))."'";
if (! isset($branch) || $branch === "''") {
if ($branch === "''") {
$branch = 'current branch';
}

View File

@@ -12,7 +12,7 @@ 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 const MYSQL_BAD_FIELD_CODE = '42S22';
public function canSolve(Throwable $throwable): bool
{

View File

@@ -0,0 +1,42 @@
<?php
namespace Facade\Ignition\SolutionProviders;
use Facade\Ignition\Solutions\LivewireDiscoverSolution;
use Facade\IgnitionContracts\HasSolutionsForThrowable;
use Livewire\Exceptions\ComponentNotFoundException;
use Livewire\LivewireComponentsFinder;
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;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Facade\Ignition\SolutionProviders;
use Facade\IgnitionContracts\BaseSolution;
use Facade\IgnitionContracts\HasSolutionsForThrowable;
use Illuminate\Support\Str;
use Throwable;
class MissingMixManifestSolutionProvider implements HasSolutionsForThrowable
{
public function canSolve(Throwable $throwable): bool
{
return Str::startsWith($throwable->getMessage(), 'The Mix manifest does not exist');
}
public function getSolutions(Throwable $throwable): array
{
return [
BaseSolution::create('Missing Mix Manifest File')
->setSolutionDescription('Did you forget to run `npm ci && npm run dev`?'),
];
}
}

View File

@@ -30,7 +30,7 @@ class RouteNotDefinedSolutionProvider implements HasSolutionsForThrowable
}
}
return preg_match(self::REGEX, $throwable->getMessage(), $matches);
return (bool)preg_match(self::REGEX, $throwable->getMessage(), $matches);
}
public function getSolutions(Throwable $throwable): array

View File

@@ -12,7 +12,7 @@ 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 const MYSQL_BAD_TABLE_CODE = '42S02';
public function canSolve(Throwable $throwable): bool
{

View File

@@ -0,0 +1,48 @@
<?php
namespace Facade\Ignition\SolutionProviders;
use Facade\Ignition\Solutions\SuggestLivewireMethodNameSolution;
use Facade\Ignition\Support\LivewireComponentParser;
use Facade\IgnitionContracts\HasSolutionsForThrowable;
use Livewire\Exceptions\MethodNotFoundException;
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();
}
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,
];
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace Facade\Ignition\SolutionProviders;
use Facade\Ignition\Solutions\SuggestLivewirePropertyNameSolution;
use Facade\Ignition\Support\LivewireComponentParser;
use Facade\IgnitionContracts\HasSolutionsForThrowable;
use Livewire\Exceptions\PropertyNotFoundException;
use Livewire\Exceptions\PublicPropertyNotFoundException;
use Throwable;
class UndefinedLivewirePropertySolutionProvider implements HasSolutionsForThrowable
{
public function canSolve(Throwable $throwable): bool
{
return $throwable instanceof PropertyNotFoundException || $throwable instanceof PublicPropertyNotFoundException;
}
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();
}
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,
];
}
}

View File

@@ -0,0 +1,98 @@
<?php
namespace Facade\Ignition\SolutionProviders;
use ErrorException;
use Facade\IgnitionContracts\BaseSolution;
use Facade\IgnitionContracts\HasSolutionsForThrowable;
use Illuminate\Support\Collection;
use ReflectionClass;
use ReflectionProperty;
use Throwable;
class UndefinedPropertySolutionProvider implements HasSolutionsForThrowable
{
protected const REGEX = '/([a-zA-Z\\\\]+)::\$([a-zA-Z]+)/m';
protected const MINIMUM_SIMILARITY = 80;
public function canSolve(Throwable $throwable): bool
{
if (! $throwable instanceof ErrorException) {
return false;
}
if (is_null($this->getClassAndPropertyFromExceptionMessage($throwable->getMessage()))) {
return false;
}
if (! $this->similarPropertyExists($throwable)) {
return false;
}
return true;
}
public function getSolutions(Throwable $throwable): array
{
return [
BaseSolution::create('Unknown Property')
->setSolutionDescription($this->getSolutionDescription($throwable)),
];
}
public function getSolutionDescription(Throwable $throwable): string
{
if (! $this->canSolve($throwable) || ! $this->similarPropertyExists($throwable)) {
return '';
}
extract($this->getClassAndPropertyFromExceptionMessage($throwable->getMessage()), EXTR_OVERWRITE);
$possibleProperty = $this->findPossibleProperty($class, $property);
return "Did you mean {$class}::\${$possibleProperty->name} ?";
}
protected function similarPropertyExists(Throwable $throwable)
{
extract($this->getClassAndPropertyFromExceptionMessage($throwable->getMessage()), EXTR_OVERWRITE);
$possibleProperty = $this->findPossibleProperty($class, $property);
return $possibleProperty !== null;
}
protected function getClassAndPropertyFromExceptionMessage(string $message): ?array
{
if (! preg_match(self::REGEX, $message, $matches)) {
return null;
}
return [
'class' => $matches[1],
'property' => $matches[2],
];
}
protected function findPossibleProperty(string $class, string $invalidPropertyName)
{
return $this->getAvailableProperties($class)
->sortByDesc(function (ReflectionProperty $property) use ($invalidPropertyName) {
similar_text($invalidPropertyName, $property->name, $percentage);
return $percentage;
})
->filter(function (ReflectionProperty $property) use ($invalidPropertyName) {
similar_text($invalidPropertyName, $property->name, $percentage);
return $percentage >= self::MINIMUM_SIMILARITY;
})->first();
}
protected function getAvailableProperties($class): Collection
{
$class = new ReflectionClass($class);
return Collection::make($class->getProperties());
}
}

View File

@@ -40,22 +40,31 @@ class UndefinedVariableSolutionProvider implements HasSolutionsForThrowable
return $solutions;
}
protected function findCorrectVariableSolutions(Throwable $throwable, string $variableName, string $viewFile): array
{
return collect($throwable->getViewData())->map(function ($value, $key) use ($variableName) {
similar_text($variableName, $key, $percentage);
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(function ($var, $key) {
return $var['match'] > 40;
})->keys()->map(function ($suggestion) use ($variableName, $viewFile) {
return new SuggestCorrectVariableNameSolution($variableName, $viewFile, $suggestion);
})->map(function ($solution) {
return $solution->isRunnable()
? $solution
: BaseSolution::create($solution->getSolutionTitle())
->setSolutionDescription($solution->getSolutionActionDescription());
})->toArray();
return ['match' => $percentage, 'value' => $value];
})
->sortByDesc('match')->filter(function ($var) {
return $var['match'] > 40;
})
->keys()
->map(function ($suggestion) use ($variableName, $viewFile) {
return 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)
@@ -65,18 +74,22 @@ class UndefinedVariableSolutionProvider implements HasSolutionsForThrowable
return $optionalSolution->isRunnable()
? $optionalSolution
: BaseSolution::create($optionalSolution->getSolutionTitle())
->setSolutionDescription($optionalSolution->getSolutionActionDescription());
->setSolutionDescription($optionalSolution->getSolutionDescription());
}
protected function getNameAndView(Throwable $throwable): ?array
{
$pattern = '/Undefined variable: (.*?) \(View: (.*?)\)/';
$pattern = '/Undefined variable:? (.*?) \(View: (.*?)\)/';
preg_match($pattern, $throwable->getMessage(), $matches);
if (count($matches) === 3) {
[$string, $variableName, $viewFile] = $matches;
[, $variableName, $viewFile] = $matches;
$variableName = ltrim($variableName, '$');
return compact('variableName', 'viewFile');
}
return null;
}
}

View File

@@ -23,7 +23,7 @@ class ViewNotFoundSolutionProvider implements HasSolutionsForThrowable
return false;
}
return preg_match(self::REGEX, $throwable->getMessage(), $matches);
return (bool)preg_match(self::REGEX, $throwable->getMessage(), $matches);
}
public function getSolutions(Throwable $throwable): array
@@ -34,6 +34,13 @@ class ViewNotFoundSolutionProvider implements HasSolutionsForThrowable
$suggestedView = $this->findRelatedView($missingView);
if ($suggestedView == $missingView) {
return [
BaseSolution::create("{$missingView} was not found.")
->setSolutionDescription('View names should not contain the . character!'),
];
}
if ($suggestedView) {
return [
BaseSolution::create("{$missingView} was not found.")

View File

@@ -0,0 +1,53 @@
<?php
namespace Facade\Ignition\Solutions;
use Facade\IgnitionContracts\RunnableSolution;
use Livewire\LivewireComponentsFinder;
class LivewireDiscoverSolution implements RunnableSolution
{
private $customTitle;
public function __construct($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. You can discover your Livewire components using `php artisan livewire:discover`.';
}
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 'Pressing the button below will try to discover your Livewire components.';
}
public function getRunButtonText(): string
{
return 'Run livewire:discover';
}
public function run(array $parameters = [])
{
app(LivewireComponentsFinder::class)->build();
}
}

View File

@@ -32,7 +32,6 @@ class MakeViewVariableOptionalSolution implements RunnableSolution
public function getSolutionActionDescription(): string
{
$path = str_replace(base_path().'/', '', $this->viewFile);
$output = [
'Make the variable optional in the blade template.',
"Replace `{{ $$this->variableName }}` with `{{ $$this->variableName ?? '' }}`",
@@ -74,10 +73,10 @@ class MakeViewVariableOptionalSolution implements RunnableSolution
protected function isSafePath(string $path): bool
{
if (!Str::startsWith($path, ['/', './'])) {
if (! Str::startsWith($path, ['/', './'])) {
return false;
}
if (!Str::endsWith($path, '.blade.php')) {
if (! Str::endsWith($path, '.blade.php')) {
return false;
}
@@ -86,7 +85,7 @@ class MakeViewVariableOptionalSolution implements RunnableSolution
public function makeOptional(array $parameters = [])
{
if (!$this->isSafePath($parameters['viewFile'])) {
if (! $this->isSafePath($parameters['viewFile'])) {
return false;
}
@@ -108,7 +107,7 @@ class MakeViewVariableOptionalSolution implements RunnableSolution
protected function generateExpectedTokens(array $originalTokens, string $variableName): array
{
$expectedTokens = [];
foreach ($originalTokens as $key => $token) {
foreach ($originalTokens as $token) {
$expectedTokens[] = $token;
if ($token[0] === T_VARIABLE && $token[1] === '$'.$variableName) {
$expectedTokens[] = [T_WHITESPACE, ' ', $token[2]];

View File

@@ -7,7 +7,7 @@ use Facade\IgnitionContracts\Solution;
class MissingPackageSolution implements Solution
{
/** @var \Facade\Flare\Support\Packagist\Package */
/** @var Package */
protected $possiblePackage;
public function __construct(Package $possiblePackage)

View File

@@ -5,6 +5,7 @@ namespace Facade\Ignition\Solutions;
use Facade\IgnitionContracts\RunnableSolution;
use Facade\IgnitionContracts\Solution;
use Illuminate\Contracts\Support\Arrayable;
use Throwable;
class SolutionTransformer implements Arrayable
{
@@ -29,7 +30,16 @@ class SolutionTransformer implements Arrayable
'run_button_text' => $isRunnable ? $this->solution->getRunButtonText() : '',
'run_parameters' => $isRunnable ? $this->solution->getRunParameters() : [],
'action_description' => $isRunnable ? $this->solution->getSolutionActionDescription() : '',
'execute_endpoint' => action('\Facade\Ignition\Http\Controllers\ExecuteSolutionController'),
'execute_endpoint' => $this->executeEndpoint(),
];
}
protected function executeEndpoint(): string
{
try {
return action('\Facade\Ignition\Http\Controllers\ExecuteSolutionController');
} catch (Throwable $exception) {
return '';
}
}
}

View File

@@ -12,6 +12,9 @@ class SuggestCorrectVariableNameSolution implements Solution
/** @var string */
private $viewFile;
/** @var string|null */
private $suggested;
public function __construct($variableName = null, $viewFile = null, $suggested = null)
{
$this->variableName = $variableName;
@@ -31,8 +34,6 @@ class SuggestCorrectVariableNameSolution implements Solution
public function getSolutionDescription(): string
{
$path = str_replace(base_path().'/', '', $this->viewFile);
return "Did you mean `$$this->suggested`?";
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Facade\Ignition\Solutions;
use Facade\IgnitionContracts\Solution;
class SuggestLivewireMethodNameSolution implements Solution
{
/** @var string */
private $methodName;
/** @var string */
private $componentClass;
/** @var string|null */
private $suggested;
public function __construct($methodName = null, $componentClass = null, $suggested = null)
{
$this->methodName = $methodName;
$this->componentClass = $componentClass;
$this->suggested = $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->suggested}()`?";
}
public function isRunnable(): bool
{
return false;
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Facade\Ignition\Solutions;
use Facade\IgnitionContracts\Solution;
class SuggestLivewirePropertyNameSolution implements Solution
{
/** @var string */
private $variableName;
/** @var string */
private $componentClass;
/** @var string|null */
private $suggested;
public function __construct($variableName = null, $componentClass = null, $suggested = null)
{
$this->variableName = $variableName;
$this->componentClass = $componentClass;
$this->suggested = $suggested;
}
public function getSolutionTitle(): string
{
return "Possible typo {$this->componentClass}::{$this->variableName}";
}
public function getDocumentationLinks(): array
{
return [];
}
public function getSolutionDescription(): string
{
return "Did you mean `$this->suggested`?";
}
public function isRunnable(): bool
{
return false;
}
}

View File

@@ -59,16 +59,18 @@ class ComposerClassMap
foreach ($prefixes as $namespace => $directories) {
foreach ($directories as $directory) {
$files = (new Finder)
->in($directory)
->files()
->name('*.php');
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);
foreach ($files as $file) {
if ($file instanceof SplFileInfo) {
$fqcn = $this->getFullyQualifiedClassNameFromFile($namespace, $file);
$classes[$fqcn] = $file->getRelativePathname();
$classes[$fqcn] = $file->getRelativePathname();
}
}
}
}
@@ -86,17 +88,19 @@ class ComposerClassMap
foreach ($prefixes as $namespace => $directories) {
foreach ($directories as $directory) {
$files = (new Finder)
->in($directory)
->files()
->name('*.php');
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');
foreach ($files as $file) {
if ($file instanceof SplFileInfo) {
$basename = basename($file->getRelativePathname(), '.php');
if ($basename === $missingClass) {
return $namespace.basename($file->getRelativePathname(), '.php');
if ($basename === $missingClass) {
return $namespace.basename($file->getRelativePathname(), '.php');
}
}
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace Facade\Ignition\Support;
class LaravelVersion
{
public static function major()
{
return substr(app()->version(), 0, 1);
}
}

View File

@@ -0,0 +1,97 @@
<?php
namespace Facade\Ignition\Support;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Livewire\LivewireManager;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
class LivewireComponentParser
{
/** @var string */
protected $componentAlias;
/** @var string */
protected $componentClass;
/** @var ReflectionClass */
protected $reflectionClass;
public static function create(string $componentAlias): self
{
return new self($componentAlias);
}
public function __construct(string $componentAlias)
{
$this->componentAlias = $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))
->reject(function (ReflectionProperty $reflectionProperty) {
return $reflectionProperty->class !== $this->reflectionClass->name;
})
->map(function (ReflectionProperty $reflectionProperty) {
return $reflectionProperty->name;
});
$computedProperties = collect($this->reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC))
->reject(function (ReflectionMethod $reflectionMethod) {
return $reflectionMethod->class !== $this->reflectionClass->name;
})
->filter(function (ReflectionMethod $reflectionMethod) {
return str_starts_with($reflectionMethod->name, 'get') && str_ends_with($reflectionMethod->name, 'Property');
})
->map(function (ReflectionMethod $reflectionMethod) {
return 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))
->reject(function (ReflectionMethod $reflectionMethod) {
return $reflectionMethod->class !== $this->reflectionClass->name;
})
->map(function (ReflectionMethod $reflectionMethod) {
return $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();
}
}

View File

@@ -7,7 +7,7 @@ class Packagist
/**
* @param string $className
*
* @return \Facade\Flare\Support\Packagist\Package[]
* @return \Facade\Ignition\Support\Packagist\Package[]
*/
public static function findPackagesForClassName(string $className): array
{

View File

@@ -0,0 +1,57 @@
<?php
namespace Facade\Ignition\Support;
use Facade\FlareClient\Report;
use Illuminate\Support\Arr;
class SentReports
{
/** @var array<int, Report> */
protected $reports = [];
public function add(Report $report): self
{
$this->reports[] = $report;
return $this;
}
public function all(): array
{
return $this->reports;
}
public function uuids(): array
{
return array_map(function (Report $report) {
return $report->trackingUuid();
}, $this->reports);
}
public function urls(): array
{
return array_map(function (string $trackingUuid) {
return "https://flareapp.io/tracked-occurrence/{$trackingUuid}";
}, $this->uuids());
}
public function latestUuid(): ?string
{
if (! $latestReport = Arr::last($this->reports)) {
return null;
}
return $latestReport->trackingUuid();
}
public function latestUrl(): ?string
{
return Arr::last($this->urls());
}
public function clear()
{
$this->reports = [];
}
}

View File

@@ -13,7 +13,7 @@ abstract class Tab implements JsonSerializable
public $styles = [];
/** @var \Facade\Ignition\Facades\Flare */
/** @var \Facade\FlareClient\Flare */
protected $flare;
/** @var Throwable */

View File

@@ -2,40 +2,31 @@
namespace Facade\Ignition\Views\Compilers;
use ErrorException;
use Illuminate\View\Compilers\BladeCompiler;
class BladeSourceMapCompiler extends BladeCompiler
{
public function detectLineNumber(string $filename, int $exceptionLineNumber): int
{
$map = $this->compileString(file_get_contents($filename));
try {
$map = $this->compileString(file_get_contents($filename));
} catch (ErrorException $e) {
return 1;
}
$map = explode("\n", $map);
$line = $map[$exceptionLineNumber - $this->getExceptionLineOffset()] ?? $exceptionLineNumber;
$line = $map[$exceptionLineNumber - 1] ?? $exceptionLineNumber;
$pattern = '/\|---LINE:([0-9]+)---\|/m';
if (preg_match($pattern, $line, $matches)) {
return $matches[1];
if (preg_match($pattern, (string)$line, $matches)) {
return (int)$matches[1];
}
return $exceptionLineNumber;
}
protected function getExceptionLineOffset(): int
{
/*
* Laravel 5.8.0- 5.8.9 added the view name as a comment in the compiled view on a new line.
* That's why the offset to detect the correct line number must be 2 instead of 1.
*/
if (version_compare(app()->version(), '5.8.0', '>=') &&
version_compare(app()->version(), '5.8.9', '<=')
) {
return 2;
}
return 1;
}
public function compileString($value)
{
try {

View File

@@ -12,6 +12,7 @@ use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use ReflectionProperty;
use Throwable;
class CompilerEngine extends \Illuminate\View\Engines\CompilerEngine
{
@@ -22,8 +23,8 @@ class CompilerEngine extends \Illuminate\View\Engines\CompilerEngine
/**
* Get the evaluated contents of the view.
*
* @param string $path
* @param array $data
* @param string $path
* @param array $data
*
* @return string
*/
@@ -39,14 +40,14 @@ class CompilerEngine extends \Illuminate\View\Engines\CompilerEngine
/**
* Handle a view exception.
*
* @param \Exception $baseException
* @param int $obLevel
* @param \Throwable $baseException
* @param int $obLevel
*
* @return void
*
* @throws \Exception
* @throws \Throwable
*/
protected function handleViewException(Exception $baseException, $obLevel)
protected function handleViewException(Throwable $baseException, $obLevel)
{
while (ob_get_level() > $obLevel) {
ob_end_clean();
@@ -58,7 +59,7 @@ class CompilerEngine extends \Illuminate\View\Engines\CompilerEngine
$viewExceptionClass = ViewException::class;
if (in_array(ProvidesSolution::class, class_implements($baseException))) {
if ($baseException instanceof ProvidesSolution) {
$viewExceptionClass = ViewExceptionWithSolution::class;
}
@@ -71,10 +72,11 @@ class CompilerEngine extends \Illuminate\View\Engines\CompilerEngine
$baseException
);
if ($viewExceptionClass === ViewExceptionWithSolution::class) {
if ($baseException instanceof ProvidesSolution) {
$exception->setSolution($baseException->getSolution());
}
$this->modifyViewsInTrace($exception);
$exception->setView($this->getCompiledViewName($baseException->getFile()));

View File

@@ -5,6 +5,7 @@ namespace Facade\Ignition\Views\Engines;
use Exception;
use Facade\Ignition\Exceptions\ViewException;
use Facade\Ignition\Views\Concerns\CollectsViewExceptions;
use Throwable;
class PhpEngine extends \Illuminate\View\Engines\PhpEngine
{
@@ -27,14 +28,14 @@ class PhpEngine extends \Illuminate\View\Engines\PhpEngine
/**
* Handle a view exception.
*
* @param \Exception $baseException
* @param \Throwable $baseException
* @param int $obLevel
*
* @return void
*
* @throws \Exception
* @throws \Throwable
*/
protected function handleViewException(Exception $baseException, $obLevel)
protected function handleViewException(Throwable $baseException, $obLevel)
{
$exception = new ViewException($baseException->getMessage(), 0, 1, $baseException->getFile(), $baseException->getLine(), $baseException);

View File

@@ -1,5 +1,7 @@
<?php
use Facade\FlareClient\Flare;
if (! function_exists('ddd')) {
function ddd()
{
@@ -13,7 +15,7 @@ if (! function_exists('ddd')) {
$handler = app(\Facade\Ignition\ErrorPage\ErrorPageHandler::class);
$client = app()->make('flare.client');
$client = app()->make(Flare::class);
$report = $client->createReportFromMessage('Dump, Die, Debug', 'info');

View File

@@ -1,114 +0,0 @@
module.exports = {
important: true,
theme: {
fontFamily: {
sans: [
'-apple-system',
'BlinkMacSystemFont',
'"Segoe UI"',
'Roboto',
'"Helvetica Neue"',
'Arial',
'"Noto Sans"',
'sans-serif',
'"Apple Color Emoji"',
'"Segoe UI Emoji"',
'"Segoe UI Symbol"',
'"Noto Color Emoji"',
],
mono: [
'SFMono-Regular',
'Menlo',
'Monaco',
'Consolas',
'"Liberation Mono"',
'"Courier New"',
'monospace',
],
},
colors: {
white: 'var(--white)',
blue: {
400: 'var(--blue-400)',
},
green: {
100: 'var(--green-100)',
300: 'var(--green-300)',
500: 'var(--green-500)',
},
purple: {
100: 'var(--purple-100)',
200: 'var(--purple-200)',
300: 'var(--purple-300)',
400: 'var(--purple-400)',
500: 'var(--purple-500)',
600: 'var(--purple-600)',
800: 'var(--purple-800)',
},
red: {
100: 'var(--red-100)',
300: 'var(--red-300)',
400: 'var(--red-400)',
},
yellow: {
100: 'var(--yellow-100)',
200: 'var(--yellow-200)',
300: 'var(--yellow-300)',
400: 'var(--yellow-400)',
},
gray: {
50: 'var(--gray-50)',
100: 'var(--gray-100)',
200: 'var(--gray-200)',
300: 'var(--gray-300)',
400: 'var(--gray-400)',
500: 'var(--gray-500)',
600: 'var(--gray-600)',
700: 'var(--gray-700)',
800: 'var(--gray-800)',
},
tint: {
50: 'var(--tint-50)',
100: 'var(--tint-100)',
200: 'var(--tint-200)',
300: 'var(--tint-300)',
400: 'var(--tint-400)',
500: 'var(--tint-500)',
600: 'var(--tint-600)',
700: 'var(--tint-700)',
},
},
extend: {
borderWidth: {
3: '3px',
},
boxShadow: {
sm: 'var(--shadow-sm)',
default: 'var(--shadow-default)',
lg: 'var(--shadow-default)',
input: 'var(--shadow-default)',
},
inset: {
full: '100%',
},
maxWidth: {
'7xl': '80rem',
},
minHeight: {
6: '1.5rem',
10: '2.5rem',
12: '3rem',
full: '100%',
},
minWidth: {
8: '2rem',
},
zIndex: {
1: '1',
},
leading: {
tight: '1.1',
},
},
},
};

View File

@@ -1,23 +0,0 @@
{
"compilerOptions": {
"allowJs": true,
"allowSyntheticDefaultImports": true,
"lib": ["dom", "es2017"],
"noEmit": true,
"module": "esnext",
"moduleResolution": "node",
"noUnusedLocals": true,
"noUnusedParameters": true,
"preserveConstEnums": true,
"removeComments": false,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"target": "esnext",
"baseUrl": ".",
"paths": {
"*": ["*", "resources/js/*"]
}
},
"include": ["resources/**/*"]
}

View File

@@ -1,50 +0,0 @@
const VueLoaderPlugin = require('vue-loader/lib/plugin');
module.exports = {
entry: {
ignition: './resources/js/app.js',
},
output: {
path: `${__dirname}/resources/compiled`,
publicPath: '/',
filename: '[name].js',
},
module: {
rules: [
{
test: /\.(js|tsx?)$/,
use: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.vue$/,
loader: 'vue-loader',
},
{
test: /\.css$/,
use: [
'style-loader',
{ loader: 'css-loader', options: { url: false } },
'postcss-loader',
],
},
],
},
plugins: [new VueLoaderPlugin()],
resolve: {
extensions: ['.css', '.js', '.ts', '.vue'],
alias: {
vue$: 'vue/dist/vue.esm.js',
},
},
stats: 'minimal',
performance: {
hints: false,
},
};