PHP结束标签会删除换行符

10

我正在做一个类似于SLIM或Jade的HTML预处理器实验

这是看起来正确的PHP代码:

nav
  ul id: "test"
    li
      @<?= $Var; ?>
    li
      @About
    li
      @Contact

这是预期的预处理HTML(是的,$Var == "Test"):

nav
  ul id: "test"
    li
      @Test
    li
      @About
    li
      @Contact

然而,在浏览器中,我得到这个错误的文本作为预处理器html

nav
  ul id: "test"
    li
      @Test    li
      @About
    li
      @Contact

最后,有两种方法可以将其更正。

  1. 手动添加断行:

    nav
      ul id: "test"
        li
          @<?= $Var . "\n"; ?>
      li
        @About
      li
        @Contact
    
  2. 在 PHP 结束标记 (??) 后面加上一个空格。

为什么第一个例子的 <?= $Var; ?> 会忽略结束 PHP 标记后面的换行符? 我没能找到有用的信息,因为我搜索时得到的结果都是关于为什么应该忽略每个搜索中的结束标记而不是我想要找的东西。


1
我刚刚注意到一个问题,当我尝试在HTML的<pre></pre>标签中使用<?= $var ?>输出变量时,在许多行的末尾需要添加额外的换行符才能保持文本输出的正确格式。虽然我可以理解为什么会添加这个功能,以防止在文件以?>结尾时输出额外的换行符(而且它可能是一个不应该输出任何内容的包含文件),但我真的不喜欢这种行为。 - Haprog
1个回答

10

更新:
查看zend语言扫描器源代码,似乎我的"直觉"是正确的: T_CLOSE_TAG标记可能包含换行符。更重要的是,似乎一个包含结束标记的脚本中最后一条语句的分号是可选的...

<ST_IN_SCRIPTING>("?>"|"</script"{WHITESPACE}*">"){NEWLINE}? {
    ZVAL_STRINGL(zendlval, yytext, yyleng, 0); /* no copying - intentional */
    BEGIN(INITIAL);
    return T_CLOSE_TAG;  /* implicit ';' at php-end tag */
}

只需要在这里的zend_language_scanner.c和zend_language_scanner.l文件中查找T_CLOSE_TAG即可。

foo:
    <?= $bar; ?>

    foobar

输出:

foo:
    bar
    foobar

看起来我的怀疑可能是有根据的。

不过,综合考虑,除非你想要去修改Zend引擎源码,否则手动添加换行符并不是太麻烦的事情。实际上,这是确保生成正确的换行符的好方法:
假设你在一个健康的*NIX系统上编写了一些代码,其中换行符基本上由\n转义序列表示,手动添加该字符可能无法产生期望的输出,例如,在 Windows 系统上(使用 \r\n),Apple 系统使用\r...
PHP有一个常量可以确保根据你的代码运行平台生成正确的换行符:PHP_EOL。为什么不使用它呢:

<?= $bar, PHP_EOL; ?>

如果你在想:没错,那里看到的是$bar逗号PHP_EOL。为什么呢?把echo<?=想象成C ++的COUT,它只是将你想要输出的任何东西推送到输出流中,无论是一个连接的字符串还是仅仅是一系列用逗号分隔的变量:它都不关心

现在,我的回答以下部分有点跑题,但这是一些如此基础,自明,但许多人却不知道的事情,我忍不住想解释一两句有关字符串连接的事情。
PHP和我所知道的大多数语言都不关心要将多少变量/值推送到输出流中。这就是它的作用。PHP和再次强调:大多数语言,确实关心字符串的连接:字符串是一种常量值。当你情绪高涨时,你不能只是让一个字符串变长。一系列字符必须存储在内存中,必须分配内存来容纳更长的字符串。连接实际上会进行以下操作(最好的情况):

  • 计算字符串1和字符串2的长度
  • 分配所需的额外内存来将字符串2连接到字符串1上
  • 将字符串2复制到新分配的内存中

而在许多情况下,实际发生的是:

  • 计算两个字符串的长度
  • 分配所需的内存,以连接两个字符串
  • 将两个字符串复制到新分配的内存块中
  • 将新指针分配给需要分配的任何变量
  • 释放不再被引用的任何内存

这里有一个第一种情况的示例:

$str1 = 'I am string constant 1';
$str2 = ' And I\'ll be concatenated';
$str1 .= $str2;

可以翻译为以下C代码:

char *str1, *str2;
//allocate mem for both strings, assign them their vals
str1 = realloc(str1,(strlen(str1) + strlen(str2)+1));//re-allocate mem for str1
strncat(str1, str2, strlen(str2);//concatenate str2 onto str1

然而,仅仅通过这样做:

$str3 = $str1 . $str2;

你实际上正在做的是:

char *str3 = malloc((strlen(str1) + strlen(str2) + 1)*sizeof(char));
strcpy(str3, str1);//copy first string to newly allocated memory
strcat(str3, str2);//concatenate second string...

就好像那个还不够,再想想这段代码意味着什么:

$str1 = $str2 . $str1;

是的,果然如此:

char *str3 = malloc((strlen(str1) + strlen(str2) + 1)*sizeof(char));
strcpy(str3, str2);//copy seconds string to start of new string
strcat(str3, str1);//add first string at the end
free(str1);//free memory associated with first string, because we're reassigning it
str1 = str3;//set str1 to point to the new block of memory

现在我还没有谈到真正的拼接噩梦(别担心,我也不会谈到)。像这样的东西:$foo = 'I ' . ' am '. 'The'. ' ' .$result.' of some'.1.' with a dot'.' fetish';。看看它,里面有变量,可能是任何东西(数组,对象,巨大的字符串... 还有一个整数... 用逗号代替句点,并将其推送到echo结构中,比开始考虑编写正确拼接所有这些值所需的代码要容易得多...
抱歉稍微偏离了一下,但我认为,由于这是如此基础,我觉得每个人都应该知道这一点...


我希望我能多次点赞。我已经使用normalize()函数转换了所有换行符,所以这可能不是问题。关于换行符被消耗的问题,我可能需要更改标题。然而,这会改变每个人的假设,因为单个空格或单个换行符显然不会发送任何标头。最后,虽然我只知道C语言基础课程中学到的内容,但我完全理解了你关于连接的解释。然而,我认为在PHP的echo中,“聪明”的不需要分配任何额外的变量。感谢您的精彩回答。 - Francisco Presencia
1
@FranciscoPresencia:很高兴能帮忙……我不是有意冒犯,但假设“echo”足够聪明并不是一个选项:“echo”只能回显已经计算为单个值的表达式:“echo 1+1;”不会回显“1+1”,而是回显“2”。同样地,对于连接字符串,连接必须具有更高的基数,因为您可能正在回显实现了“__toString”方法的数组或对象,在回显任何内容之前必须调用该方法(发出通知、警告等所有这些都优先于“echo”)…… - Elias Van Ootegem
好的,从现在开始,每当我需要回显连接时,我会像你指出的那样使用echo $str1, $str2;。我甚至找到了一些指标。 - Francisco Presencia
1
@FranciscoPresencia:好吧,我不会强迫任何人使用逗号。这只是其中一件事情:编写代码需要一定的注意细节。对我来说,这个细节表明程序员在较低层次上了解他的代码正在做什么,这可能表明一个具有更深入理解不仅仅是他所编写的语言,而且还有计算机工作方式的编码器。 - Elias Van Ootegem
1
我认为这是设计如此:许多编辑器在保存文件时会在文件末尾添加一个换行符,所以如果你的脚本仅限于PHP,并且PHP不能匹配该新行,则发送回客户端的响应将始终包含该新行。但只有在PHP完成其工作,将输出发送到http守护程序(大多数情况下是Apache)之后,它才会到达客户端,然后守护程序会注意到PHP没有处理所有字符。服务器可能会尝试使用其他(f)cgi进程来解释那个新行,然后再发送出去。这就是我猜测这样做的原因。 - Elias Van Ootegem
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接