Chamando funções PHP dentro de strings HEREDOC

91

No PHP, as declarações de string HEREDOC são realmente úteis para a saída de um bloco de html. Você pode fazer com que ele analise as variáveis ​​apenas prefixando-as com $, mas para uma sintaxe mais complicada (como $ var [2] [3]), você deve colocar sua expressão entre {} colchetes.

No PHP 5, é possível fazer chamadas de função entre {} colchetes dentro de uma string HEREDOC, mas você precisa trabalhar um pouco. O nome da função em si deve ser armazenado em uma variável e você deve chamá-la como se fosse uma função nomeada dinamicamente. Por exemplo:

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

Como você pode ver, isso é um pouco mais confuso do que apenas:

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

Existem outras maneiras além do primeiro exemplo de código, como sair do HEREDOC para chamar a função ou reverter o problema e fazer algo como:

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

O último tem a desvantagem de que a saída é colocada diretamente no fluxo de saída (a menos que eu esteja usando buffer de saída), o que pode não ser o que desejo.

Então, a essência da minha pergunta é: existe uma maneira mais elegante de abordar isso?

Editar com base nas respostas: certamente parece que algum tipo de mecanismo de modelo tornaria minha vida muito mais fácil, mas exigiria que eu basicamente invertesse meu estilo PHP usual. Não que isso seja uma coisa ruim, mas explica minha inércia. Porém, estou disposto a descobrir maneiras de tornar a vida mais fácil, então estou procurando modelos agora.

Doug Kavendek
fonte
3
Isso não é estritamente uma resposta à sua pergunta, mas devido ao suporte insuficiente para chamadas de função em instruções heredoc, geralmente apenas gero as strings de que preciso antes de imprimir o heredoc. Então, posso simplesmente usar algo como Text {$string1} Text {$string2} Textno heredoc.
rinogo

Respostas:

51

Eu não usaria HEREDOC para isso, pessoalmente. Simplesmente não é um bom sistema de "construção de modelos". Todo o seu HTML está bloqueado em uma string que tem várias desvantagens

  • Sem opção para WYSIWYG
  • Sem autocompletar código para HTML de IDEs
  • Saída (HTML) bloqueada para arquivos lógicos
  • Você acaba tendo que usar hacks como o que está tentando fazer agora para obter modelos mais complexos, como looping

Obtenha um mecanismo de modelo básico ou apenas use PHP com inclusões - é por isso que a linguagem tem os delimitadores <?phpe ?>.

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
fonte
8
Existe uma abreviação para echo: <?=$valueToEcho;?>ou <%=$valueToEcho;%>depende de suas configurações INI.
Peter Bailey
3
Quase tudo que li sobre como usar essas abreviações diz que usá-las é uma prática ruim, e eu concordo. Então, infelizmente, se você está escrevendo código para distribuição, você não pode depender dessas configurações INI, tornando o "suporte" do PHP para eles discutível para código distribuído. FWIW, tive que consertar bugs nos plug-ins WordPress de outras pessoas mais de uma vez porque eles usaram essas abreviações.
MikeSchinkel,
1
Não, não estou dizendo que é uma pena ter que digitar 7 caracteres; você atribui incorretamente meus problemas. Não estou preocupado com a digitação , mas com a leitura . Esses personagens criam muito ruído visual que torna muito mais difícil verificar o código-fonte e entender o que o código está fazendo. Para mim, pelo menos, é MUITO mais fácil ler um HEREDOC. (E,
falando sério
12
Curto é mais agradável, mais limpo e mais fácil de ler. Na sua opinião, <?=$title?>é muito mais agradável do que <? Php echo $ title; ?>. A desvantagem é, sim, para a distribuição, muitos ini têm tags curtas. Mas , adivinhe ?? A partir do PHP 5.4 , as tags curtas são habilitadas no PHP independentemente das configurações ini! Então, se você está programando com um requisito 5.4+ (digamos que você esteja usando características, por exemplo), vá em frente e use essas tags curtas incríveis !!
Jimbo
2
A propósito, <? = $ Blah?> É habilitado no 5.4 por padrão, mesmo se as tags curtas estiverem desligadas.
callmetwan
72

Se você realmente deseja fazer isso, mas um pouco mais simples do que usar uma classe, você pode usar:

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

$my_string = <<<EOT
Number of seconds since the Unix Epoch: {$fn(time())}
EOT;
CJ Dennis
fonte
Ótimo @CJDennis! Essa é a solução melhor e mais limpa para usar chamadas de funções dentro do HEREDOC. Existem bons recursos em algumas situações. No meu site, utilizo HEREDOC para gerar formulários com 22 linhas de conjuntos de campos (um bloco HEREDOC dentro de um loop FOR), com chamada de função para gerar a posição tabindex.
Paulo Buchsbaum
Você pode até fazer isso:$my_string = "Half the number of seconds since the Unix Epoch: {$fn(time() / 2 . ' Yes! Really!')}";
CJ Dennis
2
uma definição mais compacta: $fn = function fn($data) { return $data; };
devsmt
1
@devsmt Você está certo. E ainda mais curto é:$fn = function ($data) { return $data; };
CJ Dennis
oh, godegolf? ok, deixe-me entrar: $fn=function($data){return $data;};rhis deve ser o mais curto
My1
42

Eu faria o seguinte:

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

Não tenho certeza se você consideraria isso mais elegante ...

boxxar
fonte
17

Tente isto (como uma variável global ou instanciada quando você precisar):

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

  $fn = new Fn();
?>

Agora, qualquer chamada de função passa pela $fninstância. Portanto, a função existente testfunction()pode ser chamada em um heredoc com{$fn->testfunction()}

Basicamente, estamos agrupando todas as funções em uma instância de classe e usando o __call magicmétodo do PHP para mapear o método da classe para a função real que precisa ser chamada.

Isofarro
fonte
2
Esta é uma boa solução para os momentos em que você não pode simplesmente adicionar um mecanismo de modelagem a um projeto existente. Obrigado, estou usando agora.
Brandon
não deve ser usado amplamente quando o desempenho é crítico: Li várias vezes que o desempenho é pior call_user_func_array, da última vez nos comentários em php.net: php.net/manual/en/function.call-user-func-array.php # 97473
Markus
Agradável! Eu adorei, por que não pensei nisso?!? :-)
MikeSchinkel
10

Para completar, você também pode usar o hack do analisador de !${''} magia negra :

echo <<<EOT
One month ago was ${!${''} = date('Y-m-d H:i:s', strtotime('-1 month'))}.
EOT;
bispo
fonte
7
Você foi para Hogwarts?
Starx
Isso funciona porque false == ''. Defina uma variável com um nome de comprimento 0 ( ''). Defina-o com o valor desejado ( ${''} = date('Y-m-d H:i:s', strtotime('-1 month'))). Negue-o ( !) e converta-o em uma variável ( ${false}). falseprecisa ser convertido em uma string e (string)false === ''. Se você tentar imprimir um valor falso, ocorrerá um erro. A seqüência de caracteres a seguir funciona em ambos truthy e valores Falsas, à custa de ser ainda mais ilegível: "${(${''}=date('Y-m-d H:i:s', strtotime('-1 month')))!=${''}}".
CJ Dennis
E se você quiser imprimir NANtambém, use "${(${''} = date('Y-m-d H:i:s', strtotime('-1 month')) )==NAN}".
CJ Dennis
9

Estou um pouco atrasado, mas me deparei com isso aleatoriamente. Para qualquer futuro leitor, eis o que eu provavelmente faria:

Gostaria apenas de usar um buffer de saída. Então, basicamente, você inicia o buffer usando ob_start (), então inclui seu "arquivo de modelo" com quaisquer funções, variáveis, etc. dentro dele, pega o conteúdo do buffer e grava em uma string e então fecha o buffer. Então, você usou todas as variáveis ​​de que precisa, pode executar qualquer função e ainda tem o realce de sintaxe HTML disponível em seu IDE.

Aqui está o que quero dizer:

Arquivo de modelo:

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

Roteiro:

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

Portanto, o script inclui o template_file.php em seu buffer, executando quaisquer funções / métodos e atribuindo quaisquer variáveis ​​ao longo do caminho. Em seguida, você simplesmente grava o conteúdo do buffer em uma variável e faz o que quiser com ele.

Dessa forma, se você não quiser ecoá-lo na página naquele segundo, não será necessário. Você pode fazer um loop e continuar adicionando à string antes de enviá-la.

Acho que essa é a melhor maneira de fazer se você não quiser usar um mecanismo de templates.

BraedenP
fonte
6

Este trecho definirá variáveis ​​com o nome de suas funções definidas dentro do escopo do usuário e as vinculará a uma string que contém o mesmo nome. Deixe-me demonstrar.

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;

Agora funcionará.

Michael McMillan
fonte
@MichaelMcMillian melhor não ter nenhuma variável com o mesmo nome de qualquer função então, certo?
s3c
6

encontrou uma boa solução com a função de agrupamento aqui: 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
fonte
2
Sugeri exatamente a mesma solução 2,5 anos antes. stackoverflow.com/a/10713298/1166898
CJ Dennis
5

Acho que usar o heredoc é ótimo para gerar código HTML. Por exemplo, acho o seguinte quase completamente ilegível.

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

No entanto, para alcançar a simplicidade, você é forçado a avaliar as funções antes de começar. Não acredito que seja uma restrição tão terrível, já que, ao fazer isso, você acaba separando seu cálculo da exibição, o que geralmente é uma boa ideia.

Acho que o seguinte é bastante legível:

$page_content = getPageContent();

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

Infelizmente, embora tenha sido uma boa sugestão que você fez em sua pergunta vincular a função a uma variável, no final, isso adiciona um nível de complexidade ao código, que não vale a pena, e torna o código menos legível, o que é a principal vantagem do heredoc.

MLU
fonte
2
Os últimos 4 anos provaram ser muito mais inteligentes do que a maioria das outras abordagens. Usar composição em seus modelos (construir uma página grande composta de páginas menores) e manter toda a lógica de controle separada é a abordagem padrão para quem leva a sério os modelos: o ReactJS do Facebook é excelente para isso (assim como o XHP), assim como o XSLT (que Eu não amo, mas é academicamente bom). As únicas notas estilísticas que eu faria: eu sempre uso {} em torno de meus vars, principalmente para consistência na legibilidade e para evitar acidentes posteriores. Além disso, não se esqueça de htmlspecialchars () quaisquer dados provenientes dos usuários.
Josh de Qaribou
4

Eu daria uma olhada no Smarty como um mecanismo de template - eu não tentei nenhum outro, mas me fez bem.

Se você quiser manter sua abordagem atual sem modelos, o que há de tão ruim no buffer de saída? Isso lhe dará muito mais flexibilidade do que ter que declarar variáveis ​​que são os nomes das strings das funções que você deseja chamar.

nickf
fonte
2

Um pouco tarde, mas ainda assim. Isso é possível no heredoc!

Dê uma olhada no manual do php, seção "Sintaxe complexa (curva)"

tftd
fonte
Já estou usando essa sintaxe no primeiro exemplo; tem a desvantagem de colocar o nome da função em uma variável antes de poder chamá-la dentro das chaves na seção heredoc, que é o que eu estava tentando evitar.
Doug Kavendek
2

Aqui está um bom exemplo usando a proposta @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 exemplo, um bom uso para a sintaxe HEREDOC é gerar formulários enormes com relacionamento mestre-detalhe em um banco de dados. Pode-se usar o recurso HEREDOC dentro de um controle FOR, adicionando um sufixo após cada nome de campo. É uma tarefa típica do lado do servidor.

Paulo Buchsbaum
fonte
2

você está se esquecendo da função lambda:

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

Você também pode usar a função create_function

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

você deveria tentar . após terminar o ETO; comando você deve dar um enter.

Rubel Hossain
fonte
0

Isso é um pouco mais elegante hoje no php 7.x

<?php

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


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