paint-brush
Laravel Under The Hood - Cách mở rộng khungby@oussamamater
1,093
1,093

Laravel Under The Hood - Cách mở rộng khung

Oussama Mater6m2024/05/29
Read on Terminal Reader

Laravel bao bọc FakerPHP, thứ mà chúng ta thường truy cập thông qua trình trợ giúp `fake()`. Faker PHP đi kèm với các công cụ sửa đổi như `valid()` và `unique()`, nhưng bạn chỉ có thể sử dụng một công cụ sửa đổi tại một thời điểm. Điều này khiến tôi suy nghĩ, nếu chúng ta muốn tạo công cụ sửa đổi của riêng mình thì sao? Ví dụ: `uniqueAndValid()` hoặc bất kỳ công cụ sửa đổi nào khác.
featured image - Laravel Under The Hood - Cách mở rộng khung
Oussama Mater HackerNoon profile picture

Một vài ngày trước, tôi đang sửa một bài kiểm tra không ổn định và hóa ra tôi cần một số giá trị duy nhấthợp lệ trong nhà máy của mình. Laravel bao bọc FakerPHP, thứ mà chúng ta thường truy cập thông qua trình trợ giúp fake() . FakerPHP đi kèm với các công cụ sửa đổi như valid()unique() , nhưng bạn chỉ có thể sử dụng một công cụ mỗi lần, vì vậy bạn không thể thực hiện fake()->unique()->valid() , đó chính xác là những gì tôi cần.


Điều này khiến tôi suy nghĩ, nếu chúng ta muốn tạo công cụ sửa đổi của riêng mình thì sao? Ví dụ: uniqueAndValid() hoặc bất kỳ công cụ sửa đổi nào khác. Làm thế nào chúng ta có thể mở rộng khuôn khổ?

Nghĩ tới điều to lớn

Tôi sẽ vứt bỏ dòng suy nghĩ của mình.


Trước khi chuyển sang bất kỳ giải pháp kỹ thuật quá mức nào, tôi luôn muốn kiểm tra xem có tùy chọn nào đơn giản hơn không và hiểu rõ những gì tôi đang giải quyết. Vì vậy, chúng ta hãy xem trình trợ giúp fake() :

 function fake($locale = null) { if (app()->bound('config')) { $locale ??= app('config')->get('app.faker_locale'); } $locale ??= 'en_US'; $abstract = \Faker\Generator::class.':'.$locale; if (! app()->bound($abstract)) { app()->singleton($abstract, fn () => \Faker\Factory::create($locale)); } return app()->make($abstract); }

Đọc mã, chúng ta có thể thấy Laravel liên kết một singleton với container. Tuy nhiên, nếu chúng ta kiểm tra phần tóm tắt thì đó là một lớp thông thường không triển khai bất kỳ giao diện nào và đối tượng được tạo thông qua một nhà máy. Điều này làm phức tạp mọi thứ. Tại sao?


  1. Bởi vì nếu đó là một giao diện, chúng ta chỉ cần tạo một lớp mới mở rộng lớp \Faker\Generator cơ sở, thêm một số tính năng mới và gắn lại nó vào vùng chứa. Nhưng chúng tôi không có thứ xa xỉ này.


  2. Có một nhà máy tham gia. Điều này có nghĩa đây không phải là một sự khởi tạo đơn giản; có một số logic đang được chạy. Trong trường hợp này, nhà máy bổ sung thêm một số nhà cung cấp (PhoneNumber, Text, UserAgent, v.v.). Vì vậy, ngay cả khi chúng tôi cố gắng liên kết lại, chúng tôi sẽ phải sử dụng nhà máy, nó sẽ trả về \Faker\Generator ban đầu.


Giải pháp 🤔? Người ta có thể nghĩ, "Điều gì đang ngăn cản chúng ta tạo ra một nhà máy riêng để trả lại máy phát điện mới như đã nêu ở điểm 1?" Chà, không có gì, chúng tôi có thể làm điều đó, nhưng chúng tôi sẽ không! Chúng tôi sử dụng một khung vì nhiều lý do, một trong số đó là cập nhật. Điều gì sẽ xảy ra nếu FakerPHP thêm nhà cung cấp mới hoặc có bản nâng cấp lớn? Laravel sẽ điều chỉnh mã và những người chưa thực hiện bất kỳ thay đổi nào sẽ không nhận thấy điều gì. Tuy nhiên, chúng tôi sẽ bị loại và mã của chúng tôi thậm chí có thể bị hỏng (rất có thể). Vì vậy, vâng, chúng tôi không muốn đi xa đến thế.

Vậy ta phải làm sao?

Bây giờ chúng ta đã khám phá các tùy chọn cơ bản, chúng ta có thể bắt đầu nghĩ đến những tùy chọn nâng cao hơn, chẳng hạn như các mẫu thiết kế. Chúng tôi không cần triển khai chính xác, chỉ cần một cái gì đó quen thuộc với vấn đề của chúng tôi. Đây là lý do tại sao tôi luôn nói rằng thật tốt khi biết họ. Trong trường hợp này, chúng ta có thể "trang trí" lớp Generator bằng cách thêm các tính năng mới trong khi vẫn duy trì các tính năng cũ. Âm thanh tốt? Hãy xem làm thế nào!


Đầu tiên, hãy tạo một lớp mới, FakerGenerator :

 <?php namespace App\Support; use Closure; use Faker\Generator; use Illuminate\Support\Traits\ForwardsCalls; class FakerGenerator { use ForwardsCalls; public function __construct(private readonly Generator $generator) { } public function uniqueAndValid(Closure $validator = null): UniqueAndValidGenerator { return new UniqueAndValidGenerator($this->generator, $validator); } public function __call($method, $parameters): mixed { return $this->forwardCallTo($this->generator, $method, $parameters); } }

Đây sẽ là "trang trí" của chúng tôi (loại). Đây là một lớp đơn giản mong đợi Generator cơ sở đóng vai trò phụ thuộc và giới thiệu một công cụ sửa đổi mới, uniqueAndValid() . Nó cũng sử dụng đặc điểm ForwardsCalls từ Laravel, cho phép nó thực hiện các lệnh gọi proxy đến đối tượng cơ sở.


Đặc điểm này có hai phương thức: forwardCallToforwardDecoratedCallTo . Sử dụng cái sau khi bạn muốn xâu chuỗi các phương thức trên đối tượng được trang trí. Trong trường hợp của chúng tôi, chúng tôi sẽ luôn có một cuộc gọi duy nhất.


Chúng ta cũng cần triển khai UniqueAndValidGenerator , đây là công cụ sửa đổi tùy chỉnh, nhưng đây không phải là mục đích của bài viết. Nếu bạn quan tâm đến việc triển khai, lớp này về cơ bản là sự kết hợp của ValidGeneratorUniqueGenerator đi kèm với FakerPHP, bạn có thể tìm thấy nó ở đây .


Bây giờ, hãy mở rộng framework trong AppServiceProvider :

 <?php namespace App\Providers; use Closure; use Faker\Generator; use App\Support\FakerGenerator; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function register(): void { $this->app->extend( $this->fakerAbstractName(), fn (Generator $base) => new FakerGenerator($base) ); } private function fakerAbstractName(): string { // This is important, it matches the name bound by the fake() helper return Generator::class . ':' . app('config')->get('app.faker_locale'); } }


Phương thức extend() kiểm tra xem một bản tóm tắt khớp với tên đã cho có được liên kết với vùng chứa hay không. Nếu vậy, nó sẽ ghi đè giá trị của nó bằng kết quả của việc đóng, hãy xem:

 // Laravel: src/Illuminate/Container/Container.php public function extend($abstract, Closure $closure) { $abstract = $this->getAlias($abstract); if (isset($this->instances[$abstract])) { // You are interested here $this->instances[$abstract] = $closure($this->instances[$abstract], $this); $this->rebound($abstract); } else { $this->extenders[$abstract][] = $closure; if ($this->resolved($abstract)) { $this->rebound($abstract); } } }

Đó là lý do tại sao chúng tôi đã xác định phương thức fakerAbstractName() , phương thức này tạo ra cùng tên với trình trợ giúp fake() liên kết trong vùng chứa.


Kiểm tra lại mã ở trên nếu bạn bỏ sót, tôi đã để lại nhận xét.


Bây giờ, mỗi khi chúng ta gọi fake() , một phiên bản của FakerGenerator sẽ được trả về và chúng ta sẽ có quyền truy cập vào công cụ sửa đổi tùy chỉnh mà chúng ta đã giới thiệu. Mỗi khi chúng ta gọi một cuộc gọi không tồn tại trên lớp FakerGenerator , __call() sẽ được kích hoạt và nó sẽ ủy quyền cuộc gọi đó tới Generator cơ sở bằng phương thức forwardCallTo() .


Đó là nó! Cuối cùng tôi cũng có thể làm fake()->uniqueAndValid()->randomElement() và nó hoạt động như một cơ duyên!


Trước khi kết luận, tôi muốn chỉ ra rằng đây không phải là một mẫu trang trí thuần túy. Tuy nhiên, khuôn mẫu không phải là văn bản thiêng liêng; điều chỉnh chúng để phù hợp với nhu cầu của bạn và giải quyết vấn đề.


Phần kết luận

Các framework cực kỳ hữu ích và Laravel có rất nhiều tính năng tích hợp sẵn. Tuy nhiên, chúng không thể bao gồm tất cả các trường hợp khó khăn trong dự án của bạn và đôi khi, bạn có thể đi vào ngõ cụt. Khi điều đó xảy ra, bạn luôn có thể mở rộng khuôn khổ. Chúng ta đã thấy nó đơn giản như thế nào và tôi hy vọng bạn hiểu ý chính, áp dụng ngoài ví dụ về Faker này.


Luôn bắt đầu đơn giản và tìm kiếm giải pháp đơn giản nhất cho vấn đề. Sự phức tạp sẽ xuất hiện khi cần thiết, vì vậy nếu tính kế thừa cơ bản thực hiện được thủ thuật thì không cần phải triển khai trình trang trí hay bất kỳ thứ gì khác. Khi bạn mở rộng khuôn khổ, hãy đảm bảo rằng bạn không đi quá xa, khiến cái mất nhiều hơn cái được. Bạn không muốn tự mình duy trì một phần của khuôn khổ.