Salut à tous,

Je travaille sur un projet depuis pas mal de temps qui doit, pour des raisons de sécurité et de confidentialité des données, gérer une bdd par client. L'application a une bdd commune contenant toutes les data communes aux clients ainsi qu'une bdd client propre à chaque utilisateur. Je voulais partager la façon dont j'ai géré ces contraintes en essayant de rester le plus proche des conventions Cake.

Le principe :

  • A la connexion, on logge l'utilisateur suivant les data présente en bdd et on fait diverses verifications (activation du compte par mail, abonnement en cours / droits)
  • on stocke dans la session les paramètres utiles au reste de l'appli (dont le nom de sa bdd)
  • on configure son accès bdd personnel au niveau de l'appModel en lançant un construct.

Voici une partie du code :

Fonction login de UsersController.php

/**
* Fonction de Login
*
*/
public function login() {
    if ($this->request->is('post')) {
        if ($this->Auth->login()) {

            if ($this->Auth->user('active') == 1) {

            // On récupère aussi d'autres infos utiles pour le reste de l'appli
                $this->loadModel('Subscription');
                $d = $this->Subscription->find('first', array(
                    'conditions'    =>  array(
                        'company_id'    =>  $_SESSION'Auth']'User']'company_id']
                        )
                    ));
                // Il faut aussi vérifier que l'abonnement existe et qu'il n'est pas expiré
                if (isset($d'Subscription']) && (strtotime($d'Subscription']'expires']) > strtotime('now'))) {
                    $this->User->id = $this->Auth->User('id');
                    // On sauvegarde date et heure de connexion
                    $this->User->saveField('lastlogin', date("Y-m-d H:i:s"), array('callbacks' => 'false'));
                    if (isset($_SESSION'Auth']'User']'Company']'db'])) {
                        // On a besoin de l'id de la compagnie, le nom de sa db et de son nom pour d'autres fonction dans l'appli
                        $this->Session->write('user.companyDB', $_SESSION'Auth']'User']'Company']'db']); // Pour la configuration du choix la bdd (données client)
                        $this->Session->write('user.companyId', $_SESSION'Auth']'User']'company_id']); // nécessaire pour certains enregistrements
                        $this->Session->write('user.companyName', $_SESSION'Auth']'User']'Company']'name']); // Affichage du nom dans le layout
                        $this->Session->write('user.role', $_SESSION'Auth']'User']'role']); // Role 
                        // Autres paramètres utiles à l'appli
                    }
                } else {
                                // Erreur si pb avec l'abonnement
                    $this->Session->setFlash(__('We encoutered a problem while retrieving your subscription informations'),
                                        "alert",
                                        array('plugin' => 'TwitterBootstrap', 'class' => 'alert-error'));
                    $this->Auth->logout();
                }               
                $this->redirect($this->Auth->loginRedirect);
            } else {
                    // Erreur si compte n'a pas été activé. Il faut le délogger et envoyer message d'erreur approprié
                    $this->Auth->logout();
                    $this->Session->setFlash(__('Your Account has not been activated yet. Please check your mail !'), 
                            'alert',
                            array(
                                'plugin' => 'TwitterBootstrap',
                                'class' => 'alert-error'
                )
                            );
                            $this->redirect(array('controller' => 'users', 'action' => 'login'));
                        }
                }

            else {
                // Erreur si pb au login
                $this->Session->setFlash(__('Incorrect login / password !'),
                        'alert',
                        array(
                            'plugin' => 'TwitterBootstrap',
                            'class' => 'alert-error'
                            )
                        );
            }
        }

        if ($this->Session->read('Auth.User')) {
            $this->Session->setFlash('You are logged in!');
            return true;//$this->redirect($this->Auth->loginRedirect);      
        }

}

Au niveau de lAppModel.php, si le model est un model 'client', cad un model qui va chercher ses données dans la bdd client, il faut configurer la connexion pour aller dans la bonne bdd. Les models 'client' ont toujours cette variable : public $useDbConfig = 'defaultCompany';

class AppModel extends Model {
    public $actsAs = array('Containable');

    // On lance un construct pour charger la connexion à la bdd de la compagnie
    function __construct($id = false, $table = null, $ds = null) {
        // Si le modèle demandé appartient à la compagnie 
        if ($this->useDbConfig == 'defaultCompany') {
            // ON vérifie qu'il n'a pas déjà été instancié
            if (!in_array('defaultCompany', ConnectionManager::sourceList())) {
                // On charge les données de session, cela ne respecte pas les règle MVC, mais comment faire ?
                App::uses('CakeSession', 'Model/Datasource');
                $db = CakeSession::read('user.companyDB');

                ConnectionManager::create ('defaultCompany', array(
                        'datasource' => 'Database/Mysql',
                        'persistent' => false,
                        'host' => 'localhost',
                        'login' => ' *****',
                        'password' => ' *******',
                        'database' => $db,
                        'encoding' => 'utf8')
                );
            }
        }
        parent::__construct($id, $table, $ds);
    }

Maintenant, si on veut faire des recherches croisées sur toutes les bdd client, j'ai eu quelques soucis pour changer à la volée les connexions bdd sur un même modele. Je n'ai pas trop le temps de faire des explications détaillées alors je poste le code (pas fini encore mais fonctionnel) tel quel. Le principe : un user peut interroger la base complète pour savoir si d'autres user possèdent un équipement identique (il peut interroger sur plusieurs modèles). On fait donc la même requête en bdd mais on doit modifier la base que l'on interroge à chaque itération. Un exemple vaut mieux qu'un grand discours :

* 
* Fonction de recherche sur la base complète
*
* @param string $name : mot(s) à rechercher dans la base
* @param $reference : reference ou No de série
* @param array $models -> models dans lesquels on doit effectuer la recherche
*
* @return array of results
*/
    public function searchDatabase() {

                if ($this->request->is('Post')) {

                    // Récupération des données postées

                    // Modèles sur lesquels on doit faire la recherche
                    $models = $this->request->data'models'];

                    $name = "%".$this->request->data'Search']'name']."%";
                    $ref = $this->request->data'Search']'ref'];

                    if (!empty($models)) {

                        // On construit un tableau des bdd qui partagent leurs données
                        $this->loadModel('Company');
                        $databases = $this->Company->find('list', array(
                            'fields' => array(
                                'db'
                            ),
                            'conditions' => array(
                                'share_data' => 1
                            ),
                        ));

                        debug($this->request->data);
                        debug($name);
                        debug($models);
                        debug($databases);

                        // On parcourt les bases à la recherche du graal
                        foreach ($databases as $db) {

                            // Création de la connexion bdd spécifique à cette recherche
                            ConnectionManager::create ('datasearch', array(
                        'datasource' => 'Database/Mysql',
                        'persistent' => false,
                        'host' => 'localhost',
                        'login' => ' *****',
                        'password' => ' *******',
                        'database' => $db,
                        'encoding' => 'utf8')
                );
                            // Pour chaque bdd que l'on interroge on parcourt les modèles demandés
                                foreach ($models as $model) {
                                    switch ($model) {
                                    case 'Equipment':
                                        $this->loadModel('Equipment');
                                        // On force le modèle à utiliser la connexion configuré ci-dessus
                                        $this->Equipment->useDbConfig = 'datasearch';

                                        // On force le changement de bdd, sinon on reste sur la première (je ne sais pas pourquoi, mais sinon cela
                                        // ne fonctionne pas.)
                                        $this->Equipment->schemaName = $db;

                                        $results$db]'Equipments'] = $this->Equipment->find('all', array(
                                            'fields' => array(
                                                'id', 'name', 'serial', 'other_reference'
                                            ),
                                            'contain' => array(
                                            ),
                                            'conditions' => array(
                                                'Equipment.name LIKE' => $name,

                                            )
                                            ));

                                    }

                                }
                                //ConnectionManager::drop('datasearch');

                        }
                        debug($results);
                        die('lol');

Voilà, j'ai passé pas mal de temps pour arriver à faire fonctionner tout ça, en compilant des solutions de GrafikArt ou d'autres sources et bien sûr un peu d'huile de coude. Si je peux donner des idées ou des pistes, c'est super, si vous avez des remarques, je suis preneur aussi.

A +

4 réponses


giizmo
Réponse acceptée

salut je trouve que ça a l'air super pour gerer des sous domaine ou autres peut être que de le développé en tuto serait un plus avec une explication en prime

en tout cas si ça fonctionne faudrait en faire un

+1

filopp
Auteur

Je confirme que cela fonctionne pas mal, j'ai une version en ligne avec quelques clients test et aucun souci pour le moment. Par contre, je suis overbooké et n'ai vraiment pas le temps d'aller plus loin dans les explications, sauf si vraiment quelqu'un s'intéresse de près au sujet.

A +

Bonsoir.
Il y a certaines manipulations dans tes fonctions que tu pourrais éviter dans tes fonctions, comme par exemple ton :

if ($this->Auth->user('active') == 1)

que tu pourrais placer dans le composant Auth de ton AppController avec un

'scope' => array('User.active' => 1)

Ensuite pourquoi tu réécris des valeurs de sessions qui existent déjà ?
Car avec

$this->Session->write('user.companyDB', $_SESSION'Auth']'User']'Company']'db']);

et

$_SESSION'Auth']'User']'Company']'db']

Tu fais une double manipulation, tu réécris la valeur de session de la BDD sur la même valeur de la BDD.
Avec

$this->Session->write('user.companyDB')

tu écris dans la session et avec

$_SESSION'Auth']'User']'Company']'db']

tu récupère la valeur dans la session, je ne vois pas l'intérêt de réécrire une valeur dans la session alors qu'elle y existe déjà et tu fais la même chose pour plusieurs valeurs.
Pour ce qui est de ton

// On charge les données de session, cela ne respecte pas les règle MVC, mais comment faire ?

Il te suffit de charger le composant dans ton AppController.

filopp
Auteur

Meric pour ces précisions, je note tout ça.