PSX Manual

GPLv3

Abstract

This is the offical manual of the PSX framework.


Table of Contents

1. Introduction
1.1. Technical interoperability
1.2. RESTful APIs
1.3. Lightweight MVC
2. Installation
3. Developing a RESTful API
3.1. Setting up the table
3.2. Creating the model
3.3. The API endpoint
3.3.1. Receiving
3.3.2. Inserting
3.4. Conclusion
4. Oauth based API authorization
4.1. Setting up the table
4.2. Oauth endpoints
4.2.1. Temporary Credential Request
4.2.2. Resource Owner Authorization
4.2.3. Token Request
4.3. Protect the API endpoint
4.3.1. Inserting
4.4. Conclusion
5. Configuration
5.1. Edit
5.2. Rights
6. Template
7. Help

List of Figures

1.1. API example

List of Examples

3.1. news table
3.2. library/Example/News.php
3.3. module/news.php
3.4. module/news.php (implement onGet method)
3.5. module/news.php (implement setWriterConfig method)
3.6. library/Example/News.php (implement export method)
3.7. Sample GET request
3.8. Sample GET response
3.9. module/news.php (implement onPost method)
3.10. library/Example/News.php (implement import method)
3.11. Sample POST request
3.12. Sample POST response
4.1. request table
4.2. module/request.php
4.3. module/auth.php
4.4. module/access.php
4.5. module/news.php (implement getConsumer and onAuthenticated method)
4.6. module/news.php (implement onPost method)
4.7. Sample POST request
4.8. Sample POST response

Chapter 1. Introduction

PSX is a framework for developing dynamic websites in PHP. The goal of PSX is to help you developing RESTful APIs serving web standard formats like JSON, XML, Atom and RSS. It has a focus on social technologies and provides classes to use and implement OAuth, OpenID, Opengraph, Opensocial, Opensearch, PubSubHubbub, Atom, and RSS. At the example page you can see sample implementations using various PSX classes wich give you an good overview how the PSX framework works. On the download section you can grab the current release of PSX or you can install it via PEAR. If you want contribute or get in contact you find at the community page all necessary informations. In the following a short overview what features PSX offers.

1.1. Technical interoperability

PSX offers a comprehensive PHP library wich is loosly coupled and designed after standard naming conventions so it can be used with other projects like i.e. PEAR, Symfony or Zend. All classes are independently usable because of dependency injection.

1.2. RESTful APIs

PSX supports you in building RESTful APIs using web standard formats like JSON, XML, Atom and RSS with standard request parameters like defined in the OpenSocial Core API Spec. To build a more programmable and federated social web.

Figure 1.1. API example

API example

1.3. Lightweight MVC

PSX includes a simple MVC architecture with an fast routing mechanism and by default PHP as template engine. The class loader defined by the php standard group is used.

Chapter 2. Installation

First you must download the current version of psx. You can find that at phpsx.org. Download psx and put the dir on your web or local server. To run PSX you need PHP 5 or higher depending on what classes you use. Goto http://host/path/to/psx/public if you see a website with the title "Template sample" psx is running successfully.

Chapter 3. Developing a RESTful API

This is the main chapter of the manual wich explains step by step howto develop a RESTful API based on PSX. In this example we create a simple news API where you can create and receive news records.

3.1. Setting up the table

For our example we need a simple table called news where all records are stored.

Example 3.1. news table

CREATE TABLE IF NOT EXISTS `news` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `author` varchar(32) NOT NULL,
  `title` varchar(128) NOT NULL,
  `body` text NOT NULL,
  `date` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
				

3.2. Creating the model

Now we have to create the model for the News. The class must be created in the file library/Example/News.php. The record must implement the PSX_Data_RecordInterface in this case we use the class PSX_Data_RecordAbstract wich already implements the PSX_Data_RecordInterface and has some methods already implemented. We only need to add the method getName wich returns the name of the record and getCols wich returns an array with property names wich can be used. The setters are used to import data into the record.

Example 3.2. library/Example/News.php

<?php

class Example_News extends PSX_Data_RecordAbstract
{
	public $id;
	public $author;
	public $title;
	public $body;
	public $date;

	private $config;

	public function __construct(PSX_Config $config)
	{
		$this->config = $config;
	}

	public function getName()
	{
		return 'news';
	}

	public function getCols()
	{
		return array('id', 'author', 'title', 'body', 'date');
	}

	public function setAuthor($author)
	{
		$author = htmlspecialchars($author);

		if(strlen($author) > 3 && strlen($author) < 32)
		{
			$this->author = $author;
		}
		else
		{
			throw new Exception('Author must greater then 3 and lower then 32 signs');
		}
	}

	public function setTitle($title)
	{
		$title = htmlspecialchars($title);

		if(strlen($title) > 3 && strlen($title) < 64)
		{
			$this->title = $title;
		}
		else
		{
			throw new Exception('Title must greater then 3 and lower then 64 signs');
		}
	}

	public function setBody($body)
	{
		$body = htmlspecialchars($body);

		if(strlen($body) > 6 && strlen($body) < 1024)
		{
			$this->body = $body;
		}
		else
		{
			throw new Exception('Body must greater then 6 and lower then 1024 signs');
		}
	}

	public function getDate()
	{
		return new DateTime($this->date);
	}
}
				

3.3. The API endpoint

We create a file called news.php in the module folder. This file can be accessed with http://127.0.0.1/psx/public/index.php/news. We define the onLoad method wich is called when the module was loaded. Because we need an SQL connection to retrieve and store the news entries we create an PSX_Sql object.

This is now the REST API endpoint where we can make GET and POST requests to. You can versioning your API by creating a folder structure i.e. put the news.php in the folder "v1" and the endpoint url would be http://127.0.0.1/psx/public/index.php/v1/news

Example 3.3. module/news.php

<?php

class news extends PSX_ApiAbstract
{
	private $sql;

	public function onLoad()
	{
		// create sql connection
		$this->sql = new PSX_Sql(new PSX_Sql_Driver_Pdo(), $this->config['psx_sql_host'], $this->config['psx_sql_user'], $this->config['psx_sql_pw'], $this->config['psx_sql_db']);
	}
}
				

3.3.1. Receiving

If someone makes an GET request to the endpoint we want return the latest news. This method creates in general an PSX_Data_ResultSet object containing multiple Example_News objects. If we call the setResponse method with the PSX_Data_ResultSet object the object is transformed into the preferred format (wich was set either by the GET parameter "format" or by the "Accept" header field).

Example 3.4. module/news.php (implement onGet method)

public function onGet()
{
	// get default params
	$params = $this->getRequestParams();


	// selected fields
	$availableFields = array('id', 'author', 'title', 'body', 'date');
	$selectedFields  = array();

	if(isset($params['fields']))
	{
		foreach($params['fields'] as $field)
		{
			if(in_array($field, $availableFields))
			{
				$selectedFields[] = $field;
			}
		}
	}

	if(empty($selectedFields))
	{
		$selectedFields = $availableFields;
	}


	// limit
	$startIndex = isset($params['startIndex']) ? $params['startIndex'] : 0;
	$count      = isset($params['count'])      ? $params['count']      : 8;


	// get complete count
	$totalResults = $this->sql->count('news');


	// make query
	$sql = 'SELECT ' . implode(',', $selectedFields) . ' FROM news ORDER BY date DESC LIMIT ?, ?';

	$result = $this->sql->getAll($sql, array($startIndex, $count), PSX_Sql::FETCH_OBJECT, 'Example_News', array($this->config));


	// create resultset
	$resultset = new PSX_Data_ResultSet($totalResults, $startIndex, $count, $result);


	// set response
	$this->setResponse($resultset);
}
					

Because we want create Atom and Rss feeds we have to set the writer config by overriding the method setWriterConfig.

Example 3.5. module/news.php (implement setWriterConfig method)

protected function setWriterConfig(PSX_Data_WriterResult $writer)
{
	switch($writer->getType())
	{
		case PSX_Data_WriterInterface::RSS:

			$updated = $this->sql->getField('SELECT `date` FROM news ORDER BY `date` DESC');

			$title       = 'News';
			$link        = $this->config['psx_url'];
			$description = 'Example RESTful News API based on PSX ';


			$writer = $writer->getWriter();

			$writer->setConfig($title, $link, $description);

			$writer->setGenerator('psx ' . $this->config['psx_version']);

			break;

		case PSX_Data_WriterInterface::ATOM:

			$updated = $this->sql->getField('SELECT `date` FROM news ORDER BY `date` DESC');

			$title   = 'News';
			$id      = $this->config->getUrn('psx', 'example', 'news');
			$updated = new DateTime($updated);


			$writer = $writer->getWriter();

			$writer->setConfig($title, $id, $updated);

			$writer->setGenerator('psx ' . $this->config['psx_version']);

			break;
	}
}
					

Because we want that the record can be exported in different formats like JSON, XML, Atom, Rss we have to specific the method export in the Example_News record.

Example 3.6. library/Example/News.php (implement export method)

public function export(PSX_Data_WriterResult $result)
{
	switch($result->getType())
	{
		case PSX_Data_WriterInterface::JSON:
		case PSX_Data_WriterInterface::XML:

			return $this->getFields();

			break;

		case PSX_Data_WriterInterface::RSS:

			$title       = $this->title;
			$link        = $this->config['psx_url'] . '/' . $this->config['psx_dispatch'] . 'news/id/' . $this->id;
			$description = $this->body;

			$item = $result->getWriter()->createItem($title, $link, $description);

			$item->setAuthor($this->author);

			return $item;

			break;

		case PSX_Data_WriterInterface::ATOM:

			$title = $this->title;
			$id    = $this->id;
			$date  = $this->getDate();

			$entry = $result->getWriter()->createEntry($title, $id, $date);

			$entry->addAuthor($this->author, $this->config->getUrn('user', $this->author));

			$entry->addLink($this->config['psx_url'] . '/' . $this->config['psx_dispatch'] . 'news/id/' . $this->id, 'alternate', 'text/html');

			$entry->setContent($this->body, 'text');

			return $entry;

			break;

		default:

			throw new PSX_Data_Exception('Writer is not supported');

			break;
	}
}
					

Here an example GET request with the response

Example 3.7. Sample GET request

GET /test/psx/public/ HTTP/1.1
Host: 127.0.0.1
Accept: application/xml
					

Example 3.8. Sample GET response

HTTP/1.1 200 OK
Date: Fri, 01 Jul 2011 22:50:11 GMT
Server: Apache/2.2.12 (Win32) DAV/2 mod_ssl/2.2.12 OpenSSL/0.9.8k mod_autoindex_color PHP/5.3.0 mod_perl/2.0.4 Perl/v5.10.0
X-Powered-By: psx
Expires: Thu, 09 Oct 1986 01:00:00 GMT
Last-Modified: Thu, 09 Oct 1986 01:00:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 336
Content-Type: application/xml

<?xml version="1.0" encoding="UTF-8"?>
<resultset>
 <totalResults>1</totalResults>
 <startIndex>0</startIndex>
 <itemsPerPage>8</itemsPerPage>
 <entry>
  <id>1</id>
  <author>Foobar</author>
  <title>Some great title</title>
  <body>And much more great content ... hf ;D</body>
  <date>2011-06-30 22:15:51</date>
 </entry>
</resultset>
					

3.3.2. Inserting

If someone makes a POST request we want insert the news in the table. We create a new Example_News object and import the request data into the record.

Example 3.9. module/news.php (implement onPost method)

public function onPost()
{
	try
	{
		$news = new Example_News($this->config);
		$news->import($this->getRequest());

		if($news->hasFields('author', 'title', 'body'))
		{
			$news->date = date(PSX_Time::SQL);

			$this->sql->insert('news', $news->getData());

			$msg = new PSX_Data_Message('News record successful created', true);

			$this->setResponse($msg, null, 201);
		}
		else
		{
			throw new Exception('Missing field in record');
		}
	}
	catch(Exception $e)
	{
		$msg = new PSX_Data_Message($e->getMessage(), false);

		$this->setResponse($msg, null, 500);
	}
}
					

Because we want import data into the Example_News record we have to specify the import method. In this case we accept application/x-www-form-urlencoded, application/json and application/xml data. That means we can also use a web form pointing to the API endpoint to insert new records.

Example 3.10. library/Example/News.php (implement import method)

public function import(PSX_Data_ReaderResult $result)
{
	switch($result->getType())
	{
		case PSX_Data_ReaderInterface::FORM:
		case PSX_Data_ReaderInterface::JSON:
		case PSX_Data_ReaderInterface::XML:

			$data = (array) $result->getData();
			$data = array_intersect_key($data, array_fill_keys($this->getCols(), null));

			foreach($data as $k => $v)
			{
				if(isset($v))
				{
					$method = 'set' . ucfirst($k);

					if(is_callable(array($this, $method)))
					{
						$this->$method($v);
					}
				}
			}

			break;

		default:

			throw new PSX_Data_Exception('Reader is not supported');

			break;
	}
}
					

Here an example POST request to the API endpoint

Example 3.11. Sample POST request

POST /test/psx/public/ HTTP/1.1
Host: 127.0.0.1
Accept: application/xml
Content-type: application/xml
Content-Length: 166

<?xml version="1.0" encoding="UTF-8"?>
<news>
 <author>Foobar</author>
 <title>Some great title</title>
 <body>And much more great content ... hf ;D</body>
</news>
					

Example 3.12. Sample POST response

HTTP/1.1 201 Created
Date: Fri, 01 Jul 2011 22:51:38 GMT
Server: Apache/2.2.12 (Win32) DAV/2 mod_ssl/2.2.12 OpenSSL/0.9.8k mod_autoindex_color PHP/5.3.0 mod_perl/2.0.4 Perl/v5.10.0
X-Powered-By: psx
Expires: Thu, 09 Oct 1986 01:00:00 GMT
Last-Modified: Thu, 09 Oct 1986 01:00:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 130
Content-Type: application/xml

<?xml version="1.0" encoding="UTF-8"?>
<message>
 <text>News record successful created</text>
 <success>true</success>
</message>
					

3.4. Conclusion

This example has shown you howto build an API with PSX wich serves content in Atom, RSS, <porperty>Json</porperty> and XML on an GET request. Also it is possible to insert new records with a POST request. This API can be used by any other website or desktop application to build a more programmable web.

Chapter 4. Oauth based API authorization

It is often the case that you want that only registered users can POST new entries to the API endpoint. In this case the user has to authorize before submitting a new record. The current standard for API authorization is Oauth. PSX comes with a set of classes wich helps implementing OAuth authorization for your endpoint. At the moment PSX supports the Oauth 1.0 specification (http://tools.ietf.org/html/rfc5849) but we are working on implementing OAuth 2.0. In this chapter we will protect the news API wich we have developed in the chapter before with Oauth so that only user with a valid consumer key and consumer secret can POST news records.

4.1. Setting up the table

In order to store the Oauth requests we use the following table

Example 4.1. request table

CREATE TABLE IF NOT EXISTS `request` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `ip` varchar(128) NOT NULL,
  `token` varchar(40) NOT NULL,
  `tokenSecret` varchar(40) NOT NULL,
  `nonce` varchar(32) NOT NULL,
  `verifier` varchar(16) DEFAULT NULL,
  `authorized` int(1) NOT NULL DEFAULT '0',
  `callback` varchar(256) DEFAULT NULL,
  `exchangeDate` datetime DEFAULT NULL,
  `authorizationDate` datetime DEFAULT NULL,
  `insertDate` datetime NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `token` (`token`),
  UNIQUE KEY `tokenSecret` (`tokenSecret`),
  UNIQUE KEY `nonce` (`nonce`),
  UNIQUE KEY `verifier` (`verifier`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 ;
				

4.2. Oauth endpoints

In order to enable Oauth authentication we have to implement the following endpoints like defined in the specification.

EndpointLocation
Temporary Credential Requesthttp://127.0.0.1/psx/public/index.php/request
Resource Owner Authorizationhttp://127.0.0.1/psx/public/index.php/auth
Token Requesthttp://127.0.0.1/psx/public/index.php/access

4.2.1. Temporary Credential Request

This endpoint is for obtaining an temporary credential.

Example 4.2. module/request.php

<?php

class request extends PSX_Oauth_Provider_RequestAbstract
{
	private $sql;

	public function onLoad()
	{
		try
		{
			$this->sql = new PSX_Sql(new PSX_Sql_Driver_Pdo(), $this->config['psx_sql_host'], $this->config['psx_sql_user'], $this->config['psx_sql_pw'], $this->config['psx_sql_db']);


			// if we call the handle method the OAuth request is proccessed and
			// the getConsumer() and getResponse() method is called
			$this->handle();
		}
		catch(Exception $e)
		{
			header('HTTP/1.1 500 Internal Server Error');

			echo $e->getMessage();

			if($this->config['psx_debug'] === true)
			{
				echo "\n\n" . $e->getTraceAsString();
			}

			exit;
		}
	}

	protected function getConsumer($consumerKey)
	{
		if($consumerKey == $this->config['consumer_key'])
		{
			return new PSX_Oauth_Provider_Data_Consumer($this->config['consumer_key'], $this->config['consumer_secret']);
		}
		else
		{
			throw new PSX_Oauth_Exception('Invalid consumer key');
		}
	}

	protected function getResponse(PSX_Oauth_Provider_Data_Consumer $consumer, PSX_Oauth_Provider_Data_Request $request)
	{
		// generate tokens
		$token       = sha1(uniqid(mt_rand(), true));
		$tokenSecret = sha1(uniqid(mt_rand(), true));


		// insert request
		$this->sql->insert('request', array(

			'ip'          => $_SERVER['REMOTE_ADDR'],
			'token'       => $token,
			'tokenSecret' => $tokenSecret,
			'nonce'       => $request->getNonce(),
			'callback'    => $request->getCallback(),
			'insertDate'  => date(PSX_Time::SQL),

		));


		// return response
		$response = new PSX_Oauth_Provider_Data_Response();
		$response->setToken($token);
		$response->setTokenSecret($tokenSecret);

		return $response;
	}
}
					

4.2.2. Resource Owner Authorization

If the Oauth client has obtained the temporary credential the user will be redirected to the Resource Owner Authorization endpoint.

Example 4.3. module/auth.php

<?php

class auth extends PSX_ModuleAbstract
{
	private $sql;

	public function onLoad()
	{
		$this->sql = new PSX_Sql(new PSX_Sql_Driver_Pdo(), $this->config['psx_sql_host'], $this->config['psx_sql_user'], $this->config['psx_sql_pw'], $this->config['psx_sql_db']);

		$token = isset($_GET['oauth_token']) ? $_GET['oauth_token'] : null;

		if(!empty($token))
		{
			$row = $this->sql->getRow('SELECT id, ip, token, authorized, callback, insertDate FROM request WHERE token = ?', array($token));

			if(!empty($row))
			{
				//  validate
				if($_SERVER['REMOTE_ADDR'] != $row['ip'])
				{
					throw new Exception('Token was requested from another ip');
				}

				if($row['authorized'] != 0)
				{
					throw new Exception('Token was already authorized');
				}

				// @todo check the insertDate whether token is expired


				// generate verifier
				$verifier = substr(sha1(uniqid(mt_rand(), true)), 0, 16);


				// update request
				$con = new PSX_Sql_Condition(array('id', '=', $row['id']));

				$this->sql->update('request', array(

					'verifier'          => $verifier,
					'authorized'        => 1,
					'authorizationDate' => date(PSX_Time::SQL),

				), $con);


				// redirect user or display verifier
				if($row['callback'] != 'oob')
				{
					$url = new PSX_Url($row['callback']);
					$url->addParam('oauth_token', $row['token']);
					$url->addParam('oauth_verifier', $verifier);

					header('Location: ' . strval($url));
					exit;
				}
				else
				{
					echo '<p>You have successful authorized a token. Please provide the following verifier to your application in order to complete the authorization proccess.</p>';
					echo '<p>Verifier:</p><p><b>' . $verifier . '</b></p>';
				}
			}
			else
			{
				throw new Exception('Invalid token');
			}
		}
		else
		{
			throw new Exception('Token not set');
		}
	}
}
					

4.2.3. Token Request

Example 4.4. module/access.php

<?php

class access extends PSX_Oauth_Provider_AccessAbstract
{
	private $sql;

	private $id;
	private $nonce;
	private $verifier;

	public function onLoad()
	{
		try
		{
			$this->sql = new PSX_Sql(new PSX_Sql_Driver_Pdo(), $this->config['psx_sql_host'], $this->config['psx_sql_user'], $this->config['psx_sql_pw'], $this->config['psx_sql_db']);


			// if we call the handle method the OAuth request is proccessed and
			// the getConsumer() and getResponse() method is called
			$this->handle();
		}
		catch(Exception $e)
		{
			header('HTTP/1.1 500 Internal Server Error');

			echo $e->getMessage();

			if($this->config['psx_debug'] === true)
			{
				echo "\n\n" . $e->getTraceAsString();
			}

			exit;
		}
	}

	protected function getConsumer($consumerKey, $token)
	{
		if($consumerKey == $this->config['consumer_key'])
		{
			$row = $this->sql->getRow('SELECT id, nonce, verifier, token, tokenSecret FROM request WHERE token = ? AND authorized = 1', array($token));

			if(!empty($row))
			{
				$this->id       = $row['id'];
				$this->nonce    = $row['nonce'];
				$this->verifier = $row['verifier'];

				return new PSX_Oauth_Provider_Data_Consumer($this->config['consumer_key'], $this->config['consumer_secret'], $row['token'], $row['tokenSecret']);
			}
			else
			{
				throw new PSX_Oauth_Exception('Invalid token');
			}
		}
		else
		{
			throw new PSX_Oauth_Exception('Invalid consumer key');
		}
	}

	protected function getResponse(PSX_Oauth_Provider_Data_Consumer $consumer, PSX_Oauth_Provider_Data_Request $request)
	{
		// validate
		if($this->nonce == $request->getNonce())
		{
			throw new PSX_Oauth_Exception('Nonce hasnt changed');
		}

		if($this->verifier != $request->getVerifier())
		{
			throw new PSX_Oauth_Exception('Invalid verifier');
		}


		// generate a new access token
		$token       = sha1(uniqid(mt_rand(), true));
		$tokenSecret = sha1(uniqid(mt_rand(), true));


		// update request
		$con = new PSX_Sql_Condition(array('id', '=', $this->id));

		$this->sql->update('request', array(

			'authorized'   => 2,
			'token'        => $token,
			'tokenSecret'  => $tokenSecret,
			'exchangeDate' => date(PSX_Time::SQL),

		), $con);


		// return response
		$response = new PSX_Oauth_Provider_Data_Response();
		$response->setToken($token);
		$response->setTokenSecret($tokenSecret);

		return $response;
	}
}
					

4.3. Protect the API endpoint

The class news has to extend the class PSX_Oauth_ProviderAbstract instead of PSX_ApiAbstract

Example 4.5. module/news.php (implement getConsumer and onAuthenticated method)

protected function getConsumer($consumerKey, $token)
{
	if($consumerKey == $this->config['consumer_key'])
	{
		$row = $this->sql->getRow('SELECT token, tokenSecret FROM request WHERE token = ? AND authorized = 2', array($token));

		if(!empty($row))
		{
			return new PSX_Oauth_Provider_Data_Consumer($this->config['consumer_key'], $this->config['consumer_secret'], $row['token'], $row['tokenSecret']);
		}
		else
		{
			throw new PSX_Oauth_Exception('Invalid token');
		}
	}
	else
	{
		throw new PSX_Oauth_Exception('Invalid consumer key');
	}
}

protected function onAuthenticated()
{
	$this->isAuthed = true;
}
				

Now we have to check for an Authentication header if a POST request was made. If we call the handle method wich is defined by the class PSX_Oauth_ProviderAbstract we check whether it is a valid Oauth request.

Example 4.6. module/news.php (implement onPost method)

public function onPost()
{
	try
	{
		// check for oauth authentication
		$this->handle();

		if(!$this->isAuthed)
		{
			throw new PSX_Exception('Not authenticated', 401);
		}


		// create news record
		$news = new Example_News($this->config);
		$news->import($this->getRequest());

		if($news->hasFields('author', 'title', 'body'))
		{
			$news->date = date(PSX_Time::SQL);

			$this->sql->insert('news', $news->getData());

			$msg = new PSX_Data_Message('News record successful created', true);

			$this->setResponse($msg, null, 201);
		}
		else
		{
			throw new Exception('Missing field in record');
		}
	}
	catch(Exception $e)
	{
		$code = $e->getCode() == 0 ? 500 : $e->getCode();
		$msg  = new PSX_Data_Message($e->getMessage(), false);

		$this->setResponse($msg, null, $code);
	}
}
				

4.3.1. Inserting

Here an example Oauth request to insert a news

Example 4.7. Sample POST request

POST /tests/psx/public/ HTTP/1.1
Host: 127.0.0.1
Accept: application/xml
Content-type: application/xml
Content-Length: 171
Authorization: OAuth realm="oat", oauth_signature="056413a4c2b9cb93bc2454902a2de25d549c219b%26eecde7a5f6e11556475c995252390b0160ba01e1", oauth_version="1.0", oauth_nonce="2ac118d94ca10fdfba2587b97a80dc89", oauth_signature_method="PLAINTEXT", oauth_consumer_key="64e197648df5f30acbc770d8a69d09bb391e78cb", oauth_token="901fbcf6e1f949f39c8458210f646c4407ec67f9", oauth_timestamp="1309609401"

<?xml version="1.0" encoding="UTF-8"?>
<news>
  <author>foobar</author>
  <title>And some great news</title>
  <body>And here the complete content ... hf ;D</body>
</news>
					

Example 4.8. Sample POST response

HTTP/1.1 201 Created
Date: Sat, 02 Jul 2011 12:23:21 GMT
Server: Apache/2.2.12 (Win32) DAV/2 mod_ssl/2.2.12 OpenSSL/0.9.8k mod_autoindex_color PHP/5.3.0 mod_perl/2.0.4 Perl/v5.10.0
X-Powered-By: psx
Expires: Thu, 09 Oct 1986 01:00:00 GMT
Last-Modified: Thu, 09 Oct 1986 01:00:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 130
Content-Type: application/xml

<?xml version="1.0" encoding="UTF-8"?>
<message>
 <text>News record successful created</text>
 <success>true</success>
</message>
					

4.4. Conclusion

This was an basic example howto protect your API with Oauth.

Note: This is an example implementation to show you the basic functionallity howto use the Oauth classes of PSX. Because of simplicity we use only a single consumer key and consumer secret normally you would save those in a table and gengerate per user a consumer key and secret. Please see the Amun project for a real implementation of the OAuth classes.

Chapter 5. Configuration

Table of Contents

5.1. Edit
5.2. Rights

To configure PSX you must make two steps first edit the configuration.php and second set the rights if you want use the caching mechanism.

5.1. Edit

To edit the configuration you must open the file configuration.php with an editor of your choice. The config is an php array with key value pairs. You must change the key "psx_url" so that its point to the psx public root. All other entries are optional. The following table describes each entry.

KeyDescription
psx_versionThe version of psx that you are using.
psx_urlThe absolute url to the psx folder
psx_dispatchWhere we get the input path normally index.php/.
psx_timezoneThe default timezone
psx_gzipWhether to gzip the output of psx. The content gets only compressed if the browser support gzip.
psx_debugWhether psx runs in debug mode or not (display the trace if an exception is thrown).
psx_module_defaultThe module wich is loaded by default
psx_module_inputFrom wich source we get our input string
psx_module_input_lengthThe max length of an input. If the request is longer the user gets an "414 Request-URI Too Long" response
psx_sql_hostYour sql host. Only necessary if you need a sql connection
psx_sql_userYour sql user. Only necessary if you need a sql connection
psx_sql_pwYour sql pw. Only necessary if you need a sql connection
psx_sql_dbYour sql db. Only necessary if you need a sql connection
psx_cache_enabledThe general option whether caching is enabled or not
psx_cache_expireHow long the cached result will remain in seconds
psx_template_dirThis is the name of an folder in the dir template
psx_template_defaultBy default you have to set in each module a template. Here you can set a default template wich is loaded if no template is specified. This must be a path to a template file in the template dir.
psx_path_cacheThe path to the cache folder
psx_path_libraryThe path to the library folder. If this is null the classes are loaded from the include_path i.e. you can set this to null if you have installed the library via PEAR.
psx_path_moduleThe path to the module folder
psx_path_templateThe path to the template folder

5.2. Rights

If you want use the cache you need in the folder cache/ read and write permissions. This is the place where all cache files are stored.

Chapter 6. Template

By default PSX uses simple php as template engine because it is fast and you have more freedome to design a website. In your template you can access the variable (that you have assigned) with $[key]. You can also use some predefined variables wich are listed here:

VariableDescription
$configThe PSX_Config object wich holds the array from the configuration.php file. Note the object may contain sensitive informations like sql username and password.
$selfThis is the current URI. You can use this in a form to point to the module
$urlThe complete url including the dispath i.e. http://foobar.org/index.php/
$locationThis is the path to your current template i.e. template/default in order to include other templates
$renderThe rendering time in seconds
$baseThe basepath wich can be used to include i.e. javascript, css or images.

Chapter 7. Help

Because PSX is in an early stage the manual is not complete. I appreciate every help in making this documentation better. The documentation is writte in XML against the docbook specification. You can checkout the current version of this manual via SVN. The XML file is in doc/docbook/manual.xml. If you have made some changes that you want commit please contact me.

Further reading about PSX

  1. Mailinglist: http://groups.google.com/group/phpsx

  2. Examples: http://example.phpsx.org

  3. Website: http://phpsx.org

  4. Bugtracker: http://code.google.com/p/psx/issues/list

  5. Repository: http://code.google.com/p/psx/source/checkout