The error hits you out of nowhere
Your app was running fine. You pulled the latest code, ran a quick deploy, and now every page throws:
Fatal error: Uncaught Error: Class 'App\Services\PaymentService' not found in /var/www/html/src/Controllers/CheckoutController.php:42
Stack trace:
#0 {main}
thrown in /var/www/html/src/Controllers/CheckoutController.php on line 42
Or maybe it's a third-party class like Stripe\Charge or Carbon\Carbon. Either way, PHP can't locate the class and execution stops cold.
Six root causes cover 99% of these failures. Work through them in this order โ each step eliminates the next most likely culprit.
Step 1: Check if the file physically exists
Before blaming autoload, confirm the class file is actually on disk:
# Replace with the actual class path
find /var/www/html -name "PaymentService.php" -type f
No output means the file is gone. It might never have been committed, got deleted, or lives on a branch that wasn't merged. Git can tell you:
git log --all --full-history -- src/Services/PaymentService.php
File exists but PHP still can't find it? Move to Step 2.
Step 2: Verify the namespace matches the directory structure
Namespace mismatches cause this error more than anything else. PHP's PSR-4 autoloader maps namespaces to directories โ if they don't align exactly, the class is invisible to PHP even when the file is sitting right there.
Open the class file and check the declared namespace:
<?php
// src/Services/PaymentService.php
namespace App\Services; // โ must match the directory path
class PaymentService
{
// ...
}
Then check composer.json to see how PSR-4 is wired:
{
"autoload": {
"psr-4": {
"App\\": "src/" // โ App\ maps to src/
}
}
}
With this config, App\Services\PaymentService must live at src/Services/PaymentService.php. Three mismatches show up constantly:
- File is at
src/services/PaymentService.php(lowercases) โ Linux filesystems are case-sensitive, Windows isn't, so it works on your laptop and explodes on the server - Namespace says
App\Service(singular) but the directory isServices(plural) - Extra folder levels in the path that aren't reflected in the namespace
Fix the namespace or move the file to match, then regenerate the autoloader.
Step 3: Regenerate the Composer autoloader
Composer builds a classmap at install time and caches it. Add new files or rename namespaces without regenerating, and PHP has no idea those classes exist.
# Standard regeneration
composer dump-autoload
# Optimized for production (classmap of every class)
composer dump-autoload --optimize
# Not sure what's registered? Check the generated file
cat vendor/composer/autoload_psr4.php
Run dump-autoload, reload the page. Error gone? Stale autoload cache was the culprit โ takes 3 seconds to fix and wastes hours when you don't think of it.
Step 4: Check the use statement in the consuming class
A correct file and a valid namespace still won't save you if the use statement has a typo:
<?php
namespace App\Controllers;
// Wrong โ singular instead of plural
use App\Service\PaymentService;
// Right
use App\Services\PaymentService;
class CheckoutController
{
public function checkout()
{
$payment = new PaymentService(); // Class 'App\Service\PaymentService' not found
}
}
Also watch for a missing use statement entirely. Without an import, PHP looks for the class in the current namespace and fails:
<?php
namespace App\Controllers;
// Missing: use Carbon\Carbon;
class ReportController
{
public function generate()
{
$date = new Carbon(); // Fatal error: Class 'App\Controllers\Carbon' not found
}
}
Step 5: For non-Composer projects, check manual require paths
Legacy codebase, custom framework, no Composer? The class file must be explicitly required before anything touches it:
<?php
// Before using MyClass anywhere
require_once __DIR__ . '/../lib/MyClass.php';
$obj = new MyClass();
Or wire a manual autoloader so you're not writing require_once everywhere:
<?php
spl_autoload_register(function ($className) {
$file = __DIR__ . '/lib/' . str_replace('\\', '/', $className) . '.php';
if (file_exists($file)) {
require_once $file;
}
});
Step 6: Vendor package class missing after install
When the missing class belongs to a package โ Stripe, Guzzle, Monolog โ the problem is usually that the package was never installed, or the vendor/ directory didn't make it to the server:
# Check if the package is actually listed
composer show | grep stripe
# Not there? Install it
composer require stripe/stripe-php
# vendor/ looks incomplete (partial FTP upload, partial deploy)
composer install --no-dev
FTP deployments are notorious for this. Someone uploads the app files, forgets vendor/, and wonders why production is broken. Always deploy the entire vendor/ directory, or run composer install directly on the server.
Verify the fix
Don't go straight to the browser. Test from the CLI first โ it gives you a clean error without any web framework noise:
# Try to instantiate the class directly
php -r "require 'vendor/autoload.php'; new App\\Services\\PaymentService();"
# Or use reflection to check if PHP can load it
php -r "
require 'vendor/autoload.php';
if (class_exists('App\\Services\\PaymentService')) {
echo 'Class found OK' . PHP_EOL;
} else {
echo 'Still not found' . PHP_EOL;
}
"
You want Class found OK. Anything else means the namespace or file path is still off.
Quick-reference checklist
- File physically exists at the expected path:
findorls - Namespace in the file matches the PSR-4 mapping in
composer.json - Directory and file name casing match the namespace exactly (watch out on Linux)
usestatement in the calling file is correct and present- Ran
composer dump-autoloadafter any file or namespace change vendor/directory is complete and up to date on the server
The pattern behind the 2 AM pages
Almost every midnight incident traces back to one of three things. A deploy that didn't include vendor/. A case sensitivity mismatch that only surfaces on Linux. Or a namespace rename where someone forgot to run dump-autoload.
The fix for all three: add composer dump-autoload --optimize to your deploy script. One line, and you eliminate an entire category of these incidents permanently.

