tobento/service-storage

一个用于存储和检索项的存储接口。

1.2.7 2024-08-13 14:14 UTC

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
);

鸣谢