PHP
PHPDoc

PHPにコメントを書かせる、あるいはDocComment入門 (前編)

コメントを書く必要がないほど読めるリーダブルなコード、つまり名が体を表し、コメントで回りくどく弁解説明の必要がないほどわかりやすいソースコードは極めて理想的なコードではあります。

しかしながら現実には、複数人で運用され続けるコードにはさまざまな文脈や背景が積み重なっていきます。そのような事情をプログラムコードだけで表現しようとすると、情報の共有に困難を生じることがあります。

コード中に適切にドキュメントを記述する習慣を身につけることで、チームでの共同開発において情報共有に役立てることができます。

私見による「よいコメント」の指標とは以下のようなものです

  • 人間がひとめ見てわかりやすい
  • ツールで処理しやすく、相互運用性がある

この記事では、チームメンバーにPHPファイルにコメントを書かせる上でのPHPでの背景を順にそこそこ詳しく説明していきます。

対象読者層

  • PHP中級者
  • チームメンバーに体系的なコードを書かせたい人

この記事はPHP初心者向けではないです。ごめんね。

あと、深夜のテンションと怒りの気持ちで書いてるので、ちゃんとした校正も内容の精査もしてないよ。

0. PHP文法上のコメント

そもそもコメントは、実行時に文法的に解釈されない部分のことです。
(ただし後述するように、この前提には例外があります)

コメントはPHPタグ内、つまり <?php の後や、 <?= 〜 ?> の間にのみ書くことができます。

PHPのコメントには4種類あります。

  1. 一行コメント
  2. shebang
  3. 複数行コメント
  4. DocComment

PHPタグの、PHPとHTMLを混在させて書くときに、<?php 〜 ?>にコメントを書くことはできないことを意味します。HTMLの <!--  〜 --> 形式のコメントはPHPでは処理されず、最終的な出力に含まれることに気をつけてください。 <!-- 〜 --> の中にプライベートな仕様などを書くとユーザーに丸見えになるので注意してください。

ここではさまざまな形式のコメント記法を採り上げますが、もっぱら利用することになるのは「DocComment」と「一行コメント」です。

一行コメント

// または # から始まるコメントは 一行コメント または (単一たんいつぎょうコメント)と呼ばれます。

<?php

// 一行コメント
// 続けて書くときはそれぞれに `//` を書く必要ある
//

hoge(); // 行の最後に書くと、それ以降がコメント

//////////////////////
// 二つ以上書いても有効 //
//////////////////////

// ↑ 個人的には好きではない

if ($a == $b) {
    // ブロック内に書くときは、合せてインデントする
    do_something();
}

//# は文法的にはどちらでも問題ありませんが、傾向としては // が多用される印象です。
混在すると見苦しいですが、チームで見慣れた方を使ってください。

shebang

shebangシェバン(シバン (Unix) - Wikipedia)は非常に特別な形式のコメントです。

この行は <?php の外にあるにも拘らず、特別な場合のみコメントとして機能します(=PHPの文として解釈されない)。

jiban.php
#!/usr/bin/env php
<?php
echo "人は誰でも ひとつの太陽", PHP_EOL;

それは、ファイルを直接実行したときです。

% php jiban.php
人は誰でも ひとつの太陽

# UNIX系のOSではシェルスクリプトとして実行できる
% chmod +x jiban.php
% ./jiban.php
人は誰でも ひとつの太陽

これは本当に特別な挙動です。以下のコードのようにincluderequireでは、shebangの神通力は通用しません。

call.php
#!/usr/bin/env php
<?php

echo "ジバン、ジバン、";

// 先程の jiban.php を読み込む
include __DIR__ . '/jiban.php';
% php call.php
ジバン、ジバン、#!/usr/bin/env php
人は誰でも ひとつの太陽

また、PHPスクリプトを標準入力から実行するときも例外ではありません。

% cat jiban.php | php
#!/usr/bin/env php
人は誰でも ひとつの太陽

(この挙動が厄介で、PHPの文法チェッカーの実装の障碍でした。PHPの細かい挙動を英語で説明するのはめんどくさかった… Fix php checker by zonuexe · Pull Request #1045 · flycheck/flycheck)

範囲コメント (複数行コメント)

/* 〜 */ の間に括られた部分はコメントになります。そのなかには改行コードを含むことができるため、複数の行にまたがることができます。

/*
  複数行コメント
 */

/* 一行になってもいい */

個人的意見では以下のような複数行コメントの利用方法は最悪です。

/* TODO: 後で戻す
if ($a == $b) {
    do_something();
}
 */

このようなコメントをする理由は、おそらく git blame で見たときに変な履歴を残したくなかったのでせう。

grep などのツールで検索したときに do_something() が有効な関数呼び出しなのかわかりにくく、きちんと文法を解釈した上でリファクタリングしようとときにも、漏れる可能性もあるからです。

おすすめはしませんが、本当に後で戻す気があるなら下記の方がちょっとましなレベルです。

// TODO: 後で戻す
if (false) {
if ($a == $b) {
    do_something();
}
}

非常に見苦しいので、おすすめはしません。

git blameが汚れることを気にしないなら、単一行コメントでコメントアウトする方が簡単です。

// TODO: 後で戻す
// if ($a == $b) {
//     do_something();
// }

DocComment

DocCommentは関数やクラスなどを修飾する、特別な形態の範囲コメントです。

これは /** */ で括られるコメントです。
開始の **必ずふたつなので気をつけてください。 (終了の * はひとつで十分です)

/**
 * DocComment
 */

/**
 * 複数行書くこともできる
 *
 * 慣習として、全ての行に * を書く
 */

/** これもDocComment */

この形式のコメントは見慣れないと不自然に見えることもありますが、重要です。

Qiitaの構文ハイライトからして全然別の色での表示ですが、これが示す通り通常の範囲コメントとDocCommentの区別は非常に重要なので混同してはいけません。

以下のようなコメントはDocCommentではありません

/*
 * これはDocCommentではない
 * DocCommentと紛しいので、この形式でコメントを書くことはオススメしません
 */

// DocCommentではない場所で長文を書きたいとき
// こう書いた方が混同されないのでまし

コメントのまとめ

ざっくり4種類(//, #, /* 〜 */, /** 〜 */)を紹介したけど、基本は ///** 〜 */ の二種類がおすすめです。

(__halt_compiler();とかもあるけど、これは普通はコメントって呼ばれないので割愛)

shebang(#!/usr/bin/env php)はスクリプトに便利だけど、includeされるファイルと直接実行するファイルは区別する必要があります。 (その区別を明確にするため、shebangを使ったファイルは拡張子.phpをつけないようにするのもオススメです)

1. DocComment

なぜDocCommentか

前の章「0. PHP文法上のコメント」では、コメントにも複数の種類があること、DocCommentはコメント記法の一種でありながら特異な意味を持つことを述べました。

その裏付けとなる根拠は、token_get_all()が返す値(パーサトークンの一覧)としてT_COMMENTT_DOC_COMMENTが区別されることと、リフレクション機能がこの形式のコメントを特別にサポートすることのふたつです。

Reflection API

リフレクション (情報工学)とは、一般的にプログラミング言語の処理系で、動的に(つまり、実行中に)プログラム自身の内容にアクセスできる機能のことです。

PHPにおいてはReflectionClass, ReflectionException, ReflectionProperty, ReflectionFunction, ReflectionParameterなどのクラスを通じて、それぞれの要素にアクセスできます。さらにPHP7ではReflectionType, ReflectionGeneratorなど、PHP 7.1で ReflectionClassConstantが追加されました。

<?php

namespace Foo;

/**
 * いえーい
 */
abstract class Bar
{
    /** わーお */
    abstract public function hoge();

    /* だめぽ */
    abstract public function fuga();
}

$ref = new \ReflectionClass(\Foo\Bar::class);
var_dump($ref->getDocComment());
// => string(23) "/**\n * いえーい\n */"

var_dump($ref->getMethod('hoge')->getDocComment());
// => string(16) "/** わーお */"

var_dump($ref->getMethod('fuga')->getDocComment());
// => bool(false)

最初の方で そもそもコメントは、実行時に文法的に解釈されない部分のことです。 って書いちゃった覚えがあるのですが、それは嘘だ

これはコメントなのに 実行時にも文字列として取得できます。基本は無視されるけどReflectionを使って読み込むこともできる、といった立ち位置です。

PHPDocタグ, アノテーション

このDocCommentの性質を活かして、ここに「タグ」や「アノテーション」と呼ばれる特別な記法が記述されます。これらの記法は基本的にさまざまなツールが独自で定義したものを勝手に使ってるだけなので、統一フォーマットとして決まった書式はありません

さまざまなタグ・アノテーションの一覧は(作業途中ですが)、emacs-jp/php-info: PHPDoc tags and annotationsにあります。

タグとアノテーションはしばしば混同して呼ばれますが、ここでは@Target("CLASS")のような書式を「アノテーション」、@license MITのような書式を「タグ」と呼ぶことにします。ただしこれらは標準仕様が存在しないので、飽くまで慣用的な呼び名です。

これらのタグの標準仕様としてはPSR-5の策定が作業中(draft)のステータスではありますが… 主導者不在の上に数年レベルで議論が停止してるので話が全然進んでないので、標準化はやはり期待できないです。 (この仕様が標準化されて一番役得を得られるのはJetBrainsのはずなので、JetBrains製品の国内代理店はPhpStorm開発チームに圧力をかけてほしい…)

飽くまで傾向ですが、タグはphpDocumentorやapigenなどのAPIリファレンスの自動作成ツールやPhpStormなどのIDEやPhanなどの静的解析器(型解析器)が、アノテーションはGo! Aspect-Oriented FrameworkDoctrine Projectなどを組み合せて実行時のメタプログラミングに利用されがちです。

実装例はいくつもありますが、ひとつ挙げるならLaravel Collectiveはコントローラクラスのメソッドにルーティング定義を書くことができます。

DocCommentのまとめ

「いろんな」ツールがDocComment形式のコメントをえこひいきしてくれるから、黙って /** 〜 */ って書いてください。

3. PHPDocの書式

ここからがこの記事の本題です

PHPDocとは、慣用的にはソースコードからリファレンスを自動生成ツールのひとつphpDocumentorが処理するDocCommentの書式です。先に述べたIDEや型解析器もこれに準拠した書式をある程度理解できるので、これに倣って書くことで基本的にはほかのツールでも処理できます

PHPDocの書式

PHPDoc形式の書式はPHPDoc reference — phpDocumentorにあります。これを中途半端に訳したものがPHPDoc リファレンス — phpDocumentorにあります。

PHPDocの用語では、これまでDocCommentと読んできたものをDocBlockと定義します。その両者の区別にはあまり大きな意味はないので、同じものと認識して差支ありません。

典型的なDocBlockはこのように書きます。

 /**
  * これはDocブロックの要約です
  *
  * これはDocブロックの説明です。このテキストは複数行を含み、
  * _markdown_ を含んでも良いです。
  *
  * * Markdown風のリスト機能もある
  * * 一度試してみてね
  *
  * 説明文のあとにはタグを含みます。
  * タグによって構造化されたメタデータを記述できます。
  *
  * @author  Mike van Riel <me@mikevanriel.com>
  *
  * @since 1.0
  *
  * @param int    $example  これは函数/メソッドの引数の説明文の例です。
  * @param string $example2 これはふたつめの例です。
  */

この形式の特徴は「要約(Summery)」「説明(Description)」「タグ(Tags)」の順番に並ぶみっつのセクションに別れることです。

要約部(Summery)

要約は、説明したい要素について手短にまとめた文章です。複数行書くこともできますが、一行に収めた方が良いでせう。

注意が必要なのは、要約は文末の.(ピリオド)または空行まで継続されることです。日本語のは、現行の実装では同一視されません。

/**
 * あいうえお.
 * ここから説明
 */

この場合、「あいうえお.」が要約部、「ここから説明」が説明部になります。

/**
 * あいうえお
 * ここから説明
 */

こう書くと、「あいうえお ここから説明」が要約部になります。説明部はありません

/**
 * あいうえお
 *
 * ここから説明
 */

それぞれのセクションを明確に分離したければ、空行(ここでは、*だけの行)で明確に分離してやる必要があります。

説明部(Description)

説明部(デスクリプション)は、細かな説明を書く場所です。phpDocumentorの説明ではMarkdownが書けることになってますが、それ以外の互換性があるAPI作成ツールではMarkdownを処理しないものがある(例:ApiGen 5.0以降)ので注意が必要です。

タグ記述部(Tags)

PHPDocタグは基本的に要約部、説明部の次に書きます。ただし、そのふたつを省略してタグのみを書くことも有効です。

どこにPHPDocを書けるのか

phpDocumentorのマニュアルの説明では、以下のような箇所(言語要素)にDocBlockを書くことができます。

  • 名前空間 / namespace
  • require, require_once
  • include, inclue_once
  • クラス / class
  • インターフェイス / interface
  • トレイト / trait
  • 函数 / function (メソッドを含む)
  • プロパティ / property
  • 定数 / constant
  • 変数(ローカルおよびグローバル) / variables, both local and global scope.

現在となってはincludeなんて書きたくないからクラスはオートローディングするのが常識なので、requireとかincludeにコメントを書くのは、あんまり気にしないでいいです。

次回豫告

ここで紹介した各言語要素に、具体的にどんなコメントを書いていけばいいのか、タグの種類、PHPDocと互換性のあるツールなんかを紹介できるとイイナ。

豫習復讐

今回書いたような内容ってびっくりするほど日本語での体系的なまとめがなくて非常につらい。 (散発的な記事はそれなりにある)

(おまけ) FAQ

Q. ソースコードにコメントを書きすぎると重くならない?

(普通は)なりません。常識外れに長大なコメントが埋め込まれた場合はその限りではありませんが……。

PHPの一般的な運用環境ではOPcacheを有効化するため、リクエストごとに律儀にソースコードのファイルが読み込まれるわけではないからです。そのため、常識的にはソースコードからコメントを取り除くことによるパフォーマンス上のメリットは、得られるとしても本当に本当に微々たるものです。