+1

Nâng tầm Enum PHP với Interface và Trait

Enum PHP là một công cụ mạnh mẽ để xác định tập hợp các hằng số cố định, đồng thời cho phép bạn gắn chức năng cho các hằng số đó. Ngoài việc chỉ giữ các giá trị, enum có thể triển khai interface và sử dụng trait để mở rộng khả năng của chúng. Điều này làm cho chúng linh hoạt và có thể tái sử dụng hơn trong các ứng dụng phức tạp.

Trong bài viết này, chúng ta sẽ nâng thiết kế enum PHP của bạn lên một tầm cao mới bằng cách kết hợp enum với interface và trait. Chúng ta sẽ xem xét cách bạn có thể tạo enum cung cấp nhãn, mô tả và thậm chí tạo các tùy chọn cho đầu vào select trong biểu mẫu. Cuối cùng, bạn sẽ có một cấu trúc có thể tái sử dụng cho phép dễ dàng mở rộng và hoạt động nhất quán trên tất cả các enum của bạn.

Tại sao nên sử dụng Enum với Interface và Trait?

Enum rất phù hợp để đại diện cho các trạng thái, hoặc kiểu. Tuy nhiên, chỉ có một danh sách các hằng số thường là chưa đủ. Trong nhiều trường hợp, bạn có thể cần:

  • Nhãn mô tả hoặc tên dễ đọc của con người cho mỗi trường hợp enum.
  • Mô tả chi tiết giải thích ý nghĩa của từng trường hợp enum.
  • Các tùy chọn có cấu trúc cho đầu vào chọn trong giao diện người dùng.

Bằng cách triển khai interface và sử dụng trait, bạn có thể đảm bảo rằng enum của bạn nhất quán, có thể mở rộng và thích ứng với các yêu cầu đang phát triển.

Hướng dẫn tạo Enum Interface

Chúng ta sẽ bắt đầu bằng cách xác định một interface mà bất kỳ enum nào cũng có thể triển khai để đảm bảo rằng nó có các phương thức để trả về nhãn, mô tả và tạo các tùy chọn cho đầu vào select.

Dưới đây là interface:

<?php

namespace App\Contracts;

interface Enum
{
    /**
     * Get the label for the enum case.
     *
     * @return string
     */
    public function label(): string;

    /**
     * Get the description for the enum case.
     *
     * @return string
     */
    public function description(): string;

    /**
     * Generate an array of options with value, label, and description for select inputs.
     *
     * @return array
     */
    public static function options(): array;
}

Interface này xác định ba phương thức:

  • label: Trả về một tên dễ đọc của con người cho mỗi trường hợp enum.
  • description: Cung cấp mô tả chi tiết về từng trường hợp enum.
  • options: Tạo một mảng các tùy chọn, trong đó mỗi tùy chọn chứa giá trị, nhãn và mô tả của enum. Điều này có thể hữu ích khi xây dựng các biểu mẫu.

Tận dụng Trait cho Logic có thể tái sử dụng

Chúng ta có thể cải thiện hơn nữa enum của mình bằng cách sử dụng một trait để đóng gói logic chung để tạo các tùy chọn từ các trường hợp enum. Đây là lúc trait InteractsWithEnumOptions xuất hiện.

<?php

namespace App\Concerns;

trait InteractsWithEnumOptions
{
    /**
     * Generate an array of options with value, label, and description for select inputs.
     *
     * @return array
     */
    public static function options(): array
    {
        return array_map(fn ($case) => [
            'value' => $case->value,
            'label' => $case->label(),
            'description' => $case->description(),
        ], self::cases());
    }
}

Trait này cung cấp một phương thức options() có thể tái sử dụng, phương thức này lặp qua tất cả các trường hợp enum (self::cases()) và trả về một mảng trong đó mỗi mục chứa giá trị, nhãn và mô tả của trường hợp enum. Điều này đặc biệt hữu ích khi tạo các tùy chọn dropdown select.

Triển khai Interface và Trait trong Enum

Bây giờ, chúng ta hãy tạo một enum để quản lý trạng thái máy chủ. Enum này sẽ triển khai interface Enum và sử dụng trait InteractsWithEnumOptions để tạo các tùy chọn cho biểu mẫu.

<?php

namespace App\Enums;

use App\Contracts\Enum as Contract;
use App\Concerns\InteractsWithEnumOptions;

enum ServerStatus: string implements Contract
{
    use InteractsWithEnumOptions;

    case PENDING = 'pending';
    case RUNNING = 'running';
    case STOPPED = 'stopped';
    case FAILED = 'failed';

    public function label(): string
    {
        return match ($this) {
            self::PENDING => 'Pending Installation',
            self::RUNNING => 'Running',
            self::STOPPED => 'Stopped',
            self::FAILED => 'Failed',
            default => throw new \Exception('Unknown enum value requested for the label'),
        };
    }

    public function description(): string
    {
        return match ($this) {
            self::PENDING => 'The server is currently being created or initialized.',
            self::RUNNING => 'The server is live and operational.',
            self::STOPPED => 'The server is stopped but can be restarted.',
            self::FAILED => 'The server encountered an error and is not running.',
            default => throw new \Exception('Unknown enum value requested for the description'),
        };
    }
}

Điều gì đang xảy ra ở đây?

  • Trường hợp Enum: Chúng ta đã xác định một số trường hợp (ví dụ: PENDING, RUNNING, STOPPED, FAILED) đại diện cho các trạng thái máy chủ khác nhau.
  • Phương thức nhãn và mô tả: Các phương thức label() và description() cung cấp tên dễ đọc của con người và mô tả chi tiết cho mỗi trường hợp enum. Các phương thức này sử dụng câu lệnh match để ánh xạ từng trường hợp với nhãn và mô tả tương ứng của nó.
  • Trait cho tùy chọn: Trait InteractsWithEnumOptions được sử dụng để tự động tạo một mảng các tùy chọn cho đầu vào chọn biểu mẫu, chứa giá trị, nhãn và mô tả của trường hợp.

Thêm tính linh hoạt với các Stub có thể tái sử dụng

Nếu bạn đang làm việc trên nhiều enum và muốn có một cách nhanh chóng để thiết lập chúng với chức năng tương tự, bạn có thể sử dụng một stub có thể tái sử dụng kết hợp mọi thứ chúng ta đã đề cập. Dưới đây là phiên bản tinh chỉnh của một stub như vậy:

<?php

namespace {{ namespace }};

use App\Contracts\Enum as Contract;
use App\Concerns\InteractsWithEnumOptions;

enum {{ class }} implements Contract
{
    use InteractsWithEnumOptions;

    case EXAMPLE; // Add actual cases here

    public function label(): string
    {
        return match ($this) {
            self::EXAMPLE => 'Example Label', // Add labels for other cases
            default => throw new \Exception('Unknown enum value requested for the label'),
        };
    }

    public function description(): string
    {
        return match ($this) {
            self::EXAMPLE => 'This is an example description.', // Add descriptions for other cases
            default => throw new \Exception('Unknown enum value requested for the description'),
        };
    }
}

Tùy chỉnh Stub:

  • Thay thế namespace bằng namespace thực tế của enum của bạn.
  • Thêm các trường hợp enum có liên quan bên trong khối case và xác định nhãn và mô tả tương ứng.

Tại sao mô hình này hoạt động

  • Tính nhất quán trên các Enum: Với thiết lập này, mọi enum triển khai interface Enum phải cung cấp nhãn và mô tả. Điều này đảm bảo rằng tất cả các enum đều tuân theo cùng một mẫu, làm cho cơ sở mã của bạn dễ bảo trì hơn.
  • Khả năng tái sử dụng với Trait: Trait InteractsWithEnumOptions đóng gói logic chung để tạo các tùy chọn, có thể được tái sử dụng trên nhiều enum mà không cần sao chép mã.
  • Khả năng mở rộng: Khi ứng dụng của bạn phát triển, việc thêm các trường hợp enum mới hoặc các phương thức bổ sung sẽ trở thành một quy trình liền mạch. Nếu bạn cần sửa đổi cách tạo các tùy chọn hoặc thêm chức năng mới, bạn có thể làm như vậy một cách tập trung trong trait hoặc interface.

Ví dụ sử dụng trong mẫu Laravel Blade

Hãy thêm một ví dụ về cách bạn có thể sử dụng enum trong mẫu Laravel Blade. Giả sử chúng ta đang làm việc với enum ServerStatus và chúng ta muốn tạo một đầu vào chọn để người dùng chọn trạng thái máy chủ, sử dụng các tùy chọn được tạo bởi trait InteractsWithEnumOptions.

Đầu tiên, hãy đảm bảo rằng bộ điều khiển của bạn đang chuyển các tùy chọn enum đến chế độ xem. Ví dụ:

Ví dụ về bộ điều khiển:

<?php

namespace App\Http\Controllers;

use App\Enums\ServerStatus;

class ServerController extends Controller
{
    public function edit($id)
    {
        $server = Server::findOrFail($id);
        $statusOptions = ServerStatus::options(); // Get enum options using the trait

        return view('servers.edit', compact('server', 'statusOptions'));
    }
}

Phương thức ServerStatus::options() sẽ trả về một mảng với cấu trúc:

[
    ['value' => 'pending', 'label' => 'Pending Installation', 'description' => 'The server is currently being created or initialized.'],
    ['value' => 'running', 'label' => 'Running', 'description' => 'The server is live and operational.'],
    // other cases...
]

Ví dụ về mẫu Blade:

Trong mẫu Blade của bạn, giờ đây bạn có thể sử dụng statusOptions để điền vào dropdown select và hiển thị mô tả khi người dùng chọn trạng thái.

@extends('layouts.app')

@section('content')
    <div class="container">
        <h1>Edit Server</h1>

        <form action="{{ route('servers.update', $server->id) }}" method="POST">
            @csrf
            @method('PUT')

            <!-- Other form fields -->

            <div class="mb-3">
                <label for="status" class="form-label">Server Status</label>
                <select name="status" id="status" class="form-control" onchange="updateDescription()">
                    @foreach ($statusOptions as $option)
                        <option value="{{ $option['value'] }}" 
                                @if ($server->status === $option['value']) selected @endif>
                            {{ $option['label'] }}
                        </option>
                    @endforeach
                </select>
            </div>

            <div id="statusDescription" class="alert alert-info">
                <!-- Default description when the page loads -->
                {{ $statusOptions[array_search($server->status, array_column($statusOptions, 'value'))]['description'] }}
            </div>

            <button type="submit" class="btn btn-primary">Update Server</button>
        </form>
    </div>

    <script>
        const statusOptions = @json($statusOptions);

        function updateDescription() {
            const selectedStatus = document.getElementById('status').value;
            const selectedOption = statusOptions.find(option => option.value === selectedStatus);
            document.getElementById('statusDescription').textContent = selectedOption ? selectedOption.description : '';
        }
    </script>
@endsection

Giải thích:

1. Bộ Điều Khiển: Phương thức ServerStatus::options() được gọi trong bộ điều khiển để tạo mảng các tùy chọn, sau đó được chuyển đến chế độ xem Blade.

2. Mẫu Blade:

  • Một phần tử <select> được tạo để cho phép người dùng chọn trạng thái máy chủ. Các tùy chọn được điền bằng cách sử dụng mảng $statusOptions.
  • Sự kiện onchange trên lựa chọn sẽ kích hoạt hàm JavaScript updateDescription(), hàm này sẽ cập nhật mô tả bên dưới dropdown dựa trên tùy chọn đã chọn.
  • Ban đầu, mô tả về trạng thái máy chủ hiện tại được hiển thị khi trang tải.

3. JavaScript: Mảng statusOptions được chuyển đến JavaScript bằng cách sử dụng chỉ thị @json và hàm updateDescription() cập nhật mô tả bất cứ khi nào người dùng thay đổi trạng thái đã chọn.

Thiết lập này sử dụng các tùy chọn enum theo cách thân thiện với người dùng, tận dụng cả phương thức label và description từ enum, cung cấp trải nghiệm phong phú trong các biểu mẫu của bạn.

Kết luận

Bằng cách kết hợp enum PHP với interface và trait, bạn tạo ra một mẫu linh hoạt, có thể mở rộng và có thể tái sử dụng để quản lý các hằng số với logic bổ sung như nhãn, mô tả và tùy chọn chọn. Thiết kế này đảm bảo tính nhất quán và khả năng bảo trì trên toàn bộ cơ sở mã của bạn, ngay cả khi độ phức tạp của ứng dụng của bạn tăng lên.

Với cấu trúc interface và trait mà chúng ta đã xây dựng, giờ đây bạn có thể triển khai enum một cách gọn gàng và có tổ chức. Cho dù bạn đang xử lý trạng thái máy chủ, vai trò người dùng hay trạng thái thanh toán, cách tiếp cận này sẽ giúp bạn viết mã sạch hơn và dễ bảo trì hơn.

Cảm ơn các bạn đã theo dõi.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí