Written by Manabu Bannai

【翻訳】Object-Oriented PHP for Beginners – Tuts+ Code Tutorial【日本語】

PHP PROGRAMMING

Tuts+というプログラミング学習サービスをご存知でしょうか?
海外ではかなり有名なサイトで、プログラミングの体系的に学ぶことができるWebサービスです。残念ながら日本語には対応していませんが、Tuts+にはプログラミングに関する良質記事が数多くあります。

今回はその中でもとくに人気のある記事『Object-Oriented PHP for Beginners – Tuts+ Code Tutorial』を翻訳しました。プログラミングにおいて、オブジェクト指向という考え方は重要視されていますが、なぜオブジェクト指向が大切なのかという点を体系的に解説している記事です。

記事内には600件ほどのコメントが寄せられていますが、いつくかをご紹介します。

Name:lollypopgr
Probably the best tutorial I have read. Congratulations.

Name:maximus
GREAT tutorial! Loved it. So simple and easy to understand

Name:win
suddenly i feel i learn so much
thanks!

英語が得意な方は原文を直接読んでいただけたらと思いますが、当記事では、英語が苦手な日本人向けに記事の翻訳を行ないました。
さっそく翻訳をしていきたいところですが、1つ注意点があります。

Shut the fuck up and write some code.

上記を言葉をご存知でしょうか?
「ぐだぐだ言ってないでコードを書けよ、ハゲ。」という意味ですが、これは本当のことで、プログラミング学習において、実際に手を動かすことはかなり重要です。したがって、当記事をただ読みながすだけではなく、実際にサンプルコードを書きながら読んでみてください。なお、スクリプトを実行するにはCodeRunnerというアプリが便利です。スクリプトをべた書きしたあとに、Ctrl + R ですぐに実行できます。

以上で前置きは終了です。
それでは、じっくり記事をご覧ください。

オブジェクト指向プログラミングとは

多くのPHPプログラム初心者にとって、オブジェクト指向プログラミングはむずかしいです。理由として、一見すると、ハードコーディングよりも、たくさんの構文つかうように感じるためです。

オブジェクト指向プログラミングを理解する

オブジェクト指向プログラミングはプログラマーがおなじタスクをクラスに分類することをいいます。そうすることによって、DRY(Don’t repeat yourself)というプログラミングにおいて大切にされている思考にしたがうことができます。

DRYプログラミングをすることによる1つのメリットは、なにかスクリプトの変更があったさいに、1つのコードのアップデートしか必要としない点です。プログラマーにとって最悪なことは、メンテナンスを必要とするコードが何度も書かれている場合です。すべてのスクリプトを書き換えることは、かなりのストレスです。

多くのプログラマーはオブジェクト指向プログラミングに臆病になりがちです。なせならハードコーディングするよりも数多くの構文を必要とするからです。しかし、実際にはオブジェクト指向プログラミングは分かりやすく、とてもシンプルなアプローチです。

オブジェクトをクラスを理解する

オブジェクト指向プログラミングを深く学習するまえに、オブジェクトクラスのちがいを理解することが大切です。このセクションでは、いくつかのクラスをつくり、異なる機能や使いかたを説明します。

オブジェクトとクラスの違いを認識する

oop
熟練したプログラマーの話を聞いていると、オブジェクト指向プログラミングの結論は、交換可能な構文といいます。しかし、そうではありません。オブジェクト指向プログラミングをかんたんにまめることは難しいです。

たとえば、クラスとは家の設計図です。家が存在していなくても、紙のうえに家のかたちを定義して、その他のパーツとの繋がりを明確化しています。

つぎに、オブジェクトとは、設計図にもとづいて建てられた実際の家です。オブジェクトに蓄積されているデータとしては、木材やワイヤー、そしてコンクリートなどです。設計図のとおりに組み立てられていなかったら、ただの部分的な部品です。しかし、設計図どおりに組み立てられると、それは家となります。

クラスはオブジェクトを構築するために、構造データと実行結果を利用します。それぞれのクラスが独立していることにより、1つのクラスで複数のオブジェクトをつくることができます。こういった分析された設計図をつかうことにより、1つの設計図から数多くの小分けした家がつくれたりします。

クラスをつくる

基礎的な構文をみてみましょう。classを宣言して、{}でかこいます。


<?php

class MyClass{
  // クラスのプロパティをここに書く
}
 

クラスをつくったあとに、newというキーワードを使い新しいクラスをインスタンス化することができます。

$obj = new MyClass;

クラス内のコンテンツをみるには var_dump(); を利用します。

var_dump($obj);

スクリプト全文は以下となります。これを実行してみましょう。


<?php
 
class MyClass{
  // クラスのプロパティをここに書く
}
 
// インスタンス化
$obj = new MyClass;
var_dump($obj);

実行結果は以下となります。

object(MyClass)#1 (0) { }

以上で、シンプルなオブジェクト指向のプログラムができあがりました。

クラスのプロパティを定義する

クラスにデータを追加するためには、プロパティを利用します。オブジェクト内部にあることを除けば、これは変数のようなものです。プロパティはオブジェクトの内部にあるため、オブジェクト内部でしか利用することができません。

以下のように、MyClassというプロパティを追加しましょう。


<?php
 
class MyClass{
	public $property1 = "私はクラスのプロパティです";
}
 
// インスタンス化
$obj = new MyClass;
var_dump($obj);

publicというキーワードはプロパティの公開範囲を決めます。この点はのちほど解説します。つぎに、プロパティは一般的な変数の構文($●●=hoge)をつかって書きます。そして、値(value)が挿入されます。

このプロパティを読み込むには、オブジェクトを参照したあとに、そのプロパティを読み込むかを宣言します。

echo $obj->property1;

なぜ、上記のように読み込む必要があるのでしょうか?なぜなら、クラスでは複数のインスタンスを利用するため、オブジェクトを指定しないと、どのプロパティを実行すべきかわからなくなるためです。また、矢印(->)マークはコンストラクタといい、どのオブジェクトに与えられたプロパティやメソッドにアクセスするかを指定します。

以下のようにスクリプトを変更し、プロパティを出力してみましょう。


<?php
 
class MyClass{
	public $property1 = "私はクラスのプロパティです";
}

// インスタンス化
$obj = new MyClass;
echo $obj->property1;

実行結果は以下となります。

私はクラスのプロパティです

クラスのメソッドを定義する

メソッドとはクラス内の特定のファンクションです。オブジェクトが実行された際のそれぞれの実行結果を、メソッドとして、クラス内に定義します。

たとえば、クラス内にあるプロパティの値(Value)の設定と取得をするメソッドをつくってみます。


<?php
 
class MyClass{
	public $property1 = "私はクラスのプロパティです";
	
	// プロパティの値(Value)の設定をするメソッド
	public function setProperty($newval) {
		$this->property1 = $newval;
	}
	
	// プロパティの値(Value)を取得するメソッド
	public function getProperty() {
		return $this->property1
	}
}

// インスタンス化
$obj = new MyClass;
echo $obj->property1;

ポイント

オブジェクト指向プログラミングでは、オブジェクトがそれ自身を参照するときに $this を使うことができます。メソッド内で作業するときは、クラスの外でオブジェクト名を利用するのと同じ方法で、$this を使います。

メソッドを使うには、まずは所属しているオブジェクトを参照してから、通常のファンクションのように呼び出します。プロパティの値(Value)を変更するには、つぎのようにします。まず、MyClassからプロパティを読み込みます、そして、値(Value)を変更します。具体的には以下のスクリプトとなります。


<?php
 
class MyClass{
	public $property1 = "私はクラスのプロパティです";
	
	// プロパティの値(Value)の設定をするメソッド
	public function setProperty($newval) {
		$this->property1 = $newval;
	}
	
	// プロパティの値(Value)を取得するメソッド
	public function getProperty() {
		return $this->property1 . "
"; } } // インスタンス化 $obj = new MyClass; // プロパティの値(Value)を取得する echo $obj->getProperty(); // 出力:私はクラスのプロパティです // 新しい値(Value)を設定する $obj->setProperty("私は、(新)クラスのプロパティです"); // プロパティを再度読み込み、結果を出力する。 echo $obj->getProperty(); // 出力:私は、(新)クラスのプロパティです

出力結果は以下となります。


私はクラスのプロパティです
私は、(新)クラスのプロパティです

1つのクラスで複数のインスタンスを使うときに、オブジェクト指向プログラミングのすごさがわかります。具体的には以下のスクリプトです。


<?php
 
class MyClass{
	public $property1 = "私はクラスのプロパティです";
	
	// プロパティの値(Value)の設定をするメソッド
	public function setProperty($newval) {
		$this->property1 = $newval;
	}
	
	// プロパティの値(Value)を取得するメソッド
	public function getProperty() {
		return $this->property1 . "
"; } } // インスタンス化 // 2つのオブジェクトを作成します $obj = new MyClass; $obj2 = new MyClass; // プロパティの値(Value)を取得する echo $obj->getProperty(); // 出力:私はクラスのプロパティです echo $obj2->getProperty(); // 出力:私はクラスのプロパティです // 新しい値(Value)を設定する $obj->setProperty("私は、(新)クラスのプロパティです"); $obj2->setProperty("私は、2つ目のインスタンスに属しています"); // プロパティを再度読み込み、結果を出力する。 echo $obj->getProperty(); // 出力:私は、(新)クラスのプロパティです echo $obj2->getProperty(); // 出力:私は、2つ目のインスタンスに属しています

出力結果は以下となります。


私はクラスのプロパティです
私はクラスのプロパティです
私は、(新)クラスのプロパティです
私は、2つ目のインスタンスに属しています

上記のとおり、オブジェクト指向プログラミングは、オブジェクトを2つの実在物にきりわけることができます。そうすることにより、関連したコードを簡単に、細かく分離することができます。

オブジェクト指向プログラミングのマジックメソッドに関して

オブジェクトの使い勝手をよくするために、PHPはいつつかのマジックメソッド(スペシャルメソッド)を用意しています。マジックメソッドはオブジェクトオブジェクト内である一定のアクションをすることで実行されます。マジックメソッドを使うことにより、プログラマーは比較的かんたんに便利なタスクを行なえます。

コンストラクタとデストラクタを使う

オブジェクトが初期化されたときに、すぐにいつくかの値がセットできたら便利です。そういった状況のために、PHPでは__construct()をつかいます。__construct()は新しいオブジェクトが生成されたときに自動的に呼び出されます。

コンストラクタの使いかたをわかりやすく解説するために、MyClassのクラスにコンストラクタを追加してみましょう。コンストラクタは新しいインスタンスが作られるたびに実行されることがわかります。


<?php
 
class MyClass{
	public $property1 = "私はクラスのプロパティです";
	
	public function __construct() {
		echo "クラス('",__CLASS__,"'が初期化されました)". "
"; } // プロパティの値(Value)の設定をするメソッド public function setProperty($newval) { $this->property1 = $newval; } // プロパティの値(Value)を取得するメソッド public function getProperty() { return $this->property1 . "
"; } } // インスタンス化 // オブジェクトを1つ作成します $obj = new MyClass; // プロパティの値(Value)を取得する echo $obj->getProperty(); // 出力:私はクラスのプロパティです echo "スクリプトの読み込みが終了しました"."
";

ポイント

__CLASS__は、呼び出されたクラス名を返すマジックコンスタンスです。そのほかにもいつくかのマジックコンスタンスがありますが、詳細はPHPのマニュアルからどうぞ。
PHP: 自動的に定義される定数 – Manual

出力結果は以下となります。


クラス('MyClass'が初期化されました)
私はクラスのプロパティです
スクリプトの読み込みが終了しました

つぎに、__destruct()というマジックメソッドをみていきます。__destruct()はオブジェクトを壊すために呼び出すファンクションです。これはクラスのクリーンアップに有効です。例えば、データベース接続の停止時などに使います。

では、MyClass内で、__destruct()を使ってみましょう。尚、__destruct()実行時に出力するメッセージも定義できます。


<?php
 
class MyClass{
	public $property1 = "私はクラスのプロパティです";
	
	public function __construct() {
		echo "クラス('",__CLASS__,"'が初期化されました)". "
"; } public function __destruct() { echo "クラス('",__CLASS__,"'が破壊されました"; } // プロパティの値(Value)の設定をするメソッド public function setProperty($newval) { $this->property1 = $newval; } // プロパティの値(Value)を取得するメソッド public function getProperty() { return $this->property1 . "
"; } } // インスタンス化 // オブジェクトを1つ作成します $obj = new MyClass; // プロパティの値(Value)を取得する echo $obj->getProperty(); // 出力:私はクラスのプロパティです echo "スクリプトの読み込みが終了しました"."
";

出力結果は以下となります。


クラス('MyClass'が初期化されました)
私はクラスのプロパティです
スクリプトの読み込みが終了しました
クラス('MyClass'が破壊されました

特定のタイミングで__destruct()を使う場合は、unset();というファンクションをつかいます。具体的には以下のとおりです。


<?php
 
class MyClass{
	public $property1 = "私はクラスのプロパティです";
	
	public function __construct() {
		echo "クラス('",__CLASS__,"'が初期化されました)". "
"; } public function __destruct() { echo "クラス('",__CLASS__,"'が破壊されました"; } // プロパティの値(Value)の設定をするメソッド public function setProperty($newval) { $this->property1 = $newval; } // プロパティの値(Value)を取得するメソッド public function getProperty() { return $this->property1 . "
"; } } // インスタンス化 // オブジェクトを1つ作成します $obj = new MyClass; // プロパティの値(Value)を取得する echo $obj->getProperty(); // 出力:私はクラスのプロパティです // オブジェクトを破壊します unset($obj); echo "スクリプトの読み込みが終了しました"."
";

出力結果は以下となります。


クラス('MyClass'が初期化されました)
私はクラスのプロパティです
クラス('MyClass'が破壊されました
スクリプトの読み込みが終了しました

Stringを変換する

スクリプトが、文字列としてMyClassを出力しようとしてエラーを起こすことを避けるために、 __toString()というマジックメソッドを使います。

__toString()なしで、オブジェクトと文字列として出力しようとするとfatal errorが起きます。では、実際に試してみましょう。


<?php
 
class MyClass{
	public $property1 = "私はクラスのプロパティです";
	
	public function __construct() {
		echo "クラス('",__CLASS__,"'が初期化されました)". "
"; } public function __destruct() { echo "クラス('",__CLASS__,"'が破壊されました"; } // プロパティの値(Value)の設定をするメソッド public function setProperty($newval) { $this->property1 = $newval; } // プロパティの値(Value)を取得するメソッド public function getProperty() { return $this->property1 . "
"; } } // インスタンス化 // オブジェクトを1つ作成します $obj = new MyClass; // オブジェクトと文字列として出力します echo $obj; // オブジェクトを破壊します unset($obj); echo "スクリプトの読み込みが終了しました"."
";

出力結果は以下となります。


クラス('MyClass'が初期化されました)
Catchable fatal error: Object of class MyClass could not be converted to string

__toString()メソッドを使うことでエラーを解決できます。


<?php
 
class MyClass{
	public $property1 = "私はクラスのプロパティです";
	
	public function __construct() {
		echo "クラス('",__CLASS__,"'が初期化されました)". "
"; } public function __destruct() { echo "クラス('",__CLASS__,"'が破壊されました"; } public function __toString() { echo "toStringメソッドを使っています⇒"; return $this->getProperty(); } // プロパティの値(Value)の設定をするメソッド public function setProperty($newval) { $this->property1 = $newval; } // プロパティの値(Value)を取得するメソッド public function getProperty() { return $this->property1 . "
"; } } // インスタンス化 // オブジェクトを1つ作成します $obj = new MyClass; // オブジェクトと文字列として出力します echo $obj; // オブジェクトを破壊します unset($obj); echo "スクリプトの読み込みが終了しました"."
";

出力結果は以下となります。


クラス('MyClass'が初期化されました)
toStringメソッドを使っています⇒私はクラスのプロパティです
クラス('MyClass'が破壊されました
スクリプトの読み込みが終了しました

ポイント

マジックメソッドはその他にも数おおくあります。マジックメソッドをさらに学びたい方はPHPのマニュアルからどうぞ。
PHP: マジックメソッド – Manual

クラスの継承をつかう

クラスはメソッドとプロパティを他のクラスに継承することができます。その際にextendsというキーワードを使います。たとえば、MyClassを継承したクラスをつくるには次のようにします。


<?php
 
class MyClass{
	public $property1 = "私はクラスのプロパティです";
	
	public function __construct() {
		echo "クラス('",__CLASS__,"'が初期化されました)". "
"; } public function __destruct() { echo "クラス('",__CLASS__,"'が破壊されました"; } public function __toString() { echo "toStringメソッドを使っています⇒"; return $this->getProperty(); } // プロパティの値(Value)の設定をするメソッド public function setProperty($newval) { $this->property1 = $newval; } // プロパティの値(Value)を取得するメソッド public function getProperty() { return $this->property1 . "
"; } } class MyOtherClass extends MyClass{ public function newMethod() { echo "".__CLASS__."を継承した新しいメソッドです"; } } // インスタンス化 $newobj = new MyOtherClass; // オブジェクトと文字列として出力します echo $newobj->newMethod(); echo "スクリプトの読み込みが終了しました"."
";

出力結果は以下となります。


クラス('MyClass'が初期化されました)
MyOtherClassを継承した新しいメソッドです
スクリプトの読み込みが終了しました
クラス('MyClass'が破壊されました

継承したプロパティとメソッドを上書きする

新しいクラスを作ったさいに、既存のプロパティやメソッドを上書きすることができます。方法は単純で、新しいクラスでそのことを再度宣言するだけです。


<?php
 
class MyClass{
	public $property1 = "私はクラスのプロパティです";
	
	public function __construct() {
		echo "クラス('",__CLASS__,"'が初期化されました)". "
"; } public function __destruct() { echo "クラス('",__CLASS__,"'が破壊されました"; } public function __toString() { echo "toStringメソッドを使っています⇒"; return $this->getProperty(); } // プロパティの値(Value)の設定をするメソッド public function setProperty($newval) { $this->property1 = $newval; } // プロパティの値(Value)を取得するメソッド public function getProperty() { return $this->property1 . "
"; } } class MyOtherClass extends MyClass{ public function __construct() { echo "".__CLASS__."の新しいコンストラクタです" ."
"; } public function newMethod() { echo "".__CLASS__."を継承した新しいメソッドです". "
"; } } // インスタンス化 $newobj = new MyOtherClass; // オブジェクトと文字列として出力します echo $newobj->newMethod(); // 親クラスのメソッドを使います echo $newobj->getProperty();

出力結果は以下となります。


MyOtherClassの新しいコンストラクタです
MyOtherClassを継承した新しいメソッドです
私はクラスのプロパティです
クラス('MyClass'が破壊されました

既存メソッドの機能を上書きしている間も保存する

子クラスに新しい機能追加しつつ、既存メソッドの機能を保持することができます。その際に、::parentというキーワードを使います。


<?php
 
class MyClass{
	public $property1 = "私はクラスのプロパティです";
	
	public function __construct() {
		echo "クラス('",__CLASS__,"'が初期化されました)". "
"; } public function __destruct() { echo "クラス('",__CLASS__,"'が破壊されました"; } public function __toString() { echo "toStringメソッドを使っています⇒"; return $this->getProperty(); } // プロパティの値(Value)の設定をするメソッド public function setProperty($newval) { $this->property1 = $newval; } // プロパティの値(Value)を取得するメソッド public function getProperty() { return $this->property1 . "
"; } } class MyOtherClass extends MyClass{ public function __construct() { // 親クラスのコンストラクタを呼び出します parent::__construct(); echo "".__CLASS__."の新しいコンストラクタです" ."
"; } public function newMethod() { echo "".__CLASS__."を継承した新しいメソッドです". "
"; } } // インスタンス化 $newobj = new MyOtherClass; // オブジェクトと文字列として出力します echo $newobj->newMethod(); // 親クラスのメソッドを使います echo $newobj->getProperty();

出力結果は以下です。


クラス('MyClass'が初期化されました)
MyOtherClassの新しいコンストラクタです
MyOtherClassを継承した新しいメソッドです
私はクラスのプロパティです
クラス('MyClass'が破壊されました

プロパティとメソッドのアクセス制御

オブジェクト、プロパティ、メソッドはアクセス制御を追加できます。この制御で、どのように、そして、どこからプロパティやメソッドにアクセスできるかをコントロールできます。キーワードとしては次の3つです。publicprotectedprivate。さらにアクセス制御をするために、メソッドやプロパティは、staticという宣言をすることができます。staticを宣言すると。インスタンス化していないクラスにアクセスすることができます。

アクセス制御における『Public』とは

当記事でつかってきたメソッドやプロパティは、すべてがpublicでした。これは、クラス内部からでも外部からでもアクセスできることを意味します。

アクセス制御における『Protected』とは

プロパティやメソッドはprotectedで宣言された場合、クラス内部か子クラスの内部からのみアクセスできます。(クラスは、protectedメソッドをもつクラスを拡大できます)

では、MyClass内で、getProperty()メソッドをprotectedとして宣言してみましょう。そして、クラス外部からアクセスをしてみます。


<?php
 
class MyClass{
	public $property1 = "私はクラスのプロパティです";
	
	public function __construct() {
		echo "クラス('",__CLASS__,"'が初期化されました)". "
"; } public function __destruct() { echo "クラス('",__CLASS__,"'が破壊されました"; } public function __toString() { echo "toStringメソッドを使っています⇒"; return $this->getProperty(); } // プロパティの値(Value)の設定をするメソッド public function setProperty($newval) { $this->property1 = $newval; } // プロパティの値(Value)を取得するメソッド protected function getProperty() { return $this->property1 . "
"; } } class MyOtherClass extends MyClass{ public function __construct() { // 親クラスのコンストラクタを呼び出します parent::__construct(); echo "".__CLASS__."の新しいコンストラクタです" ."
"; } public function newMethod() { echo "".__CLASS__."を継承した新しいメソッドです". "
"; } } // インスタンス化 $newobj = new MyOtherClass; // オブジェクトと文字列として出力します echo $newobj->getProperty();

出力結果は以下となります。


クラス('MyClass'が初期化されました)
MyOtherClassの新しいコンストラクタです
Fatal error: Call to protected method MyClass::getProperty() from context

では、MyOtherClass内に新しいメソッドとして、getProperty()を作ってみましょう。


<?php
 
class MyClass{
	public $property1 = "私はクラスのプロパティです";
	
	public function __construct() {
		echo "クラス('",__CLASS__,"'が初期化されました)". "
"; } public function __destruct() { echo "クラス('",__CLASS__,"'が破壊されました"; } public function __toString() { echo "toStringメソッドを使っています⇒"; return $this->getProperty(); } // プロパティの値(Value)の設定をするメソッド public function setProperty($newval) { $this->property1 = $newval; } // プロパティの値(Value)を取得するメソッド protected function getProperty() { return $this->property1 . "
"; } } class MyOtherClass extends MyClass{ public function __construct() { // 親クラスのコンストラクタを呼び出します parent::__construct(); echo "".__CLASS__."の新しいコンストラクタです" ."
"; } public function newMethod() { echo "".__CLASS__."を継承した新しいメソッドです". "
"; } public function callProtected() { return $this->getProperty(); } } // インスタンス化 $newobj = new MyOtherClass; // オブジェクトと文字列として出力します echo $newobj->callProtected();

出力結果は以下です。


クラス('MyClass'が初期化されました)
MyOtherClassの新しいコンストラクタです
私はクラスのプロパティです
クラス('MyClass'が破壊されました

アクセス制御における『Private』とは

privateと宣言されたプロパティやメソッドは、それを定義しているクラス内でしか実行できません。これは、たとえ子クラスが親クラスを継承したからといっても、子クラスでは継承したプロパティやメソッドが使えないことを意味します。

では、MyClass内で、getProperty()メソッドをprivateとして宣言してみましょう。そして、子クラス(MyOtherClass)からアクセスをしてみます。


<?php
 
class MyClass{
	public $property1 = "私はクラスのプロパティです";
	
	public function __construct() {
		echo "クラス('",__CLASS__,"'が初期化されました)". "
"; } public function __destruct() { echo "クラス('",__CLASS__,"'が破壊されました"; } public function __toString() { echo "toStringメソッドを使っています⇒"; return $this->getProperty(); } // プロパティの値(Value)の設定をするメソッド public function setProperty($newval) { $this->property1 = $newval; } // プロパティの値(Value)を取得するメソッド private function getProperty() { return $this->property1 . "
"; } } class MyOtherClass extends MyClass{ public function __construct() { // 親クラスのコンストラクタを呼び出します parent::__construct(); echo "".__CLASS__."の新しいコンストラクタです" ."
"; } public function newMethod() { echo "".__CLASS__."を継承した新しいメソッドです". "
"; } public function callProtected() { return $this->getProperty(); } } // インスタンス化 $newobj = new MyOtherClass; // オブジェクトと文字列として出力します echo $newobj->callProtected();

出力結果は以下となります。


クラス('MyClass'が初期化されました)
MyOtherClassの新しいコンストラクタです
Fatal error: Call to private method MyClass::getProperty() from context 'MyOtherClass'

アクセス制御における『Static』とは

staticと宣言されたメソッドやプロパティは、インスタンス化なしでも利用することができます。単純に、スコープ定義演算子 (::)と一緒に、プロパティやメソッドのクラス名呼び出すだけです。

staticなプロパティを利用することのメリットは、スクリプト実行中に値(Value)を保持できるてんです。

では、実際にためしてみましょう。$countというstaticなプロパティと、$plusOne()というstaticなメソッドをMyClassに追加してみます。そして、do...whileループで$count値を増加させてみます。(※値が10以下のあいだだけ実行します)


<?php
 
class MyClass{
	public $property1 = "私はクラスのプロパティです";
	
	public static $count = 0;
	
	public function __construct() {
		echo "クラス('",__CLASS__,"'が初期化されました)". "
"; } public function __destruct() { echo "クラス('",__CLASS__,"'が破壊されました"; } public function __toString() { echo "toStringメソッドを使っています⇒"; return $this->getProperty(); } // プロパティの値(Value)の設定をするメソッド public function setProperty($newval) { $this->property1 = $newval; } // プロパティの値(Value)を取得するメソッド private function getProperty() { return $this->property1 . "
"; } public static function plusOne() { return "カウント:".++self::$count ."
"; } } class MyOtherClass extends MyClass{ public function __construct() { // 親クラスのコンストラクタを呼び出します parent::__construct(); echo "".__CLASS__."の新しいコンストラクタです" ."
"; } public function newMethod() { echo "".__CLASS__."を継承した新しいメソッドです". "
"; } public function callProtected() { return $this->getProperty(); } } do{ echo MyClass::plusOne(); }while (MyClass::$count < 10);

ポイント

staticなプロパティにアクセスする際には、スコープ定義演算子 (::)の後にドルマーク($)を使います。

出力結果は以下です。


カウント:1
カウント:2
カウント:3
カウント:4
カウント:5
カウント:6
カウント:7
カウント:8
カウント:9
カウント:10

コメントブロックの書き方

クラスを説明する際には、コメントブロックという書き方が一般です。これはオブジェクト指向プログラム正式なパートではありませんが、コメントブロックは幅広く受け入れられています。EclipseやNetBeansのような有名なソフトウェア開発ツールにおいても、コメントブロックは使われています。

コメントブロックは以下のように書きます。


/**
 * これが基本的なコメントブロックです
 */

タグを利用することで、コメントブロックの力が発揮されます。使い方としては、タグ名のすぐあとにアットマーク(@)をおき、プログラマーがファイルの著者、クラスのライセンス、プロパティやメソッドの情報、そして、その他の有用な情報を書き記します。

一般的なタグの使いかたは次のとおりです。

@author
クラス、ファイル、メソッド、コードの著者を表記します。
フォーマット:Manabu Bannai

@copyright
著作権の日付と、著者の名前を書きます。
フォーマット:2014 Copyright Holder

@license
該当エレメントのライセンス先URLをあらわします。
フォーマット:http://hoge.com/path/license.txt License Name

@var
プロパティの変数やクラスがもつタイプや説明を書きます。
フォーマット:type element description

@param
ファンクションやメソッドのパラメータのタイプと説明を書きます
フォーマット:type $element_name element description

@return
ファンクションやメソッドの返り値のタイプや説明を書きます。
フォーマット:type return element description

以下が、コメントブロック付きスクリプト例です。


<?php
 
/**
 * simple class
 *
 * クラスの説明文です。
 * 長文を書く必要はありませんが、手短な説明は必要です。
 * 
 * @author Manabu Bannai 
 * @copyright 2014 Manabu Bannai
 * @license https://manablog.org/license/3.txt PHP License 3.01
 */
class SimpleClass
{
  /**
   * public変数
   *
   * @var string クラスのためにデータを保持します
   */
  public $foo;
 
  /**
   * クラスのインスタンス作成時の新しい値に $foo を設定します。
   *
   * @param string $val a value required for the class
   * @return void(値を返さない)
   */
  public function __construct($val)
  {
      $this->foo = $val;
  }
 
  /**
   * 2つの整数を乗算する
   *
   * 整数のペアを受け入れ、両者の積を返します。
   *
   * @param int $bat が乗算される
   * @param int $baz が乗算される
   * @return int 2つのパラメータの積
   */
  public function bar($bat, $baz)
  {
      return $bat * $baz;
  }
}

コメントブロックの価値はわかりやすいです。一度コードに目をとおすだけで、クラスがどのように実行されているか把握することができます。そのため、他のプログラマーがコードを変更する場合でも、コードの意味や、保持している値がわからなくなることがありません。

オブジェクト指向プログラミングとそうでないプログラミングの比較

コードを書くことにおいて、正しい方法や間違った方法は存在しないと言われています。確かにことですが、オブジェクト指向プログラミングを使わないことで生産性が下がる場合があります。このセクションではオブジェクト指向プログラミングを使う3つのメリットを説明していきます。

その1:実装がかんたんであること

最初は疑うかもしれませんが、オブジェクト指向プログラミングはデータをあう買うことにおいて、かんたんなアプローチです。なぜなら、オブジェクトは内部的にデータに保持でき、2つのファンクションを正常に動作させるために変数をわたす必要がありません、

また、複数のインスタンスを同時に存在させることができ、おおきなデータを扱う場合にはとても便利です。たとえば、あなたが2人の人の情報をファイルで処理する場合です。彼らには、名前、職業、歳など必要です。

オブジェクト指向プログラミングではない方法

以下が例となります。


<?php

function changeJob($person, $newjob) {	
	$person["職業"] = $newjob;	// $personの職業を変更する
	return $person;	
}

function happyBirthday($person) {
	++$person["年齢"];	// $personの年齢を増やす
	return$person;
}

$person1 = array(
	"名前" => "山田 太郎",
	"職業" => "教師",
	"年齢" => "34"
);

$person2 = array(
	"名前" => "佐々木 花子",
	"職業" => "エンジニア",
	"年齢" => "26"
);

// それぞれの人物の値を出力する
echo "人物①:", print_r($person1, true);
echo "人物②:", print_r($person2, true);

//山田 太郎さんが誕生日を迎え、かつ、昇級しました
$person1 = changeJob($person1, "校長");
$person1 = happyBirthday($person1);

// 佐々木 花子さんは誕生日を迎えました
$person2 = happyBirthday($person2);

// そう一度、それぞれの人物の値を出力する
echo "人物①:", print_r($person1, true);
echo "人物②:", print_r($person2, true);

出力結果は以下となります。


人物①:Array
(
    [名前] => 山田 太郎
    [職業] => 教師
    [年齢] => 34
)
人物②:Array
(
    [名前] => 佐々木 花子
    [職業] => エンジニア
    [年齢] => 26
)
人物①:Array
(
    [名前] => 山田 太郎
    [職業] => 校長
    [年齢] => 35
)
人物②:Array
(
    [名前] => 佐々木 花子
    [職業] => エンジニア
    [年齢] => 27
)

このコードは悪いわけではありませんが、コードを書くうえで考えておかなければいけないことがあります。人物の属性の影響を受けたArrayは、それぞれのファンクションが呼び出されてから、値が渡され、返さなくてはいけません。それはエラーの原因になりやすいです。

このコードをよくするには、可能な限り開発者次第で自由度がきくかたちにしておくことが望ましいです。現在のオペレーションにおいて、必ず必要となる情報のみをファンクション内で通過させます。


<?php
 
class Person{
	private $_name;
	private $_job;
	private $_age;
	
	public function __construct($name, $job, $age) {
		$this->_name = $name;
		$this->_job = $job;
		$this->_age = $age;
	}
	
	public function changeJob($newjob) {
		$this->_job = $newjob;
	}
	
	public function happyBirthday() {
		++$this->_age;
	}
	
}

// 2名の人物を作成する
$person1 = new Person("山田 太郎", "教師", 34);
$person2 = new Person("佐々木 花子", "エンジニア", 26);

// それぞれの人物の値を出力する
echo "人物①:", print_r($person1, true);
echo "人物②:", print_r($person2, true);

//山田 太郎さんが誕生日を迎え、かつ、昇級しました
$person1->changeJob("校長");
$person1->happyBirthday();

// 佐々木 花子さんは誕生日を迎えました
$person2->happyBirthday();

// そう一度、それぞれの人物の値を出力する
echo "人物①:", print_r($person1, true);
echo "人物②:", print_r($person2, true);

出力結果は以下となります。


人物①:Person Object
(
    [_name:Person:private] => 山田 太郎
    [_job:Person:private] => 教師
    [_age:Person:private] => 34
)
人物②:Person Object
(
    [_name:Person:private] => 佐々木 花子
    [_job:Person:private] => エンジニア
    [_age:Person:private] => 26
)
人物①:Person Object
(
    [_name:Person:private] => 山田 太郎
    [_job:Person:private] => 校長
    [_age:Person:private] => 35
)
人物②:Person Object
(
    [_name:Person:private] => 佐々木 花子
    [_job:Person:private] => エンジニア
    [_age:Person:private] => 27
)

クラスの定義後に、人々の情報を書き換えることがかなりかんたんになりました。人々の情報はメソッドを通す必要がなくなり、必ず必要となる情報だけ、メドッド内にとおされています。

オブジェクト指向プログラミングをただしく利用すると、生産性を向上されることができます。

小規模の開発では、この違いは小さくみえるかもしれません。しかし、アプリケーションの規模が大きくなるに連れて、正しく導入されたオブジェクト指向プログラミングは、大きな生産性向上に繋がります。

ポイント

すべてのコードをオブジェクト指向にする必要はありません。場合に応じてオブジェクト指向プログラミングを使うかどうかを決めていくことが大切です。

その2:より編成しやすいこと

オブジェクト指向プログラミングにおける、もう1つのメリットとしては、

簡単にパッケージ化できる

点です。それぞれのクラスをそれぞれ分割されたファイルに保存しておきます。そして、もし汎用的な命名規則が使われていたら、該当のクラスにアクセスすることはかなり簡単です。

想像してみてください。150個のクラスがアプリケーションフォルダのルートファイルから呼び出されています。そして、150個のクラスはclass.classname.inc.phpという規則に基づいて命名されており、ファイルはすべてincフォルダに格納されています。

その際には、PHPの__autoload()ファンクションを使うことにより、必要に応じてクラスを呼び出すことが出来ます。コード内で150個のコントローラーファイルを必要に応じて、呼び出すよりもはるかに生産的です。具体的には次のコードとなります。


<?php
function __autoload($class_name){
	include_once 'inc/class.' . $class_name . '.inc.php';
}

それぞれをクラスを分離したファイルにすることで、何度もコードをコピペするよりも生産的であることがわかったかと思います。

その3:メンテナンスが楽であること

オブジェクト指向プログラミングを性質をさらに活かすことで、スパゲッティコードを修正するよりもはるかに簡単にすることができます。

もし、ある特定のArrayが新たな属性を取得した際に、オブジェクト指向プログラミングをしていないと、新たな属性を追加するたびにArrayをくり返さなくてはいけません。

新たなプロパティを追加するとき、オブジェクト指向プログラミングは潜在的にアップデートすることができます。そして、適切にメソッドを扱うことができます。

– ・ – ・ – ・ – ・ –

この章では、オブジェクト指向プログラミングと"don't repeat yourself" (DRY)に基づいて、さまざまなメリットを書いてきました。オブジェクト指向プログラミングは、悪夢を引き起こさないコードを書くことができると同時に、ひどいオブジェクト指向プログラムを生成することにも繋がります。『Pro PHP and jQuery』ではそういった具体例を数多く記載しています。

まとめ

ここまで読んでくださったみなさんにとって、オブジェクト指向プログラミングが便利であることは伝わったかと思います。また、オブジェクト指向プログラミングを学ぶことは、プログラミングスキルのスキルアップに繋がります。オブジェクト指向プログラミングを適切に導入することで、読みやすく、メンテナンスがしやすく、拡張性のあるコードを作ることができます。

以上となります。
※翻訳に誤りなどありましたら、ご指摘いただけるとたすかります。

原文:Object-Oriented PHP for Beginners - Tuts+ Code Tutorial

※P.S:無料メルマガで発信中:過去の僕は「ブログ発信で5億円」を稼ぎました。次は「30億円」を目指します。挑戦しつつ、裏側の思考を「メルマガ」から発信します。不満足なら1秒で解約できます。無料登録は「こちら」です。