Originally published on php-to-js.learnio.dev — part of the PHP to JavaScript course. View this lesson on the original site.

One long function is hard to test; small units are easy to name

A 120-line handler that validates, maps, filters, and formats is familiar in legacy PHP. The refactor is the same in JavaScript: extract pure helpers, keep the orchestrator thin.

PHP habit: before extraction

<?php
function buildInvoiceSummary(array $lines): array {
    $active = [];
    foreach ($lines as $line) {
        if (($line['status'] ?? '') !== 'active') {
            continue;
        }
        $qty = (int) ($line['qty'] ?? 0);
        $price = (float) ($line['price'] ?? 0);
        $active[] = [
            'label' => $line['label'] ?? 'Item',
            'total' => $qty * $price,
        ];
    }
    $sum = array_sum(array_column($active, 'total'));
    return ['lines' => $active, 'grandTotal' => $sum];
}

JavaScript equivalent: after extraction

function isActive(line) {
  return line.status === 'active';
}

function lineTotal(line) {
  const qty = Number(line.qty ?? 0);
  const price = Number(line.price ?? 0);
  return {
    label: line.label ?? 'Item',
    total: qty * price,
  };
}

function buildInvoiceSummary(lines) {
  const active = lines.filter(isActive).map(lineTotal);
  const grandTotal = active.reduce((sum, row) => sum + row.total, 0);
  return { lines: active, grandTotal };
}
Smell in one big function Extract to
Repeated validation isActive, parsePort-style guards
Mapping shape lineTotal, toRow
Final fold reduce or a named sumTotals

Refactor steps that survive review

  1. Name the outputs (what does the caller need?).
  2. Move one loop or branch into a function with a clear name.
  3. Run the sandbox or a quick test — behaviour should match before and after.
  4. Repeat until the top function reads like a table of contents.
flowchart TD
  A[buildInvoiceSummary] --> B[filter isActive]
  B --> C[map lineTotal]
  C --> D[reduce grandTotal]

Failure mode: extracting too early

Three one-liner wrappers with vague names (doStuff, handleData) add noise. Extract when the name explains intent better than the inline block.

Try it

Use the amber Try it panel below. Click Run.

const lines = [
  { label: 'Hosting', qty: 1, price: 20, status: 'active' },
  { label: 'Legacy', qty: 2, price: 5, status: 'archived' },
  { label: 'Support', qty: 3, price: 10, status: 'active' },
];

console.log(buildInvoiceSummary(lines));

Expect two active lines and grandTotal of 50 (20 + 30).

From PHP to JavaScript — Control Flow, Functions, and Scope