Automatically test your laravel application
imanghafoori/laravel-microscope is a Laravel package for automatically test your laravel application.
It currently has 1.493 GitHub stars and 632.613 downloads on Packagist (latest version v1.0.437).
Install it with composer require imanghafoori/laravel-microscope.
Discover more Laravel packages by imanghafoori
or browse all Laravel packages to compare alternatives.
Last updated
Table Of Contents
early returns automatically.If you found this package useful, and you want to encourage the maintainer to work on it, just press the star button to declare your willingness.
You can install the package via Composer:
composer require imanghafoori/laravel-microscope --dev
You may also publish config file:
php artisan vendor:publish --provider="Imanghafoori\LaravelMicroscope\LaravelMicroscopeServiceProvider"
You can run :point_down:
|#|Artisan Command|
|---|---|
|1|php artisan search_replace|
|2|php artisan check:early_returns|
|3|php artisan check:all|
| # | Artisan Command |
|----|--------------------------------------------|
| 1 | php artisan check:views |
| 2 | php artisan check:routes |
| 3 | php artisan check:psr4 {-s\|--nofix} |
| 4 | php artisan check:imports {-s\|--nofix} |
| 5 | php artisan check:stringy_classes |
| 6 | php artisan check:dd |
| 7 | php artisan check:bad_practices |
| 8 | php artisan check:compact |
| 9 | php artisan check:blade_queries |
| 10 | php artisan check:action_comments |
| 11 | php artisan check:extract_blades |
| 12 | php artisan check:abort_if |
| 13 | php artisan list:models |
| 14 | php artisan check:endif |
| 16 | php artisan check:gates |
| 17 | php artisan check:dynamic_where |
| 18 | php artisan check:aliases |
| 19 | php artisan check:dead_controllers |
| 20 | php artisan check:generic_docblocks |
| 21 | php artisan enforce:helper_functions |
| 22 | php artisan enforce:imports |
| 23 | php artisan check:fqcn |
Let's start with the:
php artisan search_replace {--name=pattern_name} {--tag=some_tag} {--file=partial_file_name} {--folder=partial_folder_name} {--except-folder=} {--except-file=}This is a smart and very powerful search/replace functionality that can be a real "time saver" for you.
If you run the command
artisan search_replacefor the first time, it will create asearch_replace.phpfile in the project's root. Then, you can define your patterns within that file.
Examples:
Let's define a pattern to replace the optional() global helper with the ?-> PHP 8 null-safe operator:
return [
'optional_to_nullsafe' => [
'search' => '"<global_func_call:optional>"("<in_between>")->',
'replace' => '"<2>"?->',
// 'tag' => 'php8,refactor',
// 'predicate' => function($matches, $tokens) {...},
// 'mutator' => function($matches) {...},
// 'post_replace' => [...],
// 'avoid_result_in' => [...],
// 'avoid_syntax_errors' => false,
// 'filters' => [...],
]
];
optional_to_nullsafe is the "unique name" of your pattern. (You can target your pattern by running php artisan search_replace --name=optional_to_nullsafe)"<in_between>" placeholder which captures everything in between the pair of parentheses.replace block, we substitute what we have captured by the first placeholder with the "<1>".
If we have more placeholders, we could have had "<2>" etc.--tag flag: php artisan search_replace --tag=php8Here is a comprehensive list of placeholders you can use:
|#|Placeholders|Description|
|---|---|---|
|1|<var> or <variable>|for variables like: $user|
|2|<str> or <string>|for hard coded strings: 'hello' or "hello"|
|3|<class_ref>|for class references: \App\User::where(... , User::where|
|4|<full_class_ref>|only for full references: \App\User::|
|5|<until>|to capture all the code until you reach a certain character.|
|6|<comment>|for comments (it does not capture doc-blocks beginning with: /** )|
|7|<doc_block>|for php doc-blocks|
|8|<statement>|to capture a whole php statement.|
|9|<name:nam1,nam2> or <name>|for method or function names. ->where or ::where|
|10|<white_space>|for whitespace blocks|
|11|<bool> or <boolean>|for true or false (acts case-insensitive)|
|12|<number>|for numeric values|
|13|<cast>|for type-casts like: (array) $a;|
|14|<int> or "<integer>"|for integer values|
|15|<visibility>|for public, protected, private|
|16|<float>|for floating point number|
|17|"<global_func_call:func1,func2>"|to detect global function calls|
|18|<in_between>|to capture code within a pair of {...} or (...) or [...]|
|19|<any>|captures any token.|
You can also define your own keywords if needed!
You just define a class for your new keyword and append the classpath to the end of the
Finder::$keywords[] = MyKeyword::classproperty. Just like the default keywords.
Example:
:one: Let's say you want to find only the "comments" that contain the word "todo:" in them.
'todo_comments' => [
'search' => '<comment>',
'predicate' => function($matches) { // <==== here we check comment has "todo:"
$comment = $matches[0]; // first placeholder value
$content = $comment[1]; // get its content
return Str::contains($content, 'todo:') ? true : false;
},
]
Note If you do not mention the 'replace' key, it only searches and reports them to you.
:two: Ok, now let's say you want to remove the "todo:" word from your comments:
'remove_todo_comments' => [
'search' => '<comment>', // <=== we capture any comment
'replace' => '<1>',
'predicate' => function($matches) {
$comment = $matches[0]; // first matched placeholder
$content = $comment[1];
return Str::contains($content, 'todo:') ? true : false;
},
'mutator' => function ($matches) { // <=== here we remove "todo:"
$matches[0][1] = str_replace('todo:', '', $matches[0][1]);
return $matches;
}
]
Converts: // todo: refactor code
Into: // refactor code
:three: Mutator:
In mutators, you are free to manipulate the $matched values as much as you need to before replacing them in the results.
You can also mention a static method instead of a function, like this: [MyClass::class, 'myStaticMethod']
:three: Let's say you want to put the optional comma for the Let's elements in the arrays if they are missing.
'enforce_optional_comma' => [
'search' => '<white_space>?]',
'replace' => ',"<1>"]',
'avoid_syntax_errors' => true,
'avoid_result_in' => [
',,]',
'[,]',
'<var>[,]'
],
]
In this case, our pattern is not very accurate and in some cases, it may result in syntax errors.
Because of that, we turn on the PHP syntax validator to check the result, but that costs us a performance penalty!!!
To exclude the usage of PHP, to validate the results we have mentioned the avoid_result_in so that if they happen in the result, it skips.
? in the "<white_space>?" notes this is an optional placeholder.If you are curious to see a better pattern that does not need any syntax checking, try this:
'enforce_optional_comma' => [
'search' => '<1:any><2:white_space>?[<3:until_match>]',
'replace' => '<1><2>[<3>,]',
'avoid_result_in' => [
',,]',
'[,]'
],
'predicate' => function ($matches) {
$type = $matches['values'][0][0];
return $type !== T_VARIABLE && $type !== ']';
},
'post_replace' => [
'<1:white_space>,]' => ',<1>]'
]
],
This is more complex but works much faster. (since it does not need the php syntax validator)
Here 'post_replace' is a pattern that is applied only and only on the resulting code to refine it, and NOT on the entire file.
You can optionally comment your placeholders (as above <1:any>) with numbers so that you know which one corresponds to which when replaced.
Currently, the microscope offers only two built-in filters: is_sub_class_of and in_array
Can you guess what the heck this pattern is doing?!
'mention_query' => [
'search' => '<1:class_ref>::<2:name>'
'replace' => '<1>::query()-><2>',
'filters' => [
1 => [
'is_sub_class_of' => \Illuminate\Database\Eloquent\Model::class
],
2 => [
'in_array' => 'where,count,find,findOrFail,findOrNew'
]
]
]
It converts these:
User::where(...)->get();
\App\Models\User::find(...);
Into these:
User::query()->where(...)->get();
\App\Models\User::query()->find(...);
So it does not tamper with something like this:
User::all(); // The `all` method can not be preceded by `query`
UserRepo::where(...); /// UserRepo is not a model
:five: Capturing php "statements":
Let's say we want to opt into PHP v7.4 arrow functions:
'fn' => [
'search' => 'function (<in_between>)<until>{ return <statement>; }',
'replace' => 'fn (<1>) => <3>',
'tags' => 'php74,refactor',
]
In this example, we have mentioned one single "statement" in the body of the function. So if it encounters a function with two or more statements, it will ignore that.
$closure = function ($a) use ($b) {
return $a + $b;
};
// will become:
$closure = fn ($a) => $a + $hello;
But this is not captured:
$closure = function ($a) {
$a++;
return $a + $b;
};
:six: Difference between <statement> and <until>;
They seem to be very similar but there is an important case in which you can not use <until>; to cover it properly!
$first = $a + $b;
$second = function ($a) {
$a++;
return $a;
};
If we define our pattern like this:
return [
'pattern_name' => [
'search' => '<var> = <until>;',
]
];
For $c = $a + $b; they act the same way, but for the second one "<until>"; will not capture the whole closure and will stop as soon as it reaches $a++; and that is a problem.
But if you define your pattern as: '<var> = <statement>' it would be smart enough to capture the correct semicolon at the end of the closure definition and the whole close would be captured.
:seven: Capturing global function calls:
Let's say you want to eliminate all the dd(...) or dump(...) before pushing to production.
return [
'remove_dd' => [
'search' => "'<global_func_call:dd,dump>'('<in_between>');",
'replace' => ''
]
];
This will NOT capture cases like below:
$this-> dd('hello'); // is technically a method call
User:: dd('I am static'); // is technically a static method call
new dd('I am a class'); // here "dd" is the name of a class.
But will detect and remove real global dd() calls with whatever parameters they have received.
dd( // <=== will be detected, even if the pattern above is written all in one line.
auth('admin')
->user()->id
);
\dd(1);
dd(1);
dump(1);
Let's say we want to refactor:
User:where('name', 'John')->where('family', 'Dou')->where('age', 20)->get();
into:
User:where([
'name' => 'John',
'family' => 'Dou',
'age'=> 20,
])->get();
Ok, how would the pattern look like then?!
"group_wheres" => [
'search' => '<1:class_ref>::where('<2:str>', '<3:str>')'<repeating:wheres>'->get();'
'replace' => '<1>::where([
<2> => <3>,
"<repeating:1:key_values>"])->get();',
'named_patterns' => [
'wheres' => '->where(<str>, <str>)<white_space>?',
'key_values' => '<1> => <2>,<3>',
]
]
Nice yeah??!
Possibilities are endless and the sky is the limit...
php artisan check:early_returnsThis will scan all your Psr-4 loaded classes and flattens your functions and loops by applying the early return rule. For example:
<?php
foreach ($products as $product) {
if ($someCond) {
// A lot of code 1
// A lot of code 1
// A lot of code 1
// A lot of code 1
// A lot of code 1
if ($someOtherCond) {
// A lot more code 2
// A lot more code 2
// A lot more code 2
// A lot more code 2
// A lot more code 2
//
} // <--- closes second if
} // <--- closes first if
}
Will be discovered and converted into:
<?php
foreach ($products as $product) {
if (! $someCond) {
continue;
}
// A lot of code 1
// A lot of code 1
// A lot of code 1
// A lot of code 1
// A lot of code 1
if (! $someOtherCond) {
continue;
}
// A lot more code 2
// A lot more code 2
// A lot more code 2
// A lot more code 2
// A lot more code 2
}
The same thing will apply for functions and methods, but with return
<?php
if ($cond1) {
if ($cond2) {
....
}
}
// we get merged into:
if ($cond1 && $cond2) {
...
}
<?php
if ($var1 > 1):
if ($var2 > 2):
echo 'Hey Man';
endif;
endif;
// Or if you avoid putting curly braces...
if ($var1 > 1)
if ($var2 > 2)
echo 'Hey Man';
Although this type of refactoring is safe and is guaranteed to do the same thing as before, be careful to commit everything before trying this feature, in case of a weird bug or something.
php artisan check:psr4 {--folder=}php artisan check:imports {--file=} {--folder=} {--except-folder=} {--except-file=}use statements) to be valid and reports invalid ones.use Request; would be valid.php artisan check:bad_practicesenv() calls outside the config files.php artisan check:routesroute(), redirect()->route(), \Redirect::route() to refer to valid routes.dead controllers are detected.php artisan check:compactcompact() calls and reports to you which parameters should be removed.php artisan check:blade_queriesEloquent models and DB query builder and shows them if any.php artisan check:extract_blades@include('myPartials.someFile')You can use {!! extractBlade('myPartials.someFile') !!} in your blade files to indicate start/end line and the path/name of the partial you intend to be made.
<html>
{!! extractBlade('myPartials.head') !!}
<head>...</head>
{!! extractBlade() !!}
{!! extractBlade('myPartials.body') !!}
<body>...</body>
{!! extractBlade() !!}
</html>
After you execute php artisan check:extract_blades it will become:
<html>
@include('myPartials.head')
@include('myPartials.body')
</html>
Also, it will create:
resources/views/myPartials/head.blade.phpresources/views/myPartials/body.blade.phpAnd put the corresponding content in them.
'MyMod::myPartials.body'php artisan check:action_comments {--file=SomeFile.php}--file= option to narrow down the scanned files.php artisan check:viewsview() and View::make() and reports if they refer to the wrong files.@include() and @extends() and reports if they refer to the wrong files.Also, it can detect unused variables which are passed into your view from the controller like this: view('hello', [...]);
For that you must open up the page in the browser and then visit the log file to see a message like this:
local.INFO: Laravel Microscope: The view file: welcome.index-1 at App\Http\Controllers\HomeController@index has some unused variables passed to it:
local.INFO: array ('$var1' , '$var2');
Remember some variables are passed into your view from a view composer and not the controller.
Those variables are also taken into consideration when detecting unused variables.
php artisan enforce:importsThis command refactors your code by importing fully qualified class namespaces at the top as use statements.
You may use --class=MyCls1,MyCls2 flag to limit the imported class names.
php artisan check:abort_ifThis command refactors your code from:
<?php
if ($some === 'Condition') {
abort(404, 'optional message.');
}
into this:
<?php
abort_if(404, 'optional message.');
php artisan check:gatesIt checks the validity of all the gates you have defined, making sure that they refer to a valid class and method.
It also checks for the policy definitions to be valid.
Gate::policy(User::class, 'UserPolicy@someMethod');
Gate::define('someAbility', 'UserGate@someMethod');
1 - It checks the User classpath to be valid.
2 - It checks the UserPolicy classpath to be valid.
3 - It checks the someMethod method to exist.
php artisan check:dynamic_wherewhereFamilyName('...') with where('family_name', '...').php artisan enforce:queryIt calls the static query method on your eloquent query chains so that IDEs can understand eloquent.
For example, converts: User::where(... to User::query()->where(...
php artisan check:dead_controllersphp artisan check:generic_docblocks {--folder=app/Models} {--file=SomeFile.php}--folder= or --file=, --except-file, --except-folder option to narrow down the scanned folders.php artisan enforce:helper_functions {--folder=app/Models} {--file=SomeFile.php}--folder=, --file=, --except-file, --except-folder option to narrow down the scanned folders.php artisan list:models {--folder=app/Models}--folder= option to narrow down the scanned folders.php artisan check:extra_fqcn {--folder=app/Models} --fix {--class=ClassRefsToBeFixed}--class= option to only fix references to a certain class.And more features will be added soon. ;)
The MIT License (MIT). Please see License File for more information.
If you find an issue or have a better way to do something, feel free to open an issue or a pull request. If you use laravel-microscope in your open source project, create a pull request to provide its URL as a sample application in the README.md file.
If you discover any security-related issues, please email [email protected] instead of using the issue tracker.
:gem: It allows us to write expressive code to authorize, validate, and authenticate.
A man will never fail unless he stops trying.
Albert Einstein
This project exists thanks to all the people who contribute. [Contributors].