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

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

Espace de nom PHP et chargement automatique

PHP 5.3 ajoute une notion intéressante : les espaces de nom (ou namespace en anglais). Les espaces de nom permettent de séparer différents … « espace« , permettant ainsi d’avoir deux classe portant le même nom. Idéal pour l’utilisation de framework mais aussi de « l’isolation » de certains composants. Voyons voir comment créer un chargeur automatique (ou autoloader) comprenant les espaces de nom.

Si vous êtes sous Ms Windows, aucun problème un simple spl_autoload_register(); suffit. Malheureusement, sous *nix un bug existe il faudra donc implémenté une solution maison.

Le code

Ce code provient du « PHP Standards Working Group » :

function autoload($className)
{
	$className = ltrim($className, '\\');
	$fileName  = '';
	$namespace = '';
	if ($lastNsPos = strripos($className, '\\'))
	{
		$namespace = substr($className, 0, $lastNsPos);
		$className = substr($className, $lastNsPos + 1);
		$fileName  = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
	}
	$fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
 
	require $fileName;
}

Pour l’utiliser, il faut définir deux choses :

  1. Ne pas oublier de modifier l’include path si nécessaire.
  2. Ajouter cette fonction au chargeur de classes présent.

L’exemple

Voici un exemple un peu plus complet.
index.php

<?php
set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . '/lib/');//on ajoute le dossier lib dans l'include path
spl_autoload_register();//sur windows ceci devrait suffire .
//le code pour les autres
//----------------------------------------------------------
function autoload($className)
{
	$className = ltrim($className, '\\');
	$fileName  = '';
	$namespace = '';
	if ($lastNsPos = strripos($className, '\\'))
	{
		$namespace = substr($className, 0, $lastNsPos);
		$className = substr($className, $lastNsPos + 1);
		$fileName  = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
	}
	$fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
 
	require $fileName;
}
spl_autoload_register('autoload');
//----------------------------------------------------------
//fin du code de fix
 
use grummfy\test\Test;
Test::sayHello();
 
\grummfy\std\Test::sayHello();
 
# EOF

./lib/grummfy/test/Test.php

<?php
namespace grummfy\test;//pour rappel ceci doit-être la première instruction php (et on ne doit pas avoir de HTML avant)
echo 'Je suis inclus (' . __FILE__ . ')!';
class Test
{
	public static function sayHello()
	{
		echo 'Bonjour depuis ' . __CLASS__;
	}
}
 
# EOF

./lib/grummfy/std/Test.php

<?php
namespace grummfy\std;
echo 'Je suis inclus (' . __FILE__ . ')!';
class Test
{
	public static function sayHello()
	{
		echo 'Bonjour depuis ' . __CLASS__;
	}
}
 
# EOF

Le résultat devrait être :

Je suis inclus (/.../lib/grummfy/test/Test.php)!
Bonjour depuis grummfy\test\Test
Je suis inclus (/.../lib/grummfy/std/Test.php)!
Bonjour depuis grummfy\std\Test

Wampserver : quick switch xdebug menu

Pour une fois, un article sous Ms Windows. Stage oblige, je passe du temps sous celui-ci (XP 🙁 ). Afin d’optimiser son travail, il y a parfois des petites choses bien pratiques, telles que ce que je vais vous présenter.

XDebug est un outil merveilleux, parfois capricieux, certes, mais très utile. Il permet, notamment1 :

  • Affichage de tracé d’erreur
  • Meilleures lectures des exceptions
  • Débogage pas-à-pas
  • Profiling d’application

Bref, des choses essentielles en développement. Et, contrairement à ce que certains IDE2 font, il permet surtout de le faire sur un serveur « réel », donc avec une utilisation « réel ».

Le but de ce billet n’est pas de présenter XDebug, d’autres le font mieux que moi 3, mais bien de vous présenter un petit script vous permettant d’activer et désactiver XDebug sur wampserver.

Installation

  1. Téléchargez le fichier 4 et décompressez-le.
  2. Suivez les instructions d’installation décrite dans le fichier installe. Il y a seulement 1 fichier à modifier + 1 fichier par version de PHP installée.
  3. Relancer wampserver et tester!

Si vous avez des questions, n’hésitez pas.

Plus d’informations

  1. On parle de serveur web avec PHP …
  2. Par exemple, Zend Studio permet un débogage pas à pas mais en interne donc réduit …
  3. cf. plus d’informations
  4. XDebug quick switch menu for wampserver