Content-Negotiation with the help of Renderer and Formatters

master
Gilles Crettenand 11 years ago
parent c0159f4b79
commit 7e0a38e989

@ -0,0 +1,76 @@
<?php
namespace BSR\Lib\Formatter;
abstract class Formatter {
private static $formats = array();
/**
* @param array $formats New available formats, array(mimetype => class)
*/
protected static function registerFormats(array $formats) {
foreach($formats as $f) {
self::$formats[$f] = get_called_class();
}
}
/**
* @return Formatter The formatter to use for this request
*/
public static function getFormatter() {
self::loadFormatters();
$format = self::getFormatFromHeader();
return new $format();
}
/**
* Load all formatters in the current directory
*/
private static function loadFormatters() {
preg_match('/(.+)\\\([a-zA-Z0-9]+)/', get_called_class(), $parts);
$us = $parts[2];
$namespace = $parts[1];
$base = __DIR__.'/';
$ext = '.php';
$files = glob(sprintf('%s%s%s', $base, '*', $ext));
foreach($files as $f) {
$c = str_replace(array($base, $ext), '', $f);
if($c !== $us) {
$c = $namespace.'\\'.$c;
call_user_func(array($c, 'init'));
}
}
}
/**
* @return string The class name to instantiate in accord to the Accept header
*/
private static function getFormatFromHeader() {
if(isset($_SERVER['HTTP_ACCEPT'])) {
$formats = array_map(function($f) {
$parts = explode(';', $f);
$parts[1] = (isset($parts[1]) ? (float) preg_replace('/[^0-9\.]/', '', $parts[1]) : 1.0) * 100;
return $parts;
}, explode(',', $_SERVER['HTTP_ACCEPT']));
usort($formats, function($a, $b) { return $b[1] - $a[1]; });
foreach($formats as $f) {
if(isset(self::$formats[$f[0]])) {
return self::$formats[$f[0]];
}
}
}
return 'BSR\Lib\Formatter\Json';
}
/**
* Output the content for the given data
* @param $array data
*/
abstract public function render($data);
}

@ -0,0 +1,80 @@
<?php
namespace BSR\Lib\Formatter;
class Html extends Formatter {
protected static function init() {
self::registerFormats(array(
'application/xhtml+xml',
'text/html',
));
}
private function result($data) {
// the format is array('result' => array('funcname' => DATA))
// so take the first element of the 'result' array
$data = reset($data['result']);
$first = reset($data);
$single = ! is_array($first);
$content = '<table><thead>';
if($single) {
$content .= "<tr><th>Field</th><th>Value</th></tr>";
} else {
$content .= '<tr>';
foreach(array_keys($first) as $k) {
$content .= "<th>$k</th>";
}
$content .= '</tr>';
}
$content .= '</thead><tbody>';
if($single) {
foreach($data as $k => $v) {
$content .= "<tr><th>$k</th><td>".print_r($v, true)."</td></tr>";
}
} else {
foreach($data as $row) {
$content .= '<tr>';
foreach($row as $col) {
$content .= '<td>'.print_r($col, true).'</td>';
}
$content .= '</tr>';
}
}
$content .= '</tbody></table>';
return $content;
}
private function error($data) {
$code = $data['error']['code'];
$name = $data['error']['name'];
$msg = $data['error']['reason'];
return '<h1>An error occured</h1>'.
"<h2>[$code] $name</h2>".
"<p>$msg</p>";
}
private function failure($data) {
$code = $data['failure']['code'];
$name = $data['failure']['reason'];
return '<h1>A failure occured</h1>'.
"<h2>[$code] $name</h2>";
}
public function render($data)
{
$type = key($data);
if (method_exists($this, $type)) {
$content = call_user_func_array(array($this, $type), array($data));
} else {
$content = '<h1>Unable to render this</h1>';
}
echo $content;
}
}

@ -0,0 +1,17 @@
<?php
namespace BSR\Lib\Formatter;
class Json extends Formatter {
protected static function init() {
self::registerFormats(array(
'application/json',
'application/x-json',
));
}
public function render($data) {
echo json_encode($data);
}
}

@ -0,0 +1,13 @@
<?php
namespace BSR\Lib\Formatter;
class Text extends Formatter {
protected static function init() {
self::registerFormats(array('text/plain'));
}
public function render($data) {
print_r($data);
}
}

@ -0,0 +1,17 @@
<?php
namespace BSR\Lib\Formatter;
class Xml extends Formatter {
protected static function init() {
self::registerFormats(array(
'text/xml',
'application/xml',
'application/x-xml',
));
}
public function render($data) {
throw new \RuntimeException('Not implemented yet.');
}
}

@ -0,0 +1,29 @@
<?php
namespace BSR\Lib;
use BSR\Lib\Formatter\Formatter;
class Renderer {
private static $statusMessages = array(
200 => 'Ok',
400 => 'Bad request',
404 => 'Not Found',
403 => 'Not Authorized',
500 => 'Server Error',
);
public function __construct() {
ob_start();
}
public function render($status, $data) {
header(sprintf('HTTP/1.0 %s %s', $status, self::$statusMessages[$status]));
ob_clean();
flush();
$formatter = Formatter::getFormatter();
$formatter->render($data);
}
}

@ -3,6 +3,7 @@
namespace BSR\Lib; namespace BSR\Lib;
use BSR\Lib\Exception\WebException; use BSR\Lib\Exception\WebException;
use BSR\Lib\Renderer;
abstract class WebService abstract class WebService
{ {
@ -30,6 +31,8 @@ abstract class WebService
public function Run() public function Run()
{ {
$renderer = new Renderer();
$this->log("------------------"); $this->log("------------------");
$this->log("Start request", 1, true); $this->log("Start request", 1, true);
$data = array(); $data = array();
@ -39,19 +42,22 @@ abstract class WebService
$data["result"][$this->func] = $result; $data["result"][$this->func] = $result;
} catch (WebException $e) { } catch (WebException $e) {
$data["error"]["code"] = $e->getCode(); $data["error"]["code"] = $e->getCode();
$data["error"]["name"] = $e->getName();
$data["error"]["reason"] = $e->getMessage(); $data["error"]["reason"] = $e->getMessage();
$data["error"]["name"] = $e->getName();
$this->status = 400; $this->status = 400;
$this->log(sprintf("Error : [%s] %s", $e->getCode(), $e->getName())); $this->log(sprintf("Error : [%s] %s", $e->getCode(), $e->getName()));
} catch (\Exception $e) { } catch (\Exception $e) {
$data["failure"]["message"] = $e->getMessage(); $data["failure"]["code"] = $e->getCode();
$data["failure"]["reason"] = $e->getMessage();
$this->status = 500; $this->status = 500;
$this->log(sprintf("Failure : %s", $e->getMessage())); $this->log(sprintf("Failure : %s", $e->getMessage()));
} }
$this->Send($data); $this->log("Data: ".print_r($data, true), 2);
$renderer->render($this->status, $data);
$this->log("Request finished", 1, true); $this->log("Request finished", 1, true);
$this->log("------------------\n\n"); $this->log("------------------\n\n");
@ -63,7 +69,6 @@ abstract class WebService
private function Call() private function Call()
{ {
ob_start();
session_save_path(Configuration::get('session.save_path')); session_save_path(Configuration::get('session.save_path'));
session_start(); session_start();
@ -100,23 +105,4 @@ abstract class WebService
$this->log("Params: ".print_r($params, true), 2); $this->log("Params: ".print_r($params, true), 2);
return call_user_func_array(array($this, $this->func), $params); return call_user_func_array(array($this, $this->func), $params);
} }
private function Send(array $data)
{
static $status_messages = array(
200 => 'Ok',
400 => 'Bad request',
404 => 'Not Found',
403 => 'Not Authorized',
500 => 'Server Error',
);
header(sprintf('HTTP/1.0 %s %s', $this->status, $status_messages[$this->status]));
ob_clean();
flush();
$this->log("Data: ".print_r($data, true), 2);
echo json_encode($data);
}
} }

Loading…
Cancel
Save