2016-12-19 16:16:27 +03:00
|
|
|
<?php
|
|
|
|
namespace GraphQL;
|
|
|
|
|
|
|
|
use GraphQL\Error\Error;
|
2017-01-19 15:23:00 +03:00
|
|
|
use GraphQL\Error\InvariantViolation;
|
2016-12-19 16:16:27 +03:00
|
|
|
use GraphQL\Executor\ExecutionResult;
|
|
|
|
use GraphQL\Executor\Promise\PromiseAdapter;
|
|
|
|
use GraphQL\Language\AST\DocumentNode;
|
|
|
|
use GraphQL\Language\Parser;
|
|
|
|
use GraphQL\Type\Definition\Directive;
|
|
|
|
use GraphQL\Type\Definition\ObjectType;
|
|
|
|
use GraphQL\Type\Definition\Type;
|
|
|
|
use GraphQL\Type\Resolution;
|
|
|
|
use GraphQL\Validator\DocumentValidator;
|
|
|
|
|
|
|
|
class Server
|
|
|
|
{
|
|
|
|
const DEBUG_PHP_ERRORS = 1;
|
|
|
|
const DEBUG_EXCEPTIONS = 2;
|
|
|
|
const DEBUG_ALL = 7;
|
|
|
|
|
|
|
|
private $queryType;
|
|
|
|
|
|
|
|
private $mutationType;
|
|
|
|
|
|
|
|
private $subscriptionType;
|
|
|
|
|
|
|
|
private $directives;
|
|
|
|
|
|
|
|
private $types = [];
|
|
|
|
|
|
|
|
private $schema;
|
|
|
|
|
|
|
|
private $debug = 0;
|
|
|
|
|
|
|
|
private $contextValue;
|
|
|
|
|
|
|
|
private $rootValue;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var callable
|
|
|
|
*/
|
|
|
|
private $phpErrorFormatter = ['GraphQL\Error\FormattedError', 'createFromPHPError'];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var callable
|
|
|
|
*/
|
|
|
|
private $exceptionFormatter = ['GraphQL\Error\FormattedError', 'createFromException'];
|
|
|
|
|
|
|
|
private $unexpectedErrorMessage = 'Unexpected Error';
|
|
|
|
|
|
|
|
private $unexpectedErrorStatus = 500;
|
|
|
|
|
|
|
|
private $validationRules;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var Resolution
|
|
|
|
*/
|
|
|
|
private $typeResolutionStrategy;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var PromiseAdapter
|
|
|
|
*/
|
|
|
|
private $promiseAdapter;
|
|
|
|
|
|
|
|
private $phpErrors = [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return static
|
|
|
|
*/
|
|
|
|
public static function create()
|
|
|
|
{
|
|
|
|
return new static();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return ObjectType|null
|
|
|
|
*/
|
|
|
|
public function getQueryType()
|
|
|
|
{
|
|
|
|
return $this->queryType;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param ObjectType $queryType
|
|
|
|
* @return Server
|
|
|
|
*/
|
|
|
|
public function setQueryType(ObjectType $queryType)
|
|
|
|
{
|
2017-01-19 15:23:00 +03:00
|
|
|
$this->assertSchemaNotSet('Query Type', __METHOD__);
|
2016-12-19 16:16:27 +03:00
|
|
|
$this->queryType = $queryType;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return ObjectType|null
|
|
|
|
*/
|
|
|
|
public function getMutationType()
|
|
|
|
{
|
|
|
|
return $this->mutationType;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param ObjectType $mutationType
|
|
|
|
* @return Server
|
|
|
|
*/
|
|
|
|
public function setMutationType(ObjectType $mutationType)
|
|
|
|
{
|
2017-01-19 15:23:00 +03:00
|
|
|
$this->assertSchemaNotSet('Mutation Type', __METHOD__);
|
2016-12-19 16:16:27 +03:00
|
|
|
$this->mutationType = $mutationType;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return ObjectType|null
|
|
|
|
*/
|
|
|
|
public function getSubscriptionType()
|
|
|
|
{
|
|
|
|
return $this->subscriptionType;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param ObjectType $subscriptionType
|
|
|
|
* @return Server
|
|
|
|
*/
|
|
|
|
public function setSubscriptionType($subscriptionType)
|
|
|
|
{
|
2017-01-19 15:23:00 +03:00
|
|
|
$this->assertSchemaNotSet('Subscription Type', __METHOD__);
|
2016-12-19 16:16:27 +03:00
|
|
|
$this->subscriptionType = $subscriptionType;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Type[] $types
|
|
|
|
* @return Server
|
|
|
|
*/
|
|
|
|
public function addTypes(array $types)
|
|
|
|
{
|
2017-01-19 15:23:00 +03:00
|
|
|
if (!empty($types)) {
|
|
|
|
$this->assertSchemaNotSet('Types', __METHOD__);
|
|
|
|
$this->types = array_merge($this->types, $types);
|
|
|
|
}
|
2016-12-19 16:16:27 +03:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2017-01-19 15:23:00 +03:00
|
|
|
/**
|
|
|
|
* @return array
|
|
|
|
*/
|
2016-12-19 16:16:27 +03:00
|
|
|
public function getTypes()
|
|
|
|
{
|
|
|
|
return $this->types;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return Directive[]
|
|
|
|
*/
|
|
|
|
public function getDirectives()
|
|
|
|
{
|
|
|
|
if (null === $this->directives) {
|
|
|
|
$this->directives = Directive::getInternalDirectives();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->directives;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Directive[] $directives
|
|
|
|
* @return Server
|
|
|
|
*/
|
|
|
|
public function setDirectives(array $directives)
|
|
|
|
{
|
2017-01-19 15:23:00 +03:00
|
|
|
$this->assertSchemaNotSet('Directives', __METHOD__);
|
2016-12-19 16:16:27 +03:00
|
|
|
$this->directives = $directives;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return int
|
|
|
|
*/
|
|
|
|
public function getDebug()
|
|
|
|
{
|
|
|
|
return $this->debug;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param int $debug
|
|
|
|
* @return Server
|
|
|
|
*/
|
|
|
|
public function setDebug($debug = self::DEBUG_ALL)
|
|
|
|
{
|
|
|
|
$this->debug = (int) $debug;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return mixed
|
|
|
|
*/
|
|
|
|
public function getContext()
|
|
|
|
{
|
|
|
|
return $this->contextValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param mixed $context
|
|
|
|
* @return Server
|
|
|
|
*/
|
|
|
|
public function setContext($context)
|
|
|
|
{
|
|
|
|
$this->contextValue = $context;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param $rootValue
|
|
|
|
* @return Server
|
|
|
|
*/
|
|
|
|
public function setRootValue($rootValue)
|
|
|
|
{
|
|
|
|
$this->rootValue = $rootValue;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return mixed
|
|
|
|
*/
|
|
|
|
public function getRootValue()
|
|
|
|
{
|
|
|
|
return $this->rootValue;
|
|
|
|
}
|
|
|
|
|
2017-01-19 15:23:00 +03:00
|
|
|
/**
|
|
|
|
* Set schema instance manually. Mutually exclusive with `setQueryType`, `setMutationType`, `setSubscriptionType`, `setDirectives`, `addTypes`
|
|
|
|
*
|
|
|
|
* @param Schema $schema
|
|
|
|
* @return $this
|
|
|
|
*/
|
|
|
|
public function setSchema(Schema $schema)
|
|
|
|
{
|
|
|
|
if ($this->queryType) {
|
|
|
|
$err = 'Query Type is already set';
|
|
|
|
$errMethod = __CLASS__ . '::setQueryType';
|
|
|
|
} else if ($this->mutationType) {
|
|
|
|
$err = 'Mutation Type is already set';
|
|
|
|
$errMethod = __CLASS__ . '::setMutationType';
|
|
|
|
} else if ($this->subscriptionType) {
|
|
|
|
$err = 'Subscription Type is already set';
|
|
|
|
$errMethod = __CLASS__ . '::setSubscriptionType';
|
|
|
|
} else if ($this->directives) {
|
|
|
|
$err = 'Directives are already set';
|
|
|
|
$errMethod = __CLASS__ . '::setDirectives';
|
|
|
|
} else if ($this->types) {
|
|
|
|
$err = 'Additional types are already set';
|
|
|
|
$errMethod = __CLASS__ . '::addTypes';
|
|
|
|
} else if ($this->typeResolutionStrategy) {
|
|
|
|
$err = 'Type Resolution Strategy is already set';
|
|
|
|
$errMethod = __CLASS__ . '::setTypeResolutionStrategy';
|
|
|
|
} else if ($this->schema && $this->schema !== $schema) {
|
|
|
|
$err = 'Different schema is already set';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($err)) {
|
|
|
|
if (isset($errMethod)) {
|
|
|
|
$err .= " ($errMethod is mutually exclusive with " . __METHOD__ . ")";
|
|
|
|
}
|
|
|
|
throw new InvariantViolation("Cannot set Schema on Server: $err");
|
|
|
|
}
|
|
|
|
$this->schema = $schema;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param $entry
|
|
|
|
* @param $mutuallyExclusiveMethod
|
|
|
|
*/
|
|
|
|
private function assertSchemaNotSet($entry, $mutuallyExclusiveMethod)
|
|
|
|
{
|
|
|
|
if ($this->schema) {
|
|
|
|
$schemaMethod = __CLASS__ . '::setSchema';
|
|
|
|
throw new InvariantViolation("Cannot set $entry on Server: Schema is already set ($mutuallyExclusiveMethod is mutually exclusive with $schemaMethod)");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-19 16:16:27 +03:00
|
|
|
/**
|
|
|
|
* @return Schema
|
|
|
|
*/
|
|
|
|
public function getSchema()
|
|
|
|
{
|
|
|
|
if (null === $this->schema) {
|
|
|
|
$this->schema = new Schema([
|
|
|
|
'query' => $this->queryType,
|
|
|
|
'mutation' => $this->mutationType,
|
|
|
|
'subscription' => $this->subscriptionType,
|
|
|
|
'directives' => $this->directives,
|
|
|
|
'types' => $this->types,
|
|
|
|
'typeResolution' => $this->typeResolutionStrategy
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
return $this->schema;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return callable
|
|
|
|
*/
|
|
|
|
public function getPhpErrorFormatter()
|
|
|
|
{
|
|
|
|
return $this->phpErrorFormatter;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-01-19 15:23:00 +03:00
|
|
|
* Expects function(\ErrorException $e) : array
|
|
|
|
*
|
2016-12-19 16:16:27 +03:00
|
|
|
* @param callable $phpErrorFormatter
|
|
|
|
*/
|
|
|
|
public function setPhpErrorFormatter(callable $phpErrorFormatter)
|
|
|
|
{
|
|
|
|
$this->phpErrorFormatter = $phpErrorFormatter;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return callable
|
|
|
|
*/
|
|
|
|
public function getExceptionFormatter()
|
|
|
|
{
|
|
|
|
return $this->exceptionFormatter;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-01-19 15:23:00 +03:00
|
|
|
* Expects function(Exception $e) : array
|
|
|
|
*
|
2016-12-19 16:16:27 +03:00
|
|
|
* @param callable $exceptionFormatter
|
|
|
|
*/
|
|
|
|
public function setExceptionFormatter(callable $exceptionFormatter)
|
|
|
|
{
|
|
|
|
$this->exceptionFormatter = $exceptionFormatter;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getUnexpectedErrorMessage()
|
|
|
|
{
|
|
|
|
return $this->unexpectedErrorMessage;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $unexpectedErrorMessage
|
|
|
|
*/
|
|
|
|
public function setUnexpectedErrorMessage($unexpectedErrorMessage)
|
|
|
|
{
|
|
|
|
$this->unexpectedErrorMessage = $unexpectedErrorMessage;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return int
|
|
|
|
*/
|
|
|
|
public function getUnexpectedErrorStatus()
|
|
|
|
{
|
|
|
|
return $this->unexpectedErrorStatus;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param int $unexpectedErrorStatus
|
|
|
|
*/
|
|
|
|
public function setUnexpectedErrorStatus($unexpectedErrorStatus)
|
|
|
|
{
|
|
|
|
$this->unexpectedErrorStatus = $unexpectedErrorStatus;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-01-19 15:23:00 +03:00
|
|
|
* Parses GraphQL query string and returns Abstract Syntax Tree for this query
|
|
|
|
*
|
2016-12-19 16:16:27 +03:00
|
|
|
* @param string $query
|
|
|
|
* @return Language\AST\DocumentNode
|
2017-01-19 15:23:00 +03:00
|
|
|
* @throws \GraphQL\Error\SyntaxError
|
2016-12-19 16:16:27 +03:00
|
|
|
*/
|
|
|
|
public function parse($query)
|
|
|
|
{
|
|
|
|
return Parser::parse($query);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getValidationRules()
|
|
|
|
{
|
|
|
|
if (null === $this->validationRules) {
|
|
|
|
$this->validationRules = DocumentValidator::allRules();
|
|
|
|
}
|
|
|
|
return $this->validationRules;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param array $validationRules
|
2017-01-19 15:23:00 +03:00
|
|
|
* @return $this
|
2016-12-19 16:16:27 +03:00
|
|
|
*/
|
|
|
|
public function setValidationRules(array $validationRules)
|
|
|
|
{
|
|
|
|
$this->validationRules = $validationRules;
|
2017-01-19 15:23:00 +03:00
|
|
|
return $this;
|
2016-12-19 16:16:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return Resolution
|
|
|
|
*/
|
|
|
|
public function getTypeResolutionStrategy()
|
|
|
|
{
|
|
|
|
return $this->typeResolutionStrategy;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Resolution $typeResolutionStrategy
|
|
|
|
* @return Server
|
|
|
|
*/
|
|
|
|
public function setTypeResolutionStrategy(Resolution $typeResolutionStrategy)
|
|
|
|
{
|
2017-01-19 15:23:00 +03:00
|
|
|
$this->assertSchemaNotSet('Type Resolution Strategy', __METHOD__);
|
2016-12-19 16:16:27 +03:00
|
|
|
$this->typeResolutionStrategy = $typeResolutionStrategy;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-01-19 15:23:00 +03:00
|
|
|
* @return PromiseAdapter|null
|
2016-12-19 16:16:27 +03:00
|
|
|
*/
|
|
|
|
public function getPromiseAdapter()
|
|
|
|
{
|
|
|
|
return $this->promiseAdapter;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* See /docs/data-fetching.md#async-php
|
|
|
|
*
|
|
|
|
* @param PromiseAdapter $promiseAdapter
|
|
|
|
* @return Server
|
|
|
|
*/
|
|
|
|
public function setPromiseAdapter(PromiseAdapter $promiseAdapter)
|
|
|
|
{
|
2017-01-19 15:23:00 +03:00
|
|
|
if ($this->promiseAdapter && $promiseAdapter !== $this->promiseAdapter) {
|
|
|
|
throw new InvariantViolation("Cannot set promise adapter: Different adapter is already set");
|
|
|
|
}
|
2016-12-19 16:16:27 +03:00
|
|
|
$this->promiseAdapter = $promiseAdapter;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-01-19 15:23:00 +03:00
|
|
|
* Returns array with validation errors (empty when there are no validation errors)
|
2016-12-19 16:16:27 +03:00
|
|
|
*
|
|
|
|
* @param DocumentNode $query
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function validate(DocumentNode $query)
|
|
|
|
{
|
2017-01-19 15:23:00 +03:00
|
|
|
try {
|
|
|
|
$schema = $this->getSchema();
|
|
|
|
} catch (InvariantViolation $e) {
|
|
|
|
throw new InvariantViolation("Cannot validate, schema contains errors: {$e->getMessage()}", null, $e);
|
|
|
|
}
|
|
|
|
|
|
|
|
return DocumentValidator::validate($schema, $query, $this->validationRules);
|
2016-12-19 16:16:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-01-19 15:23:00 +03:00
|
|
|
* Executes GraphQL query against this server and returns execution result
|
|
|
|
*
|
2016-12-19 16:16:27 +03:00
|
|
|
* @param string|DocumentNode $query
|
|
|
|
* @param array|null $variables
|
|
|
|
* @param string|null $operationName
|
|
|
|
* @return ExecutionResult
|
|
|
|
*/
|
|
|
|
public function executeQuery($query, array $variables = null, $operationName = null)
|
|
|
|
{
|
|
|
|
$this->phpErrors = [];
|
|
|
|
if ($this->debug & static::DEBUG_PHP_ERRORS) {
|
|
|
|
// Catch custom errors (to report them in query results)
|
|
|
|
set_error_handler(function($severity, $message, $file, $line) {
|
|
|
|
$this->phpErrors[] = new \ErrorException($message, 0, $severity, $file, $line);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
$result = GraphQL::executeAndReturnResult(
|
|
|
|
$this->getSchema(),
|
|
|
|
$query,
|
|
|
|
$this->getRootValue(),
|
|
|
|
$this->getContext(),
|
|
|
|
$variables,
|
|
|
|
$operationName
|
|
|
|
);
|
|
|
|
|
|
|
|
// Add details about original exception in error entry (if any)
|
|
|
|
if ($this->debug & static::DEBUG_EXCEPTIONS) {
|
|
|
|
$result->setErrorFormatter([$this, 'formatError']);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add reported PHP errors to result (if any)
|
|
|
|
if (!empty($this->phpErrors) && ($this->debug & static::DEBUG_PHP_ERRORS)) {
|
|
|
|
$result->extensions['phpErrors'] = array_map($this->phpErrorFormatter, $this->phpErrors);
|
|
|
|
}
|
|
|
|
|
2017-01-19 15:23:00 +03:00
|
|
|
if ($this->debug & static::DEBUG_PHP_ERRORS) {
|
2016-12-19 16:16:27 +03:00
|
|
|
restore_error_handler();
|
|
|
|
}
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
2017-01-19 15:23:00 +03:00
|
|
|
/**
|
|
|
|
* GraphQL HTTP endpoint compatible with express-graphql
|
|
|
|
*/
|
2016-12-19 16:16:27 +03:00
|
|
|
public function handleRequest()
|
|
|
|
{
|
|
|
|
try {
|
|
|
|
$httpStatus = 200;
|
|
|
|
if (isset($_SERVER['CONTENT_TYPE']) && strpos($_SERVER['CONTENT_TYPE'], 'application/json') !== false) {
|
2017-01-19 15:23:00 +03:00
|
|
|
$raw = $this->readInput();
|
2016-12-19 16:16:27 +03:00
|
|
|
$data = json_decode($raw, true);
|
|
|
|
} else {
|
|
|
|
$data = $_REQUEST;
|
|
|
|
}
|
|
|
|
$data += ['query' => null, 'variables' => null];
|
|
|
|
$result = $this->executeQuery($data['query'], (array) $data['variables'])->toArray();
|
2017-01-19 15:23:00 +03:00
|
|
|
} catch (\Exception $e) {
|
2016-12-19 16:16:27 +03:00
|
|
|
// This is only possible for schema creation errors and some very unpredictable errors,
|
|
|
|
// (all errors which occur during query execution are caught and included in final response)
|
|
|
|
$httpStatus = $this->unexpectedErrorStatus;
|
2017-01-19 15:23:00 +03:00
|
|
|
$error = new Error($this->unexpectedErrorMessage, null, null, null, null, $e);
|
|
|
|
$result = ['errors' => [$this->formatError($error)]];
|
|
|
|
} catch (\Error $e) {
|
|
|
|
$httpStatus = $this->unexpectedErrorStatus;
|
|
|
|
$error = new Error($this->unexpectedErrorMessage, null, null, null, null, $e);
|
2016-12-19 16:16:27 +03:00
|
|
|
$result = ['errors' => [$this->formatError($error)]];
|
|
|
|
}
|
|
|
|
|
2017-01-19 15:23:00 +03:00
|
|
|
$this->produceOutput($result, $httpStatus);
|
2016-12-19 16:16:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
private function formatException(\Exception $e)
|
|
|
|
{
|
|
|
|
$formatter = $this->exceptionFormatter;
|
|
|
|
return $formatter($e);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Error $e
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function formatError(\GraphQL\Error\Error $e)
|
|
|
|
{
|
|
|
|
$result = $e->toSerializableArray();
|
|
|
|
|
|
|
|
if (($this->debug & static::DEBUG_EXCEPTIONS) && $e->getPrevious()) {
|
|
|
|
$result['exception'] = $this->formatException($e->getPrevious());
|
|
|
|
}
|
|
|
|
return $result;
|
|
|
|
}
|
2017-01-19 15:23:00 +03:00
|
|
|
|
|
|
|
protected function readInput()
|
|
|
|
{
|
|
|
|
return file_get_contents('php://input') ?: '';
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function produceOutput(array $result, $httpStatus)
|
|
|
|
{
|
|
|
|
header('Content-Type: application/json', true, $httpStatus);
|
|
|
|
echo json_encode($result);
|
|
|
|
}
|
2016-12-19 16:16:27 +03:00
|
|
|
}
|