lunedì 23 novembre 2009

Lyra, backend cataloghi ed etichette

Credo sia chiaro a chiunque abbia seguito gli articoli precedenti che lo sviluppo del backend di un'applicazione symfony segue linee abbastanza standard: le funzioni base di ogni modulo come la visualizzazione dell'elenco record e le funzioni di inserimento, modifica, cancellazione sono generate automaticamente con il comando doctrine:generate-admin; le personalizzazioni per la maggior parte si realizzano impostando parametri nel file di configurazione (generator.yml nella cartella config del modulo) anche se è naturalmente possibile scrivere proprio codice per realizzare funzionalità aggiuntive o che comunque si discostano dallo standard.

Per questo motivo non mi soffermerò nei dettagli dello sviluppo della parte restante del backend di Lyra perché si finirebbe per ripetere quanto già detto in precedenza. Di volta in volta metterò in evidenza solo gli aspetti su cui a mio parere vale la pena soffermarsi, d'altra parte il codice completo è sempre disponibile nel repository su Google Code.

Gestione cataloghi

./symfony doctrine:generate-admin backend LyraCatalog --module=catalog

Dopo la consueta personalizzazione di generator.yml si ottiene questo risultato.

Il contenuto delle colonne Etichette e Pubblicato è generato da due partial (_labels.php e _published.php in apps/backend/modules/catalog/templates): il primo visualizza il contatore delle etichette inserite nel catalogo e mostra un link per accedere alla relativa lista.

apps/backend/modules/catalog/templates/_labels.php

<?php
    echo link_to($lyra_catalog->countLabels()-1,'@lyra_label_label?id='.$lyra_catalog->getId());
?>&nbsp;(<?php echo link_to(__('LINK_SHOW_LABELS'),'@lyra_label_label?id='.$lyra_catalog->getId());?>)

Il secondo genera i link per le icone usate per modificare lo stato pubblicato / non pubblicato del catalogo e funziona nel modo già visto per la gestione articoli e commenti.

La configurazione del form per l'inserimento e modifica di un catalogo (classe LyraCatalogForm) non presenta particolarità. Ogni catalogo potrà essere associato ad uno o più tipi di contenuto, selezionabili da una lista di checkbox nel form; non è necessario scrivere codice particolare per creare l'insieme delle checkbox in quanto Doctrine genera automaticamente un widget sfWidgetFormDoctrineChoiceMany in base alla relazione cataloghi - tipi di contenuto definita in schema.yml. Basta impostare l'attributo expanded a true in configure() per avere un'insieme di checkbox al posto della lista di selezione.

Quando si crea un nuovo catalogo viene anche inserito nella tabella etichette un record che costituisce la 'radice' dell'albero delle etichette appartenenti al catalogo. Questo viene fatto nel metodo doSave() di LyraCatalogForm che viene eseguito da symfony quando si salva il record.

lib/form/doctrine/LyraCatalogForm.class.php

class LyraCatalogForm extends BaseLyraCatalogForm
{
  public function configure()
  {
    unset($this['created_at'], $this['updated_at'], $this['locked_by']);
    $this->widgetSchema['name']->setLabel('NAME');
    $this->widgetSchema['description']->setLabel('DESCRIPTION');
    $this->widgetSchema['is_active']->setLabel('IS_ACTIVE');
    $this->widgetSchema['catalog_content_types_list']->setLabel('CATALOG_CONTENT_TYPES');

    $this->widgetSchema['catalog_content_types_list']->setOption('expanded', true);
  }
  protected function doSave($con = null)
  {
    $savingnew = $this->isNew();
    parent::doSave($con);

    if ($savingnew) {
      $label = new LyraLabel();
      $label->setName($this->object->getName());

      $label->setCatalogId($this->object->getId());
      $label->save();
      $treeObject = Doctrine::getTable('LyraLabel')->getTree();
      $treeObject->createRoot($label);
    }
  }
}

Ho eseguito a questo punto il commit della revisione 21.

Gestione etichette

./symfony doctrine:generate-admin backend LyraLabel --module=label

Non esiste un link nel menù principale che porti ad una lista di tutte le etichette, è possibile visualizzare le etichette di ogni catalogo dal link 'Mostra' nella colonna Etichette della lista cataloghi.

La colonna Nome non mostra direttamente il contenuto di un campo, ma il risultato del metodo getIndentName() della classe LyraLabel che fa sì che il nome venga indentato a seconda del livello che l'etichetta occupa nell'albero.

lib/model/doctrine/LyraLabel.class.php

class LyraLabel extends BaseLyraLabel
{
  ...
  function getIndentName()
  {
    $indent = $this->level-1;
    if($indent < 0) {
      $indent = 0;
    }
    return str_repeat('-- ', $indent).$this->name;
  }
}

La colonna Ordina contiene due frecce per modificare l'ordine di un'etichetta rispetto alle etichette di pari livello (i nodi fratelli). In questo caso si utilizza un partial per generare il contenuto della colonna.

apps/backend/modules/label/templates/_order.php

<?php
$node = $lyra_label->getNode();

if($node->hasNextSibling()) {
  echo link_to(image_tag('backend/arrow-down.png', array('alt' => __('LINK_T_MOVE_DOWN'))),'label/move?id='.$lyra_label->getId().'&dir=0',array('title' => __('LINK_T_MOVE_DOWN')));
}
if($node->hasPrevSibling()) {
  echo link_to(image_tag('backend/arrow-up.png', array('alt' => __('LINK_T_MOVE_UP'))),'label/move?id='.$lyra_label->getId().'&dir=1',array('title' => __('LINK_T_MOVE_UP')));
}
I link sono collegati ad un'azione move implementata in labelActions.
apps/backend/modules/label/actions/actions.class.php

class labelActions extends autoLabelActions
{
  ...  
  public function executeMove(sfWebRequest $request)
  {
    $record = $this->getRoute()->getObject();
    
    switch($request->getParameter('dir')) {
      case 0:
        $next = $record->getNode()->getNextSibling();
        if($next) {
          $record->getNode()->moveAsNextSiblingOf($next);
        }
        break;
      case 1:
        $prev = $record->getNode()->getPrevSibling();
        if($prev) {
          $record->getNode()->moveAsPrevSiblingOf($prev);
        }
        break;
    }
    $this->redirect('@lyra_label_label');
  }
  ...
}
Per quanto riguarda l'utilizzo dei metodi getNode(), hasNextSibling(), hasPrevSibling(), moveAsNextSiblingOf(), moveAsPrevSiblingOf() utilizzati nel partial e nel metodo executeMove() della classe action rimando alla documentazione ufficiale di Doctrine sui nested set.

L'unica particolarità che presenta la configurazione del form per l'inserimento e modifica di un'etichetta(classe LyraLabelForm) è data dalla generazione della lista per la selezione dell'etichetta 'padre'. Il problema è che mentre una nuova etichetta può essere inserita come figlia di una qualsiasi delle etichette esistenti, quando si modifica un'etichetta non si può selezionare come padre nessuna delle etichette sue discendenti.

Serve un esempio. Data una struttura di questo tipo

PHP
-- Framework
---- Symfony
---- CakePHP

Quando si modifica l'etichetta PHP non deve essere possibile selezionare come nuovo padre né Framework, nè Symfony, né CakePHP, perché in questo modo si distruggerebbe la struttura dell'albero. Questo controllo è fatto nel metodo configure()

lib/form/doctrine/LyraLabelForm.class.php

class LyraLabelForm extends BaseLyraLabelForm
{
  public function configure()
  {
    ...
    $query = Doctrine_Query::create()->from('LyraLabel l');

    if($this->isNew()) {
        $catalog_id = sfContext::getInstance()->getUser()->getAttribute('lyra_catalog_id');
        if($catalog_id) {
          $this->setDefault('catalog_id', $catalog_id);
          $query->where('l.catalog_id = ?', $catalog_id);
        }
    } else {
        $query->where('l.catalog_id = ? AND (l.lft < ? OR l.rgt > ?)', array($this->object->getCatalogId(), $this->object->getLft(), $this->object->getRgt()));
    }

    $this->widgetSchema['parent_id'] = new sfWidgetFormDoctrineChoice(array('model'=>'LyraLabel', 'order_by'=>array('root_id, lft', ''), 'method'=>'getIndentName', 'query'=>$query));
    $this->validatorSchema['parent_id'] = new sfValidatorDoctrineChoice(array('required'=>false, 'model'=>'LyraLabel'));
    ...
  }

In inserimento si mostra nella lista tutto l'albero delle etichette appartenenti al catalogo, in modifica si escludono i discendenti del record etichetta che stiamo modificando. Da dove viene fuori la query nell'else? Per capirlo occorre conoscere più nei dettagli come viene costruito un nested set e in particolare la funzione dei campi lft e rgt: una spiegazione che personalmente ho trovato utile è questa.

Per riassumere la gestione etichette nel backend: con le frecce nell'elenco si modifica l'ordine tra etichette sullo stesso livello, entrando in modifica del record si può spostare un'etichetta e tutti i suoi discendenti (un ramo) sotto un'altra. Per ora è così, questa parte della gestione è un po' complessa e richiederà altro lavoro.

Chi volesse giocare un po' con le nuove funzioni può allinearsi alla revisione 22 di Google Code. Ricordo che per entrare nel backend basta inserire nel browser l'indirizzo

http://lyra/backend_dev.php o http://lyra/backend.php

Questo se si è configurato un virtual host secondo le istruzioni date nei primi articoli.

Nessun commento:

Posta un commento

Nota. Solo i membri di questo blog possono postare un commento.