Compare commits

40 Commits
Author SHA1 Message Date
lukas 6193b450ac Documented usage 2026-10-16 08:51:44 +00:00
lukas 12ed032a31 Improved exception handling 2026-10-16 08:38:08 +00:00
lukas 70c52b8ad2 Updated test script 2026-10-16 08:32:20 +00:00
lukas afafb1ac3d Introduced common exception superclass 2026-10-15 13:39:44 +00:00
lukas f749d4a05e Updated badge for CI pipeline 2026-10-15 10:34:06 +00:00
lukas 62ad60f3df Use TTL for in-memory cache to support long-lived instances 2026-10-14 16:03:21 +00:00
lukas d52d7c3f2d Replace libraries with better-maintained forks 2026-09-15 14:50:13 +00:00
lukas 19645edf45 Fixed deprecations 2026-09-15 14:49:58 +00:00
lukas 2f511160f7 Changed autoloading from PSR-0 to PSR-4 2026-08-18 11:13:30 +00:00
lukas f9fabe838a Use ? to mark parameters as nullable to prevent PHP 8.4 deprecation warning 2026-06-19 08:28:06 +00:00
lukas ce77709d37 Added global function cloudobjects_get_object() 2026-06-15 09:29:04 +00:00
lukas a7eb83be44 Implemented a facade for ObjectRetriever to allow static access 2026-06-15 09:19:59 +00:00
lukas 3653513818 Simplified comment 2026-06-15 09:06:53 +00:00
lukas 58df9cb476 Added getLabel() helper function 2026-06-15 09:04:16 +00:00
lukas 3c958dc5df Throw exception when namespace cannot be retrieved 2026-06-12 18:28:30 +00:00
lukas 993218f21d getAuthenticatingNamespaceObject* may return null as well 2026-06-12 17:52:55 +00:00
lukas aa37259a21 Extend SDKLoader to support non-OO SDKs, starting with Sentry 2026-06-12 17:38:13 +00:00
lukas 69a16e64d1 Fixed return type hints to not fail when objects are null 2026-06-12 17:30:25 +00:00
lukas cb03c811ef Allow deeper nesting of hostnames 2026-06-02 13:19:25 +00:00
lukas 6f3c7338c7 Changed indentation and added type hints 2026-06-02 13:14:16 +00:00
lukas da0ddd572e Refactored and renamed getAuthenticatingNamespaceObject(), added more type hints 2026-06-02 12:32:09 +00:00
lukas 5f5a31df68 Improved cache reading 2026-06-01 17:45:55 +00:00
lukas 58585a6875 Fixed copy&paste error in README 2026-05-26 15:39:03 +00:00
lukas 23f00b2374 Remove outdated doctrine/cache package and change configuration 2026-05-26 15:38:15 +00:00
lukas 9f339953fe Fixed Buddy badge 2026-05-15 14:46:22 +00:00
lukas d5d766cbf4 Added getRevision() helper function, refactored revision constant 2026-05-15 14:06:15 +00:00
lukas 06df8b0be1 Use new API endpoint URL 2026-05-15 11:24:21 +00:00
lukas de355ff2f9 Added a has() method to CloudObject 2026-05-15 11:22:15 +00:00
lukas 7d8ece0df1 Set visibility of getReader to allow inheritance 2026-05-15 11:20:41 +00:00
lukas 1122b9faf5 Implemented getObjectNode as well, renamed other function for consistency 2026-05-15 10:58:54 +00:00
lukas 0cb2655494 Add retriever to CloudObject and allow retrieval of related objects 2026-05-15 10:54:39 +00:00
lukas a9689d5a2b Extend and reformat tests 2026-05-15 10:41:38 +00:00
lukas 6cb8a9d603 Added default reader functionality 2026-05-15 10:41:25 +00:00
lukas 4724c4988e Make setReader fluent 2026-05-15 10:39:42 +00:00
lukas bb738a1d79 Replace deprecated getObject with getObjectNode 2026-05-15 10:05:41 +00:00
lukas fd029a79bf Ignore bash and phpUnit files 2026-05-15 09:49:16 +00:00
lukas 2954d9fc99 Remove phpUnit cache from version control 2026-05-15 09:48:38 +00:00
lukas f29af2b664 First implementation of a new CloudObject node wrapper class 2026-05-15 09:44:44 +00:00
lukas 5584f10462 Fix homepage in composer.json 2026-03-09 15:48:10 +00:00
lukas f937f2b426 Update caching library to latest version, updated and fixed phpUnit tests 2026-02-18 15:16:34 +00:00
40 changed files with 2535 additions and 1463 deletions

No files matched your search

+3
View File
@@ -5,3 +5,6 @@ cache
.config .config
.local .local
*.phar *.phar
.composer
.phpunit*
.bash_history
@@ -13,6 +13,7 @@ use Nyholm\Psr7\Factory\Psr17Factory;
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory; use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
use GuzzleHttp\Client, GuzzleHttp\HandlerStack, GuzzleHttp\Middleware; use GuzzleHttp\Client, GuzzleHttp\HandlerStack, GuzzleHttp\Middleware;
use Psr\Http\Message\RequestInterface, Psr\Http\Message\ResponseInterface; use Psr\Http\Message\RequestInterface, Psr\Http\Message\ResponseInterface;
use CloudObjects\SDK\Exceptions\SDKException;
/** /**
* The context of an request for an account. * The context of an request for an account.
@@ -40,9 +41,9 @@ class AccountContext {
/** /**
* Create a new context using an AAUID and an OAuth 2.0 bearer access token. * Create a new context using an AAUID and an OAuth 2.0 bearer access token.
*/ */
public function __construct(IRI $aauid, $accessToken, DataLoader $dataLoader = null) { public function __construct(IRI $aauid, $accessToken, ?DataLoader $dataLoader = null) {
if (AAUIDParser::getType($aauid) != AAUIDParser::AAUID_ACCOUNT) if (AAUIDParser::getType($aauid) != AAUIDParser::AAUID_ACCOUNT)
throw new \Exception("Not a valid AAUID"); throw new SDKException("Not a valid AAUID");
$this->aauid = $aauid; $this->aauid = $aauid;
$this->accessToken = $accessToken; $this->accessToken = $accessToken;
@@ -324,7 +325,7 @@ class AccountContext {
*/ */
public function setLogCode($logCode) { public function setLogCode($logCode) {
if (!$this->request) { if (!$this->request) {
throw new \Exception('Not in a request context.'); throw new SDKException('Not in a request context.');
} }
$this->logCode = $logCode; $this->logCode = $logCode;
} }
+9 -9
View File
@@ -20,7 +20,7 @@ class COIDParser {
const COID_VERSIONED = 3; const COID_VERSIONED = 3;
const COID_VERSION_WILDCARD = 4; const COID_VERSION_WILDCARD = 4;
const REGEX_HOSTNAME = "/^([a-z0-9-]+\.)?[a-z0-9-]+\.[a-z]+$/"; const REGEX_HOSTNAME = "/^([a-z0-9-]+\.)*[a-z0-9-]+\.[a-z]+$/";
const REGEX_SEGMENT = "/^[A-Za-z-_0-9\.]+$/"; const REGEX_SEGMENT = "/^[A-Za-z-_0-9\.]+$/";
const REGEX_VERSION_WILDCARD = "/^((\^|~)(\d+\.)?\d|(\d+\.){1,2}\*)$/"; const REGEX_VERSION_WILDCARD = "/^((\^|~)(\d+\.)?\d|(\d+\.){1,2}\*)$/";
@@ -31,7 +31,7 @@ class COIDParser {
* @param string $coidString A COID string. * @param string $coidString A COID string.
* @return IRI * @return IRI
*/ */
public static function fromString($coidString) { public static function fromString($coidString) : IRI {
$coidPre = new IRI( $coidPre = new IRI(
(strtolower(substr($coidString, 0, 7)) == 'coid://') ? $coidString : 'coid://'.$coidString (strtolower(substr($coidString, 0, 7)) == 'coid://') ? $coidString : 'coid://'.$coidString
); );
@@ -45,7 +45,7 @@ class COIDParser {
* @param IRI $coid * @param IRI $coid
* @return int|null * @return int|null
*/ */
public static function getType(IRI $coid) { public static function getType(IRI $coid) : ?int {
if ($coid->getScheme()!='coid' || $coid->getHost()=='' if ($coid->getScheme()!='coid' || $coid->getHost()==''
|| preg_match(self::REGEX_HOSTNAME, $coid->getHost()) != 1) || preg_match(self::REGEX_HOSTNAME, $coid->getHost()) != 1)
return self::COID_INVALID; return self::COID_INVALID;
@@ -79,9 +79,9 @@ class COIDParser {
* Checks whether the given IRI object is a valid COID. * Checks whether the given IRI object is a valid COID.
* *
* @param IRI $coid * @param IRI $coid
* @return boolean * @return bool
*/ */
public static function isValidCOID(IRI $coid) { public static function isValidCOID(IRI $coid) : bool {
return (self::getType($coid)!=self::COID_INVALID); return (self::getType($coid)!=self::COID_INVALID);
} }
@@ -91,7 +91,7 @@ class COIDParser {
* @param IRI $coid * @param IRI $coid
* @return string|null * @return string|null
*/ */
public static function getName(IRI $coid) { public static function getName(IRI $coid) : ?string {
if (self::getType($coid)!=self::COID_INVALID if (self::getType($coid)!=self::COID_INVALID
&& self::getType($coid)!=self::COID_ROOT) { && self::getType($coid)!=self::COID_ROOT) {
$segments = explode('/', $coid->getPath()); $segments = explode('/', $coid->getPath());
@@ -106,7 +106,7 @@ class COIDParser {
* @param IRI $coid * @param IRI $coid
* @return string|null * @return string|null
*/ */
public static function getVersion(IRI $coid) { public static function getVersion(IRI $coid) : ?string {
if (self::getType($coid)==self::COID_VERSIONED) { if (self::getType($coid)==self::COID_VERSIONED) {
$segments = explode('/', $coid->getPath()); $segments = explode('/', $coid->getPath());
return $segments[2]; return $segments[2];
@@ -121,7 +121,7 @@ class COIDParser {
* @param IRI $coid * @param IRI $coid
* @return string|null * @return string|null
*/ */
public static function getVersionWildcard(IRI $coid) { public static function getVersionWildcard(IRI $coid) : ?string {
if (self::getType($coid)==self::COID_VERSION_WILDCARD) { if (self::getType($coid)==self::COID_VERSION_WILDCARD) {
$segments = explode('/', $coid->getPath()); $segments = explode('/', $coid->getPath());
return $segments[2]; return $segments[2];
@@ -136,7 +136,7 @@ class COIDParser {
* @param IRI $coid * @param IRI $coid
* @return IRI|null * @return IRI|null
*/ */
public static function getNamespaceCOID(IRI $coid) { public static function getNamespaceCOID(IRI $coid) : ?IRI {
switch (self::getType($coid)) { switch (self::getType($coid)) {
case self::COID_ROOT: case self::COID_ROOT:
return $coid; return $coid;
+176
View File
@@ -0,0 +1,176 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK;
use ML\IRI\IRI;
use ML\JsonLD\Node;
use CloudObjects\SDK\NodeReader;
use Webmozart\Assert\Assert;
/**
* A CloudObject encapsulates an object node for an object stored in CloudObjects Core
* with convenience methods to access properties.
*/
class CloudObject {
private $coid;
private $node;
private $reader = null;
private $retriever = null;
public function __construct(IRI $coid, Node $node) {
Assert::eq((string)$coid, $node->getId(), "COID and Node ID must match.");
$this->coid = $coid;
$this->node = $node;
}
/**
* Specify a custom NodeReader to use for this CloudObject.
* If not set, a default NodeReader will be used.
*/
public function setReader(NodeReader $reader) : self {
$this->reader = $reader;
return $this;
}
/**
* Get the NodeReader used for this CloudObject.
* Returns a default NodeReader if non has been set.
*/
protected function getReader() : NodeReader {
if (!$this->reader) {
$this->reader = new NodeReader;
}
return $this->reader;
}
/**
* Specify an ObjectRetriever to use for retrieval
* of related objects.
*/
public function setObjectRetriever(ObjectRetriever $retriever) : self {
$this->retriever = $retriever;
return $this;
}
/**
* Get the COID of this object.
*/
public function getCOID() : IRI {
return $this->coid;
}
/**
* Get the object node encapsulated in this CloudObject.
*/
public function getAsNode() : Node {
return $this->node;
}
/**
* Checks if a property exists on this object.
*/
public function has($property) : bool {
return $this->getReader()->hasProperty($this->node, $property);
}
/**
* Get the value of a property as a string.
* If the property has multiple values, only the first is returned.
*/
public function getString($property, ?string $default = null) : ?string {
return $this->getReader()->getFirstValueString($this->node, $property, $default);
}
/**
* Get the value of a property as an integer.
* If the property has multiple values, only the first is returned.
*/
public function getInt($property, ?int $default = null) : ?int {
return $this->getReader()->getFirstValueInt($this->node, $property, $default);
}
/**
* Get the value of a property as a float.
* If the property has multiple values, only the first is returned.
*/
public function getFloat($property, ?float $default = null) : ?float {
return $this->getReader()->getFirstValueFloat($this->node, $property, $default);
}
/**
* Get the value of a property as a boolean.
* If the property has multiple values, only the first is returned.
*/
public function getBool($property, ?bool $default = null) : ?bool {
return $this->getReader()->getFirstValueBool($this->node, $property, $default);
}
/**
* Get the value of a property as an IRI.
* If the property has multiple values, only the first is returned.
*/
public function getIRI($property, ?IRI $default = null) : ?IRI {
return $this->getReader()->getFirstValueIRI($this->node, $property, $default);
}
/**
* Get the value of a property as a Node.
* If the property has multiple values, only the first is returned.
*/
public function getNode($property, ?Node $default = null) : ?Node {
return $this->getReader()->getFirstValueNode($this->node, $property, $default);
}
/**
* Get the value of a property and, if it's a COID, retrieve the corresponding CloudObject.
*/
public function getCloudObject($property) : ?CloudObject {
Assert::notNull($this->retriever, "No ObjectRetriever set for CloudObject. Cannot retrieve related object.");
$coid = $this->getReader()->getFirstValueIRI($this->node, $property);
if (!($coid instanceof IRI)) {
return null;
}
return $this->retriever->getCloudObject($coid);
}
/**
* Get the value of a property and, if it's a COID, retrieve the corresponding object node.
*/
public function getObjectNode($property) : ?Node {
Assert::notNull($this->retriever, "No ObjectRetriever set for CloudObject. Cannot retrieve related object.");
$coid = $this->getReader()->getFirstValueIRI($this->node, $property);
if (!($coid instanceof IRI)) {
return null;
}
return $this->retriever->getObjectNode($coid);
}
/**
* Get the revision of the object.
*/
public function getRevision() : string {
return $this->getString(Constants::PROPERTY_REVISION);
}
/**
* Get the label of the object.
*/
public function getLabel() : ?string {
return $this->getString(Constants::RDFS_LABEL);
}
}
+5 -5
View File
@@ -6,7 +6,7 @@
namespace CloudObjects\SDK\Common; namespace CloudObjects\SDK\Common;
use Exception; use CloudObjects\SDK\Exceptions\SDKException;
use ML\IRI\IRI; use ML\IRI\IRI;
use CloudObjects\SDK\NodeReader; use CloudObjects\SDK\NodeReader;
use Defuse\Crypto\Key, Defuse\Crypto\Crypto; use Defuse\Crypto\Key, Defuse\Crypto\Crypto;
@@ -53,14 +53,14 @@ class CryptoHelper {
* @param ObjectRetriever $objectRetriever An initialized and authenticated object retriever. * @param ObjectRetriever $objectRetriever An initialized and authenticated object retriever.
* @param IRI|null $namespaceCoid The namespace used to retrieve keys. If this parameter is not provided, the namespace provided with the "auth_ns" configuration option from the object retriever is used. * @param IRI|null $namespaceCoid The namespace used to retrieve keys. If this parameter is not provided, the namespace provided with the "auth_ns" configuration option from the object retriever is used.
*/ */
public function __construct(ObjectRetriever $objectRetriever, IRI $namespaceCoid = null) { public function __construct(ObjectRetriever $objectRetriever, ?IRI $namespaceCoid = null) {
if (!class_exists('Defuse\Crypto\Crypto')) if (!class_exists('Defuse\Crypto\Crypto'))
throw new Exception("Run composer require defuse/php-encryption before using CryptoHelper."); throw new SDKException("Run composer require defuse/php-encryption before using CryptoHelper.");
$this->objectRetriever = $objectRetriever; $this->objectRetriever = $objectRetriever;
$this->namespace = isset($namespaceCoid) $this->namespace = isset($namespaceCoid)
? $objectRetriever->getObject($namespaceCoid) ? $objectRetriever->getObjectNode($namespaceCoid)
: $objectRetriever->getAuthenticatingNamespaceObject(); : $objectRetriever->getAuthenticatingNamespaceObjectNode();
$this->reader = new NodeReader([ $this->reader = new NodeReader([
'prefixes' => [ 'prefixes' => [
+14
View File
@@ -0,0 +1,14 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK;
class Constants {
const PROPERTY_REVISION = 'coid://cloudobjects.io/isAtRevision';
const RDFS_LABEL = 'http://www.w3.org/2000/01/rdf-schema#label';
}
@@ -9,6 +9,14 @@ namespace CloudObjects\SDK\Exceptions;
/** /**
* An Exception that is thrown when the Core API returned an error. * An Exception that is thrown when the Core API returned an error.
*/ */
class CoreAPIException extends \Exception { class CoreAPIException extends SDKException {
/**
* @param string $message The error message.
* @param \Throwable|null $previous The original exception that caused this error, if any.
*/
public function __construct(string $message = "", ?\Throwable $previous = null) {
parent::__construct($message, 0, $previous);
}
} }
@@ -10,6 +10,6 @@ namespace CloudObjects\SDK\Exceptions;
* An Exception that is thrown when an object's configuration * An Exception that is thrown when an object's configuration
* doesn't match the client's expectations. * doesn't match the client's expectations.
*/ */
class InvalidObjectConfigurationException extends \Exception { class InvalidObjectConfigurationException extends SDKException {
} }
@@ -0,0 +1,14 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK\Exceptions;
/**
* An Exception that is thrown when the SDK's configuration isn't valid.
*/
class InvalidSDKConfigurationException extends SDKException {
}
@@ -0,0 +1,15 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK\Exceptions;
/**
* The common superclass for all Exceptions thrown by the SDK.
* It is also thrown directly for errors that have no more specific Exception.
*/
class SDKException extends \Exception {
}
+43 -9
View File
@@ -6,7 +6,8 @@
namespace CloudObjects\SDK\Helpers; namespace CloudObjects\SDK\Helpers;
use Exception; use CloudObjects\SDK\Exceptions\SDKException;
use CloudObjects\SDK\Exceptions\InvalidSDKConfigurationException;
use CloudObjects\SDK\NodeReader, CloudObjects\SDK\ObjectRetriever; use CloudObjects\SDK\NodeReader, CloudObjects\SDK\ObjectRetriever;
/** /**
@@ -28,19 +29,52 @@ class SDKLoader {
} }
/** /**
* Initialize and return the SDK with the given classname. * Initialize the SDK with the given name and options; used for
* Throws Exception if the SDK is not supported. * SDKs that are initialized with a function and not a class name.
* *
* @param $classname Classname for the SDK's main class * @param string $sdkName The name of the SDK to initialize.
* @param array $options Additional options for the SDK (if necessary) * @param array $options Additional options for the SDK (if necessary).
* @return mixed The return value of the SDK initialization, which may vary depending on the SDK.
* @throws SDKException If the SDK is not supported or cannot be initialized.
*/ */
public function get($classname, array $options) { public function init(string $sdkName, array $options = []) : mixed {
$namespace = $this->objectRetriever->getAuthenticatingNamespaceCloudObject();
if (!$namespace)
throw new InvalidSDKConfigurationException("The authenticating namespace object could not be retrieved.");
switch (strtolower($sdkName)) {
case "sentry":
// --- Sentry (https://sentry.io/) ---
$initFunction = '\Sentry\init';
return $initFunction(array_merge([
'dsn' => $namespace->getString('coid://sentry.io.3rd-party.co/DSN')
], $options));
default:
throw new SDKException("No rules defined to initialize SDK with name <".$sdkName.">.");
}
}
/**
* Initialize and return the SDK with the given classname.
* Throws SDKException if the SDK is not supported.
*
* @param string $classname Classname for the SDK's main class.
* @param array $options Additional options for the SDK (if necessary).
* @return mixed The initialized SDK instance.
* @throws SDKException If the SDK is not supported or cannot be initialized.
*/
public function get(string $classname, array $options) : mixed {
if (!class_exists($classname)) if (!class_exists($classname))
throw new Exception("<".$classname."> is not a valid classname."); throw new SDKException("<".$classname."> is not a valid classname.");
$hashkey = md5($classname.serialize($options)); $hashkey = md5($classname.serialize($options));
if (!isset($this->classes[$hashkey])) { if (!isset($this->classes[$hashkey])) {
$nsNode = $this->objectRetriever->getAuthenticatingNamespaceObject(); $nsNode = $this->objectRetriever->getAuthenticatingNamespaceObjectNode();
if (!$nsNode)
throw new InvalidSDKConfigurationException("The authenticating namespace object could not be retrieved.");
// --- Amazon Web Services (https://aws.amazon.com/) --- // --- Amazon Web Services (https://aws.amazon.com/) ---
// has multiple classnames, so check for common superclass // has multiple classnames, so check for common superclass
@@ -76,7 +110,7 @@ class SDKLoader {
} }
if (!isset($class)) if (!isset($class))
throw new Exception("No rules defined to initialize <".$classname.">."); throw new SDKException("No rules defined to initialize <".$classname.">.");
$this->classes[$hashkey] = $class; $this->classes[$hashkey] = $class;
return $this->classes[$hashkey]; return $this->classes[$hashkey];
@@ -50,7 +50,7 @@ class SharedSecretAuthentication {
return self::RESULT_INVALID_PASSWORD; return self::RESULT_INVALID_PASSWORD;
// Retrieve namespace // Retrieve namespace
$namespace = $retriever->getObject($namespaceCoid); $namespace = $retriever->getObjectNode($namespaceCoid);
if (!isset($namespace)) if (!isset($namespace))
return self::RESULT_NAMESPACE_NOT_FOUND; return self::RESULT_NAMESPACE_NOT_FOUND;
@@ -87,7 +87,7 @@ class SharedSecretAuthentication {
return self::RESULT_INVALID_PASSWORD; return self::RESULT_INVALID_PASSWORD;
// Retrieve namespace // Retrieve namespace
$namespace = $this->objectRetriever->getObject($namespaceCoid); $namespace = $this->objectRetriever->getObjectNode($namespaceCoid);
if (!isset($namespace)) if (!isset($namespace))
return self::RESULT_NAMESPACE_NOT_FOUND; return self::RESULT_NAMESPACE_NOT_FOUND;
+1 -1
View File
@@ -68,7 +68,7 @@ class SchemaValidator {
* @param Node $node The COID of the specification. * @param Node $node The COID of the specification.
*/ */
public function validateAgainstCOID($data, IRI $coid) { public function validateAgainstCOID($data, IRI $coid) {
$object = $this->objectRetriever->getObject($coid); $object = $this->objectRetriever->getObjectNode($coid);
Assert::true($this->reader->hasType($object, 'json:Element'), Assert::true($this->reader->hasType($object, 'json:Element'),
"You can only validate data against JSON elements!"); "You can only validate data against JSON elements!");
$this->validateAgainstNode($data, $object); $this->validateAgainstNode($data, $object);
+18 -18
View File
@@ -38,7 +38,7 @@ class NodeReader {
* @param string|object $type The type to check for. * @param string|object $type The type to check for.
* @return boolean * @return boolean
*/ */
public function hasType(Node $node = null, $type) { public function hasType(?Node $node, $type) {
if (!isset($node)) if (!isset($node))
return false; return false;
$type = $this->expand($type); $type = $this->expand($type);
@@ -60,7 +60,7 @@ class NodeReader {
return false; return false;
} }
private function getFirstValue(Node $node = null, $property, $default = null) { private function getFirstValue(?Node $node, $property, $default = null) {
if (!isset($node)) if (!isset($node))
return $default; return $default;
$valueFromNode = $node->getProperty($this->expand($property)); $valueFromNode = $node->getProperty($this->expand($property));
@@ -82,7 +82,7 @@ class NodeReader {
* @param $default The default that is returned if no value for the property exists on the node. * @param $default The default that is returned if no value for the property exists on the node.
* @return string|null * @return string|null
*/ */
public function getFirstValueString(Node $node = null, $property, $default = null) { public function getFirstValueString(?Node $node, $property, $default = null) {
$valueFromNode = $this->getFirstValue($node, $property, $default); $valueFromNode = $this->getFirstValue($node, $property, $default);
if ($valueFromNode == $default) if ($valueFromNode == $default)
return $default; return $default;
@@ -103,7 +103,7 @@ class NodeReader {
* @param $default The default that is returned if no value for the property exists on the node. * @param $default The default that is returned if no value for the property exists on the node.
* @return bool|null * @return bool|null
*/ */
public function getFirstValueBool(Node $node = null, $property, $default = null) { public function getFirstValueBool(?Node $node, $property, $default = null) {
return (in_array( return (in_array(
$this->getFirstValueString($node, $property, $default), $this->getFirstValueString($node, $property, $default),
[ '1', 'true' ] [ '1', 'true' ]
@@ -120,7 +120,7 @@ class NodeReader {
* @param $default The default that is returned if no value for the property exists on the node. * @param $default The default that is returned if no value for the property exists on the node.
* @return int|null * @return int|null
*/ */
public function getFirstValueInt(Node $node = null, $property, $default = null) { public function getFirstValueInt(?Node $node, $property, $default = null) {
$value = $this->getFirstValueString($node, $property); $value = $this->getFirstValueString($node, $property);
if (is_numeric($value)) if (is_numeric($value))
return (int)($value); return (int)($value);
@@ -138,7 +138,7 @@ class NodeReader {
* @param $default The default that is returned if no value for the property exists on the node. * @param $default The default that is returned if no value for the property exists on the node.
* @return float|null * @return float|null
*/ */
public function getFirstValueFloat(Node $node = null, $property, $default = null) { public function getFirstValueFloat(?Node $node, $property, $default = null) {
$value = $this->getFirstValueString($node, $property); $value = $this->getFirstValueString($node, $property);
if (is_numeric($value)) if (is_numeric($value))
return (float)($value); return (float)($value);
@@ -156,7 +156,7 @@ class NodeReader {
* @param $default The default that is returned if no value for the property exists on the node. * @param $default The default that is returned if no value for the property exists on the node.
* @return string|null * @return string|null
*/ */
public function getFirstValueIRI(Node $node = null, $property, IRI $default = null) { public function getFirstValueIRI(?Node $node, $property, ?IRI $default = null) {
$valueFromNode = $this->getFirstValue($node, $property, $default); $valueFromNode = $this->getFirstValue($node, $property, $default);
if ($valueFromNode == $default) if ($valueFromNode == $default)
return $default; return $default;
@@ -177,7 +177,7 @@ class NodeReader {
* @param $default The default that is returned if no value for the property exists on the node. * @param $default The default that is returned if no value for the property exists on the node.
* @return string|null * @return string|null
*/ */
public function getFirstValueNode(Node $node = null, $property, Node $default = null) { public function getFirstValueNode(?Node $node, $property, ?Node $default = null) {
$valueFromNode = $this->getFirstValue($node, $property, $default); $valueFromNode = $this->getFirstValue($node, $property, $default);
if ($valueFromNode == $default) if ($valueFromNode == $default)
return $default; return $default;
@@ -196,7 +196,7 @@ class NodeReader {
* @param string|object $value The expected value. * @param string|object $value The expected value.
* @return boolean * @return boolean
*/ */
public function hasPropertyValue(Node $node = null, $property, $value) { public function hasPropertyValue(?Node $node, $property, $value) {
if (!isset($node)) if (!isset($node))
return false; return false;
$valuesFromNode = $node->getProperty($this->expand($property)); $valuesFromNode = $node->getProperty($this->expand($property));
@@ -225,14 +225,14 @@ class NodeReader {
* @param string|object $property The property to read. * @param string|object $property The property to read.
* @return boolean * @return boolean
*/ */
public function hasProperty(Node $node = null, $property) { public function hasProperty(?Node $node, $property) {
if (!isset($node)) if (!isset($node))
return false; return false;
return ($node->getProperty($this->expand($property)) != null); return ($node->getProperty($this->expand($property)) != null);
} }
private function getAllValues(Node $node = null, $property) { private function getAllValues(?Node $node, $property) {
if (!isset($node)) if (!isset($node))
return []; return [];
@@ -248,7 +248,7 @@ class NodeReader {
* Get the language-tagged-string for the property in the specified language. * Get the language-tagged-string for the property in the specified language.
* If no value is found for the specified language, the default is returned. * If no value is found for the specified language, the default is returned.
*/ */
public function getLocalizedString(Node $node = null, $property, $language, $default = null) { public function getLocalizedString(?Node $node, $property, $language, $default = null) {
$values = $this->getAllValues($node, $property); $values = $this->getAllValues($node, $property);
foreach ($values as $v) { foreach ($values as $v) {
if (is_a($v, 'ML\JsonLD\LanguageTaggedString') && $v->getLanguage() == $language) if (is_a($v, 'ML\JsonLD\LanguageTaggedString') && $v->getLanguage() == $language)
@@ -265,7 +265,7 @@ class NodeReader {
* @param string|object $property The property to read. * @param string|object $property The property to read.
* @return array<string> * @return array<string>
*/ */
public function getAllValuesString(Node $node = null, $property) { public function getAllValuesString(?Node $node, $property) {
$allValues = $this->getAllValues($node, $property); $allValues = $this->getAllValues($node, $property);
$output = []; $output = [];
foreach ($allValues as $a) foreach ($allValues as $a)
@@ -284,7 +284,7 @@ class NodeReader {
* @param string|object $property The property to read. * @param string|object $property The property to read.
* @return array<bool> * @return array<bool>
*/ */
public function getAllValuesBool(Node $node = null, $property) { public function getAllValuesBool(?Node $node, $property) {
$allValues = $this->getAllValuesString($node, $property); $allValues = $this->getAllValuesString($node, $property);
$output = []; $output = [];
foreach ($allValues as $a) foreach ($allValues as $a)
@@ -300,7 +300,7 @@ class NodeReader {
* @param string|object $property The property to read. * @param string|object $property The property to read.
* @return array<bool> * @return array<bool>
*/ */
public function getAllValuesInt(Node $node = null, $property) { public function getAllValuesInt(?Node $node, $property) {
$allValues = $this->getAllValuesString($node, $property); $allValues = $this->getAllValuesString($node, $property);
$output = []; $output = [];
foreach ($allValues as $a) foreach ($allValues as $a)
@@ -316,7 +316,7 @@ class NodeReader {
* @param string|object $property The property to read. * @param string|object $property The property to read.
* @return array<bool> * @return array<bool>
*/ */
public function getAllValuesFloat(Node $node = null, $property) { public function getAllValuesFloat(?Node $node, $property) {
$allValues = $this->getAllValuesString($node, $property); $allValues = $this->getAllValuesString($node, $property);
$output = []; $output = [];
foreach ($allValues as $a) foreach ($allValues as $a)
@@ -333,7 +333,7 @@ class NodeReader {
* @param string|object $property The property to read. * @param string|object $property The property to read.
* @return array<IRI> * @return array<IRI>
*/ */
public function getAllValuesIRI(Node $node = null, $property) { public function getAllValuesIRI(?Node $node, $property) {
$allValues = $this->getAllValues($node, $property); $allValues = $this->getAllValues($node, $property);
$output = []; $output = [];
foreach ($allValues as $a) foreach ($allValues as $a)
@@ -351,7 +351,7 @@ class NodeReader {
* @param string|object $property The property to read. * @param string|object $property The property to read.
* @return array<Node> * @return array<Node>
*/ */
public function getAllValuesNode(Node $node = null, $property) { public function getAllValuesNode(?Node $node, $property) {
$allValues = $this->getAllValues($node, $property); $allValues = $this->getAllValues($node, $property);
$output = []; $output = [];
foreach ($allValues as $a) foreach ($allValues as $a)
+210 -82
View File
@@ -6,15 +6,25 @@
namespace CloudObjects\SDK; namespace CloudObjects\SDK;
use Exception; use DateTime, Exception;
use ML\IRI\IRI, ML\JsonLD\JsonLD; use ML\IRI\IRI;
use Doctrine\Common\Cache\RedisCache; use ML\JsonLD\JsonLD, ML\JsonLD\Node;
use Psr\Log\LoggerInterface, Psr\Log\LoggerAwareTrait; use Psr\Log\LoggerInterface, Psr\Log\LoggerAwareTrait;
use GuzzleHttp\ClientInterface, GuzzleHttp\Client, GuzzleHttp\HandlerStack; use GuzzleHttp\ClientInterface, GuzzleHttp\Client, GuzzleHttp\HandlerStack;
use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Exception\RequestException;
use Kevinrob\GuzzleCache\CacheMiddleware, Kevinrob\GuzzleCache\Storage\DoctrineCacheStorage; use GuzzleHttp\Psr7\Request,
GuzzleHttp\Psr7\Response;
use Kevinrob\GuzzleCache\CacheMiddleware;
use Kevinrob\GuzzleCache\Strategy\PrivateCacheStrategy; use Kevinrob\GuzzleCache\Strategy\PrivateCacheStrategy;
use CloudObjects\SDK\Exceptions\CoreAPIException; use Kevinrob\GuzzleCache\Storage\CacheStorageInterface,
Kevinrob\GuzzleCache\Storage\Psr6CacheStorage,
Kevinrob\GuzzleCache\Storage\Psr16CacheStorage,
Kevinrob\GuzzleCache\CacheEntry;
use Psr\Cache\CacheItemPoolInterface;
use Psr\SimpleCache\CacheInterface;
use CloudObjects\SDK\Exceptions\CoreAPIException,
CloudObjects\SDK\Exceptions\InvalidSDKConfigurationException,
CloudObjects\SDK\Exceptions\SDKException;
use CloudObjects\SDK\AccountGateway\AccountContext; use CloudObjects\SDK\AccountGateway\AccountContext;
/** /**
@@ -26,18 +36,18 @@ class ObjectRetriever implements CustomCacheAndLogInterface {
private $client; private $client;
private $prefix; private $prefix;
private $options; private $options = [];
private $cache; private $cache;
private $objects; private $objects = [];
private $defaultReader;
private $customCacheDefaultRequest;
const CO_API_URL = 'https://api.cloudobjects.net/'; const CO_API_URL = 'https://od.coid.link/';
const REVISION_PROPERTY = 'coid://cloudobjects.io/isAtRevision';
public function __construct($options = []) { public function __construct($options = []) {
// Merge options with defaults // Merge options with defaults
$this->options = array_merge([ $this->options = array_merge([
'cache_provider' => 'none', 'cache_storage' => null,
'cache_prefix' => 'clobj:', 'cache_prefix' => 'clobj:',
'cache_ttl' => 60, 'cache_ttl' => 60,
'static_config_path' => null, 'static_config_path' => null,
@@ -49,35 +59,27 @@ class ObjectRetriever implements CustomCacheAndLogInterface {
'connect_timeout' => 5 'connect_timeout' => 5
], $options); ], $options);
// Set up object cache // Check object cache configuration
switch ($this->options['cache_provider']) { if (isset($this->options['cache_storage'])
case 'none': && !($this->options['cache_storage'] instanceof CacheStorageInterface)
// no caching && !($this->options['cache_storage'] instanceof CacheItemPoolInterface)
$this->cache = null; && !($this->options['cache_storage'] instanceof CacheInterface)
break; ) {
case 'redis': throw new InvalidSDKConfigurationException('Invalid cache_storage specified; must be an instance of Kevinrob\GuzzleCache\CacheStorageInterface, Psr\Cache\CacheItemPoolInterface or Psr\SimpleCache\CacheInterface.');
// caching with Redis }
$redis = new \Redis();
$redis->pconnect(
isset($this->options['cache_provider.redis.host']) ? $this->options['cache_provider.redis.host'] : '127.0.0.1',
isset($this->options['cache_provider.redis.port']) ? $this->options['cache_provider.redis.port'] : 6379);
$this->cache = new RedisCache(); if ($this->options['cache_storage'] instanceof CacheStorageInterface) {
$this->cache->setRedis($redis); $this->cache = $this->options['cache_storage'];
break; } elseif ($this->options['cache_storage'] instanceof CacheItemPoolInterface) {
case 'file': $this->cache = new Psr6CacheStorage($this->options['cache_storage']);
// caching on the filesystem } elseif ($this->options['cache_storage'] instanceof CacheInterface) {
$this->cache = new \Doctrine\Common\Cache\FilesystemCache( $this->cache = new Psr16CacheStorage($this->options['cache_storage']);
isset($this->options['cache_provider.file.directory']) ? $this->options['cache_provider.file.directory'] : sys_get_temp_dir()
);
break;
default:
throw new Exception('Valid values for cache_provider are: none, redis, file');
} }
// Set up logger // Set up logger
if (is_a($this->options['logger'], LoggerInterface::class)) if (is_a($this->options['logger'], LoggerInterface::class)) {
$this->setLogger($this->options['logger']); $this->setLogger($this->options['logger']);
}
// Set up handler stack // Set up handler stack
$stack = HandlerStack::create(); $stack = HandlerStack::create();
@@ -86,11 +88,14 @@ class ObjectRetriever implements CustomCacheAndLogInterface {
if (isset($this->cache)) { if (isset($this->cache)) {
$stack->push( $stack->push(
new CacheMiddleware( new CacheMiddleware(
new PrivateCacheStrategy( new PrivateCacheStrategy($this->cache)
new DoctrineCacheStorage($this->cache)
)
) )
); );
// We also use the cache for storing object descriptions and attachments
// without the HTTP request, so we create a dummy request; this way
// we can reuse GuzzleCache for maximum compatibility
$this->customCacheDefaultRequest = new Request('GET', '/');
} }
// Initialize client // Initialize client
@@ -107,23 +112,53 @@ class ObjectRetriever implements CustomCacheAndLogInterface {
$this->client = new Client($options); $this->client = new Client($options);
} }
/**
* Set a default reader for CloudObject instances.
*/
public function setDefaultReader(NodeReader $reader) : self {
$this->defaultReader = $reader;
return $this;
}
/**
* Get the default reader for CloudObject instances.
*/
public function getDefaultReader() : NodeReader {
return $this->defaultReader;
}
public function logInfoWithTime($message, $ts) { public function logInfoWithTime($message, $ts) {
if (isset($this->logger)) if (isset($this->logger))
$this->logger->info($message, [ 'elapsed_ms' => round((microtime(true) - $ts) * 1000) ]); $this->logger->info($message, [ 'elapsed_ms' => round((microtime(true) - $ts) * 1000) ]);
} }
private function getCacheKey($id) { public function getCacheKey($id) {
return $this->options['cache_prefix'] . $this->options['auth_ns'] . '/' . $id; return $this->options['cache_prefix'] . $this->options['auth_ns'] . '/' . $id;
} }
private function getFromCache($id) { private function getFromCache($id) {
return (isset($this->cache) && $this->cache->contains($this->getCacheKey($id))) if (!$this->cache) {
? $this->cache->fetch($this->getCacheKey($id)) : null; return null;
}
$cachedData = $this->cache->fetch($this->getCacheKey($id));
if (!$cachedData) {
return null;
}
return (string)$cachedData->getResponse()->getBody(true);
} }
private function putIntoCache($id, $data, $ttl) { private function putIntoCache($id, $data, $ttl) {
if (isset($this->cache)) if ($this->cache) {
$this->cache->save($this->getCacheKey($id), $data, $ttl); $entry = new CacheEntry($this->customCacheDefaultRequest,
new Response(200, [], $data),
((new DateTime)->modify('+'.$ttl.' seconds')));
$this->cache->save($this->getCacheKey($id), $entry, $ttl);
}
} }
public function getFromCacheCustom($id) { public function getFromCacheCustom($id) {
@@ -170,26 +205,57 @@ class ObjectRetriever implements CustomCacheAndLogInterface {
} }
/** /**
* Get an object description from CloudObjects. Attempts to get object * Get an object description and return a CloudObject.
* from in-memory cache first, stored static configurations next, */
* configured external cache third, and finally calls the Object API public function getCloudObject(IRI $coid) : ?CloudObject {
* on CloudObjects Core. Returns null if the object was not found. $node = $this->getObjectNode($coid);
if (!$node) {
// Object not found
return null;
}
$object = (new CloudObject($coid, $node))
->setObjectRetriever($this);
if ($this->defaultReader) {
// Initialize CloudObject with default reader if it is set
$object->setReader($this->defaultReader);
}
return $object;
}
/**
* Get an object description and return a node.
*
* @deprecated Use getObjectNode() instead
*/
public function getObject(IRI $coid) {
return $this->getObjectNode($coid);
}
/**
* Get an object description from CloudObjects and return a node.
* *
* @param IRI $coid COID of the object * @param IRI $coid COID of the object
* @return Node|null * @return Node|null
*/ */
public function getObject(IRI $coid) { public function getObjectNode(IRI $coid) : ?Node {
if (!COIDParser::isValidCOID($coid)) if (!COIDParser::isValidCOID($coid))
throw new Exception("Not a valid COID."); throw new SDKException("Not a valid COID.");
$uriString = (string)$coid; $uriString = (string)$coid;
if (isset($this->objects[$uriString])) $this->removeExpiredFromInMemoryCache();
// Return from in-memory cache if it exists
return $this->objects[$uriString];
$ts = microtime(true); $ts = microtime(true);
if (isset($this->objects[$uriString])) {
// Return from in-memory cache if it exists
$this->logInfoWithTime('Returning previous node for <'.$uriString.'>.', $ts);
return $this->objects[$uriString][1];
}
if (isset($this->options['static_config_path'])) { if (isset($this->options['static_config_path'])) {
$location = realpath($this->options['static_config_path'].DIRECTORY_SEPARATOR. $location = realpath($this->options['static_config_path'].DIRECTORY_SEPARATOR.
$coid->getHost().str_replace('/', DIRECTORY_SEPARATOR, $coid->getPath()) $coid->getHost().str_replace('/', DIRECTORY_SEPARATOR, $coid->getPath())
@@ -217,17 +283,30 @@ class ObjectRetriever implements CustomCacheAndLogInterface {
$this->putIntoCache($uriString, $object, $this->options['cache_ttl']); $this->putIntoCache($uriString, $object, $this->options['cache_ttl']);
$this->logInfoWithTime('Fetched <'.$uriString.'> from Core API ['.$response->getStatusCode().'].', $ts); $this->logInfoWithTime('Fetched <'.$uriString.'> from Core API ['.$response->getStatusCode().'].', $ts);
} catch (RequestException $e) { } catch (RequestException $e) {
if ($e->hasResponse()) if ($e->hasResponse() && $e->getResponse()->getStatusCode() == 404) {
$this->logInfoWithTime('Object <'.$uriString.'> not found in Core API ['.$e->getResponse()->getStatusCode().'].', $ts); // not found
else $this->logInfoWithTime('Object <'.$uriString.'> not found in Core API.', $ts);
$this->logInfoWithTime('Object <'.$uriString.'> could not be retrieved from Core API.', $ts);
return null; return null;
} else
if ($e->hasResponse()) {
// other API error
$this->logInfoWithTime('Object <'.$uriString.'> could not be retrieved from Core API ['.$e->getResponse()->getStatusCode().'].', $ts);
throw new CoreAPIException($e->getMessage(), $e);
} else {
// API error without response
$this->logInfoWithTime('Object <'.$uriString.'> could not be retrieved from Core API (no status code).', $ts);
throw new CoreAPIException($e->getMessage(), $e);
}
} catch (Exception $e) {
// other exceptions
$this->logInfoWithTime('Object <'.$uriString.'> could not be retrieved from Core API (unexpected exception).', $ts);
throw new CoreAPIException($e->getMessage(), $e);
} }
} }
$document = JsonLD::getDocument($object); $document = JsonLD::getDocument($object);
$this->objects[$uriString] = $document->getGraph()->getNode($uriString); $this->objects[$uriString] = [ time(), $document->getGraph()->getNode($uriString) ];
return $this->objects[$uriString]; return $this->objects[$uriString][1];
} }
/** /**
@@ -243,7 +322,7 @@ class ObjectRetriever implements CustomCacheAndLogInterface {
*/ */
public function fetchObjectsInNamespaceWithType(IRI $namespaceCoid, $type) { public function fetchObjectsInNamespaceWithType(IRI $namespaceCoid, $type) {
if (COIDParser::getType($namespaceCoid) != COIDParser::COID_ROOT) if (COIDParser::getType($namespaceCoid) != COIDParser::COID_ROOT)
throw new Exception("Not a valid namespace COID."); throw new SDKException("Not a valid namespace COID.");
$ts = microtime(true); $ts = microtime(true);
$type = (string)$type; $type = (string)$type;
@@ -264,14 +343,14 @@ class ObjectRetriever implements CustomCacheAndLogInterface {
if (!COIDParser::isValidCOID($iri)) continue; if (!COIDParser::isValidCOID($iri)) continue;
if ($iri->getHost() != $namespaceCoid->getHost()) continue; if ($iri->getHost() != $namespaceCoid->getHost()) continue;
$this->objects[$object->getId()] = $object; $this->objects[$object->getId()] = [ time(), $object ];
$this->putIntoCache($object->getId(), $object, $this->options['cache_ttl']); $this->putIntoCache($object->getId(), $object, $this->options['cache_ttl']);
$allIris[] = $iri; $allIris[] = $iri;
} }
$this->logInfoWithTime('Fetched all objects with <'.$type.'> for <'.$namespaceCoid->getHost().'> from Core API ['.$response->getStatusCode().'].', $ts); $this->logInfoWithTime('Fetched all objects with <'.$type.'> for <'.$namespaceCoid->getHost().'> from Core API ['.$response->getStatusCode().'].', $ts);
} catch (Exception $e) { } catch (Exception $e) {
throw new CoreAPIException; throw new CoreAPIException($e->getMessage(), $e);
} }
return $allIris; return $allIris;
@@ -288,7 +367,7 @@ class ObjectRetriever implements CustomCacheAndLogInterface {
*/ */
public function fetchAllObjectsInNamespace(IRI $namespaceCoid) { public function fetchAllObjectsInNamespace(IRI $namespaceCoid) {
if (COIDParser::getType($namespaceCoid) != COIDParser::COID_ROOT) if (COIDParser::getType($namespaceCoid) != COIDParser::COID_ROOT)
throw new Exception("Not a valid namespace COID."); throw new SDKException("Not a valid namespace COID.");
$ts = microtime(true); $ts = microtime(true);
@@ -305,7 +384,7 @@ class ObjectRetriever implements CustomCacheAndLogInterface {
if (!COIDParser::isValidCOID($iri)) continue; if (!COIDParser::isValidCOID($iri)) continue;
if ($iri->getHost() != $namespaceCoid->getHost()) continue; if ($iri->getHost() != $namespaceCoid->getHost()) continue;
$this->objects[$object->getId()] = $object; $this->objects[$object->getId()] = [ time(), $object ];
$this->putIntoCache($object->getId(), $object, $this->options['cache_ttl']); $this->putIntoCache($object->getId(), $object, $this->options['cache_ttl']);
$allIris[] = $iri; $allIris[] = $iri;
} }
@@ -313,7 +392,7 @@ class ObjectRetriever implements CustomCacheAndLogInterface {
$this->logInfoWithTime('Fetched all objects for <'.$namespaceCoid->getHost().'> from Core API ['.$response->getStatusCode().'].', $ts); $this->logInfoWithTime('Fetched all objects for <'.$namespaceCoid->getHost().'> from Core API ['.$response->getStatusCode().'].', $ts);
} catch (Exception $e) { } catch (Exception $e) {
throw new CoreAPIException; throw new CoreAPIException($e->getMessage(), $e);
} }
return $allIris; return $allIris;
@@ -329,7 +408,7 @@ class ObjectRetriever implements CustomCacheAndLogInterface {
*/ */
public function getCOIDListForNamespace(IRI $namespaceCoid) { public function getCOIDListForNamespace(IRI $namespaceCoid) {
if (COIDParser::getType($namespaceCoid) != COIDParser::COID_ROOT) if (COIDParser::getType($namespaceCoid) != COIDParser::COID_ROOT)
throw new Exception("Not a valid namespace COID."); throw new SDKException("Not a valid namespace COID.");
$ts = microtime(true); $ts = microtime(true);
@@ -347,7 +426,7 @@ class ObjectRetriever implements CustomCacheAndLogInterface {
$this->logInfoWithTime('Fetched object list for <'.$namespaceCoid->getHost().'> from Core API ['.$response->getStatusCode().'].', $ts); $this->logInfoWithTime('Fetched object list for <'.$namespaceCoid->getHost().'> from Core API ['.$response->getStatusCode().'].', $ts);
} catch (Exception $e) { } catch (Exception $e) {
throw new CoreAPIException; throw new CoreAPIException($e->getMessage(), $e);
} }
return $allIris; return $allIris;
@@ -364,7 +443,7 @@ class ObjectRetriever implements CustomCacheAndLogInterface {
*/ */
public function getCOIDListForNamespaceWithType(IRI $namespaceCoid, $type) { public function getCOIDListForNamespaceWithType(IRI $namespaceCoid, $type) {
if (COIDParser::getType($namespaceCoid) != COIDParser::COID_ROOT) if (COIDParser::getType($namespaceCoid) != COIDParser::COID_ROOT)
throw new Exception("Not a valid namespace COID."); throw new SDKException("Not a valid namespace COID.");
$ts = microtime(true); $ts = microtime(true);
$type = (string)$type; $type = (string)$type;
@@ -386,7 +465,7 @@ class ObjectRetriever implements CustomCacheAndLogInterface {
$this->logInfoWithTime('Fetched object list with <'.$type.'> for <'.$namespaceCoid->getHost().'> from Core API ['.$response->getStatusCode().'].', $ts); $this->logInfoWithTime('Fetched object list with <'.$type.'> for <'.$namespaceCoid->getHost().'> from Core API ['.$response->getStatusCode().'].', $ts);
} catch (Exception $e) { } catch (Exception $e) {
throw new CoreAPIException; throw new CoreAPIException($e->getMessage(), $e);
} }
return $allIris; return $allIris;
@@ -394,10 +473,11 @@ class ObjectRetriever implements CustomCacheAndLogInterface {
/** /**
* Get an object description from CloudObjects. Shorthand method for * Get an object description from CloudObjects. Shorthand method for
* "getObject" which allows passing the COID as string instead of IRI. * "getObjectNode" which allows passing the COID as string instead of IRI.
* *
* @param any $coid * @param any $coid
* @return Node|null * @return Node|null
* @deprecated Interface may change
*/ */
public function get($coid) { public function get($coid) {
if (is_string($coid)) if (is_string($coid))
@@ -406,7 +486,7 @@ class ObjectRetriever implements CustomCacheAndLogInterface {
if (is_object($coid) && get_class($coid)=='ML\IRI\IRI') if (is_object($coid) && get_class($coid)=='ML\IRI\IRI')
return $this->getObject($coid); return $this->getObject($coid);
throw new Exception('COID must be passed as a string or an IRI object.'); throw new SDKException('COID must be passed as a string or an IRI object.');
} }
/** /**
@@ -434,7 +514,7 @@ class ObjectRetriever implements CustomCacheAndLogInterface {
} }
if (!isset($fileData) if (!isset($fileData)
|| $fileRevision!=$object->getProperty(self::REVISION_PROPERTY)->getValue()) { || $fileRevision !== $object->getProperty(Constants::PROPERTY_REVISION)->getValue()) {
// Does not exist in cache or is outdated, fetch from CloudObjects // Does not exist in cache or is outdated, fetch from CloudObjects
try { try {
@@ -442,7 +522,7 @@ class ObjectRetriever implements CustomCacheAndLogInterface {
.'/'.basename($filename)); .'/'.basename($filename));
$fileContent = $response->getBody()->getContents(); $fileContent = $response->getBody()->getContents();
$fileData = $object->getProperty(self::REVISION_PROPERTY)->getValue().'#'.$fileContent; $fileData = $object->getProperty(Constants::PROPERTY_REVISION)->getValue().'#'.$fileContent;
$this->putIntoCache($cacheId, $fileData, 0); $this->putIntoCache($cacheId, $fileData, 0);
$this->logInfoWithTime('Fetched attachment <'.$filename.'> for <'.$object->getId().'> from Core API ['.$response->getStatusCode().'].', $ts); $this->logInfoWithTime('Fetched attachment <'.$filename.'> for <'.$object->getId().'> from Core API ['.$response->getStatusCode().'].', $ts);
@@ -456,21 +536,69 @@ class ObjectRetriever implements CustomCacheAndLogInterface {
return $fileContent; return $fileContent;
} }
/** private function assertAuthenticatingNamespaceAndGetId() : IRI {
* Retrieve the object that describes the namespace provided with the "auth_ns"
* configuration option.
*
* @return Node
*/
public function getAuthenticatingNamespaceObject() {
if (!isset($this->options['auth_ns'])) if (!isset($this->options['auth_ns']))
throw new Exception("Missing 'auth_ns' configuration option."); throw new InvalidSDKConfigurationException("Missing 'auth_ns' configuration option.");
$namespaceCoid = COIDParser::fromString($this->options['auth_ns']); $namespaceCoid = COIDParser::fromString($this->options['auth_ns']);
if (COIDParser::getType($namespaceCoid) != COIDParser::COID_ROOT) if (COIDParser::getType($namespaceCoid) != COIDParser::COID_ROOT)
throw new Exception("The 'auth_ns' configuration option is not a valid namespace/root COID."); throw new InvalidSDKConfigurationException("The 'auth_ns' configuration option is not a valid namespace/root COID.");
return $this->getObject($namespaceCoid); return $namespaceCoid;
}
/**
* Retrieve the object node that describes the namespace
* provided with the "auth_ns" configuration option.
*
* @deprecated Use getAuthenticatingNamespaceObjectNode() instead
* @return Node
*/
public function getAuthenticatingNamespaceObject() : ?Node {
return $this->getObject($this->assertAuthenticatingNamespaceAndGetId());
}
/**
* Retrieve the object node that describes the namespace
* provided with the "auth_ns" configuration option.
*
* @return Node
*/
public function getAuthenticatingNamespaceObjectNode() : ?Node {
return $this->getObject($this->assertAuthenticatingNamespaceAndGetId());
}
/**
* Retrieve the CloudObject that describes the namespace
* provided with the "auth_ns" configuration option.
*
* @return CloudObject
*/
public function getAuthenticatingNamespaceCloudObject() : ?CloudObject {
return $this->getCloudObject($this->assertAuthenticatingNamespaceAndGetId());
}
/**
* Remove expired objects from the in-memory cache. Does not affect external cache.
*/
public function removeExpiredFromInMemoryCache() {
$expiryThreshold = time() - $this->options['cache_ttl'];
foreach ($this->objects as $uriString => $entry) {
if ($entry[0] < $expiryThreshold) {
unset($this->objects[$uriString]);
}
}
return $this;
}
/**
* Remove all entries from the in-memory cache. Does not affect external cache.
*/
public function clearInMemoryCache() {
$this->objects = [];
return $this;
} }
} }
@@ -0,0 +1,80 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK;
use CloudObjects\SDK\Exceptions\SDKException;
use ML\IRI\IRI;
use ML\JsonLD\Node;
/**
* Static facade for ObjectRetriever. Call setObjectRetriever() once to
* initialize; all subsequent static calls are forwarded to that instance.
*/
class ObjectRetrieverFacade {
private static ?ObjectRetriever $instance = null;
public static function setObjectRetriever(ObjectRetriever $retriever) : void {
self::$instance = $retriever;
}
private static function getInstance() : ObjectRetriever {
if (self::$instance === null)
throw new SDKException('ObjectRetrieverFacade has not been initialized. Call setObjectRetriever() first.');
return self::$instance;
}
public static function setDefaultReader(NodeReader $reader) : ObjectRetriever {
return self::getInstance()->setDefaultReader($reader);
}
public static function getDefaultReader() : NodeReader {
return self::getInstance()->getDefaultReader();
}
public static function getClient() {
return self::getInstance()->getClient();
}
public static function getCloudObject(IRI $coid) : ?CloudObject {
return self::getInstance()->getCloudObject($coid);
}
public static function getObjectNode(IRI $coid) : ?Node {
return self::getInstance()->getObjectNode($coid);
}
public static function fetchObjectsInNamespaceWithType(IRI $namespaceCoid, $type) : array {
return self::getInstance()->fetchObjectsInNamespaceWithType($namespaceCoid, $type);
}
public static function fetchAllObjectsInNamespace(IRI $namespaceCoid) : array {
return self::getInstance()->fetchAllObjectsInNamespace($namespaceCoid);
}
public static function getCOIDListForNamespace(IRI $namespaceCoid) : array {
return self::getInstance()->getCOIDListForNamespace($namespaceCoid);
}
public static function getCOIDListForNamespaceWithType(IRI $namespaceCoid, $type) : array {
return self::getInstance()->getCOIDListForNamespaceWithType($namespaceCoid, $type);
}
public static function getAttachment(IRI $coid, $filename) {
return self::getInstance()->getAttachment($coid, $filename);
}
public static function getAuthenticatingNamespaceObjectNode() : ?Node {
return self::getInstance()->getAuthenticatingNamespaceObjectNode();
}
public static function getAuthenticatingNamespaceCloudObject() : ?CloudObject {
return self::getInstance()->getAuthenticatingNamespaceCloudObject();
}
}
@@ -0,0 +1,63 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK\TestHelpers;
use Psr\Log\LoggerInterface;
class InMemoryLogger implements LoggerInterface {
private $logs = [];
public function getLogs() : array {
return $this->logs;
}
public function getLastLogMessage() : string {
if (empty($this->logs)) {
return '';
}
return end($this->logs)['message'];
}
public function emergency(string|\Stringable $message, array $context = []): void {
$this->logs[] = [ 'level' => 'emergency', 'message' => (string)$message, 'context' => $context ];
}
public function alert(string|\Stringable $message, array $context = []): void {
$this->logs[] = [ 'level' => 'alert', 'message' => (string)$message, 'context' => $context ];
}
public function critical(string|\Stringable $message, array $context = []): void {
$this->logs[] = [ 'level' => 'critical', 'message' => (string)$message, 'context' => $context ];
}
public function error(string|\Stringable $message, array $context = []): void {
$this->logs[] = [ 'level' => 'error', 'message' => (string)$message, 'context' => $context ];
}
public function warning(string|\Stringable $message, array $context = []): void {
$this->logs[] = [ 'level' => 'warning', 'message' => (string)$message, 'context' => $context ];
}
public function notice(string|\Stringable $message, array $context = []): void {
$this->logs[] = [ 'level' => 'notice', 'message' => (string)$message, 'context' => $context ];
}
public function info(string|\Stringable $message, array $context = []): void {
$this->logs[] = [ 'level' => 'info', 'message' => (string)$message, 'context' => $context ];
}
public function debug(string|\Stringable $message, array $context = []): void {
$this->logs[] = [ 'level' => 'debug', 'message' => (string)$message, 'context' => $context ];
}
public function log($level, string|\Stringable $message, array $context = []): void {
$this->logs[] = [ 'level' => $level, 'message' => (string)$message, 'context' => $context ];
}
}
+8 -7
View File
@@ -14,7 +14,8 @@ use GuzzleHttp\Client, GuzzleHttp\HandlerStack, GuzzleHttp\Middleware;
use Psr\Http\Message\RequestInterface; use Psr\Http\Message\RequestInterface;
use CloudObjects\SDK\COIDParser, CloudObjects\SDK\ObjectRetriever; use CloudObjects\SDK\COIDParser, CloudObjects\SDK\ObjectRetriever;
use CloudObjects\SDK\Exceptions\InvalidObjectConfigurationException, use CloudObjects\SDK\Exceptions\InvalidObjectConfigurationException,
CloudObjects\SDK\Exceptions\CoreAPIException; CloudObjects\SDK\Exceptions\CoreAPIException,
CloudObjects\SDK\Exceptions\SDKException;
/** /**
* The APIClientFactory can be used to create a preconfigured Guzzle HTTP API client * The APIClientFactory can be used to create a preconfigured Guzzle HTTP API client
@@ -157,7 +158,7 @@ class APIClientFactory {
if ($this->reader->hasProperty($api, 'oauth2:hasAuthorizationServer')) { if ($this->reader->hasProperty($api, 'oauth2:hasAuthorizationServer')) {
// We have an authorization server for this endpoint/API // We have an authorization server for this endpoint/API
$authServerCoid = $this->reader->getFirstValueIRI($api, 'oauth2:hasAuthorizationServer'); $authServerCoid = $this->reader->getFirstValueIRI($api, 'oauth2:hasAuthorizationServer');
$authServerObject = $this->objectRetriever->getObject($authServerCoid); $authServerObject = $this->objectRetriever->getObjectNode($authServerCoid);
if (!isset($authServerObject)) if (!isset($authServerObject))
throw new InvalidObjectConfigurationException("Authorization server object <" throw new InvalidObjectConfigurationException("Authorization server object <"
. (string)$authServerCoid . "> not available."); . (string)$authServerCoid . "> not available.");
@@ -205,7 +206,7 @@ class APIClientFactory {
if ($this->reader->hasType($api, 'wa:GraphQLEndpoint')) { if ($this->reader->hasType($api, 'wa:GraphQLEndpoint')) {
if (!class_exists('GraphQL\Client')) if (!class_exists('GraphQL\Client'))
throw new Exception("Install the gmostafa/php-graphql-client package to retrieve a specific client for wa:GraphQLEndpoint objects."); throw new SDKException("Install the gmostafa/php-graphql-client package to retrieve a specific client for wa:GraphQLEndpoint objects.");
return new \GraphQL\Client($clientConfig['base_uri'], return new \GraphQL\Client($clientConfig['base_uri'],
isset($clientConfig['headers']) ? $clientConfig['headers'] : []); isset($clientConfig['headers']) ? $clientConfig['headers'] : []);
@@ -217,11 +218,11 @@ class APIClientFactory {
* @param ObjectRetriever $objectRetriever An initialized and authenticated object retriever. * @param ObjectRetriever $objectRetriever An initialized and authenticated object retriever.
* @param IRI|null $namespaceCoid The namespace of the API client. Used to retrieve credentials. If this parameter is not provided, the namespace provided with the "auth_ns" configuration option from the object retriever is used. * @param IRI|null $namespaceCoid The namespace of the API client. Used to retrieve credentials. If this parameter is not provided, the namespace provided with the "auth_ns" configuration option from the object retriever is used.
*/ */
public function __construct(ObjectRetriever $objectRetriever, IRI $namespaceCoid = null) { public function __construct(ObjectRetriever $objectRetriever, ?IRI $namespaceCoid = null) {
$this->objectRetriever = $objectRetriever; $this->objectRetriever = $objectRetriever;
$this->namespace = isset($namespaceCoid) $this->namespace = isset($namespaceCoid)
? $objectRetriever->getObject($namespaceCoid) ? $objectRetriever->getObjectNode($namespaceCoid)
: $objectRetriever->getAuthenticatingNamespaceObject(); : $objectRetriever->getAuthenticatingNamespaceObjectNode();
$this->reader = new NodeReader([ $this->reader = new NodeReader([
'prefixes' => [ 'prefixes' => [
@@ -242,7 +243,7 @@ class APIClientFactory {
public function getClientWithCOID(IRI $apiCoid, bool $specificClient = false) { public function getClientWithCOID(IRI $apiCoid, bool $specificClient = false) {
$idString = (string)$apiCoid.(string)$specificClient; $idString = (string)$apiCoid.(string)$specificClient;
if (!isset($this->apiClients[$idString])) { if (!isset($this->apiClients[$idString])) {
$object = $this->objectRetriever->getObject($apiCoid); $object = $this->objectRetriever->getObjectNode($apiCoid);
if (!isset($object)) if (!isset($object))
throw new CoreAPIException("Could not retrieve API <".(string)$apiCoid.">."); throw new CoreAPIException("Could not retrieve API <".(string)$apiCoid.">.");
$this->apiClients[$idString] = $this->createClient($object, $specificClient); $this->apiClients[$idString] = $this->createClient($object, $specificClient);
@@ -6,9 +6,11 @@
namespace CloudObjects\SDK\WebAPI\Exceptions; namespace CloudObjects\SDK\WebAPI\Exceptions;
use CloudObjects\SDK\Exceptions\SDKException;
/** /**
* An Exception that is thrown when an an OAuth flow failed. * An Exception that is thrown when an an OAuth flow failed.
*/ */
class OAuthFlowException extends \Exception { class OAuthFlowException extends SDKException {
} }
+2 -2
View File
@@ -6,7 +6,7 @@
namespace CloudObjects\SDK\WebAPI; namespace CloudObjects\SDK\WebAPI;
use Exception; use CloudObjects\SDK\Exceptions\SDKException;
use ML\JsonLD\Node; use ML\JsonLD\Node;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use Webmozart\Assert\Assert, use Webmozart\Assert\Assert,
@@ -114,7 +114,7 @@ class OAuth2AuthServer {
// no additional params needed // no additional params needed
break; break;
default: default:
throw new Exception("No flow/grant_type found."); throw new SDKException("No flow/grant_type found.");
} }
$grantCacheKey = sha1(json_encode($params)); $grantCacheKey = sha1(json_encode($params));
+49 -42
View File
@@ -2,66 +2,73 @@
[![Latest Stable Version](https://poser.pugx.org/cloudobjects/sdk/v/stable)](https://packagist.org/packages/cloudobjects/sdk) [![Total Downloads](https://poser.pugx.org/cloudobjects/sdk/downloads)](https://packagist.org/packages/cloudobjects/sdk) [![Latest Stable Version](https://poser.pugx.org/cloudobjects/sdk/v/stable)](https://packagist.org/packages/cloudobjects/sdk) [![Total Downloads](https://poser.pugx.org/cloudobjects/sdk/downloads)](https://packagist.org/packages/cloudobjects/sdk)
[![buddy branch](https://app.buddy.works/cloudobjects/php-sdk/repository/branch/main/badge.svg?token=52ae28bf71dbbd3dde018f3f3e7caafa04f95bdb451b3dfca547414ec7a01739 "buddy branch")](https://app.buddy.works/cloudobjects/php-sdk/repository/branch/main) [![buddy pipeline](https://eu.buddy.works/cloudobjects/php-sdk/pipelines/pipeline/213082/badge.svg?token=4899fb24460ff689fc1971e7c46fd9a2c1d00781b2ff19bf1c00f814123b93e2 "buddy pipeline")](https://eu.buddy.works/cloudobjects/php-sdk/pipelines/pipeline/213082)
The CloudObjects PHP SDK provides simple access to [CloudObjects](https://cloudobjects.io/) from PHP-based applications. It wraps the [Object API](https://coid.link/cloudobjects.io/ObjectAPI/1.0) to fetch objects from the CloudObjects Core database and provides object-based access to their RDF description. A two-tiered caching mechanism (in-memory and Doctrine cache drivers) is included. The SDK also contains a helper class to validate COIDs.
## Installation ## Installation
The SDK is [distributed through packagist](https://packagist.org/packages/cloudobjects/sdk). Add `cloudobjects/sdk` to the `require` section of your `composer.json`, like this: The SDK is [distributed through packagist](https://packagist.org/packages/cloudobjects/sdk).
````json Install with the following command:
{
"require": {
"cloudobjects/sdk" : ">=0.7"
}
}
````
## Retrieving Objects ```
composer require cloudobjects/sdk
```
In order to retrieve objects from the CloudObjects Core database you need to create an instance of `CloudObjects\SDK\ObjectRetriever`. Then you can call `getObject()`. This method returns an `ML\JsonLD\Node` instance or `null` if the object is not found. You can use the object interface of the [JsonLD library](https://github.com/lanthaler/JsonLD/) to read the information from the object. ## Usage
Here's a simple example: The primary purpose of the SDK is the retrieval of object descriptions from the CloudObjects Core Object API. To do so, you create an instance of the `CloudObjects\SDK\ObjectRetriever` class. You can use this class directy, but you can also use the static facade (`CloudObjects\SDK\ObjectRetrieverFacade`) or the global function `cloudobjects_get_object()`, depending on the architecture of your PHP application and your preferred coding style. You can use the high-level `CloudObjects\SDK\CloudObject` interface or the lower-level `ML\JsonLD\Node` interface to work with the parsed JSON-LD representation of the object.
````php ### Using the ObjectRetriever directly
use ML\IRI\IRI;
```php
use CloudObjects\SDK\ObjectRetriever; use CloudObjects\SDK\ObjectRetriever;
use ML\IRI\IRI;
/* ... */ $retriever = new ObjectRetriever;
$object = $retriever->getCloudObject(new IRI('coid://cloudobjects.io'));
echo $object->getLabel();
```
$retriever = new ObjectRetriever(); ### Using the static facade
$object = $this->retriever->getObject(new IRI('coid://cloudobjects.io'));
if (isset($object))
echo $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue();
else
echo "Object not found.";
````
### Configuration ```php
use CloudObjects\SDK\ObjectRetriever;
use CloudObjects\SDK\ObjectRetrieverFacade;
use ML\IRI\IRI;
You can pass an array of configuration options to the ObjectRetriever's constructor: ObjectRetrieverFacade::setObjectRetriever(new ObjectRetriever());
| Option | Description | Default | $object = ObjectRetrieverFacade::getCloudObject(new IRI('coid://cloudobjects.io'));
|---|---|---| echo $object->getLabel();
| `cache_provider` | The type of cache used. Currently supports `redis`, `file` and `none`. | `none` | ```
| `cache_prefix` | A prefix used for cache IDs. Normally this should not be set but might be necessary on shared caches. | `clobj:` |
| `cache_ttl` | Determines how long objects can remain cached. | `60` |
| `auth_ns` | The namespace of the service that this retriever acts for. If not set the API is accessed anonymously. | `null` |
| `auth_secret` | The shared secret between the namespace in `auth_ns` and `cloudobjects.io` for authenticated. If not set the API is accessed anonymously. | `null` |
#### For `redis` cache: ### Using the global function
| Option | Description | Default | The global function uses the static facade, so the facade must be initialized first.
|---|---|---|
| `cache_provider.redis.host` | The hostname or IP of the Redis instance. | `127.0.0.1` |
| `cache_provider.redis.port` | The port number of the Redis instance. | `6379` |
#### For `file` cache: ```php
use CloudObjects\SDK\ObjectRetriever;
use CloudObjects\SDK\ObjectRetrieverFacade;
| Option | Description | Default | ObjectRetrieverFacade::setObjectRetriever(new ObjectRetriever());
|---|---|---|
| `cache_provider.file.directory` | The directory to store cache data in. | The system's temporary directory. | $object = cloudobjects_get_object('coid://cloudobjects.io');
echo $object->getLabel();
```
### Working with the `CloudObject` interface
```php
$object = $retriever->getCloudObject(new IRI('coid://cloudobjects.io'));
echo $object->getString('http://www.w3.org/2000/01/rdf-schema#comment');
```
### Working with the `Node` interface
```php
$node = $retriever->getObjectNode(new IRI('coid://cloudobjects.io'));
echo $node->getProperty('http://www.w3.org/2000/01/rdf-schema#comment')->getValue();
```
## License ## License
+17 -14
View File
@@ -2,16 +2,16 @@
"name": "cloudobjects/sdk", "name": "cloudobjects/sdk",
"description": "CloudObjects SDK for PHP for working with COIDs and object descriptions from CloudObjects.", "description": "CloudObjects SDK for PHP for working with COIDs and object descriptions from CloudObjects.",
"keywords": ["cloudobjects", "sdk"], "keywords": ["cloudobjects", "sdk"],
"homepage": "https://github.com/CloudObjects/CloudObjects-PHP-SDK", "homepage": "https://codeberg.org/CloudObjects/CloudObjects-PHP-SDK",
"license": "MPL-2.0", "license": "MPL-2.0",
"require" : { "require" : {
"ml/json-ld": ">=1.0.7", "sweetrdf/json-ld": ">=1.3",
"doctrine/common" : ">=2.6.1",
"doctrine/cache" : "1.*",
"guzzlehttp/guzzle" : ">=6.0", "guzzlehttp/guzzle" : ">=6.0",
"psr/cache": ">=1.0",
"psr/log": ">=1.1", "psr/log": ">=1.1",
"kevinrob/guzzle-cache-middleware": "^3.2", "kevinrob/guzzle-cache-middleware": "^7.0.0",
"webmozart/assert": "^1.6" "webmozart/assert": "^1.6",
"psr/simple-cache": "^3.0"
}, },
"authors": [ "authors": [
{ {
@@ -19,21 +19,24 @@
} }
], ],
"autoload": { "autoload": {
"psr-0": { "psr-4": {
"CloudObjects\\SDK" : "" "CloudObjects\\SDK\\" : "CloudObjects/SDK/"
} },
"files": [
"functions.php"
]
}, },
"require-dev" : { "require-dev" : {
"phpunit/phpunit": ">=4.8.0,<5.0", "phpunit/phpunit": "^10",
"symfony/http-foundation" : ">=4.0", "symfony/http-foundation" : ">=4.0",
"symfony/psr-http-message-bridge" : ">=1.1.0", "symfony/psr-http-message-bridge" : ">=1.1.0",
"nyholm/psr7" : "~1.5.1", "nyholm/psr7" : "^1.5",
"defuse/php-encryption" : "^2.2" "defuse/php-encryption" : "^2.2"
}, },
"suggest" : { "suggest" : {
"symfony/http-foundation" : "Required to use parseSymfonyRequest() in AccountContext.", "symfony/http-foundation" : "Required to use fromSymfonyRequest() in AccountContext.",
"symfony/psr-http-message-bridge" : "Required to use parseSymfonyRequest() in AccountContext.", "symfony/psr-http-message-bridge" : "Required to use fromSymfonyRequest() in AccountContext.",
"nyholm/psr7" : "Required to use parseSymfonyRequest() in AccountContext.", "nyholm/psr7" : "Required to use fromSymfonyRequest() in AccountContext.",
"defuse/php-encryption": "Required to use CryptoHelper" "defuse/php-encryption": "Required to use CryptoHelper"
} }
} }
Generated
+1389 -1210
View File
File diff suppressed because it is too large. Load diff
+18
View File
@@ -0,0 +1,18 @@
## Caching
# Standard Caching
Object descriptions that are requested through the ObjectRetriever are fetched through the CloudObjects Core Object API. They are cached in-memory inside the class for a default of 60 seconds. This can be changed using the `cache_ttl` option, which takes a number of seconds.
This in-class memory obviously only works as long as the class is alive and prevents subsequent requests for the same object hitting the API, but the cache will not exist between multiple requests handled in different processes. For this purpose, an external cache should be added.
To add an external cache, provide a storage using the `cache_storage` interface. The storage must be a class that implements either of the following interfaces:
- `Kevinrob\GuzzleCache\Storage\CacheStorageInterface`
- `Psr\Cache\CacheItemPoolInterface`
- `Psr\SimpleCache\CacheInterface`
External storage is used twice:
- Storing the HTTP request according to the APIs caching headers.
- Storing the object data according to `cache_ttl`.
+23
View File
@@ -0,0 +1,23 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use ML\IRI\IRI;
use CloudObjects\SDK\CloudObject,
CloudObjects\SDK\ObjectRetrieverFacade;
if (!function_exists('cloudobjects_get_object')) {
/**
* Retrieve a CloudObject by COID. Accepts a COID as an IRI object or a string.
*/
function cloudobjects_get_object($coid) : ?CloudObject {
if (is_string($coid))
$coid = new IRI($coid);
elseif (!($coid instanceof IRI))
throw new InvalidArgumentException('COID must be a string or an IRI object.');
return ObjectRetrieverFacade::getCloudObject($coid);
}
}
-5
View File
@@ -1,13 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false" <phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true" colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false" processIsolation="false"
stopOnFailure="false" stopOnFailure="false"
syntaxCheck="false"
bootstrap="./tests/bootstrap.php"> bootstrap="./tests/bootstrap.php">
<testsuites> <testsuites>
<testsuite name="OfflineTests"> <testsuite name="OfflineTests">
+5
View File
@@ -0,0 +1,5 @@
docker run -d -v .:/root --name cloudobjects-sdk-test cloudobjects/php-build-base:8.4
docker exec -t cloudobjects-sdk-test bash -c "cd /root && composer install"
docker exec -t cloudobjects-sdk-test bash -c "cd /root && vendor/bin/phpunit"
docker stop cloudobjects-sdk-test
docker rm cloudobjects-sdk-test
@@ -8,7 +8,7 @@ namespace CloudObjects\SDK\AccountGateway;
use ML\IRI\IRI; use ML\IRI\IRI;
class AAUIDParserTest extends \PHPUnit_Framework_TestCase { class AAUIDParserTest extends \PHPUnit\Framework\TestCase {
public function testValidAccountAAUID() { public function testValidAccountAAUID() {
$aauid = new IRI('aauid:abcd1234abcd1234'); $aauid = new IRI('aauid:abcd1234abcd1234');
@@ -9,7 +9,7 @@ namespace CloudObjects\SDK\AccountGateway;
use GuzzleHttp\Psr7\Request as GuzzlePsrRequest; use GuzzleHttp\Psr7\Request as GuzzlePsrRequest;
use Symfony\Component\HttpFoundation\Request as SymfonyRequest; use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
class AccountContextParseTest extends \PHPUnit_Framework_TestCase { class AccountContextParseTest extends \PHPUnit\Framework\TestCase {
public function testParsePsrRequest() { public function testParsePsrRequest() {
$request = new GuzzlePsrRequest('GET', '/', [ $request = new GuzzlePsrRequest('GET', '/', [
@@ -8,11 +8,11 @@ namespace CloudObjects\SDK\AccountGateway;
use ML\IRI\IRI; use ML\IRI\IRI;
class AccountContextTest extends \PHPUnit_Framework_TestCase { class AccountContextTest extends \PHPUnit\Framework\TestCase {
private $context; private $context;
protected function setUp() { protected function setUp(): void {
$this->context = new AccountContext(new IRI('aauid:aaaabbbbccccdddd'), 'DUMMY'); $this->context = new AccountContext(new IRI('aauid:aaaabbbbccccdddd'), 'DUMMY');
} }
+22 -4
View File
@@ -8,18 +8,26 @@ namespace CloudObjects\SDK;
use ML\IRI\IRI; use ML\IRI\IRI;
class COIDParserTest extends \PHPUnit_Framework_TestCase { class COIDParserTest extends \PHPUnit\Framework\TestCase {
public function testRootCOID() { public function testRootCOID() {
$coid = new IRI('coid://example.com'); $coid = new IRI('coid://example.com');
$this->assertEquals(COIDParser::COID_ROOT, COIDParser::getType($coid)); $this->assertEquals(COIDParser::COID_ROOT, COIDParser::getType($coid));
$coid = new IRI('coid://subdomain.example.com');
$this->assertEquals(COIDParser::COID_ROOT, COIDParser::getType($coid));
$coid = new IRI('coid://anotherlevel.subdomain.example.com');
$this->assertEquals(COIDParser::COID_ROOT, COIDParser::getType($coid));
} }
public function testInvalidRootCOID() { public function testInvalidRootCOID() {
$coid = new IRI('coid://example'); $coid = new IRI('coid://example');
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid)); $this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
$coid = new IRI('coid://exämple.com'); $coid = new IRI('coid://exämple.com');
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid)); $this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
$coid = new IRI('coid://ex&mple.com'); $coid = new IRI('coid://ex&mple.com');
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid)); $this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
} }
@@ -27,34 +35,41 @@ class COIDParserTest extends \PHPUnit_Framework_TestCase {
public function testInvalidCOID() { public function testInvalidCOID() {
$coid = new IRI('http://example.com'); $coid = new IRI('http://example.com');
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid)); $this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
$coid = new IRI('example.com'); $coid = new IRI('example.com');
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid)); $this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
$coid = new IRI('COID://example.com'); $coid = new IRI('COID://example.com');
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid)); $this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
$coid = new IRI('Coid://example.com'); $coid = new IRI('Coid://example.com');
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid)); $this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
$coid = new IRI('coid://EXAMPLE.COM'); $coid = new IRI('coid://EXAMPLE.COM');
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid)); $this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
$coid = new IRI('coid://exAMPle.CoM'); $coid = new IRI('coid://exAMPle.CoM');
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid)); $this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
} }
public function testUnversionedCOID() { public function testUnversionedCOID() {
$coid = new IRI('coid://example.com/Example'); $coid = new IRI('coid://subdomain.example.com/Example');
$this->assertEquals(COIDParser::COID_UNVERSIONED, COIDParser::getType($coid)); $this->assertEquals(COIDParser::COID_UNVERSIONED, COIDParser::getType($coid));
} }
public function testInvalidUnversionedCOID() { public function testInvalidUnversionedCOID() {
$coid = new IRI('coid://example.com/Exümple'); $coid = new IRI('coid://example.com/Exümple');
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid)); $this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
$coid = new IRI('coid://example.com/Examp%e'); $coid = new IRI('coid://example.com/Examp%e');
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid)); $this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
} }
public function testVersionedCOID() { public function testVersionedCOID() {
$coid = new IRI('coid://example.com/Example/1.0'); $coid = new IRI('coid://anotherlevel.subdomain.example.com/Example/1.0');
$this->assertEquals(COIDParser::COID_VERSIONED, COIDParser::getType($coid)); $this->assertEquals(COIDParser::COID_VERSIONED, COIDParser::getType($coid));
$coid = new IRI('coid://example.com/Example/alpha');
$coid = new IRI('coid://subdomain.example.com/Example/alpha');
$this->assertEquals(COIDParser::COID_VERSIONED, COIDParser::getType($coid)); $this->assertEquals(COIDParser::COID_VERSIONED, COIDParser::getType($coid));
} }
@@ -66,8 +81,10 @@ class COIDParserTest extends \PHPUnit_Framework_TestCase {
public function testVersionWildcardCOID() { public function testVersionWildcardCOID() {
$coid = new IRI('coid://example.com/Example/^1.0'); $coid = new IRI('coid://example.com/Example/^1.0');
$this->assertEquals(COIDParser::COID_VERSION_WILDCARD, COIDParser::getType($coid)); $this->assertEquals(COIDParser::COID_VERSION_WILDCARD, COIDParser::getType($coid));
$coid = new IRI('coid://example.com/Example/~1.0'); $coid = new IRI('coid://example.com/Example/~1.0');
$this->assertEquals(COIDParser::COID_VERSION_WILDCARD, COIDParser::getType($coid)); $this->assertEquals(COIDParser::COID_VERSION_WILDCARD, COIDParser::getType($coid));
$coid = new IRI('coid://example.com/Example/1.*'); $coid = new IRI('coid://example.com/Example/1.*');
$this->assertEquals(COIDParser::COID_VERSION_WILDCARD, COIDParser::getType($coid)); $this->assertEquals(COIDParser::COID_VERSION_WILDCARD, COIDParser::getType($coid));
} }
@@ -75,6 +92,7 @@ class COIDParserTest extends \PHPUnit_Framework_TestCase {
public function testInvalidVersionWildcardCOID() { public function testInvalidVersionWildcardCOID() {
$coid = new IRI('coid://example.com/Example/^1.*'); $coid = new IRI('coid://example.com/Example/^1.*');
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid)); $this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
$coid = new IRI('coid://example.com/Example/1.a.*'); $coid = new IRI('coid://example.com/Example/1.a.*');
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid)); $this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
} }
@@ -11,7 +11,7 @@ use GuzzleHttp\Client, GuzzleHttp\Handler\MockHandler,
GuzzleHttp\HandlerStack, GuzzleHttp\Psr7\Response; GuzzleHttp\HandlerStack, GuzzleHttp\Psr7\Response;
use CloudObjects\SDK\ObjectRetriever; use CloudObjects\SDK\ObjectRetriever;
class CryptoHelperTest extends \PHPUnit_Framework_TestCase { class CryptoHelperTest extends \PHPUnit\Framework\TestCase {
private $retriever; private $retriever;
private $graph; private $graph;
@@ -22,7 +22,7 @@ class CryptoHelperTest extends \PHPUnit_Framework_TestCase {
$this->retriever->setClient(new Client(['handler' => $handler])); $this->retriever->setClient(new Client(['handler' => $handler]));
} }
public function setUp() { protected function setUp(): void {
$this->retriever = new ObjectRetriever([ $this->retriever = new ObjectRetriever([
'auth_ns' => 'test.cloudobjects.io', 'auth_ns' => 'test.cloudobjects.io',
'auth_secret' => 'TEST' 'auth_secret' => 'TEST'
+63
View File
@@ -0,0 +1,63 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK;
use ML\IRI\IRI;
use ReflectionProperty,
InvalidArgumentException;
class FunctionsTest extends \PHPUnit\Framework\TestCase {
protected function setUp(): void {
$prop = new ReflectionProperty(ObjectRetrieverFacade::class, 'instance');
$prop->setAccessible(true);
$prop->setValue(null, null);
}
private function makeMockRetrieverReturning(?CloudObject $object) : ObjectRetriever {
$mock = $this->createMock(ObjectRetriever::class);
$mock->method('getCloudObject')->willReturn($object);
return $mock;
}
public function testAcceptsIRI(): void {
$coid = new IRI('coid://cloudobjects.io');
$mockObject = $this->createMock(CloudObject::class);
$mockRetriever = $this->createMock(ObjectRetriever::class);
$mockRetriever->expects($this->once())
->method('getCloudObject')
->with($coid)
->willReturn($mockObject);
ObjectRetrieverFacade::setObjectRetriever($mockRetriever);
$this->assertSame($mockObject, cloudobjects_get_object($coid));
}
public function testConvertsStringToIRI(): void {
$mockObject = $this->createMock(CloudObject::class);
$mockRetriever = $this->createMock(ObjectRetriever::class);
$mockRetriever->expects($this->once())
->method('getCloudObject')
->with($this->callback(fn($arg) => $arg instanceof IRI && (string)$arg === 'coid://cloudobjects.io'))
->willReturn($mockObject);
ObjectRetrieverFacade::setObjectRetriever($mockRetriever);
$this->assertSame($mockObject, cloudobjects_get_object('coid://cloudobjects.io'));
}
public function testThrowsOnInvalidArgumentType(): void {
ObjectRetrieverFacade::setObjectRetriever($this->makeMockRetrieverReturning(null));
$this->expectException(InvalidArgumentException::class);
cloudobjects_get_object(42);
}
}
+17 -10
View File
@@ -10,12 +10,12 @@ use InvalidArgumentException;
use ML\JsonLD\JsonLD; use ML\JsonLD\JsonLD;
use CloudObjects\SDK\ObjectRetriever; use CloudObjects\SDK\ObjectRetriever;
class SchemaValidatorTest extends \PHPUnit_Framework_TestCase { class SchemaValidatorTest extends \PHPUnit\Framework\TestCase {
private $schemaValidator; private $schemaValidator;
private $graph; private $graph;
public function setUp() { protected function setUp(): void {
$this->schemaValidator = new SchemaValidator(new ObjectRetriever); $this->schemaValidator = new SchemaValidator(new ObjectRetriever);
$this->graph = JsonLD::getDocument('{}')->getGraph(); $this->graph = JsonLD::getDocument('{}')->getGraph();
} }
@@ -24,10 +24,11 @@ class SchemaValidatorTest extends \PHPUnit_Framework_TestCase {
$node = $this->graph->createNode(); $node = $this->graph->createNode();
$node->setType($this->graph->createNode('coid://json.co-n.net/String')); $node->setType($this->graph->createNode('coid://json.co-n.net/String'));
$this->schemaValidator->validateAgainstNode("Test", $node); $this->schemaValidator->validateAgainstNode("Test", $node);
$this->addToAssertionCount(1);
} }
public function testNotString() { public function testNotString() {
$this->setExpectedException(InvalidArgumentException::class); $this->expectException(InvalidArgumentException::class);
$node = $this->graph->createNode(); $node = $this->graph->createNode();
$node->setType($this->graph->createNode('coid://json.co-n.net/String')); $node->setType($this->graph->createNode('coid://json.co-n.net/String'));
@@ -38,10 +39,11 @@ class SchemaValidatorTest extends \PHPUnit_Framework_TestCase {
$node = $this->graph->createNode(); $node = $this->graph->createNode();
$node->setType($this->graph->createNode('coid://json.co-n.net/Number')); $node->setType($this->graph->createNode('coid://json.co-n.net/Number'));
$this->schemaValidator->validateAgainstNode(3.5, $node); $this->schemaValidator->validateAgainstNode(3.5, $node);
$this->addToAssertionCount(1);
} }
public function testNotNumber() { public function testNotNumber() {
$this->setExpectedException(InvalidArgumentException::class); $this->expectException(InvalidArgumentException::class);
$node = $this->graph->createNode(); $node = $this->graph->createNode();
$node->setType($this->graph->createNode('coid://json.co-n.net/Number')); $node->setType($this->graph->createNode('coid://json.co-n.net/Number'));
@@ -52,10 +54,11 @@ class SchemaValidatorTest extends \PHPUnit_Framework_TestCase {
$node = $this->graph->createNode(); $node = $this->graph->createNode();
$node->setType($this->graph->createNode('coid://json.co-n.net/Integer')); $node->setType($this->graph->createNode('coid://json.co-n.net/Integer'));
$this->schemaValidator->validateAgainstNode(12, $node); $this->schemaValidator->validateAgainstNode(12, $node);
$this->addToAssertionCount(1);
} }
public function testNotInteger() { public function testNotInteger() {
$this->setExpectedException(InvalidArgumentException::class); $this->expectException(InvalidArgumentException::class);
$node = $this->graph->createNode(); $node = $this->graph->createNode();
$node->setType($this->graph->createNode('coid://json.co-n.net/Integer')); $node->setType($this->graph->createNode('coid://json.co-n.net/Integer'));
@@ -66,10 +69,11 @@ class SchemaValidatorTest extends \PHPUnit_Framework_TestCase {
$node = $this->graph->createNode(); $node = $this->graph->createNode();
$node->setType($this->graph->createNode('coid://json.co-n.net/Array')); $node->setType($this->graph->createNode('coid://json.co-n.net/Array'));
$this->schemaValidator->validateAgainstNode([ 1, 2, "foo" ], $node); $this->schemaValidator->validateAgainstNode([ 1, 2, "foo" ], $node);
$this->addToAssertionCount(1);
} }
public function testNotArray() { public function testNotArray() {
$this->setExpectedException(InvalidArgumentException::class); $this->expectException(InvalidArgumentException::class);
$node = $this->graph->createNode(); $node = $this->graph->createNode();
$node->setType($this->graph->createNode('coid://json.co-n.net/Array')); $node->setType($this->graph->createNode('coid://json.co-n.net/Array'));
@@ -83,10 +87,11 @@ class SchemaValidatorTest extends \PHPUnit_Framework_TestCase {
'a' => 'A', 'a' => 'A',
'b' => 'B' 'b' => 'B'
], $node); ], $node);
$this->addToAssertionCount(1);
} }
public function testNotObject() { public function testNotObject() {
$this->setExpectedException(InvalidArgumentException::class); $this->expectException(InvalidArgumentException::class);
$node = $this->graph->createNode(); $node = $this->graph->createNode();
$node->setType($this->graph->createNode('coid://json.co-n.net/Object')); $node->setType($this->graph->createNode('coid://json.co-n.net/Object'));
@@ -105,10 +110,11 @@ class SchemaValidatorTest extends \PHPUnit_Framework_TestCase {
'a' => 'A', 'a' => 'A',
'b' => 'B' 'b' => 'B'
], $node); ], $node);
$this->addToAssertionCount(1);
} }
public function testObjectWithPropertyTypeError() { public function testObjectWithPropertyTypeError() {
$this->setExpectedException(InvalidArgumentException::class); $this->expectException(InvalidArgumentException::class);
$stringNode = $this->graph->createNode(); $stringNode = $this->graph->createNode();
$stringNode->setProperty('coid://json.co-n.net/hasKey', 'a'); $stringNode->setProperty('coid://json.co-n.net/hasKey', 'a');
@@ -136,10 +142,11 @@ class SchemaValidatorTest extends \PHPUnit_Framework_TestCase {
'a' => 'A', 'a' => 'A',
'b' => 'B' 'b' => 'B'
], $node); ], $node);
$this->addToAssertionCount(1);
} }
public function testObjectWithRequiredPropertyTypeError() { public function testObjectWithRequiredPropertyTypeError() {
$this->setExpectedException(InvalidArgumentException::class); $this->expectException(InvalidArgumentException::class);
$stringNode = $this->graph->createNode(); $stringNode = $this->graph->createNode();
$stringNode->setProperty('coid://json.co-n.net/hasKey', 'a'); $stringNode->setProperty('coid://json.co-n.net/hasKey', 'a');
@@ -156,7 +163,7 @@ class SchemaValidatorTest extends \PHPUnit_Framework_TestCase {
} }
public function testObjectWithRequiredPropertyMissing() { public function testObjectWithRequiredPropertyMissing() {
$this->setExpectedException(InvalidArgumentException::class); $this->expectException(InvalidArgumentException::class);
$stringNode = $this->graph->createNode(); $stringNode = $this->graph->createNode();
$stringNode->setProperty('coid://json.co-n.net/hasKey', 'a'); $stringNode->setProperty('coid://json.co-n.net/hasKey', 'a');
+58 -19
View File
@@ -7,10 +7,11 @@
namespace CloudObjects\SDK; namespace CloudObjects\SDK;
use ML\IRI\IRI; use ML\IRI\IRI;
use ML\JsonLD\Node;
use GuzzleHttp\Client, GuzzleHttp\Handler\MockHandler, use GuzzleHttp\Client, GuzzleHttp\Handler\MockHandler,
GuzzleHttp\HandlerStack, GuzzleHttp\Psr7\Response; GuzzleHttp\HandlerStack, GuzzleHttp\Psr7\Response;
class NodeReaderMockTest extends \PHPUnit_Framework_TestCase { class NodeReaderMockTest extends \PHPUnit\Framework\TestCase {
private $retriever; private $retriever;
private $reader; private $reader;
@@ -24,10 +25,11 @@ class NodeReaderMockTest extends \PHPUnit_Framework_TestCase {
private function useRootResourceMock() { private function useRootResourceMock() {
$this->setMockResponse(new Response(200, $this->setMockResponse(new Response(200,
['Content-Type' => 'application/ld+json'], ['Content-Type' => 'application/ld+json'],
'{"@context":{"co":"coid:\/\/cloudobjects.io\/","rdf":"http:\/\/www.w3.org\/1999\/02\/22-rdf-syntax-ns#","agws":"coid:\/\/aauid.net\/","rdfs":"http:\/\/www.w3.org\/2000\/01\/rdf-schema#"},"@id":"coid:\/\/cloudobjects.io","@type":["agws:Service","co:Namespace"],"co:isAtRevision":"6-fbea0c90b2c5e5300e4039ed99be9b2d","co:isVisibleTo":{"@id":"co:Public"},"co:recommendsPrefix":"co","co:wasUpdatedAt":{"@type":"http:\/\/www.w3.org\/2001\/XMLSchema#dateTime","@value":"2017-01-16T17:29:22+00:00"},"rdfs:comment":"The CloudObjects namespace defines the essential objects.","rdfs:label":"CloudObjects"}')); '{"@context":{"co":"coid:\/\/cloudobjects.io\/","rdf":"http:\/\/www.w3.org\/1999\/02\/22-rdf-syntax-ns#","agws":"coid:\/\/aauid.net\/","rdfs":"http:\/\/www.w3.org\/2000\/01\/rdf-schema#"},"@id":"coid:\/\/cloudobjects.io","@type":["agws:Service","co:Namespace"],"co:isAtRevision":"6-fbea0c90b2c5e5300e4039ed99be9b2d","co:isVisibleTo":{"@id":"co:Public"},"co:recommendsPrefix":"co","co:wasUpdatedAt":{"@type":"http:\/\/www.w3.org\/2001\/XMLSchema#dateTime","@value":"2017-01-16T17:29:22+00:00"},"rdfs:comment":"The CloudObjects namespace defines the essential objects.","rdfs:label":"CloudObjects"}'
));
} }
protected function setUp() { protected function setUp(): void {
$this->retriever = new ObjectRetriever; $this->retriever = new ObjectRetriever;
$this->reader = new NodeReader([ $this->reader = new NodeReader([
'prefixes' => [ 'prefixes' => [
@@ -35,12 +37,13 @@ class NodeReaderMockTest extends \PHPUnit_Framework_TestCase {
'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#' 'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#'
] ]
]); ]);
$this->retriever->setDefaultReader($this->reader);
} }
public function testHasType1() { public function testHasType1() {
$coid = new IRI('coid://cloudobjects.io'); $coid = new IRI('coid://cloudobjects.io');
$this->useRootResourceMock(); $this->useRootResourceMock();
$object = $this->retriever->getObject($coid); $object = $this->retriever->getObjectNode($coid);
$this->assertTrue($this->reader->hasType($object, 'coid://cloudobjects.io/Namespace')); $this->assertTrue($this->reader->hasType($object, 'coid://cloudobjects.io/Namespace'));
$this->assertTrue($this->reader->hasType($object, 'co:Namespace')); $this->assertTrue($this->reader->hasType($object, 'co:Namespace'));
@@ -51,7 +54,7 @@ class NodeReaderMockTest extends \PHPUnit_Framework_TestCase {
public function testHasPropertyValue1() { public function testHasPropertyValue1() {
$coid = new IRI('coid://cloudobjects.io'); $coid = new IRI('coid://cloudobjects.io');
$this->useRootResourceMock(); $this->useRootResourceMock();
$object = $this->retriever->getObject($coid); $object = $this->retriever->getObjectNode($coid);
$this->assertTrue($this->reader->hasPropertyValue($object, 'http://www.w3.org/2000/01/rdf-schema#label', 'CloudObjects')); $this->assertTrue($this->reader->hasPropertyValue($object, 'http://www.w3.org/2000/01/rdf-schema#label', 'CloudObjects'));
$this->assertTrue($this->reader->hasPropertyValue($object, 'rdfs:label', 'CloudObjects')); $this->assertTrue($this->reader->hasPropertyValue($object, 'rdfs:label', 'CloudObjects'));
@@ -60,7 +63,7 @@ class NodeReaderMockTest extends \PHPUnit_Framework_TestCase {
public function testGetFirstValueString1() { public function testGetFirstValueString1() {
$coid = new IRI('coid://cloudobjects.io'); $coid = new IRI('coid://cloudobjects.io');
$this->useRootResourceMock(); $this->useRootResourceMock();
$object = $this->retriever->getObject($coid); $object = $this->retriever->getObjectNode($coid);
$this->assertEquals('CloudObjects', $this->reader->getFirstValueString($object, 'http://www.w3.org/2000/01/rdf-schema#label')); $this->assertEquals('CloudObjects', $this->reader->getFirstValueString($object, 'http://www.w3.org/2000/01/rdf-schema#label'));
$this->assertEquals('CloudObjects', $this->reader->getFirstValueString($object, 'rdfs:label')); $this->assertEquals('CloudObjects', $this->reader->getFirstValueString($object, 'rdfs:label'));
@@ -70,36 +73,72 @@ class NodeReaderMockTest extends \PHPUnit_Framework_TestCase {
$this->assertEquals('theDefaultValue', $this->reader->getFirstValueString($object, 'coid://cloudobjects.io/makesTriplesVisibleTo', 'theDefaultValue')); $this->assertEquals('theDefaultValue', $this->reader->getFirstValueString($object, 'coid://cloudobjects.io/makesTriplesVisibleTo', 'theDefaultValue'));
$this->assertEquals('theDefaultValue', $this->reader->getFirstValueString($object, 'co:makesTriplesVisibleTo', 'theDefaultValue')); $this->assertEquals('theDefaultValue', $this->reader->getFirstValueString($object, 'co:makesTriplesVisibleTo', 'theDefaultValue'));
$object = $this->retriever->getCloudObject($coid);
$this->assertEquals('CloudObjects', $object->getString('http://www.w3.org/2000/01/rdf-schema#label'));
$this->assertEquals('CloudObjects', $object->getString('rdfs:label'));
$this->assertNull($object->getString('coid://cloudobjects.io/makesTriplesVisibleTo'));
$this->assertNull($object->getString('co:makesTriplesVisibleTo'));
$this->assertEquals('theDefaultValue', $object->getString('coid://cloudobjects.io/makesTriplesVisibleTo', 'theDefaultValue'));
$this->assertEquals('theDefaultValue', $object->getString('co:makesTriplesVisibleTo', 'theDefaultValue'));
} }
public function testGetFirstValueIRI1() { public function testGetFirstValueIRI1() {
$coid = new IRI('coid://cloudobjects.io'); $coid = new IRI('coid://cloudobjects.io');
$this->useRootResourceMock(); $this->useRootResourceMock();
$object = $this->retriever->getObject($coid); $object = $this->retriever->getObjectNode($coid);
$this->assertInstanceOf('ML\IRI\IRI', $this->reader->getFirstValueIRI($object, 'coid://cloudobjects.io/isVisibleTo')); $this->assertInstanceOf(IRI::class, $this->reader->getFirstValueIRI($object, 'coid://cloudobjects.io/isVisibleTo'));
$this->assertInstanceOf('ML\IRI\IRI', $this->reader->getFirstValueIRI($object, 'co:isVisibleTo')); $this->assertInstanceOf(IRI::class, $this->reader->getFirstValueIRI($object, 'co:isVisibleTo'));
$this->assertEquals(new IRI('coid://cloudobjects.io/Public'), $this->reader->getFirstValueIRI($object, 'coid://cloudobjects.io/isVisibleTo')); $this->assertEquals(new IRI('coid://cloudobjects.io/Public'), $this->reader->getFirstValueIRI($object, 'coid://cloudobjects.io/isVisibleTo'));
$this->assertEquals(new IRI('coid://cloudobjects.io/Public'), $this->reader->getFirstValueIRI($object, 'co:isVisibleTo')); $this->assertEquals(new IRI('coid://cloudobjects.io/Public'), $this->reader->getFirstValueIRI($object, 'co:isVisibleTo'));
$object = $this->retriever->getCloudObject($coid);
$this->assertInstanceOf(IRI::class, $object->getIRI('coid://cloudobjects.io/isVisibleTo'));
$this->assertInstanceOf(IRI::class, $object->getIRI('co:isVisibleTo'));
$this->assertEquals(new IRI('coid://cloudobjects.io/Public'), $object->getIRI('coid://cloudobjects.io/isVisibleTo'));
$this->assertEquals(new IRI('coid://cloudobjects.io/Public'), $object->getIRI('co:isVisibleTo'));
} }
public function testGetFirstValueNode1() { public function testGetFirstValueNode1() {
$coid = new IRI('coid://cloudobjects.io'); $coid = new IRI('coid://cloudobjects.io');
$this->useRootResourceMock(); $this->useRootResourceMock();
$object = $this->retriever->getObject($coid); $object = $this->retriever->getObjectNode($coid);
$this->assertInstanceOf('ML\JsonLD\Node', $this->reader->getFirstValueNode($object, 'coid://cloudobjects.io/isVisibleTo')); $this->assertInstanceOf(Node::class, $this->reader->getFirstValueNode($object, 'coid://cloudobjects.io/isVisibleTo'));
$this->assertInstanceOf('ML\JsonLD\Node', $this->reader->getFirstValueNode($object, 'co:isVisibleTo')); $this->assertInstanceOf(Node::class, $this->reader->getFirstValueNode($object, 'co:isVisibleTo'));
$this->assertEquals('coid://cloudobjects.io/Public', $this->reader->getFirstValueNode($object, 'coid://cloudobjects.io/isVisibleTo')->getId()); $this->assertEquals('coid://cloudobjects.io/Public', $this->reader->getFirstValueNode($object, 'coid://cloudobjects.io/isVisibleTo')->getId());
$this->assertEquals('coid://cloudobjects.io/Public', $this->reader->getFirstValueNode($object, 'co:isVisibleTo')->getId()); $this->assertEquals('coid://cloudobjects.io/Public', $this->reader->getFirstValueNode($object, 'co:isVisibleTo')->getId());
$object = $this->retriever->getCloudObject($coid);
$this->assertInstanceOf(Node::class, $object->getNode('coid://cloudobjects.io/isVisibleTo'));
$this->assertInstanceOf(Node::class, $object->getNode('co:isVisibleTo'));
$this->assertEquals('coid://cloudobjects.io/Public', $object->getNode('coid://cloudobjects.io/isVisibleTo')->getId());
$this->assertEquals('coid://cloudobjects.io/Public', $object->getNode('co:isVisibleTo')->getId());
}
public function testConstants() {
$coid = new IRI('coid://cloudobjects.io');
$this->useRootResourceMock();
$object = $this->retriever->getCloudObject($coid);
$this->assertEquals('6-fbea0c90b2c5e5300e4039ed99be9b2d', $object->getRevision());
$this->assertEquals('CloudObjects', $object->getLabel());
} }
public function testGetAllValuesString1() { public function testGetAllValuesString1() {
$coid = new IRI('coid://cloudobjects.io'); $coid = new IRI('coid://cloudobjects.io');
$this->useRootResourceMock(); $this->useRootResourceMock();
$object = $this->retriever->getObject($coid); $object = $this->retriever->getObjectNode($coid);
$this->assertCount(1, $this->reader->getAllValuesString($object, 'http://www.w3.org/2000/01/rdf-schema#label')); $this->assertCount(1, $this->reader->getAllValuesString($object, 'http://www.w3.org/2000/01/rdf-schema#label'));
$this->assertCount(1, $this->reader->getAllValuesString($object, 'rdfs:label')); $this->assertCount(1, $this->reader->getAllValuesString($object, 'rdfs:label'));
@@ -116,7 +155,7 @@ class NodeReaderMockTest extends \PHPUnit_Framework_TestCase {
public function testGetAllValuesIRI1() { public function testGetAllValuesIRI1() {
$coid = new IRI('coid://cloudobjects.io'); $coid = new IRI('coid://cloudobjects.io');
$this->useRootResourceMock(); $this->useRootResourceMock();
$object = $this->retriever->getObject($coid); $object = $this->retriever->getObjectNode($coid);
$this->assertCount(0, $this->reader->getAllValuesIRI($object, 'http://www.w3.org/2000/01/rdf-schema#label')); $this->assertCount(0, $this->reader->getAllValuesIRI($object, 'http://www.w3.org/2000/01/rdf-schema#label'));
$this->assertCount(0, $this->reader->getAllValuesIRI($object, 'rdfs:label')); $this->assertCount(0, $this->reader->getAllValuesIRI($object, 'rdfs:label'));
@@ -126,8 +165,8 @@ class NodeReaderMockTest extends \PHPUnit_Framework_TestCase {
$this->assertCount(2, $this->reader->getAllValuesIRI($object, '@type')); $this->assertCount(2, $this->reader->getAllValuesIRI($object, '@type'));
$this->assertInstanceOf('ML\IRI\IRI', $this->reader->getAllValuesIRI($object, 'coid://cloudobjects.io/isVisibleTo')[0]); $this->assertInstanceOf(IRI::class, $this->reader->getAllValuesIRI($object, 'coid://cloudobjects.io/isVisibleTo')[0]);
$this->assertInstanceOf('ML\IRI\IRI', $this->reader->getAllValuesIRI($object, 'co:isVisibleTo')[0]); $this->assertInstanceOf(IRI::class, $this->reader->getAllValuesIRI($object, 'co:isVisibleTo')[0]);
$this->assertEquals(new IRI('coid://cloudobjects.io/Public'), $this->reader->getAllValuesIRI($object, 'coid://cloudobjects.io/isVisibleTo')[0]); $this->assertEquals(new IRI('coid://cloudobjects.io/Public'), $this->reader->getAllValuesIRI($object, 'coid://cloudobjects.io/isVisibleTo')[0]);
$this->assertEquals(new IRI('coid://cloudobjects.io/Public'), $this->reader->getAllValuesIRI($object, 'co:isVisibleTo')[0]); $this->assertEquals(new IRI('coid://cloudobjects.io/Public'), $this->reader->getAllValuesIRI($object, 'co:isVisibleTo')[0]);
@@ -136,7 +175,7 @@ class NodeReaderMockTest extends \PHPUnit_Framework_TestCase {
public function testGetAllValuesNode1() { public function testGetAllValuesNode1() {
$coid = new IRI('coid://cloudobjects.io'); $coid = new IRI('coid://cloudobjects.io');
$this->useRootResourceMock(); $this->useRootResourceMock();
$object = $this->retriever->getObject($coid); $object = $this->retriever->getObjectNode($coid);
$this->assertCount(0, $this->reader->getAllValuesNode($object, 'http://www.w3.org/2000/01/rdf-schema#label')); $this->assertCount(0, $this->reader->getAllValuesNode($object, 'http://www.w3.org/2000/01/rdf-schema#label'));
$this->assertCount(0, $this->reader->getAllValuesNode($object, 'rdfs:label')); $this->assertCount(0, $this->reader->getAllValuesNode($object, 'rdfs:label'));
@@ -146,8 +185,8 @@ class NodeReaderMockTest extends \PHPUnit_Framework_TestCase {
$this->assertCount(2, $this->reader->getAllValuesNode($object, '@type')); $this->assertCount(2, $this->reader->getAllValuesNode($object, '@type'));
$this->assertInstanceOf('ML\JsonLD\Node', $this->reader->getAllValuesNode($object, 'coid://cloudobjects.io/isVisibleTo')[0]); $this->assertInstanceOf(Node::class, $this->reader->getAllValuesNode($object, 'coid://cloudobjects.io/isVisibleTo')[0]);
$this->assertInstanceOf('ML\JsonLD\Node', $this->reader->getAllValuesNode($object, 'co:isVisibleTo')[0]); $this->assertInstanceOf(Node::class, $this->reader->getAllValuesNode($object, 'co:isVisibleTo')[0]);
$this->assertEquals('coid://cloudobjects.io/Public', $this->reader->getAllValuesNode($object, 'coid://cloudobjects.io/isVisibleTo')[0]->getId()); $this->assertEquals('coid://cloudobjects.io/Public', $this->reader->getAllValuesNode($object, 'coid://cloudobjects.io/isVisibleTo')[0]->getId());
$this->assertEquals('coid://cloudobjects.io/Public', $this->reader->getAllValuesNode($object, 'co:isVisibleTo')[0]->getId()); $this->assertEquals('coid://cloudobjects.io/Public', $this->reader->getAllValuesNode($object, 'co:isVisibleTo')[0]->getId());
@@ -0,0 +1,76 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK;
use Exception;
use ML\IRI\IRI;
use ML\JsonLD\Node;
use ReflectionProperty;
class ObjectRetrieverFacadeTest extends \PHPUnit\Framework\TestCase {
protected function setUp(): void {
$prop = new ReflectionProperty(ObjectRetrieverFacade::class, 'instance');
$prop->setAccessible(true);
$prop->setValue(null, null);
}
public function testThrowsExceptionBeforeInitialization(): void {
$this->expectException(Exception::class);
ObjectRetrieverFacade::getObjectNode(new IRI('coid://cloudobjects.io'));
}
public function testForwardsGetObjectNodeToInstance(): void {
$coid = new IRI('coid://cloudobjects.io');
$mockNode = $this->createMock(Node::class);
$mockRetriever = $this->createMock(ObjectRetriever::class);
$mockRetriever->expects($this->once())
->method('getObjectNode')
->with($coid)
->willReturn($mockNode);
ObjectRetrieverFacade::setObjectRetriever($mockRetriever);
$this->assertSame($mockNode, ObjectRetrieverFacade::getObjectNode($coid));
}
public function testForwardsGetCloudObjectToInstance(): void {
$coid = new IRI('coid://cloudobjects.io');
$mockCloudObject = $this->createMock(CloudObject::class);
$mockRetriever = $this->createMock(ObjectRetriever::class);
$mockRetriever->expects($this->once())
->method('getCloudObject')
->with($coid)
->willReturn($mockCloudObject);
ObjectRetrieverFacade::setObjectRetriever($mockRetriever);
$this->assertSame($mockCloudObject, ObjectRetrieverFacade::getCloudObject($coid));
}
public function testReplacingRetrieverUsesNewInstance(): void {
$coid = new IRI('coid://cloudobjects.io');
$mockNode = $this->createMock(Node::class);
$firstRetriever = $this->createMock(ObjectRetriever::class);
$firstRetriever->expects($this->never())->method('getObjectNode');
$secondRetriever = $this->createMock(ObjectRetriever::class);
$secondRetriever->expects($this->once())
->method('getObjectNode')
->with($coid)
->willReturn($mockNode);
ObjectRetrieverFacade::setObjectRetriever($firstRetriever);
ObjectRetrieverFacade::setObjectRetriever($secondRetriever);
$this->assertSame($mockNode, ObjectRetrieverFacade::getObjectNode($coid));
}
}
+38 -5
View File
@@ -9,8 +9,9 @@ namespace CloudObjects\SDK;
use ML\IRI\IRI; use ML\IRI\IRI;
use GuzzleHttp\Client, GuzzleHttp\Handler\MockHandler, use GuzzleHttp\Client, GuzzleHttp\Handler\MockHandler,
GuzzleHttp\HandlerStack, GuzzleHttp\Psr7\Response; GuzzleHttp\HandlerStack, GuzzleHttp\Psr7\Response;
use CloudObjects\SDK\Exceptions\CoreAPIException;
class ObjectRetrieverMockTest extends \PHPUnit_Framework_TestCase { class ObjectRetrieverMockTest extends \PHPUnit\Framework\TestCase {
private $retriever; private $retriever;
@@ -20,21 +21,53 @@ class ObjectRetrieverMockTest extends \PHPUnit_Framework_TestCase {
$this->retriever->setClient(new Client(['handler' => $handler])); $this->retriever->setClient(new Client(['handler' => $handler]));
} }
protected function setUp() { protected function setUp(): void {
$this->retriever = new ObjectRetriever; $this->retriever = new ObjectRetriever;
} }
public function testGetRootResource() { public function testGetRootResource() {
$this->setMockResponse(new Response(200, $this->setMockResponse(new Response(200,
['Content-Type' => 'application/ld+json'], ['Content-Type' => 'application/ld+json'],
'{"@context":{"cloudobjects":"coid:\/\/cloudobjects.io\/","rdf":"http:\/\/www.w3.org\/1999\/02\/22-rdf-syntax-ns#","rdfs":"http:\/\/www.w3.org\/2000\/01\/rdf-schema#"},"@id":"coid:\/\/cloudobjects.io","@type":"cloudobjects:Namespace","cloudobjects:hasPublicListing":"true","cloudobjects:revision":"1-325baa62b76105f56dc09386f5a2ec91","rdfs:comment":"The CloudObjects namespace defines the essential objects.","rdfs:label":"CloudObjects"}')); '{"@context":{"cloudobjects":"coid:\/\/cloudobjects.io\/","rdf":"http:\/\/www.w3.org\/1999\/02\/22-rdf-syntax-ns#","rdfs":"http:\/\/www.w3.org\/2000\/01\/rdf-schema#"},"@id":"coid:\/\/cloudobjects.io","@type":"cloudobjects:Namespace","cloudobjects:hasPublicListing":"true","cloudobjects:revision":"1-325baa62b76105f56dc09386f5a2ec91","rdfs:comment":"The CloudObjects namespace defines the essential objects.","rdfs:label":"CloudObjects"}'
));
$coid = new IRI('coid://cloudobjects.io'); $coid = new IRI('coid://cloudobjects.io');
$object = $this->retriever->getObject($coid);
// Test node interface first
$object = $this->retriever->getObjectNode($coid);
$this->assertNotNull($object); $this->assertNotNull($object);
$this->assertEquals((string)$coid, $object->getID()); $this->assertEquals((string)$coid, $object->getId());
$this->assertNotNull($object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')); $this->assertNotNull($object->getProperty('http://www.w3.org/2000/01/rdf-schema#label'));
$this->assertEquals('CloudObjects', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue()); $this->assertEquals('CloudObjects', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue());
$this->assertNull($object->getProperty('urn:example:nonexistingvalue'));
// Test CloudObject interface
$object = $this->retriever->getCloudObject($coid);
$this->assertNotNull($object);
$this->assertEquals((string)$coid, $object->getAsNode()->getId());
$this->assertEquals($coid, $object->getCOID());
$this->assertNotNull($object->getString('http://www.w3.org/2000/01/rdf-schema#label'));
$this->assertEquals('CloudObjects', $object->getString('http://www.w3.org/2000/01/rdf-schema#label'));
$this->assertNull($object->getString('urn:example:nonexistingvalue'));
}
public function testGetNonExistingResource() {
$this->setMockResponse(new Response(404));
// should return "null"
$coid = new IRI('coid://nonexistingresource.example.com');
$object = $this->retriever->getObjectNode($coid);
$this->assertNull($object);
}
public function testWhenAPIThrowsError() {
$this->setMockResponse(new Response(500));
$coid = new IRI('coid://nonexistingresource.example.com');
$this->expectException(CoreAPIException::class);
$this->retriever->getObjectNode($coid);
} }
} }
@@ -10,11 +10,11 @@ use InvalidArgumentException;
use ML\IRI\IRI; use ML\IRI\IRI;
use CloudObjects\SDK\ObjectRetriever; use CloudObjects\SDK\ObjectRetriever;
class SchemaValidatorPublicTest extends \PHPUnit_Framework_TestCase { class SchemaValidatorPublicTest extends \PHPUnit\Framework\TestCase {
private $schemaValidator; private $schemaValidator;
public function setUp() { protected function setUp(): void {
$this->schemaValidator = new SchemaValidator(new ObjectRetriever); $this->schemaValidator = new SchemaValidator(new ObjectRetriever);
} }
@@ -24,10 +24,11 @@ class SchemaValidatorPublicTest extends \PHPUnit_Framework_TestCase {
'region' => 'Hessen', 'region' => 'Hessen',
'country-name' => 'Germany' 'country-name' => 'Germany'
], new IRI('coid://json.co-n.net/Address')); ], new IRI('coid://json.co-n.net/Address'));
$this->addToAssertionCount(1);
} }
public function testNotAddress() { public function testNotAddress() {
$this->setExpectedException(InvalidArgumentException::class); $this->expectException(InvalidArgumentException::class);
$this->schemaValidator->validateAgainstCOID([ $this->schemaValidator->validateAgainstCOID([
'region' => 'Hessen', 'region' => 'Hessen',
@@ -0,0 +1,54 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK;
use ML\IRI\IRI;
use Kevinrob\GuzzleCache\Storage\VolatileRuntimeStorage;
use CloudObjects\SDK\TestHelpers\InMemoryLogger;
class ObjectRetrieverCacheTest extends \PHPUnit\Framework\TestCase {
public function testCacheInRuntimeStorage() {
$cacheStorage = new VolatileRuntimeStorage;
$logger = new InMemoryLogger;
$coid = new IRI('coid://cloudobjects.io');
$retriever = new ObjectRetriever([
'cache_storage' => $cacheStorage,
'logger' => $logger
]);
$object1 = $retriever->getCloudObject($coid);
$this->assertNotNull($object1);
$this->assertNotNull($cacheStorage->fetch($retriever->getCacheKey((string)$coid)));
$this->assertStringContainsString('from Core API', $logger->getLastLogMessage());
// Get from same retriever, assert in-memory-cache
$object2 = $retriever->getCloudObject($coid);
$this->assertNotNull($object1);
$this->assertStringContainsString('previous', $logger->getLastLogMessage());
// Reinitialize retriever with same cache storage to verify that cache is used
$retriever = new ObjectRetriever([
'cache_storage' => $cacheStorage,
'logger' => $logger
]);
$object3 = $retriever->getCloudObject($coid);
$this->assertNotNull($object2);
$this->assertNotNull($cacheStorage->fetch($retriever->getCacheKey((string)$coid)));
$this->assertStringContainsString('from object cache', $logger->getLastLogMessage());
$this->assertEquals($object1->getRevision(), $object2->getRevision());
$this->assertEquals($object2->getRevision(), $object3->getRevision());
}
}
@@ -8,11 +8,11 @@ namespace CloudObjects\SDK;
use ML\IRI\IRI; use ML\IRI\IRI;
class ObjectRetrieverTest extends \PHPUnit_Framework_TestCase { class ObjectRetrieverPublicTest extends \PHPUnit\Framework\TestCase {
private $retriever; private $retriever;
protected function setUp() { protected function setUp(): void {
$this->retriever = new ObjectRetriever; $this->retriever = new ObjectRetriever;
} }
@@ -26,13 +26,25 @@ class ObjectRetrieverTest extends \PHPUnit_Framework_TestCase {
public function testGetRootObject() { public function testGetRootObject() {
$coid = new IRI('coid://cloudobjects.io'); $coid = new IRI('coid://cloudobjects.io');
$object = $this->retriever->getObject($coid); $object = $this->retriever->getObjectNode($coid);
$this->assertNotNull($object); $this->assertNotNull($object);
$this->assertEquals((string)$coid, $object->getID()); $this->assertEquals((string)$coid, $object->getId());
$this->assertNotNull($object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')); $this->assertNotNull($object->getProperty('http://www.w3.org/2000/01/rdf-schema#label'));
$this->assertEquals('CloudObjects', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue()); $this->assertEquals('CloudObjects', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue());
} }
public function testGetRelatedObject() {
$coid = new IRI('coid://cloudobjects.io');
$object = $this->retriever->getCloudObject($coid);
$this->assertNotNull($object);
$this->assertNotNull($object->getIRI('coid://cloudobjects.io/isVisibleTo'));
$this->assertEquals('coid://cloudobjects.io/Public', $object->getString('coid://cloudobjects.io/isVisibleTo'));
$relatedObject = $object->getCloudObject('coid://cloudobjects.io/isVisibleTo');
$this->assertNotNull($relatedObject);
$this->assertEquals('coid://cloudobjects.io/Public', (string)$relatedObject->getCOID());
}
public function testGetCOIDList() { public function testGetCOIDList() {
$coid = new IRI('coid://cloudobjects.io'); $coid = new IRI('coid://cloudobjects.io');
$list = $this->stringifyItems( $list = $this->stringifyItems(