Tag Archives: webmastering

webmastering

Zend Framework i podświetlanie wybranego elementu menu

W życiu każdego progra­mi­sty nadcho­dzi chwila, od której staje się leniwy (czytaj: mądrzej­szy) i zaczyna korzy­stać z goto­wych, wydaj­niej­szych rozwią­zań w postaci frame­wor­ków. Do tej pory progra­mi­sta tworzył w pocie czoła swoje „klocki”, z których mógł sobie skła­dać apli­ka­cje. W lite­ra­tu­rze zwie się takie coś „reinven­ting the wheel”, czyli po naszemu trace­nie czasu na klepa­nie kodu, który ktoś napi­sał za nas. Śpie­chu niniej­szym prze­cho­dzi na następny level i zaczyna pozna­wać Zend Frame­work.

Nie ma niestety dobrej lite­ra­tury w języku polskim do Zenda. Przy okazji czeka­nia na pociąg zaha­czy­łem o Empik i kupi­łem sobie PHP5. Progra­mo­wa­nie z wyko­rzy­sta­niem Symfony, Cake­PHP, Zend Frame­work Tomasz Skara­czyń­skiego i Andrzeja Zoła. 2/3 książki zajmuje niestety opis Symfony. Myślę jednak, że te kilka­dzie­siąt stron na start z Zendem wystar­czy. Potem wgry­zamy się w doku­men­ta­cję na stro­nie producenta.

W skró­cie: Zend Frame­work stosuje wzorzec projek­towy Model-Widok-Kontroler (MVC). Na świat wysta­wiony mamy jedy­nie plik index.php. Router rozpra­co­wuje żąda­nie wyświe­tle­nia strony i kieruje do odpo­wied­niego kontro­lera i jego akcji. Jeden kontro­ler może mieć wiele akcji, z których każda może robić co innego. Dla każdego kontro­lera przy­pi­sany jest osobny kata­log z wido­kami, z których gene­ro­wana jest osta­teczna strona. Kontro­ler w razie potrzeby odpala również jakieś zapy­ta­nia do bazy danych (model) i prze­ka­zuje wyniki do widoku.

Chcemy uzyskać taki efekt, że po klik­nię­ciu konkret­nego elementu menu ten nam się podświe­tli. I to tak żeby cała opera­cja odby­wała się bez inge­ren­cji progra­mi­sty piszą­cego kolejne akcje. Samego startu z Zendem nie będę omawiał. Zapy­taj­cie wujka Google o Zend_Layout i o rozsze­rza­nie Zend_Controller_Plugin_Abstract.

Pierw­sze czego potrze­bu­jemy to plik layout.phtmlapplication/layouts/scripts. Jest super prosty i zawiera 2 place­hol­dery — 1 na menu i 1 na treść z akcji.

<html><head></head>
<body>
   <div id="menu">
      <?php echo $this->layout()->menu; ?>
   </div>
   <div id="content">
      <?php echo $this->layout()->content; ?>
   </div>
</body>
</html>

Następ­nie potrze­bu­jemy kontro­lera, który będzie zajmo­wał się tylko wyświe­tla­niem menu. Tworzymy plik MenuController.phpapplication/controllers.

class MenuController extends Zend_Controller_Action {
   public function init() {
      $this->_helper->viewRenderer->setResponseSegment('menu');
   }
   public function showmenuAction() {
      $menuItems = array(
         array('tytul' => 'pierwszy link',
            'kontroler' => 'index',
            'akcja' => 'pierwszy'),
         array('tytul' => 'drugi link',
            'kontroler' => 'index',
            'akcja' => 'drugi'),
         array('tytul' => 'trzeci link',
            'kontroler' => 'index',
            'akcja' => 'trzeci')
      );
 
      $this->view->assign('menu', $menuItems);
      $front = Zend_Controller_Front::getInstance();
      $this->view->assign('highlight_action', $front->getPlugin('Spiechu_Menu_Plugin_Menu')->getActionName());
      $this->view->assign('highlight_controller', $front->getPlugin('Spiechu_Menu_Plugin_Menu')->getControllerName());
   }
}

Powyż­szy kod usta­wia place­hol­der na menu (content jest domyślny), następ­nie prze­ka­zuje zmienne potrzebne do wyświe­tle­nia widoku.

Teraz trzeba by jakoś wyświe­tlić te elementy menu. Tworzymy plik widoku odpo­wia­da­jący nazwie akcji. W naszym przy­padku jest to showmenu.phtmlviews/scripts/menu.

<ul>
 <?php foreach ($this->menu as $menuItem): ?>
  <?php if ($this->highlight_action == $menuItem['akcja'] && $this->highlight_controller == $menuItem['kontroler']): ?>
   <li><a style="background-color:green;" "href="<?php echo $this->url(array('controller' => $menuItem['kontroler'] , 'action' => $menuItem['akcja'])); ?>"><?php echo $menuItem['tytul'] ?></a></li>
  <?php else: ?>
   <li><a href="<?php echo $this->url(array('controller' => $menuItem['kontroler'] , 'action' => $menuItem['akcja'])); ?>"><?php echo $menuItem['tytul'] ?></a></li>
  <?php endif; ?>
 <?php endforeach; ?>
</ul>

Powyż­sza hybryda php i html iteruje nam po wszyst­kich elemen­tach tablicy $this->menu. Jeżeli aktu­al­nie itero­wany element odpo­wiada temu do podświe­tle­nia to wkle­jamy mu styl background-color podświe­tla­jący na zielono. Warty uwagi jest również helper widoku $this->url(), który montuje nam prawi­dłowy adres do danego elementu menu.

Zapewne zwró­ci­li­ście uwagę na takie dziwne coś jak Spiechu_Menu_Plugin_Menu. Jest to mój plugin podcze­pia­jący się do kontro­lera fron­to­wego i pobie­ra­ją­cego z niego dane na temat żąda­nego kontro­lera i akcji. A czemu taka dziwna nazwa? Zend rozpra­co­wuje sobie gdzie ma szukać danej klasy w struk­tu­rze kata­lo­gów za pomocą nazw poprze­dzie­la­nych podkreślnikiem.

Tworzymy kod pluginu w library/Spiechu/Menu/Plugin/Menu.php

class Spiechu_Menu_Plugin_Menu extends Zend_Controller_Plugin_Abstract {
 protected $_actionName;
 protected $_controllerName;
 
 public function routeShutdown(Zend_Controller_Request_Abstract $request) {
  $this->_actionName = $request->getActionName();
  $this->_controllerName = $request->getControllerName();
  $menu = clone $request;
  $menu->setControllerName('menu')->setActionName('showmenu');
  $frontController = Zend_Controller_Front::getInstance();
  if (!$frontController->hasPlugin('Zend_Controller_Plugin_ActionStack')) $frontController->registerPlugin(new Zend_Controller_Plugin_ActionStack());
  $actionStack = $frontController->getPlugin('Zend_Controller_Plugin_ActionStack');
  $actionStack->pushStack($menu);
 }
 
 public function getActionName() {
  return $this->_actionName;
 }
 
 public function getControllerName() {
  return $this->_controllerName;
 }
}

Macie tutaj sporo niezro­zu­mia­łego kodu. Dla każdego pluginu po zakoń­cze­niu procesu routingu, a przed rozpo­czę­ciem pętli wyko­ny­wa­nia akcji odpa­lana jest m.in. metoda route­Shut­down(). Używamy jej do zapisu danych nt. żąda­nego kontro­lera i akcji, a następ­nie wrzu­camy na stos (Zend_Controller_Plugin_ActionStack) dodat­kową akcję do wyko­na­nia, która wyświe­tli nam menu. Jest tutaj użyty numer z klono­wa­niem, który gdzieś znala­złem na forum. Potrze­bu­jemy jakie­goś żąda­nia, które można prze­flan­co­wać na wyświe­tle­nie menu. Ponadto nie możemy popsuć tego właści­wego, więc używamy klonowania.

Ostat­nie czego potrze­bu­jemy to zare­je­stro­wać plugin, gdyż Zend nie wie czy chcemy go używać czy nie. Najle­piej to zrobić w pliku application/bootstrap.php. U mnie wygląda to tak:

class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
 public function __construct($application) {
  parent::__construct($application);
 
  Zend_Loader_Autoloader::getInstance()->registerNamespace('Spiechu_');
  $frontController = Zend_Controller_Front::getInstance();
  $frontController->registerPlugin(new Spiechu_Menu_Plugin_Menu());
 }
}

Najpierw reje­stru­jemy nową ścieżkę, a następ­nie reje­stru­jemy plugin. Jak zapewne zauwa­ży­li­ście, Zend_Loader_Autoloader jak i Zend_Controller_Front to single­tony. Mamy przez to pewność, że gdy w kontro­le­rze chcemy wycią­gnąć dane z naszego pluginu, to jest to ta sama instancja.

Na razie wszystko to jest dla mnie dosyć nowe. Sam nie do końca czuję czemu to nie wyrzuca jakie­goś błędu. :-)

Myślę, że ten wpis to dopiero począ­tek pisa­nia o Zendzie.

PS.: Nie zapo­mnij­cie sobie stwo­rzyć akcji pierw­szy­Ac­tion(), drugiAc­tion(), trze­ciAc­tion() w kontro­le­rze Inde­xCon­trol­ler (może wyświe­tlać tylko 1, 2, 3 — ważne, żeby można było poznać czy rzeczy­wi­ście podświe­tla nam ten element, który wybraliśmy).

webmastering

Weź mi zrób procesor tekstu cz. 2 / 2

Zgod­nie z zapo­wie­dzią, dzisiaj będziemy imple­men­to­wać moduły, które coś z poda­nym tekstem robią. Do tego celu głów­nie używać będziemy wyra­żeń regularnych.

1. Na pierw­szy ogień pójdzie popra­wiacz spacji. Jak często trafiają się wam doku­menty forma­to­wane za pomocą 20 spacji? Za pomocą tego cuda wywa­limy wszystko co ma powy­żej dwóch za koleją.

class SpaceFixer implements TextJob {
   public function process($string) {
      $string = trim($string);
      $string = preg_replace('/\s{2,}/' , ' ' , $string);
      return $string;
   }
}

Zasada dzia­ła­nia prosta: najpierw wyrzuć wszel­kie spacje przed i za strin­giem, a następ­nie poszu­kaj dwie lub więcej spacji za koleją i zamień je na pojedyncze.

2. Teraz coś sporo trud­niej­szego: doda­wacz twar­dych spacji do spój­ni­ków. Zasada dzia­ła­nia taka: znajdź mi spację, poje­dyn­czy znak, znowu spację i jeden lub więcej znaków (np. jedze­nie_i_picie).

class AddNbsps implements TextJob {
   public function process($string) {
      $output = ''; // inicjalizujemy pustą zmienną, zostało mi to po Javie
      foreach ($this->splitStringsByTagChar($string) as $split) {
         $output .= substr($split , 0 , 1) == '<' ? $split : $this->fixLine($split);
      }
      return $output;
   }
   private function splitStringsByTagChar($string) {
      return preg_split('/(<.+?>)/', $string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
   }
   private function fixLine($line) {
      return preg_replace('/(\s{1}[-\("\w]{1})\s{1}([-\)"\w]+)/' , '$1&nbsp;$2' , $line);
   }
}

Jak widać trochę się pokom­pli­ko­wało. Po co nam te dodat­kowe funk­cje? Ano po to żeby nie popsuły się nasze HTMLowe tagi. Funk­cja splitStringsByTagChar nieza­chłan­nie szuka czegoś co przy­po­mina sekwen­cję <coś> i szat­kuje nam ciąg do tablicy.

Opera­tor trój­ar­gu­men­towy po fore­ach spraw­dza czy element tablicy zaczyna się od znaku < . Jeżeli tak, to zosta­wia go w spokoju, a jak nie, to kieruje do prze­róbki do fixLine. Można zauwa­żyć, że oprócz klasy znaków \w (alfa­nu­me­ryczne i _) doda­łem również ” ( ). Po prze­ana­li­zo­wa­niu kilku tekstów stwier­dzi­łem, że tak lepiej działa (a mogłoby pewnie lepiej gdybym poświę­cił na to jesz­cze trochę czasu).

3. Wyszu­ki­wacz linków.

class AddLinks implements TextJob {
   public function process($string) {
      return preg_replace('`(?<!href="|src=")((https?://|www\.)[\w{1,}./?=#&]+)`i' , '<a href="$1">$1</a>' , $string);
   }
}

Tutaj można zauwa­żyć dziwną konstruk­cję (?<! Jest to aser­cja wsteczna. Działa tak: znajdź mi ciąg znaków zaczy­na­jący się od http:// lub www., a jak znaj­dziesz, to sprawdź czy czasami przed znale­zio­nym ciągiem nie ma konstruk­cji z aser­cji. Po co to? Do wykry­cia, czy link znaj­duje się już w tagu <a href=” lub <img src=”, a więc czy nie popsu­jemy kodu html. Do tego mógł­bym równie dobrze użyć funk­cji szat­ku­ją­cej z poprzed­niego modułu, ale chcia­łem żeby było trudniej :-)

Jak tego wszyst­kiego używać? Dosyć prosto: w atry­bu­tach poda­jemy moduły, które nas inte­re­sują i poda­jemy tekst:

$jakisTekst = 'Ala ma kota, a kot ma HIV';
$txtProcesser = TextProcesser::getInstance(array('SpaceFixer','AddNbsps','AddLinks'));
$processedTxt = $txtProcesser->processJobs($jakisTekst);

Tekst będzie prze­twa­rzany w poda­nej kolej­no­ści. Fajne, co?
[czeka na oklaski]

webmastering

Weź mi zrób procesor tekstu cz. 1 / 2

Dzisiaj będzie wpis o ułatwia­niu życia użyt­kow­ni­kom paneli admi­ni­stra­cyj­nych. Chodzi tutaj o sytu­ację, gdy użyt­kow­nik naszej strony wkleja byle jak jakiś tekst z Worda do TinyMCE, a następ­nie dziwi się, że na stro­nie wycho­dzi kaszana.

Parę rzeczy da się popra­wić z auto­matu poprzez prze­two­rze­nie przez proce­sor tekstu. Skąd taki wziąć? A napi­szemy sobie sami.

W tym wpisie stwo­rzymy „rusz­to­wa­nie”, a w następ­nej części parę modu­łów robią­cych jakieś konkretne rzeczy.

Pierw­sze czego potrze­bu­jemy to inter­fejs poje­dyn­czego modułu prze­twa­rza­ją­cego tekst. Będzie banal­nie prosty: metoda process pobiera stringa i zwraca prze­two­rzo­nego stringa.

interface TextJob {
   public function process($string);
}

Dalej piszemy klasę proce­sora tekstu.

class TextProcesser {
   private $jobsToDo = array();
   private function __construct() {}
   public static function getInstance($params = array()) {
      $textProcesser = new TextProcesser();
      foreach ($params as $param) {
         try {
            if (!class_exists($param)) throw new Exception('Klasa ' . $param . ' nie istnieje!');
            $param = new $param;
            if (!($param instanceof TextJob)) throw new Exception('Klasa ' . $param . ' nie obsluguje interfejsu TextJob!');
         } catch (Exception $e) {
            echo $e->getMessage();
            die();
         }
         $textProcesser->addJob($param);
      }
      return $textProcesser;
   }
 
   private function addJob(TextJob $tj) {
      $this->jobsToDo[] = $tj;
   }
 
   public function processJobs($string) {
      foreach ($this->jobsToDo as $job) {
         $string = $job->process($string);
      }
      return $string;
   }
}

Po kolei:

  1. Zmienna $jobsToDo trzyma tablicę prac do wykonania.
  2. Klasy nie będziemy tworzyć poprzez konstruk­tor, dlatego został zmie­niony na prywatny.
  3. Statyczna metoda getInstance tworzy obiekt proce­sora i przy okazji spraw­dza czy podana tablica $params zawiera istnie­jące klasy i czy imple­men­tują one inter­fejs TextJob.
  4. Metoda addJob dodaje kolejną pracę do wyko­na­nia do kolejki.
  5. processJobs wyko­nuje właściwą robotę — pobiera stringa do prze­two­rze­nia i w kolej­no­ści doda­nia prac prze­twa­rza go.

Co mamy do tej pory? Klasę, do której możemy doda­wać tyle konkret­nych zadań ile chcemy, o ile tylko imple­men­tują inter­fejs TextJob. W następ­nej części będziemy wyrzu­cać zbędne spacje, doda­wać twarde spacje i szukać linków.