baopham/dynamodb

DynamoDB的Eloquent语法

6.5.0 2024-04-14 02:11 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
  • 安装服务提供者 (< Laravel 5.5)

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

    php artisan vendor:publish --provider 'BaoPham\DynamoDb\DynamoDbServiceProvider'
  • 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 Facade support
    $app->withFacades();
    
    // Enable Eloquent support
    $app->withEloquent();

用法

  • 通过扩展BaoPham\DynamoDb\DynamoDbModel来扩展您的模型,然后您可以使用受支持的Eloquent方法。这里的想法是,您可以在不更改查询的情况下切换回Eloquent。
  • 或者,如果您想将数据库表与DynamoDb表同步,请使用特质BaoPham\DynamoDb\ModelTrait,它将在模型保存后调用PutItem
  • 或者,您可以使用查询构建器外观构建更复杂的查询。
  • 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 的一个实例

装饰查询

当您想增强查询时使用 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();

with

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')
    ->setIndexName('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 属性
答案:尝试这个?

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

问题:如何与工厂一起使用?
答案:请参阅此问题

问题:如何使用作业?获得一个 SerializesModels 错误
答案:您可以编写自己的 restoreModel 或从您的作业中删除 SerializesModels 特性。

作者和贡献者