betalabs/dynamodb

DynamoDB 的 Eloquent 语法

4.16.0 2022-07-08 18:00 UTC

README

Latest Stable Version Total Downloads Latest Unstable Version Build Status Code Coverage License

支持所有键类型 - 主哈希键和组合键。

仅适用于高级用户。如果您不熟悉 Laravel、Laravel Eloquent 和 DynamoDB,则建议您先熟悉这些内容。

v2 版本中的重大变更:配置不再位于 config/services.php 中

安装

  • Composer 安装

    composer require baopham/dynamodb
  • 安装服务提供者

    // config/app.php
    
    'providers' => [
        ...
        BaoPham\DynamoDb\DynamoDbServiceProvider::class,
        ...
    ];
  • 运行

    php artisan vendor:publish
  • config/dynamodb.php 中更新 DynamoDb 配置

对于 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();

使用方法

  • 通过扩展 BaoPham\DynamoDb\DynamoDbModel 来扩展您的模型,然后您可以使用受支持的 Eloquent 方法。这里的想法是您可以切换回 Eloquent 而无需更改查询。
  • 或者,如果您想将数据库表与 DynamoDb 表同步,请使用 trait BaoPham\DynamoDb\ModelTrait,它将在模型保存后调用 PutItem
  • 或者,您可以使用 查询构建器 facade 来构建更复杂的查询。
  • AWS SDK v3 for PHP 使用 guzzlehttp promises 来允许异步工作流程。使用此包,您可以在 DynamoDb 上异步运行类似 deleteupdatesave 的 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 — 从项中删除属性

参见:http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.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;

其中$rawRawDynamoDbQuery的实例,RawDynamoDbQuery

装饰查询

当您想要增强查询时,请使用decorate。例如

设置排序键的顺序

$items = $model
    ->where('hash', 'hash-value')
    ->where('range', '>', 10)
    ->decorate(function (RawDynamoDbQuery $raw) {
        // desc order
        $raw->query['ScanIndexForward'] = false;
    })
    ->get();

如果库无法检测到正确的操作,则强制使用"查询"而不是"扫描"

$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 BaoPham\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();

要求

Laravel ^5.1

从 v1 迁移到 v2

按照以下步骤操作

  1. 更新您的composer.json以使用v2
  2. 运行composer update
  3. 运行php artisan vendor:publish
  4. config/services.php中的DynamoDb配置移动到新的配置文件config/dynamodb.php作为其中一个连接
    1. keysecrettoken放在credentials内部
    2. local_endpoint重命名为endpoint
    3. 删除local字段

常见问题解答

问题:如果不能在填充数组中分配id属性怎么办?
答案:尝试这个

问题:如何创建迁移?
答案:请参阅这个问题

问题:如何使用工厂?
答案:请参阅这个问题

问题:如何使用Job?获取SerializesModels错误
答案:您可以选择编写自己的restoreModel或从您的Job中删除SerializesModels特征。

作者和贡献者