|
<?php |
|
|
|
/** |
|
* @link http://tools.ietf.org/html/draft-nottingham-http-problem |
|
* @link http://tools.ietf.org/html/rfc5988 |
|
* |
|
* @package ApiProblem |
|
* @category Exception |
|
*/ |
|
|
|
/** |
|
* @package ApiProblem |
|
* @category Exception |
|
*/ |
|
class ApiProblem extends \Exception |
|
{ |
|
/** |
|
* |
|
*/ |
|
const FORMAT_JSON = 'application/api-problem+json'; |
|
|
|
/** |
|
* |
|
*/ |
|
const FORMAT_XML = 'application/api-problem+xml'; |
|
|
|
/** |
|
* @link http://tools.ietf.org/html/draft-nottingham-http-problem |
|
*/ |
|
const XMLNS = 'urn:ietf:draft:nottingham-http-problem'; |
|
|
|
/** |
|
* An absolute URI [RFC3986] that identifies the problem type. When dereferenced, it SHOULD provide human-readable |
|
* documentation for the problem type (e.g., using HTML) |
|
* @var string |
|
*/ |
|
protected $problemType; |
|
|
|
/** |
|
* A short, human-readable summary of the problem type. It SHOULD NOT change from occurrence to occurrence of the |
|
* problem, except for purposes of localisation. |
|
* @var string |
|
*/ |
|
protected $title; |
|
|
|
/** |
|
* The HTTP status code ([RFC2616], Section 6) generated by the origin server for this occurrence of the problem. |
|
* (optional) |
|
* @var number |
|
*/ |
|
protected $httpStatus; |
|
|
|
/** |
|
* An human readable explanation specific to this occurrence of the problem. |
|
* (optional) |
|
* @var string |
|
*/ |
|
protected $detail; |
|
|
|
/** |
|
* An absolute URI that identifies the specific occurrence of the problem. It may or may not yield further |
|
* information if dereferenced. |
|
* (optional) |
|
* @var string |
|
*/ |
|
protected $problemInstance; |
|
|
|
/** |
|
* @var array |
|
*/ |
|
protected $extension = array(); |
|
|
|
/** |
|
* @var \DOMDocument |
|
*/ |
|
protected $xml; |
|
|
|
/** |
|
* @param string $problemType |
|
* @param string $title |
|
* @param null|number $httpStatus |
|
* @param null|string $detail |
|
* @param null|string $problemInstance |
|
*/ |
|
public function __construct($problemType, $title, $httpStatus=null, $detail=null, $problemInstance=null) |
|
{ |
|
$this->problemType = $problemType; |
|
$this->title = $title; |
|
$this->message = $title; |
|
if ($httpStatus) { |
|
$this->code = $httpStatus; |
|
} |
|
if ($httpStatus) { |
|
$this->httpStatus = $httpStatus; |
|
} |
|
if ($detail) { |
|
$this->detail = $detail; |
|
} |
|
if ($problemInstance) { |
|
$this->problemInstance = $problemInstance; |
|
} |
|
} |
|
|
|
/** |
|
* |
|
* @param string $detail |
|
* @return ApiProblem |
|
*/ |
|
public function setDetail($detail) |
|
{ |
|
$this->detail = $detail; |
|
return $this; |
|
} |
|
|
|
/** |
|
* @return string |
|
*/ |
|
public function getDetail() |
|
{ |
|
return $this->detail; |
|
} |
|
|
|
/** |
|
* |
|
* @param array $extension |
|
* @return ApiProblem |
|
*/ |
|
public function setExtension($extension) |
|
{ |
|
$this->extension = $extension; |
|
return $this; |
|
} |
|
|
|
/** |
|
* @return array |
|
*/ |
|
public function getExtension() |
|
{ |
|
return $this->extension; |
|
} |
|
|
|
/** |
|
* |
|
* @param number $httpStatus |
|
* @return ApiProblem |
|
*/ |
|
public function setHttpStatus($httpStatus) |
|
{ |
|
$this->httpStatus = $httpStatus; |
|
return $this; |
|
} |
|
|
|
/** |
|
* @return number |
|
*/ |
|
public function getHttpStatus() |
|
{ |
|
return $this->httpStatus; |
|
} |
|
|
|
/** |
|
* |
|
* @param string $problemInstance |
|
* @return ApiProblem |
|
*/ |
|
public function setProblemInstance($problemInstance) |
|
{ |
|
$this->problemInstance = $problemInstance; |
|
return $this; |
|
} |
|
|
|
/** |
|
* @return string |
|
*/ |
|
public function getProblemInstance() |
|
{ |
|
return $this->problemInstance; |
|
} |
|
|
|
/** |
|
* |
|
* @param string $problemType |
|
* @return ApiProblem |
|
*/ |
|
public function setProblemType($problemType) |
|
{ |
|
$this->problemType = $problemType; |
|
return $this; |
|
} |
|
|
|
/** |
|
* @return string |
|
*/ |
|
public function getProblemType() |
|
{ |
|
return $this->problemType; |
|
} |
|
|
|
/** |
|
* |
|
* @param string $title |
|
* @return ApiProblem |
|
*/ |
|
public function setTitle($title) |
|
{ |
|
$this->title = $title; |
|
return $this; |
|
} |
|
|
|
/** |
|
* @return string |
|
*/ |
|
public function getTitle() |
|
{ |
|
return $this->title; |
|
} |
|
|
|
/** |
|
* @param string $extensionKey |
|
* @param null|string $extensionValue |
|
*/ |
|
public function setExtensionData($extensionKey, $extensionValue) |
|
{ |
|
if (!preg_match('/(^[a-z])/i', $extensionKey)) { |
|
trigger_error('api-problem extension names must begin with an ALPHA character', E_ERROR); |
|
} |
|
if (!preg_match('/^([a-z0-9_]){1,}$/i', $extensionKey)) { |
|
trigger_error('api-problem extension names must contain an "ALPHA", "DIGIT" or "_" character', E_ERROR); |
|
} |
|
if (!in_array($extensionKey, array('problemType','title','httpStatus','detail','problemInstance'))){ |
|
$this->extension[$extensionKey] = $extensionValue; |
|
} else { |
|
trigger_error('api-problem extension names MUST NOT share the same name as api-problem members', E_ERROR); |
|
} |
|
} |
|
|
|
/** |
|
* @return array |
|
*/ |
|
protected function formatResult() |
|
{ |
|
$firstClassMembers = array( |
|
'problemType' => $this->problemType, |
|
'title' => $this->title, |
|
'httpStatus' => $this->httpStatus, |
|
'detail' => $this->detail, |
|
'problemInstance' => $this->problemInstance |
|
); |
|
return array_filter($firstClassMembers); |
|
} |
|
|
|
/** |
|
* @return string |
|
*/ |
|
public function toXml() |
|
{ |
|
$this->xml = new \DOMDocument('1.0', 'UTF-8'); |
|
$problemElement = $this->xml->createElement('problem'); |
|
$problemElement->setAttribute('xmlns', self::XMLNS); |
|
foreach ($this->formatResult() as $node => $nodeValue) { |
|
$node = $problemElement->appendChild(new \DOMElement($node)); |
|
$node->appendChild($this->xml->createTextNode($nodeValue)); |
|
} |
|
foreach ($this->extension as $extensionName => $extensionValue) { |
|
$childNode = $this->appendXmlChildren($extensionName, $extensionValue); |
|
$problemElement->appendChild($childNode); |
|
} |
|
$this->xml->appendChild($problemElement); |
|
return $this->xml->saveXML(); |
|
} |
|
|
|
/** |
|
* @param $nodeName |
|
* @param $nodeValue |
|
* @return \DOMElement|\DOMText |
|
*/ |
|
protected function appendXmlChildren($nodeName, $nodeValue) |
|
{ |
|
if (is_array($nodeValue)) { |
|
$thisNode = $this->xml->createElement($nodeName); |
|
foreach ($nodeValue as $childName => $childValue) { |
|
$childName = is_numeric($childName) ? 'i' : $childName; |
|
$thisNode->appendChild($this->appendXmlChildren($childName, $childValue)); |
|
} |
|
return $thisNode; |
|
} else { |
|
return $this->xml->createElement($nodeName, $nodeValue); |
|
} |
|
} |
|
|
|
/** |
|
* @return string |
|
*/ |
|
public function toJson() |
|
{ |
|
$result = $this->formatResult(); |
|
foreach ($this->getExtension() as $n => $v) { |
|
$result[$n] = $v; |
|
} |
|
if (version_compare(PHP_VERSION, '5.4.0', '<')) { |
|
$json = json_encode($result); |
|
$json = preg_replace('/\\\//', null, $json); |
|
} else { |
|
$json = json_encode($result, JSON_UNESCAPED_SLASHES); |
|
} |
|
return $json; |
|
} |
|
|
|
/** |
|
* @link http://tools.ietf.org/html/rfc5988 |
|
* @return string |
|
*/ |
|
protected function declareAndSetLink() |
|
{ |
|
return sprintf('Link: <%s>; rel="%s"; title="%s"', $this->problemType, $this->problemType, $this->title); |
|
} |
|
|
|
/** |
|
* @param string $format |
|
* @param bool $terminate |
|
* @return string |
|
*/ |
|
public function sendHTTPResponse($format = self::FORMAT_JSON, $terminate = false) |
|
{ |
|
switch ($format) { |
|
case self::FORMAT_XML: |
|
$body = $this->toXml(); |
|
break; |
|
case self::FORMAT_JSON: |
|
default: |
|
$body = $this->toJson(); |
|
break; |
|
} |
|
$link = $this->declareAndSetLink(); |
|
$contentType = sprintf('Content-Type:%s', $format); |
|
if ($terminate === true && (defined('PHPUNIT_TEST_ACTIVE') && !PHPUNIT_TEST_ACTIVE)) { |
|
ob_clean(); |
|
if (!headers_sent()) { |
|
header($contentType); |
|
header($link); |
|
} |
|
exit($body); |
|
} elseif (defined('PHPUNIT_TEST_ACTIVE') && PHPUNIT_TEST_ACTIVE) { |
|
return $contentType . "\r\n" . $link . "\r\n\r\n" . $body; |
|
} else { |
|
return $body; |
|
} |
|
} |
|
} |
|
|