--------------- EDITED ------------------
Based on CakePHP-LinkedIn plugin
After using this plugin succesfully for Twitter and Linkedin, I found a lot of trouble configuring Facebook. I realized it was because OAuth V2 wasn't fully supported, or at least for Facebook implementation. Using this modified version and below instructions, will make it.
-
Follow steps on this tutorial or LinkedIn example. We're going to create a plugin called Facebook
-
Use the fork I created on [HttpSocketOAuth] (https://github.com/ProLoser/http_socket_oauth)
-
In Config/database.php insert as other examples (scope is optional)
var $facebook = array(
'datasource' => 'Facebook.Facebook',
'login' => '<facebook api key>',
'password' => '<facebook api secret>',
//'scope' => 'email,email,read_stream'
);
- Create a file Plugin/Facebook/Model/Datasource/Facebook.php taking the Linkedin one as an example and changing line 27 from
$config['method'] = 'OAuth';
to
$config['method'] = 'OAuthV2';
- Create the method map
on Plugin/Facebook/Config/Facebook.php
<?php
$config['Apis']['Facebook']['hosts'] = array(
'oauth' => 'www.facebook.com/dialog/oauth',
'oauth_acess_token' => 'graph.facebook.com/oauth', // In facebook they're different, in others could be the same
'rest' => 'graph.facebook.com',
);
$config['Apis']['Facebook']['oauth'] = array(
'authorize' => '',
'access' => 'access_token',
'scheme' => 'https',
'version' => '2.0'
);
$config['Apis']['Facebook']['read'] = array(
'credentials' => array(
'me/' => array()
)
);
$config['Apis']['Facebook']['create'] = array(
);
$config['Apis']['Facebook']['update'] = array(
);
$config['Apis']['Facebook']['delete'] = array(
);
- You can use this as an example on your model:
<?php
class MyModel extends AppModel {
// Get credentials from authorized user
function readProfile() {
$this->setDataSource('facebook');
$data = $this->find('all', array(
'path' => 'me/'
//'fields' => array()
));
$this->setDataSource('default');
return $data;
}
}
?>
---------------- End Facebook OAuth V2 instructions -----------------
Since I started going through several restful apis things started to become repetitive. I decided to layout my code in a more 'proper ' fashion then.
:: database.php ::
var $myapi = array(
'datasource' => 'MyPlugin.MyPlugin', // Example: 'Github.Github'
// These are only required for authenticated requests (write-access)
'login' => '--Your API Key--',
'password' => '--Your API Secret--',
);
:: MyModel.php ::
var $useDbConfig = 'myapi';
[MyPlugin]/Config/[MyPlugin].php
REST paths must be ordered from most specific conditions to least (or none). This is because the map is iterated through until the first path which has all of its required conditions met is found. If a path has no required conditions, it will be used. Optional conditions aren't checked, but are added when building the request.
$config['Apis']['MyPlugin']['hosts'] = array(
'oauth' => 'api.myplugin.com/login/oauth',
'rest' => 'api.myplugin.com/v1',
);
$config['Apis']['MyPlugin']['oauth'] = array(
'authorize' => 'authorize', // Example URI: api.linkedin.com/uas/oauth/authorize
'request' => 'requestToken',
'access' => 'accessToken',
'login' => 'authenticate', // Like authorize, just auto-redirects
'logout' => 'invalidateToken',
);
$config['Apis']['MyPlugin']['read'] = array(
// field
'people' => array(
// api url
'people/id=' => array(
// required conditions
'id',
),
'people/url=' => array(
'url',
),
'people/~' => array(),
),
'people-search' => array(
'people-search' => array(
// optional conditions the api call can take
'optional' => array(
'keywords',
),
),
),
);
$config['Apis']['MyPlugin']['write'] = array(
);
$config['Apis']['MyPlugin']['update'] = array(
);
$config['Apis']['MyPlugin']['delete'] = array(
);
Try browsing the apis datasource and seeing what automagic functionality you can hook into!
[MyPlugin]/Model/Datasource/[MyPlugin].php
App::uses('ApisSource', 'Apis.Model/Datasource');
Class MyPlugin extends ApisSource {
// Examples of overriding methods & attributes:
public $options = array(
'format' => 'json',
'ps' => '&', // param separator
'kvs' => '=', // key-value separator
);
// Key => Values substitutions in the uri-path right before the request is made. Scans uri-path for :keyname
public $tokens = array();
// Enable OAuth for the api
public function __construct($config) {
$config['method'] = 'OAuth'; // or 'OAuthV2'
parent::__construct($config);
}
// Last minute tweaks
public function beforeRequest(&$model, $request) {
$request['header']['x-li-format'] = $this->options['format'];
return $request;
}
}
[MyPlugin]/Controller/Component/[MyPlugin].php
App::uses('Oauth', 'Apis.Component/Component');
Class MyPluginComponent extends OauthComponent {
// Override & supplement your methods & attributes
}
Lets say you don't feel like bothering to make a new plugin just to support your api, or the existing plugin doesn't cover enough of the features. Good news! The plugin degrades gracefully and allows you to manually manipulate the request (thanks to NeilCrookes' RESTful plugin).
Simply populate Model->request with any request params you wish and then fire off the related action. You can even continue
using the $data
& $this->data
for save()
and update()
or pass a 'path'
key to find()
and it will automagically
be injected into your request object.
MyController extends AppController {
var $components = array(
'Apis.Oauth' => 'linkedin',
);
function connect() {
$this->Oauth->connect();
}
function linkedin_callback() {
$this->Oauth->callback();
}
}
You can also use multiple database configurations
var $components = array(
'Apis.Oauth' => array(
'linkedin',
'github',
'flickr',
);
);
However this requires you to specify which config to use before calling authentication methods
function beforeFilter() {
$this->Oauth->useDbConfig = 'github';
}
I'm eager to hear any recommendations or possible solutions.
- More automagic
- Better map scanning:
I'm not sure of a good way to add map scanning to
save()
,update()
anddelete()
methods yet since I have little control over the arguments passed to the datasource. It is easy to supplementfind()
with information and utilize it for processing. - Complex query-building versatility: Some APIs have multiple different ways of passing query params. Sometimes within the same request! I still need to flesh out param-building functions and options in the driver so that people extending the datasource have less work.
- OAuth v2.0 Confirmed support: Github uses v2.0. I updated the component to work accordingly, however I have to test that the HttpSocketOauth can use it.