內容目录

上一个主题

< 教程 3: 保护INVO(Tutorial 3: Securing INVO)

下一个主题

教程5: 定制INVO(Tutorial 5: Customizing INVO) >

教程4: 使用CRUD(Tutorial 4: Working with the CRUD)

后台通常提供表单来允许用户提交数据. 继续对INVO的解释, 我们现在处理CRUD的创建, 一个非常常见的操作任务, Phalcon将会帮助你使用表单, 校验, 分页和更多.

在INVO(公司, 产品和产品类型)中大部分选项操作数据都是使用一个基础的常见的 CRUD (创建, 读取, 更新和删除)开发的. 每个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 组件提供的元素. 每个元素都遵循近乎相同的结构:

<?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”操作是基于用户输入的数据执行搜索的.

创建和更新记录(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 元素, 比如一个菜单.