SEO Metadata
SEO Title Options
- PHP Code Review Checklist: 50 Things Senior Developers
- PHP Code Review Checklist: 50 Things: Practical 2026 Guide
- Tooling Playbook: PHP Code Review Checklist: 50 Things
Meta Description Options
- Learn PHP Code Review Checklist: 50 Things Senior Developers Always Check with a practical Tooling framework, expert mistakes, implementation steps, examples.
- 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
- What PHP Code Review Checklist: 50 Things Senior Developers Always Check means
- Why it matters now
- Implementation framework
- Practical comparison
- Expert workflow
- Common mistakes
- Media and link plan
- Original technical deep dive
- FAQ
- Structured data
- Conclusion
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.
- Define the user problem and the production risk.
- Identify the smallest reliable implementation boundary.
- Keep configuration, secrets, and environment-specific behavior outside the article's core logic.
- Add tests for the behavior that would hurt if it regressed.
- Document the trade-off, not only the final code.
- Measure the result with logs, metrics, or user-facing outcomes.
- 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 area | Strong approach | Weak approach | Why it matters |
|---|---|---|---|
| Scope | Solve one clear problem | Mix unrelated concerns | Focus improves testing and search intent |
| Architecture | Put logic in explicit classes or documented boundaries | Hide behavior in templates or incidental callbacks | Future changes stay easier to review |
| Data flow | Pass prepared data into the view or endpoint | Query or compute in presentation code | Reduces regressions and performance surprises |
| Testing | Cover the risky behavior directly | Test only the happy path | Catches production failures earlier |
| Documentation | Explain trade-offs and limits | Repeat generic definitions | Builds E-E-A-T and reader trust |
| Operations | Track logs, metrics, and rollback steps | Ship without measurement | Makes 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]
Media and link plan
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.]
Trustworthy outbound links
- PHP manual - use this as the trust reference for language-level reference.
- Google Search quality guidance - use this as the trust reference for people-first content and E-E-A-T alignment.
Internal linking opportunities
- Internal guide: PHP Workers and Queues: Supervisor, Horizon - use this when readers need a related Tooling follow-up.
- Internal guide: PHP Webhooks With Filament: Admin Panels and - use this when readers need a related Tooling follow-up.
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:
| Question | Reviewer 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:
declare(strict_types=1);
Review nuance:
- Do not require
strict_typesin 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.
.envcommitted.- 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 auditreport 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.
| Severity | Use for |
|---|---|
| Must fix | Security issue, data loss, broken behavior, missing authorization, unsafe migration |
| Should fix | Maintainability risk, unclear contract, missing edge-case test, likely performance issue |
| Could fix | Naming, small readability improvement, local simplification |
| Note | Context, 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.