nham24 / laravel-dynamodb
基于 DynamoDB 的 Eloquent 模型和查询构建器,适用于 Laravel。
Requires
- php: ^8.0
- aws/aws-sdk-php: ^3.0
- illuminate/container: ^9.0
- illuminate/database: ^9.0
- illuminate/hashing: ^9.0
- illuminate/support: ^9.0
Requires (Dev)
- illuminate/auth: ^9.0
- mockery/mockery: ^1.3
- phpunit/phpunit: ^8.0|^9.0
- symfony/var-dumper: ^5.0
- vlucas/phpdotenv: ^4.1
README
本包仅是 kitar/laravel-dynamodb 的分支,以支持 Laravel 5.8。如果你的项目正在使用 Laravel 6+,你应该使用 kitar/laravel-dynamodb。
基于 DynamoDB 的 Eloquent 模型和查询构建器,适用于 Laravel。
- Laravel DynamoDB
动机
- 我想使用 DynamoDB 与 Laravel。(例如,使用自定义用户提供者进行身份验证)
- 我想使用一个简单的 API,无需担心手动处理表达式属性等繁琐事情。
- 我想尽可能扩展 Laravel 的代码来
- 依赖于 Laravel 严谨的代码。
- 保持额外的实现简单且易于维护。
- 我不想让它完全兼容 Eloquent,因为 DynamoDB 与关系型数据库不同。
- 我渴望 jessengers/laravel-mongodb。如果我们有 DynamoDB 的那个会怎么样?
安装
通过 Composer 安装包
$ composer require nham24/laravel-dynamodb
Laravel
我们只支持 Laravel >= 5.8。
将 dynamodb 配置添加到 config/database.php
'connections' => [ 'dynamodb' => [ 'driver' => 'dynamodb', 'region' => env('AWS_DEFAULT_REGION'), 'access_key' => env('AWS_ACCESS_KEY_ID'), 'secret_key' => env('AWS_SECRET_ACCESS_KEY') ], ... ],
非 Laravel 项目
对于 Laravel 以外的使用,您可以手动创建连接并开始使用 查询构建器 进行查询。
$connection = new Kitar\Dynamodb\Connection([ 'region' => env('AWS_DEFAULT_REGION'), 'access_key' => env('AWS_ACCESS_KEY_ID'), 'secret_key' => env('AWS_SECRET_ACCESS_KEY') ]); $connection->table('your-table')->...
示例数据
本文档中的许多示例代码都在查询 DynamoDB 的官方示例数据。如果您想尝试这些代码,请先将其加载到您的表中。
模型
DynamoDB 模型扩展了 Eloquent 模型,因此我们可以使用熟悉的功能,例如转换器、序列化等。
Eloquent 模型与 DynamoDB 模型的主要区别是
- Eloquent 模型
- 可以处理关系。
- 将调用转发到模型(Eloquent)查询构建器。(例如,
create、createOrFirst、where、with)
- DynamoDB 模型
- 不能处理关系。
- 将调用转发到数据库(DynamoDB)查询构建器。(例如,
getItem、putItem、scan、filter)
扩展基本模型
大多数属性与原始 Eloquent 模型相同,但有一些 DynamoDB 特定属性。
例如,如果我们的表只有一个分区键,则模型将如下所示
use Kitar\Dynamodb\Model\Model; class ProductCatalog extends Model { protected $table = 'ProductCatalog'; protected $primaryKey = 'Id'; protected $fillable = ['Id', 'Price', 'Title']; }
如果我们的表还有一个排序键
use Kitar\Dynamodb\Model\Model; class Thread extends Model { protected $table = 'Thread'; protected $primaryKey = 'ForumName'; protected $sortKey = 'Subject'; protected $fillable = ['ForumName', 'Subject']; }
如果我们设置了 sortKeyDefault,那么在没有指定排序键的情况下实例化或调用 find 时将使用它。
use Kitar\Dynamodb\Model\Model; use Illuminate\Auth\Authenticatable; use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; class User extends Model implements AuthenticatableContract { use Authenticatable; protected $table = 'User'; protected $primaryKey = 'email'; protected $sortKey = 'type'; protected $sortKeyDefault = 'profile'; protected $fillable = [ 'name', 'email', 'password', 'type', ]; }
请注意,此模型实现了
Illuminate\Contracts\Auth\Authenticatable并使用Illuminate\Auth\Authenticatable。这是可选的,但如果我们使用它们,我们也可以使用此模型进行身份验证。有关身份验证的详细信息,请参阅身份验证部分。
基本用法
检索所有模型
$products = ProductCatalog::scan();
或者,
$products = ProductCatalog::all();
DynamoDB 每次调用只能处理最多 1MB 的结果集,因此如果有更多结果,我们必须进行分页。有关详细信息,请参阅分页结果。
检索一个模型
如果模型只有分区键
ProductCatalog::find(101);
如果模型也有排序键
Thread::find([ 'ForumName' => 'Amazon DynamoDB', // Partition key 'Subject' => 'DynamoDB Thread 1' // Sort key ]);
如果模型有排序键且定义了 sortKeyDefault
User::find('foo@bar.com'); // Partition key. sortKeyDefault will be used for Sort key.
save()
$user = new User([ 'email' => 'foo@bar.com', 'type' => 'profile' // Sort key. If we don't specify this, sortKeyDefault will be used. ]); $user->save();
$user->name = 'foo'; $user->save();
update()
$user->update([ 'name' => 'foobar' ]);
delete()
$user->delete();
increment() / decrement()
当我们调用 increment() 和 decrement() 时,底层将使用 原子计数器。
$user->increment('views', 1); $user->decrement('views', 1);
我们还可以传递其他要更新的属性。
$user->increment('views', 1, [ 'last_viewed_at' => '...', ]);
高级查询
我们可以在模型中通过例如 query、scan、filter、condition、keyCondition 等函数使用 Query Builder。
例如
Thread::keyCondition('ForumName', '=', 'Amazon DynamoDB') ->keyCondition('Subject', 'begins_with', 'DynamoDB') ->filter('Views', '=', 0) ->query();
请参阅查询构建器以获取详细信息。
通过模型进行身份验证
我们可以创建一个自定义用户提供程序以使用 DynamoDB 进行身份验证。有关详细信息,请参阅Laravel 的官方文档。
要使用模型进行身份验证,模型应实现 Illuminate\Contracts\Auth\Authenticatable 协议。在本节中,我们将使用上面提到的 User 模型。
注册自定义用户提供者
在准备可验证模型后,我们需要创建自定义用户提供程序。我们可以自己创建它(很简单),但本节我们将使用 Kitar\Dynamodb\Model\AuthUserProvider。
要注册自定义用户提供程序,请在 App/Providers/AuthServiceProvider.php 中添加以下代码。
use Kitar\Dynamodb\Model\AuthUserProvider; ... public function boot() { $this->registerPolicies(); Auth::provider('dynamodb', function ($app, array $config) { return new AuthUserProvider( $app['hash'], $config['model'], $config['api_token_name'] ?? null, $config['api_token_index'] ?? null ); }); }
更改 auth 配置
然后在 config/auth.php 中指定身份验证的驱动程序和模型名称。
'providers' => [ // Eloquent // 'users' => [ // 'driver' => 'eloquent', // 'model' => App\User::class, // ], // DynamoDB 'users' => [ 'driver' => 'dynamodb', 'model' => App\User::class, 'api_token_name' => 'api_token', 'api_token_index' => 'api_token-index' ], ],
api_token_name 和 api_token_index 是可选的,但如果我们使用 API 令牌身份验证,则需要它们。
注册控制器
可能需要修改注册控制器。例如,如果我们使用 Laravel Breeze,修改如下。
class RegisteredUserController extends Controller { ... public function store(Request $request) { $request->validate([ 'name' => 'required|string|max:255', 'email' => ['required', 'string', 'email', 'max:255', function ($attribute, $value, $fail) { if (User::find($value)) { $fail('The '.$attribute.' has already been taken.'); } }], 'password' => 'required|string|confirmed|min:8', ]); $user = new User([ 'name' => $request->name, 'email' => $request->email, 'password' => Hash::make($request->password), ]); $user->save(); Auth::login($user); event(new Registered($user)); return redirect(RouteServiceProvider::HOME); } }
有两个修改。第一个是添加了对 email 的闭包验证器,而不是 unique 验证器。第二个是使用 save() 方法创建用户,而不是使用 create() 方法。
查询构建器
我们可以不通过模型使用 Query Builder。
$result = DB::table('Thread')->scan();
甚至可以完全在 Laravel 之外使用。
$connection = new Kitar\Dynamodb\Connection([ 'region' => env('AWS_DEFAULT_REGION'), 'access_key' => env('AWS_ACCESS_KEY_ID'), 'secret_key' => env('AWS_SECRET_ACCESS_KEY') ]); $result = $connection->table('Thread')->scan();
如果我们通过模型进行查询,我们不需要指定表名,并且响应将是模型实例(s)。
$threads = Thread::scan();
基本用法
getItem()
$response = DB::table('ProductCatalog') ->getItem(['Id' => 101]);
而不是手动打包,传递一个纯数组。
Kitar\Dynamodb\Query\Grammar将在查询之前自动打包它们。
putItem()
DB::table('Thread') ->putItem([ 'ForumName' => 'Amazon DynamoDB', 'Subject' => 'New discussion thread', 'Message' => 'First post in this thread', 'LastPostedBy' => 'fred@example.com', 'LastPostedDateTime' => '201603190422' ]);
updateItem()
DB::table('Thread') ->key([ 'ForumName' => 'Laravel', 'Subject' => 'Laravel Thread 1' ])->updateItem([ 'LastPostedBy' => null, // REMOVE 'Replies' => null, // REMOVE 'Message' => 'Updated' // SET ]);
目前,我们仅支持简单的 SET 和 REMOVE 操作。如果属性有值,它将传递到 SET 操作。如果值为 null,它将传递到 REMOVE 操作。
deleteItem()
DB::table('Thread') ->deleteItem([ 'ForumName' => 'Amazon DynamoDB', 'Subject' => 'New discussion thread' ]);
投影表达式
投影表达式是一个标识所需属性的字符串。(它类似于 SQL 的 select 语句)
select()
我们可以以与原始 select 子句相同的方式指定投影表达式。
$response = DB::table('ProductCatalog') ->select('Price', 'Title') ->getItem(['Id' => 101]);
条件表达式
当我们操作 Amazon DynamoDB 表中的数据时,我们使用 putItem、updateItem 和 DeleteItem。我们可以使用条件表达式来确定应修改哪些项目。
condition()
要指定条件表达式,我们使用 condition 子句。这与原始的 where 子句基本相同,但它是为条件表达式而设计的。
DB::table('ProductCatalog') ->condition('Id', 'attribute_not_exists') ->putItem([ 'Id' => 101, 'ProductCategory' => 'Can I overwrite?' ]);
请注意,我们指定
attribute_not_exists作为条件的操作符。这是 DynamoDB 特有的操作符,称为function。有关详细信息,请参阅DynamoDB 的条件() 和 filter() 特定运算符。
OR 语句
DB::table('ProductCatalog') ->condition('Id', 'attribute_not_exists') ->orCondition('Price', 'attribute_not_exists) ->putItem([...]);
AND 语句
DB::table('ProductCatalog') ->condition('Id', 'attribute_not_exists') ->condition('Price', 'attribute_not_exists) ->putItem([...]);
conditionIn()
ProductCatalog::key(['Id' => 101]) ->conditionIn('ProductCategory', ['Book', 'Bicycle']) ->updateItem([ 'Description' => 'updated!' ]);
conditionBetween()
ProductCatalog::key(['Id' => 101]) ->conditionBetween('Price', [0, 10]) ->updateItem([ 'Description' => 'updated!' ]);
处理查询
Amazon DynamoDB 中的查询操作是根据主键值查找项。
query() 和 keyCondition()
当我们进行 查询 时,必须指定 keyCondition。
我们可以使用一些比较运算符来排序键,但我们必须使用等式条件来指定分区键。
$response = DB::table('Thread') ->keyCondition('ForumName', '=', 'Amazon DynamoDB') ->keyCondition('Subject', 'begins_with', 'DynamoDB') ->query();
keyConditionBetween()
$response = DB::table('Thread') ->keyCondition('ForumName', '=', 'Amazon DynamoDB') ->keyConditionBetween('Subject', ['DynamoDB Thread 1', 'DynamoDB Thread 2']) ->query();
处理扫描
scan()
$response = DB::table('Thread')->scan();
过滤结果
当我们进行 查询 或 扫描 时,我们可以在返回之前使用过滤表达式来过滤结果。
这不能减少读取容量,但可以减少流量数据的大小。
filter()
$response = DB::table('Thread') ->filter('LastPostedBy', '=', 'User A') ->scan();
OR 语句
$response = DB::table('Thread') ->filter('LastPostedBy', '=', 'User A') ->orFilter('LastPostedBy', '=', 'User B') ->scan();
AND 语句
$response = DB::table('Thread') ->filter('LastPostedBy', '=', 'User A') ->filter('Subject', 'begins_with', 'DynamoDB') ->scan();
filterIn()
$response = DB::table('Thread') ->filterIn('LastPostedBy', ['User A', 'User B']) ->scan();
filterBetween()
$response = DB::table('ProductCatalog') ->filterBetween('Price', [0, 100]) ->scan();
分页结果
单个 查询 或 扫描 只能返回一个大小在 1 MB 限制以内的结果集。如果有更多结果,我们需要分页。
exclusiveStartKey()
如果有更多结果,响应将包含 LastEvaluatedKey。
$response = DB::table('ProductCatalog') ->limit(5) ->scan(); $response['LastEvaluatedKey']; // array
我们可以将此键传递给 exclusiveStartKey 来获取下一页结果。
$response = DB::table('ProductCatalog') ->exclusiveStartKey($response['LastEvaluatedKey']) ->limit(5) ->scan();
如果您通过模型使用 Query Builder,您可以通过以下方式访问到 exclusiveStartKey:
$products = ProductCatalog::limit(5)->scan(); $products->first()->meta()['LastEvaluatedKey']; // array
使用全局二级索引
某些应用程序可能需要执行许多不同类型的查询,使用不同的属性作为查询条件。为了支持这些需求,您可以在 Amazon DynamoDB 中创建一个或多个全局二级索引,并对这些索引发出 查询 请求。
index()
使用 index 子句来指定全局二级索引的名称。
$response = DB::table('Reply') ->index('PostedBy-Message-index') ->keyCondition('PostedBy', '=', 'User A') ->keyCondition('Message', '=', 'DynamoDB Thread 2 Reply 1 text') ->query();
原子计数器
DynamoDB 支持原子计数器。[链接](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithItems.html#WorkingWithItems.AtomicCounters)。当我们通过模型或查询构建器调用 increment() 和 decrement() 时,底层将使用原子计数器。
DB::('Thread')->key([ 'ForumName' => 'Laravel', 'Subject' => 'Laravel Thread 1' ])->increment('Replies', 2);
我们还可以传递其他要更新的属性。
DB::('Thread')->key([ 'ForumName' => 'Laravel', 'Subject' => 'Laravel Thread 1' ])->increment('Replies', 2, [ 'LastPostedBy' => 'User A', ]);
condition() 和 filter() 的 DynamoDB 特定操作符
对于 condition 和 filter 子句,我们可以使用 DynamoDB 的比较运算符和函数。
比较器
可以使用以下形式的 =、<>、<、<=、>、>=。
filter($key, $comparator, $value);
函数
可用的函数有
filter($key, 'attribute_exists'); filter($key, 'attribute_not_exists'); filter($key, 'attribute_type', $type); filter($key, 'begins_with', $value); filter($key, 'contains', $value);
当前不支持
size函数。
测试
$ ./vendor/bin/phpunit