post-image

Laravel đơn giản, đẹp và hiệu quả hơn với Laravel Facade

Tổng quan

1. Giới thiệu

Có thể nói Laravel là framework PHP số 1 hiện nay vì mấy lý do sau:

  • Cú pháp đơn giản, dễ bắt đầu ngay cả với người mới làm quen Laravel.
  • Một lý do nữa là framework Laravel là tổng hợp của rất nhiều các gói thư viện tốt nhất, các thành phần này có thể được viết bởi nhóm phát triển Laravel hoặc từ các framework khác. Đặc biệt, Laravel cũng lấy khá nhiều các ý tưởng từ các ngôn ngữ lập trình khác như .NET, Java cũng như các IDE tiên tiến như Visual Studio. Cũng dễ hiểu thôi bởi Taylor Otwell, người sáng lập ra Laravel đã từng có rất nhiều năm kinh nghiệm trong lập trình .NET.

2. Laravel Facade là gì?

Quay trở lại bài viết, sở dĩ chúng ta có tiêu đề bài viết “Laravel đơn giản, đẹp và hiệu quả hơn với Laravel Facade”, đúng vậy kể từ khi học Laravel từ các bài đọc trước bạn đã bắt đầu ngay với các khái niệm ở thượng tầng hệ thống một cách rất dễ hiểu, nhưng bạn không ngờ tới rằng mình sử dụng Laravel Facade rất nhiều. Ví dụ, khái niệm đầu tiên khi làm quen với Laravel là Laravel Route, bạn sẽ thấy ngay một route được định nghĩa sẵn trong routes\web.php:

Route::get('/', function () { return view('welcome'); });
Code language: PHP (php)

Route ở đây chính là một Laravel Facade và get() là một phương thức của Facade Route. Facade là một đề tài nóng trong cộng đồng Laravel, một số người thích nó, một số người khác thì không. Cá nhân tôi, Facade là một phần không thể thiếu của Laravel, không có nó Laravel không thể trở thành một framework PHP phổ biến như hôm nay. Laravel Facade có mấy điểm mà rất nhiều người thắc mắc:

  • Thứ nhất, Laravel Facade có tên giống một mẫu lập trình (Design Pattern) rất hay dùng là Facade Pattern, tuy nhiên khi bạn làm việc sâu hơn bạn sẽ thấy nó tương đồng hơn với Proxy Pattern, cũng có một số ý kiến rằng ở một góc độ khác nó giống với Adapter Pattern hoặc Decorator Pattern. Trước hết Laravel Facade là một thành phần trong framework, nó không phải là một pattern và sau đó Laravel Facade áp dụng nhiều các pattern để tạo ra một thư viện rất tuyệt vời.
  • Thứ hai, các phương thức của Laravel Facade được gọi giống như các phương thức static với việc sử dụng toán tử phạm vi :: để gọi phương thức. Nhưng không phải vậy, bên trong nội tại Facade sử dụng magic method __callStatic() một phương thức được xây dựng sẵn trong các đối tượng được thực hiện khi có một lời gọi phương thức static đến class. Chúng ta sẽ bàn luận chi tiết hơn ở phần tới.

3. Laravel Facade hoạt động như thế nào?

3.1 __callStatic là mấu chốt của Laravel Facade

Các thành phần trong Laravel được lưu giữ trong một container, bạn có thể tưởng tượng nó là một cái hộp lớn lưu trữ toàn bộ các service. Các service này chỉ được tạo ra instance một lần và chỉ tạo ra khi cần dùng đến, tức là bạn không cần khởi tạo các dịch vụ này, nó tự động hoàn toàn. Chúng ta cùng tìm hiểu xem cách Laravel Facade sử dụng container như thế nào?

Trở lại với ví dụ Route đầu tiên ở trên, nó gọi đến phương thức get() theo cách gọi một phương thức static, nhưng không phải vậy Laravel Facade tương đồng với Proxy Pattern, nó sử dụng magic method __callStatic() để làm một bức tường chắn phía trước (proxy) để thực hiện các phương thức của lớp. Khi chúng ta gọi phương thức get(), Facade sẽ lấy các service liên quan trong container và trả về một instance, do vậy nó có thể thực thi được phương thức của class tương ứng. Chính vì thế cách viết code sau là cùng kết quả với ví dụ đầu.

$app->router->get('/', function () { return view('welcome'); });
Code language: PHP (php)

Đến đây, bạn thấy rõ cú pháp của Laravel đã đơn giản, dễ đọc hơn khi áp dụng Laravel Facade. Tuy nhiên, một câu hỏi chưa được trả lời “Laravel Facade hoạt động như thế nào?”. Chúng ta cùng tiếp tục tìm hiểu nhé. Khi bạn gọi Route::get() thì Route là một cái tên ngắn (alias) cho tên lớp đầy đủ Illuminate\Support\Facades\Route. Thiết lập này được thực hiện trong file cấu hình config\app.php trong mảng aliases:

'aliases' => [ ... 'Route' => Illuminate\Support\Facades\Route::class, ... ],
Code language: PHP (php)

Như vậy, khi bạn thực hiện Route::get() thì thực ra là bạn gọi đến Illuminate\Support\Facades\Route::get(). Chúng ta sẽ đi sâu hơn vào framework để hiểu cách hoạt động của Laravel Facade, class Illuminate\Support\Facades\Route được lưu trữ trong file vendor\laravel\framework\src\Illuminate\Support\Facades\Route.php, mở file này chúng ta thấy các Facade có chung một phương thức getFacadeAccessor:

namespace Illuminate\Support\Facades; class Route extends Facade { /** * Get the registered name of the component. * * @return string */ protected static function getFacadeAccessor() { return 'router'; } }
Code language: PHP (php)

Các class này đều được mở rộng từ class cơ bản Facade, như ở trên đã nói thì các phương thức của Facade được gọi theo kiểu phương thức static do đó khi gọi nó sẽ thực hiện magic method callStatic() của class. Như vậy, phương thức callStatic của class Facade chính là nơi xử lý các lời gọi phương thức kiểu như Route::get().

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

Laravel sử dụng spread operator (toán tử …) từ phiên bản Laravel 5.0 trở đi, với các phiên bản cũ hơn, phương thức __callStatic() được thực hiện như sau:

public static function __callStatic($method, $args) { $instance = static::getFacadeRoot(); switch (count($args)) { case 0: return $instance->$method(); case 1: return $instance->$method($args[0]); case 2: return $instance->$method($args[0], $args[1]); case 3: return $instance->$method($args[0], $args[1], $args[2]); case 4: return $instance->$method($args[0], $args[1], $args[2], $args[3]); default: return call_user_func_array(array($instance, $method), $args); } }
Code language: PHP (php)

Chúng ta thấy dù là theo cách nào, tại đây Facade sẽ lấy một instance tương ứng với class và thực hiện gọi đến phương thức với việc truyền vào cả các tham số.

3.2 Sử dụng IoC Container

IoC viết tắt của Inversion of Control, để hiểu được Inversion of Control là gì chúng ta đến với vấn đề sau trong lập trình. Một class A phụ thuộc vào một service hoặc một component là những lớp cụ thể (concrete class) trong lúc chạy ứng dụng.

Dependency runtime

Chúng ta có một số vấn đề sau:

  • Nếu thay thế hoặc cập nhật các phụ thuộc, chúng ta cần thay đổi mã nguồn của class.
  • Các lớp cụ thể của một phụ thuộc có thể dùng được trong thời gian chạy hay không?
  • Các class như vậy không tách biệt và rất khó cho unit test bởi chúng phụ thuộc trực tiếp, như vậy các phụ thuộc không thể thay thế bởi các stub hoặc mock trong test.
  • Các class cần phải viết những đoạn mã cho việc tạo, tìm kiếm và quản lý các phụ thuộc.

Nguyên lý Inversion of Control để giải quyết vấn đề trên, nó ủy quyền thực hiện việc lựa chọn các class cụ thể cho một component ở ngoài. Thực hiện nguyên lý này có hai pattern hay được sử dụng là Service Locator và Dependency Injection. Dưới đây là hai cách thực hiện nguyên lý trên.

Service Locator:

Service Locator Pattern

Dependency Injection:

Dependency Injection

Các khái niệm này chúng ta sẽ cùng nhau bàn cụ thể hơn trong các bài viết khác, quay trở lại với Laravel Facade, nó sử dụng một Inversion of Control Container hay chính là một trong hai pattern trên, ở đây Laravel sử dụng IoC Container là Service Locator. Container này đơn giản là một lưu trữ kiểu key-value để tìm ra thực thể của class cần sử dụng.

Trong luồng hướng đối tượng, việc gán tĩnh các object được thay thế bởi builder object, nó sẽ đưa ra các object ở runtime (thông thường là tạo đối tượng ngay, nhưng ở đây sẽ tạo đối tượng trong một lúc nào đó không xác định khi ứng dụng chạy). Với kỹ thuật này, dễ dàng thay thế các đối tượng với các thực thể khác phù hợp. Chúng ta cùng xem một ví dụ về container:

class App { private static $instances = []; public static function set($name, $instance) { if (is_string($instance)) { $instance = new $instance(); } static::$instances[$name] = $instance; } public static function get($name) { $instance = static::$instances[$name]; if ($instance instanceof Closure) { $instance = $instance(); } return $instance; } }
Code language: PHP (php)

Hai phương thức set và get trong class trên cho phép tương tác với mảng các instance, người dùng có thể thêm hoặc lấy ra các thực thể thông qua tên class. Ví dụ chúng ta có một class sau:

interface MessageInterface { public function greeting(); } class EnglishMessage implements MessageInterface { public function greeting() { return 'Hello!'; } } class VietnameseMessage implements MessageInterface { public function greeting() { return 'Xin chào!'; } }
Code language: PHP (php)

Khi đó chúng ta có thể sử dụng Service Locator như sau:

App::set('message', 'EnglishMessage'); echo App::get('message')->greeting(); // Hello!
Code language: PHP (php)

Khi bạn muốn thay thế thực thể của EnglishMessage bởi một thực thể của class khác phù hợp, ví dụ như VietnameseMessage, chúng ta chỉ cần thiết lập lại và vẫn thực hiện các phương thức get như bình thường.

App::set('message', 'VietnameseMessage'); echo App::get('message')->greeting(); // Xin chào!
Code language: PHP (php)

Bạn thấy đấy, phần code thực hiện không bị thay đổi gì, và nó giải quyết vấn đề về phụ thuộc trong lập trình. Trong Laravel Facade, nó cũng thực hiện tương tự thông qua thuộc tính $resolvedInstance và phương thức resolveFacadeInstance() trong class Facade:

protected static function resolveFacadeInstance($name) { if (is_object($name)) { return $name; } if (isset(static::$resolvedInstance[$name])) { return static::$resolvedInstance[$name]; } return static::$resolvedInstance[$name] = static::$app[$name]; }
Code language: PHP (php)

4. Tạo Laravel Facade riêng trong Laravel 5.4

Đôi khi bạn cần sử dụng lại các đoạn code, các chức năng, bạn hoàn toàn có thể tạo ra một Laravel Facade riêng để sử dụng. Cách tạo một Facade cũng khá đơn giản, chúng ta cùng thực hiện các bước sau: Bước 1: Tạo class Trước hết bạn cần có một môi trường đã cài đặt sẵn Laravel, tạo một thư mục Allaravel trong thư mục app của project để lưu trữ các class tạo ra. Tiếp đó tạo một file DemoClass.php trong app\Allaravel với nội dung bạn muốn thực hiện:

namespace App\Allaravel; class DemoClass { public function demoMethod() { return 'This is custome Facade demo'; } }
Code language: PHP (php)

Class này rất đơn giản với phương thức demoMethod() trả về dòng chữ ‘This is custome Facade demo’. Bước 2: Tạo Service Provider Service Provider dùng để đăng ký class với framework, sử dụng câu lệnh Laravel Artisan để thực hiện:

$ php artisan make:provider DemoClassServiceProvider Provider created successfully.

câu lệnh này sẽ tạo ra file DemoClassServiceProvider.php trong thư mục app\Providers, mở file ra và thêm code gắn class.

<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use App; class DemoClassServiceProvider extends ServiceProvider { /** * Bootstrap the application services. * * @return void */ public function boot() { // } /** * Register the application services. * * @return void */ public function register() { App::bind('democlass', function() { return new \App\Allaravel\DemoClass; }); } }
Code language: HTML, XML (xml)

Bước 3: Tạo Facade class

Trong bước này chúng ta tạo ra một class DemoClassFacade.php nằm trong thư mục app\Allaravel, class này phải kế thừa từ class Facade.

namespace App\Allaravel; use Illuminate\Support\Facades\Facade; class DemoClassFacade extends Facade{ protected static function getFacadeAccessor() { return 'democlass'; } }
Code language: PHP (php)

Bước 4: Thiết lập cấu hình ứng dụng

Service Provider ở bước 2 và lớp DemoClassFacade cần được thiết lập trong file cấu hình của ứng dụng Laravel, các thiết lập này trong file config\app.php:

'providers' => [ ... App\Providers\DemoClassServiceProvider::class, ... ], 'aliases' => [ ... 'DemoClass'=> App\Allaravel\DemoClassFacade::class, ... ],
Code language: PHP (php)

Thiết lập thứ nhất cho Service Provider, thiết lập thứ hai để alias cho Facade.

Bước 5: Sử dụng DemoClassFacade

Mở file routes\web.php và thử sử dụng DemoClassFacade:

Route::get('/', function () { $message = DemoClass::demoMethod(); echo $message;\\ This is custome Facade demo });
Code language: PHP (php)

5. Lời kết

Laravel Facade là một nội dung nâng cao trong framework Laravel do nó ăn sâu vào trong hệ thống, cái hay của Laravel là cho người dùng mới sử dụng ngay Facade nhưng người dùng không biết gì về khái niệm phức tạp này. Qua bài viết, các bạn chỉ cần nhớ một số điều sau về Laravel Facade:

  • Laravel Facade không phải là Facade Pattern một mẫu rất phổ biến trong Design Pattern, nó giống hơn với Proxy Pattern.
  • Mấu chốt của Laravel Facade là sử dụng magic method __callStatic() kết hợp với Inversion of Control Container.
  • Tạo mới một Laravel Facade để sử dụng riêng là khá đơn giản.

Trong bài viết này chúng ta cũng nhắc đến Dependency Injection, đây cũng là một mảng kiến thức rất hay mà có dịp chúng ta sẽ bàn luận chi tiết về nó. Bài viết trên là những hiểu biết cá nhân, có thể có những khái niệm chưa rõ ràng do hạn chế về hiểu biết, rất mong các bạn có ý kiến cùng trao đổi cùng nhau nâng cao kiến thức.

Cảm ơn các bạn đã đọc.

Các bạn có thể tham khảo các bài viết hay về Laravel tại đây.


Hãy tham gia nhóm Học lập trình để thảo luận thêm về các vấn đề cùng quan tâm.

Nguồn tham khảo: allaravel

Leave a Reply

Your email address will not be published. Required fields are marked *