Autor wpisu: batman, dodany: 29.09.2009 19:15, tagi: zend_framework
Autor wpisu: Diabl0, dodany: 29.05.2009 22:25, tagi: zend_framework
Jakiś czas temu stworzyłem prostą klasę do pobierania kursów walut z NBP. Dla przyśpieszenia działania i odciążenia serwerów NBP wykorzystuje ona Zend_Cache do przechowywania danych po pobraniu. Niestety, ostatnio kilka razy zdarzyła się sytuacja że cache się już przeterminował, trzeba pobrać nowe dane a… serwer NBP odpowiada błędem. I wszyscy są wściekli. Pracownicy, bo nie mogą pracować, szefostwo, bo pracownicy nie pracują, oraz ja bo muszę tłumaczyć że nie mam na to wpływu… Jak się jednak okazało - mam
Jak zwykle, wystarczyła dokładniejsza lektura API i odkrycie że metoda Zend_Cache_Core::load ma kilka parametrów, w tym jeden bardzo nas interesujący:
@param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
Wystarczyło teraz troszkę ruszyć palcami i:
/* @var $cache Zend_Cache_Core */ $cache = Zend_Registry::get ( 'cache' ); // UWAGA - porównie z przypisaniem = TUTAJ MA BYĆ POJEDYŃCZY ZNAK RÓWNOŚCI if ($data = $cache->load ( 'Mao_Service_ExchangeRates_NBP' )) { // Pobrano dane z cache i dane są jeszcze aktualne $data = unserialize ( $data ); $this->_dataDate = $data ['_dataDate']; $this->_data = $data ['_data']; } else { // Dane pobrane z cache są przeterminowane, próbujemy pobrać nowe try { $this->_fetchData (); $data = array ( '_dataDate' => $this->_dataDate, '_data' => $this->_data ); $cache->save ( serialize ( $data ), 'Mao_Service_ExchangeRates_NBP', array (), 3600 ); } catch ( Exception $e ) { // Nie udało się pobrać danych z źrudła (przyczyna nas na razie nie interesuje) // Pobieramy dane z cache nawet jeśli są przeterminowane. $old_data = $cache->load ( 'Mao_Service_ExchangeRates_NBP', true ); if ($old_data) { // Na szczęście mamy "stare" dane które możemy wykorzystać $data = unserialize ( $old_data ); $this->_dataDate = $data ['_dataDate']; $this->_data = $data ['_data']; try { // Próbujemy raportować możliwy problem do "systemowego" loggera // Teoretycznie logger powinien być, ale kto wie czy kiedyś ktoś // tego nie wykorzysta gdzieś indzie i będzie się zastanawiał czemu wyskakuje exception $logger = Zend_Registry::get ( 'logger' ); $logger->warn ( 'Mao_Service_ExchangeRates_NBP old data used. Reason:' . $e->getMessage () ); } catch (Exception $e) { // Uuuu... Problem z loggerem? co to za konfiguracja? // Na szczęście to nie jest krytyczny problem więc ignorujemy } } else { // No to mamy spory problem, nie ma ani danych z źrudła, ani danych // Nic więcej nie jesteśmy w stanie zrobić tutaj z tym problemem, // więc wyrzucamy go wyżej throw $e; } } }
Próbujemy wczytać dane z cache. Jeśli nie ma danych lub są przeterminowane ($cache->load zwraca false) to próbujemy pobrać nowe dane z NBP. Jeśli z jakiegoś powodu nie uda się to nam, to ponownie próbujemy pobrać dane z cache tym razem nie zwracając uwagi na ich “przydatność do spożycia”. No i w ostateczności jeśli i to zawiedzie, to i tak jesteśmy na tyle głęboko w czarnej d… że możemy wyrzucić wyjątek wyżej i dać innym zająć się tym problemem. My zrobiliśmy wszystko co w naszej mocy.
Aby dopełnić arta zamieszczam również pełną klasę Mao_Service_ExchangeRates_NBP. Może się komuś przyda.
Autor wpisu: Diabl0, dodany: 20.05.2009 14:59, tagi: sql, zend_framework
Czasami przy próbach zapisu danych do tabeli z referencjami wyskakuje wyjątek o niewiele mówiącej treści: “Cannot refresh row as parent is missing“. Ustalenie faktycznej przyczyny jest już trochę cięższe, ale da się to zrobić stosując lekką sztuczkę.
Sam wyjątek wygląda lakonicznie i nie dostarcza ciekawych danych:
Fatal error: Uncaught exception 'Zend_Db_Table_Row_Exception' with message 'Cannot refresh row as parent is missing' in E:\HTDOCS\recepcja\library\Zend\Db\Table\Row\Abstract.php:695 Stack trace: #0 E:\HTDOCS\recepcja\library\Zend\Db\Table\Row\Abstract.php(429): Zend_Db_Table_Row_Abstract->_refresh() #1 E:\HTDOCS\recepcja\library\Zend\Db\Table\Row\Abstract.php(372): Zend_Db_Table_Row_Abstract->_doInsert() #2 E:\HTDOCS\recepcja\application\messages\controllers\IndexController.php(60): Zend_Db_Table_Row_Abstract->save() #3 E:\HTDOCS\recepcja\library\Zend\Controller\Action.php(502): Messages_IndexController->addAction() #4 E:\HTDOCS\recepcja\library\Zend\Controller\Dispatcher\Standard.php(293): Zend_Controller_Action->dispatch('addAction') #5 E:\HTDOCS\recepcja\library\Zend\Controller\Front.php(914): Zend_Controller_Dispatcher_Standard->dispatch(Object(Zend_Controller_Request_Http), Object(Zend_Controller_Response_Http)) #6 E:\HTDOCS\recepcja\public_html\index.php(153): Zend_Controller_Front->dispatch() #7 {main} thrown in E:\HTDOCS\recepcja\library\Zend\Db\Table\Row\Abstract.php on line 695
Aby ustalić przyczynę musimy zagłębić się w wyrzucany wyjątek i odszukać w Stack trace miejsce w NASZYM kodzie gdzie ten wyjątek się zaczyna. Na powyższym przykładzie jest to #2
#2 E:\HTDOCS\recepcja\application\messages\controllers\IndexController.php(60): Zend_Db_Table_Row_Abstract->save()
Teraz musimy zajrzeć w ten kod i opatulić go własnym try/catch:
try { $someRow->save(); } catch ( Exception $e ) { echo '<pre>Data: '; print_r( $someRow ); echo '</pre>'; echo '<pre>Exception: '; print_r( $e ); echo '</pre>'; exit(); }
Teraz zobaczymy znacznie więcej informacji:
Data: Zend_Db_Table_Row Object ( [_data:protected] => Array ( [patient_id] => Zend_Db_Statement_Exception Object ( [message:protected] => SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '34415-1' for key 2 ---CUT---
I jak na dłoni widać faktyczną przyczynę wyjątku: Integrity constraint violation: 1062 Duplicate entry ‘34415-1′ for key 2. A skoro wiemy gdzie jest problem, to możemy też jakoś mu zaradzić.
Autor wpisu: WojciechNaruniec, dodany: 01.05.2009 17:22, tagi: php, zend_framework
Autor wpisu: Diabl0, dodany: 25.04.2009 22:56, tagi: zend_framework
Początkowo jako następny artykuł miało się tutaj pojawić podsumowanie tematu drzew wraz z przykładami praktycznego zastosowania. Jednym z tematów miało być wykorzystanie drzewa i elementu dijit.Tree w formie własnego elementu Zend_Form. Niestety, podczas prac trafiłem na pewien problem który mnie zablokował i za bardzo nie wiem jak do niego podejść w miarę prosty sposób. Tak więc tym razem zamiast gotowego rozwiązania będzie bardziej pytanie i prośba o pomoc.
Uwaga - zawarte fragmenty kodu są wersjami roboczymi, zapewne zawierają sporo błędów i można je napisać znacznie ładniej i zgodniej z standardami. Znajdują się one tutaj aby lepiej zobrazować problem i stanowić tylko i wyłącznie wskazówki i pomoc, tak więc proszę ich nie wykorzystywać bez zastanowienia i dopracowania, aby nie mieć później pretensji że coś nie działa lub co gorsza działa źle.
Kto interesuje się Dojo wie że w jego skład wchodzi widget dijit.Tree. Naturalna więc wydaje się próba ułatwienia sobie życia i próba stworzenia elementu formy który da użytkownikom możliwość szybkiego i wygodnego wyboru elementu za pomocą rozwijanego drzewa. Początkowo problem wydawał się banalny, i pierwszy “szkic” takiego elementu napisałem w około godzinkę.
Idea działania jest prosta - tworzymy zwykły ukryty element INPUT w którym będzie lądował identyfikator wybranego elementu drzewa. Do tego tworzymy element DIV który następnie zostanie przekształcony w element dijit.Tree.
/** * Tree form element * * @uses Mao_View_Helper_FormDiv */ class Mao_Form_Element_Tree extends Zend_Dojo_Form_Element_ComboBox { /** * Use FormTree view helper * @var string */ public $helper = 'FormTree'; }
/** * Dojo Tree dijit * * @uses Zend_Dojo_View_Helper_Dijit * @license http://framework.zend.com/license/new-bsd New BSD License */ class Mao_View_Helper_FormTree extends Zend_Dojo_View_Helper_Dijit { /** * Dijit being used * @var string */ protected $_dijit = 'dijit.Tree'; /** * HTML element type * @var string */ protected $_elementType = 'text'; /** * Dojo module to use * @var string */ protected $_module = 'dijit.Tree'; /** * dijit.form.ComboBox * * @param int $id * @param mixed $value * @param array $params Parameters to use for dijit creation * @param array $attribs HTML attributes * @param array|null $options Select options * @return string */ public function formTree($id, $value = null, array $params = array(), array $attribs = array(), array $options = null) { $html = ''; if (!array_key_exists('id', $attribs)) { $attribs['id'] = $id; } if (array_key_exists('store', $params) && is_array($params['store'])) { // using dojo.data datastore if (false !== ($store = $this->_renderStore($params['store']))) { $params['store'] = $params['store']['store']; if (is_string($store)) { $html .= $store; } $html .= $this->_createFormElement($id, $value, $params, $attribs); return $html; } unset($params['store']); } elseif (array_key_exists('store', $params)) { if (array_key_exists('storeType', $params)) { $storeParams = array( 'store' => $params['store'], 'type' => $params['storeType'], ); unset($params['storeType']); if (array_key_exists('storeParams', $params)) { $storeParams['params'] = $params['storeParams']; unset($params['storeParams']); } if (false !== ($store = $this->_renderStore($storeParams))) { if (is_string($store)) { $html .= $store; } } } $html .= $this->_createFormElement($id, $value, $params, $attribs); return $html; } throw new Exception('No dojo.data store provided.'); } /** * Create HTML representation of a dijit form element * * @param string $id * @param string $value * @param array $params * @param array $attribs * @param string|null $dijit * @return string */ public function _createFormElement($id, $value, array $params, array $attribs, $dijit = null) { if (! array_key_exists ( 'id', $attribs )) { $attribs ['id'] = $id; } $attribs ['name'] = $id; $attribs ['value'] = ( string ) $value; $attribs ['type'] = $this->_elementType; $attribs ['id'] = '_' . $id . '_'; $attribs = $this->_prepareDijit ( $attribs, $params, 'element', $dijit ); $attribs ['id'] = $id; // atrybuty na potrzeby DIVa reprezentującego dijit.Tree $divAttribs = $attribs; // Ukrywamy element INPUT $attribs ['type'] = 'hidden'; // Wyrzucamy ukryty input $html = '<input' . $this->_htmlAttribs($attribs) . $this->getClosingBracket(); // Modyfikujemy atrybuty na potrzeby elementu DIV reprezentującego dijitTree $divAttribs ['id'] = '_' . $id . '_'; $divAttribs ['name'] = '_' . $id . '_'; unset ( $divAttribs ['name'], $divAttribs ['value'] ); $html .= '<div ' . $this->_htmlAttribs( $divAttribs ) . $this->getClosingBracket() . '</div>'; // Wyrzucamy kod JS odpowiadający za połączenie ukrytego inputa z wyświetlonym drzewkiem $this->_renderJsHelper ( $id, $params ); return $html; } /** * Render data store element * * Renders to dojo view helper * * @param array $params * @return string|false */ protected function _renderStore(array $params) { if (!array_key_exists('store', $params) || !array_key_exists('type', $params)) { return false; } $this->dojo->requireModule($params['type']); $extraParams = array(); $storeParams = array( 'dojoType' => $params['type'], 'jsId' => $params['store'], ); if (array_key_exists('params', $params)) { $storeParams = array_merge($storeParams, $params['params']); $extraParams = $params['params']; } if ($this->_useProgrammatic()) { if (!$this->_useProgrammaticNoScript()) { require_once 'Zend/Json.php'; $js = 'var ' . $storeParams['jsId'] . ' = ' . 'new ' . $storeParams['dojoType'] . '(' . Zend_Json::encode($extraParams) . ");\n"; $this->dojo->addJavascript($js); } return true; } return '<div' . $this->_htmlAttribs($storeParams) . '></div>'; } /** * Render data store element * * Renders to dojo view helper * * @param string $id * @param array $params * @return string|false */ protected function _renderJsHelper($id, array $params) { if ($this->_useProgrammatic ()) { if (! $this->_useProgrammaticNoScript ()) { require_once 'Zend/Json.php'; $js = ' dojo.subscribe("_' . $id . '_", "execute", function( data ){ dojo.byId("' . $id . '").value = ' . $params ['store'] . '.getIdentity( data.item ); }); '; $this->dojo->addJavascript ( $js ); } return true; } return '<div' . $this->_htmlAttribs ( $storeParams ) . '></div>'; } }
Powyższe 2 klasy załatwiają nam utworzenie wspomnianych elementów INPUT i DIV. Dodatkowo w metodzie _renderJsHelper znajduje się prosty kod JS odpowiedzialny za nasłuch zdarzeń w utworzonym widgecie dijitTree i w po wybraniu jakiegoś elementu wstawienie jego identyfikatora do pola INPUT (to ten fragment z dojo.subscribe).
Od strony formy wykorzystanie tego elementu wygląda mniej więcej tak:
class default_forms_TreeTest extends Mao_Form { public function __construct($options = null) { parent::__construct ( $options ); // Dojo-enable the form: Zend_Dojo::enableForm ( $this ); $this->addElement ( 'Tree', 'tree_id', array ( 'label' => 'Tree', 'storeId' => 'tstr', 'storeType' => 'dojo.data.ItemFileReadStore', 'storeParams' => array( 'url' => '/mag/categories/categoriesTreeJson.json', ), 'dijitParams' => array( 'labelAttr' => 'name', ), 'value' => 4, 'style' => 'height: 200px; overflow: auto;', ) ); $this->addElement ( 'SubmitButton', 'submit', array ( 'label' => 'Zapisz' ) ); } }
Natomiast wynikowy kod HTML:
[sourcecode] <!-- W części JS generowanej przez Zend_Dojo --> <script type="text/javascript"> //<![CDATA[ dojo.require("dijit.Tree"); var tstr = new dojo.data.ItemFileReadStore({"url":"\/mag\/categories\/categoriesTreeJson.json"}); // dojo.subscribe("_tree_id_", "execute", function( data ){ // console.log( tstr.getIdentity( data.item ) ); dojo.byId("tree_id").value = tstr.getIdentity( data.item ); }); //]]> </script> <!-- I część formy --> <dt><label for="tree_id" class="optional">Tree</label></dt> <dd> <input style="height: 200px; overflow: auto;" id="tree_id" name="tree_id" value="4" type="hidden"> <div style="height: 200px; overflow: auto;" id="_tree_id_" type="text"></div> </dd> [/sourcecode]
W sumie wszystko teoretycznie działa, brakuje jednak pewnej “małej” ale ważnej funkcjonalności - przy wyświetlaniu formy powinno nastąpić automatyczne zaznaczenie elementu drzewa którego identyfikator znajduje się w polu INPUT, oraz rozwinięcie odpowiedniej gałęzi drzewa. Niestety twórcy dijit.Tree z jakiegoś powodu nie zaimplementowali do tego odpowiednich metod przez co trzeba to zrobić samemu. I tutaj mamy 2 problemy:
- lazy loading
- znalezienie wybranego elementu, rozwinięcie gałęzi do której on należy i zaznaczenie go
Problem pierwszy wynika z tego że dane do drzewa ładowane są asynchronicznie po stworzeniu elementu. Na szczęście problem ten jest dość prosty do rozwiązania z wykorzystaniem interwałów:
var _tree_id_timer; dojo.addOnLoad( function () { if ( dojo.byId("tree_id").value != "") { _tree_id_timer = setInterval( function(){ if ( dijit.byId("_tree_id_").store._loadFinished ) { // Dane do elementu zostały załadowane clearInterval( _tree_id_timer ); // Czas znaleźć i zaznaczyć wybrany element } }, 100); } });
W skrócie, jeśli w pole input zawiera jakąś wartość, to tworzymy interwał który co określony czas sprawdza czy źródło danych wykorzystywane przez dijit.Tree zakończyło wczytywanie danych (dijit.byId(”_tree_id_”).store._loadFinished). Jeśli skończyło to czyścimy interwał (już nam nie będzie potrzebny) i przystępujemy do problemu drugiego.
Autor wpisu: Diabl0, dodany: 03.03.2009 11:52, tagi: zend_framework, php
Po ostatniej aktualizacji ZF do 1.7.6 w jednym z projektów pojawił się nowy “nieplanowany” wyjątek:
Warning: Exception caught by form: Requested scripts may not include parent directory traversal (”../”, “..\” notation)
Cały wyjątek wygląda następująco:
Warning: Exception caught by form: Requested scripts may not include parent directory traversal ("../", "..\" notation) Stack Trace: #0 C:\HTDOCS\projekt\library\Zend\View\Abstract.php(816): Zend_View_Abstract->_script('../forms/AddApp...') #1 C:\HTDOCS\projekt\library\Zend\View\Helper\Partial.php(103): Zend_View_Abstract->render('../forms/AddApp...') #2 [internal function]: Zend_View_Helper_Partial->partial('../forms/AddApp...', Array) #3 C:\HTDOCS\projekt\library\Zend\View\Abstract.php(329): call_user_func_array(Array, Array) #4 [internal function]: Zend_View_Abstract->__call('partial', Array) #5 C:\HTDOCS\projekt\library\Zend\Form\Decorator\ViewScript.php(130): Zend_View->partial('../forms/AddApp...', Array) #6 C:\HTDOCS\projekt\library\Zend\Form.php(2595): Zend_Form_Decorator_ViewScript->render('') #7 C:\HTDOCS\projekt\library\Zend\Form.php(2610): Zend_Form->render() #8 C:\HTDOCS\projekt\application\cytologia\views\scripts\doctor\addapplication.phtml(6): Zend_Form->__toString() #9 C:\HTDOCS\projekt\library in C:\HTDOCS\projekt\library\Zend\Form.php on line 2615
Po dokładniejszych oględzinach okazało się że problem tkwi w używaniu względnych ścieżek i “wychodzenia” ponad katalog (więcej: ZF-5748) w widokach. Z jednej strony miło że ktoś dba o nasze bezpieczeństwo, ale z drugiej strony po raz kolejny sprawia to problemy przy migracji istniejących projektów (IMHO zmiany wpływające na kompatabilność w obrębie jednej gałęzi są niedopuszczalne).
Na szczęście zostało to szybko zauważone i dodano możliwość wyłączenia tego sprawdzania.
r14063: ZF-5748: Added ability to override LFI protection as needed Documented fix in new “Zend_View Migration” chapter
Przykładowy kod dla Zend_Layout inicjalizowanego w bootstrapie:
$view = Zend_Layout::getMvcInstance ()->getView (); $view->setLfiProtection ( false ); // override LFI protection
Inne przykłady za manualem:
// Disabling via constructor $view = new Zend_View(array('lfiProtectionOn' => false)); // Disabling via exlicit method call: $view = new Zend_View(); $view->setLfiProtection(false);