Pagina Principale/Yii

Page last edited 2 038 giorni 7 ore fa
Da wiki.betaingegneria.it.

<a name="top"></a>

Yes, It Is![modifica]

Yii framework, cantiere, armamentario, per la creazione di applicazioni Web 2.0.

Nuova applicazione[modifica]

 marcob@jsbach:~$ cd www/yii/framework
 marcob@jsbach:~/www/yii/framework$ ./yiic webapp ../../jbook
 PHP Warning:  Module 'oci8' already loaded in Unknown on line 0
 Create a Web application under '/var/www/jbook'? [Yes|No] Yes

Questo crea tutto il sistema di cartelle nella directory /var/www/jbook/ Per la creazione del pattern MVC, occorre collegarsi al database: Editare

 /var/www/jbook/protected/config/main.php

alla riga corripsondente 'db'=>array( personalizzare la stringa di connessione e commentare sqlite.

Modificare anche adminEmail

 'adminEmail'=>'marco@betaingegneria.it'

Siccome si deve usare gii da interfaccia web per fare la costruzione del modello, abilitare, sempre nello stesso file, il tool gii

 http://jsbach/jbook/index.php?r=gii

Poiché Apache deve scrivere su alcune di queste cartelle, ne modifichiamo il proprietario, con il comando

 marcob@jsbach:~/www/jbook$ sudo chown -R www-data protected/

da

 marcob@jsbach:~/www/jbook$ ll
 totale 44
 drwxr-xr-x   7 marcob marcob  4096 2011-10-26 16:22 ./
 drwxr-xr-x 122 marcob root   12288 2011-10-26 16:22 ../
 drwxrwxrwx   4 marcob marcob  4096 2011-10-26 16:47 assets/
 drwxr-xr-x   2 marcob marcob  4096 2011-10-26 16:22 css/
 drwxr-xr-x   2 marcob marcob  4096 2011-10-26 16:22 images/
 -rw-r--r--   1 marcob marcob   463 2011-10-26 16:22 index.php
 -rw-r--r--   1 marcob marcob   466 2011-10-26 16:22 index-test.php
 drwxr-xr-x  14 marcob marcob  4096 2011-10-26 16:22 protected/
 drwxr-xr-x   3 marcob marcob  4096 2011-10-26 16:22 themes/

a

 marcob@jsbach:~/www/jbook$ ll
 totale 44
 drwxr-xr-x   7 marcob   marcob  4096 2011-10-26 16:22 ./
 drwxr-xr-x 122 marcob   root   12288 2011-10-26 16:22 ../
 drwxrwxrwx   4 marcob   marcob  4096 2011-10-26 16:47 assets/
 drwxr-xr-x   2 marcob   marcob  4096 2011-10-26 16:22 css/
 drwxr-xr-x   2 marcob   marcob  4096 2011-10-26 16:22 images/
 -rw-r--r--   1 marcob   marcob   463 2011-10-26 16:22 index.php
 -rw-r--r--   1 marcob   marcob   466 2011-10-26 16:22 index-test.php
 drwxr-xr-x  14 www-data marcob  4096 2011-10-26 16:22 protected/
 drwxr-xr-x   3 marcob   marcob  4096 2011-10-26 16:22 themes/

Nota bene[modifica]

Così facendo mi sono chiuso fuori, non riesco più a salvare:

 marcob@jsbach:/var/www/factotum$ sudo chmod -R g+w protected/
 marcob@jsbach:/var/www/factotum$ ll
 totale 44
 drwxr-xr-x   7 marcob   marcob  4096 2012-01-28 18:08 ./
 drwxr-xr-x 126 marcob   root   12288 2012-01-28 18:08 ../
 drwxrwxrwx   2 marcob   marcob  4096 2012-01-28 18:08 assets/
 drwxr-xr-x   2 marcob   marcob  4096 2012-01-28 18:08 css/
 drwxr-xr-x   2 marcob   marcob  4096 2012-01-28 18:08 images/
 -rw-r--r--   1 marcob   marcob   463 2012-01-28 18:08 index.php
 -rw-r--r--   1 marcob   marcob   466 2012-01-28 18:08 index-test.php
 drwxrwxr-x  14 www-data marcob  4096 2012-01-28 18:08 protected/ <--- faccio così
 drwxr-xr-x   3 marcob   marcob  4096 2012-01-28 18:08 themes/

gii[modifica]

fare girare Gii, si avrà un menu con gli oggetti da creare; si parte con Model generator. Si fa Preview e poi Generate, a meno di errori sui permessi la cosa va a buon fine. Il modello è così creato.

Uso dei prefissi nella tabelle[modifica]

Si possono usare prefissi alle tabelle per far convivere più sottoschemi all'interno di un solo schema. Buono per ambienti promiscui.

Editare il file protected/config/main.php e aggiungere la riga

 tablePrefix' => 'jb_',

qui

 db'=>array(
       'class'=>'CDbConnection', <-- a dire la verità ho aggiunto anche questo
       'connectionString' => 'mysql:host=jbookdevdb.db.8544297.hostedresource.com;dbname=jbookdevdb',
       'tablePrefix' => 'jb_', <-------------
       'emulatePrepare' => true,
       'username' => 'jbookdevdb',
       'password' => 'Devdbase1',
       'charset' => 'utf8',
 ),

PHPUnit[modifica]

^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[modifica]

^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 relazioni uno a molti[modifica]

^TOP

Con questo capitolo impariamo a gestire cose tipo: display della maschera dell'utente (User) a cui posso assegnare un (solo) ruolo (Role) che scelgo da una dropdown list.

1) alla view dello User (protected/views/user/_form.php) tolgo l'istruzione

  echo $form->textField($model,'role_id');

e lo sostituisco con

  echo $form->dropDownList($model,'role_id', $model->getRoles());

2) ho invocato un metodo del modello che ancora non esiste, getRoles: lo aggiungo a protected/models/User.php

   public function getRoles()
   { 
     return CHtml::listData(Role::model()->findAll(), 'id', 'description');
   }

mi viene in soccorso il metodo findAll che il mio modello eredita dalla classe madre CActiveRecord

3) basta. O se si vuole si premette a tutto ciò lo unit test

  class UserTest extends CTestCase
  {
       public function testGetRoles()
       {
               $options = User::model()->getRoles();
               $this->assertTrue(is_array($options));
       }
  }

dove anche qui uso il metodo definito nel modello. E' semplicissimo ma ci ho perso un pomeriggio.

Gestione delle relazioni molti-a-molti.[modifica]

^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.

Altro problema con il disply etichette[modifica]

non funziona il test a pg. 132, però l'applicazione funziona e da quello che sci si aspetta

Indagare su questo problema che è accaduto quando ho modificato i file models/Issue.php aggiungendo getStatusText() e getTypeText() e tests/units/IssueTest.php aggiungendo testGetStatusText() e testGetTypeText() per fare uscire le etichette al posto degli ID

 .PHP Fatal error:  Call to undefined method IssueTest::issues() in /var/www/trackstar/protected/tests/unit/IssueTest.php on line 24

[Forum Yii | http://www.yiiframework.com/forum/index.php?/topic/11954-chapter-6-undefined-method-issuetestissues/]

Roles, Tasks, Operations[modifica]

  • owner (ruolo)
    • user management (compito)
      • create new user (operazione)
      • edit user (operazione)
      • delete user (operazione)
    • issue management (compito)

Tabella di assegnazione ruoli ad utenti e progetti[modifica]

 create table project_user_role
 (
   project_id INTEGER NOT NULL,
   `user_id` INTEGER NOT NULL,
   role VARCHAR(64) NOT NULL,
   primary key (project_id,`user_id`,role),
   foreign key (project_id) references project (id),
   foreign key (`user_id`) references `user` (id),
   foreign key (role) references AuthItem (name)
 );

Errore nel manuale a pag. 193[modifica]

  public function associateUserToRole($role, $userId)
  {
  $sql = "
  INSERT INTO project_user_role 
  	(project_id, user_id, role) 
  VALUES 
  	(:projectId, :userId, :role)";
  
  $command = Yii::app()->db->createCommand($sql);
  
  $command->bindValue(":projectId", $this->id, PDO::PARAM_INT);
  $command->bindValue(":userId", $userId, PDO::PARAM_INT);
  $command->bindValue(":role", $role, PDO::PARAM_STR);
  
  return $command->execute(); <--- nel libro non c'è ()
  }
  

Mettere mano al menu[modifica]

Aprire il file /views/layouts/main.php

Ad esempio aggiungiamo la voce Clienti: