アイキャッチ画像: デスクの上でパソコンを使用している

前回の私の投稿では Drupal のコーディングスタンダードを翻訳してみました。 Drupal のコーディングスタンダードは前回ご紹介したもの以外にも、 オブジェクト指向、HTML、CSS、JS などさまざまな範囲のものが提案されています。

今回も前回に引き続き、数ある Drupal スタンダードの中から、オブジェクト指向に関するスタンダードを翻訳してご紹介できればと思います。

Drupal オブジェクト指向スタンダード、原文はこちらです。 Object-Oriented Code | Drupal.org

以下、日本語訳になります。2013年12月20日更新分を元にしています。

日本語としてのわかりやすさを重視し、ところによっては直訳、ところによっては意訳にしていますので、参考にされる際はその点ご認識いただければと思います。原文の厳密なニュアンスが知りたい方は原文の方にあたってみてください。

Drupal コーディングスタンダード日本語訳 オブジェクト指向コード

Drupal は一般的なオブジェクト指向コードのための PHP 規約や確立された産業ベスト・プラクティスをフォローしています。ただし、いつものとおり Drupal 特有の考慮ポイントがあります。

別の場所でカバーされているクラス関連のコーディングスタンダード

このページのコンテンツ

  • インデントと空白
  • 命名規約
  • インタフェースの使用
  • 可視性
  • タイプヒンティング
  • インスタンス化
  • チェイニング

インデントと空白

オブジェクト指向にも適用可能な インデント の基本についてのセクションも参照してください。オブジェクト指向特有のポイントというのはほんのわずかです。

クラス/インタフェースの定義の始まりとプロパティ/メソッドの定義の始まりとの間の空行は削除しましょう。

<?php
class GarfieldTheCat implements FelineInterface {
  public function meow() {
...
...
...

プロパティの定義の終わりとメソッドの定義の始まりの間の空行は削除しましょう。

<?php
...
...
...
  protected $lasagnaEaten = 0;
  public function meow() {
    return t('Meow!');

メソッドの終わりの部分とクラスの定義の終わりの部分の間の空行は削除しましょう。

<?php
class GarfieldTheCat implements FelineInterface {
...
...
...
  public function eatLasagna($amount) {
    $this->lasagnaEaten += $amount;
  }
}

命名規約

  1. クラスとインタフェースには大文字始まりのキャメルケース( UpperCamel )の名前を使うこと。
  2. メソッドとクラスプロパティには小文字始まりのキャメルケース( lowerCamel )を使うこと。
  3. クラスやメソッドの名前に短縮語を使う場合には、それもキャメルケースにすること( SampleXMLClass ではなく SampleXmlClass とする)。 [メモ:このスタンダードは 2013 年03月に、以前のスタンダードを覆す形で適用されました ]
  4. クラスには、継承されたクラス名を動的に得るときにどうしても必要な場合以外は、名前にアンダースコアは使わないこと。これは、 Drupal がクラス名とファイル名の一致を強制しないからこそのとても稀なケースです。
  5. 名前には「 Drupal 」を含めないこと。
  6. インタフェースには必ず「 Interface 」というサフィックスをつけること。
  7. テストクラスには必ず「 Test 」というサフィックスを使うこと。
  8. protected や private をつけたプロパティやメソッドの先頭にはアンダースコアをつけないこと。
  9. クラスとインタフェースは名前空間を参照したりしっかり読んだりしなくても単独で何をするかを表す名前をつけること。また、機能性の情報を失ったりあいまいになったりしてしまわない範囲でできるかぎり短い名前にすること。メモ:
    • 明瞭さのために必要な場合やあいまいさを防ぎたい場合には、名前空間の末尾のコンポーネントを名前に含めましょう。
    • Drupal 8.x の例外:データベースクラスが読み込まれる方法にあわせて、データベースエンジンの名前( MySQL など)をエンジン固有のデータベースクラスの名前に含めることはやめましょう。
    • テストクラスについての例外:テストクラスはテスト対象モジュールのコンテクストの中でのみあいまいさを防げばよいでしょう。

スタンドアロンな名前の例:(訳注:ここではリストで表示していますが原文ではテーブル表記になっています)

  • 名前空間: Drupal\Core\Database\Query\
    • 良い名前: QueryCondition
    • 悪い名前: Condition (あいまい) DatabaseQueryCondition ( Database は特に理解を深めません)
  • 名前空間: Drupal\Core\FileTransfer\
    • 良い名前: LocalFileTransfer
    • 悪い名前: Local (あいまい)
  • 名前空間: Drupal\Core\Cache\

    • 良い名前: CacheDatabaseDriver
    • 悪い名前: Database (あいまい/誤解を生む) DatabaseDriver (あいまい/誤解を生む)
  • 名前空間: Drupal\entity\

    • 良い名前: Entity EntityInterface
    • 悪い名前: DrupalEntity (不必要な単語) EntityClass (不必要な単語)
  • 名前空間: Drupal\comment\Tests\

    • 良い名前: ThreadingTest
    • 悪い名前: CommentThreadingTest (コメントのコンテクストでのみ明瞭であればOK) Threading (名前が Test で終わらない)

クラス/インタフェース/メソッド名の完全な例:

<?php
interface FelineInterface {
  public function meow();
  public function eatLasagna($amount);
}
class GarfieldTheCat implements FelineInterface {
  protected $lasagnaEaten = 0;
  public function meow() {
    return t('Meow!');
  }
  public function eatLasagna($amount) {
    $this->lasagnaEaten += $amount;
  }
}

インタフェースの使用

コードを後々拡張する際の柔軟性が高まるので、クラスの実装とは別にインタフェースを定義することは強く推奨されます。分離されたインタフェースの定義は、ドキュメントをコンパクトにまとめ読みやすさも向上してくれます。すべてのインタフェースには、定番のドキュメントスタンダードに従ってドキュメントをもれなく作りましょう。

いつか将来クラスの実装が変わる可能性が少しでもあるのであれば、メソッドの定義をフォーマルなインタフェースに分割するようにしましょう。継承して使うことを意図したクラスについては、それを extend して使うベースクラスを作るよりも、 implement するインタフェースを提供するようにしましょう。

クラスを extend したりインタフェースを implement したりする場合は、 .inc ファイルを使って .info ファイルの中で files[] で指定するようにしましょう。クラスを extend したりインタフェースを implement したりするファイルを include したとき、親となるクラスやインタフェースが読み込まれていなければ PHP はフェイタルエラーを返します。ですので、コントリビュートモジュールや場合によってはコアによってクラスが提供されている場合は、 .module ファイルの中にクラスを置くのは安全ではありません。 .inc ファイルを使って .info ファイルの中で files[] を使う方法の方がベターです。たとえば、モジュールが依存対象を持つ場合でも、あなたの .module ファイルが読み込まれているときにモジュールとその依存先を無効にすることは可能です。レジストリが無効化されたモジュール内のクラスをオートロードすることはないため、これはエラーを発生させます。また、 hook_boot() が呼ばれたとき、モジュールの依存先は読み込まれません。ですので、クラスを追加し、のちほど hook_boot() を実装すれば、あなたのモジュールは依存関係を伴わずに読み込むことができてしまい、フェイタルエラーを生成します。これらのエラーを防ぐには、 .inc ファイルを使い .info ファイルの中で files[] を使うことが必要となります。

可視性

クラスのメソッドとプロパティにおいては必ず public や protected 、 private などの可視性を指定しなければなりません。 PHP 4 スタイルの「 var 」宣言は使ってはいけません。

public プロパティの使用は非推奨です。 public プロパティの使用は副作用をもたらすためです。また、実装の詳細を外部に晒してしまい、クラスの実装の変更を困難にしてしまうからです(これはオブジェクトを使う大きな理由のひとつです)。プロパティはクラス内部のものと考えましょう。

タイプヒンティング

PHP は、関数やメソッドの引数に対して、クラスや配列などのタイプ指定(型指定)をオプションでサポートしています。「タイプヒンティング」と呼ばれますが、これは特定のタイプであることを必須条件とします。特定のタイプにあわないオブジェクトが渡されたら、タイプヒンティングはフェイタルエラーを発生させます。

関数やメソッドが特定のインタフェースに一致することを仮定しているなら、必ずタイプを指定するようにしましょう。必要なインタフェースを指定すると、適切でない値が渡されたときにより有用なエラーメッセージが表示されるようになり、デバッグがかんたんになります。タイプヒンティングで指定タイプとしてクラスを使うのはやめましょう。タイプを指定するときは、必ずインタフェースを指定するようにしましょう。そうすることで、必要に応じて他の開発者が既存のコードに手を加えることなくそれぞれの実装を提供することが可能となります。

例:

<?php
// まちがい:
function make_cat_speak(GarfieldTheCat $cat) {
  print $cat->meow();
}
// 正解:
function make_cat_speak(FelineInterface $cat) {
  print $cat->meow();
}

インスタンス化

クラスを直接作るやり方はおすすめではありません。かわりに、適切なオブジェクトを生成して返すファクトリ関数を使いましょう。これにはふたつのメリットがあります:

  1. 間接レイヤーを提供します。つまり、状況が異なれば異なるオブジェクトを(共通のインタフェースのもとに)返す関数です。
  2. PHP はクラスコンストラクタがチェインされることは許していませんが、関数やメソッドの戻り値のチェインは許しています。

チェイニング

PHP は関数やメソッドから返されたオブジェクトの「チェイン」を許しています。つまり、戻り値オブジェクトのメソッドがすかさず呼ばれることを許しています。

<?php
// チェインしないバージョン
$result = db_query("SELECT title FROM {node} WHERE nid = :nid", array(':nid' => 42));
$title = $result->fetchField();
// チェインしたバージョン
$title = db_query("SELECT title FROM {node} WHERE nid = :nid", array(':nid' => 42))->fetchField();

一般的なルールとして、メソッドが $this を返すようにすればチェイン可能となり、論理的に異なる値が返されることはありません。よくある例は、特定の状態やオブジェクトのプロパティをセットするメソッドです。このタイプのメソッドでは TRUE/FALSE や NULL を返すよりも $this を返す方がよいでしょう。

・・・以上です。

いかがだったでしょうか?

現在主流の Drupal 7 では今回ご紹介したオブジェクト関連のスタンダードを意識する機会はあまりありませんが、次のリリースとなる Drupal 8 ではオブジェクト指向を本格的に取り入れた実装になるようですので、このあたりのスタンダードもぜひしっかりと押さえていきたいところです。