はじめに
phpでswitch文の「switch($hoge)★ここ★{case0:」に改行いれるとエラーになる。を読んでこれどうなってるんだろうと思ったので調べて見ました。
PHPのマニュアルのPHPタグのところには
つまり、開始タグと終了タグで囲まれている 箇所以外のすべての部分は、PHP パーサに無視されます。
と書いてあるので何を挟んでも良さそうに読めるのですが...
結論
<?php
...?>
の外側の文字列は、内部的にはその文字列を出力するecho文のように扱われます。ただし?>
の直後の改行は除きます。パーサに無視されてなどいなかったのです。
したがって、echo文が許されないところで?>
でタグを終了して次の<?php
との間に1文字でも挟むと構文エラーになります。
先の記事で駄目だった例の
<?php
$i = 0;
?>
<?php
switch($i){
?>
<?php
case 0:
?>
<p>aaa</a>
<?php
break;
}
は
<?php
$i = 0;
echo "\n";
switch($i){
echo "\n";
case 0:
echo "<p>aaa</a>\n";
break;
}
と書くのと一緒なので、switch
の直後の最初のcase
の前にはecho
文を書いてはいけないのと同じ意味でNGということになります。
詳しい話
マニュアルを読んでもわからないので該当部分のソースを見てみました。
字句解析
字句解析はZend/zend_language_scannar.l
で行っています。
phpタグの外側
INITIAL
はphpタグの外側にいる状態で、これより前に<?php
などは処理されているのでそれらに当てはまらない文字列がここで処理されます。最終的にはT_INLINE_HTML
というトークンになります。
<INITIAL>{ANY_CHAR} {
// 長いので略
RETURN_TOKEN(T_INLINE_HTML);
}
?>
の直後の改行
以下のようになっており、直後の文字が改行なら閉じタグの一部として食われます。だから次に来る<?php
との間に改行一つだけ挟むのはセーフ。
<ST_IN_SCRIPTING>"?>"{NEWLINE}? {
BEGIN(INITIAL);
if (yytext[yyleng-1] != '>') {
CG(increment_lineno) = 1;
}
RETURN_TOKEN(T_CLOSE_TAG); /* implicit ';' at php-end tag */
}
構文解析
構文解析はZend/zend_language_parser.y
で行っています。T_INLINE_HTML
が出てくるのはstatement
のところです。ひとつの文として扱われていることがわかります。
statement:
'{' inner_statement_list '}' { $$ = $2; }
| if_stmt { $$ = $1; }
| alt_if_stmt { $$ = $1; }
| T_WHILE '(' expr ')' while_statement
{ $$ = zend_ast_create(ZEND_AST_WHILE, $3, $5); }
| T_DO statement T_WHILE '(' expr ')' ';'
{ $$ = zend_ast_create(ZEND_AST_DO_WHILE, $2, $5); }
| T_FOR '(' for_exprs ';' for_exprs ';' for_exprs ')' for_statement
{ $$ = zend_ast_create(ZEND_AST_FOR, $3, $5, $7, $9); }
| T_SWITCH '(' expr ')' switch_case_list
{ $$ = zend_ast_create(ZEND_AST_SWITCH, $3, $5); }
| T_BREAK optional_expr ';' { $$ = zend_ast_create(ZEND_AST_BREAK, $2); }
| T_CONTINUE optional_expr ';' { $$ = zend_ast_create(ZEND_AST_CONTINUE, $2); }
| T_RETURN optional_expr ';' { $$ = zend_ast_create(ZEND_AST_RETURN, $2); }
| T_GLOBAL global_var_list ';' { $$ = $2; }
| T_STATIC static_var_list ';' { $$ = $2; }
| T_ECHO echo_expr_list ';' { $$ = $2; }
| T_INLINE_HTML { $$ = zend_ast_create(ZEND_AST_ECHO, $1); }
| expr ';' { $$ = $1; }
| T_UNSET '(' unset_variables ')' ';' { $$ = $3; }
| T_FOREACH '(' expr T_AS foreach_variable ')' foreach_statement
{ $$ = zend_ast_create(ZEND_AST_FOREACH, $3, $5, NULL, $7); }
| T_FOREACH '(' expr T_AS foreach_variable T_DOUBLE_ARROW foreach_variable ')'
foreach_statement
{ $$ = zend_ast_create(ZEND_AST_FOREACH, $3, $7, $5, $9); }
| T_DECLARE '(' const_list ')'
{ zend_handle_encoding_declaration($3); }
declare_statement
{ $$ = zend_ast_create(ZEND_AST_DECLARE, $3, $6); }
| ';' /* empty statement */ { $$ = NULL; }
| T_TRY '{' inner_statement_list '}' catch_list finally_statement
{ $$ = zend_ast_create(ZEND_AST_TRY, $3, $5, $6); }
| T_THROW expr ';' { $$ = zend_ast_create(ZEND_AST_THROW, $2); }
| T_GOTO T_STRING ';' { $$ = zend_ast_create(ZEND_AST_GOTO, $2); }
| T_STRING ':' { $$ = zend_ast_create(ZEND_AST_LABEL, $1); }
;