PHP'de namespace, sınıfları, arayüzleri, trait'leri, enum'ları, fonksiyonları ve sabitleri Magaza\Siparis gibi hiyerarşik bir ad altında gruplayan yapıdır; use ise başka bir ad alanındaki öğeyi kısa adla kullanabilmeniz için içe aktarır veya takma ad verir. Dosyanın başına namespace Magaza\Siparis; yazdığınızda o dosyadaki class Fatura aslında Magaza\Siparis\Fatura adını alır ve başka bir kütüphanedeki Fatura sınıfıyla çakışmaz.

Bu yazıda ad alanı tanımlama kurallarını, niteliksiz, nitelikli ve tam nitelikli ad farkını, use, use function ve use const ile içe aktarmayı, fonksiyon ve sabitlerin global alana geri düşmesini (fallback), sınıflarda bu geri düşmenin olmamasından doğan klasik Exception hatasını ve metinle verilen dinamik adları çalışan örneklerle anlatıyoruz. Örnekler PHP 8.5.12 ile çalıştırıldı. Serinin tamamı için PHP öğrenme rehberi sayfasına bakabilirsiniz.

Ad Alanları Hangi Sorunu Çözer?

Ad Alanları Hangi Sorunu Çözer?

Kılavuz ad alanlarını işletim sistemindeki klasörlere benzetir. foo.txt adlı bir dosya hem /home/greg hem de /home/other klasöründe bulunabilir, ama aynı klasörde iki tane olamaz. Klasörün dışından erişirken dosya adının başına klasör yolunu eklersiniz. PHP ad alanları da aynı mantıkla iki sorunu çözer: kendi yazdığınız kodla PHP'nin iç sınıfları, fonksiyonları ve sabitleri ya da üçüncü taraf kütüphaneler arasındaki ad çakışmaları; ve bu çakışmaları önlemek için uydurulan Uzun_Alt_Cizgili_Sinif_Adlari yerine kısa ve okunur adlar kullanabilmek.

Ad alanından etkilenen öğeler bellidir: sınıflar (soyut sınıflar, trait'ler ve enum'lar dahil), arayüzler, fonksiyonlar ve sabitler. Değişkenler ad alanından etkilenmez. Kılavuzun iki küçük notu da önemlidir: ad alanı adları büyük küçük harfe duyarsızdır ve PHP adı ile PHP\ ile başlayan adlar dilin kendi kullanımına ayrılmıştır, kendi kodunuzda kullanmamalısınız.

Ad Alanı Nasıl Tanımlanır?

Ad alanı namespace anahtar kelimesiyle tanımlanır ve dosyadaki ilk ifade olmalıdır. Tek istisna declare ifadesidir; declare(strict_types=1); ad alanından önce gelebilir. Bunun dışında hiçbir kod, hatta <?php etiketinden önce yazılmış HTML veya boşluk bile ad alanı bildiriminden önce bulunamaz. Alt seviyeler ters bölü ile ayrılır: namespace Magaza\Siparis\Fatura;. Ad alanı bildiriminde baştaki ters bölüye izin verilmez; namespace \Magaza; geçersizdir.

php
<html>
<?php
namespace Proje;
// Fatal error: Namespace declaration statement has to be the very first statement or after any declare call in the script in /yol/ornek.php on line 3

Aynı ad alanı birden fazla dosyada tanımlanabilir. Bu, bir ad alanının içeriğini dosya sistemine dağıtmanızı sağlar ve pratikte "her sınıf kendi dosyasında" düzeninin temelidir. Dosyaları sınıf adlarına göre otomatik yükleme spl_autoload_register yazısında anlatılıyor. Aynı dosyada birden fazla ad alanı da tanımlanabilir; kılavuz bunun için süslü parantezli sözdizimini önerir ve yine de bu yöntemi ancak birden çok betiği tek dosyada birleştirmek gibi özel durumlar için uygun görür. Bu yazıdaki bazı örnekler, birden fazla dosyayı tek seferde çalıştırabilmek için bu sözdizimini kullanıyor.

Üç Ad Türü: Niteliksiz, Nitelikli, Tam Nitelikli

Üç Ad Türü: Niteliksiz, Nitelikli, Tam Nitelikli: Niteliksiz (unqualified), Nitelikli (qualified), Tam nitelikli (fully qualified), Göreli (relative)

PHP bir adı gördüğünde onu bulunduğu ad alanına göre çözer. Kılavuz bunu yine dosya yollarına benzetir: göreli dosya adı, göreli yol ve mutlak yol. Mevcut ad alanının Magaza olduğunu varsayalım:

Ad türüÖrnekMagaza ad alanında çözüldüğü ad
Niteliksiz (unqualified)FaturaMagaza\Fatura
Nitelikli (qualified)Siparis\FaturaMagaza\Siparis\Fatura
Tam nitelikli (fully qualified)\Siparis\FaturaSiparis\Fatura
Göreli (relative)namespace\FaturaMagaza\Fatura

Tam nitelikli ad, baştaki ters bölü sayesinde her zaman yazıldığı gibi çözülür; içe aktarma kuralları onu etkilemez. Göreli addaki namespace anahtar kelimesi, sınıflardaki self gibi, mevcut ad alanını temsil eder. __NAMESPACE__ sihirli sabiti ise mevcut ad alanının adını metin olarak verir; global kodda boş metindir. Bir sınıfın tam adını metin olarak almak için en güvenli yol Sinif::class yazımıdır.

php
<?php
namespace Magaza\Siparis {
    class Fatura {}
}

namespace Magaza {
    class Fatura {}

    echo Fatura::class, "\n";              // çıktı: Magaza\Fatura
    echo Siparis\Fatura::class, "\n";      // çıktı: Magaza\Siparis\Fatura
    echo \Magaza\Fatura::class, "\n";      // çıktı: Magaza\Fatura
    echo namespace\Fatura::class, "\n";    // çıktı: Magaza\Fatura
    echo __NAMESPACE__, "\n";              // çıktı: Magaza
    echo Olmayan\Sinif::class, "\n";       // çıktı: Magaza\Olmayan\Sinif
}

Son satır önemli bir ayrıntıyı gösteriyor: ::class derleme anında yapılan bir dönüşümdür. Kılavuzun belirttiği gibi bu sırada otomatik yükleme yapılmaz ve sınıf var olmasa bile ad genişletilir, hata verilmez. Yani ::class size sınıfın var olduğunu değil, yalnızca adın nasıl çözüleceğini söyler.

use ile İçe Aktarma ve Takma Ad

use ifadesi başka bir ad alanındaki öğeye kısa bir adla erişmenizi sağlar. Sınıflar, arayüzler, trait'ler, enum'lar ve ad alanlarının kendisi use ile; fonksiyonlar use function ile; sabitler use const ile içe aktarılır. as ile istediğiniz takma adı verebilirsiniz; vermezseniz adın son parçası kullanılır. Aynı ad alanından birden fazla öğeyi süslü parantezle gruplayabilirsiniz.

php
<?php
namespace Uygulama\Model {
    class Kullanici {}
    class Urun {}
    const SURUM = '1.2';
    function slug(string $s): string { return strtolower(str_replace(' ', '-', $s)); }
}

namespace Uygulama {
    use Uygulama\Model\Kullanici;                 // sınıf
    use Uygulama\Model\Urun as Mal;               // takma adlı sınıf
    use Uygulama\Model as M;                       // ad alanına takma ad
    use function Uygulama\Model\slug;             // fonksiyon
    use const Uygulama\Model\SURUM;               // sabit
    use Uygulama\Model\{Kullanici as K2, Urun};   // gruplu use

    echo Kullanici::class, "\n";        // çıktı: Uygulama\Model\Kullanici
    echo Mal::class, "\n";              // çıktı: Uygulama\Model\Urun
    echo M\Kullanici::class, "\n";      // çıktı: Uygulama\Model\Kullanici
    echo slug('Yaz Kampanyası'), "\n";  // çıktı: yaz-kampanyası
    echo SURUM, "\n";                   // çıktı: 1.2
    echo K2::class, "\n";               // çıktı: Uygulama\Model\Kullanici
}

use ile ilgili üç kural sık hatalara yol açar. Birincisi, içe aktarma derleme anında yapılır; bu yüzden use yalnızca dosyanın en dış kapsamında veya ad alanı bildiriminin içinde yazılabilir, fonksiyon veya sınıf gövdesine konamaz. PHP 8.5.12'de fonksiyon içinde yazılan use şu hatayı verir: Parse error: syntax error, unexpected token "use". Sınıf gövdesindeki use ise içe aktarma değil, trait kullanımıdır; bkz. trait yazısı. Closure'lardaki function () use ($x) de tamamen farklı bir şeydir ve dış değişken yakalar; bkz. anonim fonksiyon yazısı.

İkincisi, içe aktarma kuralları dosya bazındadır: include veya require ile dahil edilen dosya, onu dahil eden dosyanın use satırlarını devralmaz. Her dosya kendi use satırlarını yazmalıdır. Üçüncüsü, içe aktarılan ad aynı dosyada tanımlanan bir sınıfla çakışamaz. use another\thing as MyClass; yazıp aynı dosyada class MyClass {} tanımlarsanız Fatal error: Cannot redeclare class my\stuff\MyClass (previously declared as local import) alırsınız. Sınıf başka bir dosyada tanımlıysa çakışma olmaz.

Global Alana Geri Düşme: Fonksiyon ve Sabitler Evet, Sınıflar Hayır

Ad alanının en çok şaşırtan kuralı budur. Bir ad alanı içinde niteliksiz bir fonksiyon veya sabit adı kullandığınızda PHP önce mevcut ad alanında arar, bulamazsa global fonksiyonu veya sabiti kullanır. Bu yüzden namespace Magaza; altında strlen(), count() veya PHP_VERSION yazmak sorunsuz çalışır. Sınıf adları için ise böyle bir geri düşme yoktur: niteliksiz bir sınıf adı her zaman mevcut ad alanına göre çözülür.

php
<?php
namespace Magaza\Siparis;

class Exception extends \Exception {}

$a = new Exception('hata');          // bu ad alanındaki Exception
var_dump($a::class);                 // çıktı: string(24) "Magaza\Siparis\Exception"
$b = new \Exception('hata');         // global Exception
var_dump($b::class);                 // çıktı: string(9) "Exception"

echo strlen('PHP'), "\n";            // çıktı: 3   (global strlen'e geri düştü)
echo \strlen('PHP'), "\n";           // çıktı: 3   (doğrudan global)

try {
    $liste = new ArrayObject([1, 2]);
} catch (\Error $e) {
    echo $e->getMessage(), "\n";
}
// çıktı: Class "Magaza\Siparis\ArrayObject" not found

try {
    echo TANIMSIZ;
} catch (\Error $e) {
    echo $e->getMessage(), "\n";
}
// çıktı: Undefined constant "Magaza\Siparis\TANIMSIZ"

Bu örnekten çıkan pratik ders şu: ad alanlı bir dosyada Exception, DateTime, PDO, ArrayObject gibi PHP'nin kendi sınıflarını kullanırken ya başına ters bölü koyun (new \DateTime()) ya da dosyanın başında use DateTime; yazın. Bunu unutmanın en sinsi hali catch (Exception $e) satırıdır: ad alanında Exception adlı bir sınıf yoksa bu satır hata vermez, sadece hiçbir zaman eşleşmez ve istisna yakalanmadan yukarı çıkar. Sorun ancak bir hata oluştuğunda fark edilir. Hata yakalama ayrıntıları için try catch yazısına bakın.

Fonksiyon geri düşmesinin de bir yan etkisi var: aynı ad alanında strlen adlı bir fonksiyon tanımlarsanız, o ad alanındaki tüm niteliksiz strlen() çağrıları sizin fonksiyonunuza gider. Kılavuzdaki örnek tam olarak bunu gösterir: global fonksiyonu sarmalayan bir yerel fonksiyon, içeride \strlen() ile asıl fonksiyonu çağırır. Kılavuzun ad çözümleme kurallarına göre niteliksiz fonksiyon ve sabit adları global olmayan kodda çalışma anında çözülür; sınıf adları ise mevcut ad alanına göre belirlenir ve bulunamazsa o tam adla otomatik yükleme denenir.

Metinle Verilen Dinamik Adlar

Sınıf, fonksiyon veya sabit adını bir değişkenden kullandığınızda (new $sinif, $fonksiyon(), constant($ad)) içe aktarma kuralları uygulanmaz, çünkü içe aktarma derleme anında yapılır. Ayrıca metinle verilen ad her zaman tam ad gibi değerlendirilir; mevcut ad alanı başına eklenmez. Kılavuzun belirttiği gibi dinamik adlarda nitelikli ve tam nitelikli ad arasında fark olmadığından baştaki ters bölüye gerek yoktur.

php
<?php
namespace Uygulama\Model {
    class Kullanici {}
}

namespace Uygulama {
    use Uygulama\Model\Kullanici;

    $ad = 'Uygulama\Model\Kullanici';
    var_dump(new $ad instanceof Kullanici);   // çıktı: bool(true)

    foreach (['Kullanici', 'Model\Kullanici'] as $ad) {
        try {
            new $ad;
        } catch (\Error $e) {
            echo $e->getMessage(), "\n";
        }
    }
    /* çıktı:
    Class "Kullanici" not found
    Class "Model\Kullanici" not found
    */

    $tehlikeli = "Uygulama\nesne";           // çift tırnakta \n satır sonu olur!
    var_dump(strlen($tehlikeli));             // çıktı: int(13)
    echo Kullanici::class, "\n";              // çıktı: Uygulama\Model\Kullanici
}

Bu örnekteki iki tuzak gerçek projelerde sık görülür. use satırı olduğu halde new $ad ile 'Kullanici' metni bulunamadı, çünkü dinamik ad içe aktarma tablosuna bakmaz. Çift tırnaklı metinde yazılan Uygulama\nesne ise \n kaçış dizisi yüzünden satır sonu içeren 13 karakterlik bir metne dönüştü. Kılavuz bu yüzden ad alanı adlarını metin içinde yazarken ters bölüyü her zaman ikilemeyi önerir. Bu sorunların ikisinden de kaçınmanın en kolay yolu, sınıf adını elle metin olarak yazmak yerine Kullanici::class kullanmaktır. Tek ve çift tırnak farkı için string fonksiyonları yazısına bakın.

Uygulamalı Mini Örnek: Çakışan İki Logger Sınıfı

Bir uygulamada kendi Logger sınıfınız var ve bir kütüphane de aynı adla bir sınıf getiriyor. Ad alanları olmadan bu iki sınıf aynı programda bulunamazdı. Aşağıdaki örnekte her biri kendi ad alanında yaşıyor; uygulama kodu ikisini de takma adlarla aynı anda kullanıyor ve fonksiyon geri düşmesi ile tam nitelikli çağrı farkını da gösteriyor. Gerçek bir projede her ad alanı ayrı dosyada olurdu; burada çalıştırabilmek için tek dosyada süslü parantezli sözdizimi kullanıldı.

php
<?php
declare(strict_types=1);

namespace Kutuphane\Gunluk {
    class Logger
    {
        public function yaz(string $mesaj): string
        {
            return '[kütüphane] ' . $mesaj;
        }
    }
}

namespace Uygulama\Altyapi {
    class Logger
    {
        public function yaz(string $mesaj): string
        {
            return '[uygulama] ' . strtoupper($mesaj);   // global strtoupper'a geri düşer
        }
    }

    function strtoupper(string $s): string                // bu ad alanında gölgeleme
    {
        return \strtoupper($s) . '!';                    // global olanı açıkça çağır
    }
}

namespace Uygulama {
    use Kutuphane\Gunluk\Logger as KutuphaneLogger;
    use Uygulama\Altyapi\Logger;

    $loglar = [new Logger(), new KutuphaneLogger()];
    foreach ($loglar as $log) {
        echo $log::class, ' => ', $log->yaz('kayıt alındı'), "\n";
    }
}
/* çıktı:
Uygulama\Altyapi\Logger => [uygulama] KAYıT ALıNDı!
Kutuphane\Gunluk\Logger => [kütüphane] kayıt alındı
*/

Çıktıdaki iki ayrıntı öğretici. Birincisi, Uygulama\Altyapi ad alanındaki niteliksiz strtoupper() çağrısı, aynı ad alanında aynı adlı bir fonksiyon tanımlandığı için global fonksiyona değil yerel fonksiyona gitti ve sona ünlem eklendi. İkincisi, global strtoupper() çok baytlı karakterleri tanımadığı için ı harfi değişmeden kaldı. Türkçe metinlerde büyük harfe çevirme için mb_strtoupper() kullanmanız gerekir; ayrıntılar mbstring yazısında.

Sık Yapılan Hatalar

Sık Yapılan Hatalar: Global sınıfların başına ters bölü koymamak, namespace satırından önce çıktı bırakmak, use satırını fonksiyonun içine yazmak, Dinamik adda use bekleme
  • Global sınıfların başına ters bölü koymamak: Ad alanlı dosyada new DateTime() veya catch (Exception $e) yazmak mevcut ad alanında arar. \DateTime yazın veya use DateTime; ekleyin.
  • namespace satırından önce çıktı bırakmak: BOM'lu UTF-8 kaydedilen dosyalar veya <?php öncesindeki boşluk ve HTML, ad alanı bildirimini geçersiz kılar.
  • use satırını fonksiyonun içine yazmak: İçe aktarma derleme anında yapılır, blok kapsamlı olamaz; dosyanın en üstüne taşıyın.
  • Dinamik adda use bekleme: new $ad, class_exists($ad) ve benzerleri içe aktarma tablosunu kullanmaz; tam adı verin ya da Sinif::class kullanın.
  • Çift tırnakta tek ters bölü: "App\new" içindeki \n satır sonudur. Tek tırnak kullanın veya ters bölüyü ikileyin.
  • Dahil edilen dosyanın use satırlarını devraldığını sanmak: Her dosya kendi içe aktarmalarını yazmalıdır.

Sık Sorulan Sorular

Ad alanı kullanmazsam ne olur?

Kodunuz global alanda çalışır, PHP 5.3 öncesindeki gibi. Kılavuzun SSS bölümü, ad alanı kullanmayan kodun bu kurallardan etkilenmediğini ve global sınıflara \ ile de, onsuz da erişebileceğini belirtir. Ancak üçüncü taraf kodla çakışma riski artar.

Ad alanları iç içe yazılabilir mi?

Hayır, bir namespace bloğu başka bir bloğun içine yazılamaz. Aynı etkiyi ters bölüyle ayrılmış tek bir ad vererek elde edersiniz: namespace Magaza\Siparis\Fatura;.

Klasör yapısı ad alanıyla aynı olmak zorunda mı?

PHP bunu zorunlu tutmaz; ad alanı yalnızca adlandırmadır. Ancak otomatik yükleyiciler genellikle ad alanını klasör yoluna çevirir, bu yüzden ikisini eşleştirmek dosyaları bulmayı kolaylaştırır. Ayrıntılar autoload yazısında.

Sabitlerin ve define() ile tanımlananların ad alanı nasıl çalışır?

const ile tanımlanan sabit bulunduğu ad alanına aittir. define() ile ad alanlı sabit tanımlamak için adı tam olarak metin içinde vermeniz gerekir. İki yöntemin farkı sabitler yazısında anlatılıyor.

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 autoload ve spl_autoload_register · PHP sınıf ve nesne · PHP sabitleri · PHP Fatal error: Uncaught Error çözümü