diff --git a/README.md b/README.md index 792fe51..c715304 100644 --- a/README.md +++ b/README.md @@ -181,7 +181,7 @@ It has support for generating & sending documents with: - resource collections - to-one and to-many relationships - errors (easily turning exceptions into jsonapi output) -- v1.1 extensions via profiles +- v1.1 extensions and profiles - v1.1 @-members for JSON-LD and others Also there's tools to help processing of incoming requests: @@ -189,9 +189,10 @@ Also there's tools to help processing of incoming requests: - parse request options (include paths, sparse fieldsets, sort fields, pagination, filtering) - parse request documents for creating, updating and deleting resources and relationships -Next to custom extensions, the following [official extensions](https://jsonapi.org/extensions/) are included: +Next to custom extensions/profiles, the following [official extensions/profiles](https://jsonapi.org/extensions/) are included: -- Cursor Pagination ([example code](/examples/cursor_pagination_profile.php), [specification](https://jsonapi.org/profiles/ethanresnick/cursor-pagination/)) +- Atomic Operations extension ([example code](/examples/atomic_operations_extension.php), [specification](https://jsonapi.org/ext/atomic/)) +- Cursor Pagination profile ([example code](/examples/cursor_pagination_profile.php), [specification](https://jsonapi.org/profiles/ethanresnick/cursor-pagination/)) Plans for the future include: diff --git a/examples/atomic_operations_extension.php b/examples/atomic_operations_extension.php new file mode 100644 index 0000000..4e97c6d --- /dev/null +++ b/examples/atomic_operations_extension.php @@ -0,0 +1,33 @@ +add('name', 'Ford'); +$user2->add('name', 'Arthur'); +$user42->add('name', 'Zaphod'); + +$document->addResults($user1); +$document->addResults($user2); +$document->addResults($user42); + +/** + * get the json + */ + +$options = [ + 'prettyPrint' => true, +]; +echo '
'.$document->toJson($options);
diff --git a/examples/bootstrap_examples.php b/examples/bootstrap_examples.php
index 2832970..fc4793d 100644
--- a/examples/bootstrap_examples.php
+++ b/examples/bootstrap_examples.php
@@ -2,7 +2,7 @@
use alsvanzelf\jsonapi\Document;
use alsvanzelf\jsonapi\ResourceDocument;
-use alsvanzelf\jsonapi\helpers\ProfileAliasManager;
+use alsvanzelf\jsonapi\interfaces\ExtensionInterface;
use alsvanzelf\jsonapi\interfaces\ProfileInterface;
use alsvanzelf\jsonapi\interfaces\ResourceInterface;
@@ -102,29 +102,59 @@ function getCurrentLocation() {
}
}
-class ExampleVersionProfile extends ProfileAliasManager implements ProfileInterface {
+class ExampleVersionExtension implements ExtensionInterface {
/**
- * the required methods (next to extending ProfileAliasManager)
+ * the required method
*/
public function getOfficialLink() {
- return 'https://jsonapi.org/format/1.1/#profile-keywords-and-aliases';
+ return 'https://jsonapi.org/format/1.1/#extension-rules';
}
- public function getOfficialKeywords() {
- return ['version'];
+ public function getNamespace() {
+ return 'version';
}
/**
- * optionally helpers for the specific profile
+ * optionally helpers for the specific extension
*/
public function setVersion(ResourceInterface $resource, $version) {
if ($resource instanceof ResourceDocument) {
- $resource->addMeta($this->getKeyword('version'), $version, $level=Document::LEVEL_RESOURCE);
+ $resource->getResource()->addExtensionMember($this, 'id', $version);
}
else {
- $resource->addMeta($this->getKeyword('version'), $version);
+ $resource->addExtensionMember($this, 'id', $version);
}
}
}
+
+class ExampleTimestampsProfile implements ProfileInterface {
+ /**
+ * the required method
+ */
+
+ public function getOfficialLink() {
+ return 'https://jsonapi.org/recommendations/#authoring-profiles';
+ }
+
+ /**
+ * optionally helpers for the specific profile
+ */
+
+ public function setTimestamps(ResourceInterface $resource, \DateTimeInterface $created=null, \DateTimeInterface $updated=null) {
+ if ($resource instanceof ResourceIdentifierObject) {
+ throw new Exception('cannot add attributes to identifier objects');
+ }
+
+ $timestamps = [];
+ if ($created !== null) {
+ $timestamps['created'] = $created->format(\DateTime::ISO8601);
+ }
+ if ($updated !== null) {
+ $timestamps['updated'] = $updated->format(\DateTime::ISO8601);
+ }
+
+ $resource->add('timestamps', $timestamps);
+ }
+}
diff --git a/examples/errors_all_options.php b/examples/errors_all_options.php
index 5be9ab7..4f628cc 100644
--- a/examples/errors_all_options.php
+++ b/examples/errors_all_options.php
@@ -38,7 +38,7 @@
$errorSpecApi->setHumanTitle($genericTitle='Too much options');
$errorSpecApi->setHumanDetails($specificDetails='Please, choose a bit less. Consult your ...');
$errorSpecApi->setAboutLink($specificAboutLink='https://www.example.com/explanation.html', ['foo'=>'bar']);
-$errorSpecApi->appendTypeLink($genericTypeLink='https://www.example.com/documentation.html', ['foo'=>'bar']);
+$errorSpecApi->setTypeLink($genericTypeLink='https://www.example.com/documentation.html', ['foo'=>'bar']);
/**
* prepare multiple error objects for the errors response
diff --git a/examples/example_profile.php b/examples/example_profile.php
deleted file mode 100644
index 66cef95..0000000
--- a/examples/example_profile.php
+++ /dev/null
@@ -1,32 +0,0 @@
- 'ref']);
-
-$document = new ResourceDocument('user', 42);
-$document->applyProfile($profile);
-
-/**
- * you can apply the rules of the profile manually
- * or use methods of the profile if provided
- */
-
-$profile->setVersion($document, '2019');
-
-/**
- * get the json
- */
-
-$options = [
- 'prettyPrint' => true,
-];
-echo ''.$document->toJson($options);
diff --git a/examples/extension.php b/examples/extension.php
new file mode 100644
index 0000000..74738bf
--- /dev/null
+++ b/examples/extension.php
@@ -0,0 +1,37 @@
+applyExtension($extension);
+
+$document->add('foo', 'bar');
+
+/**
+ * you can apply the rules of the extension manually
+ * or use methods of the extension if provided
+ */
+
+$extension->setVersion($document, '2019');
+
+/**
+ * get the json
+ */
+
+$contentType = Converter::prepareContentType(Document::CONTENT_TYPE_OFFICIAL, [$extension], []);
+echo 'Content-Type: '.$contentType.''.PHP_EOL;
+
+$options = [
+ 'prettyPrint' => true,
+];
+echo ''.$document->toJson($options);
diff --git a/examples/index.html b/examples/index.html
index 8c1963c..1687b28 100644
--- a/examples/index.html
+++ b/examples/index.html
@@ -49,7 +49,9 @@ Misc
Content-Type: '.$contentType.''.PHP_EOL;
+
+$options = [
+ 'prettyPrint' => true,
+];
+echo ''.$document->toJson($options);
diff --git a/src/Document.php b/src/Document.php
index 2967cd2..ddb75d9 100644
--- a/src/Document.php
+++ b/src/Document.php
@@ -2,26 +2,30 @@
namespace alsvanzelf\jsonapi;
+use alsvanzelf\jsonapi\exceptions\DuplicateException;
use alsvanzelf\jsonapi\exceptions\Exception;
use alsvanzelf\jsonapi\exceptions\InputException;
use alsvanzelf\jsonapi\helpers\AtMemberManager;
use alsvanzelf\jsonapi\helpers\Converter;
+use alsvanzelf\jsonapi\helpers\ExtensionMemberManager;
use alsvanzelf\jsonapi\helpers\HttpStatusCodeManager;
use alsvanzelf\jsonapi\helpers\LinksManager;
use alsvanzelf\jsonapi\helpers\Validator;
use alsvanzelf\jsonapi\interfaces\DocumentInterface;
+use alsvanzelf\jsonapi\interfaces\ExtensionInterface;
use alsvanzelf\jsonapi\interfaces\ProfileInterface;
use alsvanzelf\jsonapi\objects\JsonapiObject;
use alsvanzelf\jsonapi\objects\LinkObject;
use alsvanzelf\jsonapi\objects\LinksObject;
use alsvanzelf\jsonapi\objects\MetaObject;
-use alsvanzelf\jsonapi\objects\ProfileLinkObject;
/**
* @see ResourceDocument, CollectionDocument, ErrorsDocument or MetaDocument
*/
abstract class Document implements DocumentInterface, \JsonSerializable {
- use AtMemberManager, HttpStatusCodeManager, LinksManager;
+ use AtMemberManager, ExtensionMemberManager, HttpStatusCodeManager, LinksManager {
+ LinksManager::addLink as linkManagerAddLink;
+ }
const JSONAPI_VERSION_1_0 = '1.0';
const JSONAPI_VERSION_1_1 = '1.1';
@@ -39,6 +43,8 @@ abstract class Document implements DocumentInterface, \JsonSerializable {
protected $meta;
/** @var JsonapiObject */
protected $jsonapi;
+ /** @var ExtensionInterface[] */
+ protected $extensions = [];
/** @var ProfileInterface[] */
protected $profiles = [];
/** @var array */
@@ -94,11 +100,7 @@ public function __construct() {
*/
public function addLink($key, $href, array $meta=[], $level=Document::LEVEL_ROOT) {
if ($level === Document::LEVEL_ROOT) {
- if ($this->links === null) {
- $this->setLinksObject(new LinksObject());
- }
-
- $this->links->add($key, $href, $meta);
+ $this->linkManagerAddLink($key, $href, $meta);
}
elseif ($level === Document::LEVEL_JSONAPI) {
throw new InputException('level "jsonapi" can not be used for links');
@@ -114,12 +116,38 @@ public function addLink($key, $href, array $meta=[], $level=Document::LEVEL_ROOT
/**
* set the self link on the document
*
+ * @note a LinkObject is added when extensions or profiles are applied
+ *
* @param string $href
* @param array $meta optional, if given a LinkObject is added, otherwise a link string is added
* @param string $level one of the Document::LEVEL_* constants, optional, defaults to Document::LEVEL_ROOT
*/
public function setSelfLink($href, array $meta=[], $level=Document::LEVEL_ROOT) {
- $this->addLink('self', $href, $meta, $level);
+ if ($level === Document::LEVEL_ROOT && ($this->extensions !== [] || $this->profiles !== [])) {
+ $contentType = Converter::prepareContentType(Document::CONTENT_TYPE_OFFICIAL, $this->extensions, $this->profiles);
+
+ $linkObject = new LinkObject($href, $meta);
+ $linkObject->setMediaType($contentType);
+
+ $this->addLinkObject('self', $linkObject);
+ }
+ else {
+ $this->addLink('self', $href, $meta, $level);
+ }
+ }
+
+ /**
+ * set a link describing the current document
+ *
+ * for example this could link to an OpenAPI or JSON Schema document
+ *
+ * @note according to the spec, this can only be set to Document::LEVEL_ROOT
+ *
+ * @param string $href
+ * @param array $meta optional, if given a LinkObject is added, otherwise a link string is added
+ */
+ public function setDescribedByLink($href, array $meta=[]) {
+ $this->addLink('describedby', $href, $meta, $level=Document::LEVEL_ROOT);
}
/**
@@ -178,6 +206,36 @@ public function unsetJsonapiObject() {
$this->jsonapi = null;
}
+ /**
+ * apply a extension which adds the link and sets a correct content-type
+ *
+ * note that the rules from the extension are not automatically enforced
+ * applying the rules, and applying them correctly, is manual
+ * however the $extension could have custom methods to help
+ *
+ * @see https://jsonapi.org/extensions/#extensions
+ *
+ * @param ExtensionInterface $extension
+ *
+ * @throws Exception if namespace uses illegal characters
+ * @throws DuplicateException if namespace conflicts with another applied extension
+ */
+ public function applyExtension(ExtensionInterface $extension) {
+ $namespace = $extension->getNamespace();
+ if (strlen($namespace) < 1 || preg_match('{[^a-zA-Z0-9]}', $namespace) === 1) {
+ throw new Exception('invalid namespace "'.$namespace.'"');
+ }
+ if (isset($this->extensions[$namespace])) {
+ throw new DuplicateException('an extension with namespace "'.$namespace.'" is already applied');
+ }
+
+ $this->extensions[$namespace] = $extension;
+
+ if ($this->jsonapi !== null) {
+ $this->jsonapi->addExtension($extension);
+ }
+ }
+
/**
* apply a profile which adds the link and sets a correct content-type
*
@@ -185,23 +243,15 @@ public function unsetJsonapiObject() {
* applying the rules, and applying them correctly, is manual
* however the $profile could have custom methods to help
*
- * @see https://jsonapi.org/format/1.1/#profiles
+ * @see https://jsonapi.org/extensions/#profiles
*
* @param ProfileInterface $profile
*/
public function applyProfile(ProfileInterface $profile) {
$this->profiles[] = $profile;
- if ($this->links === null) {
- $this->setLinksObject(new LinksObject());
- }
-
- $link = $profile->getAliasedLink();
- if ($link instanceof LinkObject) {
- $this->links->appendLinkObject('profile', $link);
- }
- else {
- $this->links->append('profile', $link);
+ if ($this->jsonapi !== null) {
+ $this->jsonapi->addProfile($profile);
}
}
@@ -213,7 +263,14 @@ public function applyProfile(ProfileInterface $profile) {
* @inheritDoc
*/
public function toArray() {
- $array = $this->getAtMembers();
+ $array = [];
+
+ if ($this->hasAtMembers()) {
+ $array = array_merge($array, $this->getAtMembers());
+ }
+ if ($this->hasExtensionMembers()) {
+ $array = array_merge($array, $this->getExtensionMembers());
+ }
if ($this->jsonapi !== null && $this->jsonapi->isEmpty() === false) {
$array['jsonapi'] = $this->jsonapi->toArray();
@@ -267,7 +324,7 @@ public function sendResponse(array $options=[]) {
http_response_code($this->httpStatusCode);
- $contentType = Converter::mergeProfilesInContentType($options['contentType'], $this->profiles);
+ $contentType = Converter::prepareContentType($options['contentType'], $this->extensions, $this->profiles);
header('Content-Type: '.$contentType);
echo $json;
diff --git a/src/ResourceDocument.php b/src/ResourceDocument.php
index a94c16e..8db9d09 100644
--- a/src/ResourceDocument.php
+++ b/src/ResourceDocument.php
@@ -179,6 +179,13 @@ public function setId($id) {
$this->resource->setId($id);
}
+ /**
+ * @param string|int $localId will be casted to a string
+ */
+ public function setLocalId($localId) {
+ $this->resource->setLocalId($localId);
+ }
+
/**
* @param AttributesObject $attributesObject
* @param array $options optional {@see ResourceObject::$defaults}
diff --git a/src/extensions/AtomicOperationsDocument.php b/src/extensions/AtomicOperationsDocument.php
new file mode 100644
index 0000000..1599fd8
--- /dev/null
+++ b/src/extensions/AtomicOperationsDocument.php
@@ -0,0 +1,57 @@
+extension = new AtomicOperationsExtension();
+ $this->applyExtension($this->extension);
+ }
+
+ /**
+ * add resources as results of the operations
+ *
+ * @param ResourceInterface[] ...$resources
+ */
+ public function addResults(ResourceInterface ...$resources) {
+ $this->results = array_merge($this->results, $resources);
+ }
+
+ /**
+ * DocumentInterface
+ */
+
+ /**
+ * @inheritDoc
+ */
+ public function toArray() {
+ $results = [];
+ foreach ($this->results as $result) {
+ $results[] = [
+ 'data' => $result->getResource()->toArray(),
+ ];
+ }
+
+ $this->addExtensionMember($this->extension, 'results', $results);
+
+ return parent::toArray();
+ }
+}
diff --git a/src/extensions/AtomicOperationsExtension.php b/src/extensions/AtomicOperationsExtension.php
new file mode 100644
index 0000000..b94cfe6
--- /dev/null
+++ b/src/extensions/AtomicOperationsExtension.php
@@ -0,0 +1,32 @@
+getOfficialLink();
+ }
+ $extensionLinks = implode(' ', $extensionLinks);
+
+ $contentType .= '; ext="'.$extensionLinks.'"';
}
- $profileLinks = [];
- foreach ($profiles as $profile) {
- $link = $profile->getAliasedLink();
- $profileLinks[] = ($link instanceof LinkObject) ? $link->toArray()['href'] : $link;
+ if ($profiles !== []) {
+ $profileLinks = [];
+ foreach ($profiles as $profile) {
+ $profileLinks[] = $profile->getOfficialLink();
+ }
+ $profileLinks = implode(' ', $profileLinks);
+
+ $contentType .= '; profile="'.$profileLinks.'"';
}
- $profileLinks = implode(' ', $profileLinks);
- return $contentType.';profile="'.$profileLinks.'", '.$contentType;
+ return $contentType;
+ }
+
+ /**
+ * @deprecated {@see prepareContentType()}
+ */
+ public static function mergeProfilesInContentType($contentType, array $profiles) {
+ return self::prepareContentType($contentType, $extensions=[], $profiles);
}
}
diff --git a/src/helpers/ExtensionMemberManager.php b/src/helpers/ExtensionMemberManager.php
new file mode 100644
index 0000000..4261628
--- /dev/null
+++ b/src/helpers/ExtensionMemberManager.php
@@ -0,0 +1,63 @@
+getNamespace();
+
+ if (strpos($key, $namespace.':') === 0) {
+ $key = substr($key, strlen($namespace.':'));
+ }
+
+ Validator::checkMemberName($key);
+
+ if (is_object($value)) {
+ $value = Converter::objectToArray($value);
+ }
+
+ $this->extensionMembers[$namespace.':'.$key] = $value;
+ }
+
+ /**
+ * internal api
+ */
+
+ /**
+ * @internal
+ *
+ * @return boolean
+ */
+ public function hasExtensionMembers() {
+ return ($this->extensionMembers !== []);
+ }
+
+ /**
+ * @internal
+ *
+ * @return array
+ */
+ public function getExtensionMembers() {
+ return $this->extensionMembers;
+ }
+}
diff --git a/src/helpers/LinksManager.php b/src/helpers/LinksManager.php
index 97c1c7f..ab234a4 100644
--- a/src/helpers/LinksManager.php
+++ b/src/helpers/LinksManager.php
@@ -29,6 +29,8 @@ public function addLink($key, $href, array $meta=[]) {
/**
* append a link to a key with an array of links
*
+ * @deprecated array links are not supported anymore {@see ->addLink()}
+ *
* @param string $key
* @param string $href
* @param array $meta optional, if given a LinkObject is added, otherwise a link string is added
@@ -56,6 +58,8 @@ public function addLinkObject($key, LinkObject $linkObject) {
/**
* set a key containing a LinksArray
*
+ * @deprecated array links are not supported anymore {@see ->addLinkObject()}
+ *
* @param string $key
* @param LinksArray $linksArray
*/
@@ -67,6 +71,8 @@ public function addLinksArray($key, LinksArray $linksArray) {
/**
* append a LinkObject to a key with a LinksArray
*
+ * @deprecated array links are not supported anymore {@see ->addLinkObject()}
+ *
* @param string $key
* @param LinkObject $linkObject
*/
diff --git a/src/helpers/ProfileAliasManager.php b/src/helpers/ProfileAliasManager.php
deleted file mode 100644
index 1ef0075..0000000
--- a/src/helpers/ProfileAliasManager.php
+++ /dev/null
@@ -1,80 +0,0 @@
-getOfficialKeywords();
- if ($officialKeywords === []) {
- return;
- }
-
- $this->keywordMapping = array_combine($officialKeywords, $officialKeywords);
- if ($aliases === []) {
- return;
- }
-
- foreach ($aliases as $keyword => $alias) {
- if ($alias === $keyword) {
- throw new InputException('an alias should be different from its keyword');
- }
- if (in_array($keyword, $officialKeywords, $strict=true) === false) {
- throw new InputException('unknown keyword "'.$keyword.'" to alias');
- }
- Validator::checkMemberName($alias);
-
- $this->keywordMapping[$keyword] = $alias;
- }
-
- $this->aliasMapping = $aliases;
- }
-
- /**
- * @inheritDoc
- */
- public function getKeyword($keyword) {
- if (isset($this->keywordMapping[$keyword]) === false) {
- throw new InputException('unknown keyword "'.$keyword.'"');
- }
-
- return $this->keywordMapping[$keyword];
- }
-
- /**
- * @inheritDoc
- */
- abstract public function getOfficialKeywords();
-
- /**
- * @inheritDoc
- */
- abstract public function getOfficialLink();
-
- /**
- * @inheritDoc
- */
- public function getAliasedLink() {
- if ($this->aliasMapping === []) {
- return $this->getOfficialLink();
- }
-
- return new ProfileLinkObject($this->getOfficialLink(), $this->aliasMapping);
- }
-}
diff --git a/src/helpers/RequestParser.php b/src/helpers/RequestParser.php
index 5ae8f25..5209fda 100644
--- a/src/helpers/RequestParser.php
+++ b/src/helpers/RequestParser.php
@@ -248,6 +248,20 @@ public function getFilter() {
return $this->queryParameters['filter'];
}
+ /**
+ * @return boolean
+ */
+ public function hasLocalId() {
+ return (isset($this->document['data']['lid']));
+ }
+
+ /**
+ * @return string
+ */
+ public function getLocalId() {
+ return $this->document['data']['lid'];
+ }
+
/**
* @param string $attributeName
* @return boolean
diff --git a/src/helpers/Validator.php b/src/helpers/Validator.php
index 6e814d7..692d473 100644
--- a/src/helpers/Validator.php
+++ b/src/helpers/Validator.php
@@ -12,6 +12,7 @@
class Validator {
const OBJECT_CONTAINER_TYPE = 'type';
const OBJECT_CONTAINER_ID = 'id';
+ const OBJECT_CONTAINER_LID = 'lid';
const OBJECT_CONTAINER_ATTRIBUTES = 'attributes';
const OBJECT_CONTAINER_RELATIONSHIPS = 'relationships';
@@ -84,7 +85,7 @@ public function clearUsedFields($objectContainerToClear) {
*/
public function claimUsedResourceIdentifier(ResourceInterface $resource) {
if ($resource->getResource()->hasIdentification() === false) {
- throw new InputException('can not validate resource without identifier, set type and id first');
+ throw new InputException('can not validate resource without identifier, set type and id/lid first');
}
$resourceKey = $resource->getResource()->getIdentificationKey();
diff --git a/src/interfaces/ExtensionInterface.php b/src/interfaces/ExtensionInterface.php
new file mode 100644
index 0000000..3c07497
--- /dev/null
+++ b/src/interfaces/ExtensionInterface.php
@@ -0,0 +1,21 @@
+alias('version', 'v') was called before, this would return 'v'
- *
- * @param string $keyword
- * @return string
- *
- * @throws InputException if the keyword is not known to the profile
- */
- public function getKeyword($keyword);
-
- /**
- * returns an array of official keywords this profile defines
- *
- * @internal
- *
- * @return string[]
- */
- public function getOfficialKeywords();
-
/**
* the unique link identifying and describing the profile
*
@@ -52,15 +11,4 @@ public function getOfficialKeywords();
* @return string
*/
public function getOfficialLink();
-
- /**
- * get the official link, or a LinkObject with the link and its aliases
- *
- * optionally also contains the aliases applied
- *
- * @internal
- *
- * @return LinkObject|string
- */
- public function getAliasedLink();
}
diff --git a/src/objects/AttributesObject.php b/src/objects/AttributesObject.php
index 5cbc5b1..867c45a 100644
--- a/src/objects/AttributesObject.php
+++ b/src/objects/AttributesObject.php
@@ -4,11 +4,12 @@
use alsvanzelf\jsonapi\helpers\AtMemberManager;
use alsvanzelf\jsonapi\helpers\Converter;
+use alsvanzelf\jsonapi\helpers\ExtensionMemberManager;
use alsvanzelf\jsonapi\helpers\Validator;
use alsvanzelf\jsonapi\interfaces\ObjectInterface;
class AttributesObject implements ObjectInterface {
- use AtMemberManager;
+ use AtMemberManager, ExtensionMemberManager;
/** @var array */
protected $attributes = [];
@@ -85,13 +86,32 @@ public function getKeys() {
* @inheritDoc
*/
public function isEmpty() {
- return ($this->attributes === [] && $this->hasAtMembers() === false);
+ if ($this->attributes !== []) {
+ return false;
+ }
+ if ($this->hasAtMembers()) {
+ return false;
+ }
+ if ($this->hasExtensionMembers()) {
+ return false;
+ }
+
+ return true;
}
/**
* @inheritDoc
*/
public function toArray() {
- return array_merge($this->getAtMembers(), $this->attributes);
+ $array = [];
+
+ if ($this->hasAtMembers()) {
+ $array = array_merge($array, $this->getAtMembers());
+ }
+ if ($this->hasExtensionMembers()) {
+ $array = array_merge($array, $this->getExtensionMembers());
+ }
+
+ return array_merge($array, $this->attributes);
}
}
diff --git a/src/objects/ErrorObject.php b/src/objects/ErrorObject.php
index 1473b04..3d40d78 100644
--- a/src/objects/ErrorObject.php
+++ b/src/objects/ErrorObject.php
@@ -6,13 +6,14 @@
use alsvanzelf\jsonapi\exceptions\InputException;
use alsvanzelf\jsonapi\helpers\AtMemberManager;
use alsvanzelf\jsonapi\helpers\Converter;
+use alsvanzelf\jsonapi\helpers\ExtensionMemberManager;
use alsvanzelf\jsonapi\helpers\HttpStatusCodeManager;
use alsvanzelf\jsonapi\helpers\LinksManager;
use alsvanzelf\jsonapi\helpers\Validator;
use alsvanzelf\jsonapi\interfaces\ObjectInterface;
class ErrorObject implements ObjectInterface {
- use AtMemberManager, HttpStatusCodeManager, LinksManager;
+ use AtMemberManager, ExtensionMemberManager, HttpStatusCodeManager, LinksManager;
/** @var string */
protected $id;
@@ -137,7 +138,7 @@ public function setHumanExplanation($genericTitle, $specificDetails=null, $speci
$this->setAboutLink($specificAboutLink);
}
if ($genericTypeLink !== null) {
- $this->appendTypeLink($genericTypeLink);
+ $this->setTypeLink($genericTypeLink);
}
}
@@ -151,9 +152,21 @@ public function setAboutLink($href, array $meta=[]) {
$this->addLink('about', $href, $meta);
}
+ /**
+ * set the link of the generic type of this error, explained in a human-friendly way
+ *
+ * @param string $href
+ * @param array $meta optional, if given a LinkObject is added, otherwise a link string is added
+ */
+ public function setTypeLink($href, array $meta=[]) {
+ $this->addLink('type', $href, $meta);
+ }
+
/**
* append a link of the generic type of this error, explained in a human-friendly way
*
+ * @deprecated array links are not supported anymore {@see ->setTypeLink()}
+ *
* @param string $href
* @param array $meta optional, if given a LinkObject is added, otherwise a link string is added
*/
@@ -288,6 +301,9 @@ public function isEmpty() {
if ($this->hasAtMembers()) {
return false;
}
+ if ($this->hasExtensionMembers()) {
+ return false;
+ }
return true;
}
@@ -296,8 +312,14 @@ public function isEmpty() {
* @inheritDoc
*/
public function toArray() {
- $array = $this->getAtMembers();
+ $array = [];
+ if ($this->hasAtMembers()) {
+ $array = array_merge($array, $this->getAtMembers());
+ }
+ if ($this->hasExtensionMembers()) {
+ $array = array_merge($array, $this->getExtensionMembers());
+ }
if ($this->id !== null) {
$array['id'] = $this->id;
}
diff --git a/src/objects/JsonapiObject.php b/src/objects/JsonapiObject.php
index ad04da2..980b0b2 100644
--- a/src/objects/JsonapiObject.php
+++ b/src/objects/JsonapiObject.php
@@ -4,14 +4,21 @@
use alsvanzelf\jsonapi\Document;
use alsvanzelf\jsonapi\helpers\AtMemberManager;
+use alsvanzelf\jsonapi\helpers\ExtensionMemberManager;
+use alsvanzelf\jsonapi\interfaces\ExtensionInterface;
use alsvanzelf\jsonapi\interfaces\ObjectInterface;
+use alsvanzelf\jsonapi\interfaces\ProfileInterface;
use alsvanzelf\jsonapi\objects\MetaObject;
class JsonapiObject implements ObjectInterface {
- use AtMemberManager;
+ use AtMemberManager, ExtensionMemberManager;
/** @var string */
protected $version;
+ /** @var ExtensionInterface[] */
+ protected $extensions = [];
+ /** @var ProfileInterface */
+ protected $profiles = [];
/** @var MetaObject */
protected $meta;
@@ -51,6 +58,20 @@ public function setVersion($version) {
$this->version = $version;
}
+ /**
+ * @param ExtensionInterface $extension
+ */
+ public function addExtension(ExtensionInterface $extension) {
+ $this->extensions[] = $extension;
+ }
+
+ /**
+ * @param ProfileInterface $profile
+ */
+ public function addProfile(ProfileInterface $profile) {
+ $this->profiles[] = $profile;
+ }
+
/**
* @param MetaObject $metaObject
*/
@@ -69,12 +90,21 @@ public function isEmpty() {
if ($this->version !== null) {
return false;
}
+ if ($this->extensions !== []) {
+ return false;
+ }
+ if ($this->profiles !== []) {
+ return false;
+ }
if ($this->meta !== null && $this->meta->isEmpty() === false) {
return false;
}
if ($this->hasAtMembers()) {
return false;
}
+ if ($this->hasExtensionMembers()) {
+ return false;
+ }
return true;
}
@@ -83,11 +113,29 @@ public function isEmpty() {
* @inheritDoc
*/
public function toArray() {
- $array = $this->getAtMembers();
+ $array = [];
+ if ($this->hasAtMembers()) {
+ $array = array_merge($array, $this->getAtMembers());
+ }
+ if ($this->hasExtensionMembers()) {
+ $array = array_merge($array, $this->getExtensionMembers());
+ }
if ($this->version !== null) {
$array['version'] = $this->version;
}
+ if ($this->extensions !== []) {
+ $array['ext'] = [];
+ foreach ($this->extensions as $extension) {
+ $array['ext'][] = $extension->getOfficialLink();
+ }
+ }
+ if ($this->profiles !== []) {
+ $array['profile'] = [];
+ foreach ($this->profiles as $profile) {
+ $array['profile'][] = $profile->getOfficialLink();
+ }
+ }
if ($this->meta !== null && $this->meta->isEmpty() === false) {
$array['meta'] = $this->meta->toArray();
}
diff --git a/src/objects/LinkObject.php b/src/objects/LinkObject.php
index 1eab36d..244d103 100644
--- a/src/objects/LinkObject.php
+++ b/src/objects/LinkObject.php
@@ -3,14 +3,25 @@
namespace alsvanzelf\jsonapi\objects;
use alsvanzelf\jsonapi\helpers\AtMemberManager;
+use alsvanzelf\jsonapi\helpers\ExtensionMemberManager;
use alsvanzelf\jsonapi\interfaces\ObjectInterface;
use alsvanzelf\jsonapi\objects\MetaObject;
class LinkObject implements ObjectInterface {
- use AtMemberManager;
+ use AtMemberManager, ExtensionMemberManager;
/** @var string */
protected $href;
+ /** @var string */
+ protected $rel;
+ /** @var LinkObject */
+ protected $describedby;
+ /** @var string */
+ protected $title;
+ /** @var string */
+ protected $type;
+ /** @var string[] */
+ protected $hreflang = [];
/** @var MetaObject */
protected $meta;
@@ -31,6 +42,25 @@ public function __construct($href=null, array $meta=[]) {
* human api
*/
+ /**
+ * @param string $href
+ */
+ public function setDescribedBy($href) {
+ $this->setDescribedByLinkObject(new LinkObject($href));
+ }
+
+ /**
+ * @param string $language
+ */
+ public function addLanguage($language) {
+ if ($this->hreflang === []) {
+ $this->setHreflang($language);
+ }
+ else {
+ $this->setHreflang(...array_merge($this->hreflang, [$language]));
+ }
+ }
+
/**
* @param string $key
* @param mixed $value
@@ -54,6 +84,45 @@ public function setHref($href) {
$this->href = $href;
}
+ /**
+ * @todo validate according to https://tools.ietf.org/html/rfc8288#section-2.1
+ *
+ * @param string $relationType
+ */
+ public function setRelationType($relationType) {
+ $this->rel = $relationType;
+ }
+
+ /**
+ * @param LinkObject $describedBy
+ */
+ public function setDescribedByLinkObject(LinkObject $describedBy) {
+ $this->describedby = $describedBy;
+ }
+
+ /**
+ * @param string $friendlyTitle
+ */
+ public function setHumanTitle($humanTitle) {
+ $this->title = $humanTitle;
+ }
+
+ /**
+ * @param string $mediaType
+ */
+ public function setMediaType($mediaType) {
+ $this->type = $mediaType;
+ }
+
+ /**
+ * @todo validate according to https://tools.ietf.org/html/rfc5646
+ *
+ * @param string ...$hreflang
+ */
+ public function setHreflang(...$hreflang) {
+ $this->hreflang = $hreflang;
+ }
+
/**
* @param MetaObject $metaObject
*/
@@ -72,12 +141,30 @@ public function isEmpty() {
if ($this->href !== null) {
return false;
}
+ if ($this->rel !== null) {
+ return false;
+ }
+ if ($this->title !== null) {
+ return false;
+ }
+ if ($this->type !== null) {
+ return false;
+ }
+ if ($this->hreflang !== []) {
+ return false;
+ }
+ if ($this->describedby !== null && $this->describedby->isEmpty() === false) {
+ return false;
+ }
if ($this->meta !== null && $this->meta->isEmpty() === false) {
return false;
}
if ($this->hasAtMembers()) {
return false;
}
+ if ($this->hasExtensionMembers()) {
+ return false;
+ }
return true;
}
@@ -86,10 +173,37 @@ public function isEmpty() {
* @inheritDoc
*/
public function toArray() {
- $array = $this->getAtMembers();
+ $array = [];
+
+ if ($this->hasAtMembers()) {
+ $array = array_merge($array, $this->getAtMembers());
+ }
+ if ($this->hasExtensionMembers()) {
+ $array = array_merge($array, $this->getExtensionMembers());
+ }
$array['href'] = $this->href;
+ if ($this->rel !== null) {
+ $array['rel'] = $this->rel;
+ }
+ if ($this->title !== null) {
+ $array['title'] = $this->title;
+ }
+ if ($this->type !== null) {
+ $array['type'] = $this->type;
+ }
+ if ($this->hreflang !== []) {
+ if (count($this->hreflang) === 1) {
+ $array['hreflang'] = $this->hreflang[0];
+ }
+ else {
+ $array['hreflang'] = $this->hreflang;
+ }
+ }
+ if ($this->describedby !== null && $this->describedby->isEmpty() === false) {
+ $array['describedby'] = $this->describedby->toArray();
+ }
if ($this->meta !== null && $this->meta->isEmpty() === false) {
$array['meta'] = $this->meta->toArray();
}
diff --git a/src/objects/LinksArray.php b/src/objects/LinksArray.php
index d390e01..d4ae042 100644
--- a/src/objects/LinksArray.php
+++ b/src/objects/LinksArray.php
@@ -9,7 +9,6 @@
/**
* an array of links (strings and LinkObjects), used for:
* - type links in an ErrorObject
- * - profile links at root level
*/
class LinksArray implements ObjectInterface {
/** @var array with string|LinkObject */
diff --git a/src/objects/LinksObject.php b/src/objects/LinksObject.php
index 9e6347b..1e759ee 100644
--- a/src/objects/LinksObject.php
+++ b/src/objects/LinksObject.php
@@ -5,13 +5,14 @@
use alsvanzelf\jsonapi\exceptions\DuplicateException;
use alsvanzelf\jsonapi\helpers\AtMemberManager;
use alsvanzelf\jsonapi\helpers\Converter;
+use alsvanzelf\jsonapi\helpers\ExtensionMemberManager;
use alsvanzelf\jsonapi\helpers\Validator;
use alsvanzelf\jsonapi\interfaces\ObjectInterface;
use alsvanzelf\jsonapi\objects\LinkObject;
use alsvanzelf\jsonapi\objects\LinksArray;
class LinksObject implements ObjectInterface {
- use AtMemberManager;
+ use AtMemberManager, ExtensionMemberManager;
/** @var array with string|LinkObject */
protected $links = [];
@@ -63,6 +64,8 @@ public function add($key, $href, array $meta=[]) {
*
* @see LinksArray for use cases
*
+ * @deprecated array links are not supported anymore {@see ->add()}
+ *
* @param string $key
* @param string $href
* @param array $meta optional, if given a LinkObject is added, otherwise a link string is added
@@ -119,6 +122,8 @@ public function addLinkObject($key, LinkObject $linkObject) {
}
/**
+ * @deprecated array links are not supported anymore {@see ->addLinkObject()}
+ *
* @param string $key
* @param LinksArray $linksArray
*
@@ -135,6 +140,8 @@ public function addLinksArray($key, LinksArray $linksArray) {
}
/**
+ * @deprecated array links are not supported anymore {@see ->addLinkObject()}
+ *
* @param string $key
* @param LinkObject $linkObject
*
@@ -161,14 +168,31 @@ public function appendLinkObject($key, LinkObject $linkObject) {
* @inheritDoc
*/
public function isEmpty() {
- return ($this->links === [] && $this->hasAtMembers() === false);
+ if ($this->links !== []) {
+ return false;
+ }
+ if ($this->hasAtMembers()) {
+ return false;
+ }
+ if ($this->hasExtensionMembers()) {
+ return false;
+ }
+
+ return true;
}
/**
* @inheritDoc
*/
public function toArray() {
- $array = $this->getAtMembers();
+ $array = [];
+
+ if ($this->hasAtMembers()) {
+ $array = array_merge($array, $this->getAtMembers());
+ }
+ if ($this->hasExtensionMembers()) {
+ $array = array_merge($array, $this->getExtensionMembers());
+ }
foreach ($this->links as $key => $link) {
if ($link instanceof LinkObject && $link->isEmpty() === false) {
diff --git a/src/objects/MetaObject.php b/src/objects/MetaObject.php
index f31eb28..03e9e1e 100644
--- a/src/objects/MetaObject.php
+++ b/src/objects/MetaObject.php
@@ -4,11 +4,12 @@
use alsvanzelf\jsonapi\helpers\AtMemberManager;
use alsvanzelf\jsonapi\helpers\Converter;
+use alsvanzelf\jsonapi\helpers\ExtensionMemberManager;
use alsvanzelf\jsonapi\helpers\Validator;
use alsvanzelf\jsonapi\interfaces\ObjectInterface;
class MetaObject implements ObjectInterface {
- use AtMemberManager;
+ use AtMemberManager, ExtensionMemberManager;
/** @var array */
protected $meta = [];
@@ -67,13 +68,32 @@ public function add($key, $value) {
* @inheritDoc
*/
public function isEmpty() {
- return ($this->meta === [] && $this->hasAtMembers() === false);
+ if ($this->meta !== []) {
+ return false;
+ }
+ if ($this->hasAtMembers()) {
+ return false;
+ }
+ if ($this->hasExtensionMembers()) {
+ return false;
+ }
+
+ return true;
}
/**
* @inheritDoc
*/
public function toArray() {
- return array_merge($this->getAtMembers(), $this->meta);
+ $array = [];
+
+ if ($this->hasAtMembers()) {
+ $array = array_merge($array, $this->getAtMembers());
+ }
+ if ($this->hasExtensionMembers()) {
+ $array = array_merge($array, $this->getExtensionMembers());
+ }
+
+ return array_merge($array, $this->meta);
}
}
diff --git a/src/objects/ProfileLinkObject.php b/src/objects/ProfileLinkObject.php
deleted file mode 100644
index 4652a53..0000000
--- a/src/objects/ProfileLinkObject.php
+++ /dev/null
@@ -1,47 +0,0 @@
-aliases = $aliases;
- }
-
- /**
- * human api
- */
-
- /**
- * spec api
- */
-
- /**
- * ObjectInterface
- */
-
- /**
- * @inheritDoc
- */
- public function toArray() {
- $array = parent::toArray();
-
- if ($this->aliases !== []) {
- $array['aliases'] = $this->aliases;
- }
-
- return $array;
- }
-}
diff --git a/src/objects/RelationshipObject.php b/src/objects/RelationshipObject.php
index ea62561..7644cd5 100644
--- a/src/objects/RelationshipObject.php
+++ b/src/objects/RelationshipObject.php
@@ -5,6 +5,7 @@
use alsvanzelf\jsonapi\CollectionDocument;
use alsvanzelf\jsonapi\exceptions\InputException;
use alsvanzelf\jsonapi\helpers\AtMemberManager;
+use alsvanzelf\jsonapi\helpers\ExtensionMemberManager;
use alsvanzelf\jsonapi\helpers\LinksManager;
use alsvanzelf\jsonapi\interfaces\ObjectInterface;
use alsvanzelf\jsonapi\interfaces\PaginableInterface;
@@ -15,7 +16,7 @@
use alsvanzelf\jsonapi\objects\ResourceObject;
class RelationshipObject implements ObjectInterface, PaginableInterface, RecursiveResourceContainerInterface {
- use AtMemberManager, LinksManager;
+ use AtMemberManager, ExtensionMemberManager, LinksManager;
const TO_ONE = 'one';
const TO_MANY = 'many';
@@ -273,6 +274,9 @@ public function isEmpty() {
if ($this->hasAtMembers()) {
return false;
}
+ if ($this->hasExtensionMembers()) {
+ return false;
+ }
return true;
}
@@ -281,7 +285,14 @@ public function isEmpty() {
* @inheritDoc
*/
public function toArray() {
- $array = $this->getAtMembers();
+ $array = [];
+
+ if ($this->hasAtMembers()) {
+ $array = array_merge($array, $this->getAtMembers());
+ }
+ if ($this->hasExtensionMembers()) {
+ $array = array_merge($array, $this->getExtensionMembers());
+ }
if ($this->links !== null && $this->links->isEmpty() === false) {
$array['links'] = $this->links->toArray();
diff --git a/src/objects/RelationshipsObject.php b/src/objects/RelationshipsObject.php
index 6764aa3..c0bf3e0 100644
--- a/src/objects/RelationshipsObject.php
+++ b/src/objects/RelationshipsObject.php
@@ -4,6 +4,7 @@
use alsvanzelf\jsonapi\exceptions\DuplicateException;
use alsvanzelf\jsonapi\helpers\AtMemberManager;
+use alsvanzelf\jsonapi\helpers\ExtensionMemberManager;
use alsvanzelf\jsonapi\helpers\Validator;
use alsvanzelf\jsonapi\interfaces\ObjectInterface;
use alsvanzelf\jsonapi\interfaces\RecursiveResourceContainerInterface;
@@ -12,7 +13,7 @@
use alsvanzelf\jsonapi\objects\ResourceObject;
class RelationshipsObject implements ObjectInterface, RecursiveResourceContainerInterface {
- use AtMemberManager;
+ use AtMemberManager, ExtensionMemberManager;
/** @var RelationshipObject[] */
protected $relationships = [];
@@ -77,14 +78,31 @@ public function getKeys() {
* @inheritDoc
*/
public function isEmpty() {
- return ($this->relationships === [] && $this->hasAtMembers() === false);
+ if ($this->relationships !== []) {
+ return false;
+ }
+ if ($this->hasAtMembers()) {
+ return false;
+ }
+ if ($this->hasExtensionMembers()) {
+ return false;
+ }
+
+ return true;
}
/**
* @inheritDoc
*/
public function toArray() {
- $array = $this->getAtMembers();
+ $array = [];
+
+ if ($this->hasAtMembers()) {
+ $array = array_merge($array, $this->getAtMembers());
+ }
+ if ($this->hasExtensionMembers()) {
+ $array = array_merge($array, $this->getExtensionMembers());
+ }
foreach ($this->relationships as $key => $relationshipObject) {
$array[$key] = $relationshipObject->toArray();
diff --git a/src/objects/ResourceIdentifierObject.php b/src/objects/ResourceIdentifierObject.php
index fe32d91..05aaa6b 100644
--- a/src/objects/ResourceIdentifierObject.php
+++ b/src/objects/ResourceIdentifierObject.php
@@ -3,19 +3,23 @@
namespace alsvanzelf\jsonapi\objects;
use alsvanzelf\jsonapi\exceptions\Exception;
+use alsvanzelf\jsonapi\exceptions\DuplicateException;
use alsvanzelf\jsonapi\helpers\AtMemberManager;
+use alsvanzelf\jsonapi\helpers\ExtensionMemberManager;
use alsvanzelf\jsonapi\helpers\Validator;
use alsvanzelf\jsonapi\interfaces\ObjectInterface;
use alsvanzelf\jsonapi\interfaces\ResourceInterface;
use alsvanzelf\jsonapi\objects\MetaObject;
class ResourceIdentifierObject implements ObjectInterface, ResourceInterface {
- use AtMemberManager;
+ use AtMemberManager, ExtensionMemberManager;
/** @var string */
protected $type;
/** @var string */
protected $id;
+ /** @var string */
+ protected $lid;
/** @var MetaObject */
protected $meta;
/** @var Validator */
@@ -42,6 +46,7 @@ public function __construct($type=null, $id=null) {
// always mark as used, as these keys are reserved
$this->validator->claimUsedFields($fieldNames=['type'], Validator::OBJECT_CONTAINER_TYPE);
$this->validator->claimUsedFields($fieldNames=['id'], Validator::OBJECT_CONTAINER_ID);
+ $this->validator->claimUsedFields($fieldNames=['lid'], Validator::OBJECT_CONTAINER_LID);
}
/**
@@ -73,11 +78,34 @@ public function setType($type) {
/**
* @param string|int $id will be casted to a string
+ *
+ * @throws DuplicateException if localId is already set
*/
public function setId($id) {
+ if ($this->lid !== null) {
+ throw new DuplicateException('id is not allowed when localId is already set');
+ }
+
$this->id = (string) $id;
}
+ /**
+ * set a local id to connect resources to each other when created on the client
+ *
+ * @note this should not be used to send back from the server to the client
+ *
+ * @param string|int $localId will be casted to a string
+ *
+ * @throws DuplicateException if normal id is already set
+ */
+ public function setLocalId($localId) {
+ if ($this->id !== null) {
+ throw new DuplicateException('localId is not allowed when id is already set');
+ }
+
+ $this->lid = (string) $localId;
+ }
+
/**
* @param MetaObject $metaObject
*/
@@ -96,7 +124,7 @@ public function setMetaObject(MetaObject $metaObject) {
* @return ResourceIdentifierObject
*/
public static function fromResourceObject(ResourceObject $resourceObject) {
- $resourceIdentifierObject = new self($resourceObject->type, $resourceObject->id);
+ $resourceIdentifierObject = new self($resourceObject->type, $resourceObject->primaryId());
if ($resourceObject->meta !== null) {
$resourceIdentifierObject->setMetaObject($resourceObject->meta);
@@ -127,7 +155,7 @@ public function equals(ResourceInterface $resource) {
* @return boolean
*/
public function hasIdentification() {
- return ($this->type !== null && $this->id !== null);
+ return ($this->type !== null && $this->primaryId() !== null);
}
/**
@@ -144,7 +172,7 @@ public function getIdentificationKey() {
throw new Exception('resource has no identification yet');
}
- return $this->type.'|'.$this->id;
+ return $this->type.'|'.$this->primaryId();
}
/**
@@ -155,7 +183,7 @@ public function getIdentificationKey() {
* @inheritDoc
*/
public function isEmpty() {
- if ($this->type !== null || $this->id !== null) {
+ if ($this->type !== null || $this->primaryId() !== null) {
return false;
}
if ($this->meta !== null && $this->meta->isEmpty() === false) {
@@ -164,6 +192,9 @@ public function isEmpty() {
if ($this->hasAtMembers()) {
return false;
}
+ if ($this->hasExtensionMembers()) {
+ return false;
+ }
return true;
}
@@ -172,13 +203,23 @@ public function isEmpty() {
* @inheritDoc
*/
public function toArray() {
- $array = $this->getAtMembers();
+ $array = [];
$array['type'] = $this->type;
if ($this->id !== null) {
$array['id'] = $this->id;
}
+ elseif ($this->lid !== null) {
+ $array['lid'] = $this->lid;
+ }
+
+ if ($this->hasAtMembers()) {
+ $array = array_merge($array, $this->getAtMembers());
+ }
+ if ($this->hasExtensionMembers()) {
+ $array = array_merge($array, $this->getExtensionMembers());
+ }
if ($this->meta !== null && $this->meta->isEmpty() === false) {
$array['meta'] = $this->meta->toArray();
@@ -197,4 +238,16 @@ public function toArray() {
public function getResource($identifierOnly=false) {
return $this;
}
+
+ /**
+ * @internal
+ */
+
+ private function primaryId() {
+ if ($this->lid !== null) {
+ return $this->lid;
+ }
+
+ return $this->id;
+ }
}
diff --git a/src/profiles/CursorPaginationProfile.php b/src/profiles/CursorPaginationProfile.php
index 1700f92..e95f523 100644
--- a/src/profiles/CursorPaginationProfile.php
+++ b/src/profiles/CursorPaginationProfile.php
@@ -4,7 +4,6 @@
use alsvanzelf\jsonapi\Document;
use alsvanzelf\jsonapi\ResourceDocument;
-use alsvanzelf\jsonapi\helpers\ProfileAliasManager;
use alsvanzelf\jsonapi\interfaces\PaginableInterface;
use alsvanzelf\jsonapi\interfaces\ProfileInterface;
use alsvanzelf\jsonapi\interfaces\ResourceInterface;
@@ -44,7 +43,7 @@
* - {@see get*ErrorObject} to generate ErrorObjects for specific error cases
* - {@see generatePreviousLink} {@see generateNextLink} to apply the links manually
*/
-class CursorPaginationProfile extends ProfileAliasManager implements ProfileInterface {
+class CursorPaginationProfile implements ProfileInterface {
/**
* human api
*/
@@ -115,7 +114,7 @@ public function setCount(PaginableInterface $paginable, $exactTotal=null, $bestG
* @return string
*/
public function generatePreviousLink($baseOrCurrentUrl, $beforeCursor) {
- return $this->setQueryParameter($baseOrCurrentUrl, $this->getKeyword('page').'[before]', $beforeCursor);
+ return $this->setQueryParameter($baseOrCurrentUrl, 'page[before]', $beforeCursor);
}
/**
@@ -126,7 +125,7 @@ public function generatePreviousLink($baseOrCurrentUrl, $beforeCursor) {
* @return string
*/
public function generateNextLink($baseOrCurrentUrl, $afterCursor) {
- return $this->setQueryParameter($baseOrCurrentUrl, $this->getKeyword('page').'[after]', $afterCursor);
+ return $this->setQueryParameter($baseOrCurrentUrl, 'page[after]', $afterCursor);
}
/**
@@ -192,10 +191,10 @@ public function setItemMeta(ResourceInterface $resource, $cursor) {
];
if ($resource instanceof ResourceDocument) {
- $resource->addMeta($this->getKeyword('page'), $metadata, $level=Document::LEVEL_RESOURCE);
+ $resource->addMeta('page', $metadata, $level=Document::LEVEL_RESOURCE);
}
else {
- $resource->addMeta($this->getKeyword('page'), $metadata);
+ $resource->addMeta('page', $metadata);
}
}
@@ -229,7 +228,7 @@ public function setPaginationMeta(PaginableInterface $paginable, $exactTotal=nul
$metadata['rangeTruncated'] = $rangeIsTruncated;
}
- $paginable->addMeta($this->getKeyword('page'), $metadata);
+ $paginable->addMeta('page', $metadata);
}
/**
@@ -249,7 +248,7 @@ public function setPaginationMeta(PaginableInterface $paginable, $exactTotal=nul
*/
public function getUnsupportedSortErrorObject($genericTitle=null, $specificDetails=null) {
$errorObject = new ErrorObject('Unsupported sort');
- $errorObject->appendTypeLink('https://jsonapi.org/profiles/ethanresnick/cursor-pagination/unsupported-sort');
+ $errorObject->setTypeLink('https://jsonapi.org/profiles/ethanresnick/cursor-pagination/unsupported-sort');
$errorObject->blameQueryParameter('sort');
$errorObject->setHttpStatusCode(400);
@@ -279,10 +278,10 @@ public function getUnsupportedSortErrorObject($genericTitle=null, $specificDetai
*/
public function getMaxPageSizeExceededErrorObject($maxSize, $genericTitle=null, $specificDetails=null) {
$errorObject = new ErrorObject('Max page size exceeded');
- $errorObject->appendTypeLink('https://jsonapi.org/profiles/ethanresnick/cursor-pagination/max-size-exceeded');
- $errorObject->blameQueryParameter($this->getKeyword('page').'[size]');
+ $errorObject->setTypeLink('https://jsonapi.org/profiles/ethanresnick/cursor-pagination/max-size-exceeded');
+ $errorObject->blameQueryParameter('page[size]');
$errorObject->setHttpStatusCode(400);
- $errorObject->addMeta($this->getKeyword('page'), $value=['maxSize' => $maxSize]);
+ $errorObject->addMeta('page', $value=['maxSize' => $maxSize]);
if ($genericTitle !== null) {
$errorObject->setHumanExplanation($genericTitle, $specificDetails);
@@ -302,7 +301,7 @@ public function getMaxPageSizeExceededErrorObject($maxSize, $genericTitle=null,
* - /errors/0/title optional
* - /errors/0/detail optional
*
- * @param int $queryParameter e.g. 'sort' or 'page[size]', aliasing should already be done using {@see getKeyword}
+ * @param int $queryParameter e.g. 'sort' or 'page[size]'
* @param string $typeLink optional
* @param string $genericTitle optional, e.g. 'Invalid Parameter.'
* @param string $specificDetails optional, e.g. 'page[size] must be a positive integer; got 0'
@@ -314,7 +313,7 @@ public function getInvalidParameterValueErrorObject($queryParameter, $typeLink=n
$errorObject->setHttpStatusCode(400);
if ($typeLink !== null) {
- $errorObject->appendTypeLink($typeLink);
+ $errorObject->setTypeLink($typeLink);
}
if ($genericTitle !== null) {
@@ -338,7 +337,7 @@ public function getInvalidParameterValueErrorObject($queryParameter, $typeLink=n
*/
public function getRangePaginationNotSupportedErrorObject($genericTitle=null, $specificDetails=null) {
$errorObject = new ErrorObject('Range pagination not supported');
- $errorObject->appendTypeLink('https://jsonapi.org/profiles/ethanresnick/cursor-pagination/range-pagination-not-supported');
+ $errorObject->setTypeLink('https://jsonapi.org/profiles/ethanresnick/cursor-pagination/range-pagination-not-supported');
$errorObject->setHttpStatusCode(400);
if ($genericTitle !== null) {
@@ -394,9 +393,14 @@ public function getOfficialLink() {
}
/**
- * @inheritDoc
+ * returns the keyword without aliasing
+ *
+ * @deprecated since aliasing was removed from the profiles spec
+ *
+ * @param string $keyword
+ * @return string
*/
- public function getOfficialKeywords() {
- return ['page'];
+ public function getKeyword($keyword) {
+ return $keyword;
}
}
diff --git a/tests/ConverterTest.php b/tests/ConverterTest.php
index ad33478..f5c7d51 100644
--- a/tests/ConverterTest.php
+++ b/tests/ConverterTest.php
@@ -4,7 +4,7 @@
use alsvanzelf\jsonapi\helpers\Converter;
use alsvanzelf\jsonapi\objects\AttributesObject;
-use alsvanzelf\jsonapi\objects\LinkObject;
+use alsvanzelf\jsonapiTests\extensions\TestExtension;
use alsvanzelf\jsonapiTests\profiles\TestProfile;
use PHPUnit\Framework\TestCase;
@@ -64,32 +64,63 @@ public function dataProviderCamelCaseToWords_HappyPath() {
];
}
- public function testMergeProfilesInContentType_HappyPath() {
- $this->assertSame('foo', Converter::mergeProfilesInContentType('foo', []));
+ /**
+ * @group Extensions
+ * @group Profiles
+ */
+ public function testPrepareContentType_HappyPath() {
+ $this->assertSame('foo', Converter::prepareContentType('foo', [], []));
}
- public function testMergeProfilesInContentType_WithProfileStringLink() {
- $profile = new TestProfile();
- $profile->setAliasedLink('bar');
+ /**
+ * @group Extensions
+ */
+ public function testPrepareContentType_WithExtensionStringLink() {
+ $extension = new TestExtension();
+ $extension->setOfficialLink('bar');
- $this->assertSame('foo;profile="bar", foo', Converter::mergeProfilesInContentType('foo', [$profile]));
+ $this->assertSame('foo; ext="bar"', Converter::prepareContentType('foo', [$extension], []));
}
- public function testMergeProfilesInContentType_WithProfileObjectLink() {
+ /**
+ * @group Profiles
+ */
+ public function testPrepareContentType_WithProfileStringLink() {
$profile = new TestProfile();
- $profile->setAliasedLink(new LinkObject('bar'));
+ $profile->setOfficialLink('bar');
- $this->assertSame('foo;profile="bar", foo', Converter::mergeProfilesInContentType('foo', [$profile]));
+ $this->assertSame('foo; profile="bar"', Converter::prepareContentType('foo', [], [$profile]));
}
- public function testMergeProfilesInContentType_WithMultipleProfiles() {
+ /**
+ * @group Extensions
+ * @group Profiles
+ */
+ public function testPrepareContentType_WithMultipleExtensionsAndProfiles() {
+ $extension1 = new TestExtension();
+ $extension1->setOfficialLink('bar');
+
+ $extension2 = new TestExtension();
+ $extension2->setOfficialLink('baz');
+
$profile1 = new TestProfile();
- $profile1->setAliasedLink('bar');
+ $profile1->setOfficialLink('bar');
$profile2 = new TestProfile();
- $profile2->setAliasedLink(new LinkObject('baz'));
+ $profile2->setOfficialLink('baz');
+
+ $this->assertSame('foo; ext="bar baz"; profile="bar baz"', Converter::prepareContentType('foo', [$extension1, $extension2], [$profile1, $profile2]));
+ }
+
+ /**
+ * test method while it is part of the interface
+ * @group Profiles
+ */
+ public function testMergeProfilesInContentType_HappyPath() {
+ $profile = new TestProfile();
+ $profile->setOfficialLink('bar');
- $this->assertSame('foo;profile="bar baz", foo', Converter::mergeProfilesInContentType('foo', [$profile1, $profile2]));
+ $this->assertSame('foo; profile="bar"', Converter::mergeProfilesInContentType('foo', [$profile]));
}
}
diff --git a/tests/DocumentTest.php b/tests/DocumentTest.php
index 2cd0916..020a70a 100644
--- a/tests/DocumentTest.php
+++ b/tests/DocumentTest.php
@@ -2,11 +2,12 @@
namespace alsvanzelf\jsonapiTests;
+use alsvanzelf\jsonapi\exceptions\DuplicateException;
use alsvanzelf\jsonapi\exceptions\Exception;
use alsvanzelf\jsonapi\exceptions\InputException;
use alsvanzelf\jsonapi\objects\LinkObject;
-use alsvanzelf\jsonapi\objects\ProfileLinkObject;
use alsvanzelf\jsonapiTests\TestableNonAbstractDocument as Document;
+use alsvanzelf\jsonapiTests\extensions\TestExtension;
use alsvanzelf\jsonapiTests\profiles\TestProfile;
use PHPUnit\Framework\TestCase;
@@ -98,6 +99,58 @@ public function testAddLink_BlocksUnknownLevel() {
$document->addLink('foo', 'https://jsonapi.org', $meta=[], $level='foo');
}
+ public function testSetSelfLink_HappyPath() {
+ $document = new Document();
+
+ $array = $document->toArray();
+ $this->assertArrayNotHasKey('links', $array);
+
+ $document->setSelfLink('https://jsonapi.org/foo');
+
+ $array = $document->toArray();
+ $this->assertArrayHasKey('links', $array);
+ $this->assertCount(1, $array['links']);
+ $this->assertArrayHasKey('self', $array['links']);
+ $this->assertSame('https://jsonapi.org/foo', $array['links']['self']);
+ }
+
+ public function testSetDescribedByLink_HappyPath() {
+ $document = new Document();
+ $document->setDescribedByLink('https://jsonapi.org/format', ['version' => '1.1']);
+
+ $array = $document->toArray();
+
+ $this->assertCount(1, $array['links']);
+ if (method_exists($this, 'assertIsArray')) {
+ $this->assertIsArray($array['links']['describedby']);
+ }
+ else {
+ $this->assertInternalType('array', $array['links']['describedby']);
+ }
+ $this->assertCount(2, $array['links']['describedby']);
+ $this->assertArrayHasKey('href', $array['links']['describedby']);
+ $this->assertArrayHasKey('meta', $array['links']['describedby']);
+ $this->assertSame('https://jsonapi.org/format', $array['links']['describedby']['href']);
+ $this->assertCount(1, $array['links']['describedby']['meta']);
+ $this->assertArrayHasKey('version', $array['links']['describedby']['meta']);
+ $this->assertSame('1.1', $array['links']['describedby']['meta']['version']);
+ }
+
+ public function testSetDescribedByLink_WithMeta() {
+ $document = new Document();
+
+ $array = $document->toArray();
+ $this->assertArrayNotHasKey('links', $array);
+
+ $document->setDescribedByLink('https://jsonapi.org/format');
+
+ $array = $document->toArray();
+ $this->assertArrayHasKey('links', $array);
+ $this->assertCount(1, $array['links']);
+ $this->assertArrayHasKey('describedby', $array['links']);
+ $this->assertSame('https://jsonapi.org/format', $array['links']['describedby']);
+ }
+
public function testAddMeta_HappyPath() {
$document = new Document();
@@ -178,48 +231,107 @@ public function testAddLinkObject_HappyPath() {
$this->assertSame('https://jsonapi.org', $array['links']['foo']['href']);
}
- public function testApplyProfile_HappyPath() {
- $profile = new TestProfile();
- $profile->setAliasedLink('https://jsonapi.org');
+ /**
+ * @group Extensions
+ */
+ public function testApplyExtension_HappyPath() {
+ $extension = new TestExtension();
+ $extension->setNamespace('test');
+ $extension->setOfficialLink('https://jsonapi.org/extension');
$document = new Document();
- $document->applyProfile($profile);
+ $document->applyExtension($extension);
+ $document->addExtensionMember($extension, 'foo', 'bar');
+ $document->setSelfLink('https://jsonapi.org/foo');
$array = $document->toArray();
+ $this->assertCount(3, $array);
+ $this->assertArrayHasKey('jsonapi', $array);
+ $this->assertCount(2, $array['jsonapi']);
+ $this->assertSame('1.1', $array['jsonapi']['version']);
+ $this->assertArrayHasKey('ext', $array['jsonapi']);
+ $this->assertCount(1, $array['jsonapi']['ext']);
+ $this->assertArrayHasKey(0, $array['jsonapi']['ext']);
+ $this->assertSame('https://jsonapi.org/extension', $array['jsonapi']['ext'][0]);
+ $this->assertArrayHasKey('test:foo', $array);
+ $this->assertSame('bar', $array['test:foo']);
$this->assertArrayHasKey('links', $array);
$this->assertCount(1, $array['links']);
- $this->assertArrayHasKey('profile', $array['links']);
- $this->assertCount(1, $array['links']['profile']);
- $this->assertArrayHasKey(0, $array['links']['profile']);
- $this->assertSame('https://jsonapi.org', $array['links']['profile'][0]);
+ $this->assertArrayHasKey('self', $array['links']);
+ $this->assertCount(2, $array['links']['self']);
+ $this->assertArrayHasKey('href', $array['links']['self']);
+ $this->assertArrayHasKey('type', $array['links']['self']);
+ $this->assertSame('https://jsonapi.org/foo', $array['links']['self']['href']);
+ $this->assertSame('application/vnd.api+json; ext="https://jsonapi.org/extension"', $array['links']['self']['type']);
+ }
+
+ /**
+ * @group Extensions
+ */
+ public function testApplyExtension_InvalidNamespace() {
+ $document = new Document();
+ $extension = new TestExtension();
+ $extension->setNamespace('foo-bar');
+
+ $this->expectException(Exception::class);
+ $this->expectExceptionMessage('invalid namespace "foo-bar"');
+
+ $document->applyExtension($extension);
}
- public function testApplyProfile_WithLinkObject() {
+ /**
+ * @group Extensions
+ */
+ public function testApplyExtension_ConflictingNamespace() {
+ $document = new Document();
+
+ $extension1 = new TestExtension();
+ $extension1->setNamespace('foo');
+ $document->applyExtension($extension1);
+
+ $extension2 = new TestExtension();
+ $extension2->setNamespace('bar');
+ $document->applyExtension($extension2);
+
+ $extension3 = new TestExtension();
+ $extension3->setNamespace('foo');
+
+ $this->expectException(DuplicateException::class);
+ $this->expectExceptionMessage('an extension with namespace "foo" is already applied');
+
+ $document->applyExtension($extension3);
+ }
+
+ /**
+ * @group Profiles
+ */
+ public function testApplyProfile_HappyPath() {
$profile = new TestProfile();
- $profile->setAliasedLink(new ProfileLinkObject('https://jsonapi.org', $aliases=['foo' => 'bar'], $meta=['baz' => 'baf']));
+ $profile->setOfficialLink('https://jsonapi.org/profile');
$document = new Document();
$document->applyProfile($profile);
+ $document->setSelfLink('https://jsonapi.org/foo');
$array = $document->toArray();
+ $this->assertCount(2, $array);
+ $this->assertArrayHasKey('jsonapi', $array);
+ $this->assertCount(2, $array['jsonapi']);
+ $this->assertSame('1.1', $array['jsonapi']['version']);
+ $this->assertArrayHasKey('profile', $array['jsonapi']);
+ $this->assertCount(1, $array['jsonapi']['profile']);
+ $this->assertArrayHasKey(0, $array['jsonapi']['profile']);
+ $this->assertSame('https://jsonapi.org/profile', $array['jsonapi']['profile'][0]);
$this->assertArrayHasKey('links', $array);
$this->assertCount(1, $array['links']);
- $this->assertArrayHasKey('profile', $array['links']);
- $this->assertCount(1, $array['links']['profile']);
- $this->assertArrayHasKey(0, $array['links']['profile']);
- $this->assertCount(3, $array['links']['profile'][0]);
- $this->assertArrayHasKey('href', $array['links']['profile'][0]);
- $this->assertArrayHasKey('aliases', $array['links']['profile'][0]);
- $this->assertArrayHasKey('meta', $array['links']['profile'][0]);
- $this->assertSame('https://jsonapi.org', $array['links']['profile'][0]['href']);
- $this->assertCount(1, $array['links']['profile'][0]['aliases']);
- $this->assertArrayHasKey('foo', $array['links']['profile'][0]['aliases']);
- $this->assertSame('bar', $array['links']['profile'][0]['aliases']['foo']);
- $this->assertCount(1, $array['links']['profile'][0]['meta']);
- $this->assertArrayHasKey('baz', $array['links']['profile'][0]['meta']);
- $this->assertSame('baf', $array['links']['profile'][0]['meta']['baz']);
+ $this->assertArrayHasKey('self', $array['links']);
+ $this->assertCount(2, $array['links']['self']);
+ $this->assertArrayHasKey('href', $array['links']['self']);
+ $this->assertArrayHasKey('type', $array['links']['self']);
+ $this->assertSame('https://jsonapi.org/foo', $array['links']['self']['href']);
+ $this->assertSame('application/vnd.api+json; profile="https://jsonapi.org/profile"', $array['links']['self']['type']);
}
public function testToJson_HappyPath() {
diff --git a/tests/ResourceDocumentTest.php b/tests/ResourceDocumentTest.php
index 8c352f2..36bcf7e 100644
--- a/tests/ResourceDocumentTest.php
+++ b/tests/ResourceDocumentTest.php
@@ -132,6 +132,19 @@ public function testAddMeta_RecreateJsonapiObject() {
$this->assertSame('jsonapi', $array['jsonapi']['meta']['baz']);
}
+ public function testSetLocalId_HappyPath() {
+ $document = new ResourceDocument();
+ $document->setType('user');
+ $document->setLocalId('42');
+
+ $array = $document->toArray();
+
+ $this->assertArrayHasKey('data', $array);
+ $this->assertArrayHasKey('lid', $array['data']);
+ $this->assertArrayNotHasKey('id', $array['data']);
+ $this->assertSame('42', $array['data']['lid']);
+ }
+
public function testAddRelationshipObject_WithIncluded() {
$resourceObject = new ResourceObject('user', 42);
$resourceObject->add('foo', 'bar');
diff --git a/tests/SeparateProcessTest.php b/tests/SeparateProcessTest.php
index 0582826..ec00ccd 100644
--- a/tests/SeparateProcessTest.php
+++ b/tests/SeparateProcessTest.php
@@ -2,8 +2,8 @@
namespace alsvanzelf\jsonapiTests;
-use alsvanzelf\jsonapi\objects\ProfileLinkObject;
use alsvanzelf\jsonapiTests\TestableNonAbstractDocument as Document;
+use alsvanzelf\jsonapiTests\extensions\TestExtension;
use alsvanzelf\jsonapiTests\profiles\TestProfile;
use PHPUnit\Framework\TestCase;
@@ -75,6 +75,39 @@ public function testSendResponse_ContentTypeHeader() {
/**
* @runInSeparateProcess
+ * @group Extensions
+ */
+ public function testSendResponse_ContentTypeHeaderWithExtensions() {
+ if (extension_loaded('xdebug') === false) {
+ $this->markTestSkipped('can not run without xdebug');
+ }
+
+ $extension = new TestExtension();
+ $extension->setNamespace('one');
+ $extension->setOfficialLink('https://jsonapi.org');
+
+ $document = new Document();
+ $document->applyExtension($extension);
+
+ ob_start();
+ $document->sendResponse();
+ ob_end_clean();
+ $this->assertSame(['Content-Type: '.Document::CONTENT_TYPE_OFFICIAL.'; ext="https://jsonapi.org"'], xdebug_get_headers());
+
+ $extension = new TestExtension();
+ $extension->setNamespace('two');
+ $extension->setOfficialLink('https://jsonapi.org/2');
+ $document->applyExtension($extension);
+
+ ob_start();
+ $document->sendResponse();
+ ob_end_clean();
+ $this->assertSame(['Content-Type: '.Document::CONTENT_TYPE_OFFICIAL.'; ext="https://jsonapi.org https://jsonapi.org/2"'], xdebug_get_headers());
+ }
+
+ /**
+ * @runInSeparateProcess
+ * @group Profiles
*/
public function testSendResponse_ContentTypeHeaderWithProfiles() {
if (extension_loaded('xdebug') === false) {
@@ -82,7 +115,7 @@ public function testSendResponse_ContentTypeHeaderWithProfiles() {
}
$profile = new TestProfile();
- $profile->setAliasedLink('https://jsonapi.org');
+ $profile->setOfficialLink('https://jsonapi.org');
$document = new Document();
$document->applyProfile($profile);
@@ -90,16 +123,16 @@ public function testSendResponse_ContentTypeHeaderWithProfiles() {
ob_start();
$document->sendResponse();
ob_end_clean();
- $this->assertSame(['Content-Type: '.Document::CONTENT_TYPE_OFFICIAL.';profile="https://jsonapi.org", '.Document::CONTENT_TYPE_OFFICIAL], xdebug_get_headers());
+ $this->assertSame(['Content-Type: '.Document::CONTENT_TYPE_OFFICIAL.'; profile="https://jsonapi.org"'], xdebug_get_headers());
$profile = new TestProfile();
- $profile->setAliasedLink('https://jsonapi.org/2');
+ $profile->setOfficialLink('https://jsonapi.org/2');
$document->applyProfile($profile);
ob_start();
$document->sendResponse();
ob_end_clean();
- $this->assertSame(['Content-Type: '.Document::CONTENT_TYPE_OFFICIAL.';profile="https://jsonapi.org https://jsonapi.org/2", '.Document::CONTENT_TYPE_OFFICIAL], xdebug_get_headers());
+ $this->assertSame(['Content-Type: '.Document::CONTENT_TYPE_OFFICIAL.'; profile="https://jsonapi.org https://jsonapi.org/2"'], xdebug_get_headers());
}
/**
diff --git a/tests/example_output/ExampleEverywhereExtension.php b/tests/example_output/ExampleEverywhereExtension.php
new file mode 100644
index 0000000..885e26b
--- /dev/null
+++ b/tests/example_output/ExampleEverywhereExtension.php
@@ -0,0 +1,15 @@
+format(\DateTime::ISO8601);
+ }
+ if ($updated !== null) {
+ $timestamps['updated'] = $updated->format(\DateTime::ISO8601);
+ }
+
+ $resource->add('timestamps', $timestamps);
+ }
+}
diff --git a/tests/example_output/ExampleVersionExtension.php b/tests/example_output/ExampleVersionExtension.php
new file mode 100644
index 0000000..bde454a
--- /dev/null
+++ b/tests/example_output/ExampleVersionExtension.php
@@ -0,0 +1,26 @@
+getResource()->addExtensionMember($this, 'id', $version);
+ }
+ else {
+ $resource->addExtensionMember($this, 'id', $version);
+ }
+ }
+}
diff --git a/tests/example_output/ExampleVersionProfile.php b/tests/example_output/ExampleVersionProfile.php
deleted file mode 100644
index 824a2e6..0000000
--- a/tests/example_output/ExampleVersionProfile.php
+++ /dev/null
@@ -1,36 +0,0 @@
-addMeta($this->getKeyword('version'), $version, $level=Document::LEVEL_RESOURCE);
- }
- else {
- $resource->addMeta($this->getKeyword('version'), $version);
- }
- }
-}
diff --git a/tests/example_output/at_members_everywhere/at_members_everywhere.json b/tests/example_output/at_members_everywhere/at_members_everywhere.json
index feb9aea..76638c7 100644
--- a/tests/example_output/at_members_everywhere/at_members_everywhere.json
+++ b/tests/example_output/at_members_everywhere/at_members_everywhere.json
@@ -21,9 +21,9 @@
"@context": "/meta/@context"
},
"data": {
- "@context": "/data/@context",
"type": "user",
"id": "42",
+ "@context": "/data/@context",
"meta": {
"@context": "/data/meta/@context"
},
@@ -48,9 +48,9 @@
"bar": {
"@context": "/data/relationships/bar/@context",
"data": {
- "@context": "/data/relationships/bar/data/@context",
"type": "user",
"id": "2",
+ "@context": "/data/relationships/bar/data/@context",
"meta": {
"@context": "/data/relationships/bar/data/meta/@context"
}
@@ -70,17 +70,17 @@
},
"included": [
{
- "@context": "/included/0/@context",
"type": "user",
"id": "1",
+ "@context": "/included/0/@context",
"attributes": {
"@context": "/included/0/attributes/@context"
}
},
{
- "@context": "/included/1/@context",
"type": "user",
"id": "3",
+ "@context": "/included/1/@context",
"relationships": {
"@context": "/included/1/relationships/@context"
}
diff --git a/tests/example_output/cursor_pagination_profile/cursor_pagination_profile.json b/tests/example_output/cursor_pagination_profile/cursor_pagination_profile.json
index cb84fda..02b81ad 100644
--- a/tests/example_output/cursor_pagination_profile/cursor_pagination_profile.json
+++ b/tests/example_output/cursor_pagination_profile/cursor_pagination_profile.json
@@ -1,11 +1,11 @@
{
"jsonapi": {
- "version": "1.1"
- },
- "links": {
+ "version": "1.1",
"profile": [
"https://jsonapi.org/profiles/ethanresnick/cursor-pagination/"
- ],
+ ]
+ },
+ "links": {
"prev": null,
"next": {
"href": "/users?sort=42&page[size]=10&page[after]=zaphod"
diff --git a/tests/example_output/errors_all_options/errors_all_options.json b/tests/example_output/errors_all_options/errors_all_options.json
index b0cd4a3..11c8e33 100644
--- a/tests/example_output/errors_all_options/errors_all_options.json
+++ b/tests/example_output/errors_all_options/errors_all_options.json
@@ -17,9 +17,7 @@
"detail": "Please, choose a bit less. Consult your ...",
"links": {
"about": "https://www.example.com/explanation.html",
- "type": [
- "https://www.example.com/documentation.html"
- ]
+ "type": "https://www.example.com/documentation.html"
}
},
{
@@ -35,14 +33,12 @@
"foo": "bar"
}
},
- "type": [
- {
- "href": "https://www.example.com/documentation.html",
- "meta": {
- "foo": "bar"
- }
+ "type": {
+ "href": "https://www.example.com/documentation.html",
+ "meta": {
+ "foo": "bar"
}
- ]
+ }
},
"source": {
"pointer": "/data/attributes/title",
diff --git a/tests/example_output/errors_all_options/errors_all_options.php b/tests/example_output/errors_all_options/errors_all_options.php
index 7ca6e96..f0a4076 100644
--- a/tests/example_output/errors_all_options/errors_all_options.php
+++ b/tests/example_output/errors_all_options/errors_all_options.php
@@ -19,7 +19,7 @@ public static function createJsonapiDocument() {
$errorSpecApi->setHumanTitle($genericTitle='Too much options');
$errorSpecApi->setHumanDetails($specificDetails='Please, choose a bit less. Consult your ...');
$errorSpecApi->setAboutLink($specificAboutLink='https://www.example.com/explanation.html', ['foo'=>'bar']);
- $errorSpecApi->appendTypeLink($genericTypeLink='https://www.example.com/documentation.html', ['foo'=>'bar']);
+ $errorSpecApi->setTypeLink($genericTypeLink='https://www.example.com/documentation.html', ['foo'=>'bar']);
$metaObject = new \stdClass();
$metaObject->property = 'value';
diff --git a/tests/example_output/example_profile/example_profile.json b/tests/example_output/example_profile/example_profile.json
deleted file mode 100644
index e7ee9ff..0000000
--- a/tests/example_output/example_profile/example_profile.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "jsonapi": {
- "version": "1.1"
- },
- "links": {
- "profile": [
- {
- "href": "https://jsonapi.org/format/1.1/#profile-keywords-and-aliases",
- "aliases": {
- "version": "ref"
- }
- }
- ]
- },
- "data": {
- "type": "user",
- "id": "42",
- "meta": {
- "ref": "2019"
- }
- }
-}
diff --git a/tests/example_output/example_profile/example_profile.php b/tests/example_output/example_profile/example_profile.php
deleted file mode 100644
index c881f25..0000000
--- a/tests/example_output/example_profile/example_profile.php
+++ /dev/null
@@ -1,19 +0,0 @@
- 'ref']);
-
- $document = new ResourceDocument('user', 42);
- $document->applyProfile($profile);
-
- $profile->setVersion($document, '2019');
-
- return $document;
- }
-}
diff --git a/tests/example_output/extension/extension.json b/tests/example_output/extension/extension.json
new file mode 100644
index 0000000..4b857c0
--- /dev/null
+++ b/tests/example_output/extension/extension.json
@@ -0,0 +1,13 @@
+{
+ "jsonapi": {
+ "version": "1.1",
+ "ext": [
+ "https://jsonapi.org/format/1.1/#extension-rules"
+ ]
+ },
+ "data": {
+ "type": "user",
+ "id": "42",
+ "version:id": "2019"
+ }
+}
diff --git a/tests/example_output/extension/extension.php b/tests/example_output/extension/extension.php
new file mode 100644
index 0000000..4535c63
--- /dev/null
+++ b/tests/example_output/extension/extension.php
@@ -0,0 +1,19 @@
+applyExtension($extension);
+
+ $extension->setVersion($document, '2019');
+
+ return $document;
+ }
+}
diff --git a/tests/example_output/extension_members_everywhere/extension_members_everywhere.json b/tests/example_output/extension_members_everywhere/extension_members_everywhere.json
new file mode 100644
index 0000000..f41faa3
--- /dev/null
+++ b/tests/example_output/extension_members_everywhere/extension_members_everywhere.json
@@ -0,0 +1,89 @@
+{
+ "everywhere:key": "/key",
+ "jsonapi": {
+ "everywhere:key": "/jsonapi/key",
+ "version": "1.1",
+ "meta": {
+ "everywhere:key": "/jsonapi/meta/key"
+ }
+ },
+ "links": {
+ "everywhere:key": "/links/key",
+ "foo": {
+ "everywhere:key": "/links/foo/key",
+ "href": "https://jsonapi.org",
+ "meta": {
+ "everywhere:key": "/links/foo/meta/key"
+ }
+ }
+ },
+ "meta": {
+ "everywhere:key": "/meta/key"
+ },
+ "data": {
+ "type": "user",
+ "id": "42",
+ "everywhere:key": "/data/key",
+ "meta": {
+ "everywhere:key": "/data/meta/key"
+ },
+ "attributes": {
+ "everywhere:key": "/data/attributes/key"
+ },
+ "relationships": {
+ "everywhere:key": "/data/relationships/key",
+ "foo": {
+ "everywhere:key": "/data/relationships/foo/key",
+ "links": {
+ "everywhere:key": "/data/relationships/foo/links/key"
+ },
+ "data": {
+ "type": "user",
+ "id": "1"
+ },
+ "meta": {
+ "everywhere:key": "/data/relationships/foo/meta/key"
+ }
+ },
+ "bar": {
+ "everywhere:key": "/data/relationships/bar/key",
+ "data": {
+ "type": "user",
+ "id": "2",
+ "everywhere:key": "/data/relationships/bar/data/key",
+ "meta": {
+ "everywhere:key": "/data/relationships/bar/data/meta/key"
+ }
+ }
+ }
+ },
+ "links": {
+ "everywhere:key": "/data/links/key",
+ "foo": {
+ "everywhere:key": "/data/links/foo/key",
+ "href": "https://jsonapi.org",
+ "meta": {
+ "everywhere:key": "/data/links/foo/meta/key"
+ }
+ }
+ }
+ },
+ "included": [
+ {
+ "type": "user",
+ "id": "1",
+ "everywhere:key": "/included/0/key",
+ "attributes": {
+ "everywhere:key": "/included/0/attributes/key"
+ }
+ },
+ {
+ "type": "user",
+ "id": "3",
+ "everywhere:key": "/included/1/key",
+ "relationships": {
+ "everywhere:key": "/included/1/relationships/key"
+ }
+ }
+ ]
+}
diff --git a/tests/example_output/extension_members_everywhere/extension_members_everywhere.php b/tests/example_output/extension_members_everywhere/extension_members_everywhere.php
new file mode 100644
index 0000000..f7747f2
--- /dev/null
+++ b/tests/example_output/extension_members_everywhere/extension_members_everywhere.php
@@ -0,0 +1,173 @@
+applyExtension($extension);
+
+ /**
+ * root
+ */
+
+ $document->addExtensionMember($extension, 'key', '/key');
+
+ /**
+ * jsonapi
+ */
+
+ $metaObject = new MetaObject();
+ $metaObject->addExtensionMember($extension, 'key', '/jsonapi/meta/key');
+
+ $jsonapiObject = new JsonapiObject();
+ $jsonapiObject->addExtensionMember($extension, 'key', '/jsonapi/key');
+ $jsonapiObject->setMetaObject($metaObject);
+ $document->setJsonapiObject($jsonapiObject);
+
+ /**
+ * links
+ */
+
+ $metaObject = new MetaObject();
+ $metaObject->addExtensionMember($extension, 'key', '/links/foo/meta/key');
+
+ $linkObject = new LinkObject('https://jsonapi.org');
+ $linkObject->addExtensionMember($extension, 'key', '/links/foo/key');
+ $linkObject->setMetaObject($metaObject);
+
+ $linksObject = new LinksObject();
+ $linksObject->addExtensionMember($extension, 'key', '/links/key');
+ $linksObject->addLinkObject('foo', $linkObject);
+ $document->setLinksObject($linksObject);
+
+ /**
+ * meta
+ */
+
+ $metaObject = new MetaObject();
+ $metaObject->addExtensionMember($extension, 'key', '/meta/key');
+ $document->setMetaObject($metaObject);
+
+ /**
+ * resource
+ */
+
+ /**
+ * resource - relationships
+ *
+ * @todo make it work to have extension members in both the identifier and the resource parts
+ * e.g. it is missing in the data of the first relationship (`data.relationships.foo.data.key`)
+ * whereas it does appear in the second relationship (`data.relationships.bar.data.key`)
+ * @see https://github.com/json-api/json-api/issues/1367
+ */
+
+ $relationshipsObject = new RelationshipsObject();
+ $relationshipsObject->addExtensionMember($extension, 'key', '/data/relationships/key');
+
+ $attributesObject = new AttributesObject();
+ $attributesObject->addExtensionMember($extension, 'key', '/included/0/attributes/key');
+
+ $resourceObject = new ResourceObject('user', 1);
+ $resourceObject->addExtensionMember($extension, 'key', '/included/0/key');
+ $resourceObject->setAttributesObject($attributesObject);
+
+ $linksObject = new LinksObject();
+ $linksObject->addExtensionMember($extension, 'key', '/data/relationships/foo/links/key');
+
+ $metaObject = new MetaObject();
+ $metaObject->addExtensionMember($extension, 'key', '/data/relationships/foo/meta/key');
+
+ $relationshipObject = new RelationshipObject(RelationshipObject::TO_ONE);
+ $relationshipObject->addExtensionMember($extension, 'key', '/data/relationships/foo/key');
+ $relationshipObject->setResource($resourceObject);
+ $relationshipObject->setLinksObject($linksObject);
+ $relationshipObject->setMetaObject($metaObject);
+
+ $relationshipsObject->addRelationshipObject('foo', $relationshipObject);
+
+ $metaObject = new MetaObject();
+ $metaObject->addExtensionMember($extension, 'key', '/data/relationships/bar/data/meta/key');
+
+ $resourceIdentifierObject = new ResourceIdentifierObject('user', 2);
+ $resourceIdentifierObject->addExtensionMember($extension, 'key', '/data/relationships/bar/data/key');
+ $resourceIdentifierObject->setMetaObject($metaObject);
+
+ $relationshipObject = new RelationshipObject(RelationshipObject::TO_ONE);
+ $relationshipObject->addExtensionMember($extension, 'key', '/data/relationships/bar/key');
+ $relationshipObject->setResource($resourceIdentifierObject);
+
+ $relationshipsObject->addRelationshipObject('bar', $relationshipObject);
+
+ /**
+ * resource - attributes
+ */
+
+ $attributesObject = new AttributesObject();
+ $attributesObject->addExtensionMember($extension, 'key', '/data/attributes/key');
+
+ /**
+ * resource - links
+ */
+
+ $metaObject = new MetaObject();
+ $metaObject->addExtensionMember($extension, 'key', '/data/links/foo/meta/key');
+
+ $linkObject = new LinkObject('https://jsonapi.org');
+ $linkObject->addExtensionMember($extension, 'key', '/data/links/foo/key');
+ $linkObject->setMetaObject($metaObject);
+
+ $linksObject = new LinksObject();
+ $linksObject->addExtensionMember($extension, 'key', '/data/links/key');
+ $linksObject->addLinkObject('foo', $linkObject);
+
+ /**
+ * resource - meta
+ */
+
+ $metaObject = new MetaObject();
+ $metaObject->addExtensionMember($extension, 'key', '/data/meta/key');
+
+ /**
+ * resource - resource
+ */
+
+ $resourceObject = new ResourceObject('user', 42);
+ $resourceObject->addExtensionMember($extension, 'key', '/data/key');
+ $resourceObject->setAttributesObject($attributesObject);
+ $resourceObject->setLinksObject($linksObject);
+ $resourceObject->setMetaObject($metaObject);
+ $resourceObject->setRelationshipsObject($relationshipsObject);
+
+ $document->setPrimaryResource($resourceObject);
+
+ /**
+ * included
+ */
+
+ $relationshipsObject = new RelationshipsObject();
+ $relationshipsObject->addExtensionMember($extension, 'key', '/included/1/relationships/key');
+
+ $resourceObject = new ResourceObject('user', 3);
+ $resourceObject->addExtensionMember($extension, 'key', '/included/1/key');
+ $resourceObject->setRelationshipsObject($relationshipsObject);
+
+ $document->addIncludedResourceObject($resourceObject);
+
+ return $document;
+ }
+}
diff --git a/tests/example_output/profile/profile.json b/tests/example_output/profile/profile.json
new file mode 100644
index 0000000..49d5808
--- /dev/null
+++ b/tests/example_output/profile/profile.json
@@ -0,0 +1,18 @@
+{
+ "jsonapi": {
+ "version": "1.1",
+ "profile": [
+ "https://jsonapi.org/recommendations/#authoring-profiles"
+ ]
+ },
+ "data": {
+ "type": "user",
+ "id": "42",
+ "attributes": {
+ "timestamps": {
+ "created": "2019-01-01T00:00:00+0000",
+ "updated": "2021-01-01T00:00:00+0000"
+ }
+ }
+ }
+}
diff --git a/tests/example_output/profile/profile.php b/tests/example_output/profile/profile.php
new file mode 100644
index 0000000..16deba8
--- /dev/null
+++ b/tests/example_output/profile/profile.php
@@ -0,0 +1,19 @@
+applyProfile($profile);
+
+ $profile->setTimestamps($document, new \DateTime('2019-01-01T00:00:00+0000'), new \DateTime('2021-01-01T00:00:00+0000'));
+
+ return $document;
+ }
+}
diff --git a/tests/extensions/AtomicOperationsDocumentTest.php b/tests/extensions/AtomicOperationsDocumentTest.php
new file mode 100644
index 0000000..e1bac4f
--- /dev/null
+++ b/tests/extensions/AtomicOperationsDocumentTest.php
@@ -0,0 +1,59 @@
+add('name', 'Ford');
+ $resource2->add('name', 'Arthur');
+ $resource3->add('name', 'Zaphod');
+ $document->addResults($resource1, $resource2, $resource3);
+ $document->setSelfLink('https://example.org/operations');
+
+ $array = $document->toArray();
+
+ $this->assertArrayHasKey('jsonapi', $array);
+ $this->assertArrayHasKey('ext', $array['jsonapi']);
+ $this->assertCount(1, $array['jsonapi']['ext']);
+ $this->assertSame((new AtomicOperationsExtension())->getOfficialLink(), $array['jsonapi']['ext'][0]);
+
+ $this->assertArrayHasKey('links', $array);
+ $this->assertArrayHasKey('self', $array['links']);
+ $this->assertArrayHasKey('href', $array['links']['self']);
+ $this->assertArrayHasKey('type', $array['links']['self']);
+ $this->assertSame('https://example.org/operations', $array['links']['self']['href']);
+ $this->assertSame('application/vnd.api+json; ext="'.(new AtomicOperationsExtension())->getOfficialLink().'"', $array['links']['self']['type']);
+
+ $this->assertArrayHasKey('atomic:results', $array);
+ $this->assertCount(3, $array['atomic:results']);
+ $this->assertSame(['data' => $resource1->toArray()], $array['atomic:results'][0]);
+ $this->assertSame(['data' => $resource2->toArray()], $array['atomic:results'][1]);
+ $this->assertSame(['data' => $resource3->toArray()], $array['atomic:results'][2]);
+ }
+
+ public function testSetResults_EmptySuccessResults() {
+ $document = new AtomicOperationsDocument();
+ $array = $document->toArray();
+
+ $this->assertArrayHasKey('jsonapi', $array);
+ $this->assertArrayHasKey('ext', $array['jsonapi']);
+ $this->assertCount(1, $array['jsonapi']['ext']);
+ $this->assertSame((new AtomicOperationsExtension())->getOfficialLink(), $array['jsonapi']['ext'][0]);
+
+ $this->assertArrayHasKey('atomic:results', $array);
+ $this->assertCount(0, $array['atomic:results']);
+ }
+}
diff --git a/tests/extensions/TestExtension.php b/tests/extensions/TestExtension.php
new file mode 100644
index 0000000..9b57f9e
--- /dev/null
+++ b/tests/extensions/TestExtension.php
@@ -0,0 +1,26 @@
+namespace = $namespace;
+ }
+
+ public function setOfficialLink($officialLink) {
+ $this->officialLink = $officialLink;
+ }
+
+ public function getNamespace() {
+ return $this->namespace;
+ }
+
+ public function getOfficialLink() {
+ return $this->officialLink;
+ }
+}
diff --git a/tests/helpers/ExtensionMemberManagerTest.php b/tests/helpers/ExtensionMemberManagerTest.php
new file mode 100644
index 0000000..745cd64
--- /dev/null
+++ b/tests/helpers/ExtensionMemberManagerTest.php
@@ -0,0 +1,70 @@
+setNamespace('test');
+
+ $this->assertFalse($helper->hasExtensionMembers());
+ $this->assertSame([], $helper->getExtensionMembers());
+
+ $helper->addExtensionMember($extension, 'foo', 'bar');
+
+ $array = $helper->getExtensionMembers();
+
+ $this->assertTrue($helper->hasExtensionMembers());
+ $this->assertCount(1, $array);
+ $this->assertArrayHasKey('test:foo', $array);
+ $this->assertSame('bar', $array['test:foo']);
+ }
+
+ public function testAddExtensionMember_WithNamespacePrefixed() {
+ $helper = new ExtensionMemberManager();
+ $extension = new TestExtension();
+ $extension->setNamespace('test');
+
+ $helper->addExtensionMember($extension, 'test:foo', 'bar');
+
+ $array = $helper->getExtensionMembers();
+
+ $this->assertArrayHasKey('test:foo', $array);
+ }
+
+ public function testAddExtensionMember_WithObjectValue() {
+ $helper = new ExtensionMemberManager();
+ $extension = new TestExtension();
+ $extension->setNamespace('test');
+
+ $object = new \stdClass();
+ $object->bar = 'baz';
+
+ $helper->addExtensionMember($extension, 'foo', $object);
+
+ $array = $helper->getExtensionMembers();
+
+ $this->assertArrayHasKey('test:foo', $array);
+ $this->assertArrayHasKey('bar', $array['test:foo']);
+ $this->assertSame('baz', $array['test:foo']['bar']);
+ }
+
+ public function testAddExtensionMember_InvalidNamespaceOrCharacter() {
+ $helper = new ExtensionMemberManager();
+ $extension = new TestExtension();
+ $extension->setNamespace('test');
+
+ $this->expectException(InputException::class);
+
+ $helper->addExtensionMember($extension, 'foo:bar', 'baz');
+ }
+}
diff --git a/tests/helpers/LinksManagerTest.php b/tests/helpers/LinksManagerTest.php
index 750ae69..be87fa2 100644
--- a/tests/helpers/LinksManagerTest.php
+++ b/tests/helpers/LinksManagerTest.php
@@ -82,6 +82,9 @@ public function testAddLinkObject_HappyPath() {
$this->assertSame('https://jsonapi.org', $array['foo']['href']);
}
+ /**
+ * @deprecated array links are not supported anymore
+ */
public function testAddLinksArray_HappyPath() {
$linksArray = new LinksArray();
$linksArray->add('https://jsonapi.org');
@@ -98,6 +101,9 @@ public function testAddLinksArray_HappyPath() {
$this->assertSame('https://jsonapi.org', $array['foo'][0]);
}
+ /**
+ * @deprecated array links are not supported anymore
+ */
public function testAppendLinkObject_HappyPath() {
$linksManager = new LinksManager();
$linksManager->addLinksArray('foo', LinksArray::fromArray(['https://jsonapi.org/1']));
diff --git a/tests/helpers/ProfileAliasManagerTest.php b/tests/helpers/ProfileAliasManagerTest.php
deleted file mode 100644
index fe086f3..0000000
--- a/tests/helpers/ProfileAliasManagerTest.php
+++ /dev/null
@@ -1,87 +0,0 @@
-assertSame([], $profileAliasManager->getAliasMapping());
- $this->assertSame(['foo' => 'foo', 'bar' => 'bar'], $profileAliasManager->getKeywordMapping());
- }
-
- public function testConstructor_WithAliases() {
- $profileAliasManager = new ProfileAliasManager(['bar' => 'baz']);
-
- $this->assertSame(['bar' => 'baz'], $profileAliasManager->getAliasMapping());
- $this->assertSame(['foo' => 'foo', 'bar' => 'baz'], $profileAliasManager->getKeywordMapping());
- }
-
- public function testConstructor_WithoutOfficialKeywords() {
- $profileAliasManager = new ProfileAliasManager_WithoutKeywords();
-
- $this->assertSame([], $profileAliasManager->getAliasMapping());
- $this->assertSame([], $profileAliasManager->getKeywordMapping());
- }
-
- public function testConstructor_NonAdjustedAliases() {
- $this->expectException(InputException::class);
-
- new ProfileAliasManager(['foo' => 'foo']);
- }
-
- public function testConstructor_NonExistingKeyword() {
- $this->expectException(InputException::class);
-
- new ProfileAliasManager(['baz' => 'bar']);
- }
-
- public function testGetKeyword_HappyPath() {
- $profileAliasManager = new ProfileAliasManager(['bar' => 'baz']);
-
- $this->assertSame('foo', $profileAliasManager->getKeyword('foo'));
- $this->assertSame('baz', $profileAliasManager->getKeyword('bar'));
- }
-
- public function testGetKeyword_NonExistingKeyword() {
- $profileAliasManager = new ProfileAliasManager();
-
- $this->expectException(InputException::class);
-
- $profileAliasManager->getKeyword('baz');
- }
-
- public function testGetAliasedLink_HappyPath() {
- $profileAliasManager = new ProfileAliasManager();
-
- if (method_exists($this, 'assertIsString')) {
- $this->assertIsString($profileAliasManager->getAliasedLink());
- }
- else {
- $this->assertInternalType('string', $profileAliasManager->getAliasedLink());
- }
- $this->assertSame('https://jsonapi.org', $profileAliasManager->getAliasedLink());
- }
-
- public function testGetAliasedLink_ObjectWithAliases() {
- $profileAliasManager = new ProfileAliasManager(['bar' => 'baz']);
-
- $this->assertInstanceOf(ProfileLinkObject::class, $profileAliasManager->getAliasedLink());
-
- $array = $profileAliasManager->getAliasedLink()->toArray();
-
- $this->assertArrayHasKey('href', $array);
- $this->assertSame('https://jsonapi.org', $array['href']);
-
- $this->assertArrayHasKey('aliases', $array);
- $this->assertCount(1, $array['aliases']);
- $this->assertArrayHasKey('bar', $array['aliases']);
- $this->assertSame('baz', $array['aliases']['bar']);
- }
-}
diff --git a/tests/helpers/RequestParserTest.php b/tests/helpers/RequestParserTest.php
index 6410c78..fd17061 100644
--- a/tests/helpers/RequestParserTest.php
+++ b/tests/helpers/RequestParserTest.php
@@ -336,6 +336,31 @@ public function testGetFilter() {
$this->assertSame(['foo' => 'bar'], $requestParser->getFilter());
}
+ public function testHasLocalId() {
+ $document = [
+ 'data' => [
+ 'id' => 'foo',
+ ],
+ ];
+ $requestParser = new RequestParser($selfLink='', $quaryParameters=[], $document);
+
+ $this->assertArrayHasKey('data', $requestParser->getDocument());
+ $this->assertArrayHasKey('id', $requestParser->getDocument()['data']);
+ $this->assertFalse($requestParser->hasLocalId());
+
+ $document = [
+ 'data' => [
+ 'lid' => 'foo',
+ ],
+ ];
+ $requestParser = new RequestParser($selfLink='', $quaryParameters=[], $document);
+
+ $this->assertArrayHasKey('data', $requestParser->getDocument());
+ $this->assertArrayNotHasKey('id', $requestParser->getDocument()['data']);
+ $this->assertTrue($requestParser->hasLocalId());
+ $this->assertSame('foo', $requestParser->getLocalId());
+ }
+
public function testHasAttribute() {
$requestParser = new RequestParser();
$this->assertFalse($requestParser->hasAttribute('foo'));
diff --git a/tests/helpers/TestableNonAbstractProfileAliasManager.php b/tests/helpers/TestableNonAbstractProfileAliasManager.php
deleted file mode 100644
index e73d7dd..0000000
--- a/tests/helpers/TestableNonAbstractProfileAliasManager.php
+++ /dev/null
@@ -1,39 +0,0 @@
-setAccessible(true);
-
- return $aliasMapping->getValue($this);
- }
-
- public function getKeywordMapping() {
- $keywordMapping = new \ReflectionProperty(ProfileAliasManager::class, 'keywordMapping');
- $keywordMapping->setAccessible(true);
-
- return $keywordMapping->getValue($this);
- }
-
- public function getOfficialKeywords() {
- return ['foo', 'bar'];
- }
-
- public function getOfficialLink() {
- return 'https://jsonapi.org';
- }
-}
-
-class TestableNonAbstractProfileAliasManager_WithoutKeywords extends TestableNonAbstractProfileAliasManager {
- public function getOfficialKeywords() {
- return [];
- }
-}
diff --git a/tests/helpers/TestableNonTraitExtensionMemberManager.php b/tests/helpers/TestableNonTraitExtensionMemberManager.php
new file mode 100644
index 0000000..516f103
--- /dev/null
+++ b/tests/helpers/TestableNonTraitExtensionMemberManager.php
@@ -0,0 +1,12 @@
+assertTrue($errorObject->isEmpty());
+
+ $errorObject->appendTypeLink('https://jsonapi.org');
+
+ $this->assertFalse($errorObject->isEmpty());
+
+ $array = $errorObject->toArray();
+
+ $this->assertArrayHasKey('links', $array);
+ $this->assertArrayHasKey('type', $array['links']);
+ $this->assertSame(['https://jsonapi.org'], $array['links']['type']);
+ }
+
public function testIsEmpty_All() {
$errorObject = new ErrorObject();
$this->assertTrue($errorObject->isEmpty());
@@ -152,6 +171,28 @@ public function testIsEmpty_All() {
$errorObject = new ErrorObject();
$errorObject->addAtMember('context', 'test');
$this->assertFalse($errorObject->isEmpty());
+
+ $errorObject = new ErrorObject();
+ $errorObject->addExtensionMember(new TestExtension(), 'foo', 'bar');
+ $this->assertFalse($errorObject->isEmpty());
+ }
+
+ /**
+ * @group Extensions
+ */
+ public function testToArray_WithExtensionMembers() {
+ $errorObject = new ErrorObject();
+ $extension = new TestExtension();
+ $extension->setNamespace('test');
+
+ $this->assertSame([], $errorObject->toArray());
+
+ $errorObject->addExtensionMember($extension, 'foo', 'bar');
+
+ $array = $errorObject->toArray();
+
+ $this->assertArrayHasKey('test:foo', $array);
+ $this->assertSame('bar', $array['test:foo']);
}
}
diff --git a/tests/objects/JsonapiObjectTest.php b/tests/objects/JsonapiObjectTest.php
index 0807a19..882c708 100644
--- a/tests/objects/JsonapiObjectTest.php
+++ b/tests/objects/JsonapiObjectTest.php
@@ -3,6 +3,8 @@
namespace alsvanzelf\jsonapiTests\objects;
use alsvanzelf\jsonapi\objects\JsonapiObject;
+use alsvanzelf\jsonapiTests\extensions\TestExtension;
+use alsvanzelf\jsonapiTests\profiles\TestProfile;
use PHPUnit\Framework\TestCase;
class JsonapiObjectTest extends TestCase {
@@ -31,4 +33,43 @@ public function testIsEmpty_WithAtMembers() {
$this->assertFalse($jsonapiObject->isEmpty());
}
+
+ /**
+ * @group Extensions
+ */
+ public function testIsEmpty_WithExtensionLink() {
+ $jsonapiObject = new JsonapiObject($version=null);
+
+ $this->assertTrue($jsonapiObject->isEmpty());
+
+ $jsonapiObject->addExtension(new TestExtension());
+
+ $this->assertFalse($jsonapiObject->isEmpty());
+ }
+
+ /**
+ * @group Profiles
+ */
+ public function testIsEmpty_WithProfileLink() {
+ $jsonapiObject = new JsonapiObject($version=null);
+
+ $this->assertTrue($jsonapiObject->isEmpty());
+
+ $jsonapiObject->addProfile(new TestProfile());
+
+ $this->assertFalse($jsonapiObject->isEmpty());
+ }
+
+ /**
+ * @group Extensions
+ */
+ public function testIsEmpty_WithExtensionMembers() {
+ $jsonapiObject = new JsonapiObject($version=null);
+
+ $this->assertTrue($jsonapiObject->isEmpty());
+
+ $jsonapiObject->addExtensionMember(new TestExtension(), 'foo', 'bar');
+
+ $this->assertFalse($jsonapiObject->isEmpty());
+ }
}
diff --git a/tests/objects/LinkObjectTest.php b/tests/objects/LinkObjectTest.php
index bdf7e89..fd356c9 100644
--- a/tests/objects/LinkObjectTest.php
+++ b/tests/objects/LinkObjectTest.php
@@ -3,9 +3,53 @@
namespace alsvanzelf\jsonapiTests\objects;
use alsvanzelf\jsonapi\objects\LinkObject;
+use alsvanzelf\jsonapiTests\extensions\TestExtension;
use PHPUnit\Framework\TestCase;
class LinkObjectTest extends TestCase {
+ public function testSetDescribedBy_HappyPath() {
+ $linkObject = new LinkObject();
+
+ $this->assertTrue($linkObject->isEmpty());
+
+ $linkObject->setDescribedBy('https://jsonapi.org');
+
+ $this->assertFalse($linkObject->isEmpty());
+
+ $array = $linkObject->toArray();
+
+ $this->assertArrayHasKey('describedby', $array);
+ $this->assertArrayHasKey('href', $array['describedby']);
+ $this->assertSame('https://jsonapi.org', $array['describedby']['href']);
+ }
+
+ public function testAddLanguage_HappyPath() {
+ $linkObject = new LinkObject();
+
+ $this->assertTrue($linkObject->isEmpty());
+
+ $linkObject->addLanguage('nl-NL');
+
+ $this->assertFalse($linkObject->isEmpty());
+
+ $array = $linkObject->toArray();
+
+ $this->assertArrayHasKey('hreflang', $array);
+ $this->assertSame('nl-NL', $array['hreflang']);
+ }
+
+ public function testAddLanguage_Multiple() {
+ $linkObject = new LinkObject();
+
+ $linkObject->addLanguage('nl-NL');
+ $array = $linkObject->toArray();
+ $this->assertSame('nl-NL', $array['hreflang']);
+
+ $linkObject->addLanguage('en-US');
+ $array = $linkObject->toArray();
+ $this->assertSame(['nl-NL', 'en-US'], $array['hreflang']);
+ }
+
public function testAddMeta_HappyPath() {
$linkObject = new LinkObject();
@@ -22,6 +66,83 @@ public function testAddMeta_HappyPath() {
$this->assertSame('bar', $array['meta']['foo']);
}
+ public function testSetRelationType_HappyPath() {
+ $linkObject = new LinkObject();
+
+ $this->assertTrue($linkObject->isEmpty());
+
+ $linkObject->setRelationType('external');
+
+ $this->assertFalse($linkObject->isEmpty());
+
+ $array = $linkObject->toArray();
+
+ $this->assertArrayHasKey('rel', $array);
+ $this->assertSame('external', $array['rel']);
+ }
+
+ public function testSetDescribedByLinkObject_HappyPath() {
+ $linkObject = new LinkObject();
+
+ $this->assertTrue($linkObject->isEmpty());
+
+ $describedBy = new LinkObject('https://jsonapi.org');
+ $linkObject->setDescribedByLinkObject($describedBy);
+
+ $this->assertFalse($linkObject->isEmpty());
+
+ $array = $linkObject->toArray();
+
+ $this->assertArrayHasKey('describedby', $array);
+ $this->assertArrayHasKey('href', $array['describedby']);
+ $this->assertSame('https://jsonapi.org', $array['describedby']['href']);
+ }
+
+ public function testSetHumanTitle_HappyPath() {
+ $linkObject = new LinkObject();
+
+ $this->assertTrue($linkObject->isEmpty());
+
+ $linkObject->setHumanTitle('A link');
+
+ $this->assertFalse($linkObject->isEmpty());
+
+ $array = $linkObject->toArray();
+
+ $this->assertArrayHasKey('title', $array);
+ $this->assertSame('A link', $array['title']);
+ }
+
+ public function testSetMediaType_HappyPath() {
+ $linkObject = new LinkObject();
+
+ $this->assertTrue($linkObject->isEmpty());
+
+ $linkObject->setMediaType('text/html');
+
+ $this->assertFalse($linkObject->isEmpty());
+
+ $array = $linkObject->toArray();
+
+ $this->assertArrayHasKey('type', $array);
+ $this->assertSame('text/html', $array['type']);
+ }
+
+ public function testSetHreflang_HappyPath() {
+ $linkObject = new LinkObject();
+
+ $this->assertTrue($linkObject->isEmpty());
+
+ $linkObject->setHreflang('nl-NL', 'en-US');
+
+ $this->assertFalse($linkObject->isEmpty());
+
+ $array = $linkObject->toArray();
+
+ $this->assertArrayHasKey('hreflang', $array);
+ $this->assertSame(['nl-NL', 'en-US'], $array['hreflang']);
+ }
+
public function testIsEmpty_WithAtMembers() {
$linkObject = new LinkObject();
@@ -31,4 +152,17 @@ public function testIsEmpty_WithAtMembers() {
$this->assertFalse($linkObject->isEmpty());
}
+
+ /**
+ * @group Extensions
+ */
+ public function testIsEmpty_WithExtensionMembers() {
+ $linkObject = new LinkObject();
+
+ $this->assertTrue($linkObject->isEmpty());
+
+ $linkObject->addExtensionMember(new TestExtension(), 'foo', 'bar');
+
+ $this->assertFalse($linkObject->isEmpty());
+ }
}
diff --git a/tests/objects/LinksArrayTest.php b/tests/objects/LinksArrayTest.php
index 5b9945c..f0d0c7d 100644
--- a/tests/objects/LinksArrayTest.php
+++ b/tests/objects/LinksArrayTest.php
@@ -5,6 +5,9 @@
use alsvanzelf\jsonapi\objects\LinksArray;
use PHPUnit\Framework\TestCase;
+/**
+ * @deprecated array links are not supported anymore
+ */
class LinksArrayTest extends TestCase {
public function testFromObject_HappyPath() {
$object = new \stdClass();
diff --git a/tests/objects/LinksObjectTest.php b/tests/objects/LinksObjectTest.php
index ac7b166..ac2995f 100644
--- a/tests/objects/LinksObjectTest.php
+++ b/tests/objects/LinksObjectTest.php
@@ -109,6 +109,9 @@ public function testAddLinkObject_ExistingKey() {
$linksObject->addLinkObject($key='foo', $linkObject);
}
+ /**
+ * @deprecated array links are not supported anymore
+ */
public function testAddLinksArray_HappyPath() {
$linksObject = new LinksObject();
$linksObject->addLinksArray('foo', LinksArray::fromArray(['https://jsonapi.org']));
@@ -122,6 +125,9 @@ public function testAddLinksArray_HappyPath() {
$this->assertSame('https://jsonapi.org', $array['foo'][0]);
}
+ /**
+ * @deprecated array links are not supported anymore
+ */
public function testAddLinksArray_BlocksReusingNonArray() {
$linksObject = new LinksObject();
$linksObject->add('foo', 'https://jsonapi.org');
diff --git a/tests/objects/MetaObjectTest.php b/tests/objects/MetaObjectTest.php
index 5014060..fc4cb6a 100644
--- a/tests/objects/MetaObjectTest.php
+++ b/tests/objects/MetaObjectTest.php
@@ -3,6 +3,7 @@
namespace alsvanzelf\jsonapiTests\objects;
use alsvanzelf\jsonapi\objects\MetaObject;
+use alsvanzelf\jsonapiTests\extensions\TestExtension;
use PHPUnit\Framework\TestCase;
class MetaObjectTest extends TestCase {
@@ -18,4 +19,27 @@ public function testFromObject_HappyPath() {
$this->assertArrayHasKey('foo', $array);
$this->assertSame('bar', $array['foo']);
}
+
+ public function testIsEmpty_WithAtMembers() {
+ $metaObject = new MetaObject();
+
+ $this->assertTrue($metaObject->isEmpty());
+
+ $metaObject->addAtMember('context', 'test');
+
+ $this->assertFalse($metaObject->isEmpty());
+ }
+
+ /**
+ * @group Extensions
+ */
+ public function testIsEmpty_WithExtensionMembers() {
+ $metaObject = new MetaObject();
+
+ $this->assertTrue($metaObject->isEmpty());
+
+ $metaObject->addExtensionMember(new TestExtension(), 'foo', 'bar');
+
+ $this->assertFalse($metaObject->isEmpty());
+ }
}
diff --git a/tests/objects/RelationshipObjectTest.php b/tests/objects/RelationshipObjectTest.php
index 346480a..e5a99fb 100644
--- a/tests/objects/RelationshipObjectTest.php
+++ b/tests/objects/RelationshipObjectTest.php
@@ -9,6 +9,7 @@
use alsvanzelf\jsonapi\objects\RelationshipObject;
use alsvanzelf\jsonapi\objects\ResourceIdentifierObject;
use alsvanzelf\jsonapi\objects\ResourceObject;
+use alsvanzelf\jsonapiTests\extensions\TestExtension;
use PHPUnit\Framework\TestCase;
class RelationshipObjectTest extends TestCase {
@@ -315,6 +316,19 @@ public function testIsEmpty_WithAtMembers() {
$this->assertFalse($relationshipObject->isEmpty());
}
+ /**
+ * @group Extensions
+ */
+ public function testIsEmpty_WithExtensionMembers() {
+ $relationshipObject = new RelationshipObject(RelationshipObject::TO_ONE);
+
+ $this->assertTrue($relationshipObject->isEmpty());
+
+ $relationshipObject->addExtensionMember(new TestExtension(), 'foo', 'bar');
+
+ $this->assertFalse($relationshipObject->isEmpty());
+ }
+
private function validateToOneRelationshipArray(array $array) {
$this->assertNotEmpty($array);
$this->assertArrayHasKey('data', $array);
diff --git a/tests/objects/ResourceIdentifierObjectTest.php b/tests/objects/ResourceIdentifierObjectTest.php
index 2508479..b7eb1ea 100644
--- a/tests/objects/ResourceIdentifierObjectTest.php
+++ b/tests/objects/ResourceIdentifierObjectTest.php
@@ -3,10 +3,56 @@
namespace alsvanzelf\jsonapiTests\objects;
use alsvanzelf\jsonapi\exceptions\Exception;
+use alsvanzelf\jsonapi\exceptions\DuplicateException;
use alsvanzelf\jsonapi\objects\ResourceIdentifierObject;
+use alsvanzelf\jsonapiTests\extensions\TestExtension;
use PHPUnit\Framework\TestCase;
class ResourceIdentifierObjectTest extends TestCase {
+ public function testSetId_HappyPath() {
+ $resourceIdentifierObject = new ResourceIdentifierObject();
+ $resourceIdentifierObject->setType('test');
+ $resourceIdentifierObject->setId('1');
+
+ $array = $resourceIdentifierObject->toArray();
+
+ $this->assertArrayHasKey('id', $array);
+ $this->assertArrayNotHasKey('lid', $array);
+ $this->assertSame('1', $array['id']);
+ }
+
+ public function testSetId_WithLocalIdAlreadySet() {
+ $resourceIdentifierObject = new ResourceIdentifierObject();
+ $resourceIdentifierObject->setType('test');
+ $resourceIdentifierObject->setLocalId('uuid-1');
+
+ $this->expectException(DuplicateException::class);
+
+ $resourceIdentifierObject->setId('1');
+ }
+
+ public function testSetLocalId_HappyPath() {
+ $resourceIdentifierObject = new ResourceIdentifierObject();
+ $resourceIdentifierObject->setType('test');
+ $resourceIdentifierObject->setLocalId('uuid-1');
+
+ $array = $resourceIdentifierObject->toArray();
+
+ $this->assertArrayHasKey('lid', $array);
+ $this->assertArrayNotHasKey('id', $array);
+ $this->assertSame('uuid-1', $array['lid']);
+ }
+
+ public function testSetLocalId_WithIdAlreadySet() {
+ $resourceIdentifierObject = new ResourceIdentifierObject();
+ $resourceIdentifierObject->setType('test');
+ $resourceIdentifierObject->setId('1');
+
+ $this->expectException(DuplicateException::class);
+
+ $resourceIdentifierObject->setLocalId('uuid-1');
+ }
+
public function testEquals_HappyPath() {
$one = new ResourceIdentifierObject('test', 1);
$two = new ResourceIdentifierObject('test', 2);
@@ -25,6 +71,19 @@ public function testEquals_WithoutIdentification() {
$one->equals($two);
}
+ public function testEquals_WithLocalId() {
+ $one = new ResourceIdentifierObject('test');
+ $two = new ResourceIdentifierObject('test');
+ $new = new ResourceIdentifierObject('test');
+
+ $one->setLocalId('uuid-1');
+ $two->setLocalId('uuid-2');
+ $new->setLocalId('uuid-1');
+
+ $this->assertFalse($one->equals($two));
+ $this->assertTrue($one->equals($new));
+ }
+
public function testGetIdentificationKey_HappyPath() {
$resourceIdentifierObject = new ResourceIdentifierObject('user', 42);
@@ -32,6 +91,7 @@ public function testGetIdentificationKey_HappyPath() {
$this->assertArrayHasKey('type', $array);
$this->assertArrayHasKey('id', $array);
+ $this->assertArrayNotHasKey('lid', $array);
$this->assertSame('user', $array['type']);
$this->assertSame('42', $array['id']);
$this->assertTrue($resourceIdentifierObject->hasIdentification());
@@ -59,6 +119,23 @@ public function testGetIdentificationKey_SetAfterwards() {
$this->assertSame('user|42', $resourceIdentifierObject->getIdentificationKey());
}
+ public function testGetIdentificationKey_WithLocalId() {
+ $resourceIdentifierObject = new ResourceIdentifierObject();
+
+ $resourceIdentifierObject->setType('user');
+ $resourceIdentifierObject->setLocalId('uuid-42');
+
+ $array = $resourceIdentifierObject->toArray();
+
+ $this->assertArrayHasKey('type', $array);
+ $this->assertArrayHasKey('lid', $array);
+ $this->assertArrayNotHasKey('id', $array);
+ $this->assertSame('user', $array['type']);
+ $this->assertSame('uuid-42', $array['lid']);
+ $this->assertTrue($resourceIdentifierObject->hasIdentification());
+ $this->assertSame('user|uuid-42', $resourceIdentifierObject->getIdentificationKey());
+ }
+
public function testGetIdentificationKey_NoIdentification() {
$resourceIdentifierObject = new ResourceIdentifierObject();
@@ -98,4 +175,17 @@ public function testIsEmpty_WithAtMembers() {
$this->assertFalse($resourceIdentifierObject->isEmpty());
}
+
+ /**
+ * @group Extensions
+ */
+ public function testIsEmpty_WithExtensionMembers() {
+ $resourceIdentifierObject = new ResourceIdentifierObject();
+
+ $this->assertTrue($resourceIdentifierObject->isEmpty());
+
+ $resourceIdentifierObject->addExtensionMember(new TestExtension(), 'foo', 'bar');
+
+ $this->assertFalse($resourceIdentifierObject->isEmpty());
+ }
}
diff --git a/tests/profiles/CursorPaginationProfileTest.php b/tests/profiles/CursorPaginationProfileTest.php
index 183df74..97704d3 100644
--- a/tests/profiles/CursorPaginationProfileTest.php
+++ b/tests/profiles/CursorPaginationProfileTest.php
@@ -14,29 +14,36 @@
*/
class CursorPaginationProfileTest extends TestCase {
public function testSetLinks_HappyPath() {
- $profile = new CursorPaginationProfile(['page' => 'pagination']);
+ $profile = new CursorPaginationProfile();
$collection = new CollectionDocument();
- $baseOrCurrentUrl = '/people?'.$profile->getKeyword('page').'[size]=10';
+ $baseOrCurrentUrl = '/people?page[size]=10';
$firstCursor = 'bar';
$lastCursor = 'foo';
+ $collection->applyProfile($profile);
$profile->setLinks($collection, $baseOrCurrentUrl, $firstCursor, $lastCursor);
$array = $collection->toArray();
+ $this->assertArrayHasKey('jsonapi', $array);
+ $this->assertArrayHasKey('profile', $array['jsonapi']);
+ $this->assertCount(1, $array['jsonapi']['profile']);
+ $this->assertSame($profile->getOfficialLink(), $array['jsonapi']['profile'][0]);
+
$this->assertArrayHasKey('links', $array);
$this->assertCount(2, $array['links']);
$this->assertArrayHasKey('prev', $array['links']);
$this->assertArrayHasKey('next', $array['links']);
$this->assertArrayHasKey('href', $array['links']['prev']);
$this->assertArrayHasKey('href', $array['links']['next']);
- $this->assertSame('/people?'.$profile->getKeyword('page').'[size]=10&'.$profile->getKeyword('page').'[before]='.$firstCursor, $array['links']['prev']['href']);
- $this->assertSame('/people?'.$profile->getKeyword('page').'[size]=10&'.$profile->getKeyword('page').'[after]='.$lastCursor, $array['links']['next']['href']);
+ $this->assertSame('/people?page[size]=10&page[before]='.$firstCursor, $array['links']['prev']['href']);
+ $this->assertSame('/people?page[size]=10&page[after]='.$lastCursor, $array['links']['next']['href']);
}
public function test_WithRelationship() {
- $profile = new CursorPaginationProfile(['page' => 'pagination']);
+ $profile = new CursorPaginationProfile();
$document = new ResourceDocument('test', 1);
+ $document->applyProfile($profile);
$person1 = new ResourceObject('person', 1);
$person2 = new ResourceObject('person', 2);
@@ -45,7 +52,7 @@ public function test_WithRelationship() {
$profile->setCursor($person2, 'arthur');
$profile->setCursor($person42, 'zaphod');
- $baseOrCurrentUrl = '/people?'.$profile->getKeyword('page').'[size]=10';
+ $baseOrCurrentUrl = '/people?page[size]=10';
$firstCursor = 'ford';
$lastCursor = 'zaphod';
$exactTotal = 3;
@@ -59,6 +66,11 @@ public function test_WithRelationship() {
$array = $document->toArray();
+ $this->assertArrayHasKey('jsonapi', $array);
+ $this->assertArrayHasKey('profile', $array['jsonapi']);
+ $this->assertCount(1, $array['jsonapi']['profile']);
+ $this->assertSame($profile->getOfficialLink(), $array['jsonapi']['profile'][0]);
+
$this->assertArrayHasKey('data', $array);
$this->assertArrayHasKey('relationships', $array['data']);
$this->assertArrayHasKey('people', $array['data']['relationships']);
@@ -67,79 +79,103 @@ public function test_WithRelationship() {
$this->assertArrayHasKey('meta', $array['data']['relationships']['people']);
$this->assertArrayHasKey('prev', $array['data']['relationships']['people']['links']);
$this->assertArrayHasKey('next', $array['data']['relationships']['people']['links']);
- $this->assertArrayHasKey('pagination', $array['data']['relationships']['people']['meta']);
+ $this->assertArrayHasKey('page', $array['data']['relationships']['people']['meta']);
$this->assertArrayHasKey('href', $array['data']['relationships']['people']['links']['prev']);
$this->assertArrayHasKey('href', $array['data']['relationships']['people']['links']['next']);
- $this->assertArrayHasKey('total', $array['data']['relationships']['people']['meta']['pagination']);
- $this->assertArrayHasKey('estimatedTotal', $array['data']['relationships']['people']['meta']['pagination']);
- $this->assertArrayHasKey('bestGuess', $array['data']['relationships']['people']['meta']['pagination']['estimatedTotal']);
+ $this->assertArrayHasKey('total', $array['data']['relationships']['people']['meta']['page']);
+ $this->assertArrayHasKey('estimatedTotal', $array['data']['relationships']['people']['meta']['page']);
+ $this->assertArrayHasKey('bestGuess', $array['data']['relationships']['people']['meta']['page']['estimatedTotal']);
$this->assertCount(3, $array['data']['relationships']['people']['data']);
$this->assertArrayHasKey('meta', $array['data']['relationships']['people']['data'][0]);
- $this->assertArrayHasKey('pagination', $array['data']['relationships']['people']['data'][0]['meta']);
- $this->assertArrayHasKey('cursor', $array['data']['relationships']['people']['data'][0]['meta']['pagination']);
+ $this->assertArrayHasKey('page', $array['data']['relationships']['people']['data'][0]['meta']);
+ $this->assertArrayHasKey('cursor', $array['data']['relationships']['people']['data'][0]['meta']['page']);
}
public function testSetLinksFirstPage_HappyPath() {
- $profile = new CursorPaginationProfile(['page' => 'pagination']);
+ $profile = new CursorPaginationProfile();
$collection = new CollectionDocument();
- $baseOrCurrentUrl = '/people?'.$profile->getKeyword('page').'[size]=10';
+ $baseOrCurrentUrl = '/people?page[size]=10';
$lastCursor = 'foo';
+ $collection->applyProfile($profile);
$profile->setLinksFirstPage($collection, $baseOrCurrentUrl, $lastCursor);
$array = $collection->toArray();
+ $this->assertArrayHasKey('jsonapi', $array);
+ $this->assertArrayHasKey('profile', $array['jsonapi']);
+ $this->assertCount(1, $array['jsonapi']['profile']);
+ $this->assertSame($profile->getOfficialLink(), $array['jsonapi']['profile'][0]);
+
$this->assertArrayHasKey('links', $array);
$this->assertCount(2, $array['links']);
$this->assertArrayHasKey('prev', $array['links']);
$this->assertArrayHasKey('next', $array['links']);
$this->assertNull($array['links']['prev']);
$this->assertArrayHasKey('href', $array['links']['next']);
- $this->assertSame('/people?'.$profile->getKeyword('page').'[size]=10&'.$profile->getKeyword('page').'[after]='.$lastCursor, $array['links']['next']['href']);
+ $this->assertSame('/people?page[size]=10&page[after]='.$lastCursor, $array['links']['next']['href']);
}
public function testSetLinksLastPage_HappyPath() {
- $profile = new CursorPaginationProfile(['page' => 'pagination']);
+ $profile = new CursorPaginationProfile();
$collection = new CollectionDocument();
- $baseOrCurrentUrl = '/people?'.$profile->getKeyword('page').'[size]=10';
+ $baseOrCurrentUrl = '/people?page[size]=10';
$firstCursor = 'bar';
+ $collection->applyProfile($profile);
$profile->setLinksLastPage($collection, $baseOrCurrentUrl, $firstCursor);
$array = $collection->toArray();
+ $this->assertArrayHasKey('jsonapi', $array);
+ $this->assertArrayHasKey('profile', $array['jsonapi']);
+ $this->assertCount(1, $array['jsonapi']['profile']);
+ $this->assertSame($profile->getOfficialLink(), $array['jsonapi']['profile'][0]);
+
$this->assertArrayHasKey('links', $array);
$this->assertCount(2, $array['links']);
$this->assertArrayHasKey('prev', $array['links']);
$this->assertArrayHasKey('next', $array['links']);
$this->assertArrayHasKey('href', $array['links']['prev']);
$this->assertNull($array['links']['next']);
- $this->assertSame('/people?'.$profile->getKeyword('page').'[size]=10&'.$profile->getKeyword('page').'[before]='.$firstCursor, $array['links']['prev']['href']);
+ $this->assertSame('/people?page[size]=10&page[before]='.$firstCursor, $array['links']['prev']['href']);
}
public function testSetCursor() {
- $profile = new CursorPaginationProfile(['page' => 'pagination']);
+ $profile = new CursorPaginationProfile();
$resourceDocument = new ResourceDocument('user', 42);
+ $resourceDocument->applyProfile($profile);
$profile->setCursor($resourceDocument, 'foo');
$array = $resourceDocument->toArray();
+ $this->assertArrayHasKey('jsonapi', $array);
+ $this->assertArrayHasKey('profile', $array['jsonapi']);
+ $this->assertCount(1, $array['jsonapi']['profile']);
+ $this->assertSame($profile->getOfficialLink(), $array['jsonapi']['profile'][0]);
+
$this->assertArrayHasKey('data', $array);
$this->assertArrayHasKey('meta', $array['data']);
- $this->assertArrayHasKey('pagination', $array['data']['meta']);
- $this->assertArrayHasKey('cursor', $array['data']['meta']['pagination']);
- $this->assertSame('foo', $array['data']['meta']['pagination']['cursor']);
+ $this->assertArrayHasKey('page', $array['data']['meta']);
+ $this->assertArrayHasKey('cursor', $array['data']['meta']['page']);
+ $this->assertSame('foo', $array['data']['meta']['page']['cursor']);
}
public function testSetPaginationLinkObjectsExplicitlyEmpty_HapptPath() {
- $profile = new CursorPaginationProfile(['page' => 'pagination']);
+ $profile = new CursorPaginationProfile();
$collection = new CollectionDocument();
+ $collection->applyProfile($profile);
$profile->setPaginationLinkObjectsExplicitlyEmpty($collection);
$array = $collection->toArray();
+ $this->assertArrayHasKey('jsonapi', $array);
+ $this->assertArrayHasKey('profile', $array['jsonapi']);
+ $this->assertCount(1, $array['jsonapi']['profile']);
+ $this->assertSame($profile->getOfficialLink(), $array['jsonapi']['profile'][0]);
+
$this->assertArrayHasKey('links', $array);
$this->assertCount(2, $array['links']);
$this->assertArrayHasKey('prev', $array['links']);
@@ -149,29 +185,35 @@ public function testSetPaginationLinkObjectsExplicitlyEmpty_HapptPath() {
}
public function testSetPaginationMeta() {
- $profile = new CursorPaginationProfile(['page' => 'pagination']);
+ $profile = new CursorPaginationProfile();
$collection = new CollectionDocument();
$exactTotal = 42;
$bestGuessTotal = 100;
$rangeIsTruncated = true;
+ $collection->applyProfile($profile);
$profile->setPaginationMeta($collection, $exactTotal, $bestGuessTotal, $rangeIsTruncated);
$array = $collection->toArray();
+ $this->assertArrayHasKey('jsonapi', $array);
+ $this->assertArrayHasKey('profile', $array['jsonapi']);
+ $this->assertCount(1, $array['jsonapi']['profile']);
+ $this->assertSame($profile->getOfficialLink(), $array['jsonapi']['profile'][0]);
+
$this->assertArrayHasKey('meta', $array);
- $this->assertArrayHasKey('pagination', $array['meta']);
- $this->assertArrayHasKey('total', $array['meta']['pagination']);
- $this->assertArrayHasKey('estimatedTotal', $array['meta']['pagination']);
- $this->assertArrayHasKey('bestGuess', $array['meta']['pagination']['estimatedTotal']);
- $this->assertArrayHasKey('rangeTruncated', $array['meta']['pagination']);
- $this->assertSame(42, $array['meta']['pagination']['total']);
- $this->assertSame(100, $array['meta']['pagination']['estimatedTotal']['bestGuess']);
- $this->assertSame(true, $array['meta']['pagination']['rangeTruncated']);
+ $this->assertArrayHasKey('page', $array['meta']);
+ $this->assertArrayHasKey('total', $array['meta']['page']);
+ $this->assertArrayHasKey('estimatedTotal', $array['meta']['page']);
+ $this->assertArrayHasKey('bestGuess', $array['meta']['page']['estimatedTotal']);
+ $this->assertArrayHasKey('rangeTruncated', $array['meta']['page']);
+ $this->assertSame(42, $array['meta']['page']['total']);
+ $this->assertSame(100, $array['meta']['page']['estimatedTotal']['bestGuess']);
+ $this->assertSame(true, $array['meta']['page']['rangeTruncated']);
}
public function testGetUnsupportedSortErrorObject_HappyPath() {
- $profile = new CursorPaginationProfile(['page' => 'pagination']);
+ $profile = new CursorPaginationProfile();
$genericTitle = 'foo';
$specificDetails = 'bar';
@@ -187,17 +229,16 @@ public function testGetUnsupportedSortErrorObject_HappyPath() {
$this->assertArrayHasKey('type', $array['links']);
$this->assertArrayHasKey('source', $array);
$this->assertArrayHasKey('parameter', $array['source']);
- $this->assertCount(1, $array['links']['type']);
$this->assertSame('400', $array['status']);
$this->assertSame('Unsupported sort', $array['code']);
$this->assertSame($genericTitle, $array['title']);
$this->assertSame($specificDetails, $array['detail']);
- $this->assertSame('https://jsonapi.org/profiles/ethanresnick/cursor-pagination/unsupported-sort', $array['links']['type'][0]);
+ $this->assertSame('https://jsonapi.org/profiles/ethanresnick/cursor-pagination/unsupported-sort', $array['links']['type']);
$this->assertSame('sort', $array['source']['parameter']);
}
public function testGetMaxPageSizeExceededErrorObject_HappyPath() {
- $profile = new CursorPaginationProfile(['page' => 'pagination']);
+ $profile = new CursorPaginationProfile();
$maxSize = 42;
$genericTitle = 'foo';
$specificDetails = 'bar';
@@ -215,21 +256,20 @@ public function testGetMaxPageSizeExceededErrorObject_HappyPath() {
$this->assertArrayHasKey('source', $array);
$this->assertArrayHasKey('parameter', $array['source']);
$this->assertArrayHasKey('meta', $array);
- $this->assertArrayHasKey('pagination', $array['meta']);
- $this->assertArrayHasKey('maxSize', $array['meta']['pagination']);
- $this->assertCount(1, $array['links']['type']);
+ $this->assertArrayHasKey('page', $array['meta']);
+ $this->assertArrayHasKey('maxSize', $array['meta']['page']);
$this->assertSame('400', $array['status']);
$this->assertSame('Max page size exceeded', $array['code']);
$this->assertSame($genericTitle, $array['title']);
$this->assertSame($specificDetails, $array['detail']);
- $this->assertSame('pagination[size]', $array['source']['parameter']);
- $this->assertSame('https://jsonapi.org/profiles/ethanresnick/cursor-pagination/max-size-exceeded', $array['links']['type'][0]);
- $this->assertSame(42, $array['meta']['pagination']['maxSize']);
+ $this->assertSame('page[size]', $array['source']['parameter']);
+ $this->assertSame('https://jsonapi.org/profiles/ethanresnick/cursor-pagination/max-size-exceeded', $array['links']['type']);
+ $this->assertSame(42, $array['meta']['page']['maxSize']);
}
public function testGetInvalidParameterValueErrorObject_HappyPath() {
- $profile = new CursorPaginationProfile(['page' => 'pagination']);
- $queryParameter = 'pagination[size]';
+ $profile = new CursorPaginationProfile();
+ $queryParameter = 'page[size]';
$typeLink = 'https://jsonapi.org';
$genericTitle = 'foo';
$specificDetails = 'bar';
@@ -246,17 +286,16 @@ public function testGetInvalidParameterValueErrorObject_HappyPath() {
$this->assertArrayHasKey('type', $array['links']);
$this->assertArrayHasKey('source', $array);
$this->assertArrayHasKey('parameter', $array['source']);
- $this->assertCount(1, $array['links']['type']);
$this->assertSame('400', $array['status']);
$this->assertSame('Invalid parameter value', $array['code']);
$this->assertSame($genericTitle, $array['title']);
$this->assertSame($specificDetails, $array['detail']);
- $this->assertSame('pagination[size]', $array['source']['parameter']);
- $this->assertSame('https://jsonapi.org', $array['links']['type'][0]);
+ $this->assertSame('page[size]', $array['source']['parameter']);
+ $this->assertSame('https://jsonapi.org', $array['links']['type']);
}
public function testGetRangePaginationNotSupportedErrorObject_HappyPath() {
- $profile = new CursorPaginationProfile(['page' => 'pagination']);
+ $profile = new CursorPaginationProfile();
$genericTitle = 'foo';
$specificDetails = 'bar';
@@ -270,12 +309,11 @@ public function testGetRangePaginationNotSupportedErrorObject_HappyPath() {
$this->assertArrayHasKey('detail', $array);
$this->assertArrayHasKey('links', $array);
$this->assertArrayHasKey('type', $array['links']);
- $this->assertCount(1, $array['links']['type']);
$this->assertSame('400', $array['status']);
$this->assertSame('Range pagination not supported', $array['code']);
$this->assertSame($genericTitle, $array['title']);
$this->assertSame($specificDetails, $array['detail']);
- $this->assertSame('https://jsonapi.org/profiles/ethanresnick/cursor-pagination/range-pagination-not-supported', $array['links']['type'][0]);
+ $this->assertSame('https://jsonapi.org/profiles/ethanresnick/cursor-pagination/range-pagination-not-supported', $array['links']['type']);
}
public function testSetQueryParameter_HappyPath() {
@@ -305,4 +343,15 @@ public function testSetQueryParameter_EncodedUrl() {
$this->assertSame('/people?sort=x&page%5Bsize%5D=10&page%5Bafter%5D=bar', $newUrl);
}
+
+ /**
+ * test method while it is part of the interface
+ */
+ public function testGetKeyword_HappyPath() {
+ $profile = new CursorPaginationProfile();
+
+ $keyword = $profile->getKeyword('page');
+
+ $this->assertSame('page', $keyword);
+ }
}
diff --git a/tests/profiles/TestProfile.php b/tests/profiles/TestProfile.php
index 91fe146..331e1a8 100644
--- a/tests/profiles/TestProfile.php
+++ b/tests/profiles/TestProfile.php
@@ -5,17 +5,13 @@
use alsvanzelf\jsonapi\interfaces\ProfileInterface;
class TestProfile implements ProfileInterface {
- private $aliasedLink;
+ private $officialLink;
- public function setAliasedLink($aliasedLink) {
- $this->aliasedLink = $aliasedLink;
+ public function setOfficialLink($officialLink) {
+ $this->officialLink = $officialLink;
}
- public function __construct(array $aliases=[]) {}
- public function getKeyword($keyword) {}
- public function getOfficialKeywords() {}
- public function getOfficialLink() {}
- public function getAliasedLink() {
- return $this->aliasedLink;
+ public function getOfficialLink() {
+ return $this->officialLink;
}
}