De multiples hébergeurs (notemment OVH en dédié) proposent à leurs utilisateurs de configurer plusieurs adresses ip sur leur machine.
Cela peut permettre par exemple de configurer, sur la machine et sur le port 80, deux serveurs web.
Un Apache et un Lighttpd.
Le second transmettant les fichiers statiques beaucoup plus rapidement et en consommant moins de ressources machines, c’est une bonne méthode d’optimisation de vos ressources système.

Aucun problème pour les connexions entrantes donc.
J’ai cependant eu envie de me confronter à autre chose. Les connexions sortantes en PHP avec Curl.
Comment sélectionner laquelle de mes deux ips je veux que mon script utilise pour faire appel à une page externe ?

Ne sachant pas trop ou chercher, je me suis dit au début « il doit y avoir une fonction native, ou un truc dans la configuration ». Queue d’ale.

Cependant après quelques recherches, j’ai trouvé la solution, qui était sous mon nez !
Pour ceux qui ne savent pas comment fonctionne curl, celui-ci possède une fonction curl_setopt qui permet de paramétrer votre appel distant.
Ainsi vous pouvez ajouter des paramètres en POST, modifier le referer, ajouter des cookies, ajouter une identification http ou ssl etc. Les possibilitées sont illimitées.

Et c’est la que l’une des options fait son apparition. CURLOPT_INTERFACE, qui prends comme paramètre … une IP ou un nom d’hôte.
Et le script utilisera l’ip définie.

Ainsi je change mon ip en faisant :
curl_setopt($ch, CURLOPT_INTERFACE, $myIp);

Notez cependant que vous ne pouvez bien evidemment pas utiliser n’importe quelle ip. Seules celles configurées sur votre carte réseau le sont. Cela serait trop facile.
Vous pouvez cependant tester cela même si vous n’avez qu’une seule ip sur votre machine. Faites appel à une page locale et utilisez votre ip distante ainsi que 127.0.0.1 :-)

Je n’ai donc ensuite eu plus qu’a récupérer aléatoirement l’une ou l’autre de mes ip et mes appels externes se font à 50% sur une de mes ip et à 50% sur l’autre.
Je viens de diviser par deux les chances que l’ip de mon serveur ne se fasse bannir par Google avec les appels faits par RefStats.

Note : cet article est une reprise d’un ancien article anciennement présent sur kazhar.org et légèrement étoffé.

La librairie php artichow permet de générer des images de manière assez simple.
Cela rends la génération de graphiques presque amusante ;-)

Malheureusement il faut trifouiller un peu pour l’intégrer avec CakePHP.
En effet, les deux applications implémentent une classe nommée « Component ». Et les espaces de nom n’étant pas encore disponibles (ils sont prévus pour PHP 5.3), il n’est pas possible de ruser de cette manière pour implémenter l’un avec l’autre.
Heureusement il existe une autre solution.

Dans le fichier Artichow.cfg.php, il y a la ligne suivante :
define(‘ARTICHOW_PREFIX’, ”);

Placez le préfixe que vous désirez (moi j’ai mis arti. Mais on se moque un peu de la valeur).

Maintenant vous n’avez plus de souci de conflit avec la classe Component. Mais vous avez des erreurs disant que des classes telles que Driver n’existent pas.
Modifiez les trois ou quatre erreurs signalées, notamment dans inc/drivers/gd.class.php en ajoutant ‘aw’ devant la classe appelée.
Ainsi, Driver devient awDriver.

Enfin l’inclusion. Dans l’action ou vous souhaitez générer un graphique, placez :

function graph() {

App::import(‘Vendor’, ‘artichow/LinePlot.class’);
$graph = new Graph(400, 400);
$graph->setAntiAliasing(TRUE);

$values = array(1, 4, 5, -2.5, 3);
$plot = new LinePlot($values);

$plot->setBackgroundGradient(
new LinearGradient(
new Color(210, 210, 210),
new Color(250, 250, 250),
0
)
);
$plot->yAxis->setLabelPrecision(1);
$plot->setSpace(5, 5, NULL, NULL);

$graph->add($plot);
$graph->draw();
}

Vous verrez alors en appellant cette action le graphique généré.

Supposons le cas d’un site multilingue, qu’il soit architecturé en sous-répertoires ou sous-domaines.
Lorsque vous arrivez sur la base du site (/ ou www), aucune langue n’est définie. Et vous n’avez aucun contenu à afficher.

Il vous faut donc rediriger l’utilisateur vers une autre page qui correspondra à la langue qu’il est le plus susceptible de désirer.
De nombreux sites, corporate y compris, ne s’embêtent pas et proposent simplement une liste de liens vers les diverses langues.

Ceci est à ne surtout pas faire. Cela avait notemment été dit par Elie Sloïm lors des ateliers Paris Web de l’an dernier (d’ailleurs les dates du prochain Paris Web ont été divulguées !)
Suite à une discussion sur le forum seosphere, je publie ici le code que j’utilise sur RefStats pour détecter la langue préférée de l’utilisateur et le rediriger vers le répertoire approprié. Si vous utilisez des sous-domaines, il suffit de déplacer /fr en fr.
Note : le code ici est adapté afin d’enleve la dépendance à CakePHP.

$langs = array('en' => array('title' => 'English', 'visible' => 0) , 'fr' => array('title' => 'Français', 'visible' => 1));
$defaultLang = 'fr';

$_d = split (‘[,;]‘, $_SERVER['HTTP_ACCEPT_LANGUAGE']);
$f = false;
$selected = null;
foreach($_d as $l) {
if (isset($langs[$l]) && $langs[$l]['visible'] == 1 && $f == false) {
$selected = $l;
$f = true;
}
}
if ($f == false) $selected = $defaultLang;

header(‘location: /’.selected, true, 301);
die();

Quelques explications.
Ce code doit être placé sur votre page /index.php. la variable $langs contient toutes les langues disponibles. Ici, j’ai mis un élément ‘nom’ car dans le cas de RefStats, cette variable sert également à mettre le nom de la langue en haut de page.
Le ‘visible’ n’est pas non plus utile ici.

Nous regardons donc le contenu de HTTP_ACCEPT_LANGUAGE, qui contient les langues préférées de l’utilisateur, par ordre de préférence.
On prends la première que l’on connaisse. Si il n’y en a aucune, on prends la valeur de $defaultLang.
Et on redirige vers le répertoire de la langue.

Il est totalement inutile de placer la langue dans la session. En effet, vu que vous serez ensuite dans le répertoire de la langue, vous aurez toujours l’information de celle-ci.
Et si vous placez l’information en session et forcez toujours cette langue, vous courrez le risque d’avoir un utilisateur qui aurait une configuration inappropriée (cela peut arriver lors de vacances à l’étranger par exemple) qui ne pourra pas voir votre site dans sa langue maternelle.

Pour finir, voici le code non modifié pour ceux qui utiliseraient CakePHP.

Dans app_controller.php, avant la déclaration de classe :
uses('L10n');

Dans app_controller.php, en dehors de toute méthode :
var $langs = array('en' => array('title' => 'English', 'id' => 'eng', 'visible' => 0) , 'fr' => array('title' => 'Francais', 'id' => 'fre', 'visible' => 1, 'wp_cat' => 3));
var $defaultLang = 'fr';

Dans app_controller.php, fonction beforeFilter :
if (!isset($this->params['lang']) && !empty($this->params)) {
$_d = split ('[,;]', env('HTTP_ACCEPT_LANGUAGE'));
$f = false;
$selected = null;
foreach($_d as $l) {
if (isset($this->langs[$l]) && $this->langs[$l]['visible'] == 1 && $f == false) {
$selected = $l;
$f = true;
}
}
if ($f == false) $selected = $this->defaultLang;

$this->redirect(‘/’.$selected, 301);
die();
} elseif (!empty($this->params['lang']) && isset($this->langs[$this->params['lang']])) {
Configure::write(‘Config.language’, $this->langs[$this->params['lang']]['id']);
} elseif (!empty($this->params)) {
$this->cakeError(‘error404′, array(array(‘action’ => $this->action)));
} else {
$url = env(‘argv’);
$url = $url[0];
$url = ‘/’.str_replace(‘url=’,  », $url);

$l = explode(‘/’, $url);
$l = $l[1];

if (!isset($this->langs[$l]) && Configure::read(‘debug’) == 0) {
$this->cakeError(‘error404′, array(array(‘action’ => $this->action)));
}
}
$this->params['langs'] = $this->langs;

Et enfin dans routes.php, pour permettre l’ajout du paramètre de langue avant le contrôleur et l’action :
Supprimer la ligne suivante :
Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home'));
Et ajouter à la place les deux lignes suivantes :
Router::connect('/:lang/', array('controller' => 'pages', 'action' => 'display', 'home'));
Router::connect('/:lang/:controller/:action/*');

Ceci définira la langue dans la configuration de CakePHP, vous permettant par la suite d’utiliser les outils d’internationalisation du framework.

Tout « bon » développeur le sait pourtant. Il ne faut jamais croire son application exhaustive et nommer ses classes et fonctions par des noms trop génériques.
Cela est encore plus valable lorsque l’on développe pour le monde du libre.

Ainsi, CakePHP définit une classe « Cache ». Classe que phpBB définit également … Rendant totalement impossible toute inclusion de l’identification de phpBB à une application conçue avec CakePHP.
Merci messieurs les développeurs de chez CakePHP et phpBB. Et vivement PHP 5.3 et les espaces de noms !

Il peut arriver que vous cherchiez à concevoir une application web et que de par le principe de cette conception, vous ayez besoin de toujours charger le même fichier quelque soit l’url appellée. Sauf si le fichier existe déjà (le cas d’une image par exemple).

La solution qui semblerait la plus simple au premier abord est de trifouiller avec ErrorDocument dans le .htaccess

ErrorDocument 404 /index.php

Ca fonctionne puisque à partir du moment ou votre fichier n’existe pas, vous êtes redirigé vers votre index.php. Vous n’avez alors plus qu’à afficher votre contenu. Et surtout ne pas oublier de forcer le code HTTP 200 au lieu de 404. Sans quoi votre site ne sera jamais indexé par les moteurs de recherche (et vous êtes en contradiction avec la norme du protocole HTTP). Cette solution est intéressante notemment si vous n’avez pas accès à l’URL rewriting.

 

Une autre, que je trouve plus propre, consiste à utiliser l’URL rewriting.

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]

Qu’est-ce que c’est que toutes ces lignes ?

RewriteEngine On

Ici, on se contente d’activer l’url rewriting.

 

RewriteCond %{REQUEST_FILENAME} !-d

Si l’url existe et est un répertoire, on ignore les lignes suivantes

RewriteCond %{REQUEST_FILENAME} !-f

Si l’url existe et est un fichier, on ignore la ligne suivante

RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]

Et donc, si ce n’est ni un fichier, ni un répertoire, on redirige l’url vers index.php. Ici, on passe également l’URL de la page en GET.

Cette astuce est utilisée dans divers frameworks. Notemment CakePHP et Ruby on Rails.

Entre temps

Et hier soir

CakePHP fournit de base quelques méthodes permettant de développer des applications dans le but de les exécuter avec PHP-cli, c’est à dire de les exécuter comme des scripts shell au lieu de passer par l’interface graphique.

Je ne propose pas ici un tutoriel permettant de développer ce genre de scripts. Il en existe déjà un résumant assez bien la chose sur cakebaker (en).

Après avoir développé une application, nous avons plusieurs tâches qui sont exécutées dans le script shell principal.

Cependant il peut être utile, pour diverses raisons de tests, d’analyses de bugs voir statistiques, d’exécuter les mêmes méthodes présentes dans nos tasks mais via une page web.

Il serait fortement bête de devoir faire un copier/coller de notre méthode et de la remettre dans un contrôleur. Cela ne serait pas vraiment en concordance avec le mojo du framework (« think twice, code once« ).

Supposons donc que dans votre contrôleur « admin », vous désiriez utiliser la méthode « makeTest » de la tâche « test ».
Créez une nouvelle action dans votre contrôleur.

Dans celle-ci, placez les lignes suivantes :

include(APP.'console/libs/shell.php');
vendor('shells/tasks/test');

Vous avez ici inclu dans votre action les fichiers nécessaires l’instanciation de votre tâche.

Le premier fichier contient la classe parente à celle que doit avoir votre tâche. La seconde contient votre tâche (remplacer « test » par le nom de votre tâche).

$tache = $test->makeTest();

Maintenant initialisons notre tâche :

$test = new TestTask();

Et exécutons la méthode que nous désirons pour notre tâche.

$tache = $text->makeTest();

Vous n’avez maintenant plus qu’à effectuer l’action que vous désirez avec les données que votre méthode vous renvoie.

Irish Coffee

J’aime beaucoup le dessin permettant d’expliquer (globalement) la méthode de codage MVC de CakePHP dans OCPHP (page 3).

Prenons un exemple. Dans RefStats, je génère des graphiques en me basant sur des données d’une base de données dont le modèle est le suivant :

myBase

  • idSite (primary key)
  • idKeyword (primary key)
  • date (primary key)
  • position

J’aurais pu, dans mon controlleur, faire une requête de sélection. On ne peut plus simple !
!

$this->myBase->findAll(array(‘idSite’ => $site, ‘idKeyword’ => $keyword));

Seulement voila, les données retournées par cette requête sont dans un format inexploitable par ma vue par la suite (dans le but d’un affichage dans un format quelconque (html, xml ou csv par exemple).

J’ai donc créé une méthode dans mon modèle qui execute la même requête de findAll et qui traite les données retournées dans le but de retourner un tableau exploitable par la suite.

La solution de facilité aurait été de faire le findAll dans le controlleur tout de même et de traiter les données là-bas.

Et pourtant, quand on y réflechit, ce n’est absolument pas le plus simple. En effet, cela va quand vous n’avez besoin de récupérer les données qu’une seule fois dans votre application.

Mais là, je dois les récupérer au moins 3 fois à trois endroits différents.

Vive les répétitions de code dans le controlleur ! Et les multiplications de risques de bugs.

Le mentra qui se trouve en dessous de l’image que j’ai donné ci-dessus dans le document pdf est donc on ne peut plus à prendre au sérieux. « Think twice, code once ». « Réflechissez deux fois, ne codez qu’une ».

Si vous ne comprenez pas bien comment MVC fonctionne et que vous désirez approfondir cela, n’hésitez pas à assister à la conférence zend framework qui se déroule dans un mois et dans laquelle je ferais une présentation complète de la méthode. Les inscriptions sont ouvertes, on a encore largement assez de place et c’est gratuit !

 
Fork me on GitHub