{"slug": "learn-php-with-claude-in-2026-build-real-skills-not-ai-dependency", "title": "Learn PHP with Claude in 2026: build real skills, not AI dependency", "summary": "A developer warns that AI-generated PHP code is creating a generation of Laravel operators who lack fundamental PHP knowledge, citing a case where a developer using Claude copied a controller that used `$request->all()` instead of `$request->validated()`, leaving a mass assignment vulnerability. The developer argues that PHP 8.3 is a modern, typed language powering 75% of the web, and that learners must understand core PHP concepts like `$_GET`, `$_POST`, and `password_hash()` before relying on frameworks or AI.", "body_md": "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()`\n\ninstead of `$request->validated()`\n\nin his controller. Blank stare. He didn't know that `all()`\n\nreturns *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.\n\nPHP 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.\n\nBefore talking pedagogy, let's settle this. \"Why learn PHP when everyone's moving to Go / Rust / TypeScript?\" Three facts, not opinions.\n\n**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`\n\nat 2am.\n\n**2. PHP 8.3 has nothing in common with PHP 5.** Type declarations, enums, `readonly`\n\n, `match`\n\n, 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.\n\n**3. AI doesn't debug your business context.** Claude generates a `UserController`\n\nin 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`\n\n, you're stuck.\n\nPHP has its own learning anti-patterns. AI amplifies them if you don't know they exist.\n\nYou ask \"how to create a contact form in PHP,\" Claude gives you WordPress code with `wp_mail()`\n\nand `add_action`\n\nhooks. You copy, it works in WordPress. You learned nothing about PHP. You don't know that native `mail()`\n\nexists, 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.\n\nLaravel 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`\n\n, `$_POST`\n\n, `$_SERVER['REQUEST_METHOD']`\n\nare, you don't understand what the framework does for you. And the day it does something unexpected, you're lost.\n\nAI 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()`\n\nor `sha1()`\n\nin certain contexts. The right reflex: `password_hash()`\n\nwith `PASSWORD_BCRYPT`\n\nor `PASSWORD_ARGON2ID`\n\n. Always check the PHP version of suggested patterns.\n\n``` php\n// ❌ PHP 5 — NEVER do this\n$hash = md5($password);\n$hash = sha1($password . $salt);\n\n// ✅ PHP 8 — the only right way\n$hash = password_hash($password, PASSWORD_ARGON2ID);\n$valid = password_verify($input, $hash);\n```\n\nPHP 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 `TypeError`\n\ns you don't understand. Enable `declare(strict_types=1)`\n\nfrom your very first file. No exceptions.\n\n```\n// ❌ No typing — everything passes, nothing is safe\nfunction calculatePrice($quantity, $unitPrice) {\n    return $quantity * $unitPrice;\n}\ncalculatePrice(\"3\", \"19.99\"); // Works... by accident\n\n// ✅ With strict_types — errors are explicit\ndeclare(strict_types=1);\n\nfunction calculatePrice(int $quantity, float $unitPrice): float {\n    return $quantity * $unitPrice;\n}\ncalculatePrice(\"3\", \"19.99\"); // Immediate TypeError\n```\n\nClaude sometimes suggests `mysqli`\n\n, sometimes `PDO`\n\n, depending on context. The real advice: use `PDO`\n\n, period. It's the standard abstraction layer, it handles prepared statements cleanly, and it works with any database engine. `mysqli`\n\nis MySQL-specific and brings nothing extra in 99% of cases.\n\n``` php\n// ❌ mysqli — coupled to MySQL, confusing API\n$conn = new mysqli(\"localhost\", \"user\", \"pass\", \"db\");\n$stmt = $conn->prepare(\"SELECT * FROM users WHERE email = ?\");\n$stmt->bind_param(\"s\", $email);\n$stmt->execute();\n$result = $stmt->get_result();\n\n// ✅ PDO — portable, clear, chainable\n$pdo = new PDO('mysql:host=localhost;dbname=db', 'user', 'pass', [\n    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,\n    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,\n]);\n$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');\n$stmt->execute(['email' => $email]);\n$user = $stmt->fetch();\n```\n\nThe same cycle as for any language — [I covered it in the Python guide](https://www.web-developpeur.com/en/blog/apprendre-python-avec-ia-2026) — but with concrete PHP examples.\n\nProblem: filter an array of users to keep only adults. Your first version, without AI:\n\n```\n// My version — filter adult users\nfunction getAdults(array $users): array {\n    $adults = [];\n    foreach ($users as $user) {\n        if ($user['age'] >= 18) {\n            $adults[] = $user;\n        }\n    }\n    return $adults;\n}\n```\n\nHere'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.\n\nClaude will point out: `array_filter`\n\nexists for this. The return type is `array`\n\nbut could be more precise with a `@return array<int, array{name: string, age: int}>`\n\ndocblock. And what if `$users`\n\nis empty? Does your function handle that? (Yes, it does — but did you think about it?)\n\n```\n// My improved version after review\n/** @param array<array{name: string, age: int}> $users */\nfunction getAdults(array $users): array {\n    return array_filter($users, fn(array $user): bool => $user['age'] >= 18);\n}\n```\n\nYou discovered `array_filter`\n\nand arrow functions. You understand that `fn() =>`\n\nis syntactic sugar for single-expression closures. You know *why* it's more concise, and you know that `array_filter`\n\npreserves keys (which can be a trap if you then iterate with numeric indices).\n\nHere's my improved version. What am I not seeing? Is there a more robust pattern for this kind of filtering?\n\nClaude will probably mention the `match`\n\nexpression for multi-criteria filtering, typed collections with classes, and the difference between `array_filter`\n\nand `array_values(array_filter(...))`\n\nto reindex the array. Three levels of understanding, and you wrote the first two yourself.\n\nThe difference between learning and delegating comes down to how you phrase the prompt. Here are patterns adapted to PHP.\n\n❌ Passive prompt\n\n✅ Pedagogical prompt\n\n\"Write a PHP CRUD for managing products\"\n\n\"I wrote this POST route that inserts a product with PDO. Do my prepared statements really protect against SQL injection?\"\n\n\"Fix this error\"\n\n\"This code raises `TypeError: Argument #1 must be of type string, null given`\n\n. Explain where the `null`\n\ncomes from without giving me the fix.\"\n\n\"Make me a Laravel auth\"\n\n\"I implemented session auth with `password_verify()`\n\nand a CSRF token. What could an attacker exploit?\"\n\n\"What are namespaces?\"\n\n\"I understand that `use App\\Service\\UserService`\n\nimports a class. But what's the difference between `use`\n\nin a namespace and `use`\n\nin a closure?\"\n\n\"Write a PHP class\"\n\n\"I have this `User`\n\nclass with 8 constructor properties. Would PHP 8 promoted properties simplify it? Show me the difference.\"\n\nThe 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.\n\nNot a rigid program — a path that worked for people I've mentored. Adapted to PHP specifics. AI comes in *after* the effort, never before.\n\nVariables, types, conditions, loops, functions, arrays. **Without Claude.** Use the official PHP documentation ([php.net/manual](https://www.php.net/manual/en/)) and do the exercises by hand. Start every file with `declare(strict_types=1)`\n\n. The goal: make `foreach ($items as $key => $value)`\n\na reflex, not a formula you ask for every time.\n\n**Exercise**: write a script that reads a CSV file and calculates statistics (average, min, max) on a numeric column. No external library. Just `fopen()`\n\n, `fgetcsv()`\n\n, an associative array. When done, ask Claude for a review.\n\nClasses, 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**.\n\n**Exercise**: a CLI task manager — add, delete, mark as \"done,\" JSON export. Each entity in its own class. Use `readonly`\n\n, constructor promoted properties, and enums for statuses. Write first, review with Claude after. It'll probably mention interfaces — that's the right moment.\n\n```\n// What you should write yourself in week 3\ndeclare(strict_types=1);\n\nenum TaskStatus: string {\n    case Todo = 'todo';\n    case Done = 'done';\n    case Cancelled = 'cancelled';\n}\n\nfinal class Task {\n    public function __construct(\n        public readonly string $id,\n        public readonly string $title,\n        public TaskStatus $status = TaskStatus::Todo,\n        public readonly \\DateTimeImmutable $createdAt = new \\DateTimeImmutable(),\n    ) {}\n}\n```\n\nComposer, 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?\".\n\n**Exercise**: a REST API that manages blog posts (CRUD). Manual routing (`$_SERVER['REQUEST_URI']`\n\n+ `match`\n\n), PDO for the database, JSON in/out. HTTP error handling (404, 422, 500). Ask Claude: \"is my routing secure?\" — the answer will surprise you.\n\nA project that combines everything. Two calibrated options:\n\nClaude 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.\n\nTwo 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.\n\nWeb interface, conversation. The right tool for:\n\n`abstract class`\n\nand `interface`\n\nin PHP with a concrete case\"`===`\n\nand not `==`\n\nin PHP?\"**Advantage**: accessible, no terminal needed, conversation history. **Limitation**: you copy-paste code, it can't see your `composer.json`\n\n.\n\nIn the terminal, directly in your project. The right tool for:\n\n`composer.json`\n\n, your PHPUnit tests`php -S`\n\n, sees the error, explains the stack trace**Advantage**: full project context, real execution. **Limitation**: more technical to set up, requires a terminal.\n\n**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.\n\nClaude 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.\n\n`camelCase`\n\nfor methods, `PascalCase`\n\nfor classes. Use PHP CS Fixer from day one.`require`\n\n`require_once 'lib/User.php'`\n\n, you're writing 2008 PHP. PSR-4 + `composer dump-autoload`\n\n, period.`new PDO(...)`\n\nin every method. That's a major anti-pattern.`declare(strict_types=1)`\n\neverywhere`\"42abc\"`\n\nto `42`\n\n. With it, it throws a `TypeError`\n\n. You want the second behavior.`array`\n\nholding structured data should be a DTO (Data Transfer Object) with typed properties. AI loves associative arrays. Resist.How to know if you're actually learning and not just \"doing things with Claude\"? Three concrete tests.\n\n**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.\n\n**Test 2 — The explanation.** Explain your code to someone. Every method. If you say \"this part with the `match`\n\nI'm not sure why it's like that,\" that's a gap in your understanding.\n\n**Test 3 — Debugging without a net.** Introduce a `null`\n\nsomewhere in your call chain. Close Claude. Find the `TypeError`\n\nwith `var_dump()`\n\n, `error_log()`\n\n, or just by reading the stack trace. The time it takes measures your real autonomy.\n\nPHP 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**.\n\nThe 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.\n\nThe same workflow applies to any language. If you're coming from Go, I wrote [the equivalent guide for Go](https://www.web-developpeur.com/en/blog/apprendre-go-guide-developpeur). For Python, it's [over here](https://www.web-developpeur.com/en/blog/apprendre-python-avec-ia-2026).", "url": "https://wpnews.pro/news/learn-php-with-claude-in-2026-build-real-skills-not-ai-dependency", "canonical_source": "https://dev.to/ohugonnot/learn-php-with-claude-in-2026-build-real-skills-not-ai-dependency-1c81", "published_at": "2026-07-01 09:00:02+00:00", "updated_at": "2026-07-01 09:19:30.270348+00:00", "lang": "en", "topics": ["large-language-models", "developer-tools", "ai-products"], "entities": ["Claude", "Laravel", "PHP", "WordPress", "Shopify", "Symfony", "Doctrine", "PHPMailer"], "alternates": {"html": "https://wpnews.pro/news/learn-php-with-claude-in-2026-build-real-skills-not-ai-dependency", "markdown": "https://wpnews.pro/news/learn-php-with-claude-in-2026-build-real-skills-not-ai-dependency.md", "text": "https://wpnews.pro/news/learn-php-with-claude-in-2026-build-real-skills-not-ai-dependency.txt", "jsonld": "https://wpnews.pro/news/learn-php-with-claude-in-2026-build-real-skills-not-ai-dependency.jsonld"}}