PHP'de autoload, henüz tanımlanmamış bir sınıf, arayüz, trait veya enum kullanıldığı anda PHP'nin sizin kaydettiğiniz bir fonksiyonu çağırıp ilgili dosyayı yüklemesine fırsat vermesidir. Bu fonksiyonu spl_autoload_register() ile kaydedersiniz; fonksiyon istenen sınıfın tam adını (örneğin Magaza\Model\Urun) alır, bunu bir dosya yoluna çevirir ve require ile yükler. Böylece her dosyanın başına uzun require listeleri yazmak zorunda kalmazsınız.

Bu yazıda spl_autoload_register() imzasını ve parametrelerini, yükleyici kuyruğunun nasıl çalıştığını, hangi işlemlerin otomatik yüklemeyi tetiklediğini ve hangilerinin tetiklemediğini, ad alanını klasör yapısına eşleyen PSR-4 benzeri bir yükleyiciyi adım adım yazmayı ve sık yapılan hataları anlatıyoruz. Çok dosyalı örnek gerçekten diskte dosyalar oluşturularak PHP 8.5.12 ile çalıştırıldı. Serinin tamamı için PHP öğrenme rehberi sayfasına bakabilirsiniz.

Otomatik Yükleme Neden Gerekli?

Kılavuz sorunu açıkça tarif eder: nesne yönelimli uygulama yazan geliştiricilerin çoğu her sınıf için ayrı bir dosya oluşturur ve her betiğin başına, ihtiyaç duyulan her sınıf için bir include satırı yazmak can sıkıcı hale gelir. Liste uzadıkça hangi dosyanın gerçekten gerektiğini takip etmek zorlaşır, kullanılmayan dosyalar yine de yüklenir ve bir dosyayı taşıdığınızda onu dahil eden her yeri düzeltmeniz gerekir.

spl_autoload_register() ile kaydedilen yükleyiciler, PHP'ye bir sınıfı hatayla durmadan önce yüklemek için son bir şans verir. PHP, tanımsız bir sınıfa ihtiyaç duyduğunda kayıtlı yükleyicileri sırayla çağırır; biri sınıfı tanımlarsa çalışma kaldığı yerden devam eder. Hiçbiri tanımlamazsa tanıdık Class "..." not found hatası gelir. Otomatik yükleme sınıflara özgü değildir: arayüzler, trait'ler ve enum'lar da aynı şekilde yüklenir.

spl_autoload_register() İmzası ve Parametreleri

spl_autoload_register() İmzası ve Parametreleri: callback, throw, prepend

Fonksiyonun imzası şöyledir: spl_autoload_register(?callable $callback = null, bool $throw = true, bool $prepend = false): bool. Başarıda true döner.

ParametreAnlamıNot
callbackKaydedilecek yükleyici; string $class alır, void döndürürnull verilirse varsayılan spl_autoload() kaydedilir
throwKayıt başarısız olursa istisna fırlatılsın mı?PHP 8.0 ile yok sayılıyor; false verilirse uyarı çıkar
prependtrue ise yükleyici kuyruğun sonuna değil başına eklenirVarsayılan false

Yükleyiciye gelen sınıf adı her zaman tam addır ve baştaki ters bölü içermez. Yani kod içinde new \Magaza\Urun() yazsanız da, class_exists('\\Magaza\\Urun') çağırsanız da yükleyici Magaza\Urun metnini alır. Geçersiz bir callback verirseniz PHP 8'den beri her zaman TypeError fırlatılır. Bu davranışların hepsini aşağıdaki küçük örnekte görebilirsiniz:

php
<?php
spl_autoload_register(function (string $sinif): void {
    echo "1. yükleyici: $sinif\n";
});
spl_autoload_register(function (string $sinif): void {
    echo "2. yükleyici: $sinif\n";
});
spl_autoload_register(function (string $sinif): void {
    echo "öne eklenen: $sinif\n";
}, prepend: true);

var_dump(class_exists('\\Magaza\\Urun'));
/* çıktı:
öne eklenen: Magaza\Urun
1. yükleyici: Magaza\Urun
2. yükleyici: Magaza\Urun
bool(false)
*/

var_dump(class_exists('Magaza\\Urun', false));  // çıktı: bool(false)  (yükleyici çağrılmadı)
echo Magaza\Urun::class, "\n";                 // çıktı: Magaza\Urun   (yükleyici çağrılmadı)
var_dump(count(spl_autoload_functions()));      // çıktı: int(3)

try {
    spl_autoload_register('olmayan_fonksiyon');
} catch (TypeError $e) {
    echo $e->getMessage(), "\n";
}
// çıktı: spl_autoload_register(): Argument #1 ($callback) must be a valid callback or null, function "olmayan_fonksiyon" not found or invalid function name

Çıktı kuyruk mantığını açıkça gösteriyor: yükleyiciler kayıt sırasıyla çağrılıyor, prepend: true ile eklenen ise sıranın başına geçiyor. Hiçbiri sınıfı tanımlamadığı için class_exists() false döndü. class_exists() fonksiyonunun ikinci parametresi false verildiğinde yükleyiciler hiç çağrılmaz; bu, "sınıf şu an yüklü mü?" sorusunu yan etkisiz sormanın yoludur. interface_exists() ve enum_exists() fonksiyonları da aynı $autoload parametresine sahiptir.

Ne Otomatik Yüklemeyi Tetikler, Ne Tetiklemez?

Kural olarak PHP'nin bir sınıfın tanımına gerçekten ihtiyaç duyduğu her an yükleyicileri çağırır: new ile nesne oluşturmak, statik metot çağırmak veya sınıf sabiti okumak, bir sınıfın başka bir sınıfı genişletmesi ya da bir arayüzü uygulaması, class_exists() gibi varsayılan olarak yükleme yapan fonksiyonlar. Kılavuzdaki bir örnek, bir sınıf tanımlanırken uyguladığı ITest arayüzü için de yükleyicinin çağrıldığını gösterir; yükleyici arayüzü tanımlamazsa Fatal error: Uncaught Error: Interface "ITest" not found alınır.

Sinif::class ise kılavuzun deyişiyle derleme anında yapılan bir dönüşümdür; o sırada otomatik yükleme yapılmaz ve sınıf var olmasa bile tam ad üretilir. Yukarıdaki örnekte Magaza\Urun::class satırının hiçbir yükleyiciyi çağırmadığını görebilirsiniz. Bir başka önemli nokta unserialize() ile ilgilidir: kılavuz, nesnelerin yeniden oluşturulması ve otomatik yükleme yüzünden güvenilmeyen veriyi unserialize() ile açmanın kod yüklenip çalıştırılmasına yol açabileceği konusunda uyarır. Dış veriyle çalışırken JSON tercih edin; ayrıntılar json_encode yazısında.

Ad Alanını Klasöre Eşleyen Bir Yükleyici Yazmak

Ad Alanını Klasöre Eşleyen Bir Yükleyici Yazmak: Magaza\Model\Urun, Magaza\Model\KdvOrani, Magaza\Servis\Sepet, Magaza\Sozlesme\Fiyatlanabilir

En yaygın düzen, ad alanının bir önekini bir kök klasöre eşlemek ve geri kalan ad parçalarını alt klasörlere çevirmektir. PHP-FIG topluluğunun PSR-4 önerisi bu düzeni tarif eder; PHP kılavuzu bu öneriyi anlatmaz, ama yaklaşımı kendiniz birkaç satırla uygulayabilirsiniz. Kuralımız şu olsun: Magaza\ önekiyle başlayan her ad src/ klasörüne karşılık gelsin, kalan ad parçaları klasör olsun ve son parça .php uzantılı dosya adı olsun. Böylece Magaza\Model\Urun sınıfı src/Model/Urun.php dosyasında aranır.

Tam sınıf adıAranan dosya
Magaza\Model\Urunsrc/Model/Urun.php
Magaza\Model\KdvOranisrc/Model/KdvOrani.php
Magaza\Servis\Sepetsrc/Servis/Sepet.php
Magaza\Sozlesme\Fiyatlanabilirsrc/Sozlesme/Fiyatlanabilir.php
Baska\Kutuphane\SinifBu yükleyici ilgilenmez, sıradakine bırakır

Önce yükleyiciyi ayrı bir dosyaya yazalım. Üç ayrıntıya dikkat edin: önek eşleşmiyorsa yükleyici hiçbir şey yapmadan döner ve kuyruktaki diğer yükleyicilere yol açar; dosya yolunu kurarken ad alanı ayırıcısı \ klasör ayırıcısına çevrilir; dosya yoksa require çağrılmaz, böylece gereksiz uyarı oluşmaz ve sıradaki yükleyici şansını kullanır.

php
<?php
// autoload.php
declare(strict_types=1);

spl_autoload_register(static function (string $sinif): void {
    $onek = 'Magaza\\';
    $kok = __DIR__ . '/src/';

    if (strncmp($sinif, $onek, strlen($onek)) !== 0) {
        return; // bu yükleyicinin alanı değil
    }

    $goreli = substr($sinif, strlen($onek));
    $dosya = $kok . str_replace('\\', '/', $goreli) . '.php';

    if (is_file($dosya)) {
        require $dosya;
    }
});

Şimdi src/ altında dört dosya oluşturalım: bir arayüz, onu uygulayan bir ürün sınıfı, bir KDV enum'u ve bunları kullanan bir sepet sınıfı. Her dosya kendi ad alanını bildiriyor ve ihtiyaç duyduğu adları use ile içe aktarıyor; hiçbir dosyada require yok. Ad alanı ve use kuralları için namespace yazısına, enum için enum yazısına bakın.

php
<?php
// src/Sozlesme/Fiyatlanabilir.php
declare(strict_types=1);

namespace Magaza\Sozlesme;

interface Fiyatlanabilir
{
    public function fiyat(): float;
}
php
<?php
// src/Model/Urun.php
declare(strict_types=1);

namespace Magaza\Model;

use Magaza\Sozlesme\Fiyatlanabilir;

final class Urun implements Fiyatlanabilir
{
    public function __construct(public readonly string $ad, private float $birimFiyat) {}

    public function fiyat(): float
    {
        return $this->birimFiyat;
    }
}
php
<?php
// src/Model/KdvOrani.php
declare(strict_types=1);

namespace Magaza\Model;

enum KdvOrani: int
{
    case Genel = 20;
    case Indirimli = 10;

    public function carpan(): float
    {
        return 1 + $this->value / 100;
    }
}
php
<?php
// src/Servis/Sepet.php
declare(strict_types=1);

namespace Magaza\Servis;

use Magaza\Model\KdvOrani;
use Magaza\Sozlesme\Fiyatlanabilir;

final class Sepet
{
    /** @var list<array{0: Fiyatlanabilir, 1: int}> */
    private array $kalemler = [];

    public function ekle(Fiyatlanabilir $kalem, int $adet = 1): self
    {
        $this->kalemler[] = [$kalem, $adet];
        return $this;
    }

    public function toplam(KdvOrani $kdv): float
    {
        $ara = 0.0;
        foreach ($this->kalemler as [$kalem, $adet]) {
            $ara += $kalem->fiyat() * $adet;
        }
        return round($ara * $kdv->carpan(), 2);
    }
}

Son olarak giriş dosyası yalnızca autoload.php dosyasını dahil ediyor. Hangi sınıfın hangi sırayla istendiğini görmek için kuyruğun başına yalnızca ekrana yazan bir gözlemci yükleyici ekledik; bu gözlemci sınıfı tanımlamadığı için PHP sıradaki asıl yükleyiciye geçiyor.

php
<?php
// index.php
declare(strict_types=1);

use Magaza\Model\{KdvOrani, Urun};
use Magaza\Servis\Sepet;

require __DIR__ . '/autoload.php';

spl_autoload_register(static function (string $sinif): void {
    echo "istendi: $sinif\n";
}, prepend: true);

$sepet = new Sepet();
$sepet->ekle(new Urun('Defter', 45.0), 2)
      ->ekle(new Urun('Kalem', 12.5), 4);

$toplam = $sepet->toplam(KdvOrani::Genel);
echo 'Toplam: ', $toplam, "\n";
echo 'Tekrar: ', (new Sepet())->ekle(new Urun('Silgi', 5.0))->toplam(KdvOrani::Indirimli), "\n";

var_dump(class_exists('Magaza\Model\Musteri'));
var_dump(class_exists('Baska\Kutuphane\Sinif'));
/* çıktı:
istendi: Magaza\Servis\Sepet
istendi: Magaza\Model\Urun
istendi: Magaza\Sozlesme\Fiyatlanabilir
istendi: Magaza\Model\KdvOrani
Toplam: 168
Tekrar: 5.5
istendi: Magaza\Model\Musteri
bool(false)
istendi: Baska\Kutuphane\Sinif
bool(false)
*/

Çıktıdan birkaç şey öğreniyoruz. Her sınıf yalnızca ilk kullanıldığı anda istendi; ikinci Sepet ve Urun nesneleri oluşturulurken yükleyici bir daha çağrılmadı, çünkü sınıflar artık tanımlıydı. Urun dosyası yüklenirken, sınıf Fiyatlanabilir arayüzünü uyguladığı için arayüz de hemen ardından istendi. use satırları tek başına hiçbir yüklemeyi tetiklemedi; içe aktarma yalnızca adların nasıl çözüleceğini belirler. Var olmayan Musteri için dosya bulunamadı ve false döndü, Baska\ önekli ad ise asıl yükleyici tarafından hiç dosya sistemine bakılmadan geri çevrildi.

Yükleyicide require_once yerine require kullanmamızın da bir nedeni var: yükleyici yalnızca henüz tanımlanmamış bir sınıf için çağrılır, dolayısıyla aynı dosyanın ikinci kez yüklenmesi normal akışta zaten olmaz. Bir dosya hem elle require edilip hem de yükleyici tarafından aranıyorsa sorun yükleyicide değil, karışık yükleme düzenindedir; projede tek bir yöntem seçmek en temiz çözümdür.

Birden Fazla Yükleyici, Sıra ve İstisnalar

spl_autoload_register() istediğiniz kadar çağrılabilir; her çağrı kuyruğa bir yükleyici ekler. Kendi kodunuz için bir yükleyici, eklentiler veya eski bir modül için başka bir yükleyici aynı anda çalışabilir. Kuyruğu spl_autoload_functions() ile görebilir, bir yükleyiciyi spl_autoload_unregister() ile çıkarabilirsiniz. Çıkarmak için kayıtta kullandığınız callable'ın aynısını vermelisiniz; bu yüzden çıkarmayı planladığınız closure'ı bir değişkende saklayın.

Kılavuzun bu konudaki uyarısı nettir: bir yükleyiciden istisna fırlatmak kuyruğu yarıda keser ve sonraki yükleyicilerin çalışmasına izin vermez. Bu yüzden yükleyicilerden istisna fırlatmak kesinlikle önerilmez. Aşağıdaki örnekte ilk yükleyici sınıfı bulamayınca istisna fırlatıyor ve sınıfı belki de yükleyebilecek ikinci yükleyici hiç çağrılmıyor:

php
<?php
spl_autoload_register(function (string $sinif): void {
    echo "A: $sinif\n";
    throw new RuntimeException("A bulamadı: $sinif");
});
spl_autoload_register(function (string $sinif): void {
    echo "B: $sinif\n";   // hiç çalışmaz
});

try {
    new Urun();
} catch (RuntimeException $e) {
    echo $e->getMessage(), "\n";
}
/* çıktı:
A: Urun
A bulamadı: Urun
*/

Doğru davranış, sınıfı bulamayan yükleyicinin sessizce dönmesidir. Sınıf hiçbir yükleyici tarafından tanımlanmazsa PHP zaten anlamlı bir Error fırlatır. Bu hatanın ayrıntıları ve diğer "not found" hataları için Fatal error: Uncaught Error yazısına bakabilirsiniz.

Yükleyicide Hata Ayıklamak

Bir sınıf beklenmedik şekilde bulunamıyorsa sorunu birkaç adımda daraltabilirsiniz. İlk olarak spl_autoload_functions() ile kuyrukta gerçekten bir yükleyici olup olmadığına bakın; kuyruk boşsa ya da yükleyiciyi kaydeden dosya hiç dahil edilmemişse sorun dosya yolunda değil, kayıttadır. İkinci adımda sınıfın yüklü olup olmadığını class_exists($ad, false) ile, yükleme denemesini ise class_exists($ad) ile ayrı ayrı sorun. spl_autoload_call($ad) fonksiyonu da kayıtlı yükleyicileri verilen adla elle çalıştırmanızı sağlar; bir test betiğinde tek bir sınıfın yüklenmesini denemek için kullanışlıdır.

Üçüncü adım, yükleyicinin ürettiği dosya yolunu görmektir. Yükleyicinin içine geçici olarak bir echo satırı ekleyip aranan yolu yazdırmak, yanlış önek, eksik klasör ayırıcısı veya harf farkı gibi hataları hemen ortaya çıkarır. Son olarak get_included_files() o ana kadar include ve require ile yüklenmiş tüm dosyaların listesini verir; bir dosyanın hiç yüklenmediğini buradan görebilirsiniz. Bu satırları sorun çözülünce kaldırmayı unutmayın; yükleyici her tanımsız sınıf için çalıştığından çıktı kısa sürede kalabalıklaşır. Komut satırında küçük test betikleri çalıştırmak için PHP CLI yazısına bakabilirsiniz.

Varsayılan spl_autoload() Uygulaması

spl_autoload_register() parametresiz çağrılırsa kuyruğa PHP'nin hazır spl_autoload() fonksiyonu eklenir. Kılavuza göre bu fonksiyon, sınıf adının küçük harfe çevrilmiş halini, varsayılan olarak .inc ve .php uzantılarıyla birleştirip include_path içindeki klasörlerde arar. Uzantı listesini spl_autoload_extensions() ile okuyup değiştirebilirsiniz; PHP 8.5.12'de parametresiz çağrı ".inc,.php" döndürür. Sınıf bulunamaz ve kayıtlı başka yükleyici yoksa spl_autoload() bir LogicException fırlatır.

Bu varsayılan uygulama küçük denemeler için yeterli olabilir, ancak dosya adlarını küçük harfe çevirmesi ve include_path ayarına bağlı olması nedeniyle çoğu projede yukarıdaki gibi açık bir eşleme yazmak daha öngörülebilirdir. Paket yöneticisi Composer da projeniz için bir vendor/autoload.php dosyası üretir ve kılavuz bu dosyayı dahil etmenin, Composer ile yönetilen paketleri ek iş yapmadan kullanılabilir hale getirdiğini belirtir; Composer bu serinin kapsamı dışındadır.

Sık Yapılan Hatalar

Sık Yapılan Hatalar: Yükleyicide include kullanıp dosya varlığını kontrol etmemek, Dosya adı ile sınıf adının harflerini farklı yazmak, Yükleyiciden istisna fırlatmak, Göreli yol...
  • Yükleyicide include kullanıp dosya varlığını kontrol etmemek: Dosya yoksa include iki uyarı üretir, ardından yine Class "Urun" not found hatası gelir. is_file() ile kontrol edip require kullanın.
  • Dosya adı ile sınıf adının harflerini farklı yazmak: Windows'ta çalışan bir yükleyici, büyük küçük harfe duyarlı dosya sistemlerinde dosyayı bulamayabilir. Dosya adını sınıf adıyla birebir aynı harflerle yazın.
  • Yükleyiciden istisna fırlatmak: Kuyruktaki diğer yükleyiciler çalışamaz. Bulamadığınız sınıf için sessizce dönün.
  • Göreli yol kullanmak: require 'src/' . $dosya çalışma dizinine göre çözülür ve betik başka bir dizinden çalıştırılınca kırılır. Yolu her zaman __DIR__ ile kurun.
  • Ad alanı ayırıcısını çevirmemek: Magaza\Model\Urun metnindeki ters bölüleri klasör ayırıcısına çevirmezseniz dosya bulunamaz.
  • Bir dosyada birden fazla sınıf tutmak: Yükleyici yalnızca istenen sınıfın adından dosya bulur; aynı dosyadaki ikinci sınıf, dosya başka bir nedenle yüklenmedikçe bulunamaz.

Sık Sorulan Sorular

Autoload sınıfları önceden yükler mi?

Hayır. Yükleyici yalnızca tanımsız bir sınıfa ihtiyaç duyulduğu anda çağrılır. Bu yüzden kullanılmayan sınıfların dosyaları hiç okunmaz; yukarıdaki örnekte use satırlarının yükleme yapmadığını gördük.

Fonksiyonlar ve sabitler de otomatik yüklenir mi?

Hayır. Kılavuz otomatik yüklemeyi sınıflar, arayüzler, trait'ler ve enum'lar için tanımlar. Ad alanlı yardımcı fonksiyonları içeren dosyaları kendiniz require etmeniz gerekir.

Yükleyiciyi bir sınıfın statik metodu olarak kaydedebilir miyim?

Evet, geçerli her callable kabul edilir: fonksiyon adı, closure, [Sinif::class, 'metot'] dizisi veya kılavuzdaki örnekte olduğu gibi 'AdAlani\Sinif::metot' metni. Callable türleri için anonim fonksiyon ve callable yazısına bakın.

Composer kullanıyorsam bunu bilmem gerekir mi?

Composer'ın ürettiği yükleyici de sonuçta spl_autoload_register() mekanizmasının üzerinde çalışır. Kuyruk sırası, istisna davranışı ve ::class ile class_exists() farkı gibi kurallar her durumda geçerlidir.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: PHP öğrenme rehberi · PHP namespace ve use · PHP sınıf ve nesne · PHP Fatal error: Uncaught Error çözümü · PHP CLI kullanımı