[PHP]データの扱い:Iterator
#プログラミング #PHP #Iterator #Generator
データをどう持たせる・扱うとコードが綺麗になる・バグが生み出されにくくなるかを考えます。
1.データを配列で持たせる
その場限りの利用ならシンプルに下記で問題はなさそうです。
<?php
class User{
public function setUser($db){
$user = array();
$user['name'] = $db['name'];
$user['age'] = $db['age'];
$user['address'] = $db['address'];
$user['mail'] = $db['mail'];
$user['homepage'] = $db['homepage'];
return $user;
}
}
連想配列でなく、stdClassを利用する方法もあります。
※利用するメリットは、好き嫌いの話だけになりそう。stdClassの方が個人的には見栄えがよくて好きです。
<?php
class User{
public function setUser($db){
$user = new stdClass;
$user->name = $db['name'];
$user->age = $db['age'];
$user->address = $db['address'];
$user->mail = $db['mail'];
$user->homepage = $db['homepage'];
return (array)$user;
}
}
stdClass型の特徴(配列と比較して)
・初期化の際、タイプ文字数が多くなる傾向(複雑なケースは多くなりますが、通常は配列とほぼ変わりません)
・利用できる関数が少ない→データ入れた後に処理する場合は向かない
・参照型として扱われる
https://qiita.com/mpyw/items/bd38da57837d35214aae
$arr2 = ['foo' => ['bar' => ['baz' => 'A']]];
$obj1 = new stdClass;
$obj1->foo = new stdClass;
$obj1->foo->bar = new stdClass;
$obj1->foo->bar->baz = 'A';
https://teratail.com/questions/3541
https://qiita.com/onomame/items/be2261c6eb566edab030
2.データ構造をメンバークラスに分離する
利用する箇所が複数あるデータの場合は、データ構造をメンバークラスに分離した方がよさそうです。(今後そのデータ構造を流用する箇所が出そうである場合も)
メリット:
・別のところで同じデータ構造を利用する場合に間違いにくい
・初期化もメンバークラスで行えるので実処理の部分で行う必要がない
・他の人が見たときに、データ構造を把握しやすい
メンバークラスのプロパティを「public」にしたケース:
プロパティは何が何でもprivateにしないと駄目というのは盲目的すぎるのでこれで十分というケースはありそうです。(getter/setterを大量に作るのも、コード量が増えてよくないですし)
<?php
//メンバークラス
class UserEntity{
public $user = "";
public $age = 0;
public $address = "";
public $mail = "";
public $homepage = "";
}
class User{
public function setUser($db){
$userEntity = new UserEntity();
$userEntity->name = $db['name'];
$userEntity->age = $db['age'];
$userEntity->address = $db['age'];
$userEntity->mail = $db['mail'];
$userEntity->homepage = $db['homepage'];
//インスタンスを返す
return $userEntity;
}
}
メンバークラスのプロパティを「private」にしたケース:
プロパティが「public」だとメンバークラスの外からもアクセス可能になり、意図しないところでデータをセットされる可能があります。そのため、メンバークラスのプロパティをprivateにして、できることを制限します。
<?php
//メンバークラス
class UserEntity{
private $user = "";
private $age = 0;
private $address = "";
private $mail = "";
private $homepage = "";
public function __construct($db){
$this->user = $db['user'];
$this->age = $db['age'];
$this->address = $db['address'];
$this->mail = $db['mail'];
$this->homepage = $db['homepage'];
}
public function getUser(){
return $this->user;
}
public function getAge(){
return $this->age;
}
public function getAddress(){
return $this->address;
}
public function getMail(){
return $this->mail;
}
public function getHomepage(){
return $this->homepage;
}
}
class User{
public function setUser($db){
$userEntity = new UserEntity($db);
//インスタンスを返す
return $userEntity;
}
}
補足:getter / setterをプロパティごとに作らない方法:
getter / setterをプロパティごとに作らない方法もありますが
その場合、どんなプロパティを持つか明示されず保守性が下がるので
なるべくやらない方がよさそうです。
https://qiita.com/mikakane/items/00c798964f7c2c122e7d
※gong023さんのコメントが参考になりました。
class UserEntity{
private $var = [];
public function __get($key){
return $this->get($key);
}
public function __set($key,$value){
$this->set($key,$value);
}
public function get($key,$default=null){
if(array_key_exists($key,$this->var)){
return $this->var[$key];
}
return $default;
}
public function set($key,$value){
$this->var[$key] = $value;
}
}
class User{
public function setUser($db){
$userEntity = new UserEntity();
$userEntity->name = $db['name'];
$userEntity->age = $db['age'];
$userEntity->address = $db['age'];
$userEntity->mail = $db['mail'];
$userEntity->homepage = $db['homepage'];
//インスタンスを返す
return $userEntity;
}
}
3.データに対しての共通処理化:Iterator
データ構造が複雑、なおかつ色々なところでそのデータ構造が利用されている場合に、機能変更の度に色々なところを書き換えるのは手間なので、処理を共通化させるためにIteratorを利用します。
<?php
class User{
private $name;
private $age;
private $address;
private $mail;
private $homepage;
public function __construct($name, $age, $address, $mail, $homepage){
$this->name = $name;
$this->age = $age;
$this->address = $address;
$this->mail = $mail;
$this->homepage = $homepage;
}
public function getName(){
return $this->name;
}
public function getAge(){
return $this->age;
}
public function getAddress(){
return $this->address;
}
public function getMail(){
return $this->mail;
}
public function getHomepage(){
return $this->homepage;
}
}
//イテレータ:オブジェクトに対する反復処理を行う
class Users implements IteratorAggregate{
private $users;
public function __construct(){
$this->users = new ArrayObject();
}
public function add(User $user){
$this->users[] = $user;
}
public function getIterator(){
return $this->users->getIterator();
}
}
//データに対してのフィルタ処理
class AdressIterator extends FilterIterator{
public function __construct($iterator){
parent::__construct($iterator);
}
/*
* 住所が「東京都」のみ抽出
*/
public function accept(){
$user = $this->current();
return ($user->getAddress() === '東京都');
}
}
$users = new Users();
$users->add(new User("山田太郎", 20, "東京都", "a@yahoo.co.jp", "https://note.mu/a"));
$users->add(new User("田中一郎", 30, "埼玉県", "b@yahoo.co.jp", "https://note.mu/b"));
$users->add(new User("佐藤幸一", 25, "東京都", "c@yahoo.co.jp", "https://note.mu/c"));
$users->add(new User("益田喜平", 28, "茨城県", "d@yahoo.co.jp", "https://note.mu/d"));
$users->add(new User("岬洋平", 32, "東京都", "e@yahoo.co.jp", "https://note.mu/e"));
$iterator = new AdressIterator($users->getIterator());
foreach($iterator as $user){
echo $user->getName() . " | " .
$user->getAge() . " | " .
$user->getAddress() . " | " .
$user->getMail() . " | " .
$user->getHomepage() . "<br>";
}
Iteratorインタフェースで簡単に色々な機能を利用することもできます。
AppendIterator
appendメソッドで、配列を追加したらまとめて処理することができます。
https://qiita.com/suin/items/697e07d32a8408ea2fc8
$headerBlock = new ArrayIterator([
['ID', 'Name', 'Email'],
]);
//複数
$contentBlock = new ArrayIterator([
['1', 'Alice', 'alice@example.com'],
['2', 'Bob', 'bob@example.com'],
]);
$rows = new AppendIterator;
$rows->append($headerBlock);
$rows->append($contentBlock);
foreach ($rows as $row) {
var_dump($row);
}
CachingIterator
イテレータのキャッシュを行うことができます。
$collection = new CachingIterator(
new ArrayIterator(
array('Cat', 'Dog', 'Elephant', 'Tiger', 'Shark')));
foreach($collection as $animal) {
echo "Current: $animal";
if($collection->hasNext()) {
echo " - Next:" . $collection->getInnerIterator()->current();
}
echo PHP_EOL;
}
//出力:
//Current: Cat - Next:Dog
//Current: Dog - Next:Elephant
//Current: Elephant - Next:Tiger
//Current: Tiger - Next:Shark
//Current: Shark
連想配列で、先読みしたい場合に利用するケース:
https://stackoverflow.com/questions/2458099/peek-ahead-when-iterating-an-array-in-php
RegexIterator
正規表現を使ってイテレータをフィルタします。
$arrayIterator = new ArrayIterator(array('test 1', 'another test', 'test 123'));
$regexIterator = new RegexIterator($arrayIterator, '/^test/');
foreach ($regexIterator as $value) {
echo $value . "\n";
}
RecursiveIteratorIterator
再帰的なイテレータの反復処理に利用します。
$array = array(
array(
array(
array(
'leaf-0-0-0-0',
'leaf-0-0-0-1'
),
'leaf-0-0-0'
),
array(
array(
'leaf-0-1-0-0',
'leaf-0-1-0-1'
),
'leaf-0-1-0'
),
'leaf-0-0'
)
);
$iterator = new RecursiveIteratorIterator(
new RecursiveArrayIterator($array),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($iterator as $key => $leaf) {
echo "$key => $leaf", PHP_EOL;
}
//出力:
//0 => leaf-0-0-0-0
//1 => leaf-0-0-0-1
//1 => leaf-0-0-0
//0 => leaf-0-1-0-0
//1 => leaf-0-1-0-1
//1 => leaf-0-1-0
//2 => leaf-0-0
サブフォルダ内のファイルをまとめて取得する
https://qiita.com/re-24/items/94eeea3e8051e212d9ed
RecursiveDirectoryIterator
ファイルシステムのディレクトリを再帰的に反復処理します。
$directory = '.';
$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory));
$it->rewind();
while($it->valid()) {
if (!$it->isDot()) {
echo 'SubPathName: ' . $it->getSubPathName() . "\n";
echo 'SubPath: ' . $it->getSubPath() . "\n";
echo 'Key: ' . $it->key() . "\n\n";
}
$it->next();
}
//出力:
//SubPathName: a/a.txt
//SubPath: a
//Key: ./a/a.txt
4.シンプルに反復処理を書く:ジェネレータ
>ジェネレータの最大のメリットは、シンプルに書けることです。 Iterator を実装するのに比べて、必要な決まり文句の数がかなり少なくなります。 また、ジェネレータを使ったコードのほうが、一般的に読みやすくなります。
http://php.net/manual/ja/language.generators.comparison.php
function filterAddress($ite) {
foreach ($ite as $v) {
if ($v[2]=="東京都") yield $v;
}
}
$users = [
["山田太郎", 20, "東京都", "a@yahoo.co.jp", "https://note.mu/a"],
["田中一郎", 30, "埼玉県", "b@yahoo.co.jp", "https://note.mu/b"],
["佐藤幸一", 25, "東京都", "c@yahoo.co.jp", "https://note.mu/c"],
["益田喜平", 28, "茨城県", "d@yahoo.co.jp", "https://note.mu/d"],
["岬洋平", 32, "東京都", "e@yahoo.co.jp", "https://note.mu/e"]
];
foreach (filterAddress($users) as $user) {
var_dump($user);
}
メリット:
・Iteratorよりシンプルに書ける
・全部の値が格納された配列を用意する必要がなく、値がループされるたびに生成されるのでメモリの節約になる
デメリット:
・ジェネレータは前方にしか進めないイテレータなので、いったん反復処理が始まれば巻き戻すことができない
コードをまとめる技術としてのイテレータとジェネレータ
https://qiita.com/Hiraku/items/0db9a8fed4743c1f00a4
Iteratorとジェネレータを組み合わせて利用しているコード例
https://github.com/koriym/Koriym.Psr4List/blob/master/src/Psr4List.php