Plus loin avec le sfDoctrineGuardPlugin : performances et astuce

Nous aborderons dans cet article 2 thèmes :


Chargement de données à l’authentification

Souvent, il est utile de pouvoir charger des informations relatives à l’utilisateur, juste après son authentification. Cela permet de stocker des données utilisées à plusieurs endroits sans avoir à les recharger à chaque utilisation.
A quel endroit peut-on faire ça ?
Il faut ajouter un module sfGuardAuth à votre application, et surdéfinir l’action, comme expliqué sur la documentation du plugin sfDoctrineGuardPlugin, au paragraphe « Customize sfGuardAuth module actions ».

Dans cette action, vous devez surdéfinir la méthode executeSignin et copier/coller la méthode mère correspondante depuis BasesfGuardAuthActions.

Ensuite, il ne vous reste qu’à ajouter votre code comme suit :

if ( $this->getUser()->isAuthenticated() )
{
    //ajouter votre code de chargement ici
    $this->getUser()->setAttribute('langue', 'fr');
}

Amélioration des performances par rapport à l’utilisation des permissions

Le problème du plugin est qu’à chaque fois qu’on teste une permission (hasPermission), il fait une requête en BDD, pas terrible…

J’ai donc amélioré tout ça en mettant en session les permissions de l’utilisateur.

Plus précisément, il s’agit de ne lancer qu’une seule requête permettant de récupérer les permissions spécifiques à l’utilisateur et les permissions des groupes auxquels appartient l’utilisateur. On stocke le tout en session pour éviter d’avoir à refaire la requête à chaque appel de page.

Pour se faire :
1. Ajouter une constante dans votre myUser :

/**
* Namespace de stockage en session du plugin sfGuard
*/
const NS_SF_GUARD = 'sfGuardSecurityUser';

1. Modifier le contenu du modèle sfGuardUser (lib/model/doctrine/sfDoctrineGuardPlugin/sfGuardUser.class.php :

/**
 * sfGuardUser
 *
 * This class has been auto-generated by the Doctrine ORM Framework
 *
 * @package    PAPEt
 * @subpackage model
 * @author     Micropole-Univers
 * @version    SVN: $Id: Builder.php 6820 2009-11-30 17:27:49Z jwage $
 */
class sfGuardUser extends PluginsfGuardUser
{
    /**
     * Returns whether or not the user has the given permission.
     * @return boolean
     */
    public function hasPermission($name)
    {
        $this->loadGroupsAndPermissions();

        $sf_user = sfContext::getInstance()->getUser();
        $debug = sfConfig::get('app_debug_permissions', false);

        if (!$sf_user->hasAttribute('all_permissions', myUser::NS_SF_GUARD))
        {

            if ( $debug )
                $prisEnSession = '(nocache)';
        }
        else
        {
            if ( $debug )
                $prisEnSession = '(cache)';
        }

        if ( $debug )
        {
            $isset = var_export(isset($this->_allPermissions[$name]), true);
            echo 'Perm['.$name.', '.$isset.'] '.$prisEnSession.'';
        }

        return isset($this->_allPermissions[$name]);
    }

    /**
     * @return array Charge les permissions de l'utilisateur, les permissions de l'utilisateur+groupes réunies et les groupes
     * la méthode getAllPermissions est également surdéfinie dans le modèle + requête avec toutes les jointures dans la DAO
     *
     * Les groupes et permissions sont désormais tous chargés dans getAllPermissions
     * cette dernière stoque les données dans la session de l'utilisateur
     */
    public function loadGroupsAndPermissions()
    {
        $this->getAllPermissions();
    }

    /**
     * La méthode proposée par le plugin sfDoctrineGuard n'est pas
     * performante puisque faisant une requête pour récupérer les permissions de l'utilisateur,
     * puis une pour récupérer les groupes, puis une pour récupérer les permissions de chaque groupe.
     * En plus, à chaque appel de page, il lance la requête pour récupérer les permissions => on va mettre un peu de cache...
     *
     * En gros le but, c'est d'avoir :
     * - les permissions spécifiques de l'utilisateur,
     * - les permissions des groupes auxquels appartient l'utilisateur
     * - les groupes auxquels appartient l'utilisateur
     *
     *
     */
    public function getAllPermissions()
    {
        $sf_user = sfContext::getInstance()->getUser();

        if (!$sf_user->hasAttribute('all_permissions', myUser::NS_SF_GUARD))
        {
            $this->_allPermissions = array();

            $userPermissions = Doctrine::getTable('sfGuardUser')->getPermissionsByUser( $this->getId() );

            foreach ($userPermissions['sfGuardUserGroup'] as $groups)
            {

                // Ici au lieu de mettre l'objet, on ne stocke que l'id, ça suffit pour le mmt...
                // le plugin sfGuard lui stockait tout l'objet, mais du coup, on avait aussi toutes les permissions...
                // or elles sont déja dans _allPermissions.
                $this->_groups[ $groups['sfGuardGroup']['name'] ] = $groups['sfGuardGroup']['id'];

                foreach($groups['sfGuardGroup']['sfGuardGroupPermission'] as $permission)
                {
                    $this->_allPermissions[ $permission['sfGuardPermission']['name'] ] = $permission['sfGuardPermission'];
                }
            }

            foreach ($userPermissions['sfGuardUserPermission'] as $permission)
            {
                    $this->_allPermissions[ $permission['sfGuardPermission']['name'] ] = $permission['sfGuardPermission'];
                    $this->_permissions[    $permission['sfGuardPermission']['name'] ] = $permission['sfGuardPermission'];
            }

            $sf_user->setAttribute('all_permissions', $this->_allPermissions, myUser::NS_SF_GUARD);
            $sf_user->setAttribute('permissions'    , $this->_permissions   , myUser::NS_SF_GUARD);
            $sf_user->setAttribute('groups'         , $this->_groups        , myUser::NS_SF_GUARD);
        }
        else
        {
            $this->_allPermissions = $sf_user->getAttribute('all_permissions', array(), myUser::NS_SF_GUARD);
            $this->_permissions    = $sf_user->getAttribute('permissions'    , array(), myUser::NS_SF_GUARD);
            $this->_groups         = $sf_user->getAttribute('groups'         , array(), myUser::NS_SF_GUARD);
        }

        return $this->_allPermissions;
    }

    /**
     * Retourne les groupes de l'utilisateur
     * (utilise la session pour récupérer les groupes)
     * @return array Liste des groupes : clé=name, value=id
     */
    public function getGroups()
    {
        $this->loadGroupsAndPermissions();
        return $this->_groups;
    }
}

2. Modifier la DAO sfGuardUserTable (même dossier que le modèle) :

class sfGuardUserTable extends PluginsfGuardUserTable
{

    /**
     * Récupère toutes les permissions d'un utilisateur
     * @param int $userId ID du user
     * @return Doctrine_Collection
     */
    public function getPermissionsByUser($userId)
    {
        $q = $this->createQuery('u')
                ->innerJoin('u.sfGuardUserGroup ug')
                ->leftJoin('ug.sfGuardGroup g')
                ->leftJoin('g.sfGuardGroupPermission gp')
                ->leftJoin('gp.sfGuardPermission p')
                ->leftJoin('u.sfGuardUserPermission up')
                ->leftJoin('up.sfGuardPermission p2')
                ->where('u.id=?', $userId);

        //$q->useQueryCache();

        return $q->fetchOne();
    }
}

Voilà, je crois que je n’ai rien oublié, n’hésitez pas à laisser un commentaire s’il manque quelque chose.