Back to blog

Tooling

PHP Code Review Checklist: 50 Things Senior Developers Always Check

A comprehensive, annotated code review checklist covering naming, error handling, security, performance, and testability issues.

  • PHP
  • Code Review
  • Tooling
  • Security
  • Testing

Reader map

Key points in PHP Code Review Checklist: 50 Things Senior Developers Always Check

Syntax first, runtime behavior second, migration cleanup last.

Read
19 min
Waypoints
5
Track
Tooling
  1. 01
    Start here

    PSR-12 formatting that PHP-CS-Fixer or PHPCS can enforce.

  2. 02
    Waypoint

    Import sorting that the IDE can fix.

  3. 03
    Waypoint

    Obvious static analysis errors already caught by PHPStan or Psalm.

  4. 04
    Waypoint

    Personal naming preferences without a concrete readability problem.

  5. 05
    Migration check

    Large redesign demands on a small bugfix unless the current design makes the fix unsafe.

SEO Metadata

SEO Title Options

  1. PHP Code Review Checklist: 50 Things Senior Developers
  2. PHP Code Review Checklist: 50 Things: Practical 2026 Guide
  3. Tooling Playbook: PHP Code Review Checklist: 50 Things

Meta Description Options

  1. Learn PHP Code Review Checklist: 50 Things Senior Developers Always Check with a practical Tooling framework, expert mistakes, implementation steps, examples.
  2. A comprehensive, annotated code review checklist covering naming, error handling, security, performance, and testability issues.

URL Slug

php-code-review-checklist-50-things-senior-developers-always-check

Focus Keyword

PHP Code Review Checklist: 50 Things Senior Developers Always Check

Additional LSI Keywords

  • Tooling
  • PHP
  • Code Review
  • Security
  • Testing
  • PHP Code Review Checklist: 50 Things Senior Developers Always Check
  • production checklist
  • implementation guide
  • best practices
  • architecture decisions
  • testing strategy
  • performance impact

Table of Contents

Article overview

PHP Code Review Checklist: 50 Things Senior Developers Always Check is the kind of topic that looks simple until it reaches production. Teams usually discover the real cost late: unclear boundaries, weak defaults, hidden maintenance work, and decisions that seemed harmless when the codebase was small.

The problem gets worse when the article, tutorial, or implementation guide only explains the happy path. This guide closes that gap with a practical framework, a comparison table, common mistakes, and a deep technical section you can use while planning real work.

Keep reading for the non-obvious part: the safest implementation is rarely the most impressive-looking one. It is the one your team can debug, test, document, and evolve without turning every future change into archaeology.

Key Takeaways

  • PHP Code Review Checklist: 50 Things Senior Developers Always Check should be evaluated as a production decision, not only as a syntax or tooling choice.
  • The best implementation keeps responsibilities visible, with clear ownership, tests, documentation, and rollback paths.
  • Search visibility improves when practical depth, structured answers, and expert examples live on the same page.

[IMAGE: A mobile-first technical article layout showing the main concept, decision table, implementation checklist, and FAQ blocks. Alt: PHP Code Review Checklist: 50 Things Senior Developers Always Check expert guide for Tooling]

What PHP Code Review Checklist: 50 Things Senior Developers Always Check means

PHP Code Review Checklist: 50 Things Senior Developers Always Check means applying tooling knowledge to a concrete engineering decision, then turning that decision into reliable code, documentation, and operational behavior. In practice, it combines the topic's core concepts with trade-off analysis, implementation boundaries, testing strategy, and maintenance discipline.

This is the definition worth optimizing for featured snippets because it avoids hype. It tells the reader what the topic does and what a professional implementation must include.

Why it matters now

The technical web is more crowded than it was a few years ago. Thin tutorials can still get indexed, but they rarely earn trust from senior developers, buyers, AI answer systems, or teams that need production guidance.

For tooling topics, the strongest content now has three layers:

  • a clear answer for fast scanning
  • a practical framework for implementation
  • expert context that explains what breaks later

That same structure helps search engines understand the page. It also helps readers decide whether the advice fits their project.

Implementation framework

Use this framework before adopting the approach described in this article.

  1. Define the user problem and the production risk.
  2. Identify the smallest reliable implementation boundary.
  3. Keep configuration, secrets, and environment-specific behavior outside the article's core logic.
  4. Add tests for the behavior that would hurt if it regressed.
  5. Document the trade-off, not only the final code.
  6. Measure the result with logs, metrics, or user-facing outcomes.
  7. Revisit the decision after real usage exposes edge cases.

The sequence is deliberately conservative. It keeps the work grounded in outcomes instead of novelty.

[IMAGE: A seven-step implementation framework with discovery, boundary design, configuration, tests, documentation, measurement, and iteration. Alt: PHP Code Review Checklist: 50 Things Senior Developers Always Check implementation framework]

Practical comparison

Decision areaStrong approachWeak approachWhy it matters
ScopeSolve one clear problemMix unrelated concernsFocus improves testing and search intent
ArchitecturePut logic in explicit classes or documented boundariesHide behavior in templates or incidental callbacksFuture changes stay easier to review
Data flowPass prepared data into the view or endpointQuery or compute in presentation codeReduces regressions and performance surprises
TestingCover the risky behavior directlyTest only the happy pathCatches production failures earlier
DocumentationExplain trade-offs and limitsRepeat generic definitionsBuilds E-E-A-T and reader trust
OperationsTrack logs, metrics, and rollback stepsShip without measurementMakes the decision reversible

This table is intentionally practical. It gives a reviewer something to check before the implementation becomes expensive to change.

Expert workflow

Expert tip: "Treat PHP Code Review Checklist: 50 Things Senior Developers Always Check as a system boundary. If the next developer cannot find where the decision lives, how it is tested, and when it should be avoided, the implementation is not finished."

A useful workflow is simple:

  • Start with the smallest working example.
  • Add the constraints that exist in your real project.
  • Remove anything that only demonstrates cleverness.
  • Write down the failure modes.
  • Add links to related decisions so future readers can navigate the topic cluster.

That last point matters for both humans and search systems. A single article can answer a question; a cluster proves authority.

Common mistakes

Mistake 1: Copying a pattern without its context

A pattern that works in a small demo can fail in a real application. The missing context is usually data volume, team experience, deployment process, security requirements, or observability.

Before copying the pattern, ask what assumption made it safe in the original example.

Mistake 2: Putting business logic in the wrong layer

This is the fastest way to make future debugging expensive. In Laravel, PHP, and server-rendered websites, presentation should receive prepared data, not discover rules on its own.

Keep decision logic in models, actions, services, policies, requests, jobs, or documented helpers where it can be tested directly.

Mistake 3: Optimizing for novelty instead of maintainability

Newer tools and language features can be valuable. They can also hide simple behavior behind unfamiliar syntax.

Use the option that makes the next production incident easier to understand.

Mistake 4: Publishing without a measurement plan

If the article describes a performance, SEO, security, or architecture improvement, define how success will be checked. Logs, tests, crawl diagnostics, analytics, and user behavior are all stronger than assumptions.

[IMAGE: A common-mistakes board with context loss, wrong layer, novelty bias, and missing measurement highlighted. Alt: PHP Code Review Checklist: 50 Things Senior Developers Always Check common mistakes]

Image placeholders

  • [IMAGE: A concept diagram for PHP Code Review Checklist: 50 Things Senior Developers Always Check with input, decision boundary, implementation, tests, and production feedback. Alt: PHP Code Review Checklist: 50 Things Senior Developers Always Check concept diagram]
  • [IMAGE: A mobile screenshot-style checklist for PHP Code Review Checklist: 50 Things Senior Developers Always Check. Alt: PHP Code Review Checklist: 50 Things Senior Developers Always Check mobile checklist]
  • [IMAGE: A comparison table visualization for strong versus weak implementation choices. Alt: PHP Code Review Checklist: 50 Things Senior Developers Always Check comparison table]

Video placeholder

[VIDEO: Insert a 5-8 minute YouTube walkthrough that demonstrates the main decision, the implementation boundary, the test strategy, and the production caveats for PHP Code Review Checklist: 50 Things Senior Developers Always Check.]

Internal linking opportunities

Original Technical Deep Dive

Code review is not a style argument.

Good review protects behavior, security, data integrity, performance, operability, and future maintenance. Formatting should already be handled by tools. Human reviewers should spend their attention on risk.

This checklist was reviewed on May 7, 2026 against PSR-12, OWASP Code Review Guide, OWASP Secure Coding Practices, PHPStan rule levels, Psalm security analysis, PHPUnit documentation, Composer documentation, and PHP manual security APIs.

The short version

Use code review to answer five questions:

QuestionReviewer focus
Is the behavior correct?Requirements, edge cases, backwards compatibility
Is it safe?Input, output, authorization, secrets, data exposure
Will it survive production?Errors, retries, logs, transactions, deploy path
Will it scale enough?Queries, memory, loops, external calls, cache invalidation
Can the team maintain it?Names, boundaries, tests, types, static analysis

Do not waste review time on:

  • PSR-12 formatting that PHP-CS-Fixer or PHPCS can enforce.
  • Import sorting that the IDE can fix.
  • Obvious static analysis errors already caught by PHPStan or Psalm.
  • Personal naming preferences without a concrete readability problem.
  • Large redesign demands on a small bugfix unless the current design makes the fix unsafe.

Senior review is not harsher. It is more focused.

Review setup

Before reviewing the code, check the pull request shape:

Can I understand the intent, risk, and verification path in five minutes?

If not, the first review comment should be about missing context, not about line 437.

Good PR description:

## Why
Invoices can currently be paid twice if two webhook deliveries arrive within the same second.

## What changed
- Added idempotency key storage for payment events.
- Wrapped invoice payment transition in a transaction.
- Added tests for duplicate webhook delivery.

## Verification
- composer test
- vendor/bin/phpstan analyse
- Manual Stripe webhook replay in staging

Bad PR description:

Fix payment bug.

Now the checklist.

Intent and scope

01The change has a clear reason

Look for a concrete bug, feature, migration, security fix, or maintenance goal.

Weak signal:

Refactor service.

Better:

Extract payment gateway adapter so failed webhook retries can be tested without Stripe.

Review question:

What user, operator, or developer problem does this change solve?

02The diff is scoped to the stated problem

Unrelated formatting, renamed variables, route changes, dependency updates, and feature work in one PR increase review risk.

Ask for a split when:

  • behavior change and broad refactor are mixed,
  • dependency upgrade and feature work are mixed,
  • database migration and UI redesign are mixed,
  • security fix is buried in cleanup.

Small PRs are not automatically good. Focused PRs are.

03The behavior change is visible

The diff should make the behavior easy to identify.

Good signs:

  • Tests describe the new behavior.
  • Controller or use case names expose the workflow.
  • Public API changes are documented.
  • Errors and status codes are explicit.

[IMAGE: Supporting visual 1 for PHP Code Review Checklist: 50 Things Senior Developers Always Check, showing PHP Code Review Checklist: 50 Things Senior Developers Always Check decisions, examples, and PHP, Code Review, Tooling. Alt: PHP Code Review Checklist: 50 Things Senior Developers Always Check php-code-review-checklist-50-things-senior-developers-always-check visual 1]

[IMAGE: Supporting visual 1 for PHP Code Review Checklist: 50 Things Senior Developers Always Check, showing PHP Code Review Checklist: 50 Things Senior Developers Always Check decisions, examples, and PHP, Code Review, Tooling. Alt: PHP Code Review Checklist: 50 Things Senior Developers Always Check php-code-review-checklist-50-things-senior-developers-always-check visual 1]

Bad sign:

if ($status !== 'active') {
    return;
}

with no explanation of what changed or why inactive records are skipped.

04Backwards compatibility is considered

Check:

  • API response shape.
  • Route names and URLs.
  • CLI arguments.
  • Event payloads.
  • Queue job serialization.
  • Database column nullability.
  • Config keys and environment variables.

Breaking changes need a migration path or a deliberate release note.

05The deploy path is safe

Ask:

  • Can old code run against the new database?
  • Can new code run against the old database during rolling deploys?
  • Are queues drained or compatible?
  • Do feature flags need defaults?
  • Does a migration lock a hot table?
  • Is rollback possible?

Risky migration:

$table->dropColumn('legacy_status');

Safer path:

Deploy 1: Add new column, dual-write.
Deploy 2: Backfill.
Deploy 3: Read new column.
Deploy 4: Drop old column after verification.

Names and readability

06Names describe domain meaning

Good names reduce review load.

Weak:

$data = $service->handle($item);

Better:

$paymentReceipt = $payments->captureInvoicePayment($invoice);

The review question:

Could a developer unfamiliar with this file infer the business meaning?

07Booleans read as facts

Weak:

if ($user->admin) {
}

Better:

if ($user->isAdmin()) {
}

For complex conditions, extract a named method:

if ($invoice->canBeCaptured()) {
    $payments->capture($invoice);
}

08Functions do one coherent job

Do not count lines first. Count reasons to change.

Smell:

public function checkout(Request $request): Response
{
    // validate input
    // calculate discount
    // create order
    // call payment gateway
    // send email
    // render HTML
}

Review direction:

The controller is doing validation, pricing, payment, persistence, notification, and rendering. Which part is the actual application operation?

09Side effects are obvious

Methods named like queries should not mutate state.

Weak:

public function getActiveSubscription(User $user): ?Subscription
{
    $this->syncFromStripe($user);

    return $user->subscription;
}

Better:

public function syncSubscription(User $user): void
{
    // side effect is explicit
}

10Comments explain why, not what

Weak:

// Set status to paid.
$invoice->status = 'paid';

Useful:

// Stripe can deliver the same event more than once, so the transition must be idempotent.
$invoice->markPaid($eventId);

Delete comments that repeat code. Keep comments that preserve hard-won context.

Types and contracts

11Files use strict types where the project expects them

For modern PHP application code:

<?php

declare(strict_types=1);

Review nuance:

  • Do not require strict_types in generated files if the project excludes them.
  • Do not churn old files just to add the declaration.
  • Do require it in new domain, application, service, and library code when the project standard says so.

12Public methods have complete type declarations

Weak:

public function total($lines)
{
    return array_sum($lines);
}

Better:

/**
 * @param list<OrderLine> $lines
 */
public function total(array $lines): Money
{
    return array_reduce(
        $lines,
        fn (Money $total, OrderLine $line): Money => $total->add($line->subtotal()),
        Money::zero('EUR'),
    );
}

Native types first. PHPDoc only where PHP cannot express the shape.

13Nullability is deliberate

Weak:

public function find(int $id): User

when the user may not exist.

Better:

public function find(int $id): ?User

or:

public function get(int $id): User
{
    return $this->find($id) ?? throw UserNotFound::forId($id);
}

Review question:

Does this method return null, throw, or always return a value?

14Arrays with structure are documented or replaced

Weak:

public function create(array $payload): void

Better:

/**
 * @param array{email: string, name: string, role: string} $payload
 */
public function create(array $payload): void

Best for important boundaries:

final readonly class CreateUserCommand
{
    public function __construct(
        public string $email,
        public string $name,
        public UserRole $role,
    ) {}
}

15mixed is contained

Sometimes mixed is honest, especially at JSON, HTTP, CLI, or vendor boundaries.

[IMAGE: Supporting visual 2 for PHP Code Review Checklist: 50 Things Senior Developers Always Check, showing PHP Code Review Checklist: 50 Things Senior Developers Always Check decisions, examples, and PHP, Code Review, Tooling. Alt: PHP Code Review Checklist: 50 Things Senior Developers Always Check php-code-review-checklist-50-things-senior-developers-always-check visual 2]

The review rule:

mixed may enter at the edge, but it should be validated and narrowed quickly.

Weak:

public function handle(mixed $payload): mixed

Better:

$payload = $this->requestParser->parse($request);

$command = new RegisterUserCommand(
    email: $payload->email,
    name: $payload->name,
);

Error handling

16Exceptions are not swallowed

Bad:

try {
    $payments->capture($invoice);
} catch (Throwable) {
}

Acceptable only when the failure is intentionally ignored and logged with context:

try {
    $analytics->trackInvoicePaid($invoice);
} catch (Throwable $exception) {
    $logger->warning('Analytics tracking failed.', [
        'invoice_id' => $invoice->id,
        'exception' => $exception::class,
    ]);
}

Payment failure is not analytics failure. Treat them differently.

17Domain failures are distinct from infrastructure failures

Weak:

throw new RuntimeException('Cannot pay invoice.');

Better:

final class InvoiceAlreadyPaid extends DomainException
{
}

and:

final class PaymentGatewayUnavailable extends RuntimeException
{
}

The caller can now choose a correct response: 409, retry, queue, or operator alert.

[IMAGE: Supporting visual 2 for PHP Code Review Checklist: 50 Things Senior Developers Always Check, showing PHP Code Review Checklist: 50 Things Senior Developers Always Check decisions, examples, and PHP, Code Review, Tooling. Alt: PHP Code Review Checklist: 50 Things Senior Developers Always Check php-code-review-checklist-50-things-senior-developers-always-check visual 2]

18External calls have timeouts

Review every HTTP client, SDK, SMTP call, Redis call, queue call, and filesystem network mount.

Weak:

$client->post('/charges', ['json' => $payload]);

Better:

$client->post('/charges', [
    'timeout' => 5,
    'connect_timeout' => 2,
    'json' => $payload,
]);

No timeout means production workers can wait forever.

19Retries are safe and bounded

Retries without idempotency can duplicate work.

Check:

  • Is there an idempotency key?
  • Is there a retry limit?
  • Is there backoff?
  • Are permanent errors excluded?
  • Are duplicate webhooks safe?

Payment and email code needs this review every time.

20Resources are closed or scoped

Check:

  • file handles,
  • temporary streams,
  • locks,
  • transactions,
  • curl handles,
  • image resources,
  • generators that keep files open.

Use finally for cleanup:

$handle = fopen($path, 'rb');

if ($handle === false) {
    throw new RuntimeException('Unable to open file.');
}

try {
    importCsv($handle);
} finally {
    fclose($handle);
}

Security

21Input is validated at trust boundaries

Trust boundaries:

  • HTTP requests.
  • CLI arguments.
  • queue payloads.
  • webhooks.
  • CSV imports.
  • database rows from legacy systems.
  • external API responses.

Weak:

$amount = (int) $_POST['amount'];

Better:

$amount = filter_input(INPUT_POST, 'amount', FILTER_VALIDATE_INT);

if (! is_int($amount) || $amount < 1) {
    throw new InvalidArgumentException('Amount must be positive.');
}

Validation is not only for browsers. It protects the application boundary.

22Output is escaped in the correct context

HTML text:

htmlspecialchars($title, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');

JavaScript data:

json_encode($payload, JSON_THROW_ON_ERROR | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT);

Review question:

What parser will read this output: HTML, attribute, URL, JavaScript, CSS, SQL, shell, JSON, XML?

Escaping must match the parser.

23SQL uses parameters for values

Bad:

$sql = "SELECT * FROM users WHERE email = '{$email}'";

Good:

$statement = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$statement->execute(['email' => $email]);

Prepared statements protect values. They do not protect table names, column names, or sort directions. Those need allowlists:

$sort = in_array($requestedSort, ['created_at', 'email'], true)
    ? $requestedSort
    : 'created_at';

24Authorization is checked near every protected action

Authentication answers:

Who is this user?

Authorization answers:

May this user do this action to this resource?

Review for missing object-level checks:

public function update(Request $request, Invoice $invoice): Response
{
    // Does this user own or manage this invoice?
}

Broken access control often hides behind valid IDs.

25State-changing browser requests have CSRF protection

Review:

  • POST forms.
  • PUT/PATCH/DELETE forms.
  • profile changes.
  • password changes.
  • billing changes.
  • admin actions.

APIs using bearer tokens are different. Cookie-authenticated browser flows need CSRF protection.

26Secrets are not in code, logs, tests, or fixtures

[IMAGE: Supporting visual 3 for PHP Code Review Checklist: 50 Things Senior Developers Always Check, showing PHP Code Review Checklist: 50 Things Senior Developers Always Check decisions, examples, and PHP, Code Review, Tooling. Alt: PHP Code Review Checklist: 50 Things Senior Developers Always Check php-code-review-checklist-50-things-senior-developers-always-check visual 3]

Flag:

  • API keys in config files.
  • test tokens copied from production.
  • private keys in fixtures.
  • passwords in logs.
  • .env committed.
  • secrets in exception messages.

Good review comment:

This logs the full payment provider response. Does it include card, token, email, or address data? Log the provider request ID and status instead.

27Passwords and tokens use correct primitives

Passwords:

$hash = password_hash($password, PASSWORD_DEFAULT);
password_verify($password, $hash);

Tokens:

$token = bin2hex(random_bytes(32));

Do not approve MD5, SHA1, unsalted hashes, mt_rand(), or homemade password storage.

28Dangerous PHP features are justified

Review hard when you see:

  • eval()
  • unserialize() on untrusted input
  • dynamic includes
  • shell commands
  • writable PHP files
  • user-controlled paths
  • reflection used to bypass visibility
  • broad call_user_func() with user input

If shell execution is required, separate command and arguments, validate inputs, and avoid passing user data through a shell.

Data and persistence

[IMAGE: Supporting visual 3 for PHP Code Review Checklist: 50 Things Senior Developers Always Check, showing PHP Code Review Checklist: 50 Things Senior Developers Always Check decisions, examples, and PHP, Code Review, Tooling. Alt: PHP Code Review Checklist: 50 Things Senior Developers Always Check php-code-review-checklist-50-things-senior-developers-always-check visual 3]

29Transactions protect multi-step consistency

Weak:

$orders->save($order);
$payments->capture($payment);
$orders->markPaid($order);

Review question:

What happens if step three fails?

Use a transaction for database consistency:

$pdo->beginTransaction();

try {
    $orders->save($order);
    $orders->markPaymentPending($order, $paymentId);
    $pdo->commit();
} catch (Throwable $exception) {
    $pdo->rollBack();

    throw $exception;
}

External calls inside transactions need extra care. Long transactions can lock data while waiting on a network.

30New queries have suitable indexes

When a PR adds:

  • new where,
  • new orderBy,
  • new join,
  • new foreign key lookup,
  • new dashboard aggregate,
  • new uniqueness rule,

ask for the index and the expected query plan.

Bad sign:

Order::where('status', 'paid')
    ->whereDate('created_at', today())
    ->orderByDesc('created_at')
    ->get();

with no index and no pagination.

31N+1 queries are prevented

Look for relationship access inside loops:

foreach ($orders as $order) {
    echo $order->customer->email;
}

Better:

$orders = Order::with('customer')->latest()->paginate(50);

For non-Laravel code, look for query calls inside loops and replace them with batch reads.

32Large reads are paginated, chunked, or streamed

Bad:

$users = User::all();

for exports, migrations, jobs, and reports.

Better:

User::query()
    ->orderBy('id')
    ->chunkById(1000, function ($users): void {
        foreach ($users as $user) {
            exportUser($user);
        }
    });

Memory problems usually enter through innocent collection code.

33Migrations are reversible or deliberately one-way

Check:

  • down() behavior.
  • data loss.
  • table locks.
  • nullable to non-nullable transitions.
  • enum changes.
  • index build strategy.
  • backfill plan.

If a migration cannot be reversed safely, the PR should say so.

34Time handling is explicit

Review:

  • timezone assumptions,
  • date parsing,
  • mutable DateTime,
  • database timezone,
  • comparisons to now(),
  • scheduled jobs around daylight saving changes.

Prefer immutable values:

new DateTimeImmutable('now', new DateTimeZone('UTC'));

In Laravel, prefer CarbonImmutable if the project standard uses Carbon.

Performance

35Algorithmic complexity matches the data size

[IMAGE: Supporting visual 4 for PHP Code Review Checklist: 50 Things Senior Developers Always Check, showing PHP Code Review Checklist: 50 Things Senior Developers Always Check decisions, examples, and PHP, Code Review, Tooling. Alt: PHP Code Review Checklist: 50 Things Senior Developers Always Check php-code-review-checklist-50-things-senior-developers-always-check visual 4]

Bad:

foreach ($users as $user) {
    foreach ($orders as $order) {
        if ($order->user_id === $user->id) {
            $matched[$user->id][] = $order;
        }
    }
}

Better:

$ordersByUser = $orders->groupBy('user_id');

foreach ($users as $user) {
    $matched[$user->id] = $ordersByUser->get($user->id, collect());
}

Senior reviewers ask what happens at 100 records, 10,000 records, and 1 million records.

36Memory usage is bounded

Watch for:

  • collecting all rows,
  • building huge arrays before writing,
  • base64-encoding large files,
  • reading full files into strings,
  • retaining models in static caches,
  • closures capturing large objects in workers.

Good pattern:

foreach (readCsvRows($path) as $row) {
    importRow($row);
}

37Cache invalidation is part of the change

Adding cache is easy. Invalidating it correctly is the work.

Check:

  • cache key includes tenant/user/locale/filter values,
  • writes invalidate affected keys,
  • stale data is acceptable or bounded by TTL,
  • stampede risk is handled,
  • cached data does not include private data for another user.

38Slow work is moved out of request-response paths

Flag direct request work that should be queued:

  • sending many emails,
  • exporting CSVs,
  • resizing large images,
  • calling slow external APIs,
  • syncing search indexes,
  • generating reports.

Do not queue work that must be completed before the user can safely proceed. Use 202 Accepted, status pages, or webhooks when the workflow is asynchronous.

[IMAGE: Supporting visual 4 for PHP Code Review Checklist: 50 Things Senior Developers Always Check, showing PHP Code Review Checklist: 50 Things Senior Developers Always Check decisions, examples, and PHP, Code Review, Tooling. Alt: PHP Code Review Checklist: 50 Things Senior Developers Always Check php-code-review-checklist-50-things-senior-developers-always-check visual 4]

39External calls are batched when possible

Bad:

foreach ($orders as $order) {
    $gateway->fetchPayment($order->payment_id);
}

Better:

$payments = $gateway->fetchPayments($orders->pluck('payment_id')->all());

If the provider has no batch endpoint, use concurrency carefully and respect rate limits.

Tests

40Tests prove behavior, not implementation details

Weak:

expect($repository)->toHaveReceived('save');

Better:

$response->assertStatus(201);
$this->assertDatabaseHas('orders', [
    'customer_id' => $customer->id,
    'status' => 'pending',
]);

Mock at true boundaries. Avoid mocking every internal collaborator.

41Failure paths are tested

Happy-path-only tests miss production reality.

Check for:

  • invalid input,
  • unauthorized user,
  • missing record,
  • duplicate webhook,
  • provider timeout,
  • failed transaction,
  • empty result set,
  • retry exhaustion.

42Security-sensitive behavior has explicit tests

Require tests for:

  • authorization policies,
  • tenant isolation,
  • CSRF-sensitive flows where framework coverage is not enough,
  • password reset token handling,
  • signed URL expiration,
  • webhook signature verification,
  • file upload validation,
  • rate limits.

These bugs are expensive after merge.

43Tests are deterministic

Bad signs:

  • real current time with no control,
  • random values with no fixed seed or assertion strategy,
  • network calls,
  • order-dependent tests,
  • sleeps,
  • shared global state,
  • tests passing only on one machine.

[IMAGE: Supporting visual 5 for PHP Code Review Checklist: 50 Things Senior Developers Always Check, showing PHP Code Review Checklist: 50 Things Senior Developers Always Check decisions, examples, and PHP, Code Review, Tooling. Alt: PHP Code Review Checklist: 50 Things Senior Developers Always Check php-code-review-checklist-50-things-senior-developers-always-check visual 5]

Use clocks, fakes, fixtures, transactions, and isolated storage.

44Risky tests are avoided

PHPUnit can mark tests risky for patterns such as no assertions, output during tests, or unintended coverage behavior depending on configuration.

Review for tests like:

public function test_import(): void
{
    $service->import($path);
}

Better:

public function test_imports_active_users(): void
{
    $service->import($path);

    self::assertSame(42, $users->countActive());
}

Every test should make a claim.

45Test data matches real constraints

Factories that create impossible data hide bugs.

Check:

  • required fields,
  • unique constraints,
  • foreign keys,
  • status transitions,
  • realistic timestamps,
  • tenant ownership,
  • permissions.

If production forbids an empty email, the default factory should not create one.

Tooling and maintainability

46Static analysis passes or the baseline does not grow

Review should not manually find issues PHPStan or Psalm can catch.

Minimum expectation:

vendor/bin/phpstan analyse

or:

vendor/bin/psalm

For legacy projects:

The baseline may exist.
The baseline must not grow.
New code should meet the current target level.

Psalm taint analysis is worth considering for security-sensitive flows where user input reaches SQL, shell, HTTP clients, file paths, redirects, or HTML output.

47Formatting is automated

PSR-12 is a reasonable shared baseline, but reviewers should not hand-format code in comments.

Use tools:

vendor/bin/php-cs-fixer fix --dry-run --diff
vendor/bin/phpcs

Reviewers can still flag readability issues. They should not argue about brace placement if the project has a formatter.

48Dependencies are intentional

Check composer.json and composer.lock changes:

  • Is this package needed?
  • Is it maintained?
  • Is it a runtime dependency or require-dev?
  • Does it duplicate existing functionality?
  • Does it widen version constraints too far?
  • Does composer audit report advisories?
  • Is a transitive dependency upgrade risky?

[IMAGE: Supporting visual 5 for PHP Code Review Checklist: 50 Things Senior Developers Always Check, showing PHP Code Review Checklist: 50 Things Senior Developers Always Check decisions, examples, and PHP, Code Review, Tooling. Alt: PHP Code Review Checklist: 50 Things Senior Developers Always Check php-code-review-checklist-50-things-senior-developers-always-check visual 5]

Weak:

"vendor/package": "*"

Better:

"vendor/package": "^2.4"

49Logs and metrics help the next incident

Review production-facing changes for:

  • structured logs,
  • request IDs or correlation IDs,
  • safe context fields,
  • counters for important outcomes,
  • alerts for failures that require action,
  • no secrets or sensitive payloads.

Good log:

$logger->warning('Payment capture failed.', [
    'invoice_id' => $invoice->id,
    'provider' => 'stripe',
    'provider_request_id' => $exception->requestId(),
]);

Bad log:

$logger->warning('Payment failed.', ['payload' => $request->all()]);

50Review comments are actionable

Weak review comment:

This feels wrong.

Better:

This method now validates input, writes the invoice, calls Stripe, and sends email. Can we move the payment and email work into an application service so the controller only translates HTTP? That would let us test duplicate webhook handling without booting the route.

Good review comments name:

  • the concrete problem,
  • the risk,
  • the requested change,
  • the reason it matters.

Code review is part of the delivery system. Make the next commit easier, not just more compliant.

[IMAGE: Supporting visual 6 for PHP Code Review Checklist: 50 Things Senior Developers Always Check, showing PHP Code Review Checklist: 50 Things Senior Developers Always Check decisions, examples, and PHP, Code Review, Tooling. Alt: PHP Code Review Checklist: 50 Things Senior Developers Always Check php-code-review-checklist-50-things-senior-developers-always-check visual 6]

Pull request gate

For most PHP applications, a reviewable PR should pass this local command set before review:

composer validate --strict
composer audit
vendor/bin/php-cs-fixer fix --dry-run --diff
vendor/bin/phpstan analyse --memory-limit=1G
vendor/bin/phpunit

Adapt the tools to the project. Keep the contract visible in composer.json:

{
  "scripts": {
    "check": [
      "composer validate --strict",
      "composer audit",
      "php-cs-fixer fix --dry-run --diff",
      "phpstan analyse --memory-limit=1G",
      "phpunit"
    ]
  }
}

Then CI and developers can run the same thing:

composer check

Automation does not replace review. It clears the cheap issues so reviewers can focus on the expensive ones.

Review severity

Use severity deliberately.

SeverityUse for
Must fixSecurity issue, data loss, broken behavior, missing authorization, unsafe migration
Should fixMaintainability risk, unclear contract, missing edge-case test, likely performance issue
Could fixNaming, small readability improvement, local simplification
NoteContext, praise, future cleanup, non-blocking idea

Do not block a release on a naming preference. Do block a release on a missing authorization check.

FAQ

What is PHP Code Review Checklist: 50 Things Senior Developers Always Check?

PHP Code Review Checklist: 50 Things Senior Developers Always Check is a practical tooling topic that should be evaluated through implementation scope, production risk, testing, documentation, and long-term maintainability.

When should a team use PHP Code Review Checklist: 50 Things Senior Developers Always Check?

Use PHP Code Review Checklist: 50 Things Senior Developers Always Check when it solves a real project constraint, improves clarity, or reduces operational risk. Avoid it when it only adds novelty or hides behavior from future maintainers.

What is the biggest risk with PHP Code Review Checklist: 50 Things Senior Developers Always Check?

The biggest risk is copying a pattern without its context. Production systems need clear boundaries, rollback options, tests, and observability before a technique becomes dependable.

How do you test PHP Code Review Checklist: 50 Things Senior Developers Always Check?

Test the smallest unit that owns the behavior, then add integration coverage for the path users or systems actually rely on. Include failure cases, configuration differences, and regression checks.

How does PHP Code Review Checklist: 50 Things Senior Developers Always Check affect SEO and AI search visibility?

It improves visibility when the article gives a direct answer, expert context, structured headings, internal links, trustworthy references, and FAQ content that matches the visible page.

Conclusion

PHP Code Review Checklist: 50 Things Senior Developers Always Check is worth doing when the implementation improves clarity, reliability, or delivery speed. It is not worth doing when it hides ownership, increases operational risk, or makes the system harder to explain.

Use the framework above as a review checklist. Then connect this topic to the rest of the project documentation so readers can move from concept to implementation without losing context.

Top