Jay GajjarSenior Software Engineer

Core PHP vs Laravel: when plain PHP still makes sense, and when a framework wins

Core PHP vs Laravel: when plain PHP is the right tool, why frameworks win on security and hiring, and how to modernize a core PHP codebase step by step.

By Jay Gajjar10 min read

Core PHP vs Laravel is not a contest between a fast language and a slow framework: Laravel is written in PHP, and both run on the same engine. The real question is whether you want to build and maintain routing, security, database access and structure yourself, or start from conventions that thousands of developers already know. For tiny scripts and some legacy systems, plain PHP is the right tool. For almost any business application that will grow, change hands or handle money and personal data, a framework wins. I have built projects from scratch in core PHP and work in Laravel every day, so here is how I decide, and how to modernize a core PHP codebase without a risky rewrite.

Quick answer: core PHP vs Laravel or another framework?

  • Use plain PHP for small, self-contained jobs: a webhook receiver, a cron script, a one-page form handler, a command-line tool, a library you will publish, or a small fix inside an existing core PHP system.
  • Use a framework (usually Laravel) for anything with logins, roles, an admin area, payments, integrations, background jobs or more than one developer over its lifetime.
  • If you already run a large core PHP app, do not rewrite it in one go. Upgrade PHP, add Composer and autoloading, switch to prepared statements, put a router in front, and then decide whether to keep modernizing or migrate module by module.

What "core PHP" actually means

"Core PHP" usually means PHP written without a full-stack framework: files like login.php, orders.php and includes/db.php, with routing done by the file system and HTML mixed with queries. It does not have to mean no libraries at all. A well-built plain PHP project can use Composer packages for routing, templating and email while still having no framework.

That distinction matters, because "core PHP vs framework" is really a spectrum:

Approach What you get What you write yourself
Classic core PHP Nothing beyond PHP itself Everything: routing, escaping, CSRF, sessions, validation, structure
Plain PHP with Composer packages Chosen components (a router, a template engine, a mailer) The glue, the structure and the security decisions
Micro-framework (Slim and similar) Routing, middleware, request and response objects Database layer, auth, validation, queues
Full-stack framework (Laravel, Symfony, CodeIgniter 4) Routing, ORM, validation, auth scaffolding, queues, testing tools Your business logic

Is core PHP still used?

Yes, a lot. Huge numbers of small business systems, internal tools and older web apps run on plain PHP, and many will keep running for years. WordPress itself has its own architecture rather than a third-party framework. Cron scripts, payment webhook handlers and small utilities are often a single PHP file, and that is fine.

What has changed is new development. For new business applications, most PHP teams start with a framework, mainly Laravel or Symfony, because the security defaults, structure and tooling save time on every project. So core PHP is still used widely, but more and more of it is code that people maintain rather than code they start.

Core PHP vs framework: side by side

Area Core PHP Laravel
Setup Upload files and go composer create-project, a .env file, a document root pointing at public/
Routing File per page, or a hand-written router Declared routes with parameters, middleware and named URLs
Database PDO or mysqli, safe only if you always use prepared statements Query builder and Eloquent with parameter binding by default
Output escaping Manual htmlspecialchars() every time Blade escapes {{ }} automatically
CSRF protection You build it On by default for web routes
Passwords password_hash() if the developer knows to use it Hashing built into auth scaffolding
Validation Hand-written checks Declarative rules with error messages
Background work Cron plus custom scripts Queues, retries and a scheduler
Testing Possible, rarely set up PHPUnit or Pest with HTTP and database helpers
Hiring and handover Each codebase has its own structure Any Laravel developer knows where things live
Upgrades Only PHP itself One major release a year with a published support window

When plain PHP is the right choice

Plain PHP is a good choice when the job is small and will stay small. Some examples:

  • A webhook endpoint that verifies a signature, stores the payload and returns 200.
  • A cron script that pulls a CSV from an SFTP server and imports it.
  • A one-page tool, such as a calculator or a single contact form on a static site.
  • A library or Composer package that should not depend on any framework.
  • A fix or feature inside an existing core PHP system, where adding a framework for one screen would cost more than it saves.
  • Learning. Writing a small app in plain PHP first teaches you what a framework is doing for you.

Even in a single file, use modern PHP habits: strict types, prepared statements and escaped output. A minimal, safe form handler looks like this:

<?php

declare(strict_types=1);

session_start();

$pdo = new PDO(
    'mysql:host=localhost;dbname=site;charset=utf8mb4',
    getenv('DB_USER') ?: '',
    getenv('DB_PASS') ?: '',
    [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES => false,
    ]
);

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $sent = $_POST['csrf'] ?? '';

    if (! isset($_SESSION['csrf']) || ! is_string($sent) || ! hash_equals($_SESSION['csrf'], $sent)) {
        http_response_code(419);
        exit('Session expired, please reload the page.');
    }

    $email = filter_var($_POST['email'] ?? '', FILTER_VALIDATE_EMAIL);

    if ($email !== false) {
        $stmt = $pdo->prepare('INSERT INTO leads (email, created_at) VALUES (:email, NOW())');
        $stmt->execute(['email' => $email]);
    }
}

$_SESSION['csrf'] ??= bin2hex(random_bytes(32));
?>
<form method="post">
    <input type="hidden" name="csrf" value="<?= htmlspecialchars($_SESSION['csrf'], ENT_QUOTES) ?>">
    <input type="email" name="email" required>
    <button type="submit">Subscribe</button>
</form>

Notice how much of that file is security plumbing. That plumbing is exactly what a framework gives you for free on every page.

Core PHP vs Laravel performance: the speed myth

The most common argument for core PHP is speed. It is true that a bare plain PHP script loads less code than a framework, so a "hello world" benchmark favors it. It is also mostly irrelevant for business software.

In real applications, response time is decided by database queries, missing indexes, external API calls, and work that should have been queued. A core PHP page that runs 200 queries in a loop is slower than a Laravel page that runs three. Laravel also gives you tools to close the gap: OPcache in production, php artisan optimize to cache config, routes and views, queues for slow work, and Octane for very high traffic. My Laravel performance checklist covers those steps in order.

If raw throughput truly is the constraint, measure your own workload before choosing. Do not pick a stack on a benchmark someone else ran.

When a framework wins

Security defaults

The biggest difference between plain PHP vs Laravel is what happens when a developer forgets something. In core PHP, a forgotten htmlspecialchars() is an XSS hole, a concatenated query is SQL injection, and a form without a token is open to CSRF. In Laravel, output is escaped, queries are bound and CSRF is checked unless someone deliberately turns it off.

The patterns that show up again and again in older core PHP code:

  • SQL built by string concatenation, sometimes still using the removed mysql_* functions.
  • Passwords stored with md5() or sha1() instead of password_hash().
  • include $_GET['page'] . '.php'; style routing that can expose files.
  • Database credentials in a file inside the web root, committed to version control.
  • No CSRF tokens on forms that change data.
  • Error display turned on in production, leaking paths and queries.

Maintainability

A framework gives every project the same shape. Controllers, models, migrations, config and tests live in predictable places. Six months later, you or someone else can find the code that sends an invoice email without reading the whole project.

Hiring and handover

This is the argument business owners feel most. Any experienced Laravel developer can open a Laravel project and be productive in days. A custom core PHP codebase needs its original author, or weeks of reading. If you are hiring, my guide on how to hire a Laravel developer explains what to look for.

The features you would otherwise build

Queues, scheduled tasks, mail, file storage, rate limiting, API authentication, database migrations and testing helpers all come in the box. Each one is a week or more of work in core PHP, and then it is your code to maintain.

Should I use a PHP framework? A checklist

Answer these honestly. If two or more are "yes", use a framework.

  • Will users log in, with more than one role?
  • Will it take payments or store personal data?
  • Will it call external APIs or receive webhooks from several services?
  • Does any task take longer than a few seconds (imports, PDFs, emails in bulk)?
  • Will another developer ever work on it?
  • Do you expect it to grow for more than a year?

If you are choosing between frameworks rather than for or against one, see Laravel vs CodeIgniter for a direct comparison.

How to modernize a core PHP codebase gradually

Rewriting a working system from scratch is the riskiest option. A gradual path keeps the business running while each step makes the code safer. This is the order I follow.

1. Get it under version control and onto supported PHP

Put the code in Git, set up a staging copy, and take database backups. Then move to a supported PHP version. As of October 2026, PHP 8.2 gets security fixes only until December 31, 2026, and 8.3, 8.4 and 8.5 are supported for longer, per the official PHP supported versions page (opens in a new tab). The PHP 8 upgrade checklist lists the errors older code usually hits.

2. Add Composer and PSR-4 autoloading

Composer lets you install libraries and load your own classes without a wall of require lines. Create composer.json at the project root:

{
    "require": {
        "php": "^8.3",
        "nikic/fast-route": "^1.3",
        "vlucas/phpdotenv": "^5.6"
    },
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    }
}

Run composer install, then add require __DIR__ . '/vendor/autoload.php'; to your entry files. New classes go in src/ with the namespace App\..., following PSR-4 (opens in a new tab). Old procedural files keep working beside them.

3. Move secrets out of the code

Put credentials in a .env file outside version control and load them with phpdotenv:

$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
$dotenv->load();
$dotenv->required(['DB_HOST', 'DB_NAME', 'DB_USER', 'DB_PASS']);

Then rotate the old passwords, because they are in your Git history.

4. Replace concatenated SQL with PDO prepared statements

This is the single most valuable security fix. Create one shared PDO connection and change queries file by file:

// Before: injectable
$result = mysqli_query($conn, "SELECT * FROM invoices WHERE customer_id = " . $_GET['id']);

// After: bound parameters
$stmt = $pdo->prepare('SELECT id, number, total FROM invoices WHERE customer_id = :customer AND status = :status');
$stmt->execute(['customer' => (int) $_GET['id'], 'status' => 'unpaid']);
$invoices = $stmt->fetchAll();

The PHP manual on prepared statements (opens in a new tab) explains why bound parameters cannot be used for injection.

5. Fix password storage on the next login

You cannot reverse old hashes, but you can upgrade them as users log in:

$ok = str_starts_with($user['password'], '$')
    ? password_verify($password, $user['password'])
    : hash_equals($user['password'], md5($password)); // legacy rows

if ($ok && (! str_starts_with($user['password'], '$') || password_needs_rehash($user['password'], PASSWORD_DEFAULT))) {
    $stmt = $pdo->prepare('UPDATE users SET password = :hash WHERE id = :id');
    $stmt->execute(['hash' => password_hash($password, PASSWORD_DEFAULT), 'id' => $user['id']]);
}

6. Put a router in front, and leave old files alone

A front controller lets new pages use clean URLs and classes, while existing .php files keep being served directly. With Apache, this .htaccess sends only requests for files that do not exist to index.php:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]

Then index.php dispatches new routes with FastRoute:

<?php

declare(strict_types=1);

require dirname(__DIR__) . '/vendor/autoload.php';

use App\Http\InvoiceController;
use FastRoute\Dispatcher;
use FastRoute\RouteCollector;

use function FastRoute\simpleDispatcher;

$dispatcher = simpleDispatcher(function (RouteCollector $r): void {
    $r->addRoute('GET', '/invoices', [InvoiceController::class, 'index']);
    $r->addRoute('GET', '/invoices/{id:\d+}', [InvoiceController::class, 'show']);
});

$path = rawurldecode(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) ?: '/');
$route = $dispatcher->dispatch($_SERVER['REQUEST_METHOD'], $path);

switch ($route[0]) {
    case Dispatcher::FOUND:
        [$class, $method] = $route[1];
        echo (new $class())->$method(...array_values($route[2]));
        break;
    case Dispatcher::METHOD_NOT_ALLOWED:
        http_response_code(405);
        echo 'Method not allowed';
        break;
    default:
        http_response_code(404);
        echo 'Not found';
}

Each time you rebuild a page as a controller, delete the old file and add a redirect from the old URL. The route parameter arrives as a string, so type controller arguments as string and cast inside.

7. Add tests around what earns money

Before touching checkout, invoicing or login, write a few tests that hit those pages and check the results. PHPUnit or Pest both work without a framework. They are your safety net for every later step.

8. Decide: keep modernizing, or move to Laravel

After these steps you have a safer core PHP app with Composer, classes and a router. Some systems can happily stay there. If the app keeps growing, the same structure makes a phased move to Laravel much easier, because your logic is already in classes you can carry across. You can run Laravel for new modules while old ones keep working, and move them one at a time. That phased approach is how I handle CodeIgniter to Laravel migration, and it applies to core PHP just as well.

Frequently asked questions

Is core PHP still used in 2026?

Yes. Plenty of business systems, internal tools and scripts run on plain PHP and will for years. Most new business applications, though, start on a framework such as Laravel or Symfony because of the security defaults and faster development.

Is Laravel slower than core PHP?

Laravel loads more code per request, so a minimal benchmark favors plain PHP. In real applications the database, caching and external calls decide speed, and a Laravel app with good queries, caching and queues is as fast as users will notice.

Should I learn core PHP before Laravel?

Learn the basics first: variables, arrays, functions, classes, PDO and how HTTP requests work. Then move to Laravel. Knowing what a framework does for you makes you better at using it and at debugging it.

Can I convert a core PHP website to Laravel?

Yes, and it is usually best done in phases. Move the database access and business logic into classes first, then rebuild screens in Laravel module by module while the old pages keep running, rather than switching everything in one release.

Is core PHP secure?

Core PHP can be secure, but only if the developer handles escaping, CSRF, prepared statements, password hashing and session settings on every page. A framework does most of that by default, which is why it is safer for teams and long-lived projects.

Picking the right foundation

Core PHP vs Laravel is a choice about who carries the plumbing: you, or the framework. For a script, carry it yourself. For a business system, let Laravel carry it and spend your time on the features. If you have a core PHP app that needs a safe path forward, or a new project to start on the right foundation, I can help through custom PHP development or Laravel development.