CakePHP

Cakephp: leggere la configurazione del database

A volte si usano Vendors di terze parti che hanno un proprio accesso al DB, quindi è utile passare i parametri di collegamento al DB specificati in app/config/database.php.

if (class_exists('DATABASE_CONFIG')) {
	$dbconfig =& new DATABASE_CONFIG();
}

$host = $dbconfig->default['host'];
$login = $dbconfig->default['login '];
$password= $dbconfig->default['password'];

Ordinare i risultati con i campi dei model associati ricorsivamente: ORDER BY FIELD.

Il titolo è un po’ contorto, ma non sono riuscito a trovare di meglio.
L’esigenza nasce quando voglio ordinare il risultato di una query che vede interessati vari Model utilizzando un determinato campo per l’ordinamento.
Faccio l’esempio: ho i seguenti model e le seguenti relazioni.

Continent hasMany Country
Country hasMany News

Il modello Continent ha un campo ‘order’ che serve a mantenere un determinato ordine di presentazione per i continenti.
Voglio ottenere una lista di News ordinate per Continent.order.
Poichè il model News ha molte relazioni con altri model (Tag, Area, Comment, ecc. ecc. ), utilizzo il behavior Containable.
A questo punto non è sufficente impostare l’elemento ‘order’ a ‘Continent.order ASC’, perchè la query restituisce un errore. Infatti CakePHP esegue prima una query per ricavare l’elenco delle News e per ogni News ricavata esegue una successiva query per determinare il continente relativo al country.

//
$this->Continent->find('all', array('order' => 'Continent.name')) // Restituisce un errore nell query SQL
//
 

SOLUZIONI
Una soluzione generica che risolve ampiamente il problema è impiegare questo behavior suggeritomi da fzanardo nel forum, ma che non ho personalmente provato.

Una soluzione light invece è la seguente:

// estraggo gli di dei continenti ordinati in base al campo 'order'
$continents = array_keys($this->News->Country->Continent->find('list',array('fields'=>array('id','order'),'recursive'=>-1,'order'=>'order')));

$lista = $this->News->find('all',array(	'conditions'=>$conditions,
												'contain'=>array(
															'Area',
															'Country'=>array('Continent'),
														),
												'order' => array('FIELD(Country.continent_id,'.implode(',',$continents).')')

ORDER BY FIELD(Country.continent_id, 1,4,3,6,5,2) sarà l’impostazione del’ordine che consentirà di ottenere l’ordine voluto, mantenendo la query leggera.
Riferimento: http://cakebaker.42dh.com/2008/06/10/order-by-field/

Attenti al model del plugin in CakePhp

Quando si crea un plugin, si possono utilizzare la maggior parte delle convenzioni comuni, facendo attenzione però al model.
Ad esempio: ho un plugin chiamato categories che mi consente di gestire le categorie strutturate con gerarchia ad albero e in questo plugin dichiaro il model Category.
Se nella mia applicazione voglio creare una relazione con il model Article dovrò fare attenzione di inserire nel campo ‘className’ il nome del plugin anteposto al nome del model, così come nell’esempio riportato qui sotto.

var $belongsTo = array(
        'Category' => array(
            'className' => 'Categories.Category',
            'foreignKey' => 'category_id'
        )
    );

Riferimento al manuale: http://book.cakephp.org/view/117/Plugin-Models

poEdit e i file .ctp

Se si tenta di utilizzare poEdit per la traduzione di file con estensione .ctp inizialmente si ottiene un messaggio del tipo:
“Poedit non ha trovato alcun file nelle directory analizzate”
Se si va in File > Preferenze > Parser e si modifica PHP si può aggiungere una estensione e si avrà una lista del tipo
“*.php;*.ctp”
Ma questo non basta perchè si otterrebbe un messaggio del tipo:
“xgettext: warning:file `users/login’ extension `ctp’ is unknown;will try C”

Per far processare correttamente i file .ctp da poEdit è sufficente aggiungere una riga nel comando del parser.

  1. Aprire le preferenze di poEdit
  2. Selezionare Parsers e scegliere PHP
  3. Modificare il setup del parser PHP e alla fine del comando del parser aggiungere:
    --language=php

Riavviare il programma

Estendere un controller

Per estendere un controller è sufficente importare il controller da inserire prima della dichiarazione della nuova classe che ne è l’estensione.

Esempio:

App::import('Controller', 'Nodes');
class ArticlesController extends NodesController {
    var $name = 'Articles';
}

Giusto da far notare che si deve importare “Nodes” e non “NodesController”

Ancora su Auth component

A completamento degli appunti sul component Auth voglio segnalare una serie di tutorial interessanti su Web Developement 2.0

http://www.webdevelopment2.com/cakephp-auth-component-tutorial-1/

http://www.webdevelopment2.com/cakephp-auth-component-tutorial-2/

http://www.webdevelopment2.com/cakephp-auth-component-tutorial-3/

Generare documenti RTF: la soluzione finale ?

E’ risaputo che la strada più semplice per produrre documenti formattati e stampabili nelle applicazioni web è quella di creare un file PDF al volo, usando una delle diverse librerie disponibili (la mia preferita rimane comunque FPDF).
Nelle applicazioni gestionali capita frequentemente che il cliente richieda la produzione di un documento da trattare con MS-Word e che abbia una formattazione predefinita e allora le soluzioni diventano più complesse: si potrebbe pensare a interfacciare PHP con OpenOffice usando ad esempio, la libreria PHP DocWriter oppure usare le funzioni COM per interfacciarsi direttamente a MS-Word.
Queste due strade sono poco percorribili quando l’applicazione deve girare su uno spazio web su server in hosting condiviso per via dell’impossibilità di installare a proprio piacimento librerie o applicazioni aggiuntive.
Ecco allora che una soluzione più facilmente realizzabile è la produzione di un documento RTF con una libreria che, secondo me, è nel panorama open source la più completa in assoluto, la più documentata in assoluto, la più ben fatta in assoluto: PhpRtf (http://www.phprtf.com).
Nonostante la versione attualmente distribuita è la 0.3, questa libreria risulta molto stabile e presenta le funzioni necessarie per creare i principali elementi di un documento RTF: Intestazioni e piè di pagina, tabelle, immagini, link, bordi, sflondi, sessioni…. che altro vi serve ?
Usando la programmazione a oggetti è stata realizzato un set di classi che possono essere usate con molta semplicità e molta flessibilità, grazie anche alla documentazione delle API realizzata con phpDocumentor.
Per creare un documento è sufficente istanziare la classe principale, aggiungere una sezione e cominciare a scrivere del testo nella sezione.
Una comodità che si può apprezzare usando la classe è quella dell’uso di tag all’interno del testo in stile HTML per la formattazione semplice e l’inserimento di elementi quali data corrente e numero di pagina.
L’integrazione con CakePHP è immediata utilizzando la funzione vendor.


vendor("rtf/rtf/Rtf");
$rtf = new Rtf();
$sect = &$rtf->addSection();
$sect->writeText('Hello World.', new Font(12), new ParFormat('center'));
$rtf->sendRtf('Hello World');

Questo è uno dei vari esempi presenti nel sito dal quale trarre spunto.

Creare una lista per la select in CakePHP

Ho bisogno di creare un’array di date per alimentare una select.
Al di là della correttezza progettuale del metodo da me adottato, quanto segue mi serve come “esercizio” nell’uso del metodo find che ha sostituito il generateList ormai deprecato.

Nella mia select vorrei avere un elenco di date, in formato italiano (d-m-Y), ordinate in ordine discendente, che rappresentano tutte le date utilizzate in una tabella nel campo ‘data’.

Una prima soluzione è quella di eseguire una normale query utilizzando il metodo $model->query().
$mylista = $this->$table->query("SELECT news.data, DATE_FORMAT(news.data , '%d-%m-%Y' ) AS `datait` FROM `news` GROUP BY `data` ORDER BY `data` DESC ");
In questo caso si dovrebbero utilizzare i nomi delle tabelle impiegate e non i nomi dei modelli.

La soluzione che utilizza il nome del model è la seguente.
$mylista = $this->$table->find('all',array('fields'=>array("DATE_FORMAT(data , '%d-%m-%Y' ) as datait",'data'),'order'=>'data DESC'));

$mylista è un array che contiene i dati cercati e è sufficente utilizzare combine per ottenre l’array desiderato.
$datelist = Set::combine($mylista,'{n}.News.data','{n}.0.datait');

FormHelper: creare una select automagicamente

Nella creazione di un form con l’helper è sufficente assegnare il giusto nome ad una variabile affinchè venga utilizzata come options in una select, senza dover specificare altri parametri. Mi spiego con un esempio.

Devo gestire dei gruppi di utenti con la possibilità di avere dei sotto-gruppi, quindi ho il model Group che agisce come un albero (beahvior Tree) e in fase di inserimento devo specificare il gruppo parent, popolando il campo parent_id con l’id del gruppo padre.
Il form di inserimento deve quindi contenere una select da dove poter scegliere il gruppo padre.

Controller: groups_controller
$groups = $this->Group->findAll(null,'id,groupname','groupname ASC',null,null,-1);
$groups = Set::combine($groups, "{n}.Group.id","{n}.Group.groupname");
$this->set('parents',$groups);

Nel controller ho ricavato l’elenco dei gruppi e lo assegno alla variabile ‘parents’. Questo è il passaggio determinante.

View: add.ctp
input('Group.parent_id', array('label'=>false));?>

La chiave di questo comportamento è l’assegnazione del giusto nome alla variabile che contiene le options, ovvero deve essere un nome che risponde alle regole delle inflessioni di CakePHP, infatti il nome della variabile giusto è definito dalla seguente espressione

$varName = Inflector::variable(Inflector::pluralize(preg_replace('/_id$/', '', $this->field())));

Ma poichè sto utilizzando un TreeBehavior è sicuramente conveniente avere una select organizzata appunto come un albero sfruttando la funzione

$groups = $this->Group->generatetreelist (null,"{n}.Group.id","{n}.Group.groupname",'-- ');

Auth Component: gestione dei permessi semplice e immediata

Una volta autenticato un utente si può facilmente controllare il suo operato intercettando ogni azione eseguita all’interno dell’applicazione senza ricorrere alle ACL.

Si possono definire controller/action per le quali non richiedere l’autenticazione definendo il valore dell’attributo
$allowedActions = array('pages'=>'display')
oppure impostandolo dinamicamente attraverso il metodo
$this->Auth->allow(array('pages'=>'display', 'users'=>'index'));

Per determinare il tipo di controllo sulle autorizzazioni da eseguire bisogna impostare l’attributo $this->Auth->authorize che può assumere i seguenti valori:

  • controller l’autorizzazione per accedere all’action richiesta è concessa dal metodo Controller::isAuthorized() che deve essere implementato
  • actions esegue un controllo dell’action richiesta richiamando il metodo AclComponent::check(), quindi richiede l’impiego delle ACL.
  • crud esegue la validazione delle mapActions usando il metodo AclComponent::check()
  • array(‘model’=> ‘name’) esegue la validazione della mapActions usando il metodo model $name::isAuthorize(user, controller, mapAction)
  • object esegue la validazione di Controller::action usando object::isAuthorized(user, controller, action)

Controller

Quando si imposta $this->Auth->authorize to ‘controller’, Auth component cercherà un’action chimata ‘isAuthorized’ per sapere se un utente è autorizzato o meno. Come nell’esempio che segue.

function isAuthorized() {
		if (isset($this->params[Configure::read('Routing.admin')])) {
			if ($this->Auth->user('admin') == 0) {
				return false;
			}
		}
		return true;
   }

« PrecedenteSuccessiva »