Stručně: Jmenné prostory jsou v PHP něco jako složky pro názvy tříd: App\Rechnung\Pdf a Bibliothek\Pdf mohou existovat vedle sebe, aniž by si překážely. Autoloading zajišťuje, že PHP načte třídu až ve chvíli, kdy je poprvé potřeba. Standard PSR-4 určuje, jak se z názvu třídy stane cesta k souboru; Composer pak vytvoří odpovídající autoloader. Protože tento autoloader běží při každém požadavku, patří po bezpečnostním incidentu k prvním věcem, které se kontrolují.
Proč PHP potřebuje jmenné prostory
Jmenné prostory existují od PHP 5.3.0. Předtím sdílely všechny třídy a funkce jediný globální jmenný prostor. Pokud se dvě knihovny jmenovaly Logger, došlo ke kolizi. Vývojáři si pomáhali dlouhými názvy jako Firma_Modul_Unterbereich_Logger.
Příručka PHP uvádí dva problémy, které jmenné prostory řeší: kolize názvů mezi vlastním kódem, interními třídami PHP a knihovnami třetích stran a příliš dlouhé názvy, které vznikly jen proto, aby se těmto kolizím předešlo. Pro rozhodovatele to znamená: teprve jmenné prostory umožnily prakticky kombinovat v jednom projektu desítky cizích balíčků, jak je dnes s Composerem běžné.
Jmenné prostory v praxi: namespace, use, aliasy
Soubor deklaruje svůj jmenný prostor v prvním příkazu. Pomocí use se přebírají třídy z jiných jmenných prostorů, podle přání pod kratším názvem.
<?php
declare(strict_types=1);
namespace App\Rechnung;
use App\Kunde\Kunde;
use Psr\Log\LoggerInterface as Log;
final class PdfErzeuger
{
public function __construct(private Log $log) {}
public function erzeugen(Kunde $kunde): string
{
$this->log->info('PDF erzeugt', ['kunde' => $kunde->id]);
return \sprintf('Rechnung für %s', $kunde->name);
}
}
Úplný název této třídy je App\Rechnung\PdfErzeuger. Úvodní zpětné lomítko u \sprintf výslovně odkazuje na globální funkci PHP.
Autoloading: načítání tříd podle potřeby
Bez autoloadingu by musel každý skript vkládat všechny potřebné soubory pomocí require. Pomocí spl_autoload_register() se místo toho zaregistruje funkce, kterou PHP zavolá, jakmile se použije neznámá třída, rozhraní, trait nebo enum. Funkce dostane název třídy a načte odpovídající soubor.
<?php
spl_autoload_register(function (string $klasse): void {
$praefix = 'App\\';
$basis = __DIR__ . '/src/';
if (!str_starts_with($klasse, $praefix)) {
return; // nejsem příslušný, na řadě je další autoloader
}
$datei = $basis . str_replace('\\', '/', substr($klasse, strlen($praefix))) . '.php';
if (is_file($datei)) {
require $datei;
}
});
spl_autoload_register() lze volat opakovaně. PHP pak registrované autoloadery vyzývá jeden po druhém, dokud některý třídu nenačte. V praxi mají velké aplikace často více autoloaderů: autoloader Composeru pro knihovny, vlastní autoloader CMS a další od pluginů nebo modulů. Pro administrátory z toho plyne: jaký kód se při požadavku skutečně načte, vyplývá ze součinnosti všech těchto autoloaderů, a ne jen ze struktury složek.
Příručka důrazně nedoporučuje v autoloaderu vyhazovat výjimky; PSR-4 to výslovně zakazuje. Starší funkce __autoload() byla v PHP 7.2.0 označena za zastaralou a v PHP 8.0.0 odstraněna.
PSR-4: od názvu třídy k cestě k souboru
PSR-4 je standard PHP-FIG. Úplný název třídy začíná jmenným prostorem výrobce. Prefix jmenného prostoru se přiřadí základnímu adresáři, zbytek názvu dává podsložky a soubor končí na .php. Velká a malá písmena se musí přesně shodovat. Takto vypadá přiřazení v composer.json:
{
"autoload": {
"psr-4": { "App\\": "src/" }
}
}
| Název třídy | Soubor |
|---|---|
App\Kunde\Kunde |
src/Kunde/Kunde.php |
App\Rechnung\PdfErzeuger |
src/Rechnung/PdfErzeuger.php |
App\Rechnung\Pdf\Vorlage |
src/Rechnung/Pdf/Vorlage.php |
Klasickým úskalím je velikost písmen. Na vývojových počítačích s Windows nebo macOS souborový systém často nerozlišuje mezi Kunde.php a kunde.php, na serveru s Linuxem ano. Aplikace, která běží lokálně, pak na serveru třídu nenajde. Pravidlo z PSR-4, používat názvy přesně ve správném pravopisu, takovým překvapením při nasazení předchází.
Vedle psr-4 zná Composer classmap pro kód bez standardní struktury a files pro soubory, které se načítají při každém požadavku, například pomocné funkce. Další standardy PHP-FIG popisuje stránka Standardy PSR.
Optimalizace autoloaderu v produkci
V produkci autoloader nejlépe nehledá v souborovém systému, ale vyhledává v hotovém seznamu. Composer k tomu nabízí dva stupně:
composer install --no-dev --optimize-autoloaderpřevede pravidla PSR-4 na seznam tříd. Podle dokumentace Composeru se to pro produkci doporučuje.--classmap-authoritativejde dál: třídy se načítají výhradně ze seznamu. Soubory, které v něm nejsou, autoloader vůbec nenačte.- Při každém nasazení se seznam vytvoří znovu, aby se nové třídy našly.
Autoloading a bezpečnost po PHP2Shell
PHP2Shell se týká interpretu PHP, ne autoloaderu. Povinná zůstává aktualizace na PHP 8.5.12, 8.4.27, 8.3.36 nebo 8.2.35 a restart všech workerů. Po možném incidentu si ale autoloader zaslouží zvláštní pozornost: vendor/autoload.php a všechny soubory uvedené v files se spouštějí při každém požadavku. Řádek, který sem útočník propašuje, tedy působí na celém webu, aniž by ve složce s nahranými soubory ležel nápadný nový soubor.
Zkontrolujte proto, zda vendor a soubory autoloadu odpovídají ověřenému stavu ze správy verzí. Užitečné jsou články Hledání nedávno změněných souborů a Porovnání jádra CMS s originálem. V případě pochybností je čerstvě vytvořený vendor ze zámkového souboru bezpečnější než jakákoli jednotlivá oprava.
Jak konkrétně pomáhá PHP SOS?
- Porovnáme
vendor, autoloader a vstupní soubory s ověřeným stavem a označíme každou odchylku. - Obnovíme stav balíčků ze zámkového souboru a nastavíme optimalizovaný autoloader.
- Zkontrolujeme, zda je ve webrootu jen veřejná složka a zdrojový kód není dosažitelný přímo přes URL.
- Při napadení převezmeme vyčištění v rámci odstranění malwaru a výsledek zdokumentujeme.
Časté dotazy
Od kdy existují jmenné prostory v PHP?
Od PHP 5.3.0. Podle příručky řeší dva problémy: kolize názvů mezi vlastním kódem, PHP a knihovnami třetích stran a příliš dlouhé názvy tříd, které vznikaly jen proto, aby se takovým kolizím předešlo.
Jaký je rozdíl mezi PSR-0 a PSR-4?
Oba přiřazují názvy tříd cestám k souborům. U PSR-4 se prefix jmenného prostoru v cestě k souboru neopakuje, což dává plošší adresářovou strukturu. PSR-0 je u PHP-FIG označeno jako zastaralé, PSR-4 je doporučený standard.
Funguje __autoload() ještě?
Ne. __autoload() bylo v PHP 7.2.0 označeno za zastaralé a v PHP 8.0.0 odstraněno. Dnes se autoloadery registrují pomocí spl_autoload_register(), v praxi většinou přes autoloader vytvořený Composerem.
Musím po vytvoření nové třídy autoloader znovu vygenerovat?
U čistých pravidel PSR-4 ne, podle dokumentace Composeru je to právě výhoda PSR-4. Kdo v produkci používá optimalizovaný seznam tříd, musí ho při každém nasazení vytvořit znovu.