mehr-it / lara-db-ext
laravel数据库抽象层的扩展
Requires
- php: >=7.1
- ext-pdo: *
- laravel/framework: ^7.0|^8.0|^9.0
Requires (Dev)
- ext-json: *
- orchestra/testbench: ^5.0|^6.0|^7.0
- phpunit/phpunit: ^7.4|^8.4|^9.0
README
此包实现了对Laravel数据库抽象的各种扩展和改进。
查询构建器
- generateChunked
- insertOnDuplicateKey
- selectPrefixed
- updateWithJoinedData
- whereMultiColumns
- whereMultiIn
- whereNotNested
- 自动检测whereIn
- 支持常见表表达式(CTE)
Eloquent构建器
- generateChunked
- insertModels
- insertModelsOnDuplicateKey
- updateWithJoinedModels
- withJoined(使用连接而不是预加载来使用查询关系)
Eloquent模型
- 允许指定临时的自定义日期序列化格式
- 静态访问标识符,如表名
- 使用模型字段创建SQL查询的辅助函数
- 检测JSON字段变化时忽略键值对的顺序变化(自v2.2起)
连接
- 禁用自定义SQL模式选项
安装
要使用composer安装,请运行
composer require mehr-it/lara-db-ext
用法
在可以使用eloquent扩展之前,必须将DbExtensions特质添加到所有模型中
class User extends Model {
use DbExtensions;
}
待办事项
README尚未涵盖所有功能。
updateWithJoinedData()
updateWithJoinedData()
方法在单个查询中执行多个数据库记录的更新。这是通过创建一个包含更新数据的虚拟表并将其与目标表连接来实现的。请参见以下示例
DB::table('test_table')
->updateWithJoinedData([
[
'id' => 1,
'name' => 'name a updated',
'x' => 12,
],
[
'id' => 2,
'name' => 'name b updated',
'x' => 22,
],
]);
这将执行以下SQL语句(给定数据绑定到连接表的参数)
update
"test_table"
inner join (
(select (?) as id, (?) as "name", (?) as "x")
union all
(select (?) as id, (?) as "name", (?) as "x")
) as "data"
on
"test_table"."id" = "data"."id"
set
"test_table"."name" = "data"."name"',
"test_table"."x" = "data"."x"'
其他参数允许自定义连接条件、要更新的字段列表和数据表的别名。有关详细信息,请参阅方法文档。
insertModels()
insertModels()
方法实现了eloquent模型的批量插入。它自动设置时间戳并应用任何突变
TestModel::insertModels([
new TestModel([
'id' => 1,
'name' => 'name a updated',
'x' => 12,
])
new TestModel([
'id' => 2,
'name' => 'name b updated',
'x' => 22,
]),
]);
默认情况下,要插入的字段通过检查第一个给定模型的所有属性来确定。如果您只想插入特定的属性,则可以指定自定义字段列表。有关详细信息,请参阅方法文档。
传递的模型实例既不会被更新也不会触发任何模型事件。
updateWithJoinedModels()
updateWithJoinedModels()
方法实现了updateWithJoinedData()
对于eloquent模型的行为。预期第一个参数是一个模型数组,而不是数据数组。
TestModel::updateWithJoinedModels([
new TestModel([
'id' => 1,
'name' => 'name a updated',
'x' => 12,
])
new TestModel([
'id' => 2,
'name' => 'name b updated',
'x' => 22,
]),
]);
此方法会自动更新时间戳,但传递的模型实例既不会被更新也不会触发任何模型事件。
使用此方法可确保在将模型数据传递到数据库之前应用任何突变和类型转换。
如果模型没有主键或您想根据除主键字段之外的字段进行连接,则可以传递自定义连接条件作为第二个参数。
默认情况下,要更新的字段通过检查第一个给定模型的属性来确定。如果您只想更新特定的属性或如果第一个模型不包含您希望更新的所有属性,则可以指定自定义更新字段列表作为第三个参数。
有关进一步详细信息,请参阅方法文档。
generateChunked()(查询构建器和eloquent)
在处理大量数据时,laravel的chunked()
方法被广泛使用。但它不提供将数据传递给另一个消费者的简单方法。generateChunked()
方法可以帮助在这里,并且对eloquent和查询构建器都可用
$generator = User::query()
->where('active', true)
->generateChunked();
它将返回一个生成器对象,该对象从数据库中以块的形式查询数据,并逐条输出。
默认情况下,数据以500条记录为一块进行查询。这可以通过第一个参数进行更改。第二个参数接受一个可调用对象,该对象在每个数据块输出之前被调用。
$generator = User::query()
->where('active', true)
->generateChunked(100, function($records) {
return $records->pluck('name');
});
如上所示,回调可以按任何期望的方式转换块数据,并且生成器将输出其返回值。**唯一限制是它必须返回一个可迭代对象**。它甚至可以过滤掉不应输出的某些记录。
selectPrefixed
有时您希望某些列名以给定的前缀开头。以下示例将所有返回的列名前缀为 "user_"
。
DB:table('users')->selectPrefixed(['id', 'name'], 'user_')->get();
// => [['user_id' => 1, 'user_name' => 'John']]
当您在具有冲突列名的表上进行连接时,这非常有用。
DB:table('users')
->join('children', 'users.id', '=', 'children.user_id')
->addSelectPrefixed('users.*', 'user_')
->addSelectPrefixed('children.*', 'child_')
->get();
// => [[ 'user_id' => 1, 'user_name' => 'John', 'child_id' => 12, 'child_name' => 'Maria']]
没有前缀的情况下,children
字段会静默地覆盖 users
字段。如您所见,它也适用于使用通配符列选择器。最好的是,它不会产生额外的查询来获取表结构。实际上,它与表列无关。
它也可以与表达式一起使用。
DB:table('users')->selectPrefixed(new Expression('count(*) as myCount'), 'user_')->get();
// => [['user_myCount' => 123]]
如果您省略前缀参数并传递一个关联数组作为第一个参数,您可以在一个调用中设置多个前缀。
DB:table('users')
->join('children', 'users.id', '=', 'children.user_id')
->selectPrefixed([
'user_' => 'users.*',
'child_' => ['id', 'name'],
])
->get();
whereNotNested
如果您想否定嵌套的 where 子句,新的 whereNotNested
函数就派上用场。
DB:table('users')
->whereNotNested(function($query) {
$query->where('name', 'John');
$query->where('age', '>', 49);
})
->get();
这将产生以下查询。
SELECT * FROM users WHERE NOT (first_name = 'John' AND age > 49)
whereMultiIn
一些 SQL 方言允许使用 IN
操作符比较多个列。您可以使用 whereMultiIn
函数来实现这一点。
DB:table('users')
->whereMultiIn(['name', 'age'], [
['John', 38],
['Ida', 49],
])
->get();
这将产生以下查询。
SELECT * FROM users WHERE (name, age) IN ( ('John', 38), ('Ida', 49) )
您甚至可以传递一个子查询而不是值数组。
DB:table('users')
->whereMultiIn(['name', 'age'], function ($query) {
return $query->select(['parent_name', 'parent_age'])
->from('children')
->where('age', '<', 3);
})
->get();
whereMultiColumns
whereMultiColumns
接受要比较的多个列。
DB:table('users')
->whereMultiColumns(['name', 'age'], ['n', 'a'])
->get();
这将产生以下查询。
SELECT * FROM users WHERE (name = n AND age = a)
运算符应用于列的组合。这就是为什么只支持 =
、!=
和 <>
的原因。
DB:table('users')
->whereMultiColumns(['name', 'age'], '!=', ['n', 'a'])
->get();
这将产生以下查询。
SELECT * FROM users WHERE NOT (name = n AND age = a)
自动 whereIn 识别。
另一个改进是,现在 where
函数可以使用数组作为值参数,因此它会自动转换为 whereIn
。当然,这也适用于多列。
DB:table('users')
->where('name', ['John', 'Ida'])
->get();
DB:table('users')
->where(['name', 'age'], [
['John', 38],
['Ida', 49],
])
->get();
这也适用于 whereColumn
函数。
DB:table('users')
->whereColumn(['name', 'age'], ['n', 'a'])
->get();
时区处理。
数据库中的时区处理可能很复杂。Laravel 将没有时区信息的日期传递到数据库。这种行为是正确的,因为数据通常存储时没有时区信息。当读取日期时,Laravel (Eloquent) 使用默认的应用程序时区解释日期。
但是 Laravel 不确保在将 DateTime 参数传递到数据库之前使用应用程序时区。因此,如果您向数据库传递具有不同时区的日期,则在读取时将被解释为另一个时区。
为了确保所有 DateTime 参数在发送到数据库之前都转换为应用程序时区,此包为数据库连接添加了 "adapt_timezone" 配置选项。如果设置为 true,任何 DateTime 值在传递到数据库之前都将转换为应用程序时区。
AdaptsAttributeTimezone
特性实现了 Eloquent 模型属性的时区适配。
数据库会话时区。
一些数据库,如 MySQL,在将日期转换为时间戳时使用数据库会话时区(有关详细信息,请参阅 MySQL 文档)。对于 MySQL,这只会影响将日期存储到 TIMESTAMP 列(而不是 DATETIME 列)以及 NOW() 和 CURTIME() 函数。因此,您应该始终将连接的 "timezone" 参数配置为与应用程序时区相同的值!
鸣谢
感谢 Jonas Staudenmeir 以及 staudenmeir/laravel-ct 包的贡献者,该包的公用表表达式支持基于此。