Tag Archives: smarty

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 }.

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 *}
webmastering

O modyfikatorach w Smarty i opisie słownym interwałów czasu

Pomysł na wpis dał mi kilka dni temu Face­book, a dokład­niej ich total­nie niekon­se­kwentne ozna­cze­nia co się kiedy wyda­rzyło na naszej tablicy. Raz jest to 23 minut(y) temu, raz 7 godz. temu, jesz­cze inaczej około godziny temu. Być może różne ekipy robią osobno opisy czasu i stąd różnice. Posta­no­wi­łem dla sportu zmie­rzyć się z proble­mem. Powstało takie coś jak Time­span Smarty Modi­fier.

Opis jak to zain­sta­lo­wać, wyma­ga­nia i sposób użycia może­cie sobie prze­czy­tać w moim kale­czo­nym angiel­skim w GitHu­bie. Tutaj chciał­bym się skupić jak to działa. Najwy­god­niej­szym sposo­bem korzy­sta­nia z biblio­teki jest poprzez mody­fi­ka­tor w szablo­nach Smarty, a więc {$jakaśZmienna|naszModyfikator}.

Przede wszyst­kim sama nazwa pliku musi nazy­wać się modifier.nazwa.php, za to funk­cja smarty_modifier_nazwa($argument). Pierw­szy argu­ment auto­ma­tycz­nie dostaje wartość zmien­nej, dla której wywo­łu­jemy mody­fi­ka­tor. Jeśli chcemy, następne poda­jemy po dwukropku. Drobna uwaga: nigdy niczego nie „echu­jemy”, tylko zwra­camy poprzez return. Chodzi o łańcu­chowe łącze­nie modyfikatorów.

Z uwagi na to, że całość rozro­sła się do ponad 400 linii kodu, nie ma sensu wszyst­kiego tutaj przy­ta­czać. Skupię się na najważ­niej­szych rzeczach.

Całość ma formę klasy abstrak­cyj­nej rozsze­rza­nej przez poszcze­gólne języki. Na podsta­wie zade­kla­ro­wa­nego języka skrypt próbuje znaleźć sobie właściwą klasę (fragm. modifier.timespan.php):

$className = 'Spiechu\TimeSpan\TimeSpan' . strtoupper($lang);
   if (class_exists($className)) {
      $timeSpan = new $className();
 
      // sprawdz czy klasa rozszerza AbstractTimeSpan
      if (!($timeSpan instanceof Spiechu\TimeSpan\AbstractTimeSpan)) {
         $timeSpan = new Spiechu\TimeSpan\TimeSpanEN();
      }
   } else {
 
      // jesli nie ma takiego jezyka lub klasa niewlasciwa uzywam angielskiego
      $timeSpan = new Spiechu\TimeSpan\TimeSpanEN();
   }

Potem tylko konfi­gu­ru­jemy klasę i zwra­camy wynik:

$timeSpan->setStartDate($date)->showSuffix($suffix);
return $timeSpan->getTimeSpan();

W klasie Abstract­Ti­me­Span z kolei mamy trochę logiki zwią­za­nej z obli­cza­niem interwału:

$curDate = new \DateTime('now');
$diff = $curDate->diff($this->_startDate); //otrzymujemt obiekt DateInterval

Począw­szy od najwięk­szej jednostki (rok) odpy­tu­jemy $diff która z jego zmien­nych publicz­nych jest więk­sza od 0 oraz za pomocą metod isHalfUnit($actualUnit, $fullUnit), almostFullUnit($actualUnit, $fullUnit) spraw­dzamy czy może prze­kro­czy­li­śmy połowę obec­nej jednostki, więk­szej jednostki oraz czy nie można jej zaokrą­glić do następ­nej całej.

Klasy rozsze­rza­jące imple­men­tują m.in. metodę getUnit($howMany, $unit­Sym­bol, $half). Liczeb­niki angiel­skie są tak trudne, że potrzeba na nie aż 1 linijki:

if ($howMany > 1) $howMany = 2;
return $this->_units[$howMany][$unitSymbol];

Jeśli mowa o $this->_units to wygląda to tak:

private $_units = array(
   -1 => array('s' => 'just now'),
   0 => array('i' => 'half minute',
      'h' => 'half hour',
      'd' => 'half day',
      'm' => 'half month',
      'y' => 'half year'),
   1 => array('s' => 'a second',
      'i' => 'a minute',
      'h' => 'an hour',
      'd' => 'a day',
      'm' => 'a month',
      'y' => 'a year'),
   2 => array('s' => 'seconds',
      'i' => 'minutes',
      'h' => 'hours',
      'd' => 'days',
      'm' => 'months',
      'y' => 'years')
);

Polskie jednostki z kolei prezen­tują się dużo bardziej okazale (3 odmiany):

private $_units = array(
   -1 => array('s' => 'przed chwilą'),
   0 => array('i' => 'pół minuty',
      'h' => 'pół godziny',
      'd' => 'pół dnia',
      'm' => 'pół miesiąca',
      'y' => 'pół roku'),
   1 => array('s' => 'sekundę',
      'i' => 'minutę',
      'h' => 'godzinę',
      'd' => 'dzień',
      'm' => 'miesiąc',
      'y' => 'rok'),
   2 => array('s' => 'sekundy',
      'i' => 'minuty',
      'h' => 'godziny',
      'd' => 'dni',
      'm' => 'miesiące',
      'y' => 'lata'),
   5 => array('s' => 'sekund',
      'i' => 'minut',
      'h' => 'godzin',
      'd' => 'dni',
      'm' => 'miesięcy',
      'y' => 'lat')
);

Mało tego, w polskiej klasie mamy jesz­cze jednostki specjalne :-) :

private $_specialUnits = array(
   'poltora' => array('s' => 'półtorej sekundy', // nigdy nie uzywane
      'i' => 'półtorej minuty',
      'h' => 'półtorej godziny',
      'd' => 'półtora dnia',
      'm' => 'półtora miesiąca',
      'y' => 'półtora roku')
);

Metodę getU­nit() przy­ta­czam w całości:

protected function getUnit($howMany, $unitSymbol, $half) {
 
   // kluczowa liczba, do ktorej wszystkie maja odmiane jak '5', potem tylko z jedynka na koncu
   if ($howMany > 21) {
 
      // badamy ostatnia cyfre 
      $howMany = substr($howMany, -1);
 
         // jesli to 1 to odmienia sie jak '5'
         if ($howMany <= 1) {
            $howMany = 5;
 
         // jesli nie to rekurencyjnie dla samej ostatniej cyfry
         } else {
            return $this->getUnit($howMany, $unitSymbol);
         }
   } elseif ($howMany >= 5) {
      $howMany = 5;
   } elseif ($howMany >= 2) {
      $howMany = 2;
 
   // jesli mamy '1 i pol' to odpytujemy specjalne jednostki
   } elseif ($howMany == 1 && $half == true) {
      return $this->_specialUnits['poltora'][$unitSymbol];
   }
   return $this->_units[$howMany][$unitSymbol];
}

Mamy już jednostki, teraz tylko skleić to w jeden ciąg znaków. Zajmuje się tym metoda getTi­me­Span() z klasy Abstract­Ti­me­Span:

public function getTimeSpan() {
   $interval = $this->getInterval();
   $timeUnit = $this->getUnit($interval['counter'], $interval['unit'], $interval['half']);
 
   // jesli gdziekolwiek dane byly szacowane to dorzucamy 'okolo'
   $prefix = ($interval['approx']) ? $this->getPrefix() . ' ' : '';
 
   // jesli mamy co najmniej 1 i pol
   $half = ($interval['half'] && $interval['counter'] > 0) ? $this->getHalf() . ' ' : '';
 
   // sprawdzamy czy chcemy 'temu'
   $suffix = ($this->_showSuffix) ? ' ' . $this->getSuffix() : '';
 
   // podajemy razem z liczba jednostkek
   if ($interval['counter'] > 1) {
      return $prefix . $interval['counter'] . ' ' . $half . $timeUnit . $suffix;
 
   // podajemy bez liczby jednostek
   } elseif ($interval['counter'] >= 0) {
      return $prefix . $timeUnit . ' ' . $half . $suffix;
 
   // zostala tylko mozliwosc, ze to 'przed chwila', czyli offset -1
   } else {
      return $timeUnit;
   }
}

Domyślne warto­ści to 10 sekund dla komu­ni­katu przed chwilą, 15% tole­ran­cji dla połowy jednostki (np. około 2 i pół godziny temu lub około pół godziny temu) i 15% tole­ran­cji dla cało­ści jednostki (np. około godzinę temu).

Myślę, że całość da się łatwo prze­nieść do innych syste­mów szablo­nów (od biedy w samym PHP też w końcu można). Póki co brakuje testów jednost­ko­wych, I’m working on it :-D