# 教程4: 使用CRUD(Tutorial 4: Working with the CRUD)
后臺通常提供表單來允許用戶提交數據. 繼續對INVO的解釋, 我們現在處理CRUD的創建, 一個非常常見的操作任務, Phalcon將會幫助你使用表單, 校驗, 分頁和更多.
在INVO(公司, 產品和產品類型)中大部分選項操作數據都是使用一個基礎的常見的[CRUD](http://en.wikipedia.org/wiki/Create,_read,_update_and_delete)(創建, 讀取, 更新和刪除)開發的. 每個CRUD包含以下文件:
~~~
invo/
app/
controllers/
ProductsController.php
models/
Products.php
forms/
ProductsForm.php
views/
products/
edit.volt
index.volt
new.volt
search.volt
~~~
每個控制器都有以下方法:
~~~
<?php
class ProductsController extends ControllerBase
{
/**
* 開始操作, 它展示"search"視圖
*/
public function indexAction()
{
// ...
}
/**
* 基于從"index"發送過來的條件處理"search"
* 返回一個分頁結果
*/
public function searchAction()
{
// ...
}
/**
* 展示創建一個"new"(新)產品的視圖
*/
public function newAction()
{
// ...
}
/**
* 展示編輯一個已存在"edit"(編輯)產品的視圖
*/
public function editAction()
{
// ...
}
/**
* 基于"new"方法中輸入的數據創建一個產品
*/
public function createAction()
{
// ...
}
/**
* 基于"edit"方法中輸入的數據更新一個產品
*/
public function saveAction()
{
// ...
}
/**
* 刪除一個已存在的產品
*/
public function deleteAction($id)
{
// ...
}
}
~~~
## 表單搜索(The Search Form)
每個 CRUD 都開始于一個搜索表單. 這個表單展示了表(products)中的每個字段, 允許用戶為一些字段創建一個搜索條件. 表 “products” 和表 “products\_types” 是關系表. 既然這樣, 我們先前查詢表中的記錄以便于字段的搜索:
~~~
<?php
/**
* 開始操作, 它展示"search"視圖
*/
public function indexAction()
{
$this->persistent->searchParams = null;
$this->view->form = new ProductsForm();
}
~~~
ProductsForm 表單的實例 (app/forms/ProductsForm.php)傳遞給了視圖. 這個表單定義了用戶可見的字段:
~~~
<?php
use Phalcon\Forms\Form;
use Phalcon\Forms\Element\Text;
use Phalcon\Forms\Element\Hidden;
use Phalcon\Forms\Element\Select;
use Phalcon\Validation\Validator\Email;
use Phalcon\Validation\Validator\PresenceOf;
use Phalcon\Validation\Validator\Numericality;
class ProductsForm extends Form
{
/**
* 初始化產品表單
*/
public function initialize($entity = null, $options = [])
{
if (!isset($options["edit"])) {
$element = new Text("id");
$element->setLabel("Id");
$this->add(
$element
);
} else {
$this->add(
new Hidden("id")
);
}
$name = new Text("name");
$name->setLabel("Name");
$name->setFilters(
[
"striptags",
"string",
]
);
$name->addValidators(
[
new PresenceOf(
[
"message" => "Name is required",
]
)
]
);
$this->add($name);
$type = new Select(
"profilesId",
ProductTypes::find(),
[
"using" => [
"id",
"name",
],
"useEmpty" => true,
"emptyText" => "...",
"emptyValue" => "",
]
);
$this->add($type);
$price = new Text("price");
$price->setLabel("Price");
$price->setFilters(
[
"float",
]
);
$price->addValidators(
[
new PresenceOf(
[
"message" => "Price is required",
]
),
new Numericality(
[
"message" => "Price is required",
]
),
]
);
$this->add($price);
}
}
~~~
表單是使用面向對象的方式聲明的, 基于[forms](http://docs.iphalcon.cn/reference/forms.html)組件提供的元素. 每個元素都遵循近乎相同的結構:
~~~
<?php
// 創建一個元素
$name = new Text("name");
// 設置它的label
$name->setLabel("Name");
// 在驗證元素之前應用這些過濾器
$name->setFilters(
[
"striptags",
"string",
]
);
// 應用此驗證
$name->addValidators(
[
new PresenceOf(
[
"message" => "Name is required",
]
)
]
);
// 增加元素到表單
$this->add($name);
~~~
在表單中其它元素也是這樣使用:
~~~
<?php
// 增加一個隱藏input到表單
$this->add(
new Hidden("id")
);
// ...
$productTypes = ProductTypes::find();
// 增加一個HTML Select (列表) 到表單
// 數據從"product_types"中填充
$type = new Select(
"profilesId",
$productTypes,
[
"using" => [
"id",
"name",
],
"useEmpty" => true,
"emptyText" => "...",
"emptyValue" => "",
]
);
~~~
注意,`ProductTypes::find()`包含的必須的數據 使用`Phalcon\Tag::select()`來填充 SELECT 標簽. 一旦表單傳遞給視圖, 它會進行渲染并呈現給用戶:
~~~
{{ form("products/search") }}
<h2>
Search products
</h2>
<fieldset>
{% for element in form %}
<div class="control-group">
{{ element.label(["class": "control-label"]) }}
<div class="controls">
{{ element }}
</div>
</div>
{% endfor %}
<div class="control-group">
{{ submit_button("Search", "class": "btn btn-primary") }}
</div>
</fieldset>
{{ endForm() }}
~~~
這會生成下面的HTML:
~~~
<form action="/invo/products/search" method="post">
<h2>
Search products
</h2>
<fieldset>
<div class="control-group">
<label for="id" class="control-label">Id</label>
<div class="controls">
<input type="text" id="id" name="id" />
</div>
</div>
<div class="control-group">
<label for="name" class="control-label">Name</label>
<div class="controls">
<input type="text" id="name" name="name" />
</div>
</div>
<div class="control-group">
<label for="profilesId" class="control-label">profilesId</label>
<div class="controls">
<select id="profilesId" name="profilesId">
<option value="">...</option>
<option value="1">Vegetables</option>
<option value="2">Fruits</option>
</select>
</div>
</div>
<div class="control-group">
<label for="price" class="control-label">Price</label>
<div class="controls">
<input type="text" id="price" name="price" />
</div>
</div>
<div class="control-group">
<input type="submit" value="Search" class="btn btn-primary" />
</div>
</fieldset>
</form>
~~~
當表單提交的時候, 控制器里面的”search”操作是基于用戶輸入的數據執行搜索的.
## 執行搜索(Performing a Search)
“search”操作有兩個行為. 當通過POST訪問, 它基于表單發送的數據執行搜索, 但是當通過GET訪問它會在分頁中移動當前的頁數. 為了區分HTTP方法,我們使用[Request](http://docs.iphalcon.cn/reference/request.html)組件進行校驗:
~~~
<?php
/**
* 基于從"index"發送過來的條件處理"search"
* 返回一個分頁結果
*/
public function searchAction()
{
if ($this->request->isPost()) {
// 創建查詢條件
} else {
// 使用已存在的條件分頁
}
// ...
}
~~~
在[Phalcon\\Mvc\\Model\\Criteria](http://docs.iphalcon.cn/api/Phalcon_Mvc_Model_Criteria.html)的幫助下, 我們基于從表單發送來的數據類型和值創建智能的搜索條件:
~~~
<?php
$query = Criteria::fromInput(
$this->di,
"Products",
$this->request->getPost()
);
~~~
這個方法驗證值 “” (空字符串) 和值 null 的區別并考慮到這些來創建搜索條件:
* 如果字段日期類型是text或者類似的(char, varchar, text, 等等) 它會使用一個SQL “like” 操作符來過濾結果.
* 如果日期類型不是text或者類似的, 它將會使用操作符”=”.
另外, “Criteria” 會忽略`$_POST`所有不與表中的任何字段相匹配的變量. 值會自動避免使用”參數綁定”.
現在, 我們在控制器的會話袋里存儲產品參數:
~~~
<?php
$this->persistent->searchParams = $query->getParams();
~~~
會話袋在控制器里面是一個特殊的屬性, 在使用 session 服務的不同請求之間依然存在. 當訪問的時候, 這個屬性會注入一個[Phalcon\\Session\\Bag](http://docs.iphalcon.cn/api/Phalcon_Session_Bag.html)實例, 對于每個控制器來說, 這是獨立的.
然后, 基于內置的參數我們執行查詢:
~~~
<?php
$products = Products::find($parameters);
if (count($products) === 0) {
$this->flash->notice(
"The search did not found any products"
);
return $this->dispatcher->forward(
[
"controller" => "products",
"action" => "index",
]
);
}
~~~
如果搜索不返回一些產品, 我們再一次轉發用戶到 index 方法. 讓我們模擬搜索返回結果, 然后我們創建一個分頁來輕松的瀏覽他們:
~~~
<?php
use Phalcon\Paginator\Adapter\Model as Paginator;
// ...
$paginator = new Paginator(
[
"data" => $products, // 分頁的數據
"limit" => 5, // 每頁的行數
"page" => $numberPage, // 查看的指定頁
]
);
// 獲取分頁中當前頁面
$page = $paginator->getPaginate();
~~~
最后我們通過返回的頁面來瀏覽:
~~~
<?php
$this->view->page = $page;
~~~
在視圖里面 (app/views/products/search.volt), 我們在當前頁面循環相應的結果, 在當前頁面給用戶展示每一行記錄:
~~~
{% for product in page.items %}
{% if loop.first %}
<table>
<thead>
<tr>
<th>Id</th>
<th>Product Type</th>
<th>Name</th>
<th>Price</th>
<th>Active</th>
</tr>
</thead>
<tbody>
{% endif %}
<tr>
<td>
{{ product.id }}
</td>
<td>
{{ product.getProductTypes().name }}
</td>
<td>
{{ product.name }}
</td>
<td>
{{ "%.2f"|format(product.price) }}
</td>
<td>
{{ product.getActiveDetail() }}
</td>
<td width="7%">
{{ link_to("products/edit/" ~ product.id, "Edit") }}
</td>
<td width="7%">
{{ link_to("products/delete/" ~ product.id, "Delete") }}
</td>
</tr>
{% if loop.last %}
</tbody>
<tbody>
<tr>
<td colspan="7">
<div>
{{ link_to("products/search", "First") }}
{{ link_to("products/search?page=" ~ page.before, "Previous") }}
{{ link_to("products/search?page=" ~ page.next, "Next") }}
{{ link_to("products/search?page=" ~ page.last, "Last") }}
<span class="help-inline">{{ page.current }} of {{ page.total_pages }}</span>
</div>
</td>
</tr>
</tbody>
</table>
{% endif %}
{% else %}
No products are recorded
{% endfor %}
~~~
在上面的例子中有很多東西值得詳細介紹. 首先, 當前頁面的記錄是使用 Volt 的 ‘for’ 循環出來的. Volt 對 PHP 的 ‘foreach’ 提供了一個簡單的語法.
~~~
{% for product in page.items %}
~~~
對于 PHP 來說也是一樣:
~~~
<?php foreach ($page->items as $product) { ?>
~~~
完整的 ‘for’ 提供了以下:
~~~
{% for product in page.items %}
{% if loop.first %}
Executed before the first product in the loop
{% endif %}
Executed for every product of page.items
{% if loop.last %}
Executed after the last product is loop
{% endif %}
{% else %}
Executed if page.items does not have any products
{% endfor %}
~~~
現在你可以返回到頁面找出每個塊都在做什么. 在”product”中的每個字段都有相應的輸出:
~~~
<tr>
<td>
{{ product.id }}
</td>
<td>
{{ product.productTypes.name }}
</td>
<td>
{{ product.name }}
</td>
<td>
{{ "%.2f"|format(product.price) }}
</td>
<td>
{{ product.getActiveDetail() }}
</td>
<td width="7%">
{{ link_to("products/edit/" ~ product.id, "Edit") }}
</td>
<td width="7%">
{{ link_to("products/delete/" ~ product.id, "Delete") }}
</td>
</tr>
~~~
正如我們看到的, 在之前使用`product.id`和在PHP中使用`$product->id`是等價的, we made the same with`product.name`and so on. 其它字段都表現的有些不同, 例如, 讓我們看下`product.productTypes.name`. 要理解這部分, 我們必須看一下 Products 模型 (app/models/Products.php):
~~~
<?php
use Phalcon\Mvc\Model;
/**
* 產品
*/
class Products extends Model
{
// ...
/**
* 產品初始化
*/
public function initialize()
{
$this->belongsTo(
"product_types_id",
"ProductTypes",
"id",
[
"reusable" => true,
]
);
}
// ...
}
~~~
一個模型有一個名為`initialize()`的方法, 這個方法在每次請求的時候調用一次兵器它服務ORM去初始化一個模型. 在這種情況話, “Products” 通過定義這個模型跟另外一個叫做 “ProductTypes” 的模型有一對多的關系從而初始化.
~~~
<?php
$this->belongsTo(
"product_types_id",
"ProductTypes",
"id",
[
"reusable" => true,
]
);
~~~
它的意思是, “Products” 的本地屬性”product\_types\_id” 跟 “ProductTypes” 模型里面的屬性 “id” 是一對多的關系. 通過定義這個關系我們可以通過如下方法來訪問產品類型的名字:
~~~
<td>{{ product.productTypes.name }}</td>
~~~
字段 “price” 使用一個 Volt 過濾器來格式化輸出:
~~~
<td>{{ "%.2f"|format(product.price) }}</td>
~~~
在原生PHP中, 它將是這樣的:
~~~
<?php echo sprintf("%.2f", $product->price) ?>
~~~
使用模型中已經實現的幫助者函數來輸出產品是否是有效的:
~~~
<td>{{ product.getActiveDetail() }}</td>
~~~
這個方法在模型中定義了.
## 創建和更新記錄(Creating and Updating Records)
現在, 讓我們看看 CRUD 如何創建和更新記錄. 從 “new” 和 “edit” 視圖, 通過用戶輸入的數據發送 “create” 和 “save” 方法從而分別執行 “creating” 和 “updating” 產品的方法.
在創建的情況下, 我們提取提交的數據然后分配它們到一個新的 “Products” 實例:
~~~
<?php
/**
* 基于在 "new" 方法中輸入的數據創建一個產品
*/
public function createAction()
{
if (!$this->request->isPost()) {
return $this->dispatcher->forward(
[
"controller" => "products",
"action" => "index",
]
);
}
$form = new ProductsForm();
$product = new Products();
$product->id = $this->request->getPost("id", "int");
$product->product_types_id = $this->request->getPost("product_types_id", "int");
$product->name = $this->request->getPost("name", "striptags");
$product->price = $this->request->getPost("price", "double");
$product->active = $this->request->getPost("active");
// ...
}
~~~
還記得我們在產品表單中定義的過濾器嗎? 數據在開始分配到`$product`對象前進行過濾. 這個過濾器是可選的; ORM同樣也會轉義輸入的數據和根據列類型執行附加的轉換:
~~~
<?php
// ...
$name = new Text("name");
$name->setLabel("Name");
// 過濾 name
$name->setFilters(
[
"striptags",
"string",
]
);
// 驗證 name
$name->addValidators(
[
new PresenceOf(
[
"message" => "Name is required",
]
)
]
);
$this->add($name);
~~~
當保存的時候, 我們就會知道 ProductsForm (app/forms/ProductsForm.php) 表單提交的數據是否否則業務規則和實現的驗證:
~~~
<?php
// ...
$form = new ProductsForm();
$product = new Products();
// V驗證輸入
$data = $this->request->getPost();
if (!$form->isValid($data, $product)) {
$messages = $form->getMessages();
foreach ($messages as $message) {
$this->flash->error($message);
}
return $this->dispatcher->forward(
[
"controller" => "products",
"action" => "new",
]
);
}
~~~
最后, 如果表單沒有返回任何驗證消息, 我們就可以保存產品實例了:
~~~
<?php
// ...
if ($product->save() === false) {
$messages = $product->getMessages();
foreach ($messages as $message) {
$this->flash->error($message);
}
return $this->dispatcher->forward(
[
"controller" => "products",
"action" => "new",
]
);
}
$form->clear();
$this->flash->success(
"Product was created successfully"
);
return $this->dispatcher->forward(
[
"controller" => "products",
"action" => "index",
]
);
~~~
現在, 在更新產品的情況下, 我們必須先將當前編輯的記錄展示給用戶:
~~~
<?php
/**
* 基于它的id編輯一個產品
*/
public function editAction($id)
{
if (!$this->request->isPost()) {
$product = Products::findFirstById($id);
if (!$product) {
$this->flash->error(
"Product was not found"
);
return $this->dispatcher->forward(
[
"controller" => "products",
"action" => "index",
]
);
}
$this->view->form = new ProductsForm(
$product,
[
"edit" => true,
]
);
}
}
~~~
通過將模型作為第一個參數傳遞過去找出被綁定到表單的數據. 多虧了這個, 用戶可以改變任何值, 然后通過 “save” 方法發送它到數據庫:
~~~
<?php
/**
* 在 "edit"方法中基于輸入的數據更新一個產品
*/
public function saveAction()
{
if (!$this->request->isPost()) {
return $this->dispatcher->forward(
[
"controller" => "products",
"action" => "index",
]
);
}
$id = $this->request->getPost("id", "int");
$product = Products::findFirstById($id);
if (!$product) {
$this->flash->error(
"Product does not exist"
);
return $this->dispatcher->forward(
[
"controller" => "products",
"action" => "index",
]
);
}
$form = new ProductsForm();
$data = $this->request->getPost();
if (!$form->isValid($data, $product)) {
$messages = $form->getMessages();
foreach ($messages as $message) {
$this->flash->error($message);
}
return $this->dispatcher->forward(
[
"controller" => "products",
"action" => "new",
]
);
}
if ($product->save() === false) {
$messages = $product->getMessages();
foreach ($messages as $message) {
$this->flash->error($message);
}
return $this->dispatcher->forward(
[
"controller" => "products",
"action" => "new",
]
);
}
$form->clear();
$this->flash->success(
"Product was updated successfully"
);
return $this->dispatcher->forward(
[
"controller" => "products",
"action" => "index",
]
);
}
~~~
我們已經看到 Phalcon 如何以一種結構化的方式讓你創建表單和從一個數據庫中綁定數據. 在下一章, 我們將會看到如何添加自定義 HTML 元素, 比如一個菜單.
- 簡介
- 安裝
- 安裝(installlation)
- XAMPP下的安裝
- WAMP下安裝
- Nginx安裝說明
- Apache安裝說明
- Cherokee 安裝說明
- 使用 PHP 內置 web 服務器
- Phalcon 開發工具
- Linux 系統下使用 Phalcon 開發工具
- Mac OS X 系統下使用 Phalcon 開發工具
- Windows 系統下使用 Phalcon 開發工具
- 教程
- 教程 1:讓我們通過例子來學習
- 教程 2:INVO簡介
- 教程 3: 保護INVO
- 教程4: 使用CRUD
- 教程5: 定制INVO
- 教程 6: V?kuró
- 教程 7:創建簡單的 REST API
- 組件
- 依賴注入與服務定位器
- MVC架構
- 使用控制器
- 使用模型
- 模型關系
- 事件與事件管理器
- Behaviors
- 模型元數據
- 事務管理
- 驗證數據完整性
- Workingwith Models
- Phalcon查詢語言
- 緩存對象關系映射
- 對象文檔映射 ODM
- 使用視圖
- 視圖助手
- 資源文件管理
- Volt 模版引擎
- MVC 應用
- 路由
- 調度控制器
- Micro Applications
- 使用命名空間
- 事件管理器
- Request Environmen
- 返回響應
- Cookie 管理
- 生成 URL 和 路徑
- 閃存消息
- 使用 Session 存儲數據
- 過濾與清理
- 上下文編碼
- 驗證Validation
- 表單_Forms
- 讀取配置
- 分頁 Pagination
- 使用緩存提高性能
- 安全
- 加密與解密 Encryption/Decryption
- 訪問控制列表
- 多語言支持
- 類加載器 Class Autoloader
- 日志記錄_Logging
- 注釋解析器 Annotations Parser
- 命令行應用 Command Line Applications
- Images
- 隊列 Queueing
- 數據庫抽象層
- 國際化
- 數據庫遷移
- 調試應用程序
- 單元測試
- 進階技巧與延伸閱讀
- 提高性能:下一步該做什么?
- Dependency Injection Explained
- Understanding How Phalcon Applications Work
- Api
- Abstract class Phalcon\Acl