Learn PHP with Claude in 2026: master the language without becoming AI-dependent

Code review last week. A "fullstack" dev shows me his Laravel API. Clean on the surface — well-organized controllers, Eloquent migrations, Form Request validation. I ask him why he's using $request->all() instead of $request->validated() in his controller. Blank stare. He didn't know that all() returns everything in the payload, including unvalidated fields. Claude generated the controller, he copied it, it worked. Six months of Laravel and a dormant mass assignment vulnerability in every endpoint.

PHP has a problem Python doesn't: its reputation. "Dead language," "spaghetti code," "only good for WordPress." In 2026, that's been wrong for a long time — PHP 8.3 is a typed, modern language with enums, fibers, readonly properties, and performance PHP 5 could never have imagined. But this reputation means many beginners skip the basics and jump straight to a framework, guided by AI. The result: Laravel operators, not PHP developers. This article explains how to use Claude to become the latter without falling into the former.

PHP in 2026: the language people love to hate

Before talking pedagogy, let's settle this. "Why learn PHP when everyone's moving to Go / Rust / TypeScript?" Three facts, not opinions.

1. PHP powers 75% of the web. WordPress, Shopify (backend), Symfony, Laravel — millions of projects in production. PHP gigs aren't scarce, and they pay well because good PHP devs are rare. Everyone wants to code in Rust; few can debug a Doctrine\ORM\Query\QueryException at 2am.

2. PHP 8.3 has nothing in common with PHP 5. Type declarations, enums, readonly, match, named arguments, fibers, JIT — it's a different language. PHP criticism dates back to 2012. In 2026, the same patterns people applauded in TypeScript exist natively in PHP.

3. AI doesn't debug your business context. Claude generates a UserController in 15 seconds. But when the client's LDAP auth breaks in staging and sessions no longer propagate between PHP-FPM workers, you're the one who needs to understand the HTTP request lifecycle. If you never understood $_SESSION, you're stuck.

The 5 PHP-specific traps of AI-assisted learning

PHP has its own learning anti-patterns. AI amplifies them if you don't know they exist.

Trap 1 — Copying WordPress code without understanding

You ask "how to create a contact form in PHP," Claude gives you WordPress code with wp_mail() and add_action hooks. You copy, it works in WordPress. You learned nothing about PHP. You don't know that native mail() exists, that it's terrible in production (no SMTP auth, no TLS), and that PHPMailer solves the real problem. WordPress is not PHP — it's a framework with its own dialect.

Trap 2 — Using a framework before understanding HTTP

Laravel in week 1. It's the most common trap. The framework abstracts everything: routing, requests, responses, sessions, middleware. If you don't understand what $_GET, $_POST, $_SERVER['REQUEST_METHOD'] are, you don't understand what the framework does for you. And the day it does something unexpected, you're lost.

Trap 3 — Confusing PHP 5 and PHP 8

AI was trained on millions of lines of old PHP. When you ask "how to hash a password in PHP," there's a non-trivial chance Claude suggests md5() or sha1() in certain contexts. The right reflex: password_hash() with PASSWORD_BCRYPT or PASSWORD_ARGON2ID. Always check the PHP version of suggested patterns.

// ❌ PHP 5 — NEVER do this
$hash = md5($password);
$hash = sha1($password . $salt);

// ✅ PHP 8 — the only right way
$hash = password_hash($password, PASSWORD_ARGON2ID);
$valid = password_verify($input, $hash);

Trap 4 — Ignoring type declarations

PHP is optionally typed. AI often generates code without types because it's "simpler." Result: you learn a lenient PHP that accepts anything and crashes in production with TypeErrors you don't understand. Enable declare(strict_types=1) from your very first file. No exceptions.

// ❌ No typing — everything passes, nothing is safe
function calculatePrice($quantity, $unitPrice) {
    return $quantity * $unitPrice;
}
calculatePrice("3", "19.99"); // Works... by accident

// ✅ With strict_types — errors are explicit
declare(strict_types=1);

function calculatePrice(int $quantity, float $unitPrice): float {
    return $quantity * $unitPrice;
}
calculatePrice("3", "19.99"); // Immediate TypeError

Trap 5 — PDO vs mysqli: the false debate

Claude sometimes suggests mysqli, sometimes PDO, depending on context. The real advice: use PDO, period. It's the standard abstraction layer, it handles prepared statements cleanly, and it works with any database engine. mysqli is MySQL-specific and brings nothing extra in 99% of cases.

// ❌ mysqli — coupled to MySQL, confusing API
$conn = new mysqli("localhost", "user", "pass", "db");
$stmt = $conn->prepare("SELECT * FROM users WHERE email = ?");
$stmt->bind_param("s", $email);
$stmt->execute();
$result = $stmt->get_result();

// ✅ PDO — portable, clear, chainable
$pdo = new PDO('mysql:host=localhost;dbname=db', 'user', 'pass', [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $email]);
$user = $stmt->fetch();

The workflow that works: write first, ask second

The same cycle as for any language — I covered it in the Python guide — but with concrete PHP examples.

Step 1 — Write your version, even ugly

Problem: filter an array of users to keep only adults. Your first version, without AI:

// My version — filter adult users
function getAdults(array $users): array {
    $adults = [];
    foreach ($users as $user) {
        if ($user['age'] >= 18) {
            $adults[] = $user;
        }
    }
    return $adults;
}

Step 2 — Ask for review, not a solution

Here's my PHP code to filter an array of users. Don't give me a corrected version. Tell me what could be improved and why, and let me rewrite it myself.

Claude will point out: array_filter exists for this. The return type is array but could be more precise with a @return array<int, array{name: string, age: int}> docblock. And what if $users is empty? Does your function handle that? (Yes, it does — but did you think about it?)

Step 3 — Rewrite yourself

// My improved version after review
/** @param array<array{name: string, age: int}> $users */
function getAdults(array $users): array {
    return array_filter($users, fn(array $user): bool => $user['age'] >= 18);
}

You discovered array_filter and arrow functions. You understand that fn() => is syntactic sugar for single-expression closures. You know why it's more concise, and you know that array_filter preserves keys (which can be a trap if you then iterate with numeric indices).

Step 4 — Ask "what am I not seeing?"

Here's my improved version. What am I not seeing? Is there a more robust pattern for this kind of filtering?

Claude will probably mention the match expression for multi-criteria filtering, typed collections with classes, and the difference between array_filter and array_values(array_filter(...)) to reindex the array. Three levels of understanding, and you wrote the first two yourself.

Prompts that teach (vs prompts that assist)

The difference between learning and delegating comes down to how you phrase the prompt. Here are patterns adapted to PHP.

❌ Passive prompt ✅ Pedagogical prompt
"Write a PHP CRUD for managing products" "I wrote this POST route that inserts a product with PDO. Do my prepared statements really protect against SQL injection?"
"Fix this error" "This code raises TypeError: Argument #1 must be of type string, null given. Explain where the null comes from without giving me the fix."
"Make me a Laravel auth" "I implemented session auth with password_verify() and a CSRF token. What could an attacker exploit?"
"What are namespaces?" "I understand that use App\Service\UserService imports a class. But what's the difference between use in a namespace and use in a closure?"
"Write a PHP class" "I have this User class with 8 constructor properties. Would PHP 8 promoted properties simplify it? Show me the difference."

The common pattern: show what you did or understood, then ask a specific question. Not "explain namespaces" but "I understood this much, what am I missing?". Claude adapts its response to your actual level instead of serving a long-form version of the PHP.net docs.

The path: zero to autonomous in 8 weeks

Not a rigid program — a path that worked for people I've mentored. Adapted to PHP specifics. AI comes in after the effort, never before.

Weeks 1-2 — The basics without AI

Variables, types, conditions, loops, functions, arrays. Without Claude. Use the official PHP documentation (php.net/manual) and do the exercises by hand. Start every file with declare(strict_types=1). The goal: make foreach ($items as $key => $value) a reflex, not a formula you ask for every time.

Exercise: write a script that reads a CSV file and calculates statistics (average, min, max) on a numeric column. No external library. Just fopen(), fgetcsv(), an associative array. When done, ask Claude for a review.

Weeks 3-4 — OOP and namespaces

Classes, interfaces, traits, inheritance, composition, namespaces, PSR-4 autoloading. This is where PHP stands out: OOP is at the core of the modern language, and namespaces are the mechanism that structures any non-trivial project. Claude moves to review mode.

Exercise: a CLI task manager — add, delete, mark as "done," JSON export. Each entity in its own class. Use readonly, constructor promoted properties, and enums for statuses. Write first, review with Claude after. It'll probably mention interfaces — that's the right moment.

// What you should write yourself in week 3
declare(strict_types=1);

enum TaskStatus: string {
    case Todo = 'todo';
    case Done = 'done';
    case Cancelled = 'cancelled';
}

final class Task {
    public function __construct(
        public readonly string $id,
        public readonly string $title,
        public TaskStatus $status = TaskStatus::Todo,
        public readonly \DateTimeImmutable $createdAt = new \DateTimeImmutable(),
    ) {}
}

Weeks 5-6 — Composer, REST APIs and databases

Composer, autoloading, dependencies. PDO, prepared statements, manual migrations. Writing a minimal REST API — without a framework. Claude becomes a patterns teacher: "why separate routing from business logic?", "why not put SQL in the controller?".

Exercise: a REST API that manages blog posts (CRUD). Manual routing ($_SERVER['REQUEST_URI'] + match), PDO for the database, JSON in/out. HTTP error handling (404, 422, 500). Ask Claude: "is my routing secure?" — the answer will surprise you.

Weeks 7-8 — A real complete project

A project that combines everything. Two calibrated options:

  • A hand-built mini MVC — router, controllers, PHP views, models with PDO, all structured with namespaces and Composer autoload. Not Symfony, not Laravel — yours. You'll finally understand what frameworks do under the hood.
  • A complete API — JWT authentication, input validation, pagination, error handling, PHPUnit tests. The kind of project you're asked to build in technical interviews.

Claude switches to pair programming mode: you write a feature, you discuss architecture with it, it challenges your choices. "Why put validation in the controller instead of a dedicated service?" — if you can't answer, you haven't consciously decided yet.

Claude Pro vs Claude Code: which tool for learning PHP

Two tools, two uses. Both cost $20/month (Claude Pro) or $20/month (Claude Code with the Max plan) — but the learning experience is very different.

Claude Pro (claude.ai) — the teacher

Web interface, conversation. The right tool for:

  • Understanding a concept — "explain the difference between abstract class and interface in PHP with a concrete case"
  • Code review — paste your code and ask for structured feedback
  • Asking "why" questions — "why === and not == in PHP?"
  • Interview prep — "ask me 5 intermediate PHP questions about OOP and types"

Advantage: accessible, no terminal needed, conversation history. Limitation: you copy-paste code, it can't see your composer.json.

Claude Code — the pair programmer

In the terminal, directly in your project. The right tool for:

  • Working in real context — Claude sees your files, your composer.json, your PHPUnit tests
  • Progressive refactoring — "this controller is 300 lines, help me extract a service without breaking tests"
  • Learning tooling — PHPStan, PHP CS Fixer, PHPUnit, Composer scripts — Claude configures them and explains why
  • Live debugging — it runs php -S, sees the error, explains the stack trace

Advantage: full project context, real execution. Limitation: more technical to set up, requires a terminal.

The ideal combination

Weeks 1-4: Claude Pro for concepts and review. You don't have a complex project yet, the web interface is enough. Weeks 5-8: Claude Code for the project. You need file context, tests, live debugging. Claude Pro remains useful for conceptual questions in parallel.

Conventions AI won't teach you

Claude writes correct PHP. But there's a difference between correct code and professional code. These conventions are learned by reading good open source code (Symfony, Laravel, PHPStan) and getting corrected.

  • PSR-12 is not optional — 4 spaces, opening braces on the next line for classes and methods, camelCase for methods, PascalCase for classes. Use PHP CS Fixer from day one.
  • Composer autoload, not manual require — if your project has more than 3 files and you're doing require_once 'lib/User.php', you're writing 2008 PHP. PSR-4 + composer dump-autoload, period.
  • Dependency injection without a framework — pass dependencies in the constructor instead of instantiating them inside. AI often generates new PDO(...) in every method. That's a major anti-pattern.
  • declare(strict_types=1) everywhere — without it, PHP silently converts "42abc" to 42. With it, it throws a TypeError. You want the second behavior.
  • Arrays are not objects — a PHP array holding structured data should be a DTO (Data Transfer Object) with typed properties. AI loves associative arrays. Resist.
  • One file = one class — PSR-4 mandates it. If your file contains two classes, Composer can't autoload properly. Claude won't spontaneously tell you "split these classes."

Measuring your progress

How to know if you're actually learning and not just "doing things with Claude"? Three concrete tests.

Test 1 — The whiteboard. Take a problem you solved with Claude last week. Rewrite the class from memory, no IDE, no AI. If you're stuck on promoted property constructor syntax, you didn't learn — you delegated.

Test 2 — The explanation. Explain your code to someone. Every method. If you say "this part with the match I'm not sure why it's like that," that's a gap in your understanding.

Test 3 — Debugging without a net. Introduce a null somewhere in your call chain. Close Claude. Find the TypeError with var_dump(), error_log(), or just by reading the stack trace. The time it takes measures your real autonomy.

📚 The complete PHP course, free and interactive

Want to actually practice this path? The PHP course on this site covers these foundations lesson by lesson: code you run right in the page, quizzes, and corrected projects built with AI. You learn by doing, not by reading. Jump to sessions and cookies, SQL databases, or understanding the HTTP request.

Conclusion

PHP isn't dead, it just grew faster than its reputation. The 2026 language — typed, with enums, readonly, match, fibers — has little in common with the PHP from Reddit jokes. But this modernity is useless if you discover it through copy-paste from Claude. The discipline stays the same: write first, ask second, rewrite yourself.

The developer who finishes this 8-week path knows how to read a stack trace, choose between an array and a DTO, write PSR-12 code others can read, and use Claude as a multiplier — not a crutch. That's exactly the profile the market is missing: someone who can code PHP and who knows how to get the most out of AI.

The same workflow applies to any language. If you're coming from Go, I wrote the equivalent guide for Go. For Python, it's over here.

Comments (0)