martedì 27 aprile 2010

symfony, gestire un profilo utente con sfGuard

Ad oggi la gestione utenti di Lyra è quasi inesistente. Viene utilizzato sfDoctrineGuardPlugin per proteggere con login il backend dell'applicazione, ma esiste di fatto un solo utente, il super-amministratore, creato automaticamente con il caricamento dei dati di esempio (fixtures). Usando le funzioni di sfDoctrineGuardPlugin possono già essere aggiunti nuovi utenti e gruppi, ma non venendo gestiti permessi specifici, ogni nuovo utente può operare senza alcuna restrizione, risultando a tutti gli effetti pratici indistinguibile dal super-amministratore.

Inoltre per ogni utente possono essere salvate solo le informazioni di base gestite dalla classe sfGuardUser: nome utente e password. Questo è il primo aspetto da migliorare con la creazione di un profilo utente che ci consenta di gestire informazioni aggiuntive: nome, cognome ed indirizzo e-mail; almeno per il momento, altri campi potranno essere aggiunti in seguito se necessario.

Ho seguito questa guida pubblicata sul blog ufficiale di symfony.

Per prima cosa si crea nello schema una classe per il profilo utente

config/doctrine/schema.yml

LyraUserProfile:
  tableName: users
  options:
    collate: utf8_unicode_ci
    charset: utf8
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    user_id:
      type: integer(4)
    first_name:
      type: string(80)
    last_name:
      type: string(80)
    email:
      type: string(150)
  relations:
    User:
      class: sfGuardUser
      local: user_id
      foreign: id
      type: one
      foreignType: one
      foreignAlias: Profile
      onDelete: CASCADE

L'impostazione 'onDelete: CASCADE' non è presente nella guida: credo sia una svista perché altrimenti l'esistenza della relazione rende impossibile cancellare un utente dal backend.

Si modificano le fixtures per includere le informazioni del profilo negli utenti creati con i dati di esempio, nel mio caso il super-amministratore

data/fixtures/users.yml

sfGuardUser:
  admin:
    username:       admin
    password:       admin
    is_super_admin: true

#User profile

LyraUserProfile:
  p_admin:
    first_name: Lyra
    last_name: Administrator
    email: lyra@localhost
    User: admin

Si ricostruiscono le classi del modello e si ricaricano i dati di esempio con

./symfony doctrine:build --all --and-load

La sintassi del comando è quella da usare in symfony 1.4.

A questo punto si deve personalizzare il form per la creazione e modifica di un utente nel backend in modo da poter inserire le informazioni del profilo. La guida citata all'inizio dice di copiare in lib/form/doctrine il file sfGuardUserAdminForm.class.php che si trova nella cartella plugins/sfDoctrineGuardPlugin/lib/form/doctrine e procedere con la personalizzazione della copia. In questo modo un successivo aggiornamento del plugin non sovrascriverà le nostre modifiche. Il problema è che facendo in questo modo a me la cosa non funziona, in particolare si verificano errori nei test funzionali. Infatti durante i test l'autoload carica la classe sfGuardUserAdminForm originale (quella nella cartella del plugin) e non la copia modificata nella cartella lib/form/docrine del progetto.

Non sono stato tanto ad indagare ed ho preso una strada leggermente diversa: ho creato una classe LyraUserAdminForm derivata da sfGuardUserAdminForm

lib/form/doctrine/LyraUserAdminForm.class.php

class LyraUserAdminForm extends sfGuardUserAdminForm
{
  public function configure()
  {
    parent::configure();

    $profileForm = new LyraUserProfileForm($this->object->Profile);
    unset($profileForm['id'], $profileForm['user_id']);
    $this->embedForm('user_profile', $profileForm);
    $this->widgetSchema['user_profile']->setLabel(false);
  }
}

Il form con le informazioni del profilo viene inizializzato con l'oggetto ottenuto dalla relazione definita nello schema (Profile, foreignAlias della relazione User di LyraUserProfile) e poi incluso nel form principale con embedForm().

Bisogna poi modificare generator.yml perché venga utilizzata la nostra classe al posto di sfGuardUserAdminForm e per visualizzare in un 'pannello' il form embedded (user_profile) con le informazioni del profilo.

apps/backend/modules/sfGuardUser/config/generator.yml
...
     form:
        class: LyraUserAdminForm
        display:
          "NONE": [username, password, password_again]
          PANEL_PROFILE: [user_profile]
          PANEL_PERMISSIONS: [is_active, is_super_admin, groups_list, permissions_list]
...

Dopo aver modificato questo file ho dovuto anche rimuovere (in realtà ne ho commentato il contenuto) il file generator.yml originale installato dal plugin in plugins/sfDoctrineGuardPlugin/modules/sfGuardUser/config/. Diversamente è impossibile personalizzare alcune opzioni di configurazione. Mi sembra di ricordare qualche post nel forum di symfony dove veniva fatto notare questo problema. La cosa seccante è la necessità di ripetere l'operazione ad ogni aggiornamento del plugin, ma soluzioni alternative non ne ho trovate.

Alla fine ho creato i file di lingua per la traduzione dell'interfaccia. Tutto il lavoro fatto è visibile nel log delle modifiche della revisione 52.

lunedì 26 aprile 2010

Lyra, ultimi sviluppi

Lo sviluppo di Lyra continua anche se ultimamente non ho ho avuto molto tempo di scrivere sul blog. Faccio quindi un breve punto della situazione riassumendo le modifiche apportate dalle ultime revisioni. Il codice come al solito può essere consultato nel repository su Google Code.

Modifiche alla gestione metatags dei contenuti

La gestione dei metatags (meta-title, meta-description, meta-keywords, meta-robots) dei contenuti avviene tramite un modulo apposito (classe LyraMetatagsForm) incorporato nel modulo di inserimento / modifica del contenuto. Sono stati inoltre creati due parametri di configurazione globale per aggiungere un prefisso o un suffisso al meta-titolo della pagina. Questo permette ad esempio di avere titoli delle pagine del tipo

Titolo articolo | Nome sito

oppure

Nome sito | Titolo articolo

Dove 'Nome sito |', '| Nome sito' sono il valore di un parametro di configurazione e possono essere quindi impostati a piacere. (revisione 43).

Modifiche al layout del backend

Ho fatto qualche modifica per migliorare l'aspetto e rendere più pulita e ordinata l'area di amministrazione (revisioni 44 e 47).

Ottimizzazione liste di selezione etichette

La generazione delle liste di selezione delle etichette da assegnare ad un contenuto è stata ottimizzata e richiede adesso una sola query anche quando ci sono più cataloghi utilizzabili per il tipo di contenuto (revisione 45). Inoltre la gestione delle liste di selezione delle etichette è stata spostata dalla classe LyraArticleForm a LyraContentForm, in quanto questa parte dovrà essere utilizzata anche per i tipi di contenuto diversi da 'Article', che ancora non esistono (revisone 46).

Modifica alla gestione dei parametri di configurazione

La gestione dei parametri di configurazione globali e dei tipi di contenuto è stata ristrutturata. Il valore di un parametro di configurazione non viene più letto con il metodo getCfg() di LyraContent, ma viene utilizzata una classe apposita (LyraConfig) istanziata nelle azioni e passata al template. Mi sembra questa una soluzione più pulita e più aderente al pattern MVC dell'implementazione precedente (revisione 48).

Aggiornamento del framework

Lyra utilizza adesso l'ultima versione di symfony (1.4.4). Anche il plugin sfDoctrineGuardPlugin è stato aggiornato. (revisioni 49, 50, 51).