Jak zbudować system szablonów za pomocą zwykłego PHP?

15

Czytałem to pytanie przy przepełnieniu stosu:

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

a zaakceptowana odpowiedź mówi, aby robić proste szablony PHP takie jak to:

template.php:

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

index.php:

<? php

$ title = „Tytuł demonstracyjny”;

funkcja getContent () {
    return '<p> Witaj świecie! </p>';
}

include („template.php”);

?>

Dla mnie powyższe nie jest dobrze skonstruowane w tym sensie, że template.php zależy od zmiennych zdefiniowanych w innych skryptach. I używasz metody include () do wykonywania kodu, kiedy to robisz include('template.php')(w przeciwieństwie do metody include () w celu uwzględnienia klasy lub funkcji, która nie jest natychmiast wykonywana).

Wydaje mi się, że lepszym rozwiązaniem jest zawinięcie szablonu w funkcję:

template.php:

<? php

szablon funkcji ($ tytuł, $ treść) {
    ob_start (); ?>

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

    <? php return ob_get_clean ();
}

?>

index.php:

<? php

requ_once ('template.php');

szablon wydruku („Tytuł demonstracji”, „<p> Hello World! </p>”);

?>

Czy drugie podejście jest lepsze? Czy jest na to lepszy sposób?

Ryan
źródło

Odpowiedzi:

12

Nie zrobiłbym drugiego podejścia, ponieważ parametry nie są nazwane.

Dobrze napisany esej o tym, jak powinien działać system szablonów pochodzi od Parra, często cytowany przez ludzi piszących systemy szablonów i / lub frameworki web-mvc.

Osobiście zazwyczaj wolę zaimplementować klasę ArrayObject z tablicą właściwości, a szablon odwoływałby się do niego $this->propertyName, który w rzeczywistości byłby obiektem szablonu $this->property['name']. Można to również osiągnąć po prostu za pomocą, __seta __getwięc:

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];
  }
}

a szablon wyglądałby następująco:

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

i wywołanie go wyglądałoby następująco:

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

O ile pamiętam, tak wyglądają stare Symfony 1.x i silniki szablonów Zend_View, a dla mnie jest w porządku.

Aadaam
źródło
TinyButStrong robi również coś podobnego
Izkata
jak radzisz sobie z zapętlaniem niektórych wyników? czy używasz osobnego szablonu „podrzędnego”?
Ryan,
Zastanawiam się, czy jest jakiś sposób, aby pozbyć się użycia $ this w szablonie, na przykład tylko $ title lub $ name.
Ryan,
1
@ Ryan Można użyć extract($this->properties);do wyodrębnienia właściwości do zmiennych, tuż przed instrukcją include.
Joyce Babu,
1

Szablon zawsze będzie zależeć od zewnętrznych zmiennych, a ponieważ PHP jest językiem dynamicznym, nie ma sposobu na wymuszenie w czasie kompilacji, że będą tam. Więc pierwsze podejście jest prawdopodobnie w porządku; wciąż jednak istnieje kilka problemów:

  • Przykład nie zajmuje się kodowaniem HTML danych wyjściowych, co oznacza, że ​​masz potencjalne luki w zabezpieczeniach XSS.
  • Jeśli jedna z tych zmiennych nie jest ustawiona, kod wyświetli ostrzeżenie; nie zobaczysz, czy ostrzeżenie ma sens, czy nie (może zmienna jest opcjonalna).

Bardzo prostym podejściem jest napisanie funkcji, najlepiej o bardzo krótkiej nazwie, która zajmie się obydwoma; nazywanie tego _()wydaje się być wspólną konwencją. Prosta wersja może wyglądać następująco:

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

A następnie w swoim szablonie zrobiłbyś:

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

Więcej rzeczy związanych z _()funkcją:

  • Podaj drugi argument, aby umożliwić zastąpienie htmlspecialcharswywołania, w przypadkach, gdy chcesz przekazać do niego HTML zamiast nieprzetworzonych ciągów.
  • Dodaj typ kontroli tak, że tablice i obiekty nie przyniesie Arrayi Object, ale raczej coś sensownego (lub pusty ciąg znaków).
  • Dodaj obsługę internacjonalizacji (kolejny argument).
  • W trybie debugowania wypisz nazwę zmiennej ze specjalnym formatowaniem, jeśli nie jest zdefiniowana. W ten sposób możesz szybko zobaczyć, czy coś jest nie tak z szablonem.
tdammers
źródło
1

Pierwszy template.phpjest łatwiejszy do załadowania w obecnej postaci do Dreamweaver / bluegriffon / czegokolwiek i edytowany przez nieznajomego projektanta stron internetowych po stronie serwera, drugi ... no cóż, nadal może, ale znacznie bardziej prawdopodobne jest uszkodzenie podczas procesu edycji .

Wybrałbym pierwszy, aby wyglądał jak HTML z korzyściami , jak zwykle wolą projektanci zewnętrzni.

ZJR
źródło
w jakim sensie? masz na myśli, że nie można tego zmienić za pomocą przeciągania i upuszczania? tekstowo drugi jest taki sam jak pierwszy, ale z kilkoma liniami PHP owiniętymi wokół siebie. Zakładając, że zawsze będzie miała taką strukturę, nie będzie im trudniej edytować ręcznie.
Ryan,
1
Dobry szablon ma sens dla programistów, świetny szablon ma sens dla projektantów.
Citricguy,
1

Ponieważ Codeigniter jest moim ulubionym frameworkiem, proponuję ci rozwiązanie oparte na Codeigniter:

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
        }
    }       
}

Twój szablon wyglądałby następująco:

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

I wyrenderowałbyś to, tworząc instancję klasy, przekazując dane do tablicy jako parametr konstruktora i wywołując metodę „renderowania”:

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

W ten sposób nie wykonujesz bezpośrednio dołączonego kodu, a Twój szablon jest czytelny, więc projektanci będą zadowoleni.

lortabak
źródło
-3

Php nie jest językiem programowania. Tak więc pisanie z silnym pisaniem na klawiaturze nie jest możliwe, jeśli twój język nie ma prawdziwego systemu pisma. Ale możesz pomóc swojemu IDE, tak jak PHP-Storm, zrobić sztuczkę, którą chcesz ...

Twoja definicja widoku:

interface mySimpleView { 
  public function getTitle(); 
} 

Twój plik szablonu:

<?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
źródło