跳转到内容

模型


概览

The Phalcon\Mvc\ModelM在MVC中。它是一个将业务对象和数据库表连接起来的类,以创建一个持久化的领域模型,其中逻辑和数据被封装在一起。它是对象关系映射(ORM)的一种实现。

模型表示应用程序的信息(数据)以及操作这些数据的规则。模型主要用于管理与相应数据库表交互的规则。在大多数情况下,数据库中的每个表都会对应应用程序中的一个模型。大部分应用程序的业务逻辑将集中在模型中。

The Phalcon\Mvc\Model是首个使用Zephir/C语言为PHP编写的ORM,它在保证开发者操作数据库时易用性的同时,也提供了高性能。

注意

模型旨在通过抽象的高层来操作数据库。如果您需要在更底层上操作数据库,请查看Phalcon\Db\Db组件文档。

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{

}
<?php

use MyApp\Models\Invoices;

/**
 * Create an invoice 
 */
$invoice = new Invoices();

$invoice->inv_cst_id      = 1;
$invoice->inv_status_flag = 1;
$invoice->inv_title       = 'Invoice for ACME Inc.';
$invoice->inv_total       = 100;
$invoice->inv_created_at  = '2019-12-25 01:02:03';

$result = $invoice->save();

if (false === $result) {

    echo 'Error saving Invoice: ';

    $messages = $invoice->getMessages();

    foreach ($messages as $message) {
        echo $message . PHP_EOL;
    }
} else {

    echo 'Record Saved';

}

注意

关于如何创建模型的信息,请参阅创建模型部分

常量

常量
DIRTY_STATE_DETACHED 2
DIRTY_STATE_PERSISTENT 0
DIRTY_STATE_TRANSIENT 1
OP_CREATE 1
OP_DELETE 3
OP_NONE 0
OP_UPDATE 2
TRANSACTION_INDEX 'transaction'

方法

final public function __construct(
    mixed $data = null, 
    DiInterface $container = null,
    ManagerInterface $modelsManager = null
)
构造模型对象。此方法接受一个用于填充对象的数据数组,内部使用assign。您也可以选择传递DI容器和Models Manager对象。如果未传递,则会使用默认值。

public function __call(string $method, array $arguments): mixed
当调用未实现的方法时进行处理。若方法不存在则抛出Phalcon\Mvc\Model\Exception异常

public static function __callStatic(
    string $method, 
    array $arguments
): mixed
当静态方法未实现时进行处理。若方法不存在则抛出Phalcon\Mvc\Model\Exception异常

public function __get(string $property)
使用关联别名作为属性的魔术方法获取相关记录

public function __isset(string $property): bool
检查属性是否是有效关联的魔术方法

public function __set(string $property, mixed $value)
分配值给模型的魔术方法

public function addBehavior(
    BehaviorInterface $behavior
): void
设置模型中的行为

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Behavior\Timestampable;

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

public function appendMessage(
    MessageInterface $message
): ModelInterface
在验证过程中添加自定义消息

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;
use Phalcon\Messages\Message as Message;

class Invoices extends Model
{
    public function beforeSave()
    {
        if (0 === $this->inv_status_flag) {
            $message = new Message(
                'Sorry, an invoice cannot be unpaid'
            );

            $this->appendMessage($message);
        }
    }
}

public function assign(
    mixed $data, 
    array $whiteList = null, 
    array $dataColumnMap = null
): ModelInterface
将数据分配给模型。data参数可以是数组或数据库行。whitelist是一个包含模型属性的数组,这些属性将在分配过程中被更新。即使数组或数据库行中包含省略的属性也不会被接受;然而,如果模型需要其中一个属性,数据将无法保存并且模型会产生错误。dataColumnMap是一个将data的列映射到实际模型中的数组。当您希望将如$_POST数组中的输入映射到数据库字段时,这非常有用。

从数组向模型赋值

<?php

$invoice->assign(
    [
        'inv_cst_id'      => 1,
        'inv_status_flag' => 1,
        'inv_title'       => 'Invoice for ACME Inc.',
        'inv_total'       => 100,
        'inv_created_at'  => '2019-12-25 01:02:03',
    ]
);

assign结合数据库行。- 需要列映射

<?php

$invoice->assign(
    $row,
    null,
    [
        'inv_cst_id'      => 'customerId',
        'inv_status_flag' => 'status',
        'inv_title'       => 'title',
        'inv_total'       => 'total',
    ]
);

只更新inv_status_flag, inv_title, inv_total字段。

<?php

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

默认情况下assign会使用setter方法(如果存在),可以通过使用ini_set来禁用该功能以直接使用属性

ini_set('phalcon.orm.disable_assign_setters', true);
<?php

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

public static function average(
    mixed $parameters = null
): float
返回结果集中符合条件的某列的平均值

<?php

use MyApp\Models\Invoices;

$average = Invoices::average(
    [
        'column' => 'inv_total',
    ]
);

echo 'AVG: ', $average, PHP_EOL;

$average = Invoices::average(
    [
        'inv_cst_id = 1',
        'column' => 'inv_total',
    ]
);

echo 'AVG [Customer: 1] ', $average, PHP_EOL;

public static function cloneResult(
    ModelInterface $base, 
    array $data, 
    int $dirtyState = 0
): ModelInterface
从数组赋值给模型并返回新模型

<?php

use MyApp\Models\Invoices;

$invoice = Invoices::cloneResult(
     new Invoices(),
    [
        'inv_cst_id'      => 1,
        'inv_status_flag' => 0,
        'inv_title'       => 'Invoice for ACME Inc. #2',
        'inv_total'       => 400,
        'inv_created_at'  => '2019-12-25 01:02:03',
    ]
 );

public static function cloneResultMap(
    mixed $base, 
    array $data, 
    array $columnMap, 
    int $dirtyState = 0, 
    bool $keepSnapshots = null
): ModelInterface
从数组赋值给模型并返回新模型,使用列映射。

<?php

use MyApp\Models\Invoices;

$invoice = Invoices::cloneResultMap(
     new Invoices(),
     [
        'customerId' => 1,
        'status'     => 0,
        'title'      => 'Invoice for ACME Inc. #2',
        'total'      => 400,
        'created'    => '2019-12-25 01:02:03',
     ]
);

public static function cloneResultMapHydrate(
    array $data, 
    array $columnMap, 
    int $hydrationMode
): mixed
根据数据和列映射返回一个填充后的结果

public static function count(
    mixed $parameters = null
): int
返回符合指定条件的记录数量

<?php

use MyApp\Models\Invoices;

$average = Invoices::count();

echo 'COUNT: ', $average, PHP_EOL;

$average = Invoices::count(
    'inv_cst_id = 1'
);

echo 'COUNT [Customer: 1] ', $average, PHP_EOL;

public function create(): bool
将模型插入数据库。如果记录已存在于数据库中,create()会抛出异常。操作成功时返回truefalse

<?php

use MyApp\Models\Invoices;

$invoice = new Invoices();
$invoice->assign(
    [
        'inv_cst_id'      => 1,
        'inv_status_flag' => 1,
        'inv_title'       => 'Invoice for ACME Inc.',
        'inv_total'       => 100,
        'inv_created_at'  => '2019-12-25 01:02:03',
    ]
);

$result = $invoice->create();

public function delete(): bool
删除模型实例。成功时返回true,否则返回false。

<?php

use MyApp\Models\Invoices;

$invoice = Invoices::findFirst('inv_id = 4');
$result  = $invoice->delete();

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

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

public function dump(): array
返回可用于var_dump()

<?php

use MyApp\Models\Invoices;

$invoice = Invoices::findFirst('inv_id = 4');

var_dump(
    $invoice->dump()
);

public static function find(
    mixed $parameters = null
): ResultsetInterface
Query for a set of records that match the specified conditions. find() is flexible enough to accept a variety of parameters to find the data required. You can check the 查找记录部分以获取更多信息。

<?php

use MyApp\Models\Invoices;

$invoice = Invoices::find();

public static function findFirst(
    mixed $parameters = null
): ModelInterface | null
查询第一个符合指定条件的记录。它将返回结果集或者null如果找不到记录。

注意

findFirst()不再返回false如果找不到记录。

<?php

use MyApp\Models\Invoices;

$invoice = Invoices::findFirst();

public function fireEvent(string $eventName): bool
触发事件,隐式调用行为,并通知事件管理器中的监听者

public function fireEventCancel(string $eventName): bool
触发事件,隐式调用行为,并通知事件管理器中的监听者。如果其中一个回调/监听者返回false

public function getChangedFields(): array
返回变更值列表。

<?php

use MyApp\Models\Invoices;

$invoice = Invoices::findFirst();

print_r(
    $invoice->getChangedFields()
); 
// []

$invoice->inv_total = 120;;

$invoice->getChangedFields();

print_r(
    $invoice->getChangedFields()
);
// ['inv_total']

public function getDirtyState(): int
返回一个DIRTY_STATE_*常量,表明记录是否存在于数据库中

public function getMessages(
    mixed $filter = null
): MessageInterface[]
返回验证消息数组

<?php

use MyApp\Models\Invoices;

$invoice = new Invoices();

$invoice->inv_cst_id      = 1;
$invoice->inv_status_flag = 1;
$invoice->inv_title       = 'Invoice for ACME Inc.';
$invoice->inv_total       = 100;
$invoice->inv_created_at  = '2019-12-25 01:02:03';

$result = $invoice->save();

if (false === $result) {

    echo 'Error saving Invoice: ';

    $messages = $invoice->getMessages();

    foreach ($messages as $message) {
        echo $message . PHP_EOL;
    }
} else {

    echo 'Record Saved';

}

注意

save()不再接受设置数据的参数。你可以使用assign替代方法。

public function getModelsManager(): ManagerInterface
返回与实体实例相关的模型管理器

public function getModelsMetaData(): MetaDataInterface
返回与实体实例相关的模型元数据服务

public function getOperationMade(): int
返回ORM最近执行的操作类型。返回OP_*类常量之一

public function getOldSnapshotData(): array
返回内部旧快照数据

final public function getReadConnection(): AdapterInterface
获取用于读取模型数据的连接

final public function getReadConnectionService(): string
返回用于读取模型相关数据的依赖注入连接服务名称

public function getRelated(
    string $alias, 
    mixed $arguments = null
): Phalcon\Mvc\Model\Resultset\Simple | null
根据定义的关系返回相关记录。如果是一对一关系且未找到记录,则返回null

注意

getRelated()不再返回false如果一对一关系中未找到记录。

<?php

use MyApp\Models\Customers;

$customer = Customers::findFirst('cst_id = 1');
$invoices = $customer->getRelated('invoices');

public function isRelationshipLoaded(
    string $relationshipAlias
): bool
检查已保存的关联记录是否已被加载。仅在记录之前通过模型且未带有任何额外参数的情况下获取时才返回true如果这些记录是之前通过模型且未带有任何额外参数获取的。

<?php

use MyApp\Models\Customers;

$customer = Customers::findFirst('cst_id = 1');
$invoices = $customer->isRelationshipLoaded('invoices'); // false

$invoices = $customer->getRelated('invoices');
$invoices = $customer->isRelationshipLoaded('invoices'); // true

final public function getSchema(): string
返回映射表所在模式的名称

public function getSnapshotData(): array
返回内部快照数据

final public function getSource(): string
返回模型中映射的表名

public function getUpdatedFields(): array
返回一个更新值的列表

<?php

use MyApp\Models\Invoices;

$invoice = Invoices::findFirst();

print_r(
    $invoice->getChangedFields()
); 
// []

$invoice->inv_total = 120;;

$invoice->getChangedFields();

print_r(
    $invoice->getChangedFields()
);
// ['inv_total']

$invoice->save();

print_r(
    $invoice->getChangedFields()
);
// []

print_r(
    $invoice->getUpdatedFields()
);
// ['inv_total']

final public function getWriteConnection(): AdapterInterface
获取用于向模型写入数据的连接

final public function getWriteConnectionService(): string
返回用于与模型相关的数据写操作的依赖注入连接服务名称

public function hasChanged(
    string | array $fieldName = null, 
    bool $allFields = false
): bool
检查特定属性是否更改过。仅当模型保留数据快照时才有效

<?php

use MyApp\Models\Invoices;

$invoice = new Invoices();

$invoice->inv_cst_id      = 1;
$invoice->inv_status_flag = 1;
$invoice->inv_title       = 'Invoice for ACME Inc.';
$invoice->inv_total       = 100;
$invoice->inv_created_at  = '2019-12-25 01:02:03';

$result = $invoice->create();

$invoice->inv_total = 120;

$hasChanged = $invoice->hasChanged('inv_title');
// false
$hasChanged = $invoice->hasChanged(
    [
        'inv_total',
    ]
);
// true
$hasChanged = $invoice->hasChanged(
    [
        'inv_title', 
        'inv_total'
    ], 
    true
);
// false

public function hasSnapshotData(): bool
检查对象是否有内部快照数据

public function hasUpdated(
    string | array $fieldName = null, 
    bool $allFields = false
): bool
检查特定属性是否被更新过。仅当模型保留数据快照时才有效

public function jsonSerialize(): array
序列化对象以供json_encode使用

echo json_encode($invoice);

public static function maximum(
    mixed $parameters = null
): mixed
返回符合指定条件的结果集行中某一列的最大值

<?php

use MyApp\Models\Invoices;

$id = Invoices::maximum(
    [
        'column' => 'inv_id',
    ]
);

echo 'MAX: ', $id, PHP_EOL;

$max = Invoices::maximum(
    [
        'inv_cst_id = 1',
        'column' => 'inv_total',
    ]
);

echo 'MAX [Customer: 1] ', $max, PHP_EOL;

public static function minimum(
    mixed parameters = null
): mixed 
返回符合指定条件的结果集行中某一列的最小值

<?php

use MyApp\Models\Invoices;

$id = Invoices::minimum(
    [
        'column' => 'inv_id',
    ]
);

echo 'MIN: ', $id, PHP_EOL;

$max = Invoices::minimum(
    [
        'inv_cst_id = 1',
        'column' => 'inv_total',
    ]
);

echo 'MIN [Customer: 1] ', $max, PHP_EOL;

public static function query(
    DiInterface $container = null
): CriteriaInterface
为特定模型创建查询条件

public function readAttribute(
    string $attribute
): mixed | null
按属性名读取属性值

echo $invoice->readAttribute('inv_title');

public function refresh(): ModelInterface
刷新模型属性,从数据库重新查询记录

public function save(): bool
插入或更新模型实例。返回true成功时返回false

<?php

use MyApp\Models\Invoices;

$invoice = new Invoices();

$invoice->inv_cst_id      = 1;
$invoice->inv_status_flag = 1;
$invoice->inv_title       = 'Invoice for ACME Inc.';
$invoice->inv_total       = 100;
$invoice->inv_created_at  = '2019-12-25 01:02:03';

$result = $invoice->save();

$invoice = Invoices::findFirst('inv_id = 100');

$invoice->inv_total = 120;

$invoice->save();

注意

save()不再接受设置数据的参数。你可以使用assign替代方法。

public function serialize(): string
序列化对象时忽略连接、服务、关联对象或静态属性

public function unserialize(mixed $data)
从序列化字符串中反序列化对象

final public function setConnectionService(
    string $connectionService
): void
设置依赖注入连接服务名称

public function setDirtyState(
    int $dirtyState
): ModelInterface | bool
使用其中一个DIRTY_STATE_*常量

public function setEventsManager(
    EventsManagerInterface $eventsManager
)
设置自定义事件管理器

final public function setReadConnectionService(
    string $connectionService
): void
设置用于读取数据的依赖注入连接服务名称

public function setOldSnapshotData(
    array $data, 
    array $columnMap = null
)
设置记录的旧快照数据。此方法由内部调用,用于当模型配置了保留快照数据时设置旧快照数据

public function setSnapshotData(
    array $data, 
    array $columnMap = null
): void
设置记录的快照数据。此方法由内部调用,用于当模型配置了保留快照数据时设置快照数据

public function setTransaction(
    TransactionInterface $transaction
): ModelInterface
设置与模型实例相关联的事务

<?php

use MyApp\Models\Customers;
use MyApp\Models\Invoices;
use Phalcon\Mvc\Model\Transaction\Manager;
use Phalcon\Mvc\Model\Transaction\Failed;

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

    $customer = new Customers();
    $customer->setTransaction($transaction);
    $customer->cst_name_last  = 'Vader';
    $customer->cst_name_first = 'Darth';

    if (false === $customer->save()) {
        $transaction->rollback('Cannot save Customer');
    }

    $invoice = new Invoices();
    $invoice->setTransaction($transaction);

    $invoice->inv_cst_id      = $customer->cst_id;
    $invoice->inv_status_flag = 1;
    $invoice->inv_title       = 'Invoice for ACME Inc.';
    $invoice->inv_total       = 100;
    $invoice->inv_created_at  = '2019-12-25 01:02:03';

    if (false === $invoice->save()) {
        $transaction->rollback('Cannot save record');
    }

    $transaction->commit();
} catch (Failed $ex) {
    echo 'ERROR: ', $ex->getMessage();
}

public static function setup(
    array $options
): void
启用/禁用 ORM 中的选项,例如事件、列重命名等

final public function setWriteConnectionService(
    string $connectionService
): void
设置用于写入数据的依赖注入连接服务名称

public function skipOperation(bool $skip): void
强制跳过当前操作并进入成功状态

public static function sum(
    array $parameters = null
): float
计算符合指定条件的结果集行中某一列的总和

<?php

use MyApp\Models\Invoices;

$total = Invoices::sum(
    [
        'column' => 'inv_total',
    ]
);

echo 'SUM: ', $total, PHP_EOL;

$total = Invoices::sum(
    [
        'inv_cst_id = 1',
        'column' => 'inv_total',
    ]
);

echo 'SUM [Customer: 1] ', $total, PHP_EOL;

public function toArray(
    array $columns = null,
    bool $useGetters = true
): array
将实例作为数组表示形式返回。接受一个包含要包含在结果中的列名的数组,默认情况下将使用 getter 方法。

<?php

use MyApp\Models\Invoices;

$invoice = Invoices::findFirst('inv_id = 4');

print_r(
    $invoice->toArray()
);

//  [
//      'inv_id'          => 4,
//      'inv_cst_id'      = $customer->cst_id,
//      'inv_status_flag' = 1,
//      'inv_title'       = 'Invoice for ACME Inc.',
//      'inv_total'       = 100,
//      'inv_created_at'  = '2019-12-25 01:02:03',
//  ]

print_r(
    $invoice->toArray(
        [
            'inv_status_flag',
            'inv_title',
            'inv_total',
        ]
    )
);

//  [
//      'inv_status_flag' = 1,
//      'inv_title'       = 'Invoice for ACME Inc.',
//      'inv_total'       = 100,
//  ]

toArray默认使用 getter 方法,若要禁用此行为,请设置$useGettersfalse

<?php

use MyApp\Models\InvoicesGetters;

$invoice = InvoicesGetters::findFirst('inv_id = 4');

print_r(
    $invoice->inv_title
);

// 'Invoice for ACME Inc.'


print_r(
    $invoice->getInvTitle()
);

// 'Invoice for ACME Inc. - Status 1'

print_r(
    $invoice->toArray()
);

//  [
//      'inv_id'          => 4,
//      'inv_cst_id'      = $customer->cst_id,
//      'inv_status_flag' = 1,
//      'inv_title'       = 'Invoice for ACME Inc. - Status 1' ,
//      'inv_total'       = 100,
//      'inv_created_at'  = '2019-12-25 01:02:03',
//  ]

print_r(
    $invoice->toArray(
        null,
        false
    )
);


//  [
//      'inv_id'          => 4,
//      'inv_cst_id'      = $customer->cst_id,
//      'inv_status_flag' = 1,
//      'inv_title'       = 'Invoice for ACME Inc.' ,
//      'inv_total'       = 100,
//      'inv_created_at'  = '2019-12-25 01:02:03',
//  ]

public function update(): bool
更新模型实例。如果该实例在持久化存储中不存在,则会抛出异常。返回true成功时返回false

<?php

use MyApp\Models\Invoices;

$invoice = Invoices::findFirst('inv_id = 4');

$invoice->inv_total = 120;

$invoice->update();

注意

当通过findFirst()查询记录时,你需要获取完整的对象(没有columns定义),但还需要通过主键进行检索。如果不这样做,ORM 将发出一个INSERT而不是UPDATE.

public function writeAttribute(
    string $attribute, 
    mixed $value
): void
按属性名写入属性值

$invoice->writeAttribute('inv_total', 120);

protected function allowEmptyStringValues(
    array $attributes
): void
设置一个属性列表,这些属性必须从生成的UPDATE语句中跳过

<?php 

use Phalcon\Mvc\Model;

class Invoices extends Model
{
    public function initialize()
    {
        $this->allowEmptyStringValues(
            [
                'inv_created_at',
            ]
        );
    }
}

protected function belongsTo(
    string | array $fields, 
    string $referenceModel, 
    string | array $referencedFields, 
    array options = null
): Relation
在两个模型之间建立反向的一对一或一对多关系

<?php 

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class InvoicesXProducts extends Model
{
    public function initialize()
    {
        $this->belongsTo(
            'ixp_inv_id',
            Invoices::class,
            'inv_id'
        );
    }
}

protected function hasMany(
    string | array $fields, 
    string $referenceModel, 
    string | array $referencedFields, 
    array options = null
): Relation
在两个模型之间建立一对多关系

<?php 

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Customers extends Model
{
    public function initialize()
    {
        $this->hasMany(
            'cst_id',
            Invoices::class,
            'inv_cst_id'
        );
    }
}

protected function hasManyToMany(
    string | array $fields,
    string $intermediateModel, 
    string | array $intermediateFields,
    string | array $intermediateReferencedFields,
    string $referenceModel, 
    string | array $referencedFields,
    array $options = null
): Relation
通过中间关系,在两个模型之间建立多对多关系

<?php 

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
    public function initialize()
    {
        $this->hasManyToMany(
            'inv_id',
            InvoicesXProducts::class,
            'ixp_inv_id',
            'ixp_prd_id',
            Products::class,
            'prd_id'
        );
    }
}

protected function hasOne(
    string | array $fields, 
    string $referenceModel, 
    string | array $referencedFields, 
    array options = null
): Relation
在两个模型之间建立一对一关系

<?php 

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
    public function initialize()
    {
        $this->hasOne(
            'inv_cst_id',
            Customers::class,
            'cst_id'
        );
    }
}

protected function keepSnapshots(
    bool $keepSnapshot
): void
设置模型是否必须在内存中保留原始记录快照

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

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

final protected function setSchema(
    string $schema
): ModelInterface
设置映射表所在模式的名称

final protected function setSource(
    string $source
): ModelInterface
设置模型应映射到的表名

protected function skipAttributes(array $attributes)
设置一个属性列表,这些属性必须从生成的INSERT/UPDATE语句中跳过

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
    public function initialize()
    {
        $this->skipAttributes(
            [
                'inv_created_at',
            ]
        );
    }
}

protected function skipAttributesOnCreate(
    array $attributes
): void
设置一个属性列表,这些属性必须从生成的INSERT语句中跳过

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
    public function initialize()
    {
        $this->skipAttributesOnCreate(
            [
                'inv_created_at',
            ]
        );
    }
}

protected function skipAttributesOnUpdate(
    array $attributes
): void
设置一个属性列表,这些属性必须从生成的UPDATE语句中跳过

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
    public function initialize()
    {
        $this->skipAttributesOnUpdate(
            [
                'inv_modified_at',
            ]
        );
    }
}

protected function useDynamicUpdate(
    bool dynamicUpdate
): void
设置模型是否应使用动态更新而非全字段更新

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

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

protected function validate(
    ValidationInterface $validator
): bool
在每次验证调用时执行验证器

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;
use Phalcon\Validation;
use Phalcon\Validation\Validator\ExclusionIn;

class Invoices extends Model
{
    public function validation()
    {
        $validator = new Validation();

        $validator->add(
            'inv_status_flag',
            new ExclusionIn(
                [
                    'domain' => [
                        0,
                        1,
                    ],
                ]
            )
        );

        return $this->validate($validator);
    }
}

public function validationHasFailed(): bool
检查验证过程是否生成了任何消息

创建模型

模型是一个继承自Phalcon\Mvc\Model的类。其类名应采用驼峰命名法:

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{

}

默认情况下,模型MyApp\Models\Invoices将映射到表invoices。如果你想手动指定其他映射表名,可以使用setSource()方法:

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

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

模型Invoices现在映射到co_invoices表。initialize()方法有助于为此模型设置自定义行为,比如不同的表。

The initialize()方法在整个请求期间只调用一次。该方法用于执行适用于应用程序内创建的所有模型实例的初始化操作。如果你想为每个创建的实例执行初始化任务,可以使用onConstruct()方法:

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
    public function onConstruct()
    {
        // ...
    }
}

属性 vs. setter/getter 方法

注意

模型类使用一些内部属性来处理服务。这些属性的名称是保留的,不能用作数据库中的字段名。请记住这一点,并在为你的表命名字段时注意避免冲突。如果存在冲突,你的模型将无法正确更新。

container, dirtyState, dirtyRelated, errorMessages, modelsManager, modelsMetaData, related, operationMade, oldSnapshot, skipped, snapshot, transaction, uniqueKey, uniqueParams, uniqueTypes

模型可以用公共属性实现,这意味着每个属性都可以从代码中任何实例化该模型类的地方进行读取和更新:

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

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 函数,这可以控制哪些属性对该模型公开可用。

使用 getter 和 setter 的好处是开发者可以在设置或获取模型属性值时进行转换和验证检查,而使用公共属性则无法做到这一点。

此外,getter 和 setter 允许在不改变模型类接口的前提下进行未来修改。因此,如果某个字段名称发生变化,唯一需要修改的地方就是模型中私有属性的名字,该名字会被相应的 getter/setter 所引用,而无需在其它代码中做改动。

<?php

namespace MyApp\Models;

use InvalidArgumentException;
use Phalcon\Mvc\Model;

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 getCustomerId(): int
    {
        return (int) $this->inv_cst_id;
    }

    public function getStatus(): int
    {
        return (int) $this->inv_status_flag;
    }

    public function getTitle(): string
    {
        return (string) $this->inv_title;
    }

    public function getTotal(): float
    {
        return (float) $this->inv_total;
    }

    public function getCreatedAt(): string
    {
        return (string) $this->inv_created_at;
    }

    public function setCustomerId(int $customerId): Invoices
    {
        $this->inv_cst_id = $customerId;

        return $this;
    }

    public function setStatus(int $status): Invoices
    {
        $this->inv_status_flag = $status;

        return $this;
    }

    public function setTitle(string $title): Invoices
    {
        $this->inv_title = $title;

        return $this;
    }

    public function setTotal(float $total): Invoices
    {
        if ($total < 0) {
            throw new InvalidArgumentException(
                'Incorrect total'
            );
        }

        $this->inv_total = $total;

        return $this;
    }

    public function setCreatedAt(string $date): Invoices
    {
        $this->inv_created_at = $date;

        return $this;
    }
}

公共属性在开发中复杂度较低。但是,使用 getter/setter 可以大幅提高应用的可测试性、可扩展性和可维护性。你需要根据应用的需求来决定哪种策略更适合你。该 ORM 支持两种定义属性的方式。

注意

在使用 getter 和 setter 时,属性名中的下划线可能会带来问题。

注意

当采用 getter/setter 方法时,你需要将属性定义为protected.

如果你在属性名中使用了下划线,在使用魔法方法时依然要在 getter/setter 声明中使用驼峰命名法。(例如:$model->getPropertyName而不是$model->getProperty_name, $model->findByPropertyName而不是$model->findByProperty_name等)。

ORM 期望使用驼峰命名法,且通常会移除下划线。因此建议按照文档中的方式给属性命名。你可以使用列映射(如上文所述)以确保属性与其数据库字段正确对应。

记录到对象

每个模型实例代表表中的一行记录。你可以通过读取对象属性轻松访问记录数据。例如,对于具有以下记录的 'co_customers' 表:

mysql> select * from co_customers;
+--------+---------------+----------------+
| cst_id | cst_name_last | cst_name_first |
+--------+---------------+----------------+
|      1 | Vader         | Darth          |
|      2 | Skywalker     | Like           |
|      3 | Skywalker     | Leia           |
+--------+---------------+----------------+
3 rows in set (0.00 sec)

你可以通过主键查找特定记录,然后打印其名称:

<?php

use MyApp\Models\Customers;

// cst_id = 3
$customer = Customers::findFirst(3);

// 'Leia'
echo $customer->cst_name_first;

一旦记录加载到内存中,你可以对其数据进行修改并保存更改:

<?php

use MyApp\Models\Customers;

// cst_id = 3
$customer = Customers::findFirst(3);

$customer->cst_name_last = 'Princess';

$customer->save();

如你所见,无需使用原始 SQL 语句。Phalcon\Mvc\Model提供了对 Web 应用程序的高度数据库抽象,简化了数据库操作。

查找记录

Phalcon\Mvc\Model还提供了多种查询记录的方法。

find

该方法返回一个Phalcon\Mvc\Model\Resultset, Phalcon\Mvc\Model\Resultset\ComplexPhalcon\Mvc\Model\Resultset\Simple记录集合,即使结果只包含一条记录也是如此。

该方法接受多种参数用于检索数据:

<?php

use MyApp\Models\Customers;

$invoice = Invoices::findFirst('inv_id = 3');
你也可以传入一个带有WHERE子句的字符串。在上面的例子中,我们获取相同的记录,并指示 ORM 提供一个inv_cst_id = 3

最灵活的语法是传入一个包含不同参数的数组:

<?php

use MyApp\Models\Customers;

$invoice = Invoices::findFirst(
    [
        'inv_id = 3',
    ]
);
数组的第一个参数(没有键的)与上面示例中的处理方式相同(传递字符串)。数组还可以接受额外的参数,提供更多的选项来自定义查找操作。

findFirst

你也可以使用findFirst()方法来仅获取符合指定条件的第一条记录:

<?php

use MyApp\Models\Invoices;

$invoice = Invoices::findFirst();
调用findFirst不带参数调用时将返回 ORM 找到的第一条记录。通常,这是表中的第一条记录。

<?php

use MyApp\Models\Invoices;

// cst_id = 3
$invoice = Invoices::findFirst(3);
传递数字时,将使用对应的主键值来查询底层模型。如果未定义主键或存在复合主键,则无法获取任何结果。

<?php

use MyApp\Models\Invoices;

$invoice = Invoices::findFirst('inv_id = 3');
你也可以传入一个带有WHERE子句的字符串。在上面的例子中,我们获取相同的记录,并指示 ORM 提供一个inv_cst_id = 3

注意

如果表的主键不是数字类型,请使用条件查询。查看下面的示例。

$uuid = '5741bfd7-6870-40b7-adf6-cbacb515b9a9';
$invoice = Invoices::findFirst([
    'uuid = ?0',
    'bind' => [$uuid],
]);

// OR

$uuid = '5741bfd7-6870-40b7-adf6-cbacb515b9a9';
$invoice = Invoices::findFirst([
    'uuid = :primary:',
    'bind' => ['primary' => $uuid],
]);

注意

如果你的条件中不使用绑定参数,PHQL 将会在内部生成一个新的执行计划,因此会消耗更多内存。强烈建议使用绑定参数!

<?php


use MyApp\Models\Invoices;

$invoice = Invoices::findFirst('uuid = "5741bfd7-6870-40b7-adf6-cbacb515b9a9"');

参数

注意

强烈建议使用带有conditionsbind的数组语法,以防止 SQL 注入攻击,尤其是当条件来自用户输入时。更多信息请参考绑定参数` 章节。

两个find()findFirst()方法都接受一个关联数组来指定搜索条件。

<?php

use MyApp\Models\Invoices;

$invoices = Invoices::find(
    [
        'inv_cst_id = 3',
        'order' => 'inv_total desc'
    ]
);

你可以(也应该)使用conditionsbind数组元素来将参数绑定到查询。使用这种方法可以确保参数被正确绑定,从而减少 SQL 注入的可能性:

<?php

use MyApp\Models\Invoices;

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

可用的查询选项包括:

bind

Bind 通常与conditions一起使用,通过替换占位符并对值进行转义来增强安全性

<?php

use MyApp\Models\Invoices;

$invoice = Invoices::findFirst(
    [
        'conditions' => 'inv_id = :inv_id:',
        'bind'       => [
            'inv_id' => 3,
        ],
    ]
);

bindTypes

在绑定参数时,你可以使用此选项对绑定参数进行额外的类型转换,进一步提升查询安全性。

<?php

use MyApp\Models\Invoices;
use Phalcon\Mvc\Model\Column;

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

cache

缓存结果集,减少对关系型系统的持续访问。

<?php

use MyApp\Models\Invoices;

$invoices = Invoices::find(
    [
        'conditions' => 'inv_cst_id = :cst_id:',
        'bind'       => [
            'cst_id' => 3,
        ],
        'cache'      => [
            'key'      => 'customer.3',
            'lifetime' => 84600,
        ],
        'order'      => 'inv_total desc',
    ]
);

columns

返回模型中的特定列。

注意

使用这个选项时,将返回一个不完整的对象,因此你不能调用诸如update(), getRelated()等等。

<?php

use MyApp\Models\Invoices;

$invoices = Invoices::find(
    [
        'columns'    => [
            'inv_id',
            'total' => 'inv_total'
        ],
        'conditions' => 'inv_cst_id = :cst_id:',
        'bind'       => [
            'cst_id' => 3,
        ],
    ]
);

如果数组元素中仅设置了值,则 columns 数组可以直接返回列。但如果指定了键,则该键将作为该字段的别名。在上述示例中,cst_name_first被别名为first.

conditions

查找操作的搜索条件。用于提取满足指定条件的记录。默认情况下,Phalcon\Mvc\Model假设第一个参数是条件。

<?php

use MyApp\Models\Invoices;

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

for_update

使用此选项时,Phalcon\Mvc\Model会读取最新的可用数据,并在其读取的每一行上设置排它锁

<?php

use MyApp\Models\Invoices;

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

group

允许跨多条记录收集数据,并按一个或多个列对结果进行分组'group' => 'name, status'

<?php

use MyApp\Models\Invoices;

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

hydration

设置填充策略,以确定如何表示结果中的每条记录

<?php

use MyApp\Models\Invoices;
use Phalcon\Mvc\Model\Resultset;

$invoices = Invoices::find(
    [
        'conditions' => 'inv_cst_id = :cst_id:',
        'bind'       => [
            'cst_id' => 3,
        ],
        'hydration' => Resultset::HYDRATE_OBJECTS,
    ]
);

limit

将查询结果限制到某个范围

<?php

use MyApp\Models\Invoices;

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

offset

将查询结果偏移指定数量

<?php

use MyApp\Models\Invoices;

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

order

用于对结果集排序。使用一个或多个逗号分隔的字段。

<?php

use MyApp\Models\Invoices;

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

shared_lock

使用此选项时,Phalcon\Mvc\Model会读取最新的可用数据,并在其读取的每一行上设置共享锁

<?php

use MyApp\Models\Invoices;

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

query

如果你愿意,还可以使用面向对象的方式创建查询,而不是使用参数数组:

<?php

use MyApp\Models\Invoices;

$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')
    ->execute()
;

该静态方法query()返回一个Phalcon\Mvc\Model\Criteria对象,适用于 IDE 自动补全功能。

所有查询在内部都被处理为PHQL查询。PHQL 是一种高级的、面向对象的、类似 SQL 的语言。该语言提供了更多功能以执行查询,例如连接其他模型、分组记录、聚合等。

findBy*

您可以使用findBy<property-name>()方法的参数相同。此方法扩展了find()上述方法的功能。它允许你通过在方法本身中使用属性名并传入一个参数来快速地从表中执行 select 查询,该参数包含你想在该列中搜索的数据。

对于以下模型:

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

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

我们有这些属性inv_cst_id, inv_id, inv_status_flag, inv_title, inv_created_at。如果我们想查找所有inv_total = 100的发票,我们可以使用:

<?php

use MyApp\Models\Invoices;

$invoices = Invoices::find(
    [
        'conditions'  => 'inv_total = :total:',
        'bind'        => [
            'total' => 100,
        ],
    ]
);

但我们也可以使用:

<?php

use MyApp\Models\Invoices;

$invoices = Invoices::findByInvTotal(100);

注意

如果属性名中包含下划线,则会将其转换为驼峰式命名。inv_total变成了InvTotal

你还可以将参数作为数组传递给第二个参数。这些参数与你可以传递给find方法设置容器。

<?php

use MyApp\Models\Invoices;

$invoices = Invoices::findByInvTotal(
    100,
    [
        'order' => `inv_cst_id, inv_created_at`
    ]
);

findFirstBy*

最后,您可以使用findFirstBy<property-name>()方法的参数相同。此方法扩展了findFirst()上述方法的功能。它允许你通过在方法本身中使用属性名并传入一个参数来快速地从表中执行 select 查询,该参数包含你想在该列中搜索的数据。

对于以下模型:

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Guestbook extends Model
{
    public $id;
    public $email;
    public $name;
    public $text;
}

我们有这些属性id, email, nametext。如果我们想查找访客留言簿中Darth Vader的条目,我们可以:

<?php

use MyApp\Models\Guestbook;

$guest = Guestbook::findFirst(
    [
        'conditions'  => 'name = :name:',
        'bind'        => [
            'name' => 'Darth Vader',
        ],
    ]
);

但我们也可以使用:

<?php

use MyApp\Models\Guestbook;

$name  = 'Darth Vader';
$guest = Guestbook::findFirstByName($name);

注意

注意我们在方法调用中使用了Name并传入了变量$name,其中包含我们要在表中查找的名称。同时请注意,当我们找到查询匹配项时,所有其他属性也都可以供我们使用。

模型结果集

虽然findFirst()在有数据返回时直接返回被调用类的一个实例,find()方法返回一个Phalcon\Mvc\Model\Resultset\Simple却不是这样。这是一个封装了结果集所需所有功能(如定位、遍历、计数等)的对象。

这些对象比标准数组更强大。其中最强大的特性之一是Phalcon\Mvc\Model\Resultset在任何时刻内存中只保存一条记录。这在处理大量数据时对内存管理非常有帮助。

遍历结果集的一些示例包括:

<?php

use MyApp\Models\Invoices;

$invoices = Invoices::find();

// foreach
foreach ($invoices as $invoice) {
    echo $invoice->inv_title, PHP_EOL;
}

// while
$invoices->rewind();
while ($invoices->valid()) {
    $invoice = $invoices->current();

    echo $invoice->inv_title, PHP_EOL;

    $invoices->next();
}

// count
echo count($invoices);
echo $invoices->count();

// seek
$invoices->seek(2);
$invoice = $invoices->current();

// array
$invoice = $invoices[5];

// array - isset
if (true === isset($invoices[3])) {
   $invoice = $invoices[3];
}

// First
$invoice = $invoices->getFirst();

// Last
$invoice = $invoices->getLast();

Phalcon 的结果集模拟了可滚动游标。你可以通过访问其位置或移动内部指针到特定位置来获取任意一行记录。

注意

一些数据库系统不支持可滚动游标。这迫使 Phalcon 重新执行查询,将游标重置到开头并获取请求位置的记录。同样地,如果多次遍历一个结果集,则必须执行相同次数的查询。

将大型查询结果存储在内存中会消耗许多资源。但你可以指示 Phalcon 按行块方式抓取数据,从而减少许多情况下的重新执行需求。你可以通过设置orm.resultset_prefetch_records设置值来实现这一点。这可以在php.ini中进行,也可以在模型的setup()中进行。特性章节中可以找到有关此内容的更多信息。

请注意,结果集可以被序列化并存储在缓存后端中。Phalcon\Cache\Cache可以帮助完成此任务。然而,序列化数据会导致Phalcon\Mvc\Model从数据库检索所有数据到一个数组中,因此在此过程中会消耗更多的内存。

<?php

use MyApp\Models\Invoices;

$invoices = Invoices::find();

file_put_contents(
    'invoices.cache',
    serialize($invoices)
);

$invoices = unserialize(
    file_get_contents('invoices.cache')
);

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

自定义结果集

有时应用程序逻辑需要在从数据库中检索数据时对其做额外的处理。以前的做法通常是扩展模型,并将功能封装在模型类或 trait 中,通常返回一个经过转换的数据数组给调用者。

使用自定义结果集后,你不再需要这样做。自定义结果集将封装那些原本放在模型中的功能,并可以被其他模型复用,从而保持代码不重复(DRY)。这样一来,find()方法将不再返回默认的Phalcon\Mvc\Model\Resultset结果集,而是返回自定义的结果集。Phalcon 允许你通过在模型中使用getResultsetClass()来实现这一点。

首先,我们需要定义结果集类:

<?php

namespace MyApp\Mvc\Model\Resultset;

use \Phalcon\Mvc\Model\Resultset\Simple;

class Custom extends Simple
{
    public function calculate() {
        // ....
    }
}

在模型中,我们在getResultsetClass()的组件中访问你的会话,如下所示:

<?php

namespace Phalcon\Test\Models\Statistics;

use MyApp\Mvc\Model\Resultset\Custom;
use Phalcon\Mvc\Model;

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

    public function getResultsetClass()
    {
        return Custom::class;
    }
}

中设置该类,最终,在你的代码中会有类似如下内容:

<?php

use MyApp\Models\Invoices;

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

$calculated = $invoices->calculate();

过滤结果集

过滤数据最有效的方法是设置一些搜索条件,数据库将利用表上的索引更快地返回数据。此外,Phalcon 还允许你使用 PHP 来过滤数据:

<?php

$invoices = Invoices::find();

$invoices = $invoices->filter(
    function ($invoice) {
        if (1 === $invoice->inv_status_flag) {
            return $invoice;
        }
    }
);

上面的例子将仅从我们的表中返回已支付的发票(inv_status_flag = 1);

绑定参数

Phalcon\Mvc\Model中也支持绑定参数。建议你使用这种机制来防止代码遭受 SQL 注入攻击。支持使用stringinteger占位符。

注意

当使用integer使用? (?0, ?1)。当使用string占位符时,你必须将字符串包含在: (:name:, :total:)。

一些示例:

<?php

use MyApp\Models\Invoices;

$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 = ?0 AND ' .
                         'inv_total > ?1',
        'bind'        => [
            0 => '%ACME%',
            1 => 1000,
        ],
    ]
);

$invoices = Invoices::find(
    [
        'conditions'  => 'inv_title = ?0 AND ' .
                         'inv_total > :total:',
        'bind'        => [
            0       => '%ACME%',
            'total' => 1000,
        ],
    ]
);

字符串会使用PDO自动转义。此函数会考虑连接的字符集,因此建议在连接参数或数据库配置中定义正确的字符集,因为错误的字符集会在存储或检索数据时产生不良效果。

此外,你还可以设置参数bindTypes,这允许根据参数的数据类型定义应如何绑定参数:

<?php

use MyApp\Models\Invoices;
use Phalcon\Db\Column;

$parameters = [
    'title' => '%ACME%',
    'total' => 1000,
];

$types = [
    'title' => Column::BIND_PARAM_STR,
    'total' => Column::BIND_PARAM_INT,
];

$invoices = Invoices::find(
    [
        'conditions'  => 'inv_title LIKE :title: AND ' .
                         'inv_total > :total:',
        'bind'        => $parameters,
        'bindTypes'   => $types,
    ]
);

注意

由于默认的绑定类型是Phalcon\Db\Column::BIND_PARAM_STR,如果你的所有列都是字符串,则无需指定 'bindTypes' 参数。

你还可以在参数中绑定数组,尤其是在使用INSQL 关键字时。

注意

数组应使用零开始的索引,并且不能缺少元素

<?php

use MyApp\Models\Invoices;

$customerIds = [1, 3, 4]; // $array: [[0] => 1, [1] => 2, [2] => 4]

$invoices = Invoices::find(
    [
        'conditions'  => 'inv_cst_id IN ({customerId:array})',
        'bind'        => [
            'customerId' => $customerIds,
        ],
    ]
);

unset($customerIds[1]);  // $array: [[0] => 1, [2] => 4]

$customerIds = array_values($customerIds);  // $array: [[0] => 1, [1] => 4]

$invoices = Invoices::find(
    [
        'conditions'  => 'inv_cst_id IN ({customerId:array})',
        'bind'        => [
            'customerId' => $customerIds,
        ],
    ]
);

注意

绑定参数适用于所有查询方法,例如find()findFirst()也适用于计算方法,比如count(), sum(), average()等等。

如果你正在使用查询器(finders)例如:find(), findFirst()等等,在使用字符串语法作为第一个参数时可以注入绑定参数,而不是使用conditions数组元素。另外,当使用findFirstBy*参数会自动绑定。

<?php

use MyApp\Models\Invoices;

$invoices = Invoices::find(
    'inv_total > ?0',
    'bind'        => [
        1000,
    ]
);

$invoices = Invoices::findByInvTotal(1000);

获取前/后操作

在某些情况下,我们需要在数据从数据库中获取后进行处理,以便模型包含应用层所需的内容。如事件文档所示,模型可以充当监听器,因此我们可以在模型中实现一些事件方法。

这些方法包括beforeSave, afterSaveafterFetch如下例所示。当数据从数据库填充到模型之后,将立即运行afterFetch方法。我们可以利用此方法修改或转换模型中的数据。

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
    public $inv_id;
    public $inv_cst_id;
    public $inv_status_flag;
    public $inv_total;
    public $status;

    public function beforeSave()
    {
        $this->status = join(',', $this->status);
    }

    public function afterFetch()
    {
        $this->status = explode(',', $this->status);
    }

    public function afterSave()
    {
        $this->status = explode(',', $this->status);
    }
}
在上面的示例中,我们从数据库接收一个逗号分隔的字符串,并将其转换为数组以便应用程序使用。之后,你可以向数组添加或删除元素;在模型保存之前,explode it to an array so that it can be used from our application. After that, you can add or remove elements in the array; before the model saves it, implode将会被调用,用于把数组以字符串形式存储到数据库。

如果你使用getter/setter替代或者结合公共属性一起使用,则可以在字段被访问时初始化它:

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
    public $inv_id;
    public $inv_cst_id;
    public $inv_status_flag;
    public $inv_total;
    public $status;

    public function getStatus()
    {
        return explode(',', $this->status);
    }
}

计算

计算(或聚合)是数据库系统常见功能的辅助工具,例如COUNT, SUM, MAX, MINAVG. Phalcon\Mvc\Model允许通过暴露的方法直接使用这些功能。

COUNT

<?php

$rowcount = Invoices::count();

// inv_cst_id = 3
$rowcount = Invoices::count(
    [
        'inv_cst_id = ?0',
        'bind'        => [
            3,
        ],
    ]
);

我们还可以使用group参数对我们结果进行分组。计数结果显示在返回集合的每个对象的rowcount属性中。

<?php

$group = Invoices::count(
    [
        'group' => 'inv_cst_id',
    ]
);
foreach ($group as $row) {
   echo 'Count: ', $row->rowcount, ' - Customer: ', $row->inv_cst_id;
}

$group = Invoices::count(
    [
        'group' => 'inv_cst_id',
        'order' => 'rowcount',
    ]
);

SUM

<?php

$total = Invoices::sum(
    [
        'column' => 'inv_total',
    ]
);

$total = Invoices::sum(
    [
        'column'     => 'total',
        'conditions' => 'inv_cst_id = ?0',
        'bind'       => [
            3
        ]
    ]
);
你也可以对结果进行分组。计数结果显示在返回集合的每个对象的sumatory属性中。

<?php

$group = Invoices::sum(
    [
        'column' => 'inv_total',
        'group'  => 'inv_cst_id',
    ]
);

foreach ($group as $row) {
   echo 'Customer: ', $row->inv_cst_id, ' - Total: ', $row->sumatory;
}

$group = Invoices::sum(
    [
        'column' => 'inv_total',
        'group'  => 'inv_cst_id',
        'order'  => 'sumatory DESC',
    ]
);

AVERAGE

<?php

$average = Invoices::average(
    [
        'column' => 'inv_total',
    ]
);

$average = Invoices::average(
    [
        'column'     => 'inv_total',
        'conditions' => 'inv_status_flag = ?0',
        'bind'       => [
            0
        ]
    ]
);

MAX - MIN

<?php

$max = Invoices::maximum(
    [
        'column' => 'inv_total',
    ]
);

$max = Invoices::maximum(
    [
        'column'     => 'inv_total',
        'conditions' => 'inv_status_flag = ?0',
        'bind'       => [
            0
        ],
    ]
);

$min = Invoices::minimum(
    [
        'column' => 'inv_total',
    ]
);

$min = Invoices::minimum(
    [
        'column'     => 'inv_total',
        'conditions' => 'inv_status_flag = ?0',
        'bind'       => [
            0
        ],
    ]
);

创建 - 更新

The Phalcon\Mvc\Model::save()方法允许你根据记录是否已存在于与模型关联的表中来创建或更新记录。save 方法由Phalcon\Mvc\Model的 create 和 update 方法在内部调用。为了使其按预期工作,实体中必须正确定义主键,以确定是应创建还是更新记录。

该方法还会执行模型中定义的相关验证器、虚拟外键和事件:

<?php

use MyApp\Models\Invoices;

$invoice = new Invoices();

$invoice->inv_cst_id      = 1;
$invoice->inv_status_flag = 1;
$invoice->inv_title       = 'Invoice for ACME Inc.';
$invoice->inv_total       = 100;
$invoice->inv_created_at  = '2019-12-25 01:02:03';

$result = $invoice->save();

if (false === $result) {

    echo 'Error saving Invoice: ';

    $messages = $invoice->getMessages();

    foreach ($messages as $message) {
        echo $message . PHP_EOL;
    }
} else {

    echo 'Record Saved';
}

您还可以使用assign()方法并传入一个包含field => value元素的数组,以避免手动逐列赋值。Phalcon\Mvc\Model将检查传递进来的数组中的列是否有对应的setter实现,并优先使用它们,而不是直接给属性赋值:

<?php

use MyApp\Models\Invoices;

$invoice = new Invoices();

$invoice->assign(
    [
        'inv_cst_id'      => 1,
        'inv_status_flag' => 1,
        'inv_title'       => 'Invoice for ACME Inc.',
        'inv_total'       => 100,
        'inv_created_at'  => '2019-12-25 01:02:03',
    ]
);

$result = $invoice->save();

直接分配的值或通过属性数组分配的值会根据相关属性的数据类型进行转义/清理。因此,你可以传递一个不安全的数组而不必担心可能的 SQL 注入问题:

<?php

use MyApp\Models\Invoices;

$invoice = new Invoices();

$invoice->assign($_POST);

$result = $invoice->save();

注意

如果没有采取防护措施,批量赋值可能会让攻击者设置任意数据库列的值。只有在你希望允许用户插入或更新模型中的每列时才使用此功能,即使这些字段不在提交的表单中。

你可以在assign中设置一个额外参数,用以指定在进行批量赋值时仅考虑的字段白名单:

<?php

use MyApp\Models\Invoices;

$invoice = new Invoices();

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

$result = $invoice->save();

注意

在非常繁忙的应用程序中,你可以使用createupdate来执行相应的操作。通过使用这两个方法代替 save,我们可以确保数据一定会保存到数据库或不会保存进去,因为它们会在create如果记录已经存在时抛出异常,并在update如果记录不存在时抛出异常。

<?php

use MyApp\Models\Invoices;

$invoice = new Invoices();

$invoice->inv_id          = 1234;
$invoice->inv_cst_id      = 1;
$invoice->inv_status_flag = 1;
$invoice->inv_title       = 'Invoice for ACME Inc.';
$invoice->inv_total       = 100;
$invoice->inv_created_at  = '2019-12-25 01:02:03';

$result = $invoice->update();

if (false === $result) {

    echo 'Error saving Invoice: ';

    $messages = $invoice->getMessages();

    foreach ($messages as $message) {
        echo $message . PHP_EOL;
    }
} else {

    echo 'Record Updated';

}

方法createupdate同样接受一个值数组作为参数。

删除

The delete()方法允许你删除一条记录。它返回一个布尔值表示成功或失败。

<?php

use MyApp\Models\Invoices;

$invoice = Invoices::findFirst(
    [
        'conditions' => 'inv_id = :id:',
        'bind'       => [
            'id' => 4,
        ]
    ]
);

if (false !== $invoice) {
    if (false === $invoice->delete()) {
        $messages = $invoice->getMessages();

        foreach ($messages as $message) {
            echo $message . PHP_EOL;
        }
    } else {

        echo 'Record Deleted';
    }
}

你还可以通过使用foreach:

<?php

use MyApp\Models\Invoices;

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

foreach ($invoices as $invoice) {
    if (false === $invoice->delete()) {
        $messages = $invoice->getMessages();

        foreach ($messages as $message) {
            echo $message . PHP_EOL;
        }
    } else {

        echo 'Record Deleted';
    }
}

注意

遍历结果集来删除多条记录。请查看事务章节了解如何在一个循环中通过一次操作删除所有记录。

数据填充模式(Hydration Modes)

如前所述,结果集是一系列完整对象的集合。这意味着每个返回的结果都是一个代表数据库中某一行的对象。这些对象可以被修改,并可以在之后保存到数据库以持久化更改。

但是,有时你需要以只读的方式获取数据,例如在单纯查看数据的情况下。在这种情况下,改变记录返回方式是有用的,这有助于节省资源并提高性能。用来表示结果集中返回对象的策略称为数据填充策略。hydration.

Phalcon 提供了三种填充数据的方式:

结果 模式
数组 Phalcon\Mvc\Model\Resultset::HYDRATE_ARRAYS
对象 Phalcon\Mvc\Model\Resultset::HYDRATE_OBJECTS
记录 Phalcon\Mvc\Model\Resultset::HYDRATE_RECORDS

默认的数据填充模式是返回记录(HYDRATE_RECORDS)。我们可以轻松地更改数据填充模式以返回数组或对象。将数据填充模式更改为非HYDRATE_RECORDS以外的模式将返回与数据库断开连接的对象(或数组),即我们不能对这些对象执行任何操作,例如save(), create(), delete()等等。

<?php

use MyApp\Models\Invoices;
use Phalcon\Mvc\Model\Resultset;

$invoices = Invoices::findFirst(
    [
        'conditions' => 'inv_id = :id:',
        'bind'       => [
            'id' => 4,
        ]
    ]
);

// Array
$invoices->setHydrateMode(
    Resultset::HYDRATE_ARRAYS
);

foreach ($invoices as $invoice) {
    echo $invoice['inv_total'], PHP_EOL;
}

// \stdClass
$invoices->setHydrateMode(
    Resultset::HYDRATE_OBJECTS
);

foreach ($invoices as $invoice) {
    echo $invoice->inv_total, PHP_EOL;
}

// Invoices
$invoices->setHydrateMode(
    Resultset::HYDRATE_RECORDS
);

foreach ($invoices as $invoice) {
    echo $invoice->inv_total, PHP_EOL;
}

数据填充模式同样可以作为find, findFirst, findFirstBy*等方法的参数传递:

<?php

use MyApp\Models\Invoices;
use Phalcon\Mvc\Model\Resultset;

$invoices = Invoices::findFirst(
    [
        'hydration'  => Resultset::HYDRATE_ARRAYS,
        'conditions' => 'inv_id = :id:',
        'bind'       => [
            'id' => 4,
        ],
    ]
);

foreach ($invoices as $invoice) {
    echo $invoice['inv_total'], PHP_EOL;
}

表前缀(Table Prefixes)

如果你想让你所有的表都有某个特定的前缀而无需在所有模型中设置源表名,你可以使用Phalcon\Mvc\Model\Manager并且使用setModelPrefix():

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model\Manager;
use Phalcon\Mvc\Model;

class Invoices extends Model
{

}

$manager = new Manager();

$manager->setModelPrefix('co_');

$invoices = new Invoices(null, null, $manager);

echo $invoices->getSource(); // will return co_invoices

标识列(Identity Columns)

某些模型可能具有标识列。这些列通常就是映射表的主键。Phalcon\Mvc\Model可以识别标识列并在生成的INSERTSQL 语句中省略它,从而允许数据库系统正确地为该字段生成新值。创建新记录后,标识字段始终会记录数据库系统为其生成的值:

<?php

$invoice->save();

echo $invoice->inv_id; // 4

Phalcon\Mvc\Model尝试从每个表中识别身份列。但是,根据不同的数据库系统,这些列可能是序列列,例如 PostgreSQL 中的情况,或者auto_increment在 MySQL 情况下的列。

PostgreSQL 使用序列为 主键 自动生成数字值。Phalcon 会尝试从序列中获取生成的值table_field_seq,例如:co_invoices_id_seq。如果序列名称不同,你可以始终在模型中使用getSequenceName()方法,告知 Phalcon 需要为主键使用的序列:

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
    public function getSequenceName()
    {
        return 'invoices_sequence_name';
    }
}

忽略列

根据你如何在数据库中实现业务规则或模型规则,一些字段在数据库操作中可能会被忽略。例如,如果我们模型中有某个inv_created_date字段,我们可以指示数据库系统在其中注入当前时间戳:

CREATE TABLE co_invoices (
    // ...
    inv_created_at datetime DEFAULT CURRENT_TIMESTAMP
)

上面这段代码(针对 MySQL)指示 RDBMS 在创建记录时为inv_created_at字段分配当前时间戳。因此,在创建记录时我们可以省略这个字段。同样地,在更新记录时我们也可能希望忽略某些字段。

要完成这个任务,我们可以使用skipAttributes(对任何操作都有效)、skipAttributesOnCreate(创建操作) 或skipAttributesOnUpdate(更新操作)

来告诉Phalcon\Mvc\Model总是在创建和/或更新记录时忽略某些字段,以便将值的赋值交由触发器或默认值来完成:

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
    public function initialize()
    {
        $this->skipAttributes(
            [
                'inv_total',
                'inv_created_at',
            ]
        );

        $this->skipAttributesOnCreate(
            [
                'inv_created_at',
            ]
        );

        $this->skipAttributesOnUpdate(
            [
                'inv_modified_at',
            ]
        );
    }
}

如果你想在你的模型属性中设置默认值(比如inv_created_at),你可以使用Phalcon\Db\RawValue:

<?php

use MyApp\Models\Invoices;
use Phalcon\Db\RawValue;

$invoice = new Invoices();
$invoice->inv_id          = 1234;
$invoice->inv_cst_id      = 1;
$invoice->inv_status_flag = 1;
$invoice->inv_title       = 'Invoice for ACME Inc.';
$invoice->inv_total       = 100;
$invoice->inv_created_at  = new RawValue('default');

$invoice->create();

我们也可以利用模型中的beforeCreate事件来在此处分配默认值:

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;
use Phalcon\Db\RawValue;

class Invoices extends Model
{
    public function beforeCreate()
    {
        $this->inv_created_at = new RawValue('default');
    }
}

注意

切勿使用Phalcon\Db\RawValue来分配外部数据(如用户输入)或可变数据。这些字段的值在将参数绑定到查询时会被忽略。因此,它可能被用于 SQL 注入攻击。

动态更新

注意

现在动态更新默认是启用状态,你可以通过修改 php.ini 文件中的 "phalcon.orm.dynamic_update" 参数或使用 ini_set 函数来禁用它。

如果全局范围内的动态更新是禁用的,则默认生成的 SQLUPDATE语句将包含模型中定义的所有列(完整的全字段 SQL 更新语句)。你可以更改特定模型以启用动态更新,在这种情况下,只有发生变更的字段才会用于生成最终的 SQL 语句。

在某些情况下,这可以通过减少应用程序和数据库服务器之间的流量来提升性能,尤其是在目标表中存在 blob/text 字段时:

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

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

列映射

ORM 支持独立的列映射功能,它允许开发者在模型中使用与数据表中不相同的列名。Phalcon 会识别新的列名,并相应地重命名它们,以匹配数据库中的相应列。当需要在不担心代码中所有查询的情况下重命名数据库字段时,这是一个非常有用的功能。

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

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;

    public function columnMap()
    {
        return [
            'inv_id'          => 'id',
            'inv_cst_id'      => 'customerId',
            'inv_status_flag' => 'status',
            'inv_title'       => 'title',
            'inv_total'       => 'total',
            'inv_created_at'  => 'createdAt',
        ];
    }
}

注意

在列映射中定义的数组中,键是数据库中字段的实际名称,值则是虚拟字段,我们可以在代码中使用这些字段

现在我们可以在代码中使用这些虚拟字段(或列映射):

<?php

use MyApp\Models\Invoices;

$invoice = Invoices::findFirst(
    [
        'conditions' => 'inv_id = :id:',
        'bind'       => [
            'id' => 4,
        ]
    ]
);

echo $invoice->customerId, PHP_EOL,
     $invoice->total, PHP_EOL,
     $invoice->createdAt, PHP_EOL;

$invoices = Invoices::find(
    [
        'order' => 'createdAt DESC',
    ]
);

foreach ($invoices as $invoice) {
    echo $invoice->customerId, PHP_EOL,
         $invoice->total, PHP_EOL,
         $invoice->createdAt, PHP_EOL;
}

$invoice = new Invoices();

$invoice->customerId = 1;
$invoice->status     = 1;
$invoice->title      = 'Invoice for ACME Inc.';
$invoice->total      = 100;
$invoice->createdAt  = '2019-12-25 01:02:03';

$invoice->save();

注意事项

在重命名列时,请考虑以下几点:

  • 对关系/验证器中属性的引用必须使用虚拟名称
  • 引用实际列名会导致 ORM 抛出异常

独立列映射允许你:

  • 使用自己的约定编写应用程序
  • 在代码中去除供应商前缀/后缀
  • 更改列名而无需更改应用程序代码

记录快照

特定模型可以设置为在查询时维护一条记录的快照。你可以使用此功能来实现审计,或者仅仅为了知道相对于数据库中的数据,模型中的哪些字段发生了变化。

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

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

当激活此功能时,应用程序会消耗更多的内存,用于跟踪从数据库中获取的原始值。在启用了此功能的模型中,你可以按如下方式检查哪些字段发生了改变:

<?php

use MyApp\Models\Invoices;

$invoice = Invoices::findFirst();

$invoice->inv_total = 120;

var_dump($invoice->getChangedFields()); // ['inv_total']

var_dump($invoice->hasChanged('inv_total')); // true

var_dump($invoice->hasChanged('inv_cst_id')); // false

快照会在模型创建/更新时进行更新。使用hasUpdated()getUpdatedFields()可以检查 create/save/update 后是否有字段更新,但如果执行getChangedFields()的调用afterUpdate(), afterSave()afterCreate().

你可以通过使用以下方法禁用此功能:

<?php

Phalcon\Mvc\Model::setup(
    [
        'updateSnapshotOnSave' => false,
    ]
);
或者如果你愿意,可以在你的php.ini

phalcon.orm.update_snapshot_on_save = 0

使用此功能将产生以下效果:

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

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;

    public function initialize()
    {
        $this->keepSnapshots(true);
    }
}

$invoice = new Invoices();

$invoice->inv_id          = 1234;
$invoice->inv_cst_id      = 1;
$invoice->inv_status_flag = 1;
$invoice->inv_title       = 'Invoice for ACME Inc.';
$invoice->inv_total       = 100;
$invoice->inv_created_at  = '2019-12-25 01:02:03';

$invoice->create();

var_dump(
    $invoice->getChangedFields() // []
);

$invoice->inv_total = 120;

var_dump(
    $invoice->getChangedFields() // ['inv_total']
);

$invoice->update();

var_dump(
    $invoice->getChangedFields() // []
);

getUpdatedFields()将正确返回已更新的字段,或者如上所述,你可以通过设置相应的 ini 值返回之前的行为。

事件

如前所述,Phalcon\Mvc\Model充当一个事件监听器。因此,模型正在监听的所有事件都可以作为方法在模型本身中实现。你可以查阅事件文档了解更多信息。

支持的事件有:

  • afterCreate
  • afterDelete
  • afterFetch
  • afterSave
  • afterUpdate
  • afterValidation
  • afterValidationOnCreate
  • afterValidationOnUpdate
  • beforeDelete
  • beforeCreate
  • beforeSave
  • beforeUpdate
  • beforeValidation
  • beforeValidationOnCreate
  • beforeValidationOnUpdate
  • notDeleted
  • notSaved
  • onValidationFails
  • prepareSave
  • validation
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;
use Phalcon\Messages\Message as Message;

class Invoices extends Model
{
    public function beforeSave()
    {
        if (0 === $this->inv_status_flag) {
            $message = new Message(
                'Sorry, an invoice cannot be unpaid'
            );

            $this->appendMessage($message);
        }
    }
}

事务

事务当我们需要在同一个操作中插入或更新多个表的数据时,事务是确保数据完整性的必要手段。Phalcon\Mvc\Model暴露了setTransaction方法允许你将每个模型绑定到一个正在进行的事务。

<?php

use MyApp\Models\Customers;
use MyApp\Models\Invoices;
use Phalcon\Mvc\Model\Transaction\Manager;
use Phalcon\Mvc\Model\Transaction\Failed;

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

    $customer = new Customers();
    $customer->setTransaction($transaction);
    $customer->cst_name_last  = 'Vader';
    $customer->cst_name_first = 'Darth';

    if (false === $customer->save()) {
        $transaction->rollback('Cannot save Customer');
    }

    $invoice = new Invoices();
    $invoice->setTransaction($transaction);

    $invoice->inv_cst_id      = $customer->cst_id;
    $invoice->inv_status_flag = 1;
    $invoice->inv_title       = 'Invoice for ACME Inc.';
    $invoice->inv_total       = 100;
    $invoice->inv_created_at  = '2019-12-25 01:02:03';

    if (false === $invoice->save()) {
        $transaction->rollback('Cannot save record');
    }

    $transaction->commit();
} catch (Failed $ex) {
    echo 'ERROR: ', $ex->getMessage();
}

你还可以在事务中包含查询器结果,甚至同时运行多个事务:

<?php

use MyApp\Models\Customers;
use MyApp\Models\Invoices;
use Phalcon\Mvc\Model\Transaction\Manager;
use Phalcon\Mvc\Model\Transaction\Failed;

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

    $customer = new Customers();
    $customer->setTransaction($transaction);
    $customer->cst_name_last  = 'Vader';
    $customer->cst_name_first = 'Darth';

    if (false === $customer->save()) {
        $transaction->rollback('Cannot save Customer');
    }

    $average = Invoices::average(
        [
            Model::TRANSACTION_INDEX => $transaction,
            'column'     => 'inv_total',
            'conditions' => 'inv_cst_id = :customerId:',
            'bind'       => [
                'customerId' => 3,
            ],
        ]
    );

    $invoice = new Invoices();
    $invoice->setTransaction($transaction);

    $invoice->inv_cst_id      = $customer->cst_id;
    $invoice->inv_status_flag = 1;
    $invoice->inv_title       = 'Invoice for ACME Inc.';
    $invoice->inv_total       = 100 + $average;
    $invoice->inv_created_at  = '2019-12-25 01:02:03';

    if (false === $invoice->save()) {
        $transaction->rollback('Cannot save record');
    }

    $transaction->commit();
} catch (Failed $ex) {
    echo 'ERROR: ', $ex->getMessage();
}

更改架构(schema)

如果某个模型所映射的表位于不同于默认架构的位置,你可以使用setSchema()来指向正确的表位置:

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

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

多数据库

Phalcon模型默认连接到依赖注入容器中定义的相同数据库连接db服务)。但是,您可能需要将特定模型连接到不同的连接,这些连接可能是连接到不同数据库的连接。

我们可以在每个模型的initialize方法中定义哪个模型连接到哪个数据库:

<?php

use Phalcon\Di\FactoryDefault;
use Phalcon\Db\Adapter\Pdo\Mysql;
use Phalcon\Db\Adapter\Pdo\PostgreSQL;

$container = new FactoryDefault();

// MySQL
$container->set(
    'dbMysql',
    function () {
        return new Mysql(
            [
                'host'     => 'localhost',
                'username' => 'root',
                'password' => 'secret',
                'dbname'   => 'tutorial',
            ]
        );
    },
    true
);

// PostgreSQL
$container->set(
    'dbPostgres',
    function () {
        return new PostgreSQL(
            [
                'host'     => 'localhost',
                'username' => 'postgres',
                'password' => '',
                'dbname'   => 'tutorial',
            ]
        );
    }
);

并且在initialize()方法:

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

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

关于数据库连接还提供了额外的灵活性。您可以为read操作指定一个不同的连接,以及为write操作指定另一个不同的连接。当您有可用于读取操作的内存数据库,以及用于write操作的不同且更强大的数据库时,这特别有用。

您可以设置两个不同的连接,并在每个模型中透明地使用各自的数据库

<?php

use Phalcon\Di\FactoryDefault;
use Phalcon\Db\Adapter\Pdo\Mysql;
use Phalcon\Db\Adapter\Pdo\PostgreSQL;

$container = new FactoryDefault();

// MySQL - read
$container->set(
    'mysqlRead',
    function () {
        return new Mysql(
            [
                'host'     => '10.0.4.100',
                'username' => 'root',
                'password' => 'secret',
                'dbname'   => 'tutorial',
            ]
        );
    },
    true
);

// MySQL - write
$container->set(
    'mysqlWrite',
    function () {
        return new Mysql(
            [
                'host'     => '10.0.4.200',
                'username' => 'root',
                'password' => 'secret',
                'dbname'   => 'tutorial',
            ]
        );
    },
    true
);

并且在initialize()方法:

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
    public function initialize()
    {
        $this->setReadConnectionService('mysqlRead');

        $this->setWriteConnectionService('mysqlWrite');
    }
}

ORM还提供了水平分片功能,允许您根据查询条件实现shard选择:

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
    /**
     * Dynamically selects a shard
     *
     * @param array $intermediate
     * @param array $bindParams
     * @param array $bindTypes
     *
     * @return Phalcon\Db\Adapter\AdapterInterface
     */
    public function selectReadConnection(
        array $intermediate, 
        array $bindParams, 
        array $bindTypes
    ) {
        if (true === isset($intermediate['where'])) {
            $conditions = $intermediate['where'];

            if ($conditions['left']['name'] === 'id') {
                $id = $conditions['right']['value'];

                if ($id > 0 && $id < 10000) {
                    return $this->getDI()->get('dbShard1');
                }

                if ($id > 10000) {
                    return $this->getDI()->get('dbShard2');
                }
            }
        }

        return $this->getDI()->get('dbShard0');
    }
}

在上面的例子中,我们检查了$intermediate数组,这是一个Phalcon内部构造的数组,提供查询的中间表示形式。我们检查是否有任何where条件。如果没有,我们就使用默认的分片dbShard0.

如果已定义条件,我们会检查条件中是否有id作为字段,并获取其值。如果id0100000之间,则使用dbShard1,否则的话使用dbShard2.

The selectReadConnection()方法在每次我们需要从数据库获取数据时都会被调用,并返回要使用的正确连接。

依赖注入

Phalcon\Mvc\Model紧密绑定到DI容器。您可以通过使用getDI方法来获取容器。因此,您可以访问注册在DI容器中的所有服务。

以下示例向您展示如何打印模型中不成功的save操作生成的任何消息,并将这些消息显示在flash消息传递器中。为此,我们使用notSaved事件:

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
    public function notSaved()
    {
        $flash    = $this->getDI()->getFlash();
        $messages = $this->getMessages();

        foreach ($messages as $message) {
            $flash->error($message);
        }
    }
}

模型特性

ORM有几个选项可以全局控制特定行为。您可以通过在您的php.ini文件中添加特定行或使用模型上的setup静态方法来启用或禁用这些功能。您可以在代码中临时启用或禁用这些功能,也可以永久启用或禁用。

phalcon.orm.column_renaming = false
phalcon.orm.events          = false

或者通过使用Model:

<?php

use Phalcon\Mvc\Model;

Model::setup(
    [
        'columnRenaming' => false,
        'events'         => false,
    ]
);

可用选项包括:

选项 默认值 描述
caseInsensitiveColumnMap false 大小写不敏感的列映射
castLastInsertIdToInt false lastInsertId转换为整数
castOnHydrate false 在填充时自动转换为原始类型
columnRenaming true 列重命名
disableAssignSetters false 禁用设置器
enableImplicitJoins true 启用隐式连接
events true 所有模型的回调、钩子和事件通知
exceptionOnFailedMetaDataSave false 当元数据保存失败时抛出异常
exceptionOnFailedSave false save()
forceCasting false 强制将绑定参数转换为其原生类型
ignoreUnknownColumns false 忽略模型上的未知列
lateStateBinding false 延迟绑定状态Phalcon\Mvc\Model::cloneResultMap()方法可以实现反向操作。
notNullValidations true 自动验证非null存在的列
phqlLiterals true PHQL解析器中的字面量
prefetchRecords 0 从ORM获取数据时预取的记录数
updateSnapshotOnSave true 更新快照save()
virtualForeignKeys true 虚拟外键

ini选项:

; phalcon.orm.cache_level = 3
; phalcon.orm.case_insensitive_column_map = false
; phalcon.orm.cast_last_insert_id_to_int = false
; phalcon.orm.cast_on_hydrate = false
; phalcon.orm.column_renaming = true
; phalcon.orm.disable_assign_setters = false
; phalcon.orm.enable_implicit_joins = true
; phalcon.orm.enable_literals = true
; phalcon.orm.events = true
; phalcon.orm.exception_on_failed_metadata_save = true
; phalcon.orm.exception_on_failed_save = false
; phalcon.orm.force_casting = false
; phalcon.orm.ignore_unknown_columns = false
; phalcon.orm.late_state_binding = false
; phalcon.orm.not_null_validations = true
; phalcon.orm.resultset_prefetch_records = "0"
; phalcon.orm.unique_cache_id = 3
; phalcon.orm.update_snapshot_on_save = true
; phalcon.orm.virtual_foreign_keys = true
; phalcon.db.escape_identifiers = On
; phalcon.db.force_casting = Off

注意

Phalcon\Mvc\Model::assign()(在创建/更新/保存模型时也会使用)始终使用设置器(如果存在)处理传递的数据参数,即使不必要或不需要也是如此。这会为您的应用增加一些额外开销。您可以通过在ini文件中添加phalcon.orm.disable_assign_setters = 1来更改此行为,它将直接使用$this->property = value.

整数与字符串

如果您希望从相关数据库字段获取整数值,则需要执行以下操作:- 确保int related database fields, you will need to do the following: - Make sure that the castOnHydrate(或设置ini_set('phalcon.orm.cast_on_hydrate', 'on'))已启用

<?php

use Phalcon\Mvc\Model;

Model::setup(
    [
        'castOnHydrate' => true,
    ]
);
  • 确保您在服务器上使用的是mysqlnd驱动程序。您可以使用phpinfo()(pdo_mysql > 客户端API版本)进行检查
  • 在您的数据库连接提供者中,您需要传递以下选项:
[
    PDO::ATTR_EMULATE_PREPARES  => false,
    PDO::ATTR_STRINGIFY_FETCHES => false,
]

注册提供者应如下所示:

<?php

$parameters = [
  // ....
];

/** @var Manager $eventsManager */
$eventsManager = $container->getShared('eventsManager');

$container->setShared(
    'db',
    function () use ($eventsManager, $parameters) {
        $options = [
            'host'     => $parameters['host'] ?? 'localhost',
            'dbname'   => $parameters['dbname'] ?? 'phalcon',
            'username' => $parameters['user'] ?? 'root',
            'password' => $parameters['pass'] ?? 'secret',
            'encoding' => $parameters['encoding'] ?? 'utf8',
            'options'  => [
                PDO::ATTR_EMULATE_PREPARES  => false,
                PDO::ATTR_STRINGIFY_FETCHES => false,
            ]
        ];

        $connection = new Mysql($options);
        $connection->setEventsManager($eventsManager);

        return $connection;
    );
}

无效的参数编号

在v5.6中,实例化PDO时使用的参数已恢复为默认设置。因此,PDO::ATTR_EMULATE_PREPARES默认为true.

但是,如果您在代码中遇到以下信息,当绑定参数在查询中多次使用时:

错误

无效的参数编号

您可以检查PDO参数并确保在注册数据库提供者时设置了PDO::ATTR_EMULATE_PREPARES被设置为true, when registering your database provider.

独立组件

你可以使用Phalcon\Mvc\Model可以单独使用,如果您愿意,可自行进行必要的设置。下面的示例演示了如何实现这一点。

<?php

use Phalcon\Di\Di;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Manager;
use Phalcon\Db\Adapter\Pdo\Sqlite;
use Phalcon\Mvc\Model\Metadata\Memory;

$container = new Di();

$container->set(
    'db',
    new Sqlite(
        [
            'dbname' => 'sample.db',
        ]
    )
);

$container->set(
    'modelsManager',
    new Manager()
);

$container->set(
    'modelsMetadata',
    new Memory()
);


class Invoices extends Model
{

}

echo Invoices::count();
无噪 Logo
无噪文档
25 年 6 月翻译
版本号 5.9
文档源↗