I’ve spent enough time moving between PHP, JavaScript, Python, and a Laravel codebase or two to have a pretty settled opinion on this: PHP the language and PHP the day-to-day writing experience are two different things. PHP itself has come a long way. Writing raw PHP by hand, without a framework sitting on top of it, is where my patience runs thin.
This isn’t a “PHP is dead” article. It isn’t. PHP still powers a huge share of the web, and plenty of good developers work in it every day. What I want to talk about is something narrower and, I think, more useful: the actual experience of typing PHP syntax compared with what I’m used to in JavaScript, Python, and especially Laravel. Small syntax decisions add up over a workday, and that’s the part nobody puts in the marketing copy.
I Don’t Think PHP Is Bad — Raw PHP Is the Problem
Let me separate two things early, because the rest of the article depends on it.
“PHP” as a language has real strengths. It has a huge ecosystem, it’s cheap to host, it’s easy to deploy, and modern versions have added typed properties, enums, match expressions, and constructor promotion — features that genuinely close the gap with newer languages. The PHP manual’s language reference documents all of this, and it’s worth reading if your mental model of PHP stopped around version 5.
What I find frustrating isn’t PHP as a concept. It’s the experience of sitting down and writing raw PHP — no framework, no ORM, no router, just PHP files handling requests directly. That’s where the verbosity, the inconsistent function naming, and the manual plumbing start to wear on me. Once you add Laravel, a lot of that friction disappears. But raw PHP, on its own, is a different experience than raw PHP with Laravel wrapped around it, and I think it’s worth being precise about which one you’re criticizing.
The Syntax Is Where My Frustration Starts
The dollar sign in front of every variable is the smallest example, but it sets the tone. $name, $user, $total — every variable reference needs that prefix. In JavaScript or Python you just write name, user, total. It’s a minor thing on its own, but multiply it across a file with a hundred variable references and it adds visual noise that other languages don’t have.
Here’s basic output and variable handling in PHP:
php
<?php
$name = "Sarah";
$age = 29;
echo "Hello, my name is " . $name . " and I am " . $age . " years old.";
Compare that to JavaScript:
javascript
const name = "Sarah";
const age = 29;
console.log(`Hello, my name is ${name} and I am ${age} years old.`);
Or Python:
python
name = "Sarah"
age = 29
print(f"Hello, my name is {name} and I am {age} years old.")
The PHP version needs string concatenation with dots, which is easy to fumble when you’re moving fast — miss a . and PHP throws a parse error rather than quietly concatenating. Modern PHP does support string interpolation inside double-quoted strings ("Hello, $name"), which helps, but it doesn’t support expressions inside the braces the way template literals or f-strings do without extra {$expression} syntax. It’s not that PHP can’t do it. It’s that the ergonomics require more care.
Function naming is the other thing that trips me up regularly. PHP’s standard library is inconsistent about argument order and naming conventions — strpos($haystack, $needle) versus array_search($needle, $haystack) is a classic example that catches people out. I don’t think this is a hot take; it’s a well-documented quirk of PHP’s history, where core functions were added over decades by different contributors without a unified style guide.
PHP vs JavaScript: A Simple Syntax Comparison
JavaScript and PHP actually overlap more than people expect — both are dynamically typed, both run on curly braces, both got serious “modern” upgrades in the last decade (ES6+ for JavaScript, PHP 7/8 for PHP). But writing them day to day feels different.
Here’s a loop and conditional in both:
php
<?php
$numbers = [1, 2, 3, 4, 5];
foreach ($numbers as $number) {
if ($number % 2 === 0) {
echo $number . " is even\n";
} else {
echo $number . " is odd\n";
}
}
javascript
const numbers = [1, 2, 3, 4, 5];
numbers.forEach((number) => {
console.log(number % 2 === 0 ? `${number} is even` : `${number} is odd`);
});
Neither is objectively “wrong.” But JavaScript’s array methods (map, filter, reduce, forEach) feel more composable to me than PHP’s array_map, array_filter, and array_reduce, mostly because of argument order again — PHP puts the callback first in array_map($callback, $array) but the array first in array_filter($array, $callback). That inconsistency is documented behavior, not an opinion, and it’s the kind of thing that forces you to check the docs more often than you’d like.
Arrow functions are another spot where JavaScript’s syntax is just shorter:
php
$doubled = array_map(fn($n) => $n * 2, $numbers);
javascript
const doubled = numbers.map((n) => n * 2);
PHP’s arrow functions (added in PHP 7.4) closed a lot of this gap, to be fair. This is one of those cases where modern PHP genuinely caught up.
PHP vs Python: Readability and Boilerplate
Python’s whole design philosophy is readability, so this comparison is a bit unfair to PHP from the start. Still, it’s useful because it shows what “low ceremony” syntax looks like.
Handling form-style data as a simple function:
php
<?php
function calculateTotal(array $items): float {
$total = 0.0;
foreach ($items as $item) {
$total += $item['price'] * $item['quantity'];
}
return $total;
}
python
def calculate_total(items: list[dict]) -> float:
return sum(item["price"] * item["quantity"] for item in items)
Python’s comprehension syntax lets you express the same logic in one line without losing much clarity. PHP requires a manual accumulator variable and a loop for the same result, unless you reach for array_reduce, which honestly reads worse than the loop:
php
$total = array_reduce($items, fn($carry, $item) => $carry + ($item['price'] * $item['quantity']), 0.0);
I’ll be fair here: not every Python one-liner is more readable than the equivalent loop, and PHP’s explicit loop is arguably easier for a beginner to follow line by line. This is a genuine trade-off, not a one-sided win for Python.
PHP vs Node.js: The Modern JavaScript Experience
This comparison is less about syntax and more about how a request gets handled, which affects the code you actually write.
Raw PHP, without a framework, typically handles a request per file, reading straight from PHP’s superglobals:
php
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = trim($_POST['email'] ?? '');
$password = $_POST['password'] ?? '';
if (empty($email) || empty($password)) {
http_response_code(422);
echo json_encode(['error' => 'Email and password are required']);
exit;
}
// ... handle login
}
A minimal Node.js/Express equivalent:
javascript
app.post('/login', (req, res) => {
const { email, password } = req.body;
if (!email || !password) {
return res.status(422).json({ error: 'Email and password are required' });
}
// ... handle login
});
Neither snippet is dramatically shorter, but the Express version separates routing from logic by default. Raw PHP mixes request handling, validation, and output directly inside the file unless you build that separation yourself. This is exactly the kind of structure a framework gives you for free — which is a preview of the Laravel section coming up.
The Database Problem With Raw PHP
This is where raw PHP frustrates me the most. Talking to a database without a framework usually means PDO, prepared statements, and manual fetch calls:
php
<?php
$pdo = new PDO('mysql:host=localhost;dbname=shop', $username, $password);
$stmt = $pdo->prepare('SELECT * FROM products WHERE category = :category');
$stmt->execute(['category' => 'electronics']);
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($products as $product) {
echo $product['name'] . ' - $' . $product['price'] . "\n";
}
This code is correct, and prepared statements are the right way to avoid SQL injection — that part isn’t up for debate; it’s documented directly in the PHP PDO manual. My issue isn’t security or correctness. It’s that every single query in a raw PHP app repeats this same pattern: prepare, bind, execute, fetch. There’s no abstraction reducing the repetition unless you build one yourself, which means most raw PHP codebases end up with a homemade, partially-tested ORM buried somewhere in a Database.php file.
Compare that to Eloquent, Laravel’s ORM:
php
$products = Product::where('category', 'electronics')->get();
foreach ($products as $product) {
echo $product->name . ' - $' . $product->price . "\n";
}
Same result, a fraction of the code, and the query building, binding, and hydration into objects all happen behind the scenes. This is the single biggest reason I reach for Laravel over raw PHP for anything beyond a small script.
Laravel Completely Changes the Experience
I want to be careful with the wording here: Laravel doesn’t “fix” PHP, because PHP wasn’t broken. What Laravel does is provide structure and conventions on top of PHP so you’re not rebuilding the same plumbing on every project. That’s an important difference, and it’s worth walking through where it actually shows up.
Routing. Instead of one PHP file per URL, routes live in one place:
php
Route::get('/products', [ProductController::class, 'index']);
Route::post('/products', [ProductController::class, 'store']);
Controllers separate request handling from business logic, instead of everything sitting in the same file as the HTML output.
Eloquent ORM, as shown above, removes almost all manual SQL for standard CRUD work.
Blade, Laravel’s templating engine, handles output escaping and layout inheritance with a simpler syntax than raw PHP’s <?php ... ?> tags scattered through HTML:
blade
@foreach ($products as $product)
<p>{{ $product->name }} - ${{ $product->price }}</p>
@endforeach
Middleware wraps request handling logic (auth checks, rate limiting, CORS) around routes without repeating that logic in every controller.
Validation is declarative instead of a chain of manual if statements:
php
$request->validate([
'email' => 'required|email',
'password' => 'required|min:8',
]);
Authentication, migrations, and queues are the areas where raw PHP asks the most of you. Building your own session-based auth system, a schema versioning system, and a background job runner from scratch is a real undertaking. Laravel ships all three as first-class features, documented in the Laravel authentication docs, migrations docs, and queues docs.
Artisan, Laravel’s command-line tool, generates boilerplate (models, controllers, migrations) and runs maintenance tasks, which removes a lot of the manual file-creation overhead raw PHP projects accumulate.
Dependency injection is built into Laravel’s service container, so classes can request what they need through their constructors instead of manually instantiating dependencies everywhere.
This is where Laravel changed my opinion about PHP as a whole. Writing PHP through Laravel’s conventions feels closer to working in a modern framework in any language. Writing PHP without it feels like doing the framework’s job yourself, one file at a time.
To be fair to raw PHP, Laravel isn’t free. It adds an abstraction layer, a learning curve, and a set of conventions you need to accept rather than invent. For a five-page brochure site, pulling in a full framework can be more overhead than the project needs. Raw PHP, or a lightweight approach, is still a reasonable choice there.
Raw PHP vs Laravel: Same Problem, Different Experience
| Task | Raw PHP | Laravel |
|---|---|---|
| Routing | One file per URL, or manual switch on $_SERVER['REQUEST_URI'] | Declarative Route::get() / Route::post() definitions |
| Database queries | Manual PDO prepare/bind/execute/fetch | Eloquent ORM with fluent query methods |
| Input validation | Manual if checks on $_POST | Declarative $request->validate() rules |
| Templating | Mixed PHP/HTML with <?php ?> tags | Blade syntax with {{ }} and @ directives |
| Authentication | Built by hand: sessions, password hashing, guards | Built-in auth scaffolding and guards |
| Background jobs | Custom cron scripts or manual queue tables | Queue system with workers and retries |
| Project structure | Whatever the developer sets up | Enforced convention (MVC-style folders) |
This table isn’t meant to say raw PHP “can’t” do any of this. It can do all of it. The difference is how much of it you have to build yourself versus how much comes configured out of the box.
Modern PHP Has Improved a Lot
I want to spend real space on this because it’s easy to write an article like this and make it sound like PHP stood still. It didn’t.
PHP 8 introduced union types, named arguments, match expressions, and the nullsafe operator. Match expressions alone are a genuine improvement over switch:
php
$message = match ($statusCode) {
200, 201 => 'Success',
404 => 'Not Found',
500 => 'Server Error',
default => 'Unknown Status',
};
No fallthrough bugs, no break statements to forget, and it returns a value directly. This is documented in the PHP match expression manual.
Constructor property promotion (PHP 8.0) cuts down class boilerplate significantly:
php
class Product {
public function __construct(
public string $name,
public float $price,
public string $category,
) {}
}
Before PHP 8, this same class needed separate property declarations and manual assignment inside the constructor — roughly double the lines for the same result.
Enums (PHP 8.1) give PHP a proper way to represent a fixed set of values, something the language lacked for years, as documented in the PHP enumerations manual:
php
enum Status: string {
case Draft = 'draft';
case Published = 'published';
case Archived = 'archived';
}
Attributes (PHP 8.0) give PHP native metadata annotations, similar in spirit to Python decorators or TypeScript decorators, replacing what used to be done through docblock comments that tools had to parse as plain text.
Typed properties and stricter type declarations mean PHP catches more mistakes before runtime than it used to, closing part of the gap with statically typed languages, even though PHP remains fundamentally dynamically typed unless you opt into declare(strict_types=1).
None of this erases my complaints about raw PHP’s syntax overall — the string concatenation, the inconsistent function signatures, the $ prefix — but it’s a real, measurable improvement over PHP from a decade ago. Anyone comparing PHP to modern languages while ignoring PHP 8’s changes isn’t giving an honest comparison.
Where PHP Still Makes Sense
I’m not going to pretend PHP has no place in 2026. It’s cheap and simple to host — most shared hosting providers support PHP out of the box, without the process-management setup Node.js or Python often need. It has an enormous ecosystem of existing code, plugins, and CMS platforms (WordPress being the obvious example) built on it. For small, self-contained scripts or simple server-rendered pages, raw PHP’s request-per-file model is genuinely simple to reason about, and Laravel’s added structure would be overkill.
If you’re maintaining an existing PHP codebase, or building something small where a full framework is unnecessary weight, raw (or lightly structured) PHP is a completely reasonable choice.
Where I Would Personally Avoid Raw PHP
For anything involving multiple routes, a database with more than a couple of tables, user authentication, or background processing, I’d reach for Laravel rather than raw PHP every time. The repeated boilerplate — the PDO blocks, the manual validation, the ad hoc routing — isn’t a hypothetical problem. It’s what raw PHP projects actually look like once they grow past a few pages, and I’ve seen enough of them to know that “we’ll add structure later” rarely happens on schedule.
This isn’t a PHP-specific problem, to be fair. Writing raw Node.js without Express, or raw Python without Flask/Django, has similar friction. The difference is that PHP’s raw, framework-free mode is still common enough — especially in older projects and tutorials — that a lot of developers experience it directly, where “raw Node” or “raw Python” is a much less common starting point today.
So, Is PHP Actually Bad?
No. I don’t think PHP is bad, and I think it’s a mistake to frame it that way. What I think is that raw PHP’s syntax carries decades of inconsistent decisions that modern PHP has partially, not fully, addressed. For me, the biggest problem isn’t that PHP cannot do something; it’s how much manual work and syntax I sometimes have to deal with to get there compared with JavaScript, Python, or PHP wrapped in Laravel.
Laravel is probably the reason I became more comfortable with PHP at all. It takes the same language and gives it conventions, tooling, and abstractions that remove most of the friction I’ve described in this article. That’s the distinction I’d want any developer new to PHP to understand before writing it off, or before assuming raw PHP and “PHP development” are the same thing.
Conclusion
Raw PHP and PHP-through-Laravel are close to two different developer experiences built on the same language. If your only exposure to PHP is raw, file-per-request code, I understand why it feels dated compared with JavaScript or Python — some of that reaction is fair, and some of it is really a framework gap, not a language gap. Modern PHP has closed part of that distance with typed properties, enums, match expressions, and constructor promotion. Laravel closes most of the rest. Whether raw PHP is worth using on its own really comes down to the size and shape of what you’re building.
FAQ
Is PHP syntax outdated compared to JavaScript or Python? Parts of it are showing their age — the required $ prefix on variables and inconsistent core function argument order are long-standing quirks. But PHP 8’s additions (match expressions, enums, constructor promotion, union types) have modernized large parts of the language, so “outdated” applies more to raw, framework-free PHP than to the language as a whole.
Is Laravel the same thing as PHP? No. Laravel is a framework written in PHP. It adds routing, an ORM (Eloquent), validation, authentication, and other structure on top of the PHP language itself. Writing Laravel code is still writing PHP, but with far less manual boilerplate than raw PHP.
Why do developers say raw PHP has a lot of boilerplate? Because without a framework, common tasks — database queries with PDO, input validation, routing — have to be handled manually in every file. Frameworks like Laravel provide built-in abstractions for these tasks, which is why PHP with Laravel often needs noticeably less code than the same task in raw PHP.
Is PHP still worth learning in 2026? Yes, particularly if you plan to work with WordPress, maintain existing PHP applications, or use Laravel. PHP remains widely deployed, and understanding raw PHP also makes it easier to understand what a framework like Laravel is actually doing underneath its abstractions.
Does modern PHP have types like TypeScript? PHP supports type declarations for function parameters, return types, and class properties, plus union types and enums. It’s not identical to TypeScript’s static type system — PHP’s checks generally happen at runtime unless combined with static analysis tools — but it’s a meaningful step beyond PHP’s fully dynamic early versions.
Should I use raw PHP or Laravel for a new project? For a small script or a simple page or two, raw PHP can be simpler and lighter. For anything with multiple routes, a database, user accounts, or background jobs, Laravel’s conventions typically save enough repeated work to be worth the added learning curve and dependencies.
Is PHP slower than Node.js or Python? Performance depends heavily on the specific workload, PHP version, server configuration, and what’s being benchmarked, so it’s not accurate to make a blanket claim either way without a specific, current benchmark. This article intentionally avoids unverified performance claims — check current, reputable benchmarks for your exact use case if performance is a deciding factor.
EXTERNAL SOURCES
- PHP Manual — Language Reference: https://www.php.net/manual/en/langref.php
- PHP Manual — Enumerations: https://www.php.net/manual/en/language.enumerations.php
- PHP Manual — Match Expression: https://www.php.net/manual/en/control-structures.match.php
- PHP Manual — PDO (Database Access): https://www.php.net/manual/en/book.pdo.php
- PHP Manual — Constructor Promotion (Classes and Objects): https://www.php.net/manual/en/language.oop5.decon.php
- PHP Manual — Attributes: https://www.php.net/manual/en/language.attributes.php
- Laravel Documentation — Routing: https://laravel.com/docs/routing
- Laravel Documentation — Eloquent ORM: https://laravel.com/docs/eloquent
- Laravel Documentation — Validation: https://laravel.com/docs/validation
- Laravel Documentation — Authentication: https://laravel.com/docs/authentication
- Laravel Documentation — Migrations: https://laravel.com/docs/migrations
- Laravel Documentation — Queues: https://laravel.com/docs/queues
- Laravel Documentation — Blade Templates: https://laravel.com/docs/blade
- MDN Web Docs — JavaScript Array methods: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
- MDN Web Docs — Template literals: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
- Python Documentation — List comprehensions: https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions
- Node.js Documentation: https://nodejs.org/en/docs
