Git hooksを使ってphpのバージョンアップ対応までの暫定対応をする
しばらく放置してしまって、久々の更新です。
php5.xが2018年末でEOLということで、仕事でphpのバージョンアップ対応をしているのでそこで行った対応の一部をまとめておきます。
環境
今回更新をしていく環境はざっくり以下のような感じです。
かなーりレガシーな感じの構成ですが、今まで動いてたので放置してました。
これを以下のような構成にバージョンアップしていきます。
PHP-7.1は結局2019年末にEOLなんですが、
メジャーバージョンアップを乗り越えればまあなんとかなるだろうということで、今回は7.1までにしました。
手順
昔からあるコードとか、どこで不具合があるかわからないので、以下のような手順を踏むことにしました。
- php7ccでリスクのあるコードを検出
GitHub - sstalle/php7cc: PHP 7 Compatibility Checker
こいつを使って互換性チェックを行いました。
使い方については以下の記事を参考にしています。
php7ccはすでに開発終了していて後継のツールがあるのですが、
それらはPHP7環境でしか動作しないので環境作成が面倒なため、あえてこれを使いました。
- ユニットテストを回す
幸いなことに、今回対応したシステムはユニットテストのコードカバレッジがそこそこ高かったので、まずはユニットが全部通ることを検証しました。
その過程でカバレッジが低い部分については追加でテストを書いて、コードカバレッジで品質を担保できるようにしました。
- 実際にweb上から操作しつつ、結合テストを実施
実際のユースケース通りに操作して問題ないかをテストしました。
ここまでくるともうほとんどバグは見つからない状況でした。
まとめ
文章ばかりになってしまいましたが・・・
PHP7のバージョンアップではすでに非推奨とされていた使い方ができなくなるなど、いい感じに移行しやすくされていると感じました。
それとユニットテストがちゃんと書いてあることの素晴らしさというか、安心感は感じました。
バージョンアップ対応のような完璧に把握することが難しい作業をするときには、テストがあると本当に便利ですね・・
Goでhttpリクエスト(エンコード編)
前回、Goでhttpリクエストを送る - まさひちの道具箱でYOLP(地図サービス)の郵便番号検索APIを叩いた記事の続きです。
無事に叩くことはできたけど、出力結果が文字化けしていてモヤっとするので、その部分を直していきます。
httpリクエストの文字エンコード
前回は単純にリクエストパラメータとURLだけ設定してAPIを叩きました。
結果は
{"ResultInfo":{"Count":1,"Total":1,"Start":1,"Status":200,"Description":"","Copyright":"","Latency":0.015},"Feature":....
のようにJSON形式で返ってきたはずです。
この時、例えば
..."Name":"\u5ddd\u53e3\u5143\u90f7"...
のようにエンコードが間違っているのが気になりますよね。
これ、 文字化け解読ツール「もじばけらった」 で解析してみると、JSONエンコードにすると正しく見えるようです。
出力する文字コードを指定するにはjson.Marshalを使ってパースしてやればいいみたいです。
構造体の定義
JSON形式にパースするにはデータを入れるための構造体の定義が必要です。でも面倒なので、さっきAPI叩いて返ってきた結果をそのまま JSON-to-Go: Convert JSON to Go instantly に突っ込んで作ってもらいます。
type YOLP struct {
ResultInfo struct {
Count int `json:"Count"`
Total int `json:"Total"`
Start int `json:"Start"`
Status int `json:"Status"`
Description string `json:"Description"`
Copyright string `json:"Copyright"`
Latency float64 `json:"Latency"`
} `json:"ResultInfo"`
Feature []struct {
ID string `json:"Id"`
Gid string `json:"Gid"`
Name string `json:"Name"`
Geometry struct {
Type string `json:"Type"`
Coordinates string `json:"Coordinates"`
} `json:"Geometry"`
Category []string `json:"Category"`
Description string `json:"Description"`
Style []interface{} `json:"Style"`
Property struct {
UID string `json:"Uid"`
CassetteID string `json:"CassetteId"`
Country struct {
Code string `json:"Code"`
Name string `json:"Name"`
} `json:"Country"`
Address string `json:"Address"`
GovernmentCode string `json:"GovernmentCode"`
AddressMatchingLevel string `json:"AddressMatchingLevel"`
PostalName string `json:"PostalName"`
Station []struct {
ID string `json:"Id"`
SubID string `json:"SubId"`
Name string `json:"Name"`
Railway string `json:"Railway"`
Exit string `json:"Exit"`
ExitID string `json:"ExitId"`
Distance string `json:"Distance"`
Time string `json:"Time"`
Geometry struct {
Type string `json:"Type"`
Coordinates string `json:"Coordinates"`
} `json:"Geometry"`
} `json:"Station"`
} `json:"Property"`
} `json:"Feature"`
}
すぐにこんな構造体を返してくれました。
typeだけ直したら完成です。そのままコードに突っ込みます。
encoding/jsonでjsonをパース
まず、jsonを使うのでimportの中に
"encodig/json"
を足します。
処理は、前回のコードの最後に
var data1 YOLP // 定義した構造体変数を宣言 json.Unmarshal(body, &data1) // jsonパース fmt.Println(data1)
を加えるだけで完成。
めでたく住所などが表示されるようになりました。
YOLPを提供しているヤフー本社の郵便番号102-8282を指定すると
....東京都千代田区紀尾井町1-3東京ガーデンテラス紀尾井町紀尾井タワー 13101 6 ヤフー 株式会社......
のように、住所が正しく取れています。
まとめ
jsonデータを受け取った時はエンコードがjsonエンコードになっているので、
encoding/jsonをインポートして、Unmarshalでパースしましょう。
Goでhttpリクエストを送る
特にこれといって動機はないのですが、Go言語でバッチ処理的にAPIを叩く処理を実装してみたのでメモ。
なにもGoでやらなくてもPHPだって、Pythonだって、なんならshellだってできるのですが、 なんとなくGoってさわったことないし、やってみるか的な感覚です。 特にすごいことでもないし、他の方の記事なんかで散々書かれているのでわざわざ新しくまとめる意味もないんですが。
Go環境構築
まずは環境を整えることから。
https://golang.org/dl/から最新バージョンのGoをダウンロードします。
sudo wget https://storage.googleapis.com/golang/go1.10.linux-amd64.tar.gz
うまくいったら、解凍
sudo tar -C /usr/local -xzf go1.10.linux-amd64.tar.gz
/usr/localは別に違っててもいいです。好きなところを指定してください。
次にgoコマンドを認識できるようにPATHを通します。
$ vim ~/.bash_profile // 以下を追記 export PATH=$PATH:/usr/local/go/bin // 認識させる $ source ~/.bash_profile
うまく通って入れば
$ go env
で、goの環境設定が表示されるはずです。
GOARCH="arm" GOBIN="" GOEXE="" GOHOSTARCH="arm" GOHOSTOS="linux" GOOS="linux" GOPATH="/home/user/go" GORACE="" GOROOT="/usr/local/go" GOTOOLDIR="/usr/local/go/pkg/tool/linux_arm" GCCGO="gccgo" GOARM="6" CC="gcc" GOGCCFLAGS="-fPIC -marm -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build984502013=/tmp/go-build -gno-record-gcc-switches" CXX="g++" CGO_ENABLED="1" CGO_CFLAGS="-g -O2" CGO_CPPFLAGS="" CGO_CXXFLAGS="-g -O2" CGO_FFLAGS="-g -O2" CGO_LDFLAGS="-g -O2" PKG_CONFIG="pkg-config"
httpリクエストの実装
適当な場所にmain.goを作って実装を書いていきます。
何か叩く先があると楽しいので、今回はYahoo!デベロッパーネットワークで公開されているYOLP(地図サービス)の郵便番号検索APIを叩いてみます。
YDNはアプリケーション名を登録するとAPIを叩くためのappidが発行されるので、簡単に叩けます。
http.Client
Goでhttpリクエストを送信する方法はいくつかあるようですが、
一番カスタマイズできるやつを覚えておけば問題ないだろう(大は小を兼ねる的な)ということで、http.Clientを使います。
importにnet/httpを加えてください。
- リクエストクエリの作成
request , err := http.NewRequest("GET", URL, nil)
のようにするとGETリクエスト用のhttp.Requestとエラーを返します。 第三引数はパラメータですが、自分の場合は
// valuesにリクエストパラメータを設定
values := url.Values{}
values.Add("appid", string(appid)) // YDNのappid
values.Add("query", "xxx-yyyy") // 検索する郵便番号
values.Add("output", "json") // レスポンスの形式:今回はJSON
reqest.URL.RawQuery = values.Encode()
として、後から設定する派です。
- リクエスト送信
client := new(http.Client)
でhttp.Clientを作ってから
resp, _ := client.Do(request) defer resp.Body.Close()
でhttpリクエストを送信します。 resp.Bodyにレスポンスの実体があるので
body, err := ioutil.ReadAll(resp.Body)
はい、レスポンス取得できました。
エンコードがおかしかったりして文字化けしてたりするので、次回はそこらへんを直していきましょうか。
まとめ
今回作ったものの全容はこんな感じ
package main
import (
"fmt"
"net/http"
"net/url"
"io/ioutil"
)
func main() {
// サンプルとしてYOLPの郵便番号検索APIを叩く
values := url.Values{}
values.Add("appid", <appid>)
values.Add("query", <郵便番号>)
values.Add("output", "json")
url := "https://map.yahooapis.jp/search/zip/V1/zipCodeSearch"
// http.Getでリクエストを作る
request, err := http.NewRequest("GET",url,nil)
if err != nil {
fmt.Println(err)
return
}
request.URL.RawQuery = values.Encode()
// リクエスト送信
client := new(http.Client)
resp, _ := client.Do(request)
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(body))
ラズパイのmysqlが再起動できない
ラズパイで使っていたmysqlが、ある日突然起動しなくなりました。 昨日まで普通に動いてたやんけー!!って思いながら原因調査。 なんだか結構ハマってしまったので、メモしておきます。
問題発生
久々にラズパイで
$ sudo apt-get update
を叩いたところ、、mysqlでエラーが発生。
試しにリスタートしてみる。
$ sudo systemctl restart mysql.service
結果は・・・
Job for mysql.service failed. See 'systemctl status mysql.service' and 'journalctl -xn' for details. $ sudo systemctl status mysql.service ● mysql.service - LSB: Start and stop the mysql database server daemon Loaded: loaded (/etc/init.d/mysql) Active: failed (Result: exit-code) since 1min 24s ago Process: 20927 ExecStart=/etc/init.d/mysql start (code=exited, status=1/FAILURE) raspberrypi /etc/init.d/mysql[20857]: error: 'Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)'
なんでやねん・・・
エラーメッセージでググってみると、どうやらsockファイルがないのが原因らしい。
適当にtouchで作ってみるが
raspberrypi /etc/init.d/mysql[20857]: error: 'Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (111)'
あー、直んね。
解決方法
しょうがないので、エラーログを見てみる。
$ sudo cat /var/log/mysql/error.log | grep ERROR [ERROR] /usr/sbin/mysqld: unknown variable 'default-set-server=utf8mb4' [ERROR] Aborting
え?この前までこれで動いていたんだが・・・?
unknown variable 'default-set-server=utf8mb4'でググってみると、中国語のサイトにたどり着いた。
どうやら、
default-set-server=utf8mb4
ではなく
character-set-server=utf8mb4
にすればいいようだ。アップデートで変更になってしまったのだろうか。。
これでちゃんと
$ mysql -u root -p
も通るようになった。
まとめ
[client]には
default-character-set=utf8mb4
[mysqld]には
character-set-server=utf8mb4
を設定しましょう。以上。
CodeIgniterのライブラリを使ってレスポンスをスッキリさせる
日本ではあんまり人気がない(らしい)PHPフレームワークのCodeIgniter
軽量だし、MVCモデルがシンプルに使えて拡張できるのはなかなか気に入っているので、
自分で作ったライブラリなんかをメモがてら残しておこうと思います。
今回作ったのは
画面表示用のライブラリを作りました。
デフォルトのCodeIgniterではviewの呼び出しは
$this->load->view('hoge', $data);
みたいに、コントローラ側でロードしてあげればいいんだが、
例えば
- ヘッダー
- コンテンツ
- フッター
みたいにviewファイルをいくつか呼び出していてコンテンツ部分だけ切り替えたい、みたいな時は、その都度必要なviewを全部呼び出さなきゃいけなくて不便。
せっかくCodeIgniterを使っているんだし、足りないものは自分で作らなきゃね、ってことで
$this->response->html('hoge');
としたら、ヘッダーとかの固定要素は全部読み込んで、共通で渡したい情報もセットできるライブラリを作りました。
実装
FWのapplication/librariesの下にResponse.phpを作成します。
htmlメソッドは例えば以下のような感じ。
------------------------------------------------------------
/**
* レスポンスをHTML形式で出力する
*
* @param string $view ビュー名
* @param array $vars 表示データ
* @return void
*/
public function html($view, $vars = array()) {
$vars = array_merge($this->vars, $vars);
$this->ci->load->view('templates/header', $vars);
$this->ci->load->view($view, $vars);
$this->ci->load->view('templates/footer', $vars);
}
------------------------------------------------------------
$varにはレスポンス時に渡すデータ配列が渡せる仕様です。
今回は詳しく書きませんが、別途setメソッドを用意して外部から変更もできるようにしています。
このライブラリの中にJSONとかXMLとかあらゆるレスポンス形式のメソッドを作り込んでいけば、どんなレスポンスに対しても
response->***
のような統一感あるレスポンスが書けるはずです。
ただ、これを作っただけだとコントローラ側でロードしないと使えないので、
autoloadに書いて、自動で読み込むようにしておきましょう。
CodeIgniterではapplication/configの下にautoload.phpがあるので
$autoload['libraries']の配列内につくったライブラリを追加するとクラスからでも使えるようになります。
おわりに
CodeIgniterは拡張性が高いから、欲しいものはどんどん自分で作りましょう!
次回は認証のライブラリでも作ろうかな。