超PHPerになろう

Enjoy PHP Programming

沖縄で変なコードを満喫してきた #omosiro_code

沖縄在住の新進気鋭の超絶技巧プログラマであるりゅうくん主催の会に参加してきました。

connpass.com

ずっとRubyで変なことをやってるhanachinさん、TRICK 2018で5作品入選で鮮烈な存在感を発揮したぺん!(tompng)さん、元同僚でギークハウス沖縄オーナーのさぼさんを始めとする東京と沖縄の変なプログラマが参加してくださいました。

主催のりゅうくんによるまとめ

ryu1kun.hatenablog.com

MatzLisp

話の枕として持ってきた記事はこれです。

なんかもともとは2013年6月にノリで書いたコードっぽくて、それをTRICK 2015に応募したけど玉砕しました。

qiita.com

僕のコードはインデントをくふうしただけの一発ねたなのですが、その年にはk-tsjさんのめっちゃ完成度が高いコードがMatz Lisp Awardとして受賞してたので玉砕は完全に納得です。

あとこのファイル名なのですが、脆弱性として利用可能な拡張子偽装ができるWindowsの「仕様」として著名なものです。

f:id:zonu_exe:20190602225550p:plain

(let []) って書いてるあたりにClojureとして誤認されたさが垣間見えてますね。まともにClojureを書いてる人は絶対誤認しないですけど… このスクリプトの正しいファイル名は choise.[RLO]jlc.rb です。jlcが反転してcljに見えるんですね。UIぶっこわれてますけど。

一方でGitHub - Bug Bounty Program | HackerOneによれば、

Phishing using Unicode homoglyphs or RTLO characters

とのことで、これは脆弱性ではないらしいです。ふーーーん。

あと、いまこの記事を書くまで忘れてたんですが、上記の本のRubyによるScheme処理系をLispっぽいインデントで書いてました。完全に記憶から消し飛んでたよ。

github.com

Objective Programming Paradigm

自分が実装したDSLっぽいものだと最初期の2012年頃に書いたやつだった気がします。元ねたは当時たしか高専生だったLisper(Schemer)の某氏がTwitterとGistに投げてた言語を適当に再実装してみたやつです。未だにそうなのですが他人が文章で説明してるけど理解できないことが多いので、自分なりに再実装してみる癖があります。

メッセージ送信演算子の選択ですが、Rubyで再定義できる演算子のうち ** だけが右結合 だったので選びました。 (目的は美しいDSL設計ではなくObjective Programming Paradigmの概念実証だったので)

演算子の結合順序に関して気になるひとはこんな感じのクラスを定義してIRBとかで a * b * c とか a ** b ** c とかやって遊んでみてください。へんてこDSLを実装してどうしても右結合性が欲しくなったら ** 。おぼえておいてください。

class Foo
  def initialize(v)
    @v = v
  end

  def inspect
   "#<Foo, @v=#{@v.inspect}>"
  end

  def **(sbj)
    p [sbj, self]
    self
  end

  def *(sbj)
    p [sbj, self]
    self
  end
end

a, b, c = Foo.new(:a), Foo.new(:b), Foo.new(:c)

この元コードを書いた某氏は数年前にWeb上の一切合切の痕跡を断ってしまったのですが(そのあと一度だけ会ったことはある)、元気にしてるかなあ。奴とこの言語の話をしたことはないので、僕の解釈が合ってたのかはよくわからないです。

クラスローダーを使ったPHPメタプログラミング

PHPに詳しくないひとが多かったので前提から説明しました。

qiita.com

そしてPHPの全ファイルで機能する use の代替をクラスローダーで実現するやつです。

qiita.com

要はそれぞれの名前空間エイリアスを生やしまくることで実現します。実行時のパフォーマンスのペナルティは(たぶん)あまりないような気がします。ちゃんと計測してないけど。アルゴリズム改善の余地はあるかも。

PHPで無名再帰

PHPには use変数リファレンスを利用することで Closure再帰ができます。もちろんそれが常套手段なのですが、その制約を超えて再帰するテクニックを二つ紹介しました。

一つめにZコンビネータを利用する方法。これは不動点コンビネータの一つです。

ここで関数 f不動点とは、f(x) = x を満たすような x のことをいう。

不動点コンビネータとしてYコンビネータで、アメリカのシードアクセラレーター(ベンチャーキャピタル)として著名なY Combinator LLCの社名として有名です。

WikipediaにはJavaScriptでの不動点コンビネータの例があります。これはYコンビネータを正格評価の言語でも実行可能なように変形したものです。さらにそれをPHPで利用可能なようにしたものをライブラリにしてあります。

github.com

変なコード紹介の場としてはこっちを見せた方が盛り上がったかもしれませんね。

qiita.com

で、第三の無名再帰の方法として最近発見したのが無名クラスを利用する方法です。

はじめに、PHPのクラスに__invoke()マジックメソッドを実装すると、そのクラスのインスタンスオブジェクトを関数のように呼び出しができるようになります。そしてPHP7には無名クラスが導入されました。しかしながら無名関数には名前がないのに対し、無名クラスには名前があるのです。何を言ってるのか? __FUNCTION____METHOD____CLASS__ などのマジカル定数を出力するとわかります。

<?php

call_user_func(function(){ var_dump(__FUNCTION__); });
// string(9) "{closure}"

call_user_func(new class{ function __invoke(){ var_dump(__METHOD__); } });
// string(87) "class@anonymous\000/Users/megurine/Dropbox/junk/2019/06/qr_omSMR3.php0x10aede09e::__invoke"

これの性質を利用して無名クラスで call_user_func(__METHOD__) と書くと自分自身を呼ぶことができます。

無名関数の __FUNCTION__{closure}としか出力されないのに対して、無名クラスの __METHOD__ はファイル具体的なクラス名が含まれてます。……クラス名?そう、 class@ とか \0 とか / とか常識的なクラス名には含まれない文字がいっぱい入ってますがクラス名です。種明かしをすると無名クラスは無名ではなく、アクセスしにくい名前が付けられた、ただのクラスなのです。

正確には無名再帰ではないのですが実際無名関数と同じことが短く書けるので、まあまあ嬉しいですね。

ELVM

ELVMはShinichiro Hamaji(shinh)さんによる、多数の言語にコンパイルできるコンパイラインフラストラクチャです。

具体的な説明はshinhさんによるスライドと記事をお読みいただくのが良いです。

shinh.skr.jp

shinh.hatenablog.com

任意のプログラムをELVM IRで表現できれば、いわゆる難解プログラミング言語を含むバックエンドによって任意の言語に変換することができます。極めつけは8ccが移植されてるので、「Brainfuckで動くCコンパイラ」のような意味不明なものが実現されてます。実用性はともかくチューリング等価って本当に等価なんだなあと実感することができます。

前置きはさておき、2016年頃にPHPのバックエンドの実装を送ったときの昔話をしました。

github.com

一から書くのは難しそうだな…! と思ったのでJavaScript実装をPHPっぽく書き直すところから作業を始めました。

HTML+CSSチューリング完全

ぺん!さんのCSSだけで動くライフゲームが衝撃的すぎたので、(2012年に書いた記事なので自分の中では黒歴史に近いのですが)むかしの自分の記事をほじくり返しました。

f:id:zonu_exe:20190615022032p:plain

こんな関係です。

dt.hateblo.jp

あと、フォントの合字で計算できるのでは? って話が出てたので、二年ほど前ににせねこさんが作ってたOpenTypeで万年カレンダー(2019/06のように並ぶとフォントでカレンダーが表示される)を紹介しました。

nixeneko.hatenablog.com

PHP voidクイズ

せっかくなので一年ちょっと前にTwitterに上げたねたの話をしました。

詳しいことはQiitaに書きました。そちらには上記のクイズをさらに意地悪にしたバージョンがあります。

qiita.com

このクイズは会場ではhanachinさん一人が正解されました。PHP予約語って難しいですね…!

まとめ

まとめも何もないのですが、ぺん!さんやhanachinさんの超絶技巧を間近で目の当たりにしたのは楽しかったです。あとりゅうくんのライブ記号プログラミングやばい。

最近沖縄界隈がめちゃくちゃ活発なので、興味を持った人はギークハウス沖縄もよろしく!

blog.geekhouse.okinawa

あとギークハウス沖縄の近くのチャーリー多幸寿(タコス)がめちゃくちゃおいしかったです。

Laravel JP Conferenceに参加 + LTした #laraveljpcon

Laravel JP Conferenceに参加してきました。あと懇親会で飛び入りのLT発表もしました。

conference2019.laravel.jp

経緯

もともとは本セッションに応募したのですが落選したのでした。当日スタッフ募集の時期に体調を崩して居り予定が不透明だったので参加を見合せたのですが、PHPカンファレンス仙台の時期には献血できる程度には元気が恢復してたので応募することにしました。

当日スタッフの仕事

今回は受付担当の配属でした。9時30分の入場開始から一時間ほどと、12時から14時までの間に参加受付をしてました。

懇親会LTについて

思いついたのは前日準備の帰り道でした。スライド途中でも触れましたが、オリジナルなアイディアではなくMoneyForwardさんの「Rails 1.0のコードを読む」リスペクトです。

www.wantedly.com

が、本当に発表を決意したのは当日のノリで、11時頃にエントリーして昼食をいただいてから12時のシフトを計画通りにこなしてからスライド作りとコードリーディングを始めました。

補足点はniconareのコメントにいろいろ書きました。実際にはLaravel 3.2.0だけではなくGitHub上の最新のコードも読んだので勉強になりました。

発表のようすです。

追記

私は直接聴けなかったのですがフレームワークとの向き合いかたについて新原さん(id:shin1x1)が素晴らしい発表をされてたので読みましょう。

blog.shin1x1.com

PSRについて話したPHPカンファレンス仙台2019 #phpconsen

PHPカンファレンス仙台2019の参加者としての感想はMediumに書きました。

medium.com

さて、ここではPHPコードの実装者としての感想を書きます。

github.com

2019年1月31日10時20分 追記

真にPSRとHTTPの関係について学びたい型は、PHPカンファレンス2018の田中ひさてるさんの発表を読みましょう。

スライド

先に資料を紹介します。

実際は31分で121枚を話すという格好だったので説明不足だった点が多々ありますね。

PSRとPHP-FIGについて

だいたい去年「PSRの誤解」に書いた通りのことを話しました。

qiita.com

実装方針について

  • Composerのオートロード機能によってロードできるファイル構造にすること
  • PHP 7.3以上をターゲットにすること
  • PhanとPHPStanによる静的解析ができること

ほかにもいくつか気をつけてたことがあった気がしますが忘れました。PhpStormは使ってないです。 (PHPStanの方がエラーをいっぱい見付けてくれるので)

一方でPhanとPHPStanは実装方式が異なり、どちらかでしか検出されないような問題もちょくちょくありました。うれしいですね。

PSR-0について

わぁいPSR-0。あかりオートロードだいすき。

PSR-0といったら@Hiraku先生の記事が有名です。

blog.tojiru.net

まあ0だし、実装した順番もスライドを作った順番も最初です。廃止済みですけど。出落ち。

私自身は実用/非実用に拘らず実際何度もクラスローダーを実装してきたわけですが、PHPを構成するもののなかでも非常におもしろい領域であります。

遅延評価によるクラスロード機構は単純なファイルロード機能を提供するに留まらず、Go! AOPフレームワークなどはメタプログラミングによるアスペクト志向プログラミングの基盤を提供します。

さて実装についてですが、オーソドックスにディレクトリをセットするだけの方式でした。パフォーマンスはまったく気にしてないです。

PSR-1とPSR-2について

PSR-1(Basic Coding Standard)とPSR-2(Coding Style Guide)は、どちらもコーディング標準の勧告ですが、何が異なるのでしょうか。

PSR-1はPHPコードを相互運用するために必要な標準コーディング要素だとされます。つまりこれには、改行コードはUTF-8でBOMを含んではいけないこと、出力やPHP設定変更は宣言の読み込みとは厳格に区別されるべきこと、そしてオートロード可能であるべきことが含まれます。

一方でPSR-2は「このガイドによる利益は規則自体ではなく、規則を共有すること」それによって「複数の作者が認知の摩擦を減らすこと」にあるとされます。

そんなわけで、今回の発表にあたって私が提示したのはコーディングガイドとPHP-CS-Fixerのルールです。

私はどちらかといったらコードフォーマッタアンチなのですが(と、けっこう発言してる)、久々にやってみたら意外とたのしかったですね。

PSR-3について

実装順的には最後でした。Loggerも何度か実装したしMonologの拡張もやってたので。

そういや説明が抜けてましたが、出力フォーマットは俗に改行区切りのJSONと呼ばれるやつです。

こういうのはやっぱり何度か実装してて、WEB+DB PRESS Vol. 96あたりにも書きました。

サンプルコードはGitHubでも公開したので興味があったら読んでね。

github.com

PSR-4について

いままで忘れてたのですが、そういやComposerの話は2017年に既にやってました。

今回はPSR-0とは趣向を変えて、せっかくなのでクラスにしました。

PSR-5について

今回は実装してません。最後にちょっとだけ触れました。

くどいようだけど何度も書いたよ!!

gihyo.jp qiita.com qiita.com

WEB+DB PRESS総集編にも収録されてるので、買って読んでね。

amzn.to

あと、僕も2016年頃にPHPDocとか実行時情報をとってきていい感じにするやつを実装しようとしてたんですけど、PHPStanとかPhpactorがいい感じだったので最近手をつけてないですね。

github.com

おもしろオレオレCUIアプリのサンプルコードとしては結構おもしろいかもしれませんね。

PSR-6について

うーん… うーん… みゃーん。

PSR-7について

今回は実装してない。

qiita.com qiita.com

実装自体はやればできるのは知ってる。

PSR-8について

今回は実装を見送った。

qiita.com

PSR-9とPHP-10について

これなんでPHPの勧告として発効しようと思ったの 誰が喜ぶのかよくわからないし、誰も喜ばないだろうから ABANDONED (放棄) になったのかと。暇なら提案の経緯の調査とか、提案内容についていろいろ考察してみたい気もするけど、今回は余裕がなかった。

PSR-11について

実装してないけど、発表当日の朝に読んでた。提案されてるInterfaceの範囲では ContainerInterfaceget()has() を持ってるだけなんだけど、例外に ContainerExceptionInterface を実装したクラスを送出することを定めて一貫したエラーハンドリングができるようになって嬉しい! ってのが提案の肝なのだと理解。PSR-11メタドキュメントまで読むと、いろいろ共通要素を議論したけどここまでしかまとまらなかったんだなって感じがして赴き深い。

マジックメソッド__get() とか __isset() が定義に含まれてなかったのは、やっぱり、うーん。[REVIEW] PSR-11 Container Interfaceのトピックは長くて全部は読んでないんだけど、このやりとりの後ならPHP-FIGに嫌気が差すのも、まあ、わかる。 これってそこまでして相互運用したいものだったの?

PSR-12について

現在DRAFTだけど、ずっと作業は進んでるみたい。PSR-2以降に追加された文法や、PSR-2では言及されなかったケースについての定義もある。

PSR-2で言及されなかったパターン、こんなの。

<?php

do {
    // structure body;
} while (
    $expr1
    && $expr2
);

あと、PSR-12では名前空間の深度についても言及がある。

Compound namespaces with a depth of more than two MUST NOT be used. Therefore the following is the maximum compounding depth allowed:

<?php

use Vendor\Package\SomeNamespace\{
    SubnamespaceOne\ClassA,
    SubnamespaceOne\ClassB,
    SubnamespaceTwo\ClassY,
    ClassZ,
};

And the following would not be allowed:

<?php

use Vendor\Package\SomeNamespace\{
    SubnamespaceOne\AnotherNamespace\ClassA,
    SubnamespaceOne\ClassB,
    ClassZ,
};

せ、攻めるなあ。

PSR-13について

PSRの最悪中の最悪。こんなのに賛成票を投じた奴は誰だと叫びたくなる。

RFC 5988 - Web Linkingの抽象化だってのはわかる。わかるよ。

www.php-fig.org

isTemplated() って何だこれ? RFC6570 URL Templateは可能性があるし混ぜたくなったのはわかるよ? 私だってURL組み立てのロジックはいろいろ書いてきたし、URL Templateのユースケースだってすぐに想像がつく。でもなんでこれを型レベルで分けるんじゃなくてオブジェクトの状態として持たせようとした? 提案者自身の概念実装のはずのCrell/HtmlModel: Domain value objects for modeling HTML pagesだって使ってないじゃねえか。「たぶんあったら便利だと思うから仕様に入れておこう」って発想は本当に危険で、このせいでPSR-13がゴミになってしまったと感じる。

PSR-13の数少ない実用的な利用者はSymfony\WebLink)なのだけれど、ここでは isTemplated() をこのように処理する。

HttpHeaderSerializer.php#L35-L38

        foreach ($links as $link) {
            if ($link->isTemplated()) {
                continue;
            }

テンプレートだったら完全に無視!!

そりゃそーだ。 テンプレートに当て嵌められるべき変数なんて、ここでは誰も知らないんだから……。冷静になってみてほしいんだけど、ほんと $link->isTemplated() === true だったときに何かできるパターンなんて本当にあるの?

それにまして、PHP-FIGが提供するphp-fig/link-utilパッケージの中でもlink-util/TemplatedHrefTrait.phpの実装がつらい。

    private function hrefIsTemplated($href)
    {
        return strpos($href, '{') !== false ||strpos($href, '}') !== false;
    }

これは新卒がやったらていねいに優しく諭すけど、この実行時の雑すぎるパースでの判定はとても危険。プロが書くコードじゃない、とまで言いたい。

そのほかこの仕様の是非を検討すると、さまざまなRFCや標準、PHPの実装までもが登場してきて非常に厄介なので、ここではこの程度で収めておきます。

PSR-14について

おもしろそうだけどPSR-13に深入りしてたらどんどん時間が溶けてしまったので全然把握してない。

PSR-15について

まだ実装してない。シングルパスのミドルウェアなのは知ってる。

PSR-16について

簡単っぽいから実装するつもりだったけどPSR-13に深入りしてたら時間が溶けたので、まだ実装してない。

PSR-17とPSR-18について

どっちもHTTP関連だけど、案の定僕はまだ実装してない。ふしぎなことにPSR-18には(PHP-FIGを抜けたはずの)GuzzleのJeremy LindblomとMark Sagi-Kazarが作業グループに参加してそう。PSR-17には参加してなそう。ふしぎ。

PSR-19について

PSR-5から分離された。どうなるんだろう、わからん。

まとめ

雑にまとめたけど、めっちゃ徒労感がある。ぜいたくな時間を送ってしまった@tadsanにFANBOXで課金して励ましのおたよりを送ろう。

www.pixiv.net

PHPerKaigi 2018前夜祭でテスティングフレームワーク設計の話をします

こんにちはこんにちは、@です。 PHPerKaigiペチパーカイギ 2018は今回初開催される新しいカンファレンスです。

phperkaigi.jp

主催者はiOSDC主催の長谷川さん(@)です。以前からあるPHPカンファレンスが参加無料・大規模会場・並列トラックである*1のに対して、PHPerKaigiは有料・1ホールで開催されるようです。今回のカンファレンスがうまくいくようなら、春のPHPerKaigi、夏のPHPカンファレンスぺちこん、みたいな感じで続いていくのではないでせうか。たぶん。

発表について

niconare.nicovideo.jp

さて、いままで私はカンファレンスでのセッション発表では実用的(?)な内容を、勉強会での発表やLTでは実用性を気にせず好きな話をするなどといった行為を繰り返してきたのですが[要出典]、今回はその中間を狙った感じではあります。

長谷川さんからは内容は詰め込みすぎないようにすべしとアドバイスをいただいたので、発表前に予習用コンテンツを用意して当日はそのエッセンスを30分で話すことになる気がします。タブンネ

発表の背景

今回の発表の正式タイトルは「PHPでテスティングフレームワークを実装する前に知っておきたい勘所」ですが、これはCFP応募前にあったいくつかの案を統合したものです。

いろんな問題意識はあるのですが、時間的に30分でこれらを話すのは とうてい無理 なので、もちろん全部の内容は話しません。 発表だけを単体で聴いても納得感のある話にできる… といいなあ。 (未来の自分に期待)

なぜPHPerKaigiに参加するのか

まだ当日のタイムテーブルは出てないのでどんな発表が聴けるのかすべては知らないのですが、PHPカンファレンスからさらに煮詰めた「濃い」PHPerぺちぱーが集まってるようなので、最前線でPHPを活用してるひとたちと話してみたいひとにとってはチケット代の分の損はさせない内容になるはずです。

私とおなじく前夜祭では数々の名プレゼンを発表してきた@さんも話します。わくわくですね。前夜祭は金曜日ですが、本篇だけではなく前夜祭から来るべし。 uzulla.hateblo.jp

チケットを購入しよう

当日ふらっと遊びにいけるぺちこんとは違って、PHPerKaigiは事前にチケットを購入する必要があります。

passmarket.yahoo.co.jp

ちなみに長谷川さんはbuildersconの運営スタッフでもあり、builderscon tokyo 2017では最高の名札を完成されていらっしゃいました。

speakerdeck.com

今回もアイコン入りの名札になるようですが、印刷の都合上2月上旬までに購入する必要があります。くれぐれも、くれぐれも早めに購入のこと。

最後に

いいですか、PHPerKaigi 2018は3月10日(土)、前夜祭は3月9日(金)の開催です。会場の練馬Coconeriホールで@tadsanと握手!

PHP開発のためのEmacs 2017

ピクシブ開発基盤チームの超PHPerことうさみ(@tadsan)です ヾ(〃><)ノ゙

この記事はピクシブ株式会社 Advent Calendar 2017の16日めです。

私はVimのウルテクで華麗にリファクタリングとかよりは平凡な環境で地味に開発していくのが得意ですが、そのなかでも開発を効率化するための手段を紹介します。

背景

ピクシブ社内にもさまざまな言語のプロジェクトがありますが、そのなかでもpixivは2007年から続く社内最大のPHPプロジェクトです。その経緯から、pixivの普段の開発環境は共用のGNU/Linuxサーバと共用のMySQLなどのデータストア上に展開されます*1

社内ネットワークのDNSApache HTTP ServerのVirtualDocumentRootワイルドカード証明書 (Common Nameに*を含んだサーバ証明書の通称)の組み合せにより、開発者個人ごとに独立したTLS通信可能なPHP動作環境を持てるため、非常に快適です。

ただし問題はコーディングと動作確認の作業です。 pixiv開発者は、大別すると以下の手段のどれかを選ぶ必要があります。

どの手法にも一長一短があります。

大雑把に一言でまとめると、Aは「ツール/エディタが限定されるが同期作業は不要」、Bは「ツールの自由度は上がるがファイルの同期作業が必要」、Cは「ツールの自由度があり明示的な同期作業も不要だがファイルの読み書きが遅い」など、それぞれ一長一短の問題があります。

私の入社時はAは比較的多かった印象です。一方で私は入社時からCです。SSHFSにはネットワーク接続がタイムアウトしたときにファイルシステムがアンマウントされるなどの問題がありますが、EmacsのTRAMPではそのような問題は起こりません

(Dockerなどで各自のローカルマシンに開発環境を構築するといった方法も想定可能ですが、現状は開発用データの量と管理方法、関連ミドルウェアの問題などから困難があります)

2016年のEmacs

PHP開発のためのEmacs 2016 (pixiv <3 Emacs)のまとめから抜萃します。

去年の記事を書いてからの報告なのですが、phpunit.elの共同メンテナとしてコミット権をいただき、PHP Modeのメインメンテナを引き継ぎました

2017年のEmacs開発環境

ここからは、前回の記事からアップデートされたもの、多用するようになったもの、新規に作成したものを中心に紹介します。

GitHub: emacs-php

github.com

GitHubemacs-phpのOrganizationを作成しました。それに伴って、拙作のPHP関連パッケージはこちらに移動しました。

.dir-locals.el

.dir-locals.elディレクトリで共通の変数設定するための仕組みです。

私はpixivのホームディレクトリに置いてる.dir-locals.elファイルはこんな感じです。

((nil . ((pixiv-dev-working-dir . "~/work/pixiv")
         (pixiv-dev-remote-working-dir . "/scp:x123:/home/tadsan/pixiv/")
         (pixiv-dev-repository-web . "https://gitlab.example.com/pixiv/pixiv")
         (phpunit-root-directory . "/scp:x123:/home/tadsan/pixiv/")
         (copy-file-on-save-dest-dir . "/scp:x123:/home/tadsan/pixiv/")
         (copy-file-on-save-ignore-patterns . ("/\\.dir-locals\\.el\\'" "/\\.git/" "/tpl_c")))))

なんか同じような記述を何回か繰り返してしまってますが、それぞれ独立したパッケージに対する設定なので仕方ないですね。 (いい感じに統合したいかもしれない)

php-mode.el

github.com

最近、メインメンテナを引き継ぎました。古いバージョンをずっと使ってる型も、地味な修正とか変更とかPHP7系の文法への対応も入ってるので、最新版にアップデートしてください。

現行バージョンは今月(2017年12月)にリリースした 1.18.4 です。

copy-file-on-save.el

github.com

私は入社して以来、仮想ファイルシステムとしてTRAMPを使ってSSH先の共有サーバーのファイルを読み書きしてましたが、ストレスを感じる要因として読み書きの遅さがあります。

また私はPhpStormも使って作業したいので、観念してローカルディスクにソースコードgit clone することにしました。そこで登場するのがPhpStormのAuto Deployment機能です。

こりゃ便利だ! インスパイアされた私は、さっそくEmacsに実装しました。

仕様はいたって簡単で、バッファのファイルを保存した直後に 別のパス にコピーします。Emacs上で別のパスをコピーする際、そのファイルパスが/ssh:/scp:などから始まるものならば、Emacsは透過的にSSH/SCPを使ってファイルを転送してくれます。

((nil . ((copy-file-on-save-dest-dir . "/scp:x123:/home/tadsan/pixiv/")
         (copy-file-on-save-ignore-patterns . ("/\\.dir-locals\\.el\\'" "/\\.git/" "/tpl_c")))))

今年に入ってからの開発スタイルの大きな変化は、このパッケージのお蔭でTRAMPを多用しなくなったことです。また、PhpStormも併用しやすくなりました。

ちなみにPhpStormは多段SSHの際に特別な工夫が必要ですが、Emacsにはそのような欠点はありません。

このcopy-file-on-saveは、保存時にファイルをコピーするだけの単機能に徹します。そのため複数ファイルやディレクトリの再帰的なコピー・同期は対応しません。そのため、作業方法によってはローカルとリモートのファイルに齟齬が生じます。 (特にローカルで git pull などでほかの開発者の変更を取り込んだ後や、git checkoutで別の作業ブランチに移動した際などです)

そのような場合は、私はSSHでログインしてリモートでも手動で明示的にgit pullgit checkoutgit reset --hardgit clean -f をすることで対処します。(私は使ってませんが)Emacsだけで完結させたい場合にはemacs-ssh-deployパッケージがあるようです。

phpunit.el

github.com

去年に引き続きですが、最近また機能追加したのでお知らせします。

phpunit.elは以前からリモートサーバー上での実行に対応してます。ところが先述したように、ファイルはローカルのファイルシステムにあるが、テストの実行環境はリモートのファイルシステムにある… といった場合に困ります。

phpunit-root-directory を設定することで、特定のディレクトリでPHPUnitを実行できるようになります。

((nil . ((phpunit-root-directory . "/scp:x123:/home/tadsan/pixiv/")))

phan.el

github.com

PhanはPHPの静的解析ツールですが、その出力ログを読みやすくカラーリングするメジャーモードです。Phanの出力ファイル名は規定されてませんが、このモードでは phan.log の名前で書き出されることを期待してます。

f:id:zonu_exe:20171215171419p:plain

flycheck-phanclient

github.com

Phanをデーモンとして起動することで、メソッドの呼び出しミスや型の不整合などを編集中に検出できます。手動で起動する必要があるのはめんどくさいですが、これをうまく活用すればPhpStorm並に確実にエラーを補足しながらコーディングできるようになります。やりましたよ。

f:id:zonu_exe:20171215195719p:plain

近い将来もっと手軽に手間を減らせるようにしたいですね。 ヾ(〃><)ノ゙

yasnippet-snippets

github.com

ないよりましレベルの実装ではありますが、構文やPHPDocタグの挿入補完ができます。

なんかスペースのとりかたとかファイル名の決定の仕方とか改善の余地はあるので、ぼちぼちやっていきます。

f:id:zonu_exe:20171215200815g:plain f:id:zonu_exe:20171215200838g:plain f:id:zonu_exe:20171215200841g:plain

php-runtime.el

github.com

Emacs LispからPHPコードをevalできます。実用ライブラリです。これはPHP Modeとの統合予定があります。

jetbrains.el

github.com

Emacsで編集中のプロジェクトでPhpStorm、IntelliJ IDEAなどのIDEを起動したり、編集中のファイルをIDE開き直すことができるコマンドです。べんり。

pixiv-dev.el

github.com

基本機能は去年と変りませんが、設定すればシェルはリモート実行できるようになりました。

まとめ

最後になりましたがピクシブ株式会社はエディタ不問企業です!! 言語もPHPだけじゃなくてRubyScalaもGoもありますし、Jetbrains IDEも当然会社の経費で購入します!

recruit.jobcan.jp

そんなpixivを改善したり新規サービスを開発する仕事に興味があったり、暇なときにpixivに遊びに来たいとか思った型はTwitterとかで声かけていただければ対応します ヾ(〃><)ノ゙

不問とは言ったがVimに強いひととかEmacsに強いひととかもっと入ってくれると心強いので、いっぱい入社してほしい!!!!

twitter.com

*1:ユニットテスト用のDBは別で、これらはユーザーごとにプロセスが起動されます

ODCとLLイベントでPHPの話をしてきた×3

2017年8月19日と8月20日の両日にOpen Developers Conference 2017 TokyoLLイベント2017ことLearn Languages 2017 in ODCに参加してきました。

www.ospn.jp PROGRAM – LLイベント2017

プログラミング環境としてのPHP

出張版 PHP勉強会@東京の枠で話すひとが決まってなかったので、話すことにしました。PHPの初学者向けの内容を意図して書きました。

内容自体は結構前からぼやっと思ってたことをまとめてみた感じです。個々の内容とかは既出の焼き直しがほとんどかな。

qiita.com

2017年のPHP開発にEmacsで勝つ

このLTは2週間以上前に申し込んでたのですが、タイトルは適当に決めて、内容は当日適当に書きました。応募した当初の構想は完全に忘れましたが、たぶんまったく別物です。

基本的には、いつも思ってることをはっきりと書きました。「つよいエディタ よわいエディタ そんなのひとのかって。ほんとうに つよい プログラマーなら すきな エディタで かてるように がんばるべき」などと私は思ってるのですが、仕事でやる以上は成果を出せると良いです。……みたいなところで、戦闘機と竹槍の話。あなたの使ってるエディタで進捗出せてますか? ひょっとしてエディタへの拘りは、単に馴れを言ひわけに研鑽を怠ってるだけで、時間を浪費してるだけなのでは???

といった疑問からのPhpStormの分析と、Emacsを使ってPhpStormを上回る進捗を出せるようにするための試みを始めよう、といった意思表明のつもりです。問題意識としてはずっと前からあったことなのですが、最近になってRequest: The Future of PHP Mode (TL;DR Near Bottom) : emacsのようなことがあって、未来について悩むことになった恰好です。

github.com

そんなこんながあって、GitHubリポジトリを作りました。EmacsPHPを開発するときに困ってることとか、ここどうにかならないかといった要望などがあればissueを作ってください。わかる範囲で解決策を提示するか、必要なものを実装していきます。

あなたの身の周りにEmacsPHPのコードを書いてるひとが居たら連絡してくれると嬉しいです。

PHPにおけるメタプログラミングの温床

LLイベントは2003年から毎年開催されるプログラミング言語を横断したコミュニティによるカンファレンスです。LL Saturday(2003)LL Weekend(2004)と続き、毎年名称が変更されるため「LLイベント」と総称されます。

とは言っても私はLLまつり(2013)に初めて参加したきり、ご無沙汰でした。今回参加しようと思ったのも上記のODCに参加してたからで、それも当日朝に ここしばらく寝不足が続いてたので 行こうか躊躇してたのですが、結果としては行って大正解でした。 当日のノリでLTに応募するところまで含めて

そんなわけで、以前からタイトルだけ構想を温めてた*1メタプログラミングの話です。

前から思ってたんですけど、メタプログラミングの定義ってなんなんですかね? みたいなことを深く悩むとインタプリタとの差、ただの実行時処理との差はいったい何だときりがないのですが、まあ単なる函数呼び出しとは異なる一見して不可思議な現象を起せればそれはメタプログラミングと言って良いのではないかと思ってます*2

めんどくさい(し時間がなかった)ので完全に説明を省いてるのですが、このスライドの内容は、ひとつ残らずPHPマニュアルに載ってます。つまり仕様内の挙動です。

参考までに、私がいままでに書いた記事では以下のものがメタプログラミングの応用です。

qiita.com qiita.com

予告

PHPカンファレンス2017 - #phpcon2017で話します。今度はエラー処理の話をするよ。

*1:タイトルだけ前から決めてただけでどこで発表するかも考へてなかったし、そもそも1文字たりとも書いてませんでした。

*2:言語仕様を熟知してる側にとっては「ちっとも不思議じゃねーよ」反論が成り立つので、あくまでアバウトで相対的な定義です。

オブジェクトをいい感じに複製(クローン)する [myclabs/deep-copy]

「オブジェクトの複製」には本質的に厄介な問題をいくつも含みます。特に、オブジェクトの再帰的な複製(ディープコピー)には直感的ではない動作や単純ではない依存関係が発生しがちです。myclabs/deep-copyはそれをいい感じに解決してくれます。

公式サイト myclabs/DeepCopy: Create deep copies (clones) of your objects
概要 Create deep copies (clones) of your objects
パッケージ名 myclabs/deep-copy
作者 My C-Labs
mnapoli (Matthieu Napoli)
ライセンス MIT License
バージョン v1.6.1 (2017-04-12) Packagist

インストール

Composerでインストール可能です。

composer.phar require myclabs/deep-copy

配列のコピー

文字列および配列を含む全てのPHPの値と変数はコピーオンライト(CoW)と呼ばれる最適化戦略がとられます。

<?php

$a = ['a', 'b', 'c'];
$b = $a;
var_dump($a === $b);
//=> true

$a[1] = 'X';
$b[] = 'ZZZ';
var_dump($a);
//=> ["a", "X", "c"]
var_dump($b);
//=> ["a", "b", "c", "ZZ"]
var_dump($a === $b);
//=> false

$b = $aが実行された状態では両者は同じ配列ですが、破壊的操作を行ったタイミングで別々の道を歩み始めます。つまり、二つの配列を複製したければ、PHPでは別の変数に代入してやるだけで十分なのです。

一方でRubyは、このような振舞はしません。

a = ['a', 'b', 'c']
#=> ["a", "b", "c"]
b = a
#=> ["a", "b", "c"]

a[1] = 'X'
b.push('ZZZ')

p a
#=> ["a", "X", "c", "ZZZ"]
p b
#=> ["a", "X", "c", "ZZZ"]
p a == b
#=> true

このように、Rubyでは単に別の変数に代入しただけでは値が複製されたことにはならず、終始において一蓮托生です。

オブジェクトのコピー

次に、stdClassを使ってオブジェクトのコピーについて実験してみます。

<?php

$book = new \stdClass;
$book->name = "共産党宣言";
$book->authors = ["カール・マルクス"];

$book2 = $book;

var_dump($book === $book2);
//=> bool(true)

$book2->authors[] = "フリードリヒ・エンゲルス";
$book->authors[0] = "Karl Heinrich Marx";

var_dump($book === $book2);
//=> bool(true)

配列と同じようにやったつもりですが、違った結果になりました。

PHPでオブジェクトを複製するには別の変数に代入するだけでは不十分で、$copy = clone $obj;のようにcloneキーワードを用ゐる必要があります。このことはPHP: オブジェクトのクローン作成 - Manualに説明があります。

再帰的なオブジェクトのコピー

ここまででオブジェクトの複製について基本的な理解が得られたところで、ここからはGitHubmyclabs/DeepCopyのREADMEから画像を拝借しつつ説明を進めていきます。

アルファベット1文字のクラスでとてもわかりにくいのですが、以下のようなクラスがあったとします。

<?php

class A
{
    /** @var string */
    public $name;
    /** @var B */
    public $b;
    /** @var C */
    public $c;

    public function __construct($name)
    {
        $this->name = "Hi, I am {$name}.";
    }
}

class B
{
    /** @var string */
    public $name;
    /** @var C */
    public $c;

    public function __construct($name)
    {
        $this->name = "Hi, I am {$name}.";
    }
}

class C
{
    /** @var string */
    public $name;

    public function __construct($name)
    {
        $this->name = "Hi, I am {$name}.";
    }
}

これらのクラスを使ってコードを書いてみますね。

<?php
$c = new C('Charlie');

$b = new B('Bob');
$b->c = $c;

$a = new A('Alice');
$a->c = $c;
$a->b = $b;

これをグラフにすると以下のような状態です。

f:id:zonu_exe:20160811234717p:plain

次に、これを「まるごと」複製してみたいと思ったとします。まるごと複製とは、ここでは$a$a2に複製したとして、$a2->cのオブジェクトを変更したとしても$a->cのオブジェクトには反映されたくないといふことだとします。$a->bについても同様。

<?php

$a2 = clone $a;
// => A {#192
//      +name: "Hi, I am Alice.",
//      +b: B {#200
//        +name: "Hi, I am Bob.",
//        +c: C {#173
//          +name: "Hi, I am Charlie.",
//        },
//      },
//      +c: C {#173},
//   }

ほうほう。では$a2->c-nameに別の文字列を入れてみたらどうかな。

<?php

$a2->c->name = 'Charlotte';
// => "Charlotte"
$a
// => A {#178
//      +name: "Hi, I am Alice.",
//      +b: B {#200
//        +name: "Hi, I am Bob.",
//        +c: C {#173
//          +name: "Charlotte",
//        },
//      },
//      +c: C {#173},
//    }

$a2
// => A {#192
//      +name: "Hi, I am Alice.",
//      +b: B {#200
//        +name: "Hi, I am Bob.",
//        +c: C {#173
//          +name: "Charlotte",
//        },
//      },
//      +c: C {#173},
//    }

$a->cの方も変っちゃったよだめじゃん…

これはグラフにすると以下のような状態です。

f:id:zonu_exe:20160812002853p:plain

__clone()大戦

今度もPHP: オブジェクトのクローン作成 - Manualを参考に__clone()マジックメソッドを定義してみることにします。またクラス定義ですが、変更点は__clone()が増えてるだけです。

<?php

class A
{
    /** @var string */
    public $name;
    /** @var B */
    public $b;
    /** @var C */
    public $c;

    public function __construct($name)
    {
        $this->name = "Hi, I am {$name}.";
    }

    public function __clone()
    {
        $this->b = clone $this->b;
        $this->c = clone $this->c;
    }
}

class B
{
    /** @var string */
    public $name;
    /** @var C */
    public $c;

    public function __construct($name)
    {
        $this->name = "Hi, I am {$name}.";
    }

    public function __clone()
    {
        $this->c = clone $this->c;
    }
}

class C
{
    /** @var string */
    public $name;

    public function __construct($name)
    {
        $this->name = "Hi, I am {$name}.";
    }
}

よし、これなら動くかな…?

<?php

$a2 = clone $a;
$a2->c->name = 'Charlotte';
// => "Charlotte"
$a
// => A {#209
//      +name: "Hi, I am Alice.",
//      +b: B {#218
//        +name: "Hi, I am Bob.",
//        +c: C {#213
//          +name: "Hi, I am Charlie.",
//        },
//      },
//      +c: C {#213},
//    }

// >>> $a2
// => A {#207
//      +name: "Hi, I am Alice.",
//      +b: B {#199
//        +name: "Hi, I am Bob.",
//        +c: C {#210
//          +name: "Hi, I am Charlie.",
//        },
//      },
//      +c: C {#197
//        +name: "Charlotte",
//      },
//    }

今度は$a->c$a2->cの状態はきっちり切り離せたようです。しかし今度は$a->c$a->b->cの関係が奇妙なことになってしまひましたね。

f:id:zonu_exe:20160812002807p:plain

やれやれ…

myclabs/deep-copyの出番だ

__clone()では問題が解決しないことがわかったので、クラス定義は元の状態に戻します。

<?php

use DeepCopy\DeepCopy;

$c = new C('Charlie');

$b = new B('Bob');
$b->c = $c;

$a = new A('Alice');
$a->c = $c;
$a->b = $b;

// Deepcopyを使ってオブジェクトのコピー
$deepCopy = new DeepCopy();
$a2 = $deepCopy->copy($a);

$a2->c->name = 'Charlotte';
// => "Charlotte"
$a
// => A {#190
//      +name: "Hi, I am Alice.",
//      +b: B {#192
//        +name: "Hi, I am Bob.",
//        +c: C {#186
//          +name: "Hi, I am Charlie.",
//        },
//      },
//      +c: C {#186},
//    }

$a2
// => A {#188
//      +name: "Hi, I am Alice.",
//      +b: B {#165
//        +name: "Hi, I am Bob.",
//        +c: C {#202
//          +name: "Charlotte",
//        },
//      },
//      +c: C {#202},
//    }

やった、ばっちりだ!

f:id:zonu_exe:20160812005805p:plain

このライブラリは利用すべきなの?

オブジェクトの複製にどのような手法をとるのが望ましいかは、場合によります。そもそも再帰的な複製(ディープコピー)は必要なく、浅い複製(シャローコピー)で十分だといふケースもあります。myclabs/deep-copyは直感に反しないディープコピーの手法として、良い選択肢のひとつです。

上記のようなオブジェクトの複製に伴った理窟がいまひとつ理解できない場合は、オブジェクトの利用を諦めて連想配列に落としてみるといったこともPHPらしい、割り切った解決法ではあります。筆者が業務として開発に携るpixiv.netでは、オブジェクトはほとんど利用しません。