Custom template class for presenter and control (experimental feature)

David Grudl
Nette Core | 8218
+
+18
-

By default, $template in controls and presenters is object of type Nette\Bridges\ApplicationLatte\Template. Any variables can be inserted into it because it behaves like a plain PHP object (like stdClass is used).

However, it is very easy to provide a code completion in PHPStorm, it describes this two-year-old article on Nette blog.

/**
 * @property ArticleTemplate $template
 */
class ArticlePresenter extends Nette\Application\UI\Presenter
{
	function renderDefault()
	{
		$this->template->lang = 'cs';
		...
	}
}

/**
 * @property string $lang
 * @property int $page
 * @property string[] $menu
 * @property Model\Page $article
 */
class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template
{
}

At the moment, @mesour is finishing a new version of the PHPStorm Latte plugin, where the code completion will also work. It will be a big bomb! The testing version is here, you have to join Slack.

You will only have to add the template class name to the template:

{templateType App\Presenters\ArticleTemplate}

{$lang}
....

Real Templates

Second, instead of inheriting from a dynamic class Nette\Bridges\ApplicationLatte\Template, we can use a strict class Nette\Bridges\ApplicationLatte\StrictTemplate that uses Nette\StrictObject. So we will really have to define all properties:

class ArticleTemplate extends Nette\Bridges\ApplicationLatte\StrictTemplate
{
	public string $lang;
	public int $page;
	/** @var string[] */
	public array $page;
	public Model\Page $article;
}

If we forgot to define a property, or if we mistyped the name, an exception will pop up.

Update: I have found a solution to simplify the whole system, so that there is no need to think about which template ancestor to inherit. StrictTemplate will not exist.

Real Templates II.

In the master of nette/application is implemented a new way how to work with custom templates.

First, you can simply change the class of $template. The easiest way is to follow the convention and create a class with the same name as the presenter/control class, only with Template suffix instead of Presenter or Control. As in the case of ArticleTemplate and ArticlePresenter. Then Nette uses it automatically.

It is up to you to decide whether the class should be strict. This will be done by adding use Nette\SmartObject.

So template class should look like this:

class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template
{
	// use Nette\SmartObject;  it is up to you

	public string $lang;

	public int $page;

	/** @var string[] */
	public array $page;

	public Model\Page $article;
}
dkorpar
Member | 135
+
+2
-

Best thing ever. I've been searching for this to happen and implement for a long long time: https://github.com/…te/issues/31#…

This is something that boost framework quality to new dimension.

David Šolc
Member | 5
+
0
-

I think, that it is pretty common to put different variables into templates (in one Presenter for different templates/render methods/latte files). Is automatic use of one custom Template class for the whole presenter ok?

The easiest way is to follow the convention and create a class with the same name as the presenter/control class, only with Template suffix instead of Presenter or Control. As in the case of ArticleTemplate and ArticlePresenter. Then Nette uses it automatically.

To be more strict I would like to create Template class for each of these instead of one for the whole presenter. When I saw Nette\Bridges\ApplicationLatte\StrictTemplate I was immediately thinking about creating and using these for each render method/latte file, so I can create these and give them to frontend developer. He is not going to need to look into presenters – which variables are put into templates, but he will click through from latte file to custom Template class. When we take into account new possibilities of Latte plugin for PHPStorm. I can force a latte file to use a Template class via “{templateType xxx}” and make suggestions.

class ArticleDefaultTemplate extends Nette\Bridges\ApplicationLatte\StrictTemplate
{
	/** @var Model\Article[] */
	public array $articles;
}

class ArticleDetailTemplate extends Nette\Bridges\ApplicationLatte\StrictTemplate
{
	public Model\Article $article;
}


class ArticlePresenter extends Nette\Application\UI\Presenter
{
	function renderDefault(): void
	{
		/** @var ArticleDefaultTemplate $template */
		$template = $this->createTemplate(ArticleDefaultTemplate::class);
		$template->articles = $articles;
		$this->sendTemplate($template);
	}

	function renderDetail(): void
	{
		/** @var ArticleDetailTemplate $template */
		$template = $this->createTemplate(ArticleDetailTemplate::class);
		$template->article = $article;
		$this->sendTemplate($template);
	}
}
// Article.default.latte
{templateType ArticleDefaultTemplate}

{foreach $articles as $article}
  {$article->getName()}
{/foreac}

// Article.detail.latte
{templateType ArticleDetailTemplate}

{$article->getName()}

Last edited by David Šolc (2020-01-06 17:15)

David Grudl
Nette Core | 8218
+
0
-

You described it absolutely accurately. I just add that in this case there will be no need for /** @var ArticleDetailTemplate $template */.

David Grudl
Nette Core | 8218
+
+5
-

In the last dev versions of Latte and nette/application is a new macro {templatePrint ClassName}, which generates the blueprint of template class. Output looks like this:

class Template extends Nette\Bridges\ApplicationLatte\Template
{
	public \App\Module\Wiki\Presenters\ViewPresenter $presenter;
	public string $mediaPath;
	public array $langs;
	public string $lang;
	public \App\Module\Wiki\Model\Page $page;
	public array $sideBar;
}


/**
 * @property \App\Module\Wiki\Presenters\ViewPresenter $presenter
 * @property string $mediaPath
 * @property array $langs
 * @property string $lang
 * @property \App\Module\Wiki\Model\Page $page
 * @property array $sideBar
 */
class Template extends Nette\Bridges\ApplicationLatte\Template
{
}
Jigs
Member | 14
+
0
-

@DavidGrudl I am curious about templatePrint macro, but I have no idea where to place it. If I place it like a regular macro in latte file, I get error “Cannot modify header information – headers already sent by”. Thanks for answer.

David Grudl
Nette Core | 8218
+
0
-

@Jigs You can put the macro anywhere. It is fixed in v2.7.0-RC2

David Grudl
Nette Core | 8218
+
0
-

See update

dakur
Member | 493
+
+1
-

@DavidGrudl

It is up to you to decide whether the class should be strict. This will be done by adding use Nette\SmartObject.

Am I wrong or is side-effect of adding SmartObject that it enables magic methods __get()/__set() as well? Isn't there some object in Nette which ensures only strictness? Currently I am not able to write strictly and without magic, which is probably the most clean way (academically). Thank you.

Last edited by dakur (2020-03-09 13:11)

Marek Bartoš
Nette Blogger | 1260
+
+1
-

I would also like to see e.g. StrictObject, which would only implement ObjectHelpers::strictSet() etc, without magic properties and events, just to fix php behavior. Maybe even without support for unsetting properties

This package should have very similar behavior https://github.com/…trictObjects

Last edited by Mabar (2020-03-09 13:19)

David Grudl
Nette Core | 8218
+
+2
-

@dakur: Strictness is just achieved using __get()/__set() methods. There is no other way to do this in PHP yet.

@Mabar: magic properties and events must be explicitly allowed. Unlike Nette\Object, SmartObject does not allow you to use magic properties unless you enable it directly. It is therefore unnecessary to have two traits. It would be confusing for users.