超PHPerになろう

Enjoy PHP Programming

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では、オブジェクトはほとんど利用しません。

すごいPHPerになるためにいろいろやってた

すごいPHPer(ぺちぱー)になりたいと思っていろいろやってたんですが、ちょっと慌しいのが重なって報告わすれてました ヾ(〃><)ノ゙☆

PHP BLT #3でしゃべってきた

PHP BLT #2は参加できなかったけど、#3には参加してました ヾ(〃><)ノ゙

PhpStormはすごいんですけど、Emacsの実力もこんなもんじゃないんで、がんばっていきたい…!

インスパイヤされて掲示板を作りたくなってた

qiita.com

しばらく更新できてなかったけど、そろそろ再開できるかな…!

WEB+DB Press Vol. 92にHTTPの話を書いた

gihyo.jp

PHPからHTTPを叩く方法はいろいろあるんだけど、とりあへずguzzle/guzzle: Guzzle, an extensible PHP HTTP clientの6.xを使っとけばいいんじゃないの、みたいなことを書きました。

PHPカンファレンス北海道2016でComposerについてしゃべってきた

関係者から招待いただいたとかじゃなくて、インターネットで見掛けたのでCFPを送ったら通ってしまったので、喋らせていただいた次第です。

phpcon.sapporo-php.net

私は地元が北海道だったので、大学生じゃなくなってぷらぷらしてた時期にはRuby札幌とかPython札幌で活動してた頃もあるので、かなり懐かしい感じでしたね!

PHP BLT #4でしゃべってきた

この話に関してはQiitaあたりに、もうちょっと詳しく書きます!

適当なライブラリ

クラス名のエイリアスはるやつ

github.com

なんかファイルごとにuseとかするのめんどいじゃん? ってときに使へるやつですね。動作原理を解説してもいいんですけど、ずこーって感じがします。

クラスの擬似動的ロード/モンキーパッチをするやつ

二ヶ月くらゐ前に書いてGitHubに上げてた気がするんだけど、幻覚だったようだ… 動いてはあるので、近々ちょっと直して公開するかも。

この期間での知見

やりたいことなんでも手をひろげていった揚句にどれも完成度いまいち、みたいなのって割と最悪に近いので、ほどほどにがんばります ヾ(〃><)ノ゙

すごいPHPerになるためにオートローディングについて調べて記事書いた

Composerとかでインストールしたライブラリではなにげなく使ってる名前空間オートローディングについて詳しく書いてます。まあ、PHP: 名前空間 - Manualとか読めば書いてあるんですが、Composerとかオートローディングと組合せてきちんと説明してる記事はWebでもあんまりないと思ふので、会社での布教などにぜひご利用くださいヾ(〃><)ノ゙

gihyo.jp

PHP BLT #1で「たのしい独自フレームワーク」を発表しました

PHP BLT #1PHP界隈のTipsについて5分で話すLT大会です。

phpblt.connpass.com

コンセプトとしてはもっと気軽な内容を意図されてたはずなのですが、このスライドでは20分枠で話すような内容を説明不足に任せて駆け足でLTにした感じです!

www.slideshare.net

独自フレームワークの設計の具体的なところについては、このblogでもきちんと書いていきたいですね ヾ(〃><)ノ゙

User-Agent文字列を解釈するには [woothee/woothee]

User-Agent/UA文字列はブラウザなどのユーザーエージェントが自己申告する識別子のことです。この値は歴史的経緯から複雑怪奇で、付け焼刃の実装では正確な判定が困難です。

Project Wootheeプログラミング言語に依存しないUA文字列パーサーを開発するプロジェクトです。言語を横断してテストを共有して居り、同じバージョンを利用すれば、どの言語実装を利用しても同じ結果が得られることが保障されます。

公式サイト Woothee: User-Agent parser/classifier for multi languages by woothee
概要 Cross-language UserAgent classifier library, PHP implementation
パッケージ名 woothee/woothee - Packagist
作者 tagomoris (TAGOMORI "moris" Satoshi)
yuya-takeyama (Yuya Takeyama)
ライセンス Apache License, Version 2.0
バージョン v1.2.0 (2015-08-14)

インストール

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

composer.phar require woothee/woothee

UA文字列とは

以前、勉強会で発表するために作成したスライドを紹介します。

www.slideshare.net

ブラウザ業界は常に渾沌として居り、現在はMozilla Firefoxを除く多くのブラウザは「WebKitだ」と名乗ります^1

# Chrome 
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2560.0 Safari/537.36
# Mozilla Firefox
Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:42.0) Gecko/20100101 Firefox/42.0
# Safari
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/601.2.7 (KHTML, like Gecko) Version/9.0.1 Safari/601.2.7
# Microsoft Edge
Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586
# Opera(OPR)
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36 OPR/33.0.1990.58

これらの変化する情勢について自前で解釈するのは不毛なので、WootheeのようなUAパーサーを利用することは有効です。

UAを分岐する目的

とは言っても現在の多くのブラウザの挙動は標準化されて居り、サーバーサイドのレベルでブラウザの種類そのものを判定することが必要な処理は、ほとんどありません。

実用例を挙げるならば、次のような用途があります。

  • UAスマートフォンかどうかを判定し、モバイル向けサイトにリダイレクトする
  • セキュリティを目的に、著しく古いブラウザ(IE8以下など)での利用を抑制する
  • TwitterFacebookクローラーなどに、TwitterカードOGPを出力する
  • OSごとに異なるファイルをダウンロードさせる

それ以外の目的で不用意に判定を追加すると、将来登場する新しい環境で意図しない挙動をするおそれがあるので、本当に必要な処理かどうか検討してください。

また、GooglebotなどのUAに対して通常のブラウザと著しく異なるレスポンスを返した場合、検索結果に想定できない悪影響がある場合があります。単にGooglebotからのアクセスを拒否することは問題ありませんが、その場合はrobots.txtを利用することを推奨します。詳細はGooglebot - Search Console ヘルプを参照ください。

UAの取得方法

標準のPHPでは、以下のような方法でUser-Agent文字列を取得できます。

<?php
$UA = isset($_SERVER['HTTP_ USER_AGENT']) ? $_SERVER['HTTP_ USER_AGENT'] : '';

$woothee = new \Woothee\Classifier;
$result = $woothee->parse($UA);

ただし、Webフレームワークを利用する場合はテスタビリティを確保するため、フレームワーク推奨の方法で取得しましょう。

以下はLaravelのファサードを利用した記法です。

<?php
$UA = Request::server('HTTP_USER_AGENT');

$woothee = new \Woothee\Classifier;
$result = $woothee->parse($UA);

解析結果

本稿執筆時点でのWoothee 1.2.0では、以下のような出力が得られます。

<?php
$woothee = new \Woothee\Classifier;

# Twitterのクローラー
$woothee->parse("Twitterbot/1.0");
# => [
#     "name" => "twitter",
#     "category" => "crawler",
#     "os" => "UNKNOWN",
#     "version" => "UNKNOWN",
#     "vendor" => "UNKNOWN",
#     "os_version" => "UNKNOWN",
# ]

$woothee->parse("facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)");
# => [
#     "name" => "facebook",
#     "category" => "crawler",
#     "os" => "UNKNOWN",
#     "version" => "UNKNOWN",
#     "vendor" => "UNKNOWN",
#     "os_version" => "UNKNOWN",
# ]

$woothee->parse("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2560.0 Safari/537.36");
# => [
#     "name" => "Chrome",
#     "vendor" => "Google",
#     "version" => "48.0.2560.0",
#     "category" => "pc",
#     "os" => "Mac OSX",
#     "os_version" => "10.10.5",
# ]

$woothee->parse("Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586");
# => [
#     "name" => "Edge",
#     "vendor" => "Microsoft",
#     "version" => "13.10586",
#     "category" => "pc",
#     "os" => "Windows 10",
#     "os_version" => "NT 10.0",
# ]

$woothee->parse('Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)');
# => [
#     "name" => "Internet Explorer",
#     "vendor" => "Microsoft",
#     "version" => "6.0",
#     "category" => "pc",
#     "os" => "Windows XP",
#     "os_version" => "NT 5.1",
# ]

$woothee->parse("Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; Media Center PC 4.0; SLCC1; .NET CLR 3.0.04320)");
# => [
#     "name" => "Internet Explorer",
#     "vendor" => "Microsoft",
#     "version" => "8.0",
#     "category" => "pc",
#     "os" => "Windows UNKNOWN Ver",
#     "os_version" => "NT 5.2",
# ]

php.netのひみつ(1) 短縮URL

php.netPHPの公式サイトです。ダウンロードやリリースノートがあるのは当然なのですが、その中でもPHP: Documentationには技術文書が網羅されてます。

RubyPythonのドキュメントが各バージョンごとや英語とその他の言語(日本語を含む)の断片化が著しいのに対し、php.netはきちんと国際化されてます。

さて、あなたは関数/メソッドの仕様を知りたくなったとき、どうやってその文書まで辿りつくでしょうか。

例としてstrtotimeの日本語版記事のURLは http://php.net/manual/ja/function.strtotime.php です。こんな長いURLを打ち込むわけにもいかないのでGoogleなどの検索エンジンに頼るでしょうか。

それもひとつの選択肢ではありますが、ブラウザにphp.net/strtotimeと打ち込みます。

このようにphp.net/xxxxxのようなURLに対して、さまざまなショートカットが設定されてます。

ただし最近はDash for OS X - API Documentation Browser, Snippet Manager - Kapeliのようなドキュメントツールがあるので、そちらの話は後日また。