Juste une quick hack rapide vieux de 15000 ans mais qui peut toujours être utile, il s’agit de la récupération via curl de flux RSS et de leur lecture avec SimpleXml.

Et comme pour moi du code bien commenté peut se passer de long discours, c’est parti:

⇀ Téléchargement du flux avec curl

<?php
// Download du flux avec curl:
$curl_hd = curl_init('http://rss.slashdot.org/Slashdot/slashdot');
// On garde une version Atom dans le coin pour la demo:
//$curl_hd = curl_init('http://rss.slashdot.org/Slashdot/slashdotatom');

// On a besoin de la reponse ...
curl_setopt($curl_hd, CURLOPT_RETURNTRANSFER, true);
// ... mais pas des headers:
curl_setopt($curl_hd, CURLOPT_HEADER, 0);

// Recuperation dans $rss du flux, et fermeture de curl.
$rss = curl_exec($curl_hd);
curl_close($curl_hd);

⇀ Deux fonctions bien utiles pour parser les flux RSS et Atom

function parse_rss($doc)
{
    // Pour chaque element...
    foreach($doc->channel->item as $item)
    {
        echo $item->title . "\n";
        echo $item->link . "\n";
        echo $item->description . "\n\n";
    }  
}  

function parse_atom($doc)
{
    // Pour chaque element...
    foreach($doc->entry as $item)
    {
        echo $item->title . "\n";
        echo $item->link->attributes() . "\n";
        echo $item->content . "\n\n";
    }  
}

⇀ Et pour finir, le parsing du flux téléchargé ($rss) avec SimpleXml:

// Creation et parsing de l'element telecharge avec SimpleXml:
$rss_doc = new SimpleXmlElement($rss, LIBXML_NOCDATA);

// Pour faire la distinction entre un fichier atom et un rss,
// on regarde juste quels tags sont presents. Dans le cas
// d'un "channel", ca sera du rss, dans le cas de l'atom,
// ce sera un "entry".
if(isset($rss_doc->channel))
{
    parse_rss($rss_doc);
}  
elseif(isset($rss_doc->entry))
{
    parse_atom($rss_doc);
}

That’s all.

Tout commença sur un coup de tête. La lutte fut longue et dure. Et je pense que j’ai eu toutes les erreurs possibles et inimaginables quand j’ai décidé de me pencher sur l’API et l’authentification OAuth de Twitter.

J’ai voulu tout refaire from scratch, en PHP et avec curl pour faire les requêtes.

Alors, commençons par le commencement. Et le commencement, c’est sur la page qui décrit le process.

Pour faire simple, voici les étapes pour chopper votre passe partout pour user de l’api twitter sur le dos d’un utilisateur béta :

⇀ Un user se pointe sur votre site. Jusque là, rien de grandiose, mais il faut réclamer à twitter un request_token à usage unique.
⇀ Quand on a ce request_token, on redirige l’utilisateur sur twitter avec ce token. L’utilisateur se retrouve sur une page type « voulez vous autoriser l’application xxx ? »
⇀ Si il n’est pas trop bête pour cliquer sur « Allow », il se retrouve redirigé avec un oauth_token et un oauth_verifier qui vous serviront à récupérer le précieux sésame, l’oauth_token_secret.

Ca parait super simple. Sauf qu’il ne faut pas se mélanger les pinceaux.

On va avoir à faire à créer et utiliser des URI encodées. Un coup de lecture de la RFC (http://www.ietf.org/rfc/rfc3986.txt) et on pond la fonction qui va nous servir à encoder nos données:

    public function _urlencode_rfc3986($input)
    {
         return str_replace('+',' ',str_replace('%7E', '~', rawurlencode($input)));
    }

Et on sort aussi une fonction qui va nous sortir à faire les requêtes:

    public function request($url, $method = 'GET', $post_params = NULL, $headers = NULL)
    {
        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURL_HTTP_VERSION_1_1, TRUE);

        $http_headers = array();

        if($method == 'POST')
        {
            curl_setopt($ch, CURLOPT_POST, TRUE);
            $http_headers[] = 'Expect:';
        }

        if(NULL !== $post_params)
        {
            curl_setopt($ch, CURLOPT_POSTFIELDS, $post_params);

            $http_headers[] = 'Content-Type: application/x-www-form-urlencoded';
        }

        if(NULL !== $headers && is_array($headers))
        {
            $http_headers = array_merge($http_headers, $headers);
        }

        if(count($http_headers))
        {
            curl_setopt($ch, CURLOPT_HTTPHEADER, $http_headers);
        }

        $response = curl_exec($ch);
        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        curl_close($ch);

        unset($ch);

        return array('code' => $code, 'response' => $response);
    }

On notera bien qu’on pourra faire un POST sans $post_params.

Incorrect Signature

Revenons à Twitter. Déjà, la doc nous dit qu’il faut signer toutes les requêtes. Il nous donne le pseudo code:

httpMethod + « & » +
url_encode( base_uri ) + « & » +
sorted_query_params.each { | k, v |
url_encode ( k ) + « %3D » +
url_encode ( v )
}.join(« %26″)

On le convertit assez aisement en PHP:

    public function buildSignature($secret, $method, $url, $params)
    {
        $str = $method . '&';
        $str.= $this->_urlencode_rfc3986($url) . '&';

        ksort($params);
        $i = 0;

        $param_str = '';
        foreach($params as $key => $value)
        {
            $param_str .= $this->_urlencode_rfc3986($key) . '=' . $this->_urlencode_rfc3986($value);
            if(++$i != count($params))
                $param_str .= '&';
        }

        $str .= $this->_urlencode_rfc3986( $param_str );

        $signed = base64_encode(hash_hmac("SHA1", $str, $secret, true));

        return $signed;
    }

Et finalement, on aura besoin pour les requêtes de construire un header Authorization: OAuth… Voici la fonction:

    public function makeAuthorization($params)
    {
        $args = array();
        foreach($params as $key => $value)
        {
            $args[] = $this->_urlencode_rfc3986( $key ). '="' . $this->_urlencode_rfc3986($value) . '"';
        }

        $str = implode($args, ', ');

        return(array('Authorization: OAuth ' . $str . "\n"));
    }

Bon, ben là, on a un peu tout les outils nécessaires pour faire nos requêtes d’authentification puis utiliser l’API.

Oauth_verifier missing

On sort le grand jeu, avec les fonctions pour récupérer un request token puis l’access token secret:

    public function parseTokens($str)
    {
        $tokens = explode('&', $str);
        $token_arr = array();
        foreach($tokens as $token)
        {
            list($field, $value) = explode('=', $token);
            $token_arr[ $field ] = $value;
        }
        return $token_arr;
    }

    public function getRequestToken($request_token_url, $callback_url = NULL)
    {
        $method = 'GET';
    $nonce = sha1('nonce' + time());
        $params = array(
                    'oauth_consumer_key' => $this->consumer_key,
                    'oauth_nonce' => $nonce,
                    'oauth_signature_method' => 'HMAC-SHA1',
                    'oauth_timestamp' => time(),
                    'oauth_version' => '1.0',
                  );

        if(NULL !== $callback_url)
        {
            $params['oauth_callback'] = $callback_url;
        }

        $signature = $this->buildSignature($this->consumer_secret . '&', $method, $request_token_url, $params);

        $params['oauth_signature'] = $signature;

        $rep_arr = $this->request($request_token_url, $method, NULL, $this->makeAuthorization($params));
        $rep = $rep_arr['response'];

    return $this->parseTokens( $rep );
    }

    public function getAccessToken($access_token_url, $oauth_token, $oauth_verifier)
    {
        $method = 'GET';

        $nonce = sha1('nonce' + time());
        $params = array(
                    'oauth_consumer_key' => $this->consumer_key,
                    'oauth_nonce' => $nonce,
                    'oauth_signature_method' => 'HMAC-SHA1',
                    'oauth_token' => $oauth_token,
                    'oauth_timestamp' => time(),
                    'oauth_version' => '1.0',
                    'oauth_verifier' => $oauth_verifier
                );

        $signature = $this->buildSignature($this->consumer_secret . '&', $method, $access_token_url, $params);
        $params['oauth_signature'] = $signature;

        $rep_arr = $this->request($access_token_url, $method, NULL, $this->makeAuthorization($params));
        $rep = $rep_arr['response'];

        return $this->parseTokens( $rep );
    }

Failed to validate oauth signature and token twitter

Structurons le tout dans une classe dédiée (d’où les utilisations des $this), et créons un index.php pour l’utilisation:

<?php

$cs_key = '...';
$cs_secret = '...';

$request_token_url = 'https://api.twitter.com/oauth/request_token';
$access_token_url = 'https://api.twitter.com/oauth/access_token';
$authorize_url = 'https://api.twitter.com/oauth/authorize';

require_once('OAuth.class.php');

$oa = new OAuth($cs_key, $cs_secret);

// En utilisant une url de callback 'oob', vous obtiendrez un PIN pour compléter.
// Le PIN sera à considérer comme la valeur de l'oauth_verifier que l'on aurait obtenu
// si l'on avait utilisé une url de callback à la place.
$request_token = $oa->getRequestToken($request_token_url, 'oob');
echo "You must go on " . $authorize_url . '?oauth_token=' . $request_token['oauth_token'] . "\n";

echo "Please give PIN number: ";
$fp = fopen('php://stdin', 'r');
$pin = trim(fgets($fp));

$access_token = $oa->getAccessToken($access_token_url, $request_token['oauth_token'], $pin);
// On gardera précieusement le contenu de l'access_token.

Status is a duplicate

Ne reste plus qu’à l’utiliser. On crée une classe Twitter dédiée à l’API Twitter et une fonction pour mettre à jour son status:

<?php
require_once 'OAuth.class.php';

class Twitter
{
    private $consumer_key = NULL;
    private $consumer_secret = NULL;

    private $oauth_token = NULL;
    private $oauth_token_secret = NULL;

    public function Twitter($consumer_key, $consumer_secret, $oauth_token, $oauth_token_secret)
    {
        $this->consumer_key = $consumer_key;
        $this->consumer_secret = $consumer_secret;

        $this->oauth_token = $oauth_token;
        $this->oauth_token_secret = $oauth_token_secret;
    }

    public function update($message)
    {
        $url = 'http://api.twitter.com/1/statuses/update.json';
        $method = 'POST';

        $oa = new OAuth($this->consumer_key, $this->consumer_secret);
        $nonce = sha1('nonce' + time());
        $params = array(
                    'oauth_consumer_key' => $this->consumer_key,
                    'oauth_nonce' => $nonce,
                    'oauth_signature_method' => 'HMAC-SHA1',
                    'oauth_timestamp' => time(),
                    'oauth_version' => '1.0',
                    'oauth_token' => $this->oauth_token,
                  );

        $twitter_params = array('status' => $message);

        $params = array_merge($params, $twitter_params);

        $signature = $oa->buildSignature($this->consumer_secret . '&' . $this->oauth_token_secret, $method, $url, $params);
        $params['oauth_signature'] = $signature;

        $ret = $oa->request( $url . '?' . http_build_query($twitter_params, '', '&'), 'POST', NULL, $oa->makeAuthorization($params) );

        return $ret;
    }
};

On notera deux choses: L’utilisation de l’authentification OAuth pour chaque requête sur Twitter, et les paramètres qui sont passés dans l’URL et non dans le body du POST.

Et un script pour l’utiliser:

<?php
$cs_key = '...';
$cs_secret = '...';

$oauth_token = 'id-...';
$oauth_token_secret = '...';

require_once 'Twitter.class.php';

$twitter = new Twitter($cs_key, $cs_secret, $oauth_token, $oauth_token_secret);

$twitter->update('Hello world');

En espérant que ça soit utile …
Cadeau: j’ai mis le code sur github ! Vous pouvez dès maintenant consulter le source directement à l’adresse http://github.com/mycroft/YeyAnotherTwitterPhpLib.

Dans mon précédent article (Créer une application facebook offline en utilisant l’api graph et le php-sdk), je vous montrais comment initier une connexion via oauth à Facebook et utiliser très brièvement l’API Graph en utilisant le php-sdk.

Je vais maintenant revenir sur l’API pour y faire des appels plus complexes (lecture/écriture, appels aux anciennes fonctions de l’API REST, et finalement une introduction au langage de requête FQL.

Repartons de notre script fetch.php. Si vous avez tout suivi, nous avions récupéré à la fin du premier article un access_token offline pour notre compte nous permettant de faire n’importe quel appel à facebook sans avoir à utiliser de navigateur web. Nous allons le réutiliser maintenant. Cependant, cela est totalement optionnel et nous pourrions également utiliser le token fournit par la session facebook (qui est fournit à la connexion et dont nous faisons l’utilisation dans le script online index.php).

Appels en lecture à l’API Graph

Comme nous l’avons vu à la fin du premier article, il nous était possible de faire des requêtes simples. La plus simple qui soit est un appel à /me:

$facebook = new Facebook(array(
                            'appId' => $app_id,
                            'secret' => $secret_key
                        ));

$params = array(
            'access_token' =>  $access_token
          );

$res = $facebook->api( '/me', 'GET', $params );
print_r($res);

De la même façon, on pourra récupérer ses contacts en faisant un appel à ‘/me/friends’:

$res = $facebook->api( '/me/friends', 'GET', $params );

Imaginons maintenant qu’on veuille récupérer le status d’un de ses amis. Il suffit remplacer ‘me’ par l’id du contact en question, et au lieu d’appeler ‘friends’, le ‘status:

$res = $facebook->api( '/.. id du contact ../status', 'GET', $params );

Cependant, les permissions demandées précédemment (« offline_access » et « read_stream ») ne suffisent pas pour faire cet appel. Il existe un certain nombre de permissions étendues devant être demandées à l’utilisateur avant que l’application puisse avoir accès aux données. Ces permissions sont détaillées sur la page Extended Permissions de l’API. On notera qu’il faut des permissions différentes pour obtenir des informations personnelles relative à soit que des informations relative aux contacts. Ici, nous avons besoin de « friends_statuses » afin d’obtenir les status de ses amis. Pour rajouter une permission, il faudra modifier le script index.php qu’on a utilisé pour générer un access_token, pour générer un nouveau lien pour faire la demande sur Facebook.

On notera qu’il est possible de faire des appels à l’API pour la récupération de données publiques, et ce sans access_token valide (par exemple, l’appel à ‘/patrick.marie’ est possible par n’importe qui sans access_token valide).

Appels en écriture à l’API Graph

L’api graph facebook permet également de publier des données sur Facebook (status, commentaires, images, liens, évènements…). Les appels possibles sont décrits dans le paragraphe Publishing to Facebook de la référence de l’API. Les appels ne sont que très peu différents de l’appel en lecture seule.

Voici un exemple simple pour changer son status:

$params['message'] = 'Coucou';
$res = $facebook->api( '/me/feed', 'POST', $params );

On notera que pour cet appel il faudra obtenir la permission ‘publish_stream’. A partir de là, on pourra faire une recherche pour trouver des éléments à « liker »:

Selon la doc à propos des requètes de recherche (http://developers.facebook.com/docs/api#search), il faut juste faire une requète sur https://graph.facebook.com/search?q=QUERY&type=OBJECT_TYPE avec comme type possible ‘post’, ‘user’, ‘page’, ‘event’, ‘group’ ou ‘checkin’.

Faisons un rapide test. Je cherche une page sur les gauffres en forme de Texas:

$params = array('access_token' =>  $access_token, 'type' => 'page', 'q' => 'texas waffles' );
$res = $facebook->api( '/search', 'GET', $params );
foreach($res['data'] as $page)
{
    print $page['id'] . ' -> ' . $page['name'] . "\n";
}

Maintenant, je vais chercher le ‘Coucou’ que j’ai posté précédemment pour le ‘liker’. Pour faire une recherche sur son feed, un appel à /me/home avec le paramètre ‘q’ suffit. Ensuite, un appel à /POST_ID/likes permettra de le liker:

$params = array('access_token' =>  $access_token, 'type' => 'post', 'q' => 'Coucou mon api' );
$res = $facebook->api( '/me/home', 'GET', $params );
foreach($res['data'] as $post)
{
    if($post['from']['id'] == $me['id'])
    {
        print $post['message'] . "\n";

        $params = array('access_token' =>  $access_token);
    // like ...
        $rep = $facebook->api( '/' . $post['id'] . '/likes', POST, $params );
    }
}

Il est finalement possible d’effacer le like posé ou encore le post en question, grâce à la méthode DELETE ( http://developers.facebook.com/docs/api#deleting ):

// Effacer le like:
$rep = $facebook->api('/' . $postid . '/likes', DELETE, $params );
// Effacer le post:
$rep = $facebook->api('/' . $postid, DELETE, $params);

On notera qu’il n’est pas encore possible de poster ou de ‘liker’ n’importe quoi ou n’importe où. J’en ai fais les frais quand je voulais liker automatiquement la page des gauffres ;-(

Utilisation des fonctions de l’API REST

Avant l’apparition en Mai de l’API Graph, il existait une autre API basée sur REST qui est encore maintenue aujourd’hui. Cette API est encore décrite sur le site de facebook. (http://developers.facebook.com/docs/reference/rest/). Il est intéressant de parler de cette API car même si elle semble être désuète par rapport à l’API Graph, je la trouve toujours indispensable grâce à tous les appels non encore supporté par la nouvelle API, comme par exemple les requêtes FQL que nous verrons dans le paragraphe final.

On fait appel à ces fonctions toujours grâce à la fonction api() de l’api, mais de façon légèrement différente:

$request = array(
            'method' => 'friends.get',
            'access_token' => $access_token
            );

$res = $facebook->api($request);
// ...
// ou encore, en utilisant users.getInfo (http://developers.facebook.com/docs/reference/rest/users.getInfo):
$request = array(
            'method' => 'users.getInfo',
            'uids' => '4',
            'fields' => 'first_name, last_name',
            'access_token' => $access_token
            );
$res = $facebook->api($request);
var_dump($res);
/*
array(1) {
  [0]=>
  array(3) {
    ["first_name"]=>
    string(4) "Mark"
    ["last_name"]=>
    string(10) "Zuckerberg"
    ["uid"]=>
    string(1) "4"
  }
}
*/

Merci Mark pour cette rapide participation à la démo !

Mais la principale utilisation que je vois de cette ancienne API est encore l’utilisation de la méthode fql.query.

Utilisation de l’API FQL

Un mécanisme puissant de facebook est l’utilisation du Facebook Query Language. En quelques mots, les tables FQL permettent d’accéder aux données facebook auxquelles on a accès comme si l’on tappait dans une base de données en langage SQL.

Il y a dans Facebook un grand nombre de tables accessibles: les utilisateurs, les groupes, pages, amis, etc…

Lançons quelques requêtes prises au hasard dans l’API:

$query = 'SELECT uid, name FROM user WHERE uid = me()';
$request = array(
            'method' => 'fql.query',
            'query' => $query,
            'access_token' => $access_token);
$res = $facebook->api( $request );
if(count($res))
{
    $uid = $res[0]['uid'];
    $name = $res[0]['name'];
    echo "My name is " . $name . " (#" . $uid . ")\n";
}
// Retourne:
// My name is Patrick Marie (#615214609)

On pourra se balader dans les exemples proposés. Un autre sera de récupérer les derniers status de ses contacts, ou encore les derniers posts de sa page:

$ts = time() - (7 * 24 * 60 * 60);
$r = fql($access_token, 'SELECT uid,status_id,message FROM status WHERE uid IN (SELECT uid2 FROM friend WHERE uid1=' . $uid . ') AND time > ' . $ts . ';');
/* ... */

$r = fql($access_token, 'SELECT message FROM stream WHERE source_id = ' . $uid);
/* ... */

Voilà, c’est la fin de cette courte présentation de l’API, sachant que même si la documentation semble exhaustive maintenant, ce ne fut pas toujours le cas et qu’elle m’a donné quelques fois pas mal de travail pour comprendre le pourquoi du comment.

Mon but: Créer une application php pouvant pooler des informations sur un compte facebook sans que la personne en question ne soit forcement connectée à ce moment là.

Outils: Mon serveur web, l’api graph facebook et son php-sdk (http://github.com/facebook/php-sdk).

Initialement, je voulais faire un tutoriel sur l’intégration et l’utilisation de ce sdk avec symfony, mais cela aurait été un poil trop long et sans réel intérèt.

Avant toute chose, il ne faut pas oublier de créer une application facebook (facebook.com/developers), de noter son id, la clef api et la clef secrète, et de récupérer le php-sdk (hint: ‘git clone http://github.com/facebook/php-sdk.git’).

Vue la nature de l’application (type « connect », oauth et utilisation du sdk), quelques modifications sont à effectuer dans les settings:
Dans Connexion, spécifier l’URL connect (qui pointera vers l’endroit où vous allez mettre votre index.php), et activez « Canvas Session Parameter » et « OAuth 2.0 for Canvas (beta) » dans l’onglet Migrations.

La démo va comporter 2 parties: Une partie web (index.php), nécessaire pour enregistrer les utilisateurs dans l’application, les authentifier et récupérer le token d’accès, et une seconde, totalement « offline » qui permettra de récupérer les données dans facebook sans intervention tierce (fetch.php).

Le workflow d’enregistrement de l’utilisateur, de l’ajout des permissions et de la création de l’access token offline est le suivant:

⇀ L’utilisateur se pointe sur notre index.php, on lui affiche un lien pour se logger sur facebook ou pour ajouter l’application, qu’il doit cliquer;
⇀ En cliquant, il se retrouve sur facebook, se loggue si besoin, autorise l’application;
⇀ Il revient sur notre page, et nous lui demandons qu’il retourne sur facebook pour qu’il nous autorise à utiliser le mode offline et certaines autres permissions;
⇀ Facebook redirige l’utilisateur vers notre script, avec un paramètre « code »;
⇀ Nous effectuons une requète secrète avec le code donné et notre clef secrete vers facebook, pour récupérer le token illimité, que l’on sauvegarde précieusement.

Ces étapes peuvent être simplifiées (à base de Location: ), mais j’ai préféré bien distinguer les différentes étapes pour que ça soit clair (pour vous comme pour moi, plus tard).

Le premier script que je propose est plus simple que celui d’exemple proposé par le SDK de facebook. Il s’appelle donc index.php, sera le script par défaut dans mon répertoire web:

<?php

require_once 'php-sdk/src/facebook.php';

// Le script a besoin de connaitre un minimum d'information sur l'application facebook.
// On donne donc son ID et ses clefs d'utilisation:
$app_id = '... application id ...';
$api_key = '... la clef API ...';
$secret_key = '... la clef secrète ...';

// Construit une interface facebook avec les infos de notre application
// Noter qu'il est obligatoire d'avoir les extensions PHP json et curl.
$facebook = new Facebook(array(
                            'appId' => $app_id,
                            'secret' => $secret_key,
                            'cookie' => true
                        ));

// Recupere la session
// Si celle ci n'est pas valide, aucune interaction avec fb ne sera possible
$session = $facebook->getSession();

if($session)
{
    try {
        // On appelle l'API graph, ici graph.facebook.com/me
        // /me contient le nom de l'utilisateur mais aussi son ID facebook.
        $me = $facebook->api('/me');
    }
    catch(FacebookApiException $e) {
    }
}

// Si aucune session n'est detectee, alors on affiche un lien de login.
if( ! $session || ! $me )
{
    $loginUrl = $facebook->getLoginUrl();
    echo '<a href=' . $loginUrl . '>login</a>';
}
else
{
    $logoutUrl = $facebook->getLogoutUrl();
    // On pourrait dumper le résultat de l'appel à /me.
    echo 'Mon nom est ' . $me['name'] . '.';
    echo '<br />';
    echo '<a href=' . $logoutUrl . '>logout</a><br />';
}

Ensuite, pour effectuer des requêtes offline, il faut demander l’autorisation de l’utilisateur. Un appel à l’api Oauth 2.0 de facebook est ici nécessaire (voir http://developers.facebook.com/docs/api#authorization).

Pour ce faire, on rajoute donc une fonction « buildOAuthPermissionLink » et son lien prêt à être utilisé:

function buildOAuthPermissionLink()
{
    global $app_id;

    $my_site = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['SCRIPT_NAME'];

    $fb_link = 'https://graph.facebook.com/oauth/authorize?';
    $fb_link.= 'client_id=' . $app_id;
    $fb_link.= '&redirect_uri=' . $my_site;
    $fb_link.= '&scope=' . 'offline_access,read_stream';

    return $fb_link;
}
/* ... */
    echo 'Mon nom est ' . $me['name'] . '.';
    echo '<br />';
    echo 'Ask for permissions: <a href="' . buildOAuthPermissionLink() . '">click</a> ';
    echo '<br />';

Dès lors qu’on cliquera sur ce lien, on sera redirigé vers facebook qui, après avoir obtenu l’autorisation de l’utilisateur de partager les données, nous enverra un code qu’on pourra utiliser pour l’échanger contre un access_token spécifique de durée illimité. Cette fois-ci, c’est à nous de faire la requète:

if( isset($_REQUEST['code']) )
{
    $my_site = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['SCRIPT_NAME'];

    $fb_link = 'https://graph.facebook.com/oauth/access_token?';
    $fb_link.= 'client_id=' . $app_id;
    $fb_link.= '&redirect_uri=' . $my_site;
    $fb_link.= '&client_secret=' . $secret_key;
    $fb_link.= '&code=' . $_REQUEST['code'];

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $fb_link);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HEADER, 0);

    $ans = curl_exec($ch);
    curl_close($ch);

    list($field, $value) = explode('=', $ans);
    if($field == 'access_token')
    {
        echo "Access token is '" . $value . "'<br />";
    }
}

Le token donné sera la clef de voute des appels effectués en mode offline. Il faut la conserver précieusement.
On va créer un 2nd script qui effectuera les appels en mode déconnecté. On l’appelle ‘fetch.php‘:

<?php

require_once 'php-sdk/src/facebook.php';

// De meme pour index.php, on a besoin des elements d'informations de l'applicaiton:
$app_id = '... l application id ...';
$api_key = '... la clef API ...';
$secret_key = '... la clef secrète ...';

// On cree l'objet:
$facebook = new Facebook(array(
                            'appId' => $app_id,
                            'secret' => $secret_key,
                            // Pas de cookie ... On est en mode offline, on donnera l'access token apres.
                        ));

// On fait un appel public, sans clef (ceci est l'url customisable)
$res = $facebook->api( '/patrick.marie' );
print_r($res);

// Ici le token qu'on a precieusement conservé suite à la fin de l'authentification OAuth
$access_token = '... l access token que l on a récupéré ...';

// On prepare un appel à /me. On a besoin de donner l'access token:
$params = array(
            'access_token' =>  $access_token
          );

$res = $facebook->api( '/me', 'GET', $params );
print_r($res);

// Dans l'index.php, j'ai demandé l'autorisation 'read_stream'. On va l'exploiter
// pour récupérer les derniers éléments de mon wall:
$wall = $facebook->api( '/me/feed', 'GET', $params );
print_r($wall);

// Ou ceux de mes amis:
$home = $facebook->api( '/me/home', 'GET', $params );
print_r($home);

Et voilà, cela clot cette première partie. Dans une seconde, je reviendrai sur les différents appels possibles au php-sdk, sur les POST possibles, les fonctions legacy et les requètes FQL.

Ceci est la seconde partie de PHP: Quelques fonctions méconnues de la programmation objet.

__get, __set, __isset, __unset

PHP permet d’affecter à une instance d’une classe n’importe quelle valeur (presque comme à un tableau de hashage) pour autant que ça ne soit pas une variable privée:

<?php

class Objet {};
$obj = new Objet;
$obj->exemple = 1;
echo $obj->exemple . "\n"; // affiche '1'

Lors de l’affectation d’une variable de telle façon, PHP appelle en fait les fonctions __set et __get pour l’affectation et la lecture de la valeur. Il est évidement bien entendu possible de les réécrire afin de définir un nouveau comportement:

<?php // Objet.class.php

class Objet
{
    private $data;
    public function __construct()
    {
        $data = array();
    }

    public function __set($name, $value)
    {
        echo "Setting $name to $value\n";
        $this->data[ $name ] = $value;
    }

    public function __get($name)
    {
        echo "returns $name\n";
        return $this->data[ $name ];
    }
};
<?php // fichier test.php

require_once("Objet.class.php");
$obj = new Objet;
$obj->priv = 1;
echo $obj->priv;

Cet exemple retournera:

$ php test.php
Setting priv to 1
return priv
1

Cela permet de dévier les affectations des variables dans les instances de classe, mais aussi de « protéger » le contenu des classes. Il faut également noter que si l’appel s’effectue pour une variable déclarée privée dans la classe, on sera également redirigé par __get et __set et l’on n’aura plus l’erreur comme quoi on fait appel à une variable privée.

De plus, de la même façon que pour __get et __set, on aura également __isset et __unset qui serviront à redéfinir le comportement lors d’un appel à isset ou à unset.

__call et __callStatic

Comme pour les variables, il est possible d’attraper les appels des fonctions non définies et privées dans les classes, statiques ou non en utilisant __call et __callStatic.

<?php // Objet.class.php
class Objet
{
    public function __call($func, $args)
    {
        echo "Calling $func\n";
    }

    public static function __callStatic($func, $args)
    {
        echo "Calling static $func\n";
    }
};
<?php // test.php

class Objet {};
$obj = new Objet;
$obj->exemple = 1; echo $obj->exemple . "\n";

Et à l’exécution:

$ php test.php
Calling hello
Calling static hello

Ces deux fonctions, ajoutées aux setters/getters du dessus, permettent de « wrapper » entièrement des instances de classes permettant des utilisations bien plus intéressantes qu’un simple héritage.

__clone

__clone est appelé lorsque l’on crée une copie d’une instance d’objet en appelant clone. Cela résulte à deux instances de la classe totalement indépendante avec le même contenu. Bien que je ne sois pas sûr que clone soit utilisé tous les jours, __clone peut éventuellement être utilisé afin de modifier l’un des deux objets avant ou après le clonage, ou encore de ne pas effectuer ce clonnage afin d’assurer d’avoir un singleton.

__sleep, __wakeup

Finalement, __sleep et __wakeup sont utilisées lors de la sérialisation/désérialisation d’un objet. Elles seront appelées respectivement à l’utilisation de serialize et unserialize. A noter que __sleep doit retourner un array avec la liste des champs à sérialiser.

Comme les courts exemples sont mieux que les longs discours, voici une classe ouvrant un fichier à sa création, le refermant à sa sérialisation, pour le réouvrir à la désérialisation:

<?php // Objet.class.php

class Objet
{
    private $file_name = NULL;
    private $file_hd = NULL;

    public function __construct($file_name)
    {
        $this->file_name = $file_name;
        $this->file_open();
    }

    public function __destruct()
    {
        if($this->file_hd !== NULL)
            $this->file_close();
    }

    private function file_open()
    {
        echo "Opening file\n";
        $this->file_hd = fopen($this->file_name, "a+");
    }

    private function file_close()
    {
        if(NULL === $this->file_hd)
            return false;

        echo "Closing file\n";
        fclose($this->file_hd);
        $this->file_hd = NULL;
    }

    public function __sleep()
    {
        echo "* Serialization.\n";
        $this->file_close();

        return(array('file_name'));
    }

    public function __wakeup()
    {
        echo "* Unserialization.\n";
        $this->file_open();
    }
};

Et testons tout ça:

<?php // fichier test.php

require_once("Objet.class.php");

$obj = new Objet('helloworld.txt');

$serializedObj = serialize($obj);
unset($obj);
var_dump($serializedObj);

$newObj = unserialize($serializedObj);

echo "Fin du script\n";

On peut vérifier le comportement:

$ php test.php
Opening file
* Serialization.
Closing file
string(62) "O:5:"Objet":1:{s:16:"Objetfile_name";s:14:"helloworld.txt";}"
* Unserialization.
Opening file
Fin du script
Closing file

J’espère que quelqu’un aura découvert quelque chose ! :)

Pour ces deux articles, je me suis un peu inspiré d’un article analogue m’ayant poussé à lire un peu plus la doc de PHP: 9 magic methods for php (thinkvitamin.com).