恥知らずのウェブエンジニア -web engineer, shameless

これは一歩を踏み出すことができない者たちのブログ

Set up gRPC server with Go and try requesting with PHP

Hi guys. In this time, I will set up the gRPC server and try requesting it.

By the way, it seems that the gRPC server with PHP is spoken by ↓, but it seems that is no plan now.(2018 / February) groups.google.com

So this time, I will set up a gRPC server with Go and I will try requesting it with PHP.

Flow

  1. Define proto
  2. Generate code for Go
  3. Create a Go gRPC server with generated code
  4. Generate code for PHP
  5. Create a PHP client with the generated code
  6. Set up a gRPC server and request it with PHP client

Define proto

Prepare the following proto.Simply to accept the string and just return it.

syntax = "proto3";

package ping;

service Ping {
    rpc Hello (HelloReqest) returns (HelloResponse) {
    }
}

message HelloReqest {
    string toMessage = 1;
}

message HelloResponse {
    string resMessage = 1;
}

Generate code for Go

The official is here

grpc.io

What is necessary for generation is as follows.

- google.golang.org/grpc
- github.com/golang/protobuf/protoc-gen-go // plugin for generating gRPC go code from proto
- protoc // protoc compiler

google.golang.org / grpc,protoc-gen-go you can go get

go get -u google.golang.org/grpc
go get -u github.com/golang/protobuf/protoc-gen-go

You can download protoc that fitting your platform from below, And place it in /bin or the like. So I use mac, I've downloaded protoc-3.5.1-osx-x86_64.zip.

github.com

The code generation command for Go is as follows

// For  protos/*.proto, The code generated for gRPC is under. /go 
protoc - go_out = plugins = grpc: ./ go protos / *. proto

The actual generated code will look like this.

come-on-proto/ping.pb.go at master · ogataka50/come-on-proto · GitHub

Create a Go gRPC server with generated code

I prepare a gRPC server with Go based on the generated code.

This time I've taken the following article quite nicely and I just made a reference as it is! Thank you! ^^

developers.eure.jp

The actual code is here

github.com

Generate code for PHP

Next, I will generate code for PHP for requesting the gRPC server.

grpc.io

What we need to generate is below

- grpc-extension
- protobuf-extension
- protoc // same as installing it at the time of Go
- grpc_php_plugin // plugin for generating gRPC PHP code from proto

grpc-extension,protobuf-extension can be installed with pecl, brew etc.

brew install php71-grpc
brew install php71-protobuf

pecl install grpc
pecl install protobuf

grpc_php_plugin can be created by cloning the repo of grpc and make grpc_php_plugin

git clone -b v1.9.x https://github.com/grpc/grpc
cd grpc && git submodule update --init && make grpc_php_plugin

The code generation command for PHP is as follows

// For  protos/*.proto, The code generated for gRPC is under. /php 
protoc --proto_path = protos / \
    - php_out = php \
    --grpc_out = php \
    --plugin = protoc-gen-grpc =. / grpc_php_plugin \
    ./protos /*.proto

The actual generated code will look like this.

come-on-proto/PingClient.php at master · ogataka50/come-on-proto · GitHub

Create a PHP client with the generated code

Next, we will create a PHP code to request using generated code.

Almost all of the following are just new request() and set to generatedclient.

This time we specified wait () to do UnaryCall (once requesting and receiving a response once).

github.com

Set up a gRPC server and request it with PHP client

Preparation is done, so I will actually check it.

Build GogRPC Server and run

go build -o bin/server server.go
go build -o bin/client client.go
- you can use `make build` too.

./bin/server

Request with PHP client

Go back to gRPC-client-PHP and request it withphp ./bin/console helloGRPC.

$ php ./bin/console helloGRPC

object (stdClass) # 52 (3) {
  ["metadata"] =>
  array (0) {
  }
  ["code"] =>
  int (0)
  ["details"] =>
  string (0) ""
}
string (24) "I hear hogeeeeeeeeeeeeee"

If you can successfully request, it should be output as above. It seems like it is done!

Summary

In this time, I set up a gRPC server with Go and requested it with PHP.

At first, there were some confused things, but I was able to check simple operation.

Next time, I'd like to try Streaming RPC etc. I also care about performance. I can use in multiple languages based on common proto, I realized that micro-services certainly seems to be compatible! ^^

The repo used this time will be below. thanks.

github.com

github.com

github.com

なぜPHPはgRPCサーバーがサポートされていないのか?

なんかもうアレなので、前回少し触れたのですが、2018/2月現在公式なPHPのgRPCサーバーはサポートされていません。

GoでgRPCサーバー立てて、PHPでリクエストしてみる - 恥知らずのウェブエンジニア -web engineer, shameless

様々な迫害には慣れているPHPerでもそもそもサポートされないというのは辛いものがあります。

なぜPHPのgRPCサーバーがサポートされていなのか下記のMLのdiscussionを英語の勉強も兼ねてまとめてみようと思います。

※素人意訳なので、間違いありましたらごめんなさい。

groups.google.com

gRPC Servers in PHP?

PHPのgRPCクライアントは作れるけど、gRPCサーバーは作れません。それの技術的ブロッカー、将来的な実装のロードマップはあります?

 

PHPのgRPCサーバーにはいくつかの問題があります。そして今の所、解決策はありません。

nginx, Apacheのフロントエンドなしで素のPHPを実行している場合解決策がありません。典型的な設定では、PHPでlong-lived streaming RPCをすることができません。

ページはすぐにtimeoutしてしまいます。unary RPCを提供することはできます。または制限時間を持つstreaming RPCを提供できますが、それはもはやgRPCではありません。

そして現時点ではPHPには適切なHTTP/2サポートはありません。

フロントエンドサーバーからPHPプロセスへリクエストを転送するモデルでは、gRPCリクエストを処理するために必要なHTTP/2 streamへのアクセスができません。

これ以上のことについては、PHPからweb-socketを提供することをおすすめします。おそらくすべての解決策はapacheなどなしの素のPHPを実行することになると思われます。 これはPHPを使いたい典型的な構成ではありません。したがって、PHPのgRPCサーバーは特殊な構成になるため、あまり役に立たないでしょう。

 

下記のようにphp-cliを使ってweb-serverを動かすことができます PHP: Built-in web server - Manual

 

ドキュメントの冒頭に、「これは開発のテスト、デモ用でパブリックネットワーク上で使用しないでください。」と書いてありますけど

 

良い方法を見つけることにも非常に興味があります。他の言語はこれをどのように処理していますか?Pythonに関しても同じ問題を抱えているのでは。 gRPCは素のPythonプロセスだけをサポートしていますか?

 

この例を見てみると grpc/route_guide_server.py at release-0_13 · grpc/grpc · GitHub PHPも同じ種類のソケットリスニング操作を実行することができます。 このようなデーモン化されたPHPプロセスを書くことは一般的です。もちろん、PHPがHTTP / 2の機能をネイティブにサポートするかどうかは別の問題として。。

 

他の言語(Ruby、Node.js、Python、C#、...)にはそのような問題がないのは、それらがすべてコマンドラインから適切に動作しているフレームワークを持っているからです。

gRPC pythonサーバーを動かすためのpythonデーモンを適切に起動することができます。

nodeにはフロントエンドなしで稼働できる組み込みの機能があります。 Rubyは長い間ネイティブでこれもやっています。

PHPのみがデーモンを実行するための適切なサポートを持っていません。オプションとしてあるのは、本番環境に適していないプロトタイプです。

 

ReactPHPのようなもので回避できるかも? Event-driven, non-blocking I/O with PHP - ReactPHP

 

素のPHPプロセスとしてサーバーを実行することはスケーラビリティがなく、PHPコミュニティは推奨していなく、PHP Web Hosting Serviceでほとんどサポートされていません。

 

私はReactPHPを掘り下げ、必要なHTTP / 2をサポートするかどうか見てみる

 

ReactPHPは、gRPCのC coreがすでに行っていること以上のものを提供しません。coreは非同期I / OとHTTP / 2を処理します。

PHPをgRPCサーバーとしてサポートすることは、third partyの依存を必要とすることなく、既に持っているものの周りにコードを書くことを意味します。

しかし、それには素のPHPが必要であり、私たちが見るに公式のPHPサーバのサポートは、LAMPのほかにPHPに対する大きな需要を見たことがないと思っています。 そのモデルをサポートするためのリソースをコミットする必要があります。

 

正しい解決策は、SAPIとして"php-fpm"と同じような "php-grpc server"があればと思う。 それはHTTP / 2ウェブサーバを起動し、$ _SERVER / $ _ POSTの適切な変数を使用してserverへの個々のPHPリクエストを作成します。

これは、CのgRPCサーバーがlong-lived streamingを処理できるようにする一方で、PHPの原理を破ることなく維持します。 しかしこれは複雑になるかもしれません。

現状の回避策は、Go FastCgiクライアントライブラリを使用して、gRPC / HTTP2上でリクエストを受け入れ、それらをphp-fpmベースのバックエンドに送信する単純なGoベースのプロキシを書くことです。

 

我々が想像している2つの主な動機は比較的共通している。 1つは、私たちのPHPベースのREST + JSON APIは、protobufやThriftのように見えるようになっているinternal pseudo-type system(内部擬似型システム)を長年にわたって成長させてきました。

2つ目はObjective-CAndroidPHP、およびJavaScriptAPIクライアントを使用し、これらのクライアントの開発を容易にするためのコード生成です。 我々はgRPC + Protobufがそれらの解決策であると見ています。

ほとんどの人がApacheやnginixでPHPを使用しているので、素のPHPサーバーをサポートすることに抵抗があることを理解しています。 しかし私たちはunary RPC ができることに興奮しています。 おそらく、PHPApache / nginixがHTTP / 2サポートを進化させると、gRPC PHPサーバーのサポートも進化する可能性があります。

 

PHPサーバーの問題と使用例を深く理解し、合理的な解決策を考案したいと考えています。

 

我々はgRPCからFastCGIPHP-FPM)にブリッジする形でPHPサーバのサポートを開発しました。興味があるなら、それをオープンソースにして、関わっているコミュニティのメンバーをさらに増やしたい。

 

素晴らしい! 間違いなくオープンソースにするべきです。 PHPのgRPCサーバーは必須です - マイクロサービスの時代にはすべてのPHPLAMPで実行されているわけではありませんから。

 

PHPサーバソリューションにも非常に興味があります。 PHPのマイクロサービスプラットフォームは、PHPが "嫌われ者の言語"(red headed step child of a language)でなくなるのを助けるだろう。 :-)

 

アップデート - 私たちはオープンソーシングをクリアし、独自のビットを抽出するよう努めています。できるだけ早くgRPCエコシステムへ組み込まれるよう評価するものを得るでしょう。

 

多くのユーザーが興味を持っているので、gRPCエコシステムへの登場を楽しみにしています

 

その後進捗どうですか???

まとめ

なんとなく背景がわかった気がします。

  • そもそもPHPは、素の(nakedな)PHPの状態でデーモン化して実行がすることができない
    • built-in serverがあるけどあくまでテスト用で本番環境では使えない
    • 一般的な設定ではすぐtime outするため、Streaming RPCがまともに使えない
    • フロントエンド(apache, nginx)を置いてしまうとHTTP/2 streamへのアクセスができない。それってもはやgRPCじゃなくない?
  • そのためthird partyなしでのPHPでのgRPCサーバーのサポートはできない
  • php-fpmのようなphp-grpc-serverが追加されるとても幸せ

  • PHPデベロッパーにとってフロントエンド(apache, nginx)があることは一般的になっている

    • 素のPHPだけのサーバーの方が違和感
  • そしてPHPデベロッパーとして有り難いのは、共通のprotoを使うことで開発が簡略化できること。unary RPCだけでもありがたい
  • Apache,nginixのHTTP / 2サポートが進むとまた状況が変わるもしれない
  • gRPCからphp-fpmへブリッジするlibraryの開発もされている(が開発者の返信が2016年末で止まっている…)

gRPCとしては、

  • PHPの言語のみではgRPCの全機能が提供できなく、中途半端なぐらいになるぐらいなら公式サポートはできない

PHP開発者としては、

  • 開発者側としてはコードの自動生成、unary RPCだけでも利点がある(しかしそれがgRPCであるべきなのかと言われるとアレな感じも。。。)

のような状況のようです。 また関連してnginx が gRPC proxy supportの実装を進めているようです(中身よくわかってない) これによって状況がまた変わるかも? Milestone 1.13 – nginx

今後も動きがありそうなので引き続きウォッチ。

GoでgRPCサーバー立てて、PHPでリクエストしてみる

なんかもうアレなので前回PHPでgRPCサーバーにリクエストするっぽいことをやったので、今回は実際にgRPCサーバーを立てて、リクエストをしてみる。

PHPでgRPC叩く手始め - 恥知らずのウェブエンジニア -web engineer, shameless

ちなみにPHPでgRPCサーバーは↓で話されているようなのですが、2018/2月現在では実装の目処は立っていない模様。 groups.google.com

なので今回は,GoでgRPCサーバーを立てて、それにPHPからリクエストをするようにしてみようと思います。

流れ

  1. protoを定義
  2. Go用のコードを生成
  3. 生成されたコードからGoでgRPCサーバーを立てる
  4. PHP用のコードを生成
  5. 生成されたコードからPHPクライントを作る
  6. gRPCサーバーを立てて、PHPクライントリクエストしてみる

protoを定義

下記のprotoを用意。とりあえず単純に文字列を受けて、返すだけです

syntax = "proto3";

package ping;

service Ping {
    rpc Hello (HelloReqest) returns (HelloResponse) {
    }
}

message HelloReqest {
    string toMessage = 1;
}

message HelloResponse {
    string resMessage = 1;
}

Go用のコードを生成

ますはGoから。公式はこちら

grpc.io

生成に必要なのは、下記です。

- google.golang.org/grpc
- github.com/golang/protobuf/protoc-gen-go //protoからgRPCのgoのコードを生成するためのplugin
- protoc //protoc compiler

google.golang.org/grpc, protoc-gen-go は普通にgo get します

go get -u google.golang.org/grpc
go get -u github.com/golang/protobuf/protoc-gen-go

protocは各platformにあったものを下記からDLし、bin以下などにおきます。 自分はmacなのでprotoc-3.5.1-osx-x86_64.zipをDLしました。

github.com

Go用のコードの生成コマンドは下記になります

// protos/*.protoを対象にして、./go以下に生成されたgo用のgRPCを使ったコードが生成されます
protoc --go_out=plugins=grpc:./go protos/*.proto

実際に生成されコードはこちらのようになります。

come-on-proto/ping.pb.go at master · ogataka50/come-on-proto · GitHub

生成されたコードからGoでgRPCサーバーを立てる

これらもとにGoでgRPCサーバーを用意します。

今回は下記の記事をかなり色濃くほぼそのまま参考にさせて頂きました!ありがとうございます!m( )m

developers.eure.jp

実際のコードはこちら

github.com

PHP用のコードを生成

次はgRPCサーバーを叩く用のPHP用のコードを生成します。

grpc.io

生成に必要なのは、下記です

- grpc-extension
- protobuf-extension
- protoc // 先ほどGoの時にinstallしたのと同じ
- grpc_php_plugin //protoからgRPCのPHPのコードを生成するためのplugin

grpc-extension, protobuf-extensionpecl, brewなどでinstallできます。

brew install php71-grpc
brew install php71-protobuf

pecl install grpc
pecl install protobuf

grpc_php_pluginは、grpcのrepoをcloneして、make grpc_php_pluginすることでつくることができます

git clone -b v1.9.x https://github.com/grpc/grpc
cd grpc && git submodule update --init && make grpc_php_plugin

PHP用のコードの生成コマンドは下記になります

// protos/*.protoを対象にして、./php以下に生成されたPHP用のgRPCを使ったコードが生成されます
protoc --proto_path=protos/ \
    --php_out=php \
    --grpc_out=php \
    --plugin=protoc-gen-grpc=./grpc_php_plugin \
    ./protos/*.proto

実際に生成されコードはこちらのようになります。

come-on-proto/PingClient.php at master · ogataka50/come-on-proto · GitHub

生成されたコードからPHPクライントを作る

次に生成されたコードを使ってリクエストを行うPHPコードをつくります。

下記がほぼすべてで、生成されたclientrequestをsetしてリクエストしているだけです。

今回はUnaryCall(1回リクエストして、1回レスポンスを受け取る)を行うwait()を指定しています。

github.com

gRPCサーバーを立てて、PHPクライントでリクエストしてみる

準備は整ったので、実際に動かしていきます。

Goサーバーのビルド、実行

go build -o bin/server server.go
go build -o bin/client client.go // goでgRPCにリクエストする用。今回はスルー。
※make build でもできるようにしています

./bin/server

でGoのgRPCサーバーを立てます

PHPクライントでリクエストしてみる

さっそくリクエストしてます。gRPC-client-PHPに戻って php ./bin/console helloGRPCでリクエストできます。

$ php ./bin/console helloGRPC

object(stdClass)#52 (3) {
  ["metadata"]=>
  array(0) {
  }
  ["code"]=>
  int(0)
  ["details"]=>
  string(0) ""
}
string(24) "I hear hogeeeeeeeeeeeeee"

正常にリクエストできてば、上記のような出力になるはずです。 それっぽくできているようです!

まとめ

今回は実際にGoでgRPCサーバーを立てて、PHPでリクエストしてみました。

最初色々戸惑いところもありましたが、簡単な動作確認することができました。

次は、Streaming RPCなども試したいところ。パフォーマンスなども気になる。 共通のprotoを元に複数言語で利用できるところなどはmicro servicesとは確かに相性良さそうなことは実感できました!^^

また今回つかったrepoは下記になります。

github.com

github.com

github.com

PHPでgRPC叩く手始め

なんかもうアレなので、PHPでgRPCのサーバーを叩いてみる。まずは環境整備から

  • 流れ的にはprotobufを定義して、それを元にPHPからgRPCと通信するclientを作ってそれを使ってgPRCを叩く模様

Environment

grpc

# peclが必要
yum install --enablerepo=remi-php71 php-pear gcc-c++ make zlib-devel php-devel

pecl install grpc
echo 'extension=grpc.so' >> /etc/php.ini

参考: https://github.com/grpc/grpc/issues/11957 https://github.com/grpc/grpc/issues/11057

protobuff

pecl install protobuf
echo 'extension=protobuf.so' >> /etc/php.ini    

# 現時点の最新版protobuf-3.5.0.1は下記のエラーが出るのでv3.4を使う。。。
PHP Warning:  PHP Startup: Unable to load dynamic library '/usr/lib64/php/modules/protobuf.so' - /usr/lib64/php/modules/protobuf.so: undefined symbol: timelib_update_ts in Unknown on line 0

See : https://github.com/google/protobuf/issues/3929

# 一旦protobuf-3.4.0 install
pecl uninstall protobuf
pecl install protobuf-3.4.0

# protobufのコンパイラは下記から
https://github.com/google/protobuf/releases/

grpc用のprotobuf plugin

#repo clone
git clone --recursive -b v1.7.x https://github.com/grpc/grpc

# 公式には書いてないけどこれやらないと下のmakeができないっぽい
cd grpc/third_party/protobuf
./autogen.sh

cd grpc
make grpc_php_plugin

※上手くいかない時は下記が足りていないかも
libtool
shtool
autoconf
autogen

サンプルproto

syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
  rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}
  • phpのコード生成
protoc --proto_path=examples/protos   --php_out=examples/php   --grpc_out=examples/php   --plugin=protoc-gen-grpc=bins/opt/grpc_php_plugin   ./examples/protos/helloworld.proto
  • sampleを実行してみる
require dirname(__FILE__).'/vendor/autoload.php';

@include_once dirname(__FILE__).'/Helloworld/GreeterClient.php';
@include_once dirname(__FILE__).'/Helloworld/HelloReply.php';
@include_once dirname(__FILE__).'/Helloworld/HelloRequest.php';
@include_once dirname(__FILE__).'/GPBMetadata/Helloworld.php';

function greet($name)
{
    $client = new Helloworld\GreeterClient('localhost:50051', [
        'credentials' => Grpc\ChannelCredentials::createInsecure(),
    ]);

    $request = new Helloworld\HelloRequest();
    $request->setName($name);
    list($reply, $status) = $client->SayHello($request)->wait();
var_dump($reply);
var_dump($status);

    return $message;
}

$name = !empty($argv[1]) ? $argv[1] : 'world';
echo greet($name)."\n";


[root@6b0b0fc608dc php]# php greeter_client.php wow
/var/workspace/grpc/examples/php/greeter_client.php:42:
NULL
/var/workspace/grpc/examples/php/greeter_client.php:43:
class stdClass#12 (3) {
  public $metadata =>
  array(0) {
  }
  public $code =>
  int(14)
  public $details =>
  string(14) "Connect Failed"
}
PHP Fatal error:  Uncaught Error: Call to a member function getMessage() on null in /var/workspace/grpc/examples/php/greeter_client.php:44
Stack trace:
#0 /var/workspace/grpc/examples/php/greeter_client.php(53): greet('wow')
#1 {main}
  thrown in /var/workspace/grpc/examples/php/greeter_client.php on line 44

それっぽく動いてる? ※grpc serverはnodeで動くので立ち上げていない

次は実際にgRPCサーバーを立てて動作確認

Phanで静的解析するようにしてみる

なんかもうアレなので業務でとあるAPIを叩く用の簡単なSDKをを作った。 その際PHPDoc,型宣言とかしたので、せっかくなのでPhanで静的解析するようにしてみたメモ。 PhanはPHPDoc,型宣言などが整備されてないと真価を発揮しないということなので。

install

  • ast-extが必要
pecl install ast
  • phan install
composer require --dev phan/phan

config

<?php
return [
//解析、パース対象dir
    'directory_list' => [
        'src',
        'vendor/' 
    ],
//解析除外対象dir
    "exclude_analysis_directory_list" => [
        'vendor/'
    ],
];

実行

./vendor/bin/phan

src/xxxxRequest.php:16 PhanUndeclaredTypeProperty Property xxxxx\AbstractRequest::request has undeclared type \xxxxx\Message
src/xxxxPoint.php:13 PhanUndeclaredClassConstant Reference to constant class from undeclared class \xxxxx\Request

こんな感じで解析結果がでーる

まだそこまでありがたみを感じていないがしばらく様子見してみる

Tips

PhanUndeclared~が出る

  • 未定義〜を呼んでいるとかとか
  • Phanはautoloadを行わない。なのでコード内でcomposerで入れたクラスなどを参照している場合、それらも解析対象にしなければいけない
  • Issue-Types-Caught-by-Phan
  • https://github.com/phan/phan/wiki/Issue-Types-Caught-by-Phan
  • ./phan/config.phpに↓のようにしてvendor以下もパース対象にしつつ解析除外にする
    'directory_list' => [
        'src',
        'vendor/' 
    ],
    "exclude_analysis_directory_list" => [
        'vendor/'
    ],

dead code検出

  • 実行時optionで、-x, --dead-code-detectionを付けると未使用の関数、変数等を検出してくれる
    ./vendor/bin/phan --dead-code-detection

    src/Client.php:38 PhanUnreferencedPrivateMethod Possibly zero references to 
    private method \xxxxx\Client::aaa

pluginを書けば独自ルールで解析を行うこともできるっぽい

wifi無線ルーターをTP-Link Archer C7に買い換えて快適になった

以前は5、6年前に買ったバッファローの無線ルーターを使っていて、特に大きな不便もなかったのですが、amazon prime,netflixを見たり、アプリダウンロードが遅かったりでどうにかならないものかなと考えていました。

でもどれが良いかよくわかんないしなーっと思っていたところ↓のpodcastTP-Link Archer C7がいけてると紹介でありました。

cloudinfra.audio

https://www.amazon.co.jp/gp/product/B071D6H9NK

podcastの内容自体もすごく面白かったのですが、
box社のセキュリティがすごいというくだりでboxが使っているソフトウェアなら信用できるという話があり、それに習って情シスの人が言っているならと思い買ってみました。
ちょうどその時タイムセールにもなっており…

f:id:ogataka50:20170611162502j:plain

設定自体は専用のアプリを使うとすぐできました。
ブラウジング、動画の再生、ダウンロードなどなど体感的にわかるぐらい速くなりました!

というわけでよくわかっていないものに関してまずは専門家に従っていこうかとw

PSR-6(Caching Interface)を見ていく

要約ですので、所々省いている箇所あります。

オリジナルはこちら

www.php-fig.org

導入

  • キャッシングはパフォーマンス向上の一般的な手法です。キャッシングライブラリを実装することはフレームワーク、ライブラリの一般機能の1つです
  • これは複数のライブラリがそれぞれ独自のレベルの機能をもったキャッシングライブラリを持つ状態を招きます。これらの差異によって、開発者に必要/不必要に関係なく複数システムを学ばなければいけません
  • 加えて、キャッシングライブラリの開発者は、限られたフレームワークのみサポートするか、多数のアダプタクラスを実装するかの選択に迫られます。

  • キャッシングシステムの共通インターフェースはそれらの問題を解決します。ライブラリ、フレームワーク開発者は、希望する動作のライブラリを利用できます。

  • キャッシングライブラリ開発者は1つのインターフェースを実装するだけでよい。

ゴール

  • このPSRのゴールは、開発者がカスタマイズの必要なく、既存のフレームワーク、システムにキャッシングライブラリを統合できるようにすることです

定義

ライブラリ呼び出し

  • ライブラリ、コードはキャッシュサービスを必要とします。
  • ライブラリはこの標準インターフェースを実装することでキャッシングサービスを利用することができますが、キャッシングサービスの実装知識は必要ありません

ライブラリの実装

  • こにライブラリは、どんなライブラリにもキャッシングサービスを提供するためこの標準を実装する責任がある
  • 実装ライブラリは、Cache\CacheItemPoolInterfaceCache\CacheItemInterfaceを実装するクラスを提供しなければいけない
  • 実装ライブラリは、下記に記載する最小TTLを秒単位でサポートしなければならない

TTL

  • キャッシュアイテムの生存時間(TTL - The Time To Live)はキャッシュされてから失効するまで時間のこと
  • TTLは通常、intの秒数またはDateIntervalで定義する

有効期限

  • キャッシュアイテムが失効する時の時刻
  • これはキャッシュされた時刻にTTLを足すことで計算されるが、明示的にDateTimeオブジェクトで定義してもよい
  • 実装ライブラリは有効期限前にキャッシュを失効してもよい
  • もし有効期限が指定されない場合は、デフォルト値を使っても良い。デフォルト値が設定されていな場合は、無期限にキャッシュしなければならない

キャッシュキー

  • キャッシュされたアイテムを一意に識別する1文字以上の文字列
  • 実装ライブラリは、UTF-8エンコーディングで最大64文字のA-Z, a-z, 0-9, _, .で構成されるキーをサポートしなければならない
  • 実装ライブラリは追加で、その他の文字列/エンコーディング/文字列長をサポートしてもよいが、少なくとも最小限はサポートしなければいけない
  • ライブラリはキーストリングスを適切なエスケープをする責任がありますが、オリジナルの未変更のキーストリングスを返せるなければいけません
  • {}()/\@:これらの文字列は将来の仕様変更に備えて予約されていので、サポートしてはいけない

Hit

  • 呼び出すライブラリからのキーでアイテムをリクエストされ、キーにマッチする値が見つかり、有効期限ではなく、なんらかの理由で失効になっていない時にキャッシュヒットが起こります。
  • 呼び出すライブラリは、すべてのget()コールで、isHit()を使って確認すべきです

Miss

  • キャッシュミスはキャッシュヒットの逆
  • リクエストされたキーにマッチするものがない、有効期限が切れている、なんらかの理由で無効になっている時はキャッシュミス
  • 有効期限切れの値はキャッシュミスにならなければいけない

Deferred

  • 遅延キャッシュは、アイテムがプールによってすぐに永続化されないことを意味します
  • プールオブジェクトはいくつかのストレージエンジンでサポートされているバルクセットを利用するためキャッシュを遅延してもよい
  • プールは遅延されたアイテムを最終的に永続化し、ロストさせてはいけない。そして呼び出すライブラリが永続化リクエスト前に保持してもよい
  • 呼び出すライブラリがcommit()を読んだ時はすべての遅延キャッシュを永続化させなければいけない
  • 実装ライブラリは、save()タイムアウト、max-itemsチェックやその他適切なロジックで遅延アイテムを永続化してよい
  • 遅延キャッシュされたアイテムがリクエストされた時は、永続化されていなくとも遅延されたアイテムを返却しないといけない

Data

  • 実装ライブラリはシリアライズできるPHPデータすべてをサポートしないといけない

    • Strings
    • Integers
    • Floats
    • Boolean
    • Null
    • Arrays
    • Object
  • 全てのデータは実装ライブラリに渡された形のまま、型も含め返却されなければいけない

  • 実装ライブラリは、内部的にPHPserialize()/unserialize()を使って良い、これは必須ではない
  • なんらかの理由で正確な値が返却できない場合、実装ライブラリは破損したデータを返すより、キャッシュミスを返さなければいけない

Key Concepts

Pool

  • プールはキャッシュシステム内のアイテムの集合を表す。プールはすべてのアイテムが含まれる論理的なリポジトリ
  • すべてのキャッシュ可能なアイテムはプールからアイテムオブジェクトとして検索され、すべてのキャッシュオブジェクトはプールを介して対話する

Items

  • アイテムはプール内の単一のkey/valueのペアを表す。keyはアイテムのユニークな識別子で、不変でなければいけない。値はいつでも変更あってもよい

Error handling

  • キャッシングはパフォーマンスにとって重要ですが、アプリケーション機能の重要な部分ではありません。なので、キャッシュシステムのエラーはアプリケーションのエラーになるべきではありません
  • 実装ライブラリは、インターフェースで定義された以上のexceptionをthrowしてはいけません。そしてキャッシュシステムでのエラーをトラップして上げるべきではありません

  • 実装ライブラリはそのようなログはエラーをログに残すか管理者に報告すべきです

  • もし呼び出しライブラリが1つかそれ以上の削除済みか、プールがクリアさてれいるアイテムをリクエストされ、キーが存在しない場合でもエラーとしてはいけません

Interfaces

CacheItemInterface

  • CacheItemInterfaceはキャッシュシステム内のアイテムを定義
  • 各アイテムオブジェクトは特定のキーに関連付けられていなければなりません。通常はCache\CacheItemPoolInterfaceオブジェクトによって渡されます。
  • Cache\CacheItemPoolInterfaceはキャッシュアイテムの格納、検索をカプセル化します
  • どのCache\CacheItemInterfaceCache\CacheItemPoolInterfaceから生成され、必要なセットアップとユニークキーとオブジェクトを関連付けます
  • Cache\CacheItemInterfaceはどのデータ型でも格納、検索できなければいけません

  • 呼び出しライブラリは自身で、アイテムオブジェクトを生成してはいけません

  • プールオブジェクトのgetItem()メソッドからリクエストされるべきです
  • 呼び出しライブラリはあるライブラリから生成されたアイテムオブジェクトが他のライブラリと互換性があると想定すべきではありません

CacheItemPoolInterface

  • Cache\CacheItemPoolInterfaceの優先目的は呼び出しライブラリからキーを受け付け、Cache\CacheItemInterfaceオブジェクトを返却することです
  • キャッシュコレクションと対話することも重要ポイント
  • プールの設定、初期化は実装ライブラリに任されます

CacheItemInterface

<?php

namespace Psr\Cache;

/**
 * CacheItemInterface defines an interface for interacting with objects inside a cache.
 */
interface CacheItemInterface
{
    /**
     * Returns the key for the current cache item.
     */
    public function getKey();

    /**
     * Retrieves the value of the item from the cache associated with this object's key.
     */
    public function get();

    /**
     * Confirms if the cache item lookup resulted in a cache hit.
     */
    public function isHit();

    /**
     * Sets the value represented by this cache item.
     */
    public function set($value);

    /**
     * Sets the expiration time for this cache item.
     */
    public function expiresAt($expiration);

    /**
     * Sets the expiration time for this cache item.
     */
    public function expiresAfter($time);
}

CacheItemPoolInterface

<?php

namespace Psr\Cache;

/**
 * CacheItemPoolInterface generates CacheItemInterface objects.
 */
interface CacheItemPoolInterface
{
    /**
     * Returns a Cache Item representing the specified key.
     */
    public function getItem($key);

    /**
     * Returns a traversable set of cache items.
     */
    public function getItems(array $keys = array());

    /**
     * Confirms if the cache contains specified cache item.
     */
    public function hasItem($key);

    /**
     * Deletes all items in the pool.
     */
    public function clear();

    /**
     * Removes the item from the pool.
     */
    public function deleteItem($key);

    /**
     * Removes multiple items from the pool.
     */
    public function deleteItems(array $keys);

    /**
     * Persists a cache item immediately.
     */
    public function save(CacheItemInterface $item);

    /**
     * Sets a cache item to be persisted later.
     */
    public function saveDeferred(CacheItemInterface $item);

    /**
     * Persists any deferred cache items.
     */
    public function commit();
}

パフォーマンスアップには、各種キャッシュが欠かせません。

しかし、そのキャッシュの処理がライブラリごとにバラバラだと何かと面倒なことが起こります。
また共通化がされていないと、キャッシュに使うサービスもmemcached,redis,fileなどなど切り替えたりするのも面倒です。

そこでインターフェースを合わせましょうっていう流れかと。
目標的にはPSR-3(Logger Interface)と同じような感じですね!

しかしざっと主要なフレームワークを見ると現状は必ずしも実装されているわけではない模様・・・

ぱっと見symfonyぐらいでしょうか。。。

GitHub - symfony/cache: [READ-ONLY] Subtree split of the Symfony Cache Component

単にkey/valで欲しいだけなので、pool -> item obj -> valはちょっと遠回りな印象
これの簡略版がPSR-16(Simple Cache)として提案されています

PSR-16: Common Interface for Caching Libraries - PHP-FIG


次はPSR-7(HTTP Message Interface)へ・・・

PSR-7: HTTP message interfaces - PHP-FIG