使用 CodeIgniter 的模型

模型

CodeIgniter 的模型提供了便捷功能和额外特性,使得在数据库中操作 单张表 更加方便。

它内置了常用数据库表交互的辅助函数,包括查找记录、更新记录、删除记录等标准操作。

访问模型

模型通常存储在 app/Models 目录中,其命名空间应与目录位置匹配,例如 namespace App\Models

可以通过创建新实例或使用 model() 辅助函数在类中访问模型:

<?php

// Create a new class manually.
$userModel = new \App\Models\UserModel();

// Create a shared instance of the model.
$userModel = model('UserModel');
// or
$userModel = model('App\Models\UserModel');
// or
$userModel = model(\App\Models\UserModel::class);

// Create a new class with the model() function.
$userModel = model('UserModel', false);

// Create shared instance with a supplied database connection.
$db        = db_connect('custom');
$userModel = model('UserModel', true, $db);

model() 内部使用 Factories::models()。有关第一个参数的详细信息,请参阅 加载类

CodeIgniter 的模型

CodeIgniter 提供的模型类包含以下特性:

该类为构建自定义模型提供了坚实基础,可快速构建应用程序的模型层。

创建模型

要使用 CodeIgniter 的模型,只需新建继承自 CodeIgniter\Model 的模型类:

<?php

namespace App\Models;

use CodeIgniter\Model;

class UserModel extends Model
{
    // ...
}

这个空类提供了对数据库连接、查询构建器和多个便捷方法的访问。

initialize()

如需在模型中进行额外设置,可扩展 initialize() 方法。该方法会在模型构造函数之后立即执行,避免重复构造函数参数,例如扩展其他模型:

<?php

namespace App\Models;

use Modules\Authentication\Models\UserAuthModel;

class UserModel extends UserAuthModel
{
    // ...

    /**
     * Called during initialization. Appends
     * our custom field to the module's model.
     */
    protected function initialize()
    {
        $this->allowedFields[] = 'middlename';
    }
}

连接数据库

当类首次实例化时,如果没有数据库连接实例传递给构造函数,且未在模型类中设置 $DBGroup 属性,模型会自动连接到数据库配置中设置的默认组。

通过添加 $DBGroup 属性可修改每个模型使用的数据库组,确保模型中所有 $this->db 引用都通过正确的连接进行:

<?php

namespace App\Models;

use CodeIgniter\Model;

class UserModel extends Model
{
    protected $DBGroup = 'group_name';

    // ...
}

将 “group_name” 替换为数据库配置文件中定义的数据库组名称。

配置模型

模型类提供以下配置选项,使类方法能无缝工作:

<?php

namespace App\Models;

use CodeIgniter\Model;

class UserModel extends Model
{
    protected $table      = 'users';
    protected $primaryKey = 'id';

    protected $useAutoIncrement = true;

    protected $returnType     = 'array';
    protected $useSoftDeletes = true;

    protected $allowedFields = ['name', 'email'];

    protected bool $allowEmptyInserts = false;
    protected bool $updateOnlyChanged = true;

    // Dates
    protected $useTimestamps = false;
    protected $dateFormat    = 'datetime';
    protected $createdField  = 'created_at';
    protected $updatedField  = 'updated_at';
    protected $deletedField  = 'deleted_at';

    // Validation
    protected $validationRules      = [];
    protected $validationMessages   = [];
    protected $skipValidation       = false;
    protected $cleanValidationRules = true;

    // Callbacks
    protected $allowCallbacks = true;
    protected $beforeInsert   = [];
    protected $afterInsert    = [];
    protected $beforeUpdate   = [];
    protected $afterUpdate    = [];
    protected $beforeFind     = [];
    protected $afterFind      = [];
    protected $beforeDelete   = [];
    protected $afterDelete    = [];
}

$table

指定模型主要操作的数据表。仅适用于内置 CRUD 方法,自定义查询不受限制。

$primaryKey

指定表中唯一标识记录的列名。不必与数据库主键完全匹配,但需与 find() 等方法使用的匹配列一致。

备注

所有模型必须指定 primaryKey 以确保功能正常。

$useAutoIncrement

指定表是否对 $primaryKey 使用自增特性。设为 false 时需手动提供主键值,适用于 1:1 关系或 UUID 场景。默认值为 true

备注

$useAutoIncrement 设为 false 时,请确保数据库主键设为 unique,以保证模型功能正常。

$returnType

模型的 find*() 方法将自动返回结果数据,而非 Result 对象。

此设置允许你定义返回的数据类型,有效值为 ‘array’(默认)、’object’ 或可与 Result 对象的 getCustomResultObject() 方法配合使用的 类的完全限定名称

使用类的特殊 ::class 常量,可使大多数 IDE 实现自动补全,并支持重构等功能以更好地理解你的代码。

$useSoftDeletes

若设为 true,任何 delete() 方法调用都会在数据库中设置 deleted_at 字段值,而非实际删除行。这可以保留可能被其他位置引用的数据,维护可恢复对象的「回收站」,或简单地将其作为安全审计轨迹的一部分。当启用时,find*() 方法默认仅返回未删除行,除非在调用 find*() 方法前先调用 withDeleted() 方法。

根据模型的 $dateFormat 设置,数据库需要包含 DATETIME 或 INTEGER 类型的字段。默认字段名为 deleted_at,但可通过 $deletedField 属性配置为任意名称。

重要

数据库中的 deleted_at 字段必须可为空。

$allowedFields

定义可通过 save()insert()update() 方法设置的字段名列表,防止大规模赋值漏洞。

备注

$primaryKey 字段不应包含在允许字段中。

$allowEmptyInserts

在 4.3.0 版本加入.

是否允许插入空数据。默认值是 false,这意味着如果你尝试插入空数据,将会抛出带有 “There is no data to insert.” 信息的 DataException

你也可以通过 allowEmptyInserts() 方法来改变这个设置。

$updateOnlyChanged

在 4.5.0 版本加入.

是否仅更新 Entity 的已更改字段。默认值是 true,这意味着在更新到数据库时仅使用已更改的字段数据。因此,如果你尝试更新一个没有更改的 Entity,将会抛出带有 “There is no data to update.” 信息的 DataException

将此属性设置为 false 将确保 Entity 的所有允许字段在任何时候都提交到数据库并进行更新。

$casts

在 4.5.0 版本加入.

该功能允许你将从数据库检索的数据转换为适当的 PHP 类型。此选项应为数组格式,其中键名对应字段名称,键值对应数据类型。详细信息请参阅 模型字段类型转换

日期配置

$useTimestamps

这个布尔值决定了是否自动将当前日期添加到所有插入和更新操作中。如果为 true,将按照 $dateFormat 指定的格式设置当前时间。这要求表中存在适当数据类型的 created_atupdated_atdeleted_at 列。另请参阅 $createdField$updatedField$deletedField

$dateFormat

该值配合 $useTimestamps$useSoftDeletes 使用,确保正确类型的日期值被插入数据库。默认情况下会生成 DATETIME 值,但有效选项包括:'datetime''date''int' (UNIX 时间戳)。如果与无效或缺失的 $dateFormat 一起使用 $useSoftDeletes$useTimestamps 将会引发异常。

$createdField

指定用于记录数据创建时间戳的数据库字段。设置为空字符串 ('') 可避免更新该字段(即使启用了 $useTimestamps)。

$updatedField

指定用于记录数据更新时间戳的数据库字段。设置为空字符串 ('') 可避免更新该字段(即使启用了 $useTimestamps)。

$deletedField

指定用于软删除操作的数据库字段。详见 $useSoftDeletes

验证

$validationRules

包含一个验证规则数组(如 如何保存规则 所述)或一个验证组名称的字符串(如相同章节所述)。另请参阅 设置验证规则

$validationMessages

包含一个自定义错误消息数组,用于验证过程中(如 设置自定义错误消息 所述)。另请参阅 设置验证规则

$skipValidation

是否在所有 插入更新 操作中跳过验证。默认值为 false,表示始终尝试验证数据。这主要由 skipValidation() 方法使用,但可更改为 true 以使模型永不验证。

$cleanValidationRules

是否移除传入数据中不存在的验证规则。这用于 更新 操作。默认值为 true,表示在验证前会(临时)移除传入数据中不存在字段的验证规则,以避免在仅更新部分字段时出现验证错误。

也可以通过 cleanRules() 方法更改此值。

备注

在 v4.2.7 之前,由于存在 bug,$cleanValidationRules 无法正常工作。

回调

$allowCallbacks

是否使用下面定义的回调。详见 模型事件

$beforeInsert
$afterInsert
$beforeUpdate
$afterUpdate
$beforeFind
$afterFind
$beforeDelete
$afterDelete
$beforeInsertBatch
$afterInsertBatch
$beforeUpdateBatch
$afterUpdateBatch

这些数组允许你指定在属性名指定时间点运行的回调方法。详见 模型事件

模型字段类型转换

在 4.5.0 版本加入.

从数据库检索数据时,整数类型的数据可能在 PHP 中被转换为字符串类型。你可能希望将日期/时间数据转换为 PHP 的 Time 对象。

模型字段类型转换允许你将从数据库检索的数据转换为适当的 PHP 类型。

重要

如果将此功能与 实体 一起使用,请勿同时使用 实体属性类型转换。同时使用两种类型转换将无法正常工作。

实体属性类型转换作用于 (1)(4),而此类型转换作用于 (2)(3):

[应用代码] --- (1) --> [实体] --- (2) --> [数据库]
[应用代码] <-- (4) --- [实体] <-- (3) --- [数据库]

使用此类型转换时,实体将在属性中持有正确类型的 PHP 值。此行为与之前的行为完全不同。不要期望属性持有数据库的原始数据。

定义数据类型

$casts 属性设置其定义。此选项应为数组,其中键是字段名称,值是数据类型:

<?php

namespace App\Models;

use CodeIgniter\Model;

class UserModel extends Model
{
    // ...
    protected array $casts = [
        'id'        => 'int',
        'birthdate' => '?datetime',
        'hobbies'   => 'json-array',
        'active'    => 'int-bool',
    ];
    // ...
}

数据类型

默认提供以下类型。在类型前添加问号可将字段标记为可空,例如 ?int?datetime

类型

PHP 类型

数据库字段类型

int

int

int 类型

float

float

float(数值)类型

bool

bool

bool/int/string 类型

int-bool

bool

int 类型(1 或 0)

array

array

string 类型(序列化)

csv

array

string 类型(CSV)

json

stdClass

json/string 类型

json-array

array

json/string 类型

datetime

Time

datetime 类型

timestamp

Time

int 类型(UNIX 时间戳)

uri

URI

string 类型

csv

使用 csv 类型转换时,使用 PHP 内置的 implode()explode() 函数,并假定所有值都是字符串安全且不含逗号。对于更复杂的数据转换,请尝试 arrayjson

datetime

你可以传递类似 datetime[ms] 的参数表示带毫秒的日期/时间,或 datetime[us] 表示带微秒的日期/时间。

日期时间格式在 数据库配置dateFormat 数组中设置,位于 app/Config/Database.php 文件。

备注

当使用 msus 作为参数时,模型 会处理 Time 的秒的小数部分。但 查询构建器 不会。因此在将 Time 传递给查询构建器的方法(如 where())时,仍需使用 format() 方法:

$model = model('SomeModel');

$now = \CodeIgniter\I18n\Time::now();

// The following code passes the microseconds to Query Builder.
$model->where('my_dt_field', $now->format('Y-m-d H:i:s.u'))->findAll();
// Generates: SELECT * FROM `my_table` WHERE `my_dt_field` = '2024-07-28 18:57:58.900326'

// But the following code loses the microseconds.
$model->where('my_dt_field', $now)->findAll();
// Generates: SELECT * FROM `my_table` WHERE `my_dt_field` = '2024-07-28 18:57:58'

备注

在 v4.6.0 之前,由于存在 bug,无法使用 msus 作为参数,因为 Time 的秒的小数部分会丢失。

timestamp

创建的 Time 实例的时区将是默认时区(应用的时区),而非 UTC。

自定义类型转换

你可以定义自己的转换类型。

创建自定义处理器

首先需要为你的类型创建一个处理器类。假设类位于 app/Models/Cast 目录:

<?php

namespace App\Models\Cast;

use CodeIgniter\DataCaster\Cast\BaseCast;
use InvalidArgumentException;

// The class must inherit the CodeIgniter\DataCaster\Cast\BaseCast class
class CastBase64 extends BaseCast
{
    public static function get(
        mixed $value,
        array $params = [],
        ?object $helper = null,
    ): string {
        if (! is_string($value)) {
            self::invalidTypeValueError($value);
        }

        $decoded = base64_decode($value, true);

        if ($decoded === false) {
            throw new InvalidArgumentException('Cannot decode: ' . $value);
        }

        return $decoded;
    }

    public static function set(
        mixed $value,
        array $params = [],
        ?object $helper = null,
    ): string {
        if (! is_string($value)) {
            self::invalidTypeValueError($value);
        }

        return base64_encode($value);
    }
}

如果不需要在获取或设置值时更改值,只需不实现相应方法:

<?php

namespace App\Models\Cast;

use CodeIgniter\DataCaster\Cast\BaseCast;
use InvalidArgumentException;

class CastBase64 extends BaseCast
{
    public static function get(
        mixed $value,
        array $params = [],
        ?object $helper = null,
    ): string {
        if (! is_string($value)) {
            self::invalidTypeValueError($value);
        }

        $decoded = base64_decode($value, true);

        if ($decoded === false) {
            throw new InvalidArgumentException('Cannot decode: ' . $value);
        }

        return $decoded;
    }
}

注册自定义处理器

现在需要注册它:

<?php

namespace App\Models;

use App\Models\Cast\CastBase64;
use CodeIgniter\Model;

class MyModel extends Model
{
    // ...

    // Specify the type for the field
    protected array $casts = [
        'column1' => 'base64',
    ];

    // Bind the type to the handler
    protected array $castHandlers = [
        'base64' => CastBase64::class,
    ];

    // ...
}

参数

在某些情况下,单一类型可能不够。此时可以使用附加参数。附加参数用方括号表示,并用逗号分隔,例如 type[param1, param2]

<?php

namespace App\Models;

use App\Models\Cast\SomeHandler;
use CodeIgniter\Model;

class MyModel extends Model
{
    // ...

    // Define a type with parameters
    protected array $casts = [
        'column1' => 'class[App\SomeClass, param2, param3]',
    ];

    // Bind the type to the handler
    protected array $castHandlers = [
        'class' => SomeHandler::class,
    ];

    // ...
}
<?php

namespace App\Models\Cast;

use CodeIgniter\DataCaster\Cast\BaseCast;

class SomeHandler extends BaseCast
{
    public static function get(
        mixed $value,
        array $params = [],
        ?object $helper = null,
    ): mixed {
        var_dump($params);
        /*
         * Output:
         * array(3) {
         *   [0]=>
         *   string(13) "App\SomeClass"
         *   [1]=>
         *   string(6) "param2"
         *   [2]=>
         *   string(6) "param3"
         * }
         */
    }
}

备注

如果类型标记为可空(如 ?bool)且传递的值不为 null,则会将带有 nullable 值的参数传递给类型转换处理器。如果类型转换已有预定义参数,则 nullable 将添加到列表末尾。

处理数据

查找数据

提供了多个函数用于对表执行基本的 CRUD 操作,包括 find()insert()update()delete() 等。

find()

返回主键与第一个参数匹配的单行数据:

<?php

$user = $userModel->find($userId);

返回值格式由 $returnType 指定。

通过传递主键值数组(而非单个值)可返回多行数据:

<?php

$users = $userModel->find([1, 2, 3]);

备注

如果不传递参数,find() 将返回模型表中的所有行,实际上等同于 findAll(),但不够明确。

findColumn()

返回 null 或列值的索引数组:

<?php

$user = $userModel->findColumn($columnName);

$column_name 应为单个字段名,否则将抛出 DataException

findAll()

返回所有结果:

<?php

$users = $userModel->findAll();

可在调用此方法前插入查询构建器命令来修改查询:

<?php

$users = $userModel->where('active', 1)->findAll();

可分别传递限制和偏移值作为第一和第二个参数:

<?php

$users = $userModel->findAll($limit, $offset);

first()

返回结果集中的第一行。最好与查询构建器结合使用。

<?php

$user = $userModel->where('deleted', 0)->first();

withDeleted()

如果 $useSoftDeletes 为 true,则 find*() 方法不会返回 deleted_at IS NOT NULL 的行。要临时覆盖此行为,可在调用 find*() 方法前使用 withDeleted() 方法。

<?php

// Only gets non-deleted rows (deleted = 0)
$activeUsers = $userModel->findAll();

// Gets all rows
$allUsers = $userModel->withDeleted()->findAll();

onlyDeleted()

withDeleted() 会返回已删除和未删除的行,而此方法会修改后续的 find*() 方法仅返回软删除的行:

<?php

$deletedUsers = $userModel->onlyDeleted()->findAll();

保存数据

insert()

第一个参数是关联数组,用于在数据库中创建新行数据。如果传递对象而非数组,将尝试将其转换为数组。

数组的键必须与 $table 中的列名匹配,数组的值是要保存的值。

可选的第二个参数为布尔类型,若设为 false,方法将返回布尔值表示查询成功与否。

可使用 getInsertID() 方法获取最后插入行的主键。

<?php

$data = [
    'username' => 'darth',
    'email'    => 'd.vader@theempire.com',
];

// Inserts data and returns inserted row's primary key
$userModel->insert($data);

// Inserts data and returns true on success and false on failure
$userModel->insert($data, false);

// Returns inserted row's primary key
$userModel->getInsertID();

allowEmptyInserts()

在 4.3.0 版本加入.

可使用 allowEmptyInserts() 方法插入空数据。默认情况下,模型在尝试插入空数据时会抛出异常。但调用此方法后,将不再执行检查。

<?php

$userModel->allowEmptyInserts()->insert([]);

也可通过 $allowEmptyInserts 属性更改此设置。

通过调用 allowEmptyInserts(false) 可重新启用检查。

update()

更新数据库中的现有记录。第一个参数是要更新记录的 $primaryKey。第二个参数是包含数据的关联数组。数组的键必须与 $table 中的列名匹配,数组的值则是要保存的对应值:

<?php

$data = [
    'username' => 'darth',
    'email'    => 'd.vader@theempire.com',
];

$userModel->update($id, $data);

重要

自 v4.3.0 起,如果生成的 SQL 语句没有 WHERE 子句,此方法会抛出 DatabaseException。在早期版本中,如果调用时未指定 $primaryKey 且生成的 SQL 语句没有 WHERE 子句,查询仍会执行并更新表中的所有记录。

通过将主键数组作为第一个参数传递,可以在一次调用中更新多条记录:

<?php

$data = [
    'active' => 1,
];

$userModel->update([1, 2, 3], $data);

当需要更灵活的解决方案时,可以留空参数,此时其功能类似于查询构建器的 update 命令,并额外具备验证、事件等优势:

<?php

$userModel
    ->whereIn('id', [1, 2, 3])
    ->set(['active' => 1])
    ->update();

save()

这是对 insert()update() 方法的封装,根据是否找到匹配 主键 值的数组键来自动处理记录的插入或更新:

<?php

// Defined as a model property
$primaryKey = 'id';

// Does an insert()
$data = [
    'username' => 'darth',
    'email'    => 'd.vader@theempire.com',
];

$userModel->save($data);

// Performs an update, since the primary key, 'id', is found.
$data = [
    'id'       => 3,
    'username' => 'darth',
    'email'    => 'd.vader@theempire.com',
];
$userModel->save($data);

save 方法还能通过识别非简单对象并将其公共和受保护值提取到数组,简化与自定义类结果对象的交互。这使得你可以非常简洁地使用实体类。实体类是表示单个对象类型实例的简单类(如用户、博客文章、任务等),负责维护围绕对象本身的业务逻辑(如特定格式的元素处理等),不应了解如何保存到数据库。最简单的实体类可能如下所示:

<?php

namespace App\Entities;

class Job
{
    protected $id;
    protected $name;
    protected $description;

    public function __get($key)
    {
        if (property_exists($this, $key)) {
            return $this->{$key};
        }
    }

    public function __set($key, $value)
    {
        if (property_exists($this, $key)) {
            $this->{$key} = $value;
        }
    }
}

与之配合的简单模型可能如下:

<?php

namespace App\Models;

use CodeIgniter\Model;

class JobModel extends Model
{
    protected $table         = 'jobs';
    protected $returnType    = \App\Entities\Job::class;
    protected $allowedFields = [
        'name', 'description',
    ];
}

此模型处理来自 jobs 表的数据,并将所有结果作为 App\Entities\Job 实例返回。当需要将记录持久化到数据库时,你可以编写自定义方法,或使用模型的 save() 方法来检查类、提取公共和私有属性并保存到数据库:

<?php

// Retrieve a Job instance
$job = $model->find(15);

// Make some changes
$job->name = 'Foobar';

// Save the changes
$model->save($job);

备注

如果你需要频繁使用实体类,CodeIgniter 提供了内置的 实体类,其中包含多个便捷功能可简化实体开发。

保存日期

在 4.5.0 版本加入.

保存数据时,如果传递 Time 实例,它们会被转换为字符串格式。转换使用的格式定义在 数据库配置dateFormat['datetime']dateFormat['date'] 中。

备注

在 v4.5.0 之前,Model 类中日期/时间格式硬编码为 Y-m-d H:i:sY-m-d

删除数据

delete()

以主键值作为第一个参数,从模型表中删除匹配记录:

<?php

$userModel->delete(12);

如果模型的 $useSoftDeletes 值为 true,此操作会将行的 deleted_at 设为当前日期时间。通过将第二个参数设为 true 可强制永久删除。

传递主键数组作为第一个参数可批量删除多条记录:

<?php

$userModel->delete([1, 2, 3]);

不传递参数时,其行为类似于 查询构建器 的 delete 方法,需要预先调用 where 条件:

<?php

$userModel->where('id', 12)->delete();

purgeDeleted()

通过永久删除所有 ‘deleted_at IS NOT NULL’ 的行来清理数据库表:

<?php

$userModel->purgeDeleted();

模型内验证

警告

模型内验证在数据存储到数据库之前执行。在此之前数据尚未验证。在验证前处理用户输入数据可能引入安全漏洞。

验证数据

Model 类提供在通过 insert()update()save() 方法保存到数据库前自动验证数据的功能。

重要

更新数据时,默认情况下模型类中的验证仅验证提供的字段,以避免在更新部分字段时出现验证错误。

这意味着并非所有设置的验证规则都会在更新时检查。因此不完整数据可能通过验证。

例如,需要其他字段值的 required* 规则或 is_unique 规则可能无法按预期工作。

为避免此类问题,可通过配置更改此行为。详见 $cleanValidationRules

设置验证规则

第一步是在 $validationRules 类属性中填写要应用的字段和规则。

备注

内置验证规则列表参见 可用规则

如果有自定义错误信息,可将其放入 $validationMessages 数组:

<?php

namespace App\Models;

use CodeIgniter\Model;

class UserModel extends Model
{
    // ...

    protected $validationRules = [
        'username'     => 'required|max_length[30]|alpha_numeric_space|min_length[3]',
        'email'        => 'required|max_length[254]|valid_email|is_unique[users.email]',
        'password'     => 'required|max_length[255]|min_length[8]',
        'pass_confirm' => 'required_with[password]|max_length[255]|matches[password]',
    ];
    protected $validationMessages = [
        'email' => [
            'is_unique' => 'Sorry. That email has already been taken. Please choose another.',
        ],
    ];
}

如果更愿意在 验证配置文件 中组织规则和错误信息,可创建验证规则组并将 $validationRules 设为组名:

<?php

namespace App\Models;

use CodeIgniter\Model;

class UserModel extends Model
{
    // ...

    protected $validationRules = 'users';
}

也可以通过函数设置字段验证规则:

class CodeIgniter\Model
CodeIgniter\Model::setValidationRule($field, $fieldRules)
参数:
  • $field (string) –

  • $fieldRules (array) –

此函数设置字段验证规则。

使用示例:

<?php

$fieldName  = 'username';
$fieldRules = 'required|max_length[30]|alpha_numeric_space|min_length[3]';

$model->setValidationRule($fieldName, $fieldRules);
CodeIgniter\Model::setValidationRules($validationRules)
参数:
  • $validationRules (array) –

此函数设置验证规则。

使用示例:

<?php

$validationRules = [
    'username' => 'required|max_length[30]|alpha_numeric_space|min_length[3]',
    'email'    => [
        'rules'  => 'required|max_length[254]|valid_email|is_unique[users.email]',
        'errors' => [
            'required' => 'We really need your email.',
        ],
    ],
];
$model->setValidationRules($validationRules);

通过函数设置字段验证信息:

CodeIgniter\Model::setValidationMessage($field, $fieldMessages)
参数:
  • $field (string) –

  • $fieldMessages (array) –

此函数设置字段错误信息。

使用示例:

<?php

$fieldName              = 'name';
$fieldValidationMessage = [
    'required' => 'Your name is required here',
];
$model->setValidationMessage($fieldName, $fieldValidationMessage);
CodeIgniter\Model::setValidationMessages($fieldMessages)
参数:
  • $fieldMessages (array) –

此函数设置字段信息。

使用示例:

<?php

$fieldValidationMessage = [
    'name' => [
        'required'   => 'Your baby name is missing.',
        'min_length' => 'Too short, man!',
    ],
];
$model->setValidationMessages($fieldValidationMessage);

获取验证结果

当调用 insert()update()save() 方法时,数据会被验证。如果验证失败,模型返回布尔值 false

获取验证错误

使用 errors() 方法获取验证错误:

<?php

if ($model->save($data) === false) {
    return view('updateUser', ['errors' => $model->errors()]);
}

返回包含字段名及其关联错误的数组,可用于在表单顶部显示所有错误或单独显示:

<?php if (! empty($errors)): ?>
    <div class="alert alert-danger">
    <?php foreach ($errors as $field => $error): ?>
        <p><?= esc($error) ?></p>
    <?php endforeach ?>
    </div>
<?php endif ?>

检索验证规则

可通过访问 validationRules 属性检索模型的验证规则:

<?php

$rules = $model->validationRules;

也可通过调用访问方法直接检索规则子集(带选项):

<?php

$rules = $model->getValidationRules($options);

$options 参数是包含一个元素的关联数组,其键为 'except''only',值为相关字段名数组:

<?php

// get the rules for all but the "username" field
$rules = $model->getValidationRules(['except' => ['username']]);
// get the rules for only the "city" and "state" fields
$rules = $model->getValidationRules(['only' => ['city', 'state']]);

验证占位符

模型提供简单方法来替换规则中基于传入数据的部分。这在 is_unique 验证规则中特别有用。占位符是由花括号包围的字段名(或数组键),会被匹配传入字段的 替换。示例:

<?php

namespace App\Models;

use CodeIgniter\Model;

class MyModel extends Model
{
    // ...

    protected $validationRules = [
        'id'    => 'max_length[19]|is_natural_no_zero',
        'email' => 'required|max_length[254]|valid_email|is_unique[users.email,id,{id}]',
    ];
}

备注

自 v4.3.5 起,必须为占位符字段(id)设置验证规则。

在此规则集中,声明电子邮件地址在数据库中应唯一,除了 id 匹配占位符值的行。假设表单 POST 数据如下:

<?php

$_POST = [
    'id'    => 4,
    'email' => 'foo@example.com',
];

{id} 占位符会被替换为数字 4,生成修订后的规则:

<?php

namespace App\Models;

use CodeIgniter\Model;

class MyModel extends Model
{
    // ...

    protected $validationRules = [
        'id'    => 'max_length[19]|is_natural_no_zero',
        'email' => 'required|max_length[254]|valid_email|is_unique[users.email,id,4]',
    ];
}

因此在校验电子邮件唯一性时,会忽略数据库中 id=4 的行。

备注

自 v4.3.5 起,如果占位符(id)值未通过验证,占位符不会被替换。

只要注意动态键不与表单数据冲突,这也可用于在运行时创建更动态的规则。

保护字段

为防止大规模赋值攻击,Model 类 要求$allowedFields 类属性中列出所有可在插入和更新时修改的字段名。超出这些字段的数据会在触及数据库前被移除。这能有效防止时间戳或主键被修改。

<?php

namespace App\Models;

use CodeIgniter\Model;

class MyModel extends Model
{
    // ...

    protected $allowedFields = ['name', 'email', 'address'];
}

有时需要在测试、迁移或种子数据时修改这些元素。此时可开关保护:

<?php

$model->protect(false)
    ->insert($data)
    ->protect(true);

运行时返回类型变更

可通过类属性 $returnType 指定使用 find*() 方法时数据的返回格式。有时可能需要不同格式的数据。模型提供方法实现这一点。

备注

这些方法仅改变下一次 find*() 方法调用的返回类型,之后会重置为默认值。

asArray()

将下一次 find*() 方法的数据作为关联数组返回:

<?php

$users = $userModel->asArray()->where('status', 'active')->findAll();

asObject()

将下一次 find*() 方法的数据作为标准对象或自定义类实例返回:

<?php

// Return as standard objects
$users = $userModel->asObject()->where('status', 'active')->findAll();

// Return as custom class instances
$users = $userModel->asObject('User')->where('status', 'active')->findAll();

处理大量数据

处理大量数据时可能存在内存不足风险。可使用 chunk() 方法获取小块数据进行处理。第一个参数是单块检索的行数,第二个参数是处理每行数据的闭包。

此方法适用于定时任务、数据导出等大型任务。

<?php

$userModel->chunk(100, static function ($data) {
    // do something.
    // $data is a single row of data.
});

使用查询构建器

获取模型的查询构建器

CodeIgniter 模型有一个针对模型数据库连接的查询构建器实例。可随时访问此 共享 实例:

<?php

$builder = $userModel->builder();

此构建器已配置模型的 $table

备注

获取查询构建器实例后,可调用 查询构建器 的方法。但由于查询构建器不是模型,不能调用模型的方法。

获取其他表的查询构建器

如需访问其他表,可获取另一个查询构建器实例。传递表名作为参数,但注意这会返回 非共享 实例:

<?php

$groupBuilder = $userModel->builder('groups');

混合使用查询构建器和模型方法

可在同一链式调用中混合使用查询构建器方法和模型的 CRUD 方法,实现优雅操作:

<?php

$users = $userModel->where('status', 'active')
    ->orderBy('last_login', 'asc')
    ->findAll();

此例中,操作的是模型持有的查询构建器共享实例。

重要

模型并非查询构建器的完美接口。模型和查询构建器是不同目的的独立类,不应期望返回相同数据。

如果查询构建器返回结果,则原样返回。此时结果可能与模型方法返回的不同,且可能不符合预期。不会触发模型事件。

为避免意外行为,请勿在方法链末尾使用返回结果的查询构建器方法并指定模型方法。

备注

也可无缝访问模型的数据库连接:

<?php

$userName = $userModel->escape($name);

模型事件

在模型执行的多个节点可指定多个回调方法。这些方法可用于规范化数据、哈希密码、保存关联实体等。

以下执行节点可通过类属性设置回调:

备注

$beforeInsertBatch$afterInsertBatch$beforeUpdateBatch$afterUpdateBatch 自 v4.3.0 起可用。

定义回调

首先在模型中创建新类方法作为回调。

此方法始终接收 $data 数组作为唯一参数。

$data 数组的具体内容因事件而异,但始终包含键名为 data 的主要数据。对于 insert*()update*() 方法,这是要插入/更新到数据库的键值对。主 $data 数组还包含传递给方法的其他值,详见 事件参数

回调方法必须返回原始 $data 数组以便其他回调使用完整信息。

<?php

namespace App\Models;

use CodeIgniter\Model;

class MyModel extends Model
{
    // ...

    protected function hashPassword(array $data)
    {
        if (! isset($data['data']['password'])) {
            return $data;
        }

        $data['data']['password_hash'] = password_hash($data['data']['password'], PASSWORD_DEFAULT);
        unset($data['data']['password']);

        return $data;
    }
}

指定运行的回调

通过将方法名添加到相应的类属性($beforeInsert$afterUpdate 等)来指定回调运行时机。单个事件可添加多个回调并按序处理。同一回调可用于多个事件:

<?php

namespace App\Models;

use CodeIgniter\Model;

class MyModel extends Model
{
    // ...

    protected $beforeInsert = ['hashPassword'];
    protected $beforeUpdate = ['hashPassword'];

    // ...
}

此外,每个模型可通过设置 $allowCallbacks 属性全局允许(默认)或禁止回调:

<?php

namespace App\Models;

use CodeIgniter\Model;

class MyModel extends Model
{
    // ...

    protected $allowCallbacks = false;

    // ...
}

也可使用 allowCallbacks() 方法临时更改单个模型调用的设置:

<?php

$model->allowCallbacks(false)->find(1); // No callbacks triggered
$model->find(1); // Callbacks subject to original property value

事件参数

各事件传递给回调的 $data 参数内容如下:

事件

$data 内容

beforeInsert

data = 要插入的键值对。如果向 insert() 传递对象或 Entity 类,会先转换为数组。

afterInsert

id = 新行的主键,失败时为 0。 data = 要插入的键值对。 result = 通过查询构建器使用的 insert() 方法结果。

beforeUpdate

id = 传递给 update() 方法的主键数组。 data = 要更新的键值对。如果向 update() 传递对象或 Entity 类,会先转换为数组。

afterUpdate

id = 传递给 update() 方法的主键数组。 data = 要更新的键值对。 result = 通过查询构建器使用的 update() 方法结果。

beforeFind

调用 方法 的名称,是否请求 单例,以及以下附加字段:

  • first()

无附加字段

  • find()

id = 要搜索行的主键。

  • findAll()

limit = 要查找的行数。 offset = 搜索期间跳过的行数。

afterFind

beforeFind,但包含结果数据行(无结果时为 null)。

beforeDelete

id = 传递给 delete() 方法的主键。 purge = 是否硬删除软删除行的布尔值。

afterDelete

id = 传递给 delete() 方法的主键。 purge = 是否硬删除软删除行的布尔值。 result = 查询构建器上 delete() 调用的结果。 data = 未使用。

beforeInsertBatch

data = 要插入的值的关联数组。如果向 insertBatch() 传递对象或 Entity 类,会先转换为数组。

afterInsertBatch

data = 要插入的值的关联数组。 result = 通过查询构建器使用的 insertbatch() 方法结果。

beforeUpdateBatch

data = 要更新的值的关联数组。如果向 updateBatch() 传递对象或 Entity 类,会先转换为数组。

afterUpdateBatch

data = 要更新的键值对。 result = 通过查询构建器使用的 updateBatch() 方法结果。

修改 Find* 数据

beforeFindafterFind 方法都可以返回修改后的数据集来覆盖模型的正常响应。对于 afterFind,返回数组中 data 的任何修改都会自动传递回调用上下文。为了让 beforeFind 拦截查找工作流,它还必须返回一个额外的布尔值 returnData

<?php

namespace App\Models;

use CodeIgniter\Model;

class MyModel extends Model
{
    // ...

    protected $beforeFind = ['checkCache'];

    // ...

    protected function checkCache(array $data)
    {
        // Check if the requested item is already in our cache
        if (isset($data['id']) && $item = $this->getCachedItem($data['id'])) {
            $data['data']       = $item;
            $data['returnData'] = true;

            return $data;
        }

        // ...
    }
}

手动创建模型

你不需要继承任何特殊类来为应用程序创建模型。你只需要获取数据库连接的实例即可开始使用。这允许你绕过 CodeIgniter 模型开箱即用的功能,创建完全自定义的体验。

<?php

namespace App\Models;

use CodeIgniter\Database\ConnectionInterface;

class UserModel
{
    protected $db;

    public function __construct(ConnectionInterface $db)
    {
        $this->db = $db;
    }
}