简单易用的 MVC 开发框架
框架特色
- 核心代码不足1000行,仅两个文件便可工作,极速加载
- 单文件入口,不依赖
PathInfo
,入口文件即是配置文件,超级简洁
- 文件夹随意移动,轻松多项目共享,入口文件随意命名,CLI模式轻松使用
-
MYSQL/SQLITE
任意切换,注入过滤/ORM,文件缓存/HTTP缓存/数据库缓存,轻松安全
- 异常捕获,
DEBUG
日志,自定义错误页,自定义异常路由一应俱全
- 普通路由,正则路由,回调处理,百变URI随心所欲,插件模式,即插即用
- 文件加载自动完成,延迟按需加载、无需
Include
简单高效
安装配置
-
index.php
入口文件即配置文件,core.php
框架核心,外加一个处理请求的控制器文件
-
PHP8.0
及以上
- 使用PDO连接数据库,支持
MySQL
和Sqlite
,需开启PDO_MYSQL
- 定义配置文件的程序路径(一般不需改变)和其他参数,例如SMTP,数据库,即可完美使用
- 需要URL REWRITE支持,否则链接中要添加
index.php
- rewrite 即为一般的index.php rewrite写法
对于nginx
, (*1)
try_files $uri $uri/ /index.php$is_args$args;
加入location / {}
里面, (*2)
对于apache
, (*3)
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]
Web程序可将目录结构调整为, (*4)
├── app
│ ├── controller
│ │ └── home.php
│ ├── model
│ │ └── util.php
│ └── system
│ └── core.php
├── README.md
├── var
│ ├── html
│ └── log
└── www
└── index.php
开始使用
入口文件index.php
即为配置文件,主要配置控制器模型等地址, (*5)
配置项lib_path
为一个数组,配置控制器,模型,类库的自动查找地址, (*6)
view_path
为模板查找文件夹, (*7)
var_path
为缓存目录和日志目录, (*8)
文件夹 controller model system 其实并无区别, (*9)
系统并不是按照文件夹结构来区分 controller 和 model 而是按照类本身的特性, (*10)
包含__invoke
魔术方法的类将被视为控制器, (*11)
在控制器抛出异常时,此方法将被调用用于处理异常., (*12)
event
字段配置闭包函数可用于扩展app
, (*13)
- 所有加载过的控制器,都会记住是否已加载过,不会重复加载,也不会重复实例化
- 加载过的类库和模型全局可用
- 所有的同名文件类都可以直接通过类名调用其静态方法,或new实例化
对于低于8.1版本,需要实现array_is_list
函数, (*14)
if (!function_exists('array_is_list'))
{
function array_is_list(array $a)
{
return $a === [] || (array_keys($a) === range(0, count($a) - 1));
}
}
对于低于8.0版本,需要实现str_contains
函数, (*15)
if (!function_exists('str_contains'))
{
function str_contains(string $haystack, string $needle)
{
return empty($needle) || strpos($haystack, $needle) !== false;
}
}
DEBUG
配置文件debug
字段用于控制日志记录,其值可为0/1
或者 false/true
, (*16)
框架判断其布尔值,非debug
模式仅记录ERROR
级别日志,忽略INFO
,WARN
, (*17)
无论是否debug,只要日志目录可写,都会记录相应的错误。, (*18)
路由
框架同时包含正则路由和普通路由., (*19)
普通路由按照目录结构路由,, (*20)
正则路由按照URI匹配., (*21)
使用 route::get() 添加正则路由, (*22)
正则路由优先级高于普通路由., (*23)
正则路由有多种写法, (*24)
字符串函数, (*25)
route::get('/print','print_r');
闭包, (*26)
route::get('/dump',function(...$a){
var_dump($a);
});
实例化控制器, (*27)
控制器类将被自动实例化,然后执行, (*28)
route::get('/hello',['home','hello'])
route::get('/hello',['home','hello','world'])
静态化控制器, (*29)
控制器类方法将被静态调用, (*30)
route::post('/static','home::echo');
捕获参数, (*31)
route::get('/userinfo/(\d+)',['home','userinfo'])
带命名空间的静态调用,可调度到子文件夹,自己再次分发路由,admin
为namespace
,form
类无实例化,直接调用, (*32)
route::get('/hi/index','admin\form::index')
带命名空间的动态调用,可调度到子文件夹,form
类校验,并实例化, (*33)
route::get('/hello/hi',['admin\form','hi'])
二级,三级,文件夹同类, (*34)
闭包模式, (*35)
route::get('/about',function(){echo 'about';})
URL 拼接, (*36)
route::u('/home/hello',['act'=>'hi'])
重定向, (*37)
route::to('/login')
控制器
默认的控制器为home
,默认的action为index
, (*38)
控制器被实例化时,将传入触发实例化时的路由参数给构造函数, (*39)
控制器实现了单例模式,一个控制器只会实例化一次, (*40)
app::run(array $r)
可以内部转移控制器,交由其他控制器执行, (*41)
自定义异常处理器
除了控制器的__invoke
方法能处理异常外,全局异常可配置自定义处理, (*42)
在配置中添加notfound
和errfound
并赋值一个闭包,开启自定义错误处理, (*43)
'notfound' => function ($e, $cli) {
echo '404 page not found';
},
'errfound' => function ($e, $cli) {
echo 'user hand this error : ', $e->getMessage();
},
自定义异常处理后,框架提供的404,500等错误详情,页面将不再展现,只能通过日志查看, (*44)
使用缓存
框架內建2种缓存,可同时工作, (*45)
HTTP 缓存
HTTP 缓存使用 app::cache(int $second)
开启, (*46)
需要在控制器输出其他内容前调用, (*47)
客户端下次请求将会200(from cache)
, (*48)
有客户端发起协商缓存时,框架也会自动验证,命中时返回304
, (*49)
可用于缓存接口,页面等, (*50)
页面缓存
模板函数template(string $v, array $data = [], callable|int $callback = 0, string $path = '')
实现了页面缓存, (*51)
设置$callback
为一个大于1的秒数,即可开启页面自动缓存, (*52)
同时必须确保配置项var_path
是可写的,缓存文件存储在其html
子文件夹, (*53)
页面缓存的缓存键是当前请求的URI
,不包含query部分, (*54)
可用于缓存公共页面,不可缓存带有个人信息的页面, (*55)
请求来临时同样鉴别对应的URI
是否已缓存, (*56)
命中时释放缓存,缓存失效时删除缓存, (*57)
配置$callback
为1,则返回模板渲染的数据而不是直接输出,配置为闭包可以获取渲染结果,手动处理后续逻辑, (*58)
验证器
request::verify(array $rule, array|bool $post = true, bool|callable $callback = false)
, (*59)
$post
为要验证的数据,若非数组,则true代表$_POST,false代表$_REQUEST, (*60)
$rule
, (*61)
例:, (*62)
$r =
[
'q' => ['maxlength=50' => 'q最大50字符'],
'type' => ['set=video' => 'type不合法'],
'order' => ['set=viewCount' => 'order不合法'],
'channelId' => ['/^[\w\-]{20,40}$/' => 'channelId为20-40位字母'],
'pageToken' => ['/^[\w\-]{4,14}$/' => 'pageToken为4-14位字母'],
'relatedToVideoId' => ['/^[\w\-]{4,14}$/' => 'relatedToVideoId为4-14位字母'],
'maxResults' => ['number=1,50' => 'maxResults不合法'],
'regionCode' => ['set=HK,TW,US,KR,JP' => 'regionCode不合法'],
'part' => 'id,snippet',
];
注意 maxResults
的配置项为一个数组,元素可为闭包和其他规则,
如果直接写一个闭包而不是数组,代表使用闭包的返回值,而不是对输入值校验., (*63)
內建的验证类型有, (*64)
类型限定, (*65)
'array', 'bool', 'float', 'int', 'null', 'numeric', 'object', 'scalar', 'string'
ctype类型, (*66)
'alnum', 'alpha', 'cntrl', 'digit', 'graph', 'lower', 'print', 'punct', 'space', 'upper', 'xdigit'
其他 require
required
default
number
json
url
ip
email
username
password
phone
, (*67)
动态比较的类型有 minlength
maxlength
length
eq
eqs
set
int
number
numeric
, (*68)
required 数字0,字符串0,空数组,空字符串等被认为校验不通过,其他true值,被认为通过校验, (*69)
require 在required的基础上允许数字0和字符串0校验通过
注意:空数组,空字符串,被认为校验不通过, 与required
的区别在于数字0和字符串0,required
更加严格, (*70)
default 如果值不存在则使用此默认值并忽略规则校验,如果有值,则规则将会生效, (*71)
int,float等为强类型规则, number可以既接受int型也接受字符串格式的整数, (*72)
numeric可以既接受float型也接受字符串格式的小数,也可以既接受int型也接受字符串格式的整数, (*73)
int,number,numeric,length 可以使用区间限定,使用,
连接两个区间,int=1,50
表示int型1和50之间,length=10,20
表示string类型长度在10至20, (*74)
无,
时,表示需大于等于此起始值,number=1
表示int或字符串整数,值大于等于1,即除0外的正整数, (*75)
minlength=8
表示string长度最小8, (*76)
set 规则只能针对值是string类型的值做判断,值为int类型的,一律判断不通过, (*77)
直接使用数组语法来判断更加复杂的是否在集合中, 此比较方法是强类型的比较, (*78)
eqs为不区分大小写,eq为区分大小写, (*79)
键可扩展为一种静态方法模式校验规则,例 r::domain 表示使用r类中的静态方法domain来校验,实现复杂校验, (*80)
定义callback
值,用于检验不通过时的动作, (*81)
false 抛出异常,此为默认, (*82)
闭包函数, 异常将传递给此闭包函数处理, (*83)
true 立即响应json并中断, (*84)
多数据库链接
某个Model要链接其他数据库,只需要重写ready方法即可,然后返回新的PDO实例, (*85)
public static function ready(): PDO
{
static $_pdo;
$_pdo ??= self::init(app::get('dbdev'));
return $_pdo;
}
轻量级的 ORM 操作
无过度封装,简单直接,轻松完成大部分数据库操作., (*86)
PDO参数, (*87)
$options = [PDO::ATTR_PERSISTENT => true, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_TIMEOUT => 3, PDO::ATTR_EMULATE_PREPARES => false, PDO::ATTR_STRINGIFY_FETCHES => false];
ATTR_EMULATE_PREPARES
若是开启的话,数据库中的int
和float
取回来时,在PHP中被转化为string
,造成类型不一致,故需要, (*88)
ATTR_EMULATE_PREPARES => false
ATTR_STRINGIFY_FETCHES => false
ATTR_EMULATE_PREPARES
配置为false
时,预编译中的同名命名参数(:name
这样的形式)只能使用一次.
见https://www.php.net/manual/en/pdo.prepare.php, (*89)
命名参数与?
占位符也不可混用, (*90)
此框架都已自动处理., (*91)
增加
db::insert(array $data,string $table='',bool $ignore=false,bool $replace=false)
db::replace(array $data,string $table='')
replace
也是通过insert
方法,只是参数不同., (*92)
$ignore
设置为true
可以使用INSERT IGNORE
模式, (*93)
insert
方法没有完成ON DUPLICATE KEY UPDATE
,若想使用,见下面说明, (*94)
insert
方法没有完成INSERT DELAYED INTO
,若想使用,见下面说明, (*95)
对于批量插入,参见下面说明, (*96)
删除
db::delete(array $where=[],string $table='')
将$where
设置为空数组即可删除全表数据, (*97)
详细的$where
使用见WHERE 构造器, (*98)
查询
db::find(array $where=[],string $table='',string $col='*',array $orderLimit=[],$fetch='fetchAll')
db::findOne(array $where=[],string $table='',string $col='*',array $orderLimit=[1],$fetch='fetch')
db::findVar(array $where=[],string $table='',string $col='COUNT(1)',array $orderLimit=[1])
db::findPage(array $where=[],string $table='',string $col='*',int $page=1,int $limit=20,array $order=[])
findOne
,findVar
,findPage
均是借助于find
方法,只不过传递参数不同., (*99)
findOne
默认LIMIT 1
,只返回一行数据, (*100)
findVar
在findOne
的基础上仅返回一个字段,并且默认是COUNT(1)
即计算行数,可以修改参数三返回希望的字段., (*101)
findPage
为分页,返回指定页的数据和上一页,下一页等,可以添加排序规则,详细见ORDERLIMIT 构造器, (*102)
详细的$where
使用见WHERE 构造器, (*103)
大量查询
如果你的一条 SQL 要查询大量数据,结果集往往超过几十万条,一次读取结果集会使得内存溢出,脚本终止., (*104)
其实find
的第五个参数可以帮助你., (*105)
该参数为获取结果集的方法,find
方法默认是一次性全部获取为数组,你可以传入参数true
交由自己主动获取., (*106)
使用参数true
后将返回一个PDOStatement
,你将可以自由进行后续操作., (*107)
$stm=User::find(['id >'=>1],'userTable','*',['id'=>'ASC'],true);
while($row=$stm->fetch())
{
}
注意: pdo->exec() 返回的是一个int值,代表被影响的行数, 例如runSql()函数, (*108)
stm->execute() 返回的是一个布尔值,代表是否操作成功,
使用 stm->rowCount() 才能取到执行DELETE、 INSERT、或 UPDATE 语句受影响的行数。, (*109)
你可以修改方法fetch
为fetchObject
, (*110)
他们二者的不同是以数组还是对象的方式返回., (*111)
即使循环获取,数据也是从 MySQL 服务器发送到了 PHP 进程中保存,若数据实在太大,可以设置数据任然保存在 MySQL 服务器,循环的过程中现场取, (*112)
在查询之前,给 PDO 实例设置, (*113)
self::setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY,false);
然后再循环获取,内存使用会显著下降, (*114)
因 PDO 使用长连接,该设置会影响一定时段内的所有 SQL 查询,你也可以查询完设置回true
避免影响其他查询
自 PHP5.5 起,可以使用 yield,大数据量下可以显著帮你节省内存, (*115)
子查询
使用原始值可以实现子查询, (*116)
$where=['!id IN'=>'(SELECT `id` FROM `user` WHERE fid=1)','age >'=>18];
更新
db::update(array $where,array $data,string $table='')
$where
的具体形式见WHERE 构造器, (*117)
$data
的具体形式见SET 构造器, (*118)
WHERE 构造器
db::condition(array &$where,string $prefix='WHERE')
在查询和删除,更新等场景下,传入一个数组作为条件, (*119)
$where
是一个数组变量,一般为一维数组,某些需要使用IN
操作时为二维数组, (*120)
$where
为一个引用,执行过后会清理$where
中的数据,因此必须传入一个变量名,执行后的$where
变量将有后续用途, (*121)
例如 $where=['username'=>'name1','age'=>18];
, (*122)
这样会筛选username
为name1
并且age
为 18 的用户, (*123)
默认的结合条件是AND
,你可以在数组的最后面添加一个指定的结合符, (*124)
改用OR
连接 $where=['username'=>'name1','age'=>18,'OR'];
, (*125)
同时对于键(例如username
)还可以添加一些修饰符和操作符., (*126)
键可以由三部分组成,以空格隔开, (*127)
第一段对应于数据库中的字段,第二段是修饰符,往往是NOT
或空,第三段是操作符,可以是>
, <
, >=
, <=
, !=
, <>
, IN
, LIKE
, REGEXP
, (*128)
例如$where=['age >'=>18,'name LIKE'=>'user%'];
, (*129)
同理,也可以使用NOT LIKE
, (*130)
如果你需要使用IN
操作符,也是可以的,给它的键值为一个数组, (*131)
$where=['id'=>[1,3,5,7]];
数组作为参数默认就置为IN
操作符, (*132)
将会等到 where id in (1,3,5,7)
,如果要使用NOT IN
需要显式指明, (*133)
$where=['id NOT IN'=>[1,3,5,7]]
, (*134)
使用字段的引用和内置函数, (*135)
$where
数组中的键值都会进行预处理操作,因此不能使用字段的引用和内置函数., (*136)
若要使用,可以在键的第一段,即数据库字段前加!
定义符,代表要使用原始值., (*137)
$where=['!time < '=>'UNIX_TIMESTAMP()']
, (*138)
使用!
定义符后对应的键值须为定值,对于用户发送来的数据,使用!
定义符前需要仔细过滤,仅能信任使用intval
过滤后的值., (*139)
!time
或者 ! time
! time <
!time <
等都是合法的,, (*140)
但!time<
! time<
是非法的,字段和操作符之间必须使用空格隔开, (*141)
NULL处理, (*142)
当条件的值为null
时,自动构造为IS NULL
语句., (*143)
例如 ['name'=>null]
转化为, (*144)
`name` IS NULL
如果需要IS NOT NULL
需要显示申明
['name IS NOT'=>null]
, (*145)
此时,使用原始值前缀!
对此构造无任何影响, (*146)
['!name'=>null]
, (*147)
['!name IS NOT'=>null]
, (*148)
都是合法的., (*149)
EXISTS 与 FIND_IN_SET, (*150)
封装一个函数使用原始值模式对where
规则添加额外的条件, (*151)
final public static function extra(array $cond, array $where = [], string $filed = null): array
{
$j = 'AND';
foreach ($cond as $k => &$v) {
if (is_array($v)) {
$v = sprintf('%s IN (%s)', (str_contains($k, '.') || str_contains($k, '`')) ? $k : "`{$k}`", implode(',', $v));
} else if (is_null($v)) {
$v = sprintf('%s IS NULL', (str_contains($k, '.') || str_contains($k, '`')) ? $k : "`{$k}`");
} else if (is_string($k)) {
$v = sprintf("FIND_IN_SET('%s',%s)", trim($v, " \n\r\t\v\0\"'"), (str_contains($k, '.') || str_contains($k, '`')) ? $k : "`{$k}`");
if (ctype_alnum(str_replace('_', '', $k))) {
$filed ??= $k;
}
} else if (is_bool($v) || is_int($v) || is_float($v)) {
$j = $v ? 'AND' : 'OR';
unset($cond[$k]);
}
}
if ($cond) {
$where["! $filed IS NOT NULL AND"] = '(' . implode(" $j ", $cond) . ')';
}
return $where;
}
对$cond
添加键值对,键代表字段,值代表此字段对应FIND_IN_SET
需要包含的值, (*152)
当没有键(键是数字),此时值为普通SQL语句,可以为EXISTS
或其他子查询等, (*153)
如果$cond
没有合适的字段key,则需要指定$filed
的值, (*154)
self::find(self::extra(['type' => 3], ['id >' => 1]), 'messages')
构造出, (*155)
SELECT * FROM `messages` WHERE (`id` > :id_1 AND `type` IS NOT NULL AND (FIND_IN_SET('3',`type`)))
如果你需要FIND_IN_SET
使用常量参数,或参数一为字段,需要用拼接模式, (*156)
self::find(self::extra(["FIND_IN_SET(`id`,'1,2,3')"], ['id >' => 1], 'id'), 'messages');
此时FIND_IN_SET就相当于查询ID是IN(1,2,3),构造出, (*157)
SELECT * FROM `messages` WHERE (`id` > :id_1 AND `id` IS NOT NULL AND (FIND_IN_SET(`id`,'1,2,3')))
性能比较:当子查询的表比较大时,使用EXISTS
可能比使用IN
性能更好,
IN
首先完成内层查询,然后在外层查询的过程中一一过滤;EXISTS
需要先完成外层查询,然后对所有记录一一执行EXISTS
子句进行过滤, (*158)
self::find(self::extra(["EXISTS(SELECT 1 FROM `contacts` WHERE contacts.`id`=messages.`cid` AND `name` <> 'group1' )"], ['id >' => 1], 'id'), 'messages');
self::find(self::extra(["messages.`cid` IN (SELECT `id` FROM `contacts` WHERE `name` <> 'group1')"], ['id >' => 1], 'id'), 'messages')
SELECT * FROM `messages` WHERE (`id` > :id_1 AND `id` IS NOT NULL AND (EXISTS(SELECT 1 FROM `contacts` WHERE contacts.`id`=messages.`cid` AND `name` <> 'group1' )))
SELECT * FROM `messages` WHERE (`id` > :id_1 AND `id` IS NOT NULL AND (messages.`cid` IN (SELECT `id` FROM `contacts` WHERE `name` <> 'group1')))
NOT EXISTS
配合INSERT INTO ... SELECT ...
还可以实现满足条件时插入, (*159)
HAVING, (*160)
简单的HAVING
语句可以同上,修改condition后的参数,包含GROUP BY
等复杂语句需要自己拼接, (*161)
final public static function findHaving(array $where = [], string $table = '', string $col = '*', array $orderLimit = [], $fetch = 'fetchAll')
{
$sql = sprintf('SELECT %s FROM %s%s%s', $col, static::table($table), self::condition($where, "HAVING"), $orderLimit ? self::orderLimit($orderLimit) : '');
return self::exec($sql, $where, $fetch);
}
SET 构造器
db::values(array &$data,bool $set=false,string $table='')
$data
使用关联数组表示,默认生成VALUES()
语句用于INSERT
,将$set
设置为true
生成用于update
的语句, (*162)
['name'=>'name1','pass'=>123]
, (*163)
数组的键也有一个前置定义符!
,表示原始值,使用此定义符可以调用函数,引用字段等,插入原始值等., (*164)
如 ['v'=>time(),'!t'=>'UNIX_TIMESTAMP()']
添加了!则存储的是时间戳,不加!则是存储此字符串, (*165)
['!count'=>'count+1']
使count
的值加一, (*166)
['!count'=>'count+age']
引用其他字段,count
设置为count+age
的和, (*167)
除非你要调用函数或引用字段,否则不建议你使用原始值,, (*168)
原始值没有引号包裹,也不是预处理字段,随意使用将会带来安全隐患., (*169)
注意, (*170)
同一个变量不可传入values()
或condition()
两次,因为这些方法会修改传入值,第二次执行时,用到的值已经被第一次修改了, (*171)
可以使用$update=$insert
,两个变量各自修改不会影响另一个, (*172)
ORDERLIMIT 构造器
db::orderLimit(array $orderLimit)
$orderLimit
使用关联数组,键为数据库字段,键值为排序规则,ASC
或DESC
,也可以使用布尔值代替,true
为ASC
,false
为DESC
, (*173)
如$orderLimit=['id'=>'DESC','name'=>'ASC']
, (*174)
还可以使用LIMIT
,添加一个整形的键值对, (*175)
$orderLimit=['id'=>'DESC','name'=>true,35=>20]
, (*176)
代表LIMIT 35,20
, (*177)
直接使用$orderLimit=['id'=>'DESC','name'=>'ASC',5]
, (*178)
代表LIMIT 0,5
, (*179)
如果要使用ORDER BY RAND()
, (*180)
可使用$orderLimit=[''=>'RAND()']
构造, (*181)
使用 ON DUPLICATE KEY UPDATE
db::insertOrUpdate(string $table, array $insert, array $update): bool
实现了插入或者更新, (*182)
自己拼接的话, (*183)
$sql = sprintf('INSERT INTO %s ON DUPLICATE KEY UPDATE id=:id,name=:name', self::values($insert, false, static::table));
return self::exec($sql, $insert + $update);
需要$update=['id'=>1,'name'=>2];
与自己写的SQL对应, (*184)
使用 INSERT DELAYED
DELAYED 仅适用于 MyISAM, MEMORY 和 ARCHIVE 表, (*185)
可采用如下方式构造, (*186)
$sql=sprintf('REPLACE DELAYED INTO %s',self::values($data,false,static::table));
$sql=sprintf('INSERT DELAYED INTO %s',self::values($data,false,static::table));
$sql=sprintf('INSERT DELAYED IGNORE INTO %s',self::values($data,false,static::table));
使用CASE WHEN
CASE WHEN
可以实现单条SQL语句将多个记录更新为不同的值, (*187)
CASE WHEN
可以实现对查询的数据对值分组转换等, (*188)
批量插入
可以使用prepare
绑定数据循环., (*189)
如果数据表是InnoDB
而不是MyISAM
,还可以开启事务,进一步提升速度., (*190)
因为InnoDB
默认是auto-commit mode
,每条 SQL 都会当做一个事务自动提交,会带来额外开销., (*191)
INSERT INTO
也可以使用IGNORE
,REPLACE
,ON DUPLICATE KEY UPDATE
, (*192)
数据源, (*193)
$column = ['title', 'text', 'ids', 'enable'];
$data = [
['title1', 'text1', 'a,b', 1],
['title2', 'text2', 'a,b', 1],
['title3', 'text3', 'b,c', 0],
['title4', 'text4', 'b,c', 0]
];
db::insertMany(string $table, array $column, array $data): bool
实现了事务批量插入, (*194)
更快的批量插入
使用单条 SQL 代替循环插入速度将会更快, (*195)
数据源和参数同上, (*196)
db::insertOnceMany(string $table, array $column, array $data, array $duplicateKeyUpdate = []): int
实现了单条SQL批量插入,并且可以指定重复键的更新策略, (*197)
批量插入中使用ON DUPLICATE KEY UPDATE
仅需配置第四个参数 , $duplicateKeyUpdate
格式同$column
,需要是其子集, (*198)
如果数据量巨大,可能造成SQL语句太长,可以使用array_chunk
切割$data
分批调用, (*199)
嵌套的AND
和OR
对WHERE 构造器传入二维数组,可以构造嵌套的AND
或OR
, (*200)
$where1=['age >'=>18,'sex'=>1];
$where2=['id >'=>20,'id <'=>40];
db::find([$where1, $where2, 'OR'], 'users');
构造出如下SQL, (*201)
SELECT * FROM users WHERE ((`age` > :age_1 AND `sex` = :sex_2) OR (`id` > :id_3 AND `id` < :id_4))
高级查询
如果你需要非常复杂的 SQL 查询,可能不能一次就利用方法完成,需要多次操作, (*202)
或者自己进行prepare
并绑定., (*203)
使用db::query
可以一次完成多个 SQL 操作,它是db::exec
的批处理., (*204)
$sql1="SELECT 1";
$sql2="SELECT 2";
$sql3="SELECT 3";
$data1=$data2=$data3=[];
[$res1,$res2,$res3]=self::query([$sql1,$data1,'fetchAll'],[$sql2,$data2,'fetch'],[$sql3,$data3,true]);
每个参数都是数组, (*205)
数组内部,第一个元素要批处理的$sql 语句,第二个参数绑定的参数,第三个参数获取方式., (*206)
所有的 SQL 执行最终都会指向db::exec($sql,array $params=[],$fetch='')
, (*207)
第三个参数fetch
, (*208)
如果为空,代表非查询语句,返回的是影响的行数(无预处理exec()
)或者是否成功(有预处理stm->execute()
), (*209)
如果是个string
,返回结果集(例如fetchAll
,无论是否走了预处理), (*210)
如果是true
(仅当是查询语句,或者是预处理操作,才能置为true),返回一个PDOStatement
(无论是否走了预处理),后续按何种方式获取结果集,自己任意操作., (*211)
>, (*212)
注意: params 为空则代表没有预处理参数,底层会直接调用 pdo->exec() 或 pdo->query()
如果不为空,则会先预处理,然后stm->execute(), (*213)
pdo->exec() 返回的是一个int值,代表被影响的行数, 例如runSql()函数, (*214)
stm->execute() 返回的是一个布尔值,代表是否操作成功,
使用 stm->rowCount() 才能取到执行DELETE、 INSERT、或 UPDATE 语句受影响的行数。, (*215)
如果你关心受影响的行数,在调用db::exec
时需注意., (*216)
SQLITE 差异
insert ignore 和 replace 与MySQL的语法存在差异, (*217)
insert or replace:如果不存在就插入,存在就更新
insert or ignore:如果不存在就插入,存在就忽略, (*218)
insert函数可以新增如下对SQLITE的改写版, (*219)
final public static function sqliteInsert(array $data, string $table = '', bool $replace_or_ignore = null)
{
$sql = sprintf('%s %sINTO %s', $replace_or_ignore ? 'INSERT OR REPLACE' : 'INSERT', $replace_or_ignore === false ? 'OR IGNORE ' : '', self::values($data, false, $table));
return self::exec($sql, $data);
}