Archivi tag: Zend framework

ZendX_JQuery e metodo addOnLoad con layout disabilitato

Zend Framework Logo

In un applicativo sviluppato su Zend Framework in cui è necessario un uso massiccio di ajax e jQuery spesso la libreria ZendX_JQuery non è sufficiente a coprire tutte le esigenze. Tempo fa avevo già riscontrato alcuni problemi sulle funzioni di Autocomplete di ZendX_JQeury. Oggi mi sono scontrato con il metodo OnLoad (che genera codice in $(document).ready(function(){ … }); )

Siamo nella situazione in cui da una pagina si caricano elenchi e svariate altre sottosezioni tramite le classiche chiamate jQuery. Prendiamo questa funzione Javascript come esempio:

function my_ChangeTab(url)
    {
        startLoadingBoxTopLeft('box_left');
        $.get( url ,
            function(data, textStatus)
            {
                stopLoadingBoxTopLeft('box_left');
                // load HTML data in box_left
                $('#box_left').html(data);
            }, 'html');
    }

Questa funzione carica il contenuto che restituisce url dentro un elemento (div) box_left.

Il mio problema nasce nel momento in cui url carica un form. Se in questo form abbiamo elementi di ZendX_JQuery (es: DatePicker, Autocomplete, ecc.) siamo fregati. Questi elementi infatti invocano internamente il metodo addOnLoad.

Vedi ad esempio ZendX_JQuery_View_Helper_DatePicker nel metodo datePicker (riga 71):

$this->jquery->addOnLoad($js);

Questo metodo carica, se correttamente configurato, nel head tag della pagina il codice $js. Questo codice serve per innescare le funzioni delle librerie jQuery UI per il DatePicker.

Tornando alla nostra situazione (vedi sopra) è comprensibile come il tutto non funzioni. La chiamata Javascript qui sopra infatti richiama solo il contenuto generato dall’action e non l’intero layout: nell’action infatti il layout sarà disabilitato:

$this->_helper->layout()->disableLayout();

Come risolvere?

Io mi sono arrangiato con un bel plugin:

<?php 
class My_Controller_Plugin_JQueryOnLoadWithoutLayout
    extends Zend_Controller_Plugin_Abstract
{
	protected $view;
 
	function __construct(){
		$viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
        if (null === $viewRenderer->view) {
            $viewRenderer->initView();
        }
        $this->view = $viewRenderer->view;
	}
 
    public function  postDispatch(Zend_Controller_Request_Abstract $request)
    {
        // get Layout
        $layout = Zend_Layout::getMvcInstance();
 
        // elenco delle action registrate in OnLoad
        $actions = $this->view->jQuery()->getOnLoadActions();
        if( count($actions) > 0
            && !$layout->isEnabled()
          )
        {
            // create jQuery code
            $content = "";
            if(ZendX_JQuery_View_Helper_JQuery::getNoConflictMode() == true) {
                $content .= '$j(document).ready(function() {'."\n    ";
            } else {
                $content .= '$(document).ready(function() {'."\n    ";
            }
            foreach($actions AS $action)
            {
                $content .= $action . PHP_EOL;
            }
            $content .= '});'."\n";
 
            // add code to script tag
            $code = '<script type="text/javascript">' . PHP_EOL
                  . '//<![CDATA[' . PHP_EOL
                  . $content
                  . '//]]>' . PHP_EOL
                  . '</script>' . PHP_EOL;
 
            // OUTPUT
            $this->getResponse()->appendBody($code);
        }
    }
 
}
?>

Questo plugin modifica l’output a fine dispatch (postDispatch). Prima di tutto verifica se esistono righe di codice da inserire nella sezione OnLoad, poi verifica che il layout sia disabilitato (altrimenti quando è abilitato scriviamo 2 volte lo stesso codice) e infine genera l’apposito codice Javascript che viene aggiunto al Response.

In questo modo risolviamo il problema posto sopra. Ogni volta che carichiamo una “porzione” di pagina tramite Ajax (quindi il layout è disabilitato) e necessitiamo di codice in OnLoad (generato automaticamente, ad esempio, dagli elementi form di ZendX_JQuery) il suddetto plugin aggiungerà il codice generato per l’OnLoad in coda all’HTML restituito via Ajax.

Zend Form, jQuery and autocomplete

When a component in a Framework is inflexible and difficult to maintain I believe is time to change way. ZendX_JQuery is powerful but not in the jQuery UI Autocomplete. After a thorough discussion about Autocomplete now we are at a dead end and we can only wait a new release. I prefer to change my way and go ahead.

This is my different solution for Autocomplete UI in Zend Form (without ZendX_JQuery).

First of all I extended the Zend_Form with a general class My_Form where I can set all defaults values of decorators, filters, validators, etc. Then I added my createMyAutoComplete method:

class My_Form extends Zend_Form
{
    // autcomplete extension of idfield
    private $_acExtension = "_ac";
 
    /*
     *  Create My AutoComplete Elements
     */
        public function createMyAutoComplete($id, $source, $params=array())
        {
            $idAc = $id . $this->_acExtension; // support element id for autocomplete
            // Create autocomplete element
            $this->addElement('text', $idAc, $params);
            // Create autocomplete hidden element (for id field)
            $this->addElement('hidden', $id, array('decorators' => $this->_noElementDecorator));
 
            // Add my custom Javascript functions to onLoad
            $view = $this->getView();
            $view->jQuery()->addOnLoad("MyAutocomplete.initialize('".$id."', '".$source."', '".$this->_acExtension."');");
 
            // return id of element Autocomplete to manage in group, add params, ecc...
            return $idAc;
        }

Above you can see the createMyAutoComplete method to generate an autocomplete field and its support field id. The parameters are:

  • $id = the id of the element
  • $source = the source where to read data for autocomplete
  • $params = like the params of the Zend_Form (see below)

As we seen the method creates two elements, for example, if we set $id=”idcity” we will have:

  • idcity, hidden field (our real id)
  • idcity_ac, text field (support field, only for autocomplete feature)

Remember to enable jQuery in the view otherwise the $view->jQuery->addOnLoad() above does not work!

Then you can call this method in the Forms:

class Application_Form_User_City extends My_Form
{
	public function init()
	{
	         // Set the method for the display form to POST
	         $this->setAttrib('id', 'user_city_form')
				->setAttrib('class', 'f1n120');
 
	        // element: Description
	        $this->addElement('text', 'description', array(
	            'label'      => $this->translate('Description'),
	            'attribs'      => array('size' => 35),
	            'required'   => true,
	            'filters'    => array('StringTrim'),
	            'validators' => array('NotEmpty'),
	        ));
 
	       // element: City
               $idAc = $this->createMyAutoComplete( "idcity",
                                         "/utility/search.city",
                                         // set params of element
                                         array(
                                             'label'    => $this->translate('City'),
                                             'attribs'    => array('size' => 25),
                                             'required' => true,
                                             'ignore'   => true, // to ignore when submit
                                             'filters'    => array('StringTrim'),
                                             'validators' => array('NotEmpty'),
                                         )
                );
	}
}

So we can create quickly an AutoComplete element and its hidden field.
Note: Add ignore attrib not submit this field. So we’ll submit directly the id.

To finish I created a custom Javascript to manage the autocomplete element.
Include this one in your Javascript file:

    MyAutocomplete = {
        initialize: function(idelement, mysource, acext)
        {
            eac = '#' + idelement + acext;
            eac_id = '#' + idelement;
 
            $(eac).autocomplete({
                minLength: 0,
                source: mysource,
                focus: function(event, ui) {
                    $(eac).val(ui.item.label);
                    return false;
                },
                select: function(event, ui) {
                    $(eac).val(ui.item.label);
                    $(eac_id).val(ui.item.value);
                    return false;
                }
            })
            .data( "autocomplete" )._renderItem = function( ul, item ) {
                return $( "<li></li>" )
                    .data( "item.autocomplete", item )
                    .append( "<a>" + item.label + "</a>" )
                    .appendTo( ul );
            };
        }
    }

The ajax response from the source must be an array with two fields: label and value.
This js function prepare data for every single item (_renderItem) and set the the value on the hidden field (eac_id). Need 3 parameters:

  • idelement – the real id
  • source – where to read data
  • acext – the extension of the autocomplete field

This parameters are set automatically in the above My_Form class.
Try it!

Ottimizzazione URL (SEO) con Zend Framework

Rimettendo mano sul framework oggi mi sono scontrato di nuovo con un problema che da tempo volevo risolvere. Come sappiamo gli url con cui chiamiamo i vari Controller di Zend Framework vengono richiamati con una codifica ben precisa:

http://www.m4ss.net/controller/action

In questo modo quindi chiamiamo tutti i Controller e le Action che desideriamo invocare. A questo indirizzo possiamo poi aggiungere i parametri in questo modo:

http://www.m4ss.net/controller/action/params1/valore1/params2/valore2

In pratica accodiamo il nome del parametro e a seguire il suo valore.

Ciò che mi premeva risolvere da tempo era il poter accorciare questo indirizzo quando chiamo il contenuto di un sito. Ipotizziamo di avere un sito semplice che richiama i vari contenuti in base ad un codice ottimizzato per il SEO. Un esempio di codice potrebbe essere:

Tale codice è presente in un campo di database e, tramite query, estraggo il contenuto richiesto. Nel mio sito avrò un Controller che si occupa di estrarre i contenuti in base a tale codice. Se utilizzassimo la route normale avremmo un indirizzo del tipo:

http://www.m4ss.net/content/get/code/zend-framework-teoria-di-base-front-controller-action

/content/get/code si ripete per ogni contenuto, allo stesso modo, senza alcuna differenza. Mi premeva accorciarlo un po’!

Ho risolto in questo modo, il mio Controller Content è così composto:

class ContentController extends Zend_Controller_Action
{
private $_myCode;
public function init()
{
// get my code
$this->_myCode = $this->getRequest()->getActionName();
// change action -> redirect always to index
$this->getRequest()->setActionName('index');
}
public function indexAction()
{
// get parameters
$cModel = new Model_ContentLang();
$myContent = $cModel->getByCode($this->_myCode);
$this->view->myC = $myContent;
}
}

In pratica intercetto l’action nella funzione init() di inizializzazione del Controller. Aggancio l’action facendolo diventare il mio codice e reimposto l’action inviando il tutto sempre ad index. In index poi prelevo il contenuto dal database in base al codice e lo invio alla View.

In questo modo l’url può essere semplificato in questo modo:

http://www.m4ss.net/content/zend-framework-teoria-di-base-front-controller-action

Zend_Validate, traduzione messaggi di errore

Come Adapter per Zend_Translate prediligo gettext, con Poedit si lavora molto bene!

In fase di deploy di un’applicazione, dopo aver tradotto i miei file, mi sono accorto che mancavano le traduzioni dei messaggi di errore di Zend_Validate (Alpha, EmailAddress, StringLength, ecc.). Dopo un po’ di ricerche ho scoperto che esiste una directory resources/languages (solo nella versione full di ZF) in cui risiedono tutti i file con le traduzioni dei suddetti messaggi di errore.

Il file però presenta le traduzioni in un array e quindi ho dovuto generare un testo da integrare nel file .po
Per fare ciò ho utilizzato queste semplici 2 righe di codice:

foreach($myAr AS $en => $it)
{
echo ‘msgid “‘ . $en . “\”\n”;
echo ‘msgstr “‘ . $it . “\”\n”;
echo “\n”;
}

Dove ovviamente $myAr è l’array che trovate nei vari file di traduzioni (i file si chiamano Zend_Validate.php).
Ho creato così un nuovo Catalogo di Poedit, poi l’ho aperto con un Editor di testo e ho incollato dentro il risultato visualizzato nel browser dalla suddetta procedura. Aprendolo con Poedit e salvando il gioco è fatto!

Potreste incontrare problemi (a seconda del SO e dell’Editor che usate) sulla codifica dei file. A me, ad esempio, Poedit è andato in errore perchè il file, una volta salvato con l’Editor, aveva perso la codifica UTF-8. Per fortuna l’editor del Mac (TextEdit) permette di scegliere la codifica in fase di salvataggio. Ripristinato l’UTF-8 tutto è filato liscio.

Esistono diversi metodi comunque per convertire la codifica dei file di testo (UTF-8).

Zend_Form e problema col quote (magic_quotes_gpc)

E’ da ieri che sbatto la testa su questo problema!
Sul mio server di sviluppo tutto ok, nessun problema. In ambiente di produzione invece riscontro un problema col quoting dei campi passati dalle form create con Zend_Form. Appena aggiungo le virgolette (“) la procedura di salvataggio dati me li quota (\”) e non capivo il perchè!

Il problema sta nell’impostazione magic_quotes_gpc del PHP.
Sul server in produzione infatti questo è settato a On. Ecco la differenza tra l’ambiente di sviluppo (su cui è Off, infatti monta PHP 5.3.x) e quello in produzione (PHP 5.2.x). A questo punto le alternative per risolvere il problema sono diverse.

Una soluzione è disabilitare tale impostazione via file .htaccess inserendo in questo file la riga:

php_flag magic_quotes_gpc Off

In alternativa, ove questo non sia possibile, consiglio l’utilizzo dell’ottimo Filter_StripSlashes creato e descritto da Phil Brown.

Monitorare gli utenti nelle Applicazioni Web

mixpanel_logo

Mixpanel è un servizio di analisi e monitoraggio real-time che aiuta le aziende a capire come gli utenti interagiscono con una determinata Applicazione web. In pratica il servizio tende a monitorare il comportamento e l’interazione che gli utenti hanno con l’applicativo stesso.

Mixpanel è basato su API. Le informazioni infatti possono essere inviate da qualsiasi applicazioni, da Facebook a quelle installate su iPhone/Android. Tra le caratteristiche importanti segnalate parlano di un vero real-time per l’elaborazione dei dati e di una personalizzazione del monitoraggio degli eventi.

Appena ho un attimo lo installo e vediamo di cosa si tratta…

Impostare mysql.sock in Zend Framework tramite file .ini

Zend Framework Logo

Come ambiente di sviluppo PHP utilizzo Zend Server Community Edition (installato su OSX). Dal giorno in cui l’ho installato le applicazioni sviluppate in Zend Framework hanno smesso di funzionare. Il problema che veniva segnalato era:

Fatal error: Uncaught exception 'Zend_Db_Adapter_Exception' with message 'SQLSTATE[HY000] [2002] Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)' in /usr/local/zend/apache2/htdocs/mySite/library/Zend/Db/Adapter/Pdo/Abstract.php:144

In pratica Zend_DB_Adapter non trova il socket per la connessione al database MySQL.

Da quando è stato inserito il modulo Zend_Application io preferisco impostare ogni cosa nel file di configurazione .ini pertanto, per risolvere il problema, ed impostare il corretto mysql socket, basta aggiungere la seguente riga tra i parametri del DB:

resources.db.params.unix_socket = "/usr/local/zend/mysql/tmp/mysql.sock"

Zend Framework, teoria di base (Controller)

Zend Framework Logo

Continuo a studiare e fare ordine nella mia testa.

I modi di instradare una richiesta (indirizzo web URI) verso il codice che svolgerà le funzioni necessarie a soddisfarla sono essenzialmente 2:

  • Page Controller
    Questa tecnica prevede l’uso di file separati, uno per ogni pagina (index.php, news.php, articoli.php, ecc.). Il controllo dell’applicazione risulta quindi decentralizzato in differenti files separati.
  • Front Controller
    Questo tipo di controller invece convoglia le richieste verso un unico file (solitamente index.php) che determina quale codice eseguire attraverso diversi sistemi.

Chi ha iniziato a programmare in PHP diversi anni fa non avrà difficoltà a ricordare quando tutto veniva smistato secondo la logica del Page controller. Zend Framework invece, come abbiamo già visto in precedenza, oltre al pattern MVC (Model View Controller), implementa anche il Front Controller pattern per rispondere alle richieste effettuate.

Il componente Zend_Controller è quello che si occupa di tutto ciò. E’ costituito dal Front Controller, dall’Action Controller e da altre classi di supporto.
Come abbiamo visto ogni richiesta effettuata al server web viene inoltrata ad un unico file (denominato “entrance point”, solitamente il nostro index.php) il quale avvia il Front Controller che ha il compito di identificare l’azione e il comando da eseguire in base all’URI richiesto. In Zend Framework questo controller è composto da due componenti essenziali: il router e il dispatcher. Il router determina quale azione deve essere chiamata e il dispatcher esegue la chiamata. Il tutto passa in mano all’Action Controller che si interfaccia a Model e View per costruire la risposta da fornire all’utente.

Vediamo ora nel dettaglio le varie fasi di avanzamento del Controller dello Zend Framework.

Request

E’ una qualsiasi richiesta HTTP che giunge al nostro web server. Viene incapsulata nella classe Zend_Controller_Request_Http che ne memorizza ogni dato (le nostre “vecchie” variabili $_GET, $_POST, $_COOKIE, $_SERVER e $_ENV). Zend Framework fornisce una serie di funzioni in grado di leggere e manipolare questi dati.

Router

La Request viene poi passata al Router che determina quale codice andrà eseguito. Il suo lavoro si basa essenzialmente sull’URI, che viene scomposto e interpretato, e giunge al termine quando vengono identificate le procedure da eseguire. Anche qui il Framework è farcito di una serie di classi che permettono di interagire direttamente con il router per poter arrivare anche a riscrivere parte del suo lavoro (Zend_Controller_Router_Rewrite).

Dispatch

Le procedure che andremo a creare per il nostro applicativo sono classi che estendono il componente Zend_Controller_Action. Ogni classe quindi identificherà un controller e i suoi metodi saranno le varie action da eseguire. Il Front Controller, una volta identificati controller e action, avvia il processo di dispatch che eseguirà le seguenti azioni:

  • caricare il controller (la classe definita da router)
  • istanziare la classe
  • invocare l’action (il metodo della classe definito da router)

Fatto ciò il dispatcher passerà il controllo dell’esecuzione alla classe dell’Action controller.

Action

Le Action sono tutte classi che dovranno essere derivate dalla principale Zend_Controller_Action. Ogni classe avrà a disposizione la Request (da cui leggere eventuali dati), la Response (per scriverli), le View (per la gestione delle viste dell’output) e i Model (per interfacciarsi al Database, ad esempio).
Una caratteristica fondamentale e molto utile delle Action è la possibilità di redirezionare tra loro le varie action (metodi delle classi) tra loro. In pratica possiamo far eseguire più di un action alla volta al nostro controller senza dover ricaricare la pagina.

Oltre alle singole azioni (metodi) che andremo a creare per ogni classe è possibile servirsi di alcuni metodi particolari che verranno eseguiti in momenti specifici: init() al momento in cui viene istanziata la classe, preDispatch() prima di eseguire il metodo, postDispatch() al termine delle operazioni svolte dal metodo. init() risulta particolarmente comodo nelle classi in cui decidiamo che potranno essere eseguite diverse action. In questo caso init() verrà eseguito una sola volta mentre il preDispatch() più volte, prima di ogni azione.

Una volta terminata l’esecuzione della/delle Action necessarie l’output che è stato generato viene inviato al client tramite il Response.

Response

Il Response è una sorta di contenitore nel quale andremo a caricare tutto l’output che invieremo all’utente. Se stiamo parlando di una risposta Web  l’oggetto del response sarà un’istanza del componente (classe) Zend_Controller_Response_Http.
Questo oggetto contiene tre tipi di dati:

  • Headers
    Non quello della pagina HTML bensì un header HTTP nel caso di una pagina Web
  • Body
    Nel caso di pagina web sarà appunto l’intero codice HTML che comporrà la pagina
  • Exception
    Per la gestione degli errori

E’ importante sottolineare che il response potrà essere di diverso tipo e non solo HTML (XML, PDF, ecc.). Il modello MVC infatti prevede che la logica sia separata dall’output (View) e questo, attraverso il response, potrà essere facilmente gestito.