Tạo lệnh sort migration với Artisan Console Laravel (phần cuối)

Tạo lệnh sort migration với Artisan Console Laravel (phần cuối)

Nội dung bài viết


Xin chào tất cả các bạn, chúng ta lại gặp nhau trong chủ đề "Làm thế nào để tạo sort migration trong Artisan Console Laravel". Ở bài hôm trước, mình đã nói về ý tưởng, cốt lõi vấn đề của chương trình sort migration. Thêm nữa, chúng ta đã cùng nhau viết các phương thức nền tảng, phục vụ cho việc xây dựng các tính năng sort migration ở bài học này. Thôi không vòng vo nữa, chúng ta đi thẳng vào bài luôn nào.

1. Tiếp nối phần trước

Ở phần trước, chúng ta đã viết các phương thức lấy thông tin của migration:

  • Lấy tên file migration
  • Lấy prefix migration
  • Lấy primary name migration
  • Lấy position migration

Và các phương thức xử lý vị trí của migration:

  • Đổi vị trí của một migration
  • Định dạng vị trí migration

Các phương thức này sẽ tham gia xây dựng từng tính năng cho chương trình. Bạn có thể quay lại đọc bài viết trước nếu chưa xem nhé!

Xem phần trước: Tạo lệnh sort migration với Artisan Console Laravel (phần đầu)

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

Phần này chúng ta sẽ viết các tính năng sắp xếp vị trí cho các migration. Các bạn còn nhớ phần lập luồng xử lý trong phương thức handle() của lớp SortMigration không? Vâng, giờ nó đã có "đất dụng võ rồi". Tại phương thức handle() chúng ta sẽ thực hiện việc gọi các tính năng sắp xếp thông qua các tùy chọn tương ứng. Trước khi đi vào từng tính năng cụ thể, các bạn hãy khai báo method bên dưới giúp mình trong class SortMigration.

protected function sortedMigrations()
{
    // Chạy lệnh "php artisan migrate:reset" mà không hiện thông báo
    $this->callSilent('migrate:reset');

    // Định dạng lại toàn bộ migration theo chuẩn tên
    $this->resetSortedMigrations($start = 1, $from = 1);
}

Phương thức này là "bước ngoặc" cho các tính năng khác, nó vừa reset lại migration database vừa re-sort lại các file migration có trong source files để sẵn sàng cho việc sắp xếp. Mình có sử dụng method callSilent() để chạy một lệnh Artisan mà không hiện thông báo của nó. Tiếp đến là method resetSortedMigrations() chúng ta đã viết ở phần trước, với tham số $start = 1, $from = 1, nó sẽ sắp xếp lại tất cả migration. Phương thức này giúp tránh các lỗi không mong muốn như chưa đặt định dạng các migration hoặc vừa tạo mới một migration nào đó mà đã tiến hành sắp xếp...

Ok, giờ chúng ta đã sẵn sàng để đi đến bước xây dựng các tính năng chính của chương trình sort migration này rồi. 

2.5. Viết phương thức định dạng migration đồng thời hiển thị danh sách migration theo thứ tự

Phương thức này giúp ta định dạng toàn bộ migration theo chuẩn ý tưởng, đồng thời hiển thị danh sách các migration có trong source files đã được sắp xếp dưới dạng bảng. Trước tiên, hãy khai báo một method với tên là showMigrations() kèm đoạn code xử lý như sau:

protected function showMigrations()
{
    // Định dạng migration
    $this->sortedMigrations();

    // Lấy danh sách migration trong source files
    $migrations = $this->getMigrations();

    // Tạo header cho bảng hiển thị
    $headers = ['#', 'Migration', 'Name'];

    // Tạo hàng cho bảng
    $sortedMigrations = array_map(function($key, $migration) {
        // Vị trí của migration
        $pos = $key + 1;

        // Tên file migration
        $fileNameMigration = $migration->getFileName();

        // Primary name migration
        $nameMigration = $this->getNameMigration($fileNameMigration);

        // Trả về hàng dữ liệu
        return [$pos, $fileNameMigration, $nameMigration];
    }, array_keys($migrations), $migrations); 

    // In bảng dữ liệu
    return $this->table($headers, $sortedMigrations);
}

Nhìn qua thì cũng không có gì đặc biệt, mình đã chú thích rất cụ thể trên code rồi. Đoạn code trên chỉ sử dụng lại các phương thức mà ta đã viết ở phần trước thôi. Ngoài ra có phương thức table() dùng để tạo bảng trên terminal, các bạn có thể tìm hiểu thêm tại đây.

Việc tiếp theo rất đơn giản, mình chỉ việc gọi phương thức showMigrations() này ở trong method handle() là xong.

// Định dạng migration đồng thời hiển thị danh sách migration theo thứ tự
if (!$migration) {
    return $this->showMigrations();
}

Các bạn copy đoạn code này bên dưới chú thích như trên nhé! Đó là lý do vì sao mà mình muốn các bạn lập luồng xử lý bằng các comment đấy, rất dễ theo bài hướng dẫn đúng không nào. Ý nghĩa đoạn code trên cũng không quá phức tạp, ta chỉ kiểm tra nếu tham số migration không tồn tại thì thực thi phương thức, lúc đó câu lệnh sẽ là:

php artisan migrate:sort

Một cấu trúc đầy "khuôn mẫu" để thực hiện các tính năng như hiển thị danh sách. Ok, test ngay và luôn.

Kết quả phương thức hiển thị danh sách migration theo thứ tự sort migration Artisan Console Laravel

Tiếp theo, các bạn vào database để xem database và bảng migrations đã được reset hay chưa.

Kết quả database sau khi thực hiện phương thức hiển thị danh sách migration sort migration Artisan Console Laravel

Tiếp theo là bảng migrations.

Kết quả bảng migrations sau khi thực hiện phương thức hiển thị danh sách migration sort migration Artisan Console Laravel

Yub, mọi thứ có vẻ suôn sẻ cho tính năng đầu tiên.

2.6. Hiển thị lỗi nếu tên migration không tồn tại

Tiếp theo chúng ta sẽ viết một phương thức valid tên migration khi truyền từ tham số trên command. Chắc các bạn còn nhớ tới phương thức getFileNameMigration() ở phần trước chứ? Vâng, chúng ta sẽ dùng luôn phương thức đó để kiểm tra, nếu nó trả về giá trị NULL thì file migration không tồn tại.

Trước tiên thì hãy viết method hiển thị thông báo lỗi tương ứng cho nó nhé! Cái này đơn giản và tùy vào mỗi bạn, có thể tham khảo đoạn code mình bên dưới:

protected function showNoExistsMigrationError()
{
    return $this->error("Migration was not found.");
}

Ổn rồi, tiếp theo là khai báo nó trong phương thức handle() thôi.

// Hiển thị lỗi nếu như tên migration không tồn tại
if ($migration && ! $this->getFileNameMigration($migration)) {
    return $this->showNoExistsMigrationError();
}

Nếu người dùng nhập một primary name không tồn tại cho tham số migration thì chương trình sẽ báo lỗi và ngừng thực hiện các tác vụ phía dưới.

Hiển thị lỗi tên migration không tồn tại sort migration Artisan Console Laravel

2.7. Viết phương thức đẩy một migration lên vị trí đầu tiên

Tính năng này sẽ giúp ta đưa một migration lên vị trí đầu tiên trong source files. Câu lệnh thực thi chúng ta cần xây dựng sẽ là

php artisan migrate:sort name_of_migration --top

Như các bạn thấy ở câu lên trên, mình đã truyền vào tùy chọn top với ý xác thực là sẽ đưa migration lên vị trí đầu tiên. Vậy làm thế nào để phương thức handle() xử lý được đây?

Đơn giản lắm các bạn ạ, hãy nhìn đoạn code mình đã viết bên dưới nhé!

// Phương thức đẩy một migration lên vị trí đầu tiên
if ($this->option('top')) {
    return $migration ? $this->pushMigrationUpToTop($migration) 
                        : $this->showEmptyMigrationError();
}

Ở đây chúng ta sẽ sử dụng cấu trúc điều kiện if để kiểm tra xem có tồn tại tham số top không. Tiếp theo bên trong mình kiểm tra $migration có được truyền giá trị hay không, nếu có ta sẽ thực thi method pushMigrationUpToTop(), ngược lại sẽ hiển thị thông báo chưa nhập target migration thông qua phương thức showEmptyMigrationError().

Ok, việc tiếp theo là đi xây dựng hai phương thức được đề cập ở trên. Đầu tiên ta sẽ viết phương thức pushMigrationUpToTop() trước. Các bạn hãy quan sát cơ chế bên dưới:

Sơ đồ ý tưởng đẩy một migration lên vị trí đầu tiên sort migration Artisan Console Laravel

Ý tưởng rất đơn giản:

  1.  Đầu tiên ta sẽ đưa target migration về vị trí 0
  2. Tiếp đến là format các migration từ vị trí $from = 2 đến hết, với $start = 2
  3. Cuối cùng là đưa target migration về ví trí số 1

Dựa vào sơ đồ trên, ta sẽ biểu diễn nó dưới dạng code. Các bạn tạo cho mình method pushMigrationUpToTop() với nội dung như bên dưới:

protected function pushMigrationUpToTop($migration)
{
    // Định dạng migration
    $this->sortedMigrations();

    // (1): Đưa target migration về vị trí 0
    $this->sortMigration($migration, 0);

    // (2): Format các migration từ $from = 2 đến hết, bắt đầu từ $start = 2
    $this->resetSortedMigrations($start = 2, $from = 2);

    // (3): Đưa target migration về vị trí 1
    $this->sortMigration($migration, 1);

    // Hiển thị thông báo thành công
    return $this->showSuccessAlert();
}

Đoạn code trên thật đơn giản đúng không nào. Chính vì chúng ta đã xây dựng các method nền tảng nên việc áp dụng chúng để viết các tính năng sắp xếp rất nhanh chóng. Mình có sử dụng method showSuccessAlert() để thông báo thành công và nhắc nhở migrate lại database sau khi công việc sắp xếp hoàn tất. Các tính năng khác về sau đều sẽ sử dụng chung method này. Vì vậy các bạn hãy định nghĩa method này giúp mình.

protected function showSuccessAlert()
{
    $this->info('Migrations was sorted.');
    $this->info('Please migrate database again if that is last step.');
}

Chỉ đơn giản vậy thôi. Tiếp theo ta sẽ viết nốt method hiển thị lỗi khi không nhập target migration showEmptyMigrationError() luôn nhé!

protected function showEmptyMigrationError()
{
    return $this->error('You must type a migration.');
}

Cũng giống như method showSuccessAlert()showEmptyMigrationError() sẽ còn được sử dụng trong các tính năng khác nữa.

Vậy là đã hoàn tất công việc cho tính năng này rồi đấy, giờ chỉ việc test thôi nào. Hiện tại trong source files của mình thứ tự các migration như thế này:

Danh sách hiện có trong source files migration Laravel

Bây giờ mình sẽ thử đưa migration create_failed_jobs_table lên vị trí đầu tiên bằng lệnh:

php artisan migrate:sort create_failed_jobs_table --top

Và...

Kết quả của phương thức đưa một migration lên vị trí đầu tiên Artisan Console Laravel

Một kết quả đầy mong đợi phải không nào.

2.8. Viết phương thức đẩy một migration xuống vị trí cuối cùng

Tính năng này giúp chúng ta có thể nhanh chóng đưa một migration chỉ định xuống vị trí cuối cùng. Lệnh tổng quát ở dạng:

php artisan migrate:sort name_of_migration --bottom

Về quy trình xử lý cũng tương tự với đẩy một migration lên vị trí đầu tiên. Các bạn hãy cùng mình quan sát sơ đồ bên dưới:

Sơ đồ ý tưởng đẩy một migration lên vị trí cuối cùng sort migration Artisan Console Laravel

Ý tưởng thực hiện như sau:

  1. Đầu tiên đưa target migration về vị trí 0
  2. Tiếp theo format các migration từ vị trí $from = 2 đến hết, bắt đầu từ $start = 1
  3. Cuối cùng đưa target migration về vị trí cuối cùng (bằng với số migration có trong source files)

Okay, giờ mình sẽ đặt tên cho method này là pushMigrationDownToBottom() với nội dung như sau:

protected function pushMigrationDownToBottom($migration)
{
    // Đếm số migration có trong source files
    $countMigration = count($this->getMigrations());

    // Định dạng migration
    $this->sortedMigrations();

    // (1): Đưa target migration về vị trí 0
    $this->sortMigration($migration, 0);

    // (2): Format các migration từ $from = 2 đến hết, bắt đầu tại $start = 1
    $this->resetSortedMigrations($start = 1, $from = 2);

    // (3): Đưa target migration về vị trí cuối cùng = số migration trong source files
    $this->sortMigration($migration, $countMigration);

    // Hiển thị thông báo thành công
    return $this->showSuccessAlert();
}

Đoạn code trên mình đã giải thích rất kỹ trong phần chú thích rồi. Việc đếm số migration, mình sẽ dùng hàm count() để đếm thông qua kết quả trả về từ phương thức getMigrations() ở phần trước. Các bước xử lý còn lại mình sẽ không nói nữa.

Tiếp theo ta chỉ việc đăng ký nó ở trong phương thức handle() của lớp SortMigration là xong.

// Phương thức đẩy một migration xuống vị trí cuối cùng
if ($this->option('bottom')) {
    return $migration ? $this->pushMigrationDownToBottom($migration) 
                        : $this->showEmptyMigrationError();
}

Ta cũng ràng buộc giá trị tùy chọn bottom bằng câu lệnh if, đồng thời kiểm tra tham số migration có tồn tại mới thực hiện tính năng này.

Đến lúc test rồi, source files của mình đang có vị trí như thế này:

Danh sách hiện có trong source files migration Laravel

Giờ mình sẽ chạy lệnh bên dưới để đưa migration create_failed_jobs_table xuống vị trí cuối cùng.

php artisan migrate:sort create_failed_jobs_table --bottom

Yub, một thông báo thành công và kết quả đúng như nội dung của câu lệnh.

Kết quả của phương thức đưa một migration xuống vị trí cuối cùng Artisan Console Laravel

2.9. Viết phương thức hoán đổi vị trí giữa hai migration

Tính năng tiếp theo đó là đổi vị trí của hai migration bất kỳ với nhau. Lệnh tổng quát cho tính năng này là:

php artisan migrate name_of_target_migration --exchange=name_of_another_migration

Về ý tưởng cho tính năng này chắc mình sẽ không cần phải vẽ sơ đồ minh họa nữa. Bản chất thì nó gần tương tự với giải thuật bubble sort (sắp xếp nổi bọt). 

  1. Lưu vị trí hiện tại của 2 migration
  2. Đưa target migration về vị trí 0
  3. Đổi vị trí của another migration thành vị trí cũ của target migration
  4. Đổi vị trí của target migration từ 0 thành vị trí cũ của another migration

Nhưng tính năng này có một bước kiểm tra tính hợp lệ của tham số another migration

  • Kiểm tra sự tồn tại của another migration
  • Kiểm tra another migration có trùng với target migration hay không

Để thực hiện việc validate này, mình sẽ viết method isValidInputBetweenTwoMigrations() với luồng xử lý như sau:

protected function isValidInputBetweenTwoMigrations($targetMigration, $anotherMigration)
{
    $isValid = true;

    // Kiểm tra sự tồn tại của another migration
    if (! $anotherMigration || ! $this->getFileNameMigration($anotherMigration)) {
        $isValid = false;
        $this->showNoExistsSecondMigrationError();
    }

    // Kiểm tra another migration có trùng với target migration hay không
    if ($targetMigration === $anotherMigration) {
        $isValid = false;
        $this->showDulicatedMigrationError();
    }

    return $isValid;
}

Code xử lý trên cũng không quá phức tạp. Mình có sử dụng thêm 2 method để hiển thị thông báo lỗi đó chính là showNoExistsSecondMigrationError() và showDulicatedMigrationError(). Các bạn có thể giúp mình định nghĩa hai method này như bên dưới:

protected function showNoExistsSecondMigrationError()
{
    return $this->error("The second migration was not found.");
}

protected function showDulicatedMigrationError()
{
    return $this->error("The migrations is dulicated.");
}

Tốt rồi, giờ chúng ta đi vào vấn đề chính là chuyển từ ý tưởng sang code cho tính năng này. Mình sẽ gọi method thực thi tính năng này là exchangeMigration().

protected function exchangeMirgation($targetMigration, $anotherMigration)
{
    // Thực việc validate another migration
    if (! $this->isValidInputBetweenTwoMigrations($targetMigration, $anotherMigration)) {
        return false;
    }

    // Định dạng migration
    $this->sortedMigrations();

    // Lưu vị trí hiện tại của 2 migration
    $targetMigrationPos = $this->getPositionMigration($targetMigration);
    $anotherMigrationPos = $this->getPositionMigration($anotherMigration);

    // (1): Đưa target migration về vị trí 0
    $this->sortMigration($targetMigration, 0);

    // (2): Đổi vị trí của another migration thành vị trí cũ của target migration
    $this->sortMigration($anotherMigration, $targetMigrationPos);

    // (3): Đổi vị trí của target migration từ 0 thành vị trí của của another migration
    $this->sortMigration($targetMigration, $anotherMigrationPos);

    // Hiển thị thông báo thành công
    return $this->showSuccessAlert();
}

Công việc quen thuộc tiếp theo là đăng ký tính năng này ở method handle() của SortMigration.

// Phương thức hoán đổi vị trí giữa hai migration
if ($this->option('exchange')) {
    return $migration ? $this->exchangeMirgation($migration, $this->option('exchange')) 
                        : $this->showEmptyMigrationError();
}

Khối lệnh này có lẽ đã quen thuộc rồi nên mình sẽ không giải thích nữa.

Giờ test lệnh thôi, mình sẽ thử đổi vị trí của hai migration create_users_tablecreate_failed_jobs_table với nhau. Hiện tại thì chúng có vị trí như thế này:

Mình sẽ đổi vị trí của chúng thông qua lệnh:

php artisan migrate:sort create_users_table --exchange=create_failed_jobs_table

hoặc 

php artisan migrate:sort create_failed_jobs_table --exchange=create_users_table

Và đây là kết quả chúng ta nhận được:

Kết quả đổi vị trí hai migration với nhau sort migration Artisan Console Laravel

2.10. Viết phương thức đặt một migration phía trên một migration khác

Với phương thức này ta có thể chỉ định một migration bất kỳ nằm vị trí phía trên migration khác. Cú pháp lệnh tổng quát như sau:

php artisan migrate:sort name_of_target_migration --above=name_of_another_migration

Các bạn hãy quan sát mô hình ý tưởng bên dưới:

Sơ đồ ý tưởng đặt một migration lên trên một migration khác

Màu đỏ sẽ tượng trưng cho target migration, màu xanh lá là another migration. Theo sơ đồ thì chức năng này sẽ chia làm hai trường hợp:

Vị trí của target migration bên dưới vị trí của another migration

  • Bước 1: Đưa target migration về vị trí số 0
  • Bước 2: Format các migration từ vị trí $from = [postion hiện tại của another migraion] + 1 cho đến hết, bắt đầu từ $start = [postion hiện tại của another migraion] + 1
  • Bước 3: Đưa target migration về đúng với vị trí ban đầu của another migration

Vị trí của target migration bên trên vị trí của another migration

  • Bước 1: Đưa target migration về vị trí số 0
  • Bước 2: Format các migration từ vị trí $from = 2 cho đến $end = [position hiện tại của another migration] - 1, bắt đầu từ $start = 1
  • Bước 3: Đưa target migration về vị trí $pos = [vị trí ban đầu của another migration] - 1

Các bạn có thể quan sát sơ đồ để hiểu rõ hơn về quá trình hoạt động của ý tưởng. Dưới đây là đoạn code mà mình viết cho phương thức này với tên là putMigrationAbove():

protected function putMigrationAbove($targetMigration, $anotherMigration)
{
    // Thực việc validate another migration
    if (! $this->isValidInputBetweenTwoMigrations($targetMigration, $anotherMigration)) {
        return false;
    }
    
    // Định dạng migration
    $this->sortedMigrations();

    // Lưu vị trí hiện tại của 2 migration
    $targetMigrationPos = $this->getPositionMigration($targetMigration);
    $anotherMigrationPos = $this->getPositionMigration($anotherMigration);

    // Bước này cả 2 trường hợp đều có nên mình sẽ gọi ở ngoài câu điều kiện một lần duy nhất
    // (1): Đưa target migration về vị trí số 0
    $this->sortMigration($targetMigration, 0);

    // Case 1: Nếu vị trí target migration bên dưới vị trí another migration
    if ($targetMigrationPos > $anotherMigrationPos) {
        // (2): Format các migration từ $from = $anotherMigrationPos + 1 đến hết
        // với $start = $anotherMigrationPos + 1
        $this->resetSortedMigrations(
            $start = $anotherMigrationPos + 1, 
            $from = $anotherMigrationPos + 1
        );

        // (3): Đưa target migration về đúng với vị trí ban đầu của another migration
        $this->sortMigration($targetMigration, $anotherMigrationPos);

    // Case 2: Nếu vị trí target migration bên trên vị trí another migration
    } else {
        // (2): Format các migration từ $from = 2 đến $end = $anotherMigrationPos - 1
        // với $start = 1
        $this->resetSortedMigrations(
            $start = 1, 
            $from = 2, 
            $end = $anotherMigrationPos - 1
        );

        // (3): Đưa target migration về đúng với vị trí $pos = $anotherMigrationPos - 1
        $this->sortMigration($targetMigration, $anotherMigrationPos - 1);
    }

    // Hiển thị thông báo thành công
    return $this->showSuccessAlert();
}

Đoạn code trên có vẻ hơi dài nhưng chỉ là do mình chú thích khá kĩ thôi. Các bạn đọc từ trên xuống thì sẽ thấy nó không quá phức tạp. Đầu tiên do phương thức này có yêu cầu tham số migration thứ hai nên mình đã dùng method isValidInputBetweenTwoMigrations() để valid nó. Tiếp theo, ta định dạng lại danh sách migration và lưu vị trí của hai mgiration trước khi thực hiện việc sắp xếp. Việc còn lại chỉ dựa vào sơ đồ ý tưởng và các phương thức nền tảng hỗ trợ cho việc sắp xếp mà thôi.

Tiếp đến là một công việc quen thuộc, đó là khai báo phương thức sắp xếp này trong method handle().

// Phương thức đặt một migration phía trên một migration khác
if ($this->option('above')) {
    return $migration ? $this->putMigrationAbove($migration, $this->option('above')) 
                        : $this->showEmptyMigrationError();
}

Ok, giờ chúng ta test xem nào. Hiện tại thì danh sách migration của mình có vị trí như thế này:

Test source migration hiện tại, đưa một migration lên trên một migration khác

Tiếp theo mình muốn đưa migration create_users_table lên trên migration create_failed_jobs_table, ta dùng lệnh sau:

php artisan migrate:sort create_users_table --above=create_failed_jobs_table

Tèn ten, đây là kết quả chúng ta nhận được.

Kết qua đưa một migration lên trên một migration khác

Ở bước test này mình chỉ sử dụng các migration có sẵn trong fresh source Laravel. Các bạn có thể tạo thêm một số migration khác để test nhiều trường hợp nhé!

2.11. Viết phương thức đặt một migration bên dưới một migration khác

Nếu phương thức ở trên là đặt một migration lên trên migration khác thì phương thức này sẽ thực hiện ngược lại, nó sẽ đặt một migration bên dưới một migraiton khác. Mình sử dụng cú pháp lệnh tổng quát cho tính năng này như sau:

php artisan migrate:sort name_of_target_migration --bellow=name_of_another_migration

Về ý tưởng thực hiện, cấu trúc sơ đồ của nó cũng gần tương tự với tính năng trước đó.

Sơ đồ ý tưởng đặt một migration bên dưới một migration khác

Ở đây mình cũng quy ước target migration là màu đỏ và another migration là màu xanh lá cây. Tính năng này cũng chia làm hai trường hợp:

Vị trí của target migration bên dưới vị trí của another migration

  • Bước 1: Đưa target migration về vị trí số 0
  • Bước 2: Format các migration từ vị trí $from = [position hiện tại của another migration] + 2 cho đến hết, bắt đầu từ $start = [position hiện tại của another migration] + 2
  • Bước 3: Đưa target migration về vị trí $pos = [position hiện tại của another migration] + 1

Vị trí của target migration bên trên vị trí của another migration

  • Bước 1: Đưa target migration về vị trí số 0
  • Bước 2: Format các migration từ vị trí $from = 2 cho đến $end = [position hiện tại của another migration], bắt đầu từ $start = 1
  • Bước 3: Đưa target migration về đúng vị trí ban đầu của another migration

Để hiểu rõ hơn chúng ta sẽ nhìn đoạn code xử lý bên dưới cho method putMigrationBellow():

protected function putMigrationBellow($targetMigration, $anotherMigration)
{
    // Thực việc validate another migration
    if (! $this->isValidInputBetweenTwoMigrations($targetMigration, $anotherMigration)) {
        return false;
    }

    // Định dạng migration
    $this->sortedMigrations();

    // Lưu vị trí hiện tại của 2 migration
    $targetMigrationPos = $this->getPositionMigration($targetMigration);
    $anotherMigrationPos = $this->getPositionMigration($anotherMigration);

    // Bước này cả 2 trường hợp đều có nên mình sẽ gọi ở ngoài câu điều kiện một lần duy nhất
    // (1): Đưa target migration về vị trí số 0
    $this->sortMigration($targetMigration, 0);

    // Case 1: Nếu vị trí target migration bên dưới vị trí another migration
    if ($targetMigrationPos > $anotherMigrationPos) {
        // (2): Format các migration từ $from = $anotherMigrationPos + 2 đến hết
        // với $start = $anotherMigrationPos + 2
        $this->resetSortedMigrations(
            $start = $anotherMigrationPos + 2,
            $from = $anotherMigrationPos + 2
        );

        // (3): Đưa target migration về vị trí $pos = $anotherMigrationPos + 1
        $this->sortMigration($targetMigration, $anotherMigrationPos + 1);
    // Case 2: Nếu vị trí target migration bên trên vị trí another migration
    } else {
        // (2): Format các migration từ $from = 2 đến $end = $anotherMigrationPos
        // với $start = 1
        $this->resetSortedMigrations(
            $start = 1, 
            $from = 2, 
            $end = $anotherMigrationPos
        );

        // (3): Đưa target migration về đúng với vị trí ban đầu của another migration
        $this->sortMigration($targetMigration, $anotherMigrationPos);
    }

    // Hiển thị thông báo thành công
    return $this->showSuccessAlert();
}

Quy trình xử lý thì nó cũng tương tự method putMigrationAbove(). Đầu tiên chúng ta cũng phải valid another migration, định dạng migration và lưu vị trí cho hai migration. Sau đó các bước bắt trường hợp chỉ cần dựa vào sơ đồ ý tưởng là sẽ ok thôi.

Việc cuối cùng ta chỉ cần khai báo nó trong phương thức handle() để xử lý khi được gọi tới.

// Phương thức đặt một migration bên dưới một migration khác 
if ($this->option('bellow')) {
    return $migration ? $this->putMigrationBellow($migration, $this->option('bellow')) 
                        : $this->showEmptyMigrationError();
}

Có vẻ ổn rồi, giờ hãy test xem nó đã hoạt động như ý muốn chưa nhé! Source files các migration trong project hiện tại của mình có thứ tự như thế này:

Test source files migration đưa một migration bên dưới migration khác

Giờ mình sẽ đưa migration create_users_table xuống bên dưới migration create_failed_jobs_table bằng cú pháp:

php artisan migrate:sort create_users_table --bellow=create_failed_jobs_table

Và... kết quả của chúng ta đây rồi.

Kết quả đưa một migration xuống dưới một migration khác

2.12. Hiển thị lỗi nếu như không nhập phương thức sắp xếp cho migration

Lỗi này xuất hiện khi bạn nhập tham số cho migration mà lại không chọn bất kỳ phương thức sắp xếp nào thông qua tùy chọn. Do mình đặt nó ở vị trí xử lý cuối cùng nên ta chẳng cần viết các xử lý logic cho phần này, chỉ đơn giản là một method showNoActionError() trả về thông báo lỗi.

protected function showNoActionError()
{
    return $this->error("Please type a action to sort the migration.");
}

Và khai báo nó trong phương thức handle() của lớp SortMigration.

// Hiển thị lỗi nếu như không nhập phương thức sắp xếp cho migration
return $this->showNoActionError();

Các bạn có thể test phương thức này như hình bên dưới:

Hiển thị lỗi khi không nhập phương thức sắp xếp cho migration

Yub, chúng ta đã hoàn thành toàn bộ chương trình sort migration thông qua Artisan Console trong Laravel rồi đấy. Giờ hãy cùng mình đi qua một ví dụ thực tế khi làm project để thấy rõ công dụng của chức năng này.

3. Ứng dụng thực tế của chức năng sort migration

Mình sẽ lấy một ví dụ nhỏ để dẫn chứng cho các bạn thấy lợi ích của chức năng sort migration này. Lấy source code hiện tại, mình sẽ tạo một migration nữa với tên là create_roles_table.

Tạo migration cho ứng dụng thực tế sort migration

Mặc định thì Laravel sẽ tạo một migration mới với prefix định dạng là mốc thời gian hiện tại. Tiếp theo các bạn mở migration vừa tạo lên và thay đoạn code này cho method up() của lớp CreateRolesTable.

Schema::create('roles', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('code');
    $table->timestamps();
});

Đây mình chỉ tạo các column cho giống thực tế thôi, các bạn có thể không thêm cũng không vấn đề, miễn giữ cho mình cột id là được.

Tiếp theo các bạn mở migration create_users_table lên và tạo cột role_id là khóa ngoại của bảng roles. Các bạn có thể tham khảo code của mình ở bên dưới:

Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('email')->unique();
    $table->timestamp('email_verified_at')->nullable();
    $table->string('password');
    
    $table->unsignedBigInteger('role_id');
    $table->foreign('role_id')->references('id')->on('roles');

    $table->rememberToken();
    $table->timestamps();
});

Ok, bây giờ nếu chúng ta chạy dòng lệnh php artisan migrate thì điều gì sẽ xảy ra nhỉ? 

Lỗi chạy ứng dụng thực tế sort migration

Vâng, chắc các bạn cũng đã đoán ra được tạo sao chúng ta lại bị lỗi khi chạy đến migraion create_users_table phải không nào. Nguyên nhân đó là do trong migration này có khai báo một khóa ngoại của bảng roles, nhưng bảng này chưa được khởi tạo tại migration create_roles_table trước đó vì thứ tự của nó nằm sau migration create_users_table. Để khắc phục lỗi này thì thông thường các bạn sẽ đổi prefix của create_roles_table sao cho nó nằm trước migration create_users_table. Nhưng giờ đây chúng ta không cần phải làm thế nữa, chúng ta đã có chức năng sort migration rồi mà. Công việc bây giờ đơn giản hơn nhiều, các bạn chỉ cần chạy chuỗi lệnh sau:

php artisan migrate:sort create_users_table --bellow=create_roles_table

Tức là chúng ta sẽ đưa migration create_users_table xuống vị trí bên dưới migration create_roles_table như thế này.

Kết quả ứng dụng thực tế sort migration

Rồi, giờ chỉ việc chạy lại lệnh php artisan migrate là ok ngay! Nếu vẫn có lỗi thì có thể là do database đã tạo một số bảng thành công trước đó dẫn đến việc trùng lặp. Các bạn cứ drop hết các bảng và chạy migrate lại là được. Nếu bạn muốn tự động hóa việc này thì có thể thêm xử lý drop tất cả các table trong database tại phương thức sortedMigrations() ở phần trên.

Kết quả database ứng dụng thức tế sort migration

4. Lời kết

Aw, cuối cùng cũng kết thúc chủ đề "Tạo lệnh sort migration với Artisan Console Laravel" rồi. Bài hôm nay khá dài nhưng mình hi vọng nó có thể truyền đạt tới các bạn một cách "sâu sắc" và "lắng đọng" nhất. Các ý tưởng trên chỉ là của cá nhân mình, nếu có gì sai sót hoặc có những ý tưởng hay hơn, tối ưu hơn, các bạn có thể để lại comment ở bên dưới để mình biết nhé! Cảm ơn các bạn đã luôn đồng hành cùng với Flipper. Chúng các bạn thành công và hẹn gặp lại trong những bài tiếp theo.


Ủng hộ chúng tôi

Bình luận