PHPの三項演算子の注意すべき挙動
PHPで以下のようなコードを書いていてしばらくはまっていたのでメモ。
<?php function a($a) { printf("%s\n", isset($a["A"]["B"]) ? $a["A"]["B"] : isset($a["AA"]["BB"]) ? $a["AA"]["BB"] : "CCC"); } $a = array(); $a["A"]["B"] = "C"; a($a); // "C" ? $a = array(); $a["AA"]["BB"] = "CC"; a($a); // "CC" $a = array(); a($a); // "CCC"
実行結果は以下の通り。
PHP Notice: Undefined index: AA in boo.php on line 8 CC CCC
8行目でNoticeが出ることがある。8行目は
$a["AA"]["BB"] :
の部分‥7行目でissetでチェックしているはずだが‥
ググってみると、以下のようなものが見つかった。
曰く
注意:
三項演算子を “積み重ねて” 使用することは避けましょう。 ひとつの文の中で複数の三項演算子を使用した際の PHP の振る舞いは、 少々わかりにくいものです。
例として挙がっているのが以下のコード。
<?php echo (true?'true':false?'t':'f');
これはPHPでは
<?php echo((true ? 'true' : false) ? 't' : 'f');
と評価され、’t' が出力されると。
‥ってこんなん分かるかいっ‥ってことで、各言語で試してみた。
PHP
<?php printf("%s\n", (true ? 'true' : false ? 't' : 'f')); printf("%s\n", (false ? 'false' : true ? 't' : 'f')); printf("%s\n", (false ? 'false' : false ? 't' : 'f'));
実行結果
t t f
Java
CentOS 7のOpenJDK 1.8.0_121
public class Hoge { public static void main(String[] args) { System.out.println(true ? "true" : false ? "t" : "f"); System.out.println(false ? "false" : true ? "t" : "f"); System.out.println(false ? "false" : false ? "t" : "f"); } }
実行結果
true t f
C
#include <stdio.h> #include <stdbool.h> void main(int argc, char** argv) { printf("%s\n", true ? "true" : false ? "t" : "f"); printf("%s\n", false ? "false" : true ? "t" : "f"); printf("%s\n", false ? "false" : false ? "t" : "f"); }
実行結果
true t f
C++
#include <iostream> using namespace std; int main(int argc, char** argv) { cout << (true ? "true" : false ? "t" : "f") << endl; cout << ("%s\n", false ? "false" : true ? "t" : "f") << endl; cout << ("%s\n", false ? "false" : false ? "t" : "f") << endl; return 0; }
実行結果
true t f
Ruby
printf("%s\n", true ? "true" : false ? "t" : "f"); printf("%s\n", false ? "false" : true ? "t" : "f"); printf("%s\n", false ? "false" : false ? "t" : "f");
実行結果
true t f
Python
print('true' if True else 't' if False else 'f'); print('false' if False else 't' if True else 'f'); print('false' if False else 't' if False else 'f');
実行結果
true t f
参考
結局‥
冒頭のコードは
<?php function a($a) { printf("%s\n", isset($a["A"]["B"]) ? $a["A"]["B"] : (isset($a["AA"]["BB"]) ? $a["AA"]["BB"] : "CCC")); } $a = array(); $a["A"]["B"] = "C"; a($a); // "C" $a = array(); $a["AA"]["BB"] = "CC"; a($a); // "CC" $a = array(); a($a); // "CCC"
のように括弧を付けて逃げましたとさ。やれやれ。
Apache TikaのPDFファイルテキスト抽出で遊んでみる
今更ながらに「Apache Tika」というものの存在を知る‥PDFとかその他諸々のファイルのメタデータやテキストを抽出してくれる‥|Apache Tika https://t.co/CsCBY74ekK
— HHeLiBeX (@hhelibex) 2017年2月27日
ということで、サイトのParser APIを追いかけてコードを組み立ててみたメモ。
環境は、CentOS 7(VM)上のOpenJDK 1.8.0_111。
PDFファイルからのテキスト抽出
以下のようなPDFファイルを使う。
PDFファイルからのテキスト抽出にはorg.apache.tika.parser.pdf.PDFParser
クラスを使う。
最低限のコードは以下のような感じ。
import java.io.*; import java.util.*; import org.apache.tika.exception.TikaException; import org.apache.tika.metadata.Metadata; import org.apache.tika.parser.ParseContext; import org.apache.tika.parser.Parser; import org.apache.tika.parser.pdf.PDFParser; import org.apache.tika.sax.BodyContentHandler; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; public class ParserSample { public static void main(String[] args) { Parser parser = new PDFParser(); for (String arg : args) { ContentHandler handler = new BodyContentHandler(); try (InputStream in = new FileInputStream(arg)) { Metadata metadata = new Metadata(); ParseContext context = new ParseContext(); try { parser.parse(in, handler, metadata, context); } catch (TikaException | IOException | SAXException e) { e.printStackTrace(); } List<String> list = new ArrayList<>(Arrays.asList(handler.toString().trim().split("\\s"))); List<String> names = new ArrayList<>(Arrays.asList(metadata.names())); Collections.sort(names); for (String name : names) { System.out.printf("%-48s = %s%n", name, metadata.get(name)); } System.out.println(); System.out.println(list); } catch (IOException e) { e.printStackTrace(); } } } }
これを実行すると、出力として以下のようなものが得られる。
Author = hhelibex Content-Type = application/pdf Creation-Date = 2017-02-27T11:04:23Z Last-Modified = 2017-02-27T11:04:23Z Last-Save-Date = 2017-02-27T11:04:23Z access_permission:assemble_document = true access_permission:can_modify = true access_permission:can_print = true access_permission:can_print_degraded = true access_permission:extract_content = true access_permission:extract_for_accessibility = true access_permission:fill_in_form = true access_permission:modify_annotations = true created = Mon Feb 27 20:04:23 JST 2017 creator = hhelibex date = 2017-02-27T11:04:23Z dc:creator = hhelibex dc:format = application/pdf; version=1.4 dc:title = Apache Tikaのテスト用 dcterms:created = 2017-02-27T11:04:23Z dcterms:modified = 2017-02-27T11:04:23Z meta:author = hhelibex meta:creation-date = 2017-02-27T11:04:23Z meta:save-date = 2017-02-27T11:04:23Z modified = 2017-02-27T11:04:23Z pdf:PDFVersion = 1.4 pdf:docinfo:created = 2017-02-27T11:04:23Z pdf:docinfo:creator = hhelibex pdf:docinfo:creator_tool = MicrosoftR Office ExcelR 2007 pdf:docinfo:modified = 2017-02-27T11:04:23Z pdf:docinfo:producer = MicrosoftR Office ExcelR 2007 pdf:docinfo:title = Apache Tikaのテスト用 pdf:encrypted = false pdfa:PDFVersion = A-1b pdfaid:conformance = B pdfaid:part = 1 producer = MicrosoftR Office ExcelR 2007 title = Apache Tikaのテスト用 xmp:CreatorTool = MicrosoftR Office ExcelR 2007 xmpMM:DocumentID = uuid:72AB1A36-B534-4714-90E1-7640E90E7083 xmpTPg:NPages = 1 [ヘッダ1, ヘッダ2, あ, ア, 阿, い, イ, 伊, う, ウ, 宇, え, エ, 江, お, オ, 尾]
PDFファイルだと「pdf:」で始まる名前を使ってMetadataから情報を取ればよい感じか。
コンテンツの方は、handler.toString()
で得られる文字列が抽出結果になっているので、あとは煮るなり焼くなりすればいい。
汎用的にしてみる
実は、上記のことはorg.apache.tika.parser.AutoDetectParser
を使っても実現できる。
ファイルタイプの検出に少し時間が掛かるのだが、PDFファイルだけではなくほかのファイルのテキストも同じコードで抽出したいという場合には有用だろう。
コードはほぼ同じになるが、以下のような感じ。
import java.io.*; import java.util.*; import org.apache.tika.exception.TikaException; import org.apache.tika.metadata.Metadata; import org.apache.tika.parser.AutoDetectParser; import org.apache.tika.parser.ParseContext; import org.apache.tika.parser.Parser; //import org.apache.tika.parser.pdf.PDFParser; import org.apache.tika.sax.BodyContentHandler; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; public class ParserSample { public static void main(String[] args) { // Parser parser = new PDFParser(); Parser parser = new AutoDetectParser(); for (String arg : args) { ContentHandler handler = new BodyContentHandler(); try (InputStream in = new FileInputStream(arg)) { Metadata metadata = new Metadata(); ParseContext context = new ParseContext(); try { parser.parse(in, handler, metadata, context); } catch (TikaException | IOException | SAXException e) { e.printStackTrace(); } List<String> list = new ArrayList<>(Arrays.asList(handler.toString().trim().split("\\s"))); List<String> names = new ArrayList<>(Arrays.asList(metadata.names())); Collections.sort(names); for (String name : names) { System.out.printf("%-48s = %s%n", name, metadata.get(name)); } System.out.println(); System.out.println(list); } catch (IOException e) { e.printStackTrace(); } } } }
試しに、先に使ったPDFファイルの元であるExcelファイルを食わせてみる。
Application-Name = Microsoft Excel Author = hhelibex Company = Content-Type = application/vnd.ms-excel Creation-Date = 2017-02-27T10:23:34Z Last-Author = hhelibex Last-Modified = 2017-02-27T10:31:13Z Last-Save-Date = 2017-02-27T10:31:13Z X-Parsed-By = org.apache.tika.parser.DefaultParser creator = hhelibex date = 2017-02-27T10:31:13Z dc:creator = hhelibex dc:title = Apache Tikaのテスト用 dcterms:created = 2017-02-27T10:23:34Z dcterms:modified = 2017-02-27T10:31:13Z extended-properties:Application = Microsoft Excel extended-properties:Company = meta:author = hhelibex meta:creation-date = 2017-02-27T10:23:34Z meta:last-author = hhelibex meta:save-date = 2017-02-27T10:31:13Z modified = 2017-02-27T10:31:13Z title = Apache Tikaのテスト用 [Sheet1, , , ヘッダ1, ヘッダ2, , あ, ア, 阿, , い, イ, 伊, , う, ウ, 宇, , え, エ, 江, , お, オ, 尾, , , Sheet2, , , , , Sheet3]
PDFファイルの場合も同様だが、AutoDetectParser
を使うと「X-Parsed-By」がMetadataに追加されている。
シフト演算とMath.pow(2, n)と(+Math.pow(2, n)の怪)
自分が時々やらかしてしまうのでメモ。
環境は、CentOS 7(VM)上のOpenJDK 1.8.0_111。
2のべき乗(整数値)が欲しい場合はシフト演算
2のべき乗(整数値)が欲しいときに時々やらかしてしまうのが、以下のようなコードを書いてしまうこと。
for (int i = 0; i < 63; ++i) { long tmp = (long)Math.pow(2, i); }
基数が2以外だったり指数が整数でなかったりdouble値が欲しかったりlongのMAX_VALUEを超える場合だったり、そんな場合は仕方がないのだが、2のべき乗(整数値)が欲しい場合はシフト演算の方が断然速い。
for (int i = 0; i < 63; ++i) { long tmp = 1L << i; }
どれくらい速いのか、実際に計ってみた。
import java.util.Scanner; public class Main { public static void main(String[] args) { long S; long G; int cnt; Scanner sc = new Scanner(System.in); int n = sc.nextInt(); int m = sc.nextInt(); System.out.print(n); S = System.currentTimeMillis(); test1(n, m); G = System.currentTimeMillis(); System.out.print("," + (G - S)); S = System.currentTimeMillis(); test2(n, m); G = System.currentTimeMillis(); System.out.print("," + (G - S)); S = System.currentTimeMillis(); test3(n, m); G = System.currentTimeMillis(); System.out.print("," + (G - S)); System.out.println(); } private static void test1(int n, int m) { for (int i = 0; i < n; ++i) { for (int j = 0; j < 63; ++j) { long tmp = 1L << j; } } } private static void test2(int n, int m) { for (int i = 0; i < n; ++i) { for (int j = 0; j < 63; ++j) { long tmp = (long)Math.pow(2, j); } } } private static void test3(int n, int m) { for (int i = 0; i < n; ++i) { for (int j = 0; j < 63; ++j) { long tmp = (long)Math.pow(2, m); } } } }
test1が「Math.pow(2, n)」、test2が「シフト演算」を使ったケース。
test3は後で使うので後述。
実行は以下のような感じ。
for i in 100 250 500 1000 2500 5000 10000 25000 50000 100000 250000 500000 1000000 ; do echo $i 62 | java Main done
実行結果のうち、test1とtest2を抜き出したものは以下の通り。単位はミリ秒。
n | test1 | test2 |
---|---|---|
100 | 0 | 1 |
250 | 0 | 2 |
500 | 0 | 3 |
1000 | 1 | 5 |
2500 | 3 | 16 |
5000 | 5 | 30 |
10000 | 9 | 54 |
25000 | 10 | 121 |
50000 | 12 | 233 |
100000 | 13 | 458 |
250000 | 21 | 1142 |
500000 | 31 | 2260 |
1000000 | 54 | 4513 |
全然違う。
Math.pow(2, n)の怪
検証している最中に妙なことに気付いたのでついでにメモ。
上記プログラムのtest1とtest3を使う。
test3のポイントは、「Math.pow(2, m)」とループの最中に変わることがない値を指数に指定していること。
実行結果のうち、test1とtest3を抜き出してみる。単位はミリ秒。
n | test1 | test3 |
---|---|---|
100 | 0 | 0 |
250 | 0 | 1 |
500 | 0 | 3 |
1000 | 1 | 8 |
2500 | 3 | 18 |
5000 | 5 | 31 |
10000 | 9 | 37 |
25000 | 10 | 37 |
50000 | 12 | 40 |
100000 | 13 | 45 |
250000 | 21 | 59 |
500000 | 31 | 83 |
1000000 | 54 | 130 |
それでもシフト演算の方が速いのだが、test2との違いは何だろう‥ソースを追おうにもnativeメソッドだし‥謎だ‥
rangeによるrangeの違い
Pythonのコードをちゃんと書いたことはないのだけど、ある理由でPythonのコードを読んでいて、ふと気になって調べたメモ。
結論から言うと、rangeで範囲を指定したときに列挙される値が、プログラム言語や、同じ言語でも書き方によって違うのだな、と。
実行環境
Python
以下のコードを実行する。
- range.py
for i in range(1, 2): print(i)
実行結果。
1
つまり、for (i = 1; i < 2; ++i)
(疑似コード)って書くのと一緒。
PHP
以下のコードを実行する。
- range.php
<?php foreach (range(1, 2) as $i) { printf("%d\n", $i); }
実行結果。
1 2
つまり、for (i = 1; i <= 2; ++i)
(疑似コード)って書くのと一緒。
Java
ついでなので調べてみたら、Java 8からjava.util.stream.IntStreamというのが追加されていた。
IntStreamには、range(int, int)
とrangeClosed(int, int)
の2種類がある。
forEachメソッドのところは適当にググって真似てみた(謎)。
- Range.java
import java.util.stream.IntStream; public class Range { public static void main(String[] args) { IntStream.range(1, 2).forEach(i -> { System.out.println(i); }); } }
実行結果。
1
こっちはPython式。
- RangeClosed.java
import java.util.stream.IntStream; public class RangeClosed { public static void main(String[] args) { IntStream.rangeClosed(1, 2).forEach(i -> { System.out.println(i); }); } }
実行結果。
1 2
こっちはPHP式。
Ruby
さらについで。Rubyのプログラムもちゃんと書いたことはないのでググって調べてみた。
- range.rb
for i in 1...2 do print(i, "\n") end
または、
for i in Range.new(1, 2, true) do print(i, "\n") end
実行結果。
1
Python式。
- rangeClosed.rb
for i in 1..2 do print(i, "\n") end
または、
for i in Range.new(1, 2) do print(i, "\n") end
実行結果。
1 2
PHP式。
まとめると・・・
言語 | プログラムの記述例 | 実際の範囲 |
---|---|---|
Python | for i in range(1, 100): |
1 <= i < 100 |
PHP | foreach (range(1, 100) as $i) |
1 <= $i <= 100 |
Java | IntStream.range(1, 100).forEach(i (略) |
1 <= i < 100 |
IntStream.rangeClosed(1, 100).forEach(i (略) |
1 <= i <= 100 | |
Ruby | for i in 1..100 do |
1 <= i <= 100 |
for i in Range.new(1, 100) do |
1 <= i <= 100 | |
for i in 1...100 do |
1 <= i < 100 | |
for i in Range.new(1, 100, true) do |
1 <= i < 100 |
QueueやStackとして使うなら‥LinkedList vs ArrayDeque
LinkedListをQueueとして使ったあるプログラム(何)を書いていて、QueueやStackとして使うならLinkedListよりArrayDequeがお勧めとアドバイスをもらったので、軽く検証してみた。
事前準備
ソースコードは以下のような感じ。
- LinkedListAsQueue.java
import java.util.LinkedList; import java.util.Queue; public class LinkedListAsQueueTest { public static void main(String[] args) { Queue<String> q = new LinkedList<>(); long S = System.currentTimeMillis(); while (true) { q.offer("a"); long E = System.currentTimeMillis(); System.err.println("q=" + q.size() + ", " + (E - S) + "ms"); } } }
- ArrayDequeAsQueue.java
import java.util.ArrayDeque; import java.util.Queue; public class ArrayDequeAsQueueTest { public static void main(String[] args) { Queue<String> q = new ArrayDeque<>(); long S = System.currentTimeMillis(); while (true) { q.offer("a"); long E = System.currentTimeMillis(); System.err.println("q=" + q.size() + ", " + (E - S) + "ms"); } } }
- LinkedListAsStack.java
import java.util.LinkedList; import java.util.Deque; public class LinkedListAsStackTest { public static void main(String[] args) { Deque<String> q = new LinkedList<>(); long S = System.currentTimeMillis(); while (true) { q.push("a"); long E = System.currentTimeMillis(); System.err.println("q=" + q.size() + ", " + (E - S) + "ms"); } } }
- ArrayDequeAsStack.java
import java.util.ArrayDeque; import java.util.Deque; public class ArrayDequeAsStackTest { public static void main(String[] args) { Deque<String> q = new ArrayDeque<>(); long S = System.currentTimeMillis(); while (true) { q.push("a"); long E = System.currentTimeMillis(); System.err.println("q=" + q.size() + ", " + (E - S) + "ms"); } } }
XxxAsStack.javaの方はDequeなので末尾からpush/popする使い方もありだが、わざと先頭に要素を追加する実装にしてある。
実行
長時間待つのが面倒なので、最大ヒープサイズを小さくして実行する。
$ java -Xmx8m <ClassName>
これでOutOfMemoryErrorが出るまで待って最終行を記録、ということを5回ずつ繰り返す。
結果
- LinkedListAsQueue
q=328077, 6279ms q=328112, 6165ms q=328077, 6187ms q=328077, 6463ms q=328122, 6526ms
- ArrayDequeAsQueue
q=524287, 6834ms q=524287, 6766ms q=524287, 6697ms q=524287, 6732ms q=524287, 6460ms
- LinkedListAsStack
q=328111, 6309ms q=328076, 6290ms q=328085, 6378ms q=328076, 6134ms q=328076, 6326ms
- ArrayDequeAsStack
q=524287, 6769ms q=524287, 6576ms q=524287, 6545ms q=524287, 7081ms q=524287, 6917ms
それぞれの平均をまとめると以下のような感じ。
要素の数 | 時間(ms) | 要素の数/時間(ms) | |
---|---|---|---|
LinkedListAsQueue | 328093.0 | 6324.0 | 51.881 |
ArrayDequeAsQueue | 524287.0 | 6697.8 | 78.277 |
LinkedListAsStack | 328084.8 | 6287.4 | 52.181 |
ArrayDequeAsStack | 524287.0 | 6777.6 | 77.356 |
確かに、メモリ使用量の観点からも速度の観点からもLinkedListよりArrayDequeの方がQueueやStackに向いていそう。速度的にはLinkedListの方が早いのかなと勝手に思っていたが違うのか‥ちょっとArrayDequeクラスのソースを覗いてみよう‥
文字列連結のパフォーマンス比較
そういえば計ったことなかったなぁ‥と思い立って、いくつかのパターンで計ってみたメモ。
実行環境は、自宅のXenServer上に載せた以下のVM環境。
ケース1:文字列連結演算子とダブルクォーテーションと
プログラムの全体は以下のような感じ。
n,test1,test2 <?php for ($n = 10000; $n <= 50000; $n += 5000) { set_time_limit(120); // test1 $s1 = microtime(true); $str = ''; for ($i = 0; $i < $n; ++$i) { $str = $str . 'a' . $i . 'b'; } $e1 = microtime(true); // test2 $s2 = microtime(true); $str = ''; for ($i = 0; $i < $n; ++$i) { $str = "{$str}a{$i}b"; } $e2 = microtime(true); printf("%d,%.3f,%.3f\n", $n, $e1 - $s1 , $e2 - $s2); }
文字列を
$str = $str . 'a' . $i . 'b';
で連結するか
$str = "{$str}a{$i}b";
で連結するかの違い。
実行すると、以下のような出力が得られる。
n,test1,test2 10000,0.075,0.029 15000,0.228,0.065 20000,0.445,0.120 25000,0.873,0.199 30000,1.422,0.300 35000,2.168,0.451 40000,3.023,0.581 45000,2.802,0.739 50000,3.962,0.938
グラフにしてみるとこんな感じ。
ケース2:ダブルクォーテーションと複合演算子と
プログラムの全体は以下のような感じ。
n,test2,test3,test4 <?php for ($n = 10000; $n <= 100000; $n += 5000) { set_time_limit(120); // test2 $s2 = microtime(true); $str = ''; for ($i = 0; $i < $n; ++$i) { $str = "{$str}a{$i}b"; } $e2 = microtime(true); // test3 $s3 = microtime(true); $str = ''; for ($i = 0; $i < $n; ++$i) { $str .= 'a' . $i . 'b'; } $e3 = microtime(true); // test4 $s4 = microtime(true); $str = ''; for ($i = 0; $i < $n; ++$i) { $str .= "a{$i}b"; } $e4 = microtime(true); printf("%d,%.3f,%.3f,%.3f\n", $n, $e2 - $s2, $e3 - $s3, $e4 - $s4); }
文字列を
$str = "{$str}a{$i}b";
$str .= 'a' . $i . 'b';
$str .= "a{$i}b";
のように連結するという3パターン。1つ目は、ケース1で早かった方と同じ。
実行すると、以下のような出力が得られる。
n,test2,test3,test4 10000,0.016,0.001,0.001 15000,0.034,0.002,0.002 20000,0.069,0.003,0.003 25000,0.102,0.004,0.003 30000,0.149,0.004,0.004 35000,0.210,0.005,0.005 40000,0.281,0.006,0.005 45000,0.363,0.006,0.006 50000,0.455,0.007,0.007 55000,0.556,0.008,0.008 60000,0.668,0.009,0.008 65000,0.782,0.009,0.009 70000,0.916,0.010,0.009 75000,1.054,0.011,0.011 80000,1.212,0.011,0.011 85000,1.364,0.012,0.012 90000,1.534,0.013,0.012 95000,1.723,0.013,0.013 100000,1.915,0.014,0.014
‥圧倒的差である。一応グラフにしてみる。
test3のグラフは隠れて見えないが、test4の下に隠れている。
結局‥
- ケース2の結果を見る限り、長い文字列
$str
が右辺に出てくるときの評価にコストがかかるのだろうか。 - ケース1の結果から、同じ長い文字列が右辺に現れざるを得ない場合は、文字列連結演算子の処理の方がコストがかかるということか。
正直、ここまで差が出るとは思わなかったので、文字列連結処理を書くときには、これからは気を使うようにしようと思う。
PHPの識別子にアスキーコード0x7Fが使えることの検証
マニュアルにもしっかり書いてあるんだけどPHPの識別子にはアスキーコード0x7Fが使えるが、これは制御文字DELなので変じゃないかという指摘。試したところ確かに使える / “PHP :: Bug #71897 :: ASCII …” https://t.co/9CkFL2aziD
— 徳丸 浩 (@ockeghem) 2016年3月27日
ということなので、実際に試してみた。
どうせなら1つのソースコードで完結させてやろうということで得られたコードが以下。
<?php $name = "\x7F"; $expr = '$' . $name . '="b";var_dump($name, $' . $name . ');'; var_dump($expr); eval($expr);
これを実行すると、以下のように出力される。(0x7Fの部分は□で表している)
string(27) "$□="b";var_dump($name, $□);" string(1) "□" string(1) "b"
ちなみに、識別子の先頭文字としては使えない数字を入れてみたコード:
<?php $name = "1\x7F"; $expr = '$' . $name . '="b";var_dump($name, $' . $name . ');'; var_dump($expr); eval($expr);
を実行すると、ちゃんとエラーになる。
string(29) "$1□="b";var_dump($name, $1□);" PHP Parse error: syntax error, unexpected T_LNUMBER, expecting T_VARIABLE or '$' in /home/kajino/foo.php(6) : eval()'d code on line 1