Mastering SPL

description »
Sotaro Karasawa

Mastering SPL
@モダンPHP勉強会
by id:sotarok
id:sotarok
http://strk.jp/

nequal:
http://nequal.jp/
えぬいこーる
今日の主催.これの代表やってる.

コミッター:
Ethna つくってる

趣味:
プリン食べあるき
Sotaro Karasawa
Follow me

@sotarok ←Twitter
id:sotarok ←はてな

sotaro.k@gmail.com
Standard PHP Library
Iterator
Question
SPL知ってる人?
Question 2
割と普段からSPL使う人?
Question 3
spl_autoload 以外の
SPL クラス・関数を割と普段から使う人?
よくわかりました!
今日開催するかいがありますね
SPLとは
Standard PHP Library

名称はなにかのぱくりっぽい...
PHP 5で標準搭載された拡張機能

PHPの
「標準的な問題」
を解決するための
クラス
インターフェイス
例外
関数
Required
PHP 5.0 以上

PHP 5.3 以降では
「無効にできない」


=常に利用可能な標準機能

という扱いでおk?
バージョンがあがるごとに
徐々に機能を拡充

PHP 5.3でも,
4つのイテレータクラス
7つのデータ構造クラス
が追加された

(前からあったクラスでも,
 メソッドが追加されてたり)
SPLの説明にはいる前に
いくつかの定義済みインターフェイス
PHP 5 以降標準搭載されている
いくつかの定義済みインターフェイス

Traversable
Iterator
IteratorAggrigator
ArrayAccess
Serializable

言語仕様に近い
特定の関数や構文が使えるようになったりとか,PHPスクリプトレベルでは実装できない
Traverable
Iterator
IteratorAggrigate
ArrayAccess
Serializable
foreach 可能になる

単体で実装不可の
抽象インターフェイス

Iterator
 or IteratorAggrigate
と一緒に実装しましょう
SPLの説明にはいる前に
 ... にはいる前に
一応おさらい
PHPのインターフェイス
<?php

interface IHoge {
    public function print_hoge();
}

class Hoge implements IHoge {
    public function print_hoge() {
        echo "Hoge", PHP_EOL;
    }
}

$h = new Hoge();
$h->print_hoge();

みんな自分で試したりって
あまりしないよね

今日の目的
「なにがあるのか」
「どんなときにつかえそうか?」

「あ,そういやなんかあったな」
と思い出せるように
MultipleIterator
NoRewindIterator
ParentIterator
RecursiveArrayIterator
RecursiveCachingIterator
RecursiveDirectoryIterator
RecursiveFilterIterator
RecursiveIteratorIterator
RecursiveRegexIterator
RecursiveTreeIterator
RegexIterator
SimpleXMLIterator
Countable
OuterIterator
RecursiveIterator
SeekableIterator
BadFunctionCallException
BadMethodCallException
DomainException
InvalidArgumentException
LengthException
LogicException
OutOfBoundsException
OutOfRangeException
OverflowException
RangeException
RuntimeException

UnderflowException
UnexpectedValueException
class_implements
class_parents
iterator_apply
iterator_count
iterator_to_array
spl_autoload_call
spl_autoload_extensions
spl_autoload_functions
spl_autoload_register
spl_autoload_unregister
spl_autoload
spl_classes
spl_object_hash
SplFileInfo
SplFileObject
SplTempFileObject
ArrayObject
SplObserver
SplSubject
SPL

SplDoublyLinkedList
SplStack
SplQueue
SplHeap
SplMaxHeap
SplMinHeap
SplPriorityQueue
SplFixedArray
SplObjectStorage
AppendIterator
ArrayIterator
CachingIterator
DirectoryIterator
EmptyIterator
FilesystemIterator
FilterIterator
GlobIterator
InfiniteIterator
IteratorIterator
LimitIterator
反復処理を行うためのインターフェイス
Traversableを継承しているので foreach 可能!
class MacrosF implements Iterator {
    private $m = array(
        '早乙女アルト' => array( 16, '中村悠一',),
        'ランカ・リー' => array( 15, '中島愛',),
        'シェリル・ノーム' => array( 17, '遠藤綾',),
    );

    public function key() { return key($this->m);}
    public function current() {
        return current($this->m);
    }
    public function next() { next($this->m); }
    public function rewind() {reset($this->m);}
    public function valid() {
        return $this->current() !== false;
    }
}

% php IteratorSample.php

早乙女アルト: 16歳, CV:中村悠一
ランカ・リー: 15歳, CV:中島愛
シェリル・ノーム: 17歳, CV:遠藤綾
外部イテレータを作成するためのインターフェイス


getIterator() メソッドが,
Iterator/Traversableを実装したクラスを返すように
実装してあげる

イテレートしたいものを外部のイテレータクラスに
まるなげ

例:
public function getIterator() {
    return ArrayIterator($this->property);
}
配列風にアクセス可能になる
 → [] これね!

これが実装されてるクラスのオブジェクトに
 [] 
でアクセスすると特定のメソッドが呼ばれる

<?php
$hoge = new Hoge();
$hoge['fuga'] = 'piyo';
 // このとき,offsetSet というメソッドが
 // 呼ばれる!
serialize/unserialize 関数が適用された時の
挙動を独自にカスタマイズできるようにする
インターフェイス

serialize でシリアライズした文字列を返し,
unserialize メソッドでシリアライズされた文字列をうけとる

<?php
class Hoge implements Serializable {
    private $data = ....;

    public function serialize() {
        return serialize($this->data);
    }

    public function unserialize($s) {
        $this->data = unserizlize($s);
    }
}
Interface
Countable
OuterIterator
RecursiveIterator
SeekableIterator
23コ
ArrayIterator
Class
配列をイテレートするためのイテレータクラス
コンストラクタで配列を指定

例えば,先程の IteratorAggregator を使った
外部イテレータの実装例!
<?php

class MacrosF implements IteratorAggregate {
    private $m = array(
        '早乙女アルト' => array( 16, '中村悠一',),
        'ランカ・リー' => array( 15, '中島愛',),
        'シェリル・ノーム' => array( 17, '遠藤綾',),
    );

    public function getIterator() {
        return new ArrayIterator($this->m);
    }
}


foreach (new MacrosF() as $k => $v) {
    echo $k, ": ", $v[0], "歳, CV:", $v[1], PHP_EOL;
}

Functions
SplFileInfo
ファイル情報を扱うクラス
SplFileObject とかのベースとなるクラス
<?php

$f = new SplFileInfo(__FILE__);

echo $f->getRealPath(), PHP_EOL;
echo $f->getBasename(), PHP_EOL;
echo $f->getSize(), PHP_EOL;

/home/sotarok/git/modernphp_spl/SplFileInfo.php
SplFileInfo.php
136

SplFileObject
オブジェクトとしてファイルの読み書きを扱えるクラス
複数のイテレータが実装されていて行アクセス可能

fopen, fgets, fgetcsv, fseek など,ファイルを扱う
関数郡がまるまるメソッドになってる
<?php

foreach (new SplFileObject('./common-access_log', 'r')
 as $line) {
    echo $line;
}
SplTmpFileObject
一時ファイルを扱うためのサブクラス
ArrayObject
オブジェクトを配列として動作させることができる

配列と同じようにイテレート可能だし,
ソート系関数もメソッドとして実装されている.
<?php

$sample = new StdClass();
$sample->apple   = 'リンゴ';
$sample->banana  = 'バナナ';

$ao = new ArrayObject($sample);

echo $ao['apple'], PHP_EOL; // リンゴ

SplObserver/SplSubject
SPL全部やるとかいって超後悔してる

だいぶやりますが
全部はやりません
Observer パターンを実装するためのクラス
のアブストラクト!実装は自分でしてね!
SplSubject
SplObserver
SplObserver
SplObserver
attach
attach
attach
update
update
update
attach
detach
notify
update
PEAR::HTTP_Request2 のロガーで使われてたりする
count 関数の引数に渡せるようになる
イテレータの要素数を返すように実装する
偉大な参考文献さんたち
d:id:hnw さんの数々の記事
http://d.hatena.ne.jp/hnw/20090523

id:rsky 先生による SPL 入門
http://www.opendogs.org/pub/SPL_Guide.pdf

そしてなにより PHPマニュアル
http://php.net/spl
でもまだ未完全なところが多い

その他Google先生によって拾い上げられた
まだまだこの辺の情報は海外のほうが多いですね

その他SPLの好きそうな人
id:hnwさん (全然その他じゃない.むしろ大本命)
EmptyIterator
空のイテレータ.....

らしいんですけど,使い道がわかりませんでした (・ω・`;)
だれか分かる人?
RecursiveArrayIterator
多次元配列も再帰的にイテレートしてくれる
foreachをつかって,階層を意識することなく
イテレート可能?
<?php

$fruits = array(
    array('apple', 'red'),
    array('banana', 'yellow'),
    array('orange', 'orange'),
);

foreach (new RecursiveArrayIterator($fruits) as $f) {
    echo $f, PHP_EOL;
}

Array
Array
Array

(´・ω・`) ...
RecursiveIteratorIterator
RecursiveIterator の実装されたクラスで,
再起を意識せずに再帰的に走査可能にしたクラス
<?php

$fruits = array(
    array('apple', 'red'),
    array('banana', 'yellow'),
    array('orange', 'orange'),
);

foreach (
    new RecursiveIteratorIterator(
        new RecursiveArrayIterator($fruits)
    ) as $f
) {
    echo $f, PHP_EOL;
}

apple
red
banana
yellow
orange
orange
RecursiveIterator が「実装されている」
階層構造を持つクラスで実装する
hasChildren, getChildlen,によって
子階層が存在するか判断し,子階層を返すメソッドを
実装する


これは RecursiveIteratorIterator などで役に立つ
seek メソッドによって,
イテレート位置を移動して要素にアクセスできる
ぼく程度では説明できない
イテレータインターフェイス(ちょ
IteratorIteratorなどのような
イテレータクラスからイテレータを
つくるようなクラスで実装されている

外部イテレータを使って
イテレータオブジェクトをつくるためのもの
(だと思います)
Data Structure
5.3の大本命 (本当か
SplObjectStorage
以外は PHP 5.3 からの登場

様々なデータ構造がクラスに
InfiniteIterator
無限イテレータ!!(カッコイイ
apple
banana
orange
apple
banana
orange
apple
banana
orange
apple
banana
orange
apple
banana
orange
apple
banana
orange
apple
banana
orange
apple
banana
orange
apple
banana
orange
apple
banana
orange
apple
banana
orange
apple
banana
orange
apple

<?php

$fruits = array(
    'apple',
    'banana',
    'orange',
);

foreach (new InfiniteIterator(
    new ArrayIterator($fruits)) as $f) {
    echo $f, PHP_EOL;
}

LimitIterator
逆に,イテレート回数に制限をかけられる
また,オフセットもつかえる

ページャーユーティリティみたいなものに
使える
<?php

$fruits = array(
    'apple',
    'banana',
    'orange',
);

foreach (new LimitIterator(
    new ArrayIterator($fruits), 0, 2) as $f) {
    echo $f, PHP_EOL;
}

apple
banana
DirectoryIterator
ディレクトリを閲覧するためのイテレータ
SplFileInfoを継承しているよ
<?php

foreach (new DirectoryIterator('.') as $d) {
    if (!$d->isDot()) {
        echo $d, PHP_EOL;
    }
}

SplSubjectObserver.php
ArrayIterator.php
RecursiveIteratorIterator.php
SplFileInfo.php
Iterator.php
DirectoryIterator.php
ArrayObject.php
SplFileObject.php
IteratorImplements.php
RecursiveIteratorIterator_vsGlob.php
InfiniteIterator.php
common-access_log
RecursiveDirectoryIterator.php
RecursiveTreeIterator.php
RecursiveDirectoryIterator
予想通り,再帰的にディレクトリを
めぐったりなんかりができます.
RecursiveIteratorIteratorと組み合わせて使うと便利
<?php

foreach (new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator('/home/sotarok/working/php'))
    as $d) {
    echo $d, PHP_EOL;
}

<?php
// 関数で実装すると ... ?
function scan_dir ($path) {
    foreach (glob($path . '/*') as $f) {
        if (($f != '.' || $f != '..') && is_dir($f)) scan_dir($f);
        echo $f, PHP_EOL;
    }
}
scan_dir('/home/sotarok/working/php');

RecursiveTreeIterator
5.3 から入った
なぞ.
よくわからないけとTree表示できたり
いや,でももっと便利な使い道がありあそう?
<?php

foreach (new RecursiveTreeIterator(
    new RecursiveDirectoryIterator('/home/sotarok/working/php'))
    as
    $t
) {
    echo $t, PHP_EOL;
}


|-/home/sotarok/working/php/Token.php
|-/home/sotarok/working/php/VBEncode.php
|-/home/sotarok/working/php/imap
| \-/home/sotarok/working/php/imap/imap.php
|-/home/sotarok/working/php/datetime
| |-/home/sotarok/working/php/datetime/2038.php
| |-/home/sotarok/working/php/datetime/dt.php
| |-/home/sotarok/working/php/datetime/.
| |-/home/sotarok/working/php/datetime/..
| \-/home/sotarok/working/php/datetime/nc.php
|-/home/sotarok/working/php/autoload
| |-/home/sotarok/working/php/autoload/Hoge.php
| |-/home/sotarok/working/php/autoload/al.php
| |-/home/sotarok/working/php/autoload/Piyo.class.php
| |-/home/sotarok/working/php/autoload/alfunc.php
| |-/home/sotarok/working/php/autoload/.
| \-/home/sotarok/working/php/autoload/..
|-/home/sotarok/working/php/2k.php
|-/home/sotarok/working/php/simplexml.php
|-/home/sotarok/working/php/phar
| |-/home/sotarok/working/php/phar/phar.php
| |-/home/sotarok/working/php/phar/.
| \-/home/sotarok/working/php/phar/..
|-/home/sotarok/working/php/.

FileSystemIterator
5.3から入った
DirectoryIterator を継承するちょい便利クラス
フラグれちょいちょい挙動を変更できる
<?php

//これだと DirectoryIterator とほぼ同じ
foreach (new FileSystemIterator(__DIR__) as $d) {
     echo $d, PHP_EOL;
}


// . / .. をスキップ
foreach (new FileSystemIterator(
        __DIR__,
        FileSystemIterator::SKIP_DOTS
    ) as $d) {
     echo $d, PHP_EOL;
}

// foreach で受け取る値をFileSystemIteratorに
// もちろん複数フラグはパイプでつなぐ
foreach (new FileSystemIterator(
        __DIR__,
        FileSystemIterator::SKIP_DOTS
        | FileSystemIterator::CURRENT_AS_SELF
    ) as $d) {
     echo $d->getFilename(), ' is ', $d->getSize(), ' Bytes', PHP_EOL;
}

GlobIterator 
5.3から入った
glob() 関数とほぼ同じ ...
<?php

// glob
// こちらも,フラグで foreach で受け取る値を変えられる
foreach (new GlobIterator(
        __DIR__ . '/*.php',
        GlobIterator::CURRENT_AS_FILEINFO
    ) as $d) {
    echo $d->getFilename(), PHP_EOL;
}

Countable が実装されてるから
count($hoge)
とかでファイル数とかわかるよ!
それ glob でできるよ
FilterIterator
「望まざる値をフィルタしてくれる」らしい
なんじゃそりゃ

これ自体は抽象クラスなので継承してクラスを
つくらなければいけない

accept メソッドで,現在の値を返すか返さないかを
bool で return すればOK

デコレーターパターンとかにつかえますね
<?php

class StopWordsFilter extends FilterIterator
{
    private $stop_list = array(
        'the',
        'is',
        'a',
        'I', // ...
    );

    public function accept() {
        return !in_array(
            parent::current(), $this->stop_list
        );
    }
}

$query_word = 'This is a blue pen';
foreach (
    new StopWordsFilter(
        new ArrayIterator(explode(' ', $query_word))
    ) as $word) {
    echo $word, PHP_EOL;
}

検索エンジンでストップワードを取り除くフィルター例
RegexIterator
FilterIteratorを継承していて,
要素が正規表現にマッチするときだけ
その値をとりだす
<?php

// 郵便番号だけ取り出す
$a = array(
    'abdkfg',
    '111-2345',
    '2220303',
    '34938372343',
);
foreach (
    new RegexIterator(
        new ArrayIterator($a),
        '/^\d{3}-?\d{4}$/'
    ) as $s) {
    echo $s, PHP_EOL;
}

111-2345
2220303

RecursiveRegexIterator
さいきてきに (r

...もうつかれてきたよ!
<?php

// 郵便番号だけ取り出す
$a = array(
    array('111-2345', '2220303', 'apple!',),
    array('121-5678', '0303', '933-2222',),
);
foreach (
    new RecursiveIteratorIterator(
        new RecursiveRegexIterator(
            new RecursiveArrayIterator($a),
            '/^\d{3}-?\d{4}$/',
            RecursiveRegexIterator::ALL_MATCHES
        )
    ) as $s) {
    echo $s, PHP_EOL;
}

NoRewindIterator
rewind できないイテレータ

ナニに使うんだこれ!
と思ったときの id:hnw 先生
 http://d.hatena.ne.jp/hnw/20090523


SplFileInfo + php://stdin
と組み合わせて使ったりできる
(stdinはrewindできないから)
<?php

foreach (
    new NoRewindIterator(
        new SplFileObject('php://stdin')) as $line) {
    if (trim($line) == 'exit') break;
    echo $line;
}

// exit とうつまでひたすらあなたの言うことを
// 繰り返す続ける...!

MultipleIterator
複数のイテレータを同時にまわすことができる

複数のファイルを並列的に同時に処理したいとか,結構使い道はあるのでは.

(にしては例がひどい
<?php

$m = new MultipleIterator(MultipleIterator::MIT_NEED_ANY);
$m->attachIterator(new SplFileObject('./ArrayIterator.php'));
$m->attachIterator(new SplFileObject('./ArrayObject.php'));

// なんという意味のない遊び
foreach ($m as $lines) {
    echo $lines[0], $lines[1];
}

<?php
<?php


$fruits = array(
$sample = new StdClass();
    array('apple', 'red'),
$sample->banana  = 'バナナ';
    array('banana', 'yellow'),
$sample->apple   = 'リンゴ';
    array('orange', 'orange'),

);
$ao = new ArrayObject($sample);


foreach (new ArrayIterator($fruits) as $f) {
echo $ao['apple'], PHP_EOL;
    echo $f, PHP_EOL;

}

SimpleXMLIterator
でました!大好き SimpleXML
SimpleXMLのオブジェクトをイテレートできる

simplexml_laod_string/file
の第二引数に指定できる
<?php

$s = simplexml_load_file(
    'http://d.hatena.ne.jp/sotarok/rss2',
    'SimpleXMLIterator'
);
foreach (new RecursiveIteratorIterator($s) as $tag) {
    echo $tag, PHP_EOL;
}

RecursiveFilterIterator
再帰的にFilterできるFilterIterator
ありすぎてこまるから
18個だけやるよ...
Exception
SPL例外
「PHPでは例外があるのに
標準関数が例外飛ばさない」と
愚痴っていたあなたに朗報

的な扱い(何

SPLはたくさん例外をもっていて,
SPLクラスはなにかってときに
例外飛ばしてくれます
LogicException
論理エラー.SPL例外はみんなこれを継承している.

BadFunctionCallException
 未定義の関数をコールバックが参照したり
   引数を指定しなかったりした場合にスローされる

badMethodCallException
未定義のメソッドをコールバックが参照したり
  引数を指定しなかったりした場合にスローされる

DomainException
ドメイン例外

InvalidArgumentException
引数が違っていたり

LengthException
長さ違反

OutOfBoundsException
値が無効

OutOfRangeException
範囲外

OverflowException
オーバーフロー

RangeException
無効な範囲

RuntimeException
実行時にただ発生するようなエラー

UnderflowException
アンダーフロー

UnexpectedValueException
いくつかの値のセットに一致しない値

帰って復習してくだしあ(´・ω・`)
全部紹介するとか
いっときながらごめんよ
正直めんd(r
リフレクション的な
class_implements
 そのクラスが実装している
 インターフェイスを配列で返す

class_parents
 そのクラスが継承している
 クラスを配列で返す
<?php

var_dump(class_implements('SplFileObject'));
var_dump(class_parents('SplFileObject'));

array(4) {
  ["RecursiveIterator"]=>
  string(17) "RecursiveIterator"
  ["Traversable"]=>
  string(11) "Traversable"
  ["Iterator"]=>
  string(8) "Iterator"
  ["SeekableIterator"]=>
  string(16) "SeekableIterator"
}
array(1) {
  ["SplFileInfo"]=>
  string(11) "SplFileInfo"
}

イテレータ的な
iterator_to_array
 イテレータの要素を配列にする

iterator_apply
 イテレータ版 array_walk

iterator_count
 イテレータの要素数をカウントする
<?php

$fruits = array(
    'apple',
    'banana',
    'orange',
);

$l = new LimitIterator(new ArrayIterator($fruits), 0, 2);
var_dump(iterator_count($l));

// らめええ
$f = new InfiniteIterator(new ArrayIterator($fruits));
var_dump(iterator_count($f));

クラス的な
spl_classes
 利用可能なSPLクラスを返す

spl_object_hash
 オブジェクトから一意のハッシュを
生成する
spl_autoload*
おまちかね,みんな使ってるspl_autoload

autoload機構をもっと使いやすくするためのいくつかの関数
spl_autoload_register
クラス・メソッドなどを autoload に
登録できる
また,autoload stack を形成する
class Al
{
    public static function load($name)
    {
        $fn = $name . ".php";
        if (file_exists($fn)) require_once $fn;
    }
    public static function loadClass($name)
    {
        $fn = $name . ".class.php";
        if (file_exists($fn)) require_once $fn;
    }
}
spl_autoload_register('Al::load');
spl_autoload_register('Al::loadClass');


$h = new Hoge();
$p = new Piyo();


spl_autoload
autolaodのデフォルトの実装
自分で実装しなくても
「小文字クラス名 + .php or .inc」
ならこの関数でOK
<?php

spl_autoload_register('spl_autoload');

// hoge.php または hoge.inc が呼ばれる
$h = new Hoge();

その他 spl_autoload 関数

spl_autoload_call
 要求されたクラスを読み込むために、すべての登録済みの __autoload() 関数を試す

spl_autoload_extensions
 spl_autoload 用のデフォルトの拡張子を登録し、それを返す

spl_autoload_functions
 すべての登録済み __autoload() 関数を返す

spl_autoload_unregister
 指定した関数の、__autoload() の実装としての登録を解除する

SplDoublyLinkedList
双方向リンクリスト

... としか良いようがない :)

・スタックになったり
・キューになったり
<?php

$l = new SplDoublyLinkedList();

$l->push(1);
$l->push(2);
$l->push(4);

$p = $l->pop();
echo "poped: ", $p, PHP_EOL;

echo "iterate:", PHP_EOL;
foreach ($l as $v) {
    echo $v, PHP_EOL;
}

$l->setIteratorMode(SplDoublyLinkedList::IT_MODE_LIFO);
echo "iterate:", PHP_EOL;
foreach ($l as $v) {
    echo $v, PHP_EOL;
}

もういいよねこれ
あんまおもしろくないし
SplStack / SplQueue
スタックとキュー
 ... としか (ry

DoublyLinkedList のサブクラス
ところで
SplQueue: 0.26700496673584
Array: 0.2801718711853

PHPの配列って
おなじようなことできるよね
<?php

$t = microtime(true);
$q = new SplStack();
for ($i = 0; $i < 10000; ++$i) {
    $q->push($i);
}
for ($i = 0; $i < 10000; ++$i) {
    $q->pop();
}

echo "SplQueue: ", microtime(true) - $t , PHP_EOL;

$t = microtime(true);
$a = array();
for ($i = 0; $i < 10000; ++$i) {
    array_push($a, $i);
}
for ($i = 0; $i < 10000; ++$i) {
    array_pop($a);
}

echo "Array: ", microtime(true) - $t , PHP_EOL;

(むりやり)まとめ
SPL便利
でも使いどころ分からないのとかもある
便利なものは便利なものとして
使ってみるといろいろ良いかも

まだまだ枯れていない分野で楽しいですよ
SplHeap
SplMaxHeap
SplMinHeap
SplPriorityQueue
SplFixedArray
SplObjectStorage
一番紹介したかったやつが ....
続きはウェブで (ぉ

Loading comments...

Please log in to add your comment.

Report abuse

More presentations by Sotaro Karasawa