使用模型

最后更新于:2022-04-02 05:14:23

                        [TOC]

使用模型

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

Phalcon\Mvc\Model 是Phalcon应用程序中所有模型的基础。它提供数据库独立性,基本CRUD功能,高级查找功能以及将模型相互关联的功能,以及其他服务。 Phalcon\Mvc\Model 避免了必须使用SQL语句的需要,因为它将方法动态转换为相应的数据库引擎操作。

>[warning] 模型旨在在高层抽象上使用数据库。如果您需要使用较低级别的数据库,请查看Phalcon\Db组件文档。

创建模型

模型是从 Phalcon\Mvc\Model扩展的类。它的类名应该是驼峰表示法:

<!--?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class RobotParts extends Model
{

}

-->[warning] 如果您使用的是PHP 5.4 / 5.5,建议您声明构成模型一部分的每一列,以节省内存并减少内存分配。

默认情况下,模型 Store\Toys\RobotParts 将映射到表 robot_parts。如果要为映射表手动指定其他名称,可以使用 setSource() 方法:

<!--?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

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

RobotParts模型现在映射到toys_robot_parts表。initialize()方法有助于使用自定义行为(即不同的表)设置此模型。

initialize()方法仅在请求期间调用一次。此方法旨在执行适用于在应用程序中创建的模型的所有实例的初始化。如果要为创建的每个实例执行初始化任务,可以使用onConstruct()方法:

<!--?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

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

公共属性与Setters / Getters

模型可以实现公共属性,这意味着可以从已实例化该模型类的代码的任何部分读取/更新每个属性:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public $id;

    public $name;

    public $price;
}

另一个实现是使用getter和setter函数,它们控制哪些属性可以公开用于该模型。使用getter和setter的好处是开发人员可以对模型设置的值执行转换和验证检查,这在使用公共属性时是不可能的。此外,getter和setter允许将来更改,而无需更改模型类的接口。因此,如果字段名称发生更改,则所需的唯一更改将在相关getter / setter中引用的模型的private属性中,并且代码中没有其他位置。

<?php

namespace Store\Toys;

use InvalidArgumentException;
use Phalcon\Mvc\Model;

class Robots extends Model
{
    protected $id;

    protected $name;

    protected $price;

    public function getId()
    {
        return $this--->id;
    }

    public function setName($name)
    {
        // The name is too short?
        if (strlen($name) &lt; 10) {
            throw new InvalidArgumentException(
                'The name is too short'
            );
        }

        $this-&gt;name = $name;
    }

    public function getName()
    {
        return $this-&gt;name;
    }

    public function setPrice($price)
    {
        // 不允许负价
        if ($price &lt; 0) {
            throw new InvalidArgumentException(
                "Price can't be negative"
            );
        }

        $this-&gt;price = $price;
    }

    public function getPrice()
    {
        // 在使用之前将值转换为double
        return (double) $this-&gt;price;
    }
}

公共属性提供较少的开发复杂性。但是,getter / setter可以大大提高应用程序的可测试性,可扩展性和可维护性。开发人员可以根据应用程序的需要决定哪种策略更适合他们正在创建的应用程序。ORM与定义属性的两种方案兼容。

>[warning] 使用getter和setter时,属性名称中的下划线可能会出现问题。

如果在属性名称中使用下划线,则必须在getter / setter声明中使用camel case以与magic方法一起使用。(例如$model-&gt;getPropertyName而不是$model-&gt;getProperty_name$model-&gt;findByPropertyName而不是$model-&gt;findByProperty_name等)。由于系统需要驼峰大多数情况,并且通常会删除下划线,因此建议您按照整个文档中显示的方式命名属性。您可以使用列映射(如上所述)来确保将属性正确映射到数据库对应项。

了解对象的记录

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

mysql&gt; select * from robots;
+----+------------+------------+------+
| id | name       | type       | year |
+----+------------+------------+------+
|  1 | Robotina   | mechanical | 1972 |
|  2 | Astro Boy  | mechanical | 1952 |
|  3 | Terminator | cyborg     | 2029 |
+----+------------+------------+------+
3 rows in set (0.00 sec)

您可以通过其主键找到某个记录,然后打印其名称:

<!--?php

use Store\Toys\Robots;

// Find record with id = 3
$robot = Robots::findFirst(3);

// Prints 'Terminator'
echo $robot--->name;

记录在内存中后,您可以对其数据进行修改,然后保存更改:

<!--?php

use Store\Toys\Robots;

$robot = Robots::findFirst(3);

$robot--->name = 'RoboCop';

$robot-&gt;save();

如您所见,不需要使用原始SQL语句。Phalcon\Mvc\Model 为Web应用程序提供高度数据库抽象。

查找记录

Phalcon\Mvc\Model 还提供了几种查询记录的方法。以下示例将向您展示如何从模型中查询一个或多个记录:

<!--?php

use Store\Toys\Robots;

// How many robots are there?
$robots = Robots::find();
echo 'There are ', count($robots), "\n";

// How many mechanical robots are there?
$robots = Robots::find("type = 'mechanical'");
echo 'There are ', count($robots), "\n";

// Get and print virtual robots ordered by name
$robots = Robots::find(
    [
        "type = 'virtual'",
        'order' =--> 'name',
    ]
);
foreach ($robots as $robot) {
    echo $robot-&gt;name, "\n";
}

// Get first 100 virtual robots ordered by name
$robots = Robots::find(
    [
        "type = 'virtual'",
        'order' =&gt; 'name',
        'limit' =&gt; 100,
    ]
);
foreach ($robots as $robot) {
   echo $robot-&gt;name, "\n";
}

>[warning] 如果要通过外部数据(例如用户输入)或可变数据查找记录,则必须使用“绑定参数”

您还可以使用findFirst()方法仅获取与给定条件匹配的第一条记录:

<!--?php

use Store\Toys\Robots;

// What's the first robot in robots table?
$robot = Robots::findFirst();
echo 'The robot name is ', $robot--->name, "\n";

// What's the first mechanical robot in robots table?
$robot = Robots::findFirst("type = 'mechanical'");
echo 'The first mechanical robot name is ', $robot-&gt;name, "\n";

// Get first virtual robot ordered by name
$robot = Robots::findFirst(
    [
        "type = 'virtual'",
        'order' =&gt; 'name',
    ]
);

echo 'The first virtual robot name is ', $robot-&gt;name, "\n";

find()findFirst()方法都接受指定搜索条件的关联数组:

<!--?php

use Store\Toys\Robots;

$robot = Robots::findFirst(
    [
        "type = 'virtual'",
        'order' =--> 'name DESC',
        'limit' =&gt; 30,
    ]
);

$robots = Robots::find(
    [
        'conditions' =&gt; 'type = ?1',
        'bind'       =&gt; [
            1 =&gt; 'virtual',
        ]
    ]
);

可用的查询选项包括:

参数 描述 示例
conditions 搜索查找操作的条件。用于仅提取满足指定标准的记录。默认情况下,Phalcon\Mvc\Model假定第一个参数是条件。 'conditions' =&gt; "name LIKE 'steve%'"
columns 返回特定列而不是模型中的完整列。使用此选项时,将返回不完整的对象。 'columns' =&gt; 'id, name'
bind 绑定与选项一起使用,通过替换占位符和转义值,从而提高安全性。 'bind' =&gt; ['status' =&gt; 'A', 'type' =&gt; 'some-time']
bindTypes 绑定参数时,可以使用此参数为绑定参数定义其他强制转换,从而提高安全性。 'bindTypes' =&gt; [Column::BIND_PARAM_STR, Column::BIND_PARAM_INT]
order 用于对结果集进行排序。使用逗号分隔的一个或多个字段。 'order' =&gt; 'name DESC, status'
limit 将查询结果限制为某个范围。 'limit' =&gt; 10
offset 将查询结果偏移一定量。 'offset' =&gt; 5
group 允许跨多个记录收集数据,并按一列或多列对结果进行分组。 'group' =&gt; 'name, status'
for_update 使用此选项,Phalcon\Mvc\Model 将读取最新的可用数据,并在其读取的每一行上设置独占锁。 'for_update' =&gt; true
shared_lock 使用此选项, Phalcon\Mvc\Model 将读取最新的可用数据,并在其读取的每一行上设置共享锁。 'shared_lock' =&gt; true
cache 缓存结果集,减少对关系系统的连续访问。 'cache' =&gt; ['lifetime' =&gt; 3600, 'key' =&gt; 'my-find-key']
hydration 设置hydration策略以表示结果中的每个返回记录。 'hydration' =&gt; Resultset::HYDRATE_OBJECTS

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

<!--?php

use Store\Toys\Robots;

$robots = Robots::query()
    --->where('type = :type:')
    -&gt;andWhere('year &lt; 2000')
    -&gt;bind(['type' =&gt; 'mechanical'])
    -&gt;order('name')
    -&gt;execute();

静态方法query()返回与IDE自动完成程序友好的Phalcon\Mvc\Model\Criteria 对象。

所有查询都在内部处理为PHQL查询。PHQL是一种高级,面向对象和类似SQL的语言。此语言为您提供了更多功能来执行查询,如加入其他模型,定义分组,添加聚合等。

最后,有 findFirstBy<property-name>()方法。此方法扩展了前面提到的findFirst()方法。它允许您通过使用方法本身中的属性名称并向其传递包含要在该列中搜索的数据的参数,从表中快速执行检索。一个例子是有序的,所以采用前面提到的机器人模型:

<!--?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public $id;

    public $name;

    public $price;
}

我们在这里有三个属性:$id$name$price 。所以,假设您要检索表中名为“Terminator”的第一条记录。这可以写成

<?php

use Store\Toys\Robots;

$name = 'Terminator';

$robot = Robots::findFirstByName($name);

if ($robot) {
    echo 'The first robot with the name ' . $name . ' cost ' . $robot--->price . '.';
} else {
    echo 'There were no robots found in our table with the name ' . $name . '.';
}

请注意,我们在方法调用中使用了'Name'并将变量 $name传递给它,其中包含我们在表中查找的名称。另请注意,当我们找到与查询匹配的内容时,我们也可以使用所有其他属性。

模型结果集

虽然findFirst()直接返回被调用类的实例(当有数据要返回时),但find()方法返回Phalcon\Mvc\Model\Resultset\Simple。这是一个对象,它封装了结果集遍历的所有功能,寻找特定记录,计数等。

这些对象比标准数组更强大。 Phalcon\Mvc\Model\Resultset的最大特色之一是,在任何时候内存中只有一条记录。这极大地有助于内存管理,尤其是在处理大量数据时。

<!--?php

use Store\Toys\Robots;

// 获取所有 robots
$robots = Robots::find();

// foreach遍历
foreach ($robots as $robot) {
    echo $robot--->name, "\n";
}

// while遍历
$robots-&gt;rewind();

while ($robots-&gt;valid()) {
    $robot = $robots-&gt;current();

    echo $robot-&gt;name, "\n";

    $robots-&gt;next();
}

// 计算结果集
echo count($robots);

// 计算结果集的替代方法
echo $robots-&gt;count();

// 将内部光标移动到第三个 robot
$robots-&gt;seek(2);

$robot = $robots-&gt;current();

//通过结果集中的位置访问 robot
$robot = $robots[5];

// 检查某个位置是否有记录
if (isset($robots[3])) {
   $robot = $robots[3];
}

// 获取结果集中的第一条记录
$robot = $robots-&gt;getFirst();

// 获取最后一条记录
$robot = $robots-&gt;getLast();

Phalcon的结果集模拟可滚动游标,只需访问其位置或寻找指向特定位置的内部指针即可获得任何行。请注意,某些数据库系统不支持可滚动游标,这会强制重新执行查询,以便将游标倒回到开头并在请求的位置获取记录。同样,如果遍历结果集多次,则查询必须执行相同的次数。

由于在内存中存储大型查询结果可能会消耗许多资源,因此从数据库中以32行为块获取结果集 - 在几种情况下减少了重新执行请求的需要。

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

<!--?php

// Query all records from model parts
$parts = Parts::find();

// Store the resultset into a file
file_put_contents(
    'cache.txt',
    serialize($parts)
);

// Get parts from file
$parts = unserialize(
    file_get_contents('cache.txt')
);

// Traverse the parts
foreach ($parts as $part) {
    echo $part--->id;
}

自定义结果集

有时,应用程序逻辑需要在从数据库中检索数据时对数据进行额外的操作。以前,我们只是扩展模型并将功能封装在模型或特征中的类中,通常返回给调用者一组转换后的数据。

使用自定义结果集,您不再需要这样做。 自定义结果集将封装模型中的功能,并且可以由其他模型重用,从而保持代码DRY。 这样,find()方法将不再返回默认的 Phalcon\Mvc\Model\Resultset,而是返回自定义的结果集。Phalcon 允许您通过在模型中使用 getResultsetClass() 来完成此操作。

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

<!--?php

namespace Application\Mvc\Model\Resultset;

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

class Custom extends Simple
{
    public function getSomeData() {
        /** CODE */
    }
}

在模型中,我们在getResultsetClass()中设置类,如下所示:

<?php

namespace Phalcon\Test\Models\Statistics;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public function getSource()
    {
        return 'robots';
    }

    public function getResultsetClass()
    {
    return 'Application\Mvc\Model\Resultset\Custom';
    }
}

最后在你的代码中你会有这样的东西:

<?php

/**
 * Find the robots 
 */
$robots = Robots::find(
    [
        'conditions' =--> 'date between "2017-01-01" AND "2017-12-31"',
        'order'      =&gt; 'date'
    ]
);

/**
 * 将数据传递给视图
 */
$this-&gt;view-&gt;mydata = $robots-&gt;getSomeData();

过滤结果集

过滤结果集过滤数据的最有效方法是设置一些搜索条件,数据库将使用在表上设置的索引来更快地返回数据。Phalcon还允许您使用PHP将数据库中的不可用方案来过滤数据:

<!--?php

$customers = Customers::find();

$customers = $customers--->filter(
    function ($customer) {
        // Return only customers with a valid e-mail
        if (filter_var($customer-&gt;email, FILTER_VALIDATE_EMAIL)) {
            return $customer;
        }
    }
);

绑定参数

Phalcon\Mvc\Model也支持绑定参数。建议您使用此方法,以消除代码遭受SQL注入攻击的可能性。支持字符串和整数占位符。绑定参数可以简单地实现如下:

<!--?php

use Store\Toys\Robots;

// 查询robots使用字符串占位符绑定参数
// 参数key与占位符相同的参数
$robots = Robots::find(
    [
        'name = :name: AND type = :type:',
        'bind' =--> [
            'name' =&gt; 'Robotina',
            'type' =&gt; 'maid',
        ],
    ]
);

// 查询robots使用整数占位符绑定参数
$robots = Robots::find(
    [
        'name = ?1 AND type = ?2',
        'bind' =&gt; [
            1 =&gt; 'Robotina',
            2 =&gt; 'maid',
        ],
    ]
);

// 查询robots使用字符串和整数占位符绑定参数
// 参数key与占位符相同的参数
$robots = Robots::find(
    [
        'name = :name: AND type = ?1',
        'bind' =&gt; [
            'name' =&gt; 'Robotina',
            1      =&gt; 'maid',
        ],
    ]
);

使用数字占位符时,您需要将它们定义为整数,即12.在这种情况下,'1''2'被视为字符串而不是数字,因此无法成功替换占位符。

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

此外,您可以设置参数 bindTypes,这允许根据数据类型定义参数的绑定方式:

<!--?php

use Phalcon\Db\Column;
use Store\Toys\Robots;

// 绑定参数
$parameters = [
    'name' =--> 'Robotina',
    'year' =&gt; 2008,
];

// Casting Types
$types = [
    'name' =&gt; Column::BIND_PARAM_STR,
    'year' =&gt; Column::BIND_PARAM_INT,
];

// 查询robots使用字符串占位符绑定参数
$robots = Robots::find(
    [
        'name = :name: AND year = :year:',
        'bind'      =&gt; $parameters,
        'bindTypes' =&gt; $types,
    ]
);

>[warning] 由于默认的绑定类型是 Phalcon\Db\Column::BIND_PARAM_STR,因此如果所有列都属于该类型,则无需指定bindTypes参数。

如果在绑定参数中绑定数组,请记住,键必须从0开始编号:

<!--?php

use Store\Toys\Robots;

$array = ['a','b','c']; // $array: [[0] =--> 'a', [1] =&gt; 'b', [2] =&gt; 'c']

unset($array[1]); // $array: [[0] =&gt; 'a', [2] =&gt; 'c']

// Now we have to renumber the keys
$array = array_values($array); // $array: [[0] =&gt; 'a', [1] =&gt; 'c']

$robots = Robots::find(
    [
        'letter IN ({letter:array})',
        'bind' =&gt; [
            'letter' =&gt; $array
        ]
    ]
);

>[warning] 绑定参数可用于所有查询方法,例如find()findFirst(),但也可用于count()sum()average()等计算方法。

如果你正在使用“finders”,例如find()findFirst()等,自动使用绑定参数:

<!--?php

use Store\Toys\Robots;

// 使用绑定参数的显式查询
$robots = Robots::find(
    [
        'name = ?0',
        'bind' =--> [
            'Ultron',
        ],
    ]
);

// 使用绑定参数的隐式查询
$robots = Robots::findByName('Ultron');

初始化/准备获取记录

可能是这样的情况:在从数据库获取记录之后,必须在应用程序的其余部分使用之前初始化数据。您可以在模型中实现 afterFetch() 方法,此事件将在创建实例后立即执行并将数据分配给它:

<!--?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public $id;

    public $name;

    public $status;

    public function beforeSave()
    {
        // 将数组转换为字符串
        $this--->status = join(',', $this-&gt;status);
    }

    public function afterFetch()
    {
        // 将字符串转换为数组
        $this-&gt;status = explode(',', $this-&gt;status);
    }

    public function afterSave()
    {
        // 将字符串转换为数组
        $this-&gt;status = explode(',', $this-&gt;status);
    }
}

如果您使用getter / setter而不是/或与公共属性一起使用,则可以在访问字段后初始化该字段:

<!--?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public $id;

    public $name;

    public $status;

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

生成计算

计算(或聚合)是数据库系统常用功能的辅助工具,例如COUNTSUMMAXMINAVGPhalcon\Mvc\Model允许直接从公开的方法使用这些函数。

Count 示例:

<!--?php

// How many employees are?
$rowcount = Employees::count();

// How many different areas are assigned to employees?
$rowcount = Employees::count(
    [
        'distinct' =--> 'area',
    ]
);

// How many employees are in the Testing area?
$rowcount = Employees::count(
    'area = 'Testing''
);

// Count employees grouping results by their area
$group = Employees::count(
    [
        'group' =&gt; 'area',
    ]
);
foreach ($group as $row) {
   echo 'There are ', $row-&gt;rowcount, ' in ', $row-&gt;area;
}

// Count employees grouping by their area and ordering the result by count
$group = Employees::count(
    [
        'group' =&gt; 'area',
        'order' =&gt; 'rowcount',
    ]
);

// Avoid SQL injections using bound parameters
$group = Employees::count(
    [
        'type &gt; ?0',
        'bind' =&gt; [
            $type
        ],
    ]
);

Sum 示例:

<!--?php

// How much are the salaries of all employees?
$total = Employees::sum(
    [
        'column' =--> 'salary',
    ]
);

// How much are the salaries of all employees in the Sales area?
$total = Employees::sum(
    [
        'column'     =&gt; 'salary',
        'conditions' =&gt; "area = 'Sales'",
    ]
);

// Generate a grouping of the salaries of each area
$group = Employees::sum(
    [
        'column' =&gt; 'salary',
        'group'  =&gt; 'area',
    ]
);
foreach ($group as $row) {
   echo 'The sum of salaries of the ', $row-&gt;area, ' is ', $row-&gt;sumatory;
}

// Generate a grouping of the salaries of each area ordering
// salaries from higher to lower
$group = Employees::sum(
    [
        'column' =&gt; 'salary',
        'group'  =&gt; 'area',
        'order'  =&gt; 'sumatory DESC',
    ]
);

// Avoid SQL injections using bound parameters
$group = Employees::sum(
    [
        'conditions' =&gt; 'area &gt; ?0',
        'bind'       =&gt; [
            $area
        ],
    ]
);

Average 示例:

<!--?php

// What is the average salary for all employees?
$average = Employees::average(
    [
        'column' =--> 'salary',
    ]
);

// What is the average salary for the Sales's area employees?
$average = Employees::average(
    [
        'column'     =&gt; 'salary',
        'conditions' =&gt; "area = 'Sales'",
    ]
);

// Avoid SQL injections using bound parameters
$average = Employees::average(
    [
        'column'     =&gt; 'age',
        'conditions' =&gt; 'area &gt; ?0',
        'bind'       =&gt; [
            $area
        ],
    ]
);

Max/Min 示例:

<!--?php

// What is the oldest age of all employees?
$age = Employees::maximum(
    [
        'column' =--> 'age',
    ]
);

// What is the oldest of employees from the Sales area?
$age = Employees::maximum(
    [
        'column'     =&gt; 'age',
        'conditions' =&gt; "area = 'Sales'",
    ]
);

// What is the lowest salary of all employees?
$salary = Employees::minimum(
    [
        'column' =&gt; 'salary',
    ]
);

创建/更新记录

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

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

<!--?php

use Store\Toys\Robots;

$robot = new Robots();

$robot--->type = 'mechanical';
$robot-&gt;name = 'Astro Boy';
$robot-&gt;year = 1952;

if ($robot-&gt;save() === false) {
    echo "Umh, We can't store robots right now: \n";

    $messages = $robot-&gt;getMessages();

    foreach ($messages as $message) {
        echo $message, "\n";
    }
} else {
    echo 'Great, a new robot was saved successfully!';
}

可以传递一个数组进行保存以避免手动分配每个列。Phalcon\Mvc\Model将检查是否为数组中传递的列实现了setter优先级,而不是直接分配属性的值:

<!--?php

use Store\Toys\Robots;

$robot = new Robots();

$robot--->save(
    [
        'type' =&gt; 'mechanical',
        'name' =&gt; 'Astro Boy',
        'year' =&gt; 1952,
    ]
);

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

<!--?php

use Store\Toys\Robots;

$robot = new Robots();

$robot--->save($_POST);

>[warning] 如果没有预防措施,批量分配可能允许攻击者设置任何数据库列的值。如果要允许用户插入/更新模型中的每个列,请仅使用此功能,即使这些字段不在提交的表单中。

您可以在save设置其他参数,以设置仅在执行批量分配时必须考虑的字段白名单:

<!--?php

use Store\Toys\Robots;

$robot = new Robots();

$robot--->save(
    $_POST,
    [
        'name',
        'type',
    ]
);

强制创建/更新记录

当一个应用程序有并发,我们可能期望create一个记录,但它实际上是update的。如果我们使用Phalcon\Mvc\Model::save() 来保存数据库中的记录,就会发生这种情况。如果我们想要绝对确定创建或更新记录,我们可以使用 create()update()更改save()调用:

<!--?php

use Store\Toys\Robots;

$robot = new Robots();

$robot--->type = 'mechanical';
$robot-&gt;name = 'Astro Boy';
$robot-&gt;year = 1952;

// This record only must be created
if ($robot-&gt;create() === false) {
    echo "Umh, We can't store robots right now: \n";

    $messages = $robot-&gt;getMessages();

    foreach ($messages as $message) {
        echo $message, "\n";
    }
} else {
    echo 'Great, a new robot was created successfully!';
}

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

删除记录

Phalcon\Mvc\Model::delete() 方法允许删除记录。您可以按如下方式使用它:

<!--?php

use Store\Toys\Robots;

$robot = Robots::findFirst(11);

if ($robot !== false) {
    if ($robot--->delete() === false) {
        echo "Sorry, we can't delete the robot right now: \n";

        $messages = $robot-&gt;getMessages();

        foreach ($messages as $message) {
            echo $message, "\n";
        }
    } else {
        echo 'The robot was deleted successfully!';
    }
}

您还可以通过使用foreach遍历结果集来删除许多记录:

<!--?php

use Store\Toys\Robots;

$robots = Robots::find(
    "type = 'mechanical'"
);

foreach ($robots as $robot) {
    if ($robot--->delete() === false) {
        echo "Sorry, we can't delete the robot right now: \n";

        $messages = $robot-&gt;getMessages();

        foreach ($messages as $message) {
            echo $message, "\n";
        }
    } else {
        echo 'The robot was deleted successfully!';
    }
}

以下事件可用于定义在执行删除操作时可以执行的自定义业务规则:

操作 名称 Can stop operation? 说明
Deleting afterDelete No 删除操作完成后运行
Deleting beforeDelete Yes 删除操作完成前运行

通过上述事件,还可以在模型中定义业务规则:

<!--?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public function beforeDelete()
    {
        if ($this--->status === 'A') {
            echo "The robot is active, it can't be deleted";

            return false;
        }

        return true;
    }
}

Hydration Modes

如前所述,结果集是完整对象的集合,这意味着每个返回的结果都是表示数据库中行的对象。可以修改这些对象并将其再次保存为持久性:

<!--?php

use Store\Toys\Robots;

$robots = Robots::find();

// Manipulating a resultset of complete objects
foreach ($robots as $robot) {
    $robot--->year = 2000;

    $robot-&gt;save();
}

有时记录只能以只读模式呈现给用户,在这些情况下,改变表示记录的方式以便于处理它们可能是有用的。用于表示结果集中返回的对象的策略称为“hydration mode”:

<!--?php

use Phalcon\Mvc\Model\Resultset;
use Store\Toys\Robots;

$robots = Robots::find();

// Return every robot as an array
$robots--->setHydrateMode(
    Resultset::HYDRATE_ARRAYS
);

foreach ($robots as $robot) {
    echo $robot['year'], PHP_EOL;
}

// Return every robot as a stdClass
$robots-&gt;setHydrateMode(
    Resultset::HYDRATE_OBJECTS
);

foreach ($robots as $robot) {
    echo $robot-&gt;year, PHP_EOL;
}

// Return every robot as a Robots instance
$robots-&gt;setHydrateMode(
    Resultset::HYDRATE_RECORDS
);

foreach ($robots as $robot) {
    echo $robot-&gt;year, PHP_EOL;
}

Hydration mode 也可以作为find的参数传递:

<!--?php

use Phalcon\Mvc\Model\Resultset;
use Store\Toys\Robots;

$robots = Robots::find(
    [
        'hydration' =--> Resultset::HYDRATE_ARRAYS,
    ]
);

foreach ($robots as $robot) {
    echo $robot['year'], PHP_EOL;
}

表前缀

如果您希望所有表都具有特定前缀并且未在所有模型中设置源,则可以使用Phalcon\Mvc\Model\Manager和方法setModelPrefix()

<!--?php

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

class Robots extends Model
{

}

$manager = new Manager();
$manager--->setModelPrefix('wp_');
$robots = new Robots(null, null, $manager);
echo $robots-&gt;getSource(); // will return wp_robots

自动生成的标识列

某些模型可能具有标识列。这些列通常是映射表的主键。Phalcon\Mvc\Model 可以识别在生成的SQL INSERT中省略它的标识列,因此数据库系统可以为它生成自动生成的值。始终在创建记录后,身份字段将注册为数据库系统中为其生成的值:

<!--?php

$robot--->save();

echo 'The generated id is: ', $robot-&gt;id;

Phalcon\Mvc\Model 能够识别标识列。根据数据库系统的不同,这些列可能是PostgreSQL中的串行列,也可能是MySQL中的auto_increment列。

PostgreSQL使用序列生成自动数值,默认情况下,Phalcon尝试从序列table_field_seq获取生成的值,例如: robots_id_seq,如果该序列具有不同的名称,则需要实现 getSequenceName()方法:

<!--?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public function getSequenceName()
    {
        return 'robots_sequence_name';
    }
}

跳过列

告诉Phalcon\Mvc\Model总是省略创建和/或更新记录中的某些字段,以便委托数据库系统通过触发器或默认值来分配值:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public function initialize()
    {
        // Skips fields/columns on both INSERT/UPDATE operations
        $this--->skipAttributes(
            [
                'year',
                'price',
            ]
        );

        // Skips only when inserting
        $this-&gt;skipAttributesOnCreate(
            [
                'created_at',
            ]
        );

        // Skips only when updating
        $this-&gt;skipAttributesOnUpdate(
            [
                'modified_in',
            ]
        );
    }
}

这将在整个应用程序上的每个INSERT/UPDATE操作上全局忽略这些字段。如果要忽略不同INSERT/UPDATE操作的不同属性,可以指定第二个参数(布尔值) - true表示替换。强制默认值可以按如下方式完成:

<!--?php

use Store\Toys\Robots;

use Phalcon\Db\RawValue;

$robot = new Robots();

$robot--->name       = 'Bender';
$robot-&gt;year       = 1999;
$robot-&gt;created_at = new RawValue('default');

$robot-&gt;create();

回调也可用于创建自动默认值的条件分配:

<!--?php

namespace Store\Toys;

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

class Robots extends Model
{
    public function beforeCreate()
    {
        if ($this--->price &gt; 10000) {
            $this-&gt;type = new RawValue('default');
        }
    }
}

>[danger] 切勿使用Phalcon\Db\RawValue分配外部数据(例如用户输入)或可变数据。将参数绑定到查询时,将忽略这些字段的值。所以它可以用来攻击注入SQL的应用程序。

动态更新

默认情况下,SQL UPDATE语句是使用模型中定义的每个列创建的(完整的全字段SQL更新)。您可以更改特定模型以进行动态更新,在这种情况下,只有已更改的字段用于创建最终的SQL语句。

在某些情况下,这可以通过减少应用程序和数据库服务器之间的流量来提高性能,这在表有blob / text字段时特别有用:

<!--?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

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

独立列映射

ORM支持独立的列映射,允许开发人员在模型中使用表中的列名。Phalcon将识别新的列名称,并相应地重命名它们以匹配数据库中的相应列。当需要重命名数据库中的字段而不必担心代码中的所有查询时,这是一个很棒的功能。模型中列映射的更改将处理其余部分。例如:

<!--?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public $code;

    public $theName;

    public $theType;

    public $theYear;

    public function columnMap()
    {
        // Keys are the real names in the table and
        // the values their names in the application
        return [
            'id'       =--> 'code',
            'the_name' =&gt; 'theName',
            'the_type' =&gt; 'theType',
            'the_year' =&gt; 'theYear',
        ];
    }
}

然后您可以在代码中自然地使用新名称:

<!--?php

use Store\Toys\Robots;

// Find a robot by its name
$robot = Robots::findFirst(
    "theName = 'Voltron'"
);

echo $robot--->theName, "\n";

// Get robots ordered by type
$robot = Robots::find(
    [
        'order' =&gt; 'theType DESC',
    ]
);

foreach ($robots as $robot) {
    echo 'Code: ', $robot-&gt;code, "\n";
}

// Create a robot
$robot = new Robots();

$robot-&gt;code    = '10101';
$robot-&gt;theName = 'Bender';
$robot-&gt;theType = 'Industrial';
$robot-&gt;theYear = 2999;

$robot-&gt;save();

重命名列时请考虑以下事项:

  • 关系/验证器中对属性的引用必须使用新名称
  • 请参阅实际列名称将导致ORM发生异常

独立的列映射允许您:

  • 使用自己的约定编写应用程序
  • 消除代码中的vendor前缀/后缀
  • 更改列名而不更改应用程序代码

记录快照

可以设置特定模型以在查询时维护记录快照。您可以使用此功能来实现审核,或者只是根据持久性中查询的数据了解哪些字段已更改:

<!--?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

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

激活此功能时,应用程序会消耗更多内存,以跟踪从持久性中获取的原始值。在激活此功能的模型中,您可以检查更改的字段如下:

<!--?php

use Store\Toys\Robots;

// Get a record from the database
$robot = Robots::findFirst();

// Change a column
$robot--->name = 'Other name';

var_dump($robot-&gt;getChangedFields()); // ['name']

var_dump($robot-&gt;hasChanged('name')); // true

var_dump($robot-&gt;hasChanged('type')); // false

在模型创建/更新时更新快照。使用hasUpdated()getUpdatedFields()可以检查字段是否在创建/保存/更新后更新,但如果在afterUpdate()afterSave()afterCreate()中执行 getChangedFields(),则可能会对应用程序造成问题()。

您可以使用以下命令禁用此功能:

Phalcon\Mvc\Model::setup(
    [
        'updateSnapshotOnSave' =&gt; false,
    ]
);

或者如果你喜欢在你的php.ini中设置它

phalcon.orm.update_snapshot_on_save = 0

使用此功能将产生以下影响:

<!--?php

use Phalcon\Mvc\Model;

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

$user       = new User();
$user-&gt;name = 'Test User';
$user-&gt;create();
var_dump($user-&gt;getChangedFields());
$user-&gt;login = 'testuser';
var_dump($user-&gt;getChangedFields());
$user-&gt;update();
var_dump($user-&gt;getChangedFields());

在Phalcon 3.1.0及更高版本上,它是:

array(0) {
}
array(1) {
[0]=&gt; 
    string(5) "login"
}
array(0) {
}

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

指向不同的架构

如果模型映射到与默认模式/数据库不同的模式/数据库中的表。您可以使用setSchema()方法来定义:

<!--?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

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

设置多个数据库

在Phalcon中,所有模型都可以属于同一个数据库连接或具有单独的数据库连接。实际上,当Phalcon\Mvc\Model需要连接到数据库时,它会在应用程序的服务容器中请求db 服务。您可以在initialize() 方法中覆盖此服务设置:

<!--?php

use Phalcon\Db\Adapter\Pdo\Mysql as MysqlPdo;
use Phalcon\Db\Adapter\Pdo\PostgreSQL as PostgreSQLPdo;

// This service returns a MySQL database
$di--->set(
    'dbMysql',
    function () {
        return new MysqlPdo(
            [
                'host'     =&gt; 'localhost',
                'username' =&gt; 'root',
                'password' =&gt; 'secret',
                'dbname'   =&gt; 'invo',
            ]
        );
    }
);

// This service returns a PostgreSQL database
$di-&gt;set(
    'dbPostgres',
    function () {
        return new PostgreSQLPdo(
            [
                'host'     =&gt; 'localhost',
                'username' =&gt; 'postgres',
                'password' =&gt; '',
                'dbname'   =&gt; 'invo',
            ]
        );
    }
);

然后,在 initialize() 方法中,我们定义模型的连接服务:

<!--?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

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

但Phalcon为您提供了更大的灵活性,您可以定义必须用于readwrite 的连接。这对于平衡实现主从架构的数据库的负载特别有用:

<!--?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

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

        $this-&gt;setWriteConnectionService('dbMaster');
    }
}

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

<!--?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    /**
     * Dynamically selects a shard
     *
     * @param array $intermediate
     * @param array $bindParams
     * @param array $bindTypes
     */
    public function selectReadConnection($intermediate, $bindParams, $bindTypes)
    {
        // Check if there is a 'where' clause in the select
        if (isset($intermediate['where'])) {
            $conditions = $intermediate['where'];

            // Choose the possible shard according to the conditions
            if ($conditions['left']['name'] === 'id') {
                $id = $conditions['right']['value'];

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

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

        // Use a default shard
        return $this-&gt;getDI()-&gt;get('dbShard0');
    }
}

调用 selectReadConnection() 方法来选择正确的连接,此方法拦截执行的任何新查询:

<!--?php

use Store\Toys\Robots;

$robot = Robots::findFirst('id = 101');

将服务注入模型

您可能需要访问模型中的应用程序服务,以下示例说明如何执行此操作:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public function notSaved()
    {
        // Obtain the flash service from the DI container
        $flash = $this--->getDI()-&gt;getFlash();

        $messages = $this-&gt;getMessages();

        // Show validation messages
        foreach ($messages as $message) {
            $flash-&gt;error($message);
        }
    }
}

每次createupdate操作失败时都会触发notSaved事件。因此,我们正在刷新验证消息,从DI容器中获取flash服务。通过这样做,我们不必在每次保存后打印消息。

禁用/启用功能

在ORM中,我们实现了一种机制,允许您动态地全局启用/禁用特定功能或选项。根据您使用ORM的方式,您可以禁用您不使用的ORM。如果需要,也可以暂时禁用这些选项:

<!--?php

use Phalcon\Mvc\Model;

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

可用选项包括:

选项 描述 默认
astCache 启用/禁用所有模型的回调,钩子和事件通知 null
cacheLevel 设置ORM的缓存级别 3
castOnHydrate false
columnRenaming 启用/禁用列重命名renaming true
disableAssignSetters 允许在模型中禁用setter false
enableImplicitJoins true
enableLiterals true
escapeIdentifiers true
events 启用/禁用所有模型的回调,钩子和事件通知 true
exceptionOnFailedSave save() 失败时启用/禁用抛出异常 false
forceCasting false
ignoreUnknownColumns 启用/禁用忽略模型上的未知列 false
lateStateBinding 启用/禁用 Phalcon\Mvc\Model::cloneResultMap() 方法的后期状态绑定 false
notNullValidations ORM自动验证映射表中存在的非空列 true
parserCache null
phqlLiterals 在PHQL解析器中启用/禁用字面量 true
uniqueCacheId 3
updateSnapshotOnSave save()上启用/禁用更新快照 true
virtualForeignKeys 启用/禁用虚拟外键 true

>[warning] NOTE Phalcon\Mvc\Model::assign() (在创建/更新/保存模型时也使用它)总是使用setter,如果它们在传递数据参数时存在,即使它是必需的或必要的。这将为您的应用程序增加一些额外的开销。您可以通过将 phalcon.orm.disable_assign_setters = 1 添加到您的ini文件来更改此行为,它只需使用 $this-&gt;property = value

独立组件

在独立模式下使用 Phalcon\Mvc\Model 可以在下面演示:

<!--?php

use Phalcon\Di;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Manager as ModelsManager;
use Phalcon\Db\Adapter\Pdo\Sqlite as Connection;
use Phalcon\Mvc\Model\Metadata\Memory as MetaData;

$di = new Di();

// Setup a connection
$di--->set(
    'db',
    new Connection(
        [
            'dbname' =&gt; 'sample.db',
        ]
    )
);

// Set a models manager
$di-&gt;set(
    'modelsManager',
    new ModelsManager()
);

// Use the memory meta-data adapter or other
$di-&gt;set(
    'modelsMetadata',
    new MetaData()
);

// Create a model
class Robots extends Model
{

}

// Use the model
echo Robots::count();
                </property-name>
';