Tự viết lệnh make view trong Artisan Laravel

Tự viết lệnh make view trong Artisan Laravel

Nội dung bài viết


Có bao giờ bạn thắc mắc tại sao trên Laravel có lệnh Artisan tạo các file như controller, model, middleware... mà lại không có lệnh tạo view hay không? Yub, mình cũng không biết lý do vì sao nhưng fresh source của Laravel framework không có lệnh nào để tạo view trên Artisan cả. Chính vì thế ngày hôm nay mình sẽ hướng dẫn các bạn cách viết lệnh Artisan riêng để có thể tạo view trong Laravel với Artisan Console. 

1. Ý tưởng

Nếu muốn tạo một controller mới thì các bạn sử dụng lệnh php artisan make:controller NameController đúng không nào. Dựa trên ý tưởng đó, mình cũng sẽ viết code để có thể tạo một view thông qua cú pháp sau:

php artisan make:view [directory.,...]view [--extension]

Như các bạn đã biết thì view trong Laravel có thể phân cấp rất nhiều tầng thư mục, chính vì thế khi viết lệnh Artisan tạo view chúng ta cũng cần phải có cú pháp linh hoạt để đảm bảo toàn vẹn tính năng. Ngoài extension .blade.php thường sử dụng thì cũng có các tùy chọn khác như .php, .html... 

Chẳng hạn nếu muốn tạo view home.blade.php từ cú pháp trên ta có thể viết như sau:

php artisan make:view home

Nâng cao một chút, giờ ta muốn tạo view auth/login.blade.php chẳng hạn.

php artisan make:view auth.login

Ngoài ra, ta có thể tạo view có extension khác .blade.php, chẳng hạn như dashboard.php

php artisan make:view dashboard --extension=php

Rồi, những ví dụ này mình sẽ để dành làm test case sau khi chương trình của chúng ta hoàn thành nhé.

2. Tiến hành thực hiện

Chức năng này mình sẽ code dựa trên tài liệu về Artisan Console mà Laravel cung cấp, các bạn có thể vào để tìm hiểu thêm chi tiết.

2.1. Tạo command

Đầu tiên chúng ta cần có một fresh project Laravel, ở đây mình sẽ không nói về cách để khởi tạo một project Laravel nữa. Sau khi đã có một project Laravel rồi, các bạn tiến hành tạo một lệnh Artisan mới bằng lệnh:

php artisan make:command MakeView

Khi lệnh hoàn tất chúng ta sẽ có được file app/Console/Command/MakeView.php

Tạo make view command trong Artisan Laravel

Chúng ta sẽ code toàn bộ chức năng ngay tại file này, đây là source code khi mới vừa khởi tạo.

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;

class MakeView extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'command:name';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Command description';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        //
    }
}

2.2. Định nghĩa cú pháp

Để có thể định nghĩa cú pháp cho command, chúng ta sẽ khai báo tại $signature như sau:

protected $signature = 'make:view {view} {--extension=}';

Với tham số {view} sẽ bắt buộc vì mục đích là tạo view thì phải nhập tên view muốn tạo phải không nào? Còn về tùy chọn {--extension=}, ở đây mình sử dụng dấu = để có thể gán giá trị cho tùy chọn.

Sau khi lưu lại, bạn có thể test xem command đã hoạt động chưa bằng cách chạy các lệnh Artisan sau:

Test cú pháp lệnh make view trong Artisan Laravel

Nếu không xuất hiện lỗi thì có nghĩa chúng ta đã khai báo cú pháp thành công rồi đấy. Việc cần làm bây giờ là viết xử lý ngay tại method handle() của command MakeView.

2.3. Viết chức năng xử lý

Đây là toàn bộ code để thực thi tạo view trên Artisan:

public function handle()
{
    // Xử lý tham số view
    $view = $this->argument('view'); 
    $view = str_replace('.', '/', $view); 
    $viewPortions = explode('/', $view);

    // Xử lý tùy chọn extension
    $extension = $this->option('extension') ?? 'blade.php';
    $extension = '.' . $extension;

    // Khai báo các đường dẫn cần thiết
    $viewPath = resource_path('views' . DIRECTORY_SEPARATOR . $view . $extension);
    $currentDir = resource_path('views');

    // Thực thi lệnh tạo view
    foreach ($viewPortions as $key => $portion) {
        // Tạo view
        if ($key === array_key_last($viewPortions)) {
            if (!File::exists($viewPath) && File::isDirectory($currentDir)) {
                File::put($viewPath, null);

                return $this->info('View created successfully.');
            } 

            return $this->error('View already exists!');
        }

        // Tạo phân cấp thư mục
        $currentDir .= DIRECTORY_SEPARATOR . $portion;
        if (!File::exists($currentDir)) File::makeDirectory($currentDir);
    }
}

Như bạn quan sát, mình có sử dụng một facade của Laravel là File để có thể thực hiện các tác vụ liên quan đến file và folder. Bạn có thể tìm hiểu thêm về lớp facade File trên Laravel API. Vì thế trước khi tiếp tục, các bạn hãy inject facade này vào file như sau:

use Illuminate\Support\Facades\File;

Bây giờ mình sẽ giải thích chi tiết từng phân đoạn xử lý ở đoạn code trên.

2.3.1. Xử lý tham số view

Dựa vào tài liệu mà Laravel cung cấp, chúng ta có thể lấy dữ liệu từ tham số view thông qua method argument(). Tiếp theo các bạn thắc mắc tại sao mình lại tốn công replace dấu . về ký tự / làm gì đúng không? Ở đây mình muốn cú pháp khai báo view sẽ có 2 cách:

  1. Ngăn cách các cấp thư mục bằng dấu .
  2. Ngăn cách các cấp thư mục bằng /

Lúc này lệnh của chúng ta sẽ có cú pháp tương tự như khi truyền tham số vào helper view(), sẽ có nhiều lựa chọn hơn cho người dùng. Cuối cùng, mình dùng hàm explode() để tách chuỗi thành một mảng chứa các thành phần của tham số view tại $viewPortions, chuẩn bị cho khâu xử lý.

2.3.2. Xử lý tùy chọn extension

Ở phần này thì không có gì nhiều để nói, chúng ta sẽ sử dụng method option() để có thể lấy dữ liệu của tùy chọn extension. Mặc định, nếu không nhập cú pháp này từ lệnh thì nó sẽ nhận giá trị NULL, nên mình đã sử dụng toán tử ?? trong PHP để gán extension mặc định là .blade.php nếu không khai báo tùy chọn extension này.

2.3.3. Khai báo các đường dẫn cần thiết

Khâu này chúng ta chỉ khai báo các biến chứa các đường dẫn cần thiết cho việc tạo cấp thư mục cũng như tạo view.

  • $viewPath: đường dẫn đầy đủ của view chúng ta muốn khởi tạo
  • $currentDir: lưu trữ cấp thư mục hiện tại trong quá trình khởi tạo view, biến này cần thiết trong việc tạo view nằm trong nhiều cấp thư mục. Mình sẽ nói rõ hơn vai trò của nó ở bước sau.

Không biết các bạn có thắc mắc về hằng DIRECTORY_SEPARATOR này không? Đây là một hằng có sẵn do PHP cung cấp để lấy ký tự ngăn chia của đường dẫn thư mục tùy vào hệ điều hành. Mình sử dụng hằng này để tránh những lỗi không mong muốn khi chạy trên các hệ điều hành khác nhau.

2.3.4. Viết chức năng xử lý

Phần quan trọng của bài hôm nay đây rồi. Ý tưởng là mình sẽ thực thi tạo view cũng như các cấp thư mục (nếu có) thông qua các thành phần mà mình đã tách ở bước trên vì thứ tự các thành phần này sẽ tương ứng với thứ tự cấp thư mục của view nên rất phù hợp. Từ đó suy ra được là tại vị trí cuối cùng, chúng ta sẽ tạo view mà chúng ta đã khai báo, còn những phần tử còn lại sẽ thực thi tạo phân cấp thư mục.

Mình sẽ lấy một ví dụ để các bạn dễ hình dung quy trình hoạt động của vòng lặp. Chẳng hạn mình muốn tạo một view là auth/passwords/change-password.blade.php, vậy cấu trúc thư mục mong muốn sẽ như thế này:

views/
├── auth/
|   ├── passwords/
|   |      change-password.blade.php

Vậy sau khi xử lý tham số view chúng ta sẽ có một mảng gồm các thành phần: 

['auth', 'passwords', 'change-password']

Mình sẽ gọi đường dẫn gốc tới thư mục views, tức $viewPath sẽ là RESOURCE_VIEW để dễ diễn đạt nhé!

Vòng lặp chúng ta sẽ duyệt qua phần tử thứ nhất 'auth', nó chưa phải phần tử cuối cùng nên sẽ tạo phân cấp thư mục. $currentDir lúc này sẽ có giá trị là RESOURCE_VIEW\auth (ở đây mình sử dụng ký tự ngăn cách của Windows). Sau đó code sẽ tạo cho chúng ta một thư mục tương ứng với giá trị của $currentDir thông qua lớp facade File của Laravel, kết thúc vòng 1.

Đến phần tử 'passwords', lúc này vẫn chưa là phần tử cuối cùng. $currentDir sẽ tiếp tục nối chuỗi thông qua toán tử .= và nhận giá trị là RESOURCE_VIEW\auth\passwords. Công việc tạo thư mục sẽ tiếp tục tương tự như ở vòng 1.

Vòng lặp cuối tại phần tử 'change-password', lúc này nó sẽ thỏa mãn với cấu trúc điều khiển if với điều kiện $key === array_key_last($viewPortions). Công việc lúc này không phải là tạo thư mục nữa mà sẽ là tạo view thông qua đường dẫn chứa trong $viewPath đã khai báo ở bước trên. Cuối cùng chỉ cần xuất ra câu thông báo qua method info() là xong.

Ngoài ra, mình có bắt một trường hợp nếu như khởi tạo một view đã tồn tại trước đó, nếu đã tồn tại sẽ xuất ra câu thông báo lỗi. 

3. Test case

Giờ chúng ta sẽ thực hiện loạt lệnh sau xem code đã hoạt động ổn chưa nhé!

Test case lmake view command trong Artisan Laravel

Tuyệt, các lệnh đều thông báo thành công. Giờ kiểm tra cấu trúc thư mục views

Test cấu trúc thư mục khi chạy lệnh make view trong Artisan Laravel

OK, lệnh tạo view trên Artisan Console đã chạy "nuột". Hãy thử tạo một view đã tồn tại xem như nào nhé!

Test lỗi lệnh make view trong Artisan Laravel

4. Kết thúc

Bài hơi dài rồi, mình sẽ kết thúc tại đây. Qua bài này mình hi vọng sẽ giúp ích các bạn một phần nào đó trong quá trình code project Laravel và sẽ thật "cool" hơn khi tạo view bằng thao tác lệnh. Ngoài ra, các bạn cũng có dịp tìm hiểu quy trình để tạo một Artisan Console cho riêng mình. Nếu thấy hay và hữu ích hãy like và share để ủng hộ Flipper nhé. Cảm ơn tất cả các bạn, hẹn gặp lại.


Ủng hộ chúng tôi

Bình luận