×
PhalApi 2.x 前言

PhalApi 2.x 快速开发

1.1 下载与安装1.2 运行Hello World1.3 如何请求接口服务1.4 接口响应与在线调试1.5 Api接口层1.6 Domain领域层和ADM模式1.7 Model数据层与数据库操作1.8 单元测试1.9 自动加载和PSR-41.10 接口文档

PhalApi 2.x 数据库

2.1 数据库链接2.2 数据库与NotORM2.3 数据库使用和查询2.4 数据库分库分表策略2.5 连接多个数据库2.6 打印和保存SQL语句2.7 定制你的Model基类

PhalApi 2.x 高级专题

3.1 PhalApi 2.x 接口参数3.2 PhalApi 2.x 配置3.3 PhalApi 2.x 日志3.4 PhalApi 2.x 缓存3.5 PhalApi 2.x 过滤器3.6 PhalApi 2.x COOKIE3.7 PhalApi 2.x 加密3.8 PhalApi 2.x 国际化3.9 PhalApi 2.x CURL请求3.10 PhalApi 2.x 工具和杂项3.11 PhalApi 2.x DI服务汇总

PhalApi 2.x 发现更多

4.1 PhalApi 2.x 扩展类库4.2 PhalApi 2.x SDK包的使用4.3 PhalApi 2.x 脚本命令

关于PhalApi 2.x

5.1 PhalApi 2.x 版本完美诠释5.2 PhalApi 2.x 升级指南5.3 PhalApi 2.x VS PhalApi 1.x

2.3 PhalApi 2.x 数据库使用和查询


这一章,主要讲解PhalApi主流的数据库使用方式。

常用:数据库操作大全

基本上,全部的常用数据库操作,都可以在下面找到对应的使用说明,以及演示示例。

假设对于前面的tbl_user表,有以下数据。

INSERT INTO `tbl_user` VALUES ('1', 'dogstar', '18', 'oschina', '2015-12-01 09:42:31');
INSERT INTO `tbl_user` VALUES ('2', 'Tom', '21', 'USA', '2015-12-08 09:42:38');
INSERT INTO `tbl_user` VALUES ('3', 'King', '100', 'game', '2015-12-23 09:42:42');

下面将结合示例,分别介绍NotORM更为丰富的数据库操作。在开始之前,假定已有:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        $user = $this->getORM();  // 在Model子类内,进行数据库操作前,先获取NotORM实例

        // $user = $this->getORM(1000);  // getORM()的第一个参数是指进行分表的依据,没有时可不传
    }
}

SQL基本语句介绍

  • SELECT字段选择

选择单个字段:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
  public function test() {
      // SELECT id FROM `tbl_user`
      return $this->getORM()->select('id')->fetchAll();
  }
}

选择多个字段:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // SELECT id, name, age FROM `tbl_user`
        return $this->getORM()->select('id, name, age')->fetchAll();
    }
}

使用字段别名:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // SELECT id, name, MAX(age) AS max_age FROM `tbl_user`
        return $this->getORM()->select('id, name, MAX(age) AS max_age')->fetchAll();
    }
}

选择全部表字段:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // SELECT * FROM `tbl_user`
        return $this->getORM()->select('*')->fetchAll();

        // 或不指定select字段,默认取全部
        return $this->getORM()->fetchAll();
    }
}

选择去重后的字段:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // SELECT DISTINCT name FROM `tbl_user`
        return $this->getORM()->select('DISTINCT name')->fetchAll();
    }
}
  • WHERE条件

单个条件:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // WHERE id = 1
        return $this->getORM()->where('id', 1)->fetchOne();

        // 或 使用占位符传参
        return $this->getORM()->where('id = ?', 1)->fetchOne();

        // 或 数组形式传参
        return $this->getORM()->where(array('id', 1))->fetchOne();
    }
}

多个AND条件:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // WHERE id > 1 AND age > 18
        return $this->getORM()->where('id > ?', 1)->where('age > ?', 18)->fetchAll();

        // 或 用多个 and 连贯操作
        return $this->getORM()->and('id > ?', 1)->and('age > ?', 18)->fetchAll();

        // 或 用含占位符的字符串组合多个条件
        return $this->getORM()->where('id > ? AND age > ?', 1, 18)->fetchAll();

        // 或 用多个元素的数组传参
        return $this->getORM()->where(array('id > ?' => 1, 'age > ?' => 10))->fetchAll();
    }

    public function test2() {
        // 如果只是判断相等,可以直接不用比较符号
        // WHERE name = 'dogstar' AND age = 18
        return $this->getORM()->where(array('name' => 'dogstar', 'age' => 18))->fetchAll();
    }
}

多个OR条件:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // WHERE name = 'dogstar' OR age = 18
        return $this->getORM()->or('name', 'dogstar')->or('age', 18)->fetchAll();
    }
}

嵌套条件:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // WHERE ((name = ? OR id = ?)) AND (note = ?) -- 'dogstar', '1', 'xxx'

        // 实现方式1:使用AND拼接
        return $this->getORM()->where('(name = ? OR id = ?)', 'dogstar', '1')->and('note = ?', 'xxx')->fetchAll();

        // 实现方式2:使用WHERE,并顺序传递多个参数
        return $this->getORM()->where('(name = ? OR id = ?) AND note = ?', 'dogstar', '1', 'xxx')->fetchAll();

        // 实现方式3:使用WHERE,并使用一个索引数组顺序传递参数
        return $this->getORM()->where('(name = ? OR id = ?) AND note = ?', array('dogstar', '1', 'xxx'))->fetchAll();

        // 实现方式4:使用WHERE,并使用一个关联数组传递参数
        return $this->getORM()->where('(name = :name OR id = :id) AND note = :note', 
            array(':name' => 'dogstar', ':id' => '1', ':note' => 'xxx'))->fetchAll();
    }
}

IN查询:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // 简单的IN查询
        // WHERE id IN (1, 2, 3)
        return $this->getORM()->where('id', array(1, 2, 3))->fetchAll();
    }

    public function test2() {
        // 排除IN
        // WHERE id NOT IN (1, 2, 3)
        return $this->getORM()->where('NOT id', array(1, 2, 3))->fetchAll();
    }

    public function test3() {
        // 多个IN查询
        // WHERE (id, age) IN ((1, 18), (2, 20))
        return $this->getORM()->where('(id, age)', array(array(1, 18), array(2, 20)))->fetchAll();
    }
}

模糊匹配查询:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // 像
        // WHERE name LIKE '%dog%'
        return $this->getORM()->where('name LIKE ?', '%dog%')->fetchAll();
    }

    public function test2() {
        // 不像
        // WHERE name NOT LIKE '%dog%'
        return $this->getORM()->where('name NOT LIKE ?', '%dog%')->fetchAll();
    }
}

温馨提示:需要模糊匹配时,不可写成:where('name LIKE %?%', 'dog')。

NULL判断查询:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // WHERE (name IS NULL)
        return $this->getORM()->where('name', null)->fetchAll();
    }
}

非NULL判断查询:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // WHERE (name IS NULL)
        return $this->getORM()->where('name IS NOT ?', null)->fetchAll();
    }
}
  • ORDER BY排序

单个字段升序排序:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // ORDER BY age
        return $this->getORM()->order('age')->fetchAll();

        // 或指定排序方式,默认是升序
        return $this->getORM()->order('age ASC')->fetchAll();
    }
}

单个字段降序排序:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // ORDER BY age DESC
        return $this->getORM()->order('age DESC')->fetchAll();
    }
}

多个字段排序:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // ORDER BY id, age DESC
        return $this->getORM()->order('id')->order('age DESC')->fetchAll();

        // 或 连起来写
        return $this->getORM()->order('id, age DESC')->fetchAll();
    }
}
  • LIMIT数量限制

限制数量,如查询前10个:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // LIMIT 10
        return $this->getORM()->limit(10)->fetchAll();
    }
}

分页限制,如从第5个位置开始,查询前10个:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // LIMIT 5, 10
        return $this->getORM()->limit(5, 10)->fetchAll();
    }
}
  • GROUP BY和HAVING

只有GROUP BY,没有HAVING:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // GROUP BY note
        return $this->getORM()->group('note')->fetchAll();
    }
}

既有GROUP BY,又有HAVING:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // GROUP BY note HAVING age > 10
        return $this->getORM()->group('note', 'age > 10')->fetchAll();
    }
}

CURD之插入操作

插入操作可分为插入单条纪录、多条纪录,或根据条件插入。

操作 说明 示例 备注 是否PhalApi新增
insert() 插入数据 $user->insert($data); 全局方式需要再调用insert_id()获取插入的ID
insert_multi() 批量插入 $user->insert_multi($rows, $isIgnore = FALSE); 可批量插入 否,但有优化,$isIgnore为TRUE时进行INSERT IGNORE INTO操作
insert_update() 插入/更新 接口签名:insert_update(array $unique, array $insert, array $update = array() 不存时插入,存在时更新

插入单条纪录数据,注意,必须是保持状态的同一个NotORM表实例,方能获取到新插入的行ID,且表必须设置了自增主键ID。

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        $data = array('name' => 'PhalApi', 'age' => 1, 'note' => 'framework');

        // INSERT INTO tbl_user (name, age, note) VALUES ('PhalApi', 1, 'framework')
        $orm = $this->getORM();
        $orm->insert($data);

        // 返回新增的ID(注意,这里不能使用连贯操作,因为要保持同一个ORM实例)
        return $orm->insert_id();
    }
}

或者使用Model封装的insert()基本方法

// App\Model\User类,不需要额外的实现
$model = new App\Model\User();
$id = $model->insert($data);
var_dump($id); // 返回新增的ID

批量插入多条纪录数据:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        $rows = array(
            array('name' => 'A君', 'age' => 12, 'note' => 'AA'),
            array('name' => 'B君', 'age' => 14, 'note' => 'BB'),
            array('name' => 'C君', 'age' => 16, 'note' => 'CC'),
        );

        // INSERT INTO tbl_user (name, age, note) VALUES ('A君', 12, 'AA'), ('B君', 14, 'BB'), ('C君', 16, 'CC')
        // 返回成功插入的条数
        return $this->getORM()->insert_multi($rows);

        // PhalApi 2.2.0 及以上版本才支持
        // 如果希望使用 IGNORE ,可加传第二个参数
        // INSERT IGNORE INTO tbl_user (name, age, note) VALUES ('A君', 12, 'AA'), ('B君', 14, 'BB'), ('C君', 16, 'CC') 
        return $this->getORM()->insert_multi($rows, true);
    }
}

插入/更新(组合操作:有则更新,没有则插入):

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        $unique = array('id' => 8);
        $insert = array('id' => 8, 'name' => 'PhalApi', 'age' => 1, 'note' => 'framework');
        $update = array('age' => 2);

        // INSERT INTO tbl_user (id, name, age, note) VALUES (8, 'PhalApi', 1, 'framework') 
        // ON DUPLICATE KEY UPDATE age = 2
        // 返回影响的行数
        return $this->getORM()->insert_update($unique, $insert, $update);
    }
}

CURD之更新操作

操作 说明 示例 备注 是否PhalApi新增
update() 更新数据 $user->where('id', 1)->update($data); 更新异常时返回false,数据无变化时返回0,成功更新返回影响的行数
updateCounter() 更新单个计数器 接口签名:updateCounter($column, $number = 1),示例:$user->where('id', 1)->updateCounter('age', 1) 返回影响的行数 是,PhalApi 2.6.0 版本及以上支持
updateMultiCounters() 更新多个计数器 接口签名:updateMultiCounters(array $data),示例:$user->where('id', 1)->updateMultiCounters(array('age' => 1)) 返回影响的行数 是,PhalApi 2.6.0 版本及以上支持

根据条件更新数据:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        $data = array('age' => 2);

        // UPDATE tbl_user SET age = 2 WHERE (name = 'PhalApi');

        // 返回更新后的结果(注意区分微妙细节)
        // int(1),表示 正常影响的行数
        // int(0),表示 无更新,或者数据没变化
        // boolean(false),表示 更新异常、失败

        return $this->getORM()->where('name', 'PhalApi')->update($data);
    }
}

再重复一下,对于更新后返回的结果。

  • int(1),表示 正常影响的行数
  • int(0),表示 无更新,或者数据没变化
  • boolean(false),表示 更新异常、失败

在使用update()进行更新操作时,如果更新的数据和原来的一样,则会返回0(表示影响0行)。这时,会和更新失败(同样影响0行)混淆。但NotORM是一个优秀的类库,它已经提供了优秀的解决文案。我们在使用update()时,只须了解这两者返回结果的微妙区别即可。因为失败异常时,返回false;而相同数据更新会返回0。即:

  • 1、更新相同的数据时,返回0,严格来说是:int(0)
  • 2、更新失败时,如更新一个不存在的字段,返回false,即:bool(false)

用代码表示,就是:

$model = new \App\Model\User();
$rs = $model->test();

if ($rs >= 1) {
    // 成功
} else if ($rs === 0) {
    // 相同数据,无更新
} else if ($rs === false) {
    // 更新失败
}

更新数据,进行加1操作:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // UPDATE tbl_user SET age = age + 1 WHERE (name = 'PhalApi')
        // 返回影响的行数
        return $this->getORM()->where('name', 'PhalApi')->update(array('age' => new \NotORM_Literal("age + 1")));
    }
}

温馨提示:在2.x版本中,当需要使用NotORM_Literal类进行加1操作时,须注意两点:需要先获取NotORM实例再创建NotORM_Literal对象;注意命名空间,即要在最前面加反斜杠。

上面的数据更新操作更灵活,因为可以在更新其它字段的同时进行数值的更新。但考虑到还需要创建NotORM_Literal对象,增加了认知成本。所以对于简单的计数器更新操作,可以使用updateCounter()接口。

注意:updateCounter()接口和updateMultiCounters()接口,需要PhalApi 2.6.0 及以上版本,方可支持。

对比改用updateCounter()接口后的简化版本:

class User extends NotORM {
    public function test() {
        // UPDATE tbl_user SET age = age + 1 WHERE (name = 'PhalApi')
        // 返回影响的行数
        return $this->getORM()->where('name', 'PhalApi')->updateCounter('age');
    }
}

须留意到,updateCounter()的第一个参数是字段名称,第二个参数是待更新数值,可以是正数或负数,默认是1,表示加1。返回的结果是影响的行数,而非最新的字段值。下以是更多示例:

// 加1
$this->getORM()->where('name', 'PhalApi')->updateCounter('age', 1);

// 减1
$this->getORM()->where('name', 'PhalApi')->updateCounter('age', -1);

与此相似,updateMultiCounters()接口也可用于更新计数器,不同的是此接口可以同时更新多个计数器,且第一个参数是数组。数组下标为字段名,数组元素值为待更新数值。例如:

// age加1,同时points加10
$this->getORM()->where('name', 'PhalApi')->updateMultiCounters(array('age' => 1, 'points' => 10));

// age减1,同时points减10
$this->getORM()->where('name', 'PhalApi')->updateMultiCounters(array('age' => -1, 'points' => -10));

CURD之查询操作

查询操作主要有获取一条纪录、获取多条纪录以及聚合查询等。

操作 说明 示例 备注 是否PhalApi新增
fetch() 循环获取每一行 while($row = $user->fetch()) { ... ... }
fetchOne() 只获取第一行 $row = $user->where('id', 1)->fetchOne(); 等效于fetchRow()
fetchRow() 只获取第一行 $row = $user->where('id', 1)->fetchRow(); 等效于fetchOne()
fetchPairs() 获取键值对 $row = $user->fetchPairs('id', 'name'); 第二个参数为空时,可取多个值,并且多条纪录;也可以指定单个字段,还可以指定多个字段。
fetchAll() 获取全部的行 $rows = $user->where('id', array(1, 2, 3))->fetchAll(); 等效于fetchRows()
fetchRows() 获取全部的行 $rows = $user->where('id', array(1, 2, 3))->fetchRows(); 等效于fetchAll()
queryAll() 复杂查询下获取全部的行,默认下以主键为下标 $rows = $user->queryAll($sql, $parmas); 等效于queryRows()
queryRows() 复杂查询下获取全部的行,默认下以主键为下标 $rows = $user->queryRows($sql, $parmas); 等效于queryAll()
count() 查询总数 $total = $user->count('id'); 第一参数可省略
min() 取最小值 $minId = $user->min('id');
max() 取最大值 $maxId = $user->max('id');
sum() 计算总和 $sum = $user->sum('age');

循环获取每一行,并且同时获取多个字段:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // SELECT id, name FROM tbl_user WHERE (age > 18);
        $orm = $this->getORM()->select('id, name')->where('age > 18');

        while ($row = $orm->fetch()) {
            var_dump($row);
        }
    }
}

// 输出
array(2) {
  ["id"]=>
  string(1) "2"
  ["name"]=>
  string(3) "Tom"
}
array(2) {
  ["id"]=>
  string(1) "3"
  ["name"]=>
  string(4) "King"
}
... ...

循环获取每一行,并且只获取单个字段。需要注意的是,指定获取的字段,必须出现在select里,并且返回的不是数组,而是字符串。

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // SELECT id, name FROM tbl_user WHERE (age > 18);
        $orm = $this->getORM()->select('id, name')->where('age > 18');

        while ($row = $orm->fetch('name')) { // 指定获取的单个字段 
            var_dump($row); // 此时,输出的是一个字段值,而非一条数组纪录
        }
    }
}


// 输出
string(3) "Tom"
string(4) "King"
... ...

注意!以下是错误的用法。还记得前面所学的NotORM状态的保持吗?因为这里每次循环都会新建一个NotORM表实例,所以没有保持前面的查询状态,从而死循环。

while ($row = $this->getORM()->select('id, name')->where('age > 18')->fetch('name')) {
     var_dump($row);
}

只获取第一行,并且获取多个字段,等同于fetchRow()操作。

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // SELECT id, name FROM tbl_user WHERE (age > 18) LIMIT 1;
        return $this->getORM()->select('id, name')->where('age > 18')->fetchOne();
    }
}

// 返回结果示例
array(2) {
  ["id"]=>
  string(1) "2"
  ["name"]=>
  string(3) "Tom"
}

只获取第一行,并且只获取单个字段,等同于fetchRow()操作。

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // SELECT id, name FROM tbl_user WHERE (age > 18) LIMIT 1;
        return $this->getORM()->where('age > 18')->fetchOne('name'); // 只获取单个字段 
    }
}

// 返回结果示例
string(3) "Tom"

获取键值对,并且获取多个字段:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // SELECT id, name, age FROM tbl_user LIMIT 2;
        return $this->getORM()->select('name, age')->limit(2)->fetchPairs('id'); //指定以ID为KEY
    }
}

// 返回结果示例
array(2) {
  [1]=> // 下标对应id字段
  array(3) {
    ["id"]=>
    string(1) "1"
    ["name"]=>
    string(7) "dogstar"
    ["age"]=>
    string(2) "18"
  }
  [2]=>
  array(3) {
    ["id"]=>
    string(1) "2"
    ["name"]=>
    string(3) "Tom"
    ["age"]=>
    string(2) "21"
  }
}

获取键值对,并且只获取单个字段。注意,这时的值不是数组,而是字符串。

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // SELECT id, name FROM tbl_user LIMIT 2
        return $this->getORM()->limit(2)->fetchPairs('id', 'name'); //通过第二个参数,指定VALUE的列
    }
}

// 返回结果示例
array(2) {
  [1]=>
  string(7) "dogstar"
  [2]=>
  string(3) "Tom"
}

获取全部的行,相当于fetchRows()操作。

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // SELECT * FROM tbl_user
        return $this->getORM()->fetchAll(); // 全部表数据
    }
}

高级:使用原生SQL语句进行查询

使用原生SQL语句进行查询,并获取全部的行:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // SELECT name FROM tbl_user WHERE age > :age LIMIT 1
        $sql = 'SELECT name FROM tbl_user WHERE age > :age LIMIT 1';
        $params = array(':age' => 18);

        return $this->getORM()->queryAll($sql, $params);
    }

    // 除了使用上面的关联数组传递参数,也可以使用索引数组传递参数
    public function test2() {
        // SELECT name FROM tbl_user WHERE age > :age LIMIT 1
        $sql = 'SELECT name FROM tbl_user WHERE age > ? LIMIT 1';
        $params = array(18);

        // 也使用queryRows()别名
        return $this->getORM()->queryRows($sql, $params);
    }
}

// 输出
array(1) {
  [0]=>
  array(1) {
    ["name"]=>
    string(3) "Tom"
  }
}

在使用queryAll()或者queryRows()进行原生SQL操作时,需要特别注意:

  • 1、需要手动填写完整的表名字,包括分表标识,并且需要通过任意表实例来运行
  • 2、尽量使用参数绑定,而不应直接使用参数来拼接SQL语句,慎防SQL注入攻击

下面是不好的写法,很有可能会导致SQL注入攻击

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    // 存在SQL注入的写法
    public function test() {
        // 存在SQL注入的写法
        $id = 1;
        $sql = "SELECT * FROM tbl_demo WHERE id = $id";

        // 存在SQL注入的写法
        return $this->getORM()->queryAll($sql);
    }
}

对于外部不可信的输入数据,应改用参数传递的方式。

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    // 使用参数绑定方式,避免SQL注入
    public function test() {
        $id = 1;
        $sql = "SELECT * FROM tbl_demo WHERE id = ?";

        return $this->getORM()->queryAll($sql, array($id));
    }
}

查询总数:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // SELECT COUNT(id) FROM tbl_user
        return $this->getORM()->count('id');
    }
}

// 输出
string(3) "3"

查询最小值:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // SELECT MIN(age) FROM tbl_user
        return $this->getORM()->min('age');
    }
}

// 输出
string(2) "18"

查询最大值:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // SELECT MAX(age) FROM tbl_user
        return $this->getORM()->max('age');
    }
}

// 输出
string(3) "100"

计算总和:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // SELECT SUM(age) FROM tbl_user
        return $this->getORM()->sum('age');
    }
}

// 输出
string(3) "139"

CURD之删除操作

操作 说明 示例 备注 是否PhalApi新增
delete() 删除 $user->where('id', 1)->delete(); 禁止无where条件的删除操作

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // DELETE FROM tbl_user WHERE (id = 404);
        // 按条件进行删除,并返回影响的行数
        return $this->getORM()->where('id', 404)->delete();
    }
}

请特别注意,PhalApi禁止全表删除操作。即如果是全表删除,将会被禁止,并抛出异常。如:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // DELETE FROM tbl_user WHERE (id = 404);
        // Exception: sorry, you can not delete the whole table
        // 禁止全表删除!!!
        return $this->getORM()->delete(); 
    }
}

高级:执行原生sql操作并返回结果

简单总结一下,对于执行原生sql操作的支持,主要有以下三个接口:

  • queryAll/queryRows,主要用于进行SELECT查询,并可以返回查询的数据结果集
  • executeSql,主要用于进行带返回结果的UPDATE、INSERT、DELETE以及数据库变更等操作,但只会返回影响的行数
  • query,最底层的原生操作,不返回任何结果

接下来,简单通过示例说明executeSql()接口的使用。

请注意,executeSql()接口需要PhalApi 2.6.0 及以上版本,方可支持。

如果是在Model子类内,可以这样实现:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class NotORMTest extends NotORM {
    public function getTableName($id) {
        return 'notormtest';
    }

    public function executeSqlInsert() {
        // 原生插入
        $sql = "INSERT  INTO tbl_notormtest (`content`, `ext_data`) VALUES ('phpunit_e_sql_1', '" . '{\"year\":2019}' . "');";
        return $this->executeSql($sql);
    }

    public function executeSqlUpdate) {
        // 原生更新
        $sql = "UPDATE tbl_notormtest SET `content` = 'phpunit_e_sql_3' WHERE (content = ? OR content = ?);";
        $params = array('phpunit_e_sql_1', 'phpunit_e_sql_2');
        return $this->getORM()->executeSql($sql, $params);
    }

    public function executeSqlDelete() {
        // 原生删除
        $sql = "DELETE FROM tbl_notormtest WHERE (content IN ('phpunit_e_sql_3'));";
        return $this->getORM()->executeSql($sql);
    }
}

另一方面,也可以通过全局NotORM实例来调用,并通过单元测试来验证执行的效果是否符合预期。

    public function testExcuteSql()
    {
        // 原生插入
        $sql = "INSERT  INTO tbl_notormtest (`content`, `ext_data`) VALUES ('phpunit_e_sql_1', '" . '{\"year\":2019}' . "');";
        $rs = \PhalApi\DI()->notorm->notormtest->executeSql($sql);

        $this->assertEquals(1, $rs);

        // 原生绑定参数插入
        $sql = "INSERT  INTO tbl_notormtest (`content`, `ext_data`) VALUES (:content, :ext_data);";
        $params = array(':content' => 'phpunit_e_sql_2', ':ext_data' => '{\"year\":2020}');
        $rs = \PhalApi\DI()->notorm->notormtest->executeSql($sql, $params);

        $this->assertEquals(1, $rs);

        // 原生更新
        $sql = "UPDATE tbl_notormtest SET `content` = 'phpunit_e_sql_3' WHERE (content = ? OR content = ?);";
        $params = array('phpunit_e_sql_1', 'phpunit_e_sql_2');
        $rs = \PhalApi\DI()->notorm->notormtest->executeSql($sql, $params);

        $this->assertEquals(2, $rs);

        // 如果是查询呢?只会返回影响的行数,而非结果
        $sql = "SELECT * FROM tbl_notormtest WHERE content IN ('phpunit_e_sql_3')";
        $rs = \PhalApi\DI()->notorm->notormtest->executeSql($sql, $params);

        $this->assertEquals(2, $rs);

        // 原生删除
        $sql = "DELETE FROM tbl_notormtest WHERE (content IN ('phpunit_e_sql_3'));";
        $rs = \PhalApi\DI()->notorm->notormtest->executeSql($sql);

        $this->assertEquals(2, $rs);
    }

复杂:事务操作、关联查询和其他操作

事务操作

事务的操作,主要分为三部分操作。分别是:

  • 开始,开启事务
  • 中间,进行事务数据操作
  • 最后,提交事务,或者回滚操作

事务是针对数据库级别的,下面再分全局方式和局部方式进行说明。

如果使用全局方式获取NotORM实例,那么可以在数据库层面开启事务。以下是事务操作的一个示例。

    // Step 1: 开启事务
    \PhalApi\DI()->notorm->beginTransaction('db_master');

    // Step 2: 数据库操作
    \PhalApi\DI()->notorm->user->insert(array('name' => 'test1'));
    \PhalApi\DI()->notorm->user->insert(array('name' => 'test2'));

    // Step 3: 提交事务/回滚
    \PhalApi\DI()->notorm->commit('db_master');
    //\PhalApi\DI()->notorm->rollback('db_master');

上面通过第一行代码,开启了事务。此时NotORM实例是在./config/di.php文件中注册的默认\PhalApi\DI()->notorm服务,参数db_master是表示数据库标识,对应./config/dbs.php配置中的数据库标识。注意,不是真实的数据库名称。

return array(
    /**
     * DB数据库服务器集群
     */
    'servers' => array(
        'db_master' => array(                         //服务器标记(对应这里的数据库标记)
            'host'      => '127.0.0.1',             //数据库域名
            // 略……
        ),
    ),
);

中间的代码,是业务层需要完成的事务操作,例如这里插入了两条数据到user表。

    // Step 2: 数据库操作
    \PhalApi\DI()->notorm->user->insert(array('name' => 'test1'));
    \PhalApi\DI()->notorm->user->insert(array('name' => 'test2'));

最后,是提交事务。提交或回滚操作时,参数也是数据库标记,和前面的一样,在这里都是db_master。

    // Step 3: 提交事务/回滚
    \PhalApi\DI()->notorm->commit('db_master');
    //\PhalApi\DI()->notorm->rollback('db_master');

另一方面,如果是在Model子类封装的操作,也可以通过当前的局部获取方式获取到NotORM实例后来开启事务。这时,实现方式可以和上面一样。也可以不指定数据库标记来完成,因为已经在./config/dbs.php中配置了数据库表和数据库的映射关系。因此可以这样编写代码:

class User extends NotORM {
    public function doSthImportant() {
        // Step 1: 开启事务
        $this->getORM()->transaction('BEGIN');

        // Step 2: 数据库操作
        $id1 = $this->getORM()->insert(array('name' => 'test1'));
        $id2 = $this->getORM()->insert(array('name' => 'test2'));

        // Step 3: 提交事务/回滚
        if ($id1 >0 && $id > 0) {
            // 提交事务
            $this->getORM()->transaction('COMMIT');
        } else {
            // 回滚事务
            $this->getORM()->transaction('ROLLBACK');
        }
    }
}

正如前面所说,此时不需要指定具体的数据库标记。因为每个Model类已经指定了那个数据库。但这里的接口和参数又有所差异。

在Model子类内,可以:

  • 开启事务:$this->getORM()->transaction('BEGIN');
  • 提交事务:$this->getORM()->transaction('COMMIT');
  • 回滚事务:$this->getORM()->transaction('ROLLBACK');

关联查询

对于关联查询,简单的关联可使用NotORM封装的方式,而复杂的关联,如多个表的关联查询,则可以使用PhalApi封装的接口。

如果是简单的关联查询,可以使用NotORM支持的写法,这样的好处在于我们使用了一致的开发,并且能让PhalApi框架保持分布式的操作方式。需要注意的是,关联的表仍然需要在同一个数据库。

以下是一个简单的示例。假设我们有这样的数据:

INSERT INTO `phalapi_user` VALUES ('1', 'wx_edebc', 'dogstar', '***', '4CHqOhe1', '1431790647', '');
INSERT INTO `phalapi_user_session_0` VALUES ('1', '1', 'ABC', '', '0', '0', '0', null);

那么对应关联查询的代码如下面:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class Session extends NotORM { // 注意,是Session类
    public function test() {
        // SELECT expires_time, user.username, user.nickname FROM tbl_session 
        // LEFT JOIN tbl_user AS user 
        // ON tbl_session.user_id = user.id 
        // WHERE (token = 'ABC') LIMIT 1
        return $this->getORM()
            ->select('expires_time, user.username, user.nickname')
            ->where('token', 'ABC')
            ->fetchRow();
    }
}

会得到类似这样的输出:

array(3) {
  ["expires_time"]=>
  string(1) "0"
  ["username"]=>
  string(35) "wx_edebc"
  ["nickname"]=>
  string(10) "dogstar"
}

这样,我们就可以实现关联查询的操作。按照NotORM官网的说法,则是:

If the dot notation is used for a column anywhere in the query ("$table.$column") then NotORM automatically creates left join to the referenced table. Even references across several tables are possible ("$table1.$table2.$column"). Referencing tables can be accessed by colon: $applications->select("COUNT(application_tag:tag_id)").

所以->select('expires_time, user.username, user.nickname')这一行调用将会NotORM自动产生关联操作,而ON的字段,则是这个字段关联你配置的表结构,外键默认为:表名_id 。

如果是复杂的关联查询,则是建议使用原生的SQL语句,但仍然可以保持很好的写法,如这样一个示例:

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class Vote extends NotORM {
    public function test() {
        $sql = 'SELECT t.id, t.team_name, v.vote_num '
          . 'FROM phalapi_team AS t LEFT JOIN phalapi_vote AS v '
          . 'ON t.id = v.team_id '
          . 'ORDER BY v.vote_num DESC';
        return $this->getORM()->queryAll($sql, array());
    }
}

如前面所述,这里需要手动填写完整的表名,以及慎防SQL注入攻击。

其他数据库操作

有时,我们还需要进行一些其他的数据库操作,如创建表、删除表、添加表字段等。对于需要进行的数据库操作,而上面所介绍的方法未能满足时,可以使用更底层更通用的接口,即:\NotORM_Result::query($query, $parameters)

例如,删除一张表。

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    public function test() {
        // DROP TABLE tbl_user
        return $this->getORM()->query('DROP TABLE tbl_user', array());
    }
}

分类导航

关注微信下载离线手册

bootwiki移动版 bootwiki
(群号:472910771)