Przestrzenie nazw i autoloading
🎯 Po tej lekcji zorganizujesz kod w przestrzenie nazw i skonfigurujesz prosty autoloading przez spl_autoload_register.
Kiedy projekt rośnie i masz dziesiątki klas, zaczynają się kolizje nazw. Wyobraź sobie, że Twoja klasa Uzytkownik koliduje z klasą Uzytkownik z biblioteki zewnętrznej. Przestrzenie nazw (ang. namespaces) rozwiązują ten problem — pozwalają organizować klasy w logiczne grupy i unikać konfliktów nazw.
namespace — deklaracja przestrzeni nazw
Przestrzeń nazw deklarujesz na początku pliku, zaraz po <?php:
<?php
declare(strict_types=1);
namespace App\Models;
class Uzytkownik
{
public function __construct(
public readonly string $imie,
public readonly string $email,
) {}
}
Teraz pełna nazwa tej klasy to App\Models\Uzytkownik. Separator przestrzeni nazw w PHP to backslash \.
namespace musi być pierwszą instrukcją PHP w pliku (po <?php i opcjonalnym declare()). Jakikolwiek kod przed nią — echo, if, zmienna — spowoduje błąd składniowy.use — import klasy
Żeby użyć klasy z innej przestrzeni nazw, możesz podać jej pełną nazwę lub zaimportować ją przez use:
<?php
declare(strict_types=1);
namespace App\Controllers;
// Import klasy przez use — teraz można pisać po prostu Uzytkownik
use App\Models\Uzytkownik;
use App\Models\Rola;
class UzytkownikController
{
public function stworzUzytkownika(): Uzytkownik
{
return new Uzytkownik('Jan Kowalski', '[email protected]');
}
}
Bez use musiałbyś pisać new \App\Models\Uzytkownik(...) za każdym razem.
Alias — use ... as ...
Gdy dwie klasy z różnych przestrzeni mają tę samą krótką nazwę, możesz użyć aliasu:
<?php
declare(strict_types=1);
namespace App;
use App\Models\Uzytkownik as ModelUzytkownik;
use App\DTO\Uzytkownik as DtoUzytkownik;
$model = new ModelUzytkownik('Jan', '[email protected]');
$dto = new DtoUzytkownik();
Globalny namespace — backslash na początku
Klasy wbudowane PHP (jak \Exception, \DateTime, \InvalidArgumentException) żyją w globalnej przestrzeni nazw. Gdy jesteś w pliku z namespace, musisz dodać \ na początku lub zaimportować przez use:
<?php
declare(strict_types=1);
namespace App\Models;
use DateTimeImmutable; // import z globalnej przestrzeni nazw
class Artykul
{
public readonly DateTimeImmutable $dataPublikacji;
public function __construct(public readonly string $tytul)
{
$this->dataPublikacji = new DateTimeImmutable();
}
}
// Alternatywnie bez use — pełna nazwa z backslashem:
// $data = new \DateTimeImmutable();
Autoloading — automatyczne wczytywanie klas
Zanim PHP może użyć klasy, musi wiedzieć, gdzie jest jej definicja. Bez autoloadera musiałbyś ręcznie pisać require 'src/Models/Uzytkownik.php'; dla każdej klasy. Autoloading automatyzuje ten krok.
Funkcja spl_autoload_register() rejestruje callback wywoływany za każdym razem, gdy PHP napotka nieznaną klasę:
<?php
declare(strict_types=1);
// autoload.php — uruchom to PRZED użyciem jakiejkolwiek klasy
spl_autoload_register(function (string $nazwaKlasy): void {
// App\Models\Uzytkownik -> src/Models/Uzytkownik.php
// App\Controllers\Home -> src/Controllers/Home.php
$prefix = 'App\\';
$katalog = __DIR__ . '/src/';
if (!str_starts_with($nazwaKlasy, $prefix)) {
return; // nie nasza klasa — inna biblioteka zajmie się tym
}
$relatywna = substr($nazwaKlasy, strlen($prefix)); // Models\Uzytkownik
$plik = $katalog . str_replace('\\', '/', $relatywna) . '.php'; // src/Models/Uzytkownik.php
if (file_exists($plik)) {
require $plik;
}
});
Teraz w pliku index.php wystarczy:
<?php
declare(strict_types=1);
require 'autoload.php'; // rejestruj autoloader raz
use App\Models\Uzytkownik;
use App\Controllers\UzytkownikController;
$u = new Uzytkownik('Ola', '[email protected]'); // PHP automatycznie wczyta src/Models/Uzytkownik.php
Jak PHP szuka klasy krok po kroku
Gdy napiszesz new Uzytkownik():
- PHP sprawdza, czy klasa
Uzytkownikjest już załadowana (pamięć). - Jeśli nie — wywołuje kolejno wszystkie zarejestrowane autoloadery.
- Każdy autoloader dostaje pełną nazwę klasy (np.
App\Models\Uzytkownik). - Jeśli żaden autoloader nie załaduje klasy → Fatal Error.
Konwencja PSR-4
Powyższy autoloader to implementacja konwencji PSR-4: pełna nazwa klasy mapuje się na ścieżkę pliku (backslashe → /, suffix .php). To standardowa konwencja PHP:
App\Models\Uzytkownik → src/Models/Uzytkownik.php
App\Http\Controller → src/Http/Controller.php
composer.json. Ręczny spl_autoload_register warto znać, żeby rozumieć, co Composer robi pod spodem.Struktura katalogów konwencji PSR-4
projekt/
├── src/
│ ├── Models/
│ │ ├── Uzytkownik.php ← namespace App\Models;
│ │ └── Produkt.php ← namespace App\Models;
│ ├── Controllers/
│ │ └── Home.php ← namespace App\Controllers;
│ └── Services/
│ └── Email.php ← namespace App\Services;
├── autoload.php ← spl_autoload_register(...)
└── index.php ← require 'autoload.php';
Struktura katalogów odzwierciedla strukturę przestrzeni nazw — to czyni kod czytelnym i przewidywalnym.
W ostatniej lekcji tego modułu poznasz wyjątki — czyli jak PHP sygnalizuje i obsługuje błędy w sposób obiektowy.
Sprawdź się
Gdzie w pliku PHP musi pojawić się deklaracja namespace?
Deklaracja namespace musi być pierwszą instrukcją PHP w pliku, zaraz po znaczniku <?php. Jedynym co może ją poprzedzać jest opcjonalny blok declare(). Umieszczenie namespace po innym kodzie lub po echo spowoduje błąd składniowy.
Do czego służy use wewnątrz pliku PHP z namespace'ami?
use App\Models\Uzytkownik; pozwala pisać new Uzytkownik() zamiast new App\Models\Uzytkownik(). To czysty alias — PHP pamięta pełną nazwę klasy. Nie myl z use wewnątrz klasy (to dołączanie traitu).
Co robi spl_autoload_register()?
spl_autoload_register() przyjmuje callback, który PHP wywoła za każdym razem, gdy kod użyje klasy, której jeszcze nie załadował. W callbacku możesz obliczyć ścieżkę do pliku z klasą i wczytać go przez require.
Ćwiczenie
Wyobraź sobie strukturę: src/Sklep/Produkt.php (namespace Sklep) i src/Sklep/Koszyk.php (namespace Sklep). Koszyk przechowuje tablicę Produkt. Napisz: oba pliki z klasami, plik autoload.php rejestrujący spl_autoload_register mapujący namespace Sklep\ na katalog src/Sklep/, i plik index.php który ładuje autoload.php i tworzy koszyk z dwoma produktami.
Pokaż rozwiązanie
// src/Sklep/Produkt.php
<?php
declare(strict_types=1);
namespace Sklep;
class Produkt
{
public function __construct(
public readonly string $nazwa,
public readonly float $cena,
) {}
}
// src/Sklep/Koszyk.php
<?php
declare(strict_types=1);
namespace Sklep;
class Koszyk
{
/** @var Produkt[] */
private array $pozycje = [];
public function dodaj(Produkt $p): void
{
$this->pozycje[] = $p;
}
public function suma(): float
{
return array_sum(array_map(fn(Produkt $p) => $p->cena, $this->pozycje));
}
}
// autoload.php
<?php
declare(strict_types=1);
spl_autoload_register(function (string $klasa): void {
$mapa = ['Sklep\\\\' => __DIR__ . '/src/Sklep/'];
foreach ($mapa as $prefix => $katalog) {
if (str_starts_with($klasa, $prefix)) {
$plik = $katalog . substr($klasa, strlen($prefix)) . '.php';
if (file_exists($plik)) { require $plik; }
}
}
});
// index.php
<?php
declare(strict_types=1);
require 'autoload.php';
use Sklep\\Koszyk;
use Sklep\\Produkt;
$koszyk = new Koszyk();
$koszyk->dodaj(new Produkt('Jabłka', 3.49));
$koszyk->dodaj(new Produkt('Chleb', 4.99));
echo $koszyk->suma(); // wynik: 8.48