Latte: jak korzystać z systemu typów?

5 lat temu przez David Grudl  

System typów. Kluczowa kwestia dla rozwoju solidnych aplikacji, w której PHP zyskało dużą przewagę nad dynamicznymi językami takimi jak Python, Ruby czy JavaScript. Framework Nette od początku prowadzi programistów do programowania typowanego i ścisłego. Latte 2.7 wprowadziło wsparcie dla typów również w szablonach.

Dzięki temu, że wiemy, jaki typ danych lub obiektu znajduje się w każdej zmiennej, może:

  • IDE poprawnie podpowiadać
  • analiza statyczna wykrywać błędy

Dwa punkty, które zasadniczo zwiększają jakość i komfort programowania.

Jak deklarować typy po stronie PHP?

Dane będziemy przekazywać do szablonu jako obiekt klasy, która definiuje wszystkie zmienne i ich typy. Wykorzystując nowe funkcje PHP 7.4, mogłaby wyglądać na przykład tak:

class MailTemplate
{
	public string $lang = 'cs';
	public Address $address;
	public string $subject;
	public ?float $price = null;
}

Użycie:

$template = new MailTemplate;
$template->price = $this->getPrice();
...
$latte->render('mail.latte', $template);

Uzupełnienie: dzięki nowym funkcjom PHP 8 przykład można zapisać jeszcze ciekawiej w ten sposób:

class MailTemplate
{
	public function __construct(
		public string $lang = 'cs',
		public Address $address,
		public string $subject,
		public ?float $price = null,
	) {}
}

$latte->render('mail.latte', new MailTemplate(
	lang: $this->lang,
	subject: $title,
	price: $this->getPrice(),
	address: $userAddress,
));

Jak deklarować typy w szablonie?

Podaną klasę możemy teraz połączyć z szablonem. Wystarczy umieścić na początku:

{templateType MailTemplate}

I tym zdefiniowaliśmy, że w szablonie będzie czwórka zmiennych $lang, $address, $subject i $price wraz z odpowiednimi typami.

Alternatywą jest zdefiniowanie typów poszczególnych zmiennych bezpośrednio w szablonie, tj. bez tworzenia klasy. Służy do tego tag {varType}:

{varType string $lang}
{varType Address $address}

Oczywiście można łączyć obie metody. Utworzyć klasę, ponieważ zapewni to podpowiadanie po stronie presentera, połączyć ją z szablonem za pomocą {templateType}, a dla pozostałych lokalnych zmiennych w blokach itp. używać {varType}. Które możemy rozumieć jako odpowiednik /** @var type $variable */, czyli komentarza, którym czasami w kodzie PHP instruujemy IDE lub analizator statyczny.

Nowo można typ podać również w tagach {var}, {default} lub {define}:

{var Model\Page $page = $items['page']}
{define form, string $name}
	...
{/define}

Uwaga: z powodów historycznych możliwe było w tagach {var} i {default} pisanie zmiennych bez początkowego dolara (i ze strzałką zamiast znaku równości). Ponieważ tworzy to niejednoznaczność, czy chodzi o typ czy zmienną, ta składnia jest zabroniona i Latte będzie ostrzegać przy jej użyciu. Prosty sposób na przeszukanie swoich szablonów, czy nie ma tam tego starego zapisu, to szukanie wyrażeń regularnych /\{(var|default) [^$]/ i /\{(var|default) [^}]*=>/.

Jak zaoszczędzić sobie pracy?

Jak najłatwiej napisać klasę szablonu lub tagi {varType}? Pozwól sobie je wygenerować. Właśnie do tego służy para tagów {templatePrint} i {varPrint}.

Jeśli umieścisz któryś z tych tagów w szablonie, zamiast zwykłego renderowania wyświetli się propozycja kodu klasy lub lista tagów {varPrint}. Kod wystarczy jednym kliknięciem zaznaczyć i skopiować do projektu.

Częścią nette/application 3.1 (w wersji beta) jest przeciążony tag {templatePrint}, który generuje kod lepiej dostosowany do presenterów.

Tag {varPrint} wypisuje zmienne lokalne, które nie są parametrami szablonu. Jeśli chcesz wypisać wszystkie zmienne, użyj {varPrint all}.

Podpowiadanie w IDE

Najnowsza wersja wtyczki Latte dla PhpStorm potrafi wykorzystać powyższe tagi, a następnie podpowiadać na podstawie typów. Wtyczka jednocześnie zna typy standardowych zmiennych, takich jak $user, $presenter, $basePath itd.

Analiza statyczna

Celem jest, aby wszystkie zadeklarowane typy można było użyć również do analizy statycznej. Aby na przykład za pomocą PHPStan można było kontrolować szablony tak samo łatwo, jak inne pliki PHP.

Pracujemy nad tym i pojawi się to w jednej z przyszłych wersji Latte.