ray / query-module
An external media access framework
Installs: 262 887
Dependents: 2
Suggesters: 0
Security: 0
Stars: 6
Watchers: 2
Forks: 5
Open Issues: 2
pkg:composer/ray/query-module
Requires
- php: ^7.3 || ^8.0
- ext-json: *
- ext-pdo: *
- aura/sql: ^3.0 | ^4.0 | ^5.0
- bear/resource: ^1.15
- doctrine/annotations: ^1.12
- guzzlehttp/guzzle: ^6.3 || ^7.0
- koriym/param-reader: ^1.0
- koriym/query-locator: ^1.4
- nikic/php-parser: ^v4.13
- ray/aop: ^2.10.3
- ray/aura-sql-module: ^1.10.0
- ray/di: ^2.11
Requires (Dev)
- bamarni/composer-bin-plugin: ^1.4
- phpunit/phpunit: ^9.6.19
README
Overview
Ray.QueryModule makes a query to an external media such as a database or Web API with a function object to be injected.
SqlQueryModuleis for DB. Convert the SQL file to a simple function object that executes that SQL.WebQueryModuleis for the Web API. Convert the URI and method set into a simple function object that Web requests to that URI.PhpQueryModuleis a generic module. It provides storage access which can not be provided by static conversion by PHP function object.
Motivation
- You can have a clear boundary between domain layer (usage code) and infrastructure layer (injected function) in code.
- Execution objects are generated automatically so you do not need to write procedural code for execution.
- Since usage codes are indifferent to the actual state of external media, storage can be changed later. Easy parallel development and stabbing.
Installation
Composer install
$ composer require ray/query-module
Module install
use Ray\Di\AbstractModule; use Ray\Query\SqlQueryModule; class AppModule extends AbstractModule { protected function configure() { // SqlQueryModule install $this->install(new SqlQueryModule($sqlDir)); // WebQueryModule install $webQueryConfig = [ 'post_todo' => ['POST', 'https://httpbin.org/todo'], // bind-name => [method, uri] 'get_todo' => ['GET', 'https://httpbin.org/todo'] ]; $guzzleConfig = []; // @see http://docs.guzzlephp.org/en/stable/request-options.html $this->install(new WebQueryModule($webQueryConfig, $guzzleConfig)); } }
SQL files
$sqlDir/todo_insert.sql
INSERT INTO todo (id, title) VALUES (:id, :title)
$sqlDir/todo_item_by_id.sql
SELECT * FROM todo WHERE id = :id
Convert SQL to SQL invocation object
A callable object injected into the constructor. Those object was made in specified sql with @Named binding.
class Todo { /** * @var callable */ private $createTodo; /** * @var callable */ private $todo; /** * @Named("createTodo=todo_insert, todo=todo_item_by_id") */ public function __construct( callable $createTodo, callable $todo ){ $this->createTodo = $createTodo; $this->todo = $todo; } public function get(string $uuid) { return ($this->todo)(['id' => $uuid]); } public function create(string $uuid, string $title) { ($this->createTodo)([ 'id' => $uuid, 'title' => $title ]); } }
Row or RowList
You can specify expected return value type is either Row or RowList with RowInterface or RowListInterface.
RowInterface is handy to specify SQL which return single row.
use Ray\Query\RowInterface; class Todo { /** * @Named("todo_item_by_id") */ public function __construct(RowInterface $todo) { $this->todo = $todo; } public function get(string $uuid) { $todo = ($this->todo)(['id' => $uuid]); // single row data } }
use Ray\Query\RowListInterface; class Todos { /** * @Named("todos") */ public function __construct(RowListInterface $todos) { $this->todos = $todos; } public function get(string $uuid) { $todos = ($this->todos)(); // multiple row data } }
Override the method with callable object
Entire method invocation can be override with callable object in specified with @Query.
class Foo { /** * @Query(id="todo_item_by_id") */ public function get(string $id) { } }
When parameter name is different method arguments and Query object arguments, uri_template style expression can solve it.
class FooTempalted { /** * @Query(id="todo_item_by_id?id={a}", templated=true) */ public function get(string $a) { } }
Specify type='row' when single row result is expected to return.
class FooRow { /** * @Query(id="ticket_item_by_id", type="row") */ public function onGet(string $id) : ResourceObject { } }
If there is no SELECT result, it returns 404 Not Found.
Convert URI to Web request object
With WebQueryModule, it converts the URI bound in the configuration into an invocation object for web access and injects it.
In the following example, an invocation object of $createTodo which makes POST request to https://httpbin.org/todo is injected as $createTodo.
use Ray\Di\AbstractModule; use Ray\Query\SqlQueryModule; class AppModule extends AbstractModule { protected function configure() { // WebQueryModuleインストール $webQueryConfig = [ 'todo_post' => ['POST', 'https://httpbin.org/todo'], 'todo_get' => ['GET', 'https://httpbin.org/todo'] ]; $guzzleConfig = []; $this->install(new WebQueryModule($webQueryConfig, $guzzleConfig)); } }
The usage code is the same as for SqlQueryModule.
/** * @Named("createTodo=todo_post, todo=todo_get") */ public function __construct( callable $createTodo, callable $todo ){ $this->createTodo = $createTodo; $this->todo = $todo; }
// POST ($this->createTodo)([ 'id' => $uuid, 'title' => $title ]); // GET ($this->todo)(['id' => $uuid]);
The usage code of @Query does not change either.
Bind to PHP class
If other dependencies are needed, we bind to PHP class and use dependency as a service.
class CreateTodo implements QueryInterface { private $pdo; private $builder; public function __construct(PdoInterface $pdo, QueryBuilderInferface $builder) { $this->pdo = $pdo; $this->builder = $builder; } public function __invoke(array $query) { // Query execution using $pdo and $builder return $result; } }
Bind to callable.
$this->bind('')->annotatedWith('cretate_todo')->to(CreateTodo::class); // callableはインターフェイスなし
The usage codes are the same. The usage code of @Query does not change either.
ISO8601 DateTime Module
Convert the specified column name value to the ISO8601 format. In PHP, it is a format defined by constants of DateTime::ATOM.
Install date column names as an array and pass it as an argument to Iso8601FormatModule.
$this->install(new Iso8601FormatModule(['created_at', 'updated_at']));
SQL file name log
The SQL file name can be appended to the SQL statement as a comment. This is useful for query logging.
use Ray\Query\SqlFileName; use Ray\Query\SqlQueryModule; $this->install(new SqlQueryModule(__DIR__ . '/Fake/sql', null, new SqlFileName()));
Execute SQL
/* todo_item_by_id.sql */ SELECT * FROM todo WHERE id = :id
Demo
php demo/run.php