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ć pierwsza: Deklaracja 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():

  1. PHP sprawdza, czy klasa Uzytkownik jest już załadowana (pamięć).
  2. Jeśli nie — wywołuje kolejno wszystkie zarejestrowane autoloadery.
  3. Każdy autoloader dostaje pełną nazwę klasy (np. App\Models\Uzytkownik).
  4. 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 robi to za Ciebie (następny moduł): W praktyce nie piszesz autoloadera ręcznie. Composer — menedżer pakietów PHP — generuje zoptymalizowany autoloader PSR-4 automatycznie na podstawie konfiguracji w 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?

Do czego służy use wewnątrz pliku PHP z namespace'ami?

Co robi spl_autoload_register()?

Ć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