使用模型(Working with Models)
模型表示应用程序信息(数据)以及这些数据的处理规则,主要用于管理与对应数据表的交互规则。大多数情况下,数据库中的每一张表都有对应的模型。应用程序中的大部分业务逻辑集中在模型中。
Phalcon应用中,Phalcon\Mvc\Model是所有模型的基类。它提供了数据库独立、基础CRUD、高级查找、模型关联以及其他服务。
Phalcon\Mvc\Model将调用的方法动态转换为相应的数据库操作,避免了直接使用SQL。
模型使用数据库高级抽象层,如果想要使用更为底层的方式操作数据库,请参考Phalcon\Db组件文档。
创建模型(Creating Models)
模型需继承Phalcon\Mvc\Model类,以大驼峰格式命名。
<?php
namespace Store\Toys;
use Phalcon\Mvc\Model;
class RobotParts extends Model
{
}
模型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');
}
}
公共属性和Setters、Getters方法(Public properties vs. Setters/Getters)
模型可以定义公共属性,在任何获取了模型实例的地方都可以读写模型的公共属性:
<?php
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model {
public $id;
public $name;
public $price;
}
另一种实现方式是getters和setters方法,控制哪些模型属性可以公开访问。这种方式的好处是,开发者可以在对模型属性进行写操作时执行转换和验证,这是使用公共属性方式无法实现的。此外,getters和setters可以在不改动模型和接口的前提下,应对未来可能的改动。如果字段名称改变,唯一需要的改动的地方是getters和setters中引用的模型私有属性。
<?php
namespace Store\Toys;
use InvalidArgumentException;
use Phalcon\Mvc\Model;
class Robots extends Model {
protected $id;
protected $name;
protected $price;
public function getId() {
return intval($this->id);
}
public function setName(string $name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
public function setPrice(int $price) {
$this->price = $price;
return $this;
}
public function getPrice() {
return intval($this->price);
}
}
虽然公共属性在开发中的复杂度更低,但是getters和setters可以大大提高程序的测试性、扩展性和可维护性。开发者可以根据需求决定哪种方式更适合他们的应用。ORM兼容这两种方式。
理解记录对象(Understanding Records To Objects)
模型的每一个实例代表数据表中的一条记录,可以通过读取模型对象属性来访问记录数据
1、通过主键查找某条记录:
<?php
use Store\Toys\Robots;
// 查找id = 3的记录
$robot = Robots::findFirst(3);
// 输出'Terminator'
echo $robot->name;
一旦记录存储在内存中,就可以修改其中的数据并保存:
<?php
use Store\Toys\Robots;
$robot = Robots::findFirst(3);
$robot->name = 'RoboCop';
$robot->save();
Phalcon\Mvc\Model为web应用提供了数据库高级抽象层,不需要使用原生SQL语句。
2、查找记录(Finding Records)
Phalcon\Mvc\Model提供了多种查询记录的方法,下面例子演示如何用模型查找一条或多条记录:
<?php
use Store\Toys\Robots;
// 查询所有记录
$robots = Robots::find();
// 查询type = 'mechanical'的记录
$robots = Robots::find("type = 'mechanical'");
// 获取type = 'virtual'的记录,根据name排序
$robots = Robots::find(
[
"type = 'virtual'",
'order' => 'name',
]
);
foreach ($robots as $robot) {
echo $robot->name, "\n";
}
// 获取type = 'virtual'的前100条记录,根据name排序
$robots = Robots::find(
[
"type = 'virtual'",
'order' => 'name',
'limit' => 100,
]
);
foreach ($robots as $robot) {
echo $robot->name, "\n";
}
使用findFirst()方法,获取满足给定条件的第一条记录:
<?php
use Store\Toys\Robots;
// 获取robots表的第一条记录
$robot = Robots::findFirst();
// 获取robots表中type = 'mechanical'的第一条记录
$robot = Robots::findFirst("type = 'mechanical'");
// 获取robots表中type = 'virtual'的第一条记录,根据name排序
$robot = Robots::findFirst(
[
"type = 'virtual'",
'order' => 'name',
]
);
find()方法和findFirst()方法都接受一个包含指定搜索条件的关联数组:
<?php
use Store\Toys\Robots;
$robots = Robots::findFirst(
[
"type = 'virtual'",
'order' => 'name DESC',
'limit' => 30,
]
);
$robots = Robots::find(
[
'conditions' => 'type = ?1',
'bind' => [
1 => 'virtual',
],
]
);
除了使用参数数组,还可以使用面向对象的方式创建查询:
<?php
use Store\Toys\Robots;
$robots = Robots::query()
->where('type = :type:')
->addWhere('year < 2000')
->bind(['type' => 'machanical'])
->order('name')
->execute();
静态方法query()返回一个IDE自动完成友好的Phalcon\Mvc\Model\Criteria对象。
所有查询在内部都以PHQL查询的方式处理。PHQL是一种高级的、面向对象的类SQL语言,这种语言提供了多种功能来执行查询,如join其他模型,定义分组,添加聚合等。
最后,还有一个findFirstBy<property-name>()方法,该方法扩展了findFirst()方法,它允许通过使用方法中的属性名称并向它传递一个包含要在该字段中搜索数据的参数,从表中快速执行检索。以上面的Robots模型为例:
<?php
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public $id;
public $name;
public $price;
}
这里有三个属性:name和$price,假设要检索name为'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,表中name字段值为$name的记录就是我们要查找的。
- 模型结果集(Model Resultsets)
findFirst()方法返回被调用类的实例(如果有结果返回),而find()方法返回Phalcon\Mvc\Model\Resultsets\Simple对象,该对象封装了结果集应有的所有功能,如遍历,查找特定记录,统计等。
这些对象比一般数组功能强大,Phalcon\Mvc\Model\Resultset一个最大的特点是,任何时刻,只有一条记录保存在内存中。这对内存管理有极大帮助,特别是在处理大批量数据时。
<?php
use Store\Toys\Robots;
// 获取所有记录
$robots = Robots::find();
// foreach遍历
foreach ($robots as $robot) {
echo $robot->name, "\n";
}
// while遍历
while ($robots->valid()) {
$robot = $robots->current();
echo $robot->name, "\n";
$robots->next();
}
// 结果集计数
echo count($robots);
// 另一种结果集计数方法
echo $robots->count();
// 移动游标到第三条记录
$robots->seek(2);
$robot = $robots->current();
// 通过位置访问结果集中的记录
$robot = $robots[5];
// 检查指定位置是否有记录
if (isset($robots[3])) {
$robot = $robots[3];
}
// 获取结果集中第一条记录
$robot = $robots->getFirst();
// 获取结果集中最后一条记录
$robot = $robots->getLast();
- 参数绑定(Binding Parameters)
Phalcon\Mvc\Model支持参数绑定,建议使用这种方法以避免SQL注入,字符串占位符和数字占位符均被支持。参数绑定简单实现如下:
<?php
use Store\Toys\Robots;
// 字符串占位符
$robots = Robots::find(
[
'name = :name: AND type = :type:',
'bind' => [
'name' => 'Robotina',
'type' => 'maid',
],
]
);
// 数字占位符
$robots = Robots::find(
[
'name = ?1 AND type = ?2',
'bind' => [
1 => 'Robotina',
2 => 'maid',
],
]
);
// 同时使用字符串占位符和数字占位符
$robots = Robots::find(
[
'name = :name: AND type = ?1',
'bind' => [
'name' => 'Robotina',
1 => 'maid',
],
]
);
使用数字占位符时,需要将它们定义成整数形式,即1或2。而'1'或'2'会被当成字符串,所以占位符不能被成功替换。
如果使用数组作为绑定参数,则数组必须是键名从0开始的索引数组:
<?php
use Store\Toys\Robots;
$array = ['a', 'b', 'c']; // $array: [[0] => 'a', [1] => 'b', [2] => 'c']
unset($array[1]); // $array: [[0] => 'a', [2] => 'c']
// 现在必须重建数组索引
$array = array_values($array); // $array: [[0] => 'a', [1] => 'c']
$robots = Robots::find(
[
'letter IN ({letter:array})',
'bind' => [
'letter' => $array,
],
]
);
- 初始化已获取记录(Initializing / Preparing fetched records)
有时从数据库获取记录之后,在数据被应用程序使用之前,需要对数据进行初始化。可以在模型中实现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->status);
}
public function afterFetch()
{
// 将字符串转换成数组
$this->status = explode(',', $this->status);
}
public function afterSave()
{
// 将字符串转换成数组
$this->status = explode(',', $this->status);
}
}
- 生成运算(Generating Calculations)
运算(或聚合)是数据库中常用的辅助方法,如COUNT,SUM,MAX,MIN和AVG。Phalcon\Mvc\Model可以直接使用这些方法。
count示例:
<?php
// 表employees总记录数
$rowcount = Employees::count();
// 表employees共有多少不同areas值
$rowcount = Employees::count(
[
'distinct' => 'area',
]
);
// 表employees共有多少area为'Testing'的记录
$rowcount = Employees::count(
'area = "Testing"'
);
// 按area分组统计表employees记录
$group = Employees::count(
[
'group' => 'area',
]
);
foreach ($group as $row) {
echo 'There are ', $row->rowcount, ' in ', $row->area;
}
// 按area分组统计表employees记录,并根据数目排序
$group = Employees::count(
[
'group' => 'area',
'order' => 'rowcount',
]
);
// 使用参数绑定避免SQL注入
$group = Employees::count(
[
'type > ?0',
'bind' => [
$type,
],
]
);
Sum示例:
<?php
// 所有employees的salaries总和
$total = Employees::sum(
[
'column' => 'salary',
]
);
// area = 'Sales'的所有employees的salaries总和
$total = Employees::sum(
[
'column' => 'salary',
'conditions' => 'area = "Sales"',
]
);
// 根据area分组统计salaries
$group = Employees::sum(
[
'column' => 'salary',
'group' => 'area',
]
);
foreach ($group as $row) {
echo 'The sum of salaries of the ', $row->area, ' is ', $row->sumatory;
}
// 根据area分组统计salaries,salaries由高到低排序
$group = Employees::sum(
[
'column' => 'salary',
'group' => 'area',
'order' => 'sumatory DESC',
]
);
// 使用参数绑定避免参数绑定
$group = Employees::sum(
[
'conditions' => 'area > ?0',
'bind' => [
$area,
],
]
);
Max / Min示例:
<?php
// 所有employees中age最大的
$age = Employees::maximum(
[
'column' => 'age',
]
);
// area = 'Sales'的employees中age最大的
$age = Employees::maximum(
[
'column' => 'age',
'conditions' => 'area = "Sales"',
]
);
// 所有employees中salary最低的
$salary = Employees::minimum(
[
'column' => 'salary',
]
);
- 创建 / 更新记录(Creating / Updating Records)
Phalcon\Mvc\Model::save()方法会根据记录是否存在于模型映射表中而创建 / 更新记录,Phalcon\Mvc\Model的创建和更新方法会在内部调用该方法。为此,必须在实体中正确定义主键,以确定是创建记录还是更新记录。
<?php
use Store\Toys\Robots;
$robot = new Robots();
$robot->type = 'mechanical';
$robot->name = 'Astro Boy';
$robot->year = 1952;
if ($robot->save() === false) {
echo "Umh, We can't store robots right now: \n";
$messages = $robot->getMessages();
foreach ($messages as $message) {
echo $message, "\n";
}
} else {
echo 'Great, a new robot was saved successfully!';
}
- 创建 / 更新执行结果(Create / Update with Confidence)
应用程序高并发时,创建记录操作可能会变成更新操作。使用Phalcon\Mvc\Model::save()方法保存记录时,可能发生这种情况。如果想确保执行创建或更新,可以使用create()和update()方法替换save():
<?php
use Store\Toys\Robots;
$robot = new Robots();
$robot->type = 'mechanical';
$robot->name = 'Astro Boy';
$robot->year = 1952;
// 仅创建记录
if ($robot->create() === false) {
echo "Umh, We can't store robots right now: \n";
$messages = $robot->getMessages();
foreach ($messages as $message) {
echo $message, "\n";
}
} else {
echo 'Great, a new robot was created successfully!';
}
create()方法和update()方法同样接受一个数组作为参数。
- 删除记录(Deleting Records)
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->getMessages();
foreach ($messages as $message) {
echo $message, "\n";
}
} else {
echo 'The robots was deleted successfully!';
}
}
- 表前缀(Table prefixes)
如果希望所有表名称都有特定前缀,并且不想在每个模型中都调用setSource()方法,则可以调用Phalcon\Mvc\Model\Manager的setModelprefix()方法:
<?php
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Manager;
class Robots extends Model
{
}
$manager = new Manager();
$manager->setModelPrefix('wp_');
$robots = new Robots(null, null, $manager);
echo $robots->getSource(); // 返回wp_robots
- 独立列映射(Independent Column Mapping)
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()
{
return [
'id' => 'code',
'the_name' => 'theName',
'the_type' => 'theType',
'the_year' => 'theYear',
];
}
}
然后,可以使用新的字段名:
<?php
use Store\Toys\Robots;
// 根据name查找一个记录
$robot = Robots::findFirst(
'theName = "Voltron"'
);
echo $robot->theName, "\n";
// 根据type排序
$robot = Robots::find(
[
'order' => 'theType DESC',
]
);
foreach ($robots as $robot) {
echo 'Code: ', $robot->code, "\n";
}
// 添加记录
$robot = new Robots();
$robot->code = '10101';
$robot->theName = 'Bender';
$robot->theType = 'Industrial';
$robot->theYear = 2999;
$robot->save();
- 多数据库配置(Setting multiple databases)
Phalcon应用中,所有模型可以属于相同的数据库连接,或具有独立的数据库连接。实际上,当Phalcon\Mvc\Model需要连接数据库时,它会在应用程序的服务容器中请求数据库服务。可以在initialize()方法中设置,覆盖该服务:
<?php
use Phalcon\Db\Adapter\Pdo\Mysql as MysqlPdo;
use Phalcon\Db\Adapter\Pdo\PostgreSQL as PostgreSQLPdo;
// 注册MySQL数据库服务
$di->set(
'dbMysql',
function () {
return new MysqlPdo(
[
'host' => 'localhost',
'username' => 'root',
'password' => 'secret',
'dbname' => 'invo',
]
);
}
);
// 注册PostgreSQL数据库服务
$di->set(
'dbPostgres',
function () {
return new PostgreSQLPdo(
[
'host' => 'localhost',
'username' => 'postgres',
'password' => '',
'dbname' => 'invo',
]
);
}
);
然后,在initialize()方法中为模型定义连接服务:
<?php
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public function initialize()
{
$this->setConnectionService('dbPostgres');
}
}
Phalcon提供了更灵活的操作,可以定义只读连接或写连接,这对于负载均衡和主从架构的的数据库非常有用:
<?php
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public function initialize()
{
$this->setReadConnectionService('dbSlave');
$this->setWriteConnectionService('dbMaster');
}
}
- 注入服务到模型(Injecting services into Models)
有时可能需要在模型中访问应用程序服务,以下示例介绍了如何执行此操作:
<?php
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public function notSaved()
{
// 从DI容器中获取flash服务
$flash = $this->getDI()->getFlash();
$messages = $this->getMessages();
// 显示验证消息
foreach ($messages as $message) {
$flash->error($message);
}
}
}
网友评论