¿Cómo estructurar un sistema de plantillas usando PHP simple?

15

Estaba leyendo esta pregunta en stackoverflow:

/programming/104516/calling-php-functions-within-heredoc-strings

y la respuesta aceptada dice hacer plantillas PHP simples como esta:

template.php:

<html>
    <head>
        <title><?=$title?> </title>
    </head>
    <cuerpo>
        <? = getContent ()?>
    </body>
</html>

index.php:

<? php

$ title = 'Título de demostración';

función getContent () {
    volver '<p> ¡Hola mundo! </p>';
}

include ('template.php');

?>

Para mí, lo anterior no está bien estructurado en el sentido de que template.php depende de variables que están definidas en otros scripts. Y está usando un include () para ejecutar código cuando lo hace include('template.php')(en lugar de usar include () para incluir una clase o una función que no se ejecuta inmediatamente).

Siento que un mejor enfoque es envolver su plantilla dentro de una función:

template.php:

<? php

plantilla de función ($ título, $ contenido) {
    ob_start (); ?>

<html>
    <head>
        <title><?=$title?> </title>
    </head>
    <cuerpo>
        <? = $ contenido?>
    </body>
</html>

    <? php return ob_get_clean ();
}

?>

index.php:

<? php

require_once ('template.php');

plantilla de impresión ('Título de demostración', '<p> ¡Hola mundo! </p>');

?>

¿Es mejor el segundo enfoque? ¿Hay una forma aún mejor de hacerlo?

Ryan
fuente

Respuestas:

12

No haría el segundo enfoque ya que los parámetros no se nombran.

Un bien escrito ensayo sobre la descripción de cómo un sistema de plantillas debería funcionar proviene de Parr, a menudo citado por la gente que escribe sistemas de plantilla y / o marcos-mvc web.

Personalmente, lo que generalmente prefiero es implementar una clase ArrayObject con una matriz de propiedades, y la plantilla se referiría $this->propertyName, que de hecho sería el objeto de la plantilla $this->property['name']. Esto también se puede lograr simplemente usando __sety __get, entonces:

class Template {
  private $_scriptPath=TEMPLATE_PATH;//comes from config.php
  public $properties;
  public function setScriptPath($scriptPath){
    $this->_scriptPath=$scriptPath;
  }
  public function __construct(){
      $this->properties = array();
  }
  public function render($filename){

   ob_start();
   if(file_exists($this->_scriptPath.$filename)){
     include($this->_scriptPath.$filename);
    } else throw new TemplateNotFoundException();
    return ob_get_clean();
  }
  public function __set($k, $v){
      $this->properties[$k] = $v;
  }
  public function __get($k){
      return $this->properties[$k];
  }
}

y una plantilla se vería así:

<html>
      <head>
         <title><?=$this->title?></title>
      </head>
      <body>Hey <?=$this->name?></body>
</html>

e invocarlo se vería así:

$view = new Template();
$view->title="Hello World app";
$view->properties['name'] = "Jude";
echo $view->render('hello.inc');

Por lo que recuerdo, así es como se ven los viejos motores de plantillas Symfony 1.xy Zend_View, y para mí está bien.

Aadaam
fuente
TinyButStrong también hace algo similar
Izkata
¿Cómo manejas el looping sobre algunos puntajes? ¿Utiliza una plantilla "sub" separada?
Ryan
Me pregunto si hay alguna forma de deshacerse del uso de $ this en la plantilla, por ejemplo, solo $ title o $ name.
Ryan
1
@Ryan Puede usar extract($this->properties);para extraer las propiedades en variables, justo antes de la declaración de inclusión.
Joyce Babu
1

Una plantilla siempre dependerá de variables externas y, dado que PHP es un lenguaje dinámico, no hay forma de exigir en el momento de la compilación que estarán allí. Entonces, el primer enfoque probablemente esté bien; Sin embargo, todavía hay algunos problemas:

  • El ejemplo no se ocupa de codificar HTML en la salida, lo que significa que tiene vulnerabilidades potenciales de XSS.
  • Si no se establece una de esas variables, el código generará una advertencia; no verá si la advertencia tiene sentido o no (tal vez la variable es opcional).

Un enfoque muy directo es escribir una función, preferiblemente con un nombre muy corto, que se encargue de ambos; llamarlo _()parece ser una convención común. Una versión simple podría verse así:

function _($raw) {
    if (isset($raw)) {
        return htmlspecialchars($raw);
    }
}

Y luego en su plantilla, haría:

<title><?= _($title) ?></title>

Más cosas que hacer con la _()función:

  • Dele un segundo argumento para permitir anular la htmlspecialcharsllamada, en los casos en que desee pasar HTML en lugar de cadenas sin formato.
  • Agregue la verificación de tipos para que las matrices y los objetos no rindan Arrayy Object, sino más bien algo significativo (o cadena vacía).
  • Agregar soporte de internacionalización (otro argumento más).
  • Cuando esté en modo de depuración, imprima el nombre de la variable con un formato especial si no está definido. De esta manera, puede ver de un vistazo si hay algún problema con la plantilla.
tdammers
fuente
1

El primero template.phpes más fácil de cargar tal como está en dreamweaver / bluegriffon / lo que sea y editado por un diseñador web no conocedor del lado del servidor, el segundo ... bueno, aún podría, pero es mucho más probable que se rompa durante el proceso de edición .

Iría con el primero para mantenerlo como HTML con beneficios , como prefieren los diseñadores de terceros.

ZJR
fuente
¿En qué sentido? ¿Quieres decir que no se puede modificar con arrastrar y soltar? textualmente, el segundo es el mismo que el primero pero con un par de líneas de PHP envueltas. Asumiendo que siempre tendrá esa estructura, no les será más difícil editar a mano.
Ryan
1
Una buena plantilla tiene sentido para los desarrolladores, una gran plantilla tiene sentido para los diseñadores.
Citricguy
1

Debido a que Codeigniter es mi marco favorito, le propongo una solución Codeigniter-ish:

class Template {
    protected $path, $data;

    public function __construct($path, $data = array()) {
        $this->path = $path;
        $this->data = $data;
    }

    public function render() {
        if(file_exists($this->path)){
            //Extracts vars to current view scope
            extract($this->data);

            //Starts output buffering
            ob_start();

            //Includes contents
            include $this->path;
            $buffer = ob_get_contents();
            @ob_end_clean();

            //Returns output buffer
            return $buffer;
        } else {
            //Throws exception
        }
    }       
}

Su plantilla se vería así:

<html>
    <head>
        <title><?=$title?></title>
    </head>
    <body>
        <?=$content?>
    </body>
</html>

Y lo renderizaría instanciando la clase, pasando datos en una matriz como un parámetro de constructor y llamando al método 'render':

$data = array('title' => 'My title', 'content' => 'My content');
$tmpl = new Template('template.php', $data);
echo $tmpl->render();

De esta manera, no está ejecutando directamente el código incluido, y también mantiene su plantilla legible, por lo que los diseñadores estarán felices.

lortabac
fuente
-3

Php no es un lenguaje de programación. Por lo tanto, no es posible escribir con letra muy fuerte si su idioma no tiene un sistema de tipos real. Pero puedes ayudar a tu IDE como PHP-Storm a hacer el truco que quieras ...

Su definición de vista:

interface mySimpleView { 
  public function getTitle(); 
} 

Su archivo de plantilla:

<?php
/**
* @var $this mySimpleView  <== Your IDE will try to use this type.
*/
?>

<h1><?= $this->getTitle() ?></h1>
<p><?= $this->getContent() ?></p> <!-- <= IDE will warn you !  -->
Roger Keulen
fuente