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
You can run:
Also You will have access to some global helper functions:
In case you wonder what are the listeners and where are they?!
You can use this (0_o) microscope_dd_listeners(MyEvent::class);
This call, also can be in boot
or register
as well.
And it works like a normal dd(...);
meaning that it will halt.
Lets start with:
php artisan search_replace {--name=pattern_name} {--tag=some_tag}
This is a smart and very powerful search/replace functionality which can be a real "time saver" for you.
If you run the command artisan search_replace
for the first time, it will create a search_replace.php
file in the project's root.
Then, you can define your patterns, within that file.
Lets 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 parentesis.replace
block we substitute what we have captured by the first placeholder by the "<1>"
. If we have more placeholders, we could have had "<2>"
and etc.php artisan search_replace --tag=php8
Here is a copmerehensive list of placeholders you can use:
"<var>"
or "<variable>"
: for variables like: $user
"<str>"
or "<string>"
: for hard coded strings: 'hello'
or "hello""<class_ref>"
: for class references: \App\User::where(...
, User::where
"<full_class_ref>"
: only for full references: \App\User::
"<until>"
: to capture all the code until you reach a certain character."<comment>"
: for comments (it does not capture doc-blocks beginning with: /** )"<doc_block>"
: for php doc-blocks"<statement>"
: to capture a whole php statement."<name:nam1,nam2>"
or "<name>"
: for method or function names. ->where
or ::where
"<white_space>"
: for whitespace blocks"<bool>"
or '<boolean>'
: for true or false (acts case-insensetive)"<number>"
: for numeric values"<cast>"
: for type-casts like: (array) $a;
"<int>"
or "<integer>"
: for integer values"<visibility>"
: for public, protected, private"<float>"
: for floating point number"<global_func_call:func1,func2>"
: to detect global function calls."<in_between>"
: to capture code within a pair of {...}
or (...)
or [...]
"<any>"
: captures any token.You just define a class for your new keyword and append the class path to the end of Finder::$keywords[] = MyKeyword::class
property.
Just like the default keywords.
1 - Lets say you want to find only the "comments" which contain the word "todo:" in them.
'todo_comments' => [
'search' => '"<comment>"',
'predicate' => function($matches) { // <==== here we check comment has "todo:"
$comment = $matches[0]; // first placehoder value
$content = $comment[1]; // get its content
return Str::containts($content, 'todo:') ? true : false;
},
]
Note: If you do not mention the 'replace'
key it only searches and reports them to you.
2 - Ok, now lets 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 placehoder
$content = $comment[1];
return Str::containts($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
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']
3 - Lets say you want to put the optional comma for the last 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 php syntax validator to check the end result, but that costs us a performance penalty!!!
In order to exclude the usage of php, to validate the end results we have mentioned the avoid_result_in
so that if they happen in the end result it skips.
?
in the "optional
placeholder.If you are curious to see a better pattern which 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 which 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 preceeded with `query`
UserRepo::where(...); /// UserRepo is not a model
Lets say we want to opt-into php 7.4 arrow functions:
'fn' => [
'search' => 'function ("<in_between>")"<until>"{ "<statement>" }',
'replace' => 'fn ("<1>") => "<3>"',
'tags' => 'php74,refactor',
'mutator' => function ($matches) {
$matches[2][1] = str_replace(['return ', ';'], '', $matches[2][1]);
return $matches;
}
]
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;
};
"<statement>"
and "<until>";
They seem to be very similar but there is an important case which you can not use "<until>";
in order to cover it properly!
$first = $a + $b;
$second = function ($a) {
$a++;
return $a;
};
If we define our pattern like this:
return [
'staty' => [
'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 semi-colon at the end of closure definition and whole close would be captured.
Lets 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 classs'); // here "dd" is the name of a class.
But will detect and remove real global dd()
calls with whatever parameters they have recieved.
dd( // <=== will be detected, even the pattern above is written all in one line.
auth('admin')
->user()->id
);
\dd(1);
dd(1);
dump(1);
Lets 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 the pattern would 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??!
php artisan check:early_returns
This 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) {
....
}
}
// merge 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 totally safe and is guaranteed to do the same thing as before, but be careful to commit everything before trying this feature, in case of a weird bug or something.
php artisan check:psr4
php artisan check:generate
You make an empty file, we fill it, based on naming conventions.
If you create an empty .php
file which ends with ServiceProvider.php
after running this command:
1 - It will be filled with a boilerplate and correct Psr-4 namespace.
2 - It will be appended to the providers
array in the config/app.php
php artisan check:imports
use
statements) to be valid and reports invalid ones.use Request;
would be valid.php artisan check:bad_practices
env()
calls outside of the config files.php artisan check:routes
route()
, redirect()->route()
, \Redirect::route()
to refer to valid routes.dead controllers
are detected.php artisan check:compact
compact()
calls and reports to you, which parameters should be removed.php artisan check:blade_queries
Eloquent 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.php
resources/views/myPartials/body.blade.php
and put the corresponding content in them.
'MyMod::myPartials.body'
php artisan check:action_comments
php artisan pp:route
microscope_pretty_print_route('my.route.name');
php artisan check:views
view()
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 check:events
For example consider:
Event::listen(MyEvent::class, '\App\Listeners\MyListener@myMethod');
1 - It checks the \App\Listeners\MyListener
classpath to be valid.
2 - It checks the myMethod
to exist on the MyListener
class
3 - It checks the myMethod
to have the right type-hint (if any) in its signature, for example:
public function myMethod(OtherEvent $e) // <---- notice type-hint here
{
//
}
This is a valid but wrong type-hint, and will be reported to you. Very cool, isn't it ??!
1- in the EventServiceProvider
,
2- By Event::listen
facade,
3- By Subscriber class... or any other way. The error would be found. :)
php artisan check:gates
It 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
to exist.
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 imanghafoori1@gmail.com
instead of using the issue tracker.
💎 It allows us to write expressive code to authorize, validate and authenticate.
💎 A minimal yet powerful package to give you the opportunity to refactor your controllers.
💎 It allows you to login with any password in the local environment only.
💎 It allows you to decouple your eloquent models to reach a modular structure
return abort();
A man will never fail unless he stops trying.
Albert einstein
A new experience in Laravel LMS followed by gamification, onboarding, marketing and course management
Online payment component for Laravel 5+ which supports all Iranian payment gateways
Spatial Map Fields for Laravel Nova
RTL layout for Laravel Nova.
Start Point For Creating Apps with Laravel (it will update to latest Laravel version)