首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 設計模式:裝飾器模式在表單元件庫中的應用

     編輯:PHP 綜合知識

裝飾器模式解決表單元件功能爆炸問題

表單元件 (Input、Select、Textarea) 常需添加標籤、驗證、錯誤顯示、CSRF 保護等功能。若用繼承會導致類別爆炸 (LabelledInput、ValidatedInput、LabelledValidatedInput...)。裝飾器模式優雅解決此問題。

問題場景:類別爆炸

BaseWidget
├── TextInput
├── Select
├── Textarea
│
繼承添加 Label:
├── LabelledTextInput
├── LabelledSelect
├── LabelledTextarea
│
繼承添加 Validation:
├── ValidatedTextInput
├── ValidatedSelect
├── ValidatedTextarea
│
繼承添加 Label + Validation:
├── LabelledValidatedTextInput
├── LabelledValidatedSelect
├── LabelledValidatedTextarea
... 組合數 = 元件類型 × 功能組合 = 爆炸性增長

裝飾器模式重構

<?php
// 1. 統一介面
interface Widget {
    public function render(): string;
    public function getName(): string;
    public function getValue(): mixed;
    public function setValue(mixed $value): void;
    public function isValid(): bool;
    public function getErrors(): array;
}

// 2. 基礎元件
abstract class BaseWidget implements Widget {
    protected string $name;
    protected mixed $value;
    protected array $errors = [];
    protected array $attributes = [];
    
    public function __construct(string $name, array $attributes = []) {
        $this->name = $name;
        $this->attributes = $attributes;
    }
    
    public function getName(): string { return $this->name; }
    public function getValue(): mixed { return $this->value; }
    public function setValue(mixed $value): void { $this->value = $value; }
    public function isValid(): bool { return empty($this->errors); }
    public function getErrors(): array { return $this->errors; }
    public function addError(string $error): void { $this->errors[] = $error; }
    public function setAttribute(string $key, string $value): void { $this->attributes[$key] = $value; }
    protected function buildAttributes(): string {
        return implode(' ', array_map(fn($k,$v) => "$k=\"$v\"", $this->attributes));
    }
}

class TextInput extends BaseWidget {
    public function render(): string {
        return "name}\" value=\"" . htmlspecialchars((string)$this->value) . "\" " . $this->buildAttributes() . ">";
    }
}

class Select extends BaseWidget {
    private array $options = [];
    public function setOptions(array $options): void { $this->options = $options; }
    public function render(): string {
        $html = "";
    }
}

// 3. 裝飾者基底
abstract class WidgetDecorator implements Widget {
    protected Widget $widget;
    
    public function __construct(Widget $widget) {
        $this->widget = $widget;
    }
    
    // 預設轉發
    public function getName(): string { return $this->widget->getName(); }
    public function getValue(): mixed { return $this->widget->getValue(); }
    public function setValue(mixed $value): void { $this->widget->setValue($value); }
    public function isValid(): bool { return $this->widget->isValid(); }
    public function getErrors(): array { return $this->widget->getErrors(); }
    abstract public function render(): string;
}

// 4. 具體裝飾者:標籤
class LabelledWidget extends WidgetDecorator {
    private string $label;
    
    public function __construct(Widget $widget, string $label) {
        parent::__construct($widget);
        $this->label = $label;
    }
    
    public function render(): string {
        return " " . $this->widget->render() . "
"; } } // 5. 具體裝飾者:驗證 class ValidatedWidget extends WidgetDecorator { private array $validators = []; // callable public function __construct(Widget $widget, array $validators = []) { parent::__construct($widget); $this->validators = $validators; } public function addValidator(callable $validator): void { $this->validators[] = $validator; } public function isValid(): bool { $this->widget->setValue($this->widget->getValue()); // 觸發驗證 foreach ($this->validators as $validator) { $error = $validator($this->widget->getValue()); if ($error) $this->widget->addError($error); } return parent::isValid(); } public function render(): string { $html = $this->widget->render(); if (!$this->widget->isValid()) { $html .= "" . implode('
', $this->widget->getErrors()) . "
"; } return $html; } } // 6. 具體裝飾者:CSRF 保護 (針對表單) class CsrfProtectedWidget extends WidgetDecorator { public function render(): string { $token = $_SESSION['csrf_token'] ??= bin2hex(random_bytes(32)); return $this->widget->render() . ""; } } ?>

使用範例:動態組合功能

<?php
// 建立基礎元件
$emailInput = new TextInput('email', ['placeholder' => 'your@email.com']);
$countrySelect = new Select('country');
$countrySelect->setOptions(['TW' => '台灣', 'US' => '美國', 'JP' => '日本']);

// 動態添加功能:標籤 + 驗證 + CSRF
$emailField = new CsrfProtectedWidget(
    new ValidatedWidget(
        new LabelledWidget($emailInput, '電子郵件:'),
        [
            fn($v) => empty($v) ? '必填' : null,
            fn($v) => filter_var($v, FILTER_VALIDATE_EMAIL) ? null : '格式錯誤',
        ]
    )
);

$countryField = new LabelledWidget(
    new ValidatedWidget($countrySelect, [
        fn($v) => empty($v) ? '請選擇國家' : null,
    ]),
    '國家:'
);

// 表單渲染
echo "
"; echo $emailField->render(); echo $countryField->render(); echo ""; echo "
"; // 伺服器端驗證 if ($_SERVER['REQUEST_METHOD'] === 'POST') { $emailField->isValid(); // 觸發驗證 $countryField->isValid(); if ($emailField->isValid() && $countryField->isValid()) { echo "驗證通過!Email: {$emailField->getValue()}, Country: {$countryField->getValue()}"; } else { echo "表單有誤,請檢查。"; // 重新渲染會顯示錯誤訊息 } } ?>

渲染結果示意


必填
格式錯誤

請選擇國家

裝飾器 vs 繼承 vs Trait 比較

方案 靈活性 類別數量 執行時組合 型別安全
繼承 低 爆炸 否 高
Trait 中 固定 否 (編譯時) 中
裝飾器 高 線性增長 是 高 (介面)

進階:條件式裝飾

<?php
// 只有管理員顯示的欄位
$adminField = new ConditionalWidget(
    new LabelledWidget(new TextInput('api_key'), 'API Key:'),
    fn() => $_SESSION['user']->isAdmin ?? false
);

// 條件裝飾者
class ConditionalWidget extends WidgetDecorator {
    private callable $condition;
    public function __construct(Widget $widget, callable $condition) {
        parent::__construct($widget);
        $this->condition = $condition;
    }
    public function render(): string {
        return ($this->condition)() ? $this->widget->render() : '';
    }
    public function isValid(): bool {
        return ($this->condition)() ? $this->widget->isValid() : true;
    }
}
?>
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved