在HEREDOC字符串中调用PHP函数

105
在PHP中,HEREDOC字符串声明非常有用,可以用于输出一块HTML。只需在变量前加上$,就可以解析变量。但对于更复杂的语法(例如$var [2] [3]),您必须将表达式放在{}括号中。
在PHP 5中,实际上可以在HEREDOC字符串内的{}括号中调用函数,但您需要进行一些工作。函数名称本身必须存储在变量中,并且必须像动态命名函数一样调用它。例如:
$fn = 'testfunction';
function testfunction() { return 'ok'; }
$string = <<< heredoc
plain text and now a function: {$fn()}
heredoc;

你可以看到,这比仅仅是:

稍微有些混乱。
$string = <<< heredoc
plain text and now a function: {testfunction()}
heredoc;

除了第一个代码示例之外,还有其他的方法,比如打破HEREDOC来调用函数,或者反转问题并执行类似以下的操作:

?>
<!-- directly output html and only breaking into php for the function -->
plain text and now a function: <?PHP print testfunction(); ?>

后者的缺点在于输出被直接放入输出流中(除非我使用输出缓冲),这可能不是我想要的。

因此,我的问题的实质是:有没有更优雅的方法来解决这个问题?

根据回复进行编辑:看起来某种模板引擎会让我的生活更加轻松,但这需要我基本上颠倒我的常规PHP风格。这并不是什么坏事,但它解释了我的惯性... 我愿意想办法让生活更容易,所以我现在正在研究模板。


3
这并不是对你问题的直接回答,但鉴于heredoc语句中函数调用支持较差,我通常会在打印heredoc之前生成需要的字符串。然后,在heredoc中,我可以简单地使用Text {$string1} Text {$string2} Text这样的表达式。 - rinogo
17个回答

79

如果您确实想要这样做,但比使用类简单一些,您可以使用:

function fn($data) {
  return $data;
}
$fn = 'fn';

$my_string = <<<EOT
Number of seconds since the Unix Epoch: {$fn(time())}
EOT;

你甚至可以这样做:$my_string = "Unix Epoch以来秒数的一半:{$fn(time() / 2 . ' 是的!真的!')}"; - CJ Dennis
2
更紧凑的定义: $fn = function fn($data) { return $data; }; - devsmt
1
@devsmt 你说得没错。甚至更简短的方式是:$fn = function ($data) { return $data; }; - CJ Dennis
@My1 高尔夫? $fn ='strval'; - Pinke Helga
@Olivier "{${'fn'}(time())}""{$fn(time())}" 的工作方式完全相同。两者都需要定义一个变量。 - CJ Dennis
显示剩余7条评论

56

我个人不会使用HEREDOC来进行模板构建,因为这不是一个很好的"模板构建"系统。所有的HTML都被锁定在一个字符串中,这有几个缺点:

  • 没有所见即所得(WYSIWYG)选项
  • 没有来自IDE的HTML代码完成功能
  • 输出(HTML)被锁定在逻辑文件中
  • 你最终不得不像你现在尝试做的那样使用各种hack来实现更复杂的模板,比如循环。

获取一个基本的模板引擎,或者只使用具有<?php?>分隔符的PHP - 这就是为什么该语言有这些分隔符的原因。

template_file.php

<html>
<head>
  <title><?php echo $page_title; ?></title>
</head>
<body>
  <?php echo getPageContent(); ?>
</body>

index.php

<?php

$page_title = "This is a simple demo";

function getPageContent() {
    return '<p>Hello World!</p>';
}

include('template_file.php');

9
echo有一个简写形式:<?=$valueToEcho;?><%=$valueToEcho;%>,具体取决于您的INI设置。 - Peter Bailey
4
我读到的大部分关于使用这些速记符号的文章都说使用它们是一种不好的实践,我也同意。所以遗憾的是,如果你正在编写要分发的代码,你不能依赖这些INI设置,因此PHP对它们的“支持”对于分发的代码来说是无效的。顺便提一下,我曾经不止一次地修复过其他人的WordPress插件中的错误,因为他们使用了这些速记符号。 - MikeSchinkel
2
不,我并不是说我必须要打7个字符很可惜;你错误地归因于我的问题。我关心的不是打字,而是阅读。这些字符会产生大量的视觉噪音,使得扫描源代码和理解代码变得更加困难。对我来说,至少使用HEREDOC更容易阅读。 (顺便说一句,无论在给定的HTML片段中使用多少次,它都需要7个字符的时间。但我跑题了。) - MikeSchinkel
15
短代码更简洁、清晰易读。在您的看法中,<?=$title?>比<?php echo $title; ?>要好得多。缺点是,为了分发,很多ini都关闭了短标签。但是,猜猜怎么着?从PHP 5.4开始,无论ini设置如何,PHP都启用了短标签!因此,如果您使用5.4+版本(例如,您正在使用traits),请放心使用这些令人惊叹的短标签! - Jimbo
3
顺便提一下,即使关闭了短标签,<?= $blah ?>在5.4中默认启用。 - callmetwan
显示剩余8条评论

46

我会按照以下方式进行:

$string = <<< heredoc
plain text and now a function: %s
heredoc;
$string = sprintf($string, testfunction());

不确定你是否认为这更为优雅...


31

为了完整性,你也可以使用!${''}黑魔法解析器hack

echo <<<EOT
One month ago was { ${!${''} = date('Y-m-d H:i:s', strtotime('-1 month'))} }.
EOT;

在 3v4l.org 上实时查看。

注意:PHP 8.2 已将字符串中的裸 ${} 变量变成了不推荐的语法,而更喜欢显式的 { ${} } 语法。上面的示例使用了这种显式格式来消除弃用提示,尽管这使得这个方法更加复杂!


17
你去过霍格沃茨吗?(改正了原文中的语法错误) - Starx
3
这是因为 false == ''。定义一个长度为 0 的变量('')。将它设置为您想要的值(${''} = date('Y-m-d H:i:s', strtotime('-1 month')))。否定它(!)并将其转换为变量(${false})。'false' 需要转换为字符串,并且 (string)false === ''。如果您尝试打印 falsy 值,它将出错。以下字符串适用于 truthy 和 falsy 值,但更难以阅读:"${(${''}=date('Y-m-d H:i:s', strtotime('-1 month')))!=${''}}" - CJ Dennis
如果您想要打印NAN,请使用"${(${''} = date('Y-m-d H:i:s', strtotime('-1 month')) )==NAN}" - CJ Dennis
1
这应该是正确答案。它工作得很好。 - jacouh
这段代码会在 EOT 后继续运行 HEREDOC,也就是说 PHP 不会解析之后的内容。 - DOMDocumentVideoSource
@DOMDocumentVideoSource 请提供指向3v4l.org或类似网站的链接以证明。 - bishop

17

试试这个(可以作为全局变量,或者在需要时实例化):

<?php
  class Fn {
    public function __call($name, $args) {
      if (function_exists($name)) {
        return call_user_func_array($name, $args);
      }
    }
  }

  $fn = new Fn();
?>

现在任何函数调用都经过 $fn 实例。所以现有的函数 testfunction() 可以在 heredoc 中使用 {$fn->testfunction()} 来调用。

基本上,我们将所有函数包装成一个类实例,并使用 PHP 的 __call 魔法方法 将类方法映射到需要调用的实际函数。


2
这是一个很好的解决方案,适用于您无法将模板引擎添加到现有项目中的情况。谢谢,我现在正在使用它。 - Brandon
当性能至关重要时,不应广泛使用:我已经多次阅读到call_user_func_array的性能较差,最近一次是在php.net的评论中:http://www.php.net/manual/en/function.call-user-func-array.php#97473 - Markus
不错!我喜欢它,为什么我没想到呢?!? :-) - MikeSchinkel

10
我来晚了,但我偶然看到了这个问题。对于任何未来的读者,以下是我可能会做的:
我会使用一个输出缓冲区。基本上,您可以使用ob_start()开始缓冲,然后通过包含带有任何函数、变量等内容的“模板文件”,获取缓冲区的内容并将其写入字符串中,然后关闭缓冲区。在你已经使用了需要的任何变量、运行了任何函数,并且你的IDE中仍然可以使用HTML语法高亮。
这是我的意思: 模板文件:
<?php echo "plain text and now a function: " . testfunction(); ?>

脚本:

<?php
ob_start();
include "template_file.php";
$output_string = ob_get_contents();
ob_end_clean();
echo $output_string;
?>
所以这个脚本将template_file.php包含到其缓冲区中,沿途运行任何函数/方法并分配任何变量。然后,你只需记录缓冲区内容到一个变量中,并根据需要进行处理即可。
这样,如果你不想立即在页面上输出它,就可以不用输出。你可以循环并继续添加到字符串中,然后再输出它。
我认为如果你不想使用模板引擎,那么这是最好的方法。

9

在这里找到了一个很好的解决方案,使用包装函数:http://blog.nazdrave.net/?p=626

function heredoc($param) {
    // just return whatever has been passed to us
    return $param;
}

$heredoc = 'heredoc';

$string = <<<HEREDOC
\$heredoc is now a generic function that can be used in all sorts of ways:
Output the result of a function: {$heredoc(date('r'))}
Output the value of a constant: {$heredoc(__FILE__)}
Static methods work just as well: {$heredoc(MyClass::getSomething())}
2 + 2 equals {$heredoc(2+2)}
HEREDOC;

// The same works not only with HEREDOC strings,
// but with double-quoted strings as well:
$string = "{$heredoc(2+2)}";

3
我在2.5年前就提出了完全相同的解决方案。https://dev59.com/K3VD5IYBdhLWcg3wDXJ3#10713298 - CJ Dennis

7

这段代码将会在用户范围内定义变量,并将其绑定到一个包含相同名称的字符串中。让我来举个例子。

function add ($int) { return $int + 1; }
$f=get_defined_functions();foreach($f[user]as$v){$$v=$v;}

$string = <<< heredoc
plain text and now a function: {$add(1)}
heredoc;

现在将会工作。


@MichaelMcMillian最好不要将任何变量命名为与任何函数相同的名称,对吧? - s3c

5

我认为使用heredoc来生成HTML代码非常好。例如,我发现以下代码几乎完全无法读取。

<html>
<head>
  <title><?php echo $page_title; ?></title>
</head>
<body>
  <?php echo getPageContent(); ?>
</body>

然而,为了实现简洁性,你需要在开始之前评估功能。我认为这并不是一个可怕的限制,因为这样做会将计算与显示分离,这通常是个好主意。我认为以下内容非常易读:
$page_content = getPageContent();

print <<<END
<html>
<head>
  <title>$page_title</title>
</head>
<body>
$page_content
</body>
END;

很不幸,尽管你在问题中提出将函数绑定到变量是一个好建议,但最终它增加了代码的复杂性,这是不值得的,并且使代码难以阅读,而这正是heredoc的主要优势。


2
过去的四年证明了这种方法比其他大多数方法更加智能。在模板中使用组合(将由较小页面组成的大页面)并保持所有控制逻辑分离是任何认真对待模板的人的标准方法:Facebook的ReactJS非常适合此类操作(XHP也是如此),XSLT也是如此(虽然我不太喜欢,但学术上是可行的)。我唯一要注意的风格是:我总是在我的变量周围使用{},主要是为了一致性和可读性,并避免以后发生意外。另外,不要忘记htmlspecialchars()任何来自用户的数据。 - Josh from Qaribou

5

今天在php 7.x上更加优雅了一些

<?php

$test = function(){
    return 'it works!';
};


echo <<<HEREDOC
this is a test: {$test()}
HEREDOC;

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