DataSources are the link between models and the source of data that models represent. In many cases, the data is retrieved from a relational database such as MySQL, PostgreSQL or MSSQL. CakePHP is distributed with several database-specific datasources (see the dbo_* class files in cake/libs/model/datasources/dbo/), a summary of which is listed here for your convenience:
Additional DataSources and those that were removed from the core in 1.3 can be found in the community-maintained CakePHP DataSources repository at github.
When specifying a database connection configuration in app/config/database.php, CakePHP transparently uses the corresponding database datasource for all model operations. So, even though you might not have known about datasources, you’ve been using them all along.
All of the above sources derive from a base DboSource
class, which
aggregates some logic that is common to most relational databases. If
you decide to write a RDBMS datasource, working from one of these (e.g.
dbo_mysql.php or dbo_mssql.php is your best bet.
Most people, however, are interested in writing datasources for external sources of data, such as remote REST APIs or even an LDAP server. So that’s what we’re going to look at now.
A datasource can, and should implement at least one of the following
methods: create
, read
, update
and/or delete
(the actual
method signatures & implementation details are not important for the
moment, and will be described later). You need not implement more of the
methods listed above than necessary - if you need a read-only
datasource, there’s no reason to implement create
, update
, and
delete
.
Methods that must be implemented
describe($model)
listSources()
create($model, $fields = array(), $values = array())
read($model, $queryData = array())
update($model, $fields = array(), $values = array())
delete($model, $id = null)
It is also possible (and sometimes quite useful) to define the
$_schema
class attribute inside the datasource itself, instead of in
the model.
And that’s pretty much all there is to it. By coupling this datasource
to a model, you are then able to use Model::find()/save()
as you
would normally, and the appropriate data and/or parameters used to call
those methods will be passed on to the datasource itself, where you can
decide to implement whichever features you need (e.g. Model::find
options such as 'conditions'
parsing, 'limit'
or even your own
custom parameters).
Here is a simple example of how to use Datasources and HttpSocket
to
implement a very basic Twitter source that
allows querying the Twitter API as well as posting new status updates to
a configured account.
This example will only work in PHP 5.2 and above, due to the use of
json_decode
for the parsing of JSON formatted data.
You would place the Twitter datasource in app/models/datasources/twitter_source.php:
<?php
/**
* Twitter DataSource
*
* Used for reading and writing to Twitter, through models.
*
* PHP Version 5.x
*
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright 2005-2009, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @filesource
* @copyright Copyright 2009, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
App::import('Core', 'HttpSocket');
class TwitterSource extends DataSource {
protected $_schema = array(
'tweets' => array(
'id' => array(
'type' => 'integer',
'null' => true,
'key' => 'primary',
'length' => 11,
),
'text' => array(
'type' => 'string',
'null' => true,
'key' => 'primary',
'length' => 140
),
'status' => array(
'type' => 'string',
'null' => true,
'key' => 'primary',
'length' => 140
),
)
);
public function __construct($config) {
$auth = "{$config['login']}:{$config['password']}";
$this->connection = new HttpSocket(
"http://{$auth}@twitter.com/"
);
parent::__construct($config);
}
public function listSources() {
return array('tweets');
}
public function read($model, $queryData = array()) {
if (!isset($queryData['conditions']['username'])) {
$queryData['conditions']['username'] = $this->config['login'];
}
$url = "/statuses/user_timeline/";
$url .= "{$queryData['conditions']['username']}.json";
$response = json_decode($this->connection->get($url), true);
$results = array();
foreach ($response as $record) {
$record = array('Tweet' => $record);
$record['User'] = $record['Tweet']['user'];
unset($record['Tweet']['user']);
$results[] = $record;
}
return $results;
}
public function create($model, $fields = array(), $values = array()) {
$data = array_combine($fields, $values);
$result = $this->connection->post('/statuses/update.json', $data);
$result = json_decode($result, true);
if (isset($result['id']) && is_numeric($result['id'])) {
$model->setInsertId($result['id']);
return true;
}
return false;
}
public function describe($model) {
return $this->_schema['tweets'];
}
}
?>
Your model implementation could be as simple as:
<?php
class Tweet extends AppModel {
public $useDbConfig = 'twitter';
}
?>
If we had not defined our schema in the datasource itself, you would get an error message to that effect here.
And the configuration settings in your app/config/database.php
would
resemble something like this:
<?php
var $twitter = array(
'datasource' => 'twitter',
'login' => 'username',
'password' => 'password',
);
?>
Using the familiar model methods from a controller:
<?php
// Will use the username defined in the $twitter as shown above:
$tweets = $this->Tweet->find('all');
// Finds tweets by another username
$conditions= array('username' => 'caketest');
$otherTweets = $this->Tweet->find('all', compact('conditions'));
?>
Similarly, saving a new status update:
<?php
$this->Tweet->save(array('status' => 'This is an update'));
?>
You can also package Datasources into plugins.
Simply place your datasource file into
plugins/[your_plugin]/models/datasources/[your_datasource]_source.php
and refer to it using the plugin notation:
var $twitter = array(
'datasource' => 'Twitter.Twitter',
'username' => 'test@example.com',
'password' => 'hi_mom',
);
In addition, you can also add to the current selection of CakePHP’s dbo drivers in plugin form.
Simply add your drivers to
plugins/[your_plugin]/models/datasources/dbo/[your_driver].php
and
again use plugin notation:
var $twitter = array(
'driver' => 'Twitter.Twitter',
...
);
Finally, you’re also able to bundle together your own DataSource and respective drivers so that they can share functionality. First create your main class you plan to extend:
plugins/[social_network]/models/datasources/[social_network]_source.php :
<?php
class SocialNetworkSource extends DataSource {
// general functionality here
}
?>
And now create your drivers in a sub folder:
plugins/[social_network]/models/datasources/[social_network]/[twitter].php
<?php
class Twitter extends SocialNetworkSource {
// Unique functionality here
}
?>
And finally setup your database.php
settings accordingly:
var $twitter = array(
'driver' => 'SocialNetwork.Twitter',
'datasource' => 'SocialNetwork.SocialNetwork',
);
var $facebook = array(
'driver' => 'SocialNetwork.Facebook',
'datasource' => 'SocialNetwork.SocialNetwork',
);
Just like that, all your files are included Automagically! No need
to place App::import()
at the top of all your files.