PhpStormを使ってOSSにコントリビュートをする

1人AdventのDay-20です。完全に師が走っておる・・・

adventar.org

今日は、こんな 強そう なタイトルでブログを書いてみます。
ただ、「技術論」や「ハイレベルテクニック」というよりは、コレはもう心構えの話です。

CakePHPへのコントリビュート

私は、過去に数度CakePHPやその周辺のプラグインなどにPRを送って、取り込んでいただく!という経験をしております。

Commits · cakephp/cakephp · GitHub

ただ、これらはすべて、enhancement系や大幅なロジックの変更をキメるぜ!!といったものではなく、些細なphpdocの修正や型宣言の修正などがほとんどです。

PRを作るタイミング

「自分で使っていて困ったとき」に送る、という程度のものです。
バランスさえとれれば、「業務時間中にちょちょっとパッチ書いて投げちゃう」程度のことは許してくれるよね〜という職場環境も大いに手伝ってくれていると感じます。

例えば「ここの型宣言が実際的ではなく、静的解析をパスしない」から「PR作ってるんだけどCI通らなくて困る」なんて動機で、書いちゃったりします。

それでもPRはPRで、contributeはcontributeだと思う

あまり小さいパッチでコアメンバーの手を煩わせるのも何だかなぁ、人によってはナンセンスに感じる面もあるよなぁ〜と感じる面もあります。
それでも、「日常的な開発を行っていく中で、些細なところで感じるストレスポイントはゼロに近いに越したことはないよな」という風に思います。

普段触っているフレームワークやOSSは、めちゃくちゃお世話になっており、そこに恩義を背負っているハッキリとした自覚があります。それであれば、何かお礼をしたいし、そのやり方として些細なことでも貢献したい。
もし、自分がパッチを書くことで、同じフレームワークを利用している人が、この「オヤ?っと思ったちょっとした点」について一切の違和感を覚えなくなったり。もしくは、その品質に対して全く当たり前に「よく動くもの」という認識を持って信頼しながら開発に取り組めるようになるのであれば、とても良いな〜と思うのです。

どうやって「貢献」のハードルを下げるか

一重に「PhpStormはいいぞ」というのがあります。コレは、めちゃくちゃ大前提としている部分といえます。

例えば、 #12770などは、「本来は可能である使い方が、パッと見ではできなさそうに見える」ので直したいなという修正でした。
これは、(内部実装的には)問題なく通る部分なので、そのまま使っていても構いません。しかし、IDEを利用していることで「arrayを期待できないけど大丈夫か?」ということで、ハッキリと警告が出ます。
その段になって初めて、「じゃあパッチを書いて正しい姿にしてしまおう」というモチベーションが生まれるわけです。

同様にphan/phpstanを通したときに生じたエラーについて修正を数度投げたりもしているのですが、これも「機械が勝手に見つけてくれて、自分は折角だし数文字だけ直しちゃおう」というリアクションによるものです

普段からソースコードを読みまくる

これまでガッツリとCakePHPを触っている中で、すっかり内部での処理の流れを追うことへの抵抗は薄れてきたと感じています。
このソースコードリーディングも、優秀なIDEに依る部分が大きいです。クラス探索はもちろん、ポップアップでのdoc表示やGo to declarationに何度救われたことか。
何ならドキュメントより生のコードを読むことがすっかり当たり前になってしまったのですが、そうしていくと自然に「コードに手を入れる」ことの抵抗が薄まってきます。フラットに「ここはちょっとおかしそうだよね」と感受できるからこそ、ごくごく些細な修正も「まぁ書いちゃったほうが早いかな」という発想に立てるようになりました。

なんかあったら、パパっと直しちゃう

やはり自分が好きなもの、お世話になっているものが「より正しい姿になっていく」のは嬉しいものです。
今後も、より深い部分での設計思想や描き出す世界観を理解していきたいし、引いては機能追加だったりRFCみたいな形で貢献できたら、もっと楽しいんだろうな〜とも感じます。

現状でも「普段の労力の延長線上でできること」があるということで、それに対して「目をつぶったりスルーしないで向き合う」という形で、自分なりの貢献の仕方を考えられるとよいなぁと思うのでした。

ストームを使いこなしたい〜〜〜〜〜〜!

CakePHP3アプリケーションをPHP7.3で動かす

1人AdventのDay-18です。

adventar.org

手元で作っていたアプリケーションをPHP7.3に乗せて見たので、その時の内容を晒してみようというのが今回の目論見です。
ちゃちゃっと。

構成について

Herokuで動くようにしています。
ということで、以前に作成したPHP7.2用のイメージをベースにしました。
o0ho0h/heroku-php72-fpm - Docker Hub

これについては、以前まとめたものです。

daisuki.nichiyoubi.land

ファイルの内容

こんな感じになっております

 $ tree -L 2
.
├── Dockerfile
├── README.md
├── app
│   ├── README.md
│   ├── bin
│   ├── composer.json
│   ├── composer.lock
│   ├── config
│   ├── index.php
│   ├── logs
│   ├── phpunit.xml.dist
│   ├── plugins
│   ├── src
│   ├── tests
│   ├── tmp
│   ├── vendor
│   └── webroot
├── composer
└── docker-compose.yml
$ cat .docker/etc/nginx/conf.d/default.conf
server {
  listen __NGINX_LISTEN_PORT__;
  server_name _;

  root /var/www/html/app/webroot;
  index index.php;

  location / {
    try_files $uri $uri/ /index.php?$args;
  }

  location ~ \.php$ {
    try_files $uri =404;
    include fastcgi_params;
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_index index.php;
    fastcgi_intercept_errors on;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  }
}
$ cat .docker/usr/local/etc/php/conf.d/xdebug.ini
zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20180731/xdebug.so
xdebug.remote_enable = On
xdebug.remote_autostart = On
xdebug.remote_host = docker.for.mac.localhost
xdebug.idekey = PHP_XDEBUG_IDE_KEY_HERE
FROM php:7.3-fpm-alpine

RUN apk add nginx supervisor \
    && mkdir -p /etc/supervisor.d/

RUN apk add \
    vim \
    git \
    zsh

RUN apk add --no-cache \
  libgcc \
  libtool \
  pkgconfig \
  re2c \
  libressl \
  xdg-utils \
  openssh-client \
  ca-certificates

RUN apk add  --virtual build-dependencies \
  autoconf \
  automake \
  make \
  gcc \
  g++ \
  libmcrypt-dev \
  zlib-dev \
  gmp-dev \
  libzip-dev \
  libxslt-dev \
  icu-dev \
  libmcrypt-dev \
  libressl-dev

# php7
WORKDIR /usr/src/php/ext
RUN git clone https://github.com/xdebug/xdebug
RUN NPROC=$(grep -c ^processor /proc/cpuinfo 2>/dev/null || 1) \
  && docker-php-ext-install -j${NPROC} \
  intl \
  xdebug \
  zip

RUN docker-php-ext-enable \
  #opcache \
  xdebug \
  && docker-php-source delete

# setup web
COPY .docker/supervisor.programs.ini /etc/supervisor.d/

COPY .docker/etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf
fpm.d/www.conf
RUN rm /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini

COPY .docker/run.sh /
RUN chmod a+x /run.sh
RUN adduser -D myapp \
    && apk add --update sudo \
    && echo "myapp ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers

 COPY ./ /var/www/html


 WORKDIR /var/www/html
CMD ["/run.sh"]
$ cat docker-compose.yml
version: '3'
services:
    web:
      build: .
      ports:
        - "9002:8888"
      volumes:
        - ./:/var/www/html
        - .docker/usr/local/etc/php/conf.d/xdebug.ini:/usr/local/etc/php/conf.d/xdebug.ini

これで、「xdebugの有効なPHP7.3でCakePHPを動かす」ができると思います。

xdebugについて

Dockefile内でxdebug.inをrmすることで、xdebugを無効にしています。
その上で(localでのみ使う想定の)docker-composeではiniファイルをマウントさせており、これによって「xdebugの有効・無効を環境に応じて切り替える」ということをしています。

opcache

ちょっと問題がある?っぽく、opcacheは無効化しています・・・
最初はうまくいかなかったのですが、外してみたら無事にPHPを動作させることができました。はて・・・

まとめ

今日はかなりさっくりです!!
これだけで、簡単にherokuにCakeアプリケーションを放り込めるかな〜と思います。
あとでGitHub/Dockerhubにあげる!

コーディング規約 for small team 2018

1人AdventのDay-17です。17っていうとFLCLプログレを思い出す数字だな〜

adventar.org

コーディング規約というものがあります。
私は、割と「社内(のサーバサイドというかPHPというか)のコーディング規約をちゃんと皆で定めていきたいよね」側にいます。
その中にありながら、昨今では私も含めIDE/PhpStormの導入が進み、PHPCSもいるし、Phan/PHPStanもいるし、「機械的にやれること」は増えてきております。

原則として「既にあるコード」や「コードを書いた人(とその気持)」は尊重すべきだよな〜という気持ちがありまして、そういう意味で「わざわざtrailing commaをつける」だけとか「空白行を挟む」だけのコミットはしたくない、と思います。もしくは「コメントを消すだけ」とか。
自分が飛ばすPR、もしくはレビュー依頼を受ける事になる変更において「極力、diff行数が少なくなる」ように個人的には目指したかったりはするので、ケツカンマを入れる(配列に新しく要素を追加しても「前の行にカンマがついた」diffを出さなくて済むから)とか、「段落ごとに空白業を挟む」みたいなのは気をつけるわけです。
が、 それはあくまで個人的な話だ
コレを「チームで利用するコーディング規約に入れるか」という話を、・・・まぁ合意が取れたら規約として採用してもいいんでしょうけど、何か議論するのが疲れるってレベルのものがある。*1

規約というのはあくまで「健全性」「気持ちよさ」「効率」を引き出すためのものであって、「ストレスを抱えさせる」ためのものであってはならないよな〜と考えるのです。


愚痴を吐いたので、ここから本題です。

(先程述べた通り)私は「規約整備していこうぜ!」側でして、新しくサーバーサイドのエンジニアが入って共同のチームで動くよ!と決まったタイミングで整備をしたり、その後にはgithub管理移行とかも仕掛けたりをしました。
そうした活動を通して、自分なりに「健全性を高めるためにこういうポイントを抑えておきたい」と感じた部分があります。
その辺りの話を、少し整理してみようかなというのが今回のエントリーです。

前提のような方針のようなもの

「整備し直した」ものはCakePHP3のプロジェクト用のものでして、その「第0章」みたいな文章を書いてあったのでそれをそのまま引用してみます。

### コーディング規約の役割

チーム内で、「誰が書いても」「誰がレビューしても」同じ様にコードが書けて、違和感を持たずにコードが読めて、迷いを少なくしながら開発を進められる状態を理想とします。
狭義のスタイルガイドにとどまらず、推奨されるイディオムやプラクティスやフレームワークのバージョンアップ等に伴い生じる「注意の必要な書き方」をも示すことで、日常の開発業務において従うべき新鮮なガイドラインが欠かせません。

これを踏まえて、本文書は以下の役割を果すべきものと定義します。

* CakePHP3の経験の浅い新規参加者が、「どういう風に書くか」を把握できる
* CakePHP3の経験者が、「こうやると書きやすい/読みやすい」を普及できる

このくらいでいいんでないかな〜という規約

OSSのフレームワークを利用している場合、そのフレームワークの採用している規約がある場合が多いと思います。
なので、スタイルに関しての「守らないといけないもの」は、ベースとして「フレームワークが言っていることをそのまま」で良い、くらいに思っています。

これによって、コミュニティが持っているアセットを活かしやすくなるというのも重要。拡張改変バリバリの「本来のそれとは相入れない」ようなものを制定したら茨の道に突入していきます。

その上で、やってもいいかなと思っているもの

基本的に、自分たちのために作っているプロダクトというのはOSSなフレームワークほど「汎用的である必要」がない、と思っています。
そのため、実装についてもより(自分たちのための)アグレッシブな方針を取れるものと思います。例えば、「言語バージョンを新しくしたら使える機能」などです。

これらについては、「フレームワークの本家にはなくても、快適さを向上させる見込みがある」と判断できる場合もあるのではないでしょうか。そういったものは、あっても良いと思います。

あった方がいいかなと思っているもの

逆に、自分たちで作った機構やライブラリについては「利用するように規約で縛る」事でコーディングのし易さを支援できる可能性がある、と考えています。そうしたものは、積極的に「使っていく」ことに決めても良いかと思います。
なぜなら、「自分たちが自分たちのために作ったもの」であれば、サービスドメインやチームメンバーのレベルに合わせて開発されたものであり、フィットするはずだからです。

例えば、私の場合はenumのような機構を作りました*2が、これは「使うべき」としました。オプショナルでなく「使えるときは使う」ことによって、この機構の保守改善を行っていく恩恵が、プロジェクト全体に浸透していく状況を生み出せるようになるからです。
何より「メンバーがそれぞれに同様の書き方を扱えるようになり、リーディングにも実装にも没頭しやすくなる」ことが規約を定める目的でもあるので、足並みを揃えられられたのは良い点でした。

あると良いかなと思っているもの

私の仕立てた「コーディング規約」では、規約やガイド・・の範疇を超えた「プラクティス」の賞も設けています。
「こう書くべき」というとかなり強い制約になると思うのですが、ここには「こう書くとCakePHPぽいよ」といったものを拡充させていっています。
実は(規約自体はフレームワーク本体が抱えているにもかかわらず)「社内規約」を管理したい、というモチベーションの1つはこの「プラクティスを管理してみたい」という部分にもありました。

例えば、「CakePHPのEntityに値を入れるときに、どういった場合にはpatchEntity()メソッドを利用して、どういった場合にset()を使うのか?」といった内容に触れています。

当然ながら、「全員が守るべき・意識するべき」という規約よりも、周知徹底すべきなのはどのレベルからだっけ・・・?というのがファジーになるので、メンテナンスを続けていくのは中々ハードルが高かったりします。それでも、「プルリクで何度も指摘される」ような話は、こういった仕掛けで担保できないかな?とも思うわけです。

いずれにせよ

「なぜなのか」を示していくことが重要なのでは、と感じています。
もちろん、規約の中には「スペース4つで示す」といった、理論立てて説明し/されて納得感を持ちながら扱う・・というのとはそぐわないものもあります。他方で、例えば(実際にCakePHP3が採用している例で)「privateメソッドには引数の型宣言を行わない」という話をするときに「型宣言を行った場合、それを解釈し実行するコストが必ずしも0ではない」から、スコープが限定されるときは「なるべく負荷がないようにしよう」という理屈があると、助かるだろうな!と思うわけです。更に、「根拠を示す」ことが「改定を望むときにpros/consを明確にしやすくなり、本質的な議論に進みやすくなる」という利点もあります。

理屈をしっかりと明示していくことで、「コーディング規約」が「押し付けられるもの」ではなく「民主的に取り組み、常にアップデートされていくべきもの」へと進化させ得ると考えています。

まとめ

規約は堅苦しかったりストレスになる危険性もないではないわけですが、「関わる人全員をハッピーにしようぜ」というモチベーションから生まれるものだと思います。
コミュニティに開かれたソフトウェアのコーディング規約と社内やチームに適用されるべき規約とでは、そもそも目的も異なるはずで。なので、「自分たちのチームには何が必要で、どういったものがあればコーディング時の憂いがなくなるか」という原点に立ち返りながら、「ぼくらのかんがえたさいきょうのこーでぃんぐきやく」を以てして、最高のコーディングライフを手に入れられたらハッピーなのではないでしょうか!

*1:prettierくらい強烈に書き換えてくれるものやgofmtとか、救いだな〜!と感じる

*2:https://speakerdeck.com/o0h/bye-bye-magic-number

yieldとコルーチンと非同期処理

1人AdventのDay-15です。もう2週間経ったのか、12月よ・・

adventar.org

最近、ちらちらっと「Swoole」という単語を聞くようになった気がしており、それについて調べてみよう〜という予定でした。が、「まずコルーチンとかについて頭の中を整理したいな・・」と思ったので、このようなタイトルになっております。

きっかけ

Swooleについて初めて意識したのが、TLにこのツイートが流れてきたときかなあーと思います・・

Swooleってなんなのさ

BEAR.Sundayのサイトに載っている説明はシンプルにまとまっていました。

SwooleとはC/C++で書かれたPHP拡張の1つで、イベント駆動の非同期&コルーチンベースの並行処理ネットワーキング通信エンジンです。 Swooleを使ってコマンドラインから直接BEAR.Sundayウエブアプリケーションを実行することができます。パフォーマンスが大幅に向上します。

bearsunday.github.io

ググったら「副題: 最近Swooleが楽しい話」というUzullaさんの記事が出てきました。

最後まで読んでみて、いろいろと「なるほど」がありました。結構ボリュームがありますね。 スライドを見ていたら、サイトも出てきました。

www.swoole.co.uk

「プロダクション品質の非同期PHPプログラミングフレームワーク」ふむ。

・・・なんだか「こんにちは、闇の方から来ました。私、色々できます」みたいな凄みを感じます。

Sooleと非同期プログラミング

私が「Swoole気になるな」と思ったのは、「非同期」があるの?と思ったのが強いきっかけの1つです。
非同期、よいですね、ふふふ。

先のuzullaさんのスライドを見ると「Swoole、めっちゃ機能多そうだな」という感想をいだきます。何か新しい仕組みや概念を理解しようとするときに、私は「なにか1つポイントを見つけて、それを軸としてイメージを具体化していく 」というアプローチを好みます。それをしながら、「 必要になったら時点で他の筋に移動したり、行ったり戻ったりする」を繰り返して、全体像を掴んでいこうという感じです。

多機能だからこの記事で全部触れるの無理よな〜っていう思いもあり、まず「非同期プログラミング」をキーワードとして調べている内に、主題が入れ替わった形です。

PHPとコルーチン

先のスライドの中にも、「コルーチン」が出てきますね。

[f:id:o0h:20181216010344p:plain f:id:o0h:20181216010434p:plain

PHPでの非同期処理を得意にさせるrecoilなどにもcoroutineについて言及があります。

では、コルーチンとは一体なにで、どうしてそれが非同期処理と関係があるのでしょう?

そもそも「コルーチン」がなにか、という話をつまみ食いしておきます。

まずは、例によってWikipediaです。

コルーチンはいったん処理を中断した後、続きから処理を再開できる

とか

接頭辞 co は協調を意味するが、複数のコルーチンが中断・継続により協調動作を行うことによる。

(https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%AB%E3%83%BC%E3%83%81%E3%83%B3)

なんて書かれています。
「継続」「中断」「再開」という言葉が出てきました。これらを意識することができれば、意味がつかみやすくなるのかもしれません。

継続

継続については、こちらの記事が良かった。
なんでも継続

「意識することができれば」と書きましたが、「今までもそこにあったのに、見えてなかった/認識することができなかった対象を、ふと「当たり前」に存在を感じられるようになれば」ということで、この書き方をしました。そして、この「なんでも継続」の記事が、正にそのスタンスで書かれていて良かったな〜と。。

すんごいザックリといって「なんかの処理をしていて、途中でどっか行って、戻ってきたらその 続き から処理を行う」というのを強調するために「継続」という概念が必要な気がします。「どっか行って」=中断、「戻ってきて」=再開。

関連: 制御構造 - Wikipedia

yield

さて、PHPにおいても「中断」「再開」といった単語を(サブルーチンのそれとは違い)強く意識する場面があるのではないでしょうか。
イテレータ、ジェネレータ、yieldといったキーワードを思い出してください。

ジェネレータ(Generator)とは、イテレータコンパチなインターフェイスを持つけど、 指すべきコレクションがあるわけでもなく、そのたんびに 値を作り出して返すようなモノを作るモノをいいます。

ジェネレータ と コルーチン - みねこあ

PHPはyieldによってジェネレータを作成できます。

  • 中断:
    • ルーチンの内部の任意の箇所で、(呼び出し側に値を返して)処理を終わらせ
  • 再開:
    • 「さっきやったとこ」の続きから処理を始める

という内容を備えています。「一旦処理を中断した後、続きから処理を再開できる」ということであれば、コルーチンの定義に当てはまると言って良いのでしょうか?

ジェネレータ/コルーチン

yield(PHP)はジェネレータ関数を作るのだから、じゃあyeildを使えばコルーチン?コルーチン = ジェネレータ?と思い、今度は「ジェネレータ」についてWikipediaでみてみます。

ジェネレータの実装としてはコルーチンやcall/ccやマルチスレッドを使う方法が考えられる。

ジェネレータ (プログラミング) - Wikipedia)

ということで、どうも一致する概念ではないようです。
どういう差なのかな、と調べてみたらPythonに関しての例を説明している記事に行き当たりました。

yieldした後にコントロール出来るという点がジェネレータと違うようです。

Pythonのyield(コルーチン編) | KISO-REN

この「コントロール可能か否か」というところで、コルーチンは分類されるぞ!というような事をStackoverlowの問答で発見しました。

In the asymmetric coroutine model, "asymmetry" refers to the fact that there is a stack-like caller–callee relationship between coroutines. A coroutine can either call another coroutine or suspend itself by yielding control to its caller, typically also yielding a value to the caller at the same time.

What is the difference between asymmetric and symmetric coroutines? - Stack Overflow

ジェネレータを含む「非対称コルーチン」においては、「一旦処理に入ったら、そのまま、最後まで内部生成された値を返す」ということになり、これを「一方的(= callerには制御権がなく、コルーチンにのみ自身の制御権がある)」と言っている・・のでしょうか?そんな感じと理解しました。

そしてさらに、Pythonの例を見つけました。

魅力的なPython: ジェネレーターによるステート・マシン

かなり前のバージョンにおける言及をしている記事で、この中では「(Python2.2で導入された)yieldでのジェネレータは半コルーチンで、本当のコルーチンを実装したよ」とされています。

内容を見てみると、「ジェネレータの中から別のジェネレータを呼ぶ事は可能」というのを応用して、「ジェネレーター同士がお互いを呼び合う」ようにしている・・というのがポイントでしょうか。

先の「Pythonのyield(コルーチン編) | KISO-REN」の記事で言及されているPython Coroutineの元ネタPEPを見てみると、次のように書かれています。

However, if it were possible to pass values or exceptions into a generator at the point where it was suspended, a simple co-routine scheduler or trampoline function would let coroutines call each other without blocking -- a tremendous boon for asynchronous applications.

PEP 342 -- Coroutines via Enhanced Generators | Python.org

ということで、IBMの記事にて実装されている内容も今のPythonならシュッとかけるのかもしれません。
これ以上は、今回のエントリーの本題から外れていきそうなので話を戻します!

PHPにも同様の機能が導入されています。

qiita.com

yieldと非同期処理

ここまで見てきたとおりで、コルーチンは「別の何かをする処理」であると言えます。そして、「中断して(=処理を呼び出し元に戻して)」「再開して(=自身の中に状態を維持・継続ができる)」というのがポイントです。
ということは、「コルーチン内部の処理を行っている最中に、呼び出し元の処理をブロックしない」もしくは「取り急ぎ呼び出し元にコントロールを戻しつつ、自分の処理もちょいちょい進めておく」事が可能なら、実質的に非同期処理が可能という着想で合っていますか。

まさに、そのような内容を扱っている資料が上がっていました。

これは、以下の条件によって実現されているものと思います

  • non-blocking APIを備えた実行内容がある
    • curl_multi
  • coroutineをstackさせておく
  • stackされたリクエストの解決まで、無制限のループ処理によって状態のチェックを行う

そうすることで、「すべての解決を待つ」ことをしつつ「個別の処理は並列的に行う」という操作が可能になったという理解をしています。

「PHPでコルーチンを利用することはできるが、それ自体は「あらゆるものをノンブロッキングにする」ような能力はなく、あくまで並列化の可否は処理内容としてノンブロッキングAPIを利用出来るか次第で、また「整いました!」とイベントを拾いに行くのは自分で監視をじっそうしなければならない・・・」 こんなところでしょうか。

ReactはイベントループとノンブロッキングI/Oの実現を目指した、ループ機構です

感想

「なんでもすぐに並列化できちゃうすごいやつ!」というのは、PHPという言語上は存在しないんだなーというのを改めて認識しました。
その一方で、ノンブロッキングな処理が実装されている領域についてはPHP上で実装が可能そうにも思います。例えばMySQLからのデータ取得などは、個人的な関心からいっても恰好のターゲットです。
実際に手を動かしてみないとイメージ湧かないな〜とも感じたので、「引き続き模索してみたいな」と思っています。

qiita.com

その他の文献

本文中では触れなかったものの参考にした記事です

個人的にPHPのassert()の使い方を整理する

1人AdventのDay-12です。12ですかぁ〜

adventar.org

PHP*1にはassertというものがあります。

www.infiniteloop.co.jp

これ便利だし、コードを「良くする」ことのできる嬉しい機能だと思っています。
ただ、往々にして「開発を健全に続けていく」という目的の下にあっては、「曖昧さ」「ゆるさ」は罪になることもあります。そんなつもりはないのに・・・

ここでいう「曖昧さ」というのは、「自分以外の、他の人が、同じようなことを書いたり書こうとした時に、同じ感じでかけるか?」という、悩ましさの大きさだと思ってください。
プログラミングをやっていて、醍醐味で貼りますが、こういう風に動けばいいな!って思いついてから「あ〜どうやって書こう、何が正解かな?」って考えさせられる部分というのは、苦しみを招きます。

処方箋として。
「やり方を決める」っていうのは、1つの手だと思います。「これはこういう時に」「こういう風に使いましょう、使って良い」っていうガイドラインがあれば、気持ちが軽くなりますね。

さて、話を戻して「assert」や・・・いつなら使っていいんだ・・・

実際に業務のコードにチラホラと織り交ぜて見ているのですが、自分なりに「ここはOK、これは微妙」っていうラインを形にしてみようと思いました。
今日はそういうブログです。

前提として

  • assertは、プロダクション環境では使わない。実行されるのは、開発環境においてのみである
  • すなわち、実行時にデータを守ったり、ユーザーを守るためのものではない
  • 要するに、「開発時に実装のミスをしないように」、「開発者を支援するため」のもの

と思っています。

「実行されるコメント」という感覚。

私の場合

「assertは使って良いもの」としつつ、先述の「曖昧になっていく」のを恐れています。そのため、限定的な使い方をしています。
とりわけ、 LogicException とはなぁ。。。という風に感じています。

かなり探り探りでありつつ、「このくらいなら・・・」というのを書き連ねてみます。

privateメソッド内での利用

assertを「実装ミスをあぶり出す」という目的で使うとなると、「どういう使われ方をしそうか」というのは限定して考える必要があります。

その点、privateメソッドに関しては「まぁ、うっかりミスは少ないかな・・」という自信を持ちやすいかと思います。
protected, publicだと、どう使われるか保証できるとは言い切れないので、LogicExceptionをしっかりつかうかもしれません。

「ここに来る前に、このメソッドを実行されているか」みたいなチェックなど。インスタンスメンバーの設定状況だったり、DIの注入状況だったり・・・といったケースでしょうか。

自作トレイト内での呼び出し元クラス設定状況

インターフェイスや抽象クラスでは「メソッドの定義を強制できる」と思うのですが、クラス定数・クラスメンバーについてはどうでしょう。 「このトレイトを使うには、 $this->config に arrayをいれてある」とかは、「実相時点で拾える必要がある」と思います。

そういった意味で、assert()を安全に使えるのではないでしょうか。

テスト中の前提条件

これを結構よく使っているかもしれません。

テストなんて「検査」しかしなくない?という話もあると思います。
たとえば、「ユーザー削除処理を行うAPIの実行時に、ユーザーの持っている投稿をすべて削除する」など。

この場合、「このユーザーIDに紐付いている投稿が0件になっている」ことをテストケースに絡めることが可能です。
が、それを実行する場合、利用するフィクスチャデータに「そもそも投稿がなかった」という状況だと、「検査は通るけどテストの意味を持たない」ことになってしまいます。
とはいえ・・・「もともと、なにか投稿していたよね?」という確認は、このテストケースにおいて「やりたいこと」ではないわけです。

そうした場合、「やりたいこと」と「守らないといけない前提」を明示的に区別するために、PHPUnitのconstraintとassertを併用するのはありかな〜と思います。

まとめ

もう少し使ってもいいんじゃないかな〜活躍してくれ!!という気持ちはあります。
その一方で、別にどんな機能も「無理に使う」必要はないと思います。
それでいて、「どっちでも良い部分」という根拠の説明がふわっとしやすい部分は辛いのです。とりわけ、チーム開発を行っていると、「なんとなくこう!」というのは、レビュイーに対して出来ることではありません。でも許容していると、「緩さ」が蔓延していき、より辛く・・・・誰も幸せになれないわけです。

なので、「チームとしての基準を決める、合意をとっていく」「アイディアや思考プロセスも含めて、プラクティスを蓄積していく」ことが重要なのかな〜と思っています!

*1:ここで言っているのは7.xですね

PhpStormでのGit/GitHub利用

1人AdventのDay-11です。

adventar.org

今日は、すでに10,000,000回くらい言及されていると思いますが、今年ちゃんと使い始めたかも!そしてすっげ、便利!!系のネタで、「PhpStormからGit/GitHubを利用する」をしたいと思います。
この辺りを気にしなくていいのが自分のブログの醍醐味じゃろ、っていうノリです。

なぜ「エディタ」でそこまで

1つに、「ウィンドウを行き来する必要が減る」というのは、やっぱりでかいです。
IntelliJ IDEA含め、世のIDEは「ショートカットを覚えたもん勝ち」「いかに思考より早く必要な機能を起動できるか、体に馴染ませるか」みたいなところがあると思っています。

その中にあっての、Git。便利です。
あと、こまめにコミットしておくと安心感がすごいんですよね。PR飛ばす前にrebaseをかけることで、コミットにもちゃんと意味をもたせる〜観点で整えよう!というくらいの木基で、本当はバシバシとコミットしていくのが良いのかも・・・というくらいに思っています。

commitをする

⌘+k です。

(大いに、私の好みの問題ではあるのですが!)
「コミット前に整える」機能がstormの力を遺憾なく発揮する形で充実しており、そして「Amend commitがすぐにできる」というのが嬉しいのです。 f:id:o0h:20181212022121p:plain

何が嬉しいのか?
とりあえず、一旦wipでコミット が、非常にやりやすいな〜と・・

Reformat codeや Optimze importsというのは、「どっか弄ったときに、直し忘れてた」みたいなのを消してくれます。 useされているけど使っていないよ とか 50音順に並んでいないよ とか、そういう。
この辺りは、当然「レビュアーに失礼のないように整えましょ」という話ではあるのですが、ここまでくると、「通常作業の自然流れの中に『細かいコミット』を組み込んで見ようかな?」という気にもなります。

commit messageを入力したら、 ^+k でコミットを実行してください

pushする

⌘+⇧+k です。

f:id:o0h:20181212022920p:plain

pushは、commitほど「細々と実行する」ようなメリットはないと思います。
ここで便利なのは、「pushする前に気づいちゃったことをさくっと開く」のが億劫でない、やはり「エディタとの距離が近い」ことによる恩恵があるな〜といった感じです

checkout

f:id:o0h:20181212023328p:plain f:id:o0h:20181212023254p:plain f:id:o0h:20181212023413p:plain

clickだけで、すすす〜っとブランチを移動することが可能に。
嬉しくないですか?

view pull requests

reviewするときに、「このブランチをじゃあ実際に落としてきて」って絶対にやるじゃないですか。

f:id:o0h:20181212024440p:plain

f:id:o0h:20181212024842p:plain

左カラムがPRの一覧です。
各PRを右クリックすると、「Create new local branch」「Open on GitHub」というメニューが現れます。すごい。

find pull request

「この行の変更は、どういう経緯だ・・・?」ってなるときに使います。

f:id:o0h:20181212025138p:plain エディタ上の、気になる箇所を右クリック→コンテクストメニューから「Find Pull Request」です。 ブラウザ上で該当のPRが表示されます。

annotate

コードの行番号を右クリックすると、「annotate」というメニューがあります。

f:id:o0h:20181212025350p:plain

これで簡単にblameができて、大助かりです。
authorの表示されている部分にhoverすると、コミットメッセージが表示されます。

f:id:o0h:20181212025538p:plain

また、クリックするとコミットの詳細が表示されます

まとめ

今の所、私に「馴染んでいる」という感覚のある機能はこのくらいです。
まだまだ知らない機能、使えていない機能がある・・・・・・というのは大いに自覚しております。探していきたいな、という所存。

CakePHPを3.7に上げてみたのでその時の作業のポイントをまとめる

1人AdventのDay-10です。

adventar.org

日本時間の9日、CakePHPの3.7のstableがリリースされました。

bakery.cakephp.org

リリースノートや移行ガイド、 GitHubの3.7ラベルがついているIssue/PRを眺めながら、主観&自分に関係しそうな箇所をまとめたのが昨日のアドカレ記事でした。

cake.nichiyoubi.land

今日は月曜日〜ということで、会社に出社したのですが、モノは試しということで社内ツールのCakeアップグレードに手を出してみました*1

結構スムーズに行ったのかな?と思うので、その際の手順などをまとめてみたいと思います。

前提・ゴール

まず、移行対象とするアプリケーションの状況や今回の作業のゴールをまとめます。

アプリケーションの内容・立ち位置

  • 社内向けの管理ツールです
  • もともとCakePHPは最新版(の1個手前!)である3.6.13までアップデート済み
    • 3.6.xならウィークリーで更新PRが作成されるようにしてあり、原則として最新版に追従させる
    • 3.5/3.6で追加されていたdeprecatedも全て推奨される方式に移行済み
  • サーバーサイドはAPIのみを提供 。なので、View/Form周りはほぼ使っておらず
    • このレイヤーを利用しているアプリケーションにおいては、大まかな作業の進め方は変わらないものの、手間は確実に増えると思います
  • モデルの単体テスト、コントローラの結合テストは原則として必須にしているので、割と機械的なチェックを信用した移行ができる

APIアプリケーションという限定的な機能に留めていること、テストや小まめにバージョンアップしているなど「気軽にアップデートしても大きな作業になりにくい」という状況は整えられていると思います。

今回のバージョンアップ作業のゴール

  • (当然ながら)デグレはさせない
    • 単体テストを完走させる
    • コード解析(PHPStan)も完全に通るようにする
    • 作業後、主要機能を中心に手動でリグレッションテストをかける
  • 新機能の利用などは都度
    • 「普通に動く」を一旦ゴールに

事前準備

変更内容の把握・見立て

何よりも、まず最初にチェックすべきはやはり移行ガイドです

3.7 Migration Guide - 3.7

deprecatedをみる

3.6に引き続き、今回のバージョンの主な意図としては「4に連れて行かない機能に廃止予告をする」ことにあると思います。
その内容は、(またしても)setter/getterの分離が目立ちます。

後述しますが、ここで「変更されるものすべて」を厳密に見る必要はないと思います。特に、メソッドの分離系は「いつものですね!」といって流してしまっても良いかもしれません。
どちらかといえば、私の場合、「自分のアプリケーションで関連しそうなクラスは、どのくらい対象になっているかな?」という観点でチェックをしました。これが多ければ作業は重くなると予想できますし、そうでないならサクッと行けそうです。
「今やっちゃうか後で腰を据えてやるか」を占うために、大体の判断を「どんなクラスがあるか」で見たわけです。

behavior changeをみる

(少なくとも今回の変更においては)「まっとうに使っていてもヤバそう」という内容の破壊的な変更はないように感じました。
しかしながら、アプリケーションに埋め込まれた地雷が発火する可能性もあり、やはり1番ケアするべきはこの項目だと思います。

意図が読み取れない・影響範囲のイメージがパッと出てこない・・というものに関しては、対象PRを探し出して読んでいってしまったほうが、安心して移行作業に取り組めるかもしれません

new featuresをみる

この節は、「とりあえず動くように移行する」という観点で言えば、無関係といえば無関係です。ただ、折角なのでこの機会に読んでみてしまったら?くらいの気持ちで目を通しています。
内容によっては、behavior change/deprecatedになった「背景」や、あるいは「どの方向に進もうとしているのか」という設計者の思想が感じ取りやすくなる場合もあるでしょう。

作業の実施

ここから実際の作業に入ります。

ごく個別的なケースになりますが、(API専門という)小さなアプリケーションですら「踏んだ穴」となれば、一般的なアプリケーションにおいても同様な作業が発生する可能性も低くないと思います。
そういう意味で、「実際に対応した内容」も併せて紹介していきたいと思います

静的解析

IDE・PHPStan(Phan)を利用します。
今回はPhpStormだけで賄えた部分だけ対応し、PHPStanは別箇所での反応がありませんでした。なのでPhpStromの活用に話を絞ります。

  • 検出する
    1. Code -> Run inspection by name 「Deprecated」
    2. 検査対象を指定し、OK
      1. なお、このときに利用するために予め「Custom scope」を定義しておくと便利。私は、AppApp\Test を定義してあります

移行作業に静的解析での結果を利用する目的は、クラス・メソッド単位で宣言されたdeprecatedへの対応です。

これはPhpStormで洗い出して潰します

IntegrationTestCase

コントローラー及びシェルのテストに主に利用していた、(Console)IntegrationTestCaseがdeprecatedに仲間入りです*2

  1. 継承元をすべて \Cake\TestSuite\TestCase へ変更する
  2. Cake\TestSuite\IntegrationTestTrait をuseする

Runtimeでのdeprecated対応

実行時に、引数やコンテキストによって deprecationWarning() を発火する部分への対応です。

  1. 検出する
    1. これは完全にテストに頼りました。なので、カバーできていない部分は拾えていない・・・逆を言えば「諦めてもいい」のかな、とも思っています

ちらっと蛇足。
ありがたいことに「廃止」ではなく「deprecated」です。つまり「なくなる」訳でもなければ「まぁ別に普通に動きはする」というものです。
どのように最新版対応を進めるか?というやり方次第ですが、大雑把に言えば「もし、見落としがあって対応がとれていなくても、死にはしない」「だから、動かしてみて、warningを感知したらその都度で事後対応を行っていく」というのも1つの上手いやり方のようにも思います。

Plugin::load()

3.5から入った新しいPluginの仕組みを使うように促しています。

  1. (bootstrap.phpから) Plugin::load()を消す
  2. Application.php 上で、 $this->addPlugin()によりプラグインを読みこむ

Email::setConfigTransport()

これもcreate-projectした時からの付き合いだと思います。
私の場合、Email周りは利用していなかったので設定ごと削除しました。
・・・どこかで参照が走ってエラーとなる可能性もありますので、その時に対応すればいいかな、くらいに捉えています。

Plugin::loaded()

今回、これが1番悩んでいるのですが・・・ bakeプラグインが依存している、twig-viewのbootstrap.php内での利用です。
masterにおいては修正が加わっているのですが、まだリリースされていません。

github.com

影響がでるのが「debugが有効」なケースでのみに限定されそうなので、今のところはスルーしています・・

Dispatcher is deprecated.

こちらは厳密には以前のバージョンで入ったDeprecatedですが、今回のIntegrationTestCaseの対応と絡んで引っかかるようになりました。

IntegrationTestがコントローラーアクションをエミュレートする際のcalliingクラスには2つあります。 MiddlewareDispatcherLegacyRequestDispatcherです。
このいずれを使うか?は、IntegrationTestCase/IntegrationTestTraitの setUp() メソッドにて設定が行われています*3

しかしながら、静的解析の時点では「継承元を変えてトレイトを食わせる」という作業を行ったのみであるため、テストクラス自体がsetUp() メソッドを持っている場合は IntegrationTestTrait::setUp() を呼び出すようになっていませんでした。
そのため、正しくフラグがセットされず、LegacyRequestDispatcher が起動される・・という流れになってしまっていたわけです。その先で、deprecatedされている Dispatcherクラスが使役されます。

以下のようにしました。

<?php
//in some controlelr test case 

use IntegrationTestTrait { setUp as integrationSetUp; }

public function setUp()
{
    $this->integrationSetUp();
}

内部的に parent::setUp() も呼ばれているので、既存の実装が破壊されるようなことはないと思います。

Declaring fixtures in underscored format in TestCase::$fixtures is deprecated.

これが1番手間がかかりました!w

利用するFixtureの宣言を、CamelCaseで行うように変更されています。
そのため、これまで $fixtures = ['app.article_categories']; のようにsnake_caseで宣言していた箇所が警告されるようになりました。
・・・fixtureを使うテストクラス全部。。。

PJ全体検索で $fixures = などとして地道に変更を行っていったのですが、Inflectorユーティリティなどと絡めて自動書き換えもできたのかな・・・?という気もしています。
このあたりは、テストのボリュームとの兼ね合いで。

まとめ

バージョン移行系の作業は、手間もストレスもかかりますが、やはりそれ相応のメリットもあるのかなと考えています。
セキュリティリスク含め、バグを含む可能性も時にはありますが、少なくとも定期的な更新(最新化である必要は必ずしもない)はやるべきだ、と思います。

その時の肝が、

  • 大幅アップデートにならないようにこまめに更新を行っておく
  • 安心感を担保するに足る十分量・幅の自動テストを用意しておく

の2つだな、と改めて感じました。

引き続きCakePHP4を待ちながら、また今回入った新しい機能をワクワクしながら使っていきたいなー!と思っています

*1:まだレビュー待ちのステータス

*2:なお、今回のケースに置いてはシェルコマンドの実装がないので、ControllerTestCaseのみの対処で完了しました

*3:正確には、ディスパッチャの選択に関わるフラグのセットをここで行う https://github.com/cakephp/cakephp/pull/12072/files#diff-30ad814a76cb9a0911ed10f377ace431L181