Home > PHP

PHP Archive

PHP 配列を回すならforかforeachか

  • 2008-04-25 (金)
  • PHP
この記事の所要時間: 337

今日、社内で「PHPの配列をループで回すのにforを使うか、foreachを使うか」という話が面白かったので、メモ。

ここでいう配列はキーが数字で、0からの連番であることを想定してます。(キーが数字以外や連番で無い場合は、foreachを使います。)

例えば↓のようにDBテーブルからレコードを読み込んだ内容が入ってる場合、$listをループで回すならforとforeachのどちらを使うべきかという話です。

<?php
$list = array();
$list&#91;&#93; = array('id' => 1, 'name' => 'hoge');
$list[] = array('id' => 2, 'name' => 'foo');
$list[] = array('id' => 3, 'name' => 'bar');
?>

for文派

<?php
for ($i = 0 ; $i < count($list); $i++) {
  echo $list&#91;$i&#93;&#91;'id'&#93; . PHP_EOL;
  echo $list&#91;$i&#93;&#91;'name'&#93; . PHP_EOL;
}
?>

1. ループ内でデータが直感的に参照できる

$listが2次元配列になっているのが分かっているので、直感的にデータへ参照できる。

2. 誰でも知っている構文である

プログラムを知っていれば、誰でも分かる。(foreachに比べると)

3. 参照する変数が変わらない

まあ1.とほぼ同じですが、$listのデータを参照・操作したいので、$list変数へ参照した方が分かりやすい。

=> foreachだとループ内で参照する変数が変わるのが直感的で無い。

foreach派

<?php
foreach ($list as $id => $rec) {
  echo $id . PHP_EOL;
  echo $rec['name'] . PHP_EOL;
}
?>

1. 添え字が数字だろうが、何だろうが要素を順番に参照できる

(PHPでは全てもともと連想配列ですが)リストかハッシュかを意識する必要が無い。

さらに言うとIteratorをimplementしていれば、どのようなデータ構造でもforeachでループを回せる。

2. 参照すべき変数を限定できる

上の例でいうと、ループ内では$listを意識する必要はなく、$idもしくは$recさえ知っておけば要素へ参照できる。

またループ内では$listがどういう構造なのかも知る必要がない。

3. 要素の内容を表す変数名で参照できる

配列は$listだが、添え字を$id・要素を$recと、内容を表す変数が指定できるので、分かりやすい。

おまけ. 若干早い

以前取ったベンチでは、foreach()の方が早かった。ただ配列の要素数が数十程度では大した差にはならないのでそれほど神経質にならなくても良い。

PHPではforeachで

個人的には連想配列しか無いPHPでは、配列は単純にforeachで回すというルールで良いと思います。

私自身はforeach派なのですが、for文派の1や3のようにあくまで配列をイメージして各要素に参照したいというのを利点として挙げていたのが新鮮でした。(私は$list[$i][‘name’]と参照するのが冗長に感じていたので。)

forかforeachか、みなさんはどちらでしょう?

追記(2008/04/26):

皆さんコメントありがとうございます!確認した限りでは、全員foreach派で、for文派はいませんでした;-)。やはりPHPではforeachを使うのが一般的のようです。

以下、foreach派のコメントを。

  • rytich foreach派
  • @shimooka iteratorのように配列の要素数を意識しなくていいのが楽です>foreach
  • @lllnorikolll 私もforeachが多いです。
  • @junya バグの入る余地が少ないからforeachのほうが好み
  • @nazo 全要素に対して何かするならforeach、指定回数何かするならfor
  • @sonkm3 for よりは foreach の方が好きだな。インデックス用の変数ってうっかりしやすいから。。。
  • id:creazynet 今はほぼforeachだけですね。for→foreachを使う様になった時がjava脳→php脳に変わった瞬間だった気がします。
  • id:Kiske 普段はforeach、処理データ大きいときはwhile使ってる / while(list($key, $value) = each($array)){…}
  • id:heavenshell ほぼ foreach かなぁ。

関連エントリ

CakePHP Modelに関する6つの誤解

この記事の所要時間: 327

CakePHPのModelはActiveRecordライクなDBアクセス方法を提供しており、さらにアソシエーションを設定することにより複数テーブルの値を同時に操作できるなど、DB操作に対するインターフェイスが数多くあります。

ただ「手軽にDB操作ができる」という印象が先行しているゆえ誤解を招くことがあるようです。

1. クラス名に対応したテーブルしか操作できない

Modelのクラス名とテーブルを自動でマッピングするのはフレームワークのいわば便利機能です。デフォルトでそのような動作をするだけで、容易に変更することができます。

Model#$useTableにテーブル名を指定すれば任意のテーブルを操作できます。

<?php
class Foo extends AppModel {
  public $useTable = 't_user'; // t_userテーブル
}
?>

2. DBを使わないといけない(DB操作が無いと使えない)

DB操作が強調されるので陥りがちですが、DBを使わないModelも作成できます。例えばバリデーションだけを行うModelやDB操作を行わないビジネスロジックを実装したModelなどももちろんokです(というかビジネスロジックを実装するもの)。

DB操作を行わないModelでは、Model#$useTableにfalseを入れておけば良いです。(nullではなく、falseです)

<?php
class Foo extends AppModel {
  public $useTable = false; // テーブルに対応させない
}
?>

3. ステートレスである

find()/findAll()等ではarrayを返すので、何となくステートレスな感じがしたりしますが、実はModelではしっかり情報を保持しています。

メソッド終了時にリセットされる値も一部ありますが、Model#set()などで設定した値はそのまま保持しています。ループの中で複数レコードを登録する際は注意する必要があります。

保持している値についてはModel#create()を呼ぶことにより初期化(クリア)できます。

<?php
    foreach ($list as $data) {
      $this->Foo->create();  // クリアしておく
      $this->Foo->save($data);
    }
?>

4. bakeで生成したModelは変更しない(できない)

bakeでバリデーションやアソシエーションを設定しておくと、生成されるModelではインスタンス変数に様々な値が記述されます。

単純なDB操作のみならばこのままModelのソースを触ることなく開発できますが、複雑なDB操作やビジネスロジックなどを実装する場合は、臆することなくModelにガンガン追加していきましょう。

5. DBのリレーションを全てアソシエーションでも設定する

アソシエーションはとても便利なものですが、上手く利用しないと思わぬSQLが発行され、パフォーマンスに影響が出ます。テーブル同士に外部整合性制約を付けてリレーションしているからと言って、必ずアソシエーションを設定する必要はありません。

はじめからシステムで不要と思われるアソシエーションについては設定しないというのも手です。

いっそ最低限のアソシエーションのみを設定しておき、後は用途に応じてModel#bind()/bindModel()する方が良いかもしれません。

特にModel#$hasManyを設定していると芋づる式に数多くのSQLが発行される場合があるので、注意しましょう。

6. DAOである

DAOのように振る舞うメソッドが多数ありますが、実際にDBへアクセスするのはDataSourceを継承したdboクラスであり、Modelではありません。

ModelをDAOとしか認識していないと、ビジネスロジックはControllerに実装することになり、Controllerが肥大していくことになります。

もちろんModelをDAOのように使うこともできますが、あくまでModelですので、ビジネスロジックはControllerでなくModelで実装して、Controllerへはそのインターフェイスを提供するようにしましょう。

CakeのModelはMVCのModel

しつこく書いてますが、CakeのModelもいわゆるMVCのModelと同じです。DB機能ばかりが強調されていますが、それに捕らわれることなく本来のModelとして活用しましょう。

CakePHP 1.2で携帯用ビューを表示する

この記事の所要時間: 332

CakePHP1.2ではCakePHP 携帯用ビューを表示するで利用していたwebservicesの機能が無くなります。

1.2-betaでRouting.webservicesをonにすると以下のようなメッセージが表示されます。

Deprecated: webservices routes are deprecated and will not be supported in future versions.  Use Router::parseExtensions() instead.

The prefix automagic in CakePHP routingで紹介されているように、1.2からはwebservicesに替わりprefixをURLルーティングで使用するようです。

そこで実際にどのように使用するかを試してみました。

1. URLルーティングでprefixを設定する

Router::connect()の第2引数ではパラメータを連想配列で設定できるのですが、そこに’prefix’というキーで値を設定しておくと、対象URLにアクセスがあればこの機能が有効となります。

下記の例では[/m/foo/bar]へアクセスがあると、prefix=mobileが有効となります。

[app/config/routes.php]

Router::connect('/m/:controller/:action', array('prefix' => 'mobile'))

2. prefix用アクションを作成する

prefixが有効な状態だと、アクションとして[prefix + _ + action]が呼ばれるので、アクションを実装します。

1. の設定で[/m/foo/bar]へアクセスされると、prefix=’mobile’が有効となるので、FooController#mobile_bar()がアクションとして呼ばれます。

[app/controllers/foo_controller.php]

<?php
class FooController extends AppController {
  public function mobile_bar() {
    // 通常のアクションを実装
  }
}
?>

3. prefix用ビューを作成する

2.で実装したアクション同様にビューテンプレートを作成します。

これは通常どおり2.で作成したアクションに対応するビューテンプレートを作成するだけです。

注意点は$html->link()などでURLを指定する際は、アクションにprefixが付いたアクション(mobile_bar)を指定するという事です。下の例なら「/m/foo/bar」がURLとして出力されます。

[app/views/foo/mobile_bar.ctp]

<?php echo $html->link('link', array('controller' => 'foo', 'action' => 'mobile_bar')); ?>

△prefix用Component/Helperが読まれない

携帯電話では入出力文字エンコーディングを変換する事が多いのですが、webservicesを使用すると読み込まれるComponent/Helperで実装していました。

今のところprefixではこの機能が無いようなので、Controller#beforeFilter()/afterFilter()等で自前で実装する必要がありそうです。

そのリクエストで設定されているprefixはController#$params[‘prefix’]で確認できます。(@see 1.2 の WEBSERVICES パラメータ)

○prefix用アクションは直接呼ばれない

prefixに設定したアクションは直接呼ぶことができず、prefixで設定したURLでないとアクセスできません。

つまり1.のような設定の場合、[/foo/mobile_bar]へアクセスしてもprivate methodとして扱われてエラーとなります。

これによりprefixが設定されず、prefixアクション(mobile_bar)が呼ばれるのを防ぎます。

このあたりの挙動はRouting.adminと同じですね。:-D

webservicesは中々便利な機能だったので無くなるのは残念ですが、代替機能があるのは嬉しいことです。

CakePHP DboSourceをPHP5らしく使う

この記事の所要時間: 044

CakePHPではDBアクセスは通常Modelを介して行うので、直接DboSourceを利用する事は無さそうですが、SQL文を自分で構築する際など、意外と使う機会があります。

DboSourceは、通常以下のよう使用します。

<?php
// $hogeをエスケープ
$db =& ConnectionManager::getDataSource($this->useDbConfig);
$db->value($hoge);
?>

もちろんこれでも問題無いのですが、やや冗長な感じもあります。そこでPHP5で以下のように書いてみました。

<?php
ConnectionManager::getDataSource($this->useDbConfig)->value($hoge);
?>

1文にまとまりました。ただ、まだ冗長なので、これをAppModelでまとめてみます。

<?php
class AppModel extends Model {
  public function getDbo() {
    return ConnectionManager::getDataSource($this->useDbConfig);
  }
}
?>

使う際はこうなります。

<?php
// Modelなら
$this->getDbo()->value($hoge);

// Controllerなら
$this->Foo->getDbo()->value($hoge);
?>

スッキリした書き方になりました;-)

そろそろPHP5も普及しつつあるので、CakePHPももっとPHP5らしい書き方に移行したいな、という話でした。

CakePHP FormHelper#datetime()で年月日表示を変える

この記事の所要時間: 116

CakePHP1.2で追加されたFormHelperには日時をプルダウンメニューで選択する機能があります。

<?php echo $form->datetime('hoge', 'YMD', 'NONE'); ?>

これで年月日を選択できるのですが、デフォルトだと↓のように月が英語表現で表示されます。

そこで月を数字表現に変更する方法です。

datetimeメソッドはFormHelper#$optionsの値をプルダウンで表示するリストに使用します。(値が無ければデフォルト値を使用)よってこれを書き換えれば任意の値をリストに表示できます。

<?php
// 月を数字へ
$form->options['month'] = array();
for ($i = 1 ; $i <= 12 ; $i++) {
  $form->options['month'][$i] = sprintf("%02d", $i);  
}
?>
<?php echo $form->datetime('hoge', 'YMD', 'NONE'); ?>

これで以下のようになります。

FormHelper#$optionsでは月の他に様々なリストを指定できるので、年を和暦表現する、任意の日のみ選択対象にするなど変更できます。

■指定できるリスト

$options[‘year’]
$options[‘month’]
$options[‘day’]
$options[‘hour’] 時(12時間表現)
$options[‘hour24’] 時(24時間表現)
$options[‘minute’]
$options[‘second’]
$options[‘meridian’] 午前・午後

日時表現としては、FormHelper#time()、FormHelper#input()もありますが、これらも内部的には同じロジックを共有しているので、同じように指定ができます。

CakePHP Model#save()のSELECT文を抑制する

この記事の所要時間: 154

Model#save()でUPDATE文が発行される際は、まず該当ID(primary key)がDBに存在するかを確認するSELECT文が発行されて、存在すればUPDATE文が発行されます。

1レコードだけを更新するなら特に気にする必要は無いのですが、大量のレコードを逐次UPDATEする際はこのSELECT文を抑制したくなります。

そこでこのSELECT文を抑制する方法を調べてみました。

1. Model#$__exitstsにtrueを入れる

ちょっと強引な方法ですが、save()を実行する直前でModel#$__exitstsにtrueを入れると、SELECT文が発行されません。

Model#$__exitstsはsave()実行後、nullにリセットされるので元の値を戻す必要はありません。

    // FooがModel
    $this->Foo->__exists = true;
    $this->Foo->save($data);
    $this->Foo->save($data); // こちらはSELECT文が発行される

2. Model#exists()を継承する

SELECT文はModel#exists()で発行されるのでこれを継承して実行を抑制する方法です。

下のソースのように抑制するか否かでフラグを持たせると使い勝手が良いでしょう。

[app/app_model.php]

<?php
class AppModel extends Model {
  public $checkExists = true;

  public function exists($reset = false) {
    if ($this->checkExists) {
      return parent::exists($reset);
    }

    return true;
  }
}
?>

使う側

    $f = $this->Foo->checkExists; // フラグ値を保存
    
    // FooがModel
    $this->Foo->checkExists = false; // 抑制
    $this->Foo->save($data);
    
    $this->Foo->checkExists = $f; // フラグ値戻す

手軽で使い勝手も良いのは1.ですが、いちおうModel#$__existsは(コメント上では)privateのようなので、2.の方がお行儀の良いコードですね。:-D

第2回CakePHP勉強会も盛り上がりました!

この記事の所要時間: 32

第2回CakePHP勉強会が無事に終了しました。

申込受付時もそうでしたが、勉強会・懇親会も熱気に包まれて盛り上がりました。

Cakeだけに焼き立てな感じでした(・・・)

addons.mozilla.org@CakePHP

  • 37.5万PV/Web1台
  • やはり実際に稼働しているのでかなり参考になる
  • ソースの他にhttp://wiki.mozilla.org/Update:Remoraも参考になる
  • 相変わらずさすがのプレゼンw
  • ソースをちらちらと見たが、すっきりまとまっており読みやすい
  • 今は1.2 に盛り込まれている内容が独自に実装されていたり
    => memcached対応、多言語化とか
  • 本家とのやりとりをしたyando++

ニフティトピックイット@CakePHP

  • 数百万PV/m => TVで紹介される => 数千万PV/mへ
  • プレゼンの場で仕様確認w
  • 使った理由
    ・PHP4で高速開発
    ・ActiveRecord
    ・Railsっぽい

  • 開発は早くできた
  • CAKE_SECURITY=highにつまづく。mediumで解決(個人的にもhighは使いづらい)
  • 次は負荷対策w
  • やっぱり事例は強力だ

CakePHP1.2のメールコンポーネント

  • メール屋さんらしく専門家からみたEmailComponent
  • SMTP実装で陥りやすい点を知っているからこその着眼点はさすが!
  • 問題点をticket投げているのもさすが!
  • ちょっぴりEmailComponentは使おうと思っていたけどキツイかなw
  • 本の宣伝ありがとうございましたーw

初心者がはまりやすいCakePHPのうっかりポイントまとめ

  • 百式のたぐちさん!
  • ユーモアを交えてテンポの良いプレゼンはさすが。
  • 相手に「使ってみたい」と思わせるのは結構大事。

CakePHP+Oracle

  • 「Do You CakePHP はてな」の中の人w
  • OracleとCakePHPの組み合わせはレアなので実際の検証された内容は貴重
  • 1.2betaでは発行されるSQLがおかしいっぽい。(PostgreSQL向けと同じ問題かも)
  • 話し慣れているご様子でした

あのオープンソースソフトウェアを CakePHP に移植する

  • RailsでできたFastladderをCakePHPに移植!
  • ディレクトリ構成やファイル構成が似ているから移植はやりやすい
  • このネタを思いついたp4life++
  • 是非ソース公開をw

懇親会

会場のビル内に隣接(!)するバーで懇親会。人口密度が高かったのでクラブっぽい熱気がありました。バーカウンタの中から全体を見渡すと、皆さん良い笑顔をされていたのが印象に残っています。

記憶に残っていて、書いても良さげなネタwをつらつらと。

  • メール配信は奥が深い
  • CakePHPのリポジトリはtrunkとbranchesが逆じゃない?
  • 1.2prebetaから1.2betaで結構変わっている
  • 今回も自分以外に大阪から参加の方が!
    これは大阪でも勉強会やらないと。
  • @niftyの方々となぜか関西ローカルネタで盛り上がるw
  • ようやくkunitさんとお会いできた!
  • ちょっとへばってたのでおとなしくしてました。でもやっぱりもうちょっと動けば良かった。。。

参加された皆さん、発表された皆さん、会場提供の他諸々の準備をやって下さったトライコーンのすずきさん、そしてあきやんさん、そしてそして安藤さん本当にありがとうございました。

PHP Fatal error: Cannot use string offset as an array

この記事の所要時間: 120

文字列を三次元以上の配列とみなしてisset()すると「Fatal error: Cannot use string offset as an array」が発生しました。

PHP5.2.5/5.1.6で試しました。

<?php
$a = "";
var_dump(isset($a&#91;'0'&#93;&#91;'0'&#93;)); // 2次元は可
var_dump(isset($a&#91;'0'&#93;&#91;'0'&#93;&#91;'0'&#93;)); // Fatal error

// empty も同じ
var_dump(empty($a&#91;'0'&#93;&#91;'0'&#93;)); // 2次元は可
var_dump(empty($a&#91;'0'&#93;&#91;'0'&#93;&#91;'0'&#93;)); // Fatal error
?>

$aは空文字でも”hoge”でも同じようにNGでした。ちなみに$aが文字列の時に発生するもので、数値やnullでは問題ありません。

文字列変数に[]を使うとインデックスで指定したオフセットの文字へアクセスできるので、これが影響しているのかもしれません。(ただPHP4.3.9ではこのエラーは発生しませんでした。)

これ自体は単にis_array()でチェックすれば良い話なのでそれほど大した事ではないです。

<?php
$a = "";
if (is_array($a)) {
  var_dump(isset($a&#91;'0'&#93;&#91;'0'&#93;&#91;'0'&#93;));
}
?>

実はこのエラーはCakePHP1.2-prebetaで稼働しているシステムをCakePHP1.2-betaのフレームワークに移行した際に気づきました(Helper#value()で発生)。Helperを上記のように書き換えれば問題無いのですが、フレームワークにあまり手は入れたくないので解決策を調査中です;-)

[2008/02/12追記]

CakePHP1.2-betaの問題は自作Component#beforeRender()での変換処理に問題があったことが原因でした。NULLな値を空文字に変換してしまっていました。これまでは上手く動いていたのが、1.2-betaのヘルパーで顕在化したというところですね。いやはや;-)。

[告知] 第2回CakePHP勉強会の申込を開始しました。

この記事の所要時間: 052

[告知] 第2回CakePHP勉強会を行います。でご案内した第2回CakePHP勉強会の申込を開始しました。

日時:2008年02月20日(水)20:30 – 22:15

会場:トライコーン株式会社

地図

参加費用 無料

懇親会参加費 実費(2000円前後予定)

events.php.gr.jp

会場はPHP勉強会でお馴染みのトライコーンさんです。(ありがとうございます!)

CakePHPを使った大規模サイトの事例としてyandoさんが調査された「addons.mozilla.org」、そして何と「ニフティトピックイット」の中の方々に発表を行っていただけます(ありがとうございます!)。

またLT枠(一人5分程度の発表)もありますので、Cakeに関するネタをお持ちの方は発表をお願いします。

CakePHPを使った大規模サイトの事例を直に聞ける機会ですので是非ご参加下さい。

申込はevents.php.gr.jpからどうぞ。

[告知] 第2回CakePHP勉強会を行います。

この記事の所要時間: 038

[2008/02/08] 申込を開始しました。

告知です。第2回CakePHP勉強会を都内で行います。

第2回CakePHP勉強会

  • 日時: 2008/2/20(水) 夜(19:00 or 20:00~?)
  • 場所: 都内(会場は調整中です)

日程はほぼ決まりなのですが、会場の最終調整を行っているところです。決まりましたらまたこのblogにてご連絡します。

今回は事例発表などもあり、かなり楽しみな内容になりそうです。またLT枠も設けるのでネタをお持ちの方是非ご参加下さい。

前回のレビュー等々はhttp://events.php.gr.jp/event.php/event_show/30から辿れます。

ホーム > PHP

検索
フィード
メタ情報

Return to page top