Emacs で PHPコードを編集するための設定と、PHPに関する学習メモ・雑記。
実行環境
- PHP 4.4.8 (cli) (built: Feb 4 2008 16:46:07) Copyright (c) 1997-2008 The PHP Group Zend Engine v1.3.0, Copyright (c) 1998-2004 Zend Technologies
- Emacs: GNU Emacs 22.1.1
- OS: Vine Linux 4.2
リンク
php-modeの導入
今まで、php-mode.el version 1.2.0 をインストールしていたのだが、version 1.4.0 がリリースされたので導入した。make すると info ファイルが生成される。
;;; php-mode.el --- major mode for editing PHP code ;; Copyright (C) 1999, 2000, 2001, 2003, 2004 Turadg Aleahmad ;; 2008 Aaron S. Hawley ;; Maintainer: Aaron S. Hawley <ashawley at users.sourceforge.net> ;; Author: Turadg Aleahmad, 1999-2004 ;; Keywords: php languages oop ;; Created: 1999-05-17 ;; Modified: 2007-01-02 ;; X-URL: http://php-mode.sourceforge.net/ (defconst php-mode-version-number "1.4.0" "PHP Mode version number.")
プロジェクトページ: PHP mode for Emacs (sourceforge.net)
日本語訳マニュアル: PHP Mode マニュアル
以下の設定で、関数名補完を利用するためにはPHPマニュアルをダウンロードする必要がある。
PHPマニュアルのダウンロードページ: Download documentation
(require 'php-mode) (eval-after-load 'php-mode '(progn ;; C-c RET: php-browse-manual (setq php-manual-url "http://www.php.net/manual/ja/") ;; C-c C-f: php-search-documentation (setq php-search-url "http://jp2.php.net/") ;; 補完のためのマニュアルのパス (setq php-manual-path "~/lib/php/html/") ;; M-TAB が有効にならないので以下の設定を追加 (define-key php-mode-map "\C-\M-i" 'php-complete-function) ;; その他 (define-key php-mode-map "\C-\M-a" 'php-beginning-of-defun) (define-key php-mode-map "\C-\M-e" 'php-end-of-defun) ))
php-mode は imenu の設定も行っているので、好みでメニューバーに追加する。
(add-hook 'php-mode-hook (lambda () ;; imenu をメニューバーに追加 (imenu-add-to-menubar "Imenu") ))
補完コマンドの改造
補完コマンドの php-complete-function を mcomplete.el と組み合わせて利用するため、インタフェースに completing-read を使うように改造した。以下のコードを .emacs にコピーする(completing-read による補完が好きな人向け)。download
(eval-after-load 'php-mode '(defun php-complete-function () "Perform function completion on the text around point. Completes to the set of names listed in the current tags table and the standard php functions. The string to complete is chosen in the same way as the default for \\[find-tag] (which see)." (interactive) (let ((pattern (php-get-pattern)) beg completion (php-functions (php-completion-table))) (if (or (= (preceding-char) ?\ )(not pattern)) (setq pattern "" completion "") (search-backward pattern) (setq beg (point)) (forward-char (length pattern)) (setq completion (try-completion pattern php-functions nil))) (cond ((eq completion t)) ((null completion) (message "Can't find completion for \"%s\"" pattern) (ding)) ((not (string= pattern completion)) (delete-region beg (point)) (insert completion)) (t (let ((end (point)) (sym (completing-read "Complete PHP symbol: " php-functions nil t pattern))) (when (not (equal sym "")) (unless (string= pattern "") (delete-region beg end)) (insert sym))))))))
マニュアルの検索
php-mode はポイント位置の単語を拾って、PHPマニュアルを検索するコマンドが含まれている(php-search-documentation, コマンドキーは、C-c C-f)。しかし、ユーザーが任意の関数を入力して調べたいと思っても、残念ながらできない。そこで、補完のための関数を利用して、ユーザーが指定する関数をマニュアルから検索するコマンドを作った。補完機能を利用しているため、うろ覚えでも関数名を指定できるし、一覧から選択することも可能なので、この方が便利だと思う。
(defun php-completing-search-documentation () "Search PHP documentation for the symbol user specified." (interactive) (let* ((default (current-word t)) (pattern (completing-read (format "PHP symbol {`%s'}: " (or default "")) (php-completion-table)))) (browse-url (concat php-search-url (if (string= pattern "") default pattern)))))
設定は、標準のキーバインドと差し替えて、次のようにすれば良いでしょう。
(define-key php-mode-map "\C-c\C-f" 'php-completing-search-documentation)
eldoc for php-mode
eldoc は、関数呼び出しの引数リストを自動的にエコーエリアに表示する簡易ヘルプ機能で、Emacs に標準で付属しているマイナーモード。バッファのメジャーモードがサポートしていれば M-x eldoc-mode で enable と disable をトグルできる。便利な機能なので、php-mode でも eldoc を使えるようにした。マニュアルから抽出した関数名と引数リストを辞書として自前で持っているだけなので、ユーザー定義関数の場合は表示できない。下の phpdoc.el をダウンロード後、ロードパスの通ったディレクトリにコピーして、以下の設定をすれば利用できる。
(require 'phpdoc) (add-hook 'php-mode-hook (lambda () (eldoc-mode t)))
Download: phpdoc.el v 1.10
(次のスクリプトを使って英語版マニュアルから辞書を生成した: phpdoc.scm)
確かに eldoc が利用できると便利なのだけれど、補完で入力した後、開きカッコを入力しても肝心の関数の情報が表示されない。カーソルを動かすとか空白を入力したりすれば表示されるけれど、これではちょっと不便だ。私自身は、Emacs22 から追加された insert-pair というコマンドを使って閉じカッコも同時に入力しているので、次のような設定をすることにした。下の設定で、'(php-mode python-mode) として、python-mode でも試してみたところ具合がいい気がします。
(eval-after-load 'eldoc '(defadvice insert-pair (after for-eldoc-mode activate) "Force display eldoc-message." (and (local-variable-if-set-p 'eldoc-documentation-function) (memq major-mode '(php-mode)) eldoc-mode (eldoc-message (funcall eldoc-documentation-function)))))
insert-pair は、次のような感じで設定すればよいです。
(setq parens-require-spaces nil) ; 開きカッコの前に空白を入れない (define-key php-mode-map "(" 'insert-pair)
flymake を利用した構文チェック
Emacs 22 に標準で付属している flymake を利用すると、PHPコードのリアルタイム構文チェックを行なうことができる。
参考ページ
Download: flymake-php.el (上記ページを参考にファイル化したものです。著作権は上記ブログの管理者 Wersja polska 氏?にあると思われます。)
flymake-php.el をダウンロード後、ロードパスの通ったディレクトリにコピーして、以下の設定を行なう。
(require 'flymake-php) (add-hook 'php-mode-hook 'flymake-php-load)
コードを書いている途中で、エラーがあると自動的にハイライトされる。また、C-c d (コマンド名は、flymake-display-err-menu-for-current-line)でエラー内容がポップアップメニューで表示されるので確認できる。必要に応じて以下のような設定をしておけば、エラー行と警告行の色を変更できる。
(eval-after-load 'flymake '(progn (set-face-background 'flymake-errline "red") (set-face-background 'flymake-warnline "blue") ))
なお、flymake には Infoマニュアルが付属している。
(info "(flymake)Top")
php-eval.el
ちょっとしたテストコードを書いて、結果を確認したいということがあって Emacs Lisp を作ってみた。emacs-lisp-mode で、式を eval すると簡単に評価値が得られて開発が楽だけれど、同じような感覚で PHPのコードを書ければなあという願望から考えたものです。つまりインタラクティブな開発環境が欲しいと(他のスクリプト言語、ruby, python, scheme, haskell...などでもインタラクティブに開発できる)。実は、PHPにもインタラクティブモードがあって、$ php -a で起動できる。これを利用した Emacs Lisp があるのかどうかわからないけれど、あれば使ってみたい。とりあえず間に合せで、php-eval.el でもそれなりに便利。
Download: php-eval.el
以下のコマンドは、shell-command-on-region を使ってコードをPHPに渡し、結果を *PHP-OUTPUT* バッファに出力します。
- php-eval-tag: ポイントをPHPのタグ内に置いて実行。
- php-eval-last-tag: ポイントをPHPタグの後に置いて実行。
- php-eval-delimiter: 特定のデリミタでコードを区切っておいて、その内側で実行。
- php-eval-region: リージョンを指定して実行。
その他、次のコマンドがあります。
- php-eval-insert-delimiter: デリミタを挿入する(C-u 実行もあり)。
- php-eval-display-buffer: *PHP-OUTPUT* バッファを表示する。
*PHP-OUTPUT* バッファでは、C-c C-b で browse-url-of-buffer を実行できます。また、defvar で宣言している変数はカスタマイズ可能です。
設定例:
(require 'php-mode) (require 'php-eval) (eval-after-load 'php-eval '(progn (define-key php-mode-map "\C-x\C-e" 'php-eval-delimiter) (define-key php-mode-map "\C-c\C-i" 'php-eval-insert-delimiter) (define-key php-mode-map "\C-c\C-x" 'php-eval-tag) (define-key php-mode-map "\C-c\C-e" 'php-eval-last-tag) (define-key php-mode-map "\C-c\C-r" 'php-eval-region) (define-key php-mode-map "\C-c\C-v" 'php-eval-display-buffer) ))
基本的な文法
タグ、セミコロン
PHPは埋め込み型の言語で、ファイルを解析して開始タグを探し、見つかればコードを実行し、終了タグが見つかれば実行を終了する。それ以外の部分は、そのまま出力する。
PHP で利用できるタグは 4種類。このうち、
<?php ?>
と
<script language="php"> </script>
は、常に使用することができる。他は短縮型のタグとASPスタイルのタグで、php.ini で有効/無効を切り替える。
PHPは、C言語ライクな文法で、文の区切りにセミコロンが必要。しかし、終了タグにはセミコロンが含まれていると認識されるので、終了タグの直前の文では省略可能。ブロックの終了タグは、直後に改行がある場合それを含む。
ファイル終端では、終了タグを付けるのは任意。include() や require() を利用する場合は、終了タグを省略する方が無難。
コメント
C、C++、Unixシェル型のコメントをサポートしている。
型
8種類の基本型をサポートし、ドキュメント上の擬似的な3種類の型がある。
基本型
- 4 種類のスカラー型:
論理値(boolean), 整数(integer), 浮動小数点数(float [doubleと同じ]), 文字列(string)
- 2 種類の複合型:
配列(array), オブジェクト(object)
- 2 種類の特別な型:
リソース(resource), ヌル(NULL)
※スカラーとは、配列などと違い、より小さな部分に'分割する'ことができない値のこと。
ドキュメント上の疑似的な型:
- mixed: 引数に多様な型を使うことができることを示す。
- number: 引数が integer または float のどちらでもよいことを示す。
- callback: ユーザが定義するコールバック関数を引数として受け取る。
型と値を調べる
- 型と値の情報を正確に知る: var_dump() 関数
- 変数に関する情報を解りやすく出力する: print_r() 関数
- デバッグ用に型を表示: gettype() 関数
- 型のチェック: is_type() 関数 (is_int, is_float, is_bool, is_array, is_string...)
型変換
強制的に変換するには、キャストするか settype() 関数を使う。
boolean 型
TRUE または FALSE を表わす。boolean型に変換した場合 FALSE となるもの。
- integer型の 0 (-1 は TRUE)
- float型の 0.0
- 空文字列、文字列の "0"
- 要素数が0の配列
- ゼロを要素とする オブジェクト
- 特別な値 NULL (値がセットされていない変数を含む)
- 空のタグから作成された SimpleXML オブジェクト
整数型
8 進数表記は数の前に 0 を付ける。16 進数表記は数の前に 0x を付ける。
整数のサイズはプラットフォーム依存。一般的には、32 ビット符号付整数の範囲。
<?php echo PHP_INT_MAX; ?> // 2147483647
integer型の範囲外の数を指定した場合、また結果が integer型の範囲外の数となるような計算を行った場合 float型になる。
<?php var_dump(pow(2, 31) - 1); ?> // float(2147483647) <?php var_dump(pow(2, 30)); ?> // int(1073741824)
文字列型
- 文字列の長さに制限はない。
- 文字列は、'.' (ドット) 演算子で連結できる。
- 文字列リテラルの3つの表記法: シングルクォート、ダブルクォート、ヒアドキュメント。
- 変数と特殊文字のエスケープシーケンスは、シングルクオートで括られた文字列の中では展開されない。
- スクリプトが二重引用符で括られるかヒアドキュメントで指定された場合、その中の変数はパースされる。
- 式を展開する構文には、変数名を展開する「簡単な構文」と、複雑な式を含めることができる「波カッコ構文」がある。
- []を使用し、インデックスを指定して文字単位でアクセス・変更できる。
配列
PHP では添字配列と連想配列の間に違いはなく、配列型は 1 つだけで、整数または文字列のインデックスを使用することができる。
- 配列を要素として持てるので、ツリー構造を表現できる。
- PHPの全ての型を要素として持てる。
- 配列を生成するには、array() 関数を使用する。array() 関数は、リテラル配列を表現するための言語構成要素で、通常の関数ではない。
- 配列の長さを調べるには、count() 関数を使用する。
NULL
NULL は、変数が値を持たないことを意味する。
- 定数 NULL が代入されている場合。
- 値が代入されていない場合。
- unset() されている場合。
変数
- 変数名の先頭文字は $ である。
- 変数名には、英文字・数字・アンダースコアが使える。
- $ に続く最初の文字は、英文字かアンダースコアであり、数字は使えない。
- 変数名の大文字・小文字は区別される。
- 変数宣言は必要がなく、変数の型を指定する必要もない(動的型付け)。
- 変数の代入は値による代入。例えば、配列をコピーするには、単に代入すればよく、内部ノードまでコピーされ、一方の配列の変更は他方に影響しない(JavaScriptとは異なる)。変数名の前に & を付けて、参照による代入も可能。
- 変数は初期化するべき。初期化しない場合、その型のデフォルト値となる。初期化されているかどうか検査するには、isset() 関数を使用する。
- PHPには、グローバル変数とは別に、自動的に全てのスコープで利用可能なスーパーグローバル変数がある。これは、Webサーバ・環境変数・ユーザ入力からの変数を値とする定義済みの配列である。PHPマニュアル 予約済みの定義済みの変数
- 全ての定義済みの変数を調べるには、get_defined_vars() 関数を使用する。
- 変数のスコープは、その変数が定義されたコンテキスト。PHPマニュアル
- ユーザー定義関数の内部では、変数の有効範囲はその関数内部に制限される。グローバル変数は、関数の内部で使用する場合、関数内部でグローバル宣言する必要がある。配列 $GLOBALSを使用しても、関数内部からグローバル変数にアクセスできる。$GLOBALSは連想配列であり、グローバル変数の名前がキー、値が配列要素の値である。
- 静的変数を使用するには、static 宣言をする(式の結果を代入できない(定数式のみ)。
- 可変変数がある。PHPマニュアル
定数
- 定数を定義するには、define() 関数を使用し、代入によってはできない。定数として指定できるのはスカラー型の値のみ。
- 定数はグローバルスコープを持つ。
- 再定義できない。
- 変数名と違い $ は不要。
- 定数と(グローバル)変数は、名前空間が異なる。
定数としてセットされているかどうかチェックするには、defined() 関数を使う。(変数について調べるには、isset() 関数、関数が定義されているかどうかは、function_exists() 関数を利用する。)
<?php var_dump(defined('PHP_INT_MAX')); ?> // bool(true)
定数の値を得るには、constant() 関数を使用する。
<?php echo constant('PHP_INT_MAX'); ?> // 2147483647
定義済みの定数の一覧は、get_defined_constants() 関数を使用する。
<?php print_r(get_defined_constants()); ?>
式
演算子
代数演算子
変数の値を負にする。(-)
<?php $a = 1; echo -$a; ?> // -1
剰余(%) は、両オペランドの小数点以下を切り捨ててから処理される。
代入演算子
文字列の連結 (.=)
<?php $a = 'Hello, '; $a .= 'world!'; echo $a; // Hello, world! ?>
比較演算子
(===): 両オペランドが等しく、同じ型でである場合に真。
(!==): 両オペランドが等しくないか、同じ型でない場合に真。
文字列と整数との比較では、文字列は整数に変換される。数値形式の文字列どうしでは、整数として比較される(switch文にも適用されるルール)。
<?php var_dump(0 == "a"); // 0 == 0 -> true var_dump("1" == "01"); // 1 == 1 -> true var_dump("1" == "1e0"); // 1 == 1 -> true ?>
エラー制御演算子
(@): 式の前に付けた場合、その式が生成するエラーメッセージが無視される。
- @演算子は、式でのみ動作する。
- 値を得ることができる、変数・関数・include()呼び出し・定数などの前に付けることができる。
<?php $file = file('存在しないファイル'); echo 'hello'; ?> // 結果 PHP Warning: file(存在しないファイル): failed to open stream: No such file or directory in - on line 2 <font color=ff0000> Warning: file(存在しないファイル): failed to open stream: No such file or directory in - on line 2 </font>hello
<?php $file = @file('存在しないファイル'); echo 'hello'; ?> // 結果 hello
<?php $file = @file('存在しないファイル') or die ('オープンできません'); echo 'hello'; ?> // 結果 // die は、exit()と同等で現在のスクリプトを終了する。 オープンできません
実行演算子
(``): シェルのコマンドを実行できる。単に出力するだけでなく、変数に代入できる。shell_exec() 関数と等価。
<?php $output = `date`; echo $output; // 2008年 3月 6日 木曜日 20:12:43 JST ?>
<?php $output = shell_exec('date'); echo $output; // 2008年 3月 6日 木曜日 20:14:54 JST ?>
配列演算子
型演算子
(instanceof): ある変数が特定のクラスのインスタンスであるかどうかを調べる。
制御構造
foreach
関数
- PHP4 以降、関数は参照される前に定義されていなくてもよい。
- 条件付きで定義される場合は、呼ばれる前に定義されている必要がある。
- 関数内関数も定義できるが、関数の名前空間はグローバル。
- 関数のオーバーロード・再定義することはできない。
- 関数名は大文字・小文字を区別しない。
- PHP4 以降、デフォルト引数値を指定できる。
- PHP4 以降、可変個の引数を渡せる。func_num_args(), func_get_arg(), func_get_args() 関数を使用すればよい。
- 再帰呼び出しも可能。しかし、100から200が限界。
- 引数として関数を渡すことができる。
- 引数は値渡し。参照渡しするには、仮引数の前に & を付ける。
※マニュアルによれば、PHP5以降、デフォルトで値は参照渡しになる。PHPマニュアル - リファレンスを返すことができる。PHPマニュアル
- create_function() で無名関数を定義できる。
- 関数の内部では、変数の有効範囲はその関数にローカル。(関数内部からグローバル変数にアクセスするには global宣言か、連想配列 $GLOBALSを使用する。)
練習
factorial (階乗)
// 再帰 <?php function factorial($n) { return $n <= 0 ? 1 : $n * factorial($n - 1); } var_dump(factorial(10)); // int(3628800) var_dump(factorial(100)); // float(9.3326215443944E+157) var_dump(factorial(170)); // float(7.257415615308E+306) var_dump(factorial(171)); // float(INF) ?>
// while 1 <?php function factorial($n) { $ans = 1; while ($n > 0) { $ans *= $n; --$n; } return $ans; } var_dump(factorial(10)); // int(3628800) var_dump(factorial(100)); // float(9.3326215443944E+157) var_dump(factorial(170)); // float(7.257415615308E+306) var_dump(factorial(171)); // float(INF) var_dump(factorial(1000)); // float(INF) ?>
// while 2 <?php function factorial($n) { $ans = $n; while (--$n > 0) { $ans *= $n; } return $ans; } var_dump(factorial(10)); // int(3628800) var_dump(factorial(100)); // float(9.3326215443944E+157) var_dump(factorial(170)); // float(7.257415615308E+306) var_dump(factorial(171)); // float(INF) var_dump(factorial(1000)); // float(INF) ?>
// for <?php function factorial($n) { $ans = 1; for ( ; $n > 0; --$n) { $ans *= $n; } return $ans; } var_dump(factorial(10)); // int(3628800) var_dump(factorial(100)); // float(9.3326215443944E+157) var_dump(factorial(170)); // float(7.257415615308E+306) var_dump(factorial(171)); // float(INF) var_dump(factorial(1000)); // float(INF) ?>
πの近似
// John Wallis <?php function pi_product($a, $b) { $ans = 1; while ($a <= $b) { $ans *= (4 * (pow($a, 2) + $a)) / pow($a * 2 + 1, 2); ++$a; } return $ans; } echo 4 * pi_product(1, 1000); // 3.1423773650939 ?>
product (高階関数)
高階関数 product を使って、factorial と pi_product を定義する。
<?php // 高階関数 function product($term, $a, $next, $b) { $ans = 1; while ($a <= $b) { $ans *= $term($a); $a = $next($a); } return $ans; } // 階乗 function factorial($n) { return product(create_function('$x', 'return $x;'), 1, create_function('$x', 'return $x + 1;'), $n); } // πの近似 function pi_product($a, $b) { return product( create_function('$x', 'return (4 * (pow($x, 2) + $x)) / pow($x * 2 + 1, 2);'), $a, create_function('$x', 'return $x + 1;'), $b); } echo factorial(10); // 3628800 echo 4 * pi_product(1, 1000); // 3.1423773650939 ?>
sum_integers (aからbまでの整数の和)
// 再帰 <?php function sum_integers($a, $b) { return $a > $b ? 0 : $a + sum_integers($a + 1, $b); } echo sum_integers(1,10); // 55 echo sum_integers(1,0); // 0 echo sum_integers(0,0); // 0 ?>
// for <?php function sum_integers($a, $b) { $ans = 0; for ( ; $a <= $b; ++$a) { $ans += $a; } return $ans; } echo sum_integers(1,10); // 55 echo sum_integers(1,0); // 0 echo sum_integers(0,0); // 0 ?>
// while <?php function sum_integers($a, $b) { $ans = 0; while ($a <= $b) { $ans += $a++; } return $ans; } echo sum_integers(1,10); // 55 echo sum_integers(1,0); // 0 echo sum_integers(0,0); // 0 ?>
sum_cube (aからbまでの整数の三乗の和)
// 匿名関数 // PHPでは、関数内関数といってもグローバルなので無名関数を使う <?php function sum_cube($a, $b) { $cube = create_function('$x', 'return $x * $x * $x;'); $ans = 0; while ($a <= $b) { $ans += $cube($a++); } return $ans; } echo sum_cube(1,10); // 3025 var_dump(isset($cube)); // bool(false) ?>
sum (高階関数)
高階関数 sum を使って、sum_integers と sum_cube を定義する
<?php // 高階関数 function sum($term, $a, $next, $b) { $ans = 0; while ($a <= $b) { $ans += $term($a); $a = $next($a); } return $ans; } // 整数の和 function sum_integers($a, $b) { return sum(create_function('$x', 'return $x;'), $a, create_function('$x', 'return $x + 1;'), $b); } // 整数の三乗の和 function sum_cube($a, $b) { return sum(create_function('$x', 'return $x * $x * $x;'), $a, create_function('$x', 'return $x + 1;'), $b); } echo sum_integers(1,10); // 55 echo sum_cube(1,10); // 3025 ?>
sigma (可変個の引数の総和)
PHPでは、次の3つの関数を利用すれば可変長引数を扱える。サンプルとして整数引数の総和を求める sigma() 関数を定義した。
int func_num_args(void); // 関数に渡された引数の数を返す mixed func_get_arg(int); // 関数の引数リストから、指定した引数を返す array func_get_args(void); // 関数の引数リストを配列として返す
// func_get_args <?php function sigma() { $a = func_get_args(); $ans = 0; foreach ($a as $x) { $ans += $x; } return $ans; } echo sigma(1,2,3,4,5); // 15 echo sigma(1,2,3,4,5,6,7,8,9,10); // 55 ?> // func_num_args, func_get_arg <?php function sigma() { $n = func_num_args(); $ans = 0; for ($i = 0; $i < $n; ++$i) { $ans += func_get_arg($i); } return $ans; } echo sigma(1,2,3,4,5); // 15 echo sigma(1,2,3,4,5,6,7,8,9,10); // 55 ?>
ライブラリ関数の array_sum() があるので、ループしなくてもよい。
<?php function sigma() { return array_sum(func_get_args()); } echo sigma(1,2,3,4,5); // 15 echo sigma(1,2,3,4,5,6,7,8,9,10); // 55 ?>
高階関数の array_reduce() を使う手もある。
<?php function sigma() { return array_reduce( func_get_args(), create_function('$k, $x', 'return $x + $k;'), 0); } echo sigma(1,2,3,4,5); // 15 echo sigma(1,2,3,4,5,6,7,8,9,10); // 55 ?>
Scheme なら fold や apply が使える。
(define (sigma . x) (fold + 0 x)) (define (sigma . x) (apply + x)) gosh> (sigma 1 2 3 4 5) =>15 gosh> (sigma 1 2 3 4 5 6 7 8 9 10) =>55
配列のコピー
PHPでは、配列をコピーするには単に代入すればよく、内部ノードまで再帰的にコピーされる。代入は、元の変数を新しい変数にコピーする(値による代入)ため、片方の変数に対する変更はもう片方に影響を与えない。この点、データ型が基本型と参照型に分かれ、配列は参照が代入される JavaScript とは異なる。
配列を受け取るユーザー定義関数では、値渡しがデフォルトなので、配列が変更されてもよい場合や配列を走査するだけのような目的なら、参照渡しにしないとリソースを無駄に消費してしまう。代入の場合も参照による代入が可能(PHPマニュアル)。
// JavaScriptの場合 a = [1,2,3]; b = a; // 代入ではコピーできない a[0] = 100; b // [100, 2, 3]
// PHPの場合 <?php $a = array(1,2,3, array(array(4,5),6,7)); $b = $a; // 値による代入 $c = &$a; // 参照による代入 $a[1] = 100; // $aを変更 $a[3][0][1] = 200; // $aを変更 print_r($a); print_r($b); // 内部ノードまでコピーされているので$aを変更しても影響がない print_r($c); // $aが変更されれば影響を受ける ?>
// $a Array ( [0] => 1 [1] => 100 [2] => 3 [3] => Array ( [0] => Array ( [0] => 4 [1] => 200 ) [1] => 6 [2] => 7 ) ) // $b Array ( [0] => 1 [1] => 2 [2] => 3 [3] => Array ( [0] => Array ( [0] => 4 [1] => 5 ) [1] => 6 [2] => 7 ) ) // $c Array ( [0] => 1 [1] => 100 [2] => 3 [3] => Array ( [0] => Array ( [0] => 4 [1] => 200 ) [1] => 6 [2] => 7 ) )
配列の参照渡し
参照渡しにする場合には、関数定義において & を仮引数の前に付ける(配列以外でも同じ)。
<?php // array_foreach: 関数と配列を受け取り、配列の各要素に関数を適用する function array_foreach($func, &$a) { for ($i = 0, $n = count($a); $i < $n; ++$i) { $func($a[$i]); } } $a = array(1,2,3); // 配列を非破壊的に変更する関数を渡す array_foreach(create_function('$x', 'echo ($x *= $x) . "\n";'), $a); print_r($a); // 配列を破壊的に変更する関数を渡す array_foreach(create_function('&$x', 'echo ($x *= $x) . "\n";'), $a); print_r($a); ?>
1 4 9 Array ( [0] => 1 [1] => 2 [2] => 3 ) 1 4 9 Array ( [0] => 1 [1] => 4 [2] => 9 )
上で定義した array_foreach() は、PHP4以降で利用できる array_walk() 関数を使えば自前で用意する必要はない。Scheme なら、map に対応する副作用目的の for-each を使う場面で、PHPの場合は array_walk() 関数を使う。結果は上と同じになる。
// 自前の array_foreach ではなく、array_walk を使う <?php $a = array(1,2,3); // 配列の要素を非破壊的に変更する関数を渡す array_walk($a, create_function('$x', 'echo ($x *= $x) . "\n";')); print_r($a); // 配列の要素を破壊的に変更する関数を渡す array_walk($a, create_function('&$x', 'echo ($x *= $x) . "\n";')); print_r($a); ?>
再帰版の array_walk_recursive() 関数もあるが、PHP5以降に限られる。自分の環境では利用できないので、再帰版の array_foreach_recursive() 関数を定義した。再帰呼び出ししているため、どの程度実用的かは不明。
// 再帰版 array_foreach <?php // array_foreach_recursive: 関数と配列を受け取り、配列の各要素に再帰的に関数を適用する function array_foreach_recursive($f, &$a) { for ($i = 0, $n = count($a); $i < $n; ++$i) { is_array($a[$i]) ? array_foreach_recursive($f, $a[$i]) : $f($a[$i]); } } $a = array(1,2,3, array(array(4,5),6,7)); // 配列の要素を非破壊的に変更する関数を渡す array_foreach_recursive(create_function('$x', 'echo ($x *= $x) . "\n";'), $a); print_r($a); // 配列の要素を破壊的に変更する関数を渡す array_foreach_recursive(create_function('&$x', 'echo ($x *= $x) . "\n";'), $a); print_r($a); ?>
1 4 9 16 25 36 49 Array ( [0] => 1 [1] => 2 [2] => 3 [3] => Array ( [0] => Array ( [0] => 4 [1] => 5 ) [1] => 6 [2] => 7 ) ) 1 4 9 16 25 36 49 Array ( [0] => 1 [1] => 4 [2] => 9 [3] => Array ( [0] => Array ( [0] => 16 [1] => 25 ) [1] => 36 [2] => 49 ) )
ツリーのマップ
array_map() 関数は、ツリー構造の配列には適用できないので、ツリー構造の配列にも適用可能な tree_map 関数を定義した。(ただし、array_map() 関数のように複数の配列を引数に取ることはできない)。関数を適用するのみか、関数を適用した結果を集めるかの違いがあるだけで、array_foreach_recursive とほとんど同じコードになる。
<?php // tree_map: ツリー構造の配列に関数を再帰的に適用し、関数が返す結果を集める function tree_map($f, $a) { $ans = array(); for ($i = 0, $n = count($a); $i < $n; ++$i) { is_array($a[$i]) ? array_push($ans, tree_map($f, $a[$i])) : array_push($ans, $f($a[$i])); } return $ans; } $a = array(1,2,3); print_r(tree_map(create_function('$x', 'return $x * $x;'), $a)); $a = array(1,2,3, array(array(4,5),6,7)); print_r(tree_map(create_function('$x', 'return $x * $x;'), $a)); ?>
Array ( [0] => 1 [1] => 4 [2] => 9 ) Array ( [0] => 1 [1] => 4 [2] => 9 [3] => Array ( [0] => Array ( [0] => 16 [1] => 25 ) [1] => 36 [2] => 49 ) )
関数とグローバル変数
関数内部では、変数の有効範囲はその関数にローカルなので、次のケースではエラーになる。
<?php $global_var = 1; function test() { return $global_var; } echo test(); // PHP Notice: Undefined variable: global_var ?>
関数内部からグローバル変数にアクセスするには、次のように global宣言するか、$GLOBALSを使用する。
<?php $a = 1; function test1() { global $a; return $a; } function test2() { return $GLOBALS['a']; } function test3() { global $b; $GLOBALS['c'] = 3; $b = 2; } echo test1(); // 1 echo test2(); // 1 test3(); echo $b; // 2 echo $c; // 3 ?>
静的(static)変数
static変数は、他の変数と同様に関数内のローカルスコープを持つが、関数が呼ばれたときに一度だけ初期化され、処理が関数の外に移っても値は保持される。
<?php function static_test () { static $count = 10; // 定数式で初期化する return $count++; } $i = 5; while ($i--) { echo static_test() . "\n"; } ?>
10 11 12 13 14
スーパーグローバル変数
スーパーグローバル変数は定義済みの変数(配列)なので、全ての定義済みの変数を配列で返す、get_defined_vars() 関数で調べることができる。
<?php foreach (get_defined_vars() as $key => $value) { printf("%-20s =>%s\n", $key, gettype($value)); } ?>
argv => array argc => integer HTTP_POST_VARS => array _POST => array HTTP_GET_VARS => array _GET => array HTTP_COOKIE_VARS => array _COOKIE => array HTTP_SERVER_VARS => array _SERVER => array HTTP_ENV_VARS => array _ENV => array HTTP_POST_FILES => array _FILES => array _REQUEST => array
※ printf() 関数は、フォーマットした文字列を出力する。sprintf() 関数は、フォーマットした文字列を返すのみで出力はしない。
int printf ( string $format [, mixed $args [, mixed $... ]] ) string sprintf ( string $format [, mixed $args [, mixed $... ]] )
特定のスーパーグローバル変数を参照する場合にも、foreach が使える。
<?php foreach ($_ENV as $key => $value) { echo "<b>$key:</b> $value<br>"; } ?>