Skip to content

Commit

Permalink
Merge pull request #3 from ray-di/web-api-query
Browse files Browse the repository at this point in the history
Support Web API
  • Loading branch information
koriym committed Feb 15, 2021
2 parents 94488fc + c780607 commit ef6c7a1
Show file tree
Hide file tree
Showing 17 changed files with 276 additions and 19 deletions.
2 changes: 1 addition & 1 deletion README.ja.md
Expand Up @@ -227,7 +227,7 @@ $sqlQuery->exec('memo_add', ['memo' => 'run', 'created_at' => new DateTime()]);

オブジェクトが渡されるとParameter Injectionと同様`toScalar()`または`__toString()`の値に変換されます。


#
## プロファイラー

メディアアクセスはロガーで記録されます。標準ではテストに使うメモリロガーがバインドされています。
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Expand Up @@ -13,8 +13,9 @@
"ext-json": "*",
"ext-pdo": "*",
"aura/sql": "^3.0",
"pagerfanta/pagerfanta": "^1.1",
"doctrine/annotations": "^1.11",
"guzzlehttp/guzzle": "^7.2",
"pagerfanta/pagerfanta": "^1.1",
"ray/aop": "^2.10",
"ray/aura-sql-module": "^1.10",
"ray/di": "2.x-dev"
Expand Down
2 changes: 1 addition & 1 deletion demo/run.php
Expand Up @@ -29,7 +29,7 @@ protected function configure()
UserAddInterface::class,
UserItemInterface::class
]);
$this->install(new MediaQueryModule($this->sqlDir, $queries));
$this->install(new MediaQueryModule($queries, $this->sqlDir));
$this->install(new AuraSqlModule($this->dsn));
}
});
Expand Down
1 change: 1 addition & 0 deletions phpcs.xml
Expand Up @@ -49,6 +49,7 @@
<exclude name="PSR12.Files.FileHeader.IncorrectOrder"/>
<exclude name="PSR12.Files.FileHeader.SpacingAfterBlock"/>
<exclude name="SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint"/>
<exclude name="SlevomatCodingStandard.TypeHints.ParameterTypeHint.UselessAnnotation"/>
</rule>


Expand Down
28 changes: 28 additions & 0 deletions src/Annotation/WebQuery.php
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Ray\MediaQuery\Annotation;

use Attribute;
use Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation;

/**
* @Annotation
* @Target("METHOD")
*/
#[Attribute(Attribute::TARGET_METHOD)]
final class WebQuery implements NamedArgumentConstructorAnnotation
{
/** @var string */
public $method;

/** @var string */
public $uri;

public function __construct(string $method, string $uri)
{
$this->method = $method;
$this->uri = $uri;
}
}
Expand Up @@ -11,7 +11,7 @@

use function substr;

class MediaQueryInterceptor implements MethodInterceptor
class DbQueryInterceptor implements MethodInterceptor
{
/** @var SqlQueryInterface */
private $sqlQuery;
Expand Down
11 changes: 11 additions & 0 deletions src/Exception/WebApiRequestException.php
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Ray\MediaQuery\Exception;

use RuntimeException;

class WebApiRequestException extends RuntimeException
{
}
2 changes: 1 addition & 1 deletion src/MediaQueryLogger.php
Expand Up @@ -24,7 +24,7 @@ public function start(): void
*/
public function log(string $queryId, array $values): void
{
$this->logs[] = sprintf('query:%s(%s)', $queryId, json_encode($values));
$this->logs[] = sprintf('query: %s(%s)', $queryId, json_encode($values));
}

public function __toString(): string
Expand Down
38 changes: 31 additions & 7 deletions src/MediaQueryModule.php
Expand Up @@ -6,10 +6,13 @@

use DateTimeImmutable;
use DateTimeInterface;
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use Ray\Di\AbstractModule;
use Ray\Di\Scope;
use Ray\MediaQuery\Annotation\DbQuery;
use Ray\MediaQuery\Annotation\SqlDir;
use Ray\MediaQuery\Annotation\WebQuery;

class MediaQueryModule extends AbstractModule
{
Expand All @@ -19,29 +22,50 @@ class MediaQueryModule extends AbstractModule
/** @var list<class-string> */
private $mediaQueries;

public function __construct(string $sqlDir, Queries $mediaQueries, ?AbstractModule $module = null)
/** @var array<string, string> */
private array $domainBindings;

/**
* @param Queries $mediaQueries
* @param string $sqlDir
* @param array<string, string> $domainBindings
* @param AbstractModule|null $module
*/
public function __construct(Queries $mediaQueries, string $sqlDir, array $domainBindings = [], ?AbstractModule $module = null)
{
$this->mediaQueries = $mediaQueries->classes;
$this->sqlDir = $sqlDir;
$this->domainBindings = $domainBindings;
parent::__construct($module);
}

protected function configure(): void
{
$this->bind(SqlQueryInterface::class)->to(SqlQuery::class);
$this->bind(MediaQueryLoggerInterface::class)->to(MediaQueryLogger::class)->in(Scope::SINGLETON);
$this->bind(ParamInjectorInterface::class)->to(ParamInjector::class);
$this->bind(ParamConverterInterface::class)->to(ParamConverter::class);
$this->bind(DateTimeInterface::class)->to(DateTimeImmutable::class);
// Bind media query interface
foreach ($this->mediaQueries as $mediaQuery) {
$this->bind($mediaQuery)->toNull();
}

// DbQuery
$this->bind(SqlQueryInterface::class)->to(SqlQuery::class);
$this->bindInterceptor(
$this->matcher->any(),
$this->matcher->annotatedWith(DbQuery::class),
[MediaQueryInterceptor::class]
[DbQueryInterceptor::class]
);
$this->bind()->annotatedWith(SqlDir::class)->toInstance($this->sqlDir);
// Bind media query interface
foreach ($this->mediaQueries as $mediaQuery) {
$this->bind($mediaQuery)->toNull();
}
// Web Query
$this->bindInterceptor(
$this->matcher->any(),
$this->matcher->annotatedWith(WebQuery::class),
[WebQueryInterceptor::class]
);
$this->bind(ClientInterface::class)->to(Client::class);
$this->bind(WebApiQueryInterface::class)->to(WebApiQuery::class);
$this->bind()->annotatedWith('web_api_query_domain')->toInstance($this->domainBindings);
}
}
58 changes: 58 additions & 0 deletions src/WebApiQuery.php
@@ -0,0 +1,58 @@
<?php

declare(strict_types=1);

namespace Ray\MediaQuery;

use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\GuzzleException;
use Ray\Di\Di\Named;
use Ray\MediaQuery\Exception\WebApiRequestException;

use function json_decode;
use function uri_template;

final class WebApiQuery implements WebApiQueryInterface
{
/** @var ClientInterface */
private $client;

/** @var MediaQueryLoggerInterface */
private $logger;

/** @var array<string, string> */
private $domainBindings;

/**
* @param array<string, string> $domainBindings
*
* @Named("'domainBindings=web_api_query_domain")
*/
#[Named('domainBindings=web_api_query_domain')]
public function __construct(ClientInterface $client, MediaQueryLoggerInterface $logger, array $domainBindings)
{
$this->client = $client;
$this->logger = $logger;
$this->domainBindings = $domainBindings;
}

/**
* {@inheritDoc}
*/
public function request(string $method, string $uri, array $query): array
{
try {
$this->logger->start();
$boundUri = uri_template($uri, $this->domainBindings + $query);
$response = $this->client->request($method, $boundUri, $query);
$json = $response->getBody()->getContents();
/** @var array<string, mixed> $body */
$body = json_decode($json, true);
$this->logger->log($boundUri, $query);

return $body;
} catch (GuzzleException $e) {
throw new WebApiRequestException($e->getMessage(), (int) $e->getCode(), $e);
}
}
}
15 changes: 15 additions & 0 deletions src/WebApiQueryInterface.php
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Ray\MediaQuery;

interface WebApiQueryInterface
{
/**
* @param array<string, string> $query
*
* @return array<string, mixed>
*/
public function request(string $method, string $uri, array $query): array;
}
38 changes: 38 additions & 0 deletions src/WebQueryInterceptor.php
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace Ray\MediaQuery;

use Ray\Aop\MethodInterceptor;
use Ray\Aop\MethodInvocation;
use Ray\MediaQuery\Annotation\WebQuery;

class WebQueryInterceptor implements MethodInterceptor
{
/** @var WebApiQueryInterface */
private $webApiQuery;

/** @var ParamInjectorInterface */
private $paramInjector;

public function __construct(WebApiQueryInterface $webApiQuery, ParamInjectorInterface $paramInjector)
{
$this->webApiQuery = $webApiQuery;
$this->paramInjector = $paramInjector;
}

/**
* @return Pages|array<mixed>
*/
public function invoke(MethodInvocation $invocation)
{
$method = $invocation->getMethod();
/** @var WebQuery $webQuery */
$webQuery = $method->getAnnotation(WebQuery::class);
/** @var array<string, string> $values */
$values = $this->paramInjector->getArgumentes($invocation);

return $this->webApiQuery->request($webQuery->method, $webQuery->uri, $values);
}
}
13 changes: 13 additions & 0 deletions tests/Fake/WebApi/FooItemInterface.php
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Ray\MediaQuery\WebApi;

use Ray\MediaQuery\Annotation\WebQuery;

interface FooItemInterface
{
#[WebQuery(method: 'GET', uri: 'https://{domain}/anything/{id}')]
public function __invoke(string $id): array;
}
12 changes: 6 additions & 6 deletions tests/MediaQueryModuleTest.php
Expand Up @@ -44,7 +44,7 @@ protected function setUp(): void
PromiseListInterface::class,
]);
$sqlDir = dirname(__DIR__) . '/tests/sql';
$module = new MediaQueryModule($sqlDir, $mediaQueries, new AuraSqlModule('sqlite::memory:'));
$module = new MediaQueryModule($mediaQueries, $sqlDir, [], new AuraSqlModule('sqlite::memory:'));
$this->injector = new Injector($module);
$pdo = $this->injector->getInstance(ExtendedPdoInterface::class);
assert($pdo instanceof ExtendedPdoInterface);
Expand All @@ -63,14 +63,14 @@ public function testInsertItem(): void
assert($todoAdd instanceof TodoAddInterface);
$todoAdd('1', 'run');
$log = (string) $this->logger;
$this->assertStringContainsString('query:todo_add', $log);
$this->assertStringContainsString('query: todo_add', $log);
$todoItem = $this->injector->getInstance(TodoItemInterface::class);

assert($todoItem instanceof TodoItemInterface);
$item = $todoItem('1');
$this->assertSame(['id' => '1', 'title' => 'run'], $item);
$log = (string) $this->logger;
$this->assertStringContainsString('query:todo_item', $log);
$this->assertStringContainsString('query: todo_item', $log);
}

public function testSelectItem(): void
Expand All @@ -80,7 +80,7 @@ public function testSelectItem(): void
$item = $todoItem('1');
$this->assertSame(['id' => '1', 'title' => 'run'], $item);
$log = (string) $this->logger;
$this->assertStringContainsString('query:todo_item', $log);
$this->assertStringContainsString('query: todo_item', $log);
}

public function testSelectList(): void
Expand All @@ -91,7 +91,7 @@ public function testSelectList(): void
$row = ['id' => '1', 'title' => 'run', 'time' => '1970-01-01 00:00:00'];
$this->assertSame([$row], $list);
$log = (string) $this->logger;
$this->assertStringContainsString('query:promise_list([])', $log);
$this->assertStringContainsString('query: promise_list([])', $log);
}

public function testSelectPager(): void
Expand All @@ -103,7 +103,7 @@ public function testSelectPager(): void
$page = $list[1];
$this->assertSame([['id' => '1', 'title' => 'run']], $page->data);
$log = (string) $this->logger;
$this->assertStringContainsString('query:todo_list', $log);
$this->assertStringContainsString('query: todo_list', $log);
}

public function testPramInjection(): void
Expand Down
2 changes: 1 addition & 1 deletion tests/SqlQueryTest.php
Expand Up @@ -55,7 +55,7 @@ public function testNewInstance(): void
public function testExec(): void
{
$this->sqlQuery->exec('todo_add', $this->insertData);
$this->assertStringContainsString('query:todo_add({"id":"1","title":"run"})', (string) $this->log);
$this->assertStringContainsString('query: todo_add({"id":"1","title":"run"})', (string) $this->log);
}

/**
Expand Down

0 comments on commit ef6c7a1

Please sign in to comment.