ZF : module et autoloader

Avec Zend Framework, lorsque l’on travail un projet conséquent il devient vite utile de travailler avec le mécanisme des modules. Celui-ci permet d’étendre pas mal de chose et surtout une séparation poussée en … module.

Le problème de ce mécanisme1 est qu’il faut définir le chemin pour le chargement automatique à l’aide de ceci :

$autoloader = new Zend_Application_Module_Autoloader(array(
	'namespace' => 'SuperModule',
	'basePath'  => 'path to super module',
));

Je vous propose donc de faire une petite amélioration afin que ce chargement soit fait automatiquement.

Avant tout, dans votre configuration (ici en .ini) vous devez au moins avoir ceci de présent :

resources.frontController.moduleDirectory = APPLICATION_PATH "/modules"
resources.modules =

Ensuite, à la base de chaque dossier module créer un fichier Bootstrap.php2 :

<?php
class SuperModule_Bootstrap extends Grummfy_Bootstrap{}
# EOF

Et pour finir créer le fichier3 library/Grummfy/Bootstrap.php

<?php
 
abstract class Grummfy_Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
	protected function _initAutoload()
	{
		$className = get_class($this);
		$zf_namespace = explode('_', $className);
		$reflector = new ReflectionClass($className);
		$autoloader = new Zend_Application_Module_Autoloader(array(
			'namespace' => $zf_namespace[0],
			'basePath'  => dirname($reflector->getFileName()),
		));
		return $autoloader;
	}
}
 
# EOF
  1. sauf si j’ai loupé un truc…
  2. ne pas oublier de changer le nom du module …
  3. n’oubliez pas de déclarer le « namespace » Grummfy ou bien d’inclure le fichier

ZF : génération automatique du fichier de navigation et ACL

Lors de l’utilisation du mécanisme d’ACL et de génération de menu dans Zend Framework, il est intéressant de limiter l’affichage de ce menu en utilisant les ressources et privilèges associer.

Pour ma part, j’utilise un fichier XML pour construire mon menu, mon sitemap, … 1. Et comme beaucoup je génère mon projet ZF à l’aide de Zend_Tool. Je trouvais donc dommage de devoir réécrire pratiquement la même chose que ce que j’avais déclaré dans Zend_Tool pour reconstruire mon menu. J’ai donc décidé de rapidement écrire un petit script qui reprendrait le fichier XML du projet et le transformerait en menu …

À noter que le script devrait certainement être amélioré, mais que cela permet un gain de temps considérable …

Téléchargement

  1. Comme expliquer dans le manuel http://framework.zend.com/manual/fr/zend.navigation.html

Réécriture d’URL, alias et plusieurs développeurs sur Apache

Lorsque l’on travaille à plusieurs sur un projet, c’est toujours intéressant. Malheureusement, cela peut aussi entrainer divers problèmes. Je vais tenter de vous expliquer un obstacle qui peut vite devenir très chi*nt…

Pour présenter cette problématique, je vais prendre exemple sur ce que je développe actuellement. Le site en cours de création se base sur zend framework et nécessite une réécriture d’URL. Il faut savoir qu’Apache utilise le chemin physique1 comme base pour calculer le chemin vers le fichier réécrit sauf si on lui précise une directive RewriteBase différente. Le problème survient à cet endroit, plusieurs développeurs entrainent plusieurs machines et donc plusieurs configurations différentes!

La solution de base est que chaque personne utilisant un alias Apache définit un RewriteBase. Cependant, cela veut dire qu’a chaque nouvelle version du fichier .htaccess il faut redéfinir celui-ci.

La réponse la plus simple consiste à utiliser un RewriteCond sur l’hostname du serveur et bien entendu à l’utiliser lors de l’accès aux tests locaux ou non …

Exemple de configuration :

  • nom du serveur : grummfy
  • URL appelée : http://grummfy/serveur/dev/projet/example.com/… (Si vous utilise http://localhost/ la directive HTTP_HOST vaudra localhost)
  • ALIAS : /serveur/ => /media/data/serveur/

Le fichier .htacccess contiendra ceci :
SetEnv APPLICATION_ENV development
php_value session.auto_start 0
php_flag magic_quotes_gpc off
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteCond %{HTTP_HOST} grummfy
RewriteRule ^.*$ /serveur/dev/projet/example.com/index.php [NC,L]
RewriteRule ^.*$ index.php [NC,L]

Le fait d’utiliser le rewrite flag « L » permet de sortir de la réécriture d’URL. Si le hostname du serveur n’est pas grummfy il appliquera la règle par défaut, à savoir tenter de trouver index.php dans /media/data/serveur/dev/projet/example.com/ comme si on avait effectuer un appel depuis http://media/data/serveur/dev/projet/example.com/

  1. physical-directory-path

Android : Tri d’une ListView tout en gardant les ids

Durant le développement du projet b-box j’ai rencontré un problème avec mon menu. Ce menu est présent sous forme de ListActivity (comprenant une ListView), était trié selon un ordre alphabétique qui est susceptible de changer puisque l’application peut-être traduite.

Au début, j’ai essayé diverses méthodes, mais les id de position étant perdus… pas moyen de savoir à quoi correspond quoi.

J’ai donc du trouver une solution, solution que je vous présente.

But

Le but est de créer un menu sous forme de liste (ListView) dont chaque élément est cliquable et permet de récupérer l’id du clic. Cet id servant à lancer une autre Activity, par exemple.

Exemple

Tout d’abord la classe Menu :

package org.android.bbox;
 
import java.util.Comparator;
 
public class Menu
{
	private int id;
	private String label;
 
	public Menu(int id, String label)
	{
		this.id = id;
		this.label = label;
	}
 
	public String getLabel()
	{
		return this.label;
	}
 
	public int getId()
	{
		return this.id;
	}
 
	public static Menu[] factory(String[] menus)
	{
		Menu[] menu = new Menu[menus.length];
		for(int i = 0; i < menus.length; i++)
		{
			menu[ i ] = new Menu(i, menus[ i ]);
		}
		return menu;
	}
 
	public static Comparator getComparator()
	{
		return new Comparator(){
			@Override
			public int compare(Menu m1, Menu m2)
			{
				return m1.getLabel().compareTo(m2.getLabel());
			}
		};
	}
 
	public String toString()
	{
		return this.getLabel();
	}
}

Et la classe de l’activité :

// ...
	public void onCreate(Bundle savedInstanceState)
	{
		//...
		Menu[] m = Menu.factory(getResources().getStringArray(R.array.main_list_array));//récupération d'un tableau de string et création d'un tableau de Menu
		ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, m);//création de l'adaptateur permettant l'affichage
		adapter.sort(Menu.getComparator());//tri du menu
		ListView myList = (ListView) findViewById(android.R.id.list);
		myList.setAdapter(adapter);//ajotu du menu à la ListView
		myList.setOnItemClickListener(this);
		//...
	}
 
	@Override
	public void onItemClick(AdapterView parent, View view, int position, long id)
	{
		System.out.print(position);
		System.out.print(" | ");
		System.out.print(parent.getItemAtPosition(position));
		System.out.print(" | ");
		System.out.print(parent.getItemAtPosition(position).getClass());
		System.out.print(" | ");
		System.out.print(((Menu)parent.getItemAtPosition(position)).getId());
		System.out.print(" | ");
		System.out.println(id);
		//...
	}
// ...

Remarque : la méthode toString permet l’affichage de l’élément dans la liste.

WebHook Google Code – recevoir un mail à chaque commit

Dans Google code il y a la possibilité d’utiliser un gestionnaire de version tel que subversion (svn) ou mercurial (hg). C’est bien pratique, mais malheureusement, de base, rien n’est prévu pour prévenir (excepté par flux RSS) les gens de ces mises à jour. Cependant, Google code permet d’utiliser un webhook en post commit.

Qu’est-ce qu’un webhook?

Un webhook c’est un « crochet web », c’est-à-dire une URL a appelée après (avant ou pendant) une action X. Dans notre cas, après chaque commit une URL est appelée.

Utilisation

Voici un exemple de code que j’utilise pour plusieurs de mes projets :

<?php
// project name
$projects = array('mon-super-projet');
 
// google code webhook key
$keys = array(
	'b-box'	=> 'Top-Secret_key_fourni_par_google-dans-l-adminsitration'
);
 
//user agent from google code
$useragent = 'Google Code Project Hosting (+http://code.google.com/p/support/wiki/PostCommitWebHooks)';
 
//email of all owner (eg. project chief)
$owners = array('vous@example.com');
 
//email of all team members except owners
$users = array('toi@example.com');
 
//sender of email
$sender = 'WebHook mailer<webmaster@exemple.com>';
 
//----------------------------------------------------------------------
$project = (isset($_GET['p']))?$_GET['p']:'';
$revision = (isset($_GET['r']))?intval($_GET['r']):-99;
$data = file_get_contents('php://input');
$digest = (isset($_SERVER['HTTP_GOOGLE_CODE_PROJECT_HOSTING_HOOK_HMAC']))?$_SERVER['HTTP_GOOGLE_CODE_PROJECT_HOSTING_HOOK_HMAC']:'';
 
//----------------------------------------------------------------------
/**
 * Send a mail
 * @param string $from email of the sender : sample@example.com or "name"<sample@example.com>;
 * @param array $to [a] => list of email [cc], [bcc] (hidden), ...
 * @param string $subject
 * @param string $body
 * @return bool true if success
 */
function mailer($from, array $to, $subject, $body)
{
	if (empty($to))
	{
		return false;
	}
 
	$headers = 'From: ' . $from . "\n";
 
	$a = '';
 
	if (isset($to['a']) &amp;&amp; !empty($to['a']))
	{
		$a = implode(',', $to['a']);
	}
 
	if (isset($to['bcc']) &amp;&amp; !empty($to['bcc']))
	{
		$headers .= 'Bcc: ' . implode(',', $to['bcc']) . "\n";
	}
 
	if (isset($to['cc']) &amp;&amp; !empty($to['cc']))
	{
		$headers .= 'Cc: ' . implode(',', $to['cc']) . "\n";
	}
 
	$headers .= 'MIME-Version: 1.0' . "\n";
	$headers .= 'Content-Type: text/plain; charset="UTF-8"' . "\n";
	$headers .= 'Content-Transfer-Encoding: 8bit' . "\n";
	$headers .= 'X-Mailer: PHP/' . phpversion();
 
	return mail($a, '[webhook]' . $subject, $body, $headers);
}
 
function failed($test_id, $msg)
{
	global $sender, $owners;
 
	$msg .= "\n--\nWebHook mail from the Google code project";
 
	mailer($sender, array('bcc' => $owners), 'failed test #' . $test_id, $msg);
 
	die('KO');
}
 
function get_ip()
{ 
	return (isset($_SERVER['HTTP_X_FORWARDED_FOR']))?$_SERVER['HTTP_X_FORWARDED_FOR']:(isset($_SERVER['HTTP_CLIENT_IP']))?$_SERVER['HTTP_CLIENT_IP']:$_SERVER['REMOTE_ADDR'];
}
 
//----------------------------------------------------------------------
if ($useragent != $_SERVER['HTTP_USER_AGENT'])
{
	// failed 1
	failed(1, 'User agent is bad : ' . htmlspecialchars($_SERVER['HTTP_USER_AGENT']) . "\n\nFrom : " . get_ip());
}
elseif (empty($project) || !in_array($project, $projects))
{
	// failed 2
	failed(2, 'No project set : ' . htmlspecialchars($project) . "\n\nFrom : " . get_ip());
}
else
{
	$hmac = hash_hmac('md5', $data, $keys[ $project ]);
	$data = json_decode($data, true);
 
	if (empty($digest) || $digest != $hmac)
	{
		// failed 3
		failed(3, 'Bad digest : ' . $digest . ' vs ' . $hmac . "\n\nFrom : " . get_ip());
	}
	elseif (intval($data['revision_count']) != count($data['revisions']))
	{
		// failed 4
		failed(4, 'Bad count : ' . count($data['revisions']) . ' vs ' . intval($data['revision_count']) . "\n\nFrom : " . get_ip());
	}
	else
	{
		$mail_body = '';
		foreach($data['revisions'] as $_revision)
		{
			$mail_body .= 'Revision : ' . "\t" . htmlentities($_revision['revision']) . ' from ' . htmlentities($_revision['author']) . ' at ' . date('Y-m-d H:i', intval($_revision['timestamp'])) . "\n";
			$mail_body .= 'Added : ' . "\t" . implode("\n\t\t", htmlentities($_revision['added'])) . "\n";
			$mail_body .= 'Modified : ' . "\t" . implode("\n\t\t\t", htmlentities($_revision['modified'])) . "\n";
			$mail_body .= 'Removed : ' . "\t" . implode("\n\t\t\t", htmlentities($_revision['removed'])) . "\n\n";
//			$mail_body .= 'URL : ' . "\t\t" . htmlentities($_revision['url']) . "\n\n";
			$mail_body .= 'Message : ' . "\t" . htmlentities($_revision['message']) . "\n\n\n";
//			$_revision['path_count'];
		}
		$mail_body .= "\n--\nWebHook mail from the Google code project : " . $project . "\nhttp://code.google.com/p/" . $project . "/\n";
 
		mailer($sender, array('bcc' => $owners + $users), '[' . $project . ']New revision #' . $revision, $mail_body);
	}
}
 
exit('OK');
 
# EOF

Plus d’info : PostCommitWebHooks