laravie / eloquent-dynamodb
DynamoDB 的 Eloquent 语法
Requires
- php: >=7.2
- aws/aws-sdk-php: ^3.37
- guzzlehttp/guzzle: ^6.3
- illuminate/database: ^5.8 || ^6.0 || ^7.0
- illuminate/support: ^5.8 || ^6.0 || ^7.0
Requires (Dev)
- orchestra/testbench: ^3.8 || ^4.5 || ^5.0
README
支持所有关键类型 - 主哈希键和组合键。
仅适用于高级用户。如果您不熟悉 Laravel、Laravel Eloquent 和 DynamoDB,则建议您首先熟悉这些。
安装
要通过 composer 安装,请在终端中运行以下命令
composer require "laravie/eloquent-dynamodb"
Laravel 设置
只有当您禁用包发现时,请将以下服务提供程序添加到 config/app.php
下的 providers
// config/app.php 'providers' => [ ... Laravie\DynamoDb\DynamoDbServiceProvider::class, ... ];
接下来,您可能想使用以下命令发布配置文件
php artisan vendor:publish --provider="Laravie\DynamoDb\DynamoDbServiceProvider"
然后,您可以在 config/dynamodb.php 中更新 DynamoDb 配置,或使用
.env
配置值。
Lumen 设置
您可以选择手动复制配置文件,或使用 laravelista/lumen-vendor-publish 安装 vendor:publish
命令。
接下来,加载配置文件并在 bootstrap/app.php
中启用 Eloquent 支持
$app = new Laravel\Lumen\Application( realpath(__DIR__.'/../') ); // Load dynamodb config file $app->configure('dynamodb'); // Enable Eloquent support $app->withEloquent();
然后,您可以在 config/dynamodb.php 中更新 DynamoDb 配置,或使用
.env
配置值。
用法
- 扩展模型以使用
Laravie\DynamoDb\DynamoDbModel
,然后您可以使用受支持的 Eloquent 方法。这里的想法是您可以切换回 Eloquent 而不更改您的查询。 - 或者,如果您想将数据库表与 DynamoDb 表同步,请使用 trait
Laravie\DynamoDb\ModelTrait
,它将在模型保存后调用PutItem
。 - 或者,您可以使用 查询构建器 门面来构建更复杂的查询。
- AWS SDK v3 for PHP 使用 guzzlehttp promises 允许异步工作流程。使用此包,您可以在 DynamoDb 上异步执行类似 删除、更新、保存 的 Eloquent 查询。
支持的功能
find() 和 delete()
$model->find($id, array $columns = []); $model->findMany($ids, array $columns = []); $model->delete(); $model->deleteAsync()->wait();
条件
// Using getIterator() // If 'key' is the primary key or a global/local index and it is a supported Query condition, // will use 'Query', otherwise 'Scan'. $model->where('key', 'key value')->get(); $model->where(['key' => 'key value']); // Chainable for 'AND'. $model->where('foo', 'bar') ->where('foo2', '!=' 'bar2') ->get(); // Chainable for 'OR'. $model->where('foo', 'bar') ->orWhere('foo2', '!=' 'bar2') ->get(); // Other types of conditions $model->where('count', '>', 0)->get(); $model->where('count', '>=', 0)->get(); $model->where('count', '<', 0)->get(); $model->where('count', '<=', 0)->get(); $model->whereIn('count', [0, 100])->get(); $model->whereNotIn('count', [0, 100])->get(); $model->where('count', 'between', [0, 100])->get(); $model->where('description', 'begins_with', 'foo')->get(); $model->where('description', 'contains', 'foo')->get(); $model->where('description', 'not_contains', 'foo')->get(); // Nested conditions $model->where('name', 'foo') ->where(function ($query) { $query->where('count', 10)->orWhere('count', 20); }) ->get(); // Nested attributes $model->where('nestedMap.foo', 'bar')->where('list[0]', 'baz')->get();
whereNull() 和 whereNotNull()
NULL 和 NOT_NULL 只检查属性是否存在,而不是其值是否为 NULL
请参阅: http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Condition.html
$model->whereNull('name'); $model->whereNotNull('name');
all() 和 first()
// Using scan operator, not too reliable since DynamoDb will only give 1MB total of data. $model->all(); // Basically a scan but with limit of 1 item. $model->first();
分页
遗憾的是,对于 DynamoDb 来说,跳过多少条记录的偏移量没有意义。相反,提供前一个查询的最后结果作为下一个查询的起点。
示例
对于查询
$query = $model->where('count', 10)->limit(2); $items = $query->all(); $last = $items->last();
将此查询结果的最后一项作为下一个 "偏移量"。
$nextPage = $query->after($last)->limit(2)->all(); // or $nextPage = $query->afterKey($items->lastKey())->limit(2)->all(); // or (for query without index condition only) $nextPage = $query->afterKey($last->getKeys())->limit(2)->all();
update()
// update $model->update($attributes);
updateAsync()
// update asynchronously and wait on the promise for completion. $model->updateAsync($attributes)->wait();
save()
$model = new Model(); // Define fillable attributes in your Model class. $model->fillableAttr1 = 'foo'; $model->fillableAttr2 = 'foo'; // DynamoDb doesn't support incremented Id, so you need to use UUID for the primary key. $model->id = 'de305d54-75b4-431b-adb2-eb6b9e546014'; $model->save();
saveAsync()
异步保存单个模型并等待完成。
$model = new Model(); // Define fillable attributes in your Model class. $model->fillableAttr1 = 'foo'; $model->fillableAttr2 = 'bar'; // DynamoDb doesn't support incremented Id, so you need to use UUID for the primary key. $model->id = 'de305d54-75b4-431b-adb2-eb6b9e546014'; $model->saveAsync()->wait();
异步保存多个模型并等待所有模型同时完成。
for($i = 0; $i < 10; $i++){ $model = new Model(); // Define fillable attributes in your Model class. $model->fillableAttr1 = 'foo'; $model->fillableAttr2 = 'bar'; // DynamoDb doesn't support incremented Id, so you need to use UUID for the primary key. $model->id = uniqid(); // Returns a promise which you can wait on later. $promises[] = $model->saveAsync(); } \GuzzleHttp\Promise\all($promises)->wait();
delete()
$model->delete();
deleteAsync()
$model->deleteAsync()->wait();
chunk()
$model->chunk(10, function ($records) { foreach ($records as $record) { } });
limit() 和 take()
// Use this with caution unless your limit is small. // DynamoDB has a limit of 1MB so if your limit is very big, the results will not be expected. $model->where('name', 'foo')->take(3)->get();
firstOrFail()
$model->where('name', 'foo')->firstOrFail(); // for composite key $model->where('id', 'foo')->where('id2', 'bar')->firstOrFail();
findOrFail()
$model->findOrFail('foo'); // for composite key $model->findOrFail(['id' => 'foo', 'id2' => 'bar']);
refresh()
$model = Model::first(); $model->refresh();
查询范围
class Foo extends DynamoDbModel { protected static function boot() { parent::boot(); static::addGlobalScope('count', function (DynamoDbQueryBuilder $builder) { $builder->where('count', '>', 6); }); } public function scopeCountUnderFour($builder) { return $builder->where('count', '<', 4); } public function scopeCountUnder($builder, $count) { return $builder->where('count', '<', $count); } } $foo = new Foo(); // Global scope will be applied $foo->all(); // Local scope $foo->withoutGlobalScopes()->countUnderFour()->get(); // Dynamic local scope $foo->withoutGlobalScopes()->countUnder(6)->get();
REMOVE — 从项中删除属性
$model = new Model(); $model->where('id', 'foo')->removeAttribute('name', 'description', 'nested.foo', 'nestedArray[0]'); // Or Model::find('foo')->removeAttribute('name', 'description', 'nested.foo', 'nestedArray[0]');
toSql() 风格
出于调试目的,您可以选择将其转换为实际的 DynamoDb 查询
$raw = $model->where('count', '>', 10)->toDynamoDbQuery(); // $op is either "Scan" or "Query" $op = $raw->op; // The query body being sent to AWS $query = $raw->query;
其中 $raw
是 RawDynamoDbQuery 的实例
装饰查询
当您想增强查询时使用 decorate
。例如
设置排序键的顺序
$items = $model ->where('hash', 'hash-value') ->where('range', '>', 10) ->decorate(function (RawDynamoDbQuery $raw) { // desc order $raw->query['ScanIndexForward'] = false; }) ->get();
如果库未能检测到正确的操作,则强制使用 "Query" 而不是 "Scan"
$items = $model ->where('hash', 'hash-value') ->decorate(function (RawDynamoDbQuery $raw) { $raw->op = 'Query'; }) ->get();
索引
如果您的表有索引,请确保在您的模型类中声明它们,如下所示
/** * Indexes. * [ * '<simple_index_name>' => [ * 'hash' => '<index_key>' * ], * '<composite_index_name>' => [ * 'hash' => '<index_hash_key>', * 'range' => '<index_range_key>' * ], * ] * * @var array */ protected $dynamoDbIndexKeys = [ 'count_index' => [ 'hash' => 'count' ], ];
请注意,当键存在于多个索引中时,索引的顺序很重要。
例如,我们有这个
$model->where('user_id', 123)->where('count', '>', 10)->get();
与
protected $dynamoDbIndexKeys = [ 'count_index' => [ 'hash' => 'user_id', 'range' => 'count' ], 'user_index' => [ 'hash' => 'user_id', ], ];
将使用 count_index
。
protected $dynamoDbIndexKeys = [ 'user_index' => [ 'hash' => 'user_id', ], 'count_index' => [ 'hash' => 'user_id', 'range' => 'count' ] ];
将使用 user_index
。
大多数情况下,您不需要做任何事情,但如果您需要使用特定的索引,您可以像这样指定它
$model->where('user_id', 123)->where('count', '>', 10)->withIndex('count_index')->get();
复合键
要使用模型中的复合键
- 将
$compositeKey
设置为包含键属性名的数组,例如
protected $primaryKey = 'customer_id'; protected $compositeKey = ['customer_id', 'agent_id'];
- 要找到具有复合键的记录
$model->find(['customer_id' => 'value1', 'agent_id' => 'value2']);
查询构建器
使用 DynamoDb
门面构建原始查询
use Laravie\DynamoDb\Facades\DynamoDb; DynamoDb::table('articles') // call set<key_name> to build the query body to be sent to AWS ->setFilterExpression('#name = :name') ->setExpressionAttributeNames(['#name' => 'author_name']) ->setExpressionAttributeValues([':name' => DynamoDb::marshalValue('Bao')]) ->prepare() // the query body will be sent upon calling this. ->scan(); // supports any DynamoDbClient methods (e.g. batchWriteItem, batchGetItem, etc.) DynamoDb::table('articles') ->setIndex('author_name') ->setKeyConditionExpression('#name = :name') ->setProjectionExpression('id, author_name') // Can set the attribute mapping one by one instead ->setExpressionAttributeName('#name', 'author_name') ->setExpressionAttributeValue(':name', DynamoDb::marshalValue('Bao')) ->prepare() ->query(); DynamoDb::table('articles') ->setKey(DynamoDb::marshalItem(['id' => 'ae025ed8'])) ->setUpdateExpression('REMOVE #c, #t') ->setExpressionAttributeName('#c', 'comments') ->setExpressionAttributeName('#t', 'tags') ->prepare() ->updateItem(); DynamoDb::table('articles') ->setKey(DynamoDb::marshalItem(['id' => 'ae025ed8'])) ->prepare() ->deleteItem(); DynamoDb::table('articles') ->setItem(DynamoDb::marshalItem(['id' => 'ae025ed8', 'author_name' => 'New Name'])) ->prepare() ->putItem(); // Or, instead of ::table() DynamoDb::newQuery() ->setTableName('articles') // Or access the DynamoDbClient instance directly DynamoDb::client(); // pass in the connection name to get a different client instance other than the default. DynamoDb::client('test');
查询构建器方法的形式为 set<key_name>
,其中 <key_name>
是要发送的查询体中的键名称。
例如,构建一个 UpdateTable
查询
[ 'AttributeDefinitions' => ..., 'GlobalSecondaryIndexUpdates' => ..., 'TableName' => ... ]
做
$query = DynamoDb::table('articles') ->setAttributeDefinitions(...) ->setGlobalSecondaryIndexUpdates(...);
准备好后
$query->prepare()->updateTable();
常见问题解答 (FAQ)
问题:如果不在可填充数组中,无法分配 id
属性
答案:尝试 这个?
问题:如何创建迁移?
答案:请参阅 这个问题
问题:如何与工厂一起使用?
答案:请参阅 这个问题
问题:如何与作业一起使用?得到 SerializesModels 错误
答案:您可以 自己编写 restoreModel 或者从您的作业中移除 SerializesModels
特性。