Tag Archives: zend framework

webmastering

Łączenie zapytań Zend_Db_Select w Zend Framework [cz. 2/2]

Konty­nu­ujemy temat zapy­tań z poprzed­niej części. Punk­tem wyjścia będzie dla nas tablica wyni­ków zapy­ta­nia dorzu­co­nego przeze mnie LEFT JOINA (w ramach aktu­ali­za­cji wpisu na samym dole poprzed­niej części). Aby nieco utrud­nić dorzu­ci­łem kolej­nych dwóch powszech­nie znanych idoli młodzieży: Anto­niego Macie­re­wi­cza i Stefana Niesio­łow­skiego, którzy dla odmiany nie będą mieli swoich ksywek. Ponadto doda­łem drugą ksywkę towa­rzy­szowi Tuskowi znanemu często na forach Onetu jako Rudy Oszust.

Reasu­mu­jąc: mamy poli­tyka z dwoma ksyw­kami, jednego z jedną i dwóch bez ksywek. Dążymy do tego aby wyświe­tlić na liście rozwi­ja­nej formu­la­rza ich wszyst­kich. Zarówno orygi­nalne imię i nazwi­sko jak i ksywkę. Co jednak zrobić, skoro ksywka i orygi­nalne imię i nazwi­sko posia­dają to samo id w bazie? Zamie­nimy je na unika­towe poprzez doda­nie jakie­goś dodatku po orygi­nal­nym id, np. 5–1, 5–2 itd.

Najpierw jednak stwo­rzymy super prosty formu­larz zawie­ra­jący pole wyboru typu select i przy­cisk zatwier­dza­jący zmiany. Potrze­bu­jemy klasy dzie­dzi­czą­cej po Zend_Form. Ja pracu­jąc w Zendzie zazwy­czaj formu­la­rze wrzu­cam do kata­logu forms równo­le­głego do control­lers, models itd.

class PolitycyForm extends Zend_Form {
 
   public function init() {
      $this->setMethod('post');
      // gdzie ma zostac wyslany formularz
      $this->setAction('/index/index');
 
      // tutaj wstawić zapytanie
      // z aktualizacji wpisu poprzedniej czesci
 
      $stmt = $select1->query();
      $rowset = $stmt->fetchAll();
 
      $wyniki = array();
      $counter = 1;
 
      foreach ($rowset as $row) {
         // tymczasowo kluczami staja sie wyniki zapytania,
         // a wartosciami id i kolejny numer
         $wyniki[$row['imie_nazwisko']] = $row['id'] . '-' . $counter++;
         // sprawdzamy czy ten ktos ma ksywke,
         // jezeli tak to dorzucamy do wynikow
         if ($row['ksywka'] != null) {
            $wyniki[$row['ksywka'] . ' (' . $row['imie_nazwisko'] . ')'] = $row['id']. '-' . $counter++;
         }
      }
      // nastepnie wszystko to sortujemy po kluczach
      // do poprawnego posortowania polskich znakow
      // uzywamy funkcji setlocale
      setlocale(LC_COLLATE, 'pl_PL.utf8');
      ksort($wyniki, SORT_LOCALE_STRING);
      // i zamieniamy miejscami klucze z wartosciami
      $wyniki = array_flip($wyniki);
 
      $formElement = new Zend_Form_Element_Select('politycy');
      $formElement->setRequired(true)
         // blokujemy tworzenie domyslnego walidatora
         // sprawdzajacego czy wynik jest w formie tablicy
         ->setRegisterInArrayValidator(false)
         ->setLabel('Wybierz swojego ulubionego polityka')
         ->setMultiOptions($wyniki)
         // sprawdzamy czy ktos nie robi psikusa
         ->addValidator(new Zend_Validate_Regex('/^[0-9]+\-[0-9]+$/'));   
      $this->addElement($formElement);
      // dodajemy pole typu submit
      $this->addElement('submit','wybierz');
   }   
}

Po wszyst­kich zabie­gach tablica $wyniki prze­ka­zy­wana do obiektu Zend_Form_Element_Select posiada nastę­pu­jącą strukturę:

array(7) {
  ["3-7"] => string(18) "Antoni Macierewicz"
  ["1-3"] => string(11) "Donald Tusk"
  ["2-6"] => string(31) "Jareczek (Jarosław Kaczyński)"
  ["2-5"] => string(20) "Jarosław Kaczyński"
  ["1-4"] => string(25) "Rudy Oszust (Donald Tusk)"
  ["1-2"] => string(27) "Słońce Peru (Donald Tusk)"
  ["4-8"] => string(20) "Stefan Niesiołowski"
}

Teraz pozo­staje nam odebrać formu­larz. Żeby zbyt­nio nie kompli­ko­wać dane odbie­rzemy w kontro­le­rze IndexController w akcji indexAction(). Na przy­kład tak:

$politycyForm = new PolitycyForm();
if ($this->_request->isPost()) {
   $dane = $this->getRequest()->getPost();
   if ($politycyForm->isValid($dane)) {
      // wyrzucamy szmelc po wlasciwym identyfikatorze
      $filtr = new Zend_Filter_PregReplace(
         array('match' => '/\-[0-9]+/',
               'replace' => ''));
      $przefiltrowane = $filtr->filter($dane['politycy']);
 
      // wykonujemy dzialania na bazie danych
      // co wykracza poza ramy tego wpisu
 
      // zakladamy, ze istenieje akcja panel-uzytkownika
      return $this->_redirect('/index/panel-uzytkownika');
   }
   else {
      // jezeli formularz nie przechodzi walidacji
      // to zostaje uzupelniony o wprowadzone poprzednio dane
      $politycyForm->populate($dane);
   }
}
// wyswietlamy formularz
$this->view->politycy = $politycyForm;

Może­cie zapy­tać po co nam ten _redirect. Otóż zabez­pie­cza nas przed ponow­nym wyświe­tle­niem użyt­kow­ni­kowi formu­la­rza i przed ewen­tu­al­nym ponow­nym wysła­niem danych.

Jeżeli za szybko z czymś poje­cha­łem, skła­dać zaża­le­nia w komentarzach :-)

webmastering

Łączenie zapytań Zend_Db_Select w Zend Framework [cz. 1/2]

Ostat­nio w ramach spor­tów wyczy­no­wych majstruję trochę z zapy­ta­niami do bazy danych w Zendzie. Spraw­dzam co można wyci­snąć z obiek­tów Zend_Db_Select. Ten wpis zdecy­do­wa­łem się podzie­lić na dwie części. W pierw­szej pokażę jak wyge­ne­ro­wać dosyć złożone zapy­ta­nie do bazy danych za pomocą kilku obiek­tów Zend_Db_Select, a w drugiej zajmiemy się stwo­rze­niem formu­la­rza i odebra­niem danych.

Naszym celem będzie utwo­rze­nie alfa­be­tycz­nej listy poli­ty­ków, których wszy­scy lubimy wraz z ich powszech­nie znanymi pseu­do­ni­mami, np.

<select>
<option value="1">Donald Tusk</option>
<option value="2">Jareczek (Jarosław Kaczyński)</option>
<option value="3">Jarosław Kaczyński</option>
<option value="4">Słońce Peru (Donald Tusk)</option>
</select>

Najpierw musimy mieć skąd brać naszych wybrań­ców narodu. Stwo­rzymy sobie bazę danych mniej więcej taką:

Obra­zek bazy danych poli­ty­ków zrobi­łem fajnym narzę­dziem online WWW SQL Desi­gner. Sche­mat jest oczy­wi­ście maksy­mal­nie uprosz­czony. Nie czepiać się, że imiona i nazwi­ska trzy­mam w jednym polu. Chodzi nam o rela­cję jeden poli­tyk ma wiele pseu­do­ni­mów. Macie poni­żej trochę kodu SQL wyeks­por­to­wa­nego przez phpMy­Ad­min plus kilka przy­kła­do­wych wartości.

CREATE TABLE IF NOT EXISTS `politycy` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `imie_nazwisko` VARCHAR(50) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COMMENT='tabela zawierajaca imiona i nazwiska politykow' AUTO_INCREMENT=3 ;
 
INSERT INTO `politycy` (`id`, `imie_nazwisko`) VALUES
(1, 'Donald Tusk'),
(2, 'Jarosław Kaczyński');
 
CREATE TABLE IF NOT EXISTS `politycy_ksywki` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `ksywka` VARCHAR(20) NOT NULL,
  `polityk_id` INT(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `polityk_id` (`polityk_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COMMENT='zawiera popularne pseudonimy politykow' AUTO_INCREMENT=3 ;
 
INSERT INTO `politycy_ksywki` (`id`, `ksywka`, `polityk_id`) VALUES
(1, 'Słońce Peru', 1),
(2, 'Jareczek', 2);
 
ALTER TABLE `politycy_ksywki`
  ADD CONSTRAINT `politycy_ksywki_ibfk_1` FOREIGN KEY (`polityk_id`) REFERENCES `politycy` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;

Następ­nie spró­bu­jemy sobie wyge­ne­ro­wać dosyć spore zapy­ta­nie SQL, które za jednym zama­chem wypi­sze wszyst­kich poli­ty­ków, wszyst­kie pseu­do­nimy i dodat­kowo w nawia­sie wypi­sze którego poli­tyka który pseu­do­nim doty­czy. Wszystko oczy­wi­ście w Zend Framework.

// adapter bazy umieszczony w rejestrze
$dbAdapter = Zend_Registry::get('db');
$select1 = $dbAdapter->select()
   // podstawowa tabela z politykami
   ->from(array('p' => 'politycy'),
          // ktore pola (lacznie z aliasem)
          array('p.id','p.imie_nazwisko'));
// podzapytanie dla ksywek
$subSelect = $dbAdapter->select()
   ->from(array('pp' => 'politycy'),
          array('pp.imie_nazwisko'))
   // warunek dla podzapytania
   ->where('pp.id = pk.polityk_id')
   // powinno przyspieszyc duze bazy
   ->limit(1);
$select2 = $dbAdapter->select()
   // tabela z ksywkami
   ->from(array('pk' => 'politycy_ksywki'),
          array('pk.polityk_id',
                // Zend_Db_Expr konieczny przy wywolywaniu funkcji SQL
                new Zend_Db_Expr("CONCAT(pk.ksywka, ' (', ({$subSelect}) , ')')")));
$selectUnion = $dbAdaptery->select()
   // konstruujemy zapytanie typu union
   ->union(array($select1, $select2))
   // szeregujemy wyniki
   ->order('imie_nazwisko ASC');
$stmt = $select->query();
$rowset = $stmt->fetchAll();

Powyż­szy kod stwo­rzył niczego sobie zapytanie:

SELECT `p`.`id`, `p`.`imie_nazwisko`
   FROM `politycy` AS `p`
UNION SELECT `pk`.`polityk_id`,
   CONCAT(pk.ksywka, ' (', (SELECT `pp`.`imie_nazwisko` FROM `politycy` AS `pp` WHERE (pp.id = pk.polityk_id) LIMIT 1) , ')')
   FROM `politycy_ksywki` AS `pk` ORDER BY `imie_nazwisko` ASC

Zmienna $rowset zawiera wynik zapy­ta­nia w postaci tablic:

array(4) {
  [0] => array(2) {
    ["id"] => string(1) "1"
    ["imie_nazwisko"] => string(11) "Donald Tusk"
  }
  [1] => array(2) {
    ["id"] => string(1) "2"
    ["imie_nazwisko"] => string(31) "Jareczek (Jarosław Kaczyński)"
  }
  [2] => array(2) {
    ["id"] => string(1) "2"
    ["imie_nazwisko"] => string(20) "Jarosław Kaczyński"
  }
  [3] => array(2) {
    ["id"] => string(1) "1"
    ["imie_nazwisko"] => string(27) "Słońce Peru (Donald Tusk)"
  }
}

Na koniec uwaga: obiekty typu Zend_Db_Select są przy­datne tylko wtedy, gdy nasze zapy­ta­nie nie ma charak­teru stałego, tzn. różne czyn­niki wpły­wają na jego kształt, przez co musi być tworzone w locie. Jeżeli wiemy, że zapy­ta­nie zawsze będzie takie samo to jest to zwykłe marno­traw­stwo zaso­bów serwera, aczkol­wiek ładnie wygląda i szybko się pisze.

Aktu­ali­za­cja (3.2.2011)

Skoro mnie zmusi­li­ście to poni­żej najbar­dziej poprawne wg mnie zapy­ta­nie zała­twia­jące sprawę.:

$select1 = $dbAdapter->select()
   ->from(array('p' => 'politycy'),
      array('p.id','p.imie_nazwisko'))
   ->joinLeft(array('pk' => 'politycy_ksywki'),
      'p.id = pk.polityk_id',
      array('pk.ksywka'));

Powyż­sze zapy­ta­nie wygeneruje

SELECT `p`.`id`, `p`.`imie_nazwisko`, `pk`.`ksywka` 
FROM `politycy` AS `p`
LEFT JOIN `politycy_ksywki` AS `pk`
ON p.id = pk.polityk_id

Które z kolei zwróci wyniki

array(5) {
  [0] => array(3) {
    ["id"] => string(1) "1"
    ["imie_nazwisko"] => string(11) "Donald Tusk"
    ["ksywka"] => string(13) "Słońce Peru"
  }
  [1] => array(3) {
    ["id"] => string(1) "1"
    ["imie_nazwisko"] => string(11) "Donald Tusk"
    ["ksywka"] => string(11) "Rudy Oszust"
  }
  [2] => array(3) {
    ["id"] => string(1) "2"
    ["imie_nazwisko"] => string(20) "Jarosław Kaczyński"
    ["ksywka"] => string(8) "Jareczek"
  }
  [3] => array(3) {
    ["id"] => string(1) "3"
    ["imie_nazwisko"] => string(18) "Antoni Macierewicz"
    ["ksywka"] => NULL
  }
  [4] => array(3) {
    ["id"] => string(1) "4"
    ["imie_nazwisko"] => string(20) "Stefan Niesiołowski"
    ["ksywka"] => NULL
  }
}
webmastering

Zend_Date i Zend_Config w Zend Framework

Mamy święta i dużo wolnego czasu, dlatego dzisiaj będziemy zajmo­wać się modu­łem Zend Frame­work odpo­wie­dzial­nym za czas – Zend_Date. Propo­nuję prze­czy­tać moje gryzmoły zamiast obja­dać się nadmier­nie karpiem czy co tam macie. Dodat­kowo zaha­czymy o Zend_Config w postaci odczytu, mody­fi­ka­cji i zapisu nowych danych do pliku .ini.

Będziemy robić moduł odpo­wie­dzialny za auto­ma­tyczne usuwa­nie dawno otwar­tych (a zatem niepo­trzeb­nych) jakichś plików na serwe­rze. Mogłyby to być pliki minia­tu­rek obraz­ków, które kiedyś nam system wyge­ne­ro­wał i są już niepotrzebne.

Domyśl­nie każda apli­ka­cja w ZF ma plik konfi­gu­ra­cyjny application.ini zapi­sany w /application/configs/. Dla naszych celów zało­żymy sobie plik thumbs.ini, który będziemy mordo­wać czytaj: ciągle nadpi­sy­wać. W pliku zapi­szemy sobie 4 linijki:

[production]
cleanafter = "Dec 25, 2010 6:26:09 PM"
checkinterval = "5"
intervalunit = "MINUTE"

Wyraz w nawia­sie ozna­cza środo­wi­sko, w którym pracu­jemy, cleanafter to data zapi­sana w forma­cie DATETIME. Checkintervalintervalunit to zmienne służące nam do wyge­ne­ro­wa­nia nowej daty cleanafter.

Kod, który podam poni­żej można sobie zapi­sać w jakimś kontro­le­rze albo jako plugin.

// ważne jest ustawienie opcji 'allowModifications' na true,
// bo inaczej konfiguracja będzie w trybie tylko do odczytu
$config = new Zend_Config_Ini(ROOT_DIR.'/application/configs/thumbs.ini','production',
   array('allowModifications' => true));
// konstruujemy obiekt Zend_Date na podstawie danych z
// konfiguracji i podajemy format, w jakim dostarczymy dane nt. daty
$cleanAfter = new Zend_Date($config->cleanafter, Zend_Date::DATETIME);
// jeżeli nic nie podamy to Zend weźmie sobie datę i czas obecny
$currentDate = new Zend_Date();
// bardzo fajna funkcja isEarlier() porównująca daty
if ($cleanAfter->isEarlier($currentDate)) {
   echo 'coś robię';
   // wyznaczamy nową datę do wpisania do konfiguracji;
   // myślę, że sklonowanie obiektu daty powoduje trochę mniejszy
   // narzut zasobów niż tworzenie od nowa obiektu
   $newCleanDate = clone $currentDate;
   // dodajemy datę; funkcja add() ma 2 argumenty: ile dodać i co dodać;
   $newCleanDate->add(
      $config->checkinterval,
      constant('Zend_Date::' . $config->intervalunit)
      );
   // przypisujemy do configa datę w formacie DATETIME
   $config->cleanafter = $newCleanDate->get(Zend_Date::DATETIME);
   // tworzymy obiekt służący do zapisu configów
   $writer = new Zend_Config_Writer_Ini();
   $writer->setConfig($config);
   $writer->setFilename(ROOT_DIR.'/application/configs/thumbs.ini');
   $writer->write();
 
   // przygotowujemy obiekt daty, z którym będziemy
   // porównywać czas ostatniego otwarcia plików
   $deleteOlderThan = clone $currentDate;
   // add() dodawało, sub() odejmuje; my odejmiemy 1 tydzień
   $deleteOlderThan->sub(1, Zend_Date::WEEK);
   // tworzymy iterator podając katalog z miniaturkami do sprawdzenia
   $dirIterator = new DirectoryIterator(APPLICATION_PATH  . '/../public/thumbs');
   foreach ($dirIterator as $file) {
      // $getATime() podaje czas ostatniego otwarcia pliku
      // w formacie TIMESTAMP, z którego skonstruujemy datę
      $fileOpenDate = new Zend_Date($file->getATime(), Zend_Date::TIMESTAMP);
      // jeżeli data ostatniego otwarcia jest wcześniejsza
      // niż przyjęty przez nas limit 1 tygodnia to wywalamy plik
      // wygłuszając ewentualne komunikaty poprzez @
      if ($fileOpenDate->isEarlier($deleteOlderThan)) {
         @unlink($file->getRealPath());
      }
   }
}

Pole­cam zwró­cić uwagę na constant(), który w locie na podsta­wie nazwy odwoła się do stałej obiektu. Konstruk­cja typu ${'jakas' . $zmienna} prawi­dłowa dla zmien­nych nie jest dostępna dla stałych i należy użyć funk­cji constant().

A poza tym to Weso­łych Świąt :-D