Closed manuhabitela closed 10 years ago
Wouldn't it be great if we could have nested templates with their own variables, not shared with the parent template?
@Leimi Thanks for your interest in Plates! Can expand on why this would be great? Because I actually designed it the opposite way intentionally. To me it was more helpful to have all variables available to all rendered views (the template, layouts, and nested).
Thanks!
Well, it would prevent to have conflicts between the template and nested templates (the "elements").
Here is a concrete example: I want to render a page that shows a list of people + shows a detailed view of one guy.
//main template
//a 'user' object (for the detailed view) and
//a 'people' array (for the list of users) is set
echo "My name is ".$this->user->name; //Jonathan;
//show the list of people with an element:
foreach ($this->people as $key => $user) {
$this->insert('elements/user', array('user' => $user));
}
//show again the name of the user: oops
//$this->user is now the last item of $this->people because of the insert
echo "My name is ".$this->user->name; //Emmanuel;
Leimi :)
This would probably break backwards compatibility, because there might be other nested templates that rely on changes on other templates? I don't think working like this is a good idea but nonetheless it would brake these applications. I'm not sure if this satisfies a version bump to 2.x.x.
Well, we could add an array of options to the insert method with a "standalone" key that defaults to false or something. With a possibility to set default behavior of the insert method at plates initialization, would be perfect. Le 15 févr. 2014 16:09, "Rene Koller" notifications@github.com a écrit :
This would probably break backwards compatibility, because there might be other nested templates that rely on changes on other templates? I don't think working like this is a good ideas but nonetheless this would brake these applications. I'm not sure if this satisfies a version bump to 2.x.x.
Reply to this email directly or view it on GitHubhttps://github.com/thephpleague/plates/issues/5#issuecomment-35158222 .
@Leimi @SxDx Gents, thanks for your input. I'm not opposed to having an option for "private" templates. I just want to think through how best to implement this from an API standpoint. Emmanuel, your "standalone" key approach would certainly work, but since no other Plates configuration is set this way, I want to see if there is a better approach first.
I agree that this is a feature that should be added. As @SxDx mentioned, some people might be expecting this behavior, which seems like a poor design choice to rely on the insert() method in this way, but since it already behaves in the fashion, it might be best to add an additional method to include via a closure of sorts.
I actually just whipped up something similar for a project I'm currently working on. It provides an include() method via a template extension. The include() methods pulls in the desired file within a closure, and extract()'s the passed array. I created it in a way that $this is still available, and provides a somewhat hacky solution to allow $this in PHP 5.3. We probably wouldn't need this hack if include() belonged to League\Plates\Template.
Note: I removed any application namespaces from the following code and I haven't tested without them. I also haven't done any benchmarking.
IncludeExtension.php
use League\Plates\Extension\ExtensionInterface;
class IncludeExtension implements ExtensionInterface
{
public $engine;
public $template;
public function getFunctions()
{
return array(
'returnInclude' => 'returnIncludeClosure',
'include' => 'includeClosure',
);
}
public function returnIncludeClosure($file, array $vars = array())
{
// If we're using PHP > 5.3, we'll create a closure and bind it to a specific context, so we
// can use `$this` variable, like the rest of Plates.
if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
$closure = function($__engine, $__file, $__vars) {
extract($__vars);
ob_start();
include($__engine->resolvePath($__file));
return ob_get_clean();
};
$closure = $closure->bindTo($this->template);
}
// Otherwise, if we're using PHP 5.3, we'll mimic a closure using the TemplateIncludeClosure
// class, which acts as a proxy to a template instance. This allows the use of `$this` in
// the templates still. A bit hacky, but it works.
//
// Note: Haven't done any performance testing to see how slow (or not) this is.
else
{
$closure = new TemplateIncludeClosure($this->template);
}
return $closure($this->engine, $file, $vars);
}
public function includeClosure($file, array $vars = array())
{
echo $this->returnIncludeClosure($file, $vars);
}
}
TemplateIncludeClosure.php
class TemplateIncludeClosure
{
protected $to;
public function __construct($to)
{
$this->to = $to;
}
public function __invoke($__engine, $__file, array $__vars = array())
{
extract($__vars);
ob_start();
include($__engine->resolvePath($__file));
return ob_get_clean();
}
public function __call($method, $args)
{
// Switch + count is (supposedly) faster than call_user_func_array().
// TODO (someday) -- Make use of PHP 5.6's argument unpacking
switch (count($args)) {
case 0: return $this->to->{$method}(); break;
case 1: return $this->to->{$method}($args[0]); break;
case 2: return $this->to->{$method}($args[0], $args[1]); break;
case 3: return $this->to->{$method}($args[0], $args[1], $args[2]); break;
case 4: return $this->to->{$method}($args[0], $args[1], $args[2], $args[3]); break;
case 5: return $this->to->{$method}($args[0], $args[1], $args[2], $args[3], $args[4]); break;
default: return call_user_func_array(array($this->to, $method), $args); break;
}
}
public function __get($prop)
{
return $this->to->{$prop};
}
public function __set($prop, $val)
{
$this->to->{$prop} = $val;
}
}
Simple template example:
people.php
<?php
$this->users = [
[
'name' => 'Peter',
'age' => 33,
],
[
'name' => 'Joe',
'age' => 28,
],
];
?>
...
<?php foreach ($this->users as $user): ?>
<?php $this->include('partial::person', ['person' => $user]) ?>
<?php endforeach; ?>
<?php var_dump($this->person) // Never set ?>
person.php
<p>
Name: <?php echo $person['name'] ?><br />
Age: <?php echo $person['age'] ?>
</p>
@Leimi @SxDx @kwoodfriend Thanks for your continued interest in this issue, and I'm not ignoring it, rather thinking how best to handle it.
I'm getting to the point that I agree that the nested templates (using <?php $this->insert() ?>
) shouldn't share variables with the main template. I do feel that the layout template should share variables, which is especially important when using inheritance.
By creating a new template object for nested templates, they would then become self contained objects, with their own variables. They could actually have their own layouts as well, if ever that made sense. Right now, calling $this->layout()
within a nested template actually changes the layout of the main template, not the nested template, since it's all technically one template object.
Note, however, at this point any content generated in the nested templates using the inheritance functions ($this->start()
and $this->end()
) would not be available in the main template, nor it's layout. I don't necessarily see that as an issue—in fact this current behaviour seems a little odd. It makes more sense that the main template fulfills all inherited layout requirements, rather than some (potentially deeply) nested template. It could still use a nested template to fulfill the inherited layout though, for example:
<?php $this->start('sidebar') ?>
<?php $this->insert('blog-sidebar') ?>
<?php $this->end() ?>
How do these rules sound? I do think this could potentially break backwards compatibility, so a version bump to 2.x.x would be required—good call @SxDx.
@kwoodfriend FYI, I'm actually avoiding the use of extract()
to put variables in local scope. It can certainly be done, as you know, but for consistency I'm avoiding it. You can read more about the use of the $this
operator here.
Okay, I've made some these changes, see c2e95880d941d6e5313a05eff8ed6700380b9009.
Nested templates are now self contained objects, meaning they have their own variables and layouts. This will definitely require a major version bump, as some people may be using nesting for page headers and footers, with shared variables. For example:
<?php $this->title = "My page title used by the header template" ?>
<?php $this->insert('header') ?>
<p>Your content.</p>
<?php $this->insert('footer') ?>
Moving forward, this will have to be done like this:
<?php $this->insert('header', array('title' => 'My page title used by the header template')) ?>
<p>Your content.</p>
<?php $this->insert('footer') ?>
As noted, this means that nested templates can now also have their own layouts. I've taken this a step further even, and as per issue #10, I've added the ability to "stack" layouts. Consider this example:
<!-- template.tpl -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><?=$this->title?></title>
</head>
<body>
<?=$this->content()?>
</body>
</html>
<!-- blog.tpl -->
<?php $this->layout('template') ?>
<h1>The Blog</h1>
<section>
<article>
<?=$this->content()?>
</article>
<aside>
<?=$this->insert('blog-sidebar')?>
</aside>
</section>
<!-- blog-article.tpl -->
<?php $this->title = $this->article->title ?>
<?php $this->layout('blog') ?>
<h2><?=$this->article->title?></h2>
<?=$this->article->content?>
Finally, with these changes I've added a new content()
method, which essentially replaces the existing child()
method (although the child()
method still exists as an alias). Seems to me that content()
is a more appropriate name.
For now this update is only available via "dev-master", until I update the documentation and do the major version release.
Great! Thanks a lot :)
Hello, I'm all new on this project and want to test it. I use to work with twig and I see plates like php template with twig functionalities. In this way I was especting the possibility to explicitly overwrite data when I insert a child template but keep the other data accessible. Since insert method need an array I can't give it the current context ( this ) so I don't see any simple way to access all my data in a child template. Am I missing something ?
If I'm right I can make a little PR in this way.
@lalop What your suggesting sounds like how Plates previously worked. Most people preferred that inserted templates have their own scope, so it was changed to work that way.
However, to be sure I understand your request, can you please post some example code to show me exactly what you're trying to do?
Yes sure, here is a little crappy hack I did to do that :
public function renderPartial($name, Array $data = null)
{
$inherit_data = [];
foreach($this->template as $k=>$v){
$inherit_data[$k] = $v;
}
$data = $data? array_merge($inherit_data, $data) : $inherit_data;
echo $this->engine->makeTemplate()->render($name, $data);
}
Like that I can use all the parent's data in the child, I can overwrite the data I want and what happens in the child doesn't interact with the parent. Is that more clear ?
A more clean should easily be possible if the data isn't setted at the root of the template but in an data property ( for exemple ), is there a reason why is not the case ?
Hey,
Wouldn't it be great if we could have nested templates with their own variables, not shared with the parent template?
I quickly hacked something that works for my case but I'm not sure it's okay so I didn't go much further:
What do you think? :)
Cheers, Leimi