模板让网络运转起来。数据和结构合成为内容。这是我们作为开发人员最酷的超能力——获取一些数据,然后让它为我们工作,以我们需要的任何形式呈现。对象数组可以变成表格、卡片列表、图表或任何我们认为对用户最有用的东西。无论数据是我们自己的 Markdown 文件中的博文,还是最新的全球汇率,标记和最终的用户体验都取决于我们前端开发人员。
PHP 是一种用于模板化的绝佳语言,它提供了许多将数据与标记合并的方法。让我们在本篇文章中深入了解一个使用数据构建 HTML 表单的示例。
想立即动手实践?跳转到实现部分。
在 PHP 中,我们可以将变量内联到使用双引号的字符串字面量中,因此如果我们有一个变量$name = 'world'
,我们可以编写echo "Hello, {$name}"
,它会打印预期的Hello, world
。对于更复杂的模板,我们始终可以连接字符串,例如:echo "Hello, " . $name . "."
。
对于老手来说,还有printf("Hello, %s", $name)
。对于多行字符串,您可以使用Heredoc(以<<<MYTEXT
开头)。最后但并非最不重要的是,我们可以在 HTML 中散布 PHP 变量,例如<p>Hello, <?= $name ?></p>
。
所有这些选项都很好,但是当需要很多内联逻辑时,事情可能会变得很混乱。如果我们需要构建复合 HTML 字符串,比如表单或导航,复杂性可能无限,因为 HTML 元素可以相互嵌套。
我们试图避免什么
在我们继续进行我们想做的事情之前,花点时间考虑一下我们不想做的事情是值得的。考虑以下来自 WordPress 核心圣经class-walker-nav-menu.php
第 170-270 节的节选
<?php // class-walker-nav-menu.php
// ...
$output .= $indent . '<li' . $id . $class_names . '>';
// ...
$item_output = $args->before;
$item_output .= '<a' . $attributes . '>';
$item_output .= $args->link_before . $title . $args->link_after;
$item_output .= '</a>';
$item_output .= $args->after;
// ...
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
// ...
$output .= "</li>{$n}";
为了在此函数中构建导航<ul>
,我们使用一个变量$output
,这是一个非常长的字符串,我们不断向其中添加内容。这种类型的代码具有非常具体且有限的操作顺序。如果我们想向<a>
添加属性,我们必须在它运行之前访问$attributes
。如果我们想选择性地在<a>
内部嵌套<span>
或<img>
,我们需要编写一个全新的代码块,该代码块将替换第 7 行的中间部分,大约需要 4 到 10 行新代码,具体取决于我们想要添加的内容。现在想象一下,您需要选择性地添加<span>
,然后选择性地添加<img>
,或者在<span>
内部或之后添加。仅此一项就需要三个if
语句,使代码更难阅读。
当像这样连接字符串时,很容易最终得到字符串意大利面,说起来很有趣,但维护起来却很痛苦。
问题的本质在于,当我们尝试推断 HTML 元素时,我们不是在考虑字符串。浏览器消耗和 PHP 输出的恰好是字符串。但我们的心理模型更像是 DOM——元素排列成树状结构,每个节点都有许多潜在的属性、特性和子节点。
如果有一种结构化、表达性的方法来构建我们的树,那不是很好吗?
进入…
DOMDocument
类
PHP 5 将DOM
模块添加到其非严格类型™类型的名单中。它的主要入口点是DOMDocument
类,它有意与 Web API 的 JavaScript DOM
相似。如果您曾经使用过document.createElement
或对于我们某些年龄段的人来说,jQuery 的$('<p>Hi there!</p>')
语法,这可能会感觉非常熟悉。
我们首先初始化一个新的DOMDocument
$dom = new DOMDocument();
现在我们可以向其中添加一个DOMElement
$p = $dom->createElement('p');
字符串'p'
表示我们想要的元素类型,因此其他有效字符串将是'div'
、'img'
等。
获得元素后,我们可以设置其属性
$p->setAttribute('class', 'headline');
我们可以向其中添加子元素
$span = $dom->createElement('span', 'This is a headline'); // The 2nd argument populates the element's textContent
$p->appendChild($span);
最后,一次性获取完整的 HTML 字符串
$dom->appendChild($p);
$htmlString = $dom->saveHTML();
echo $htmlString;
请注意,这种编码风格使我们的代码根据我们的心理模型保持井井有条——文档有元素;元素可以有任意数量的属性;元素相互嵌套而无需了解彼此的信息。“HTML 只是一个字符串”部分在最后出现,一旦我们的结构就位。
此处的“文档”与实际的 DOM 略有不同,因为它不需要表示整个文档,而只需要表示 HTML 代码块。实际上,如果您需要创建两个相似的元素,您可以使用saveHTML()
保存 HTML 字符串,进一步修改 DOM“文档”,然后通过再次调用saveHTML()
保存新的 HTML 字符串。
获取数据并设置结构
假设我们需要使用来自 CRM 提供商的数据和我们自己的标记在服务器上构建表单。来自 CRM 的 API 响应如下所示
{
"submit_button_label": "Submit now!",
"fields": [
{
"id": "first-name",
"type": "text",
"label": "First name",
"required": true,
"validation_message": "First name is required.",
"max_length": 30
},
{
"id": "category",
"type": "multiple_choice",
"label": "Choose all categories that apply",
"required": false,
"field_metadata": {
"multi_select": true,
"values": [
{ "value": "travel", "label": "Travel" },
{ "value": "marketing", "label": "Marketing" }
]
}
}
]
}
此示例未使用任何特定 CRM 的确切数据结构,但它具有代表性。
并且假设我们希望我们的标记如下所示
<form>
<label class="field">
<input type="text" name="first-name" id="first-name" placeholder=" " required>
<span class="label">First name</span>
<em class="validation" hidden>First name is required.</em>
</label>
<label class="field checkbox-group">
<fieldset>
<div class="choice">
<input type="checkbox" value="travel" id="category-travel" name="category">
<label for="category-travel">Travel</label>
</div>
<div class="choice">
<input type="checkbox" value="marketing" id="category-marketing" name="category">
<label for="category-marketing">Marketing</label>
</div>
</fieldset>
<span class="label">Choose all categories that apply</span>
</label>
</form>
placeholder=" "
是什么?这是一个小技巧,它允许我们在 CSS 中跟踪字段是否为空,而无需使用 JavaScript。只要输入为空,它就与input:placeholder-shown
匹配,但用户看不到任何可见的占位符文本。当我们控制标记时,您可以做的事情就是这样!
现在我们知道了我们想要的结果,以下是游戏计划
- 从 API 获取字段定义和其他内容
- 初始化
DOMDocument
- 迭代字段并根据需要构建每个字段
- 获取 HTML 输出
所以让我们构建我们的流程并解决一些技术细节
<?php
function renderForm ($endpoint) {
// Get the data from the API and convert it to a PHP object
$formResult = file_get_contents($endpoint);
$formContent = json_decode($formResult);
$formFields = $formContent->fields;
// Start building the DOM
$dom = new DOMDocument();
$form = $dom->createElement('form');
// Iterate over the fields and build each one
foreach ($formFields as $field) {
// TODO: Do something with the field data
}
// Get the HTML output
$dom->appendChild($form);
$htmlString = $dom->saveHTML();
echo $htmlString;
}
到目前为止,我们已经获取了数据并对其进行了解析,初始化了我们的DOMDocument
并回显了其输出。我们想对每个字段做什么?首先,让我们构建容器元素,在我们的示例中,它应该是<label>
,以及对所有字段类型都通用的标签<span>
<?php
// ...
// Iterate over the fields and build each one
foreach ($formFields as $field) {
// Build the container `<label>`
$element = $dom->createElement('label');
$element->setAttribute('class', 'field');
// Reset input values
$label = null;
// Add a `<span>` for the label if it is set
if ($field->label) {
$label = $dom->createElement('span', $field->label);
$label->setAttribute('class', 'label');
}
// Add the label to the `<label>`
if ($label) $element->appendChild($label);
}
由于我们在循环中,并且 PHP 不会在循环中对变量进行作用域限定,因此我们在每次迭代时都会重置$label
元素。然后,如果字段有标签,我们就构建该元素。最后,我们将其附加到容器元素。
请注意,我们使用setAttribute
方法设置类。与 Web API 不同,不幸的是,没有对类列表进行特殊处理。它们只是另一个属性。如果我们有一些非常复杂的类逻辑,由于它只是 PHP™,我们可以创建一个数组,然后将其合并$label->setAttribute('class', implode($labelClassList))
.
单个输入
由于我们知道 API 只会返回特定的字段类型,因此我们可以切换类型并为每个类型编写特定的代码
<?php
// ...
// Iterate over the fields and build each one
foreach ($formFields as $field) {
// Build the container `<label>`
$element = $dom->createElement('label');
$element->setAttribute('class', 'field');
// Reset input values
$input = null;
$label = null;
// Add a `<span>` for the label if it is set
// ...
// Build the input element
switch ($field->type) {
case 'text':
case 'email':
case 'telephone':
$input = $dom->createElement('input');
$input->setAttribute('placeholder', ' ');
if ($field->type === 'email') $input->setAttribute('type', 'email');
if ($field->type === 'telephone') $input->setAttribute('type', 'tel');
break;
}
}
现在让我们处理文本区域、单个复选框和隐藏字段
<?php
// ...
// Iterate over the fields and build each one
foreach ($formFields as $field) {
// Build the container `<label>`
$element = $dom->createElement('label');
$element->setAttribute('class', 'field');
// Reset input values
$input = null;
$label = null;
// Add a `<span>` for the label if it is set
// ...
// Build the input element
switch ($field->type) {
//...
case 'text_area':
$input = $dom->createElement('textarea');
$input->setAttribute('placeholder', ' ');
if ($rows = $field->field_metadata->rows) $input->setAttribute('rows', $rows);
break;
case 'checkbox':
$element->setAttribute('class', 'field single-checkbox');
$input = $dom->createElement('input');
$input->setAttribute('type', 'checkbox');
if ($field->field_metadata->initially_checked === true) $input->setAttribute('checked', 'checked');
break;
case 'hidden':
$input = $dom->createElement('input');
$input->setAttribute('type', 'hidden');
$input->setAttribute('value', $field->field_metadata->value);
$element->setAttribute('hidden', 'hidden');
$element->setAttribute('style', 'display: none;');
$label->textContent = '';
break;
}
}
注意我们在复选框和隐藏情况下做了一些新的事情?我们不仅创建了<input>
元素;我们还在更改容器<label>
元素!对于单个复选框字段,我们希望修改容器的类,以便我们可以水平对齐复选框和标签;隐藏<input>
的容器也应该完全隐藏。
现在,如果我们只是连接字符串,那么此时将无法更改。我们将不得不在代码块顶部添加许多关于元素类型及其元数据的if
语句。或者,也许更糟糕的是,我们更早地开始switch
,然后在每个分支之间复制粘贴大量通用代码。
而使用像DOMDocument
这样的构建器的真正好处在于——在我们到达saveHTML()
之前,一切都仍然可编辑,并且一切都仍然结构化。
嵌套循环元素
让我们添加<select>
元素的逻辑
<?php
// ...
// Iterate over the fields and build each one
foreach ($formFields as $field) {
// Build the container `<label>`
$element = $dom->createElement('label');
$element->setAttribute('class', 'field');
// Reset input values
$input = null;
$label = null;
// Add a `<span>` for the label if it is set
// ...
// Build the input element
switch ($field->type) {
//...
case 'select':
$element->setAttribute('class', 'field select');
$input = $dom->createElement('select');
$input->setAttribute('required', 'required');
if ($field->field_metadata->multi_select === true)
$input->setAttribute('multiple', 'multiple');
$options = [];
// Track whether there's a pre-selected option
$optionSelected = false;
foreach ($field->field_metadata->values as $value) {
$option = $dom->createElement('option', htmlspecialchars($value->label));
// Bail if there's no value
if (!$value->value) continue;
// Set pre-selected option
if ($value->selected === true) {
$option->setAttribute('selected', 'selected');
$optionSelected = true;
}
$option->setAttribute('value', $value->value);
$options[] = $option;
}
// If there is no pre-selected option, build an empty placeholder option
if ($optionSelected === false) {
$emptyOption = $dom->createElement('option');
// Set option to hidden, disabled, and selected
foreach (['hidden', 'disabled', 'selected'] as $attribute)
$emptyOption->setAttribute($attribute, $attribute);
$input->appendChild($emptyOption);
}
// Add options from array to `<select>`
foreach ($options as $option) {
$input->appendChild($option);
}
break;
}
}
好的,这里有很多事情发生,但底层逻辑是一样的。在设置外部<select>
之后,我们创建了一个<option>
数组并将其附加到其中。
我们在这里也做了一些特定于<select>
的技巧:如果没有预选选项,我们将添加一个已选但用户无法选择的空占位符选项。目标是使用 CSS 将我们的<label class="label">
作为“占位符”,但此技术可用于各种设计。通过在附加其他选项之前将其附加到$input
,我们确保它是标记中的第一个选项。
现在让我们处理单选按钮和复选框的<fieldset>
<?php
// ...
// Iterate over the fields and build each one
foreach ($formFields as $field) {
// Build the container `<label>`
$element = $dom->createElement('label');
$element->setAttribute('class', 'field');
// Reset input values
$input = null;
$label = null;
// Add a `<span>` for the label if it is set
// ...
// Build the input element
switch ($field->type) {
// ...
case 'multiple_choice':
$choiceType = $field->field_metadata->multi_select === true ? 'checkbox' : 'radio';
$element->setAttribute('class', "field {$choiceType}-group");
$input = $dom->createElement('fieldset');
// Build a choice `<input>` for each option in the fieldset
foreach ($field->field_metadata->values as $choiceValue) {
$choiceField = $dom->createElement('div');
$choiceField->setAttribute('class', 'choice');
// Set a unique ID using the field ID + the choice ID
$choiceID = "{$field->id}-{$choiceValue->value}";
// Build the `<input>` element
$choice = $dom->createElement('input');
$choice->setAttribute('type', $choiceType);
$choice->setAttribute('value', $choiceValue->value);
$choice->setAttribute('id', $choiceID);
$choice->setAttribute('name', $field->id);
$choiceField->appendChild($choice);
// Build the `<label>` element
$choiceLabel = $dom->createElement('label', $choiceValue->label);
$choiceLabel->setAttribute('for', $choiceID);
$choiceField->appendChild($choiceLabel);
$input->appendChild($choiceField);
}
break;
}
}
首先,我们确定字段集应该使用复选框还是单选按钮。然后,我们根据需要设置容器类,并构建 `<fieldset>`。之后,我们遍历可用的选项,并为每个选项构建一个包含 `<input>` 和 `<label>` 的 `<div>`。
请注意,我们在第 21 行使用普通的 PHP 字符串插值来设置容器类,并在第 30 行创建每个选项的唯一 ID。
片段
我们还需要添加的最后一种类型比看起来稍微复杂一点。许多表单包含说明字段,这些字段不是输入,而只是我们需要在其他字段之间打印的一些 HTML。
我们需要使用另一个 `DOMDocument` 方法 `createDocumentFragment()`。这允许我们添加任意 HTML,而无需使用 DOM 结构。
<?php
// ...
// Iterate over the fields and build each one
foreach ($formFields as $field) {
// Build the container `<label>`
$element = $dom->createElement('label');
$element->setAttribute('class', 'field');
// Reset input values
$input = null;
$label = null;
// Add a `<span>` for the label if it is set
// ...
// Build the input element
switch ($field->type) {
//...
case 'instruction':
$element->setAttribute('class', 'field text');
$fragment = $dom->createDocumentFragment();
$fragment->appendXML($field->text);
$input = $dom->createElement('p');
$input->appendChild($fragment);
break;
}
}
此时您可能想知道我们是如何得到一个名为 `$input` 的对象的,它实际上表示一个静态的 `<p>` 元素。目标是为字段循环的每次迭代使用一个通用的变量名,这样在最后我们就可以始终使用 `$element->appendChild($input)` 来添加它,而不管实际的字段类型是什么。所以,是的,命名事物很困难。
验证
我们正在使用的 API 为每个必填字段提供了单独的验证消息。如果提交时出错,我们可以将错误与字段一起内联显示,而不是在底部显示通用的“糟糕,您错了”消息。
让我们将验证文本添加到每个元素中。
<?php
// ...
// Iterate over the fields and build each one
foreach ($formFields as $field) {
// build the container `<label>`
$element = $dom->createElement('label');
$element->setAttribute('class', 'field');
// Reset input values
$input = null;
$label = null;
$validation = null;
// Add a `<span>` for the label if it is set
// ...
// Add a `<em>` for the validation message if it is set
if (isset($field->validation_message)) {
$validation = $dom->createElement('em');
$fragment = $dom->createDocumentFragment();
$fragment->appendXML($field->validation_message);
$validation->appendChild($fragment);
$validation->setAttribute('class', 'validation-message');
$validation->setAttribute('hidden', 'hidden'); // Initially hidden, and will be unhidden with Javascript if there's an error on the field
}
// Build the input element
switch ($field->type) {
// ...
}
}
就是这样!无需修改字段类型逻辑——只需为每个字段有条件地构建一个元素即可。
整合所有内容
那么,构建完所有字段元素后会发生什么?我们需要将 `$input`、`$label` 和 `$validation` 对象添加到我们正在构建的 DOM 树中。我们还可以利用这个机会添加通用属性,例如 `required`。然后,我们将添加提交按钮,该按钮在此 API 中与字段分开。
<?php
function renderForm ($endpoint) {
// Get the data from the API and convert it to a PHP object
// ...
// Start building the DOM
$dom = new DOMDocument();
$form = $dom->createElement('form');
// Iterate over the fields and build each one
foreach ($formFields as $field) {
// Build the container `<label>`
$element = $dom->createElement('label');
$element->setAttribute('class', 'field');
// Reset input values
$input = null;
$label = null;
$validation = null;
// Add a `<span>` for the label if it is set
// ...
// Add a `<em>` for the validation message if it is set
// ...
// Build the input element
switch ($field->type) {
// ...
}
// Add the input element
if ($input) {
$input->setAttribute('id', $field->id);
if ($field->required)
$input->setAttribute('required', 'required');
if (isset($field->max_length))
$input->setAttribute('maxlength', $field->max_length);
$element->appendChild($input);
if ($label)
$element->appendChild($label);
if ($validation)
$element->appendChild($validation);
$form->appendChild($element);
}
}
// Build the submit button
$submitButtonLabel = $formContent->submit_button_label;
$submitButtonField = $dom->createElement('div');
$submitButtonField->setAttribute('class', 'field submit');
$submitButton = $dom->createElement('button', $submitButtonLabel);
$submitButtonField->appendChild($submitButton);
$form->appendChild($submitButtonField);
// Get the HTML output
$dom->appendChild($form);
$htmlString = $dom->saveHTML();
echo $htmlString;
}
为什么我们要检查 `$input` 是否为真?因为我们在循环顶部将其重置为 `null`,并且仅在类型符合我们预期的 switch case 时才构建它,这确保我们不会意外地包含代码无法正确处理的意外元素。
瞧,一个自定义的 HTML 表单!
加分项:行和列
您可能知道,许多表单构建器允许作者为字段设置行和列。例如,一行可能同时包含名字和姓氏字段,每个字段都位于一个宽度为 50% 的列中。那么,您可能会问,我们如何实现这一点?当然,通过举例说明 `DOMDocument` 如何易于循环!
我们的 API 响应包含如下所示的网格数据。
{
"submit_button_label": "Submit now!",
"fields": [
{
"id": "first-name",
"type": "text",
"label": "First name",
"required": true,
"validation_message": "First name is required.",
"max_length": 30,
"row": 1,
"column": 1
},
{
"id": "category",
"type": "multiple_choice",
"label": "Choose all categories that apply",
"required": false,
"field_metadata": {
"multi_select": true,
"values": [
{ "value": "travel", "label": "Travel" },
{ "value": "marketing", "label": "Marketing" }
]
},
"row": 2,
"column": 1
}
]
}
我们假设添加 `data-column` 属性足以设置宽度,但每一行都需要是它自己的元素(即没有 CSS 网格)。
在深入研究之前,让我们思考一下为了添加行我们需要什么。基本逻辑如下所示。
- 跟踪遇到的最新行。
- 如果当前行更大,即我们跳到了下一行,则创建一个新的行元素并开始向其中添加内容,而不是添加到前一个元素中。
现在,如果我们正在连接字符串,我们会怎么做?可能是在到达新行时添加一个类似于 `'</div><div class="row">'` 的字符串。这种“反向 HTML 字符串”对我来说总是非常令人困惑,所以我只能想象我的 IDE 的感受。最重要的是,由于浏览器自动关闭打开的标签,一个简单的错误会导致无数嵌套的 `<div>`。就像很有趣,但恰恰相反。
那么结构化的处理方法是什么?感谢您的提问。首先,让我们在循环之前添加行跟踪,并构建一个额外的行容器元素。然后,我们将确保将每个容器 `$element` 附加到其 `$rowElement` 而不是直接附加到 `$form`。
<?php
function renderForm ($endpoint) {
// Get the data from the API and convert it to a PHP object
// ...
// Start building the DOM
$dom = new DOMDocument();
$form = $dom->createElement('form');
// init tracking of rows
$row = 0;
$rowElement = $dom->createElement('div');
$rowElement->setAttribute('class', 'field-row');
// Iterate over the fields and build each one
foreach ($formFields as $field) {
// Build the container `<label>`
$element = $dom->createElement('label');
$element->setAttribute('class', 'field');
$element->setAttribute('data-row', $field->row);
$element->setAttribute('data-column', $field->column);
// Add the input element to the row
if ($input) {
// ...
$rowElement->appendChild($element);
$form->appendChild($rowElement);
}
}
// ...
}
到目前为止,我们只是在字段周围添加了另一个 `<div>`。让我们为循环中的每一行构建一个新的行元素。
<?php
// ...
// Init tracking of rows
$row = 0;
$rowElement = $dom->createElement('div');
$rowElement->setAttribute('class', 'field-row');
// Iterate over the fields and build each one
foreach ($formFields as $field) {
// ...
// If we've reached a new row, create a new $rowElement
if ($field->row > $row) {
$row = $field->row;
$rowElement = $dom->createElement('div');
$rowElement->setAttribute('class', 'field-row');
}
// Build the input element
switch ($field->type) {
// ...
// Add the input element to the row
if ($input) {
// ...
$rowElement->appendChild($element);
// Automatically de-duped
$form->appendChild($rowElement);
}
}
}
我们只需要将 `$rowElement` 对象覆盖为一个新的 DOM 元素,PHP 会将其视为一个新的唯一对象。因此,在每次循环结束时,我们只需附加当前的 `$rowElement`——如果它仍然与前一次迭代中的相同,则更新表单;如果它是一个新元素,则将其附加到末尾。
接下来去哪里?
表单是面向对象模板的一个很好的用例。考虑到 WordPress 核心中的那个代码片段,也可以认为嵌套菜单也是一个很好的用例。任何标记遵循复杂逻辑的任务都是这种方法的良好候选者。`DOMDocument` 可以输出任何 XML,因此您还可以使用它根据帖子数据构建 RSS 提要。
这是我们表单的完整代码片段。 随意将其调整到您遇到的任何表单 API。 这是官方文档,它有助于了解可用的 API。
我们甚至没有提到 `DOMDocument` 可以解析现有的 HTML 和 XML。然后,您可以使用 XPath API 查找元素,这有点类似于 Node.js 上的 `document.querySelector` 或 `cheerio`。学习曲线有点陡峭,但它是一个非常强大的 API,用于处理外部内容。
有趣(?)的事实:以 `x` 结尾的 Microsoft Office 文件(例如 `xlsx`)是 XML 文件。不要告诉营销部门,但可以在服务器上解析 Word 文档并输出 HTML。
最重要的是要记住,模板化是一种强大的功能。能够为合适的情况构建正确的标记可能是实现出色用户体验的关键。
非常鼓舞人心的文章,阅读关于 PHP 的内容总是很棒。但说实话,我还没有完全信服,并且仍然认为在大多数情况下,使用 PHP(或 JS!)中的模板字符串更容易编写和阅读。使用 (s)printf、(编号)占位符、三元运算符(以及它的简短变体,如 ?? 和 ?:) 以及格式良好的代码,您可以用更少的代码完成很多工作,并且更容易看到最终 HTML 输出的样子。但是是的,对于具有大量变化的非常复杂的任务,最好记住 DOMDocument 存在!
很棒的文章!我想分享一下,我一直在维护 https://GitHub.com/PhpGt/Dom,它建立在 DOMDocument 之上,添加了我们在客户端开发中期望的所有不错的现代功能。希望它能帮助到某些人。
哇,这看起来真的很棒。我下次一定会试试。使用部分的反面示例为其增加了加分 :)
您的写作非常流畅!我几个月来一直在考虑构建类似的东西,而您在这篇文章中给了我一个新的视角。谢谢!
关于解释 OO HTML 生成的很棒的文章。这是正确的方法。模板和包含文件太 1950 年代了。FORTRAN 使用包含文件,我们不必如此。
我采用了略微不同的方法。我想自动化创建响应式页面的工作,并移除所有样板代码。我还希望采用比你在这里概述的更高的层次方法。因此,我围绕着Foundation编写了一个PHP面向对象库。基本上,你创建一个页面,它处理使页面成为完整Foundation页面所需的一切,包括所有JS等等,然后你向其中添加内容。你可以添加纯文本或其他可以转换为字符串的对象。对象可以是手风琴菜单、提示框或任何你想要创建的东西。
通常,我的应用程序有一个标准的页面对象,其中包含所有菜单、页脚、页眉等,然后我向其中添加特定的视图对象,例如编辑表单或表格视图。
使用echo输出页面,我就完成了。简单、可扩展、类型安全且完全有效的HTML。查看像列表转换这样的示例。有一个源代码选项卡,显示使用100% PHP生成页面所需的所有内容。所有其他工作,如包含JavaScript库、CSS等,都为你完成了。
我还使用我的PHP文档工具编写了该网站,因此你可以完全了解类结构、文件源代码,甚至Git历史记录(对于此项目,是实际网站,而不是各个库的历史记录)。
查看一下。网页设计的未来。