Home > Blog > Geek

Forcer l’UTF-8 sur toutes les tables avec Doctrine

Un petit aide-mémoire pour que toutes les tables d’une base de données soient en UTF-8, la solution la plus courante étant de rajouter ceci en haut de son fichier schema.yml :

options:
  collate: utf8_unicode_ci
  charset: utf8

Cela fonctionne très bien… mais si on utilise plusieurs fichiers schema.yml (ou si on utilise un plugin ayant son propre schéma, sfDoctrineGuard par exemple), on se retrouve avec des tables en latin1_swedish_ci mélangées avec d’autres en utf8_unicode_ci, et ça, c’est moche (tout le contraire d’une bonne pratique). Pire encore, l’utilisation d’encodages différents génère l’erreur MySQL “Illegal mix of collations”.

Pour y remédier, il faut forcer Doctrine à utiliser l’UTF-8 partout, en modifiant le fichier /config/ProjectConfiguration.class.php (merci à Damien pour le tip) :

<?php

class ProjectConfiguration extends sfProjectConfiguration
{
  public function configureDoctrine(Doctrine_Manager $manager)
  {
    $manager->setCollate('utf8_unicode_ci');
    $manager->setCharset('utf8');

    Doctrine_Migration_Base::setDefaultTableOptions(array(
      'type' => 'INNODB',
      'charset' => 'utf8',
      'collate' => 'utf8_unicode_ci'
    ));
  }
}

18/01/2011 — Mots-clés : , , — Classé dans GeekEcrire un commentaire

Bonnes pratiques de développement PHP/symfony

Voici une liste de bonnes pratiques que j’ai pu appliquer depuis que je travaille dans le développement Web avec symfony. J’en rajouterai d’autres de temps en temps.

Présentation du code

  • Ne jamais utiliser de tabulations, toujours indenter avec des espaces (2 ou 4 c’est une autre question… personnellement, depuis symfony 1.0, j’en mets 2) ;
  • Les accolades ouvrantes et fermantes doivent être respectivement alignées ;
  • Toujours revenir à la ligne avant une accolade ;
  • Les méthodes protected viennent après les méthodes public (jamais de private) ;
  • Pour faciliter la navigation dans les sources, toujours utiliser le même nom de fichier que la classe qu’il contient : classe sfSessionStorage dans sfSessionStorage.class.php (une seule classe par fichier) ;
  • Utiliser des noms de variables/méthodes/classes explicites et en anglais.

Utilisation de librairies externes

  • Toutes les librairies externes doivent être insérées dans le dossier /lib/vendor/xxx ;
  • Ne jamais modifier les fichiers externes au projet (source de symfony, librairies, plugins…) sous peine de casser la compatibilité avec les mises à jour (il est cependant possible de surcharger proprement la plupart des classes) ;
  • Essayer de les maintenir à jour (sauf en cas d’incompatibilité).

Organisation

  • Utiliser le même encodage partout (même pour la base de données), le plus souvent UTF-8 ;
  • Éviter les URLs, chemins ou autres variables dépendantes de l’environnement codés en dur dans l’application, préférer un fichier de configuration spécifique, /config/app.yml par exemple ;
  • Tout ce qui doit être testable unitairement (librairies, modèle, formulaires…) doit être rangé dans le dossier /lib ;
  • Éviter d’utiliser les dossiers apps/xxx/lib et apps/xxx/modules/yyy/lib, sauf pour des cas vraiment spécifiques ;
  • Ne pas réinventer la roue, toujours utiliser (ou étendre) sfUser pour gérer la session utilisateur ;
  • Ne pas créer un myTools.class.php avec une tonne de méthodes, essayer de ranger les différentes méthodes suivant leur utilité ;
  • Eviter au maximum l’utilisation de sfContext::getInstance(), utiliser l’injection de dépendances ;
  • Supprimer les contrôleurs *_dev de l’instance de production.

Modèle

  • Le HTML est uniquement autorisé dans les templates, partials ou components, jamais dans un contrôleur ou une action ;
  • Ne pas oublier de créer quelques fixtures pour éviter d’avoir une base de données vierge à chaque réinitialisation ;
  • Les “Criteria” Propel, “Doctrine_Query” ou requêtes SQL doivent être exclusivement dans le dossier /lib ;
  • Vérifier le nombre de requêtes par page, essayer de les réduire au maximum et optimiser les jointures.

Vue

  • Ne jamais utiliser la syntaxe PHP courte <?=$var?> ;
  • Laisser l’output escaping activé (il faut aussi éviter de changer le paramètre de l’output escaping en plein milieu du développement pour éviter les mauvaises surprises) ;
  • Toujours placer les slots en bas des templates ;
  • Afin d’être lisibles par les intégrateurs, les templates doivent contenir du PHP en syntaxe alternative (foreach/endforeach, if/endif…) ;
  • Eviter d’utiliser view.yml et préférer les slots pour gérer plus facilement le contenu des balises <title> <meta> et optimiser ainsi le SEO ;
  • Ne pas oublier les pages d’erreur 404, 500 et de maintenance.

Contrôleur

  • Les actions ne doivent jamais dépasser 20 lignes de code, et une classe du contrôleur ne doit pas avoir plus d’une quinzaine d’actions ;
  • Tout ce qui n’est pas executeXXX(), preExecute(), ou postExecute() doit être protected ;
  • Ne jamais stocker d’objet en session (même avec serialize).

Formulaires

  • Toujours rediriger la requête après un envoi de données en POST afin d’éviter la fenêtre de confirmation du navigateur (autorisé lors de l’affichage d’erreurs sur un formulaire, car le rafraîchissement entraîne une soumission des données, ce qui est logique) ;
  • Ne pas désactiver la protection CSRF ;
  • Créer des classes de formulaires différentes pour chaque action (UserSignUpForm, UserEditForm…) et ne laisser que les propriétés communes dans la classe UserForm (valeurs par défauts, validateurs…).

Versionning

  • Essayer d’utiliser un SCM (gestionnaire de versions) sur tous les projets (“Tout ce qui n’est pas versionné n’existe pas” Fabien Potencier) ;
  • Les fichiers versionnés ne doivent jamais contenir de mots de passe, URLs ou chemins locaux… Par exemple, il faut versionner un fichier databases.yml.dist “vierge” et ajouter la propriété “ignore” sur le vrai databases.yml, ainsi les développeurs pourront le configurer différemment sur chaque instance de l’application sans créer de conflit ni dévoiler de données sensibles ;
  • Certains répertoires n’ont pas à être versionnés (/cache, /log, /web/uploads…), il faut leur ajouter la propriété ignore ;
  • Les fichiers générés (classes modèle/formulaires de base) ne doivent pas être versionnés.

Configuration

  • magic_quotes_gpc et magic_quotes_runtime ne doivent pas être activées dans la configuration de PHP ;
  • En production, l’option PHP error_reporting doit toujours être à 0 ;
  • Toujours configurer le cache symfony sur l’instance de production (sauf cas particuliers).

Documentation

  • Documenter les classes et méthodes en utilisant la syntaxe phpDoc, cela assurera la compatibilité avec la plupart des IDE et permettra l’auto-complétion ;
  • Rédiger la documentation en anglais ;
  • Ajouter un fichier README à la racine du projet, il devra décrire la procédure de déploiement d’une instance de l’application (création d’une base de données, configuration, insertion de fixtures, tests à exécuter…).

Tests

  • Utiliser la commande symfony test:coverage pour vérifier la couverture de l’application par les tests unitaires ;
  • Rédiger des tests fonctionnels pour les principales actions clés du site, ne pas tester les fonctions déjà testées par symfony (formulaires…) ;
  • Utiliser sfLogger pour le stockage/affichage des messages d’erreurs.

Routing

  • Ne jamais faire apparaître de clé primaire dans une URL, utiliser au maximum un slug à l’aide des object route classes ;
  • Eviter d’utiliser la route par défaut :module/:action (le premier réflexe à avoir sur un nouveau projet symfony est de supprimer les routes default et default_index).

01/01/2011 — Mots-clés : , — Classé dans GeekVoir les 4 commentaires

Locate SWAP file on MacOS X

You will be able to find the swap file with the following command :

ps -wax | grep dynamic_pager -m1

And if you are using the default OS X configuration, you’ll get something like that (swap file is /private/var/vm/swapfile) :

   34 ??         0:00.01 /sbin/dynamic_pager -F /private/var/vm/swapfile

28/03/2010 — Classé dans GeekEcrire un commentaire

Create a Facebook application with symfony

This is a tiny tutorial to quickly set-up a symfony application for Facebook from scratch, using FBML.

In your Facebook account, just add the Facebook Developer application and create a new one using these parameters :

  • Canvas Callback URL : the URL of your application like http://www.mydomain.com/
  • Render Method : FBML

Now, create a symfony project instance, and then set the PHP5 library as an external from the official repository in your /lib/vendor directory :

svn ps svn:external "facebook http://svn.facebook.com/svnroot/platform/clients/php/trunk/" lib/vendor

If you get an error like Fatal error: Class ‘Facebook’ not found in (…)/lib/filters/sfFacebookFilter.class.php, just add this in your /config/ProjectConfiguration.class.php :

class ProjectConfiguration extends sfProjectConfiguration
{
  public function setup()
  {
    require_once realpath(dirname(__FILE__).'/../lib/vendor/facebook/facebook.php');
  }
}

Of course, if you don’t use subversion, you can get it here : http://svn.facebook.com/svnroot/platform/clients/packages/facebook-platform.tar.gz. Just unpack it in /lib/vendor/facebook and the symfony autoloading system will do the rest.

In this tutorial, we use FBML, so you don’t need a full layout. Edit your application’s layout in /apps/frontend/templates/layout.php and keep the essential :

<?php echo $sf_content ?>

Since we use FBML, we can’t get the symfony web debug toolbar, so disable it in /apps/frontend/config/settings.yml :

dev:
  .settings:
    web_debug: false

Create a config file (/config/app.yml) and fill it with your Facebook application keys :

all:
  facebook:
    api_key:      xxxxx
    app_secret:   xxxxx
    callback_url: http://www.yourdomain.com/
    canvas_url:   http://apps.facebook.com/xxxxx/

Create a filter to initialize Facebook API and authenticate current user in /lib/filters/sfFacebookFilter.class.php :

<?php

/**
 * Initializes Facebook API.
 */

class sfFacebookFilter extends sfFilter
{
  public function execute($filterChain)
  {
    $user = $this->getContext()->getUser();

    if ($this->isFirstCall() && !$user->isAuthenticated())
    {
      $facebook = new Facebook(sfConfig::get('app_facebook_api_key'), sfConfig::get('app_facebook_app_secret'));

      $user->setAttribute('facebook',     $facebook);
      $user->setAttribute('facebook_uid', $facebook->require_login());

      $user->setAuthenticated(true);
    }

    $filterChain->execute();
  }
}

Don’t forget to add it in /apps/frontend/config/filters.yml :

rendering: ~
security:  ~

sfFacebookFilter:
  class: sfFacebookFilter

cache:     ~
execution: ~

19/03/2010 — Mots-clés : , — Classé dans GeekEcrire un commentaire

Close a remote SSH session

To close all SSH sessions of a given user :

skill -KILL -u username

You can list connected users with the who command.

05/03/2010 — Mots-clés : , — Classé dans GeekEcrire un commentaire

Replace string in MySQL query

UPDATE `table` SET `row` = REPLACE(`row`, 'from_string', 'to_string');

04/03/2010 — Mots-clés : — Classé dans GeekEcrire un commentaire

Using a custom config file with symfony 1.2/1.4

If you want to store some parameters in your own configuration file (other than /config/app.yml for different reasons) with your very own prefix, you just have to create a /config/config_handlers.yml file :

config/my_config_file.yml:
  class:    sfDefineEnvironmentConfigHandler
  param:
    prefix: myprefix_

Now, symfony will process your config file with sfDefineEnvironmentConfigHandler and add your parameters to global settings values with myprefix_ prefix. Don’t forget to tell symfony to include your file, in your application configuration file (/app/myapp/config/myappConfiguration.class.php) :

<?php

class myappConfiguration extends sfApplicationConfiguration
{
  /* ... */

  public function initialize()
  {
    require_once($this->getConfigCache()->checkConfig('config/my_config_file.yml'));
  }
}

You can have more information about symfony internals by checking the /cache/myapp/myenv/config/config_settings.yml.php file.

23/02/2010 — Mots-clés : , — Classé dans GeekVoir les 2 commentaires

Perform a replacement on multiple files

find /my_folder -name "*.php" -exec sed -i "s/search/replacement/g" {} \;

And some explanations :

/my_folder
the directory where to search (recursively).

-name "*.php"
the files I want to process.

-exec a_command \;
means I want to execute a_command command on each file found. In this example, the command is sed -i "s/search/replacement/g" {}, the {} parameter will be replaced by the name of each file found.

21/02/2010 — Mots-clés : — Classé dans GeekEcrire un commentaire

Some examples of Propel Criteria syntax

To start, we have a Criteria, with a Criterion for each condition :

$criteria = new Criteria();

$a = $criteria->getNewCriterion( /* A */ );
$b = $criteria->getNewCriterion( /* B */ );
$c = $criteria->getNewCriterion( /* C */ );
...

A AND B

$a->addAnd($b);

A AND (B OR C)

$b->addOr($c);
$a->addAnd($b);

(A AND B) OR (C AND D)

$a->addAnd($b);
$c->addAnd($d);
$a->addOr($c);

A AND (B OR (C AND D))

$c->addAnd($d);
$b->addOr($c);
$a->addAnd($b);

And, don’t forget to link your main Criteria with your first condition ($a in these examples) :

$criteria->add($a);

You can generate more complicated cases with the Propel Criteria Builder.

If you use Propel 1.5 (or newer), you can avoid using criterias, check Propel Documantation to learn more. Get more advanced examples on Propel 1.5 lead developer’s blog.

16/02/2010 — Mots-clés : , , — Classé dans GeekEcrire un commentaire

Change symfony default culture in admin-generator

By default, symfony admin is in english, but you can easily change the default language for the generated pages (without creating a specific dictionary). Just enable the i18n and set up your culture in your apps/backend/config/settings.yml :

all:
  .settings:
    i18n:                   on
    default_culture:        fr

Then, retrieve your ORM plugin’s dictionary and put it into your application :

mkdir -p apps/backend/i18n/en/
cp lib/vendor/symfony/lib/plugins/sfPropelPlugin/i18n/sf_admin.fr.xml apps/backend/i18n/en/
./symfony plugin:publish-assets
./symfony cache:clear

12/02/2010 — Mots-clés : , — Classé dans Geek1 commentaire