Mise en place Hadoop et MapReduce

Hadoop et MapReduce en Java

Ce site ne sera plus alimenté de contenu après août 2014. Tous les nouveaux articles seront redigés pour www.waitingforcode.com
Maintenant quand on connaît le fonctionnement théorique de Hadoop, on peut se focaliser sur son aspect pratique.

Dans cet article on verra comment installer Hadoop sur sa machine locale en mode "pseudo-distribué". Il s'agit d'un mode qui va imiter le comportement du système installé dans un cluster. On expliquera également la configuration du HDFS. Ensuite on écrira une simple tâche de MapReduce qui consistera à extraire du texte tous les mots contenant les lettres "ont" collées. On verra après les éléments qui composent le résultat d'une tâche MapReduce ainsi quels problèmes on peut rencontrer pendant ce premier travail d'introduction. Avant toute manipulation, il faut bien évidemment télécharger Hadoop et le placer quelque part dans son système.

Configurer Hadoop en mode pseudo-distribué
Le mode pseudo-distribué signifie que Hadoop tourne sur la machine locale et simule le fonctionnement dans un petit cluster. Comme dans le mode distribué, le mode pseudo-distribué exige aussi la possibilité d'accéder à l'hôte de Hadoop, en occurrence localhost, sans la saisie de mot de passe. On doit donc commencer notre configuration par l'installation du SSH qui se fait dans plusieurs étapes (exemple Linux) :


  1. ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa
    Cette commande permet de générer la paire des clés privée et publique :
    - du type dsa (paramètre -t)
    - dont le passphrase qui va encrypter la clé privée est vide (-P '')
    - qui sont sauvegardés dans ~/.ssh/id_dsa (paramètre -f)


  2. cat ~/.ssh/id_dsa.pub >> ~/.ssh/authorized_keys
    Ici on rajoute la clé publique générée dans le fichier des clés autorisées par le système distant. Dans notre cas il s'agit d'un faux système distant car la machine à laquelle on se connecte est bien localhost.

  3. bartosz@bartosz-K70ID:~$ ssh localhost
    A travers cette commande on vérifie si l'on peut se connecter à localhost sans saisir le mot de passe. Si c'est le cas, on devrait voir sur notre écran le message semblable à celui-ci :

    Welcome to Ubuntu 12.04.2 LTS (GNU/Linux 3.5.0-23-generic x86_64)

    * Documentation: https://help.ubuntu.com/

    Last login: Sun Jul 28 09:24:38 2013 from localhost


    Une fois SSH installée, on peut passer à la configuration de Hadoop. Elle est rapide et nécessite la modifications des trois fichiers. Voici la liste des fichiers avec les modifications à apporter :

    • mapred-site.xml : la propriété de configuration "mapred.job.tracker" doit prendre comme valeur "localhost:8021"

    • core-site.xml : la propriété de configuration "fs.default.name" doit prendre comme valeur "hdfs://localhost/"

    • hdfs-site.xml : la propriété de configuration "dfs.replication" doit prendre comme valeur "1"


    On y indique l'adresse du jobtracker (mapred-site.xml), le nom du système de fichier par défaut (core-site.xml) ainsi que le niveau de réplication (hdfs-site.xml).

    Maintenant, pour pouvoir utiliser Hadoop, il est très préférable d'exporter certaines variables d'environnement. Pour ce faire, on se connecte en tant que l'utilisateur qui exécutera les commandes Hadoop. Ensuite on modifie le fichier ~/.bashrc en y rajoutant les lignes suivantes :

    # Set Hadoop-related environment variables
    export HADOOP_HOME=/home/bartosz/hadoop-1.1.2

    # Set JAVA_HOME (we will also configure JAVA_HOME directly for Hadoop later on)
    export JAVA_HOME=/usr/lib/jvm/java-7-openjdk-amd64

    # Add Hadoop bin/ directory to PATH
    export PATH=$PATH:$HADOOP_HOME/bin


    Après ces opérations on vérifie avec une simple commande hadoop --version si Hadoop est utilisable en ligne de commande.

    Configurer HDFS
    Maintenant quand on a préparé l'environnement, on peut s'occuper d'isntaller le système de fichiers HDFS. Tout d'abord on doit le formatter. La commande hadoop namenode -format s'en charge. Si tout se passe bien on devrait voir sur l'écran un résultat semblable à celui-ci :

    13/08/10 08:04:16 INFO namenode.NameNode: STARTUP_MSG:
    /************************************************************
    STARTUP_MSG: Starting NameNode
    STARTUP_MSG: host = bartosz-K70ID/127.0.1.1
    STARTUP_MSG: args = [-format]
    STARTUP_MSG: version = 1.1.2
    STARTUP_MSG: build = https://svn.apache.org/repos/asf/hadoop/common/branches/branch-1.1 -r 1440782; compiled by 'hortonfo' on Thu Jan 31 02:03:24 UTC 2013
    ************************************************************/
    13/08/10 08:04:16 INFO util.GSet: VM type = 64-bit
    13/08/10 08:04:16 INFO util.GSet: 2% max memory = 17.77875 MB
    13/08/10 08:04:16 INFO util.GSet: capacity = 2^21 = 2097152 entries
    13/08/10 08:04:16 INFO util.GSet: recommended=2097152, actual=2097152
    13/08/10 08:04:17 INFO namenode.FSNamesystem: fsOwner=bartosz
    13/08/10 08:04:17 INFO namenode.FSNamesystem: supergroup=supergroup
    13/08/10 08:04:17 INFO namenode.FSNamesystem: isPermissionEnabled=true
    13/08/10 08:04:17 INFO namenode.FSNamesystem: dfs.block.invalidate.limit=100
    13/08/10 08:04:17 INFO namenode.FSNamesystem: isAccessTokenEnabled=false accessKeyUpdateInterval=0 min(s), accessTokenLifetime=0 min(s)
    13/08/10 08:04:17 INFO namenode.NameNode: Caching file names occuring more than 10 times
    13/08/10 08:04:17 INFO common.Storage: Image file of size 113 saved in 0 seconds.
    13/08/10 08:04:17 INFO namenode.FSEditLog: closing edit log: position=4, editlog=/tmp/hadoop-bartosz/dfs/name/current/edits
    13/08/10 08:04:17 INFO namenode.FSEditLog: close success: truncate to 4, editlog=/tmp/hadoop-bartosz/dfs/name/current/edits
    13/08/10 08:04:18 INFO common.Storage: Storage directory /tmp/hadoop-bartosz/dfs/name has been successfully formatted.
    13/08/10 08:04:18 INFO namenode.NameNode: SHUTDOWN_MSG:
    /************************************************************
    SHUTDOWN_MSG: Shutting down NameNode at bartosz-K70ID/127.0.1.1
    ************************************************************/

    Pendant le formattage Hadoop crée le répertoire dans lequel il placera les fichiers du namenode. Si le paramètre dfs.name.dir est absent dans la configuration, le chemin vers le répertoire sera construit comme dans le log ci-dessus : tmp/$nom_utilisateur_hadoop/dfs/name.

    Le formattage signifie que le système de fichiers est prêt à être lancé. On peut alors commencer à stocker les fichiers ou à les faire analyser par MapReduce. Mais avant cela, il faut démarrer HDFS avec la commande start-dfs.sh. Si la commande s'exécute correctement, on devrait voir sur l'écran le message suivant :

    starting namenode, logging to /home/bartosz/hadoop-1.1.2/libexec/../logs/hadoop-bartosz-namenode-bartosz-K70ID.out
    localhost: starting datanode, logging to /home/bartosz/hadoop-1.1.2/libexec/../logs/hadoop-bartosz-datanode-bartosz-K70ID.out
    localhost: starting secondarynamenode, logging to /home/bartosz/hadoop-1.1.2/libexec/../logs/hadoop-bartosz-secondarynamenode-bartosz-K70ID.out


    Après le démarrage de HDFS, on doit aussi initialiser MapReduce. On le fera avec la commande start-mapred.sh. Elle devrait donner le message suivant sur l'écran :

    starting jobtracker, logging to /home/bartosz/hadoop-1.1.2/libexec/../logs/hadoop-bartosz-jobtracker-bartosz-K70ID.out
    localhost: starting tasktracker, logging to /home/bartosz/hadoop-1.1.2/libexec/../logs/hadoop-bartosz-tasktracker-bartosz-K70ID.out


    On peut également utiliser les scripts de raccourci pour démarrer et arrêter HDFS et MapReduce. Le démarrage s'effectue avec start-all.sh et l'arrêt avec stop-all.sh.

    Un exemple de MapReduce
    Pour illustrer le fonctionnement de MapReduce on peut se composer un fichier de texte quelconque, composé de plusieurs lignes. La tâche map va l'analyser ligne par ligne. Dans notre cas on utilisera quelques actualités trouvées sur le site de "L'Equipe". On voudra trouver les mots les plus longs contenant une chaîne de caractères "on".

    Avant d'écrire le code il faut copier le fichier avc les actualités depuis le système de fichiers local à notre système de fichiers "pseudo-distribué". On appelle alors la commande hadoop dfs -copyFromLocal /home/bartosz/hadoop-1.1.2/tests/sampleShortInput.txt ./sampleInput1.txt. Pour vérifier si le fichier a été bien copié, on peut exécuter la commande suivante hadoop dfs -ls /user/bartosz. S'il est présent dans la liste, on peut commencer le développement. Voici la classe de map :

    public class MapperSample extends MapReduceBase implements Mapper<LongWritable, Text, LongWritable, Text> {

    private static Pattern model = Pattern.compile("(\\p{L}+|)on(\\p{L}+|)");

    @Override
    public void map(LongWritable key, Text value, OutputCollector<LongWritable, Text> output, Reporter reporter) throws IOException {
    StringBuffer regexResult = new StringBuffer();
    Matcher match = model.matcher(value.toString());
    while (match.find()) {
    regexResult.append(match.group());
    regexResult.append(", ");
    }
    // remove the last ,
    if (regexResult.lastIndexOf(",") > -1) regexResult.deleteCharAt(regexResult.lastIndexOf(","));
    output.collect(new LongWritable(new Date().getTime()), new Text(regexResult.toString()));
    }

    }


    La classe implémente l'interface Mapper dont les types génériques indique, en ordre, la clé d'entrée (LongWritable), la valeur d'éntre (Text), la clé de sortie (LongWritable) et la valeur de sortie (Text). On voit également que MapReduce n'opère pas sur les classes Java (Long et String en occurrence). A la place de cela, il utilise ses propres types de données qui sont optimisés pour être sérialisés dans la réseau. Ensuite on voit dans notre code la seule méthode, map. Elle prend en paramètre la clé et la valeur d'entrée, un objet qui collectione des données pour les passer à reducer et un réporteur. Ce dernier n'est pas utilisé mais son rôle consiste à notifier l'application de la progresson de la tâche ainsi que préciser les messages d'état liés au traitement des données. On voit que la dernière ligne contient le fragment de code qui permet de passer le résultat de map à la tâche de réduction. D'ailleurs, la voici :

    public class ReducerSample extends MapReduceBase implements Reducer<LongWritable, Text, LongWritable, Text> {

    @Override
    public void reduce(LongWritable key, Iterator<Text> values, OutputCollector<LongWritable, Text> output, Reporter reporter) throws IOException {
    StringBuffer words = new StringBuffer();
    String maxLengthWord = null;
    while (values.hasNext()) {
    Text text = (Text) values.next();
    String[] wordsArray = text.toString().split(",");
    for (String oneWord : wordsArray) {
    if (oneWord.trim().length() > 1 && (maxLengthWord == null || maxLengthWord.length() < oneWord.trim().length())) {
    maxLengthWord = oneWord.trim();
    }
    }
    words.append(text.toString());
    }
    if (words != null && maxLengthWord != null) {
    output.collect(key, new Text("The longest word from the next words '"+words+"' is "+maxLengthWord + " ("+maxLengthWord.length()+" characters)"));
    }
    }

    }

    Elle diffère peut de la tâche de mapping. Seules l'interface (Reducer à la place de Mapper) et la méthode surchargée (reduce à la place du map) changent. Les deux classes ont besoin d'une sorte de contrôleur qui va les maintenir (démarrer, arrêter) en état et suivre la progression. Elle peut ressembler à celle-ci :

    public class WorkerSample {
    public static void main(String[] args) throws IOException {
    JobConf configuration = new JobConf(WorkerSample.class);
    configuration.setJobName("Worker sample test");

    FileSystem hdfsFileSystem = FileSystem.get(configuration);
    hdfsFileSystem.delete(new Path("/user/bartosz/sampleOutput"), true);

    FileInputFormat.addInputPath(configuration, new Path("/user/bartosz/sampleInput1.txt"));
    FileOutputFormat.setOutputPath(configuration, new Path("/user/bartosz/sampleOutput"));

    configuration.setMapperClass(MapperSample.class);
    configuration.setReducerClass(ReducerSample.class);

    configuration.setMapOutputKeyClass(LongWritable.class);
    configuration.setMapOutputValueClass(Text.class);
    JobClient.runJob(configuration);
    }
    }

    Au début on initialise la configuration (instance de la classe JobConf). On y indique la classe qui va coordiner le travail du mapper et reducer, avec le constructeur du JobConf. Ensuite on spécifie le fichier d'entrée, copié précédemment (addInputPath, et l'endroit dans lequel on pourra retrouver le résultat (setOutputPath). On fait également une chose important dans le cas où l'on exécuterait la tâche plusieurs fois. Il s'agit de la suppression du résultat précédent avec la méthode FileSystem.delete. Vers la fin on spécifie les types pour la clé (setMapOutputKeyClass) et la valeur (setOutputMapValueClass) de sortie. Dans la dernière ligne on démarre la tâche.

    L'exécution des tâches MapReduce dans Hadoop nécessite la détermination du classpath. On peut le faire en modifiant le fichier hadoop-env.sh par rajout de cette ligne : HADOOP_CLASSPATH=/home/bartosz/hadoop-1.1.2/javabin. Elle indique le chemin vers les classes compilées. Maintenant on peut exécuter la tâche avec la commande hadoop songClient.WorkerSample. Suite à son lancement, on devrait voir sur l'écran :

    13/08/10 09:33:44 WARN mapred.JobClient: Use GenericOptionsParser for parsing the arguments. Applications should implement Tool for the same.
    13/08/10 09:33:44 WARN mapred.JobClient: No job jar file set. User classes may not be found. See JobConf(Class) or JobConf#setJar(String).
    13/08/10 09:33:44 INFO util.NativeCodeLoader: Loaded the native-hadoop library
    13/08/10 09:33:44 WARN snappy.LoadSnappy: Snappy native library not loaded
    13/08/10 09:33:44 INFO mapred.FileInputFormat: Total input paths to process : 1
    13/08/10 09:33:45 INFO mapred.JobClient: Running job: job_201308100848_0008
    13/08/10 09:33:46 INFO mapred.JobClient: map 0% reduce 0%
    13/08/10 09:33:56 INFO mapred.JobClient: map 50% reduce 0%
    13/08/10 09:33:57 INFO mapred.JobClient: map 100% reduce 0%
    13/08/10 09:34:06 INFO mapred.JobClient: map 100% reduce 33%
    13/08/10 09:34:08 INFO mapred.JobClient: map 100% reduce 100%
    13/08/10 09:34:11 INFO mapred.JobClient: Job complete: job_201308100848_0008
    13/08/10 09:34:11 INFO mapred.JobClient: Counters: 30
    13/08/10 09:34:11 INFO mapred.JobClient: Job Counters
    13/08/10 09:34:11 INFO mapred.JobClient: Launched reduce tasks=1
    13/08/10 09:34:11 INFO mapred.JobClient: SLOTS_MILLIS_MAPS=18804
    13/08/10 09:34:11 INFO mapred.JobClient: Total time spent by all reduces waiting after reserving slots (ms)=0
    13/08/10 09:34:11 INFO mapred.JobClient: Total time spent by all maps waiting after reserving slots (ms)=0
    13/08/10 09:34:11 INFO mapred.JobClient: Launched map tasks=2
    13/08/10 09:34:11 INFO mapred.JobClient: Data-local map tasks=2
    13/08/10 09:34:11 INFO mapred.JobClient: SLOTS_MILLIS_REDUCES=11227
    13/08/10 09:34:11 INFO mapred.JobClient: File Input Format Counters
    13/08/10 09:34:11 INFO mapred.JobClient: Bytes Read=4734
    13/08/10 09:34:11 INFO mapred.JobClient: File Output Format Counters
    13/08/10 09:34:11 INFO mapred.JobClient: Bytes Written=3321
    13/08/10 09:34:11 INFO mapred.JobClient: FileSystemCounters
    13/08/10 09:34:11 INFO mapred.JobClient: FILE_BYTES_READ=3330
    13/08/10 09:34:11 INFO mapred.JobClient: HDFS_BYTES_READ=4942
    13/08/10 09:34:11 INFO mapred.JobClient: FILE_BYTES_WRITTEN=160901
    13/08/10 09:34:11 INFO mapred.JobClient: HDFS_BYTES_WRITTEN=3321
    13/08/10 09:34:11 INFO mapred.JobClient: Map-Reduce Framework
    13/08/10 09:34:11 INFO mapred.JobClient: Map output materialized bytes=3336
    13/08/10 09:34:11 INFO mapred.JobClient: Map input records=9
    13/08/10 09:34:11 INFO mapred.JobClient: Reduce shuffle bytes=3336
    13/08/10 09:34:11 INFO mapred.JobClient: Spilled Records=18
    13/08/10 09:34:11 INFO mapred.JobClient: Map output bytes=3294
    13/08/10 09:34:11 INFO mapred.JobClient: Total committed heap usage (bytes)=379584512
    13/08/10 09:34:11 INFO mapred.JobClient: CPU time spent (ms)=2220
    13/08/10 09:34:11 INFO mapred.JobClient: Map input bytes=3156
    13/08/10 09:34:11 INFO mapred.JobClient: SPLIT_RAW_BYTES=208
    13/08/10 09:34:11 INFO mapred.JobClient: Combine input records=0
    13/08/10 09:34:11 INFO mapred.JobClient: Reduce input records=9
    13/08/10 09:34:11 INFO mapred.JobClient: Reduce input groups=4
    13/08/10 09:34:11 INFO mapred.JobClient: Combine output records=0
    13/08/10 09:34:11 INFO mapred.JobClient: Physical memory (bytes) snapshot=442486784
    13/08/10 09:34:11 INFO mapred.JobClient: Reduce output records=4
    13/08/10 09:34:11 INFO mapred.JobClient: Virtual memory (bytes) snapshot=3383615488
    13/08/10 09:34:11 INFO mapred.JobClient: Map output records=9


    Maintenant on peut vérifier les résultats. Au début on s'assure qu'ils ont été bien placés dans l'endroit spécifié dans WorkerSample, à savoir dans /user/bartosz/sampleOutput. Voici la commande qui permet de le faire : hadoop fs -ls /user/bartosz/sampleOutput. Elle devrait donner l'affichage suivant :

    Found 3 items
    -rw-r--r-- 1 bartosz supergroup 0 2013-08-10 09:34 /user/bartosz/sampleOutput/_SUCCESS
    drwxr-xr-x - bartosz supergroup 0 2013-08-10 09:33 /user/bartosz/sampleOutput/_logs
    -rw-r--r-- 1 bartosz supergroup 3321 2013-08-10 09:34 /user/bartosz/sampleOutput/part-00000

    Le résultat de traitement MapReduce se trouve dans part-00000. Il peut y avoir plusieurs fichiers préfixés par "part-". Leur nombre correspond au nombre des tâches reduce qui analysent les sorties de map. On voit que dans notre cas il s'agit d'une seule tâche reduce. Le fichiers du répertoire _logs contiennent les informations sur l'environnement de traitement. On y retrouve la configuration du système au moment de l'exécution des tâches ainsi que les statistiques sur le bytes écrits, les lignes analysées ou la mémoire occupée. Le fichier _SUCCESS est vide. Il y existe uniquement pour marquer la tâche comme "exécutée correctement".

    Problèmes HDFS et MapReduce
    Bien que Hadoop soit très intuitif dans la mise en place, on peut rencontrer quelques soucis :


    1. Error: Could not find or load main class songClient.WorkerSample

      L'erreur apparaît quand le classpath n'est pas renseignée. Elle veut dire que Hadoop n'arrive pas à trouver la classe à exécuter.



    2. 13/08/10 08:32:30 ERROR security.UserGroupInformation: PriviledgedActionException as:bartosz cause:org.apache.hadoop.ipc.RemoteException: org.apache.hadoop.mapred.SafeModeException: JobTracker is in safe mode
      at org.apache.hadoop.mapred.JobTracker.checkSafeMode(JobTracker.java:5270)
      at org.apache.hadoop.mapred.JobTracker.getStagingAreaDir(JobTracker.java:3797)
      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)

      Le JobTracker est en safe-mode. Il faut alors patienter qu'il le quitte. Pour en savoir plus, consultez l'article consacré à >>>>>>>>>>>>>voir quel article consulter>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<



    3. 13/08/10 08:33:21 ERROR security.UserGroupInformation: PriviledgedActionException as:bartosz cause:org.apache.hadoop.mapred.InvalidInputException: Input path does not exist: hdfs://localhost:54310/home/bartosz/hadoop-1.1.2/tests/sampleShortInput.txt
      Exception in thread "main" org.apache.hadoop.mapred.InvalidInputException: Input path does not exist: hdfs://localhost:54310/home/bartosz/hadoop-1.1.2/tests/sampleShortInput.txt
      at org.apache.hadoop.mapred.FileInputFormat.listStatus(FileInputFormat.java:197)
      at org.apache.hadoop.mapred.FileInputFormat.getSplits(FileInputFormat.java:208)
      at org.apache.hadoop.mapred.JobClient.writeOldSplits(JobClient.java:1051)
      at org.apache.hadoop.mapred.JobClient.writeSplits(JobClient.java:1043)
      at org.apache.hadoop.mapred.JobClient.access$700(JobClient.java:179)

      Cette exception de MapReduce veut dire que le fichier d'entrée n'existe pas. Probablement il a été mal copié ou, comme dans le cas ci-dessus, on a fourni un chemin incorrect (en occurrence, l'URI complète vers le fichier stocké dans le système de fichier standard et non pas celui de HDFS).



    4. 13/08/10 08:39:32 INFO mapred.JobClient: Task Id : attempt_201308100832_0002_m_000003_0, Status : FAILED
      org.apache.hadoop.ipc.RemoteException: java.io.FileNotFoundException: Parent path is not a directory: /user/bartosz
      at org.apache.hadoop.hdfs.server.namenode.FSDirectory.mkdirs(FSDirectory.java:974)
      at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.mkdirsInternal(FSNamesystem.java:2217)

      Ce problème apparaîtra si l'on copiera mal le fichier. On peut penser à tort que la commande hadoop dfs -copyFromLocal /home/bartosz/hadoop-1.1.2/tests/sampleShortInput.txt ./ copiera le fichier sampleShortInput.txt dans /user/bartosz. On pourra donc accéder au fichier /user/bartosz/sampleShortInput.txt. Cependant, ce n'est pas le cas et on peut s'en aperçevoir en exécutant la commande hadoop dfs -ls /user/bartosz. Le résultat donnera alors :

      Found 1 items
      -rw-r--r-- 1 bartosz supergroup 3156 2013-08-10 08:36 /user/bartosz




    Cet article nous a présentés comment mettre en place une simple tâche de MapReduce. On a vu d'abord la méthode d'installation de Hadoop. Ensuite on a expliqué la mise en place du système de fichier (pseudo) distribué pour pouvoir écrire les tâches de map et réduce. On les a exécutées grâce au JobTracker qui coordinait le traitement de fichier d'entrée. A la fin on a vu comment retrouver les résultats de traitement et quels problèmes on peut rencontrer pendant le travail avec Hadoop.
Bartosz KONIECZNY 08-09-2013 16:13 Hadoop
Moi

Développeur d'applications Internet et journaliste passionné par l'adjectif français. Un aigle polonais orienté vers la progression, volant très haut et écoutant du zouk après les matches du foot français.

Vous appréciez mon travail ?

Pour contribuer au développement de ce site, ou pour remercier pour des articles rédigés, vous pouvez faire un don.

Un conseil Symfony2

Problème avec les valeurs par défaut pour un champ type checkbox ?

La gestion des formulaires sous Symfony2 correspond parfaitement au slogan de jQuery "write less, do more". En effet, il suffit de déterminer les validateurs une fois et après seulement les adapter en fonction du groupe du formulaire. Egalement la définition des champs est très intuitive. La récupération des données saisies, en cas d'une erreur de validation, est aussi automatique. Cependant, il se peut que vous renctonreriez un problème avec les champs du type checkbox. Supposons, qu'on veut construire un checkbox qui prendra pour valeur des chiffres (integers) correspondant aux identifiants aux apparements recherchés (1 pièce, 2 pièces, 3 pièces etc.) :

protected static $types = array(1 => '1 room', 2 => '2 rooms', 3 => '3 rooms');
Après une validation incorrecte, on retourne sur la page avec le formulaire pré-rempli. Pour être sûr que la liste des checkboxes va avoir les champs pré-cochés (par exemple 1 room et 3 rooms), il faut s'assurer que les types des valeurs sauvegardées sont égales à celles de la variable statique $types. Souvent il faut faire une boucle pour régler un éventuel problème avec les valeurs pré-cochés dans multiple checkboxes sous Symfony2 :
    foreach($values as $v => $val)
    {
      $vals[] = (int)$val;
    }