7 Ways How PhpStorm Helps to Refactor Code

7 Ways How PhpStorm Helps to Refactor Code
Admin
Saturday, March 25, 2023 6 mins to read
Share
7 Ways How PhpStorm Helps to Refactor Code

PhpStorm is a powerful IDE for PHP development, with many features to write code faster and more efficiently, including refactoring your code. In this tutorial, let's look at some examples, with screenshots.


Example 1: Renaming Variables

When working with variables - it often gets hard to name them (naming is hard right?). It sometimes leads to variables being named in a way that is not very descriptive. So you want to rename them to something more meaningful. PhpStorm has a feature that helps you do that:

In this case, our variable is going to be $q and it's not very descriptive here:

So we want to rename it to something more meaningful. We can do that by right-clicking on the variable name and selecting Refactor > Rename:

This will even give us suggestions:

We can select one of the suggestions or type in our own name. In this case, we are going to rename it to $query:

This has a few benefits:

  1. It suggests a name that is more descriptive given the context it understands
  2. It renames the variable in all places where it is used
  3. You don't have to worry about missing any places where the variable is used

Example 2: Renaming Classes

Renaming classes is a bit more involved than renaming variables. But PhpStorm has a feature that helps you do that as well:

We have a class named UserService:

app/Services/UserService.php

class UserService
{
// ...
}

And we want to rename it to CustomerService as it interacts with customers. Normally we'd have to rename the class in all places it is used (for example, in this case, UsersController). We can do that by right-clicking on the class and selecting Refactor > Rename:

This will give us a dialog where we can type in the new name:

In this dialog we have a few options:

  1. Search for references - this will search for all places where the class is used and rename it there as well
  2. Search in comments and strings - this will search for all places where the class is mentioned in comments and strings and rename it there as well
  3. Rename Class - this will rename the class in all places where it is used

We can select all of these options and click OK:

It did the following actions automatically for us:

  1. Renamed the file
  2. Renamed the class
  3. Replaced old name with a new name in all places where it was used (use imports and in the code itself)

Example 3: Moving Classes

Moving classes is a bit more involved than renaming them. But PhpStorm makes it FAST:

We have a class named UserService and want to move it from app/Services to app/Services/Customers. We can do that by simply creating a new directory called Customers and dragging the file in:

PhpStorm will be smart enough and update the namespace for us:

app/Services/Customers/CustomerService.php

namespace App\Services\Customers;
 
class CustomerService
{
// ...
}

Along with updating the use imports:

It might fail in some cases, but it's usually pretty good at it, and I did not have many issues with it. Maybe once in 10 times, it fails to update the namespace correctly. But it's still a lot faster than doing it manually.


Example 4: Extracting Methods

Extracting methods is a common refactoring task. It's also a very easy to do in PhpStorm:

We have a method that is doing a lot of things:

app/Controllers/Admin/ClientReportsController.php

$query = Transaction::with('project')
->with('transaction_type')
->with('income_source')
->with('currency')
->orderBy('transaction_date', 'desc');
 
if ($request->has('project')) {
$query->where('project_id', $request->project);
}
 
$transactions = $query->get();
 
$entries = [];
foreach ($transactions as $row) {
if ($row->transaction_date != null) {
$date = Carbon::createFromFormat(config('panel.date_format'), $row->transaction_date)->format('Y-m');
if (! isset($entries[$date])) {
$entries[$date] = [];
}
$currency = $row->currency->code;
if (! isset($entries[$date][$currency])) {
$entries[$date][$currency] = [
'income' => 0,
'expenses' => 0,
'fees' => 0,
'total' => 0,
];
}
$income = 0;
$expenses = 0;
$fees = 0;
if ($row->amount > 0) {
$income += $row->amount;
} else {
$expenses += $row->amount;
}
if (! is_null($row->income_source->fee_percent)) {
$fees = $row->amount * ($row->income_source->fee_percent / 100);
}
 
$total = $income + $expenses - $fees;
$entries[$date][$currency]['income'] += $income;
$entries[$date][$currency]['expenses'] += $expenses;
$entries[$date][$currency]['fees'] += $fees;
$entries[$date][$currency]['total'] += $total;
}
}
$projects = Project::pluck('name', 'id')->prepend('--- ' . trans('cruds.clientReport.reports.allProjects') . ' ---', '');
if ($request->has('project')) {
$currentProject = $request->get('project');
} else {
$currentProject = '';
}

It seems like it's doing a lot of things, and it's hard to understand what it's doing. We can extract a few methods from this. Let's start with the big foreach:

We can right-click on the foreach and select Refactor > Extract > Method:

After that's done we will have a new method:

app/Controllers/Admin/ClientReportsController.php

public function index(Request $request)
{
// ...
$entries = $this->transformEntries($transactions);
// ...
 
}
 
/**
* @param \Illuminate\Database\Eloquent\Collection|array $transactions
* @return array
*/
private function transformEntries(\Illuminate\Database\Eloquent\Collection|array $transactions): array
{
$entries = [];
foreach ($transactions as $row) {
if ($row->transaction_date != null) {
$date = Carbon::createFromFormat(config('panel.date_format'), $row->transaction_date)->format('Y-m');
if (!isset($entries[$date])) {
$entries[$date] = [];
}
$currency = $row->currency->code;
if (!isset($entries[$date][$currency])) {
$entries[$date][$currency] = [
'income' => 0,
'expenses' => 0,
'fees' => 0,
'total' => 0,
];
}
$income = 0;
$expenses = 0;
$fees = 0;
if ($row->amount > 0) {
$income += $row->amount;
} else {
$expenses += $row->amount;
}
if (!is_null($row->income_source->fee_percent)) {
$fees = $row->amount * ($row->income_source->fee_percent / 100);
}
 
$total = $income + $expenses - $fees;
$entries[$date][$currency]['income'] += $income;
$entries[$date][$currency]['expenses'] += $expenses;
$entries[$date][$currency]['fees'] += $fees;
$entries[$date][$currency]['total'] += $total;
}
}
return $entries;
}

We can quickly spot that the extracted method still contains the same functionality and PhpStorm even added a doc block, param types, and return type for us. We didn't have to do anything else!

Another example here that is a bit more complex:

Extracting this method will result in:

app/Controllers/Admin/ClientReportsController.php

public function index(Request $request)
{
// ...
[$projects, $currentProject] = $this->getProjectInformationFromRequest($request);
// ...
}
 
/**
* @param Request $request
* @return array
*/
private function getProjectInformationFromRequest(Request $request): array
{
$projects = Project::pluck('name', 'id')->prepend('--- ' . trans('cruds.clientReport.reports.allProjects') . ' ---', '');
if ($request->has('project')) {
$currentProject = $request->get('project');
} else {
$currentProject = '';
}
return [$projects, $currentProject];
}

In this example, you can quickly spot that 2 variables are returned from the new function. Yet PhpStorm still handled it correctly! While it's not ideal to return 2 variables that are not related to each other, it's still a good example of how PhpStorm can handle this.

The final code will look like this:

app/Controllers/Admin/ClientReportsController.php

public function index(Request $request)
{
$query = Transaction::with('project')
->with('transaction_type')
->with('income_source')
->with('currency')
->orderBy('transaction_date', 'desc');
 
if ($request->has('project')) {
$query->where('project_id', $request->project);
}
 
$transactions = $query->get();
 
$entries = $this->transformEntries($transactions);
[$projects, $currentProject] = $this->getProjectInformationFromRequest($request);
 
return view('admin.clientReports.index', compact(
'entries',
'projects',
'currentProject'
));
}
 
/**
* @param Collection|array $transactions
* @return array
*/
private function transformEntries(Collection|array $transactions): array
{
$entries = [];
foreach ($transactions as $row) {
if ($row->transaction_date != null) {
$date = Carbon::createFromFormat(config('panel.date_format'), $row->transaction_date)->format('Y-m');
if (!isset($entries[$date])) {
$entries[$date] = [];
}
$currency = $row->currency->code;
if (!isset($entries[$date][$currency])) {
$entries[$date][$currency] = [
'income' => 0,
'expenses' => 0,
'fees' => 0,
'total' => 0,
];
}
$income = 0;
$expenses = 0;
$fees = 0;
if ($row->amount > 0) {
$income += $row->amount;
} else {
$expenses += $row->amount;
}
if (!is_null($row->income_source->fee_percent)) {
$fees = $row->amount * ($row->income_source->fee_percent / 100);
}
 
$total = $income + $expenses - $fees;
$entries[$date][$currency]['income'] += $income;
$entries[$date][$currency]['expenses'] += $expenses;
$entries[$date][$currency]['fees'] += $fees;
$entries[$date][$currency]['total'] += $total;
}
}
return $entries;
}
 
/**
* @param Request $request
* @return array
*/
private function getProjectInformationFromRequest(Request $request): array
{
$projects = Project::pluck('name', 'id')->prepend('--- ' . trans('cruds.clientReport.reports.allProjects') . ' ---', '');
if ($request->has('project')) {
$currentProject = $request->get('project');
} else {
$currentProject = '';
}
return [$projects, $currentProject];
}

Our index method became much cleaner and easier to read. This also separated the logic into smaller chunks that are easier to maintain.

Example 5: Refactoring if Statements

We write a lot of if statements in our code. Sometimes we can refactor them to make them more readable and easier to maintain. Let's take a look at this example:

$document = Document::find($id);
 
if ($document) {
if ($document->document_file) {
$documentFile = $document->document_file->getUrl();
}
}

At first glance, it seems like a simple example. But if we look closer we can see that we have 2 nested if statements. This is a code smell, and we should refactor it. We can do this by extracting the nested if statement into a new method:

Once an if statement is marked as an error - you can click Alt + Enter and select Extract 'if' statement:

Once the action is complete our code will look like this:

if ($document && $document->document_file) {
$documentFile = $document->document_file->getUrl();
}

While this was a simple example - PhpStorm will warn you about complex ones too! And it will refactor it to be correct for you.


Example 6: Refactor Inline Variable

Sometimes we have variables that are only used once in our code. We can refactor them to be inline variables. Let's take a look at this example:

public function retrieveCustomerData()
{
$data = Client::query()
->where('country', 'US')
->where('status_id', 2)
->whereNotNull('company')
->get();
 
return $data;
}

In this example, we can see that we have a variable $data that is only used once. We can refactor it to be an inline variable:

This will result in our code looking like this:

public function retrieveCustomerData()
{
return Client::query()
->where('country', 'US')
->where('status_id', 2)
->whereNotNull('company')
->get();
}

It simply suggested removing a variable that is not needed and instead returning the value directly via one line.


Example 7: Introduce New Parameter to a Function

Since requirements change all the time - we sometimes need to add new parameters to our functions. PhpStorm can add that for us:

public function retrieveCustomerData()
{
return Client::query()
->where('country', 'US')
->where('status_id', 2)
->whereNotNull('company')
->get();
}

We can quickly spot that we have 2 hard-coded values in our code - country and status_id. We can refactor them to be parameters:

This will result in our code looking like this:

public function retrieveCustomerData($country, $statusId)
{
return Client::query()
->where('country', $country)
->where('status_id', $statusId)
->whereNotNull('company')
->get();
}

It's that simple! We can now pass the values to our function, and it will work as expected. Sadly, it didn't copy hard-coded values as defaults


Conclusion

PhpStorm is a powerful tool that can automate a lot of your daily tasks. It helps with file management, spots mistakes, and even suggests you refactor things for cleaner code.