Heavymind
Gdyby ludzie rozmawiali tylko o tym, co rozumieją, zapadłaby nad światem wielka cisza.

19/08/2007

Zend Framework Tutorial - Rozwijanie aplikacji

Opublikowane jako: Off topic — Kubek Bartosz @ 18:21

Zend Framework Tutorial

cześć II

Rozwijanie aplikcaji

Kubek Bartosz, www.heavymind.net
Wersja dokumentu 1.3
Copyright © 2007 - 2008

Witam ponownie w drugiej odsłonie samouczka Zend Framework. W części tej zajmiemy się drobnymi sprawami, które przetrą nam drogę do bardziej konkretnych i pożytecznych zagadnień, którymi z kolei zajmiemy się w następnych częściach samouczka. Skupmy się jednak na tym, co mamy zamiar uczynić tym razem. Otóż architektura Zend Framework daje programiście wiele dróg rozwiązania danego zagadnienia. Wspaniałą rzeczą jest móc znać teoretycznie i praktycznie wszystkie te drogi, bo jest to najlepsza metoda do wyboru implementacji najwłaściwszego rozwiązania. Nawiązując do tego, chciałbym przedstawić propozycję małej reorganizacji i rozszerzenia naszej aplikacji testowej, którą to propozycję opieram właśnie na najlepszych praktykach jakie było mi dane poznać. Zmiany te przygotują naszą aplikację do dalszego rozwijania aplikacji w jeszcze bardziej zaawansowanej składniowo i technicznie konwencji (w stosunku do tego co miało miejsce już w pierwszej części samouczka).

UWAGA: Ten samouczek wymaga znajomości zagadnień z pierwszej części: Pierwsze kroki z Zend Framework, jak również posiadać należy działającą aplikację testową z pierwszej części samouczka. Przykłady zawarte w tym dokumencie, przetestowane zostały w oparciu o Zend Framework w wersji 1.5.1. Najprawdopodobniej będą działały także z nowszymi wersjami, jednak jest mało prawdopodobne, by prawidłowo mogły być uruchamiane z wersjami wcześniejszymi niż wersja 1.0.0.

Spis treści:

Katalog bazowy, a BaseURL – teoria, a praktyka

Chciałbym przedstawić tutaj „słowo” objaśnienia co do metod getBaseUrl() i setBaseUrl(), ponieważ jest ważne by dobrze rozumieć zagadnienie z nimi związane.

Tak więc w typowym dla aplikacji przypadku, DucumentRoot jest ustawiony tak by wskazywał na główny katalog projektu PHP, a inne katalogi (public, application) są jego podkatalogami. Bez względu na to czy bootstraper w tym przypadku znajduje się w głównym katalogu (przykładem jest aplikacja z pierwszej części Zend Framework Tutorial), czy w podkatalogu public (propozycja którą wprowadzamy w tej części), to zasadą jest, by pliki które inkludujemy w szablonach HTML (pliki CSS, JavaScript, obrazki) określały ścieżką relatywną do katalogu głównego. Przykład z naszego samouczka (część I):

plik: application/views/scripts/header.phtml

...
<title><?php echo $this->escape($this->title); ?></title>
<link rel="stylesheet" type="text/css" media="screen"
   href="<?php echo $this->baseUrl;?>/public/styles/style.css" />
</head>
...

Uzupełnieniem tego zapisu jest tutaj $this->baseUrl, ustawiony w pliku application/controllers/IndexsController.php:

plik: application/controllers/IndexsController.php:

...
public function init(){
   $this->view->baseUrl = $this->_request->getBaseUrl();
   Zend_Loader::loadClass('Album');
}
...

Rolę jaką odgrywają w ten czas metody getBaseUrl() i setBaseUrl(), służą temu, by powiedzieć Zend Framework, gdzie “niżej” od głównego katalogu - w podkatalogu katalogu DocumentRoot, mieści się tak naprawdę główny katalog projektu napisanego w Zend Framework. Innymi słowy, dzięki używaniu getBaseUrl() i setBaseUrl(), mówimy w którym podkatalogu naszej domeny, mieści się nasz projekt. Jest to między innymi po to, by metoda ta w połączeniu z ścieżką dostępu, np:

< a href="<?php echo $this->baseUrl;?>/public/styles/style.css" />

dała pełną informację, gdzie znajduje się plik względem katalogu DocumentRoot. Pamiętać należy jednak, że setBaseUrl() można wykorzystywać tylko po to by okreslić zagłębienie projektu. Nie można natomiast wskazywać na katalogi nadrzędne do DocumentRoot. Dobrze.

Istnieje też możliwość nietypowego skonfigurowania DocumentRoot’a, tak aby do razu wskazywał na katalog Public. W przypadku tym, w katalogu Public musi się znajdywać bootstrape’r, bo to tam serwer Apache będzie go wtedy szukał. To rozwiązanie jest również alternatywną propozycją tego samouczka. Odnośnie jednak spraw które opisałem wyżej - przypomnę: inkludowane pliki muszą być określoną ścieżką, zaczynającą się od głównego katalogu DocumentRoot’a. Tutaj syuacja się zmienia w stosunku do poprzedniego przykładu. Otóż “wnętrze” folderu Public jest DocumentRoot’em, tak więc ścieżka do pliku z poprzedniego przykładu, wyglądała by w taki sposób:

...
<title><?php echo $this->escape($this->title); ?></title>
<link rel="stylesheet" type="text/css" media="screen"
   href="<?php echo $this->baseUrl;?>/styles/style.css" />
</head>
...

Jak widzimy, określamy położenie pliku style.css już bez określania katalogu Public, a to dlatego, że DocumentRoot wskazuje na niego (a nie na katalog wyżej jak wcześniej). Aby ostatecznie rostrzygnąć różnicę, przedstawiam przykładowe konfiguracje, wraz z niezbędnymi zmianami w odpowiednich plikach:

Konfiguracja 1

Plik konfiguracyjny Apache:

<VirtualHost *:80>
   DocumentRoot D:/www/zftutorial
   ServerName www.zf.dom
</VirtualHost>

Plik index.php:

// nie wymaga zmian - "BaseUrl" ustawi się na pusty string

Plik .htaccess:

RewriteEngine On
RewriteRule .* index.php
php_flag magic_quotes_gpc ogg
php_flag register_globals off

Plik public/.htaccess:

RewriteEngine Off
allow from all

Wywoływany adres:

http://www.zf.dom

w efekcie wywoływany plik:

D:/www/zftutorial/index.php

Definicja ścieżki dostępu do przykładowego pliku CSS:

href="<?php echo $this->baseUrl;?>/public/styles/style.css"

która w efekcie wygeneruje się jako:

href="/public/styles/style.css"

Konfiguracja 2

Plik konfiguracyjny Apache:

<VirtualHost *:80>
   DocumentRoot D:/www
   ServerName localhost
</VirtualHost>

Plik index.php:

// nie wymaga zmian - "BaseUrl" zostanie automatycznie ustawione
// na wartość '/zftutorial/' przez obiekt rządania (Request)

Plik .htaccess:

RewriteEngine On
RewriteRule .* index.php
php_flag magic_quotes_gpc ogg
php_flag register_globals off

Plik public/.htaccess:

RewriteEngine Off
allow from all

Wywoływany adres:

http://localhost/zftutorial

w efekcie wywoływany plik:

D:/www/zftutorial/index.php

Definicja ścieżki dostępu do przykładowego pliku CSS:

href="<?php echo $this->baseUrl;?>/public/styles/style.css"

która w efekcie wygeneruje się jako:

href="/zftutorial/public/styles/style.css"

Konfiguracja 3

Plik konfiguracyjny Apache:

<VirtualHost *:80>
   DocumentRoot D:/www/zftutorial/public
   ServerName www.zf.dom
</VirtualHost>

Plik index.php:

// nie wymaga zmian - "BaseUrl" ustawi się na pusty string

Plik .htaccess:

jest niepotrzebny!

Plik public/.htaccess:

RewriteEngine On
RewriteRule !\.(js|ico|gif|jpg|png|css)$ index.php
php_flag magic_quotes_gpc ogg
php_flag register_globals off

Wywoływany adres:

http://www.zf.dom

w efekcie wywoływany plik:

D:/www/zftutorial/public/index.php

Definicja ścieżki dostępu do przykładowego pliku CSS:

href="<?php echo $this->baseUrl;?>/styles/style.css"

która w efekcie wygeneruje się jako:

href="/styles/style.css"

Konfiguracja 4

Plik konfiguracyjny Apache:

<VirtualHost *:80>
   DocumentRoot D:/www/zftutorial
   ServerName www.zf.dom
</VirtualHost>

Plik index.php:

// nie wymaga zmian - "BaseUrl" ustawi się na pusty string

Plik .htaccess:

RewriteEngine On
RewriteRule .* public/index.php
php_flag magic_quotes_gpc ogg
php_flag register_globals off

Plik public/.htaccess:

RewriteEngine Off
allow from all

Wywoływany adres:

http://www.zf.dom

w efekcie wywoływany plik:

D:/www/zftutorial/public/index.php

Definicja ścieżki dostępu do przykładowego pliku CSS:

href="<?php echo $this->baseUrl;?>/public/styles/style.css"

która w efekcie wygeneruje się jako:

href="/public/styles/style.css"

Konfiguracja 5

Plik konfiguracyjny Apache:

<VirtualHost *:80>
   DocumentRoot D:/www
   ServerName localhost
</VirtualHost>

Plik index.php:

// z tym przypadkiem obiekt rządania już sobie samodzielnie nie poradzi. Ustawiamy więc ręcznie:
$frontController->setBaseUrl('/zftutorial/');

Plik .htaccess:

RewriteEngine On
RewriteRule .* public/index.php
php_flag magic_quotes_gpc ogg
php_flag register_globals off

Plik public/.htaccess:

RewriteEngine Off
allow from all

Wywoływany adres:

http://localhost/zftutorial

w efekcie wywoływany plik:

D:/www/zftutorial/public/index.php

Definicja ścieżki dostępu do przykładowego pliku CSS:

href="<?php echo $this->baseUrl;?>/public/styles/style.css"

która w efekcie wygeneruje się jako:

href="/zftutorial/public/styles/style.css"

W kazdym z w/w przypadków, zachowanie aplikacji jest prawidłowe. Metody getBaseUrl() i setBaseUrl() odgrywają jeszcze pożyteczną rolę przy tworzeniu hyper-linków (odnośników), jednak tutaj sytuacja jest prosta i nie wymagająca żadnych zmian w żadnym z przypadków. Dlatego więc pozwolę sobie nie komentować tego.

Wracając do rzeczy - na potrzeby tej części samouczka, przyjmiemy w użycie konfigurację 5.

Przeniesienie bootstraper’a

Praktyki programowania w języku PHP z ubiegłych lat dopuszczały, by aplikacja rozpoczynała się plikiem index.php, umiejscowionym w “najwyższym”, głównym katalogu dokument-root’a. Każdy inny plik wchodzący w skład aplikacji, znajdywać się miał bądź również w głównym katalogu, bądź w podkatalogach. Rozwiązanie to niosło z sobą zagrożenie bezpieczeństwa tych elementów aplikacji, które nie powinny być widoczne z poziomu przeglądarki internetowej użytkownika, ponieważ każdy plik był osiągalny do wywołania przez adres URL przeglądarki w ten sam sposób, jak wywołujemy index.php. Zaczęto stosować pliki .htaccess, w których odpowiednią dyrektywą blokowano dostęp z zewnątrz do krytycznych podkatalogów. Rozwiązanie to jest dobre i często wykorzystywane (to samo wykonaliśmy w pierwszej części samouczka). Jednak posunięto się dalej w tej myśli i zaproponowano, by sam plik index.php umieścić w podkatalogu (np: public) katalogu głównego ( document-root’a), równocześnie z wszystkimi plikami (umiejscowionymi w podkatalogach katalogu public), które powinny być dostępne z poziomu przeglądarki internetowej (tj. obrazki, pliki JavaScript, pliki stylów). Z kolei to co jest jądrem aplikacji (tj. silnik Zend Framework, katalog aplikacji, katalog z konfiguracją, katalog z plikami cache) może się znajdować w równorzędnych dla public katalogach, które to już będą wtedy poza katalogiem document-root’a, czyli poza public.

Poniższy przykład przedstawia tę ideę:

zf-tutorial/
   /application    < -- poza dostępem
      /controllers
      /models
      /views
         /filters
         /helpers
         /scripts
   /library        <-- poza dostępem
      /Zend
   /public         <-- document root (dostępny)
      /images
      /scripts
      /styles

Jest to bardzo “czyste” rozwiązanie i nie wymaga pamiętania o konfigurowaniu licznych plików .htaccess. Proces udostępnienia dla przeglądarki katalogu public odbywa się automatycznie poprzez ustawienia na niego dyrektywy DocumentRoot i sekcji w konfiguracji wirtualnego hosta. Z kolei reszta katalogów i plików, która nie jest uwzględniona w tej konfiguracji, domyślnie jest niedostępna dla serwera Apache.

Niestety w bardzo wielu przypadkach, providerzy usług hostingowych nie udostępniają możliwości wskazania katalogu, na który host ma się kierować. Przez to niestety, ustawienie konfiguracji, którą opisałem wyżej, jest niemożliwe. Niemniej jednak warto przynajmniej symulować taką strukturę, po to by być przygotowanym w razie napotkania na takiego usługodawcę, który udostępnia taką możliwość. Przygotowanie wtedy aplikacji pod tą strukturę, to zaledwie odpowiednia konfiguracja wirtualnego hosta oraz usunięcie dyrektyw dostępowych w plikach .htaccess (lub usunięcie tych plików, jeśli nic innego nie zawierają). Zróbmy więc to - przygotujmy naszą aplikację pod taką strukturę. Jest to de facto opis czynności, które odpowiadają „Konfiguracji 5” z poprzedniego rozdziału.

Krokiem pierwszym będzie przeniesienie pliku bootstraper’a (index.php) z katalogu głównego, do katalogu public. Tak więc to jest stan z przed zmiany:

zf-tutorial/
   /application
   /library
   /public
      /images
      /scripts
      /styles
   index.php

i po zmianie:

zf-tutorial/
   /application
   /library
   /public
      /images
      /scripts
      /styles
      index.php < -- znajduje się w public

Teraz musimy poprawić odnośniki do katalogów, które znajdują się w bootstraper’ze. Wystarczy dodać dodatkową ‘kropkę’, obok już istniejącej, w każdej z nich, tak by powstały dwie kropki, jedna obok drugiej. Przyjmując także, że naszą aplikację uruchamiamy nadal adresem URL: http://localhost/zftutorial, musimy dodać jedną linię z metodą setBaseUrl(). Zróbmy to:

plik: public/index.php

<?php
error_reporting(E_ALL|E_STRICT);
date_default_timezone_set('Europe/London');

set_include_path(’.’ . PATH_SEPARATOR . ‘../library’
   . PATH_SEPARATOR . ‘../application/models/’
   . PATH_SEPARATOR . get_include_path());

include “Zend/Loader.php”;

Zend_Loader::loadClass(’Zend_Controller_Front’);
Zend_Loader::loadClass(’Zend_Config_Ini’);
Zend_Loader::loadClass(’Zend_Registry’);
Zend_Loader::loadClass(’Zend_Db’);
Zend_Loader::loadClass(’Zend_Db_Table’);

// load configuration
$config = new Zend_Config_Ini(’../application/config.ini’, ‘general’);
$registry = Zend_Registry::getInstance();
$registry->set(’config’, $config); 

// setup database
$db = Zend_Db::factory( $config->db->adapter,
$config->db->config->toArray() );
Zend_Db_Table::setDefaultAdapter($db);

// setup controller
$frontController = Zend_Controller_Front::getInstance();
$frontController->throwExceptions(true);
$frontController->setBaseUrl(’/zftutorial/’);
$frontController->setParam(’useDefaultControllerAlways’, true);
$frontController->setControllerDirectory(’../application/controllers’);

// run!
$frontController->dispatch();

Zanim sprawdzimy czy aplikacja nadal nam działa, musimy jeszcze uwzględnić naszą zmianę w pliku wywołującym naszego bootstraper’a, czyli .htaccess z głównego katalogu:

plik: .htaccess:

RewriteEngine On
RewriteRule .* public/index.php

php_flag magic_quotes_gpc ogg
php_flag register_globals off

Linie, które uległy zmianie zostały wytłuszczone. Zmiany te powinny pozwolić już uruchomić ponownie naszą aplikację w przeglądarce internetowej.

Auto ładowanie klas

Temat dość prosty. Otóż PHP udostępnia pewną „magiczną” funkcję, którą można wykorzystać do celu automatycznego inkludowania plików klas, w zamiast każdorazowego ładowania manualnego z osobna każdej klasy. Jest to niesamowicie wygodne rozwiązanie, a że dotychczas nie napotkałem żadnego argumentu przeciw temu, polecam go i stosuję. W pliku bootstrapera usuwamy więc tę część kodu:

plik: public/index.php

...
include "Zend/Loader.php";
Zend_Loader::loadClass('Zend_Controller_Front');
Zend_Loader::loadClass('Zend_Config_Ini');
Zend_Loader::loadClass('Zend_Registry');
Zend_Loader::loadClass('Zend_Db');
Zend_Loader::loadClass('Zend_Db_Table');
...

i zastępujemy go takim wywołaniem:

...
//If class not found instanciate it automatically
require_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();
...

W ten oto sposób wywołanie jakiejkolwiek biblioteki Zend Framework, lub naszej własnej pociągnie za sobą w pierwszym kroku automatyczne inkludowanie odpowiedniego pliku klasy.

Pozwólmy więc sobie w tym miejscu zmodyfikować pod tym kątem także nasz IndexController, tak by nie potrzebnie nie inkludował już klas poleceniem Zend_Loader::loadClass(). Usuńmy każde wystąpienie wspomnianego wywołania w naszym kontrolerze (linie które należy usunąć zostału wytłuszczone!):

plik: application/controllers/IndexController.php

<?php
class IndexController extends Album_Controller_Action{

   public function init() {
      parent::init();
      $this->view->baseUrl = $this->_request->getBaseUrl();
      Zend_Loader::loadClass(’Album’);
   }

   function indexAction() {
      $this->view->title = “My Albums”;

      $album = new Album();
      $this->view->albums = $album->fetchAll();
   }

   function addAction() {
      $this->view->title = “Add New Album”;

      if ($this->_request->isPost()) {
         Zend_Loader::loadClass(’Zend_Filter_StripTags’);
         $filter = new Zend_Filter_StripTags();

         $artist = $filter->filter($this->_request->getPost(’artist’));
         $artist = trim($artist);
         $title = trim($filter->filter(
         $this->_request->getPost(’title’)));

         if ($artist != ” && $title != ”) {
            $data = array(
               ‘artist’ => $artist,
               ‘title’ => $title,
            );
            $album = new Album();
            $album->insert($data);
            $this->_redirect(’/');
            return;
         }
      }
      // set up an “empty” album
      $this->view->album = new stdClass();
      $this->view->album->id = null;
      $this->view->album->artist = ”;
      $this->view->album->title = ”;

      // additional view fields required by form
      $this->view->action = ‘add’;
      $this->view->buttonText = ‘Add’;
   }

   function editAction() {
      $this->view->title = “Edit Album”;
      $album = new Album();

      if ($this->_request->isPost()) {
         Zend_Loader::loadClass(’Zend_Filter_StripTags’);
         $filter = new Zend_Filter_StripTags();

         $id = (int)$this->_request->getPost(’id’);
         $artist = $filter->filter($this->_request->getPost(’artist’));
         $artist = trim($artist);
         $title = trim($filter->filter(
         $this->_request->getPost(’title’)));

         if ($id !== false) {
            if ($artist != ” && $title != ”) {
               $data = array(
                  ‘artist’ => $artist,
                  ‘title’ => $title,
               );
               $where = ‘id = ‘ . $id;
               $album->update($data, $where);

               $this->_redirect(’/');
               return;
            } else {
               $this->view->album = $album->fetchRow(’id=’.$id);
            }
         }
      } else {
         // album id should be $params[’id’]
         $id = (int)$this->_request->getParam(’id’, 0);
         if ($id > 0) {
            $this->view->album = $album->fetchRow(’id=’.$id);
         }
      }
      // additional view fields required by form
      $this->view->action = ‘edit’;
      $this->view->buttonText = ‘Update’;
   } 

   function deleteAction() {
      $this->view->title = “Delete Album”;
      $album = new Album();

      if ($this->_request->isPost()) {
         Zend_Loader::loadClass(’Zend_Filter_Alpha’);
         $filter = new Zend_Filter_Alpha();

         $id = (int)$this->_request->getPost(’id’);
         $del = $filter->filter($this->_request->getPost(’del’));
         if ($del == ‘Yes’ && $id > 0) {
            $where = ‘id = ‘ . $id;
            $rows_affected = $album->delete($where);
         }
      } else {
         $id = (int)$this->_request->getParam(’id’);
         if ($id > 0) {
            // only render if we have an id and can find the album.
            $this->view->album = $album->fetchRow(’id=’.$id);
            if ($this->view->album->id > 0) {
               // render template automatically
               return;
            }
         }
      }
      // redirect back to the album list unless we have rendered the view
      $this->_redirect(’/');
   }
}
?>

Stworzenie własnego kontrolera frontowego

Zend Framework, jako w pełni obiektowy framework daje nam nieograniczone możliwości. Wykorzystując obiektowość PHP5 możemy rozszerzać możliwości Zend Framework wedle naszych potrzeb. I tak też zrobimy.

Kierując się własnym instynktem, dochodzę do wniosku, że plik bootstraper’a, powinien być jak najlżejszy i najprostszy. Pragnę by jego prostota wskazywała bardziej na to, że jest on zaczątkiem aplikacji napisanej w oparciu o Zend Framework, niż aby on sam był znaczącą częścią logiki aplikacji.

To czego chcę dokonać ma także inne uzasadnienie. Rozwijając aplikację do postaci, w której zapewne mielibyśmy już po kilka kontrolerów, zapewne natrafilibyśmy na sytuację kiedy to chcielibyśmy wykorzystywać jakiś wspólny obiekt (np. Zend_Config, Zend_Session) w różnych akcjach różnych kontrolerów. W zamiast każdorazowego odwoływania się do nowej instancji potrzebnego nam obiektu, wolelibyśmy za pewne jeden wspólny obiekt dostępny z poziomu dowolnej akcji i kontrolera. Moglibyśmy więc skorzystać z rejestru Zend_Registry, który jest odpowiednim kontenerem na takie obiekty. Chcę jednak przypomnieć, że kontener ten jest czymś na wzór zmiennych globalnych. Oznacza to, że dowolna cześć aplikacji może mieć dostęp do jego zawartości. Należy więc korzystać z tego z umiarem, mając na uwadze względy bezpieczeństwa danych jakimi zarządza nasza aplikacja.

Biorąc to wszystko pod uwagę, chciałbym abyśmy zaimplementowali własną klasę głównego kontrolera akcji, po którym będą dziedziczyć wszystkie nasze kontrolery poszczególnych akcji, a sam nasz kontroler będzie dziedziczył (będzie “dzieckiem”) głównego kontrolera Zend_Controller_Action. Dla jasności przypominam, że w tym momencie nasz kontroler akcji IndexController dziedziczy bezpośrednio właśnie po Zend_Controller_Action. Gdybyśmy mieli więcej kontrolerów, to zapewne także dziedziczyły bo one po tym głównym kontrolerze. To czego chcemy, to „włożyć” obiekt pomiędzy główny kontroler akcji, a kontrolery poszczególnych akcji, tak aby te ostatnie dziedziczyły funkcjonalność z głównego kontrolera akcji Zend_Controller_Action, mając za pośrednika nasz kontroler frontowy – nazwijmy go Album_Controller_Action (czyli główny kontroler applikacji do zarządzania albumami CD). Implementując funkcjonalność i własności wspólne do naszego Album_Controller_Action, będziemy mogli mieć do nich dostęp z poziomu wszystkich „dzieci” - poszczególnych kontrolerów akcji.

Czas na konkrety. Kierując się konwencją Zend Framweork co do nazewnioctwa klas i ich umiejscowienia w strukturze katalogów, klasę Album_Controller_Action zapiszemy w katalogu: /library/Album/Controller/Action.php

Oto ta klasa:

plik: /library/Album/Controller/Action.php

<?php
abstract class Album_Controller_Action extends Zend_Controller_Action {

   public function init() {
   }

}
?>

Po kolei więc przyjrzyjmy się temu plikowi:

abstract class Album_Controller_Action extends Zend_Controller_Action {}

W taki sposób definiujemy nasz kontroler frontowy, który jako abstrakcyjny, może być użyty tylko poprzez „generalizację” przez klasę-dziecko (użyty przez klasę-dziecko, która dziedziczy po tej klasie).

public function init() {}

Tę funkcję już znamy z poprzedniej części samouczka, jako metodę występująca w konkretnym kontrolerze (np. IndexController). Wywoływana jest ona zawsze i to na samym początku procesu, przed wywołaniem jakiejkolwiek akcji. Tutaj jest podobnie, tyle że funkcja init() będzie wywoływana nie tyle niezależnie od wywołanej akcji w kontrolerze, co nawet niezależnie od wywołanego kontrolera (będzie wywoływana dla wszystkich kontrolerów, ponieważ wszystkie kontrolery będą dziedziczyć po tej klasie). W tej właśnie funkcji będziemy implementować funkcjonalność i ustawiać własności, które będą przeznaczone na wspólny użytek wszystkich kontrolerów. I to wszystko na ten moment.

Jednak co się stanie, gdy w jednym z naszych kontrolerów akcji znajdzie się także metoda init() ? (uwaga! tak właśnie jest w naszym przypadku. Kontroler IndexController zawieta metodę init() ). Już wyjaśniam.

W sytuacji tej zaistniał by konflikt pomiędzy tą metodą init() w IndexController, a metodą init() rodzica, którym jest Album_Controller_Action. W językach obiektowych, takim jak PHP5, sytuacja taka nazywa się „przeciążeniem metody” i jest rozwiązywana w taki sposób, że jeśli dwie takie same metody występują w klasie rodzica i dziecka, górę bierze metoda z klasy dziecka. Tak wiec metoda init() w Album_Controller_Action, została by „nadpisana” metodą init() w klasie dziecka IndexController. I tylko ta metoda w klasie dziecka została by wykonana, zaś metoda init() w klasie rodzica nie została by w ogóle uruchomiona – wtedy jakby nie istnieje dla interpretera PHP5.

Istnieje jednak elegancki - obiektowy (nie inaczej) sposób rozwiązywania takich konfliktów. Otóż metody, które przeciążają (czyli np. metoda init() w IndexController ) mają możliwość wywołać tę samą metodę swego rodzica konstrukcją:

parent::init();

Ni mniej, ni więcej, oznacza to uruchomienie metody rodzica, po wykonaniu której, sterowanie wraca w miejsce tego wywołania.

Wykonajmy więc kolejny krok – uwzględnijmy omówione wyżej zagadnienia dziedziczenia po własnym kontrolerze akcji, jak i wywoływanie przeciążonych metod w naszym IndexController

plik: /application/controllers/IndexController.php

<?php
class IndexController extends Album_Controller_Action{

   public function init() {
      parent::init();
      $this->view->baseUrl = $this->_request->getBaseUrl();
   }

   function indexAction() {
   …

Zmiany zaznaczone są na czarno. Bardzo prosto – prawda? Zastąpiliśmy używany wcześniej Zend_Controller_Action, na rzecz Album_Controller_Action. Z kolei rozwiązanie problemu przeciążenia metody init() zawarło się także w podmianie jednej linii.

I to wszystko na ten rozdział. Zauważmy, że w tym momencie otwarliśmy sobie furtkę dodatkowych możliwości : implementacji funkcjonalności i własności w naszym własnym głównym kontrolerze akcji, który nazywa się Album_Controller_Action, który to uwspólni je dla całej naszej aplikacji, dla wszystkich kontrolerów jakie posiadamy i posiadać będziemy. Wystarczy, że kontrolery te będą dziedziczyć po naszym głównym kontrolerze, tak jak to zrobiliśmy powyżej.

Możesz wykonać teraz próbę ponownego uruchomienia naszej aplikacji – wszystko powinno działać jak dawniej. Jeśli rozdział ten wydaje się dla Ciebie niejasny, nie przejmuj się i spróbuj przeczytać do raz jeszcze. Dotykamy się w nim bardziej teorii programowania obiektowego, niż samego pisania kodu. Dlatego też zrozumienie tego wymaga więcej czasu.

Odchudzenie bootstrap’a

Plik startowy index.php, jako że jest plikiem, który bierze udział w każdym wywołaniu jakiejkolwiek akcji jakiegokolwiek kontrolera w naszej aplikacji, należy utrzymywać go w jak najlepszej czytelności i prostocie. Na tym etapie przeprowadzimy proste przeniesienia kodu, tak aby możliwie maksymalnie uprościć jego treść. Bierzmy się więc do pracy.

W pierwszym kroku wytniemy inicjalizację obiektu konfiguracujnego i przeniesiemy go do Album_Controller_Action. Przygotujmy najpierw grunt w tym drugim pliku.

Na potrzeby naszego przykładu, nie musimy korzystać z rejestru Zend_Registry, by przetrzymywać w nim obiekt konfiguracji. Do tego celu możemy stworzyć własność chronioną $obConfig w klasie Album_Controller_Action, która to własność, dzięki dziedziczeniu przez klasy-dzieci, tj. poszczególne kontrolery akcji, będzie dostępna dla każdej z nich.

Stwórzmy więc tę własność:

plik: library/Album/Controller/Action.php

abstract class Album_Controller_Action extends Zend_Controller_Action {

   protected $obConfig;

   public function init() {
…

Teraz wytnijmy następujący blok kodu z bootstraper’a:

plik: public/index.php

...
// load configuration
$config = new Zend_Config_Ini('../application/config.ini', 'general');
$registry = Zend_Registry::getInstance();
$registry->set('config', $config);
...

i zapiszmy go w Album_Controler_Action, jako pierwszą czynność w metodzie init(). Jednak proszę zwróć uwagę na to, że nie zapisujemy już obiektu do rejestru Zend Framework. W zamiast tego bezpośrednio zapisujemy obiekt konfiguracyjny w własności chronionej $this->obConfig :

plik: library/Album/Controller/Action.php

public function init() {
   // load configuration
   $this->obConfig = new Zend_Config_Ini(’../application/config.ini’, ‘general’);
}

Krok drugi. Przenosimy podanie ścieżki do modelów z bootstrapera do głównego kontrolera akcji. Wycinamy w taki spodób linijkę kodu, by z takiej postaci bloku kodu:

plik: public/index.php

set_include_path('.' . PATH_SEPARATOR . '../library'
   . PATH_SEPARATOR . '../application/models/'
   . PATH_SEPARATOR . get_include_path());

pozostawić taką:

plik: public/index.php

set_include_path('.' . PATH_SEPARATOR . '../library'
   . PATH_SEPARATOR . get_include_path());

Wyciągniętą teraz linijkę kodu(dodatkowo odpowiednio sformatowaną), wrzucamy ponownie do metody init().

plik: library/Album/Controller/Action.php

public function init() {
   // load configuration
   $this->obConfig = new Zend_Config_Ini('../application/config.ini', 'general');

   set_include_path(’.’ . PATH_SEPARATOR . ‘../application/models/’
      . PATH_SEPARATOR . get_include_path());
}
…

Krok trzeci i ostatni: inicjalizację bazy danych i obiektu Zend_Db_Table, także przenosimy z bootstraper’a do kontorlera głównego.

Więc wycinamy ten blok:

plik: public/index.php

// setup database
$db = Zend_Db::factory( $config->db->adapter,
$config->db->config->toArray() );
Zend_Db_Table::setDefaultAdapter($db);

I wstawiamy jako kolejny blok w init(), jednak z modyfikacją uwzględniającą inną nazwę zmiennej obiektu konfiguracyjnego:

plik: library/Album/Controller/Action.php

   set_include_path('.' . PATH_SEPARATOR . '../application/models/'
      . PATH_SEPARATOR . get_include_path());

   // setup database
   $db = Zend_Db::factory(
      $this->obConfig->db->adapter,
      $this->obConfig->db->config->toArray()   );
   Zend_Db_Table::setDefaultAdapter($db);
}
…

I to wszystko. Po tych wszystkich zabiegach elementarnych, tak powinny wyglądać nasze pliki:

plik: public/index.php

<?php
   error_reporting(E_ALL|E_STRICT);
   date_default_timezone_set('Europe/London');

   set_include_path('.' . PATH_SEPARATOR . '../library'
      . PATH_SEPARATOR . get_include_path());

   //If class not found instanciate it automatically
   require_once 'Zend/Loader.php';
   Zend_Loader::registerAutoload();

   // setup controller
   $frontController = Zend_Controller_Front::getInstance();
   $frontController->throwExceptions(true);
   $frontController->setParam('useDefaultControllerAlways', true);
   $frontController->setControllerDirectory('../application/controllers');

   // run!
   $frontController->dispatch();

plik: library/Album/Controller/Action.php

<?php
abstract class Album_Controller_Action extends Zend_Controller_Action {

   protected $obConfig;

   public function init() {
      // load configuration
      $this->obConfig = new Zend_Config_Ini('../application/config.ini', 'general');

      set_include_path('.' . PATH_SEPARATOR . '../application/models/'
         . PATH_SEPARATOR . get_include_path());

      // setup database
      $db = Zend_Db::factory(
         $this->obConfig->db->adapter,
         $this->obConfig->db->config->toArray()   );
      Zend_Db_Table::setDefaultAdapter($db);
   }
}
?>

Czysto i zwięźle. Ostatnią rzeczą jaką chciałbym abyśmy wykonali by upiększyć strukturę naszej palikacji, to:

Przeniesienie maksimum do pliku konfiguracyjnego

Dotychczas nasz plik konfiguracyjny miewał się bardzo dobrze – bez żadnych zmian i kłopotów. Przyszła więc także pora na niego. Celem naszym tym razem, będzie przeniesienie pewnej zmiennej do pliku konfiguracyjnego config.ini, jak również przygotowanie tego pliku pod przyszłe prawdopodobne potrzeby aplikacji. Mam tutaj na celu zaprezentowanie wzorcowego pliku konfiguracyjnego, który powinien sam w sobie być konfiguracją aplikacji, jak i opisem tej konfiguracji. Jest to bardzo ważne aby parametry jakie mają wpływ na naszą aplikację były pogrupowane tematycznie, jak i możliwie najbardziej opisane. Zawsze trzeba mieć w pamięci, że za parę miesięcy czy lat, osoba która będzie przyglądać się aplikacji którą piszemy (to możemy być nawet my sami), musi mieć możliwość zrozumienia jej w jak najkrótszym czasie. Dobrze napisane pliki konfiguracyjne są wielkim wtedy ułatwieniem.

Zacznijmy więc od konkretów. Niżej znajduje się listing nowego pliku konfiguracyjnego. Proszę pamiętaj drogi mój czytelniku, by dane konfiguracyjne dostosować do własnego środowiska testowego.

plik: appllication/config.ini:

[general]
;######################################################################
;# Envoirment Config
;#
;#########################################################
   server.site_url      = http://zf2.dom

;#########################################################
;# Database Config
;#
;# dbhost:    SQL Database Hostname
;# dbuname:   SQL Username
;# dbpass:    SQL Password
;# dbname:    SQL Database Name
;#########################################################
    db.adapter = PDO_MYSQL
    db.config.host = localhost
    db.config.username = albumap
    db.config.password = 123qwe
    db.config.dbname = albumdb

;#########################################################
;# Debug Configuration
;#
;#########################################################
   debug.status      = true

;#########################################################
;# General Site Configuration
;#
;#########################################################
   general.admin_mail   = kubek.bartosz@gmail.com

;#########################################################
;# General Paths Configuration
;#
;#########################################################
   path.models            = ../application/models/
   path.images            = ../public/img/

W tym momencie, taka konfiguracja może wydawać się nieco na wyrost. Jednak bardzo szybko w raz z rozwojem aplikacji okazuje się, że łatwość poruszania się po pliku konfiguracyjnym jest rzeczą kluczową.

To co nowego nas interesuje, to ścieżka do Modelów, która znalazła się w nowym pliku konfiguracyjnym i którą my wykorzystamy w głównym kontrolerze.

Zmodyfikujmy więc go:

plik: library/Album/Controller/Action.php

public function init() {
   // load configuration
   $this->obConfig = new Zend_Config_Ini('../application/config.ini', 'general');

   set_include_path(’.’ . PATH_SEPARATOR . $this->obConfig->path->models
      . PATH_SEPARATOR . get_include_path());

   // setup database
   …

W taki oto sposób kolejny parametr konfiguracyjny znalazł się w pliku konfiguracyjnym. Gorąco polecam, aby zawsze wprowadzając jakiś nowy parametr, rozwarzyć czy jego miejsce nie powinno się znajdywać w pliku ini.

Podsumowanie

W tym momencie nasza aplikacja jest po raz kolejny gotowa do uruchomienia. I choć po całej tej części samouczka absolutnie nic się nie zmieniło w sposobie wyświetlania i funkcjonowania aplikacji od strony tego co widzimy w przeglądarce internetowej, to zmieniło się bardzo dużo od wnętrza strony. Zwiększyliśmy bezpieczeństwo aplikacji, poprzez wydzielenie tylko jednego katalogu public jako dostępnego z poziomu przeglądarki internetowej. Stworzyliśmy własną klasę głównego kontrolera akcji, dzięki czemu otworzyliśmy sobie możliwość czerpania z tego co daje nam architektura obiektowa – w tym przypadku „dziedziczenie”. Sprawiliśmy że nasz bootstraper jest maksymalnie uproszczony, a ładownie nowych klas odbywa się automatycznie. W końcu zaimplementowaliśmy rozbudowany plik konfiguracyjny, co również daje man możliwości na przyszłość. Wszystko to jest bardzo ważne, ponieważ rozwija nasze możliwości jako architektów systemów i daje większe pole do wykorzystania swojej wyobraźni. Praktyczne zalety tych rozwiązań przyjdzie nam poznać bliżej, przy okazji jednej z kolejnych części Zend Framework pt: Zend View - Implementacja Smarty.

Repozytorium SVN

Opisywana wyżej aplikacja testowa jest dostępna do pobrania za pośrednictwem systemu kontroli wersji Subversion (SVN), na serwerach udostępnionych przez usługę Google Code. Kod źródłowy kolejnych wersji tego samouczka, które to powstawały ze względu na kolejne wersje Zend Framework, został odpowiednio rozdzielony, na tzw. tagi. Tak więc są nimi:

Aby skorzystać z w/w repozytoriów, należy użyć jednego z istniejących klientów SVN, tj. np.: KSVN (dla KDE linux), TortoiseSVN (dla windows), lub po prostu CLI klienta SVN w *nix systemach.

Kod aplikacji zawarty w repozytorium można także przeglądać poprzez przeglądarkę internetową, umieszczając adres repozytorium w pasku adresu.

Pozdrawiam

Kubek Bartosz



Komentarze: 50 »

  1. […] has been done. The second part of Zend Framework Tutorial : “Application Extending” has been finished and published. I’d like to invite everyone to enjoy it (sorry, […]

    Pingback od Second part of Zend Framework Tutorial — 19/08/2007 @ 18:55

  2. […] stało się. Druga część Zend Framework Tutorial, pt.: Rozwijanie Aplikacji została ukończona i opublikowana. Serdecznie więc zapraszam do zapoznania się z lekturą tego […]

    Pingback od Zend Framework Tutorial - Rozwijanie aplikacji — 19/08/2007 @ 18:56

  3. Kawał dobrej roboty odwaliłeś, czekam na kolejną część ze Smarty :)

    Komentarz od Kam — 23/08/2007 @ 21:27

  4. “Tak wiec metoda init() w Album_Controller_Action, została by „nadpisana” metodą init() w klasie dziecka IndexController. I tylko ta metoda w klasie dziecka została by wykonana, zaś metoda init() w klasie rodzica nie została by w ogóle uruchomiona – wtedy jakby nie istnieje dla interpretera PHP5.”

    Ale chłopie namieszałeś. Po co wprowadzasz jakieś init_child(), skoro można to zastąpić prostym parent::init() ?

    Komentarz od Łucio — 25/08/2007 @ 21:11

  5. Eeee tam… Taka kosmetyka. Nic ciekawego powiem szczerze :P . Czekam za to z niecierpliwością na tutorial dot. implementacji layout’ów za pomocą Helper’ów.

    Komentarz od aaaa — 25/08/2007 @ 22:50

  6. @Łucio : masz oczywiście rację, że można w taki sposób to rozwiązać. Jeśli więc ktoś preferuje przeciążanie z wywoływaniem rodzica parent::init(), to musi pamiętać jak to robić. Jeśli ktoś preferuje nie wchodzić “w drogę” stałej funkcji inicjalizacyjnej rodzica, będzie musiał pamiętać by zapisywać każdą funkcję inicjalizacyjną jako init_child(). Dowolność w wyborze drogi to jest kwestią gustu. Mnie szczerze oba rozwiązania przypadają do gustu, choć bardziej “obiektowe” wydaje się być użycie “parent::”. Dziękuję za sugestię.

    @aaaa: otóż prawda, dla osób czujących się swobodnie w obiektowym programowaniu, artykuł ten może być nic nie wnoszący. Jednak tematyka i forma tego artykułu skupia się wokół osób, które być może po raz pierwszy spotkają się z takimi rozwiązaniami. Dla seniorów nie pisze się tutorialów - oni je piszą ;)

    Komentarz od Kubek Bartosz — 25/08/2007 @ 23:37

  7. a dla mnie to bardzo dobry tutorial.
    Niezależnie od tego czy ktoś dopiero zaczyna z OOP czy zęby na tym zjadł miło jest zobaczyć że ktoś myśli podobnie i rozwiązuje te same problemy w podobny sposób. Ja osobiście oprócz implementacji smarty czekam na temat autoryzacji.
    Póki co pozdrawiam autora i trzymam kciuki za kolejne tutki.

    Komentarz od NorthPole — 31/08/2007 @ 00:39

  8. Genialny tutorial… czekam na kolejne

    Komentarz od MArcin4444 — 02/09/2007 @ 21:30

  9. Swoją drogą dobrze będzie poprawić to : “Rozwijanie aplikcaji”. Tak to bardzo fajny i przydatny tutorial. Wielkie brawo!!!

    Komentarz od CrOOgie — 05/09/2007 @ 21:21

  10. Konfiguracja 2
    jest:
    href=”baseUrl;?>/public/styles/style.css”

    powinno być:
    href=”baseUrl;?>/public/styles/style.css”

    Taka kosmetyka :)

    Komentarz od Mario — 09/09/2007 @ 02:10

  11. cd… widze ze w moim poście też tą część zjadło. Ale wiadomo o co chodzi.

    Komentarz od Mario — 09/09/2007 @ 02:11

  12. @mario: Dziękuję za uwagę, już poprawiam…

    Komentarz od Kubek Bartosz — 11/09/2007 @ 19:49

  13. fajna stronka, czekam z niecierpliwoscia na kolejne tutoriale.

    Pozdrawiam

    Komentarz od Arek — 12/09/2007 @ 01:16

  14. Bardzo fajny art.
    Pozdrawiam.

    Komentarz od Witkacy — 01/10/2007 @ 00:01

  15. Mam problem z wydajnością…
    Dlaczego u mnie najprostszy skrypt generuje się aż 0.5s. Wyświetlenie pustego widoku, bez zapytan do bazy. Jedynie zaladowanie klas już daje taki wynik.
    Dzieje się tak na dwóch serwerach a na jednym jest dużo szybciej…
    Nie macie czasem takich problemów ?
    Może mam coś źle ustawione w konfiguracji apache, php ?
    Jeśli ktoś ma podobny problem proszę o informacje.

    Komentarz od slave — 23/11/2007 @ 17:37

  16. @slave: Przede wszystkim Zend Framework sam w sobie jest sporą “armatą”, przez co na większości serwerów, wyświetlenie strony “Hello Word” zajmuje około 2MB pamięci! Z tego powodu wnioskował bym, że wygenerowanie 2MB instancji klas zajmuje różnym serwerom różny przedział czasu, np: 0.5 sec. Swoją drogą 2MB to dziś żadna ilość, a 0.5sec dla skryptu PHP to trochę dużo. Chyba szukał bym winy na drodze sprzętowej: HDD-Procesor-RAM

    Komentarz od Kubek Bartosz — 09/12/2007 @ 13:26

  17. W przesłaniającej metodzie init() (tam, gdzie wywołujesz parent::init()) dostęp powinien być public, nie protected. W sumie banał, ale ktoś, kto się dopiero uczy, może nie dojść za szybko.

    Komentarz od nerfin — 13/12/2007 @ 18:27

  18. Poza tym to nie działa (przynajmniej u mnie na ZF 1.0.3):

    function __autoload($class) {
    Zend_Loader::loadClass($class);
    }

    potrzebny jest drugi parametr z katalogiem.
    W dispatcherze loader wywoływany jest w ten sposob:

    Zend_Loader::loadFile($file, $dir, true);

    ale dispatcher ma katalogi w $dir ustawione przez:

    $frontController->setControllerDirectory(’../application/controllers’);

    natomiast w __autoload idzie tam pusta tablica i kontrolery nie będą działać.

    Z drugiej strony, moim zdaniem, niezbyt fajnie wygląda podawanie tej samej ścieżki w tym samym pliku dwukrotnie. Coś tu nie tak z koncepcją. Można ewentualnie podać ją raz w set_include_path(). Ja w każdym razie preferuję jawne włączanie wymaganych klas.

    A tak w ogóle, sprawdzałeś przed publikacją przykłady tego tutoriala, czy działają?

    Komentarz od nerfin — 13/12/2007 @ 21:11

  19. @nerfin: Oczywiście masz rację. Wkradł się błąd z protecred/public. Już poprawione. Dziękuję.
    Przykłady są oczywiście sprawdzane - ich sprawność to rzecz podstawowa i krytyczna. Ze względu jednak na pojawienie się niedawno nowej wersji Zenf Framework 1.0.3, postaram się je przystosować.

    Komentarz od Kubek Bartosz — 15/12/2007 @ 21:03

  20. […] Najlepszym miejscem na zapisanie takich instrukcji, będzie funkcja init() inicjująca nasz własny, główny kontroler akcji Album_Controller_Action (przypominam, że jest to kontroler, po którym w naszej aplikacji dziedziczą wszystkie kontrolery akcji, czyli ten w którym funkcja inicjalizacyjna init() jest uruchamiana jako pierwsza – przed wykonaniem jakiejkolwiek akcji w kontrolerach akcji. Jeśli czujesz się zagubiony w nadmienionych tutaj terminach, zapraszam do odświeżenia sobie wiedzy podczas lektury Pierwsze kroki z Zend Framework oraz Zend Framework Tutorial – Rozwijanie aplikacji). […]

    Pingback od Zend Framework Tutorial - Rozwijanie Zend View - Zend Layout - Samouczek | Heavymind — 20/12/2007 @ 00:26

  21. Bardzo spoko tutoriale piszesz stary :) czytam i ogarniam i coraz jasniejsze to wszystko się staje. Taka mala sugestia z mojej strony.. SESJE - AUTORYZACJA - DOSTEPU - LOGOWANIE ;] ..czyli newralgiczne tematy, jakbys jeszcze o tym napisal to padam na kolana i bije poklony hehe

    Komentarz od mr.espe — 20/12/2007 @ 23:39

  22. […] Zend Framework Tutorial - Rozwijanie aplikacji - po podmianie bibliotek w katalogu /library/Zend/, należy jeszcze zająć się użyciem nowo dodanej funkcji w Zend_Loader’ze. […]

    Pingback od Aktualizacja samouczków Zend Framework Tutorial do Zend Framework 1.0.3 | Heavymind — 02/01/2008 @ 22:16

  23. tutoriale fajnie napisane. Bardzo mi pomogly, ale (bo zawsze jest ale (; )
    Po co rozdzieliles set_include_path ? Piszesz dwa razy, do tego ktos kto przejmie projekt lub bedzie z Toba wspolpracowal moze nie wiedziec, ze akurat …. /application/modules wladowales do init jednej z klass - moim skromnym zdaniem juz lepiej w jednym miejscu ustalac sciezki. Wiem, ze chciales jak najbardziej odchudzic index ale czy warto az tak?

    W pierwszej czesci kursu, jak jeszcze nie byly stworzone *.phtml Zend Framework 1.0.3 wysypal mi wyjatek. Nie nalezy sie tym przejmowac tylko czytac i robic dalej, po stworzeniu w/w plikow buja wszystko ladnie. To nie zaden blad. Taka porada dla zaczynajacych by nie wpadali w panike, ze ledwo zaczeli i juz zonk ((:.

    Pozatym tak trzymaj. Pozdrawiam

    Komentarz od Tomasz Sikora — 14/01/2008 @ 09:24

  24. Tak sie zastanawiam, czy konfiguracja nie powinna byc przechowywana w sesji? Parsowanie pliku konfiguracyjnego przy kazdym wywolaniu wydaje sie byc mało ekonomiczne.

    Komentarz od Raven — 14/02/2008 @ 01:14

  25. Jak dla mnie bardzo dobra robota. Od niedawna interesuję się tworzeniem aplikacji internetowych i jest to dla mnie duża pomoc, szczególnie że (aż wstyd się przyznać, ale )mój angielski jest średni.
    Także jeszcze raz dziękuję za udzielenie wskazówek przy części pierwszej.

    Pozdrawiam,
    Michał

    Komentarz od Mick71 — 12/04/2008 @ 14:22

  26. Odnosnie Zend_Loader::registerAutoload(); - korzystanie z tego jest bardzo wygodne ale znacznie (ze wstepnych analiz) wplywa na szybkosc i zajetosc pamieci :/ Dlatego tez mozna zaincludowac standardowe najbardziej uzywane pliki recznie (a jest ich troche) a do pozostalych korzystac z automatyzacji :-)

    Komentarz od Szymon — 31/05/2008 @ 15:29

  27. rozważyć, a nie rozwarzyć

    Komentarz od Jupek — 24/06/2008 @ 18:10

  28. sorki za moja opinie ale musze stwierdzic ze slaby jest ten tutek. wiele rozwiazan jest w tym momencie juz przestarzalych nie stosowanych w praktyce. Chcecie sie nauczyc Zenda ktorego wykorzystacie przy wiekszych projektach to jedynie angielskie fora i pisac pisac pisac… pozdrawiam

    Komentarz od kole — 18/07/2008 @ 13:55

  29. Nawet jak wkleje Twoje kody to skrypt zwraca mi blad:
    Fatal error: Uncaught exception ‘Zend_Db_Table_Exception’ with message ‘No adapter found for Album’ in C:\server\www\zend\library\Zend\Db\Table\Abstract.php:548 Stack trace: #0 C:\server\www\zend\library\Zend\Db\Table\Abstract.php(531): Zend_Db_Table_Abstract->_setupDatabaseAdapter() #1 C:\server\www\zend\library\Zend\Db\Table\Abstract.php(268):…
    wiecie moze co jest nie tak?

    Komentarz od Baro — 19/07/2008 @ 11:40

  30. @Baro: a jakiego adaptera używasz do połączenia z DB? PDO_MySql? Jeśli tak, to jesteś pewien że jest on zainstalowany jako rozszerzenie PHP (php_pdo; php_pdo_mysql)? Czy aplikacja z pierwszej części samouczka działa Tobie poprawnie?

    Komentarz od Kubek Bartosz — 19/07/2008 @ 12:12

  31. tak dzialala poprawnie:) korzystalem wczesniej z PDO mysql bez Zenda i tez dzialalo wiec to mam napewno zainstalowane. Wydaje mi sie ze jakby library/Album/Conrtoller/Action.php wogole sie nie “wykonywala” ale do konca nie wiem czy dobrze mysle gdyz tam wlasnie ustawiamy ten adapter.

    Komentarz od Baro — 19/07/2008 @ 12:43

  32. moje pytanie: dlaczego wlasnie /Album/Conrtoller/Action.php ma sie wykonac jak wchodze na index.php? Jestem poczatkujacy jezeli chodzi o ZF i moze jeszcze do konca tego nie rozumiem..

    Komentarz od Baro — 19/07/2008 @ 12:49

  33. @Baro: rozwiązanie pisania własnego głównego kontrolera akcji, jest rozwiązaniem propozycyjnym, bardzo często spotykanym w aplikacjach Zend Framework. Rozdział “Stworzenie własnego kontrolera frontowego” tej części samouczka wyjaśnia podłoże. Wierzę że wielokrotne ćwiczenia z Zend Framework przyniosą Tobie wszystkie odpowiedzi.
    ps: nie zapominaj o debugowaniu [ Zend_Debug::dump(); ]. Lekarstwo dla wszystkich - początkujących i zaawansowanych.

    Komentarz od Kubek Bartosz — 19/07/2008 @ 13:05

  34. juz znalazlem byka:)
    class IndexController extends Album_Controller_Action tej lini nie podmienilem i dziedziczylem caly czas po Zend_Controller_Action
    bledu nie ma i moje pytanie tez juz rozwiazane:)
    pozdrawiam

    Komentarz od Baro — 19/07/2008 @ 13:07

  35. Skopiowałem całość z repozytorium http://zft.googlecode.com/svn/tags/zft_part2/zf151/. Po wpisaniu http://localhost/ wyświetla mi się wszystko tak jak powinno.
    Jednak kiedy uruchomię podstronę Add new album, Edit czy Delete wyskakuje mi następujący błąd:

    404 Not Found

    Not Found

    The requested URL /index/edit/id/1 was not found on this server.

    Jak rozwiązać ten problem???

    Komentarz od Adam — 21/07/2008 @ 13:56

  36. @Adam: podejrzewam, że problem leży w złym skonfigurowaniu środowiska, lub aplikacji. Samo skopiowanie kodu nie wystarczy. Zachęcam do zapoznania się z artykułami: “Instalacja WAMP dla Zend Framework”, oraz “Zend Framework Tutorial - Pierwsze kroki z Zend Framework”. Mam nadzieję że pomoże Ci to odnaleźć miejsca, w których możesz nanieść zmiany/poprawki.

    Komentarz od Kubek Bartosz — 21/07/2008 @ 14:05

  37. Nie wiem co zrobiłem, ale pobrałem ponownie repozytorium i działa :) Dziękuje za odpowiedź. Pozdrawiam

    Komentarz od Adam — 21/07/2008 @ 17:19

  38. wszedzie piszesz przeciazana metoda. ona nie jest przeciazana tylko przeslaniana. niemniej jednak duuuze (!) brawa za ten art :) dzieki !

    Komentarz od magnat — 15/08/2008 @ 19:14

  39. Odp na komentarz 23 ;]

    Otóż moim skromnym zdaniem te rozdzielenie nie służy wcale przerzucaniu jak największej ilości kodu do Album_Controller_Action, a enkapsulacji danych, ograniczenia sobie do nich dostępu tam, gdzie nie jest to konieczne. Jak wiadomo, ścieżka do naszych bibliotek [/library] jest wymagana globalnie, zaś ścieżka do katalogu z modelami będzie potrzebna jedynie w kontrolerach - więc tylko tam będzie dostępna.

    Do autora: Jeżeli się mylę, proszę o wyjaśnienie. myślę, że większość ludzi niezrozumiała dlaczego tak właśnie zrobiłeś ;)

    Komentarz od psychowico — 15/10/2008 @ 22:50

  40. @psychowico - no tak, faktycznie. Zagadnienie enkapsulacji leży tak nisko u podstaw programowania obiektowo, że można czasem zapomnieć dlaczego odruchowo dąży się do tego. Dziękują za sprostowanie :)

    Komentarz od Kubek Bartosz — 27/10/2008 @ 19:27

  41. “Krok drugi. Przenosimy podanie ścieżki do modelów z bootstrapera do
    głównego kontrolera akcji.”
    Mógłbyś mi wytłumaczyć dlaczego tylko ścieżki modelów bo nie bardzo to rozumiem.
    Tutoriale świetne!

    Komentarz od therias — 05/01/2009 @ 20:11

  42. W końcu ktoś poruszył problem wydajności Zend Framework. Faktycznie ZF nie jest demonem prędkości i zużywa sporo zasobów. Zastanawiające jest w tej sytuacji, że tak wiele firm korzysta z niego przy tworzeniu swoich aplikacji.

    Nie mam większych zastrzeżeń do tutoriala. Warto poznać funkjonowanie kilku napopularniejszych frameworków dla PHP, aby wybrać taki, który będzie odpowiadał naszym potrzebom.

    Komentarz od Łukasz Adamczuk — 20/01/2009 @ 14:52

  43. Brawo za artykuł ,ale popraw te błędy ortograficzne!!
    I nie pisze się “w ten czas”….
    Eh.

    Komentarz od warden — 30/04/2009 @ 19:08

  44. Witam świetny tut…
    widze ze b.dawno pisany ale bardzo dobry wiec dokleje info

    loader jest na dzien dzisieszy przestarzały wiec ten fragment kodu

    //If class not found instanciate it automatically
    require_once ‘Zend/Loader.php’;
    Zend_Loader::registerAutoload();

    mozna zastapic czyms takim

    require_once ‘Zend/Loader/Autoloader.php’;
    Zend_Loader_Autoloader::getInstance();

    Komentarz od Loader problem — 11/08/2009 @ 14:47

  45. lub
    require_once ‘Zend/Loader/Autoloader.php’;
    Zend_Loader_Autoloader::getInstance()->setFallbackAutoloader(true);

    Komentarz od Loader problem — 11/08/2009 @ 14:51

  46. Przestał mi całkowicie działać css. tak zmieniłem nazwę z site.css na style.css:
    <link rel=”stylesheet” type=”text/css” media=”screen”
    href=”baseUrl;?>/styles/style.css” />

    Wszystko działa poprawnie oprócz tego, nie wiem jak go właściwie podpiąć i co z nim jest nie tak.

    Komentarz od WodzJarek — 06/11/2009 @ 23:28

  47. Komentarz do 46.
    Może wpisujesz adres np. tak: http://www…/index.php, a powinno się pisać tak http://www…/index. Jakby ktoś mógłby mi wytłumaczyć czego tak jest, że CSSy nie działają jak się wpisze rozszerzenie php??
    Komentarz do 46 cz.2
    Sprawdź czy masz index.php w katalogu public.

    PROBLEM!!!
    Dlaczego nie widzi mi w IndexControlerze klasy Album_Controller_Action? Wyrzuca błąd, że nie może znaleść tej klasy. Ścieżka path do library jest ustawiona, a klasę tą umieściłem tak jak w tutorialu w katalogu library/Album/Controller/Action.php

    Komentarz od IGotAPower — 29/11/2009 @ 22:05

  48. W plikach .htaccess zamiast “php_flag magic_quotes_gpc ogg” powinno być chyba “php_flag magic_quotes_gpc off”. Pozatym bardzo fajny materiał.

    Komentarz od TomanDesign — 21/01/2010 @ 10:47

  49. W nowym Zend zamiast w/w autoloadera trzeba wpisać:

    require_once ‘Zend/Loader/Autoloader.php’;
    $autoloader = Zend_Loader_Autoloader::getInstance();
    $autoloader->setFallbackAutoloader(true);

    Komentarz od wrona — 13/02/2010 @ 16:27

  50. Wersja PDF:

    http://hoth.amu.edu.pl/~g_michalak/zend%20framework.pdf

    Komentarz od some_user — 16/08/2010 @ 18:53

Kanał RSS dla tego wpisu. TrackBack URL

Dodaj komentarz

Oparte na WordPress