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);
Kanał ATOM
