rennokki / dynamodb
Laravel 7+ 的 AWS DynamoDB Eloquent ORM
Requires
- aws/aws-sdk-php: ^3.142
- guzzlehttp/guzzle: ^6.5|^7.0
- illuminate/database: ^7.30|^8.23
- illuminate/support: ^7.30|^8.23
Requires (Dev)
- laravel/legacy-factories: ^1.1
- mockery/mockery: ^1.4
- orchestra/database: ^5.0|^6.0
- orchestra/testbench: ^5.0|^6.0
README
Laravel DynamoDB
此包是从 Bao Pham 的原始包 分支出来的。
仅适用于高级用户。如果您不熟悉 Laravel、Laravel Eloquent 和 DynamoDB,则建议您首先熟悉这些。
安装
使用 Composer 安装包
$ composer require rennokki/dynamodb
如果您的 Laravel 包不支持自动发现,请将以下内容添加到您的 config/app.php
文件中
'providers' => [ ... Rennokki\DynamoDb\DynamoDbServiceProvider::class, ...**** ];
发布配置文件。
php artisan vendor:publish
安装(适用于 Lumen)
对于 Lumen,请尝试 此方法 来安装 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();
用法
扩展模型
通过扩展 Rennokki\DynamoDb\DynamoDbModel
来扩展您的模型,然后您可以使用支持 Eloquent 的方法。这里的想法是您可以在不更改查询的情况下切换回 Eloquent。
use Rennokki\DynamoDb\DynamoDbModel; class MyModel extends DynamoDbModel { // }
向模型添加特性(用于同步)
要同步您的数据库表与 DynamoDb 表,请使用特性 Rennokki\DynamoDb\ModelTrait
。此特性将在模型保存、更新或删除后调用 PutItem
。
use Rennokki\DynamoDb\ModelTrait as DynamoDbable; class MyModel extends Model { use DynamoDbable; }
查询构建器
您可以使用 查询构建器 外观构建更复杂的查询。
AWS SDK
AWS SDK v3 for PHP 使用 guzzlehttp promises 以允许异步工作流程。使用此包,您可以在 DynamoDb 上异步执行类似 delete、update、save 的 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) { return $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()
$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();
getItemsCount()
// returns the approximate total count of the table items $total = Model::getItemsCount(); // ex: 5
查询范围
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]'); // Equivalent of: 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();
索引
如果您的表有索引,请确保在您的模型类中声明它们,如下所示
/** * The DynamoDb 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' ], ];
请注意,当键存在于多个索引中时,索引的顺序很重要。
例如,对于以下查询,将使用 count_index
$model->where('user_id', 123)->where('count', '>', 10)->get();
protected $dynamoDbIndexKeys = [ 'count_index' => [ 'hash' => 'user_id', 'range' => 'count' ], 'user_index' => [ 'hash' => 'user_id', ], ];
大多数情况下,您不需要做任何事情,但如果您需要使用特定的索引,可以指定如下
$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 Rennokki\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();
常见问题解答
问题:如果 id
属性不在可填充数组中,则无法分配它。答案:试试 这个?
问题:如何创建迁移?答案:请参阅 此问题
问题:如何与工厂一起使用?答案:请参阅 此问题
问题:如何使用 Job?收到 SerializesModels 错误。答案:您可以编写自己的 restoreModel 或从您的 Job 中移除 SerializesModels
特性。
安全
如果您发现任何与安全相关的问题,请通过电子邮件 alex@renoki.org 而不是使用问题跟踪器来报告。
致谢
- Bao Pham
- warrick-loyaltycorp
- Alexander Ward
- Quang Ngo
- David Higgins
- Damon Williams
- Alex Renoki
- 所有贡献者
许可
MIT 许可证(MIT)。有关更多信息,请参阅 许可文件