code quality and tooling
Refactoring Basics
Refactoring means changing the structure of code without changing its behaviour. The code should become easier to read, test, extend, or maintain, but the outside result should stay the same.
This matters in PHP work because many tasks start in existing code. A junior developer must be able to improve code safely without accidentally changing business rules.
Refactoring is not a bug fix
These are different kinds of changes:
- formatting: changing layout
- refactoring: changing structure while preserving behaviour
- bug fixing: changing behaviour to correct a fault
- feature work: adding new behaviour
A pull request can contain more than one kind of change, but mixing them makes review harder.
Start with behaviour
Before refactoring, identify what must stay true.
<?php
declare(strict_types=1);
function orderTotal(array $items): int
{
$total = 0;
foreach ($items as $item) {
if ($item['active']) {
$total += $item['price'] * $item['quantity'];
}
}
return $total;
}
The behaviour is: total only active items, multiplying price by quantity, returning integer pennies.
Extract a named function
If one expression has a clear meaning, extract it into a function.
<?php
declare(strict_types=1);
function lineTotal(array $item): int
{
return $item['price'] * $item['quantity'];
}
function orderTotal(array $items): int
{
$total = 0;
foreach ($items as $item) {
if ($item['active']) {
$total += lineTotal($item);
}
}
return $total;
}
The rule is the same, but the calculation now has a name.
Improve names
Refactoring often improves names so the next reader understands the code faster.
<?php
declare(strict_types=1);
function activeOrderTotal(array $items): int
{
$total = 0;
foreach ($items as $item) {
if ($item['active']) {
$total += lineTotal($item);
}
}
return $total;
}
The new name says that inactive items are ignored.
Use guard clauses to reduce nesting
Guard clauses can make loops easier to read.
<?php
declare(strict_types=1);
function activeOrderTotal(array $items): int
{
$total = 0;
foreach ($items as $item) {
if (! $item['active']) {
continue;
}
$total += lineTotal($item);
}
return $total;
}
The behaviour is still the same. Inactive items are skipped.
Verify before and after
A small test or manual check protects the refactor.
<?php
declare(strict_types=1);
$items = [
['active' => true, 'price' => 499, 'quantity' => 2],
['active' => false, 'price' => 999, 'quantity' => 1],
['active' => true, 'price' => 129, 'quantity' => 3],
];
echo activeOrderTotal($items);
// Prints:
// 1385
If the output changes during refactoring, stop and understand why.
Small steps are safer
Refactor in small steps:
- format the code
- rename one symbol
- extract one function
- run checks
- commit or continue
Do not rewrite a whole file just because one helper is messy. The larger the change, the harder it is to prove behaviour stayed the same.
Red flags
Be careful when refactoring code that touches:
- money
- permissions
- authentication
- SQL queries
- escaping and output safety
- date and time rules
- external APIs
- legacy edge cases that are not documented
Refactoring these areas may still be necessary, but it needs stronger tests and review.
What to remember
Refactoring improves structure while preserving behaviour. Work in small steps, name behaviour clearly, keep tests or examples close, and separate refactoring from unrelated bug fixes when possible.
Before moving on, make sure you can extract a helper function from a small PHP calculation without changing its output.
Practice
Task: Extract A Line Total Helper
Refactor this function without changing its output.
<?php
declare(strict_types=1);
function total(array $items): int
{
$total = 0;
foreach ($items as $item) {
if ($item['active']) {
$total += $item['price'] * $item['quantity'];
}
}
return $total;
}
Requirements
- Extract a
lineTotal()helper. - Rename
total()to make the active-item rule clearer. - Keep integer pennies.
- Keep inactive items excluded.
- Include a small usage example with expected output.
Show solution
<?php
declare(strict_types=1);
function lineTotal(array $item): int
{
return $item['price'] * $item['quantity'];
}
function activeOrderTotal(array $items): int
{
$total = 0;
foreach ($items as $item) {
if (! $item['active']) {
continue;
}
$total += lineTotal($item);
}
return $total;
}
$items = [
['active' => true, 'price' => 499, 'quantity' => 2],
['active' => false, 'price' => 999, 'quantity' => 1],
['active' => true, 'price' => 129, 'quantity' => 3],
];
echo activeOrderTotal($items);
// Prints:
// 1385
The calculation rule is unchanged. The helper names the line calculation, and activeOrderTotal() makes it clear that inactive items are skipped.