6. 模型基础与数据库操作

6.模型基础与数据库操作

在 Phalcon 开发中,模型是 MVC 架构中的核心组件,负责处理与数据库的交互。这一章我们将深入探讨如何创建数据模型、执行 CRUD 操作以及利用模型事件来处理业务逻辑。

创建数据模型

Phalcon 的模型本质上是一个继承自 Phalcon\Mvc\Model 的类,它充当业务对象和数据库表之间的桥梁。这种对象关系映射(ORM)实现让开发者能够以面向对象的方式操作数据库,同时保持高性能。

创建一个基础模型非常简单,只需创建一个继承自 Phalcon\Mvc\Model 的类:

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{

}

默认情况下,模型类名 Invoices 会映射到数据库中的 invoices 表。如果需要自定义表名,可以在 initialize() 方法中使用 setSource() 方法:

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
    public function initialize()
    {
        $this->setSource('co_invoices');
    }
}

initialize() 方法在请求期间只被调用一次,用于设置模型的初始行为。如果需要为每个实例执行初始化任务,可以使用 onConstruct() 方法:

public function onConstruct()
{
    // 实例初始化逻辑
}

属性定义方式

模型属性有两种常见定义方式:公共属性和 getter/setter 方法。

公共属性方式简单直接,适合快速开发:

class Invoices extends Model
{
    public $inv_id;
    public $inv_cst_id;
    public $inv_status_flag;
    public $inv_title;
    public $inv_total;
    public $inv_created_at;
}

getter/setter 方式提供更好的封装性和数据验证能力:

class Invoices extends Model
{
    protected $inv_id;
    protected $inv_cst_id;
    protected $inv_status_flag;
    protected $inv_title;
    protected $inv_total;
    protected $inv_created_at;

    public function getId(): int
    {
        return (int) $this->inv_id;
    }

    public function setTotal(float $total): Invoices
    {
        if ($total < 0) {
            throw new InvalidArgumentException('金额不能为负数');
        }

        $this->inv_total = $total;
        return $this;
    }
    // 其他getter和setter...
}

使用 getter/setter 方式时,需要注意属性命名约定:如果数据库字段使用下划线命名(如 inv_total),对应的 getter/setter 方法应使用驼峰命名(如 getInvTotal())。

记录查询方法

Phalcon 提供了多种灵活的方式来查询数据库记录,让数据检索变得简单直观。

基础查询方法

最常用的查询方法是 find()findFirst()find() 返回符合条件的结果集,而 findFirst() 返回第一个匹配的记录:

// 获取所有记录
$invoices = Invoices::find();

// 获取第一条记录
$firstInvoice = Invoices::findFirst();

// 通过主键获取记录
$invoice = Invoices::findFirst(15);

条件查询

可以通过传递条件参数来过滤查询结果。最灵活的方式是使用数组参数:

$invoices = Invoices::find([
    'conditions' => 'inv_cst_id = :cst_id:',
    'bind'       => [
        'cst_id' => 3,
    ],
    'order'      => 'inv_total DESC',
    'limit'      => 10,
]);

上述代码查询客户 ID 为 3 的前 10 条发票记录,并按总金额降序排列。

参数绑定

为避免 SQL 注入并提高查询安全性,Phalcon 支持参数绑定。可以使用命名占位符或问号占位符:

// 命名占位符
$invoices = Invoices::find([
    'conditions' => 'inv_title LIKE :title: AND inv_total > :total:',
    'bind'       => [
        'title' => '%ACME%',
        'total' => 1000,
    ],
]);

// 问号占位符
$invoices = Invoices::find([
    'conditions' => 'inv_title LIKE ?0 AND inv_total > ?1',
    'bind'       => ['%ACME%', 1000],
]);

还可以指定参数类型以提高安全性:

use Phalcon\Db\Column;

$invoices = Invoices::find([
    'conditions' => 'inv_id = :inv_id:',
    'bind'       => ['inv_id' => 3],
    'bindTypes'  => [Column::BIND_PARAM_INT],
]);

快捷查询方法

Phalcon 提供了 findBy*findFirstBy* 魔术方法,让简单查询更加简洁:

// 查找所有金额为100的发票
$invoices = Invoices::findByInvTotal(100);

// 查找客户ID为3的第一张发票
$invoice = Invoices::findFirstByInvCstId(3);

这些方法会自动处理参数绑定,提高了代码安全性和开发效率。

高级查询构建

对于复杂查询,可以使用查询构建器以面向对象的方式构建查询:

$invoices = Invoices::query()
    ->where('inv_cst_id = :cst_id:')
    ->andWhere('inv_total > :total:')
    ->bind(['cst_id' => 3, 'total' => 1000])
    ->orderBy('inv_status_flag, inv_total DESC')
    ->limit(10)
    ->execute();

这种方式提供了更好的代码可读性和 IDE 自动完成支持。

结果集处理

find() 方法返回的结果集是 Phalcon\Mvc\Model\Resultset 对象,它提供了多种遍历和操作结果的方法:

$invoices = Invoices::find();

// 遍历结果集
foreach ($invoices as $invoice) {
    echo $invoice->inv_title . PHP_EOL;
}

// 获取结果数量
echo 'Total invoices: ' . count($invoices);

// 获取第一条和最后一条记录
$first = $invoices->getFirst();
$last = $invoices->getLast();

// 结果集过滤
$paidInvoices = $invoices->filter(function ($invoice) {
    return $invoice->inv_status_flag == 1;
});

结果集采用延迟加载机制,只有在需要时才会从数据库加载记录,这有助于提高性能和内存使用效率。

数据创建与更新

Phalcon 模型提供了简单直观的方法来创建和更新数据库记录。

创建记录

要创建新记录,只需实例化模型类,设置属性值,然后调用 save() 方法:

$invoice = new Invoices();

$invoice->inv_cst_id      = 1;
$invoice->inv_status_flag = 1;
$invoice->inv_title       = 'ACME公司发票';
$invoice->inv_total       = 100;
$invoice->inv_created_at  = date('Y-m-d H:i:s');

$result = $invoice->save();

if ($result === false) {
    echo '保存失败: ';
    $messages = $invoice->getMessages();
    foreach ($messages as $message) {
        echo $message . PHP_EOL;
    }
} else {
    echo '记录保存成功';
}

save() 方法会根据模型是否有主键值来决定执行插入还是更新操作。如果确定是创建新记录,也可以使用 create() 方法:

$result = $invoice->create();

批量赋值

使用 assign() 方法可以批量设置属性值,这在处理表单提交等场景时特别有用:

$invoice = new Invoices();

$invoice->assign([
    'inv_cst_id'      => 1,
    'inv_status_flag' => 1,
    'inv_title'       => 'ACME公司发票',
    'inv_total'       => 100,
    'inv_created_at'  => date('Y-m-d H:i:s'),
]);

$result = $invoice->save();

为了安全起见,可以指定允许赋值的字段白名单,防止批量赋值漏洞:

$invoice->assign(
    $_POST,
    [
        'inv_cst_id',
        'inv_status_flag',
        'inv_title',
        'inv_total',
    ]
);

更新记录

更新记录的过程与创建类似,先查询到记录,修改属性值,然后调用 save() 方法:

$invoice = Invoices::findFirst(15);

if ($invoice) {
    $invoice->inv_total = 120; // 更新金额
    $result = $invoice->save();

    if ($result === false) {
        // 处理错误
    } else {
        echo '记录更新成功';
    }
}

也可以使用 update() 方法明确执行更新操作,如果记录不存在会抛出异常:

$result = $invoice->update();

动态更新

默认情况下,Phalcon 会更新模型的所有字段,即使某些字段没有变化。启用动态更新可以只更新修改过的字段,提高性能:

class Invoices extends Model
{
    public function initialize()
    {
        $this->useDynamicUpdate(true);
    }
}

启用后,只有被修改的字段才会出现在 SQL 更新语句中。

数据删除操作

删除记录同样简单,只需获取记录对象并调用 delete() 方法:

$invoice = Invoices::findFirst(15);

if ($invoice) {
    $result = $invoice->delete();

    if ($result === false) {
        echo '删除失败: ';
        $messages = $invoice->getMessages();
        foreach ($messages as $message) {
            echo $message . PHP_EOL;
        }
    } else {
        echo '记录删除成功';
    }
}

要删除多条记录,可以遍历结果集并逐个删除:

$invoices = Invoices::find([
    'conditions' => 'inv_cst_id = :id:',
    'bind'       => ['id' => 3]
]);

foreach ($invoices as $invoice) {
    $invoice->delete();
}

对于大量记录的删除,考虑使用事务来确保数据一致性和性能:

use Phalcon\Mvc\Model\Transaction\Manager;
use Phalcon\Mvc\Model\Transaction\Failed;

try {
    $txManager = new Manager();
    $transaction = $txManager->get();

    $invoices = Invoices::find([
        'conditions' => 'inv_cst_id = :id:',
        'bind'       => ['id' => 3]
    ]);

    foreach ($invoices as $invoice) {
        $invoice->setTransaction($transaction);
        if (!$invoice->delete()) {
            $transaction->rollback('删除失败: ' . implode(', ', $invoice->getMessages()));
        }
    }

    $transaction->commit();
    echo '所有记录删除成功';
} catch (Failed $e) {
    echo '事务失败: ' . $e->getMessage();
}

模型事件

Phalcon 模型提供了丰富的事件系统,允许在模型生命周期的不同阶段执行自定义逻辑。这些事件可以通过在模型类中定义特定方法来实现。

常用事件方法

以下是一些常用的模型事件方法:

  • beforeCreate(): 在创建新记录前触发
  • afterCreate(): 在创建新记录后触发
  • beforeUpdate(): 在更新记录前触发
  • afterUpdate(): 在更新记录后触发
  • beforeSave(): 在保存(创建或更新)记录前触发
  • afterSave(): 在保存(创建或更新)记录后触发
  • beforeDelete(): 在删除记录前触发
  • afterDelete(): 在删除记录后触发
  • afterFetch(): 在从数据库获取记录后触发

事件使用示例

下面是一个使用事件的例子,自动设置创建时间和更新时间:

class Invoices extends Model
{
    public function beforeCreate()
    {
        $this->inv_created_at = date('Y-m-d H:i:s');
    }

    public function beforeUpdate()
    {
        $this->inv_updated_at = date('Y-m-d H:i:s');
    }
}

另一个例子是在保存前验证数据:

public function beforeSave()
{
    if ($this->inv_total <= 0) {
        $this->appendMessage(new Message('发票金额必须大于零'));
        return false; // 阻止保存操作
    }
}

事件执行流程

模型事件的执行顺序如下:

  1. 当调用 save() 时,首先触发 beforeValidation()
  2. 如果通过验证,触发 beforeValidationOnCreate()beforeValidationOnUpdate()
  3. 如果验证通过,触发 afterValidation()afterValidationOnCreate()/afterValidationOnUpdate()
  4. 然后触发 beforeSave()beforeCreate()/beforeUpdate()
  5. 执行数据库操作
  6. 最后触发 afterCreate()/afterUpdate()afterSave()

如果在任何验证事件中返回 false 或添加错误消息,将阻止后续操作执行。

使用行为扩展事件

Phalcon 提供了行为(Behaviors)来封装可重用的事件逻辑。例如,Timestampable 行为可以自动管理时间戳字段:

use Phalcon\Mvc\Model\Behavior\Timestampable;

class Invoices extends Model
{
    public function initialize()
    {
        $this->addBehavior(
            new Timestampable([
                'beforeCreate' => [
                    'field'  => 'inv_created_at',
                    'format' => 'Y-m-d H:i:s',
                ],
                'beforeUpdate' => [
                    'field'  => 'inv_updated_at',
                    'format' => 'Y-m-d H:i:s',
                ]
            ])
        );
    }
}

这样配置后,inv_created_at 字段会在创建时自动设置,inv_updated_at 字段会在更新时自动更新,无需手动编写事件方法。

总结

本章详细介绍了 Phalcon 模型的基础用法和数据库操作技巧。我们学习了如何创建模型类、定义属性,以及使用各种查询方法获取数据。对于数据操作,我们探讨了创建、更新和删除记录的方法,以及如何使用事务确保数据一致性。

模型事件系统是 Phalcon 的强大特性之一,它允许我们在模型生命周期的不同阶段注入自定义逻辑,实现数据验证、自动字段填充等功能。通过行为(Behaviors),我们可以将这些逻辑封装为可重用的组件,提高代码的可维护性。

掌握这些模型操作技巧将为构建复杂的数据库驱动应用程序打下坚实基础。在下一章中,我们将深入探讨模型关系、事务管理和高级查询等高级特性。