tarantool/mapper

Tarantool 的 PHP 对象映射器。

6.2.2 2024-07-10 15:25 UTC

This package is auto-updated.

Last update: 2024-09-10 15:51:59 UTC


README

License Testing Latest Version Total Downloads Telegram

入门

支持的版本是 php 8+ 和 tarantool 2+。
安装库的推荐方式是通过 Composer

composer require tarantool/mapper

通常,您在服务提供者中管理依赖项。
要开始使用,您应该创建客户端实例并将其传递给映射器构造函数。
在此示例中,我们使用 PurePacker 和 StreamConnection。
要查看其他实现,请查看 客户端文档

use Tarantool\Client\Client;
use Tarantool\Mapper\Mapper;

$client = Client::fromDefaults();
$mapper = new Mapper($client);

// internaly mapper wraps client with special middleware
assert($mapper->client !== $client);

模式管理

要开始,您应该描述您的空间、它们的格式和索引。

$person = $mapper->createSpace('person', [
    'engine' => 'memtx',
    'if_not_exists' => true,
]);

// add properties - name, type and options
$person->addProperty('id', 'unsigned');
$person->addProperty('name', 'string');
$person->addProperty('birthday', 'unsigned');
$person->addProperty('gender', 'string', [
    'default' => 'male'
]);

// indexes are created using fields array and optional index configuration
$person->addIndex(['name']);
$person->addIndex(['birthday'], ['unique' => true]);

// index name is fields based, but you can specify any preffered one
$person->addIndex(['name', 'birthday'], [
    'type' => 'hash',
    'name' => 'name_with_birthday',
]);

/**
 * define format using properties
 */
class Tracker
{
    public int $id;
    public int $reference;
    public string $status;

    public static function initSchema(\Tarantool\Mapper\Space $space)
    {
        $space->addIndex(['reference']);
    }
}

$tracker = $mapper->createSpace('tracker');
$tracker->setClass(Tracker::class);
$tracker->migrate();

/**
 * define format using constructor promotion
 */
class Policy
{
    public function __construct(
        public int $id,
        public string $nick,
        public string $status,
    ) {
    }

    public static function initialize(\Tarantool\Mapper\Space $space)
    {
        $space->addIndex(['nick'], ['unique' => true]);
    }
}

$policy = $mapper->createSpace('policy');
$policy->setClass(Policy::class, 'initialize'); // use custom initialize method
$policy->migrate();

与数据交互

现在您可以使用映射器实例从 tarantool 存储中存储和检索数据。

// get space instance
$persons = $mapper->getSpace('person');

// create new entity
$dmitry = $persons->create([
    'id' => 1,
    'name' => 'Dmitry'
]);

// create entities using mapper wrapper.
// this way entity will be created and saved in the tarantool
$vasily = $mapper->create('person', [
    'id' => 2,
    'name' => 'Vasily'
]);

// retreive entites by id using space
$helloWorld = $mapper->getSpace('post')->findOne(['id' => 3]);

// or using mapper wrapper
$helloWorld = $mapper->findOne('post', ['id' => 3]);

// pass client criteria object as well
$criteria = Criteria::index('age')->andKey([18])->andGeIterator();
$adults = $mapper->find('user', $criteria);

// updates are easy
$posts = $mapper->getSpace('post');
$helloWorld = $posts->update($helloWorld, [
    'title' => 'Hello world'
]);

// if you use instance classes, instance would be updated
$policy = $mapper->findOrFail('policy', ['id' => 3]);
$policy = $mapper->get('policy', 3); // getter shortcut
$mapper->update('policy', $policy, [
    'title' => 'updated title',
]);
echo $policy->title; // updated title

// use client operations as well
use Tarantool\Client\Schema\Operations;
$mapper->getSpace('policy')->update($policy, Operations::add('counter', 1));
var_dump($policy->counter); // actual value

模式缓存

任何新的映射器实例都会从 tarantool 获取模式,这可能会对数据库产生一些负载。
使用您喜欢的 psr/cache 实现来在应用程序端持久化模式。
例如,我们使用来自 symfony/cache 包的 apcu 适配器。
如果新的模式版本未在缓存中持久化,映射器将获取它

use Symfony\Component\Cache\Adapter\ApcuAdapter;
$cache = new ApcuAdapter();

$mapper = new Mapper(Client::fromDefaults());
$mapper->cache = $cache;
$mapper->getSpace('_vspace'); // schema is fetched now

$mapper = new Mapper(Client::fromDefaults());
$mapper->cache = $cache;
$mapper->getSpace('_vspace'); // no new requests are made

查询缓存

如果您不想重复选择查询,可以向空间注入缓存接口实现。
使用您喜欢的 psr/cache 实现来在应用程序端持久化模式。
例如,我们使用来自 symfony/cache 包的数组适配器。

use Symfony\Component\Cache\Adapter\ArrayAdapter;

$mapper = new Mapper(Client::fromDefaults());
$mapper->getSpace('_vspace')->cache = new ArrayAdapter(); // feel free to set default ttl

$mapper->find('_vspace'); // query is executed
$mapper->find('_vspace'); // results are fetched from cache
$mapper->find('_vspace'); // results are fetched from cache

变更注册

在某些情况下,您可能想要获取当前会话期间所做的所有更改。
默认情况下,spy 配置设置为 false,这会略微提高性能。

$mapper->spy = true;

$nekufa = $mapper->create('user', ['login' => 'nekufa']);
$firstPost = $mapper->create('post', [
    'user_id' => $nekufa->id,
    'title' => 'hello world',
]);
$mapper->update('post', $firstPost, ['title' => 'Final title']);

// now there are two changes
[$first, $second] = $mapper->getChanges();
echo $first->type; // insert
echo $first->space; // user
echo $first->data; // ['login' => 'nekufa']

// all changes would be merged by space and key
// this reduces changes duplicates
echo $second->type; // insert
echo $second->space; // post
echo $second->data; // ['user_id' => 1, 'title' => 'Final title']

// of course you can flush all changes and start registration from scratch
$mapper->flushChanges();

多连接

如果您将数据分散在多个 tarantool 实例中,可以使用基于前缀的数据 API。
API 与之前相同,但您需要在连接前缀前加上空间名称。

$pool = new Pool(function (string $prefix) {
    return new Mapper(Client::fromDsn('tcp://' . $prefix));
});

// connect to tarantool instance `volume` and find all timelines.
$trackers = $pool->findOne('volume.timeline');

$nekufa = $pool->findOrCreate('guard.login', ['username' => 'nekufa']);
$pool->update('guard.login', $nekufa, ['locked_at' => time()]);

// pool also wraps changes with the prefixes
echo $pool->getChanges()[0]->space; // guard.login

// all expressions do the same behind the scenes
$pool->find('flow.tracker', ['status' => 'active']);
$pool->getMapper('flow')->find('tracker', ['status' => 'active']);
$pool->getMapper('flow')->getSpace('tracker')->find(['status' => 'active']);

Lua 代码交付

iProto 的使用非常强大,但有时可能不够。
您可以使用关联数组轻松执行 Lua 代码并传递局部变量。

此外,如果您不想在每次请求中都传递它,可以使用神奇的 call 方法。
当您使用 call 方法时,映射器会生成唯一的函数名称,并在不存在时创建它。

// this method will always deliver and parse lua code on the tarantool side
$mapper->evaluate('return a + b', ['a' => 2, 'b' => 7]); // 9

// first call a function would be created with name evaluate_{BODYHASH}
// there would be two requests - create function and call it
$mapper->call('return a + b', ['a' => 2, 'b' => 7]); // 9

// second call will produce single request with function name and arguments
$mapper->call('return a + b', ['a' => 2, 'b' => 7]); // 9

迁移

使用基本迁移类在创建模式前后实现一些逻辑。
将迁移传递给映射器迁移方法,这样就完成了。


use Tarantool\Mapper\Migration;
use Tarantool\Mapper\Space;

class DropLegacySpaces extends Migration
{
    public function beforeSchema(Mapper $mapper)
    {
        $mapper->call(<<<LUA
            if box.space.legacy then
                box.space.legacy:drop()
                box.space.legacy_detail:drop()
            end
        LUA);
    }
}
class InitializeData extends Migration
{
    public function afterSchema(Mapper $mapper)
}
$mapper = $container->get(Mapper::class);

// also migrate accepts migration instance, or migration class arrays
$mapper->migrate(DropLegacySpaces::class, InitializeData::class);

性能

我们可以使用每个实例调用的 getInstance 方法来计算映射器开销。
如果您不使用缓存,则每次连接和模式升级时都会进行单个模式检索。
性能测试在 (AMD Ryzen 5 3600X),Ubuntu 23.10 上使用 PHP 8.3.6 进行。