Bạn vừa cài đặt một ứng dụng Laravel mới, khởi động nó và có trang chào mừng. Giống như những người khác, bạn thử xem nó được hiển thị như thế nào, vì vậy bạn truy cập vào tệp và gặp mã này: web.php <?php use Illuminate\Support\Facades\Route; Route::get('/', function () { return view('welcome'); }); Rõ ràng là chúng ta đã có được chế độ xem chào mừng như thế nào, nhưng bạn tò mò về cách hoạt động của bộ định tuyến của Laravel nên bạn quyết định đi sâu vào mã. Giả định ban đầu là: Có một lớp mà trên đó chúng ta đang gọi một phương thức tĩnh . Tuy nhiên, khi nhấp vào nó, không có phương thức nào ở đó. Vậy loại ma thuật đen tối nào đang xảy ra? Hãy làm sáng tỏ điều này! Route get() get() Mặt tiền thông thường Xin lưu ý rằng tôi đã loại bỏ hầu hết PHPDocs và nội tuyến các loại chỉ để đơn giản, "..." đề cập đến nhiều mã hơn. Tôi thực sự khuyên bạn nên mở IDE của mình và làm theo mã để tránh mọi nhầm lẫn. Theo ví dụ của chúng tôi, hãy cùng khám phá lớp . Route <?php namespace Illuminate\Support\Facades; class Route extends Facade { // ... protected static function getFacadeAccessor(): string { return 'router'; } } Ở đây không có gì nhiều, chỉ có phương thức trả về chuỗi . Hãy ghi nhớ điều này, hãy chuyển sang lớp cha. getFacadeAccessor() router <?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); } } Trong lớp cha, có rất nhiều phương thức, tuy nhiên không có phương thức . Nhưng có một điều thú vị, đó là phương thức . Đó là một phương thức , được gọi bất cứ khi nào một phương thức tĩnh không xác định, như trong trường hợp của chúng ta, được gọi. Do đó, lệnh gọi của chúng tôi thể hiện những gì chúng tôi đã chuyển khi gọi , tuyến đường và trả về chế độ xem chào mừng. get() __callStatic() kỳ diệu get() __callStatic('get', ['/', Closure()]) Route::get() / Closure() Khi được kích hoạt, trước tiên, nó sẽ cố gắng đặt một biến bằng cách gọi , giữ lớp thực tế mà cuộc gọi sẽ được chuyển tiếp, chúng ta hãy xem xét kỹ hơn, nó sẽ có ý nghĩa một chút __callStatic() $instance getFacadeRoot() $instance // Facade.php public static function getFacadeRoot() { return static::resolveFacadeInstance(static::getFacadeAccessor()); } Này, nhìn này, đây là từ lớp con mà chúng ta biết đã trả về chuỗi . Sau đó, chuỗi này được chuyển đến , cố gắng phân giải nó thành một lớp, một loại ánh xạ có nội dung "Chuỗi này đại diện cho lớp nào?" Hãy xem nào. getFacadeAccessor() Route router router resolveFacadeInstance() // 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]; } } Đầu tiên, nó kiểm tra xem một mảng tĩnh, , có giá trị được đặt với đã cho hay không (một lần nữa, là ). Nếu tìm thấy kết quả khớp, nó sẽ trả về giá trị đó. Đây là bộ nhớ đệm của Laravel để tối ưu hóa hiệu suất một chút. Bộ nhớ đệm này xảy ra trong một yêu cầu duy nhất. Nếu phương thức này được gọi nhiều lần với cùng một đối số trong cùng một yêu cầu thì phương thức này sẽ sử dụng giá trị được lưu trong bộ nhớ đệm. Giả sử đó là cuộc gọi đầu tiên và tiếp tục. $resolvedInstance $name router Sau đó, nó sẽ kiểm tra xem có được đặt hay không và có phải là một phiên bản của vùng chứa ứng dụng hay không $app $app // Facade.php protected static \Illuminate\Contracts\Foundation\Application $app; Nếu bạn tò mò về vùng chứa ứng dụng là gì, hãy coi nó như một hộp nơi các lớp của bạn được lưu trữ. Khi bạn cần những lớp đó, bạn chỉ cần chạm vào hộp đó. Đôi khi, thùng chứa này thực hiện một chút phép thuật. Ngay cả khi hộp trống và bạn với tay lấy một lớp, nó sẽ lấy lớp đó cho bạn. Đó là một chủ đề cho một bài viết khác. Bây giờ, bạn có thể thắc mắc, "Khi nào được đặt?", bởi vì nó cần phải như vậy, nếu không, chúng ta sẽ không có . Vùng chứa ứng dụng này được thiết lập trong quá trình khởi động ứng dụng của chúng tôi. Chúng ta hãy xem nhanh lớp : $app $instance \Illuminate\Foundation\Http\Kernel <?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()); } } } Khi có yêu cầu được gửi đến, nó sẽ được gửi đến bộ định tuyến. Ngay trước đó, phương thức được gọi, phương thức này sử dụng mảng để chuẩn bị ứng dụng. Nếu bạn khám phá phương thức trong lớp , nó sẽ lặp qua các trình khởi động này, gọi phương thức của chúng. bootstrap() bootstrappers bootstrapWith() \Illuminate\Foundation\Application bootstrap() Để đơn giản, chúng ta hãy tập trung vào mà chúng ta biết có chứa phương thức sẽ được gọi trong \Illuminate\Foundation\Bootstrap\RegisterFacades bootstrap() bootstrapWith() <?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(); } } Và thế là chúng ta đang thiết lập vùng chứa ứng dụng trên lớp bằng phương thức tĩnh Facade setFacadeApplication(). // RegisterFacades.php public static function setFacadeApplication($app) { static::$app = $app; } Hãy xem, chúng tôi chỉ định thuộc tính mà chúng tôi đang thử nghiệm trong . Điều này trả lời câu hỏi; tiếp tục đi. $app resolveFacadeInstance() // 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]; } } Chúng tôi xác nhận rằng được đặt trong quá trình khởi động ứng dụng. Bước tiếp theo là kiểm tra xem phiên bản đã giải quyết có nên được lưu vào bộ đệm hay không bằng cách xác minh , giá trị mặc định là true. Cuối cùng, chúng tôi truy xuất phiên bản từ vùng chứa ứng dụng, trong trường hợp của chúng tôi, nó giống như yêu cầu cung cấp bất kỳ lớp nào được liên kết với chuỗi . $app $cached static::$app['router'] router Bây giờ, bạn có thể thắc mắc tại sao chúng ta truy cập giống như một mảng mặc dù nó là một phiên bản của vùng chứa ứng dụng, do đó, một . Vâng, bạn nói đúng! Tuy nhiên, vùng chứa ứng dụng triển khai giao diện PHP có tên , cho phép truy cập giống như mảng. Chúng ta có thể xem xét nó để xác nhận sự thật này: $app object ArrayAccess <?php namespace Illuminate\Container; use ArrayAccess; // <- this guy use Illuminate\Contracts\Container\Container as ContainerContract; class Container implements ArrayAccess, ContainerContract { // ... } Vì vậy, thực sự trả về một phiên bản được liên kết với chuỗi , cụ thể là . Làm sao tôi biết được? Hãy nhìn vào mặt tiền ; thông thường, bạn sẽ tìm thấy PHPDoc gợi ý về những gì mặt tiền này che giấu hoặc chính xác hơn là lớp mà các lệnh gọi phương thức của chúng ta sẽ được ủy quyền. resolveFacadeInstance() router \Illuminate\Routing\Router Route @see Bây giờ, hãy quay lại phương thức của chúng tôi. __callStatic <?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); } } Chúng ta có , một đối tượng của lớp . Chúng tôi kiểm tra xem nó đã được đặt chưa (trong trường hợp của chúng tôi là được xác nhận) và chúng tôi trực tiếp gọi phương thức trên đó. Vì vậy, chúng tôi kết thúc với. $instance \Illuminate\Routing\Router // Facade.php return $instance->get('/', Closure()); Và bây giờ, bạn có thể xác nhận tồn tại trong lớp . get() \Illuminate\Routing\Router <?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); } } Điều đó kết thúc nó! Cuối cùng điều đó không khó khăn sao? Tóm lại, một mặt tiền trả về một chuỗi được liên kết với vùng chứa. Ví dụ: có thể bị ràng buộc với lớp . Khi chúng ta gọi tĩnh một phương thức không xác định trên một mặt tiền, chẳng hạn như , sẽ bước vào. hello-world HelloWorld HelloWorldFacade __callStatic() Nó phân giải chuỗi đã đăng ký trong phương thức của nó thành bất kỳ chuỗi nào bị ràng buộc trong vùng chứa và ủy quyền lệnh gọi của chúng ta tới phiên bản được truy xuất đó. Vì vậy, chúng tôi kết thúc với . Đó là bản chất của nó! Vẫn chưa nhấp chuột cho bạn? Hãy tạo mặt tiền của chúng ta sau đó! getFacadeAccessor() (new HelloWorld())->method() Hãy làm mặt tiền của chúng tôi Giả sử chúng ta có lớp này: <?php namespace App\Http\Controllers; class HelloWorld { public function greet(): string { return "Hello, World!"; } } Mục tiêu là gọi . Để làm điều này, chúng ta sẽ liên kết lớp của mình với vùng chứa ứng dụng. Đầu tiên, điều hướng đến . HelloWorld::greet() AppServiceProvider <?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; }); } // ... } Bây giờ, bất cứ khi nào chúng ta yêu cầu từ vùng chứa ứng dụng của mình (hoặc hộp, như tôi đã đề cập trước đó), nó sẽ trả về một phiên bản của . Những gì còn lại? Chỉ cần tạo một mặt tiền trả về chuỗi . hello-world HelloWorld hello-world <?php namespace App\Http\Facades; use Illuminate\Support\Facades\Facade; class HelloWorldFacade extends Facade { protected static function getFacadeAccessor() { return 'hello-world'; } } Với điều này tại chỗ, chúng tôi đã sẵn sàng để sử dụng nó. Hãy gọi nó trong web.php. <?php use App\Http\Facades; use Illuminate\Support\Facades\Route; Route::get('/', function () { return HelloWorldFacade::greet(); // Hello, World! }); Chúng tôi biết rằng không tồn tại trên mặt tiền , được kích hoạt. Nó kéo một lớp được biểu thị bằng một chuỗi ( trong trường hợp của chúng tôi) từ vùng chứa ứng dụng. Và chúng tôi đã thực hiện ràng buộc này trong ; chúng tôi đã hướng dẫn nó cung cấp một phiên bản bất cứ khi nào ai đó yêu cầu . Do đó, bất kỳ lệnh gọi nào, chẳng hạn như , sẽ hoạt động trên phiên bản được truy xuất đó của . Và thế là xong. greet() HelloWorldFacade __callStatic() hello-world AppServiceProvider HelloWorld hello-world greet() HelloWorld Chúc mừng! Bạn đã tạo ra mặt tiền của riêng mình! Mặt tiền thời gian thực của Laravel Bây giờ bạn đã hiểu rõ về mặt tiền, sẽ có thêm một trò ảo thuật nữa sắp được tiết lộ. Hãy tưởng tượng bạn có thể gọi mà không cần tạo mặt tiền, sử dụng . HelloWorld::greet() mặt tiền thời gian thực Chúng ta hãy có một cái nhìn: <?php use Facades\App\Http\Controllers; // Notice the prefix use Illuminate\Support\Facades\Route; Route::get('/', function () { return HelloWorld::greet(); // Hello, World! }); Bằng cách thêm tiền tố vào không gian tên của bộ điều khiển với , chúng ta đạt được kết quả tương tự như trước đó. Tuy nhiên, chắc chắn rằng bộ điều khiển không có bất kỳ phương thức tĩnh nào có tên là ! Và thậm chí đến từ đâu? Tôi hiểu điều này có vẻ giống như một phép thuật nào đó, nhưng một khi bạn nắm bắt được nó thì nó khá đơn giản. Facades HelloWorld greet() Facades\App\Http\Controllers\HelloWorld Chúng ta hãy xem xét kỹ hơn về mà chúng ta đã kiểm tra trước đó, lớp chịu trách nhiệm thiết lập \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 } } Bạn có thể thấy ở phần cuối phương thức được gọi. Chúng ta hãy nhìn vào bên trong: register() <?php namespace Illuminate\Foundation; class AliasLoader { // ... protected $registered = false; public function register(): void { if (! $this->registered) { $this->prependToLoaderStack(); $this->registered = true; } } } Biến ban đầu được đặt thành . Do đó, chúng ta nhập câu lệnh và gọi phương thức . Bây giờ, hãy khám phá việc thực hiện nó. $registered false if prependToLoaderStack() // AliasLoader.php protected function prependToLoaderStack(): void { spl_autoload_register([$this, 'load'], true, true); } Đây là nơi phép thuật xảy ra! Laravel đang gọi hàm , một hàm PHP tích hợp kích hoạt khi cố gắng truy cập một lớp không xác định. Nó xác định logic để thực thi trong những tình huống như vậy. Trong trường hợp này, Laravel chọn gọi phương thức khi gặp một cuộc gọi không xác định. spl_autoload_register() load() Ngoài ra, tự động chuyển tên của lớp không xác định cho bất kỳ phương thức hoặc hàm nào mà nó gọi. spl_autoload_register() Hãy cùng khám phá phương thức ; nó phải là chìa khóa. load() // 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); } } Chúng tôi kiểm tra xem có được đặt hay không và nếu bất kỳ lớp nào được thông qua, trong trường hợp của chúng tôi, bắt đầu với bất kỳ lớp nào được đặt trong $facadeNamespace Facades\App\Http\Controllers\HelloWorld $facadeNamespace Logic kiểm tra xem có được đặt hay không và liệu lớp đã truyền, trong trường hợp của chúng ta là (không xác định), có bắt đầu bằng giá trị được chỉ định trong $facadeNamespace Facades\App\Http\Controllers\HelloWorld $facadeNamespace. // AliasLoader.php protected static $facadeNamespace = 'Facades\\'; Vì chúng tôi đã thêm tiền tố vào không gian tên của bộ điều khiển bằng , thỏa mãn điều kiện, nên chúng tôi tiến hành Facades loadFacade() // AliasLoader.php protected function loadFacade($alias) { require $this->ensureFacadeExists($alias); } Ở đây, phương thức này yêu cầu bất kỳ đường dẫn nào được trả về từ . Vì vậy, bước tiếp theo là đi sâu vào việc thực hiện nó. ensureFacadeExists() // 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; } Đầu tiên, chúng tôi thực hiện kiểm tra để xác định xem tệp có tên có tồn tại hay không. Trong trường hợp của chúng tôi, tệp này không xuất hiện, điều này sẽ kích hoạt bước tiếp theo: . Hàm này tạo một tệp và lưu nó vào được chỉ định. Nội dung của tệp được tạo bởi , dựa trên tên của nó, sẽ tạo ra một mặt tiền từ một sơ khai. Nếu bạn kiểm tra , bạn sẽ thấy như sau: framework/cache/facade-'.sha1($alias).'.php' file_put_contents() $path formatFacadeStub() facade.stub <?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'; } } Trông quen quen nhỉ? Về cơ bản đó là những gì chúng tôi đã làm bằng tay. Bây giờ, thay thế nội dung giả bằng lớp không xác định của chúng ta sau khi xóa tiền tố . Mặt tiền cập nhật này sau đó được lưu trữ. Do đó, khi yêu cầu tệp, nó thực hiện chính xác và cuối cùng nó yêu cầu tệp sau: formatFacadeStub() Facades\\ loadFacade() <?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'; } } Và bây giờ, theo quy trình thông thường, chúng tôi yêu cầu vùng chứa ứng dụng trả về bất kỳ phiên bản nào được liên kết với chuỗi . Bạn có thể thắc mắc, chúng tôi không liên kết chuỗi này với bất kỳ thứ gì, chúng tôi thậm chí còn không chạm vào của mình. Nhưng hãy nhớ những gì tôi đã đề cập về vùng chứa ứng dụng ngay từ đầu? App\Http\Controllers\HelloWorld AppServiceProvider , nhưng với một điều kiện, lớp không được có hàm tạo. Nếu không, nó sẽ không biết cách xây dựng nó cho bạn. Trong trường hợp của chúng tôi, lớp không cần xây dựng bất kỳ đối số nào. Vì vậy, vùng chứa sẽ giải quyết nó, trả về nó và tất cả các cuộc gọi sẽ được ủy quyền cho nó. Ngay cả khi hộp trống, nó sẽ trả về thể hiện HelloWorld Tóm tắt lại các mặt tiền thời gian thực: Chúng tôi đã thêm tiền tố cho lớp của mình bằng . Trong quá trình khởi động ứng dụng, Laravel đăng ký , điều này sẽ kích hoạt khi chúng ta gọi các lớp không xác định. Cuối cùng nó dẫn tới phương thức . Bên trong , chúng tôi kiểm tra xem lớp không xác định hiện tại có tiền tố là hay không. Nó khớp nên Laravel cố tải nó. Facades spl_autoload_register() load() load() Facades Vì mặt tiền không tồn tại nên nó tạo nó từ một sơ khai và sau đó yêu cầu tệp. Và Voila! Bạn có một mặt tiền thông thường, nhưng mặt tiền này được tạo ra một cách nhanh chóng. Khá tuyệt phải không? Phần kết luận Chúc mừng bạn đã tiến xa đến mức này! Tôi hiểu nó có thể hơi quá sức. Vui lòng quay lại và đọc lại bất kỳ phần nào chưa phù hợp với bạn. Theo dõi IDE của bạn cũng có thể hữu ích. Nhưng này, không còn ma thuật đen nữa, hẳn là cảm thấy dễ chịu, ít nhất đó là cảm giác của tôi lần đầu tiên! Và hãy nhớ rằng, lần tới khi bạn gọi một phương thức tĩnh, điều đó có thể không đúng như vậy 🪄