GraphQLをより理解する[PHP編]

### Resolver
The resolver is basically a call back function for each field. There is always a default resolver for all fields, when we define own resolve function for a field, we simply override the default resolver.

### How to define Schema for Query

$schema_obj = new Schema {
	"query" => $queryType,
	"mutation" => $mutationType,
	"subscription" => $subscriptionType
}

index.php

require_once __DIR__ . '/vendor/autoload.php';

use GraphQL\GraphQL;
use GraphQL\Type\Schema;
try {
	$schema_obj = new Schema([
		'query' => $queryType
	]);
	$Input = file_get_contents('php://input');
    $input_data = json_decode($Input, true);
    $query_data = $input_data['query'];
    $variable_values = isset($input_data['variables']) ? $input_data['variables'] : null;
    $value = ['prefix' => 'Output: '];
    $resultant = GraphQL::executeQuery($schema_obj, $query_data, $value, null, $variable_values);
    $output_value = $resultant->toArray();
} catch(\Exception $e){
	$output_value = [
		'errors' => [
			[
				'message' => $e->getMessage()
			]
		]
	];
}
header('Content-Type: application/json');
echo json_encode($output_value);

$ curl http://192.168.34.10:8000 -d ‘{“query”: “query { echo(message: \” Hi Knowband, this is my first GraphQL program \”) }” }’
{“errors”:[{“message”:”Schema does not define the required query root type.”,”extensions”:{“category”:”graphql”},”locations”:[{“line”:1,”column”:1}]}]}

うむ… 実践的にやらないとあかんな

PHPでGraphQLを試したい

まずubuntuにcomposerを入れます。

$ curl -sS https://getcomposer.org/installer | php
$ sudo mv composer.phar /usr/local/bin/composer
$ sudo chmod +x /usr/local/bin/composer
$ source ~/.bashrc
$ composer -v
Composer version 2.1.8 2021-09-15 13:55:14

### ライブラリのインストール
$ composer require webonyx/graphql-php

public/graphql/index.php

require_once __DIR__ . '/../../vendor/autoload.php';

use GraphQL\Type\Definition\ObjectType;

class Query extends ObjectType {
	public function __contruct(){
		parent::__construct([
			'name' => 'Query',
			'fields' => [
				'number' => [
					'type' => Type::int(),
					'args' => [
						'number' => Type::int(),
					],
					'resolve' => function($value, $args, $context, ResolveInfo $resolveInfo){
						return $args['number'];
					}
				],
			],
		]);
	}
}

$schema = new GraphQL\Type\Schema([
	'query' => new Query(),
]);

$server = new Graph\Server\StandardServer([
	'schema' => $schema
]);

$ php -S 192.168.34.10:8000
$ curl -X POST -H “Content-Type: application/json” “http://192.168.34.10:8000/graphql/” \
> -d ‘{“query”: “query { number(number: 20210928) }”}’

### Repositoryを使う
src/Type/User/User.php

namespace Hpscript\Type\User;

use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use Hpscript\Repository\UserRepository;
use Hpscript\Type\Definition\DomainType;

class User extends ObjectType {

	private $userRepository;

	public function __construct(){
		$this->userRepository = new UserRepository;
		parent::__construct([
			'name' => 'User',
			'fields' => function(){
				return [
					'id' => [
						'type' => Type::int(),
						'resolve' => function($id){
							return $id;
						},
					],
					'name' => [
						'type' => Type::string(),
						'resolve' => function($id){
							return $this->getUser($id)->getName();
						}
					],
					'profile' => [
						'type' => Type::string(),
						'resolve' => function($id){
							return $this->getUser($id)->getProfile();
						}
					],
					'address' => [
						'type' => Type::string(),
						'resolve' => function($id){
							return $this->getUser($id)->getName();
						}
					],
				];
			},
		]);
	}
}

private function getUser($id){
	return $this->userRepository->getUser($id);
}

src/Type/Definition/DomainType.php

namespace Hpscript\Type\Definition;

use Hpscript\Type\User\User;

class DomainType {

	private static $user;

	public static function user(){
		if(!isset(static::$user)){
			static::$user = new User();
		}
		return static::$user;
	}
}

src/Repository/UserRepository.php

namespace Hpscript\Repository;

class UserRepository {

	private $dummyData;

	public function __construct(){
		$sakamoto = new User(1, 'Sakamoto', '坂本太郎', '東京都');
		$sato = new User(2, 'Sato', '佐藤和子', '神奈川');
		$tanaka = new User(3, 'Tanaka', '田中はじめ', '愛知県');
		$this->dummyData = [
			1 => $sakamoto,
			2 => $sato,
			3 => $tanaka
		];
	}

	public function getUser($id){
		data_default_timezone_set('Asia/Tokyo');
		error_log(date('Y-m-d H:i:s') . "\t" . __METHOD__ . "\n", 3, "/tmp/php-graphql-sample.log");
		return $this->dummyData[$id];
	}
}

class User {
	private $id;
	private $name;
	private $profile;
	private $address;

	public function __construct($id, $name, $profile, $address){
		$this->id = $id;
		$this->name = $name;
		$this->profile = $profile;
		$this->address = $address;
	}

	public function getId(){
		return $this->id;
	}
	public function getName(){
		return $this->name;
	}
	public function getProfile(){
		return $this->profile;
	}
	public function getAddress(){
		return $this->address;
	}
}

public/grapql/index.php

require_once __DIR__ . '/../../vendor/autoload.php';

use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\ResolveInfo;
use Hpscript\Type\Definition\DomainType;

class Query extends ObjectType {
	public function __contruct(){
		parent::__construct([
			'name' => 'Query',
			'fields' => [
				'user' => [
					'type' => DomainType::user(),
					'args' => [
						'id' => Type::int(),
					],
					'resolve' => function($value, $args, $context, ResolveInfo $resolveInfo){
						return $args['id'];
					}
				],
			],
		]);
	}
}

$schema = new GraphQL\Type\Schema([
	'query' => new Query(),
]);

$server = new Graph\Server\StandardServer([
	'schema' => $schema
]);

$server->handleRequest();

composer.json

{
	"autoload": {
		"psr-4": {
			"Hpscript\\": "src"
		}
	},
    "require": {
        "webonyx/graphql-php": "^14.9"
    }
}

$ curl -X POST -H “Content-Type: application/json” “http://192.168.34.10:8000/graphql/” -d ‘{“query”: “query { user(id: 2){id name} }”}
POST /graphql/ – Uncaught ArgumentCountError: Too few arguments to function GraphQL\Type\Definition\ObjectType::__construct(), 0 passed in /home/vagrant/dev/graphql/public/graphql/index.php on line 30 and exactly 1 expected in /home/vagrant/dev/graphql/vendor/webonyx/graphql-php/src/Type/Definition/ObjectType.php:86
エラーになるな。mutationはいらないから、querytypeだけで良いような気がするんだが。。

[GraphQL]入門

フロントエンドエンジニアの間ではGraphQLはもはや必須スキル?
ということでGraphQLを入門

### GraphQLとは
– APIのためのクエリ言語(Graph Query Language)
– SQLのようなクエリでAPIを定義する
– REST APIとはエンドポイントが異なる(特定の値を取得するために、余分なデータも取得されてしまう、あるいは、1つのエンドポイントでは必要なデータが取得できない といった問題を解決できる 必要なデータだけ要求する)
– 型付スキーマで記述する
– GraphQLのSubscriptionsはWebSocketを使用しているので、簡単にリアルタイム通信ができる
– Client側がGraphQL Query Languageで、Server側がGraphQL Schema Language

注) サーバ側とクライアント側 両方を学ぶ必要がある

### GraphQL Query Language(GET)
Githubの例: https://docs.github.com/en/graphql/overview/explorer

query { 
  viewer { 
    login
  }
}
query { 
  user(login: "hpscript"){
    id
    name
    url
  }
}

おおおお、これは凄い
POST, PUT, DELETEはmutationを使用する

“!”は必須という意味

type User {
	login: String!
	id: String!
	name: String!
	age: Int
	bio: String
	url: String
}

実際に動かしてみないとわからないね

[Python3 x React.js] S3にアップロードしたjsonファイルをリアルタイムで読み込む

pythonでS3にアップロードします
 L S3 full accessのcredentialは別ファイルからimportにします。

import boto3
import json
import credentials

str = {
	"speech":"最高"
}

with open("sample.json", "w") as f:
	json.dump(str, f, ensure_ascii=False)

s3 = boto3.client('s3', aws_access_key_id=credentials.Accesskey, aws_secret_access_key= credentials.Secretkey, region_name=credentials.Region)
 
filename = "sample.json"
bucket_name = "hogehoge"
 
s3.upload_file(filename,bucket_name,filename, ExtraArgs={'ContentType': "application/json",'ACL':'public-read'})
print("upload {0}".format(filename))

React.jsでJsonをsetIntervalで読み込みます

function App(){
			const [data, setData] = React.useState([]);

			React.useEffect(() => {
				const fetchData = async() => {
				fetch("https://hoge.s3.ap-northeast-1.amazonaws.com/sample.json")
					.then((res) => res.json())
					.then((data) =>{
						setData(data);
				});
				}

				const id = setInterval(() => {
				    fetchData();
				 }, 2000);
				
			}, []);

			console.log(data);

			return(
				<h1>コメント:{data.speech}</h1>
			);
		}

		const target = document.getElementById('app');
		ReactDOM.render(<App />, target);

pythonでuploadしたワードがブラウザの更新なくHTML側に表示されます。
OK、後はUIを作っていく

[Python3] jsonを生成してS3にuploadする

strのところは、mufj, 1375で作ってますが、本来はpython3で処理した値をuploadする想定です。

import boto3
import json

accesskey = ""
secretkey = ""
region = ""

// 機械学習等の処理

str = {
	"三菱UFJフィナンシャル・グループ":668.3,
	"日経ダブルインバース上場投信":367
}

with open("stock.json", "w") as f:
	json.dump(str, f, ensure_ascii=False)

 
s3 = boto3.client('s3', aws_access_key_id=accesskey, aws_secret_access_key= secretkey, region_name=region)
 
filename = "stock.json"
bucket_name = "speech-dnn"
 
s3.upload_file(filename,bucket_name,filename, ExtraArgs={'ACL':'public-read'})
print("upload {0}".format(filename))

result
{“三菱UFJフィナンシャル・グループ”: 668.3, “日経ダブルインバース上場投信”: 367}

OK これをreactと接続して、インタラクティブに表示されるか確認する

### 修正
‘ContentType’: “application/json”で指定しないと、jsonファイルとして保存されない

s3.upload_file(filename,bucket_name,filename, ExtraArgs={'ContentType': "application/json",'ACL':'public-read'})

[Python3] botoでS3にpublicでuploadする方法

s3.upload_file で、 ExtraArgs={‘ACL’:’public-read’}を追加する

s3 = boto3.client('s3', aws_access_key_id=accesskey, aws_secret_access_key= secretkey, region_name=region)
 
filename = "a.json"
bucket_name = "hoge"
 
s3.upload_file(filename,bucket_name,filename, ExtraArgs={'ACL':'public-read'})
print("upload {0}".format(filename))

※追加しないとaccess denyとなるので注意が必要

[音声認識] RaspberryPI 4(model B)でJuliusを動かす

音声認識をwavファイルではなく、ラズパイでやります。

### 前準備
– ラズパイ4 model B (秋葉原で電源、microSDなどセットで1万くらい)
– モニター、キーボード, マウス(メルカリでセットで3500円くらい)
– USBマイク(amazonで300円くらい)
※初期設定でwifiの設定とキーボードをJapaneseにする必要がある

$ mkdir julius
$ cd julius
$ wget https://github.com/julius-speech/julius/archive/v4.4.2.1.tar.gz
$ tar xvzf v4.4.2.1.tar.gz
$ cd julius-4.4.2.1
$ sudo apt-get install libasound2-dev libesd0-dev libsndfile1
※libsndfile1-devはRaspberryPI 4ではinstallできなかった
$ ./configure –with-mictype=alsa
$ make
$ sudo make install

$ cd ../
$ mkdir julius-kit
$ cd julius-kit
$ wget https://osdn.net/dl/julius/dictation-kit-v4.4.zip
$ unzip dictation-kit-v4.4.zip

$ sudo vim /etc/modprobe.d/alsa-base.conf

options snd slots=snd_usb_audio,snd_bcm2835
options snd_usb_audio index=0
options snd_bcm2835 index=1

$ sudo vim ~/.profile
一番最後の行に追加

export ALSADEV="plughw:0,0"

$ sudo apt-get install alsa-utils sox libsox-fmt-all
$ sudo sh -c “echo snd-pcm >> /etc/modules”

ラズパイ再起動

$ cd ~/julius/julius-kit/dicration-kit-v4.4/
$ julius -C main.jconf -C am-gmm.jconf -demo

AWSにjsonを送信したい
-> S3に保存すれば良いのかな
そこさえできれば、ほぼ基本的な挙動は完成

[ TypeScript ] 環境構築でWebsocketServer.js:10エラーになる時

### コンパイラ導入
$ node -v
v10.19.0
$ sudo npm install -g typescript
$ tsc -v
Version 4.4.3

hello.ts

const message:string = 'Hello! TypeScript!'
console.log(message);

$ tsc hello.ts
$ ls
hello.js hello.ts
$ node hello.js
Hello! TypeScript!

### npm環境
$ npm install typescript ts-loader webpack webpack-cli webpack-dev-server –save-dev

package.json

  // 省略
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode=development",
    "start": "webpack-dev-server --mode=development"
  },
    // 省略

webpack.config.js

const path = require('path');
module.exports = {
	entry: {
		bundle: './src/app.ts'
	},
	output: {
		path: path.join(__dirname,'dist'),
		filename: '[name].js'
	},
	resolve: {
		extensions:['.ts','.js']
	},
	devServer: {
		contentBase: path.join(__dirname,'dist')
	},
	module: {
		rules: [
			{
				test:/\.ts$/,loader:'ts-loader'
			}
		]
	}
}

$ tsc –init
-> tsconfig.json が生成される

dist, srcフォルダを作成する

src/app.ts

import {Item} from './item';
var elem = document.getElementById('output');
var aBook = new Item('はじめてのTypeScript', 2020);
aBook.say(elem);

src/item.ts

export class Item {
	constructor(private name:string, private price:number){}

	public say(elem: HTMLElement | null) : void {
		if(elem){
			elem.innerHTML = '書名:' + this.name + ' 価格: ' + this.price + '円';
		}
	}
}

dist/index.html

<body>
	<div id="output"></div>
	<script src="bundle.js"></script>
</body>
</html>

$ npm run start

> tutorial@1.0.0 start /home/vagrant/dev/typescript/tutorial
> webpack-dev-server –mode=development

[webpack-cli] /home/vagrant/dev/typescript/tutorial/node_modules/webpack-dev-server/lib/servers/WebsocketServer.js:10
static heartbeatInterval = 1000;

何でだろう? なんかエラーになるな。。

$ sudo apt-get purge nodejs

▼この記事を参考に最新版を入れる
https://www.digitalocean.com/community/tutorials/how-to-install-node-js-on-ubuntu-20-04-ja

$ cd ~
$ curl -sL https://deb.nodesource.com/setup_14.x -o nodesource_setup.sh
$ sudo bash nodesource_setup.sh
$ sudo apt install nodejs
$ node -v
v14.17.6
$ npm run start

http://192.168.34.10:8000/

nodeがv10だとwebpack-dev-serverでエラーになるみたい 
https://github.com/VickScarlet/lifeRestart/issues/176
v14にupgradeするとエラー解消
マジかよ~~~~ もうやだー

[JavaScript] Classの追加(add)を操作する

タブメニューで、URLのパスに応じてメニューのis-activeをつけたい時
L ulのli要素にclassで”is-active”をつけたい
html

<div class="tabs">
      	<ul id="mylist">
      		<li><a href="/btc">BTC</a></li>
      		<li><a href="/eth">ETH</a></li>
      		<li><a href="/xrp">EXP</a></li>
      		<li><a href="/xlm">XLM</a></li>
      		<li><a href="/mona">MONA</a></li>
      	</ul>
      </div>

javascript側
L location.pathnameでURLを取得する
  L querySelectorAllでli要素を全て取得する
  L classList.addでclassを付与する

const pathname = location.pathname;
  	var i;
  	switch(pathname){
  		case '/btc':
  			i = 0;break;
  		case '/eth':
  			i = 1;break;
  		case '/xrp':
  			i = 2;break;
  		case '/xlm':
  			i = 3;break;
  		case '/mona':
  			i = 4;break;
  	}
  	var cols = document.querySelectorAll('#mylist li');
  	cols[i].classList.add('is-active');

OK
やりたいことの7割くらいまでは出来た

[Go] HandleFuncのhandlerをswitchでまとめて実装する

ルーティングによって処理を変えたいが、handlerの中身はほとんど一緒なため、handlerの中でswitch文を使って切り分ける
L html.EscapeString(r.URL.Path) でパスの値を取得できる

func apiHandler(w http.ResponseWriter, r *http.Request){

	var code string // 変数として宣言
	switch html.EscapeString(r.URL.Path) {
	case "/btc":
		code = "BTC_JPY" // ビットコイン
	case "/eth":
		code = "ETH_JPY" // イーサリアム
	case "/xrp":
		code = "XRP_JPY" // リップル
	case "/xlm":
		code = "XML_JPY" // ステラルーメン
	case "/mona":
		code = "MONA_JPY" // モナコイン
	default:
		code = "BTC_JPY"
	}

	uri := "https://api.bitflyer.com/v1/getticker?product_code=" + code
	req, _ := http.NewRequest("GET", uri, nil)

	// 省略
}


func main() {
	http.HandleFunc("/btc", apiHandler)
	http.HandleFunc("/eth", apiHandler)
	http.HandleFunc("/xrp", apiHandler)
	http.HandleFunc("/xlm", apiHandler)
	http.HandleFunc("/mona", apiHandler)
	log.Fatal(http.ListenAndServe(":8080",nil))
}

code := “BTC_JPY” とすると定数になるので、 var code string と変数として宣言する必要がある
なるほど、後はテンプレート側の処理

bulma cssとtypescriptを使いたい