Language switch

Just a quick note to underline the fact this website has been translated to English, in order to be understandable by a broader audience. Besides being the world's most widespread language, it is also a growing standard in web development in every country, including France, hence the need I have felt to do it.

Old articles in French will be kept that way as there is no point in translating them, and will remain for historical purposes.

Enjoy, and see you soon (I hope) for new articles !

Une extension Twig pour partager sur les réseaux sociaux

Et voici venir ShareExtension, une extension Twig destinée à faciliter l'utilisation de liens de partage vers les réseaux sociaux. Sont actuellement supportés Twitter, Facebook, Pinterest, Tumblr et Google+ (si, si).

Voici un rapide aperçu de son utilisation :

<a href="{{ twitter(my_url, 'Tweet this text') }}">Share on Twitter</a>
<a href="{{ facebook(my_url) }}">Share on Facebook</a>

Le projet suit la norme PSR-2, est installable via Composer et peut être forké à l'envi sur GitHub !

Un bot IRC propulsé par Node.js

Cet article a pour objet dbot, un tournant sur Node.js. La particularité de ce bot est qu'il permet l'inclusion de plug-ins proposant - pour l'heure - des fonctionnalités utiles aux développeurs web, faisant appel à différents services web et permettant d'interagir directement avec eux via des messages privés.

Le bot lui-même et ses plugins sont disponibles sur NPM et installables de la façon suivante :

$ git clone [email protected]:dddware/dbot.git
$ cd dbot
$ npm install dbot-list dbot-dfill dbot-dpaste dbot-h5p dbot-url
$ npm start # démarre le bot

Si ce projet vous intéresse, n'hésitez pas à y contribuer via GitHub !

Mettre en place une authentification basique HTTP fixe sur Laravel

L'authentification des utilisateurs est un point plutôt bien traité par Laravel, à tel point que celui-ci est livré avec un modèle User basique, ainsi que tout ce qu'il faut pour mettre rapidement en place un process d'authentification basé sur les utilisateurs enregistrés par son biais.

Mais qu'en est-il si vous devez gérer vos utilisateurs d'une part, et avez simplement besoin de protéger d'autre part une page d'administration, à laquelle vous serez seul(e) à avoir accès, derrière un couple d'identifiants prédéfinis ? On pourrait bien sûr envisager d'ajouter une notion de rôle à la base d'utilisateurs existants, de façon à tout articuler autour du même système, mais je ne suis personnellement pas très à l'aise avec cette idée : bien qu'il s'agisse d'authentification dans les deux cas, celle-ci n'a pas du tout le même sens vis-à-vis de votre application.

Heureusement, nous allons tout de même pouvoir nous appuyer en partie sur ce que propose Laravel en termes d'authentification basique HTTP. La meilleure manière de procéder sera, sans aucun doute, de définir un filtre dans le fichier app/filters.php :

Route::filter(
'auth.admin',
function ($route, $request) {
$user = $request->getUser();
$password = $request->getPassword();

if (($user != Config::get('auth.admin.user')) || ($password != Config::get('auth.admin.password'))) {
// La méthode qui nous intéresse n'est pas publique, on va tricher
// Une manière plus propre de procéder serait d'étendre la classe concernée
$method = new ReflectionMethod('Illuminate\Auth\Guard', 'getBasicResponse');
$method->setAccessible(true);

$response = $method->invoke(new Illuminate\Auth\Guard());
return $response;
}
}
);

Vous remarquerez que j'ai choisi de stocker les identifiants attendus dans la configuration de l'application offerte par le framework. Pour ce faire, il suffit d'éditer le fichier app/config/auth.php comme suit (en bonus, vous pourrez facilement faire varier ces identifiants selon l'environnement d'exécution) :

return array(
// ...

'admin' => array(
'user' => 'mylogin',
'password' => 'mypassword'
)
);

Enfin, il ne nous reste plus qu'à attacher le filtre en question à une route :

Route::get(
'/admin',
array(
'before' => 'auth.admin',
function () {
// L'administrateur est authentifié
}
)
);

Ou directement dans un contrôleur :

class AdminController extends BaseController
{
public function __construct()
{
$this->beforeFilter('auth.admin');
}
}

Utiliser Behat et Mink dans une application Laravel 4

Fan de développement web élégant basé sur le framework PHP Laravel, et féru de Behavior-Driven Development à grands coups de Behat et Mink ? Si tel est le cas, il est possible que vous ayez rencontré quelques difficultés à faire cohabiter les deux via Composer, notamment avec la quatrième et dernière version en date du framework. Il n'est pourtant pas vraiment en cause ; je pense pouvoir affirmer sans trop me tromper que gérer ces deux outils de tests fonctionnels en tant que dépendances est un beau bordel actuellement, entre la version 2.5 "stable" et la version 3.0 encore-en-beta-mais-qui-sort-quand-même-par-défaut-un-peu-trop-facilement, mais passons.

Dans mon cas, la première difficulté rencontrée a donc principalement été liée à des conflits de dépendances. Qu'à cela ne tienne, ce paquet Composer, sobrement intitulé behat-laravel, m'a été fort utile pour pouvoir tout installer simplement. Voici un fichier composer.json rudimentaire avec les packages employés :

{
"require": {
"laravel/framework": "4.1.*",
"phpunit/phpunit": "3.8.*@dev",
"guilhermeguitte/behat-laravel": "dev-master",
"behat/mink": "[email protected]",
"behat/mink-extension": "*",
"behat/mink-selenium2-driver": "*"
},

...
}

Une fois le tout prêt à l'emploi, on commence donc à écrire ses premiers tests pour vite se rendre compte que l'écosystème global de Laravel, très complet, s'avère difficilement dispensable lorsqu'on souhaite n'utiliser que certaines parties du framework. Pour cet exemple qui sent bon le vécu, imaginons que nous souhaitions tester un formulaire de connexion et que nous voulions donc pouvoir créer un utilisateur en base de données histoire d'avoir une paire d'identifiants valides à tester. Voici le fichier .feature correspondant :

Feature: Log in to the app
In order to be able to access my account
I need to be able to fill in a login form with my credentials

Background:
Given I am on "/"

Scenario: Type in a valid account's credentials
Given "[email protected]" has an account with "prout" as the password
When I fill in "Adresse e-mail" with "[email protected]"
And I fill in "Mot de passe" with "prout"
And I press "Connexion"
Then I should be on "/account"

Scenario: Type in a invalid account's credentials
When I fill in "Adresse e-mail" with "[email protected]"
And I fill in "Mot de passe" with "nothing"
And I press "Connexion"
Then I should be on "/"
And I should see "Votre identification a échoué, veuillez réessayer"

Scenario: Submit the form without filling it
When I press "Connexion"
Then I should be on "/"
And I should see "Adresse e-mail : ce champ est requis"
And I should see "Mot de passe : ce champ est requis"

Scenario: Submit the form with an invalid e-mail address in
When I fill in "Adresse e-mail" with "jesuisinvalidelol"
And I press "Connexion"
Then I should be on "/"
And I should see "Adresse e-mail : ce champ est invalide"

Le vocabulaire de base de Mink couvrira à lui seul l'essentiel de ces instructions. Nous allons simplement devoir donner du sens à l'expression Given /^"([^"]*)" has an account with "([^"]*)" as the password$/ :

<?php

use Behat\Behat\Exception\PendingException;
use Behat\MinkExtension\Context\MinkContext;

class LoginContext extends MinkContext
{
/**
* @Given /^"([^"]*)" has an account with "([^"]*)" as the password$/
*/
public function hasAnAccountWithAsThePassword($email, $password)
{
$user = new User();
$user->name = 'toto';
$user->email = $email;
$user->setPassword($password);
$user->save();
}
}

Enfantin, n'est-il pas ? Oui, sauf que dans les faits :

  • On n'a pas accès à la base de données
  • La classe User hérite d'Eloquent, qui est un alias d'une classe namespacée, accessible uniquement dans le contexte d'une application Laravel
  • Même topo pour Hash, qui de plus doit être initialisé avec un provider de hash (de mon temps, on appelait ça un dealer)

Je pourrais vous guider pas à pas, d'erreur d'exécution en erreur d'exécution, à travers les méandres de mon parcours sur le chemin tortueux menant à la résolution du problème exposé ici, mais je doute que ce soit vraiment intéressant. Voyons donc directement les modifications apportées à cette classe de contexte pour faire tourner le bazar :

<?php

use Behat\Behat\Exception\PendingException;
use Behat\MinkExtension\Context\MinkContext;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\AliasLoader;
use Illuminate\Hashing\HashServiceProvider;
use Illuminate\Support\Facades\Facade;
use Illuminate\Database\Capsule;

class LoginContext extends MinkContext
{
public function __construct()
{
// On instancie une app bidon
$app = new Application();

// On lui évite de faire appel à des propriétés indéfinies
$app->bind(
'path.storage',
function () {
return '';
}
);

// On instancie un fournisseur de chiffrement (belle francisation, tiens)
$hash = new HashServiceProvider($app);
$hash->register();

// On utilise notre fausse app
Facade::setFacadeApplication($app);

// On charge les alias existants dans une instance de la classe qui va bien
$appConfig = require(dirname(dirname(dirname(__DIR__))).'/config/app.php');
$this->aliasLoader = AliasLoader::getInstance($appConfig['aliases']);

// On récupère les infos de connexion à la base de données de test
$dbConfig = require(dirname(dirname(dirname(__DIR__))).'/config/database.php');
$this->db = new Capsule\Manager();
$this->db->addConnection($dbConfig['connections']['sqlite_test']);
}



/**
* @Given /^"([^"]*)" has an account with "([^"]*)" as the password$/
*/
public function hasAnAccountWithAsThePassword($email, $password)
{
// On charge les alias dont on va avoir besoin
$this->aliasLoader->load('Eloquent');
$this->aliasLoader->load('Hash');

// On boot Eloquent avec notre instance de la base de données
$this->db->bootEloquent();

// On peut exécuter des requêtes arbitraires comme ceci :
$this->db->getConnection()->delete('DELETE FROM users');

$user = new User();
$user->name = 'toto';
$user->email = $email;
$user->setPassword($password);
$user->save();

// On est bien \o/
}
}

Voilà qui devrait vous donner une meilleure idée du type de bidouille (car c'est bien ce dont il s'agit) envisageable pour pouvoir utiliser une partie de l'environnement de Laravel sans démarrer complètement l'application. Je suis bien évidemment preneur de toute solution plus solide, n'hésitez pas à laisser un petit mot doux au bas de cet article si le coeur vous en dit !