お前のパソコンを構成管理してやろうか

この記事は GMOペパボ Advent Calendar 2018 の14日目の記事です。

qiita.com

 さっむいですね。。
前回の記事が「あっついですね」で始まっていたのにもう寒くなってしまいました。寒暖差が体に堪えても、普通に会社に出て普通に仕事しています。

 最近ですね、お仕事してる時にこう言われることが多くなってきたんです。

  • 開発環境が壊れて開発すべきページが見れなくて困っている
  • テンプレートのデザインしたので、コミットしておいてもらえませんか

 あれ?何かが違う気がする。。。

気がついたら、そのたびに小一時間、下手したら丸一日かかりっきりでパソコンを直してました。

  • Ruby のバージョンが違う
  • コンテナが buzy で止まってる
  • pid ファイルがあるのにサーバが止まってる
  • Python3 にしないと動かないはずなのに動かない
  • Composer がエラー吐く
  • PuppetLabs の VM イメージが古すぎて GnuPG の署名が通らなくなってた
  • rbenv の git リポジトリが変わってるみたいで Puppet が止まる
  • 会社でホストしてた yum リポジトリがなくなってた

 などなど、そもそも構築方法を見直さなければならないものから、手順自体を見直さなければならないものまで、多種多様の「動かなくなった」案件が舞い降りました。
 僕はわりと壊れたものを直すのが好きなので、時間がかかっても最終的に直してしまっていたのですが、ちょっと時間取られすぎかなって思ったり、いやいや、そもそもちゃんとした開発環境もないのにちゃんと開発できてるんか?生産性とか開発のテンポってみんなどう感じてる?みたいな思いが頭の中でグールグルしていました。
 そんな家でビールを飲んでテレビを見てうとうとしていた時に、なぜかデーモン閣下が僕にこうささやいていました。

「お前のパソコンを構成管理してやろうか!」

 というのは嘘なんですが、これは結構大事なんじゃあなかろうかと思いました。
 確かに僕たちが開発してるものは、維持しなければならない技術スタックが多くなってきてまして、それらを維持するための解決案もあってケアされていると思っていましたが、それをブラッシュアップしきれていない状況でした。
このことについては、我らが偉大なるリーダーも課題感を持っており、会社ブログで書いてました。

tech.pepabo.com

 ただ、僕としては DB を除く全てのロールをコンテナ化したいと思っていて、そのコンテナから社内 OpenStack のイメージを作成してサンドボックス環境化して行きたかったんですね。
で、環境作るときはとりあえず docker-compose up しといて!って言ったら終わる感じにしておきたかったんですが、今の puppet どうする? capistrano どうする?みたいなよくある構成管理はどのツールに請け負わす?みたいな解決案を持っていませんでした。
 docker-compose.ymldockerfile に全部書けるぐらいなら管理も楽だしそれがいいんですが、ちょっとそんなに単純でもないしな。かと言って本番の Terraform からキックされるクックブックとは違う開発環境のクックブックをブラッシュアップしていくことにどれほどの意味があるのか。 mitamae か確かに便利そうだなぁ、割と Ruby っぽいレシピ書くっぽいけどこれ自体を維持していけるかな。あでもなんか mruby 使ってるだけあって速いらしいけど、今のやつは Fabric と混ざってる??
 うーんうーん、と悩んでたんですが、結局、まずまずは目の前の環境が壊れている子を助けることが主眼なんだから、今手元でずっと温め続けてきた ansible-playbook をもうちょっとブラッシュアップして使ってもらったほうが早いのでは?と思い直し、とりあえずみんなの環境ができて、お隣に行ってガチャガチャ直す時間が減ってからベストプラクティスを定義しても遅くはないのではなかろうか?どうせコンテナ化したらまたバックグラウンドが変わるだろうし。と思って社内で公開しました。

 構成はベストプラクティスに寄せたつもりです。

$ tree -L 2
.
├── ansible.cfg
├── dev.yml
├── hosts
├── log
│   ├── debug.log
│   roles
│   ├── composer
│   ├── docker
│   ├── homebrew
│   ├── hosts
│   ├── migration
│   ├── node
│   ├── pip
│   ├── ruby
│   ├── sidekiq
│   ├── styleguide
│   ├── vagrant
│   ├── vagrant.puppet
│   └── workspace
└── workspace
    ├── repository A
    ├── repository B
    └── repository C

 抽象化した作業をロールで分解して、使いたいタスクを dev.yml のコメントを外して使ってもらって好きな playbook を使ってもらえるように意識したつもりで、例えば好きなロールのサーバの起動だけしたいとか、 homebrew でインストールしたアプリのアップデートだけしたいとか、ユースケースに併せて使えるように意識しました。今後、使ってもらっていって、固定化されたユースケースが浮き上がってきたらそれ用の playbook を作ったらいいんだし、再利用性を考慮したつもりなんですけど、こういう設計って答えがなくて無限に悩めますね。
(ちなみに、これが全てではなくて、ロール固有の処理とかビジネスロジックに依存するタスクは割愛しております。

 Ansible 自体は前職のゲーム作っていた時に本番のサーバプロビジョニングで使っていたので、記法や勘所はすぐに思い出せたのですが、個人のパソコンをプロビジョニングする時にとっても悩んだ点を紹介します。
サーバのプロビジョニングはベースとなる構成が同じなので、各個人の macOS でベースが違うプロビジョニングはかなりハードルが高いと思いました。例えば

  • 使っているシェルが zsh や fish 、 bash など異なる
  • ndenv 、 nodebrew など、言語のバージョン管理ツールが各個人で異なる
  • sudo する時のパスワード入力
  • python の実行パスが各個人で異なる
  • インストールパスに各個人の趣向が異なる
  • 既にインストール済みのモジュールのエラー回避
  • ロールの設計

 また、普通にハマったことはこんなもの

  • 変数にハイフン( - )を使ったら見つからなくなった
TASK [test : debug] ***************************************************************
Tuesday 11 December 2018  17:00:54 +0900 (0:00:01.294)       0:00:01.437 ******
ok: [localhost] => {
    "ps-test": "VARIABLE IS NOT DEFINED!"
}

 こんな感じで、 VARIABLE IS NOT DEFINED! になってあれ?って思ったんですけど、ハイフン使わなかったら出ました
( yml かなこれは

[local]
localhost ansible_python_interpreter=/usr/local/bin/python3

hosts に ansible_python_interpreter を指定して Python3 のインタプリタをフルパス指定したら動きました

 個人 mac なんで、 NOPASSWD とか指定してませんから、 sudo が必要なタスクで --ask-become-pass を指定しておかないと権限がなくてエラーになります。
パスワードの入力自体はいいんですけど、NOPASSWD を記述してる VM 内の操作にそぐわなかったので、まぁ、playbook を分けるか mac に NOPASSWD を記述します。
 でも、セキュリティ上、良くないので、 vault で解決できたらそれがいいんですが、ちょっとそこまで行きませんでした。。

  • rbenv はバージョンが切り替わるけど、 ndenv は切り替わらなかった

 これは、今でもよくわかっていないのですが、 shell モジュールの実行時、 .node_version での node バージョンの切り替えがうまくいきませんでした。
rbenv の場合、 .ruby_version に記載のバージョンに切り替わるので、 chdir を指定したらバージョンが切り替わったのですが、なぜか ndenv の場合、 .node_version のバージョンに切り替わりませんでした。
 lookup を使って .node_version を参照して node global xxx で切り替わったのでそうしましたが、もうちょっと上手いことやれる気がしてます。、

感想など

 やっぱり自動化は面白い!って思いました。
Ansible は、エラーが実行したコマンドのエラーそのまま出ますので、えー、これ使ってるんや!とか、なんでこのファイルのここをコメントアウトしてるんだ・・・とか発見が多かったです。
 多分、普通にアプリ実行環境(本番とかステージング環境)の構築に Ansible を使っているだけではぶつからないエラーに引っかかるので、ちょっと Ansible 力が上がった気がします。

 ということで、明日の GMOペパボ Advent Calendar 2018@litencatt さんです。

WebExtensions で Firefox Add-ons を公開してみました

 あっついですね。。
前の記事からだいぶサボってました。。
ブログ記事にするようなことがなかったわけではないのですが、なぜだか間が空いてしまいました。
資格試験の勉強をしていたのですが、だんだん飽きてきてしまったので、そういえばブログだな〜っていうテンションで書いてます。
公開してから結構経ってしまったのですが、初めて WebExtensions に触れて公開したので、メモ書きします。

 今年のはじめ、QAチームのエンジニアさんというポジションに配属したのですが、問い合わせ業務や改善作業や障害対応もいろいろ関連付けて仕事していたら、なんだか何でも屋さんになってしまっていて、結局僕はなんていう名前のポジションなんだろうって考えてまして、あえて言うなら最近流行りの SRE( Site Reliability Engineering )や CRE( Customer Reliability Engineer )かな〜って思ってたんですが、まぁそんな大それた感じでもないか〜って思ってました。


 で、いろいろな作業をやるうちの一つに、内部脆弱性検査を実施するお仕事もありまして、 OWASP ZAPBurpSuite を触っていたのですが、エンジニアが少ないQAチームメンバーでも効率的に脆弱性を検査できるツールで VAddy を使おうということになりました。
このツール、素晴らしく脆弱性を検査してくれます。アップデートが早いし、どんどん高機能になっているので、僕は今後もお世話になっていくことになるでしょう。 中の方が茅場町に来てくださったので、上役とお邪魔してきました。

vaddy.doorkeeper.jp

 ただ、自動テストを想定しているせいか、手動でのテストは割と手間がかかるかなぁという印象です。
自動テストは僕がやりたい領域のトップに入るのですが、残念ながら今、それに注力できる時間が少なく、そもそも、環境からじゃね?ってなっていまして、いろいろ遠回りするしかなくてなかなかテストスクリプトを書く段階まで至らず、でもその間にも脆弱性は検知したいので、どうしても手動によるテストや探索的テストに大きく頼らざるを得ません。
そういう時、以下のマニュアルに沿って手で進めるのですが、大量にクロールデータ(テストシナリオ)を作る作業で時間がかかってしまうなぁと感じました。

support.vaddy.net

 まぁ、とはいえ手順が多いとか、複雑とかではなく、やることは至って単純なのです。
上記はクロールデータを作成する手順でして、ブラウザのプロキシを設定して、 begin URL(開始) を叩いて、画面操作して、 commit URL(終了)を叩けば終わりです。
あとは僕が作ってくれたシナリオをスキャンするのに、ボタンポチポチするか、スクリプトでガーッとやるかなんですが、クロールデータを作成するのは、僕ではなかったりしました。テスト担当者や、 CS のメンバーにお願いしていたため、どうしてもどれのあとにこれをやって、でどうなってっていう説明が難しくてですね。
説明が難しくても、厚めにフォローしてたら解決できる範囲だったのでいいのですが、 begin URL を叩いたら、変なエラーがでたとか、うまく操作してるつもりだったんだけど、クロールデータができなかったとかありまして、そのときにでたエラーを1つ1つ解消するのに苦労しました。


 なんでだろうな〜、って思っていたのですが、間違って begin URL を2回叩いていたとか、クロールデータ作成中に他のメンバーが begin URL を叩いてしまって、中断されてしまったとかでした。
複数メンバーで一気にクロールデータ作成を行っていたために起きていた問題と、そもそも操作ミスが原因で起きていた問題に切り分けられました。
前者は検証環境を増やすことで解消できたのですが、操作ミスは依然残ってしまっていて、どうしたらスムーズに進行してもらえるかな〜と考えていまして、その結果、 Add-ons を作ることにしました。
要するに、開始 URL と終了 URL を1回ずつ叩けばいいので、ブラウザの右端にアイコン作ってあげて、操作ミスを軽減したらいいのではないかと。
で、開始 URL を叩いたタイミングでテスト対象の URL を開いてあげたら更にいい感じになるのではと思ったので、それぐらいなら速攻できそうだなと思われました。

Easy VAddy Proxy Crawling – Firefox 向けアドオン

 はじめ、 Chrome を想定して作っていたのですが、割と出来上がってきたタイミングで、あ、Chromeのプロキシってシステムのプロキシ利用するんだったってことに気づいて、あぁあぁぁぁ。。ってなりました。
確かにマニュアルにも近しいこと書いてるなぁ。なんかそういうのなかったっけって調べたんですが、 sudoChrome 叩いて、引数に Proxy を渡したら、どうやらいい感じに動いたんですが、いや、流石にちょっとPCを管理している感じの人に怒られそうだな、じゃぁ、そのままシステムのプロキシ使ってもいいんじゃない?って思ったんですが、あれ? Slack... ってなったんで、やっぱ Firefox しかねぇって方向転換しました。
最近、 Firefox も WebExtensions になったみたいだし、書いたコードも流用できるでしょうと思ってたのですが、案外 Firefox の WebExtensions の作例が少なかったので、 MDN 読んでたら結構なボリュームだったんで、読むのに時間かけてしまいました。。。 ^^;

github.com

 作りながら、うーん、このタイミングで Notification を出したいな〜とか、このタイミングのときは Icon を変えたくないなぁとか、 Option ページは bootstrap とか使ってそれなりのデザインにしたいなぁとか欲求がでてきまして、速攻とか言ってましたけど、 Add-ons を作ること自体が楽しくなってきてしまって2、3日かけてしまいました。。


 まだ、ちょっと使いづらいし、例外ハンドリングが甘いのですが、僕たちがクロールデータを手動で作成するワークフローに合うように作れたと思います。
チームメンバーに使ってもらって、フィードバックを得たいですが、初めてブラウザのアドオン作ったので、いい勉強になりました!

Androidアプリ公開しました!

 かつてより作ってみたいな〜と思っていたAndroidアプリをGooglePlayに製品版を公開しました!

play.google.com

 WebViewで作られていたソシャゲを担当していた時、ネイティブの機能を使ってあれやりたいこれやりたいと思ったり、このJavascriptはどういう仕組で動いているんだ?と疑問に感じていたので、なんでもいいからスマホアプリを自分で手がけたいと悶々と思っていました。
 まぁ、それまでアプリプラットフォームチームのコードを読んだりもしていたのですが、そもそも今の組織に入ったとき、流行り始めているスマホアプリを開発したかったという思いもあって、6年越しに一通り自分で管理できる機会に恵まれました。

 作ったアプリは、HTML5canvas で生成した画像をスマホのストレージに保存する機能しか持たない簡単なアプリですが、なかなかはまりました。お遊びでアプリは作っていたのですが、ちょっと込み入ったことをすると途端にはまるのはいつまで経っても変わらないな。。
 普通のWebViewのアンカーリンク(A要素)から画像ファイルをダウンロードするのであれば、特にはまらなかったかもしれませんが、今回はbase64エンコードされた文字列をアプリで受け取って、画像ファイルにデコードして保存という流れです。というのも、WebView上の canvas に文字やスタンプ画像を好きに配置して、 toDataURL() メソッドで data:URI を生成するためです。
 この場合、 DownloadListener で受け取れるのか、 WebViewClient.shouldOverrideUrlLoading(WebView view, String url) で受け取れるのか挙動が分からずに色々悩みました。素人同然のため、API Levelで挙動が違うのか、端末依存なのか、WebViewのバージョンで違うのかが全く分からず、なんだかできたと思ったら他の端末で動かない、それを直したら他で動かないみたいなモグラたたき状態に陥っていました。
 結局、この記事に行き着き、 DownloadListener ではなく shouldOverrideUrlLoading でもないブラウザのセキュリティの更新が原因だったみたいでした。 JavascriptInterface を用意して、 data:URI をWebViewのJSから渡してもらう方法に軌道修正して期待通りの動きにできました。なんとなくアンカーリンク(A要素)だし初アプリ実装だから、王道のメソッドで受け取りたかったんだろうなと自分にかかったバイアスの強さを感じました。

pokkurutime.hatenablog.com

 だから端末によってイベントをキャッチできたりできなかったりしたんだろうなぁと振り返って思いました。他にもはまったポイントや知り得たことを忘れないように書いておこう。

  • 端末の権限の取り方
    • Android 7.0以降は onRequestPermissionsResult をOverrideしてユーザの操作をハンドリングする
    • これをOverrideしていないと権限無い状態で先の処理が走ってしまってアプリが落ちる
  • ProgressDialog の実装方法
    • Thread で非同期にしておかないと Activity で管理?していないダイアログになってアプリが落ちる
  • JSのAlertみたいなダイアログの実装方法
    • AlertDialog.Builder でボタンの挙動だったり表示文字をセットできる
  • 画面回転した場合、WebView の更新処理が走る
    • restoreState()savedInstanceState を保存しておくメソッドをOverrideしておく
  • Webアプリじゃないので、戻るボタンを押されたときの挙動を実装しておく
    • onKeyDown() をOverrideして実装しておかないと、トップページに戻った時に再度戻るボタンを押したら別のページに遷移したりする
    • この挙動がネイティブアプリっぽくないから、適切に押下を無視するとか finish() とかを実装しておく
  • アプリからWebViewのHTMLを変えたい
    • onPageFinished() とかをOverrideして loadUrl() でJSを即時実行させると動く
  • リファレンス機は必要
    • エミュレータで確認していたのですが、どうやらWebViewの更新が追いついていなかったのかな?動かなかった。。
    • Nexux5xも息が長いけど、公式リファレンス実機の確認は絶対やったほうがいい

 実装はとても楽しかったので、最後にアプリの紹介をば。 これは所属する会社のお産合宿に誘われたのがきっかけで始まったことでした。

osan.pepabo.com

 さらっと canvas で画像ファイルを・・・と話しましたが、それはアイドルのコンサートなんかで光らせるキングブレードというペンライトに装飾をするための画像ファイルです。キングブレードの内紙にバッチリ合う画像ファイルをコンビニで印刷できる(コンサートへ向かう最中でも推しメン用にカスタマイズできる)ことが売りです。
 そちら方面はまったく詳しくないのですが、 @souseiji さんや @_mmmix さんの話を聞く内にチャンスなのではないかと感じて参戦させていただいた次第です。

www.amazon.co.jp

 途中、まったくわからないキーワードに出会ったり、自分用のキングブレード買ったり、帰りにスターマリーのコンサートに連れて行ってもらったりと、開発以外にも色々初体験が多い期間で楽しかったです。

starmarie.com

 今回は、金銭的にも期間的にもAndroidアプリに絞って作りましたが、会社の人たちはiPhone率が高いので、空いた時間にちょびちょび作ってしれっとiOSアプリも公開しとこうかな〜

コードレビューのおきもち

 今日、わりとふと感じたことがあったので、一応メモしておこうかと思って書いてます。 チームでペアレビューの話が軽く出て、あぁ、そうかと納得したのですが、レビューってどう感じてますか? レビューって、以下のようなことを目的としてやってるかと思います。

  • 多角的な視点でコードを見て、不具合を減らす
  • タイポの防止
  • メンバー間のコミュニケーション
  • 人に説明できるコードを書けているかの確認

 よく言われてるから、今更どうとかではないし、上記を考えるとレビューをやる意義があると考えています。


 ただ、まだ駆け出しの頃は、レビューをするのもされるのも苦手でした。 振り返ってみて、なんで苦手だったかは、既に自分の中で答えが出ていました。

なんかケチつける or つけられてるみたいでマイナスなおきもち

 どんなにメンバーの関係が良好でも、言葉を選んでも、1%はそういう意味合いが僕にあったし感じていました。(関西人だから、自意識が高いのかなw?)
でも多分、それは今でも変わっていないので、だから、今でもする時もされる時も気を使っています。
 で、その気を遣うっていうのは、間違っていないんだとも思っています。 コードレビューじゃなくても、普段の生活をしていて、誰かに指摘や注意をする時って、気を遣いますよね。 それは、関係性をうまくし続けるために、必要な人間的活動なんだと思います。

 じゃあ、そのもやもやした意識を持ち続けて今後レビュー活動に勤しんでいても良いものかと、多分漠然と思っていたんでしょう。自分が。 でですね、やっと感じていたもやもやを打ち勝たせる魔法の考えがふと頭をよぎったのでした。


 コードレビューに対話やコミュニケーションの意義を見出すことに近いのですが、多分、僕が今後、レビューやペアレビューをするときは以下の思いを持って接していると思います。

僕の思想や考え方をあげるからちょうだい

 レビューをですね、いろんな人とすることによって、ペアになった人の思考や思想がどんな形であれ、自分の中に流れてきますよね。 それを受け取ったら、どのような感情を抱いても自分の中には入ってきてるんですよね。多分。そういう自分の考えじゃないものをいっぱい受け取って気に入ったものを反芻することで、自分の感性がブラッシュアップされていくんだと思うんですよね。
 今、僕が立っている場所はそういう活動を活発にしようとしているから、若いエンジニアは、広い視野を持ったり、知識を得る機会に恵まれて早く成長できるんだろうなと思ったんですよ。
 良いアイデアなのかそうでないのかを相対的に判断する必要はなくて、受け取った人の感性で継承していけばいいんじゃないのかな。それを個々人がコミュニティでやっていれば、自然淘汰されて良いアイデアが残っていくのではないか。自分のクローンは作らなくていいけど、いわゆる上位のエンジニアレベルの人間をできるだけ早く作るために、良いアイデアの生存競争を活発にすれば良いのではないかと。


 まぁ、であるならば生存競争の機会はレビューじゃなくても良いんだけど、レビューはわかりやすい場であるかな。 そう思えば、多少苦手でも、苦にならないなと自分は思った次第です。


 なんで今さらそんなことをふと思いついたのかはよくわかりませんが、WEB+DB PRESS を執筆させて頂く機会に恵まれた事は関係していると思います。 エンジニアになってから、よく目にする機会があった本に執筆させて頂く機会を与えてくださり、ありがとうございました。
 報告することが遅れてしまって申し訳ありませんでしたが、自分の知識の棚卸しを本にするという、なかなか体験できない経験をさせて頂いた技術評論社 池田様や、弊社メンバーに感謝しています。

gihyo.jp

Zephirはじめてみました

どうも

unionsep.hatenablog.com

前回の記事で少し話しましたが、Zephirを触ってみました
ZephirはPHPカンファレンス2015ぐらいに見聞きしていて、興味のあるプロダクトでした

zephir-lang.com

phpcon.php.gr.jp

ただ、インストールしてみるものの、なかなか実際にどんなものか確かめるまでに至らなかったのですが、おもむろに見てみたらバージョンが 0.9.8 になっててそろそろ正式版になるのではと思って焦って触りましたw


InstallationIf you’re using Ubuntu って書いてあったので、手持ちのUbuntu16.04にインストールしてみました
ホント、最近 apt でなんでもインストールできますよね
みんなUbuntu好きなのかな

インストール自体は書いてある通りに gcc やら PHP を入れて git clone して installer叩くだけなので、手順は割愛します

で、とりあえず Tutorial にあるものを作ってみました
他でも書かれてありますが、ちょっとコードを書いてPHPとの違いを意識したのは以下ぐらいでした

  • 変数の書き換えに let を指定
  • namespace は必須
  • forforeach になる
  • if で条件式に括弧いらない
  • 型がPHPより厳密
  • functionの戻り値の型をhintで指定できる

まぁ、JavaとかC#とか触ってる人だったら、あんまり意識しなくても自然な流れで書けるのではないでしょうか
聞いていた elseif が使えないというのも、このバージョンでは使えるようになっているみたい

Control Structures — Zephir 0.9.4 documentation


でですね、再帰的なarray_searchを作ろうと思ってこんなコードを書いてみたわけですが、結構ハマりました
書いたと言いましたが、多分これじゃない感じなんですけど、それっぽいものが書けたので、とりあえずコードは載せようと思いまして^^;
どうもZephirにはPHPuse に相当する機能(参照渡し)がないようで、どう解消しようかハマっていました
結果どうなのか追えていないのですが、以下あたりで議論されてるみたいです

github.com

namespace Utils;

class Arrays
{
    private static keys;

    public static function arraySearchRecursive(var needle, var heystack) -> array
    {
        let self::keys = [];
        self::search(needle, heystack);
        return self::keys;
    }

    private static function search(var needle, var heystack) -> bool
    {
        var key, val;
        for key, val in heystack {
            if is_array(val) {
                self::search(needle, val);
            } else {
                if needle == val {
                    let self::keys[] = key;
                }                
            }
        }
        return;
    }

}

どうやらSuperGlobal、_POSTの値とかも取れるみたいで、取る必要ないんちゃうかなとか思いました^^;
言語仕様は、そこまで込み入った感じはなかったのですが、function-> 指定で返却値の型をhintで書けるぐらいが独特かなぁというぐらいでした

【PHPカンファレンス2016 フォローアップ】Cygamesを支えるPHPについて講演しました | Cygames Engineers' Blog

こちらの記事にも書かれている通り、配列処理で高速化が見込めるそうです
僕も計測をしてみたかったのですが、わりとハマったので一旦ここで断念しましたが、ソーシャルゲームを開発していた時は、巨大な配列処理を行う事が結構あったので、ガチャロジックとか開催中のイベント配列とかで使えないかなと思いました
あと、単純にextension化することで、簡単に処理を変更できなくするという意味でも有効性を見いだせる気がしています

今はゲーム事業から離れてしまったので、Zephirを使って高速化を試みるより、リファクタリングやDBの正規化で事足りますが、いつかキラーコンテンツとして提案できる日が来たらいいなぁと思っています
いつか使うど!

UbuntuデフォのFirefoxはてな開くと英語になったのにちょっと感動した!)

再帰的なarray_searchを作った

どもども

すっかり春な気候になってきて、フレッシュな方々を目にする機会が多くなりました。 フレッシュな感じに羨望の眼差しを向けながら、ここ4年ぐらい、ず〜っとPHPをメインに扱っています。

う〜ん、もともとず〜っとJavaPerlな感じの人で、敷居が低いと噂のPHPを横目に見てましたが、ソシャゲを扱うようになってPHPも扱うようになったんですが、もはや何を作るにしても、もうPHPでよくね?みたいな脳みそになってしまったのが良かったのか悪かったのか。。
まぁ、アンチPHPな方もお見かけしますし、当然、僕自身もPHPのこの言語仕様はやだな〜って思ったりしたりすることもあります。 確かにレガシーなシステムを扱うときに、脳みそひねくり回さないとダメなときもあるし、PHPマニュアル 見てて、え、そうなの。。って思うときもあります。
でも地雷を踏まない能力ってエンジニアに必要な能力の一つだったりするのかなぁって今は思っています。 そういう能力を養うコストと、PHP の強力な文字列処理や配列処理の恩恵を受けるメリットを天秤にかけて、やっぱPHPで良いかなぁと思う次第です。。


個人的なコラムはどうでもいいとして、僕がPHPを気に入っている面の一つが配列処理です。 array_column とか usort とかは超気にいってますが、 array_search が単一階層の配列しか検索してくれない。。 配列の中に設定項目を連想配列にして詰め込んで、特定の設定項目を探し出すっていうケース、ソシャゲでは結構あったんですが、ソシャゲから離れた今、またそういう要件に対峙する時が来ました。 あれ、こういうパターンって確か共通関数にしてたなぁって思ったので、はてブから「そろそろブログ書かない?」メールも来たので、筆を取る気になりました。

想定している連想配列は以下のような構造です。

$event = array(
    array(
        'start_date' => '2017-01-01 00:00:00',
        'end_date' => '2017-03-31 23:59:59',
        'key' => 'event_1'
    ),
    array(
        'start_date' => '2017-02-01 00:00:00',
        'end_date' => '2017-04-31 23:59:59',
        'key' => 'event_2'
    ),
);

keyevent_2 の要素番号を取りたいといった場面がよくありました。 ここで、 array_search でって思うのですが、このような配列の中に配列がある多重配列構造の場合、思った通りの動作をしてくれません。 そこで、こんな関数を作ってみました。

function array_search_recursive($needle, $heystack)
{
    $keys = array();
    $func = function ($needle, $heystack) use (&$func, &$keys) {
        foreach ($heystack as $key => $val) {
            if (is_array($val)) {
                if ($func($needle, $val) !== false) {
                    $keys[] = $key;
                }
            }
            if ($needle == $val) {
                $keys[] = $key;
            }
        }
        return false;
    };
    $func($needle, $heystack);
    return $keys;
}

$needle に検索したいキーワード、 $heystack に検索対象の配列を指定すると、検索に引っかかる要素番号を配列で返します。 検索に引っかからなかった場合は、空配列を返します。

これ、同じことを考えておられた方がいて、こちらのQiitaの記事を参考にしました。

qiita.com

記事では検索キーワードに引っかかった最初の要素番号を返却していますが、仰っている通り、引っかかった全ての要素番号を返却した方が、汎用性が高くなりそうだったので、少し改良しています。 とは言え、クロージャに配列を参照渡しして、引っかかったら配列に詰めるってとこしか改良していませんが。。^^;


あと、 start_date2017-01-15 00:00:00 以降の要素番号とかを取りたいとかもありますよね。
そういう場合は、 array_search_recursive に関数を渡せるようにしてあげたら、もっとかっこよくなるかなぁ。 でも今回はそういう要件ではなかったので、脳みそ終了しました。


クロージャを代入した変数を参照渡しして、検索対象が配列だった場合、どんどん下層まで検索していくので、リークしないか気になるとこですが、そこそこ使えるのではないでしょうか
とは言え、 array_column で似たようなことできるよね〜とも思うのですが、実は開発環境は5.3みたいなとこあるのでは?

こういうの、Zephirで実装できたらかっこいいよねぇ

zephir-lang.com

最後に、いつぞやのPHPカンファレンスで見た尊敬するRasmus Lerdorfのインタビュー記事でも載せておこうかな〜

gihyo.jp

続 select結果をcsvでファイル出力

unionsep.hatenablog.com

先月、mysqlinto outfile でレコードをCSVファイルにサクッと取ってた記事を書きました
で、これは知ってるんだけど、今回はこれじゃダメなんだよな〜ってときに、どうしていたか思い出したので、記事書く気になりました

というのも、1. into outfile を使うと access denied になる場合も権限によってはあるね
2. mysqlsshトンネルの先にあって、その先のsshは許可されてないの・・・とか
3. ファイルにしたら巨大過ぎて、そんなの置くスペースないぜ!とか

過去を思い返すと、3. のパターンはあったな〜という思い出です
確かあれはサーバフルリプレイスしてたときに、あのvmが・・・

まぁ、というわけなんですが、手順はこんな感じ
1. mysql コマンドに -e つけてSQLを投げる
2. 1. はタブ区切りで出力されるから、 sed でタブをカンマに変換
3. csvファイルにリダイレクトする

1 . のSQLですが、文字列系はダブルクオーテーションで囲う

select
  concat('"', hoge, '"') as hoge,
  hige as hige,
  concat('"', huga, '"') as huga
from hogehoge

ワンライナーで書けるSQLだったら -e に続けて書いてもいいけど、 concat とか使ってたらそうも行かないので、SQLファイルにしておく
で、これでファイルを cat してクエリをmysqlに流し込んで、ファイルにリダイレクトしておく
この時、mysqlはタブ区切りに出力してくれるみたいなので、なんとなく拡張子を tsv にしてます

mysql -uroot hohoidb -e "`cat hogehoge.sql`" > ./hoge.tsv

2 . 3 . sed でタブをカンマに変換してリダイレクト

sed -e 's/\t/,/g' ./hoge.tsv > hoge.csv

てな感じでヌルっと出してます
まぁ、だいたいSQL用意してこんな感じでバーンとしてます

mysql -uroot hohoidb -e "`cat hogehoge.sql`" | sed -e 's/\t/,/g' > hogehoge.csv

あ、前回書き忘れたけど、csvmysqlにインポートするときはこんなSQL使ってます

LOAD DATA LOCAL INFILE '/home/ore/hogehoge.csv' INTO TABLE hogehoge FIELDS TERMINATED BY ',' ENCLOSED BY '"'

自分が完全に管理してるDBだったら、ALTER やっちゃえ!とかも思うんですが、「別チームんとこだしなー」「申請とかなー」とか思う時、エンジニアだったらあるはず!
そんなときに、サックリ自己解決したいときに使ってます
遅せークエリがボトルネックになってるんだけど、ローカルにデータ欲しいわーって思ったときとかかなぁ

今のところ、この2つの手法でデータが取れなかったことはないかなぁ


こういうのって、結構レガシーな手法で、知ってても自慢できるようなことじゃないんだけど、知らない人は知らない感じなのかなぁ
僕は結構頻繁に使うのですが、知らない人は、思いつかない限り使わないのかなぁと思ったりしてます
これらの気にいってるとこは、 select しか投げてないで!ってとこ
流石に select で壊れないっしょっていう安心感があるw
ブログ書いてるの、わりと会社の人にバレた気配がするので、役立ってくれたらうれしいなぁ〜