Llamar a funciones PHP dentro de cadenas HEREDOC

91

En PHP, las declaraciones de cadenas HEREDOC son realmente útiles para generar un bloque de html. Puede hacer que analice en variables simplemente prefijándolas con $, pero para una sintaxis más complicada (como $ var [2] [3]), debe poner su expresión entre llaves {}.

En PHP 5, es posible realizar llamadas a funciones entre llaves {} dentro de una cadena HEREDOC, pero hay que trabajar un poco. El nombre de la función en sí debe almacenarse en una variable, y debe llamarlo como si fuera una función con nombre dinámico. Por ejemplo:

$fn = 'testfunction';
function testfunction() { return 'ok'; }
$string = <<< heredoc
plain text and now a function: {$fn()}
heredoc;

Como puede ver, esto es un poco más complicado que solo:

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

Hay otras formas además del primer ejemplo de código, como salir del HEREDOC para llamar a la función, o revertir el problema y hacer algo como:

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

Este último tiene la desventaja de que la salida se coloca directamente en el flujo de salida (a menos que esté usando el almacenamiento en búfer de salida), que podría no ser lo que quiero.

Entonces, la esencia de mi pregunta es: ¿hay una forma más elegante de abordar esto?

Edite en función de las respuestas: Ciertamente parece que algún tipo de motor de plantilla haría mi vida mucho más fácil, pero básicamente me requeriría invertir mi estilo PHP habitual. No es que eso sea algo malo, pero explica mi inercia ... Sin embargo, estoy dispuesto a encontrar formas de hacer la vida más fácil, así que estoy buscando plantillas ahora.

Doug Kavendek
fuente
3
Esto no es estrictamente una respuesta a su pregunta, pero dado el pobre soporte para llamadas a funciones en declaraciones heredoc, generalmente solo genero las cadenas que necesitaré antes de imprimir el heredoc. Entonces, simplemente puedo usar algo como Text {$string1} Text {$string2} Texten el heredoc.
rinogo

Respuestas:

51

Personalmente, no usaría HEREDOC en absoluto para esto. Simplemente no es un buen sistema de "construcción de plantillas". Todo su HTML está bloqueado en una cadena que tiene varias desventajas

  • Sin opción para WYSIWYG
  • Sin finalización de código para HTML de IDE
  • Salida (HTML) bloqueada a archivos lógicos
  • Termina teniendo que usar trucos como el que está tratando de hacer ahora para lograr plantillas más complejas, como bucles

Obtenga un motor de plantilla básico, o simplemente use PHP con incluye: es por eso que el lenguaje tiene los delimitadores <?phpy ?>.

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');
Peter Bailey
fuente
8
Hay una abreviatura de eco: <?=$valueToEcho;?>o <%=$valueToEcho;%>depende de la configuración de INI.
Peter Bailey
3
Casi todo lo que he leído sobre el uso de esas abreviaturas dice que usarlas es una mala práctica, y estoy de acuerdo. Entonces, desafortunadamente, si está escribiendo código para distribución, no puede depender de esas configuraciones INI, lo que hace que el "soporte" de PHP sea discutible para el código distribuido. FWIW, he tenido que corregir errores en los complementos de WordPress de otras personas más de una vez porque usaban estas abreviaturas.
MikeSchinkel
1
No, no digo que sea una pena que tenga que escribir 7 caracteres; atribuye incorrectamente mis problemas. No es la mecanografía lo que me preocupa, es la lectura . Esos caracteres crean mucho ruido visual que hace que sea mucho más difícil escanear el código fuente y comprender lo que hace el código. Para mí al menos es MUCHO más fácil leer un HEREDOC. (Y por cierto, es el tiempo de 7 caracteres
importar
12
Corto es más agradable, limpio y fácil de leer. En su opinión, <?=$title?>es mucho mejor que <? Php echo $ title; ?>. La desventaja es, sí, para la distribución, muchos ini tienen etiquetas cortas. Pero , ¿adivinen qué? A partir de PHP 5.4 , las etiquetas cortas están habilitadas en PHP independientemente de la configuración de ini. Entonces, si está codificando con un requisito de 5.4+ (digamos que está usando rasgos, por ejemplo), ¡siga adelante y use esas increíbles etiquetas cortas!
Jimbo
2
Por cierto, <? = $ Blah?> Está habilitado en 5.4 de forma predeterminada, incluso si las etiquetas cortas están desactivadas.
callmetwan
72

Si realmente desea hacer esto, pero es un poco más simple que usar una clase, puede usar:

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

$my_string = <<<EOT
Number of seconds since the Unix Epoch: {$fn(time())}
EOT;
CJ Dennis
fuente
¡Genial @CJDennis! Esa es la mejor y más limpia solución para usar funciones de llamada dentro de HEREDOC. Hay un buen recurso en algunas situaciones. En mi sitio, uso HEREDOC para generar formularios con 22 líneas de conjuntos de campos (un bloque HEREDOC dentro de un bucle FOR), con llamada de función para generar la posición del tabindex.
Paulo Buchsbaum
Incluso puedes hacer esto:$my_string = "Half the number of seconds since the Unix Epoch: {$fn(time() / 2 . ' Yes! Really!')}";
CJ Dennis
2
una definición más compacta: $fn = function fn($data) { return $data; };
devsmt
1
@devsmt Tienes razón. Y aún más corto es:$fn = function ($data) { return $data; };
CJ Dennis
oh, godegolf? bien, déjame entrar: este $fn=function($data){return $data;};debería ser el más corto
Mi1
42

Yo haría lo siguiente:

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

No estoy seguro si consideraría que esto es más elegante ...

boxxar
fuente
17

Pruebe esto (ya sea como una variable global o instanciado cuando lo necesite):

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

  $fn = new Fn();
?>

Ahora cualquier llamada a función pasa por la $fninstancia. Entonces la función existente testfunction()se puede llamar en un heredoc con{$fn->testfunction()}

Básicamente, estamos agrupando todas las funciones en una instancia de clase y usando el __call magicmétodo de PHP para asignar el método de clase a la función real que se necesita llamar.

Isofarro
fuente
2
Esta es una buena solución para los momentos en los que no puede simplemente agregar un motor de plantillas a un proyecto existente. Gracias, lo estoy usando ahora.
Brandon
no debe usarse ampliamente cuando el rendimiento es crítico: he leído varias veces que el rendimiento es peor call_user_func_array, la última vez en los comentarios en php.net: php.net/manual/en/function.call-user-func-array.php # 97473
Markus
¡Agradable! Me encanta, ¿por qué no pensé en esto? :-)
MikeSchinkel
10

Para completar, también puede usar el truco del analizador de !${''} magia negra :

echo <<<EOT
One month ago was ${!${''} = date('Y-m-d H:i:s', strtotime('-1 month'))}.
EOT;
obispo
fuente
7
¿Fuiste a Hogwarts?
Starx
Esto funciona porque false == ''. Defina una variable con un nombre de longitud 0 ( ''). Ajústelo al valor que desee ( ${''} = date('Y-m-d H:i:s', strtotime('-1 month'))). Negarlo ( !) y convertirlo en una variable ( ${false}). falsenecesita convertirse en una cadena, y (string)false === ''. Si intenta imprimir un valor falso, se producirá un error. La siguiente cadena funciona tanto en los valores Falsy Truthy y, a expensas de ser aún más ilegible: "${(${''}=date('Y-m-d H:i:s', strtotime('-1 month')))!=${''}}".
CJ Dennis
Y si también desea imprimir NAN, utilice "${(${''} = date('Y-m-d H:i:s', strtotime('-1 month')) )==NAN}".
CJ Dennis
9

Llego un poco tarde, pero me lo encontré al azar. Para los futuros lectores, esto es lo que probablemente haría:

Solo usaría un búfer de salida. Entonces, básicamente, comienzas el almacenamiento en búfer usando ob_start (), luego incluyes tu "archivo de plantilla" con cualquier función, variable, etc. dentro de él, obtienes el contenido del búfer y lo escribes en una cadena, y luego cierras el búfer. Luego, ha utilizado las variables que necesita, puede ejecutar cualquier función y aún tiene el resaltado de sintaxis HTML disponible en su IDE.

Esto es lo que quiero decir:

Archivo de plantilla:

<?php echo "plain text and now a function: " . testfunction(); ?>

Guión:

<?php
ob_start();
include "template_file.php";
$output_string = ob_get_contents();
ob_end_clean();
echo $output_string;
?>

Entonces, el script incluye template_file.php en su búfer, ejecutando cualquier función / método y asignando cualquier variable en el camino. Luego, simplemente registra el contenido del búfer en una variable y haz lo que quieras con él.

De esa manera, si no desea que se repita en la página en ese segundo, no es necesario. Puede repetir y seguir agregando a la cadena antes de enviarla.

Creo que es la mejor manera de hacerlo si no desea utilizar un motor de plantillas.

BraedenP
fuente
6

Este fragmento definirá variables con el nombre de sus funciones definidas dentro del ámbito de usuario y las vinculará a una cadena que contiene el mismo nombre. Déjame demostrarte.

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;

Ahora funcionará.

Michael McMillan
fuente
@MichaelMcMillian es mejor que no tenga ninguna variable con el mismo nombre que cualquier función, entonces, ¿verdad?
s3c
6

Encontré una buena solución con función de envoltura aquí: 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)}";
p.voinov
fuente
2
Sugerí exactamente la misma solución 2,5 años antes de esto. stackoverflow.com/a/10713298/1166898
CJ Dennis
5

Creo que usar heredoc es excelente para generar código HTML. Por ejemplo, encuentro lo siguiente casi completamente ilegible.

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

Sin embargo, para lograr la simplicidad, se ve obligado a evaluar las funciones antes de comenzar. No creo que sea una restricción tan terrible, ya que al hacerlo, terminas separando tu cálculo de la pantalla, lo que suele ser una buena idea.

Creo que lo siguiente es bastante legible:

$page_content = getPageContent();

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

Desafortunadamente, aunque fue una buena sugerencia que hizo en su pregunta vincular la función a una variable, al final, agrega un nivel de complejidad al código, que no vale la pena, y hace que el código sea menos legible, que es la principal ventaja de heredoc.

MLU
fuente
2
Los últimos 4 años demostraron esto mucho más inteligente que la mayoría de los otros enfoques. Usar la composición en sus plantillas (construir una página grande compuesta de páginas más pequeñas) y mantener toda la lógica de control separada es el enfoque estándar para cualquiera que se tome en serio la creación de plantillas: ReactJS de Facebook es excelente para esto (al igual que XHP), al igual que XSLT (que No amo, pero es académicamente sólido). Las únicas notas de estilo que haría: siempre uso {} alrededor de mis vars, principalmente para lograr coherencia en la legibilidad y evitar accidentes más adelante. Además, no olvide htmlspecialchars () cualquier dato que provenga de los usuarios.
Josh de Qaribou
4

Echaría un vistazo a Smarty como un motor de plantillas; no he probado ningún otro yo mismo, pero me ha funcionado bien.

Si quería seguir con su enfoque actual sin plantillas, ¿qué tiene de malo el almacenamiento en búfer de salida? Le dará mucha más flexibilidad que tener que declarar variables que son los nombres de cadena de las funciones que desea llamar.

nickf
fuente
3

te estás olvidando de la función lambda:

$or=function($c,$t,$f){return$c?$t:$f;};
echo <<<TRUEFALSE
    The best color ever is {$or(rand(0,1),'green','black')}
TRUEFALSE;

También puede utilizar la función create_function

Ismael Miguel
fuente
2

Un poco tarde pero aún así. ¡Esto es posible en heredoc!

Eche un vistazo en el manual de php, sección "Sintaxis compleja (rizada)"

tftd
fuente
Ya estoy usando esa sintaxis en el primer ejemplo; tiene la desventaja de tener que poner el nombre de la función en una variable antes de poder llamarla dentro de las llaves en la sección heredoc, que es lo que estaba tratando de evitar.
Doug Kavendek
2

Aquí un buen ejemplo usando la propuesta de @CJDennis:

function double($i)
{ return $i*2; }

function triple($i)
{ return $i*3;}

$tab = 'double';
echo "{$tab(5)} is $tab 5<br>";

$tab = 'triple';
echo "{$tab(5)} is $tab 5<br>";

Por ejemplo, un buen uso de la sintaxis HEREDOC es generar formularios enormes con una relación maestro-detalle en una base de datos. Se puede usar la función HEREDOC dentro de un control FOR, agregando un sufijo después de cada nombre de campo. Es una tarea típica del lado del servidor.

Paulo Buchsbaum
fuente
1
<div><?=<<<heredoc
Use heredoc and functions in ONE statement.
Show lower case ABC="
heredoc
. strtolower('ABC') . <<<heredoc
".  And that is it!
heredoc
?></div>
Conocido
fuente
0
<?php
echo <<<ETO
<h1>Hellow ETO</h1>
ETO;

deberías probarlo . después de finalizar el ETO; comando debe dar una entrada.

Rubel Hossain
fuente
0

Esto es un poco más elegante hoy en php 7.x

<?php

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


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