Dans PHP, il y a des tonnes de fonctions qu’on n’utilise pas très souvent.

Ce soir, je vais vous montrer qu’on peu avec les fonctions de contrôle de sortie créer facilement un système de cache très simpliste.

Attention: Ce système de cache est loin d’être parfait, en fait, c’est surement le pire qu’on puisse coder: il est peut performant (car en PHP), non partagé, et devrait comporter un système de locking.

Voilà le code:

<?php

class SimpleCache
{
    var $is_started = FALSE;
    var $dir = '/tmp/cache';

    public function __construct($auto_start = FALSE)
    {
        if(TRUE == $auto_start)
        {
            $this->startCache();
        }
    }

    public function __destruct()
    {
        $this->endCache();
    }

    public function getCacheFileName()
    {
        $request_data = sha1(json_encode($_REQUEST));
        $file_name = $this->dir . '/' . $request_data . '.cache';

        return($file_name);
    }

    public function startCache()
    {
        $file_name = $this->getCacheFileName();

        if(file_exists($file_name))
        {
            echo file_get_contents($file_name);
            die();
        }

        ob_start();
        $this->is_started = TRUE;
    }

    public function endCache()
    {
        $file_name = $this->getCacheFileName();

        if(FALSE == $this->is_started)
            return;

        $contents = ob_get_contents();

        $f = fopen($file_name, 'w+');
        if($f)
        {
            fwrite($f, $contents);
            fclose($f);
        }

        ob_end_flush();
    }
}

Comment ça marche: A n’importe quel moment, on démarre le mécanisme du cache en appelant la fonction startCache. Celle ci va soit stocker en mémoire ce qu’il va afficher sur stdout avec ob_start, soit si le fichier de cache existe, ressortir le contenu. Ce fichier de cache sera différent à chaque type de requête car il doit son nom à la fonction getCacheFileName, qui calculera un nom unique à pour chaque requète différente. Il est évident que cela casse toute sorte de « dynanisme ».

A la fin du script, lors du garbage collector, ou alors dès que le développeur le veut, la fonction endCache est appelée. Celle ci va stocker dans un fichier la sortie du script, récupéré grâce à ob_get_contents depuis l’appel à startCache.

On pourra utiliser cette classe de cette façon:

<?php

require_once 'SimpleCache.class.php';

$cache = new SimpleCache( TRUE );

/* Code à cacher */
echo time();

Et voilà le résultat:

$ lynx -dump http://localhost/cache/index.php
   1290807633

$ sleep 5 ; lynx -dump http://localhost/cache/index.php
   1290807633

$ ls /tmp/cache/
97d170e1550eee4afc0af065b78cda302a97674c.cache

$ cat /tmp/cache/97d170e1550eee4afc0af065b78cda302a97674c.cache
1290807633

Le résultat est bien caché dans /tmp/cache et récupéré à l’appel suivant comme désiré.

J’ai récemment commencé à utiliser le framework Javascript SDK Facebook, qui assure une couche Javascript d’authentification et d’utilisation de l’API Facebook. Mon but à terme est de porter l’application que je développe, actuellement sous forme canvas/fbml/fbjs sous forme d’iframe/javascript sdk.

Alors, à base de jquery pour quelques appels Ajax, avec un soupçon de symfony sans ORM (mais pas réellement nécessaire sur le coup, limite j’ai perdu du temps à le mettre en place tout bien vu qu’il n’y a qu’une page), j’ai designé une page que je qualifierai de console de test Facebook, disponible sur http://fb.mkz.me/ et également sur http://apps.facebook.com/monkeyzfb/ dans sa version iframe. La console permet de tester certains appels issus de FB.api (graph et old rest), quelques appels FB.ui et quelques socials plugins.

Le code, quant à lui est très simple. Tout commence par quelques éléments html, un petit script javascript utilisant jquery, deux appels ajax, l’un pour changer le code et l’autre pour executer la commande FQL désirée.

Et hop, ça donne ça:

On notera les deux petits tricks suivants:

Pour savoir si la session facebook a été déconnectée, 3 lignes de codes suffisent. Au besoin, on pourra changer le document.location pour délogguer l’utilisateur de notre application:

    FB.Event.subscribe('auth.sessionChange', function(response) {
        if (response.session) {
            FB.api('/me', function(response) { $('span#_status').text('Logged in as ' + response.name);});
        } else {
            $('span#_status').text('No session');
        }
    });

Et encore l’appel à FB.XFBML.parse() pour reparser manuellement toute la page après l’inclusion de nouveaux éléments xfbml (fb:comments, fb:profile-pic):

    $('input#_run').click(function() {
        // On modifie une section cachée (_code) avec le code ecrit dans le textarea
        $('span#_code').html($('textarea#_editor').val());
        // On vide le result car le script va ecrire dedans
        $('span#_result').html('');

        // On parse le xfbml pour le voir s'afficher:
        FB.XFBML.parse();
    });

Ce tutoriel va vous expliquer comment créer une application très simple Facebook Connect, en gérant le fait d’être loggué sur Facebook ou pas, qui va nous permettre de faire des appels sur l’API Graph et utilisant le Javascript SDK.

Je passe très rapidement sur la création de l’application symfony. Il y a déjà des tas de tutoriaux pour cela, je vous conseille de reprendre les tutoriaux, en particulier celui de Jobeet.

On crée donc l’application (sans ORM dans mon cas):

cd devel/
mkdir -p sfbook/lib/vendor
curl http://www.symfony-project.org/get/symfony-1.4.8.tgz | tar -xzv -C !-1:2 -f -
SFBOOK=$(pwd)/sfbook; cd $SFBOOK; ./lib/vendor/symfony-1.4.8/data/bin/symfony generate:project sfbook --orm=none

Puis on récupère le php-sdk Facebook et on l’on active facebook dans l’autoloader:

cd lib/vendor ; git clone https://github.com/facebook/php-sdk.git
cat > $SFBOOK/config/autoload.yml << EOF
autoload:
    facebook:
        name: facebook
        path: %SF_LIB_DIR%/vendor/php-sdk/src
        recursive: off
EOF

On crée une application, ‘frontend’ et deux modules dans celle ci ‘default’ et ‘home’. Le premier module servira pour se logger, le 2nd pour l’application en elle même et dans notre exemple, on récupèrera juste la liste de ses amis.

php symfony generate:app frontend
php symfony generate:module frontend home
php symfony generate:module frontend default

Le module « home » ne sera accessible qu’aux utilisateurs authentifiés sur facebook. On le protège donc:

mkdir apps/frontend/modules/home/config
cat >> apps/frontend/modules/home/config/security.yml << EOF
all:
    is_secure: true
EOF

Et on oublie pas de modifier apps/frontend/config/settings.yml en rajoutant dans la section all: l’action suivantes:

 .actions:
    login_module
:          default
    login_action
:          login

A ce moment, on crée l’application sur facebook. On définit uniquement le Site URL (Onglet Website) pour accéder à notre application symfony et on note nos application Id, api key et application secret.

Comment ça va marcher ? On va mettre dans le layout général le code « connect »: Un script javascript facebook va être chargé et nous redirigera directement sur la page de login si jamais la session facebook venait à être fermée par l’utilisateur. Le template default/login contiendra le bouton facebook connect, et le template home/index qui ne s’affichera que si la session

On ajoute dans apps/frontend/template/layout.php le code suivant dans le :

<div id="fb-root"></div>
<script src="http://connect.facebook.net/en_US/all.js"></script>
<script>
  FB.init({appId: '[APP_ID]', status: true, cookie: true, xfbml: true});
  FB.Event.subscribe('auth.sessionChange', function(response) {
    if (response.session) {
        window.location = '<?php echo url_for('home/index'); ?>';
    } else {
        window.location = '<?php echo url_for('default/login'); ?>';
    }
  });
</script>

Le module « default » n’aura que l’action « login ». Cette action s’occupe d’authentifier l’utilisateur. Si l’utilisateur est loggué sur facebook, alors on le redirige vers home/index. Voilà le code de apps/frontend/default/actions/actions.class.php:

<?php
class defaultActions extends sfActions
{
    public function executeLogin(sfWebRequest $request)
    {
        $facebook = new Facebook(array('appId' => '[APP_ID]',
                                       'secret' => '[APP_SECRET]',
                                       'cookie' => TRUE ));
        $session = $facebook->getSession();

        if(NULL !== $session)
        {
            $me = $facebook->api('/me', 'GET', array('access_token' => $session['access_token']));

            // On verifie qu'on existe.
            if(NULL !== $me)
            {
                $this->getContext()->getUser()->setAuthenticated(true);
                $this->redirect('home/index');
            }
        }

        $this->getContext()->getUser()->setAuthenticated(false);

        return sfView::SUCCESS;
    }
}

Le template et default/login contiendra uniquement le bouton facebook connect:

<div id='content' align='center'>
<h1>Welcome !</h1>
<fb:login-button></fb:login-button>
</div>

Dans home, on va mettre un peu ce que l’on veut. Pour le coup, on va par exemple récupérer et afficher ses amis facebook:

<?php
class homeActions extends sfActions
{
    public function preExecute()
    {
        $this->facebook = new Facebook(array('appId' => '[APP_ID]',
                                             'secret' => '[APP_SECRET]',
                                             'cookie' => TRUE ));
        $this->session = $this->facebook->getSession();
    }

    public function executeIndex(sfWebRequest $request)
    {
        $this->friends = $this->facebook->api('/me/friends',
                                              'GET',
                                              array('access_token' => $this->session['access_token']));

        return sfView::SUCCESS;
    }
}

Le template associé est très simple:

My friends:<br />
<br />
<?php foreach($friends['data'] as $friend) : ?>
<?php echo $friend['name'] . "<br />"; ?>
<?php endforeach; ?>

Et voilà. Si en visitant le site on est déjà loggué facebook, mais que l’utilisateur n’a jamais utilisé l’application, on aura le droit à l’écran de login pour installer l’application. Ensuite, l’application affichera directement home/index.

On pourra même rajouter un bouton « logout » dans le layout global:

<?php if($sf_user->isAuthenticated()) : ?>
<input type='button' onclick='FB.logout(function(response) {});' value='logout' />
<?php endif ; ?>