Search
Write a publication
Pull to refresh

Бэкенд на PHP для кастомного RPC протокола

Пусть фронт(ы) общае(ю)тся с апи сервером с помощью набора методов, каждый из которых ожидает свой набор аргументов, что очень похоже на работу GraphQL (и GRPC).

Посылаем любым способом запрос вида:

{"method": "package.method1", "params": {"arg1": 123, "arg2": "some string", "arg3": [1, 2, 3]}}

Задача реализовать на пхп обработчик таких запросов с максимально удобным для программиста интерфейсом и минимальным оверхедом. Причем уже в клиентском коде я хочу точно знать какой ответ я ожидаю.

Условно говоря, я хочу создать обработчик такого вида:

<?php
#[Method("package.method1")]
function someFunction(int $arg1, string $arg2, array $arg3, SomeService $service): Method1Response {
  $r = new Method1Response();
  $r->val1 = 0;
  $r->val2 = "abc";
  $r->val3 = [4, 5, 6];
}

Здесь я хочу чтобы $arg1-3 были взяты из пришедшего запроса, а $service был инжектед.

Что получилось:

<?php
class TestMethodResponse extends MethodResponse {
    public string $key;
    public int $value;
    public float $rnd;
}

class TestMethod extends Method {
    // $key, $value and $testService will be injected from kernel (using definitions and passed params)
	public function __invoke(string $key, int $value, TestService $testService): TestMethodResponse {
		return (new TestMethodResponse()) // explicitly create response
            ->key($key) // use auto-setter with the same name as response' property $key
            ->value($value) // use auto-setter with the same name as response' property $value
            ->rnd($testService->test($value)) // use auto-setter with the same name as response' property $rnd
            // ->something(123) // will fail => no property $something exists in response object
        ;
	}
}

class TestPackage extends Package {
	public static function getMethods(): array {
		return [
            /** @return TestMethodResponse */ // optional set method return type
			"test.method" => TestMethod::class, // add method name and class
		];
	}
}

$config = (new Config())
    ->setBuildPath(__DIR__ . "/build") // set build path where wired injections will be stored
    ->setBuildNamespace("X\\build") // set namespace of built sources
    ->setDefinitions([ // injection rules for each type used
        "int" => function(string $name, array $params): int { return (int) $params[$name]; },
        "string" => function(string $name, array $params): string { return (string) $params[$name]; },
        TestService::class => function(string $name, array $params): TestService { return new TestService(); },
    ])->setPackages([
        TestPackage::class, // add method packages
    ]);

// optional build, better be moved to console command to use in workflows
(new Builder())->build($config);

// -----------------------------------------------------------------------------
// driver code

// create kernel with config
$kernel = new Kernel($config);

// call some method
// these params will be passed to method's parameters with the same names and types
$res = $kernel->run("test.method", ["key" => "data", "value" => 123]);

Минимально конфигурируемый, максимально низкоуровневый контейнер для обработки таких ситуаций.

https://github.com/tkx/kernel

Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.