diff --git a/phpcs.bonus.xml b/phpcs.bonus.xml index be72666..ee7fe9e 100644 --- a/phpcs.bonus.xml +++ b/phpcs.bonus.xml @@ -4,13 +4,6 @@ ./.ddev/* ./vendor/* - ./src/base.php - ./src/collection.php - ./src/error.php - ./src/errors.php - ./src/exception.php - ./src/resource.php - ./src/response.php diff --git a/phpcs.xml b/phpcs.xml index dfe3e69..84e1035 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -4,13 +4,6 @@ ./.ddev/* ./vendor/* - ./src/base.php - ./src/collection.php - ./src/error.php - ./src/errors.php - ./src/exception.php - ./src/resource.php - ./src/response.php diff --git a/phpstan.neon b/phpstan.neon index 496ebc7..cab4788 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -5,14 +5,6 @@ parameters: - src/ - tests/ - examples/ - excludePaths: - - src/base.php - - src/collection.php - - src/error.php - - src/errors.php - - src/exception.php - - src/resource.php - - src/response.php treatPhpDocTypesAsCertain: false diff --git a/phpunitWithCodeCoverage.xml b/phpunitWithCodeCoverage.xml index 4358d1a..9ef0558 100644 --- a/phpunitWithCodeCoverage.xml +++ b/phpunitWithCodeCoverage.xml @@ -16,13 +16,6 @@ src/interfaces - src/base.php - src/collection.php - src/error.php - src/errors.php - src/exception.php - src/resource.php - src/response.php diff --git a/rector.php b/rector.php index 03c18b2..2a34ae9 100644 --- a/rector.php +++ b/rector.php @@ -12,15 +12,6 @@ __DIR__ . '/tests', __DIR__ . '/examples', ]) - ->withSkip([ - __DIR__ . '/src/base.php', - __DIR__ . '/src/collection.php', - __DIR__ . '/src/error.php', - __DIR__ . '/src/errors.php', - __DIR__ . '/src/exception.php', - __DIR__ . '/src/resource.php', - __DIR__ . '/src/response.php', - ]) // tab-based indenting ->withIndent(indentChar: "\t", indentSize: 1) diff --git a/src/base.php b/src/base.php deleted file mode 100644 index e8f7802..0000000 --- a/src/base.php +++ /dev/null @@ -1,97 +0,0 @@ -get_array()) - * - outputs exception details for errors (@see errors->add_exception()) - * - * @note the effects marked with an asterisk (*) are automatically turned on .. - * .. when requested by a human developer (request with an accept header w/o json) - */ -public static $debug = null; - -/** - * the root of the application using jsonapi - * this is currently used to shorten filename of exception traces .. - * .. and thus only used when ::$debug is set to true - */ -public static $appRoot = __DIR__.'/../../../../'; - -/** - * base constructor for all objects - * - * few things are arranged here: - * - determines ::$debug based on the display_errors directive - */ -public function __construct() { - // set debug mode based on display_errors - if (is_null(self::$debug)) { - self::$debug = (bool)ini_get('display_errors'); - } - - self::$appRoot = realpath(self::$appRoot).'/'; -} - -/** - * converting an object to an array - * - * @param object $object by default, its public properties are used - * if it is a \alsvanzelf\jsonapi\resource, its ->get_array() is used - * @return array - */ -protected static function convert_object_to_array($object) { - if (is_object($object) == false) { - throw new \Exception('can only convert objects'); - } - - if ($object instanceof \alsvanzelf\jsonapi\resource) { - return $object->get_array(); - } - - return get_object_vars($object); -} - -} diff --git a/src/collection.php b/src/collection.php deleted file mode 100644 index c32a968..0000000 --- a/src/collection.php +++ /dev/null @@ -1,143 +0,0 @@ -fill_collection() or ->add_resource() - * - self link @see ->set_self_link() - * - output @see ->send_response() or ->get_json() - * - * extra elements - * - meta data @see ->add_meta() or ->fill_meta() - * - included although possible, you should set those via the resource - * - * @deprecated {@see CollectionDocument} - */ -class collection extends response { - -/** - * internal data containers - */ -protected $primary_type = null; -protected $primary_collection = []; -protected $primary_resource_objects = []; - -/** - * creates a new collection - * - * @param string $type typically the name of the endpoint or database table - */ -public function __construct($type=null) { - parent::__construct(); - - $this->primary_type = $type; -} - -/** - * get the primary type as set via the constructor - * - * @return string|null - */ -public function get_type() { - return $this->primary_type; -} - -/** - * generates an array for the whole response body - * - * @see jsonapi.org/format - * - * @return array, containing: - * - links - * - data [] - * - {everything from the resource's data-key} - * - included {from the resource's included-key} - * - meta - */ -public function get_array() { - $response = []; - - // links - if ($this->links) { - $response['links'] = $this->links; - } - - // primary data - $response['data'] = $this->primary_collection; - - // included resources - if ($this->included_data) { - $response['included'] = array_values($this->included_data); - } - - // meta data - if ($this->meta_data) { - $response['meta'] = $this->meta_data; - } - - return $response; -} - -/** - * returns the primary resource objects - * this is used by a resource to add a collection or resource relations - * - * @return array - */ -public function get_resources() { - return $this->primary_resource_objects; -} - -/** - * adds a resource to the primary collection - * this will end up in response.data[] - * - * @note only data and meta(root-level) of a resource are used - * that is its type, id, attributes, relations, links, meta(data-level) - * and meta(root-level) is added to response.meta[] - * further, its included resources are separately added to response.included[] - * - * @see jsonapi\resource - * @see ->fill_collection() for adding a whole array of resources directly - * - * @param \alsvanzelf\jsonapi\resource $resource - * @return void - */ -public function add_resource(\alsvanzelf\jsonapi\resource $resource) { - $resource_array = $resource->get_array(); - - $included_resources = $resource->get_included_resources(); - if (!empty($included_resources)) { - $this->fill_included_resources($included_resources); - } - - // root-level meta-data - if (!empty($resource_array['meta'])) { - $this->fill_meta($resource_array['meta']); - } - - $this->primary_collection[] = $resource_array['data']; - - // make a backup of the actual resource, to pass on as a collection for a relation - $this->primary_resource_objects[] = $resource; -} - -/** - * fills the primary collection with resources - * this will end up in response.data[] - * - * @see ->add_resource() - * - * @param array $resources - * @return void - */ -public function fill_collection($resources) { - foreach ($resources as $resource) { - $this->add_resource($resource); - } -} - -} diff --git a/src/error.php b/src/error.php deleted file mode 100644 index 985c8fe..0000000 --- a/src/error.php +++ /dev/null @@ -1,291 +0,0 @@ - 'OK', - 201 => 'Created', - 204 => 'No Content', - 304 => 'Not Modified', - 307 => 'Temporary Redirect', - 308 => 'Permanent Redirect', - 400 => 'Bad Request', - 401 => 'Unauthorized', - 403 => 'Forbidden', - 404 => 'Not Found', - 405 => 'Method Not Allowed', - 422 => 'Unprocessable Entity', - 500 => 'Internal Server Error', - 503 => 'Service Unavailable', -]; - -/** - * creates a new error for inclusion in the errors collection - * - * @note error message is only shown when debug mode is on (@see base::$debug) - * - * @param string $error_message - * @param string $friendly_message optional, @see ->set_friendly_message() - * @param string $about_link optional, @see ->set_about_link() - */ -public function __construct($error_message, $friendly_message=null, $about_link=null) { - parent::__construct(); - - $this->set_error_message($error_message); - - if ($friendly_message) { - $this->set_friendly_message($friendly_message); - } - - if ($about_link) { - $this->set_about_link($about_link); - } -} - -/** - * generates an array for inclusion in the whole response body of an errors collection - * - * @note error message (`code`) is only shown when debug mode is on (@see base::$debug) - * - * @see jsonapi.org/format - * - * @return array, containing: - * - status - * - code - * - title - * - detail - * - source - * - pointer - * - parameter - * - links - * - about - * - id - * - meta - */ -public function get_array() { - $response_part = []; - - // the basics - $response_part['status'] = $this->http_status; - if (base::$debug) { - $response_part['code'] = $this->error_message; - } - - // user guidance - if ($this->friendly_message) { - $response_part['title'] = $this->friendly_message; - } - if ($this->friendly_detail) { - $response_part['detail'] = $this->friendly_detail; - } - - // the source of the problem - if ($this->post_body_pointer || $this->get_parameter_name) { - $response_part['source'] = []; - - if ($this->post_body_pointer) { - $response_part['source']['pointer'] = $this->post_body_pointer; - } - if ($this->get_parameter_name) { - $response_part['source']['parameter'] = $this->get_parameter_name; - } - } - - // technical guidance - if ($this->about_link) { - $response_part['links'] = [ - 'about' => $this->about_link, - ]; - } - if ($this->identifier) { - $response_part['id'] = $this->identifier; - } - - // meta data - if ($this->meta_data) { - $response_part['meta'] = $this->meta_data; - } - - return $response_part; -} - -/** - * returns the set status code apart from the response array - * used by the errors collection to figure out the generic status code - * - * @return int probably one of the predefined ones in jsonapi\response::STATUS_* - */ -public function get_http_status() { - return (int)$this->http_status; -} - -/** - * sets a status code for the single error - * this will end up in response.errors[].status - * - * @note this does only hint but not strictly set the actual status code send out to the browser - * use jsonapi\errors->set_http_status() to be sure - * - * @param mixed $http_status string: an http status, should start with the numeric status code - * integer: one of the predefined ones in response::STATUS_* .. - * .. will be converted to string - */ -public function set_http_status($http_status) { - if (is_int($http_status)) { - $http_status = (string)$http_status; - - // add status message for a few known ones - if (isset(self::$http_status_messages[$http_status])) { - $http_status .= ' '.self::$http_status_messages[$http_status]; - } - } - - $this->http_status = $http_status; -} - -/** - * sets the main error message, aimed at developers - * this will end up in response.errors[].code - * - * @note error message is only shown when debug mode is on (@see base::$debug) - * - * @param string $error_message - */ -public function set_error_message($error_message) { - $this->error_message = $error_message; -} - -/** - * sets a main user facing message - * it should be human friendly and ready to show the user as the main problem - * this will end up in response.errors[].title - * - * @note keep it short, more information can be added via ->set_friendly_detail() - * - * @param string $friendly_message - */ -public function set_friendly_message($friendly_message) { - $this->friendly_message = $friendly_message; -} - -/** - * sets a more detailed explanation of the problem, meant to the end user - * this will end up in response.errors[].detail - * - * @param string $friendly_detail - */ -public function set_friendly_detail($friendly_detail) { - $this->friendly_detail = $friendly_detail; -} - -/** - * blames a specific field/value pair from the POST body as the source of the problem - * this will end up in response.errors[].source.pointer - * - * @param string $post_body_pointer it should point out the field in the jsonapi structure - * i.e. "/data/attributes/title" - */ -public function blame_post_body($post_body_pointer) { - $this->post_body_pointer = $post_body_pointer; -} - -/** - * blames a specific GET query string parameter as the source of the problem - * this will end up in response.errors[].source.parameter - * - * @param string $get_parameter_name - */ -public function blame_get_parameter($get_parameter_name) { - $this->get_parameter_name = $get_parameter_name; -} - -/** - * sets a link which can help in solving the problem - * this will end up in response.errors[].links.about - * - * @param string $about_link string with link - * @param mixed $meta_data optional, meta data as key-value pairs - * objects are converted in arrays, @see base::convert_object_to_array() - */ -public function set_about_link($about_link, $meta_data=null) { - if ($meta_data) { - if (is_object($meta_data)) { - $meta_data = parent::convert_object_to_array($meta_data); - } - - $about_link = [ - 'href' => $about_link, - 'meta' => $meta_data, - ]; - } - - $this->about_link = $about_link; -} - -/** - * sets an id to help identifying the encountered problem - * this could be an id used by internal logging which can help during a helpdesk issue - * this will end up in response.errors[].id - * - * @param mixed $identifier can be an int or string - */ -public function set_identifier($identifier) { - $this->identifier = $identifier; -} - -/** - * adds some meta data - * this will end up in response.errors[].meta.{$key} - * - * @param string $key - * @param mixed $meta_data objects are converted in arrays, @see base::convert_object_to_array() - * @return void - */ -public function add_meta($key, $meta_data) { - if (is_object($meta_data)) { - $meta_data = parent::convert_object_to_array($meta_data); - } - - $this->meta_data[$key] = $meta_data; -} - -/** - * fills the meta data - * this will end up in response.meta - * - * @param array $meta_data - * @return void - */ -public function fill_meta($meta_data) { - foreach ($meta_data as $key => $single_meta_data) { - $this->add_meta($key, $single_meta_data); - } -} - -} diff --git a/src/errors.php b/src/errors.php deleted file mode 100644 index 4b6bc67..0000000 --- a/src/errors.php +++ /dev/null @@ -1,251 +0,0 @@ -add_exception() - * - error @see ::__construct() or ->add_error() or ->fill_errors() - * - http status @see ->set_http_status() - * - output @see ->send_response() or ->get_json() - * - * extra elements - * - meta data @see ->add_meta() or ->fill_meta() - * - self link @see ->set_self_link() - * - * @note ease error handling by using a jsonapi\exception - * @see examples/errors_exception_direct.php - * @see jsonapi\exception::__toString() when you want to use your own exception handling - * - * @deprecated {@see ErrorsDocument} - */ -class errors extends response { - -/** - * internal data containers - */ -protected $links; -protected $errors_collection; -protected $http_status = response::STATUS_INTERNAL_SERVER_ERROR; -protected $meta_data; - -/** - * creates a new errors collection - * it can be instantiated with a first/single exception/error to start the collection with - * (further) errors can be added via ->add_exception() or ->add_error() or ->fill_errors() - * - * @note error message (if string) is only shown when debug mode is on (@see base::$debug) - * - * @param mixed $error_message optional, can be exception, jsonapi\error object, or string - * @param string $friendly_message optional, @see jsonapi\error->set_friendly_message() - * @param string $about_link optional, @see jsonapi\error->set_about_link() - */ -public function __construct($error_message=null, $friendly_message=null, $about_link=null) { - parent::__construct(); - - if ($error_message instanceof \Exception) { - $this->add_exception($error_message, $friendly_message, $about_link); - return; - } - - $this->add_error($error_message, $friendly_message, $about_link); -} - -/** - * generates an array for the whole response body - * - * @note error message (`code`) is only shown when debug mode is on (@see base::$debug) - * - * @see jsonapi.org/format - * - * @return array, containing: - * - links - * - errors [] - * - status - * - code - * - title - * - detail - * - source - * - pointer - * - parameter - * - links - * - about - * - id - * - meta - * - meta - */ -public function get_array() { - $response = []; - - // links - if ($this->links) { - $response['links'] = $this->links; - } - - // errors - $response['errors'] = $this->errors_collection; - - // meta data - if ($this->meta_data) { - $response['meta'] = $this->meta_data; - } - - return $response; -} - -/** - * sends out the json response to the browser - * this will fetch the response from ->get_json() if not given via $response - * - * @note this is the same as jsonapi\response->send_response() .. - * .. but it will also terminate script execution afterwards - * - * @param string $content_type optional, defaults to ::CONTENT_TYPE_OFFICIAL (the official IANA registered one) .. - * .. or to ::CONTENT_TYPE_DEBUG, @see ::$debug - * @param int $encode_options optional, $options for json_encode() - * defaults to ::ENCODE_DEFAULT or ::ENCODE_DEBUG, @see ::$debug - * @param json $response optional, defaults to ::get_json() - * @param string $jsonp_callback optional, response as jsonp - * @return void more so, a string will be echo'd to the browser .. - * .. and script execution will terminate - */ -public function send_response($content_type=null, $encode_options=null, $response=null, $jsonp_callback=null) { - parent::send_response($content_type, $encode_options, $response, $jsonp_callback); - exit; -} - -/** - * sets the http status code for this errors response - * - * @note this does the same as response->set_http_status() except it forces an error status - * - * @param int $http_status any will do, you can easily pass one of the predefined ones in ::STATUS_* - */ -public function set_http_status($http_status) { - if ($http_status < 400) { - // can't use that as http status code - return; - } - - return parent::set_http_status($http_status); -} - -/** - * adds an error to the errors collection - * this will end up in response.errors[] - * - * @note error message (if string) is only shown when debug mode is on (@see base::$debug) - * - * @param mixed $error_message optional, can be jsonapi\error object or string - * @param string $friendly_message optional, @see jsonapi\error->set_friendly_message() - * @param string $about_link optional, @see jsonapi\error->set_about_link() - */ -public function add_error($error_message=null, $friendly_message=null, $about_link=null) { - if ($error_message instanceof error == false) { - $error_message = new error($error_message, $friendly_message, $about_link); - } - - $this->add_error_object($error_message); -} - -/** - * fills the errors collection with an array of jsonapi\error objects - * this will end up in response.errors[] - * - * @param array $errors with jsonapi\error objects inside - * @return void - */ -public function fill_errors($errors) { - foreach ($errors as $error) { - $this->add_error_object($error); - } -} - -/** - * adds an exception as error to the errors collection - * this will end up in response.errors[] - * - * @note exception meta data (file, line, trace) is only shown when debug mode is on (@see base::$debug) - * - * @param object $exception extending \Exception - * @param string $friendly_message optional, @see jsonapi\error->set_friendly_message() - * @param string $about_link optional, @see jsonapi\error->set_about_link() - */ -public function add_exception($exception, $friendly_message=null, $about_link=null) { - $error_message = $exception->getMessage(); - $error_status = $exception->getCode(); - - $new_error = new error($error_message, $friendly_message, $about_link); - if ($error_status) { - $new_error->set_http_status($error_status); - } - - // meta data - if (base::$debug) { - $file = $exception->getFile(); - if ($file) { - $file = str_replace(base::$appRoot, '', $file); - $new_error->add_meta('file', $file); - } - - $line = $exception->getLine(); - if ($line) { - $new_error->add_meta('line', $line); - } - - $trace = $exception->getTrace(); - if ($trace) { - foreach ($trace as &$place) { - if (!empty($place['file'])) { - $place['file'] = str_replace(base::$appRoot, '', $place['file']); - } - } - $new_error->add_meta('trace', $trace); - } - } - - $this->add_error_object($new_error); - - $previous_exception = $exception->getPrevious(); - if ($previous_exception) { - $this->add_exception($previous_exception); - } -} - -/** - * adds a jsonapi\error object to the errors collection - * used internally by ->add_error(), ->fill_errors() and ->add_exception() - * - * further, a generic http status is gathered from added objects - * - * @param jsonapi\error $error - */ -private function add_error_object(\alsvanzelf\jsonapi\error $error) { - $error_response_part = $error->get_array(); - $error_http_status = $error->get_http_status(); - - $this->errors_collection[] = $error_response_part; - if ($error_http_status) { - $this->set_http_status($error_http_status); - } -} - -/** - * blocks included resources on errors collections - */ -public function add_included_resource(\alsvanzelf\jsonapi\resource $resource) { - throw new \Exception('can not add included resources to errors, add them as meta data instead'); -} - -/** - * blocks included resources on errors collections - */ -public function fill_included_resources($resources) { - throw new \Exception('can not add included resources to errors, add them as meta data instead'); -} - -} diff --git a/src/exception.php b/src/exception.php deleted file mode 100644 index 6a8ac49..0000000 --- a/src/exception.php +++ /dev/null @@ -1,98 +0,0 @@ -send_response()) output a errors collection response - * - * @note throwing the exception alone doesn't give you json output - * - * @deprecated {@see ErrorsDocument::fromException()} - */ -class exception extends \Exception { - -/** - * internal data containers - */ -protected $friendly_message; -protected $about_link; - -/** - * custom exception for usage by jsonapi projects - * when echo'd, sends a jsonapi\errors response with the exception in it - * - * can be thrown as a normal exception, optionally with two extra parameters - * - * @param string $message - * @param integer $code optional, defaults to 500 - * if using one of the predefined ones in jsonapi\response::STATUS_* - * sends out those as http status - * @param Exception $previous - * @param string $friendly_message optional, which message to output to clients - * the exception $message is hidden unless base::$debug is true - * @param string $about_link optional, a url to send clients to for more explanation - * i.e. a link to the api documentation - */ -public function __construct($message='', $code=0, $previous=null, $friendly_message=null, $about_link=null) { - // exception is the only class not extending base - new base(); - - parent::__construct($message, $code, $previous); - - if ($friendly_message) { - $this->set_friendly_message($friendly_message); - } - if ($about_link) { - $this->set_about_link($about_link); - } -} - -/** - * sets a main user facing message - * - * @see error->set_friendly_message() - */ -public function set_friendly_message($friendly_message) { - $this->friendly_message = $friendly_message; -} - -/** - * sets a link which can help in solving the problem - * - * @see error->set_about_link() - */ -public function set_about_link($about_link) { - $this->about_link = $about_link; -} - -/** - * sends out the json response of an jsonapi\errors object to the browser - * - * @see errors->send_response() - */ -public function send_response($content_type=null, $encode_options=null, $response=null, $jsonp_callback=null) { - $jsonapi = new errors($this, $this->friendly_message, $this->about_link); - $jsonapi->send_response($content_type, $encode_options, $response, $jsonp_callback); - exit; // sanity check -} - -/** - * alias for ->send_response() - * - * @deprecated as this causes hard to debug issues .. - * .. when exceptions are called as a by-effect of this function - * - * @return string empty for sake of correctness - * as ->send_response() already echo's the json and terminates script execution - */ -public function __toString() { - if (base::$debug) { - trigger_error('toString conversion of exception is deprecated, use ->send_response() instead', E_USER_DEPRECATED); - } - - $this->send_response(); - return ''; -} - -} diff --git a/src/resource.php b/src/resource.php deleted file mode 100644 index 7d34604..0000000 --- a/src/resource.php +++ /dev/null @@ -1,472 +0,0 @@ -add_data() or ->fill_data() - * - self link @see ->set_self_link() - * - output @see ->send_response() or ->get_json() - * - * extra elements - * - relations @see ->add_relation() or ->fill_relations() - * - links @see ->add_link() or ->fill_links() - * - meta data @see ->add_meta() or ->fill_meta() - * - included @see ->add_included_resource() or ->fill_included_resources() - * - * @deprecated {@see ResourceDocument} - */ -class resource extends response { - -/** - * relation types - */ -const RELATION_TO_MANY = 'to_many'; -const RELATION_TO_ONE = 'to_one'; - -/** - * which links should be set for relations - */ -const RELATION_LINKS_RELATIONSHIP = 'relationship'; -const RELATION_LINKS_RESOURCE = 'resource'; -const RELATION_LINKS_BOTH = 'both'; -const RELATION_LINKS_NONE = 'none'; - -/** - * placement of link objects - */ -const LINK_LEVEL_DATA = 'data'; -const LINK_LEVEL_ROOT = 'root'; -const LINK_LEVEL_BOTH = 'both'; - -/** - * methods for filling the self link - * @see ::$self_link_method - */ -const SELF_LINK_SERVER = 'server'; -const SELF_LINK_TYPE = 'type'; -const SELF_LINK_NONE = 'none'; - -/** - * the method to use for filling the self link - * - * the current default ::SELF_LINK_SERVER fills the link using the $_SERVER request info - * for backwards compatibility this stays for the 1.x releases - * from 2.x this will (probably) switch to ::SELF_LINK_TYPE - */ -public static $self_link_data_level = self::SELF_LINK_SERVER; - -/** - * allow to toggle the auto generated links for relations - * @todo allow to customize the format completly instead of only toggling - */ -public static $relation_links = self::RELATION_LINKS_BOTH; - -/** - * internal data containers - */ -protected $primary_type = null; -protected $primary_id = null; -protected $primary_attributes = []; -protected $primary_relationships = []; -protected $primary_links = []; -protected $primary_meta_data = []; - -/** - * creates a new resource - * - * @param string $type typically the name of the endpoint or database table - * @param mixed $id optional, provide if you want to provide this to the client - * can be integer or hash or whatever - */ -public function __construct($type, $id=null) { - $this->primary_type = $type; - $this->primary_id = $id; - - parent::__construct(); -} - -/** - * get the primary type as set via the constructor - * - * @return string|null - */ -public function get_type() { - return $this->primary_type; -} - -/** - * get the primary id as set via the constructor - * - * @return mixed|null - */ -public function get_id() { - return $this->primary_id; -} - -/** - * whether data has been added via ->add_data()/->fill_data() - * this can be useful when adding a resource to another one as included resource - * - * @return boolean - */ -public function has_data() { - return (bool)$this->primary_attributes; -} - -/** - * generates an array for the whole response body - * - * @see jsonapi.org/format - * - * @return array, containing: - * - links - * - data - * - type - * - id - * - attributes - * - relationships - * - links - * - meta - * - included - * - meta - */ -public function get_array() { - $response = []; - - // links - if ($this->links) { - $response['links'] = $this->links; - } - - // primary data - $response['data'] = [ - 'type' => $this->primary_type, - ]; - if ($this->primary_id) { - $response['data']['id'] = $this->primary_id; - } - if ($this->primary_attributes) { - $response['data']['attributes'] = $this->primary_attributes; - } - if ($this->primary_relationships) { - $response['data']['relationships'] = $this->primary_relationships; - } - if ($this->primary_links) { - $response['data']['links'] = $this->primary_links; - } - if ($this->primary_meta_data) { - $response['data']['meta'] = $this->primary_meta_data; - } - - // included resources - if ($this->included_data) { - $response['included'] = array_values($this->included_data); - } - - // meta data - if ($this->meta_data) { - $response['meta'] = $this->meta_data; - } - - return $response; -} - -/** - * adds a data-point to the primary data - * this will end up in response.data.attributes.{$key} - * - * values don't have to be scalar, it can be lists or objects as well - * - * @see ->fill_data() for adding a whole array directly - * - * @param string $key - * @param mixed $value objects are converted in arrays, @see base::convert_object_to_array() - * @return void - */ -public function add_data($key, $value) { - if (is_object($value)) { - $value = parent::convert_object_to_array($value); - } - - $this->primary_attributes[$key] = $value; -} - -/** - * fills the primary data - * this will end up in response.data.attributes - * - * this is meant for adding an array as the primary data - * objects will be converted using their public keys - * - * @note skips an 'id'-key inside $values if identical to the $id given during construction - * - * @see ->add_data() - * - * @param mixed $values objects are converted in arrays, @see base::convert_object_to_array() - * @return void - */ -public function fill_data($values) { - if (is_object($values)) { - $values = parent::convert_object_to_array($values); - } - if (is_array($values) == false) { - throw new \Exception('use add_data() for adding scalar values'); - } - - if (isset($values['id']) && $values['id'] == $this->primary_id) { - unset($values['id']); - } - - foreach ($values as $key => $single_value) { - $this->add_data($key, $single_value); - } -} - -/** - * adds a relation to another resource - * this will end up in response.data.relationships.{$key} - * - * $relation should be in the following format (any point can be omitted): - * - links - * - self - * - related - * - data - * - type - * - id - * - * if $relation is a jsonapi\resource or jsonapi\collection, it will also add an included resource - * @see ->add_included_resource() - * - * @param string $key - * @param mixed $relation can be array or jsonapi\resource or jsonapi\collection - * @param boolean $skip_include optional, defaults to false - * @param string $type optional, defaults to null - * @return void - * - * @todo allow to add collections as well - */ -public function add_relation($key, $relation, $skip_include=false, $type=null) { - if ($type && in_array($type, [self::RELATION_TO_ONE, self::RELATION_TO_MANY]) == false) { - throw new \Exception('unknown relation type'); - } - if (isset($this->primary_relationships[$key]) && $relation instanceof \alsvanzelf\jsonapi\resource == false) { - throw new \Exception('can not add a relation twice, unless using a resource object'); - } - if (isset($this->primary_relationships[$key]) && $relation instanceof \alsvanzelf\jsonapi\resource) { - if ($type != self::RELATION_TO_MANY || isset($this->primary_relationships[$key]['data']['type'])) { - throw new \Exception('$type should be set to RELATION_TO_MANY for resources using the same key'); - } - } - if ($relation instanceof \alsvanzelf\jsonapi\collection && $type == self::RELATION_TO_ONE) { - throw new \Exception('collections can only be added as RELATION_TO_MANY'); - } - - if ($relation instanceof \alsvanzelf\jsonapi\resource == false && $relation instanceof \alsvanzelf\jsonapi\collection == false && is_array($relation) == false) { - throw new \Exception('unknown relation format'); - } - - if (is_array($relation)) { - $this->primary_relationships[$key] = $relation; - return; - } - - if ($relation instanceof \alsvanzelf\jsonapi\resource) { - // add whole resources as included resource, while keeping the relationship - if ($relation->has_data() && $skip_include == false) { - $this->add_included_resource($relation); - } - - $base_url = (isset($this->primary_links['self']['href'])) ? $this->primary_links['self']['href'] : $this->primary_links['self']; - $relation_id = $relation->get_id() ?: null; - $relation_data = [ - 'type' => $relation->get_type(), - 'id' => $relation_id, - ]; - - if (isset($this->primary_relationships[$key])) { - $this->primary_relationships[$key]['data'][] = $relation_data; - return; - } - if ($type == self::RELATION_TO_MANY) { - $relation_data = [$relation_data]; - } - } - elseif ($relation instanceof \alsvanzelf\jsonapi\collection) { - $relation_resources = $relation->get_resources(); - - // add whole resources as included resource, while keeping the relationship - if ($relation_resources && $skip_include == false) { - $this->fill_included_resources($relation); - } - - $base_url = (isset($this->primary_links['self']['href'])) ? $this->primary_links['self']['href'] : $this->primary_links['self']; - $relation_data = []; - foreach ($relation_resources as $relation_resource) { - $relation_data[] = [ - 'type' => $relation_resource->get_type(), - 'id' => $relation_resource->get_id(), - ]; - } - } - - $this->primary_relationships[$key] = [ - 'data' => $relation_data, - ]; - - $relation_links = []; - if (self::$relation_links == self::RELATION_LINKS_RELATIONSHIP || self::$relation_links == self::RELATION_LINKS_BOTH) { - $relation_links['self'] = $base_url.'/relationships/'.$key; - } - if (self::$relation_links == self::RELATION_LINKS_RESOURCE || self::$relation_links == self::RELATION_LINKS_BOTH) { - $relation_links['related'] = $base_url.'/'.$key; - } - - if ($relation_links) { - $this->primary_relationships[$key]['links'] = $relation_links; - } -} - -/** - * fills the relationships to other resources - * this will end up in response.data.relationships - * - * @see ->add_relation() - * - * @param array $relations - * @return void - */ -public function fill_relations($relations, $skip_include=false) { - foreach ($relations as $key => $relation) { - $this->add_relation($key, $relation, $skip_include); - } -} - -/** - * this will end up in response.data.links.{$key} - * if $also_root is set to true, it will also end up in response.links.{$key} - * - * @see jsonapi\response->add_link() - * - * @param string $key - * @param mixed $link objects are converted in arrays, @see base::convert_object_to_array() - * @param mixed $meta_data should not be used if $link is non-string - * @param string $level one of the predefined ones in ::LINK_LEVEL_* - * @return void - */ -public function add_link($key, $link, $meta_data=null, $level=self::LINK_LEVEL_DATA) { - if (is_object($link)) { - $link = parent::convert_object_to_array($link); - } - - // can not combine both raw link object and extra meta data - if ($meta_data && is_string($link) == false) { - throw new \Exception('link "'.$key.'" should be a string if meta data is provided separate'); - } - - if ($level === self::LINK_LEVEL_DATA) { - $revert_root_level = (isset($this->links[$key])) ? $this->links[$key] : null; - } - - parent::add_link($key, $link, $meta_data); - - if ($level === self::LINK_LEVEL_DATA || $level === self::LINK_LEVEL_BOTH) { - $this->primary_links[$key] = $this->links[$key]; - } - if ($level === self::LINK_LEVEL_DATA) { - if ($revert_root_level) { - $this->links[$key] = $revert_root_level; - } - else { - unset($this->links[$key]); - } - } -} - -/** - * sets the link to the request used to give this response - * this will end up in response.links.self and response.data.links.self - * this overrides the jsonapi\response->set_self_link() which only adds it to response.links.self - * - * @see jsonapi\response->set_self_link() - * - * by default this is already set using $_SERVER variables - * use this method to override this default behavior - * @see jsonapi\response::__construct() - * - * @param string $link - * @param mixed $meta_data optional, meta data as key-value pairs - * objects are converted in arrays, @see base::convert_object_to_array() - * @return void - */ -public function set_self_link($link, $meta_data=null) { - parent::set_self_link($link, $meta_data); - - if (self::$self_link_data_level == self::SELF_LINK_SERVER) { - $this->add_link($key='self', $link, $meta_data); - } - if (self::$self_link_data_level == self::SELF_LINK_TYPE) { - $link = '/'.$this->primary_type.'/'.$this->primary_id; - $this->add_link($key='self', $link, $meta_data); - } -} - -/** - * adds meta data to the default self link - * this will end up in response.links.self.meta.{$key} and response.data.links.self.meta.{$key} - * this overrides the jsonapi\response->add_self_link_meta() which only adds it to response.links.self.meta.{$key} - * - * @see jsonapi\response->add_self_link_meta() - * - * @note you can also use ->set_self_link() with the whole meta object at once - * - * @param string $key - * @param mixed $meta_data objects are converted in arrays, @see base::convert_object_to_array() - * @return void - */ -public function add_self_link_meta($key, $meta_data) { - parent::add_self_link_meta($key, $meta_data); - - $this->primary_links['self'] = $this->links['self']; -} - -/** - * adds some meta data - * this will end up in response.meta.{$key} or response.data.meta.{$key} .. - * .. depending on $data_level - * - * @param string $key - * @param mixed $meta_data objects are converted in arrays, @see base::convert_object_to_array() - * @param boolean $data_level optional, defaults to false - * @return void - */ -public function add_meta($key, $meta_data, $data_level=false) { - if ($data_level == false) { - return parent::add_meta($key, $meta_data); - } - - if (is_object($meta_data)) { - $meta_data = parent::convert_object_to_array($meta_data); - } - - $this->primary_meta_data[$key] = $meta_data; -} - -/** - * fills the meta data - * this will end up in response.meta or response.data.meta .. - * .. depending on $data_level - * - * @param array $meta_data - * @param boolean $data_level optional, defaults to false - * @return void - */ -public function fill_meta($meta_data, $data_level=false) { - foreach ($meta_data as $key => $single_meta_data) { - $this->add_meta($key, $single_meta_data, $data_level); - } -} - -} diff --git a/src/response.php b/src/response.php deleted file mode 100644 index d41361d..0000000 --- a/src/response.php +++ /dev/null @@ -1,448 +0,0 @@ -send_response() sends out basic status headers - * if set to true, it sends the status code and the location header - */ -public static $send_status_headers = true; - -/** - * internal data containers - */ -protected $links = []; -protected $meta_data = []; -protected $included_data = []; -protected $included_resources = []; -protected $http_status = self::STATUS_OK; -protected $redirect_location = null; - -/** - * base constructor for all response objects (resource, collection, errors) - * - * a few things are arranged here: - * - sets the self link using $_SERVER variables - * - * @see ->set_self_link() to override this default behavior - */ -public function __construct() { - parent::__construct(); - - // auto-fill the self link based on the current request - $self_link = $_SERVER['REQUEST_URI']; - if (isset($_SERVER['PATH_INFO'])) { - $self_link = $_SERVER['PATH_INFO']; - } - - $this->set_self_link($self_link); -} - -/** - * alias for ->get_json() - * - * @see ->get_json() - * - * @return string - */ -public function __toString() { - return $this->get_json(); -} - -/** - * generates an array for the whole response body - * - * @see jsonapi.org/format - * - * @return array, containing: - * - links - * - meta - */ -public function get_array() { - $response = []; - - // links - if ($this->links) { - $response['links'] = $this->links; - } - - // meta data - if ($this->meta_data) { - $response['meta'] = $this->meta_data; - } - - return $response; -} - -/** - * returns the whole response body as json - * it generates the response via ->get_array() - * - * @see ->get_array() for the structure - * @see json_encode() options - * - * @param int $encode_options optional, $options for json_encode() - * defaults to ::ENCODE_DEFAULT or ::ENCODE_DEBUG, @see ::$debug - * @return json - */ -public function get_json($encode_options=null) { - if (is_int($encode_options) == false) { - $encode_options = self::ENCODE_DEFAULT; - } - if (base::$debug || strpos($_SERVER['HTTP_ACCEPT'], '/json') == false) { - $encode_options = self::ENCODE_DEBUG; - } - - $response = $this->get_array(); - - $json = json_encode($response, $encode_options); - - return $json; -} - -/** - * sends out the json response to the browser - * this will fetch the response from ->get_json() if not given via $response - * - * @note this also sets the needed http headers (status, location and content-type) - * - * @param string $content_type optional, defaults to ::CONTENT_TYPE_OFFICIAL (the official IANA registered one) .. - * .. or to ::CONTENT_TYPE_DEBUG, @see ::$debug - * @param int $encode_options optional, $options for json_encode() - * defaults to ::ENCODE_DEFAULT or ::ENCODE_DEBUG, @see ::$debug - * @param json $response optional, defaults to ::get_json() - * @param string $jsonp_callback optional, response as jsonp - * @return void however, a string will be echo'd to the browser - */ -public function send_response($content_type=null, $encode_options=null, $response=null, $jsonp_callback=null) { - if (is_null($response) && $this->http_status != self::STATUS_NO_CONTENT) { - $response = $this->get_json($encode_options); - } - - if (empty($content_type)) { - $content_type = self::CONTENT_TYPE_OFFICIAL; - } - if (base::$debug || strpos($_SERVER['HTTP_ACCEPT'], '/json') == false) { - $content_type = self::CONTENT_TYPE_DEBUG; - } - - if (self::$send_status_headers) { - $this->send_status_headers(); - } - - header('Content-Type: '.$content_type.'; charset=utf-8'); - - if ($this->http_status == self::STATUS_NO_CONTENT) { - return; - } - - // jsonp response - if ($content_type == self::CONTENT_TYPE_JSONP) { - if (empty($jsonp_callback)) { - $jsonp_callback = self::JSONP_CALLBACK_DEFAULT; - } - echo $jsonp_callback.'('.$response.')'; - return; - } - - echo $response; -} - -/** - * sends out the http status code and optional redirect location - * defaults to ::STATUS_OK, or ::STATUS_INTERNAL_SERVER_ERROR for an errors response - * - * @return void - */ -private function send_status_headers() { - if ($this->redirect_location) { - if ($this->http_status == self::STATUS_OK) { - $this->set_http_status(self::STATUS_TEMPORARY_REDIRECT); - } - - header('Location: '.$this->redirect_location, $replace=true, $this->http_status); - return; - } - - http_response_code($this->http_status); -} - -/** - * sets the http status code for this response - * - * @param int $http_status any will do, you can easily pass one of the predefined ones in ::STATUS_* - */ -public function set_http_status($http_status) { - $this->http_status = $http_status; -} - -/** - * sets a new location the client should follow - * - * @param string $location absolute url - */ -public function set_redirect_location($location) { - if (self::$send_status_headers == false && base::$debug) { - trigger_error('location will not be send out unless response::$send_status_headers is true', E_USER_NOTICE); - } - - $this->redirect_location = $location; -} - -/** - * returns the included resource objects - * this is used by a collection to work with the actual objects - * - * @return array - */ -public function get_included_resources() { - return $this->included_resources; -} - -/** - * adds a link - * this will end up in response.links.{$key} - * - * useful for links which can not be added as relation, @see ->add_relation() - * - * @param string $key - * @param mixed $link string with link, or raw link object array/object - * @param mixed $meta_data optional, meta data as key-value pairs - * objects are converted in arrays, @see base::convert_object_to_array() - * @return void - */ -public function add_link($key, $link, $meta_data=null) { - if ($meta_data) { - if (is_object($meta_data)) { - $meta_data = parent::convert_object_to_array($meta_data); - } - - $link = [ - 'href' => $link, - 'meta' => $meta_data, - ]; - } - - $this->links[$key] = $link; -} - -/** - * fills the set of links - * this will end up in response.links - * - * @see ->add_link() - * - * @param array $links - * @return void - */ -public function fill_links($links) { - foreach ($links as $key => $link) { - $this->add_link($key, $link); - } -} - -/** - * sets the link to the request used to give this response - * this will end up in response.links.self .. - * and in response.data.links.self for single resource objects - * - * by default this is already set using $_SERVER variables - * use this method to override this default behavior - * @see ::__construct() - * - * @param string $link - * @param mixed $meta_data optional, meta data as key-value pairs - * objects are converted in arrays, @see base::convert_object_to_array() - * @return void - */ -public function set_self_link($link, $meta_data=null) { - if ($meta_data) { - // can not combine both raw link object and extra meta data - if (is_string($link) == false) { - throw new \Exception('link "self" should be a string if meta data is provided separate'); - } - - if (is_object($meta_data)) { - $meta_data = parent::convert_object_to_array($meta_data); - } - - $link = [ - 'href' => $link, - 'meta' => $meta_data, - ]; - } - - $this->links['self'] = $link; -} - -/** - * adds meta data to the default self link - * this will end up in response.links.self.meta.{$key} - * - * @note you can also use ->set_self_link() with the whole meta object at once - * - * @param string $key - * @param mixed $meta_data objects are converted in arrays, @see base::convert_object_to_array() - * @return void - */ -public function add_self_link_meta($key, $meta_data) { - if (is_object($meta_data)) { - $meta_data = self::convert_to_array($meta_data); - } - - // converts string-type link - if (is_string($this->links['self'])) { - $this->links['self'] = [ - 'href' => $this->links['self'], - 'meta' => [], - ]; - } - - $this->links['self']['meta'][$key] = $meta_data; -} - -/** - * adds an included resource - * this will end up in response.included[] - * - * prefer using ->add_relation() instead - * - * a $resource should have its 'id' set - * - * @note this can only be used by resource and collection, not by errors - * - * @param \alsvanzelf\jsonapi\resource $resource - */ -public function add_included_resource(\alsvanzelf\jsonapi\resource $resource) { - if (property_exists($this, 'included_resources') == false) { - throw new \Exception(get_class($this).' can not contain included resources'); - } - - $resource_array = $resource->get_array(); - if (empty($resource_array['data']['id'])) { - return; - } - - // root-level meta-data - if (!empty($resource_array['meta'])) { - $this->fill_meta($resource_array['meta']); - } - - $resource_array = $resource_array['data']; - - $key = $resource->get_type().'/'.$resource->get_id(); - - $this->included_data[$key] = $resource_array; - - // make a backup of the actual resource, to pass on to a collection - $this->included_resources[$key] = $resource; - - // allow nesting relationshios - foreach ($resource->get_included_resources() as $included_resource) { - if (empty($included_resource->primary_id)) { - continue; - } - - $included_key = $included_resource->get_type().'/'.$included_resource->get_id(); - - $this->included_resources[$included_key] = $included_resource; - - $included_array = $included_resource->get_array(); - $included_array = $included_array['data']; - $this->included_data[$included_key] = $included_array; - } -} - -/** - * fills the included resources - * this will end up in response.included[] - * - * prefer using ->fill_relations() instead - * - * @param mixed $resources array of \alsvanzelf\jsonapi\resource objects - * or \alsvanzelf\jsonapi\collection object - * @return void - */ -public function fill_included_resources($resources) { - if ($resources instanceof \alsvanzelf\jsonapi\collection) { - $resources = $resources->get_resources(); - } - - foreach ($resources as $resource) { - $this->add_included_resource($resource); - } -} - -/** - * adds some meta data - * this will end up in response.meta.{$key} - * - * @param string $key - * @param mixed $meta_data objects are converted in arrays, @see base::convert_object_to_array() - * @return void - */ -public function add_meta($key, $meta_data) { - if (is_object($meta_data)) { - $meta_data = parent::convert_object_to_array($meta_data); - } - - $this->meta_data[$key] = $meta_data; -} - -/** - * fills the meta data - * this will end up in response.meta - * - * @param array $meta_data - * @return void - */ -public function fill_meta($meta_data) { - foreach ($meta_data as $key => $single_meta_data) { - $this->add_meta($key, $single_meta_data); - } -} - -}