webmastering

10 trików w Smarty

Być może sami wykom­bi­no­wa­li­ście więk­szość o czym dzisiaj prze­czy­ta­cie, a może jednak nie…

1. Odwo­ła­nia do metod statycz­nych klasy będą­cej w prze­strzeni nazw.
Nie możemy sobie od tak wywo­ły­wać metod klasy. Trzeba ją najpierw zare­je­stro­wać poprzez regi­sterC­lass():

$smarty->registerClass('Logger', '\My\Beautiful\Logger');

a następ­nie w szablonie:

{Logger::logReport()}

2. Prze­twa­rza­nie szablo­nów bezpo­śred­nio ze stringa.
Czasami prze­cho­wu­jemy kawałki szablo­nów w zmien­nych i chcemy zwró­cić wynik ich prze­twa­rza­nia do zmien­nej. Nic prostszego:

$result = $smarty->fetch('string:My name is {$name}');

3. Ładne grupo­wa­nie dużych liczb po 3 znaki.

{1000000|number_format:0:'':' '}

Da nam wynik 1 000 000 zł

4. Skró­cony zapis tworze­nia zmien­nej w szablo­nie.
Wynik wywo­ła­nia metody przy­pi­szemy do zmien­nej $my_var. Konieczne jest zacho­wa­nie kolej­no­ści: assign idzie drugie, nazwa zmien­nej w „podwój­nych pazurkach”:

{$object->method() assign="my_var"}

5. Tworze­nie tablic asocja­cyj­nych w szablo­nie.
Nie wiedzieć czemu zapis Smar­towi nie pasuje:

{assign var="my_arr" value=array('a' => 'aa', 'b' => 'bb')}

należy użyć sposobu znanego z tworze­nia tablic w PHP 5.4:

{assign var="my_arr" value=['a' => 'aa', 'b' => 'bb']}

6. Bieżąca data.
To oczy­wi­ste, ale może komuś umknęło:

{$smarty.now|date_format:'%d.%m.%Y'}

Wynik 11.03.2013

7. Zaokrą­gla­nie liczb.
Uwaga: Smarty nie jest od wyko­ny­wa­nia obli­czeń. Szablony powinny wyświe­tlać wyniki, a nie zajmo­wać się logiką. Czuj­cie się ostrzeżeni ;-)

{math equation='round(my_val, 1)' my_val=7.95 format='%.1f'}

Wynik 8.0

8. Śledze­nie aktu­al­nej itera­cji
Chcemy nume­ro­wać wier­sze od 1:

{foreach $array as $key => $val}
  {$val@iteration}<br>   
{/foreach}

Wynik:
1
2
3

9. Specjalne akcje dla szcze­gól­nych wier­szy.
Zamiast gimna­sty­ko­wać się ze zlicza­niem wier­szy mamy do dyspo­zy­cji gotowe zmienne:

{foreach $array as $key => $val}
  {if $val@first eq true}pierwszy{/if}
  {$val}
  {if $val@last eq true}ostatni{/if}   
{/foreach}

Co trzeci wiersz:

{foreach $array as $key => $val}
  {if $val@iteration is div by 3}row{/if}   
{/foreach}

10. Użycie deli­mi­te­rów w szablo­nie.
Jeśli koniecz­nie chcemy wyświe­tlić znaki { i } mamy 3 możliwości:

{literal} { } {/literal}
{ldelim} {rdelim}
$smarty->left_delimiter = '[!';
$smarty->right_delimiter = '!]';

Ostatni sposób jest trochę hard­co­rowy, używać jeśli rzeczy­wi­ście wszę­dzie pełno znaków { i }.

Dart

Pierwsze kroki z Dart

Mam ostat­nio wraże­nie, że wszy­scy dookoła posta­no­wili „ulep­szać” Java­Script. Co chwilę widzę jakieś nakładki, mena­dżery pakie­tów, nawet całe frame­worki do tworze­nia apli­ka­cji. Google też wzięło udział w tej zaba­wie i to całkiem z grubej rury — stwo­rzyło od podstaw cały język — Dart, który ma tę właści­wość, że działa na maszy­nie wirtu­al­nej, ale równie dobrze za pomocą narzę­dzia dart2js kompi­luje się do nieza­leż­nego kodu JavaScript.

Część środo­wi­ska z góry neguje, że Google chce zmusić progra­mi­stów do przej­ścia na Chrome. Osobi­ście nie podzie­lam tego zdania. Testo­wa­łem kod na Fire­fok­sie i działa jak należy. Na IE nie spraw­dza­łem z racji tego, że nie uznaję za właściwe testo­wać programu do pobie­ra­nia przeglądarek ;-)

Muszę przy­znać, że po godzi­nie zabawy z nowym języ­kiem jestem pozy­tyw­nie nakrę­cony. To co podoba mi się najbar­dziej to warun­kowe statyczne typo­wa­nie zmien­nych. Dart pracuje w dwóch trybach: chec­ked modeproduc­tion mode. Przy chec­ked mode spraw­dza wszyst­kie aser­cje, typy argu­men­tów, zmien­nych i zwra­cane typy funk­cji. W produc­tion mode wyrzuca wszel­kie „zwalniacze”.

Dobra, nie będę piał z zachwytu tylko pokażę Wam efekt godzin­nego zapo­zna­wa­nia się z API Darta. Poni­żej omówię cieka­wostkę, którą tam zasto­so­wa­łem: isola­tes (izolaty?). To taka namiastka wielo­wąt­ko­wo­ści w jedno­wąt­ko­wym środo­wi­sku prze­glą­darki. Te obra­ca­jące się cyferki to nie żaden animo­wany gif. Wpisu­jąc kilka­na­ście cyfe­rek wyraź­nie widać zwolnienie.

Kod wyko­ny­wany „w izolatce” ma formę funk­cji nie zwra­ca­ją­cej warto­ści, za to mają­cej dostęp do zmien­nej port umoż­li­wia­ją­cej komu­ni­ka­cję ze świa­tem. Para­metr msg to komu­ni­kat „w dół”, a replyTo „w górę”.

void numCounter() {
  port.receive((msg, replyTo) {
    Element h1 = query("h1#counter${msg['id']}");
    int counter = 0;
    int currentNumber = 0;
    new Timer.repeating(25, (Timer timer) {
      if (currentNumber > 9) {
        currentNumber = 0;
      }
      h1.text = currentNumber.toString();
      currentNumber++;
      counter++;
      if (counter > msg['counter']) {
        timer.cancel();
        h1.text = msg['number'];
        replyTo.send(msg['number']);
      }
    });
  });
}

Wszę­dzie mógł­bym użyć var zmienna, ale nie chcia­łem sobie psuć zabawy. Funk­cja query to taki zaka­mu­flo­wany jQuery, który zwraca nam obiekty dzie­dzi­czące po Element. Selek­tory tworzymy w podobny sposób.

Kod obsłu­gu­jący izolaty wygląda tak jakoś „Javopodobnie”.

void main() { 
  Element submitInput = query('input#submit');
  Element errorBox = query('span#errorbox');
  Element countersDiv = query('div#counters');
  submitInput.on.click.add((e) {
    errorBox.text = '';
    countersDiv.text = '';
    HTMLInputElement inputNumbers = query('input#numbers');
    String inputtedNumbers = inputNumbers.value;
    List<String> numbers = inputtedNumbers.splitChars();
    bool hasErrors = false;
    numbers.forEach((element) {
      try {
        int.parse(element);
      } on FormatException {
         hasErrors = true;
         print('element $element is not an integer');
      }
    });
 
    if (hasErrors) {
      errorBox.text = 'Wpisz same cyfry!';
    }
    else {
      for (num i = 1; i <= numbers.length; i++) {
        HeadingElement h1 = new HeadingElement.h1();
        h1.id = 'counter$i';
        h1.text = '0';
        countersDiv.append(h1);
      }
 
      List<SendPort> spawned = new List<SendPort>();
 
      num timeout = 20;
      num firstId = 1;
 
      numbers.forEach((item) {
        SendPort sender = spawnFunction(numCounter);
        sender.call({
          'number' : item,
          'counter' : timeout,
          'id' : firstId
        }).then((String res) => print('result was: [$res]'));
        spawned.add(sender);
        timeout += 10;
        firstId += 1;
      });
    }
  });
}

Izolaty można obsłu­gi­wać na kilka sposo­bów. Ja wyko­rzy­sta­łem obiekt klasy Future, który powstaje poprzez sender.call(), a następ­nie przej­muję odpo­wiedź izolaty za pomocą then().

Kod można sobie ścią­gnąć poprzez Gist na GitHu­bie.

Na koniec ważna uwaga: nie pisz w Darcie nic poważ­nego, bo za dwa miesiące pewnie coś nie będzie dzia­łać. Ostat­nio robią duże prze­ta­so­wa­nia w biblio­te­kach poprzez masowe tworze­nie klas BaseCośTam.

webmastering

Rozszerzanie możliwości Smarty za pomocą pluginów

Wpis może trochę stary tema­tyką, jednak podsu­mo­wu­jący co nieco z punktu widze­nia mojej codzien­nej pięcio­mie­sięcz­nej praktyki.

Czy potrze­bu­jesz rozsze­rzać Smarty? Jeśli w twoich templat­kach coraz więcej {assign}, {math}, {if 5|in_array:$object->getFeatureIds()}, {$object->getPhotosLinks() assign="photos"}{$photos[0]}, {assign var="values" value=$values|cat:'random shit here'} itd. to wiedz, że coś się dzieje. Warstwa logiki niebez­piecz­nie wkrada się do warstwy prezen­ta­cji. Problemu jesz­cze nie ma dopóki nie potrze­bu­jesz prze­rzu­cać sporych części szablo­nów w różne miej­sca i nikt nie musi konty­nu­ować Twojej roboty. W pozo­sta­łych przy­pad­kach natę­że­nie frustra­cji konty­nu­ato­rów rośnie (jak to k… działa?, po ch… tu jest ta zmienna?) i tak soczy­ście dalej…

Plugins to the rescue!

W zasa­dzie w moich szablo­nach używam trzech rodza­jów plugi­nów: mody­fi­ka­to­rów, funk­cji i funk­cji bloko­wych. Jest tego sporo więcej, jednak nie będę pisał o pozostałych.

Żeby korzy­stać z rozsze­rzeń należy najpierw wska­zać Smarty gdzie ma szukać plugi­nów. Robi się to super prosto. Wystar­czy w kodzie tworzą­cym instan­cję Smarty dodać

$smarty = new Smarty();
$smarty->addPluginsDir(__DIR__ . '/smarty_plugins');
 
// rest of initialization stuff

Druga rzecz to konwen­cja nazew­ni­cza. Mam na myśli nazew­nic­two funk­cji i plików. Jeśli nie prze­strze­gamy konwen­cji, Smarty wywali błąd.

Mody­fi­ka­tor

Prze­zna­cze­nie: prosta mody­fi­ka­cja tekstu.
Nazwa pliku: modifier.nazwa_modyfikatora.php.
Sygna­tura funk­cji: smarty_modifier_nazwa_modyfikatora($value, [$param1, $paramN]).
Przykład:

/**
 * Modyfikator odwracajacy podany tekst,
 * opcjonalnie wrzucajacy jakis ciag pomiedzy znaki tekstu.
 * 
 * @param string $value
 * @param string $separationChar
 * @return string
 */
function smarty_modifier_reverse_me($value, $separationChar = '') {
  $arrayString = preg_split('//u', $value, -1, PREG_SPLIT_NO_EMPTY);
  $reversedString = '';
  for ($i = count($arrayString) - 1; $i >= 0; $i--) {
    $reversedString .= $arrayString[$i] . $separationChar;
  }
 
  return $reversedString;
}

Użycie:

{'some text string'|reverse_me}
{* gnirts txet emos *}
 
{assign var='text_string' value='some text string'}
{$text_string|reverse_me:'.'}
{* g.n.i.r.t.s. .t.x.e.t. .e.m.o.s. *}

Funk­cja

Prze­zna­cze­nie: zaawan­so­wane gene­ro­wa­nie treści do wyświe­tle­nia.
Nazwa pliku: function.nazwa_funkcji.php.
Sygna­tura funk­cji: smarty_function_nazwa_funkcji(array $params, \Smarty_Internal_Template $template).
Przykład:

/**
 * Funkcja zwracajaca biezacy czas w postaci ciagu 'dzien tygodnia, dd.mm.yyyy'.
 *
 * @param array $params
 * @param \Smarty_Internal_Template $smarty
 * @return string
 */
function smarty_function_get_localized_time_string(array $params, \Smarty_Internal_Template $template) {
  static $weekdays = ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'];
  static $today = null;
  if ($today == null) {
    $today = new \DateTime();
  }  
 
  return "{$weekdays[$today->format('w')]}, {$today->format('d.m.Y')}";
}

Użycie:

{get_localized_time_string}
{* Czwartek, 27.12.2012 *}

Funk­cja blokowa

Prze­zna­cze­nie: otacza­nie treści wyge­ne­ro­wa­nym ciągiem znaków.
Nazwa pliku: block.nazwa_funkcji.php.
Sygna­tura funk­cji: smarty_block_nazwa_funkcji(array $params, $content, \Smarty_Internal_Template $template, &$repeat).
Przykład:

/**
 * Funkcja blokowa doklejajaca zawartosc parametrow do tresci.
 * 
 * Wymagane parametry:
 * - (string) prefix
 * - (string) suffix
 * 
 * @param array $params
 * @param string $content
 * @param \Smarty_Internal_Template $template
 * @param boolean $repeat
 * @return string
 */
function smarty_block_test_function(array $params, $content, \Smarty_Internal_Template $template, &$repeat) {
  // return only when $content is not null
  if (!$repeat) {
 
    // in case of debug mode
    if ($template->getTemplateVars('debug_mode') === true || $template->getConfigVars('debug_mode') === true) {
      if (!isset($params['prefix']) || $params['prefix'] === null) {
        throw new \SmartyException('Unknown prefix');
      }
      if (!isset($params['suffix']) || $params['suffix'] === null) {
        throw new \SmartyException('Unknown suffix');
      }
    }
 
    $prefix = (isset($params['prefix'])) ? $params['prefix'] : '';
    $suffix = (isset($params['suffix'])) ? $params['suffix'] : '';
 
    return "{$prefix} {$content} {$suffix}";
  }
}

Użycie:

{test_function prefix='prefix string' suffix='suffix string'}contents{/test_function}
{* prefix string contents suffix string *}