這篇文章是針對 Dan Harrin 在 laracasts 的影片進行的設計範例和理解筆記,其設計哲學貫穿整個 filament package,理解其原理可以為後續開發思路打下基礎。

Filament/livewire 設計概念是以 component 為基礎,透過對應設計達到 component based design,意思就是透過 component 完成功能。

設計 TextInput Component

namespace App\Components;

class TextInput
{
    public function __construct(
        protected string $name
    ) {
    }

    // 透過靜態類別方法來創建實體,避免要寫 new TextInput()
    public static function make(string $name): self
    {
        // 由靜態類 new instance
        return new self($name);
    }

    // render view component
    public function render(): View
    {
        return view('components.text-input');
    }

接下來創建其對應的 html component,components/text-input.blade.php:

<input type="text" />

在主要 layout 當中使用其組件,這邊我的主 layout 為 demo.blade.php:

{!! $input->render()->render() !!}

最後在呼叫主 layout 時注入 input:

Route::get('/', function () {
    $input = \App\Components\TextInput::make('Email');

    return view('demo', [
        'input' => $input
    ]);
});

這樣子就是一個基礎組件設計,可以很方便的在其他地方使用,接下來進行語句優化,這邊會用到 Laravel 的一個特性 contract Htmlable,這個介面一旦實現他後他會將其轉換為 html 格式的字串,這樣在呼叫時就不需要再寫一個 render() 函數。

class TextInput implements Htmlable
{
    public function render(): View
    {
        return view('components.text-input');
    }

    public function toHtml(): string
    {
        // $this->render() <- 呼叫原有 render()
        // ->render() <- 在將其渲染成 html
        return $this->render()->render();
    }

接著修改 demo.blade.php 並且在呼叫主 layout 時注入 input:

{{ $input }}

添加方法

在 filament 當中有很多方便的 API 可以進行調用,這邊以 label 為基礎進行添加:

protected string $label;

public function label(string $label): self
{
    $this->label = $label;

    return $this;
}

public function render(): View
{
    return view('components.text-input', [
        'label' => $this->label,
    ]);
}

//web.php
$input = \App\Components\TextInput::make('Email')
        ->label('Email Address');

這樣子運作沒問題,但 label 就會是必須項目,所以這邊當沒有呼叫 label 則以 $name 作為預設:

public function getLabel():string
{
    return $this->label ?? str($this->name)->title();
}

public function render(): View
{
    return view('components.text-input',[
        'label' => $this->getLabel(),
    ]);
}

方法反射

當添加 `label 時我們就必須一直修改 render() 當中的 data passed,有沒有辦法可以讓他更加方便,這邊就會需要透過反射方法進行映射到 blade:

public function extractPublicMethods(): array
{
    // 反射
    $reflection = new \ReflectionClass($this);

    $methods = [];
    // 取得所有 public 方法
    foreach ($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
        // 取得方法名,並綁定方法
        $methods[$method->getName()] = \Closure::fromCallable([$this, $method->getName()]);
    }

    return $methods;
}

public function render(): View
{
    return view('components.text-input',$this->extractPublicMethods());
}

// text-input.blade.php
<label>
<span>
    {{ $getLabel() }}
</span>


    <input type="text" />

</label>

非常佩服 Dan Harrin 的設計,其組件和邏輯整合度非常高,並且很方便的以組件型態去調用,可以很方便的在其他地方使用。