PHP Timezone Europe/London (date_default_timezone_set)
Use Europe/London in PHP the safe way: date_default_timezone_set, DateTimeImmutable, GMT vs BST, store UTC, display dd/mm/YYYY, and avoid DST surprises.
Composer PSR-4 autoload means: you declare a namespace prefix and a folder in composer.json, run composer dump-autoload, then require 'vendor/autoload.php' once at the start of your app. After that, new App\Billing\Invoice loads src/App/Billing/Invoice.php automatically — no hand-written require for each class. The namespace path must mirror the directory path after the prefix. If they disagree, you get “Class not foundâ€.
This is the packaging half of plain PHP apps that grow past a single file. It sits beside PDO, sessions and validation without replacing them. The demo below uses Composer 2.10 and PHP 8.4; you can follow along with a tiny Invoice class.
Early PHP projects look like:
<?php
require_once __DIR__ . '/lib/Database.php';
require_once __DIR__ . '/lib/User.php';
require_once __DIR__ . '/lib/Invoice.php';
That list rots. Forget one file and you get fatal errors only on certain pages. An autoloader loads a class the first time you reference it. Composer generates that autoloader from a clear convention (PSR-4) so every dependency you install (composer require ...) is available the same way.
PSR-4 is the modern mapping: namespace prefix → base directory. PSR-0 is an older underscored style; new projects should use PSR-4.
project/
composer.json
src/
App/
Billing/
Invoice.php
public/
index.php
vendor/ (created by Composer)
autoload.php
public/index.php is the web entry point. Application code lives under src/App so the document root does not expose your classes directly.
{
"name": "mfaysal/autoload-demo",
"autoload": {
"psr-4": {
"App\\": "src/App/"
}
}
}
Notes:
"App\\" is the namespace prefix. The double backslash is JSON escaping for a single \."src/App/" is the folder relative to composer.json. Trailing slash is conventional.App\Billing\Invoice therefore must live at src/App/Billing/Invoice.php.Invoice.php not invoice.php.<?php
declare(strict_types=1);
namespace App\Billing;
final class Invoice
{
public function __construct(
public readonly string $number,
public readonly int $pence,
) {}
public function formatGbp(): string
{
return '£' . number_format($this->pence / 100, 2);
}
}
The first line after declare is namespace App\Billing; — that must match the folder Billing under the App\ root. The class name must match the file name.
composer dump-autoload
# or: php composer.phar dump-autoload
Then in public/index.php or a CLI script:
<?php
declare(strict_types=1);
require __DIR__ . '/../vendor/autoload.php';
use App\Billing\Invoice;
$inv = new Invoice('INV-1001', 1999);
echo $inv->number, ' = ', $inv->formatGbp(), PHP_EOL;
Test output:
INV-1001 = £19.99
class loaded from: /workspace/seo-posts-php-sched/test/post05/src/App/Billing/Invoice.php
One require of vendor/autoload.php is enough per request. Do not require individual class files afterwards unless you are debugging.
For production deployments, composer install --no-dev -o generates an optimised classmap so lookups are faster. During development, plain dump-autoload is enough; Composer also registers a more flexible autoloader that finds new classes after you add files (you still re-dump after changing composer.json mappings).
You do not edit these by hand:
vendor/autoload.php — the entry point you requirevendor/composer/autoload_psr4.php — prefix → directory mapvendor/composer/autoload_classmap.php — when optimised, class → fileYour job is to keep composer.json truthful and run dump-autoload when the map changes.
{
"autoload": {
"psr-4": {
"App\\": "src/App/",
"Tools\\": "src/Tools/"
},
"files": [
"src/helpers.php"
]
}
}
files entries are always loaded when the autoloader is required — useful for a handful of functions (for example a global e() helper from the htmlspecialchars post). Prefer namespaced classes for most code so nothing loads until needed.
Dev-only autoload (test helpers) goes under "autoload-dev".
require vendor/autoload.php in that entry script.namespace App\Billing but file in src/App/Billling/."App\\": "src/" while files live in src/App/... (would expect App\App\Billing\...).composer dump-autoload after editing composer.json.vendor/autoload.php — use __DIR__.composer dump-autoload -o.Typical bootstrap:
<?php
declare(strict_types=1);
require __DIR__ . '/../vendor/autoload.php';
date_default_timezone_set('Europe/London'); // see timezone post
session_start();
$pdo = App\Database\Connection::make(); // your PDO wrapper
Then controllers use PDO (prepared statements), sessions (login example), and escaping on output. Composer does not secure your app; it only loads classes. Keep secrets out of the repo — environment variables rather than hard-coded DSN passwords — and validate uploads if you accept files (upload validation).
Under the hood PSR-4 is “turn namespace into pathâ€. A minimal sketch:
<?php
spl_autoload_register(function (string $class): void {
$prefix = 'App\\';
$base = __DIR__ . '/src/App/';
if (strncmp($prefix, $class, strlen($prefix)) !== 0) {
return;
}
$relative = substr($class, strlen($prefix));
$file = $base . str_replace('\\', '/', $relative) . '.php';
if (is_file($file)) {
require $file;
}
});
Composer’s generated autoloader is more complete (classmaps, multiple packages, plugins). Use Composer in real projects; the sketch is only to demystify it.
composer.json in the wrong directory relative to src/./vendor is public — point the vhost at public/ instead.vendor/ on some teams vs not — either works if deployment runs composer install; do not commit secrets.require trees with half-autoloaded code.use App\Billing\Invoice; and then referencing Invoice in the global namespace.No. Autoload pays off once you have multiple classes or third-party libraries. For one-off scripts, a single file is fine.
PSR-4 maps prefixes to directories and discovers classes by path. A classmap lists concrete class → file pairs (faster, generated). Composer can build a classmap from your PSR-4 tree with -o.
Both are valid. "App\\": "src/" means App\Billing\Invoice → src/Billing/Invoice.php. "App\\": "src/App/" means src/App/Billing/Invoice.php. Pick one and stay consistent; I like the second when other top-level folders sit beside App.
Use "autoload-dev": { "psr-4": { "Tests\\": "tests/" } } and require the same vendor/autoload.php from PHPUnit.
No — it depends on namespaces (or a classmap). Without a namespace, Composer can still autoload via classmap, but PSR-4 expects namespaces.
Run composer install --no-dev -o in CI or on your laptop, deploy the generated vendor/ directory, and keep PHP versions compatible. Still develop with Composer locally.
cd project
composer init # or write composer.json by hand
# add the autoload psr-4 block, then:
composer dump-autoload
composer require monolog/monolog # example third-party lib
After composer require, the package’s own PSR-4 prefixes are merged into vendor/composer/autoload_psr4.php. Your App\ mapping stays intact. Commit composer.json and composer.lock; whether you commit vendor/ depends on the team. Many PHP hosts run composer install --no-dev -o on deploy.
public/index.php -> requires vendor/autoload.php, routes request
src/App/Http/... -> controllers
src/App/Domain/... -> business logic
Apache/nginx should set the document root to public/ so that /vendor and /src are not reachable by URL. That is an ops concern as much as an autoload concern — autoload makes more PHP files exist; the web server decides which are executable via HTTP.
If you add src/helpers.php with function e(): string, list it under "files". Those functions load on every request that pulls in the autoloader — keep the file tiny. Prefer App\Support\Str::e() if you want lazy loading.
Composer’s platform config can pin "php": ">=8.2". Run installs on the same major version you deploy. Autoload itself rarely breaks across versions; typed properties and readonly in your classes might. Test composer install in CI on the target PHP, same as I ran the Invoice demo on 8.4.
vendor/autoload.php is wrong.vendor/.vendor/.-o classmap — rebuild autoload on deploy.Once that checklist is muscle memory, Composer fades into the background and you spend time on sessions, uploads and XSS instead of require_once archaeology.
composer.json with the App\\ mapping.src/App/... matching namespaces.namespace declarations to each file.require_once bootstrap with vendor/autoload.php.includes/ tree.Do not rename everything in one untested commit. Keep a branch. If a class is referenced as a string ('Invoice' in a config), update those strings to FQCNs (App\Billing\Invoice).
In production, PHP’s opcache caches compiled bytecode. After deploy, reload PHP-FPM so both new files and a new Composer classmap are visible. A half-reloaded server shows intermittent class-not-found errors that look like autoload bugs but are really cache.
vendor/ contains third-party code. Run composer audit occasionally, pin versions in composer.lock, and do not expose the directory on the web. Autoloading a package does not mean you trust its security history blindly — especially for packages that touch auth, crypto or file parsing.
| Class | Prefix App\ → src/App/ | File |
|-------|----------------------------|------|
| App\Billing\Invoice | | src/App/Billing/Invoice.php |
| App\Http\LoginController | | src/App/Http/LoginController.php |
| App\Support\e (invalid) | functions are not PSR-4 classes | use files or a class method |
| Class | Prefix App\ → src/ | File |
|-------|------------------------|------|
| App\Billing\Invoice | | src/Billing/Invoice.php |
Pick one column style for the whole repo. Mixing both is the fastest route to class-not-found.
{
"scripts": {
"dump": "composer dump-autoload -o",
"test": "phpunit"
}
}
composer test then runs your suite. Autoload-dev must include test namespaces. This keeps local and CI commands identical.
Laravel, Symfony and Slim all assume Composer autoload from day one. Learning PSR-4 on a tiny plain PHP demo — like the Invoice class above — makes their directory layouts less magical. When you later composer create-project a framework, you already know why app/ or src/ appears in composer.json.
<?php
declare(strict_types=1);
require dirname(__DIR__) . '/vendor/autoload.php';
use App\Billing\Invoice;
header('Content-Type: text/plain; charset=utf-8');
echo (new Invoice('INV-1001', 1999))->formatGbp();
Point the vhost at public/, run composer dump-autoload, open the site, and you should see £19.99. From there, grow folders under App\ for HTTP, domain and infrastructure without inventing a new loading system each time.
Composer PSR-4 is a naming contract: namespaces mirror directories, one autoload entry boots them all, and third-party packages join the same mechanism. Once that contract holds, you can focus on behaviour — passwords, sessions, uploads, timezones — instead of file plumbing.
composer.json has a PSR-4 prefix ending in \\.composer dump-autoload has been run after map changes.vendor/autoload.php via __DIR__.public/, not the project root.composer install --no-dev -o (or ships a built vendor/).Follow that list and “class not found†becomes a rare, usually typo-shaped event.
Map the prefix, mirror folders to namespaces, require vendor/autoload.php once, and dump the autoloader when the map changes. That is the whole Composer PSR-4 habit for small PHP apps.
// note
This is a learning note from studying the web. It is one small topic, written so I can remember it. It is not a course and not a claim that I have finished the subject.
If a sentence is wrong, say so from the contact page and name this title. Drafts never appear here. Related notes, when they exist, are other published posts, and the same sample rule applies to each of them.
Use Europe/London in PHP the safe way: date_default_timezone_set, DateTimeImmutable, GMT vs BST, store UTC, display dd/mm/YYYY, and avoid DST surprises.
Validate PHP uploads with UPLOAD_ERR_OK, finfo MIME detection, size limits and random names — and ignore $_FILES type and the original filename.
A tested PHP login with sessions and PDO: password_verify, session_regenerate_id on success, a protected dashboard, and a logout that clears the session cookie.