在 Mustache 中执行此操作的惯用方法是创建一个视图(或 ViewModel),而不是传入数据散列:
<?php
class Dropdown
{
public $name;
public $value;
private $options;
public function __construct($name, array $options, $value)
{
$this->name = $name;
$this->options = $options;
$this->value = $value;
}
public function options()
{
$value = $this->value;
return array_map(function($k, $v) use ($value) {
return array(
'value' => $k,
'display' => $v,
'selected' => ($value === $k),
)
}, array_keys($this->options), $this->options);
}
}
然后你可以将它与dropdown
部分...
<select name="{{ name }}">
{{# options }}
<option value="{{ value }}"{{# selected }} selected{{/ selected }}>
{{ display }}
</option>
{{/ options }}
</select>
您可以像这样在模板中使用它:
{{# state }}
<label for="{{ name }}">State</label>
{{> dropdown }}
{{/ state }}
{{# country }}
<label for="{{ name }}">Country</label>
{{> dropdown }}
{{/ country }}
并渲染它:
<?php
$data = array(
'state' => new Dropdown('state', $someListOfStates, 'CA'),
'country' => new Dropdown('country', $someListOfCountries, 'USA'),
);
$template->render($data);
...但你可以做得更好:)
有了这个:
<?php
class StateDropdown extends Dropdown
{
static $states = array(...);
public function __construct($value, $name = 'state')
{
parent::__construct($name, self::$states, $value);
}
}
还有这个:
<?php
class CountryDropdown extends Dropdown
{
static $countries = array(...);
public function __construct($value, $name = 'country')
{
parent::__construct($name, self::$countries, $value);
}
}
其中之一:
<?php
class Address
{
public $street;
public $city;
public $state;
public $zip;
public $country;
public function __construct($street, $city, $state, $zip, $country, $name = 'address')
{
$this->street = $street;
$this->city = $city;
$this->state = new StateDropdown($state, sprintf('%s[state]', $name));
$this->zip = $zip;
$this->country = new CountryDropdown($country, sprintf('%s[country]', $name));
}
}
加入一个新的address
部分:
<label for="{{ name }}[street]">Street</label>
<input type="text" name="{{ name }}[street]" value="{{ street }}">
<label for="{{ name }}[city]">City</label>
<input type="text" name="{{ name }}[city]" value="{{ city }}">
{{# state }}
<label for="{{ name }}">State</label>
{{> dropdown }}
{{/ state }}
<label for="{{ name }}[zip]">Postal code</label>
<input type="text" name="{{ name }}[zip]" value="{{ zip }}">
{{# country }}
<label for="{{ name }}">Country</label>
{{> dropdown }}
{{/ country }}
更新您的主模板:
<h2>Shipping Address</h2>
{{# shippingAddress }}
{{> address }}
{{/ shippingAddress }}
<h2>Billing Address</h2>
{{# billingAddress }}
{{> address }}
{{/ billingAddress }}
去!
<?php
$data = array(
'shippingAddress' => new Address($shipStreet, $shipCity, $shipState, $shipZip, $shipCountry, 'shipping'),
'billingAddress' => new Address($billStreet, $billCity, $billState, $billZip, $billCountry, 'billing'),
};
$template->render($data);
现在,您有了模块化、可重用、易于测试、可扩展的代码和部分代码。
请注意,我们创建的类是“Views”或“ViewModels”。它们不是您的域模型对象...它们不关心持久性或验证,它们只关心为您的模板准备值。如果你也在使用模型,那就更容易了,因为像我们的地址类这样的东西可以包装你的地址模型,并直接从模型中获取它需要的值,而不是要求你将一堆东西传递给构造函数.
胡子之禅
如果您采用这种方法得出其合乎逻辑的结论,您最终会在您的应用程序中为每个操作/模板对拥有一个顶级 View 或 ViewModel 类 — View 可以在内部委托给子 Views 和 partials,就像我们对 Dropdowns 所做的那样我们的地址视图,但您将有一个一流的视图或视图模型负责呈现每个操作。
这意味着(在 MVC/MVVM 世界中),您的 Controller 操作将执行所需的任何“操作”,然后创建负责填充模板的 View 或 ViewModel 类,将几个域模型对象交给它,然后调用 render on模板。控制器不会准备任何数据,因为这是视图层的责任。它会简单地将几个模型对象交给它。
现在你所有的“渲染”逻辑都整齐地封装在视图层中,你所有的标记都整齐地封装在你的模板文件中,你的模型摆脱了丑陋的格式化业务,你的控制器就像它应该的那样漂亮和轻巧:)