Torna a PHP

Yii

Spread the love


logo Yii

In questa pagina riassumo domande e risposte su problemi e soluzioni sul framework MVC Yii. Una specie di Wiki.

PHPUnit

^TOP
Questo package è utilissimo per il test delle classi PHP in un’ottica di unit testing. Lo unit testing è una procedura per verificare il funzionamento di singole parti di codice sorgente (units). Maggiori dettagli qui.

Per usare PHPUnit ho dovuto fare una serie di passaggi propedeutici come l’installazione di PEAR.

Innanzitutto ho acceduto alla consolle come super user:

$ sudo -s

Ho installato php-pear

# apt-get install php-pear

Dopo ho scaricato PEAR nella versione 1.9.4:

root@jsbach:~# pear install PEAR-1.9.4

e poi PHPUnit:

root@jsbach:~# pear install -a phpunit/PHPUnit

solo che ho incontrato questo problema:

Unknown remote channel: pear.symfony-project.com

Dunque ho dovuto fare questi passaggi:

# pear channel-update pear.php.net
     Updating channel "pear.php.net"
     Channel "pear.php.net" is up to date
# pear upgrade-all
# pear channel-discover pear.phpunit.de
    Adding Channel "pear.phpunit.de" succeeded
    Discovery of channel "pear.phpunit.de" succeeded
# pear channel-discover components.ez.no
    Adding Channel "components.ez.no" succeeded
    Discovery of channel "components.ez.no" succeeded
# pear channel-discover pear.symfony-project.com
    Adding Channel "pear.symfony-project.com" succeeded
    Discovery of channel "pear.symfony-project.com" succeeded

A questo punto potevo installare PHPUnit:

# pear install -a phpunit/PHPUnit
...
install ok: channel://pear.symfony-project.com/YAML-1.0.6
install ok: channel://pear.phpunit.de/PHPUnit-3.6.7

Qui attenzione, mi ha installato la 3.6.7 con la quale però ho avuto un problema che sfortunatamente non ho documentato: per risolverlo ho dovuto installare una versione più vecchia (downgrade) di PHPUnit:

# pear uninstall phpunit/PHPUnit
    uninstall ok: channel://pear.phpunit.de/PHPUnit-3.6.7
# pear install -a phpunit/PHPUnit-3.4.15

Ho inoltre scaricato e avviato il Selenium server java

$ cd software/java/
$ wget http://selenium.googlecode.com/files/selenium-server-standalone-2.15.0.jar $ java -jar selenium-server-standalone-2.15.0.jar ... 16:16:53.654 INFO - Started org.openqa.jetty.jetty.Server@1827391d

Alla fine potevo lanciare il comando per eseguire lo unit test:

marcob@jsbach:~/www/trackstar/protected/tests$ phpunit unit/ProjectTest.php

che però falliva dicendomi che non trovava il file MockObject/Autoload.php. Semplicemente il file c’era ma si trovava in un’altra directory, per cui ho pensato alla soluzione più veloce di fare un link simbolico nella directory dove se lo aspettava:

root@jsbach:/usr/share/php/PHPUnit# ln -s ./Framework/MockObject/Autoload.php .

A questo punto lo unit test ha funzionato:

marcob@jsbach:~/www/trackstar/protected/tests$ phpunit unit/ProjectTest.php
PHPUnit 3.4.15 by Sebastian Bergmann.

.

Time: 1 second, Memory: 12.00Mb

OK (1 test, 1 assertion)
marcob@jsbach:~/www/trackstar/protected/tests$

 Validatori

^TOP
Se aggiungo un vincolo ad un campo di una tabella, in un secondo momento, sul db già creato e con le classi già create, posso aggiungere il controllo del vincolo nel file models/NomeClasse.php, al metodo rules.

Ad esempio, il frammento di file seguente (appname/protected/models/Project.php) implementa i controlli per alcuni campi del form:

class Project extends CActiveRecord
{
...
    public function rules()
    {
        return array(
            array('create_time, name', 'required'),
            array('create_user_id, update_user_id', 'numerical', 'integerOnly'=>true),
...

Nell’esempio precedente, all’interno del metodo Project.rules() viene definito l’array dei controlli da applicare; i controlli si configurano come array di array: a livello più alto ogni componente è un controllo; a livello più basso, ogni controllo è a sua volta un array che può essere costituito da due o più componenti; nell’esempio, il primo controllo predispone che i campi create_time e name siano cambi obbligatori (requested), il secondo controllo dice che i campi create/update_user_id siano numerici (‘numerical’) e, ancorché numerici, strettamente interi (‘integerOnly’ => true).

I valori che ho scritto tra parentesi sono i tipi di controllo o tipi di validatore. Ci sono molti tipi di validatori semplici e complessi predefiniti in Yii (che controllano, ad esempio, che un campo sia un intero, che sia un URL ben formato, che sia un captcha).

Oppure si possono aggiungere altri validatori customizzati in due modi alternativi ed equivalenti:

  • aggiungendo un metodoValidator alla classe oppure
  • creando una nuova classeValidator che eredita da CValidator.

Gestione delle relazioni molti-a-molti.

^TOP

Con Yii lo possiamo fare senza scrivere una riga di SQL, ma handle with care!

Sto rifacendo l’esempio del Manuale Agile Web Application Development with Yii 1.1 and PHP 5 e ho trovato un particolare non corretto.

In breve l’esempio è l’applicazione Trackstar che è un rudimentale sistema di gestione di ticket. Le entità fondamentali sono i Project (progetti), Issue (segnalazioni) e User (utenti); Issue e User sono collegati da una relazione molti-a-molti.

La situazione è la seguente: sono in una specifica Issue il cui progetto padre è Project1;
Project 1 è gestito dagli utenti 1 e 2.
Obiettivo: voglio una tendina nella Issue per visualizzare gli utenti 1 e 2.
Passaggi:
Dall’IssueId devo procurarmi

  • ProjectId
  • Id, e Nome degli utenti collegati al ProjectId calcolato, per costruire il menu a tendina.

Nel controller IssueController.php ho questa istruzione per il calcolo del menu a tendina:

$this->getProject()->getUserOptions())

ProjectId lo calcolo “affogando” $this->getProject() in

views/issue/_form.php,

essendo getProject() un metodo di IssueController.php; la variabile privata _project viene inizializzata quando creo il controller, infatti all’inizio ho la funzione loadProject che inizializza _project.
Attenzione: a questo stadio non ho alcun Id Progetto, è il filter (che è un pezzo di programa che va in esecuzione subito prima di invocare il Controller, come un trigger), che inizializza correttamente l’Id in base alla request HTTP.
Il secondo pezzo (Id, Nome degli utenti collegati al ProjectId calcolato == getUserOptions()) è un metodo del model che passa all’helper CHtml::listData l’array con id e descrizione degli elementi da fa comprarire nella tendina; l’array è $this->users all’interno del file models/Project.php il quale viene popolato all’atto della istanziazione dell’oggetto Project: quando viene chiamato il costruttore, la classe madre (CActiveRecord, dalla quale proviene il mio modello Project) manda in esecuzione il metodo relations(), che nella classe genitore è vuoto, ma viene sovrascritto (overridden) dal metodo relations della classe figlia (Project). Questo metodo va scritto in base a come è fatto il database: nel mio caso ho una relazione che mi lega Project a Issue e User a Project; la parte più intereesante è quella molti a molti in cui inizializzo la proprietà ‘users’:

'users' => array(self::MANY_MANY,
   'User',
   'project_user_assignment(project_id, user_id)'
),

Attenzione, punto cruciale: nel voler listare le issues appartenenti ad un project, se opero come da manuale Yii (pg 129), vado incontro ad un problema: nel listato proposto nel manuale invoco la funzione loadModel di Project senza argomenti, mentre, per come è definita in controllers/ProjectController.php essa ha bisogno di un parametro obbligatorio: l’$id del progetto. Ne risulta un errore del server che evidenzia questo problema.
Nell’ambito di questa maschera si può risolvere il problema modificando così la definizione del metodo:

public function loadModel($id = null)
{
  $id = (!isset($id)) ? $_REQUEST['id'] : $id;
  $model=Project::model()->findByPk($id);
  if($model===null)
    throw new CHttpException(404,'The requested page does not exist.');
  return $model;
}

dando una possibilità di avere $id null e passando al metodo findByPk, anziché il parametro, il valore calcolato in base alla presenza del parametro di ingresso o in alternativa dalla REQUEST.

In realtà guardando meglio il problema è un’errata del libro, non del CRUD! prima della modifica c’era:

public function actionView($id)    
{
        $this->render('view',array(
            'model'=>$this->loadModel($id),
        ));
}

dopo la modifica suggerita dal libro c’è

public function actionView()
{
    $issueDataProvider=new CActiveDataProvider('Issue', array(
        'criteria'=>array(
        'condition'=>'project_id=:projectId',
        'params'=>array(':projectId'=>$this->loadModel()->id),
    ),
    'pagination'=>array(
    'pageSize'=>1,
    ),
));
$this->render('view',array(
    'model'=>$this->loadModel(),
    'issueDataProvider'=>$issueDataProvider,
));
}

Evidentemente si sono fumati qualcosa, sicuramente l’$id che il CRUD passava prima.

Role Based Access Control

^TOP

Sostanzialmente i concetti sono:

  • esistono utenti, esistono ruoli ed esistono dati su cui vogliamo definire in cosa consistono i permessi e raggruppare questi permessi in ruoli, che poi vengono assegnati all’utente. In più c’è che in una stessa tabella un utente può avere privilegi diversi su righe diverse (a questo livello non ero mai arrivato: di solito se un utente aveva un permesso su una tabella ce l’aveva uguale per tutte le righe).
  • esistono le unità di autorizzazione  (UdA) che sono i ruoli (roles), i compiti (tasks) e le operazioni atomiche (operations). I roles sono ragruppamenti di tasks, e roles già esistenti; i tasks sono raggruppamenti di operations e task già esistenti; le operazioni atomiche non si suddividono ulteriormente, sono operazioni elementari su un oggetto (es. ProjectRead o IssueDelete)
  • c’è un oggetto di Yii che si chiama CDbAuthManager che permette di gestire questo schema di autorizzazioni a livello di database (scrivo nel db anziché in un file le informazioni che servono all’applicazione per capire chi può fare cosa). Ci sono 3 tabelle per gestire le UdA i cui nomi sono cablati dentro la classe CDbAuthManager (AuthItem, AuthItemChild, e AuthAssignment)
  • L’assegnazione dei permessi avviene in modo gerarchico, dal basso verso l’alto: definisco i permessi elementari e li associo ad un ruolo; poi un nuovo ruolo potrà ereditare questi permessi e aggiungerne di nuovi. E così via. La classe authManager possiede i metodi createRole e createOperation per creare il ruolo e la componente su cui dare il permesso.
  • qui viene la parte più potente: il comando di shell yiic può essere arricchito di nuove funzionalità: si scrive un meteodo run() in una nuova classe che estende CConsoleCommand che fa esattamente quello che vogliamo: in questo contesto, vogliamo fare un nuovo comando che crei una struttura RBAC (Role Based Access Control); in esso definiamo i comandi che creano i ruoli e le operazioni elementari, così come le relazioni gerarchiche (ad esempio
    • garzone => stende la pasta;
    • pizzaiolo => fa quello che fa il garzone e, in più, la inforna, etc).

 

la classe authManager che verrà invocata in questa classe figlia scriverà questi dati sulle tre tabelle summenzionate.

  • dopo aver scritto il php, si può invocare il nuovo comando da shell che effettivamente lancia il metodo run() e popola le tabelle.
  • passo successivo, si assegnano i ruoli agli utenti e agli oggetti. Così non solo potremo avere che solo determinati utenti potranno agire su determinate tabelle, ma addirittura determinati utenti potranno eseguire determinate operazioni su determinate righe della tabella che rappresenta l’oggetto! Per fare questo dovremo fornire una tabella di permessi per ogni tabella dello schema!
  • Infine: come si applicano questi controlli? cioè come facciamo a sapere se un determinato utente può svolgere una determinata operazione su qualche record di una tabella? occorre usare i filtri, pezzi di codice che vanno in esecuzioni immediatamente prima o subito dopo l’esecuzione del controller (come un trigger in un database).

Yii Reloaded

Spread the love

Spread the loveYiiReloaded Con questo post voglio rivedere il concetto di MVC secondo un altro paradigma tratto dalla vita reale. Indice 1 Vivisezione di index.php 1.1 La centrale comandi 1.2 L’applicazione Web 1.3 Come creare una applicazione a partire dall’URL 2 Motore: azione! 3 The Blues 4 Come salvare in sessione altri parametri Vivisezione di …

Lascia un commento

Your email address will not be published.