KIELMAS.COM Kielmas Jarosław

Blog głównie o programowaniu

Browsing Posts in Technologie IT

Dla ludzi, którzy mają problem z wygenerowaniem sobie najnowszej dokumentacji Zend Framework w polskim języku udostępniam  quickstart, pochodzący z   wersji 2.0.0dev1.

Oprócz tego dla pozostałych, pracujących na *nix (dla Windows wymagany jest CYGWIN), króciutki opis jak wygenerować sobie pełną  dokumentację ( oczywiście nie wszystko jest przetłumaczone).

Wcześniej zainstaluj sobie następujące biblioteki:

  • autoconf
  • make
  • xsltproc
  • xmllint

Następnie:

$ git clone git://git.zendframework.com/zf.git
$ cd ZendFramework-2.0.0dev1/documentation/manual/pl
$ autoconf (zostanie wygenerowany ./configure)
$ ./configure
$ make

Czekamy chwilkę, w między czasie powstaje sobie _temp_manual.xml, który potem zostanie pokrojony na odpowiednie dokumenty html w katalogu html/

Poniżej dokumentacja w postaci html do ściągnięcia

Od czterech dni dostępna  jest  wersja 2.0.0dev1 tego znakomitego frameworka dla PHP5. W ramach pierwszego   milestone’a ekipa Zend Frameworka bardzo się napracowała.

Rewolucja, przez którą nastapiła  zmiana  z „1″ na „2″ w numerze wersji już dla tego wydania, polega przede wszystkim na:

  • usunięciu (niby) wszystkich „reqiure_once” z komponentów ZF (uzywamy autoloader),
  • wprowadzenie namespace,
  • poprawa mechanizmu testów jednostkowych,
  • przepisanie od zera komponentu Zend/Session (w ramach ticketu powstał nowy nowy komponent Zend/SignalSlot,
  • wprowadzenie nowej przestrzeni nazw Zend/Stdlib,

Wydanie tak naprawdę pokazuje jedynie tyle, że ekipa rozwijająca Zend Framework nie próżnuje i można je traktować  jako możliwość zapoznania się z zaawansowaniem prac, nie jest to ukończony framework  – stawianie aplikacji na tym wydaniu, jak oznajmił zespół, jest na własne ryzyko.

Roadmap ZF z gałęzi 2.0 jest jeszcze daleki do ukończenia.

Zrobiłem malutki teścik zastosowania indexów na wybranej tabeli w bazie Mysql. Chciałem zobaczyć jakie będą  różnice czasowe zapytań podczas zastosowania różnych indexów na mojej tabeli.

Tabela ma postać:

CREATE  TABLE IF NOT EXISTS `media` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT ,
  `acl_user_id` INT(11) DEFAULT NULL,
  `parent_id` INT(11) DEFAULT NULL,
  `size` INT(11) DEFAULT NULL,
  `extension` ENUM('mp3','mp4','mpg', 'mpeg', 'jpg', 'png', 'bmp' ) DEFAULT NULL,
  `type`  ENUM('audio','video','image', 'avatar')NOT NULL,
  `status` ENUM('active','inactive','deleted', 'processing', 'banned' ) NOT NULL ,
  `date_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ,
  `date_activated` DATETIME DEFAULT NULL,
  `date_deleted` DATETIME DEFAULT NULL,
  `date_banned` DATETIME DEFAULT NULL,
  `name` VARCHAR(255) NOT NULL,
  `path` VARCHAR(255) DEFAULT NULL,
  `description` VARCHAR(255) DEFAULT NULL ,
  `guid` CHAR(255) DEFAULT NULL,
  PRIMARY KEY (`id`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_polish_ci;

Użyłem procedury sql do wypełnienia tej tabeli pół milionem losowych danych.

W przypadku braku indexów wypełnianie tabeli danymi  trwało: 4 min 17.78 sec

Czas na pytania bez indexów:

SELECT count(id) FROM media WHERE STATUS = 'active'(trwało: 1.08 sec)
SELECT id FROM media WHERE size = 30;(trwało:  0.99 sec)
SELECT count(id) FROM media WHERE name = 'nazawa'(trwało: 0.93 sec)
SELECT count(id) FROM media WHERE size = 30 AND STATUS = 'active' AND name = 'nazawa'; (trwało: 1.10 sec)

Następnie dodałem następujące indexy:

ALTER TABLE media ADD INDEX idx_media_status (STATUS);
ALTER TABLE media ADD INDEX idx_media_size (`size`);
ALTER TABLE media ADD INDEX idx_media_name (`name`);

(* dodawanie indeksów do istniejących pół miliona wierszy trwało odpowiednio: 18.11 sec, 23.24 sec oraz 39.00 sec)

W przypadku, gdy indexy istniały już w schemacie, napełnienie tabeli o pół mioliona danych trwało: 5 min 13.60 sec

Ponowiłem zapytania z wprowadzonymi indexami. Oto wyniki:

SELECT count(id) FROM media WHERE STATUS = 'active'; (trwało: 0.02 sec)
SELECT id FROM media WHERE size = 30;(trwało: 0.01 sec)
SELECT count(id) FROM media WHERE name = 'nazawa'; (trwało: 0.02 sec)
SELECT count(id) FROM media WHERE size = 30 AND STATUS = 'active' AND name = 'nazawa'; (trwało: 0.10 sec)

Dodałem jeszcze jeden klucz, aby zoptymalizować ostatnie zapytanie, z którego bardzo często będę korzystał w aplikacji:

ALTER TABLE media ADD INDEX idx_media_name_status_size (`name`, STATUS, `size`);

A oto wyniki zapytania:

SELECT count(id) FROM media WHERE size = 30 AND STATUS = 'active' AND name = 'nazawa'; (0.03 sec)

Jak widać z powyższych przykładów stosowanie indexów ma ogromny wpływ na wydajność. Podobne wnioski można zauważyć na wielu blogach, jednak najlepiej wykonać taki prosty test samemu, aby przekonać się o sile stosowania indexów. Pamiętajmy jednak, aby stosować indexy z umiarem – ich wprowadzenie opóźnia zapis danych do bazy. Ponadto, tak jak mówi dokumentacja mysql,  należy zauważyć różnicę  pomiędzy indexem dla jednego pola i dla wielu:

ALTER TABLE media ADD INDEX idx_media_size (`size`);

a

ALTER TABLE media ADD INDEX idx_media_name_status_size (`name`, STATUS, `size`);

są to dwa różne klucze, służące różnym typom zapytań.

Logowanie błędów jest kluczowym elementem minitoringu aplikacji. Przekonał się o tym chociażby każdy programista, który pracuje w firmie oferującej dodatkowo dla swoich produktów terminowy support.

Dzięki temu, że błędy są logowane jesteśmy w stanie szybko i czasami transparentnie wyeliminować bugi w aplikacji, tak że klient nawet się nie dowie o ich istnieniu:)

Jeśli Zend Framework jest podstawą twojego projektu oferuję Tobie bardzo wygodny i przejrzysty sposób na logowanie błędów aplikacji.

Potrzebna będzie jedna klasa, ponieważ aż się prosi aby system logowania błędów był ładowany jako resource, przez Zend_Application. Zarządzanie zasobów zaprezentowane w Zend Framework jest moim zdaniem rewelacyjne, ponieważ bardzo łatwo możemy wydzielić zasoby, dzięki czemu mamy wprowadzoną dodatkową  logikę i ład w naszej aplikacji,  ponadto  dowolny zasób  można łatwo i sprawnie włączyć/wyłączyć.

Przykładowa klasa mogłaby wygłądać następująco:

class Zextend_Application_Resource_Log extends Zend_Application_Resource_ResourceAbstract
{
 
    public function init()
    {
        /**
         * Pobranie głównej konfiguracji.
         */
        $config = $this->getBootstrap()->getContainer()->config;
 
        /**
         * Przygotowanie formatu zapisanej informacji.
         *
         * @example Postać zmiennej $config->format:
         * "%timestamp% %priorityName%: %message%"
         */
        $format = new Zend_Log_Formatter_Simple($config->format . PHP_EOL);
 
        /**
         * Sprawdzanie, ktory sposób logowania błędów został włączony.
         *
         * @example Postać zmiennej $config->log->stream:
         * "../temporary/log/error.log" 
         */
        $logger = new Zend_Log();
        if($config->log->file) {
           $logger->addWriter(
                   $this->_getStreamWriter($format, $config->log->stream));
        }
 
        if($config->log->stdoutput) {
           $logger->addWriter(
                   $this->_getStreamWriter($format, 'php://output'));
        }
 
        if($config->log->firebug) {
           $logger->addWriter(
                   $this->_getFirebugWriter($format));
        }
        if($config->log->mail) {
            $logger->addWriter($this->_getMailWriter($config));
        }
 
        /**
         * Używamy tego logger wszędzie w aplikacji.
         */
        Zend_Registry::set( 'log', $logger );
        return $logger;
    }
    /**
     * Zwraca obiekt zapisania informacja do Firebug.
     * 
     * @param Zend_Log_Formatter_Simple $format
     * @return Zend_Log_Writer_Firebug
     */
    private function _getFirebugWriter(Zend_Log_Formatter_Simple $format)
    {
        $writer = new Zend_Log_Writer_Firebug();
        $writer->setFormatter($format);
 
        return $writer;
    }
    /**
     * Zwraca obiekt zapisania informacji w postaci emaila.
     *
     * @param Zend_Config_Ini $config
     * @return Zend_Log_Writer_Mail
     */
    private function _getMailWriter(Zend_Config_Ini $config)
    {
        $mail = new Zend_Mail();
        $mail->setDefaultTransport(new Zend_Mail_Transport_Smtp());
 
        $mail->setFrom($config->email->from, $config->email->fromAlias);
        $mail->addTo($config->app->admin->email);
        $mail->addTo($config->app->webmaster->email);
        $mail->setSubject($config->app->name . ' - nastapil nieoczekiwany blad!');
 
        $writer = new Zend_Log_Writer_Mail($mail);
        $writer->addFilter(Zend_Log::WARN);
 
        return $writer;
    }
    /**
     * Zwraca obiekt zapisania informacji do PHP stream.
     * 
     * @param Zend_Log_Formatter_Simple $format
     * @param string $stream
     * @return Zend_Log_Writer_Stream
     */
    private function _getStreamWriter(Zend_Log_Formatter_Simple $format, $stream = '')
    {
        $writer = new Zend_Log_Writer_Stream($stream);
        $writer->setFormatter($format);
 
        return $writer;
    }
}

Jak widać oferuje ona możliwość logowania błędów na kilka sposobów: streaming do pliku, badź na ekran, JSON’em prosto do firebug’a (warunkiem odczytu jest zainstalowanie wtyczki FirePHP w Firefox) lub powiadomienie na email. W przykładzie część konfiguracji znajduje się w pliku /application/configs/application.ini, ktory ładowany jest poprzez Zend_Config_Ini do rejestru. Odczytany zostaje między innymi email do webmastera oraz do admina, czyli osób odpowiedzialnych za poprawne działanie aplikacji, ponadto zdefiniowane są takie parametry jak scieżka do pliku  z blędami (error.log) czy też format komunikatu z błędem.

Oczywiście trzeba tę funkcjonalność dołączyć  do projektu jako  nowy zasób. Nic prostszego – w pliku konfiguracyjnym /application/configs/application.ini dodajemy kolejny resource do listy:

resources.types      = "array()"
resources.view       = "array()"
resources.navigation = "array()"
resources.log        = "array()"

Dodatkowo musimy zdefniować ustawienia, które załączają odpowiednei przekazanie informacji o błędzie. Czyli znów w /application/configs/application.ini dopisujemy:

log.file      = true
log.firebug   = true
log.mail      = true
log.stdoutput = true

Rzecz jasna korzystamy z odpowiedniego typu logowania w zależności od potrzeb. Bardzo wygodnie jest w trakcie tworzenia kodu, czy też jego testowania, otrzymywać komunikaty do konsoli FF, wykorzystując firePHP, dodatkowo wygodne jest odczytywanie błędów z pliku error.log – otwieramy konsole i  dajemy polecenie:

# tail -f error.log

i mamy nasz system monitorujacy, w którym dokładnie zapisane jest gdzie wystąpił nieoczekiwany błąd (kupujemy telewizor LCD, który pokazuje te komunikaty przez cały czas :) oczywiście żartuje :) ).  Jeśli aplikacja jest już w wersji produkcyjnej to bardzo fajne jest rozwiązanie z przychodzącym emailem – można wtedy szybko interweniować.

Pamiętajmy jednak, że jeśli aplikacja jest już na produkcji, należy zablokować logowanie błędów do FireBug’a!!

Logowane błędy są tylko dla nas – developerów, adminów, …,  i mają stanowić wskazówkę do szybkiego ich  rozwiązania. Jeśli te informacje trafią do niepowołanych rąk to niestety możemy wskazać najsłabsze ogniwo naszej aplikacji, a tym samym stworzyć okazję do włamania się do niej np. hackerowi.

Kolejną sprawą jest miejsce, w którym nasz logger, trzymany w rejestrze, może dokonać zapisu błędu. Jednym z lepszym miejsc jest kontroler ErrorController.php i w nim metoda errorAction,  na którą domyślnie przekierowany jest błąd. Oczywiście gdzie będzie odbywało się logowanie błędów utożsamiony z tą linijką kodu:

if (Zend_Registry::isRegistered('log')) {
            $logger = Zend_Registry::get('log');
            if (!is_null($this->_error)) {
                $logger->log(
                    $this->_error->exception->getMessage() . "\n\n" .
                    $this->_error->exception->getTraceAsString(),
                    Zend_Log::ERR
                );
            }
        }

jest uzależnione od  potrzeb aplikacji i zaprojektowania jej przez programistę.

Taki sposób logowania błędów  można implementować w każdym projekcie – nie tylko opartym o Zend Framework. Po drobnych modyfikacjach, korzystając tylko z kilku komponentów (w wersji ZF 1.10.6), Log(20 plików), Wildfire (8 plików),  Json (14 plików), Config (8 plików) + Exception.php, można stworzyć taki sam system logowania błędów.