diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000000000000000000000000000000000000..1c046bd0a9cbbffe3385b58dfc052ce63f48e5a2
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,23 @@
+CHANGELOG
+=========
+
+3.0.0 (01/10/2020)
+------------------
+
+- Adaptation du code pour le passage à ZF3.
+- Scission des classes liées aux entités ("Generic", "Group", "People", "Root", "Structure" et "System") en deux classes :
+    - classe de base avec un setter pour (presque) chaque attribut Ldap
+    - classe qui hérite de la classe de base avec des fonctions spécifiques à l'entité
+- Gestions des attributs "supannActivite", "ucbnSecteurDisciplinaire" et "supannEmpCorps"
+- Ajout des getters pour les attributs de la classe "People" : "rid", "sambaSID", "uidNumber", "gidNumber", "loginShell" et "homeDirectory"
+- Ajout des setters pour les différents attributs liés aux structures de la classe "People"
+- Ajout des attributs cachés "createTimestamp" et "modifyTimestamp" comme DateTimeAttributes
+- Ajout des setters pour les attributs de la classe "People" : "supannEtablissement", "supannEtuAnneeInscription", "supannEtuCursusAnnee", "supannEtuDiplome" et "supannEtuEtape"
+- Ajout des setters pour les attributs de la classe "People" : "supannEtuInscription", "ucbnSiteLocalisation", "ucbnAnneePostBac", "ucbnCodeEtape", "ucbnEtuComplementInscription", 
+"ucbnPrivateAddress", "ucbnPrivateAddresseBis", "supannEtuElementPedagogique", "supannEtuRegimeInscription", "supannEtuSecteurDisciplinaire" 
+et "supannEtuTypeDiplome"
+- Gestion de la modification de l'attribut "supannRefId" par étiquette
+- Ajout de fonctions de vérification du statut d'une personne dans la classe "People"
+- Fonction de récupération de champs date au format Php DateTime
+- Mise en place d'une entité "Root" pour gérer la racine de l'annuaire Ldap et d'un service associé
+- Ajout des setters pour les attributs de la classe "People" : "supannAutreMail" et "mailForwardingAddress"
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..0e6ea14691837d42ed7dca7ba7b2ca30c9940acd
--- /dev/null
+++ b/README.md
@@ -0,0 +1,42 @@
+# UnicaenLdap
+
+ * [Introduction](#introduction)
+ * [Installation](#installation)
+ * [Configuration](#configuration)
+ 
+## Introduction
+ 
+Ce module permet de se connecter à l'annuaire Ldap de l'université et de consulter/modifier ses données. 
+
+## Pré-requis
+
+L'utilisation de ce module nécessite l'installation de l'extension `ext-ldap` de PHP.
+
+## Installation
+
+```bash 
+$ composer require unicaen/ldap
+```
+
+## Configuration
+
+> Récupérer les fichiers de config du module 
+ 
+```bash
+$ cp -n vendor/unicaen/zimbra/config/unicaen-ldap.local.php.dist config/autoload/unicaen-ldap.local.php
+```
+
+> Adapter le contenu à vos besoins en configurant notamment les paramètres de connexion au serveur Ldap.
+
+```php
+'unicaen-ldap' => [
+    'host'                => 'ldap-test.unicaen.fr',
+    'port'                => 389,
+    'version'             => 3,
+    'baseDn'              => "dc=unicaen,dc=fr", // racine de l'annuaire
+    'bindRequiresDn'      => true,
+    'username'            => "uid=xxxx,ou=system,dc=unicaen,dc=fr",
+    'password'            => "xxxx",
+    'accountFilterFormat' => "(&(objectClass=supannPerson)(supannAliasLogin=%s))",
+]
+```
\ No newline at end of file
diff --git a/config/module.config.php b/config/module.config.php
index 130e11db74c5a7fbffe72445913b92ed755666d3..615aed75d6780e509a69f0d6e977acded45985df 100644
--- a/config/module.config.php
+++ b/config/module.config.php
@@ -14,51 +14,54 @@ use UnicaenLdap\Service\Group as LdapGroupService;
 use UnicaenLdap\Service\GroupFactory as GroupServiceFactory;
 use UnicaenLdap\Service\People as LdapPeopleService;
 use UnicaenLdap\Service\PeopleFactory as PeopleServiceFactory;
+use UnicaenLdap\Service\Root as LdapRootService;
+use UnicaenLdap\Service\RootFactory as RootServiceFactory;
 use UnicaenLdap\Service\Structure as LdapStructureService;
 use UnicaenLdap\Service\StructureFactory as StructureServiceFactory;
 use UnicaenLdap\Service\System as LdapSystemService;
 use UnicaenLdap\Service\SystemFactory as SystemServiceFactory;
 use Zend\ServiceManager\Proxy\LazyServiceFactory;
 
-;;
-
 return array(
     'unicaen-ldap' => [
 
     ],
     'service_manager' => [
         'factories' => [
-            'Ldap'        => LdapFactory::class,
-            'LdapOptions' => ModuleOptionsFactory::class,
-
-            'LdapServiceGeneric'   => GenericServiceFactory::class,
-            'LdapServiceGroup'     => GroupServiceFactory::class,
-            'LdapServicePeople'    => PeopleServiceFactory::class,
-            'LdapServiceStructure' => StructureServiceFactory::class,
-            'LdapServiceSystem'    => SystemServiceFactory::class,
+            'Ldap'                      => LdapFactory::class,
+            'LdapOptions'               => ModuleOptionsFactory::class,
+            'LdapServiceGeneric'        => GenericServiceFactory::class,
+            'LdapServiceGroup'          => GroupServiceFactory::class,
+            'LdapServicePeople'         => PeopleServiceFactory::class,
+            'LdapServiceRoot'           => RootServiceFactory::class,
+            'LdapServiceStructure'      => StructureServiceFactory::class,
+            'LdapServiceSystem'         => SystemServiceFactory::class,
         ],
         'aliases' => [
-            'ldap'                 => 'Ldap',
-            'ldapOptions'          => 'LdapOptions',
-            'ldapServiceGeneric'   => 'LdapServiceGeneric',
-            'ldapServiceGroup'     => 'LdapServiceGroup',
-            'ldapServicePeople'    => 'LdapServicePeople',
-            'ldapServiceStructure' => 'LdapServiceStructure',
-            'ldapServiceSystem'    => 'LdapServiceSystem',
-            LdapGenericService::class          =>  'LdapServiceGeneric',
-            LdapPeopleService::class           =>  'LdapServicePeople',
-            LdapGroupService ::class           =>  'LdapServiceGroup',
-            LdapStructureService::class        =>  'LdapServiceStructure',
-            LdapSystemService::class           =>  'LdapServiceSystem',
+            'ldap'                      => 'Ldap',
+            'ldapOptions'               => 'LdapOptions',
+            'ldapServiceGeneric'        => 'LdapServiceGeneric',
+            'ldapServiceGroup'          => 'LdapServiceGroup',
+            'ldapServicePeople'         => 'LdapServicePeople',
+            'ldapServiceRoot'           => 'LdapServiceRoot',
+            'ldapServiceStructure'      => 'LdapServiceStructure',
+            'ldapServiceSystem'         => 'LdapServiceSystem',
+            LdapGenericService::class   => 'LdapServiceGeneric',
+            LdapGroupService ::class    => 'LdapServiceGroup',
+            LdapPeopleService::class    => 'LdapServicePeople',
+            LdapRootService::class      => 'LdapServiceRoot',
+            LdapStructureService::class => 'LdapServiceStructure',
+            LdapSystemService::class    => 'LdapServiceSystem',
         ],
         'lazy_services' => [
             // Mapping services to their class names is required since the ServiceManager is not a declarative DIC.
             'class_map' => [
-                'LdapServicePeople'    => LdapPeopleService::class,
-                'LdapServiceGeneric'   => LdapGenericService::class,
-                'LdapServiceGroup'     => LdapGroupService::class,
-                'LdapServiceStructure' => LdapStructureService::class,
-                'LdapServiceSystem'    => LdapSystemService::class,
+                'LdapServicePeople'     => LdapPeopleService::class,
+                'LdapServiceGeneric'    => LdapGenericService::class,
+                'LdapServiceGroup'      => LdapGroupService::class,
+                'LdapServiceRoot'       => LdapRootService::class,
+                'LdapServiceStructure'  => LdapStructureService::class,
+                'LdapServiceSystem'     => LdapSystemService::class,
             ],
         ],
         'delegators' => [
@@ -71,14 +74,15 @@ return array(
             'LdapServiceGroup' => [
                 LazyServiceFactory::class,
             ],
+            'LdapServiceRoot' => [
+                LazyServiceFactory::class,
+            ],
             'LdapServiceStructure' => [
                 LazyServiceFactory::class,
             ],
             'LdapServiceSystem' => [
                 LazyServiceFactory::class,
             ],
-
         ],
-
     ],
 );
diff --git a/config/unicaen-ldap.local.php.dist b/config/unicaen-ldap.local.php.dist
index e4091d5a8e837f36f3dbc4e2b93d147fa1198ce5..dd8d54c08dc1722477cf66af54d26956d4591998 100644
--- a/config/unicaen-ldap.local.php.dist
+++ b/config/unicaen-ldap.local.php.dist
@@ -1,14 +1,14 @@
 <?php
 
-return array(
-   'unicaen-ldap' => array(
-         'host'                => 'host.domain.fr',
-         'port'                => 389,
-         'version'             => 3,
-         'baseDn'              => "ou=xxxxxxxxxxx,dc=domain,dc=fr",
-         'bindRequiresDn'      => true,
-         'username'            => "uid=xxxxxxxxx,ou=xxxxxxxxxx,dc=domain,dc=fr",
-         'password'            => "xxxxxxxxxxxx",
-         'accountFilterFormat' => "(&(objectClass=posixAccount)(supannAliasLogin=%s))",
-     )
-);
+return [
+    'unicaen-ldap' => [
+        'host'                => 'host.domain.fr',
+        'port'                => 389,
+        'version'             => 3,
+        'baseDn'              => "dc=domain,dc=fr", // racine de l'annuaire
+        'bindRequiresDn'      => true,
+        'username'            => "uid=xxxxxxxxx,ou=xxxxxxxxxx,dc=domain,dc=fr",
+        'password'            => "xxxxxxxxxxxx",
+        'accountFilterFormat' => "(&(objectClass=posixAccount)(supannAliasLogin=%s))",
+    ]
+];
diff --git a/src/UnicaenLdap/Entity/Base/Generic.php b/src/UnicaenLdap/Entity/Base/Generic.php
new file mode 100644
index 0000000000000000000000000000000000000000..e3f8c63fcfead988d951355df5c68c54d33a57fb
--- /dev/null
+++ b/src/UnicaenLdap/Entity/Base/Generic.php
@@ -0,0 +1,189 @@
+<?php
+
+namespace UnicaenLdap\Entity\Base;
+
+use UnicaenLdap\Entity\Entity;
+use UnicaenLdap\Exception;
+use Zend\Ldap\Attribute;
+use Zend\Ldap\Exception\LdapException;
+
+/**
+ * Classe mère des entités de la branche "generic" de l'annuaire LDAP.
+ *
+ * @author David Surville <david.surville@unicaen.fr>
+ */
+class Generic extends Entity
+{
+    /**
+     * @var string
+     */
+    protected $type = 'Generic';
+
+    /**
+     * Liste des classes d'objet nécessaires à la création d'une adresse générique
+     *
+     * @var array
+     */
+    protected $objectClass = [
+        'top',
+        'inetOrgPerson',
+        'organizationalPerson',
+        'person',
+    ];
+
+    /**
+     * Liste des attributs autorisés pour une entité "Generic"
+     *
+     * @var array
+     */
+    protected $authorizedAttributes = [
+        // Attributes classes
+        'objectClass',
+        // Attributes
+        'cn',
+        'description',
+        'mail',
+        'sn',
+        'supannAliasLogin',
+        'ucbnServiceIMAP',
+        'userPassword',
+    ];
+
+
+    /**
+     * Liste des attributs contenant des dates
+     *
+     * @var string[]
+     */
+    protected $dateTimeAttributes = [
+    ];
+
+    /**
+     * Liste des attributs monovalués
+     *
+     * @var array
+     */
+    protected $monoValuedAttributes = [
+        'supannALiasLogin',
+        'ucbnServiceIMAP',
+        'userPassword',
+    ];
+
+
+    /**
+     * Attribut Ldap "cn"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     */
+    public function setCn($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $this->appendOrNot('cn', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "description"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     */
+    public function setDescription($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $this->appendOrNot('description', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "mail"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setMail($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter(filter_var_array($value, FILTER_VALIDATE_EMAIL));
+        $this->appendOrNot('mail', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "sn"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     */
+    public function setSn($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $this->appendOrNot('sn', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannALiasLogin"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannAliasLogin($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map('strtolower', $value);
+        $value = array_filter($value, function ($v) {
+            return preg_match('/^[0-9a-z\-]+$/', $v);
+        });
+
+        $this->appendOrNot('supannAliasLogin', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "ucbnServiceIMAP"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setUcbnServiceIMAP($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map([$this, 'formatBoolean'], $value);
+        $this->appendOrNot('ucbnServiceIMAP', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "userPassword"
+     *
+     * @param string $value
+     * @return self
+     * @throws LdapException
+     */
+    public function setUserPassword(string $value)
+    {
+        $this->getNode()->setPasswordAttribute($value, Attribute::PASSWORD_HASH_SHA, 'userPassword');
+
+        return $this;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenLdap/Entity/Base/Group.php b/src/UnicaenLdap/Entity/Base/Group.php
new file mode 100644
index 0000000000000000000000000000000000000000..23fafdcf46199c78508d6f309ee1d0352312af48
--- /dev/null
+++ b/src/UnicaenLdap/Entity/Base/Group.php
@@ -0,0 +1,272 @@
+<?php
+
+namespace UnicaenLdap\Entity\Base;
+
+use UnicaenLdap\Entity\Entity;
+use UnicaenLdap\Entity\People as PeopleEntity;
+use UnicaenLdap\Entity\Structure as StructureEntity;
+use UnicaenLdap\Entity\System as SystemEntity;
+use UnicaenLdap\Exception;
+use Zend\Ldap\Dn;
+use Zend\Ldap\Exception\LdapException;
+
+/**
+ * Classe mère des entités de la branche "groups" de l'annuaire LDAP.
+ *
+ * @author David Surville <david.surville@unicaen.fr>
+ */
+class Group extends Entity
+{
+    /**
+     * Membre par défaut d'un groupe lorsque le groupe est vide
+     */
+    const MEMBER_NOBODY = 'nobody';
+
+    /**
+     * @var string
+     */
+    protected $type = 'Group';
+
+    /**
+     * Liste des classes d'objet nécessaires à la création d'un groupe
+     *
+     * @var string[]
+     */
+    protected $objectClass = [
+        'top',
+        'groupOfNames',
+        'supannGroupe',
+    ];
+
+    /**
+     * Liste des attributs autorisés pour une entité "Group"
+     *
+     * @var array
+     */
+    protected $authorizedAttributes = [
+        // Attributes classes
+        'objectClass',
+        // Attributes
+        'description',
+        'member',
+        'owner',
+        'supannGroupeDateFin',
+        'supannGroupeLecteurDN',
+        'supannGroupeAdminDN',
+        'supannRefId',
+    ];
+
+    /**
+     * Liste des attributs contenant des dates
+     *
+     * @var string[]
+     */
+    protected $dateTimeAttributes = [
+        'supannGroupeDateFin',
+    ];
+
+    /**
+     * Liste des attributs monovalués
+     *
+     * @var array
+     */
+    protected $monoValuedAttributes = [
+        'supannGroupeDateFin',
+    ];
+
+
+    /**
+     * Retourne le DN du membre par défaut
+     *
+     * @return string
+     */
+    public function getMemberNobody()
+    {
+        return sprintf('uid=%s,%s',
+            self::MEMBER_NOBODY,
+            $this->service->getLdapSystemService()->getBranches()[0]
+        );
+    }
+
+    /**
+     * Attribut Ldap "description"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     */
+    public function setDescription($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $this->appendOrNot('description', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "member"
+     *
+     * @param array|string|Dn|PeopleEntity|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setMember($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map(function ($val) {
+            if (is_string($val)) {
+                return Dn::checkDn($val) ? $val : null;
+            } elseif ($val instanceof Dn) {
+                return $val->toString();
+            } elseif ($val instanceof PeopleEntity) {
+                return $val->getDn();
+            } else {
+                return null;
+            }
+        }, $value);
+
+        $this->appendOrNot('member', array_filter($value), $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "owner"
+     *
+     * @param array|string|Dn|PeopleEntity|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setOwner($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map(function ($val) {
+            if (is_string($val)) {
+                return Dn::checkDn($val) ? $val : null;
+            } elseif ($val instanceof Dn) {
+                return $val->toString();
+            } elseif ($val instanceof PeopleEntity) {
+                return $val->getDn();
+            } else {
+                return null;
+            }
+        }, $value);
+
+        $this->appendOrNot('owner', array_filter($value), $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannGroupeDateFin"
+     *
+     * @param array|string|DateTime|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannGroupeDateFin($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map(function ($val) {
+            if (is_string($val)) {
+                $val = new DateTime($val, new \DateTimeZone('+0000')); // définition du timezone à +0h
+            }
+            return (int)$val->format('U');
+        }, $value);
+
+        $this->appendOrNot('supannGroupeDateFin', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannGroupeLecteurDN"
+     *
+     * @param array|string|Dn|PeopleEntity|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannGroupeLecteurDN($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map(function ($val) {
+            if (is_string($val)) {
+                return Dn::checkDn($val) ? $val : null;
+            } elseif ($val instanceof Dn) {
+                return $val->toString();
+            } elseif ($val instanceof PeopleEntity) {
+                return $val->getDn();
+            } elseif ($val instanceof SystemEntity) {
+                return $val->getDn();
+            }
+            else {
+                return null;
+            }
+        }, $value);
+
+        $this->appendOrNot('supannGroupeLecteurDN', array_filter($value), $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannGroupeAdminDN"
+     *
+     * @param array|string|Dn|PeopleEntity|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannGroupeAdminDN($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map(function ($val) {
+            if (is_string($val)) {
+                return Dn::checkDn($val) ? $val : null;
+            } elseif ($val instanceof Dn) {
+                return $val->toString();
+            } elseif ($val instanceof PeopleEntity) {
+                return $val->getDn();
+            } elseif ($val instanceof SystemEntity) {
+                return $val->getDn();
+            }
+            else {
+                return null;
+            }
+        }, $value);
+
+        $this->appendOrNot('supannGroupeAdminDN', array_filter($value), $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannRefId"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannRefId($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter($value, function ($v) {
+            return preg_match(self::$attribute_with_label_pattern, $v);
+        });
+
+        $this->appendOrNot('supannRefId', $value, $append);
+
+        return $this;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenLdap/Entity/Base/People.php b/src/UnicaenLdap/Entity/Base/People.php
new file mode 100644
index 0000000000000000000000000000000000000000..fedc45f9966c57e6e29ea0d985c6bff3ad75bb2d
--- /dev/null
+++ b/src/UnicaenLdap/Entity/Base/People.php
@@ -0,0 +1,2059 @@
+<?php
+
+namespace UnicaenLdap\Entity\Base;
+
+use DateTime;
+use UnicaenLdap\Entity\Entity;
+use UnicaenLdap\Entity\People as PeopleEntity;
+use UnicaenLdap\Entity\Structure as StructureEntity;
+use UnicaenLdap\Exception;
+use Zend\Ldap\Attribute;
+use Zend\Ldap\Dn;
+use Zend\Ldap\Exception\LdapException;
+
+/**
+ * Classe mère des entités de la branche "people" de l'annuaire LDAP.
+ *
+ * @author David Surville <david.surville@unicaen.fr>
+ */
+class People extends Entity
+{
+    /**
+     * Constantes pour les champs eduPersonAffiliation et eduPersonPrimaryAffiliation
+     */
+    const STATUS_AFFILIATE = 'affiliate';
+    const STATUS_ALUM = 'alum';
+    const STATUS_EMERITUS = 'emeritus';
+    const STATUS_EMPLOYEE = 'employee';
+    const STATUS_FACULTY = 'faculty';
+    const STATUS_MEMBER = 'member';
+    const STATUS_REGISTERED_READER = 'registered-reader';
+    const STATUS_RESEARCHER = 'researcher';
+    const STATUS_RETIRED = 'retired';
+    const STATUS_STAFF = 'staff';
+    const STATUS_STUDENT = 'student';
+    const STATUS_TEACHER = 'teacher';
+
+    /**
+     * @var string
+     */
+    protected $type = 'People';
+
+    /**
+     * Liste des classes d'objet nécessaires à la création d'une entité "People"
+     * Il est nécessaire d'ajouter la classe 'ucbnEtu' ou 'ucbnEmp' selon le
+     * statut de la personne.
+     *
+     * @var array
+     */
+    protected $objectClass = [
+        'top',
+        'inetOrgPerson',
+        'organizationalPerson',
+        'person',
+        'eduPerson',
+        'supannPerson',
+        'schacPersonalCharacteristics',
+        'schacEntryMetadata',
+        'ucbnEmp',
+        'ucbnEtu',
+        'posixAccount',
+        'sambaAccount',
+        'sambaSamAccount',
+    ];
+
+    /**
+     * Liste des attributs autorisés pour une entité "People"
+     *
+     * @var array
+     */
+    protected $authorizedAttributes = [
+        // Attributes classes
+        'objectClass',
+        // Attributes
+        'cn',
+        'dateDeNaissance',
+        'dateFinInscription',
+        'displayName',
+        'eduPersonAffiliation',
+        'eduPersonNickname',
+        'eduPersonOrgDN',
+        'eduPersonOrgUnitDN',
+        'eduPersonPrimaryAffiliation',
+        'eduPersonPrimaryOrgUnitDN',
+        'eduPersonPrincipalName',
+        'facsimileTelephoneNumber',
+        'gidNumber',
+        'givenName',
+        'homeDirectory',
+        'labeledURI',
+        'leoCode',
+        'loginShell',
+        'mail',
+        'mailForwardingAddress',
+        'mobile',
+        'ntPassword',
+        'pager',
+        'postalAddress',
+        'preferredLanguage',
+        'rid',
+        'sambaNTPassword',
+        'sambaSID',
+        'schacDateOfBirth',
+        'schacExpiryDate',
+        'sexe',
+        'sn',
+        'supannActivite',
+        'supannAffectation',
+        'supannAliasLogin',
+        'supannAutreMail',
+        'supannAutreTelephone',
+        'supannCivilite',
+        'supannCodeINE',
+        'supannEmpCorps',
+        'supannEmpId',
+        'supannEntiteAffectation',
+        'supannEntiteAffectationPrincipale',
+        'supannEtablissement',
+        'supannEtuAnneeInscription',
+        'supannEtuCursusAnnee',
+        'supannEtuDiplome',
+        'supannEtuElementPedagogique',
+        'supannEtuEtape',
+        'supannEtuId',
+        'supannEtuInscription',
+        'supannEtuRegimeInscription',
+        'supannEtuSecteurDisciplinaire',
+        'supannEtuTypeDiplome',
+        'supannListeRouge',
+        'supannMailPerso',
+        'supannParrainDN',
+        'supannRoleGenerique',
+        'supannRoleEntite',
+        'supannRefId',
+        'supannTypeEntiteAffectation',
+        'telephoneNumber',
+        'title',
+        'ucbnAnneePostBac',
+        'ucbnCodeEtape',
+        'ucbnEtuComplementInscription',
+        'ucbnFonctionStructurelle',
+        'ucbnOrganisme',
+        'ucbnPrivateAddress',
+        'ucbnPrivateAddressBis',
+        'ucbnSecteurDisciplinaire',
+        'ucbnServiceADE',
+        'ucbnServicePapercut',
+        'ucbnSiteLocalisation',
+        'ucbnSousStructure',
+        'ucbnSquidHash',
+        'ucbnStatus',
+        'ucbnStructureRecherche',
+        'uidNumber',
+        'unicaenMonEtupass',
+        'unicaenTermsOfUse',
+        'userCertificate',
+        'userPassword',
+    ];
+
+    /**
+     * Liste des attributs contenant des dates
+     *
+     * @var array
+     */
+    protected $dateTimeAttributes = [
+        'schacExpiryDate',
+    ];
+
+    /**
+     * Liste des attributs monovalués
+     *
+     * @var array
+     */
+    protected $monoValuedAttributes = [
+        'cn',
+        'dateDeNaissance',
+        'dateFinInscription',
+        'displayName',
+        'eduPersonOrgDN',
+        'eduPersonPrimaryAffiliation',
+        'eduPersonPrimaryOrgUnitDN',
+        'eduPersonPrincipalName',
+        'gidNumber',
+        'givenName',
+        'homeDirectory',
+        'leoCode',
+        'loginShell',
+        'ntPassword',
+        'preferredLanguage',
+        'rid',
+        'sambaNTPassword',
+        'sambaSID',
+        'schacDateOfBirth',
+        'schacExpiryDate',
+        'sexe',
+        'supannAliasLogin',
+        'supannCivilite',
+        'supannCodeINE',
+        'supannEmpId',
+        'supannEntiteAffectationPrincipale',
+        'supannEtuId',
+        'supannListeRouge',
+        'telephoneNumber',
+        'ucbnServiceADE',
+        'ucbnServicePapercut',
+        'ucbnSquidHash',
+        'uidNumber',
+        'userPassword',
+    ];
+
+    /**
+     * Liste des valeurs autorisées pour les attributs "eduPersonAffiliation" et "eduPersonPrimaryAffiliation"
+     *
+     * - affiliate : personne qui ne dépend pas de l'établissement (exemple : un partenaire extérieur). La valeur
+     * "member" est exclue pour ces personnes.
+     * - alum : ancien étudiant conservant des relations avec l'établissement.
+     * - emeritus :  professeur ayant obtenu l'éméritat dans l'établissement.
+     * - employee : tout personnel rémunéré par l'établissement, quelque soit son activité.
+     * - faculty : personnel dont l'activité principale (dans l'établissement) est pédagogique, d'enseignement ou/et de
+     * recherche. La valeur “member” est positionnée si ce personnel est géré par l'établissement.
+     * - member : personne inscrite dans la (les) base(s) de gestion des étudiants ou celle(s) des personnels.
+     * - registered-reader : lecteur de bibliothèque autorisé.
+     * - researcher : personne assurant une activité de recherche. La valeur “member” est positionnée si ce personnel
+     * est géré par l'établissement.
+     * - retired :  personne à la retraite conservant des relations avec l'établissement.
+     * - staff : personnel dont l'activité principale (dans l'établissement) est autre qu'enseignant ou chercheur
+     * (typiquement BIATOS).
+     * - student : personne suivant une formation quelconque dans l'établissement. Si la valeur “member” est positionnée,
+     * la personne est enregistrée dans la base des étudiants.
+     * - teacher : personnel assurant une activité d'enseignement.
+     *
+     * @var array
+     */
+    protected $eduPersonAffiliationAuthorizedValues = [
+        self::STATUS_AFFILIATE,
+        self::STATUS_ALUM,
+        self::STATUS_EMERITUS,
+        self::STATUS_EMPLOYEE,
+        self::STATUS_FACULTY,
+        self::STATUS_MEMBER,
+        self::STATUS_REGISTERED_READER,
+        self::STATUS_RESEARCHER,
+        self::STATUS_RETIRED,
+        self::STATUS_STAFF,
+        self::STATUS_STUDENT,
+        self::STATUS_TEACHER,
+    ];
+
+    /**
+     * Liste des valeurs autorisées pour l'attribut "ucbnStatus"
+     *
+     * @var array
+     */
+    protected $ucbnStatusAuthorizedValues = [
+        'ALUMNI',
+        'APPRENANT',
+        'AUDITEUR_LIBRE',
+        'CONTRACTUEL',
+        'ETUDIANT',
+        'HEBERGE',
+        'INVITE',
+        'LECTEUR_SCD',
+        'TITULAIRE',
+    ];
+
+    /**
+     * Liste des patterns spécifiques utilisés pour différents attributs
+     */
+    static protected $structure_pattern = '/^(?<code>[\w\-]+);(?<libelle>.+)$/';
+    static protected $localisation_pattern = '/^(?<code>[\w\-]+);(?<libelle>.+)$/';
+    static protected $etape_pattern = '/^(?<code>.+);(?<libelle>.+)$/';
+    static protected $secteur_disciplinaire_pattern = '/^(?<code>\d+);(?<libelle>.+)$/';
+    static protected $inscription_pattern =
+        '/^\[etab=(?<etab>\{[\w\-:]+\}.+)\]' .
+        '\[anneeinsc=(?<anneeinsc>\d{4})\]' .
+        '\[regimeinsc=(?<regimeinsc>\{[\w\-:]+\}\w*)\]' . // {SISE}.* ou {INCONNU}
+        '\[sectdisc=(?<sectdisc>\{[\w\-:]+\}\w*)\]' . // {SISE}.* ou {INCONNU}
+        '\[typedip=(?<typedip>\{[\w\-:]+\}\w*)\]' . // {SISE}.* ou {INCONNU}
+        '\[cursusann=(?<cursusann>\{SUPANN\}\w+)\]' .
+        '\[affect=(?<affect>[\w\-]+)\]' .
+        '\[diplome=(?<diplome>\{[\w\-:]+\}\w+)\]' .
+        '\[etape=(?<etape>\{[\w\-:]+\}.+)\]$/';
+    static protected $inscription_complement_pattern = 
+        '/^\[anneeinsc=(?<anneeinsc>\d{4})\]' .
+        '\[etape=(?<etape>\{[\w\-:]+\}.+)\]' .
+        '\[adistance=(?<adistance>\{[\w\-:]+\}\w{1})\]$/';
+    static protected $role_pattern =
+        '/^\[role=(?<role>\{SUPANN\}[\w\-]+)\]' .
+        '\[type=(?<type>\{SUPANN\}[\w\-]+)\]' .
+        '\[code=(?<code>[\w\-]+)\]' .
+        '\[libelle=(?<libelle>.+)\]$/';
+    static protected $role_src_pattern = '/^(?<code>\d{4});(?<libelle>.+)$/';
+
+
+    /**
+     * Attribut Ldap "sn"
+     *
+     * Nom d'un individu
+     * Doit contenir le nom d'usage. Il est possible d'ajouter le nom de famille en seconde valeur.
+     *
+     * Multivalué
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSn($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map([$this, 'formatName'], $value);
+        $this->appendOrNot('sn', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "givenName"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setGivenName($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map([$this, 'formatName'], $value);
+        $this->appendOrNot('givenName', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "displayName"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setDisplayName($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map([$this, 'formatName'], $value);
+        $this->appendOrNot('displayName', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "cn"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setCn($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map([$this, 'formatName'], $value, [true]);
+        $this->appendOrNot('cn', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "dateDeNaissance"
+     *
+     * @param array|string|DateTime|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     * @deprecated Deprecated, use {@link self::setSchacDateOfBirth}
+     *
+     */
+    public function setDateDeNaissance($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map(function ($val) {
+            if (is_string($val)) {
+                $val = new DateTime($val);
+            }
+            return $val->format('Ymd');
+        }, $value);
+
+        $this->appendOrNot('dateDeNaissance', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "schacDateOfBirth"
+     *
+     * @param array|string|DateTime|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSchacDateOfBirth($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map(function ($val) {
+            if (is_string($val)) {
+                $val = new DateTime($val);
+            }
+            return $val->format('Ymd');
+        }, $value);
+
+        $this->appendOrNot('schacDateOfBirth', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "dateFinInscription"
+     *
+     * @param array|string|DateTime|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     * @deprecated Deprecated, use {@link self::setSchacExpiryDate}
+     *
+     */
+    public function setDateFinInscription($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map(function ($val) {
+            if (is_string($val)) {
+                $val = new DateTime($val);
+            }
+            return $val->format('Ymd');
+        }, $value);
+
+        $this->appendOrNot('dateFinInscription', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "schacDateOfBirth"
+     *
+     * @param array|string|DateTime|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSchacExpiryDate($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map(function ($val) {
+            if (is_string($val)) {
+                $val = new DateTime($val, new \DateTimeZone('+0000')); // définition du timezone à +0h
+            }
+            return (int)$val->format('U');
+        }, $value);
+
+        $this->appendOrNot('schacExpiryDate', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "sexe"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSexe($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter($value, function ($v) {
+            return in_array($v, ['F', 'M']);
+        });
+
+        $this->appendOrNot('sexe', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "eduPersonAffiliation"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setEduPersonAffiliation($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter($value, function ($v) {
+            return in_array($v, $this->eduPersonAffiliationAuthorizedValues);
+        });
+
+        $this->appendOrNot('eduPersonAffiliation', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "eduPersonPrimaryAffiliation"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setEduPersonPrimaryAffiliation($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter($value, function ($v) {
+            return in_array($v, $this->eduPersonAffiliationAuthorizedValues);
+        });
+
+        $this->appendOrNot('eduPersonPrimaryAffiliation', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "postalAddress"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setPostalAddress($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter($value, function ($v) {
+            return preg_match(self::$postal_address_pattern, $v);
+        });
+
+        $this->appendOrNot('postalAddress', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "telephoneNumber"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setTelephoneNumber($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map([$this, 'formatTel'], $value);
+        $this->appendOrNot('telephoneNumber', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannAutreTelephone"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannAutreTelephone($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map([$this, 'formatTel'], $value);
+        $this->appendOrNot('supannAutreTelephone', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "mail"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setMail($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter(filter_var_array($value, FILTER_VALIDATE_EMAIL));
+        $this->appendOrNot('mail', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannAutreMail"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannAutreMail($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter(filter_var_array($value, FILTER_VALIDATE_EMAIL));
+        $this->appendOrNot('supannAutreMail', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "mailForwardingAddress"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setMailForwardingAddress($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter(filter_var_array($value, FILTER_VALIDATE_EMAIL));
+        $this->appendOrNot('mailForwardingAddress', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "mobile"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setMobile($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map([$this, 'formatTel'], $value);
+        $this->appendOrNot('mobile', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "pager"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setPager($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map([$this, 'formatTel'], $value);
+        $this->appendOrNot('pager', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "preferredLanguage"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setPreferredLanguage($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map('strtolower', $value);
+        $this->appendOrNot('preferredLanguage', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "leoCode"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setLeoCode($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter($value, function ($v) {
+            return preg_match('/^\d{13}$/', $v);
+        });
+
+        $this->appendOrNot('leoCode', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannALiasLogin"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannAliasLogin($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map('strtolower', $value);
+        $value = array_filter($value, function ($v) {
+            return preg_match('/^[0-9a-z\-]+$/', $v);
+        });
+
+        $this->appendOrNot('supannAliasLogin', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannEmpId"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannEmpId($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $this->appendOrNot('supannEmpId', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannEtuId"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannEtuId($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $this->appendOrNot('supannEtuId', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannEtuInscription"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannEtuInscription($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+
+        $supannLabel = $this->getLabel('SUPANN');
+        $siseLabel = $this->getLabel('SISE');
+        $inconnuLabel = $this->getLabel('INCONNU');
+        $value = array_map(function ($v) use ($supannLabel, $siseLabel, $inconnuLabel) {
+            if (preg_match(self::$inscription_pattern, $v, $matches)) {
+                /**
+                 * @var string $etab
+                 * @var string $anneeinsc
+                 * @var string $regimeinsc
+                 * @var string $sectdisc
+                 * @var string $typedip
+                 * @var string $cursusann
+                 * @var string $affect
+                 * @var string $diplome
+                 * @var string $etape
+                 */
+                foreach (['regimeinsc', 'sectdisc', 'typedip'] as $part) {
+                    $$part = preg_match("/^($siseLabel|$inconnuLabel)\w*$/", $matches[$part])
+                        ? $matches[$part]
+                        : sprintf('%s%s', $siseLabel, $matches[$part]);
+                }
+                foreach (['cursusann'] as $part) {
+                    $$part = preg_match("/^$supannLabel\w+$/", $matches[$part])
+                        ? $matches[$part]
+                        : sprintf('%s%s', $supannLabel, $matches[$part]);
+                }
+
+                $prefixe = $this->service->getLdapStructureService()->getCodeStructurePrefixe();
+                $affect = (0 !== strpos($matches['affect'], $prefixe))
+                    ? sprintf('%s%s', $structure_prefixe, $matches['affect'])
+                    : $matches['affect'];
+
+                $prefixe = $this->service->getLdapStructureService()->getCodeModuleEnseignementPrefixe();
+                preg_match('/^(?<etiquette>\{[\w\-:]+\})(?<identifiant>.+)$/', $matches['etape'], $parts);
+                $val = (0 !== strpos($parts['identifiant'], $prefixe))
+                    ? $prefixe . $parts['identifiant']
+                    : $parts['identifiant'];
+                $etape = sprintf('%s%s', $parts['etiquette'], $val);
+
+                return preg_replace(
+                    self::$inscription_pattern,
+                    "[etab=\\1][anneeinsc=\\2][regimeinsc=$regimeinsc][sectdisc=$sectdisc][typedip=$typedip][cursusann=$cursusann][affect=$affect][diplome=\\8][etape=$etape]",
+                    $v, 1);
+            } else {
+                return null;
+            }
+
+        }, $value);
+
+        $this->appendOrNot('supannEtuInscription', array_filter($value), $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannCodeINE"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannCodeINE($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map('strtoupper', $value);
+        $value = array_filter($value, function ($v) {
+            return preg_match('/^[\w]{11}$/', $v);
+        });
+
+        $this->appendOrNot('supannCodeINE', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannCivilite"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannCivilite($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter($value, function ($v) {
+            return in_array($v, ['Mme', 'M.']);
+        });
+
+        $this->appendOrNot('supannCivilite', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannListeRouge"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannListeRouge($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map([$this, 'formatBoolean'], $value);
+        $this->appendOrNot('supannListeRouge', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannMailPerso"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannMailPerso($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter(filter_var_array($value, FILTER_VALIDATE_EMAIL));
+        $this->appendOrNot('supannMailPerso', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannAffectation"
+     *
+     * @param array|string|StructureEntity|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannAffectation($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map(function ($val) {
+            if (is_string($val)) {
+                return preg_match(self::$structure_pattern, $val) ? $val : null;
+            } elseif ($val instanceof StructureEntity) {
+                $val = sprintf('%s;%s', strtr($val->get('supannCodeEntite'), $this->service->getLdapStructureService()->getCodeStructurePrefixe(), ''), $val->get('description'));
+                return preg_match(self::$structure_pattern, $val) ? $val : null;
+            } else {
+                return null;
+            }
+        }, $value);
+
+        $this->appendOrNot('supannAffectation', array_filter($value), $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannEntiteAffectation"
+     *
+     * @param array|string|StructureEntity|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannEntiteAffectation($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map(function ($val) {
+            if (is_string($val)) {
+                if (0 !== strpos($val, $this->service->getLdapStructureService()->getCodeStructurePrefixe())) {
+                    $val = $this->service->getLdapStructureService()->getCodeStructurePrefixe() . $val;
+                }
+                return $val;
+            } elseif ($val instanceof StructureEntity) {
+                return $val->get('supannCodeEntite');
+            } else {
+                return null;
+            }
+        }, $value);
+
+        $this->appendOrNot('supannEntiteAffectation', array_filter($value), $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannEntiteAffectationPrincipale"
+     *
+     * @param array|string|StructureEntity|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannEntiteAffectationPrincipale($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map(function ($val) {
+            if (is_string($val)) {
+                if (0 !== strpos($val, $this->service->getLdapStructureService()->getCodeStructurePrefixe())) {
+                    $val = $this->service->getLdapStructureService()->getCodeStructurePrefixe() . $val;
+                }
+                return $val;
+            } elseif ($val instanceof StructureEntity) {
+                return $val->get('supannCodeEntite');
+            } else {
+                return null;
+            }
+        }, $value);
+
+        $this->appendOrNot('supannEntiteAffectationPrincipale', array_filter($value), $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannTypeEntiteAffectation"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannTypeEntiteAffectation($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $supannLabel = $this->getLabel('SUPANN');
+        $value = array_map(function ($val) use ($supannLabel) {
+            if (is_string($val)) {
+                if (preg_match("/^" . $supannLabel . "[\w\-]+$/", $val)) {
+                    return $val;
+                } elseif (preg_match("/^[\w\-]+$/", $val)) {
+                    return sprintf('%s%s', $supannLabel, $val);
+                }
+            }
+            return null;
+        }, $value);
+
+        $this->appendOrNot('supannTypeEntiteAffectation', array_filter($value), $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "ucbnSiteLocalisation"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setUcbnSiteLocalisation($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter($value, function ($v) {
+            return preg_match(self::$localisation_pattern, $v);
+        });
+
+        $this->appendOrNot('ucbnSiteLocalisation', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "ucbnSousStructure"
+     *
+     * @param array|string|StructureEntity|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setUcbnSousStructure($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map(function ($val) {
+            if (is_string($val)) {
+                return preg_match(self::$structure_pattern, $val) ? $val : null;
+            } elseif ($val instanceof StructureEntity) {
+                $val = sprintf('%s;%s', strtr($val->get('supannCodeEntite'), $this->service->getLdapStructureService()->getCodeStructurePrefixe(), ''), $val->get('description'));
+                return preg_match(self::$structure_pattern, $val) ? $val : null;
+            } else {
+                return null;
+            }
+        }, $value);
+
+        $this->appendOrNot('ucbnSousStructure', array_filter($value), $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "ucbnStructureRecherche"
+     *
+     * @param array|string|StructureEntity|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setUcbnStructureRecherche($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map(function ($val) {
+            if (is_string($val)) {
+                return preg_match(self::$structure_pattern, $val) ? $val : null;
+            } elseif ($val instanceof StructureEntity) {
+                $val = sprintf('%s;%s', strtr($val->get('supannCodeEntite'), $this->service->getLdapStructureService()->getCodeStructurePrefixe(), ''), $val->get('description'));
+                return preg_match(self::$structure_pattern, $val) ? $val : null;
+            } else {
+                return null;
+            }
+        }, $value);
+
+        $this->appendOrNot('ucbnStructureRecherche', array_filter($value), $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "eduPersonPrincipalName"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setEduPersonPrincipalName($value = null, $append = false)
+    {
+        $universiteDomaine = $this->service->getLdapRootService()->getEtablissementDomain();
+        $value = $this->preFormat($value);
+        $value = array_map(function ($v) use ($universiteDomaine) {
+            list($identifiant, $domaine) = array_pad(explode('@', strtolower($v)), 2, null);
+            if ($domaine != $universiteDomaine) {
+                $v = sprintf('%s@%s', $v, $universiteDomaine);
+            }
+            return $v;
+        }, $value);
+        $value = array_filter($value, function ($v) use ($universiteDomaine) {
+            return preg_match('/^[0-9a-z\-]+@' . $universiteDomaine . '$/', $v);
+        });
+
+        $this->appendOrNot('eduPersonPrincipalName', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "eduPersonOrgDN"
+     *
+     * @param array|string|Dn|StructureEntity|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setEduPersonOrgDN($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map(function ($val) {
+            if (is_string($val)) {
+                return Dn::checkDn($val) ? $val : null;
+            } elseif ($val instanceof Dn) {
+                return $val->toString();
+            } elseif ($val instanceof StructureEntity) {
+                return $val->getDn();
+            } else {
+                return null;
+            }
+        }, $value);
+
+        $this->appendOrNot('eduPersonOrgDN', array_filter($value), $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "eduPersonOrgUnitDN"
+     *
+     * @param array|string|Dn|StructureEntity|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setEduPersonOrgUnitDN($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map(function ($val) {
+            if (is_string($val)) {
+                return Dn::checkDn($val) ? $val : null;
+            } elseif ($val instanceof Dn) {
+                return $val->toString();
+            } elseif ($val instanceof StructureEntity) {
+                return $val->getDn();
+            } else {
+                return null;
+            }
+        }, $value);
+
+        $this->appendOrNot('eduPersonOrgUnitDN', array_filter($value), $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "eduPersonPrimaryOrgUnitDN"
+     *
+     * @param array|string|Dn|StructureEntity|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setEduPersonPrimaryOrgUnitDN($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map(function ($val) {
+            if (is_string($val)) {
+                return Dn::checkDn($val) ? $val : null;
+            } elseif ($val instanceof Dn) {
+                return $val->toString();
+            } elseif ($val instanceof StructureEntity) {
+                return $val->getDn();
+            } else {
+                return null;
+            }
+        }, $value);
+
+        $this->appendOrNot('eduPersonPrimaryOrgUnitDN', array_filter($value), $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannRoleGenerique"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannRoleGenerique($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $supannLabel = $this->getLabel('SUPANN');
+        $value = array_map(function ($val) use ($supannLabel) {
+            if (is_string($val)) {
+                if (preg_match("/^" . $supannLabel . "[\w\-]+$/", $val)) {
+                    return $val;
+                } elseif (preg_match("/^[\w\-]+$/", $val)) {
+                    return sprintf('%s%s', $supannLabel, $val);
+                }
+            }
+            return null;
+        }, $value);
+
+        $this->appendOrNot('supannRoleGenerique', array_filter($value), $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannRoleEntite"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannRoleEntite($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+
+        $supannLabel = $this->getLabel('SUPANN');
+        $value = array_map(function ($v) use ($supannLabel) {
+            if (preg_match(self::$role_pattern, $v, $matches)) {
+                /**
+                 * @var string $role
+                 * @var string $type
+                 * @var string $code
+                 */
+                foreach (['role', 'type'] as $part) {
+                    $$part = preg_match("/^" . $supannLabel . "[\w\-]+$/", $matches[$part])
+                        ? $matches[$part]
+                        : sprintf('%s%s', $supannLabel, $matches[$part]);
+                }
+
+                $prefixe = $this->service->getLdapStructureService()->getCodeStructurePrefixe();
+                $code = (0 !== strpos($matches['code'], $prefixe))
+                    ? sprintf('%s%s', $prefixe, $matches['code'])
+                    : $matches['code'];
+
+                return preg_replace(self::$role_pattern, "[role=$role][type=$type][code=$code][libelle=\\4]", $v, 1);
+            } else {
+                return null;
+            }
+
+        }, $value);
+
+        $this->appendOrNot('supannRoleEntite', array_filter($value), $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "ucbnAnneePostBac"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return $this
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setUcbnAnneePostBac($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter($value, function ($v) {
+            return preg_match('/^\d{1}$/', $v);
+        });
+
+        $this->appendOrNot('ucbnAnneePostBac', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "ucbnCodeEtape"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setUcbnCodeEtape($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter($value, function ($v) {
+            return preg_match(self::$etape_pattern, $v);
+        });
+
+        $this->appendOrNot('ucbnCodeEtape', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "ucbnEtuComplementInscription"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setUcbnEtuComplementInscription($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+
+        $value = array_map(function ($v) {
+            if (preg_match(self::$inscription_complement_pattern, $v, $matches)) {
+                /**
+                 * @var string $anneeinsc
+                 * @var string $etape
+                 * @var string $adistance
+                 */
+                $prefixe = $this->service->getLdapStructureService()->getCodeModuleEnseignementPrefixe();
+                preg_match('/^(?<etiquette>\{[\w\-:]+\})(?<identifiant>.+)$/', $matches['etape'], $parts);
+                $val = (0 !== strpos($parts['identifiant'], $prefixe))
+                    ? $prefixe . $parts['identifiant']
+                    : $parts['identifiant'];
+                $etape = sprintf('%s%s', $parts['etiquette'], $val);
+
+                return preg_replace(
+                    self::$inscription_pattern,
+                    "[anneeinsc=\\1][etape=$etape][adistance=\\3]",
+                    $v, 1);
+            } else {
+                return null;
+            }
+
+        }, $value);
+
+        $this->appendOrNot('ucbnEtuComplementInscription', array_filter($value), $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "ucbnFonctionStructurelle"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setUcbnFonctionStructurelle($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter($value, function ($v) {
+            return preg_match(self::$role_src_pattern, $v);
+
+        });
+
+        $this->appendOrNot('ucbnFonctionStructurelle', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "ucbnPrivateAddress"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setUcbnPrivateAddress($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter($value, function ($v) {
+            return preg_match(self::$postal_address_pattern, $v);
+        });
+
+        $this->appendOrNot('ucbnPrivateAddress', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "ucbnPrivateAddressBis"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setUcbnPrivateAddressBis($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter($value, function ($v) {
+            return preg_match(self::$postal_address_pattern, $v);
+        });
+
+        $this->appendOrNot('ucbnPrivateAddressBis', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannParrainDN"
+     *
+     * @param array|string|Dn|PeopleEntity|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannParrainDN($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map(function ($val) {
+            if (is_string($val)) {
+                return Dn::checkDn($val) ? $val : null;
+            } elseif ($val instanceof Dn) {
+                return $val->toString();
+            } elseif ($val instanceof PeopleEntity) {
+                return $val->getDn();
+            } else {
+                return null;
+            }
+        }, $value);
+
+        $this->appendOrNot('supannParrainDN', array_filter($value), $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannRefId"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannRefId($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+
+        foreach ($value as $v) {
+            if (preg_match(self::$attribute_with_label_pattern, $v, $matches)) {
+                if ($append) {
+                    $this->appendOrNot('supannRefId', $v, true);
+                } else {
+                    $label = $matches['etiquette'];
+                    $identifiant = $matches['identifiant'];
+                    $currentValues = $this->preFormat($this->supannRefId);
+                    array_walk($currentValues, function (&$cv) use ($label, $identifiant) {
+                        if (preg_match("/^".$label."(?<identifiant>.+)$/", $cv, $matches)) {
+                            if($matches['identifiant'] != $identifiant) {
+                                $this->remove('supannRefId', $cv);
+                                $cv = null;
+                            }
+                        }
+                    });
+                    if(!in_array($v, $currentValues)) {
+                        $currentValues[] = $v;
+                    }
+                    $this->appendOrNot('supannRefId', array_filter($currentValues), false);
+                }
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "ucbnStatus"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return $this
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setUcbnStatus($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter($value, function ($v) {
+            return in_array($v, $this->ucbnStatusAuthorizedValues);
+        });
+
+        $this->appendOrNot('ucbnStatus', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "unicaenTermsOfUse"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return $this
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setUnicaenTermsOfUse($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $this->appendOrNot('unicaenTermsOfUse', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannActivite"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return $this
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannActivite($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter($value, function ($v) {
+            return preg_match(self::$attribute_with_label_pattern, $v);
+        });
+
+        $this->appendOrNot('supannActivite', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "ucbnSecteurDisciplinaire"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return $this
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setUcbnSecteurDisciplinaire($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter($value, function ($v) {
+            return preg_match(self::$secteur_disciplinaire_pattern, $v);
+
+        });
+
+        $this->appendOrNot('ucbnSecteurDisciplinaire', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannEmpCorps"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return $this
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannEmpCorps($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $corpsLabel = $this->getLabel('NCORPS');
+        $value = array_map(function ($val) use ($corpsLabel) {
+            if (is_string($val)) {
+                if (preg_match("/^$corpsLabel\w+$/", $val)) {
+                    return $val;
+                } elseif (preg_match("/^\w+$/", $val)) {
+                    return sprintf('%s%s', $corpsLabel, $val);
+                }
+            }
+            return null;
+        }, $value);
+
+        $this->appendOrNot('supannEmpCorps', array_filter($value), $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannEtablissement"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return $this
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannEtablissement($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter($value, function ($v) {
+            return preg_match(self::$attribute_with_label_pattern, $v);
+        });
+
+        $this->appendOrNot('supannEtablissement', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannEtuAnneeInscription"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return $this
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannEtuAnneeInscription($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter($value, function ($v) {
+            return preg_match('/^\d{4}$/', $v);
+        });
+
+        $this->appendOrNot('supannEtuAnneeInscription', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannEtuCursusAnnee"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return $this
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannEtuCursusAnnee($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $supannLabel = $this->getLabel('SUPANN');
+        $value = array_map(function ($val) use ($supannLabel) {
+            if (is_string($val)) {
+                if (preg_match("/^$supannLabel\w+$/", $val)) {
+                    return $val;
+                } elseif (preg_match("/^\w+$/", $val)) {
+                    return sprintf('%s%s', $supannLabel, $val);
+                }
+            }
+            return null;
+        }, $value);
+
+        $this->appendOrNot('supannEtuCursusAnnee', array_filter($value), $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannEtuDiplome"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return $this
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannEtuDiplome($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter($value, function ($v) {
+            return preg_match(self::$attribute_with_label_pattern, $v);
+        });
+
+        $this->appendOrNot('supannEtuDiplome', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannEtuEtape"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return $this
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannEtuEtape($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map(function ($val) {
+            if (is_string($val)) {
+                if (preg_match(self::$attribute_with_label_pattern, $val, $matches)) {
+                    $prefixe = $this->service->getLdapStructureService()->getCodeModuleEnseignementPrefixe();
+                    $val = (0 !== strpos($matches['identifiant'], $prefixe))
+                        ? $prefixe . $matches['identifiant']
+                        : $matches['identifiant'];
+                    return sprintf('%s%s', $matches['etiquette'], $val);
+                }
+                return null;
+            }
+            return null;
+        }, $value);
+
+        $this->appendOrNot('supannEtuEtape', array_filter($value), $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannEtuElementPedagogique"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return $this
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannEtuElementPedagogique($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map(function ($val) {
+            if (is_string($val)) {
+                if (preg_match(self::$attribute_with_label_pattern, $val, $matches)) {
+                    $prefixe = $this->service->getLdapStructureService()->getCodeModuleEnseignementPrefixe();
+                    $val = (0 !== strpos($matches['identifiant'], $prefixe))
+                        ? $prefixe . $matches['identifiant']
+                        : $matches['identifiant'];
+                    return sprintf('%s%s', $matches['etiquette'], $val);
+                }
+                return null;
+            }
+            return null;
+        }, $value);
+
+        $this->appendOrNot('supannEtuElementPedagogique', array_filter($value), $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannEtuRegimeInscription"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return $this
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannEtuRegimeInscription($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $siseLabel = $this->getLabel('SISE');
+        $value = array_map(function ($val) use ($siseLabel) {
+            if (is_string($val)) {
+                if (preg_match("/^$siseLabel\w+$/", $val)) {
+                    return $val;
+                } elseif (preg_match("/^\w+$/", $val)) {
+                    return sprintf('%s%s', $siseLabel, $val);
+                }
+            }
+            return null;
+        }, $value);
+
+        $this->appendOrNot('supannEtuRegimeInscription', array_filter($value), $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannEtuSecteurDisciplinaire"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return $this
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannEtuSecteurDisciplinaire($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $siseLabel = $this->getLabel('SISE');
+        $value = array_map(function ($val) use ($siseLabel) {
+            if (is_string($val)) {
+                if (preg_match("/^$siseLabel\w+$/", $val)) {
+                    return $val;
+                } elseif (preg_match("/^\w+$/", $val)) {
+                    return sprintf('%s%s', $siseLabel, $val);
+                }
+            }
+            return null;
+        }, $value);
+
+        $this->appendOrNot('supannEtuSecteurDisciplinaire', array_filter($value), $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannEtuTypeDiplome"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return $this
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannEtuTypeDiplome($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $siseLabel = $this->getLabel('SISE');
+        $value = array_map(function ($val) use ($siseLabel) {
+            if (is_string($val)) {
+                if (preg_match("/^$siseLabel\w+$/", $val)) {
+                    return $val;
+                } elseif (preg_match("/^\w+$/", $val)) {
+                    return sprintf('%s%s', $siseLabel, $val);
+                }
+            }
+            return null;
+        }, $value);
+
+        $this->appendOrNot('supannEtuTypeDiplome', array_filter($value), $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "rid"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setRid($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter($value, function ($v) {
+            return preg_match('/^\d+$/', $v);
+        });
+        $this->appendOrNot('rid', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "sambaSID"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSambaSID($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $this->appendOrNot('sambaSID', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "uidNumber"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setUidNumber($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter($value, function ($v) {
+            return preg_match('/^\d+$/', $v);
+        });
+        $this->appendOrNot('uidNumber', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "gidNumber"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setGidNumber($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter($value, function ($v) {
+            return preg_match('/^\d+$/', $v);
+        });
+        $this->appendOrNot('gidNumber', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "loginShell"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setLoginShell($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $this->appendOrNot('loginShell', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "homeDirectory"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setHomeDirectory($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $this->appendOrNot('homeDirectory', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "userPassword"
+     *
+     * @param string $value
+     * @return self
+     * @throws LdapException
+     */
+    public function setUserPassword(string $value)
+    {
+        $this->getNode()->setPasswordAttribute($value, Attribute::PASSWORD_HASH_SHA, 'userPassword');
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "ntPassword"
+     *
+     * @param string $value
+     * @return $this
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setNtPassword(string $value)
+    {
+        $uni = '';
+        $value = (string)$value;
+        for ($i = 0; $i < strlen($value); $i++) {
+            $a = ord($value{$i}) << 8;
+            $uni .= sprintf('%X', $a);
+        }
+        $nthash = hash("md4", pack('H*', $uni), true);
+        $this->appendOrNot('ntPassword', strtoupper(bin2hex($nthash)), false);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "sambaNTPassword"
+     * @param string $value
+     * @return $this
+     * @throws Exception
+     * @throws LdapException
+     * @see setNtPassword
+     *
+     */
+    public function setSambaNTPassword(string $value)
+    {
+        $uni = '';
+        $value = (string)$value;
+        for ($i = 0; $i < strlen($value); $i++) {
+            $a = ord($value{$i}) << 8;
+            $uni .= sprintf('%X', $a);
+        }
+        $nthash = hash("md4", pack('H*', $uni), true);
+        $this->appendOrNot('sambaNTPassword', strtoupper(bin2hex($nthash)), false);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "ucbnSquidHash"
+     *
+     * @param string $value
+     * @param bool $append
+     * @return $this
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setUcbnSquidHash(string $value)
+    {
+        $value = "Unicaen:" . md5(sprintf('%s:Unicaen:%s', $this->supannAliasLogin, $value));
+        $this->appendOrNot('ucbnSquidHash', $value, false);
+
+        return $this;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenLdap/Entity/Base/Root.php b/src/UnicaenLdap/Entity/Base/Root.php
new file mode 100644
index 0000000000000000000000000000000000000000..b5b3264181ef407922cecf86044446eec125dcf1
--- /dev/null
+++ b/src/UnicaenLdap/Entity/Base/Root.php
@@ -0,0 +1,271 @@
+<?php
+
+namespace UnicaenLdap\Entity\Base;
+
+use UnicaenLdap\Entity\Entity;
+use UnicaenLdap\Entity\Structure as StructureEntity;
+use UnicaenLdap\Exception;
+use Zend\Ldap\Exception\LdapException;
+
+/**
+ * Classe mère de l'entité racine de l'annuaire LDAP.
+ *
+ * @author David Surville <david.surville@unicaen.fr>
+ */
+class Root extends Entity
+{
+    protected $type = 'Root';
+
+
+    /**
+     * Liste des classes d'objet nécessaires à la création d'une structure
+     *
+     * @var string[]
+     */
+    protected $objectClass = [
+        'top',
+        'dcObject',
+        'organization',
+        'eduOrg',
+        'supannOrg',
+    ];
+
+    /**
+     * Liste des attributs autorisés pour l'entité "Root"
+     *
+     * @var array
+     */
+    protected $authorizedAttributes = [
+        // Attributes classes
+        'objectClass',
+        // Attributes
+        'description',
+        'eduOrgHomePageURI',
+        'eduOrgLegalName',
+        'eduOrgSuperiorURI',
+        'eduOrgWhitePagesURI',
+        'facsimileTelephoneNumber',
+        'l',
+        'o',
+        'postalAddress',
+        'supannEtablissement',
+        'telephoneNumber',
+    ];
+
+    /**
+     * Liste des attributs contenant des dates
+     *
+     * @var array
+     */
+    protected $dateTimeAttributes = [];
+
+    /**
+     * Liste des attributs monovalués
+     *
+     * @var array
+     */
+    protected $monoValuedAttributes = [
+        'eduOrgLegalName',
+    ];
+
+    /**
+     * Attribut Ldap "description"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setDescription($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $this->appendOrNot('description', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "eduOrgHomePageURI"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setEduOrgHomePageURI($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter(filter_var_array($value, FILTER_VALIDATE_URL));
+        $this->appendOrNot('eduOrgHomePageURI', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "eduOrgLegalName"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setEduOrgLegalName($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $this->appendOrNot('eduOrgLegalName', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "eduOrgSuperiorURI"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setEduOrgSuperiorURI($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter(filter_var_array($value, FILTER_VALIDATE_URL));
+        $this->appendOrNot('eduOrgSuperiorURI', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "eduOrgWhitePagesURI"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setEduOrgWhitePagesURI($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter(filter_var_array($value, FILTER_VALIDATE_URL));
+        $this->appendOrNot('eduOrgWhitePagesURI', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "facsimileTelephoneNumber"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setFacsimileTelephoneNumber($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map([$this, 'formatTel'], $value);
+        $this->appendOrNot('facsimileTelephoneNumber', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "l"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setL($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $this->appendOrNot('l', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "o"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setO($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $this->appendOrNot('o', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "postalAddress"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setPostalAddress($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter($value, function ($v) {
+            return preg_match(self::$postal_address_pattern, $v);
+        });
+
+        $this->appendOrNot('postalAddress', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannEtablissement"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return $this
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannEtablissement($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter($value, function ($v) {
+            return preg_match(self::$attribute_with_label_pattern, $v);
+        });
+
+        $this->appendOrNot('supannEtablissement', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "telephoneNumber"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setTelephoneNumber($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map([$this, 'formatTel'], $value);
+        $this->appendOrNot('telephoneNumber', $value, $append);
+
+        return $this;
+    }
+
+}
\ No newline at end of file
diff --git a/src/UnicaenLdap/Entity/Base/Structure.php b/src/UnicaenLdap/Entity/Base/Structure.php
new file mode 100644
index 0000000000000000000000000000000000000000..76007e2a5f22196d10c8fc190291dcf4e39cd7b6
--- /dev/null
+++ b/src/UnicaenLdap/Entity/Base/Structure.php
@@ -0,0 +1,232 @@
+<?php
+
+namespace UnicaenLdap\Entity\Base;
+
+use UnicaenLdap\Entity\Entity;
+use UnicaenLdap\Entity\Structure as StructureEntity;
+use UnicaenLdap\Exception;
+use Zend\Ldap\Exception\LdapException;
+
+/**
+ * Classe mère des entités de la branche "structures" de l'annuaire LDAP.
+ *
+ * @author David Surville <david.surville@unicaen.fr>
+ */
+class Structure extends Entity
+{
+    protected $type = 'Structure';
+
+    /**
+     * Liste des classes d'objet nécessaires à la création d'une structure
+     *
+     * @var string[]
+     */
+    protected $objectClass = [
+        'top',
+        'organizationalUnit',
+        'supannEntite',
+        'ucbnEntite',
+    ];
+
+    /**
+     * Liste des attributs autorisés pour une entité "People"
+     *
+     * @var array
+     */
+    protected $authorizedAttributes = [
+        // Attributes classes
+        'objectClass',
+        // Attributes
+        'description',
+        'facsimileTelephoneNumber',
+        'ou',
+        'postalAddress',
+        'supannCodeEntiteParent',
+        'supannRefId',
+        'supannTypeEntite',
+        'telephoneNumber',
+    ];
+
+    /**
+     * Liste des attributs contenant des dates
+     *
+     * @var array
+     */
+    protected $dateTimeAttributes = [];
+
+    /**
+     * Liste des attributs monovalués
+     *
+     * @var array
+     */
+    protected $monoValuedAttributes = [];
+
+
+    /**
+     * Attribut Ldap "description"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setDescription($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $this->appendOrNot('description', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "ou"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setOu($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $this->appendOrNot('ou', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannCodeEntiteParent"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannCodeEntiteParent($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map(function ($val) {
+            if (is_string($val)) {
+                if (0 !== strpos($val, $this->service->getCodeStructurePrefixe())) {
+                    $val = $this->service->getCodeStructurePrefixe() . $val;
+                }
+                return $val;
+            } elseif ($val instanceof StructureEntity) {
+                return $val->get('supannCodeEntite');
+            } else {
+                return null;
+            }
+        }, $value);
+
+        $this->appendOrNot('supannCodeEntiteParent', array_filter($value), $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannTypeEntite"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannTypeEntite($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $supannLabel = $this->getLabel('SUPANN');
+        $value = array_map(function ($val) use ($supannLabel) {
+            if (is_string($val)) {
+                return preg_match("/^$supannLabel.+$/", $val) ? $val : sprintf('%s%s', $supannLabel, $val);
+            } else {
+                return null;
+            }
+        }, $value);
+
+        $this->appendOrNot('supannTypeEntite', array_filter($value), $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "telephoneNumber"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setTelephoneNumber($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map([$this, 'formatTel'], $value);
+        $this->appendOrNot('telephoneNumber', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "facsimileTelephoneNumber"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setFacsimileTelephoneNumber($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_map([$this, 'formatTel'], $value);
+        $this->appendOrNot('facsimileTelephoneNumber', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "postalAddress"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setPostalAddress($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter($value, function ($v) {
+            return preg_match(self::$postal_address_pattern, $v);
+        });
+
+        $this->appendOrNot('postalAddress', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "supannRefId"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     * @throws Exception
+     * @throws LdapException
+     */
+    public function setSupannRefId($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $value = array_filter($value, function ($v) {
+            return preg_match(self::$attribute_with_label_pattern, $v);
+        });
+
+        $this->appendOrNot('supannRefId', $value, $append);
+
+        return $this;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenLdap/Entity/Base/System.php b/src/UnicaenLdap/Entity/Base/System.php
new file mode 100644
index 0000000000000000000000000000000000000000..4d282a8045a3496d0778167309e58573b37f8371
--- /dev/null
+++ b/src/UnicaenLdap/Entity/Base/System.php
@@ -0,0 +1,97 @@
+<?php
+
+namespace UnicaenLdap\Entity\Base;
+
+use UnicaenLdap\Entity\Entity;
+use Zend\Ldap\Attribute;
+use Zend\Ldap\Exception\LdapException;
+
+/**
+ * Classe mère des entités de la branche "system" de l'annuaire LDAP.
+ *
+ * @author David Surville <david.surville@unicaen.fr>
+ */
+class System extends Entity
+{
+    protected $type = 'System';
+
+    /**
+     * Liste des classes d'objet nécessaires à la création d'un compte système
+     *
+     * @var string[]
+     */
+    protected $objectClass = [
+        'top',
+        'inetOrgPerson',
+        'organizationalPerson',
+        'person',
+        'supannPerson',
+        'ucbnEmp',
+    ];
+
+    /**
+     * Liste des attributs autorisés pour une entité "Generic"
+     *
+     * @var array
+     */
+    protected $authorizedAttributes = [
+        // Attributes classes
+        'objectClass',
+        // Attributes
+        'cn',
+        'sn',
+        'userPassword',
+    ];
+
+    /**
+     * Liste des attributs contenant des dates
+     *
+     * @var string[]
+     */
+    protected $dateTimeAttributes = [
+    ];
+
+    /**
+     * Attribut Ldap "cn"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     */
+    public function setCn($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $this->appendOrNot('cn', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "sn"
+     *
+     * @param array|string|null $value
+     * @param bool $append
+     * @return self
+     */
+    public function setSn($value = null, $append = false)
+    {
+        $value = $this->preFormat($value);
+        $this->appendOrNot('sn', $value, $append);
+
+        return $this;
+    }
+
+    /**
+     * Attribut Ldap "userPassword"
+     *
+     * @param string $value
+     * @return self
+     * @throws LdapException
+     */
+    public function setUserPassword(string $value)
+    {
+        $this->getNode()->setPasswordAttribute($value, Attribute::PASSWORD_HASH_SHA, 'userPassword');
+
+        return $this;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenLdap/Entity/Entity.php b/src/UnicaenLdap/Entity/Entity.php
index 9899c7f0505d260dcc0ef75b32edad8c9667e876..c1274b38abaf670d5543b487c7aa1f436832cc89 100644
--- a/src/UnicaenLdap/Entity/Entity.php
+++ b/src/UnicaenLdap/Entity/Entity.php
@@ -5,6 +5,9 @@ namespace UnicaenLdap\Entity;
 use UnicaenLdap\Node;
 use UnicaenLdap\Exception;
 use UnicaenLdap\Service\AbstractService;
+use UnicaenLdap\Util;
+use Zend\Ldap\Dn;
+use Zend\Ldap\Exception\LdapException;
 
 /**
  * Classe mère des entrées de l'annuaire LDAP.
@@ -35,40 +38,92 @@ abstract class Entity
     /**
      * Liste des classes d'objet nécessaires à la création de l'entité
      *
-     * @var string[]
+     * @var array
      */
-    protected $objectClass = [
+    protected $objectClass = [];
 
+    /**
+     * Liste des attributs autorisés pour une entité
+     *
+     * @var array
+     */
+    protected $authorizedAttributes = [];
+
+    /**
+     * Liste des attributs contenant des dates communs à toutes les entités
+     */
+    private $sharedDateTimeAttributes = [
+        'createTimestamp',
+        'modifyTimestamp',
     ];
 
     /**
      * Liste des attributs contenant des dates
      *
-     * @var string[]
+     * @var array
      */
-    protected $dateTimeAttributes = [
+    protected $dateTimeAttributes = [];
 
-    ];
+    /**
+     * Liste des attributs monovalués
+     *
+     * @var array
+     */
+    protected $monoValuedAttributes = [];
+
+    /**
+     * Liste des patterns génériques utilisés pour différents attributs
+     */
+    static protected $attribute_with_label_pattern = '/^(?<etiquette>\{[\w\-:]+\})(?<identifiant>.+)$/';
 
+    /**
+     * Liste des patterns spécifiques utilisés pour différents attributs
+     */
+    static protected $postal_address_pattern = '/^(.*)\$(.*)\$(.*)\$(.*)\$(.*)\$(.*)$/';
+
+
+    /**
+     * @param string $type type d'entité
+     * @return array
+     * @throws Exception
+     */
     public static function getNodeParams($type)
     {
         $params = [
-            'Generic'   => ['uid', 'UnicaenLdap\\Entity\\Generic'],
-            'Group'     => ['cn', 'UnicaenLdap\\Entity\\Group'],
-            'People'    => ['uid', 'UnicaenLdap\\Entity\\People'],
+            'Generic' => ['uid', 'UnicaenLdap\\Entity\\Generic'],
+            'Group' => ['cn', 'UnicaenLdap\\Entity\\Group'],
+            'People' => ['uid', 'UnicaenLdap\\Entity\\People'],
+            'Root' => ['dc', 'UnicaenLdap\\Entity\\Root'],
             'Structure' => ['supannCodeEntite', 'UnicaenLdap\\Entity\\Structure'],
-            'System'    => ['uid', 'UnicaenLdap\\Entity\\System'],
+            'System' => ['uid', 'UnicaenLdap\\Entity\\System'],
         ];
         if (!isset($params[$type])) throw new Exception('Paramètres de "' . $type . '" non trouvés');
 
         return $params[$type];
     }
 
+
+    /**
+     * Vérifie le format de l'identifiant Ldap selon le type de l'entité
+     *
+     * @param $type
+     * @param $id
+     * @return false|int
+     */
+    public static function checkIdFormat($type, $id)
+    {
+        switch($type) {
+            default:
+                return preg_match('/^[[:alnum:]_-]+$/', $id);
+        }
+    }
+
     /**
-     * Construit une entrée.
+     * Entity constructor.
      *
-     * @param AbstractService      $service Service qui gèrera la future entité
-     * @param Node|Dn|array|string $data    Noeud si Node, sinon DN
+     * @param AbstractService $service Service qui gèrera la future entité
+     * @param Node|Dn|array|string $data Noeud si Node, sinon DN
+     * @throws LdapException
      */
     public function __construct(AbstractService $service, $data)
     {
@@ -82,7 +137,7 @@ abstract class Entity
     }
 
     /**
-     * Retourne le type du service
+     * Retourne le type de l'entité
      *
      * @return string
      */
@@ -132,98 +187,113 @@ abstract class Entity
     }
 
     /**
-     * Retourne la clé primaire correspondant à l'entité
+     * Retourne l'Organizational Unit (OU) de l'entité
      *
      * @return string
      */
-    public function getId()
+    public function getOu()
     {
-        list($key) = self::getNodeParams($this->type);
+        if ($result = $this->getNode()->getDn()->get(1)) {
+            return $result['ou'];
+        }
 
-        return $this->get($key);
+        return null;
     }
 
     /**
-     * Retourne la clé primaire correspondant à l'entité
+     * Retourne le nom de la clé primaire correspondant à l'entité
      *
-     * @return string
+     * @return mixed
+     * @throws Exception
      */
     public function getKey()
     {
-        return reset(self::getNodeParams($this->type));
+        $params = self::getNodeParams($this->type);
+        return reset($params);
     }
 
     /**
-     * Retourne la liste des attributs de l'entité
+     * Retourne la valeur de la clé primaire correspondant à l'entité
      *
-     * @return string[]
+     * @return string
+     * @throws Exception
+     * @throws LdapException
      */
-    public function getAttributesList()
+    public function getId()
     {
-        return array_keys($this->getNode()->getAttributes());
+        return $this->get($this->getKey());
     }
 
     /**
-     * Exporte sous forme de tableau le contenu de l'entité
+     * Retourne l'attribut "objectClass"
      *
-     * @return array
+     * @return array|int|mixed|null
+     * @throws LdapException
      */
-    public function toArray()
+    public function getObjectClass()
     {
-        $result = [];
-        $attrsList = $this->getAttributesList();
-        foreach ($attrsList as $attrName) {
-            $result[$attrName] = $this->get($attrName);
-        }
-
-        return $result;
+        return $this->get('objectClass');
     }
 
     /**
-     * Mise à jour de l'entité
+     * Retourne la liste des attributs de l'entité
      *
-     * @return self
+     * @param  bool $includeSystemAttributes
+     * @return string[]
      */
-    public function update()
+    public function getAttributesList($includeSystemAttributes = true)
     {
-        $this->getNode()->update();
-
-        return $this;
+        return array_keys($this->getNode()->getAttributes($includeSystemAttributes));
     }
 
     /**
-     * Suppression de l'entité
+     * Retourne un label constitué des différentes clés passées en paramètre
+     * Format : {$key1:$key2:$key3:...}
      *
-     * @return self
+     * @param string ...$key
+     * @return string
      */
-    public function delete()
+    public function getLabel(...$key)
     {
-        $this->getNode()->delete();
+        $label = '';
+        $c = 0;
+        foreach($key as $k) {
+            if(is_string($k)) {
+                $label .= ($c == 0) ? $k : ":$k";
+            }
+            $c++;
+        }
 
-        return $this;
+        return sprintf('{%s}', $label);
     }
 
     /**
-     * Insertion de l'entité
+     * Exporte sous forme de tableau le contenu de l'entité
      *
-     * @return self
+     * @return array
+     * @throws LdapException
      */
-    public function insert()
+    public function toArray()
     {
-        $this->getNode()->attachLdap($this->service->getLdap());
+        $result = [];
+        $attrsList = $this->getAttributesList();
+        foreach ($attrsList as $attrName) {
+            $result[$attrName] = $this->get($attrName);
+        }
 
-        return $this;
+        return $result;
     }
 
     /**
      * Retourne un attribut
      *
      * @param string $attrName
-     * @return mixed
+     * @return array|int|mixed|null
+     * @throws LdapException
      */
     public function get($attrName)
     {
-        if (in_array($attrName, $this->dateTimeAttributes)) {
+        if (in_array($attrName, array_merge($this->dateTimeAttributes, $this->sharedDateTimeAttributes))) {
             $value = $this->getNode()->getDateTimeAttribute($attrName);
         } else {
             $value = $this->getNode()->getAttribute($attrName);
@@ -240,13 +310,28 @@ abstract class Entity
     /**
      * Affecte une nouvelle valeur à un attribut
      *
+     * This is an offline method.
+     * Node will be updated on calling update().
+     *
      * @param string $attrName
-     * @param mixed  $value
+     * @param mixed $value
      * @return self
+     * @throws Exception
+     * @throws LdapException
      */
     public function set($attrName, $value)
     {
-        if (in_array($attrName, $this->dateTimeAttributes)) {
+        if(!in_array($attrName, $this->authorizedAttributes)) {
+            throw new Exception(sprintf("L'attribut Ldap '%s' n'est pas autorisé pour une entité '%s'.", $attrName, get_class($this)));
+        }
+
+        if(is_array($value)
+            && count($value) > 1
+            && in_array($attrName, $this->monoValuedAttributes)) {
+            throw new Exception(sprintf("L'attribut Ldap '%s' est monovalué et ne doit contenir qu'une seule valeur.", $attrName));
+        }
+
+        if (in_array($attrName, array_merge($this->dateTimeAttributes, $this->sharedDateTimeAttributes))) {
             $this->getNode()->setDateTimeAttribute($attrName, $value, true);
         } else {
             $this->getNode()->setAttribute($attrName, $value);
@@ -259,7 +344,7 @@ abstract class Entity
      * Retourne <code>true</code> si l'attribut $param possède la valeur $value, <code>false</code> sinon.
      *
      * @param string $attrName
-     * @param mixed  $value
+     * @param mixed $value
      * @return boolean
      */
     public function has($attrName, $value)
@@ -269,27 +354,45 @@ abstract class Entity
 
     /**
      * Ajoute une valeur à un attribut
+     * Si l'attribut est monovalué, la valeur actuelle est remplacée par la nouvelle valeur
+     *
+     * This is an offline method.
+     * Node will be updated on calling update().
      *
      * @param string $attrName
-     * @param mixed  $value
+     * @param mixed $value
      * @return self
+     * @throws Exception
+     * @throws LdapException
      */
     public function add($attrName, $value)
     {
-        if (in_array($attrName, $this->dateTimeAttributes)) {
-            $this->getNode()->appendToDateTimeAttribute($attrName, $value);
-        } else {
-            $this->getNode()->appendToAttribute($attrName, $value);
+        if(!in_array($attrName, $this->authorizedAttributes)) {
+            throw new Exception(sprintf("L'attribut Ldap '%s' n'est pas autorisé pour une entité '%s'.", $attrName, get_class($this)));
         }
 
-        return $this;
+        if(in_array($attrName, $this->monoValuedAttributes)) {
+            $this->set($attrName, $value);
+        }
+        else {
+            if (in_array($attrName, array_merge($this->dateTimeAttributes, $this->sharedDateTimeAttributes))) {
+                $this->getNode()->appendToDateTimeAttribute($attrName, $value);
+            } else {
+                $this->getNode()->appendToAttribute($attrName, $value);
+            }
+
+            return $this;
+        }
     }
 
     /**
      * Retire une valeur à un attribut
      *
-     * @param type $attrName
-     * @param type $value
+     * This is an offline method.
+     * Node will be updated on calling update().
+     *
+     * @param string $attrName
+     * @param mixed|array $value
      * @return self
      */
     public function remove($attrName, $value)
@@ -299,11 +402,31 @@ abstract class Entity
         return $this;
     }
 
+    /**
+     * Ajoute ou affecte une valeur à un attribut Ldap
+     *
+     * This is an offline method.
+     * Node will be updated on calling update().
+     *
+     * @param string $attrName
+     * @param mixed $value
+     * @param bool $append
+     * @throws Exception
+     * @throws LdapException
+     */
+    protected function appendOrNot($attrName, $value, $append)
+    {
+        (!$append)
+            ? $this->set($attrName, $value)
+            : $this->add($attrName, $value);
+    }
+
     /**
      * Méthode magique...
      *
      * @param string $attrName
      * @return mixed
+     * @throws LdapException
      */
     public function __get($attrName)
     {
@@ -314,8 +437,9 @@ abstract class Entity
      * Méthode magique...
      *
      * @param string $attrName
-     * @param mixed  $value
+     * @param mixed $value
      * @return self
+     * @throws LdapException
      */
     public function __set($attrName, $value)
     {
@@ -325,8 +449,8 @@ abstract class Entity
     /**
      * Methode magique ...
      *
-     * @param string $method    Nom de la méthode à appeler
-     * @param array  $arguments Tableau de paramètres
+     * @param string $method Nom de la méthode à appeler
+     * @param array $arguments Tableau de paramètres
      * @return mixed
      */
     public function __call($method, array $arguments)
@@ -341,4 +465,175 @@ abstract class Entity
             }
         }
     }
+
+    /**
+     * Déplacement de l'entité dans un autre OU (Organizational Unit)
+     *
+     * This is an offline method.
+     * Node will be moved on calling update().
+     *
+     * @param string | Dn $newDn
+     * @throws \Zend\Ldap\Exception\LdapException
+     */
+    public function move(string $newOu)
+    {
+        $arrayOu = $this->getService()->getOu();
+
+        if(empty($arrayOu) || !in_array($newOu, $arrayOu)) {
+            throw new Exception(sprintf("L'entité ne peut être déplacée dans l'OU '%s'.", $newOu));
+        }
+
+        if($newOu == $this->getOu()) {
+            throw new Exception(sprintf("L'entité est déjà présente dans l'OU '%s'.", $newOu));
+        }
+
+        $newDn = sprintf('%s=%s,ou=%s,%s', $this->getKey(), $this->getId(), $newOu, $this->getNode()->getLdap()->getBaseDn());
+
+        $this->getNode()->move($newDn);
+
+        return $this;
+    }
+
+    /**
+     * Renommage de l'entité
+     *
+     * This is an offline method. Node will be renamed on calling update().
+     *
+     * @param string | Dn $newDn
+     * @throws \Zend\Ldap\Exception\LdapException
+     */
+    public function rename($newId)
+    {
+        $newDn = sprintf('%s=%s,ou=%s,%s', $this->getKey(), $newId, $this->getOu(), $this->getNode()->getLdap()->getBaseDn());
+
+        $this->getNode()->move($newDn);
+    }
+
+    /**
+     * Suppression de l'entité
+     *
+     * This is an offline method.
+     * Node will be deleted on calling update().
+     *
+     * @return self
+     */
+    public function delete()
+    {
+        $this->getNode()->delete();
+
+        return $this;
+    }
+
+    /**
+     * Mise à jour de l'entité
+     *
+     * @return self
+     * @throws LdapException
+     */
+    public function update()
+    {
+        $this->getNode()->update();
+
+        return $this;
+    }
+
+    /**
+     * Attache une connexion
+     *
+     * @return self
+     * @throws LdapException
+     */
+    public function attach()
+    {
+        $this->getNode()->attachLdap($this->service->getLdap());
+
+        return $this;
+    }
+
+    /**
+     * Formate un nom ou un prénom
+     *
+     * @param string $name
+     * @param bool $removeAccents remplace les caractères avec un signe diacritique
+     * @return mixed|null|string|string[]
+     */
+    protected function formatName($name, $removeAccents = false)
+    {
+        $name = preg_replace('/[[:blank:]]+/', ' ', trim($name));
+
+        if ($removeAccents) {
+            $name = Util::removeAccents($name);
+        }
+
+        return mb_convert_case($name, MB_CASE_TITLE, "UTF-8");
+    }
+
+    /**
+     * Formate un numéro de téléphone (0XXXXXXXXX) au format international (+33 X XX XX XX XX)
+     *
+     * @param string $num
+     * @return string
+     */
+    protected function formatTel($num)
+    {
+        if (is_null($num)) {
+            return null;
+        }
+
+        $num = preg_replace('/[\.\-\s]+/', '', $num);
+
+        return preg_match("/0\d{9}/", $num)
+            ? preg_replace("/0(\d{1})(\d{2})(\d{2})(\d{2})(\d{2})/", "+33 $1 $2 $3 $4 $5", $num)
+            : $num;
+    }
+
+    /**
+     * Formate les données sources correspondant à un booléen
+     *
+     * @param mixed $value
+     * @return string
+     */
+    protected function formatBoolean($value)
+    {
+        if (!is_bool($value) && $value === null) {
+            return null;
+        }
+
+        if (is_string($value)) {
+            $value = strtolower($value);
+        }
+
+        switch (true) {
+            case $value === false;
+            case $value === 0:
+            case $value === 'n':
+                return 'FALSE';
+            case $value === true:
+            case $value === 1:
+            case $value === 'o':
+            case $value === 'y':
+                return 'TRUE';
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * Vérifie une valeur et supprime les valeurs nulles
+     *
+     * @param mixed $value
+     * @return array
+     */
+    protected function preFormat($value)
+    {
+        if (is_scalar($value)
+            || is_null($value)
+            || is_a($value, 'DateTime')) {
+            $value = [$value];
+        }
+
+        $value = array_filter($value, 'strlen'); // Delete NULL, '' and FALSE values
+
+        return $value;
+    }
 }
\ No newline at end of file
diff --git a/src/UnicaenLdap/Entity/Generic.php b/src/UnicaenLdap/Entity/Generic.php
index 2f0d585f1451fc51d74504b1c15a841f8d2aa01b..0e27cc1d7e5ca9117c136053df4b4d7292b1f649 100644
--- a/src/UnicaenLdap/Entity/Generic.php
+++ b/src/UnicaenLdap/Entity/Generic.php
@@ -2,34 +2,12 @@
 
 namespace UnicaenLdap\Entity;
 
+use UnicaenLdap\Entity\Base\Generic as BaseGeneric;
+
 /**
- * Classe mère des adresses génériques de l'annuaire LDAP.
+ * Classe de gestion des entités de la branche "generic" de l'annuaire LDAP.
  *
  * @author Laurent Lécluse <laurent.lecluse at unicaen.fr>
  */
-class Generic extends Entity
-{
-    protected $type = 'Generic';
-
-    /**
-     * Liste des classes d'objet nécessaires à la création d'une adresse générique
-     *
-     * @var string[]
-     */
-    protected $objectClass = [
-        'top',
-        'inetOrgPerson',
-        'organizationalPerson',
-        'person',
-        'supannPerson',
-        'ucbnEmp',
-    ];
-
-    /**
-     * Liste des attributs contenant des dates
-     *
-     * @var string[]
-     */
-    protected $dateTimeAttributes = [
-    ];
-}
\ No newline at end of file
+class Generic extends BaseGeneric
+{}
\ No newline at end of file
diff --git a/src/UnicaenLdap/Entity/Group.php b/src/UnicaenLdap/Entity/Group.php
index 84dccb263276f4fabb02cca18f37e2bd8c846243..38b87836f1d49849ce7eb62ca07c20e8fba2a446 100644
--- a/src/UnicaenLdap/Entity/Group.php
+++ b/src/UnicaenLdap/Entity/Group.php
@@ -3,37 +3,17 @@
 namespace UnicaenLdap\Entity;
 
 use DateTime;
+use UnicaenLdap\Entity\Base\Group as BaseGroup;
 
 /**
- * Classe mère des groupes de l'annuaire LDAP.
+ * Classe de gestion des entités de la branche "groups" de l'annuaire LDAP.
  *
  * @author Laurent Lécluse <laurent.lecluse at unicaen.fr>
  */
-class Group extends Entity
+class Group extends BaseGroup
 {
-    protected $type = 'Group';
-
-    /**
-     * Liste des classes d'objet nécessaires à la création d'un groupe
-     *
-     * @var string[]
-     */
-    protected $objectClass = [
-        'groupOfNames',
-        'supannGroupe',
-    ];
-
     /**
-     * Liste des attributs contenant des dates
-     *
-     * @var string[]
-     */
-    protected $dateTimeAttributes = [
-        'supannGroupeDateFin',
-    ];
-
-    /**
-     * Détermine si un groupe est valide ou non, c'est-à-dire si sa date de fin de validité n'est pas antérieure à la
+     * Détermine si un groupe est valide ou non, c'est-à-dire si sa date de fin de validité est postérieure ou égal à la
      * date testée
      *
      * @param DateTime $dateObservation
@@ -64,14 +44,12 @@ class Group extends Entity
      *
      * @param string $orderBy Champ de tri (au besoin)
      * @return People[]
+     * @throws \Zend\Ldap\Exception\LdapException
      */
-    public function getPeople($orderBy = null)
+    public function getMembres($orderBy = null)
     {
-        /* @var $peopleService \UnicaenLdap\Service\People */
-        $peopleService = $this->getService()->getLdapPeopleService();
-
-        /** @var People[] $result */
-        $result = $peopleService->getAllBy($this->get('member'), 'dn', $orderBy);
+        $peopleService = $this->service->getLdapPeopleService();
+        $result = $peopleService->getAllBy($this->member, 'dn', $orderBy);
 
         return $result;
     }
diff --git a/src/UnicaenLdap/Entity/People.php b/src/UnicaenLdap/Entity/People.php
index d647ee75a3333caa16549fa987b4832aa77a4d6c..2028027dc72aa7680954b6fe8b8e8faf738f1cfa 100644
--- a/src/UnicaenLdap/Entity/People.php
+++ b/src/UnicaenLdap/Entity/People.php
@@ -3,276 +3,723 @@
 namespace UnicaenLdap\Entity;
 
 use DateTime;
+use UnicaenLdap\Entity\Base\People as BasePeople;
+use UnicaenLdap\Entity\Group as GroupEntity;
+use UnicaenLdap\Entity\Structure as StructureEntity;
+use UnicaenLdap\Entity\System as SystemEntity;
+use UnicaenLdap\Exception;
+use Zend\Ldap\Dn;
 
 /**
- * Classe mère des people de l'annuaire LDAP.
+ * Classe de gestion des entités de la branche "people" de l'annuaire LDAP.
  *
  * @author Laurent Lécluse <laurent.lecluse at unicaen.fr>
  * @author Bertrand GAUTHIER <bertrand.gauthier@unicaen.fr>
+ * @author David SURVILLE <david.surville@unicaen.fr>
  */
-class People extends Entity
+class People extends BasePeople
 {
-    static protected $role_pattern = '/^\[role={SUPANN}(.*)\]\[type={SUPANN}(.*)\]\[code=(.*)\]\[libelle=(.*)\]$/';
-
     /**
-     * Liste des rôles existants
+     * Détermine si l'individu est actif ou non
      *
-     * @var string[]
+     * @return boolean
      */
-    public static $roles_list = [
-        'DIRECTEUR'          => 'D30',
-        'RESPONSABLE'        => 'R00',
-        'RESP_ADMINISTRATIF' => 'R40',
-    ];
-
-    protected $type = 'People';
+    public function isPeople()
+    {
+        return 'people' == $this->getOu();
+    }
 
     /**
-     * Liste des classes d'objet nécessaires à la création d'une personne
-     * Il est nécessaire d'ajouter la classe 'ucbnEtu' ou 'ucbnEmp' selon le
-     * statut de la personne.
+     * Détermine si l'individu est désactivé ou non
      *
-     * @var string[]
+     * @return boolean
      */
-    protected $objectClass = [
-        'top',
-        'inetOrgPerson',
-        'organizationalPerson',
-        'person',
-        'eduPerson',
-        'supannPerson',
-        'sambaAccount',
-        'sambaSamAcount',
-        'posixAccount',
-    ];
+    public function isDeactivated()
+    {
+        return 'deactivated' == $this->getOu();
+    }
 
     /**
-     * Liste des attributs contenant des dates
+     * Détermine si l'individu est bloqué ou non
      *
-     * @var string[]
+     * @return boolean
      */
-    protected $dateTimeAttributes = [
-
-    ];
+    public function isBlocked()
+    {
+        return 'blocked' == $this->getOu();
+    }
 
     /**
-     * Retourne le nom complet de cet individu LDAP.
+     * Retourne le nom complet.
      *
-     * @param boolean $nomEnMajuscule Mettre le nom de famille en majuscules ?
-     * @param boolean $avecCivilite Inclure la civilité ?
-     * @param boolean $prenomDabord Mettre le prénom avant le nom de famille ?
+     * @param boolean $uppercaseName nom de famille en majuscules
+     * @param boolean $withCivility inclure la civilité
+     * @param boolean $firstNameFirst prénom avant le nom de famille
      * @return string
      */
-    public function getNomComplet($nomEnMajuscule = false, $avecCivilite = false, $prenomDabord = false)
+    public function getNomComplet($uppercaseName = false, $withCivility = false, $firstNameFirst = false)
     {
-        $sn = $this->get('sn');
+        $sn = $this->sn;
         $sn = is_array($sn) ? current($sn) : $sn;
 
         if (!$sn) {
             return '';
         }
 
-        $nom = $nomEnMajuscule ? strtoupper($sn) : $sn;
-        $prenom = $this->get('givenname');
-        $civilite = $avecCivilite ? ' ' . $this->get('supannCivilite') : null;
+        $nom = $uppercaseName ? mb_strtoupper($sn, 'UTF-8') : $sn;
+        $prenom = $this->givenName;
+        $civilite = $withCivility ? $this->supannCivilite . ' ' : '';
 
-        return ($prenomDabord ? $prenom . ' ' . $nom : $nom . ' ' . $prenom) . $civilite;
+        return  $civilite . ($firstNameFirst ? $prenom . ' ' . $nom : $nom . ' ' . $prenom);
     }
 
     /**
-     * Détermine si l'individu est actif ou non
+     * Retourne le nom d'usage
      *
-     * @return boolean
+     * @return string|null
      */
-    public function isPeople()
+    public function getNomUsage()
     {
-        return 'people' == $this->getOu();
+        return is_array($this->sn) ? $this->sn[0] : $this->sn;
     }
 
     /**
-     * Détermine si l'individu est désactivé ou non
+     * Retourne le nom de famille s'il existe, sinon le nom d'usage
      *
-     * @return boolean
+     * @return string|null
      */
-    public function isDeactivated()
+    public function getNomFamille()
     {
-        return 'deactivated' == $this->getOu();
+        return is_array($this->sn) ? $this->sn[1] : $this->sn;
     }
 
     /**
-     * Détermine si l'individu est bloqué ou non
+     * Retourne la date de naissance
      *
-     * @return boolean
+     * @return DateTime|false
      */
-    public function isBlocked()
+    public function getDateNaissance()
     {
-        return 'blocked' == $this->getOu();
+        return DateTime::createFromFormat('Ymd h:i:s', $this->schacDateOfBirth . ' 00:00:00');
     }
 
     /**
-     * Retourne l'Organizational Unit (OU) de l'utilisateur
+     * Retourne la date d'expiration
      *
-     * @return string
+     * @return DateTime|false
      */
-    public function getOu()
+    public function getDateExpiration()
     {
-        if ($result = $this->getNode()->getDn()->get(1)) {
-            return $result['ou'];
+        return DateTime::createFromFormat('Ymd h:i:s', $this->schacExpiryDate . ' 00:00:00');
+    }
+
+    /**
+     * Retourne le responsable de l'entrée Ldap
+     *
+     * @return People|SystemEntity|string|null
+     * @throws Exception
+     * @throws \Zend\Ldap\Exception\LdapException
+     */
+    public function getParrain()
+    {
+        $systemService = $this->service->getLdapSystemService();
+        $node = $this->service->getLdap()->getNode($this->supannParrainDN);
+        $branch = $node->getDn()->get(1);
+        switch($branch['ou']) {
+            case strtolower($this->service->getType()):
+                return $this->service->getBy($node->getDnString(), 'dn');
+            case strtolower($systemService->getType()):
+                return $systemService->getBy($node->getDnString(), 'dn');
+            default:
+                return $this->supannParrainDN;
         }
+    }
 
-        return null;
+    /**
+     * Retourne l'adresse postale professionnelle
+     *
+     * @param string $type type de l'adresse : 'professionnelle' ou 'personnelle'
+     * @return array|null
+     */
+    public function getAdressePostale($type = 'professionnelle')
+    {
+        if(!in_array($type, ['professionnelle', 'personnelle'])) {
+            return null;
+        }
+
+        $attributes = ($type == 'professionnelle')
+            ? $this->postalAddress
+            : [$this->ucbnPrivateAddress, $this->ucbnPrivateAddressBis];
+        $value = $this->preFormat($attributes);
+        $value = array_map(function ($val) {
+            return explode('$', $val);
+        }, $value);
+
+        return 1 == count($value) ? $value[0] : $value;
     }
 
     /**
-     * Retourne la liste des groupes dont l'utilisateur fait partie
-     * Si le groupe n'est plus valide à la date d'observation, alors il n'est pas retourné dans la liste
+     * Test si une valeur est renseignée pour le champ "eduPersonAffiliation"
      *
-     * @param DateTime $dateObservation
-     * @param string   $orderBy Champ de tri (au besoin)
-     * @return Group[]
+     * @param string $status
+     * @return bool
      */
-    public function getGroups(DateTime $dateObservation = null, $orderBy = null)
+    protected function whichStatus(string $status): bool
     {
-        $groupService = $this->getService()->getLdapGroupService();
+        $value = $this->preFormat($this->eduPersonAffiliation);
+        return in_array($status, $value);
+    }
 
-        return $groupService->filterValids($groupService->getAllBy($this->get('memberOf'), 'dn', $orderBy), $dateObservation);
+    /**
+     * Check "affiliate" status
+     *
+     * @return bool
+     */
+    public function isAffiliate(): bool
+    {
+        return $this->whichStatus(parent::STATUS_AFFILIATE);
+    }
+    /**
+     * Check "alum" status
+     *
+     * @return bool
+     */
+    public function isAlum(): bool
+    {
+        return $this->whichStatus(parent::STATUS_ALUM);
     }
 
     /**
-     * Détermine si la personne est étudiante
+     * Check "emeritus" status
      *
-     * @return boolean
+     * @return bool
      */
-    public function estEtudiant()
+    public function isEmeritus(): bool
     {
-        return 0 === strpos($this->uid, 'e');
+        return $this->whichStatus(parent::STATUS_EMERITUS);
     }
 
     /**
-     * Détermine si la personne est un personnel
+     * Check "employee" status
      *
-     * @return boolean
+     * @return bool
      */
-    public function estPersonnel()
+    public function isEmployee(): bool
     {
-        return 0 === strpos($this->uid, 'p');
+        return $this->whichStatus(parent::STATUS_EMPLOYEE);
     }
 
     /**
-     * Détermine si la personne est un invité
+     * Check "faculty" status
      *
-     * @return boolean
+     * @return bool
      */
-    public function estInvite()
+    public function isFaculty(): bool
     {
-        return 0 === strpos($this->uid, 'i');
+        return $this->whichStatus(parent::STATUS_FACULTY);
     }
 
     /**
-     * Retourne les structures auxquelles appartiennent la personne
+     * Check "member" status
      *
-     * @return Entity[]|Structure[]
+     * @return bool
      */
-    public function getEduPersonOrgUnit()
+    public function isMember(): bool
     {
-        $structureService = $this->getService()->getLdapStructureService();
-        $dn = $this->eduPersonOrgUnitDN;
-        if (empty($dn)) return null;
+        return $this->whichStatus(parent::STATUS_MEMBER);
+    }
 
-        return $structureService->getAllBy($dn, 'dn');
+    /**
+     * Check "registered reader" status
+     *
+     * @return bool
+     */
+    public function isRegisteredReader(): bool
+    {
+        return $this->whichStatus(parent::STATUS_REGISTERED_READER);
     }
 
     /**
-     * Retourne la structure principale à laquelle appartient la personne
+     * Check "researcher" status
      *
-     * @return Entity|Structure
-     * @throws \UnicaenLdap\Exception
+     * @return bool
      */
-    public function getEduPersonPrimaryOrgUnit()
+    public function isResearcher(): bool
     {
-        $structureService = $this->getService()->getLdapStructureService();
-        $dn = $this->eduPersonPrimaryOrgUnitDN;
-        if (empty($dn)) return null;
+        return $this->whichStatus(parent::STATUS_RESEARCHER);
+    }
 
-        return $structureService->getBy($dn, 'dn');
+    /**
+     * Check "retired" status
+     *
+     * @return bool
+     */
+    public function isRetired(): bool
+    {
+        return $this->whichStatus(parent::STATUS_RETIRED);
+    }
+
+    /**
+     * Check "staff" status
+     *
+     * @return bool
+     */
+    public function isStaff(): bool
+    {
+        return $this->whichStatus(parent::STATUS_STAFF);
     }
 
     /**
-     * Retourne la structure d'affectation de la personne
+     * Check "student" status
      *
-     * @todo à terminer
-     * @return Entity|Structure
-     * @throws \Exception
+     * @return bool
      */
-    public function getEntiteAffectation()
+    public function isStudent(): bool
     {
-        throw new \Exception('Méthode pas finie');
-        $structureService = $this->getService()->getLdapStructureService();
-        $codes = $this->getNode()->getAttribute('supannEntiteAffectation');
-        var_dump($codes);
+        return $this->whichStatus(parent::STATUS_STUDENT);
+    }
 
-        return $structureService->getBy($dn, 'dn');
+    /**
+     * Check "teacher" status
+     *
+     * @return bool
+     */
+    public function isTeacher(): bool
+    {
+        return $this->whichStatus(parent::STATUS_TEACHER);
     }
 
     /**
-     * Retourne la structure d'affectation de la personne
+     * Retourne le léocode associé à l'individu
      *
-     * @todo à terminer
-     * @return Structure
-     * @throws \Exception
+     * @return string|null
      */
-    public function getEntiteAffectationPrincipale()
+    public function getLeocarteCode()
     {
-        throw new \Exception('Méthode pas finie');
-        $structureService = $this->getService()->getLdapStructureService();
+        $attributeValues = $this->preFormat($this->supannRefId);
+        $label = $this->getLabel('LEOCODE');
 
-        $codes = [];
-        $affectations = $this->getNode()->getAttribute('supannAffectation');
+        $value = array_filter($attributeValues, function ($v) use ($label) {
+            return preg_match("/^$label(?<identifiant>.+)$/", $v);
+        });
 
+        return !empty($value)
+            ? str_replace($label, '', array_values($value)[0])
+            : null;
+    }
 
-        list($code, $description) = explode(';', $this->supannAffectation);
-        $code = $this->supannAffectation;
-        if (empty($dn)) return null;
+    /**
+     * Retourne le code de la léocarte associée à l'individu
+     *
+     * @return string|null
+     */
+    public function getLeocarteCSN()
+    {
+        $attributeValues = $this->preFormat($this->supannRefId);
+        $label = $this->getLabel('UNICAEN', 'REVERSECSN');
+
+        $value = array_filter($attributeValues, function ($v) use ($label) {
+            return preg_match("/^$label(?<identifiant>.+)$/", $v);
+        });
 
-        return $structureService->getBy($dn, 'dn');
+        return !empty($value)
+            ? str_replace($label, '', array_values($value)[0])
+            : null;
     }
 
     /**
-     * Retourne la structure d'affectation de la personne
+     * Retourne le numéro d'étudiant
      *
-     * @todo à terminer
-     * @return Structure
-     * @throws \Exception
+     * @return string|null
      */
-    public function getAffectationDescription()
+    public function getEtudiantId()
     {
-        throw new \Exception('Méthode pas finie');
-        $structureService = $this->getService()->getLdapStructureService();
+        return $this->supannEtuId;
+    }
+
+    /**
+     * Retourne le numéro d'employé
+     *
+     * @return string|null
+     */
+    public function getEmployeId()
+    {
+        return $this->supannEmpId;
+    }
+
+    /**
+     * Retourne l'identifiant de l'individu dans le référentiel
+     *
+     * @return string|null
+     */
+    public function getReferentielId()
+    {
+        $attributeValues = $this->preFormat($this->supannRefId);
+        $label = $this->getLabel('OCTOPUS', 'ID');
+
+        $value = array_filter($attributeValues, function ($v) use ($label) {
+            return preg_match("/^$label(?<identifiant>.+)$/", $v);
+        });
+
+        return !empty($value)
+            ? str_replace($label, '', array_values($value)[0])
+            : null;
+    }
+
+    /**
+     * Établissement auquel appartient la personne
+     *
+     * @return StructureEntity|null
+     */
+    public function getEtablissement()
+    {
+        $rootService = $this->service->getLdapRootService();
+        $structureService = $this->service->getLdapStructureService();
+        $dn = $this->eduPersonOrgDN;
 
-        list($code, $description) = explode(';', $this->supannAffectation);
-        $code = $this->supannAffectation;
         if (empty($dn)) return null;
+        switch($dn) {
+            case $rootService->getEtablissementDN():
+                return $rootService->getStructureEntity();
+            default:
+                try {
+                    return $structureService->getBy($dn, 'dn');
+                } catch (Exception $e) {
+                    return null;
+                }
+        }
+    }
+
+    /**
+     * Retourne les structures auxquelles appartient la personne
+     *
+     * @return StructureEntity|StructureEntity[]|null
+     */
+    public function getStructure()
+    {
+        $structureService = $this->service->getLdapStructureService();
+        $value = $this->eduPersonOrgUnitDN;
+        if (empty($value)) return null;
+
+        $value = $structureService->getAllBy($value, 'dn');
+        return 1 == count($value) ? array_shift($value) : $value;
+    }
+
+    /**
+     * Retourne la structure principale à laquelle appartient la personne
+     *
+     * @return StructureEntity|null
+     */
+    public function getStructurePrincipale()
+    {
+        $structureService = $this->service->getLdapStructureService();
+        $value = $this->eduPersonPrimaryOrgUnitDN;
+        if (empty($value)) return null;
+
+        try {
+            return $structureService->getBy($value, 'dn');
+        } catch (Exception $e) {
+            return null;
+        }
+    }
+
+    /**
+     * Retourne les structures recherche auxquelles appartient la personne
+     *
+     * @return StructureEntity|StructureEntity[]|null
+     */
+    public function getStructureRecherche()
+    {
+        $structureService = $this->service->getLdapStructureService();
+        $structurePrefixe = $structureService->getCodeStructurePrefixe();
+
+        $value = $this->preFormat($this->ucbnStructureRecherche);
+        if (empty($value)) return null;
+
+        $value = array_map(function ($v) use ($structurePrefixe) {
+            preg_match(self::$structure_pattern, $v, $matches);
+            return $structurePrefixe . $matches['code'];
+        }, $value);
+
+        $value = $structureService->getAllBy($value, 'supannCodeEntite');
+        return 1 == count($value) ? array_shift($value) : $value;
+    }
+
+    /**
+     * Vérifie l'appartenance d'une personne à une structure (structures recherche incluses)
+     *
+     * @param string|Dn|StructureEntity $value
+     * @return bool
+     * @throws Exception
+     * @throws \Zend\Ldap\Exception\LdapException
+     */
+    public function isInStructure($value)
+    {
+        $structureService = $this->service->getLdapStructureService();
+        $structurePrefixe = $structureService->getCodeStructurePrefixe();
+
+        if (is_string($value)) {
+            if(Dn::checkDn($value)) {
+                $value = $structureService->getBy($value, 'dn');
+            }
+            else {
+                if (0 !== strpos($value, $structurePrefixe)) {
+                    $value = $structurePrefixe . $value;
+                }
+                $value = $structureService->get($value);
+            }
+        } elseif ($value instanceof Dn) {
+            $value = $structureService->getBy($value->toString(), 'dn');
+        }
+
+        if(!$value instanceof StructureEntity) {
+            return false;
+        }
+
+        $structures = $this->preFormat($this->supannEntiteAffectation);
+        $structuresRecherche = $this->preFormat($this->ucbnStructureRecherche);
+
+        $structuresRecherche = array_map(function ($v) use ($structurePrefixe) {
+            preg_match(self::$structure_pattern, $v, $matches);
+            return $structurePrefixe . $matches['code'];
+        }, $structuresRecherche);
+
+        return in_array($value->getId(), array_merge($structures, $structuresRecherche));
+    }
+
+    /**
+     * Retourne les étapes auxquelles appartient l'étudiant
+     *
+     * @return mixed|StructureEntity[]|null
+     * @throws Exception
+     */
+    public function getInscriptionEtape()
+    {
+        $value = $this->preFormat($this->supannEtuEtape);
+        if (empty($value)) return null;
+
+        $label = $this->getLabel('UAI', $this->service->getLdapRootService()->getEtablissementUAI());
+        $structureService = $this->service->getLdapStructureService();
+        list($structureKey) = Entity::getNodeParams($structureService->getType());
+        $structureBranch = $structureService->getBranches()[0];
+        $value = array_map(function ($v) use ($label, $structureKey, $structureBranch) {
+            return sprintf('%s=%s,%s',
+                $structureKey,
+                str_replace($label, '' , $v),
+                $structureBranch
+            );
+        }, $value);
+
+        $value = $structureService->getAllBy($value, 'dn');
+        return 1 == count($value) ? array_shift($value) : $value;
+    }
 
-        return $structureService->getBy($dn, 'dn');
+    /**
+     * Retourne les éléments pédagogiques auxquels appartient l'étudiant
+     *
+     * @return mixed|StructureEntity[]|null
+     * @throws Exception
+     */
+    public function getInscriptionElementPedagogique()
+    {
+        $value = $this->preFormat($this->supannEtuElementPedagogique);
+        if (empty($value)) return null;
+
+        $label = $this->getLabel('UAI', $this->service->getLdapRootService()->getEtablissementUAI());
+        $structureService = $this->service->getLdapStructureService();
+        list($structureKey) = Entity::getNodeParams($structureService->getType());
+        $structureBranch = $structureService->getBranches()[0];
+        $value = array_map(function ($v) use ($label, $structureKey, $structureBranch) {
+            return sprintf('%s=%s,%s',
+                $structureKey,
+                str_replace($label, '' , $v),
+                $structureBranch
+            );
+        }, $value);
+
+        $value = $structureService->getAllBy($value, 'dn');
+        return 1 == count($value) ? array_shift($value) : $value;
+    }
+
+    /**
+     * Retourne les inscriptions complètes d'un étudiant
+     *
+     * @return array
+     */
+    public function getInscription()
+    {
+        $value = $this->preFormat($this->supannEtuInscription);
+        if (empty($value)) return null;
+
+        $value = array_map(function ($v) {
+            preg_match(self::$inscription_pattern, $v, $matches);
+            $affect = $matches['affect'];
+            $etape = str_replace($this->getLabel('UAI', $this->service->getLdapRootService()->getEtablissementUAI()), '', $matches['etape']);
+            $replace = [$this->getLabel('SISE'), $this->getLabel('SUPANN'), $this->getLabel('INCONNU')];
+            return [
+                'etab'          => str_replace($this->getLabel('UAI'), '', $matches['etab']),
+                'anneeinsc'     => $matches['anneeinsc'],
+                'regimeinsc'    => str_replace($replace, '', $matches['regimeinsc']),
+                'sectdisc'      => str_replace($replace, '', $matches['sectdisc']),
+                'typedip'       => str_replace($replace, '', $matches['typedip']),
+                'cursusann'     => str_replace($replace, '', $matches['cursusann']),
+                'affect'        => $this->service->getLdapStructureService()->get($affect) ?: $affect,
+                'diplome'       => str_replace($replace, '', $matches['diplome']),
+                'etape'         => $this->service->getLdapStructureService()->get($etape) ?: $etape,
+            ];
+        }, $value);
+
+        return 1 == count($value) ? array_shift($value) : $value;
     }
 
     /**
-     * Retourne true si l'argument est au format "supannRoleEntite".
+     * Retourne le complément des inscriptions d'un étudiant
+     *
+     * @return array
+     */
+    public function getInscriptionComplement()
+    {
+        $value = $this->preFormat($this->ucbnEtuComplementInscription);
+        if (empty($value)) return null;
+
+        $value = array_map(function ($v) {
+            preg_match(self::$inscription_complement_pattern, $v, $matches);
+            $uaiLabel = $this->getLabel('UAI', $this->service->getLdapRootService()->getEtablissementUAI());
+            $etape = str_replace($uaiLabel, '', $matches['etape']);
+            return [
+                'anneeinsc' => $matches['anneeinsc'],
+                'etape'     => $this->service->getLdapStructureService()->get($etape) ?: $etape,
+                'adistance' => str_replace($uaiLabel, '', $matches['adistance'])
+            ];
+        }, $value);
+
+        return 1 == count($value) ? array_shift($value) : $value;
+    }
+
+    /**
+     * Retourne les rôles d'une personne
+     *
+     * @return array
+     */
+    public function getRole()
+    {
+        $value = $this->preFormat($this->supannRoleEntite);
+        if (empty($value)) return null;
+
+        $value = array_map(function ($v) {
+            preg_match(self::$role_pattern, $v, $matches);
+            $code = $matches['code'];
+            $replace = [$this->getLabel('SUPANN'), $this->getLabel('INCONNU')];
+            return [
+                'role'      => str_replace($replace, '', $matches['role']),
+                'type'      => str_replace($replace, '', $matches['type']),
+                'code'      => $this->service->getLdapStructureService()->get($code) ?: $code,
+                'libelle'   => $matches['libelle'],
+            ];
+        }, $value);
+
+        return 1 == count($value) ? array_shift($value) : $value;
+    }
+
+    /**
+     * Vérifie si la personne à une fonction dans un type de structure et une structure
      *
      * @param string $role
+     * @param string $type
+     * @param string|Dn|StructureEntity|null $value
      * @return bool
+     * @throws Exception
+     * @throws \Zend\Ldap\Exception\LdapException
      */
-    static public function isSupannRoleEntite($string, &$role = null, &$typeStructure = null, &$codeStructure = null, &$libelleRole = null)
+    public function hasRoleInStructure(string $role, string $type, $structure = null): bool
     {
-        if (preg_match(static::$role_pattern, $string, $matches)) {
-            $role = $matches[1];
-            $typeStructure = $matches[2];
-            $codeStructure = $matches[3];
-            $libelleRole = $matches[4];
+        $structureService = $this->service->getLdapStructureService();
+        $structurePrefixe = $structureService->getCodeStructurePrefixe();
 
-            return true;
+        if($structure == null) {
+            $structure = '.*';
+        }
+        else {
+            if (is_string($structure)) {
+                if (Dn::checkDn($structure)) {
+                    $structure = $structureService->getBy($structure, 'dn');
+                } else {
+                    if (0 !== strpos($structure, $structurePrefixe)) {
+                        $structure = $structurePrefixe . $structure;
+                    }
+                    $structure = $structureService->get($structure);
+                }
+            } elseif ($structure instanceof Dn) {
+                $structure = $structureService->getBy($structure->toString(), 'dn');
+            }
+
+            if (!$structure instanceof StructureEntity) {
+                return false;
+            }
         }
 
-        return false;
+        $structure = $structure instanceof StructureEntity ? $structure->supannCodeEntite : $structure;
+        $pattern = "/^\[role=\{SUPANN\}$role\]\[type=\{SUPANN\}$type\]\[code=$structure\]\[libelle=.*\]$/";
+
+        $matches = array_filter($this->supannRoleEntite, function ($v) use ($pattern) {
+            return preg_match($pattern, $v);
+        });
+
+        return !empty($matches);
+    }
+
+    /**
+     * Retourne le site d'une personne
+     *
+     * @return array|null
+     */
+    public function getSite()
+    {
+        $value = $this->preFormat($this->ucbnSiteLocalisation);
+        if (empty($value)) return null;
+
+        $value = array_map(function ($v) {
+            preg_match(self::$localisation_pattern, $v, $matches);
+            $code = $matches['code'];
+            return [
+                'code'      => $matches['code'],
+                'libelle'   => $matches['libelle'],
+            ];
+        }, $value);
+
+        return 1 == count($value) ? array_shift($value) : $value;
+    }
+
+    /**
+     * Retourne la liste des groupes dont l'utilisateur fait partie
+     * Si le groupe n'est plus valide à la date d'observation, alors il n'est pas retourné dans la liste
+     *
+     * @param DateTime $dateObservation
+     * @param string   $orderBy Champ de tri (au besoin)
+     * @return GroupEntity[]|null
+     */
+    public function getGroups(DateTime $dateObservation = null, $orderBy = null)
+    {
+        $groupService = $this->service->getLdapGroupService();
+
+        return $groupService->filterValids($groupService->getAllBy($this->get('memberOf'), 'dn', $orderBy), $dateObservation);
+    }
+
+    /**
+     * Modifie l'ensemble des attributs liés au mot de passe
+     *
+     * @param string $value
+     * @return $this
+     * @throws \UnicaenLdap\Exception
+     * @throws \Zend\Ldap\Exception\LdapException
+     */
+    public function setPassword(string $value)
+    {
+        parent::setUserPassword($value);
+        parent::setNtPassword($value);
+        parent::setSambaNTPassword($value);
+        parent::setUcbnSquidHash($value);
+
+        return $this;
     }
 }
\ No newline at end of file
diff --git a/src/UnicaenLdap/Entity/Root.php b/src/UnicaenLdap/Entity/Root.php
new file mode 100644
index 0000000000000000000000000000000000000000..31c1297f41fc93797598c7471981806b8c35f63c
--- /dev/null
+++ b/src/UnicaenLdap/Entity/Root.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace UnicaenLdap\Entity;
+
+use UnicaenLdap\Entity\Base\Root as BaseRoot;
+use UnicaenLdap\Filter\Filter;
+use Zend\Ldap\Exception\LdapException;
+
+/**
+ * Classe de gestion de l'entité racine de l'annuaire LDAP.
+ *
+ * @author David SURVILLE <david.surville@unicaen.fr>
+ */
+class Root extends BaseRoot
+{}
\ No newline at end of file
diff --git a/src/UnicaenLdap/Entity/Structure.php b/src/UnicaenLdap/Entity/Structure.php
index 1644fc14fe7c57a134a652acde1a91a470224e29..9b5c114f0a73ae49a9ac545650eaea37861fdf85 100644
--- a/src/UnicaenLdap/Entity/Structure.php
+++ b/src/UnicaenLdap/Entity/Structure.php
@@ -2,38 +2,18 @@
 
 namespace UnicaenLdap\Entity;
 
+use UnicaenLdap\Entity\Base\Structure as BaseStructure;
 use UnicaenLdap\Filter\Filter;
+use Zend\Ldap\Exception\LdapException;
 
 /**
- * Classe mère des structures de l'annuaire LDAP.
+ * Classe de gestion des entités de la branche "structures" de l'annuaire LDAP.
  *
  * @author Laurent Lécluse <laurent.lecluse at unicaen.fr>
+ * @author David SURVILLE <david.surville@unicaen.fr>
  */
-class Structure extends Entity
+class Structure extends BaseStructure
 {
-
-    protected $type = 'Structure';
-
-    /**
-     * Liste des classes d'objet nécessaires à la création d'une structure
-     *
-     * @var string[]
-     */
-    protected $objectClass = [
-        'top',
-        'organizationalUnit',
-        'supannEntite',
-        'ucbnEntite',
-    ];
-
-    /**
-     * Liste des attributs contenant des dates
-     *
-     * @var string[]
-     */
-    protected $dateTimeAttributes = [
-    ];
-
     /**
      * Retourne la structure parente, si elle existe
      *
@@ -41,7 +21,7 @@ class Structure extends Entity
      */
     public function getParents()
     {
-        if (null !== $parentIds = $this->get('supannCodeEntiteParent')) {
+        if (null !== ($parentIds = $this->get('supannCodeEntiteParent'))) {
             return $this->service->getAll($parentIds);
         }
 
@@ -70,17 +50,19 @@ class Structure extends Entity
     }
 
     /**
-     * Retourne le code Harpège
+     * Retourne le code dans la base source
      *
      * @return string
+     * @throws LdapException
+     * @throws \UnicaenLdap\Exception
      */
-    public function getCodeHarpege()
+    public function getCodeSource()
     {
-        $code = $this->get('supannCodeEntite');
-        if (0 === strpos($code, 'HS_')) {
-            return substr($code, 3);
-        } else {
-            return null; // Ne retourne rien si le code ne correspond pas à la nomenclature Harpège
-        }
+        $code = $this->getId();
+        return (
+            0 === strpos($code, $this->service->getCodeStructurePrefixe()) ||
+            0 === strpos($code, $this->service->getCodeModuleEnseignementPrefixe()))
+            ? substr($code, 3)
+            : $code;
     }
 }
\ No newline at end of file
diff --git a/src/UnicaenLdap/Entity/System.php b/src/UnicaenLdap/Entity/System.php
index bb21d910c636e25af6df509952155f0eee8f49e6..1e51c1bd2fa250fd6888a0240db8869f4e44f8f2 100644
--- a/src/UnicaenLdap/Entity/System.php
+++ b/src/UnicaenLdap/Entity/System.php
@@ -2,32 +2,12 @@
 
 namespace UnicaenLdap\Entity;
 
+use UnicaenLdap\Entity\Base\System as BaseSystem;
+
 /**
- * Classe mère des utilisateurs système de l'annuaire LDAP.
+ * Classe de gestion des entités de la branche "system" de l'annuaire LDAP.
  *
  * @author Laurent Lécluse <laurent.lecluse at unicaen.fr>
  */
 class System extends Entity
-{
-    protected $type = 'System';
-
-    /**
-     * Liste des classes d'objet nécessaires à la création d'un compte système
-     *
-     * @var string[]
-     */
-    protected $objectClass = [
-        'top',
-        'inetOrgPerson',
-        'organizationalPerson',
-        'person',
-    ];
-
-    /**
-     * Liste des attributs contenant des dates
-     *
-     * @var string[]
-     */
-    protected $dateTimeAttributes = [
-    ];
-}
\ No newline at end of file
+{}
\ No newline at end of file
diff --git a/src/UnicaenLdap/Service/AbstractService.php b/src/UnicaenLdap/Service/AbstractService.php
index 9a4ed90c3e17a5a7ff95f03c1b224ee91249362b..f2f7175c8e413a54dc9cb11ac7d65291f96a94cc 100644
--- a/src/UnicaenLdap/Service/AbstractService.php
+++ b/src/UnicaenLdap/Service/AbstractService.php
@@ -8,8 +8,8 @@ use UnicaenLdap\Entity\Entity;
 use UnicaenLdap\Exception;
 use UnicaenLdap\Filter\Filter;
 use UnicaenLdap\Ldap;
-use Zend\Ldap\Dn;
 use Zend\Ldap\Filter\AbstractFilter;
+use Zend\Ldap\Dn;
 use Zend\Stdlib\ErrorHandler;
 
 /**
@@ -18,17 +18,19 @@ use Zend\Stdlib\ErrorHandler;
  * @author Laurent Lécluse <laurent.lecluse at unicaen.fr>
  */
 abstract class AbstractService implements
+    LdapGenericServiceAwareInterface,
+    LdapGroupServiceAwareInterface,
     LdapPeopleServiceAwareInterface,
+    LdapRootServiceAwareInterface,
     LdapStructureServiceAwareInterface,
-    LdapGroupServiceAwareInterface,
-    LdapSystemServiceAwareInterface,
-    LdapGenericServiceAwareInterface
+    LdapSystemServiceAwareInterface
 {
+    use LdapGenericServiceAwareTrait;
+    use LdapGroupServiceAwareTrait;
     use LdapPeopleServiceAwareTrait;
+    use LdapRootServiceAwareTrait;
     use LdapStructureServiceAwareTrait;
-    use LdapGroupServiceAwareTrait;
     use LdapSystemServiceAwareTrait;
-    use LdapGenericServiceAwareTrait;
 
     /**
      * Limite de recherche par défaut
@@ -46,6 +48,7 @@ abstract class AbstractService implements
     protected $ldap;
 
     /**
+     * Type de l'entité
      *
      * @var string
      */
@@ -53,28 +56,19 @@ abstract class AbstractService implements
 
     /**
      * Organizational Units
-     * 
-     * @var string[]
+     *
+     * @var array
      */
-    protected $ou = array();
+    protected $ou = [];
 
     /**
+     * Compteur utilisé dans la recherche
      *
      * @var integer
      */
     protected $count;
 
 
-    /**
-     * Retourne le type du service
-     * 
-     * @return string
-     */
-    public function getType()
-    {
-        return $this->type;
-    }
-
     /**
      * Retourne l'objet d'accès à l'annuaire LDAP.
      *
@@ -89,18 +83,29 @@ abstract class AbstractService implements
      * Spécifie l'objet d'accès à l'annuaire LDAP.
      *
      * @param Ldap $ldap
-     * @return AbstractService
+     * @return self
      */
     public function setLdap(Ldap $ldap = null)
     {
         $this->ldap = $ldap;
+
         return $this;
     }
 
+    /**
+     * Retourne le type du service
+     *
+     * @return string
+     */
+    public function getType()
+    {
+        return $this->type;
+    }
+
     /**
      * Retourne la liste des organizational units
      *
-     * @return string[]
+     * @return array
      */
     public function getOu()
     {
@@ -108,52 +113,52 @@ abstract class AbstractService implements
     }
 
     /**
-     * Redéfinie la liste des organizational units
+     * Retourne la liste des branches liées à la liste des organizational units
      *
-     * @param string[] $ou
-     * @return AbstractService
+     * @return array
      */
-    public function setOu(array $ou)
+    public function getBranches()
     {
-        $this->ou = $ou;
-        return $this;
+        return array_map(function ($v) {
+            return sprintf('ou=%s,%s', $v, $this->getLdap()->getBaseDn());
+        }, $this->ou);
     }
 
     /**
      * Retourne la liste de toutes les entités correspondantes
-     * 
+     *
      * @param string $orderBy Propriété de référence pour le tri
      * @return Collection
      */
-    public function getList( $orderBy=null, $limit=self::DEFAULT_LIMIT, $offset=self::DEFAULT_OFFSET )
+    public function getList($orderBy = null, $limit = self::DEFAULT_LIMIT, $offset = self::DEFAULT_OFFSET)
     {
         list($key) = Entity::getNodeParams($this->type);
-        return $this->search( "($key=*)", $orderBy, $limit, $offset );
+        return $this->search("($key=*)", $orderBy, $limit, $offset);
     }
 
     /**
      * Recherche une liste d'entités correspondantes
      *
-     * @param string|AbstractFilter $filter  Valeur de recherche
-     * @param string                $orderBy Attribut de référence pour le tri
-     * @param integer               $limit   Nombre maximum d'occurences renvoyées (-1 = infini)
-     * @param integer               $offset  Renvoi les entités à partir de $offset uniquement
+     * @param string|AbstractFilter $filter Valeur de recherche
+     * @param string $orderBy Attribut de référence pour le tri
+     * @param integer $limit Nombre maximum d'occurences renvoyées (-1 = infini)
+     * @param integer $offset Renvoi les entités à partir de $offset uniquement
      * @return Collection
      * @throws Exception
      */
-    public function search( $filter, $orderBy=null, $limit=self::DEFAULT_LIMIT, $offset=self::DEFAULT_OFFSET )
+    public function search($filter, $orderBy = null, $limit = self::DEFAULT_LIMIT, $offset = self::DEFAULT_OFFSET)
     {
         list($key) = Entity::getNodeParams($this->type);
         if ($limit < 0) $limit = 3999999999; // Limite maximum à 4 milliard...
         if ($offset < 0) $offset = 0; // Moins de zéro = impossible
 
-        list( $resource, $search ) = $this->__searchBegin($filter, $this->ou, array($key,$orderBy));
+        list($resource, $search) = $this->__searchBegin($filter, $this->ou, [$key, $orderBy]);
 
         ErrorHandler::start(E_WARNING);
         $this->count = ldap_count_entries($resource, $search);
         ErrorHandler::stop();
 
-        if ($this->count > 0){
+        if ($this->count > 0) {
 
             if ($orderBy !== null && is_string($orderBy)) {
                 ErrorHandler::start(E_WARNING);
@@ -167,21 +172,21 @@ abstract class AbstractService implements
             $result = array();
             $i = 0;
             ErrorHandler::start(E_WARNING);
-            for ($entry=ldap_first_entry($resource,$search); $entry; $entry=ldap_next_entry($resource,$entry)) {
-                list($value) = ldap_get_values_len($resource,$entry,$key);
-                if (null !== $value){
+            for ($entry = ldap_first_entry($resource, $search); $entry; $entry = ldap_next_entry($resource, $entry)) {
+                list($value) = ldap_get_values_len($resource, $entry, $key);
+                if (null !== $value) {
                     $result[] = $value;
                     $i++;
                 }
                 if ($i > $limit + $offset - 1) break; // Pas besoin d'aller plus loin...
             }
             ErrorHandler::stop();
-        }else{
+        } else {
             $result = array();
         }
-        $this->__searchEnd( $search );
+        $this->__searchEnd($search);
         $this->count = count($result);
-        return new Collection( $this, array_slice( $result, $offset, $limit ) );
+        return new Collection($this, array_slice($result, $offset, $limit));
     }
 
     /**
@@ -191,9 +196,9 @@ abstract class AbstractService implements
      * @return integer
      * @throws Exception
      */
-    public function searchCount( $filter )
+    public function searchCount($filter)
     {
-        list( $resource, $search ) = $this->__searchBegin($filter, $this->ou);
+        list($resource, $search) = $this->__searchBegin($filter, $this->ou);
 
         if ($search === false) {
             throw new Exception('searching: ' . $filter);
@@ -206,29 +211,29 @@ abstract class AbstractService implements
     /**
      * Recherche une liste d'entités correspondantes
      *
-     * @param string|AbstractFilter $filter     Valeur de recherche
-     * @param array                 $attributes Liste des attributs à retourner
-     * @param string                $orderBy    Champ de référence pour le tri
-     * @param integer               $limit      Nombre maximum d'occurences renvoyées (-1 = infini)
-     * @param integer               $offset     Renvoi les entités à partir de $offset uniquement
+     * @param string|AbstractFilter $filter Valeur de recherche
+     * @param array $attributes Liste des attributs à retourner
+     * @param string $orderBy Champ de référence pour le tri
+     * @param integer $limit Nombre maximum d'occurences renvoyées (-1 = infini)
+     * @param integer $offset Renvoi les entités à partir de $offset uniquement
      * @return Collection
      * @throws Exception
      */
-    public function searchAttributes( $filter, array $attributes, $orderBy=null, $limit=self::DEFAULT_LIMIT, $offset=self::DEFAULT_OFFSET )
+    public function searchAttributes($filter, array $attributes, $orderBy = null, $limit = self::DEFAULT_LIMIT, $offset = self::DEFAULT_OFFSET)
     {
         list($key) = Entity::getNodeParams($this->type);
         if ($limit < 0) $limit = 3999999999; // Limite maximum à 4 milliard...
         if ($offset < 0) $offset = 0; // Moins de zéro = impossible
 
         $searchAttributes = $attributes;
-        if (! in_array($key, $searchAttributes)) $searchAttributes[] = $key;
-        if (null !== $orderBy && ! in_array($orderBy, $searchAttributes)) $searchAttributes[] = $orderBy;
-        list( $resource, $search ) = $this->__searchBegin($filter, $this->ou, $searchAttributes);
+        if (!in_array($key, $searchAttributes)) $searchAttributes[] = $key;
+        if (null !== $orderBy && !in_array($orderBy, $searchAttributes)) $searchAttributes[] = $orderBy;
+        list($resource, $search) = $this->__searchBegin($filter, $this->ou, $searchAttributes);
 
         ErrorHandler::start(E_WARNING);
         $this->count = ldap_count_entries($resource, $search);
         ErrorHandler::stop();
-        if ($this->count > 0){
+        if ($this->count > 0) {
             if ($orderBy !== null && is_string($orderBy)) {
                 ErrorHandler::start(E_WARNING);
                 $isSorted = ldap_sort($resource, $search, $orderBy);
@@ -241,11 +246,11 @@ abstract class AbstractService implements
             $result = array();
             $i = 0;
             ErrorHandler::start(E_WARNING);
-            for ($entry=ldap_first_entry($resource,$search); $entry; $entry=ldap_next_entry($resource,$entry)) {
-                list($id) = ldap_get_values_len($resource,$entry,$key);
+            for ($entry = ldap_first_entry($resource, $search); $entry; $entry = ldap_next_entry($resource, $entry)) {
+                list($id) = ldap_get_values_len($resource, $entry, $key);
                 $data = array();
-                foreach( $attributes as $attribute ){
-                    $attrValue = ldap_get_values_len($resource,$entry,$attribute);
+                foreach ($attributes as $attribute) {
+                    $attrValue = ldap_get_values_len($resource, $entry, $attribute);
                     if (1 == $attrValue['count']) $attrValue = $attrValue[0];
                     $data[$attribute] = $attrValue;
                 }
@@ -254,22 +259,21 @@ abstract class AbstractService implements
                 if ($i > $limit + $offset - 1) break; // Pas besoin d'aller plus loin...
             }
             ErrorHandler::stop();
-        }else{
+        } else {
             $result = array();
         }
-        $this->__searchEnd( $search );
-        return array_slice( $result, $offset, $limit );
+        $this->__searchEnd($search);
+        return array_slice($result, $offset, $limit);
     }
 
     /**
-     *
-     * @param string|AbstractFilter $filter     Filtre Ldap à appliquer
-     * @param string[]              $ou         Liste des organisations dans lesquelles rechercher
-     * @param string[]              $attributes Liste des attributs à retourner
+     * @param string|AbstractFilter $filter Filtre Ldap à appliquer
+     * @param string[] $ou Liste des organisations dans lesquelles rechercher
+     * @param string[] $attributes Liste des attributs à retourner
      * @return array
      * @throws Exception
      */
-    private function __searchBegin( $filter, array $ou=null, array $attributes=null )
+    private function __searchBegin($filter, array $ou = null, array $attributes = null)
     {
         /* Initialisation $basedn et $filter */
         if ($filter instanceof AbstractFilter) {
@@ -279,15 +283,18 @@ abstract class AbstractService implements
         if (is_string($ou)) $ou = array($ou);
         elseif (null === $ou) $ou = $this->ou;
 
-        if (1 == count($ou)){
-            $basedn = "ou=".$ou[0].",".$this->getLdap()->getBaseDn();
-        }else{
+        if (0 === count($ou)) {
+            $basedn = $this->getLdap()->getBaseDn();
+        }
+        elseif (1 === count($ou)) {
+            $basedn = "ou=" . $ou[0] . "," . $this->getLdap()->getBaseDn();
+        } else {
             $basedn = $this->getLdap()->getBaseDn();
             $ouFilter = '(&(|';
-            foreach( $ou as $ouItem ){
+            foreach ($ou as $ouItem) {
                 $ouFilter .= "(ou:dn:=$ouItem)";
             }
-            $filter = $ouFilter.")$filter)";
+            $filter = $ouFilter . ")$filter)";
         }
         $resource = $this->getLdap()->getResource();
         if (null === $attributes) $search = ldap_search($resource, $basedn, $filter);
@@ -295,7 +302,7 @@ abstract class AbstractService implements
         if ($search === false) {
             throw new Exception('searching: ' . $filter);
         }
-        return array( $resource, $search );
+        return array($resource, $search);
     }
 
     /**
@@ -303,7 +310,7 @@ abstract class AbstractService implements
      *
      * @param resource $search
      */
-    private function __searchEnd( $search )
+    private function __searchEnd($search)
     {
         ldap_free_result($search);
     }
@@ -315,7 +322,7 @@ abstract class AbstractService implements
      * @param resource $entry
      * @return null|array
      */
-    private function __getEntryAttributes( $resource, $entry )
+    private function __getEntryAttributes($resource, $entry)
     {
         if (!is_resource($resource)) {
             return null;
@@ -327,7 +334,7 @@ abstract class AbstractService implements
             $resource, $entry, $berIdentifier
         );
 
-        $attributes         = array();
+        $attributes = array();
 
         while ($name) {
             ErrorHandler::start(E_WARNING);
@@ -357,14 +364,18 @@ abstract class AbstractService implements
     /**
      * Retourne une entité du type correspondant au service courant
      *
-     * 
      * @param string $id
-     * @return Entity
+     * @return Entity|null
+     * @throws Exception
      */
-    public function get( $id )
+    public function get($id)
     {
         list($key) = Entity::getNodeParams($this->type);
-        return $this->getBy( $id, $key );
+        try {
+            return $this->getBy($id, $key);
+        } catch (Exception $e) {
+            return null;
+        }
     }
 
     /**
@@ -376,42 +387,42 @@ abstract class AbstractService implements
      * @return Entity
      * @throws Exception
      */
-    public function getBy( $value, $by )
+    public function getBy($value, $by)
     {
-        if ('dn' == $by){
+        if ('dn' == $by) {
             $value = Dn::factory($value)->get(0);
             $by = key($value);
             $value = current($value);
         }
 
         list($tmp, $classname) = Entity::getNodeParams($this->type);
-        list( $resource, $search ) = $this->__searchBegin( Filter::equals($by, $value), $this->ou, array('*','+') );
+        list($resource, $search) = $this->__searchBegin(Filter::equals($by, $value), $this->ou, array('*', '+'));
         $count = ldap_count_entries($resource, $search);
-        switch( $count ){
+        switch ($count) {
             case 0:
                 $this->__searchEnd($search);
-                throw new Exception( 'Entité de type "'.$this->type.'" ayant "'.$by.'"="'.$value.'" non trouvée');
+                throw new Exception('Entité de type "' . $this->type . '" ayant "' . $by . '"="' . $value . '" non trouvée');
             case 1:
-                $entry = ldap_first_entry($resource,$search);
+                $entry = ldap_first_entry($resource, $search);
                 $attributes = $this->__getEntryAttributes($resource, $entry);
-                $dn = ldap_get_dn( $resource, $entry );
+                $dn = ldap_get_dn($resource, $entry);
                 $attributes['dn'] = $dn;
                 $this->__searchEnd($search);
-                return new $classname( $this, \UnicaenLdap\Node::fromEntry($dn, $this->getLdap(), $attributes) );
+                return new $classname($this, \UnicaenLdap\Node::fromEntry($dn, $this->getLdap(), $attributes));
             default:
                 $this->__searchEnd($search);
-                throw new Exception( 'Plusieurs entités de type "'.$this->type.'" ayant "'.$by.'"="'.$value.'" ont été trouvées');
+                throw new Exception('Plusieurs entités de type "' . $this->type . '" ayant "' . $by . '"="' . $value . '" ont été trouvées');
         }
     }
 
     /**
      * Retourne un tableau d'entités de format array[ID] = Entite
-     * 
-     * @param string[] $ids   Tableau d'identifiants
+     *
+     * @param string[] $ids Tableau d'identifiants
      * @param string $orderBy Nom d'attribut à trier
      * @return Entity[]
      */
-    public function getAll( $ids, $orderBy=null )
+    public function getAll($ids, $orderBy = null)
     {
         list($key) = Entity::getNodeParams($this->type);
         return $this->getAllBy($ids, $key, $orderBy);
@@ -420,31 +431,36 @@ abstract class AbstractService implements
     /**
      * Retourne un tableau d'entités de format array[ID] = Entite
      *
-     * @param array     $values     Tableau des valeurs à rechercher
-     * @param string    $by         Nom de champ à rechercher
-     * @param string    $orderBy    Nom d'attribut à trier
+     * @param array $values Tableau des valeurs à rechercher
+     * @param string $by Nom de champ à rechercher
+     * @param string $orderBy Nom d'attribut à trier
      * @return Entity[]
      */
-    public function getAllBy( $values, $by, $orderBy=null )
+    public function getAllBy($values, $by, $orderBy = null)
     {
-        if (! is_array($values)) $values = array($values);
-        
+        if (!is_array($values)) $values = array($values);
+
         $data = array();
         $sortedData = array();
-        foreach( $values as $val ){
-            $valRes = $this->getBy( $val, $by );
-            if (! empty($orderBy)){
+        foreach ($values as $val) {
+            try {
+                $valRes = $this->getBy($val, $by);
+            }
+            catch (Exception $e) {
+                continue;
+            }
+            if (!empty($orderBy)) {
                 $sortedData[$valRes->getId()] = $valRes->get($orderBy);
             }
             $data[$valRes->getId()] = $valRes;
         }
-        if (! empty($orderBy)){
+        if (!empty($orderBy)) {
             asort($sortedData);
-            foreach( $sortedData as $id => $val ){
+            foreach ($sortedData as $id => $val) {
                 $sortedData[$id] = $data[$id];
             }
             return $sortedData;
-        }else{
+        } else {
             return $data;
         }
     }
@@ -455,7 +471,7 @@ abstract class AbstractService implements
      * @param string $id Identifiant de l'entité
      * @return boolean
      */
-    public function exists( $id )
+    public function exists($id)
     {
         list($key) = Entity::getNodeParams($this->type);
         return $this->existsBy($id, $key);
@@ -463,20 +479,22 @@ abstract class AbstractService implements
 
     /**
      * Détermine si une entité existe ou non en fonction d'un champ déterminé
-     * 
+     *
      * @param mixed $value Valeur de champ à rechercher
      * @param string $by Nom du champ à tester
      * @return boolean
+     * @throws Exception
+     * @throws \Zend\Ldap\Exception\LdapException
      */
-    public function existsBy( $value, $by )
+    public function existsBy($value, $by)
     {
-        if ('dn' == $by){
+        if ('dn' == $by) {
             $value = Dn::factory($value)->get(0);
             $by = key($value);
             $value = current($value);
         }
 
-        return 1 == $this->searchCount( Filter::equals($by, $value) );
+        return 1 == $this->searchCount(Filter::equals($by, $value));
     }
 
     /**
@@ -490,18 +508,22 @@ abstract class AbstractService implements
      * @return Entity
      * @throws Exception
      */
-    public function create( $id, $ou=null )
+    public function create(string $id, $ou = null)
     {
-        list($key, $classname) = Entity::getNodeParams($this->type);
-        if (empty($ou)){
-            if (count($this->ou) > 1){
-                throw new Exception('$ou non renseigné alors que le service couvre plusieurs Organizational Units (OU).');
+        if(!Entity::checkIdFormat($this->type, $id)) {
+            throw new Exception(sprintf("Le format de l'identifiant '%s' n'est pas correct.", $id));
+        }
+
+        if (empty($ou)) {
+            if (count($this->ou) > 1) {
+                throw new Exception('Le paramètre $ou doit être renseigné car le service couvre plusieurs Organizational Units (OU).');
             }
             $ou = $this->ou[0];
         }
-        $dn = $key.'='.$id.',ou='.$ou.','.$this->getLdap()->getBaseDn();
-        $classname = 'UnicaenLdap\\Entity\\'.$this->type;
-        return new $classname( $this, $dn);
+
+        list($key, $classname) = Entity::getNodeParams($this->type);
+        $dn = sprintf('%s=%s,ou=%s,%s', $key, $id, $ou, $this->getLdap()->getBaseDn());
+        return new $classname($this, $dn);
     }
 
     /**
diff --git a/src/UnicaenLdap/Service/Generic.php b/src/UnicaenLdap/Service/Generic.php
index 273102577c4173e9bf8b68ff7812ed6fd02dcae7..a94458260cd13b69e7fab4b477104b29ee237296 100644
--- a/src/UnicaenLdap/Service/Generic.php
+++ b/src/UnicaenLdap/Service/Generic.php
@@ -9,7 +9,19 @@ namespace UnicaenLdap\Service;
  */
 class Generic extends AbstractService
 {
+    /**
+     * Type de l'entité
+     *
+     * @var string
+     */
     protected $type = 'Generic';
 
-    protected $ou = array('generic');
+    /**
+     * Organizational Units
+     *
+     * @var array
+     */
+    protected $ou = [
+        'generic'
+    ];
 }
\ No newline at end of file
diff --git a/src/UnicaenLdap/Service/GenericFactory.php b/src/UnicaenLdap/Service/GenericFactory.php
index a3e2a7d243d4597d4260746d05e0a0b6e7d7820b..b3362da58afcbf34b0f8d074c6a777be18e6a639 100644
--- a/src/UnicaenLdap/Service/GenericFactory.php
+++ b/src/UnicaenLdap/Service/GenericFactory.php
@@ -27,22 +27,25 @@ class GenericFactory implements FactoryInterface
     {
         /**
          * @var Ldap $ldap
+         * @var Service\Group $ldapGroupService
          * @var Service\People $ldapPeopleService
+         * @var Service\Root $ldapRootService
          * @var Service\Structure $ldapStructureService
-         * @var Service\Group $ldapGroupService
          * @var Service\System $ldapSystemService
          */
-        $ldap = $container->get('ldap');
+        $ldap                   = $container->get('ldap');
+        $ldapGroupService       = $container->get('ldapServiceGroup');
         $ldapPeopleService      = $container->get('ldapServicePeople');
+        $ldapRootService        = $container->get('ldapServiceRoot');
         $ldapStructureService   = $container->get('ldapServiceStructure');
-        $ldapGroupService       = $container->get('ldapServiceGroup');
         $ldapSystemService      = $container->get('ldapServiceSystem');
 
         $service = new Generic();
         $service->setLdap($ldap);
+        $service->setLdapGroupService($ldapGroupService);
         $service->setLdapPeopleService($ldapPeopleService);
+        $service->setLdapRootService($ldapRootService);
         $service->setLdapStructureService($ldapStructureService);
-        $service->setLdapGroupService($ldapGroupService);
         $service->setLdapSystemService($ldapSystemService);
 
         return $service;
diff --git a/src/UnicaenLdap/Service/Group.php b/src/UnicaenLdap/Service/Group.php
index 48cb06316f229012b0aea4dc5609ef1b5cd9ecda..5a15593ec81f25cd2c4235052bacb1ab1a058f95 100644
--- a/src/UnicaenLdap/Service/Group.php
+++ b/src/UnicaenLdap/Service/Group.php
@@ -12,9 +12,21 @@ use UnicaenLdap\Collection;
  */
 class Group extends AbstractService
 {
+    /**
+     * Type de l'entité
+     *
+     * @var string
+     */
     protected $type = 'Group';
 
-    protected $ou = array('groups');
+    /**
+     * Organizational Units
+     *
+     * @var array
+     */
+    protected $ou = [
+        'groups'
+    ];
 
     /**
      * Filtre une liste de groupes pour ne retourner que ceux qui sont encore valides
diff --git a/src/UnicaenLdap/Service/GroupFactory.php b/src/UnicaenLdap/Service/GroupFactory.php
index 4966a39f1317d9fcb61f043ceb05b6456ca3f801..137503682953c44f866c4656125dc457f53dd461 100644
--- a/src/UnicaenLdap/Service/GroupFactory.php
+++ b/src/UnicaenLdap/Service/GroupFactory.php
@@ -27,23 +27,26 @@ class GroupFactory implements FactoryInterface
     {
         /**
          * @var Ldap $ldap
+         * @var Service\Generic $ldapGenericService
          * @var Service\People $ldapPeopleService
+         * @var Service\Root $ldapRootService
          * @var Service\Structure $ldapStructureService
          * @var Service\System $ldapSystemService
-         * @var Service\Generic $ldapGenericService
          */
-        $ldap = $container->get('ldap');
+        $ldap                   = $container->get('ldap');
+        $ldapGenericService     = $container->get('ldapServiceGeneric');
         $ldapPeopleService      = $container->get('ldapServicePeople');
+        $ldapRootService        = $container->get('ldapServiceRoot');
         $ldapStructureService   = $container->get('ldapServiceStructure');
         $ldapSystemService      = $container->get('ldapServiceSystem');
-        $ldapGenericService     = $container->get('ldapServiceGeneric');
 
         $service = new Group();
         $service->setLdap($ldap);
+        $service->setLdapGenericService($ldapGenericService);
         $service->setLdapPeopleService($ldapPeopleService);
+        $service->setLdapRootService($ldapRootService);
         $service->setLdapStructureService($ldapStructureService);
         $service->setLdapSystemService($ldapSystemService);
-        $service->setLdapGenericService($ldapGenericService);
 
         return $service;
     }
diff --git a/src/UnicaenLdap/Service/LdapPeopleServiceAwareInterface.php b/src/UnicaenLdap/Service/LdapPeopleServiceAwareInterface.php
index c08423e5ef66489043ab4ed109ae66a495df2b7a..a2f404fcf865b5f2b84443b843974919b6cb6014 100644
--- a/src/UnicaenLdap/Service/LdapPeopleServiceAwareInterface.php
+++ b/src/UnicaenLdap/Service/LdapPeopleServiceAwareInterface.php
@@ -16,4 +16,4 @@ interface LdapPeopleServiceAwareInterface
      * @return LdapPeopleService
      */
     public function getLdapPeopleService();
-}
+}
\ No newline at end of file
diff --git a/src/UnicaenLdap/Service/LdapRootServiceAwareInterface.php b/src/UnicaenLdap/Service/LdapRootServiceAwareInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..d086a4c83a2b7794a0835183c68dfe65a14f164e
--- /dev/null
+++ b/src/UnicaenLdap/Service/LdapRootServiceAwareInterface.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace UnicaenLdap\Service;
+
+use UnicaenLdap\Service\Root as LdapRootService;
+
+interface LdapRootServiceAwareInterface
+{
+    /**
+     * @param LdapRootService $ldapRootService
+     * @return mixed
+     */
+    public function setLdapRootService(LdapRootService $ldapRootService);
+
+    /**
+     * @return LdapRootService
+     */
+    public function getLdapRootService();
+}
\ No newline at end of file
diff --git a/src/UnicaenLdap/Service/LdapRootServiceAwareTrait.php b/src/UnicaenLdap/Service/LdapRootServiceAwareTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..39945b23986ab9a7a8405d32a18cceea7737a598
--- /dev/null
+++ b/src/UnicaenLdap/Service/LdapRootServiceAwareTrait.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace UnicaenLdap\Service;
+
+use UnicaenLdap\Service\Root as LdapRootService;
+
+trait LdapRootServiceAwareTrait
+{
+    /**
+     * @var LdapRootService
+     */
+    protected $ldapRootService;
+
+    /**
+     * @param LdapRootService $ldapRootService
+     */
+    public function setLdapRootService(LdapRootService $ldapRootService)
+    {
+        $this->ldapRootService = $ldapRootService;
+    }
+
+    /**
+     * @return LdapRootService
+     */
+    public function getLdapRootService()
+    {
+        return $this->ldapRootService;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenLdap/Service/LdapStructureServiceAwareInterface.php b/src/UnicaenLdap/Service/LdapStructureServiceAwareInterface.php
index 1ce386a6dc7f2942de821c437e876b0e85625051..496f863ec170ba9f197870c8f7f72b9434d21f99 100644
--- a/src/UnicaenLdap/Service/LdapStructureServiceAwareInterface.php
+++ b/src/UnicaenLdap/Service/LdapStructureServiceAwareInterface.php
@@ -16,4 +16,4 @@ interface LdapStructureServiceAwareInterface
      * @return LdapStructureService
      */
     public function getLdapStructureService();
-}
+}
\ No newline at end of file
diff --git a/src/UnicaenLdap/Service/People.php b/src/UnicaenLdap/Service/People.php
index a7c34fc53c7a469d16c1e0d4c4dda556aa9fa3c6..87bd8c96bd0027be6dc451b65ba52b1d2fb43b10 100644
--- a/src/UnicaenLdap/Service/People.php
+++ b/src/UnicaenLdap/Service/People.php
@@ -9,7 +9,28 @@ namespace UnicaenLdap\Service;
  */
 class People extends AbstractService
 {
+    /**
+     * Type de l'entité
+     *
+     * @var string
+     */
     protected $type = 'People';
 
-    protected $ou = array('people', 'deactivated', 'blocked');
+    /**
+     * Organizational Units
+     *
+     * @var array
+     */
+    protected $ou = [
+        'people',
+        'deactivated',
+        'blocked'
+    ];
+
+    public function checkPassword($value)
+    {
+        /**
+         * @todo implémenter la fonction de vérification d'un mot de passe
+         */
+    }
 }
\ No newline at end of file
diff --git a/src/UnicaenLdap/Service/PeopleFactory.php b/src/UnicaenLdap/Service/PeopleFactory.php
index b1de6d368f346bf567d326ace635ef0fa41276aa..ab9149a46a934179543dc23713516984c227f397 100644
--- a/src/UnicaenLdap/Service/PeopleFactory.php
+++ b/src/UnicaenLdap/Service/PeopleFactory.php
@@ -32,23 +32,26 @@ class PeopleFactory implements FactoryInterface
     {
         /**
          * @var Ldap $ldap
-         * @var Service\Structure $ldapStructureService
+         * @var Service\Generic $ldapGenericService
          * @var Service\Group $ldapGroupService
+         * @var Service\Root $ldapRootService
+         * @var Service\Structure $ldapStructureService
          * @var Service\System $ldapSystemService
-         * @var Service\Generic $ldapGenericService
          */
-        $ldap = $container->get('ldap');
-        $ldapStructureService   = $container->get('ldapServiceStructure');
+        $ldap                   = $container->get('ldap');
+        $ldapGenericService     = $container->get('ldapServiceGeneric');
         $ldapGroupService       = $container->get('ldapServiceGroup');
+        $ldapRootService        = $container->get('ldapServiceRoot');
+        $ldapStructureService   = $container->get('ldapServiceStructure');
         $ldapSystemService      = $container->get('ldapServiceSystem');
-        $ldapGenericService     = $container->get('ldapServiceGeneric');
 
         $service = new People();
         $service->setLdap($ldap);
-        $service->setLdapStructureService($ldapStructureService);
+        $service->setLdapGenericService($ldapGenericService);
         $service->setLdapGroupService($ldapGroupService);
+        $service->setLdapRootService($ldapRootService);
+        $service->setLdapStructureService($ldapStructureService);
         $service->setLdapSystemService($ldapSystemService);
-        $service->setLdapGenericService($ldapGenericService);
 
         return $service;
     }
diff --git a/src/UnicaenLdap/Service/Root.php b/src/UnicaenLdap/Service/Root.php
new file mode 100644
index 0000000000000000000000000000000000000000..3a1a9b1c698d179051ad4cbb505a4b8296ae72f1
--- /dev/null
+++ b/src/UnicaenLdap/Service/Root.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace UnicaenLdap\Service;
+
+use UnicaenLdap\Entity\Entity;
+use UnicaenLdap\Entity\Root as RootEntity;
+use UnicaenLdap\Entity\Structure as StructureEntity;
+use UnicaenLdap\Ldap;
+
+/**
+ * Classe de service pour la racine l'annuaire LDAP.
+ *
+ * @author David Surville <david.surville@unicaen.fr>
+ */
+class Root extends AbstractService
+{
+    /**
+     * Type de l'entité
+     *
+     * @var string
+     */
+    protected $type = 'Root';
+
+    /**
+     * Distinguished Name de l'établissement
+     *
+     * @var string
+     */
+    protected $etablissement_dn = 'dc=unicaen,dc=fr';
+
+    /**
+     * Domaine de l'établissement
+     *
+     * @var string
+     */
+    protected $etablissement_domain = 'unicaen.fr';
+
+    /**
+     * Code UAI de l'établissement
+     *
+     * @var string
+     */
+    protected $etablissement_uai = '0141408E';
+
+    /**
+     * Retourne le type du service
+     *
+     * @return string
+     */
+    public function getType()
+    {
+        return $this->type;
+    }
+
+    /**
+     * @return string
+     */
+    public function getEtablissementDN()
+    {
+        return $this->etablissement_dn;
+    }
+
+    /**
+     * @return string
+     */
+    public function getEtablissementDomain()
+    {
+        return $this->etablissement_domain;
+    }
+
+    /**
+     * @return string
+     */
+    public function getEtablissementUAI()
+    {
+        return $this->etablissement_uai;
+    }
+
+    /**
+     * Retourne l'entité racine
+     *
+     * @return RootEntity
+     */
+    public function getEntity()
+    {
+        list($key) = Entity::getNodeParams($this->type);
+        return $this->getBy($this->etablissement_dn, 'dn');
+    }
+
+    /**
+     * Retourne l'entité dans la branche "structures" liée à l'établissement
+     *
+     * @return StructureEntity
+     */
+    public function getStructureEntity()
+    {
+        return $this->getLdapStructureService()->getStructureMere();
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenLdap/Service/RootFactory.php b/src/UnicaenLdap/Service/RootFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..a289953b22a0d8b97ce5b32ecd91400bcb3feb95
--- /dev/null
+++ b/src/UnicaenLdap/Service/RootFactory.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace UnicaenLdap\Service;
+
+use Interop\Container\ContainerInterface;
+use UnicaenLdap\Ldap;
+use UnicaenLdap\Service;
+use Zend\ServiceManager\FactoryInterface;
+use Zend\ServiceManager\ServiceLocatorInterface;
+
+/**
+ *
+ *
+ * @author Unicaen
+ */
+class RootFactory implements FactoryInterface
+{
+    public function createService(ServiceLocatorInterface $serviceLocator)
+    {
+        return $this->__invoke($serviceLocator, '?');
+    }
+
+    /**
+     * Create service
+     *
+     * @param ContainerInterface $container
+     * @param string $requestedName
+     * @param array|null $options
+     * @return object|System
+     */
+    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
+    {
+        /**
+         * @var Ldap $ldap
+         * @var Service\Structure $ldapStructureService
+         */
+        $ldap                   = $container->get('ldap');
+        $ldapStructureService   = $container->get('ldapServiceStructure');
+
+        $service = new Root();
+        $service->setLdap($ldap);
+        $service->setLdapStructureService($ldapStructureService);
+
+        return $service;
+    }
+}
diff --git a/src/UnicaenLdap/Service/Structure.php b/src/UnicaenLdap/Service/Structure.php
index 37d5bf16dfea813a49e611dff966de435eeb7641..d88345f0f4650acb6a5c4342fc8bc1a1a9ed3319 100644
--- a/src/UnicaenLdap/Service/Structure.php
+++ b/src/UnicaenLdap/Service/Structure.php
@@ -2,6 +2,9 @@
 
 namespace UnicaenLdap\Service;
 
+use UnicaenLdap\Entity\Entity;
+use UnicaenLdap\Entity\Structure as StructureEntity;
+
 /**
  * Classe regroupant les opérations de recherche de structures dans l'annuaire LDAP.
  *
@@ -9,17 +12,71 @@ namespace UnicaenLdap\Service;
  */
 class Structure extends AbstractService
 {
+    /**
+     * Type de l'entité
+     *
+     * @var string
+     */
     protected $type = 'Structure';
 
-    protected $ou = array('structures');
+    /**
+     * Organizational Units
+     *
+     * @var array
+     */
+    protected $ou = [
+        'structures'
+    ];
+
+    /**
+     * Préfixe utilisé pour le code des structures
+     *
+     * @var string
+     */
+    protected $code_structure_prefixe = 'HS_';
+
+    /**
+     * Préfixe utilisé pour le code des modules d'enseignement
+     *
+     * @var string
+     */
+    protected $code_module_enseignement_prefixe = 'AE_';
+
+    /**
+     * Code de la structure lié à l'établissement
+     *
+     * @var string
+     */
+    protected $code_structure_mere = 'UNIV';
+
+
+    /**
+     * Retourne le préfixe utilisé pour les codes des structures
+     *
+     * @return string
+     */
+    public function getCodeStructurePrefixe()
+    {
+        return $this->code_structure_prefixe;
+    }
+
+    /**
+     * Retourne le préfixe utilisé pour les codes des modules d'enseignement
+     *
+     * @return string
+     */
+    public function getCodeModuleEnseignementPrefixe()
+    {
+        return $this->code_module_enseignement_prefixe;
+    }
 
     /**
      * Retourne la structure mère : Université
      *
-     * @return \UnicaenLdap\Entity\Structure
+     * @return StructureEntity
      */
-    public function getUniv()
+    public function getStructureMere()
     {
-        return $this->get('HS_UNIV');
+        return $this->get(sprintf('%s%s', $this->code_structure_prefixe, $this->code_structure_mere));
     }
 }
\ No newline at end of file
diff --git a/src/UnicaenLdap/Service/StructureFactory.php b/src/UnicaenLdap/Service/StructureFactory.php
index 7759770f09479d5130bd90bc1edefe42f9dfa54b..5bbf00586ff602612367701290dbe2b20a7a0107 100644
--- a/src/UnicaenLdap/Service/StructureFactory.php
+++ b/src/UnicaenLdap/Service/StructureFactory.php
@@ -32,23 +32,26 @@ class StructureFactory implements FactoryInterface
     {
         /**
          * @var Ldap $ldap
-         * @var Service\People $ldapPeopleService
+         * @var Service\Generic $ldapGenericService
          * @var Service\Group $ldapGroupService
+         * @var Service\People $ldapPeopleService
+         * @var Service\Root $ldapRootService
          * @var Service\System $ldapSystemService
-         * @var Service\Generic $ldapGenericService
          */
-        $ldap = $container->get('ldap');
-        $ldapPeopleService      = $container->get('ldapServicePeople');
+        $ldap                   = $container->get('ldap');
+        $ldapGenericService     = $container->get('ldapServiceGeneric');
         $ldapGroupService       = $container->get('ldapServiceGroup');
+        $ldapPeopleService      = $container->get('ldapServicePeople');
+        $ldapRootService        = $container->get('ldapServiceRoot');
         $ldapSystemService      = $container->get('ldapServiceSystem');
-        $ldapGenericService     = $container->get('ldapServiceGeneric');
 
         $service = new Structure();
         $service->setLdap($ldap);
-        $service->setLdapPeopleService($ldapPeopleService);
+        $service->setLdapGenericService($ldapGenericService);
         $service->setLdapGroupService($ldapGroupService);
+        $service->setLdapPeopleService($ldapPeopleService);
+        $service->setLdapRootService($ldapRootService);
         $service->setLdapSystemService($ldapSystemService);
-        $service->setLdapGenericService($ldapGenericService);
 
         return $service;
     }
diff --git a/src/UnicaenLdap/Service/System.php b/src/UnicaenLdap/Service/System.php
index 2e5d54760db8737e026fbb49c97a8895e67d78c3..7f72614887c87475fc1b02c94828b21f6eb0341b 100644
--- a/src/UnicaenLdap/Service/System.php
+++ b/src/UnicaenLdap/Service/System.php
@@ -9,7 +9,19 @@ namespace UnicaenLdap\Service;
  */
 class System extends AbstractService
 {
+    /**
+     * Type de l'entité
+     *
+     * @var string
+     */
     protected $type = 'System';
 
-    protected $ou = array('system');
+    /**
+     * Organizational Units
+     *
+     * @var array
+     */
+    protected $ou = [
+        'system'
+    ];
 }
\ No newline at end of file
diff --git a/src/UnicaenLdap/Service/SystemFactory.php b/src/UnicaenLdap/Service/SystemFactory.php
index 73953d8d7534440506ccac06b98418db92ddf187..e08a2c254a54d36ab15d8efa42bfc6ecea68c502 100644
--- a/src/UnicaenLdap/Service/SystemFactory.php
+++ b/src/UnicaenLdap/Service/SystemFactory.php
@@ -32,22 +32,25 @@ class SystemFactory implements FactoryInterface
     {
         /**
          * @var Ldap $ldap
-         * @var Service\People $ldapPeopleService
-         * @var Service\Structure $ldapStructureService
          * @var Service\Group $ldapGroupService
          * @var Service\Generic $ldapGenericService
+         * @var Service\People $ldapPeopleService
+         * @var Service\Root $ldapRootService
+         * @var Service\Structure $ldapStructureService
          */
-        $ldap = $container->get('ldap');
+        $ldap                   = $container->get('ldap');
+        $ldapGenericService     = $container->get('ldapServiceGeneric');
+        $ldapGroupService       = $container->get('ldapServiceGroup');
         $ldapPeopleService      = $container->get('ldapServicePeople');
+        $ldapRootService        = $container->get('ldapServiceRoot');
         $ldapStructureService   = $container->get('ldapServiceStructure');
-        $ldapGroupService       = $container->get('ldapServiceGroup');
-        $ldapGenericService     = $container->get('ldapServiceGeneric');
 
         $service = new System();
         $service->setLdap($ldap);
         $service->setLdapPeopleService($ldapPeopleService);
         $service->setLdapStructureService($ldapStructureService);
         $service->setLdapGroupService($ldapGroupService);
+        $service->setLdapRootService($ldapRootService);
         $service->setLdapGenericService($ldapGenericService);
 
         return $service;
diff --git a/src/UnicaenLdap/Util.php b/src/UnicaenLdap/Util.php
new file mode 100644
index 0000000000000000000000000000000000000000..4791e52b3f4bb21426710352ef0daa0921f4d39f
--- /dev/null
+++ b/src/UnicaenLdap/Util.php
@@ -0,0 +1,113 @@
+<?php
+
+namespace UnicaenLdap;
+
+class Util
+{
+    static public function removeAccents($string)
+    {
+        if (!preg_match('/[\x80-\xff]/', $string))
+            return $string;
+
+        $chars = [
+            // Decompositions for Latin-1 Supplement
+            chr(195) . chr(128) => 'A', chr(195) . chr(129) => 'A',
+            chr(195) . chr(130) => 'A', chr(195) . chr(131) => 'A',
+            chr(195) . chr(132) => 'A', chr(195) . chr(133) => 'A',
+            chr(195) . chr(135) => 'C', chr(195) . chr(136) => 'E',
+            chr(195) . chr(137) => 'E', chr(195) . chr(138) => 'E',
+            chr(195) . chr(139) => 'E', chr(195) . chr(140) => 'I',
+            chr(195) . chr(141) => 'I', chr(195) . chr(142) => 'I',
+            chr(195) . chr(143) => 'I', chr(195) . chr(145) => 'N',
+            chr(195) . chr(146) => 'O', chr(195) . chr(147) => 'O',
+            chr(195) . chr(148) => 'O', chr(195) . chr(149) => 'O',
+            chr(195) . chr(150) => 'O', chr(195) . chr(153) => 'U',
+            chr(195) . chr(154) => 'U', chr(195) . chr(155) => 'U',
+            chr(195) . chr(156) => 'U', chr(195) . chr(157) => 'Y',
+            chr(195) . chr(159) => 's', chr(195) . chr(160) => 'a',
+            chr(195) . chr(161) => 'a', chr(195) . chr(162) => 'a',
+            chr(195) . chr(163) => 'a', chr(195) . chr(164) => 'a',
+            chr(195) . chr(165) => 'a', chr(195) . chr(167) => 'c',
+            chr(195) . chr(168) => 'e', chr(195) . chr(169) => 'e',
+            chr(195) . chr(170) => 'e', chr(195) . chr(171) => 'e',
+            chr(195) . chr(172) => 'i', chr(195) . chr(173) => 'i',
+            chr(195) . chr(174) => 'i', chr(195) . chr(175) => 'i',
+            chr(195) . chr(177) => 'n', chr(195) . chr(178) => 'o',
+            chr(195) . chr(179) => 'o', chr(195) . chr(180) => 'o',
+            chr(195) . chr(181) => 'o', chr(195) . chr(182) => 'o',
+            chr(195) . chr(182) => 'o', chr(195) . chr(185) => 'u',
+            chr(195) . chr(186) => 'u', chr(195) . chr(187) => 'u',
+            chr(195) . chr(188) => 'u', chr(195) . chr(189) => 'y',
+            chr(195) . chr(191) => 'y',
+            // Decompositions for Latin Extended-A
+            chr(196) . chr(128) => 'A', chr(196) . chr(129) => 'a',
+            chr(196) . chr(130) => 'A', chr(196) . chr(131) => 'a',
+            chr(196) . chr(132) => 'A', chr(196) . chr(133) => 'a',
+            chr(196) . chr(134) => 'C', chr(196) . chr(135) => 'c',
+            chr(196) . chr(136) => 'C', chr(196) . chr(137) => 'c',
+            chr(196) . chr(138) => 'C', chr(196) . chr(139) => 'c',
+            chr(196) . chr(140) => 'C', chr(196) . chr(141) => 'c',
+            chr(196) . chr(142) => 'D', chr(196) . chr(143) => 'd',
+            chr(196) . chr(144) => 'D', chr(196) . chr(145) => 'd',
+            chr(196) . chr(146) => 'E', chr(196) . chr(147) => 'e',
+            chr(196) . chr(148) => 'E', chr(196) . chr(149) => 'e',
+            chr(196) . chr(150) => 'E', chr(196) . chr(151) => 'e',
+            chr(196) . chr(152) => 'E', chr(196) . chr(153) => 'e',
+            chr(196) . chr(154) => 'E', chr(196) . chr(155) => 'e',
+            chr(196) . chr(156) => 'G', chr(196) . chr(157) => 'g',
+            chr(196) . chr(158) => 'G', chr(196) . chr(159) => 'g',
+            chr(196) . chr(160) => 'G', chr(196) . chr(161) => 'g',
+            chr(196) . chr(162) => 'G', chr(196) . chr(163) => 'g',
+            chr(196) . chr(164) => 'H', chr(196) . chr(165) => 'h',
+            chr(196) . chr(166) => 'H', chr(196) . chr(167) => 'h',
+            chr(196) . chr(168) => 'I', chr(196) . chr(169) => 'i',
+            chr(196) . chr(170) => 'I', chr(196) . chr(171) => 'i',
+            chr(196) . chr(172) => 'I', chr(196) . chr(173) => 'i',
+            chr(196) . chr(174) => 'I', chr(196) . chr(175) => 'i',
+            chr(196) . chr(176) => 'I', chr(196) . chr(177) => 'i',
+            chr(196) . chr(178) => 'IJ', chr(196) . chr(179) => 'ij',
+            chr(196) . chr(180) => 'J', chr(196) . chr(181) => 'j',
+            chr(196) . chr(182) => 'K', chr(196) . chr(183) => 'k',
+            chr(196) . chr(184) => 'k', chr(196) . chr(185) => 'L',
+            chr(196) . chr(186) => 'l', chr(196) . chr(187) => 'L',
+            chr(196) . chr(188) => 'l', chr(196) . chr(189) => 'L',
+            chr(196) . chr(190) => 'l', chr(196) . chr(191) => 'L',
+            chr(197) . chr(128) => 'l', chr(197) . chr(129) => 'L',
+            chr(197) . chr(130) => 'l', chr(197) . chr(131) => 'N',
+            chr(197) . chr(132) => 'n', chr(197) . chr(133) => 'N',
+            chr(197) . chr(134) => 'n', chr(197) . chr(135) => 'N',
+            chr(197) . chr(136) => 'n', chr(197) . chr(137) => 'N',
+            chr(197) . chr(138) => 'n', chr(197) . chr(139) => 'N',
+            chr(197) . chr(140) => 'O', chr(197) . chr(141) => 'o',
+            chr(197) . chr(142) => 'O', chr(197) . chr(143) => 'o',
+            chr(197) . chr(144) => 'O', chr(197) . chr(145) => 'o',
+            chr(197) . chr(146) => 'OE', chr(197) . chr(147) => 'oe',
+            chr(197) . chr(148) => 'R', chr(197) . chr(149) => 'r',
+            chr(197) . chr(150) => 'R', chr(197) . chr(151) => 'r',
+            chr(197) . chr(152) => 'R', chr(197) . chr(153) => 'r',
+            chr(197) . chr(154) => 'S', chr(197) . chr(155) => 's',
+            chr(197) . chr(156) => 'S', chr(197) . chr(157) => 's',
+            chr(197) . chr(158) => 'S', chr(197) . chr(159) => 's',
+            chr(197) . chr(160) => 'S', chr(197) . chr(161) => 's',
+            chr(197) . chr(162) => 'T', chr(197) . chr(163) => 't',
+            chr(197) . chr(164) => 'T', chr(197) . chr(165) => 't',
+            chr(197) . chr(166) => 'T', chr(197) . chr(167) => 't',
+            chr(197) . chr(168) => 'U', chr(197) . chr(169) => 'u',
+            chr(197) . chr(170) => 'U', chr(197) . chr(171) => 'u',
+            chr(197) . chr(172) => 'U', chr(197) . chr(173) => 'u',
+            chr(197) . chr(174) => 'U', chr(197) . chr(175) => 'u',
+            chr(197) . chr(176) => 'U', chr(197) . chr(177) => 'u',
+            chr(197) . chr(178) => 'U', chr(197) . chr(179) => 'u',
+            chr(197) . chr(180) => 'W', chr(197) . chr(181) => 'w',
+            chr(197) . chr(182) => 'Y', chr(197) . chr(183) => 'y',
+            chr(197) . chr(184) => 'Y', chr(197) . chr(185) => 'Z',
+            chr(197) . chr(186) => 'z', chr(197) . chr(187) => 'Z',
+            chr(197) . chr(188) => 'z', chr(197) . chr(189) => 'Z',
+            chr(197) . chr(190) => 'z', chr(197) . chr(191) => 's'
+        ];
+
+        $string = strtr($string, $chars);
+
+        return $string;
+    }
+}
\ No newline at end of file