Niezalogowany [ logowanie ]
Subskrybuj kanał ATOM Kanał ATOM    Subskrybuj kanał ATOM dla tagu zend_framework Kanał ATOM (tag: zend_framework)

Autor wpisu: batman, dodany: 29.09.2009 19:20, tagi: zend_framework

Zend Framework dostarcza programiście ogromną ilość narzędzi, które znacznie ułatwiają pracę nad aplikacjami internetowymi. Jednym z takich narzędzi jest Zend_Form. Jego wykorzystanie znacznie przyspiesza proces tworzenia formularzy wraz z walidacją i filtrowaniem wprowadzonych danych. Formularz można stworzyć na co najmniej kilka sposobów. W zasadzie każdy, kto używa ZF od dłuższego czasu,

Autor wpisu: batman, dodany: 29.09.2009 19:15, tagi: zend_framework

W poprzednim tekście poświęconym formularzom w Zend Framework-u, pokazałem jak w prosty i szybki sposób można stworzyć bezpieczny formularz, wykorzystując do tego Zend_Form. Dzisiaj pokażę w jaki sposób można stworzyć skomplikowany formularz, zawierający dwie kolumny z kontrolkami oraz podpowiedzi (tooltip).  By nie przesłonić głównego celu tego wpisu, nie będę dodawał filtrów i walidatorów do

Autor wpisu: Diabl0, dodany: 29.05.2009 22:25, tagi: zend_framework

nbpJakiś 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

Wczoraj w serwisie Zend Developer Zone ukazała się informacja, że dostępny jest już Zend Framework 1.8.0. Tę wersję można nazwać przełomową - w końcu pojawiły się narzędzia wspomagające szybkie tworzenie aplikacji (RAD). Teraz wywołaniem jednej komendy można utworzyć praktycznie całą strukturę projektu.

Autor wpisu: Diabl0, dodany: 25.04.2009 22:56, tagi: zend_framework

tree_7Począ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:

  1. lazy loading
  2. 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.

Czytaj dalej tutaj (rozwija treść wpisu)
Czytaj dalej na blogu autora...

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);

Related posts

Wszystkie wpisy należą do ich twórców. PHP.pl nie ponosi odpowiedzialności za treść wpisów.