0

I'm developing a new templating language for PHP, and one of the features is an object-oriented approach to nested child templates. For example, I can declare an object of type HtmlBuilder like this:

$template = "
  <div>
    <h1>{{field1}}</h1>
    <p>{{field2}}</p>
  </div>";

$content = [ 
   "field1" => "value1",
   "field2" => "value2"
];

$hb = new HtmlBuilder($content, $template);
echo $hb->render();

In this case, HtmlBuilder does a simple find-and-replace on the fields. But, I could also nest one HtmlBuilder (or one of its more specialized subtypes) inside of another:

// Child template
$template1 = "
  <p>{{field2}}</p>
  <p>{{field4}}</p>
  ";

$content1 = [ 
   "field4" => "something else"
];

$hb1 = new HtmlBuilder($content1, $template1);

// Parent template
$template2 = "
  <div>
    <h1>{{field1}}</h1>
    {{field3}}
  </div>";

$content2 = [ 
   "field1" => "value1",
   "field2" => "value2",
   "field3" => $hb1
];

$hb2 = new HtmlBuilder($content2, $template2);
echo $hb2->render();

In this case, when $hb2 is rendered, $hb1 will be automatically rendered in the field3 placeholder.

My question is, does it make sense to design this system such that the value of field2 is automatically passed in from the parent template to the child template (while allowing it to be overridden in the child)? Or, should I require that it be explicitly passed in $content1?

1 Answer 1

1

Implicit dependencies are bad, especially if your template engine will not complain about missing variables. I believe that it would be beneficial for each template fragment to know which variables it needs, and to refuse to be rendered or instantiated with wrong arguments.

Here is an example where the lack of scoping will introduce difficulties:

$academic_staff_template = "
  <div>
    <h4>{{ title }} {{ name }}</h4>
    {{ bio }}
  </div>
";
$lecturer = new HtmlBuilder([], $academic_staff_template);

$lecture_template = "
  <html>
    <head>
      <title>{{ title }}</title>
    </head>
    <body>
      <h1>Lecture {{ title }}</h1>
      {{ description }}
      {{ lecturer }}
    </body>
  </html>
";
$vars = [
  "title" => "Archaeology 101",
  "description" => "...",
  "lecturer" => $lecturer,
  "name" => 'Henry Walton "Indiana" Jones, Jr.',
  "title" => "Dr.",
  "bio" => "...",
];
$lecture = new HtmlBuilder($vars, $lexture_template);

Both template use a title variable, but use the same name to mean something completely different. Your current design would produce this rendered output:

<html>
  <head>
    <title>Dr.</title>
  </head>
  <body>
    <h1>Lecture Dr.</h1>
    ...
    <div>
      <h4>Henry Walton "Indiana" Jones, Jr.</h4>
      ...
    </div>
  </body>
</html>

Sometimes, it is extremely useful to include another template without entering a new variable scope. For example, {% include "snippet/footer" %} could be a useful directive to load a snippet. But if we try to create encapsulated, loosely coupled templates, that's running straight into namespace clashes and all kinds of issues.

Here's an idea: When you parse a template, you take note of all variables that are required. Such a parsed template can be rendered in one of two ways:

  • Via the API: $result = $engine->parse($source)->render(["var1" => "value1"]). The render method will throw an exception if not all necessary variables are provided.
  • Inside the template language: If block is bound to a parsed template object, then syntax like {{ block var1: "value" var2: someVariable }} could render the template. No variables except those passed explicitly would be available inside the block. If the given arguments don't match the arguments required by the block, an error would be thrown.

Since some data naturally belongs together, it would be great to offer access to arrays within your template language. Then we could pass a single dictionary with info about the lecturer to the academic staff template. Here is how I'd design that interface:

$lecturer_template = $engine->parse("
  <div>
    <h4>{{ person.title }} {{ person.name }}</h4>
    {{ person.bio }}
  </div>
");
$page_template = $engine->parse("
  <html>
    <head>
      <title>{{ course.title }}</title>
    </head>
    <body>
      <h1>Lecture {{ course.title }}</h1>
      {{ course.description }}
      {{ lecturer person: course.lecturer }}
    </body>
  </html>
");

$course = [
  "title" => "Archaeology 101",
  "description" => "...",
  "lecturer" => [
    "name" => "Henry Walton \"Indiana\" Jones, Jr.",
    "title" => "Dr.",
    "bio" => "...",
  ],
];

$result = $page_template->render(["course" => $course, "lecturer" => $lecturer_template]);
1
  • Awesome, thanks for the thorough answer. I think I will have the "require all variables be defined" as an option - this might be useful for debugging. As for the array access, I've actually implemented that already - just didn't want to confuse the question by bringing it up ;-) As for the {{ block var1: "value" var2: someVariable }} style, I haven't decided if I want to allow that. I know that other engines like Smarty support that, but I want to try and enforce a strict separation of "content" and "layout". Maybe we can talk more on chat, if you're interested.
    – alexw
    Jan 9, 2015 at 18:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.