paint-brush
Laravel Altında - Cepheler Nedir?ile@oussamamater
260 okumalar

Laravel Altında - Cepheler Nedir?

ile Oussama Mater14m2024/06/29
Read on Terminal Reader

Çok uzun; Okumak

Laravel sıklıkla kullandığımız birçok Cephe ile birlikte gelir. Bunların ne olduğunu, kendi Cephelerimizi nasıl oluşturabileceğimizi tartışacağız ve ayrıca gerçek zamanlı Cepheler hakkında bilgi edineceğiz.
featured image - Laravel Altında - Cepheler Nedir?
Oussama Mater HackerNoon profile picture

Yeni bir Laravel uygulaması yüklediniz, başlattınız ve hoş geldiniz sayfasını aldınız. Herkes gibi siz de bunun nasıl oluşturulduğunu görmeye çalışıyorsunuz, bu nedenle web.php dosyasına atlıyorsunuz ve şu kodla karşılaşıyorsunuz:

 <?php use Illuminate\Support\Facades\Route; Route::get('/', function () { return view('welcome'); });

Hoş geldiniz görünümünü nasıl elde ettiğimiz açık, ancak Laravel'in yönlendiricisinin nasıl çalıştığını merak ediyorsunuz, bu yüzden kodun içine dalmaya karar veriyorsunuz. İlk varsayım şudur: Üzerinde get() statik yöntemini çağırdığımız bir Route sınıfı var. Ancak tıklandığında orada get() yöntemi yoktur. Peki ne tür bir kara büyü oluyor? Hadi bunu aydınlatalım!

Düzenli Cepheler

Lütfen PHPDoc'ların çoğunu çıkardığımı ve yalnızca basitlik amacıyla türleri satır içi yazdığımı unutmayın; "..." daha fazla kodu ifade eder.

Herhangi bir karışıklığı önlemek için IDE'nizi açmanızı ve kodu takip etmenizi şiddetle tavsiye ederim.


Örneğimizi takip ederek Route sınıfını inceleyelim.

 <?php namespace Illuminate\Support\Facades; class Route extends Facade { // ... protected static function getFacadeAccessor(): string { return 'router'; } }


Burada fazla bir şey yok, yalnızca router dizesini döndüren getFacadeAccessor() yöntemi var. Bunu aklımızda tutarak ebeveyn sınıfına geçelim.

 <?php namespace Illuminate\Support\Facades; use RuntimeException; // ... abstract class Facade { // ... public static function __callStatic(string $method, array $args): mixed { $instance = static::getFacadeRoot(); if (! $instance) { throw new RuntimeException('A facade root has not been set.'); } return $instance->$method(...$args); } }

Ebeveyn sınıfında pek çok yöntem vardır, ancak get() yöntemi yoktur. Ancak ilginç bir yöntem var: __callStatic() yöntemi. Bu, bizim durumumuzda get() gibi tanımsız bir statik yöntem çağrıldığında çağrılan sihirli bir yöntemdir. Bu nedenle, __callStatic('get', ['/', Closure()]) çağrımız Route::get() çağırırken ilettiğimiz şeyi, rotayı / ve hoş geldiniz görünümünü döndüren Closure() temsil eder.


__callStatic() tetiklendiğinde, önce getFacadeRoot() öğesini çağırarak bir $instance değişkeni ayarlamaya çalışır, $instance çağrının iletilmesi gereken gerçek sınıfı tutar, daha yakından bakalım, birazdan anlamlı olacaktır

 // Facade.php public static function getFacadeRoot() { return static::resolveFacadeInstance(static::getFacadeAccessor()); }


Hey, bakın bu Route alt sınıfındaki getFacadeAccessor() yöntemidir ve bunun router dizesini döndürdüğünü biliyoruz. Bu router dizesi daha sonra, "Bu dize hangi sınıfı temsil ediyor?" diyen bir tür eşleme olan bir sınıfa çözümlemeye çalışan resolveFacadeInstance() a iletilir. Görelim.

 // Facade.php protected static function resolveFacadeInstance($name) { if (isset(static::$resolvedInstance[$name])) { return static::$resolvedInstance[$name]; } if (static::$app) { if (static::$cached) { return static::$resolvedInstance[$name] = static::$app[$name]; } return static::$app[$name]; } }

Öncelikle $resolvedInstance adlı statik bir dizinin verilen $name (yine router ) ile ayarlanmış bir değere sahip olup olmadığını kontrol eder. Bir eşleşme bulursa yalnızca bu değeri döndürür. Bu, performansı biraz optimize etmek için Laravel önbelleğe alma işlemidir. Bu önbelleğe alma tek bir istekte gerçekleşir. Bu yöntem aynı istek içinde aynı argümanla birden çok kez çağrılırsa önbelleğe alınan değeri kullanır. Bunun ilk çağrı olduğunu varsayalım ve devam edelim.


Daha sonra $app ayarlanıp ayarlanmadığını ve $app uygulama kapsayıcısının bir örneği olup olmadığını kontrol eder.

 // Facade.php protected static \Illuminate\Contracts\Foundation\Application $app;

Uygulama kapsayıcısının ne olduğunu merak ediyorsanız onu sınıflarınızın depolandığı bir kutu olarak düşünün. Bu derslere ihtiyacınız olduğunda o kutuya uzanmanız yeterli. Bazen bu kap biraz sihir gerçekleştirir. Kutu boş olsa ve bir ders almak için uzansanız bile, onu sizin için alacaktır. Bu başka bir makalenin konusu.


Şimdi, " $app ne zaman ayarlanır?" diye merak edebilirsiniz, çünkü öyle olması gerekiyor, aksi halde $instance sahip olmayacağız. Bu uygulama kapsayıcısı, uygulamamızın önyükleme işlemi sırasında ayarlanır. \Illuminate\Foundation\Http\Kernel sınıfına hızlıca bir göz atalım:

 <?php namespace Illuminate\Foundation\Http; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Support\Facades\Facade; use Illuminate\Contracts\Http\Kernel as KernelContract; // ... class Kernel implements KernelContract { // ... protected $app; protected $bootstrappers = [ \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class, \Illuminate\Foundation\Bootstrap\LoadConfiguration::class, \Illuminate\Foundation\Bootstrap\HandleExceptions::class, \Illuminate\Foundation\Bootstrap\RegisterFacades::class, // <- this guy \Illuminate\Foundation\Bootstrap\RegisterProviders::class, \Illuminate\Foundation\Bootstrap\BootProviders::class, ]; public function bootstrap(): void { if (! $this->app->hasBeenBootstrapped()) { $this->app->bootstrapWith($this->bootstrappers()); } } }

Bir istek geldiğinde yönlendiriciye gönderilir. Bundan hemen önce, uygulamayı hazırlamak için bootstrappers dizisini kullanan bootstrap() yöntemi çağrılır. \Illuminate\Foundation\Application sınıfında bootstrapWith() yöntemini keşfederseniz, bu önyükleyiciler arasında yinelenir ve onların bootstrap() yöntemini çağırır.


Basit olması açısından, bootstrapWith() içinde çağrılacak bir bootstrap() yöntemi içerdiğini bildiğimiz \Illuminate\Foundation\Bootstrap\RegisterFacades öğesine odaklanalım.

 <?php namespace Illuminate\Foundation\Bootstrap; use Illuminate\Contracts\Foundation\Application; use Illuminate\Foundation\AliasLoader; use Illuminate\Foundation\PackageManifest; use Illuminate\Support\Facades\Facade; class RegisterFacades { // ... public function bootstrap(Application $app): void { Facade::clearResolvedInstances(); Facade::setFacadeApplication($app); // Interested here AliasLoader::getInstance(array_merge( $app->make('config')->get('app.aliases', []), $app->make(PackageManifest::class)->aliases() ))->register(); } }


Ve işte burada, setFacadeApplication() statik yöntemini kullanarak uygulama konteynerini Facade sınıfına ayarlıyoruz setFacadeApplication().

 // RegisterFacades.php public static function setFacadeApplication($app) { static::$app = $app; }


Bakın, resolveFacadeInstance() içinde test ettiğimiz $app özelliğini atadık. Bu sorunun cevabını veriyor; devam edelim.

 // Facade.php protected static function resolveFacadeInstance($name) { if (isset(static::$resolvedInstance[$name])) { return static::$resolvedInstance[$name]; } if (static::$app) { if (static::$cached) { return static::$resolvedInstance[$name] = static::$app[$name]; } return static::$app[$name]; } }

Uygulama önyüklemesi sırasında $app ayarlandığını doğruladık. Bir sonraki adım, varsayılan olarak true olan $cached değerini doğrulayarak çözümlenen örneğin önbelleğe alınması gerekip gerekmediğini kontrol etmektir. Son olarak, örneği uygulama kapsayıcısından alıyoruz, bizim durumumuzda bu, static::$app['router'] router dizesine bağlı herhangi bir sınıfı sağlamasını istemek gibidir.


Şimdi, $app uygulama kabının bir örneği, yani bir nesne olmasına rağmen neden bir dizi gibi eriştiğimizi merak edebilirsiniz. Haklısın! Ancak uygulama kapsayıcısı, dizi benzeri erişime izin veren ArrayAccess adlı bir PHP arabirimi uygular. Bu gerçeği teyit etmek için şuna bir göz atabiliriz:

 <?php namespace Illuminate\Container; use ArrayAccess; // <- this guy use Illuminate\Contracts\Container\Container as ContainerContract; class Container implements ArrayAccess, ContainerContract { // ... }


Dolayısıyla, resolveFacadeInstance() gerçekten de router dizesine bağlı bir örneği, özellikle de \Illuminate\Routing\Router döndürür. Nasıl bildim? Route cephesine bir göz atın; Çoğu zaman, bu cephenin neyi gizlediğine veya daha kesin olarak yöntem çağrılarımızın hangi sınıfa proxy uygulanacağına dair ipucu veren bir PHPDoc @see bulacaksınız.


Şimdi __callStatic yöntemimize geri dönelim.

 <?php namespace Illuminate\Support\Facades; use RuntimeException; // ... abstract class Facade { // ... public static function __callStatic(string $method, array $args): mixed { $instance = static::getFacadeRoot(); if (! $instance) { throw new RuntimeException('A facade root has not been set.'); } return $instance->$method(...$args); } }


\Illuminate\Routing\Router sınıfının bir nesnesi olan $instance sahibiz. Ayarlanıp ayarlanmadığını test ederiz (ki bu bizim durumumuzda onaylanmıştır) ve doğrudan üzerindeki yöntemi çağırırız. Böylece bitiriyoruz.

 // Facade.php return $instance->get('/', Closure());


Artık get() öğesinin \Illuminate\Routing\Router sınıfı içinde mevcut olduğunu doğrulayabilirsiniz.

 <?php namespace Illuminate\Routing; use Illuminate\Routing\Route; use Illuminate\Contracts\Routing\BindingRegistrar; use Illuminate\Contracts\Routing\Registrar as RegistrarContract; // ... class Router implements BindingRegistrar, RegistrarContract { // ... public function get(string $uri, array|string|callable|null $action = null): Route { return $this->addRoute(['GET', 'HEAD'], $uri, $action); } }

Bu konuyu tamamlıyor! Sonuçta bu çok zor olmadı mı? Özetlemek gerekirse, bir cephe konteynere bağlı bir dize döndürür. Örneğin hello-world , HelloWorld sınıfına bağlı olabilir. Bir cephe üzerinde tanımsız bir metodu statik olarak çağırdığımızda örneğin HelloWorldFacade devreye __callStatic() devreye giriyor.


getFacadeAccessor() yönteminde kayıtlı dizeyi, kapsayıcı içinde bağlı olan şeye çözer ve çağrımızı, alınan bu örneğe proxy olarak yönlendirir. Böylece (new HelloWorld())->method() ile son buluruz. İşin özü bu! Hala tıklamadınız mı? O zaman cephemizi oluşturalım!

Cephemizi Yapalım

Diyelim ki bu sınıfa sahibiz:

 <?php namespace App\Http\Controllers; class HelloWorld { public function greet(): string { return "Hello, World!"; } }


Amaç HelloWorld::greet() çağırmaktır. Bunu yapmak için sınıfımızı uygulama konteynerine bağlayacağız. İlk önce AppServiceProvider gidin.

 <?php namespace App\Providers; use App\Http\Controllers; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function register(): void { $this->app->bind('hello-world', function ($app) { return new HelloWorld; }); } // ... }


Artık uygulama kapsayıcımızdan (veya daha önce bahsettiğim gibi kutudan) hello-world isteğinde bulunduğumuzda, HelloWorld örneğini döndürür. Ne kaldı? Basitçe hello-world dizesini döndüren bir cephe oluşturun.

 <?php namespace App\Http\Facades; use Illuminate\Support\Facades\Facade; class HelloWorldFacade extends Facade { protected static function getFacadeAccessor() { return 'hello-world'; } }


Bunu yerine getirdiğimizde, onu kullanmaya hazırız. Bunu web.php.

 <?php use App\Http\Facades; use Illuminate\Support\Facades\Route; Route::get('/', function () { return HelloWorldFacade::greet(); // Hello, World! });


HelloWorldFacade cephesinde greet() öğesinin bulunmadığını biliyoruz, __callStatic() tetikleniyor. Uygulama kapsayıcısından bir dizeyle (bizim durumumuzda hello-world ) temsil edilen bir sınıfı çeker. Ve bu bağlamayı zaten AppServiceProvider yaptık; Birisi bir hello-world isteğinde bulunduğunda ona bir HelloWorld örneği sağlaması talimatını verdik. Sonuç olarak, greet() gibi herhangi bir çağrı, alınan HelloWorld örneği üzerinde çalışacaktır. Ve bu kadar.


Tebrikler! Kendi cephenizi yarattınız!

Laravel Gerçek Zamanlı Cepheler

Artık cepheleri iyi anladığınıza göre, ortaya çıkarmanız gereken bir sihir numarası daha var. Gerçek zamanlı cepheleri kullanarak bir cephe oluşturmadan HelloWorld::greet() işlevini arayabildiğinizi hayal edin.


Bir bakalım:

 <?php use Facades\App\Http\Controllers; // Notice the prefix use Illuminate\Support\Facades\Route; Route::get('/', function () { return HelloWorld::greet(); // Hello, World! });


Denetleyicinin ad alanının önüne Facades ekleyerek, daha önce elde ettiğimiz sonucun aynısını elde ederiz. Ancak, HelloWorld denetleyicisinin greet() adında herhangi bir statik yönteme sahip olmadığı kesindir! Peki Facades\App\Http\Controllers\HelloWorld nereden geliyor? Bunun bir tür büyücülük gibi görünebileceğinin farkındayım ama bir kere anladığınızda oldukça basit.


Daha önce kontrol ettiğimiz \Illuminate\Foundation\Bootstrap\RegisterFacades $app:

 <?php namespace Illuminate\Foundation\Bootstrap; use Illuminate\Contracts\Foundation\Application; use Illuminate\Foundation\AliasLoader; use Illuminate\Foundation\PackageManifest; use Illuminate\Support\Facades\Facade; class RegisterFacades { public function bootstrap(Application $app): void { Facade::clearResolvedInstances(); Facade::setFacadeApplication($app); AliasLoader::getInstance(array_merge( $app->make('config')->get('app.aliases', []), $app->make(PackageManifest::class)->aliases() ))->register(); // Interested here } }


En sonunda register() yönteminin çağrıldığını görebilirsiniz. İçeriye bir göz atalım:

 <?php namespace Illuminate\Foundation; class AliasLoader { // ... protected $registered = false; public function register(): void { if (! $this->registered) { $this->prependToLoaderStack(); $this->registered = true; } } }


$registered değişkeni başlangıçta false olarak ayarlanmıştır. Bu nedenle if ifadesini girip prependToLoaderStack() yöntemini çağırıyoruz. Şimdi uygulamasını inceleyelim.

 // AliasLoader.php protected function prependToLoaderStack(): void { spl_autoload_register([$this, 'load'], true, true); }


Sihir yapılan yer burasıdır! Laravel, tanımsız bir sınıfa erişmeye çalışıldığında tetiklenen yerleşik bir PHP işlevi olan spl_autoload_register() işlevini çağırıyor. Bu gibi durumlarda yürütülecek mantığı tanımlar. Bu durumda Laravel, tanımsız bir çağrıyla karşılaştığında load() yöntemini çağırmayı seçer.


Ek olarak spl_autoload_register() , tanımlanmamış sınıfın adını çağırdığı yönteme veya işleve otomatik olarak iletir.


load() yöntemini inceleyelim; anahtar olmalı.

 // AliasLoader.php public function load($alias) { if (static::$facadeNamespace && str_starts_with($alias, static::$facadeNamespace)) { $this->loadFacade($alias); return true; } if (isset($this->aliases[$alias])) { return class_alias($this->aliases[$alias], $alias); } }

$facadeNamespace ayarlanıp ayarlanmadığını kontrol ederiz ve bizim durumumuzda hangi sınıf geçtiyse, Facades\App\Http\Controllers\HelloWorld $facadeNamespace ayarlananla başlar


Mantık, $facadeNamespace ayarlanıp ayarlanmadığını ve aktarılan sınıfın (bizim durumumuzda Facades\App\Http\Controllers\HelloWorld (tanımsız) olup olmadığını) $facadeNamespace.

 // AliasLoader.php protected static $facadeNamespace = 'Facades\\';


Koşulları karşılayarak denetleyicimizin ad alanının önüne Facades eklediğimiz için, loadFacade() işlemine geçiyoruz

 // AliasLoader.php protected function loadFacade($alias) { require $this->ensureFacadeExists($alias); }


Burada yöntem, ensureFacadeExists() öğesinden döndürülen yolu gerektirir. Dolayısıyla bir sonraki adım, bunun uygulanmasına girmektir.

 // AliasLoader.php protected function ensureFacadeExists($alias) { if (is_file($path = storage_path('framework/cache/facade-'.sha1($alias).'.php'))) { return $path; } file_put_contents($path, $this->formatFacadeStub( $alias, file_get_contents(__DIR__.'/stubs/facade.stub') )); return $path; }


Öncelikle framework/cache/facade-'.sha1($alias).'.php' isimli bir dosyanın var olup olmadığı kontrol ediliyor. Bizim durumumuzda bu dosya mevcut değil ve bir sonraki adımı tetikliyor: file_put_contents() . Bu işlev bir dosya oluşturur ve onu belirtilen $path kaydeder. Dosyanın içeriği, adına bakılırsa bir saplamadan bir cephe oluşturan formatFacadeStub() tarafından oluşturulur. Eğer facade.stub incelerseniz aşağıdakileri bulursunuz:

 <?php namespace DummyNamespace; use Illuminate\Support\Facades\Facade; /** * @see \DummyTarget */ class DummyClass extends Facade { /** * Get the registered name of the component. */ protected static function getFacadeAccessor(): string { return 'DummyTarget'; } }


Tanıdık geliyor? Aslında bunu manuel olarak yaptık. Artık formatFacadeStub() , Facades\\ önekini kaldırdıktan sonra boş içeriği tanımsız sınıfımızla değiştiriyor. Bu güncellenmiş cephe daha sonra saklanır. Sonuç olarak, loadFacade() bir dosya gerektirdiğinde bunu doğru bir şekilde yapar ve sonunda aşağıdaki dosyayı gerektirir:

 <?php namespace Facades\App\Http\Controllers; use Illuminate\Support\Facades\Facade; /** * @see \App\Http\Controllers\HelloWorld */ class HelloWorld extends Facade { /** * Get the registered name of the component. */ protected static function getFacadeAccessor(): string { return 'App\Http\Controllers\HelloWorld'; } }

Ve şimdi, olağan akışta, uygulama kapsayıcısından App\Http\Controllers\HelloWorld dizesine bağlı herhangi bir örneği döndürmesini istiyoruz. Bu dizeyi hiçbir şeye bağlamadığımızı, AppServiceProvider bile dokunmadığımızı merak ediyor olabilirsiniz. Ama en başta uygulama kabı hakkında bahsettiğimi hatırlıyor musunuz?


Kutu boş olsa bile örneği döndürür , ancak bir şartla sınıfın bir yapıcısının olmaması gerekir. Aksi halde onu sizin için nasıl inşa edeceğini bilemezdi. Bizim durumumuzda HelloWorld sınıfımızın oluşturulması için herhangi bir argümana ihtiyacı yoktur. Böylece kapsayıcı bunu çözer, geri gönderir ve tüm çağrılar ona proxy olarak aktarılır.


Gerçek zamanlı cephelerin özetlenmesi: Sınıfımıza Facades ön ekini ekledik. Uygulama önyüklemesi sırasında Laravel, tanımsız sınıfları çağırdığımızda tetiklenen spl_autoload_register() dosyasını kaydeder. Sonunda load() yöntemine yol açar. load() içinde, mevcut tanımsız sınıfın Facades önekiyle eklenip eklenmediğini kontrol ederiz. Eşleşiyor, dolayısıyla Laravel onu yüklemeye çalışıyor.


Cephe mevcut olmadığından onu bir saplamadan oluşturur ve ardından dosyayı gerektirir. Ve işte! Sıradan bir cepheniz var ama bu anında yaratıldı. Oldukça hoş, değil mi?

Çözüm

Bu noktaya kadar geldiğiniz için tebrikler! Bunun biraz bunaltıcı olabileceğini anlıyorum. Geri dönmekten ve sizin için tam olarak tatmin edici olmayan bölümleri yeniden okumaktan çekinmeyin. IDE'nizi takip etmek de yardımcı olabilir. Ama hey, artık kara büyü yok, iyi hissettiriyor olmalı, en azından ilk seferinde böyle hissettim!


Ve unutmayın, bir dahaki sefere bir yöntemi statik olarak çağırdığınızda durum böyle olmayabilir 🪄