tobento / service-storage
一个用于存储和检索项的存储接口。
Requires
- php: >=8.0
- tobento/service-collection: ^1.0.7
- tobento/service-file-creator: ^1.0
- tobento/service-filesystem: ^1.0
- tobento/service-iterable: ^1.0
- tobento/service-support: ^1.0
Requires (Dev)
- phpunit/phpunit: ^9.5
- tobento/service-database: ^1.0
- vimeo/psalm: ^4.0
README
存储服务附带一个用于存储和检索项的查询构建器。
目录
入门
使用以下命令添加正在运行的存储服务的最新版本。
composer require tobento/service-storage
要求
- PHP 8.0 或更高版本
亮点
- 框架无关,适用于任何项目
- 解耦设计
- 查询构建器
- PDO MariaDb 存储
- PDO MySql 存储
- Json 文件存储
- 内存存储
简单示例
以下是一个如何使用存储服务的简单示例。
use Tobento\Service\Storage\Tables\Tables; use Tobento\Service\Storage\JsonFileStorage; use Tobento\Service\Storage\ItemInterface; $tables = new Tables(); $tables->add('products', ['id', 'sku', 'price'], 'id'); $storage = new JsonFileStorage( dir: 'home/private/storage/', tables: $tables ); $inserted = $storage ->table('products') ->insert([ 'id' => 1, 'sku' => 'pencil', 'price' => 1.29, ]); $item = $storage->table('products')->find(1); var_dump($item instanceof ItemInterface); // bool(true)
文档
存储
Pdo MariaDb 存储
use Tobento\Service\Database\PdoDatabaseFactory; use Tobento\Service\Storage\Tables\Tables; use Tobento\Service\Storage\PdoMariaDbStorage; use Tobento\Service\Storage\StorageInterface; use PDO; $pdo = (new PdoDatabaseFactory())->createPdo( name: 'mysql', config: [ 'dsn' => 'mysql:host=localhost;dbname=db_name', 'username' => 'root', 'password' => '', 'options' => [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false, ], ], ); $tables = new Tables(); $tables->add('products', ['id', 'sku', 'price'], 'id'); $tables->add('users', ['id', 'firstname', 'lastname', 'email'], 'id'); $storage = new PdoMariaDbStorage($pdo, $tables); var_dump($storage instanceof StorageInterface); // bool(true)
Pdo MySql 存储
use Tobento\Service\Database\PdoDatabaseFactory; use Tobento\Service\Storage\Tables\Tables; use Tobento\Service\Storage\PdoMySqlStorage; use Tobento\Service\Storage\StorageInterface; use PDO; $pdo = (new PdoDatabaseFactory())->createPdo( name: 'mysql', config: [ 'dsn' => 'mysql:host=localhost;dbname=db_name', 'username' => 'root', 'password' => '', 'options' => [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false, ], ], ); $tables = new Tables(); $tables->add('products', ['id', 'sku', 'price'], 'id'); $tables->add('users', ['id', 'firstname', 'lastname', 'email'], 'id'); $storage = new PdoMySqlStorage($pdo, $tables); var_dump($storage instanceof StorageInterface); // bool(true)
Json 文件存储
use Tobento\Service\Storage\Tables\Tables; use Tobento\Service\Storage\JsonFileStorage; use Tobento\Service\Storage\StorageInterface; $tables = new Tables(); $tables->add('products', ['id', 'sku', 'price'], 'id'); $tables->add('users', ['id', 'firstname', 'lastname', 'email'], 'id'); $storage = new JsonFileStorage( dir: 'home/private/storage/', tables: $tables ); var_dump($storage instanceof StorageInterface); // bool(true)
内存存储
use Tobento\Service\Storage\Tables\Tables; use Tobento\Service\Storage\InMemoryStorage; use Tobento\Service\Storage\StorageInterface; $tables = new Tables(); $tables->add('products', ['id', 'sku', 'price'], 'id'); $tables->add('products_lg', ['product_id', 'language_id', 'title']); $tables->add('users', ['id', 'firstname', 'lastname', 'email'], 'id'); $storage = new InMemoryStorage([ 'products' => [ 1 => ['id' => 1, 'sku' => 'paper', 'price' => 1.2], 2 => ['id' => 2, 'sku' => 'pen', 'price' => 1.56], ], 'products_lg' => [ ['product_id' => 1, 'language_id' => 1, 'title' => 'Papier'], ['product_id' => 1, 'language_id' => 2, 'title' => 'Paper'], ['product_id' => 2, 'language_id' => 1, 'title' => 'Stift'], ], 'users' => [ 1 => ['id' => 1, 'firstname' => 'Erika', 'lastname' => 'Mustermann', 'email' => 'erika.mustermann@example.com'], 2 => ['id' => 2, 'firstname' => 'Mustermann', 'lastname' => 'Mustermann', 'email' => 'mustermann@example.com'], ], ], $tables); var_dump($storage instanceof StorageInterface); // bool(true)
存储比较
支持返回项列
查询
选择语句
检索方法
get
检索项。
use Tobento\Service\Storage\ItemsInterface; $products = $storage->table('products')->get(); var_dump($products instanceof ItemsInterface); // bool(true) $products->all(); /*Array ( [1] => Array ( [id] => 1 [sku] => paper [price] => 1.2 ) [2] => Array ( [id] => 2 [sku] => pen [price] => 1.56 ) )*/
查看项接口了解更多信息。
column
use Tobento\Service\Storage\ItemInterface; $column = $storage->table('products')->column('price'); var_dump($column instanceof ItemInterface); // bool(true) $column->all(); /*Array ( [0] => 1.2 [1] => 1.56 )*/
通过特定列进行索引
$storage->table('products')->column('price', 'sku')->all(); /*Array ( [paper] => 1.2 [pen] => 1.56 )*/
查看项接口了解更多信息。
first
返回找到的第一个项或 NULL。
use Tobento\Service\Storage\ItemInterface; $product = $storage->table('products')->first(); var_dump($product instanceof ItemInterface); // bool(true) $product->all(); /*Array ( [id] => 1 [sku] => paper [price] => 1.2 )*/
查看项接口了解更多信息。
find
通过 ID 返回单个项或 NULL。
use Tobento\Service\Storage\ItemInterface; $product = $storage->table('products')->find(2); var_dump($product instanceof ItemInterface); // bool(true) $product->all(); /*Array ( [id] => 2 [sku] => pen [price] => 1.56 )*/
查看项接口了解更多信息。
value
从找到的第一个项中获取单个列的值。
$value = $storage->table('products')->value('sku'); var_dump($value); // string(5) "paper"
count
获取项计数。
$count = $storage->table('products')->count(); var_dump($count); // int(2)
WHERE 子句
where / orWhere
$products = $storage->table('products') ->where('price', '>', 1.3) ->get(); $products = $storage->table('products') ->where('price', '>', 1.2) ->orWhere(function($query) { $query->where('price', '>', 1.2) ->where('sku', '=', 'pen'); }) ->get(); $products = $storage->table('products') ->where(function($query) { $query->where('price', '>', 1.2) ->orWhere('sku', '=', 'pen'); }) ->get(); // Finds any values that start with "a" $products = $storage->table('products') ->where('sku', 'like', 'a%') ->get(); // Finds any values that end with "a" $products = $storage->table('products') ->where('sku', 'like', '%a') ->get(); // Finds any values that have "a" in any position $products = $storage->table('products') ->where('sku', 'like', '%a%') ->get();
支持的运算符:=, !=, >, <, >=, <=, <>, <=>, like, not like
whereIn / whereNotIn / orWhereIn / orWhereNotIn
$products = $storage->table('products') ->whereIn('id', [2, 3]) ->get(); $products = $storage->table('products') ->whereNotIn('id', [2, 3]) ->get();
whereNull / whereNotNull / orWhereNull / orWhereNotNull
$products = $storage->table('products') ->whereNull('price') ->get(); $products = $storage->table('products') ->whereNotNull('price') ->get();
whereBetween / whereNotBetween / orWhereBetween / orWhereNotBetween
$products = $storage->table('products') ->whereBetween('price', [1.2, 15]) ->get(); $products = $storage->table('products') ->whereNotBetween('price', [1.2, 15]) ->get();
whereColumn / orWhereColumn
$users = $storage->table('users') ->whereColumn('firstname', '=', 'lastname') ->get();
支持的运算符:=, !=, >, <, >=, <=, <>, <=>
JSON WHERE 子句
whereJsonContains / orWhereJsonContains
$products = $storage->table('products') ->whereJsonContains('options->color', 'blue') ->get(); $products = $storage->table('products') ->whereJsonContains('options->color', ['blue', 'red']) ->get();
whereJsonContainsKey / orWhereJsonContainsKey
$products = $storage->table('products') ->whereJsonContainsKey('options->color') ->get();
whereJsonLength / orWhereJsonLength
$products = $storage->table('products') ->whereJsonLength('options->color', '>', 2) ->get();
支持的运算符:=, !=, >, <, >=, <=, <>, <=>
JOIN 子句
join
$products = $storage->table('products') ->join('products_lg', 'id', '=', 'product_id') ->get();
leftJoin / rightJoin
$products = $storage->table('products') ->leftJoin('products_lg', 'id', '=', 'product_id') ->get(); $products = $storage->table('products') ->rightJoin('products_lg', 'id', '=', 'product_id') ->get();
高级 JOIN 子句
$products = $storage->table('products') ->join('products_lg', function($join) { $join->on('id', '=', 'product_id') ->orOn('id', '=', 'language_id'); }) ->get(); $products = $storage->table('products') ->join('products_lg', function($join) { $join->on('id', '=', 'product_id') ->where('product_id', '>', 2); }) ->get();
GROUP 子句
$products = $storage->table('products') ->groupBy('price') ->having('price', '>', 2) ->get(); $products = $storage->table('products') ->groupBy('price') ->havingBetween('price', [1, 4]) ->get();
选择列
您可以选择特定的列。
$products = $storage->table('products') ->select('id', 'sku') ->get(); $product = $storage->table('products') ->select('id', 'sku') ->first();
索引列
指定要索引的项的列。
$products = $storage->table('products') ->index('sku') ->get();
排序
$products = $storage->table('products') ->order('sku', 'ASC') ->get(); $products = $storage->table('products') ->order('sku', 'DESC') ->get();
限制
$products = $storage->table('products') ->limit(number: 2, offset: 10) ->get();
插入语句
use Tobento\Service\Storage\ItemInterface; $insertedItem = $storage ->table('products') ->insert([ 'sku' => 'glue', 'price' => 4.55, ]); var_dump($insertedItem instanceof ItemInterface); // bool(true)
查看项接口了解更多信息。
返回特定列
$insertedItem = $storage ->table('products') ->insert( item: ['sku' => 'glue'], return: ['id'] ); var_dump($insertedItem->all());
返回 null
$insertedItem = $storage ->table('products') ->insert( item: ['sku' => 'glue'], return: null ); var_dump($insertedItem->all()); // array(0) { }
批量插入语句
use Tobento\Service\Storage\ItemsInterface; $insertedItems = $storage ->table('products') ->insertItems([ ['sku' => 'glue', 'price' => 4.55], ['sku' => 'pencil', 'price' => 1.99], ]); var_dump($insertedItems instanceof ItemsInterface); // bool(true)
查看项接口了解更多信息。
返回特定列
$insertedItems = $storage ->table('products') ->insertItems( items: [ ['sku' => 'glue', 'price' => 4.55], ['sku' => 'pencil', 'price' => 1.99], ], return: ['id'] ); var_dump($insertedItems->all()); // array(2) { [0]=> array(1) { ["id"]=> int(3) } [1]=> array(1) { ["id"]=> int(4) } }
返回 null
$insertedItems = $storage ->table('products') ->insertItems( items: [ ['sku' => 'glue', 'price' => 4.55], ['sku' => 'pencil', 'price' => 1.99], ], return: null ); var_dump($insertedItems->all()); // array(0) { }
项工厂
您可以使用项工厂迭代器来初始化项,并使用 Seeder 服务来生成模拟数据。
use Tobento\Service\Iterable\ItemFactoryIterator; use Tobento\Service\Seeder\Str; use Tobento\Service\Seeder\Num; $insertedItems = $storage->table('products') ->chunk(length: 20000) ->insertItems( items: new ItemFactoryIterator( function() { return [ 'sku' => Str::string(10), 'price' => Num::float(min: 1.5, max: 55.5), ]; }, create: 1000000 // create 1 million items ) ); foreach($insertedItems as $product) {}
Json 文件项
use Tobento\Service\Iterable\JsonFileIterator; use Tobento\Service\Iterable\ModifyIterator; $iterator = new JsonFileIterator( file: 'private/src/products.json', ); // you may use the modify iterator: $iterator = new ModifyIterator( iterable: $iterator, modifier: function(array $item): array { return [ 'sku' => $item['sku'] ?? '', 'price' => $item['price'] ?? '', ]; } ); $insertedItems = $storage->table('products') ->chunk(length: 20000) ->insertItems($iterator); foreach($insertedItems as $product) {}
更新语句
您可以使用 WHERE 子句来约束更新查询。
use Tobento\Service\Storage\ItemsInterface; $updatedItems = $storage ->table('products') ->where('id', '=', 2) ->update([ 'price' => 4.55, ]); var_dump($updatedItems instanceof ItemsInterface); // bool(true)
查看项接口了解更多信息。
返回特定列
$updatedItems = $storage ->table('products') ->where('price', '>', 1.5) ->update( item: ['price' => 4.55], return: ['id'] ); var_dump($updatedItems->all()); // array(2) { [0]=> array(1) { ["id"]=> int(2) } }
⚠️ PDO MySql 存储不支持返回语句,因此不会返回项。
但是您可以获取计数
var_dump($updatedItems->count()); // int(1)
返回 null
$updatedItems = $storage ->table('products') ->where('price', '>', 1.5) ->update( item: ['price' => 4.55], return: null ); var_dump($updatedItems->all()); // array(0) { }
updateOrInsert
use Tobento\Service\Storage\ItemInterface; use Tobento\Service\Storage\ItemsInterface; $items = $storage->table('products')->updateOrInsert( ['id' => 2], // where clauses ['sku' => 'glue', 'price' => 3.48], return: ['id'] ); // if updated: var_dump($items instanceof ItemsInterface); // bool(true) // if inserted: var_dump($items instanceof ItemInterface); // bool(true)
查看项接口了解更多信息。
查看项接口了解更多信息。
更新 JSON 列
$updated = $storage ->table('products') ->where('id', 2) ->update([ 'options->color' => ['red'], 'options->active' => true, ]);
删除语句
use Tobento\Service\Storage\ItemsInterface; $deletedItems = $storage->table('products') ->where('price', '>', 1.33) ->delete(); var_dump($deletedItems instanceof ItemsInterface); // bool(true)
查看项接口了解更多信息。
返回特定列
$deletedItems = $storage->table('products') ->where('id', '=', 2) ->delete(return: ['sku']); var_dump($deletedItems->all()); // array(1) { [0]=> array(1) { ["sku"]=> string(3) "pen" } }
返回 null
$deletedItems = $storage->table('products') ->where('id', '=', 2) ->delete(return: null); var_dump($deletedItems->all()); // array(0) { }
事务
commit
$storage->begin(); // your queries $storage->commit();
rollback
$storage->begin(); // your queries $storage->rollback();
transaction
您可以使用事务方法在事务内运行一系列存储操作。如果在事务闭包内抛出异常,事务将自动回滚。如果闭包成功执行,事务将自动提交。
use Tobento\Service\Storage\StorageInterface; $storage->transaction(function(StorageInterface $storage) { // your queries });
分块结果和插入
如果您需要处理成千上万甚至数百万个项,可以使用分块方法。
column
以生成器模式返回列。
$column = $storage->table('products') ->chunk(length: 2000) ->column('price'); foreach($column as $price) { var_dump($price); // float(1.2) }
get
以生成器模式返回项。
$products = $storage->table('products') ->chunk(length: 2000) ->get(); foreach($products as $product) { var_dump($product['sku']); // string(5) "paper" }
insertItems
以生成器模式返回插入的项。
$insertedItems = $storage ->table('products') ->chunk(length: 10000) ->insertItems([ ['sku' => 'glue', 'price' => 4.55], ['sku' => 'pencil', 'price' => 1.99], // ... ]); foreach($insertedItems as $product) { var_dump($product['id']); // int(3) }
如果您将返回参数设置为null,它将立即插入项但不在生成器模式中返回它们。不过,您可能能够获取创建的项数。
$insertedItems = $storage ->table('products') ->chunk(length: 10000) ->insertItems([ ['sku' => 'glue', 'price' => 4.55], ['sku' => 'pencil', 'price' => 1.99], // ... ], return: null); var_dump($insertedItems->count()); // int(2)
杂项
new
new方法将返回一个新的存储实例。
$newStorage = $storage->new();
fetchItems
fetchItems方法将返回所有表项。
$iterable = $storage->fetchItems(table: 'products');
storeItems
storeItems方法将存储项到表中,表将被先截断。
$storedItems = $storage->storeItems( table: 'products', items: [] // iterable );
deleteTable
deleteTable方法将完全删除表。
$storage->deleteTable('products');
调试
[$statement, $bindings] = $storage->table('products') ->where('id', '=', 1) ->getQuery(); var_dump($statement); // string(56) "SELECT `id`,`sku`,`price` FROM `products` WHERE `id` = ?" var_dump($bindings); // array(1) { [0]=> int(1) }
使用闭包
如果您想调试除"get"或插入、更新和删除语句之外的检索方法,则必须使用闭包。
[$statement, $bindings] = $storage->table('products') ->where('id', '=', 1) ->getQuery(function(StorageInterface $storage) { $storage->update([ 'price' => 4.55, ]); }); var_dump($statement); // string(48) "UPDATE `products` SET `price` = ? WHERE `id` = ?" var_dump($bindings); // array(2) { [0]=> float(4.55) [1]=> int(1) }
项接口
迭代项属性
use Tobento\Service\Storage\ItemInterface; use Tobento\Service\Storage\Item; $item = new Item(['title' => 'Title']); var_dump($item instanceof ItemInterface); // bool(true) foreach($item as $attr) { var_dump($attr); // string(5) "Title" }
get
通过键获取项值。
use Tobento\Service\Storage\ItemInterface; use Tobento\Service\Storage\Item; $item = new Item(['title' => 'Title']); var_dump($item instanceof ItemInterface); // bool(true) var_dump($item->get('title')); // string(5) "Title" // returns the default value if the key does not exist. var_dump($item->get('sku', 'Sku')); // string(3) "Sku"
all
返回所有项(属性)。
use Tobento\Service\Storage\Item; $item = new Item(['title' => 'Title']); var_dump($item->all()); // array(1) { ["title"]=> string(5) "Title" }
count
返回属性的数量。
use Tobento\Service\Storage\Item; $item = new Item(['title' => 'Title']); var_dump($item->count()); // int(1)
collection
返回一个新的包含属性的Collection。
use Tobento\Service\Storage\Item; use Tobento\Service\Collection\Collection; $item = new Item(['title' => 'Title']); var_dump($item->collection() instanceof Collection); // bool(true)
查看Collection以了解更多信息。
项接口
迭代项
use Tobento\Service\Storage\ItemsInterface; use Tobento\Service\Storage\Items; $items = new Items([ 'foo' => ['title' => 'Title'], ]); var_dump($items instanceof ItemsInterface); // bool(true) foreach($items as $item) { var_dump($item['title']); // string(5) "Title" }
get
通过键获取项值。
use Tobento\Service\Storage\Items; $items = new Items([ 'foo' => ['title' => 'Title'], ]); var_dump($items->get('foo.title')); // string(5) "Title" // returns the default value if the key does not exist. var_dump($items->get('foo.sku', 'Sku')); // string(3) "Sku"
all
返回所有项。
use Tobento\Service\Storage\Items; $items = new Items([ 'foo' => ['title' => 'Title'], ]); var_dump($items->all()); // array(1) { ["foo"]=> array(1) { ["title"]=> string(5) "Title" } }
first
返回第一个项,否则为null。
use Tobento\Service\Storage\Items; $items = new Items([ ['foo' => 'Foo'], ['bar' => 'Bar'], ]); var_dump($items->first()); // array(1) { ["foo"]=> string(3) "Foo" }
column
返回项的列。
use Tobento\Service\Storage\Items; $items = new Items([ ['name' => 'Foo', 'id' => 3], ['name' => 'Bar', 'id' => 5], ]); var_dump($items->column(column: 'name')); // array(2) { [0]=> string(3) "Foo" [1]=> string(3) "Bar" } // with index var_dump($items->column(column: 'name', index: 'id')); // array(2) { [3]=> string(3) "Foo" [5]=> string(3) "Bar" }
map
对每个项进行映射,返回一个新的实例。
use Tobento\Service\Storage\Items; use Tobento\Service\Storage\Item; $items = new Items([ ['foo' => 'Foo'], ['bar' => 'Bar'], ]); $itemsNew = $items->map(function(array $item): object { return new Item($item); }); var_dump($itemsNew->first()); // object(Tobento\Service\Storage\Item)#8 ...
groupBy
返回一个具有分组项的新实例。
use Tobento\Service\Storage\Items; $items = new Items([ ['name' => 'bear', 'group' => 'animals'], ['name' => 'audi', 'group' => 'cars'], ['name' => 'ant', 'group' => 'animals'], ]); $groups = $items->groupBy(groupBy: 'group')->all(); /*Array ( [animals] => Array ( [0] => Array ( [name] => bear [group] => animals ) [2] => Array ( [name] => ant [group] => animals ) ) [cars] => Array ( [1] => Array ( [name] => audi [group] => cars ) ) )*/ // using a callable: $groups = $items->groupBy( groupBy: fn ($item) => $item['group'], ); // using a callable for grouping: $groups = $items->groupBy( groupBy: 'group', groupAs: fn (array $group) => (object) $group, ); // without preserving keys: $groups = $items->groupBy( groupBy: 'group', preserveKeys: false, );
reindex
重新索引项,返回一个新的实例。
use Tobento\Service\Storage\Items; $items = new Items([ ['sku' => 'foo', 'name' => 'Foo'], ['sku' => 'bar', 'name' => 'Bar'], ]); $itemsNew = $items->reindex(function(array $item): int|string { return $item['sku']; }); var_dump(array_keys($itemsNew->all())); // array(2) {[0]=> string(3) "foo" [1]=> string(3) "bar"}
count
返回项的数量。
use Tobento\Service\Storage\Items; $items = new Items([ 'foo' => ['title' => 'Title'], ]); var_dump($items->count()); // int(1)
collection
返回一个包含项的新Collection。
use Tobento\Service\Storage\Items; use Tobento\Service\Collection\Collection; $items = new Items([ 'foo' => ['title' => 'Title'], ]); var_dump($items->collection() instanceof Collection); // bool(true)
查看Collection以了解更多信息。
表
存储器使用表来验证用于构建查询的表和列名称。
add
use Tobento\Service\Storage\Tables\Tables; $tables = new Tables(); $tables->add( table: 'products', columns: ['id', 'sku'], primaryKey: 'id' // or null if none );