概念・言語仕様・補完的な実装
参考:
PHPのオブジェクト指向基礎
http://www.objective-php.net/basic/interface
PHP: 言語リファレンス - Manual
http://goo.gl/TFWYSf
オブジェクト指向
「オブジェクト」は「車」とかいうレベルの「物体, もの, 実物, 対象」といったような「概念」
「クラス」はその設計図にあたる
「インスタンス」は設計図を実体化した実体にあたる
「メンバ」は「プロパティ(フィールド・属性)やメソッド」の総称こと
修飾子・キーワード
public:公開
private:非公開・継承されない
protected:非公開・継承される
final:メソッドの拡張を許さない(privateだけでは拡張ではなく同名定義できてしまう)
スタティック
プロパティに絡まない処理はスタティックにしてインスタンスなしで呼び出す。(速度は速い)
文字処理とか計算処理とか、スタティック関数だけ集めたクラスを「ラッパークラス」と呼ぶ
クラス定数
グローバルに定数を定義でき、通常の定義よりも分類されるため、重複の心配が減り、整理がつけられる
抽象クラス・抽象メソッド
継承を強制する抽象クラスと、実装を強制するメソッドを利用して、
任意のメソッドを実装させて機能を実現させるクラスを作ることが出来る。
インターフェイス
実装の道筋伊達をするためのもので、多重継承(多重実装?)もできる。
定義できるのは抽象メソッドのみ
抽象メソッドだが「abstract」はいらない
アクセス修飾子には「public」しか指定できない
直接インスタンスの生成はできない
直接インスタンスを生成できないので継承するわけですが、インターフェイスの場合、継承とは呼ばず、「実装」と呼ぶ。
定義済みのインターフェイスとクラス
組み込み
Traversable
Iterator
IteratorAggregate
ArrayAccess
Serializable
Closure
Generator
SPLのインターフェイス
Countable
OuterIterator
RecursiveIterator
SeekableIterator
ポリモーフィズム
PHPの場合、クラスは異なっても同じ名前のメソッドでいろんな動きを実現させる事。
抽象クラスやインターフェイスで実現される。
型定義・キャスト・タイプヒンティング
型定義はPHPではサポートしないが、キャストはできる。
タイプヒンティングを使って、継承の際に受け付けるインスタンスを指定(オブジェクト・インターフェイス・配列・コールバック)できる。
リソースやトレイトはタイプヒンティングにつかえない。
疑似的な型および変数
mixed
引数に多様な型 (全てである必要はない) を使うことができることを示します。
number
引数が integer または float のどちらでもよいことを示します。
callback
callableと同じものを指す
void
返り値の型が void である場合は、 返り値に意味がないことを表します。
パラメータ一覧で void が使用されている場合は、 その関数がパラメータを受け付けないことを表します。
...
関数のプロトタイプ宣言における $... は、 …など を表します。 この変数名を用いるのは、たとえば任意の数の引数を取りうる関数などです。
例外処理
PHPは勝手に例外を投げてくれないので自分でいれる。
フレームワークやライブラリは投げることがあるだろう。
file_get_contents とかでよく使う。
表示ではなくログに記録するおまじない
Logger::write($e->getMessage());
無名関数・クロージャー
少々問題が深い気がするので概念の勉強から少し深掘りメモ。
http://qiita.com/hugo-sb/items/3e344486658e3cfbd407
http://php.net/manual/ja/functions.anonymous.php
PHPのクロージャーの実装はどうなっているんだろう。
メリットと使いどころはどうなんだろう。
下記にはポリモーフィズムを実現するのに無名関数を使い、foreachで関数の入った配列を回したりしている。
file open()やusort()とかに無名関数を渡す方法も実用的かな。
クロージャーの例も基礎的な部分でわかりやすい。
バッチでプロセスを分けるほどではないけども隔離したい処理とかを、
クロージャーに閉じ込めて処理しても良いかもなあ。
$daughter_hello = function() {
echo "こんにちは娘です!<br />";
};
$childlen_hello = array($son_hello, $daughter_hello, $daughter_hello, $son_hello);
foreach ($childlen_hello as $hello) {
// 実行したい関数が$helloに入ってる
$hello();
}
下記にも詳しいメリットなどがかいてある、、長くてなかなか、、あとでよむか。
クロージャーによって可読性が格段に上がる場合(分離して再利用しない処理群)を例示しているみたい。
PHPのクロージャの記述性については、必ずしも高いとは言えない。が、記述のコストを払うことで、可読性については間違いなく向上する。なぜなら、クロージャを使うことで、読むべき状態変数が明らかに少なくなり、読む必要のないスコープ外の部分が明確になるからだ。コードを読むというのは、ステップを表面的に追いかけることではなく、関連する部分の意味をすべて読解/解析する仕事であるはずだ。意味のまとまりの結合が適切に切れてくれるということが、安易に平易な字面を使うことに勝る、本当の読みやすさではないだろうか。
下記にも、関数名が参照になっているとか、スコープについての説明が書いてある。
こちらも結構長い、、あとでよむか。
アサート処理のような1カ所(アサートオプションの指定時)でしか使わないようなものはリファクタリングの負荷を下げるために無名関数がいいみたい。(まあ、そこまで違いは無いかなと思わなくもないけど。)
ラムダを使わない場合に定義している関数は 1 ヶ所でしか使われないことに注意してください。コードは作成する機会よりも読む機会の方がはるかに多いため、コードの単純化が重要であることを理解する必要があります。
クロージャーのスコープでuseで定義した場合にはレキシカル・スコープになるあたりの処理イメージもちょっと詳しい説明がある。(スタックに入れずにストレージに保存するとか、参照の場合を例に出したりとか(静的領域、ヒープ領域、スタック領域とかの理解が関係するのかな))
下の方にある紹介記事の複数のリンクも実用的っぽい。。
配列型のラッパークラスへ渡す無名関数とか。。
下記にはクロージャーを変数に定義してから値を渡す例とかがある。
マジックメソッド__invoke も同じようなことが出来るとも記載がある。
<?php
$f = function($times = 2) {
return function($v) use ($times) {
return pow($v, $times);
};
};
$cube = $f(3);
echo $cube(1) . '<br>';
echo $cube(2) . '<br>';
下記ではクロージャーでの5.3と5.4のデストラクタの呼び出しの違いの記載がある。気をつける。
スコープ・スタティック
・スーパーグローバルはどのスコープからも参照できる。($_POSTや$_GLOBALS)
・globalキーワードはグローバルスコープで使うことができ、関数内からincludeしたときに評価される。
・スタティック変数は初期化を最初しかしないためにカウンターの実装などにつかえる。
・スタティックはコンパイル時に解決されるので式の代入にはパースエラーが発生する。
・スタティック変数にはリファレンスを保持できない。(クロージャーには保持できるよね。)
$a = 1; /* グローバルスコープ */
function test()
{
echo $a; /* ローカルスコープ変数の参照 */
}
可変変数・動的変数・可変関数
http://php.net/manual/ja/language.variables.variable.php
http://php.net/manual/ja/functions.variable-functions.php
・波括弧をつかって明確化するのがよさそう。
・$thisとスーパーグローバル変数は可変変数として呼び出せない。
・下記面白い。スペースあっても解釈するのね。
//You can even add more Dollar Signs
$Bar = "a";
$Foo = "Bar";
$World = "Foo";
$Hello = "World";
$a = "Hello";
$a; //Returns Hello
$$a; //Returns World
$$$a; //Returns Foo
$$$$a; //Returns Bar
$$$$$a; //Returns a
$$$$$$a; //Returns Hello
$$$$$$$a; //Returns World
定数
・PHP5.3以降であればグローバルスコープにてconstキーワードで定義可能。
・定義済み定数に5.4から__TRAIT__が追加されている。
・下記で一覧取得可能。
get_defined_constants()
演算子
・代数演算子の累乗が5.6以降追加。
制御構造
http://php.net/manual/ja/control-structures.declare.php#control-structures.declare.ticks
http://ku.ido.nu/post/90223971739/what-is-tick-of-php
ticksとdeclare(ディクレア)は、CPU時間毎で処理を実行するのではなく、PHPのOPCODEの一定間隔毎に実行される処理を設定するみたい。
コンパイル時に解決するため動的に変数で引数を指定するとコンパイルエラーとなる。
gotoは基本的には使わない方が良いが、場合によっては有用となる可能性があるかも知れないみたい。
・普通は関数を使うけど関数にまとめたくないとき+無名関数を使うほどではないとき(数値計算とかかなあ、関数で良いか)
・ガード節を関数を使わず記述
関数呼び出しよりもオーバーヘッドが少ないということもあるみたい。(そもそもPHPでやる範囲なら、、)
継続
カウンターなどをクロージャーなどで途中経過を保持しておくような場合のことかな。
委譲
アプリケーションのクラスへsetLogger()メソッドを使ってロガーのインスタンスを渡すなどの処理
has-a関係になる。こういうロガーを使いたいだけの場合(has-a)には継承でなく委譲を使う。
マジックメソッド
効率が上がって生産性が上がるなら使うが、速度を犠牲にする側面があるので注意して実装したほうがいいみたい。
その辺をポリモーフィズムみたいな感じで切り替えられないのかな。
(インスタンス化かそれ以前に選択って、それならコードでかけってことになるか。マジックメソッドと疑似・等価なコードってテンプレがあるのかな。)
以下の関数名 __construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __toString(), __invoke(), __set_state(), __clone() および __debugInfo() は、PHP クラスにおける特殊関数の名前です。
マジックメソッドを使用すると動作速度に影響しますし、
あまりトリッキーなことはやらないほうが身のためかもしれません。
一部のマジックメソッドのベンチマーク
static method.
2.1691780090332 sec.
instance method.
2.7707538604736 sec.
magic method __call.
6.9698939323425 sec.
magic method __get.
0.43218612670898 sec.
名前空間
この項は多分理解が足りない気がするのでちゃんとよむ。
・「PHP」など予約された名前空間もある。
・"namespace 演算子および NAMESPACE 定数"で名前空間を取得できる。
PHP の世界では、名前空間は次のふたつの問題を解決するための手段として用意されています。
・あなたが作成したコードと PHP の組み込みのクラス/関数/定数 あるいはサードパーティのクラス/関数/定数の名前が衝突する
・最初の問題を解決するためには、Extra_Long_Names のような長い名前をつけなければならない
名前空間を含むファイルでは、他のコードより前にファイルの先頭で名前空間を宣言しなければなりません。 ただし declare キーワードは例外です。
名前空間の宣言より前に書くことが許されているコードは declare 文のみです。ソースファイルのエンコーディングを定義するために使用します。
さらに、PHP コード以外であっても名前空間の宣言の前に記述することはできません (スペースも同様です)。
同一ファイル内での複数の名前空間の定義
波括弧を使うべし。
ただ、複数の名前空間をひとつのファイルに記述するようなコーディングはできるだけ避けるべきです。 主な使い道としては、複数の PHP スクリプトをひとつのファイルにまとめるときくらいでしょう。
名前空間に属さないグローバルなコードを名前空間つきのコードと組み合わせるときには、 波括弧構文しか使用できません。グローバルなコードは、名前空間の名前を指定しない namespace 文で囲みます。たとえば次のようになります。
名前空間の使用法: エイリアス/インポート
・下記の意味合いがちょっと分かってないかも。インポートしたものはカレント空間にあるのだからその後の呼び出しにバックスラッシュはつけるなということかな。
・useはカンマ区切りで複数して出来る
・useってクロージャーでも使うのだけどどこか同じなのだろうか。別物かな。
名前空間つきの名前 (完全修飾形式の名前空間は区切り文字を含んだ Foo\Bar のようなもので、グローバルな場合は区切り文字を含まない FooBar のようになります) では先頭のバックスラッシュは不要で、推奨されないことに注意しましょう。 インポートする名前は完全修飾形式でなければならず、 現在の名前空間からの相対指定で処理されることはないからです。
あれ、クロージャーは、、?
インポート時のスコープ規則
use キーワードの宣言は、ファイル内の一番外側のスコープ (グローバルスコープ) あるいは名前空間宣言の中で行わなければなりません。 これは、インポートが実行時ではなくコンパイル時に行われるためです。 ブロック内のスコープではインポートできません。 次の例は、use キーワードの間違った使い方を示すものです。
名前解決のルール
この項は理解するのに大事そう。
完全修飾された関数、クラス、定数への コール は コンパイル時 に解決されます。 たとえば、new \A\B は A\B クラスと解釈されます。
非修飾名および (完全修飾でない) 修飾名の変換は、現在のインポートルールに基づいて コンパイル時 に行われます。
名前空間内で、インポートルールによる変換が行われなかった修飾名は 現在の名前空間が先頭に付加されます。
非修飾クラス名の変換は、現在のインポートルールに基づいて コンパイル時 に行われます
名前空間内で、非修飾な関数への コール は 実行時 に解決されます。
名前空間内で、非修飾あるいは (完全修飾でない) 修飾なクラスへの コール は 実行時 に解決されます
関数や定数は use 文でインポートできない
エイリアスはできるんだよね。
use 文の影響を受けるのは名前空間とクラス名のみです。 長い名前の定数や関数を短い名前にするには、それらが含まれる名前空間をインポートします。
例外
・PHP 5.5 以降では、catch ブロックの後に finally ブロックも指定できるようになりました。
・定義済みの例外としては、組み込み(ErrorExceptionエラー例外,Exception既定クラス)とSPL(Standard PHP Library)のものがあるみたい。
・エラーをエラー例外にするにはErrorExceptionを基本的には使えば良いみたい。
・SPLの例外はいろんなライブラリでスタンダードに呼び出されるモノなのかしら。
・例外を拡張するには、Exceptionを継承してコンストラクタに処理を下記、親コンストラクタを実行する。
<?php
/**
* カスタム例外クラスを定義する
*/
class MyException extends Exception
{
// 例外を再定義し、メッセージをオプションではなくする
public function __construct($message, $code = 0, Exception $previous = null) {
// なんらかのコード
// 全てを正しく確実に代入する
parent::__construct($message, $code, $previous);
}
// オブジェクトの文字列表現を独自に定義する
public function __toString() {
return __CLASS__ . ": [{$this->code}]: {$this->message}\n";
}
public function customFunction() {
echo "A Custom function for this type of exception\n";
}
}
Traversable インターフェイス
このインターフェイスにはメソッドがありません。 traverse 可能なすべてのクラス用の基底インターフェイスとしてのみ存在しています。
これは内部エンジンのインターフェイスであり、PHP スクリプトないで実装することはできません。
Traversable {
}
イテレータ
負荷と速度面を理解しておけば使うべき仕組みかもな。
(イテレートの特徴的処理をマジックメソッドで実装したり出来るのかな。等価ではないのだろうな。)
トータルでは、ArrayIteratorを使うことが多くなりそうな気がする。
Iterator extends Traversable {
/* メソッド */
abstract public mixed current ( void )
abstract public scalar key ( void )
abstract public void next ( void )
abstract public void rewind ( void )
abstract public boolean valid ( void )
}
オブジェクトがforeachで反復処理をするとプロパティを定義順に参照する。
イテレータインターフェイスをつかってメソッドを定義すると、foreachのループでキーと値を取得できる。
PHP には多くのイテレータがあらかじめ用意されており、日々の作業に使えます。その一覧は SPL イテレータ を参照ください。
IteratorAggregate
結果を見ると分かるように、SquareAggregateクラス自体にはイテレータの機能を実装していないにもかかわらず、foreachのループに入った際にSquareクラスと同様のイテレーションが実行されている。
これは、foreach内でIteratorAggregateインターフェイスのgetIteratorメソッドが自動的に呼び出され、その結果生成されたSquareクラスのインスタンスがイテレーションに利用されたためである。
Iteratorを委譲に使うってどういう場合のことだろう。
IteratorAggregate と Iterator は "単なる foreach で回すための interface であってどちらも動作に大差ない" という認識は誤り。
ライブラリとしてクラスを公開する際には、Iterator は委譲のために利用しておき、表には IteratorAggregate として提供するといった設計のほうが無難。
ここで毎回同じ Iterator を返したりすると元の木阿弥となるのでご注意を。
ArrayObject/ArrayIteratorクラス
ArrayObjectは、PHPのオブジェクトを本来PHPではプリミティブである配列と同様に扱えるようにしたものだ。オブジェクトであることに変わりはないため完全なエミュレーションにはなっていないが、基本的な挙動は配列とほとんど変わらない。
ArrayIteratorはArrayObjectの外部イテレータで、それ自体もArrayObjectと同様に配列オブジェクトとして振る舞う。
なるほど、便利だ。
奇数番目の要素のみを抽出するようなロジックを、ArrayIteratorを拡張して実装してみよう。
<?php
class OddArrayIterator extends ArrayIterator
{
public function next() {
parent::next();
if (parent::valid()) {
parent::next();
}
}
}
$array = array(10,20,30,40,50);
$obj = new ArrayObject($array, 0, 'OddArrayIterator');
foreach ($obj as $key => $val) {
echo $key . ':' . $val . PHP_EOL;
}
var_dump($obj);
上の例のようにArrayObjectの第3引数としてイテレータのオブジェクト名を渡してやることで、利用するイテレータを切り替えられる。配列のさまざまなイテレーション実装の際に適切に、これらのオブジェクトを用いればコードの見通しも良くなり、メンテナンス性も担保できるだろう。
SPLのイテレータ
CallbackFilterIterator:処理をコールバック関数でを共通化する
AppendIterator:複数のIteratorを連結する
CachingIterator:イテレータの結果をキャッシュする
FilterIterator:イテレータの一部をフィルタし、残った値だけをループに渡す
InfiniteIterator:イテレータを無限に繰り返す
LimitIterator:イテレータの一部分を抜き出す
ジェネレータ
・PHP5.5以上でつかえる。サーバーの制約で業務ではつかえない場合があるかもな。
・コンパイラはコンパイルの時点でyieldがある関数をジェネレータ関数とするのかな。
・yieldをつかって、値だけではなく(キーと値)、参照も返せる。
・yieldを変数に格納した後でGenerator::send()することもできる。
・iterator_to_array()は組み合わせて使いそう。
ジェネレータを使うと、foreach でデータ群を順に処理するコードを書くときに メモリ内で配列を組み立てなくても済むようになります。 メモリ内で配列を組み立てると memory_limit を越えてしまうかもしれないし、 無視できないほどの時間がかかってしまうかもしれません。 配列を作る代わりに、ジェネレータ関数を書くことになります。これは通常の 関数と同じものですが、 ジェネレータ関数は一度だけ return するのではなく、必要に応じて何度でも yield することができます。 つまり、値を繰り返し返せるということです。
ジェネレータと Iterator オブジェクトとの比較
場合によってはcloneする機会が出てくるかな。
ジェネレータの最大のメリットは、シンプルに書けることです。 Iterator を実装するのに比べて、必要な決まり文句の数がかなり少なくなります。 また、ジェネレータを使ったコードのほうが、一般的に読みやすくなります。 たとえば、次の関数とクラスを比べてみましょう。これらはどちらも同じ働きをするものです。
しかし、柔軟性を実現するために犠牲にしていることもあります。 ジェネレータは前方にしか進めないイテレータなので、いったん反復処理が始まれば巻き戻すことができません。 これはつまり、同じジェネレータを何度も使い回せないということです。 ジェネレータ関数を呼んでもう一度作り直すか、 clone でクローンしないといけません。
イテレータとの比較・負荷と速度
目安として、ジェネレータとイテレータの速度は3倍違い、forなどでシンプルな実装の場合には1桁違うみたい。
大きなデータの場合にはもっと顕著になると思われる。
ここでは、ArrayIteratorはforにまけないベンチになっている。
また、ArrayIteratorは確かに高速ですが、大きい配列を扱うとnewするときのコストが大きくなりすぎて使い物にならないこともあるでしょう(上のベンチマークテストはインスタンスを作った後のループ時間を測定しており、配列の大きさの悪影響は現れていません)。ジェネレータは省メモリかつ速度もそこそこで拡張性が高いので、PHP 5.5以降では活躍するはずです。
ジェネレータをつかえる処理のときは使った方が良さそう。
実は、PHPメソッドの方だけ実装すれば正常に動作します。C関数を実装する理由は速度面のメリットからだというのが僕の理解です。C関数は関数ポインタで単に呼び出せるのに対し、PHPメソッドの呼び出しは命令実行器の状態保存・復元の必要があるなど、呼び出しのコストがやや高いのです。
ジェネレータとIteratorAggregateを組み合わせるといいらしい。
IteratorAggregateはPHP5.5から導入されたジェネレータと大変相性がいいです。getIteratorメソッドそのものをジェネレータ関数にしてもよいのです。
class Foo implements IteratorAggregate
{
protected $attributes = array();
function __construct(array $arr)
{
$this->attributes = $arr;
}
function getIterator()
{
foreach ($this->attributes as $key => $val) {
yield $key => $val;
}
}
}
SPL関数
標準ライブラリ。
The Standard PHP Library (SPL) は、標準的な問題を解決するためのインターフェイスやクラスを集めたものです。
このエクステンションは、PHP 5.0.0 ではデフォルトでコンパイルされ利用可能です。
注意:
PHP 5.3.0 以降では、このエクステンションは無効化できなくなります。 つまり、常に使用可能となります。
優先度つきキューか、ちょっとつかってみようかな。