Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fde9661c2c | ||
|
|
6f2fd40464 | ||
|
|
bcfeedc703 | ||
|
|
c4f5504fdf | ||
|
|
55419acb09 | ||
|
|
1f938cf19d | ||
|
|
29e59c2b3c | ||
|
|
d3741e03ba | ||
|
|
b3c810473f | ||
|
|
b9141326b1 | ||
|
|
60a6db97bc | ||
|
|
0295627da1 | ||
|
|
bb4a95e8de | ||
|
|
6193b450ac | ||
|
|
12ed032a31 | ||
|
|
70c52b8ad2 | ||
|
|
afafb1ac3d | ||
|
|
f749d4a05e | ||
|
|
62ad60f3df | ||
|
|
d52d7c3f2d | ||
|
|
19645edf45 | ||
|
|
2f511160f7 | ||
|
|
f9fabe838a | ||
|
|
ce77709d37 | ||
|
|
a7eb83be44 | ||
|
|
3653513818 | ||
|
|
58df9cb476 | ||
|
|
3c958dc5df | ||
|
|
993218f21d | ||
|
|
aa37259a21 | ||
|
|
69a16e64d1 | ||
|
|
cb03c811ef | ||
|
|
6f3c7338c7 | ||
|
|
da0ddd572e | ||
|
|
5f5a31df68 | ||
|
|
58585a6875 | ||
|
|
23f00b2374 | ||
|
|
9f339953fe | ||
|
|
d5d766cbf4 | ||
|
|
06df8b0be1 | ||
|
|
de355ff2f9 | ||
|
|
7d8ece0df1 | ||
|
|
1122b9faf5 | ||
|
|
0cb2655494 | ||
|
|
a9689d5a2b | ||
|
|
6cb8a9d603 | ||
|
|
4724c4988e | ||
|
|
bb738a1d79 | ||
|
|
fd029a79bf | ||
|
|
2954d9fc99 | ||
|
|
f29af2b664 | ||
|
|
5584f10462 | ||
|
|
f937f2b426 | ||
|
|
b127c3cba8 | ||
|
|
3039ddc2ec | ||
|
|
6d9ea6584d | ||
|
|
fde083f36f |
No files matched your search
@@ -5,3 +5,6 @@ cache
|
||||
.config
|
||||
.local
|
||||
*.phar
|
||||
.composer
|
||||
.phpunit*
|
||||
.bash_history
|
||||
@@ -0,0 +1 @@
|
||||
Testing: Run `sh run-docker-testsuite.sh` instead of trying to run phpUnit directly.
|
||||
@@ -13,6 +13,7 @@ use Nyholm\Psr7\Factory\Psr17Factory;
|
||||
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
|
||||
use GuzzleHttp\Client, GuzzleHttp\HandlerStack, GuzzleHttp\Middleware;
|
||||
use Psr\Http\Message\RequestInterface, Psr\Http\Message\ResponseInterface;
|
||||
use CloudObjects\SDK\Exceptions\SDKException;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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)
|
||||
throw new \Exception("Not a valid AAUID");
|
||||
throw new SDKException("Not a valid AAUID");
|
||||
|
||||
$this->aauid = $aauid;
|
||||
$this->accessToken = $accessToken;
|
||||
@@ -324,7 +325,7 @@ class AccountContext {
|
||||
*/
|
||||
public function setLogCode($logCode) {
|
||||
if (!$this->request) {
|
||||
throw new \Exception('Not in a request context.');
|
||||
throw new SDKException('Not in a request context.');
|
||||
}
|
||||
$this->logCode = $logCode;
|
||||
}
|
||||
|
||||
+131
-126
@@ -13,140 +13,145 @@ use ML\IRI\IRI;
|
||||
*/
|
||||
class COIDParser {
|
||||
|
||||
const COID_INVALID = 0;
|
||||
const COID_INVALID = 0;
|
||||
|
||||
const COID_ROOT = 1;
|
||||
const COID_UNVERSIONED = 2;
|
||||
const COID_VERSIONED = 3;
|
||||
const COID_VERSION_WILDCARD = 4;
|
||||
const COID_ROOT = 1;
|
||||
const COID_UNVERSIONED = 2;
|
||||
const COID_VERSIONED = 3;
|
||||
const COID_VERSION_WILDCARD = 4;
|
||||
|
||||
const REGEX_HOSTNAME = "/^([a-z0-9-]+\.)?[a-z0-9-]+\.[a-z]+$/";
|
||||
const REGEX_SEGMENT = "/^[A-Za-z-_0-9\.]+$/";
|
||||
const REGEX_VERSION_WILDCARD = "/^((\^|~)(\d+\.)?\d|(\d+\.){1,2}\*)$/";
|
||||
const REGEX_HOSTNAME = "/^([a-z0-9-]+\.)*[a-z0-9-]+\.[a-z]+$/";
|
||||
const REGEX_SEGMENT = "/^[A-Za-z-_0-9\.]+$/";
|
||||
const REGEX_VERSION_WILDCARD = "/^((\^|~)(\d+\.)?\d|(\d+\.){1,2}\*)$/";
|
||||
|
||||
/**
|
||||
* Creates a new IRI object representing a COID from a string.
|
||||
* Adds the "coid://" prefix if necessary and normalizes case.
|
||||
*
|
||||
* @param string $coidString A COID string.
|
||||
* @return IRI
|
||||
*/
|
||||
public static function fromString($coidString) {
|
||||
$coidPre = new IRI(
|
||||
(strtolower(substr($coidString, 0, 7))=='coid://') ? $coidString : 'coid://'.$coidString
|
||||
);
|
||||
// Normalize scheme and host segments to lower case
|
||||
return new IRI('coid://'.strtolower($coidPre->getHost()).$coidPre->getPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of a COID.
|
||||
*
|
||||
* @param IRI $coid
|
||||
* @return int|null
|
||||
*/
|
||||
public static function getType(IRI $coid) {
|
||||
if ($coid->getScheme()!='coid' || $coid->getHost()==''
|
||||
|| preg_match(self::REGEX_HOSTNAME, $coid->getHost()) != 1)
|
||||
return self::COID_INVALID;
|
||||
|
||||
if ($coid->getPath()=='' || $coid->getPath()=='/')
|
||||
return self::COID_ROOT;
|
||||
|
||||
$segments = explode('/', $coid->getPath());
|
||||
switch (count($segments)) {
|
||||
case 2:
|
||||
return (preg_match(self::REGEX_SEGMENT, $segments[1]) == 1)
|
||||
? self::COID_UNVERSIONED
|
||||
: self::COID_INVALID;
|
||||
case 3:
|
||||
if (preg_match(self::REGEX_SEGMENT, $segments[1]) != 1)
|
||||
return self::COID_INVALID;
|
||||
|
||||
if (preg_match(self::REGEX_SEGMENT, $segments[2]) == 1)
|
||||
return self::COID_VERSIONED;
|
||||
else
|
||||
if (preg_match(self::REGEX_VERSION_WILDCARD, $segments[2]) == 1)
|
||||
return self::COID_VERSION_WILDCARD;
|
||||
else
|
||||
return self::COID_INVALID;
|
||||
default:
|
||||
return self::COID_INVALID;
|
||||
/**
|
||||
* Creates a new IRI object representing a COID from a string.
|
||||
* Adds the "coid://" prefix if necessary and normalizes case.
|
||||
*
|
||||
* @param string $coidString A COID string.
|
||||
* @return IRI
|
||||
*/
|
||||
public static function fromString($coidString) : IRI {
|
||||
$coidPre = new IRI(
|
||||
(strtolower(substr($coidString, 0, 7)) == 'coid://') ? $coidString : 'coid://'.$coidString
|
||||
);
|
||||
// Normalize scheme and host segments to lower case
|
||||
// Keep query and fragment (will currently make those IRIs invalid as COIDs
|
||||
// but may be used in future)
|
||||
return new IRI('coid://'.strtolower($coidPre->getHost()).$coidPre->getPath()
|
||||
.($coidPre->getQuery() !== null ? '?'.$coidPre->getQuery() : '')
|
||||
.($coidPre->getFragment() !== null ? '#'.$coidPre->getFragment() : ''));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given IRI object is a valid COID.
|
||||
*
|
||||
* @param IRI $coid
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isValidCOID(IRI $coid) {
|
||||
return (self::getType($coid)!=self::COID_INVALID);
|
||||
}
|
||||
/**
|
||||
* Get the type of a COID.
|
||||
*
|
||||
* @param IRI $coid
|
||||
* @return int|null
|
||||
*/
|
||||
public static function getType(IRI $coid) : ?int {
|
||||
if ($coid->getScheme()!='coid' || $coid->getHost()==''
|
||||
|| preg_match(self::REGEX_HOSTNAME, $coid->getHost()) !== 1
|
||||
|| $coid->getQuery() !== null || $coid->getFragment() !== null)
|
||||
return self::COID_INVALID;
|
||||
|
||||
/**
|
||||
* Get the name segment of a valid COID or null if not available.
|
||||
*
|
||||
* @param IRI $coid
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getName(IRI $coid) {
|
||||
if (self::getType($coid)!=self::COID_INVALID
|
||||
&& self::getType($coid)!=self::COID_ROOT) {
|
||||
$segments = explode('/', $coid->getPath());
|
||||
return $segments[1];
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
if ($coid->getPath()=='' || $coid->getPath()=='/')
|
||||
return self::COID_ROOT;
|
||||
|
||||
/**
|
||||
* Get the version segment of a valid, versioned COID or null if not available.
|
||||
*
|
||||
* @param IRI $coid
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getVersion(IRI $coid) {
|
||||
if (self::getType($coid)==self::COID_VERSIONED) {
|
||||
$segments = explode('/', $coid->getPath());
|
||||
return $segments[2];
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
$segments = explode('/', $coid->getPath());
|
||||
switch (count($segments)) {
|
||||
case 2:
|
||||
return (preg_match(self::REGEX_SEGMENT, $segments[1]) == 1)
|
||||
? self::COID_UNVERSIONED
|
||||
: self::COID_INVALID;
|
||||
case 3:
|
||||
if (preg_match(self::REGEX_SEGMENT, $segments[1]) != 1)
|
||||
return self::COID_INVALID;
|
||||
|
||||
/**
|
||||
* Get the version segment of a versioned or version wildcard COID or
|
||||
* null if not available.
|
||||
*
|
||||
* @param IRI $coid
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getVersionWildcard(IRI $coid) {
|
||||
if (self::getType($coid)==self::COID_VERSION_WILDCARD) {
|
||||
$segments = explode('/', $coid->getPath());
|
||||
return $segments[2];
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the COID itself if it is a root COID or a new IRI object
|
||||
* representing the namespace underlying the given COID.
|
||||
*
|
||||
* @param IRI $coid
|
||||
* @return IRI|null
|
||||
*/
|
||||
public static function getNamespaceCOID(IRI $coid) {
|
||||
switch (self::getType($coid)) {
|
||||
case self::COID_ROOT:
|
||||
return $coid;
|
||||
case self::COID_UNVERSIONED:
|
||||
case self::COID_VERSIONED:
|
||||
case self::COID_VERSION_WILDCARD:
|
||||
return new IRI('coid://'.$coid->getHost());
|
||||
default:
|
||||
return null;
|
||||
if (preg_match(self::REGEX_SEGMENT, $segments[2]) == 1)
|
||||
return self::COID_VERSIONED;
|
||||
else
|
||||
if (preg_match(self::REGEX_VERSION_WILDCARD, $segments[2]) == 1)
|
||||
return self::COID_VERSION_WILDCARD;
|
||||
else
|
||||
return self::COID_INVALID;
|
||||
default:
|
||||
return self::COID_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given IRI object is a valid COID.
|
||||
*
|
||||
* @param IRI $coid
|
||||
* @return bool
|
||||
*/
|
||||
public static function isValidCOID(IRI $coid) : bool {
|
||||
return (self::getType($coid)!=self::COID_INVALID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name segment of a valid COID or null if not available.
|
||||
*
|
||||
* @param IRI $coid
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getName(IRI $coid) : ?string {
|
||||
if (self::getType($coid)!=self::COID_INVALID
|
||||
&& self::getType($coid)!=self::COID_ROOT) {
|
||||
$segments = explode('/', $coid->getPath());
|
||||
return $segments[1];
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version segment of a valid, versioned COID or null if not available.
|
||||
*
|
||||
* @param IRI $coid
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getVersion(IRI $coid) : ?string {
|
||||
if (self::getType($coid)==self::COID_VERSIONED) {
|
||||
$segments = explode('/', $coid->getPath());
|
||||
return $segments[2];
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version segment of a versioned or version wildcard COID or
|
||||
* null if not available.
|
||||
*
|
||||
* @param IRI $coid
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getVersionWildcard(IRI $coid) : ?string {
|
||||
if (self::getType($coid)==self::COID_VERSION_WILDCARD) {
|
||||
$segments = explode('/', $coid->getPath());
|
||||
return $segments[2];
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the COID itself if it is a root COID or a new IRI object
|
||||
* representing the namespace underlying the given COID.
|
||||
*
|
||||
* @param IRI $coid
|
||||
* @return IRI|null
|
||||
*/
|
||||
public static function getNamespaceCOID(IRI $coid) : ?IRI {
|
||||
switch (self::getType($coid)) {
|
||||
case self::COID_ROOT:
|
||||
return $coid;
|
||||
case self::COID_UNVERSIONED:
|
||||
case self::COID_VERSIONED:
|
||||
case self::COID_VERSION_WILDCARD:
|
||||
return new IRI('coid://'.$coid->getHost());
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace CloudObjects\SDK\Common;
|
||||
|
||||
use Exception;
|
||||
use CloudObjects\SDK\Exceptions\SDKException;
|
||||
use ML\IRI\IRI;
|
||||
use CloudObjects\SDK\NodeReader;
|
||||
use Defuse\Crypto\Key, Defuse\Crypto\Crypto;
|
||||
@@ -53,14 +53,14 @@ class CryptoHelper {
|
||||
* @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.
|
||||
*/
|
||||
public function __construct(ObjectRetriever $objectRetriever, IRI $namespaceCoid = null) {
|
||||
public function __construct(ObjectRetriever $objectRetriever, ?IRI $namespaceCoid = null) {
|
||||
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->namespace = isset($namespaceCoid)
|
||||
? $objectRetriever->getObject($namespaceCoid)
|
||||
: $objectRetriever->getAuthenticatingNamespaceObject();
|
||||
? $objectRetriever->getObjectNode($namespaceCoid)
|
||||
: $objectRetriever->getAuthenticatingNamespaceObjectNode();
|
||||
|
||||
$this->reader = new NodeReader([
|
||||
'prefixes' => [
|
||||
|
||||
@@ -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';
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?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;
|
||||
|
||||
interface CustomCacheAndLogInterface {
|
||||
|
||||
public function logInfoWithTime($message, $ts);
|
||||
|
||||
public function getFromCacheCustom($id);
|
||||
|
||||
public function putIntoCacheCustom($id, $data, $ttl);
|
||||
|
||||
}
|
||||
@@ -9,6 +9,14 @@ namespace CloudObjects\SDK\Exceptions;
|
||||
/**
|
||||
* 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
|
||||
* 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 {
|
||||
|
||||
}
|
||||
@@ -6,7 +6,8 @@
|
||||
|
||||
namespace CloudObjects\SDK\Helpers;
|
||||
|
||||
use Exception;
|
||||
use CloudObjects\SDK\Exceptions\SDKException;
|
||||
use CloudObjects\SDK\Exceptions\InvalidSDKConfigurationException;
|
||||
use CloudObjects\SDK\NodeReader, CloudObjects\SDK\ObjectRetriever;
|
||||
|
||||
/**
|
||||
@@ -28,19 +29,52 @@ class SDKLoader {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize and return the SDK with the given classname.
|
||||
* Throws Exception if the SDK is not supported.
|
||||
* Initialize the SDK with the given name and options; used for
|
||||
* SDKs that are initialized with a function and not a class name.
|
||||
*
|
||||
* @param $classname Classname for the SDK's main class
|
||||
* @param array $options Additional options for the SDK (if necessary)
|
||||
* @param string $sdkName The name of the SDK to initialize.
|
||||
* @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))
|
||||
throw new Exception("<".$classname."> is not a valid classname.");
|
||||
throw new SDKException("<".$classname."> is not a valid classname.");
|
||||
|
||||
$hashkey = md5($classname.serialize($options));
|
||||
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/) ---
|
||||
// has multiple classnames, so check for common superclass
|
||||
@@ -76,7 +110,7 @@ class SDKLoader {
|
||||
}
|
||||
|
||||
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;
|
||||
return $this->classes[$hashkey];
|
||||
|
||||
@@ -50,7 +50,7 @@ class SharedSecretAuthentication {
|
||||
return self::RESULT_INVALID_PASSWORD;
|
||||
|
||||
// Retrieve namespace
|
||||
$namespace = $retriever->getObject($namespaceCoid);
|
||||
$namespace = $retriever->getObjectNode($namespaceCoid);
|
||||
if (!isset($namespace))
|
||||
return self::RESULT_NAMESPACE_NOT_FOUND;
|
||||
|
||||
@@ -87,7 +87,7 @@ class SharedSecretAuthentication {
|
||||
return self::RESULT_INVALID_PASSWORD;
|
||||
|
||||
// Retrieve namespace
|
||||
$namespace = $this->objectRetriever->getObject($namespaceCoid);
|
||||
$namespace = $this->objectRetriever->getObjectNode($namespaceCoid);
|
||||
if (!isset($namespace))
|
||||
return self::RESULT_NAMESPACE_NOT_FOUND;
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ class SchemaValidator {
|
||||
* @param Node $node The COID of the specification.
|
||||
*/
|
||||
public function validateAgainstCOID($data, IRI $coid) {
|
||||
$object = $this->objectRetriever->getObject($coid);
|
||||
$object = $this->objectRetriever->getObjectNode($coid);
|
||||
Assert::true($this->reader->hasType($object, 'json:Element'),
|
||||
"You can only validate data against JSON elements!");
|
||||
$this->validateAgainstNode($data, $object);
|
||||
|
||||
@@ -38,7 +38,7 @@ class NodeReader {
|
||||
* @param string|object $type The type to check for.
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasType(Node $node = null, $type) {
|
||||
public function hasType(?Node $node, $type) {
|
||||
if (!isset($node))
|
||||
return false;
|
||||
$type = $this->expand($type);
|
||||
@@ -60,7 +60,7 @@ class NodeReader {
|
||||
return false;
|
||||
}
|
||||
|
||||
private function getFirstValue(Node $node = null, $property, $default = null) {
|
||||
private function getFirstValue(?Node $node, $property, $default = null) {
|
||||
if (!isset($node))
|
||||
return $default;
|
||||
$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.
|
||||
* @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);
|
||||
if ($valueFromNode == $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.
|
||||
* @return bool|null
|
||||
*/
|
||||
public function getFirstValueBool(Node $node = null, $property, $default = null) {
|
||||
public function getFirstValueBool(?Node $node, $property, $default = null) {
|
||||
return (in_array(
|
||||
$this->getFirstValueString($node, $property, $default),
|
||||
[ '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.
|
||||
* @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);
|
||||
if (is_numeric($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.
|
||||
* @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);
|
||||
if (is_numeric($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.
|
||||
* @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);
|
||||
if ($valueFromNode == $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.
|
||||
* @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);
|
||||
if ($valueFromNode == $default)
|
||||
return $default;
|
||||
@@ -196,7 +196,7 @@ class NodeReader {
|
||||
* @param string|object $value The expected value.
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasPropertyValue(Node $node = null, $property, $value) {
|
||||
public function hasPropertyValue(?Node $node, $property, $value) {
|
||||
if (!isset($node))
|
||||
return false;
|
||||
$valuesFromNode = $node->getProperty($this->expand($property));
|
||||
@@ -225,14 +225,14 @@ class NodeReader {
|
||||
* @param string|object $property The property to read.
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasProperty(Node $node = null, $property) {
|
||||
public function hasProperty(?Node $node, $property) {
|
||||
if (!isset($node))
|
||||
return false;
|
||||
|
||||
return ($node->getProperty($this->expand($property)) != null);
|
||||
}
|
||||
|
||||
private function getAllValues(Node $node = null, $property) {
|
||||
private function getAllValues(?Node $node, $property) {
|
||||
if (!isset($node))
|
||||
return [];
|
||||
|
||||
@@ -248,7 +248,7 @@ class NodeReader {
|
||||
* 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.
|
||||
*/
|
||||
public function getLocalizedString(Node $node = null, $property, $language, $default = null) {
|
||||
public function getLocalizedString(?Node $node, $property, $language, $default = null) {
|
||||
$values = $this->getAllValues($node, $property);
|
||||
foreach ($values as $v) {
|
||||
if (is_a($v, 'ML\JsonLD\LanguageTaggedString') && $v->getLanguage() == $language)
|
||||
@@ -265,7 +265,7 @@ class NodeReader {
|
||||
* @param string|object $property The property to read.
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getAllValuesString(Node $node = null, $property) {
|
||||
public function getAllValuesString(?Node $node, $property) {
|
||||
$allValues = $this->getAllValues($node, $property);
|
||||
$output = [];
|
||||
foreach ($allValues as $a)
|
||||
@@ -284,7 +284,7 @@ class NodeReader {
|
||||
* @param string|object $property The property to read.
|
||||
* @return array<bool>
|
||||
*/
|
||||
public function getAllValuesBool(Node $node = null, $property) {
|
||||
public function getAllValuesBool(?Node $node, $property) {
|
||||
$allValues = $this->getAllValuesString($node, $property);
|
||||
$output = [];
|
||||
foreach ($allValues as $a)
|
||||
@@ -300,7 +300,7 @@ class NodeReader {
|
||||
* @param string|object $property The property to read.
|
||||
* @return array<bool>
|
||||
*/
|
||||
public function getAllValuesInt(Node $node = null, $property) {
|
||||
public function getAllValuesInt(?Node $node, $property) {
|
||||
$allValues = $this->getAllValuesString($node, $property);
|
||||
$output = [];
|
||||
foreach ($allValues as $a)
|
||||
@@ -316,7 +316,7 @@ class NodeReader {
|
||||
* @param string|object $property The property to read.
|
||||
* @return array<bool>
|
||||
*/
|
||||
public function getAllValuesFloat(Node $node = null, $property) {
|
||||
public function getAllValuesFloat(?Node $node, $property) {
|
||||
$allValues = $this->getAllValuesString($node, $property);
|
||||
$output = [];
|
||||
foreach ($allValues as $a)
|
||||
@@ -333,7 +333,7 @@ class NodeReader {
|
||||
* @param string|object $property The property to read.
|
||||
* @return array<IRI>
|
||||
*/
|
||||
public function getAllValuesIRI(Node $node = null, $property) {
|
||||
public function getAllValuesIRI(?Node $node, $property) {
|
||||
$allValues = $this->getAllValues($node, $property);
|
||||
$output = [];
|
||||
foreach ($allValues as $a)
|
||||
@@ -351,7 +351,7 @@ class NodeReader {
|
||||
* @param string|object $property The property to read.
|
||||
* @return array<Node>
|
||||
*/
|
||||
public function getAllValuesNode(Node $node = null, $property) {
|
||||
public function getAllValuesNode(?Node $node, $property) {
|
||||
$allValues = $this->getAllValues($node, $property);
|
||||
$output = [];
|
||||
foreach ($allValues as $a)
|
||||
|
||||
@@ -6,78 +6,127 @@
|
||||
|
||||
namespace CloudObjects\SDK;
|
||||
|
||||
use Exception;
|
||||
use ML\IRI\IRI, ML\JsonLD\JsonLD;
|
||||
use Doctrine\Common\Cache\RedisCache;
|
||||
use DateTime, Exception;
|
||||
use ML\IRI\IRI;
|
||||
use ML\JsonLD\JsonLD, ML\JsonLD\Node;
|
||||
use Psr\Log\LoggerInterface, Psr\Log\LoggerAwareTrait;
|
||||
use GuzzleHttp\ClientInterface, GuzzleHttp\Client, GuzzleHttp\HandlerStack;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use Kevinrob\GuzzleCache\CacheMiddleware, Kevinrob\GuzzleCache\Storage\DoctrineCacheStorage;
|
||||
use GuzzleHttp\Exception\ConnectException,
|
||||
GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\Psr7\Request,
|
||||
GuzzleHttp\Psr7\Response;
|
||||
use Kevinrob\GuzzleCache\CacheMiddleware;
|
||||
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;
|
||||
|
||||
/**
|
||||
* The ObjectRetriever provides access to objects on CloudObjects.
|
||||
*/
|
||||
class ObjectRetriever {
|
||||
class ObjectRetriever implements CustomCacheAndLogInterface {
|
||||
|
||||
use LoggerAwareTrait;
|
||||
|
||||
private $client;
|
||||
private $prefix;
|
||||
private $options;
|
||||
private $options = [];
|
||||
private $cache;
|
||||
private $objects;
|
||||
private $objects = [];
|
||||
private $defaultReader;
|
||||
private $customCacheDefaultRequest;
|
||||
private $fallbackStoreScope;
|
||||
private $permanentStoreScope;
|
||||
|
||||
const CO_API_URL = 'https://api.cloudobjects.net/';
|
||||
|
||||
const REVISION_PROPERTY = 'coid://cloudobjects.io/isAtRevision';
|
||||
const CO_API_URL = 'https://od.coid.link/';
|
||||
|
||||
public function __construct($options = []) {
|
||||
// Merge options with defaults
|
||||
$this->options = array_merge([
|
||||
'cache_provider' => 'none',
|
||||
'cache_storage' => null,
|
||||
'cache_prefix' => 'clobj:',
|
||||
'cache_ttl' => 60,
|
||||
'static_config_path' => null,
|
||||
'static_object_directory' => null,
|
||||
'auth_ns' => null,
|
||||
'auth_secret' => null,
|
||||
'api_base_url' => null,
|
||||
'logger' => null,
|
||||
'timeout' => 20,
|
||||
'connect_timeout' => 5
|
||||
'connect_timeout' => 5,
|
||||
'fallback_store_directory' => null,
|
||||
'fallback_store_scope' => null,
|
||||
'permanent_store_directory' => null,
|
||||
'permanent_store_scope' => null
|
||||
], $options);
|
||||
|
||||
// Set up object cache
|
||||
switch ($this->options['cache_provider']) {
|
||||
case 'none':
|
||||
// no caching
|
||||
$this->cache = null;
|
||||
break;
|
||||
case 'redis':
|
||||
// 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);
|
||||
// Support deprecated name of static_object_directory
|
||||
if (!isset($this->options['static_object_directory']) && isset($options['static_config_path']))
|
||||
$this->options['static_object_directory'] = $options['static_config_path'];
|
||||
|
||||
$this->cache = new RedisCache();
|
||||
$this->cache->setRedis($redis);
|
||||
break;
|
||||
case 'file':
|
||||
// caching on the filesystem
|
||||
$this->cache = new \Doctrine\Common\Cache\FilesystemCache(
|
||||
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');
|
||||
// Check object cache configuration
|
||||
if (isset($this->options['cache_storage'])
|
||||
&& !($this->options['cache_storage'] instanceof CacheStorageInterface)
|
||||
&& !($this->options['cache_storage'] instanceof CacheItemPoolInterface)
|
||||
&& !($this->options['cache_storage'] instanceof CacheInterface)
|
||||
) {
|
||||
throw new InvalidSDKConfigurationException('Invalid cache_storage specified; must be an instance of Kevinrob\GuzzleCache\CacheStorageInterface, Psr\Cache\CacheItemPoolInterface or Psr\SimpleCache\CacheInterface.');
|
||||
}
|
||||
|
||||
if ($this->options['cache_storage'] instanceof CacheStorageInterface) {
|
||||
$this->cache = $this->options['cache_storage'];
|
||||
} elseif ($this->options['cache_storage'] instanceof CacheItemPoolInterface) {
|
||||
$this->cache = new Psr6CacheStorage($this->options['cache_storage']);
|
||||
} elseif ($this->options['cache_storage'] instanceof CacheInterface) {
|
||||
$this->cache = new Psr16CacheStorage($this->options['cache_storage']);
|
||||
}
|
||||
|
||||
// Check fallback store configuration
|
||||
if (isset($this->options['fallback_store_directory'])
|
||||
&& isset($this->options['fallback_store_scope'])
|
||||
&& !is_dir($this->options['fallback_store_directory'])
|
||||
) {
|
||||
throw new InvalidSDKConfigurationException('Invalid fallback_store_directory specified; must be an existing directory.');
|
||||
}
|
||||
|
||||
if (isset($this->options['fallback_store_directory'])
|
||||
&& isset($this->options['fallback_store_scope'])) {
|
||||
$this->fallbackStoreScope = is_array($this->options['fallback_store_scope'])
|
||||
? $this->options['fallback_store_scope']
|
||||
: array_map('trim', explode(',', $this->options['fallback_store_scope']));
|
||||
}
|
||||
|
||||
// Check permanent store configuration
|
||||
if (isset($this->options['permanent_store_directory'])
|
||||
&& isset($this->options['permanent_store_scope'])
|
||||
&& !is_dir($this->options['permanent_store_directory'])
|
||||
) {
|
||||
throw new InvalidSDKConfigurationException('Invalid permanent_store_directory specified; must be an existing directory.');
|
||||
}
|
||||
|
||||
if (isset($this->options['permanent_store_directory'])
|
||||
&& isset($this->options['permanent_store_scope'])) {
|
||||
$this->permanentStoreScope = is_array($this->options['permanent_store_scope'])
|
||||
? $this->options['permanent_store_scope']
|
||||
: array_map('trim', explode(',', $this->options['permanent_store_scope']));
|
||||
}
|
||||
|
||||
if (isset($this->fallbackStoreScope) && isset($this->permanentStoreScope)
|
||||
&& realpath($this->options['fallback_store_directory']) === realpath($this->options['permanent_store_directory'])
|
||||
) {
|
||||
throw new InvalidSDKConfigurationException('The permanent_store_directory and the fallback_store_directory must be different directories.');
|
||||
}
|
||||
|
||||
// Set up logger
|
||||
if (is_a($this->options['logger'], LoggerInterface::class))
|
||||
if (is_a($this->options['logger'], LoggerInterface::class)) {
|
||||
$this->setLogger($this->options['logger']);
|
||||
}
|
||||
|
||||
// Set up handler stack
|
||||
$stack = HandlerStack::create();
|
||||
@@ -86,11 +135,14 @@ class ObjectRetriever {
|
||||
if (isset($this->cache)) {
|
||||
$stack->push(
|
||||
new CacheMiddleware(
|
||||
new PrivateCacheStrategy(
|
||||
new DoctrineCacheStorage($this->cache)
|
||||
)
|
||||
new PrivateCacheStrategy($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
|
||||
@@ -107,19 +159,139 @@ class ObjectRetriever {
|
||||
$this->client = new Client($options);
|
||||
}
|
||||
|
||||
private function logInfoWithTime($message, $ts) {
|
||||
/**
|
||||
* 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) {
|
||||
if (isset($this->logger))
|
||||
$this->logger->info($message, [ 'elapsed_ms' => round((microtime(true) - $ts) * 1000) ]);
|
||||
}
|
||||
|
||||
public function getCacheKey($id) {
|
||||
return $this->options['cache_prefix'] . $this->options['auth_ns'] . '/' . $id;
|
||||
}
|
||||
|
||||
private function getFromCache($id) {
|
||||
return (isset($this->cache) && $this->cache->contains($this->options['cache_prefix'].$id))
|
||||
? $this->cache->fetch($this->options['cache_prefix'].$id) : null;
|
||||
if (!$this->cache) {
|
||||
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) {
|
||||
if (isset($this->cache))
|
||||
$this->cache->save($this->options['cache_prefix'].$id, $data, $ttl);
|
||||
if ($this->cache) {
|
||||
$entry = new CacheEntry($this->customCacheDefaultRequest,
|
||||
new Response(200, [], $data),
|
||||
((new DateTime)->modify('+'.$ttl.' seconds')));
|
||||
|
||||
$this->cache->save($this->getCacheKey($id), $entry, $ttl);
|
||||
}
|
||||
|
||||
$this->putIntoFallbackStore($id, $data);
|
||||
$this->putIntoPermanentStore($id, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file location of an object in a store, or null if the store
|
||||
* is not configured or the object is out of scope.
|
||||
*/
|
||||
private function getStoreLocation($directory, ?array $scope, $id) : ?string {
|
||||
if (!isset($scope))
|
||||
return null;
|
||||
|
||||
// Only objects are stored, not custom data or attachments
|
||||
$coid = new IRI($id);
|
||||
if (!COIDParser::isValidCOID($coid))
|
||||
return null;
|
||||
|
||||
if (!in_array('*', $scope)
|
||||
&& !in_array($coid->getHost(), $scope))
|
||||
return null;
|
||||
|
||||
return $directory.DIRECTORY_SEPARATOR.
|
||||
$coid->getHost().str_replace('/', DIRECTORY_SEPARATOR, $coid->getPath())
|
||||
.DIRECTORY_SEPARATOR.'object.jsonld';
|
||||
}
|
||||
|
||||
private function putIntoStore($location, $id, $data, $storeName) {
|
||||
// Write to a temporary file and rename it so that concurrent readers
|
||||
// never see a partially written object
|
||||
$directory = dirname($location);
|
||||
$tempFile = $location.'.'.uniqid('', true).'.tmp';
|
||||
if ((!is_dir($directory) && !@mkdir($directory, 0777, true) && !is_dir($directory))
|
||||
|| @file_put_contents($tempFile, $data) === false
|
||||
|| !@rename($tempFile, $location)) {
|
||||
@unlink($tempFile);
|
||||
if (isset($this->logger))
|
||||
$this->logger->warning('Could not write <'.$id.'> to '.$storeName.' store.');
|
||||
}
|
||||
}
|
||||
|
||||
private function getFromStore($location) : ?string {
|
||||
if (!isset($location) || !is_file($location))
|
||||
return null;
|
||||
|
||||
$data = @file_get_contents($location);
|
||||
return ($data === false) ? null : $data;
|
||||
}
|
||||
|
||||
private function getFallbackStoreLocation($id) : ?string {
|
||||
return $this->getStoreLocation($this->options['fallback_store_directory'],
|
||||
$this->fallbackStoreScope, $id);
|
||||
}
|
||||
|
||||
private function putIntoFallbackStore($id, $data) {
|
||||
$location = $this->getFallbackStoreLocation($id);
|
||||
if (isset($location))
|
||||
$this->putIntoStore($location, $id, $data, 'fallback');
|
||||
}
|
||||
|
||||
private function getFromFallbackStore($id) : ?string {
|
||||
return $this->getFromStore($this->getFallbackStoreLocation($id));
|
||||
}
|
||||
|
||||
private function getPermanentStoreLocation($id) : ?string {
|
||||
return $this->getStoreLocation($this->options['permanent_store_directory'],
|
||||
$this->permanentStoreScope, $id);
|
||||
}
|
||||
|
||||
private function putIntoPermanentStore($id, $data) {
|
||||
// Objects in the permanent store are never overwritten
|
||||
$location = $this->getPermanentStoreLocation($id);
|
||||
if (isset($location) && !is_file($location))
|
||||
$this->putIntoStore($location, $id, $data, 'permanent');
|
||||
}
|
||||
|
||||
private function getFromPermanentStore($id) : ?string {
|
||||
return $this->getFromStore($this->getPermanentStoreLocation($id));
|
||||
}
|
||||
|
||||
public function getFromCacheCustom($id) {
|
||||
return $this->getFromCache('custom/'.$id);
|
||||
}
|
||||
|
||||
public function putIntoCacheCustom($id, $data, $ttl) {
|
||||
$this->putIntoCache('custom/'.$id, $data, $ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,34 +330,65 @@ class ObjectRetriever {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an object description from CloudObjects. Attempts to get object
|
||||
* from in-memory cache first, stored static configurations next,
|
||||
* configured external cache third, and finally calls the Object API
|
||||
* on CloudObjects Core. Returns null if the object was not found.
|
||||
* Get an object description and return a CloudObject.
|
||||
*/
|
||||
public function getCloudObject(IRI $coid) : ?CloudObject {
|
||||
$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
|
||||
* @return Node|null
|
||||
*/
|
||||
public function getObject(IRI $coid) {
|
||||
public function getObjectNode(IRI $coid) : ?Node {
|
||||
if (!COIDParser::isValidCOID($coid))
|
||||
throw new Exception("Not a valid COID.");
|
||||
throw new SDKException("Not a valid COID.");
|
||||
|
||||
$uriString = (string)$coid;
|
||||
|
||||
if (isset($this->objects[$uriString]))
|
||||
// Return from in-memory cache if it exists
|
||||
return $this->objects[$uriString];
|
||||
$this->removeExpiredFromInMemoryCache();
|
||||
|
||||
$ts = microtime(true);
|
||||
|
||||
if (isset($this->options['static_config_path'])) {
|
||||
$location = realpath($this->options['static_config_path'].DIRECTORY_SEPARATOR.
|
||||
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_object_directory'])) {
|
||||
$location = realpath($this->options['static_object_directory'].DIRECTORY_SEPARATOR.
|
||||
$coid->getHost().str_replace('/', DIRECTORY_SEPARATOR, $coid->getPath())
|
||||
.DIRECTORY_SEPARATOR.'object.jsonld');
|
||||
|
||||
if ($location && file_exists($location)) {
|
||||
$object = $location;
|
||||
$this->logInfoWithTime('Fetched <'.$uriString.'> from static configuration.', $ts);
|
||||
$this->logInfoWithTime('Fetched <'.$uriString.'> from static object directory.', $ts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,6 +398,12 @@ class ObjectRetriever {
|
||||
$this->logInfoWithTime('Fetched <'.$uriString.'> from object cache.', $ts);
|
||||
}
|
||||
|
||||
if (!isset($object)) {
|
||||
$object = $this->getFromPermanentStore($uriString);
|
||||
if (isset($object))
|
||||
$this->logInfoWithTime('Fetched <'.$uriString.'> from permanent store.', $ts);
|
||||
}
|
||||
|
||||
if (!isset($object)) {
|
||||
try {
|
||||
$response = $this->client
|
||||
@@ -204,18 +413,54 @@ class ObjectRetriever {
|
||||
$object = (string)$response->getBody();
|
||||
$this->putIntoCache($uriString, $object, $this->options['cache_ttl']);
|
||||
$this->logInfoWithTime('Fetched <'.$uriString.'> from Core API ['.$response->getStatusCode().'].', $ts);
|
||||
} catch (ConnectException $e) {
|
||||
// connection failed or timed out
|
||||
$object = $this->getFromFallbackStore($uriString);
|
||||
if (!isset($object)) {
|
||||
$this->logInfoWithTime('Object <'.$uriString.'> could not be retrieved from Core API (connection failed).', $ts);
|
||||
throw new CoreAPIException($e->getMessage(), $e);
|
||||
}
|
||||
$this->logInfoWithTime('Fetched <'.$uriString.'> from fallback store (connection failed).', $ts);
|
||||
} catch (RequestException $e) {
|
||||
if ($e->hasResponse())
|
||||
$this->logInfoWithTime('Object <'.$uriString.'> not found in Core API ['.$e->getResponse()->getStatusCode().'].', $ts);
|
||||
else
|
||||
$this->logInfoWithTime('Object <'.$uriString.'> could not be retrieved from Core API.', $ts);
|
||||
return null;
|
||||
if ($e->hasResponse() && $e->getResponse()->getStatusCode() == 404) {
|
||||
// not found (404)
|
||||
$this->logInfoWithTime('Object <'.$uriString.'> not found in Core API.', $ts);
|
||||
return null;
|
||||
} else
|
||||
if ($e->hasResponse() && $e->getResponse()->getStatusCode() >= 500) {
|
||||
// other API error (5xx)
|
||||
$object = $this->getFromFallbackStore($uriString);
|
||||
$statusCode = $e->getResponse()->getStatusCode();
|
||||
if (!isset($object)) {
|
||||
$this->logInfoWithTime('Object <'.$uriString.'> could not be retrieved from Core API ['.$statusCode.'].', $ts);
|
||||
throw new CoreAPIException($e->getMessage(), $e);
|
||||
}
|
||||
$this->logInfoWithTime('Fetched <'.$uriString.'> from fallback store (Core API returned ['.$statusCode.']).', $ts);
|
||||
} else
|
||||
if (!$e->hasResponse()) {
|
||||
// API error without response
|
||||
$object = $this->getFromFallbackStore($uriString);
|
||||
if (!isset($object)) {
|
||||
$this->logInfoWithTime('Object <'.$uriString.'> could not be retrieved from Core API (no status code).', $ts);
|
||||
throw new CoreAPIException($e->getMessage(), $e);
|
||||
}
|
||||
$this->logInfoWithTime('Fetched <'.$uriString.'> from fallback store (Core API returned no status code).', $ts);
|
||||
} else {
|
||||
// other API error (e.g. 4xx)
|
||||
$statusCode = $e->getResponse()->getStatusCode();
|
||||
$this->logInfoWithTime('Object <'.$uriString.'> could not be retrieved from Core API ['.$statusCode.'].', $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);
|
||||
$this->objects[$uriString] = $document->getGraph()->getNode($uriString);
|
||||
return $this->objects[$uriString];
|
||||
$this->objects[$uriString] = [ time(), $document->getGraph()->getNode($uriString) ];
|
||||
return $this->objects[$uriString][1];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -231,7 +476,7 @@ class ObjectRetriever {
|
||||
*/
|
||||
public function fetchObjectsInNamespaceWithType(IRI $namespaceCoid, $type) {
|
||||
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);
|
||||
$type = (string)$type;
|
||||
@@ -252,14 +497,14 @@ class ObjectRetriever {
|
||||
if (!COIDParser::isValidCOID($iri)) 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']);
|
||||
$allIris[] = $iri;
|
||||
}
|
||||
|
||||
$this->logInfoWithTime('Fetched all objects with <'.$type.'> for <'.$namespaceCoid->getHost().'> from Core API ['.$response->getStatusCode().'].', $ts);
|
||||
} catch (Exception $e) {
|
||||
throw new CoreAPIException;
|
||||
throw new CoreAPIException($e->getMessage(), $e);
|
||||
}
|
||||
|
||||
return $allIris;
|
||||
@@ -276,7 +521,7 @@ class ObjectRetriever {
|
||||
*/
|
||||
public function fetchAllObjectsInNamespace(IRI $namespaceCoid) {
|
||||
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);
|
||||
|
||||
@@ -293,7 +538,7 @@ class ObjectRetriever {
|
||||
if (!COIDParser::isValidCOID($iri)) 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']);
|
||||
$allIris[] = $iri;
|
||||
}
|
||||
@@ -301,7 +546,7 @@ class ObjectRetriever {
|
||||
$this->logInfoWithTime('Fetched all objects for <'.$namespaceCoid->getHost().'> from Core API ['.$response->getStatusCode().'].', $ts);
|
||||
|
||||
} catch (Exception $e) {
|
||||
throw new CoreAPIException;
|
||||
throw new CoreAPIException($e->getMessage(), $e);
|
||||
}
|
||||
|
||||
return $allIris;
|
||||
@@ -317,7 +562,7 @@ class ObjectRetriever {
|
||||
*/
|
||||
public function getCOIDListForNamespace(IRI $namespaceCoid) {
|
||||
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);
|
||||
|
||||
@@ -335,7 +580,7 @@ class ObjectRetriever {
|
||||
$this->logInfoWithTime('Fetched object list for <'.$namespaceCoid->getHost().'> from Core API ['.$response->getStatusCode().'].', $ts);
|
||||
|
||||
} catch (Exception $e) {
|
||||
throw new CoreAPIException;
|
||||
throw new CoreAPIException($e->getMessage(), $e);
|
||||
}
|
||||
|
||||
return $allIris;
|
||||
@@ -352,7 +597,7 @@ class ObjectRetriever {
|
||||
*/
|
||||
public function getCOIDListForNamespaceWithType(IRI $namespaceCoid, $type) {
|
||||
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);
|
||||
$type = (string)$type;
|
||||
@@ -374,7 +619,7 @@ class ObjectRetriever {
|
||||
$this->logInfoWithTime('Fetched object list with <'.$type.'> for <'.$namespaceCoid->getHost().'> from Core API ['.$response->getStatusCode().'].', $ts);
|
||||
|
||||
} catch (Exception $e) {
|
||||
throw new CoreAPIException;
|
||||
throw new CoreAPIException($e->getMessage(), $e);
|
||||
}
|
||||
|
||||
return $allIris;
|
||||
@@ -382,19 +627,20 @@ class ObjectRetriever {
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return Node|null
|
||||
* @deprecated Interface may change
|
||||
*/
|
||||
public function get($coid) {
|
||||
if (is_string($coid))
|
||||
return $this->getObject(new IRI($coid));
|
||||
return $this->getObjectNode(new IRI($coid));
|
||||
|
||||
if (is_object($coid) && get_class($coid)=='ML\IRI\IRI')
|
||||
return $this->getObject($coid);
|
||||
return $this->getObjectNode($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.');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -404,7 +650,7 @@ class ObjectRetriever {
|
||||
* @param string $filename
|
||||
*/
|
||||
public function getAttachment(IRI $coid, $filename) {
|
||||
$object = $this->getObject($coid);
|
||||
$object = $this->getObjectNode($coid);
|
||||
|
||||
if (!$object)
|
||||
// Cannot get attachment for non-existing object
|
||||
@@ -413,7 +659,7 @@ class ObjectRetriever {
|
||||
$ts = microtime(true);
|
||||
|
||||
$cacheId = $object->getId().'#'.$filename;
|
||||
$fileData = $this->getFromCache($cacheId);
|
||||
$fileData = $this->getFromCache($cacheId);
|
||||
|
||||
// Parse cached data into revision and content
|
||||
if (isset($fileData)) {
|
||||
@@ -422,7 +668,7 @@ class ObjectRetriever {
|
||||
}
|
||||
|
||||
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
|
||||
try {
|
||||
@@ -430,35 +676,104 @@ class ObjectRetriever {
|
||||
.'/'.basename($filename));
|
||||
|
||||
$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->logInfoWithTime('Fetched attachment <'.$filename.'> for <'.$object->getId().'> from Core API ['.$response->getStatusCode().'].', $ts);
|
||||
} catch (ConnectException $e) {
|
||||
// connection failed or timed out
|
||||
$this->logInfoWithTime('Attachment <'.$filename.'> for <'.$object->getId().'> could not be retrieved from Core API (connection failed).', $ts);
|
||||
throw new CoreAPIException($e->getMessage(), $e);
|
||||
} catch (RequestException $e) {
|
||||
if ($e->hasResponse() && $e->getResponse()->getStatusCode() == 404) {
|
||||
// not found (404)
|
||||
$this->logInfoWithTime('Attachment <'.$filename.'> for <'.$object->getId().'> not found in Core API.', $ts);
|
||||
return null;
|
||||
} else
|
||||
if ($e->hasResponse()) {
|
||||
// other API error (4xx, 5xx)
|
||||
$statusCode = $e->getResponse()->getStatusCode();
|
||||
$this->logInfoWithTime('Attachment <'.$filename.'> for <'.$object->getId().'> could not be retrieved from Core API ['.$statusCode.'].', $ts);
|
||||
throw new CoreAPIException($e->getMessage(), $e);
|
||||
} else {
|
||||
// API error without response
|
||||
$this->logInfoWithTime('Attachment <'.$filename.'> for <'.$object->getId().'> could not be retrieved from Core API (no status code).', $ts);
|
||||
throw new CoreAPIException($e->getMessage(), $e);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->logInfoWithTime('Attachment <'.$filename.'> for <'.$object->getId().'> not found in Core API ['.$e->getResponse()->getStatusCode().'].', $ts);
|
||||
// ignore exception - treat as non-existing file
|
||||
// other exceptions
|
||||
$this->logInfoWithTime('Attachment <'.$filename.'> for <'.$object->getId().'> could not be retrieved from Core API (unexpected exception).', $ts);
|
||||
throw new CoreAPIException($e->getMessage(), $e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $fileContent;
|
||||
return $fileContent ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the object that describes the namespace provided with the "auth_ns"
|
||||
* configuration option.
|
||||
*
|
||||
* @return Node
|
||||
*/
|
||||
public function getAuthenticatingNamespaceObject() {
|
||||
private function assertAuthenticatingNamespaceAndGetId() : IRI {
|
||||
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']);
|
||||
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->getObjectNode($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->getObjectNode($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 ];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,7 +14,8 @@ use GuzzleHttp\Client, GuzzleHttp\HandlerStack, GuzzleHttp\Middleware;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use CloudObjects\SDK\COIDParser, CloudObjects\SDK\ObjectRetriever;
|
||||
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
|
||||
@@ -154,16 +155,19 @@ class APIClientFactory {
|
||||
'timeout' => self::DEFAULT_TIMEOUT
|
||||
];
|
||||
|
||||
if ($this->reader->hasProperty($api, 'wa:hasAuthorizationServer')) {
|
||||
if ($this->reader->hasProperty($api, 'oauth2:hasAuthorizationServer')) {
|
||||
// We have an authorization server for this endpoint/API
|
||||
$authServerCoid = $this->reader->getFirstValueIRI($api, 'wa:hasAuthorizationServer');
|
||||
$authServerObject = $this->objectRetriever->getObject($authServerCoid);
|
||||
if (!isset($authServer))
|
||||
$authServerCoid = $this->reader->getFirstValueIRI($api, 'oauth2:hasAuthorizationServer');
|
||||
$authServerObject = $this->objectRetriever->getObjectNode($authServerCoid);
|
||||
if (!isset($authServerObject))
|
||||
throw new InvalidObjectConfigurationException("Authorization server object <"
|
||||
. (string)$authServerCoid . "> not available.");
|
||||
|
||||
try {
|
||||
$authServer = new OAuth2AuthServer($authServerObject);
|
||||
$authServer = new OAuth2AuthServer($authServerObject, $this->objectRetriever);
|
||||
} catch (InvalidObjectConfigurationException $e) {
|
||||
throw new InvalidObjectConfigurationException("Authorization server object <"
|
||||
. (string)$authServerCoid . "> could not be loaded; error: " . $e->getMessage());
|
||||
} catch (Exception $e) {
|
||||
throw new InvalidObjectConfigurationException("Authorization server object <"
|
||||
. (string)$authServerCoid . "> could not be loaded. Its definition may be invalid.");
|
||||
@@ -202,7 +206,7 @@ class APIClientFactory {
|
||||
|
||||
if ($this->reader->hasType($api, 'wa:GraphQLEndpoint')) {
|
||||
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'],
|
||||
isset($clientConfig['headers']) ? $clientConfig['headers'] : []);
|
||||
@@ -214,11 +218,11 @@ class APIClientFactory {
|
||||
* @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.
|
||||
*/
|
||||
public function __construct(ObjectRetriever $objectRetriever, IRI $namespaceCoid = null) {
|
||||
public function __construct(ObjectRetriever $objectRetriever, ?IRI $namespaceCoid = null) {
|
||||
$this->objectRetriever = $objectRetriever;
|
||||
$this->namespace = isset($namespaceCoid)
|
||||
? $objectRetriever->getObject($namespaceCoid)
|
||||
: $objectRetriever->getAuthenticatingNamespaceObject();
|
||||
? $objectRetriever->getObjectNode($namespaceCoid)
|
||||
: $objectRetriever->getAuthenticatingNamespaceObjectNode();
|
||||
|
||||
$this->reader = new NodeReader([
|
||||
'prefixes' => [
|
||||
@@ -239,7 +243,7 @@ class APIClientFactory {
|
||||
public function getClientWithCOID(IRI $apiCoid, bool $specificClient = false) {
|
||||
$idString = (string)$apiCoid.(string)$specificClient;
|
||||
if (!isset($this->apiClients[$idString])) {
|
||||
$object = $this->objectRetriever->getObject($apiCoid);
|
||||
$object = $this->objectRetriever->getObjectNode($apiCoid);
|
||||
if (!isset($object))
|
||||
throw new CoreAPIException("Could not retrieve API <".(string)$apiCoid.">.");
|
||||
$this->apiClients[$idString] = $this->createClient($object, $specificClient);
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
|
||||
namespace CloudObjects\SDK\WebAPI\Exceptions;
|
||||
|
||||
use CloudObjects\SDK\Exceptions\SDKException;
|
||||
|
||||
/**
|
||||
* An Exception that is thrown when an an OAuth flow failed.
|
||||
*/
|
||||
class OAuthFlowException extends \Exception {
|
||||
class OAuthFlowException extends SDKException {
|
||||
|
||||
}
|
||||
@@ -6,66 +6,80 @@
|
||||
|
||||
namespace CloudObjects\SDK\WebAPI;
|
||||
|
||||
use Exception;
|
||||
use CloudObjects\SDK\Exceptions\SDKException;
|
||||
use ML\JsonLD\Node;
|
||||
use GuzzleHttp\Client;
|
||||
use Webmozart\Assert\Assert;
|
||||
use CloudObjects\SDK\NodeReader;
|
||||
use Webmozart\Assert\Assert,
|
||||
Webmozart\Assert\InvalidArgumentException;
|
||||
use CloudObjects\SDK\NodeReader,
|
||||
CloudObjects\SDK\CustomCacheAndLogInterface;
|
||||
use CloudObjects\SDK\Exceptions\InvalidObjectConfigurationException;
|
||||
|
||||
class OAuth2AuthServer {
|
||||
|
||||
private $reader;
|
||||
private $authServer;
|
||||
private $consumer;
|
||||
private $cacheAndLog;
|
||||
|
||||
private $grantType;
|
||||
private $clientId;
|
||||
private $clientSecret;
|
||||
|
||||
public function __construct(Node $authServer) {
|
||||
public function __construct(Node $authServer, CustomCacheAndLogInterface $cacheAndLog) {
|
||||
$this->reader = new NodeReader([
|
||||
'prefixes' => [
|
||||
'oauth2' => 'coid://oauth2.co-n.net/'
|
||||
]
|
||||
]);
|
||||
|
||||
Assert::true($this->reader->hasProperty($authServer, 'oauth2:hasTokenEndpoint'),
|
||||
"Authorization Server must have a token endpoint.");
|
||||
Assert::startsWith($this->reader->getFirstValueString($authServer, 'oauth2:hasTokenEndpoint'),
|
||||
"https://",
|
||||
"Token endpoint must be an https:// URL.");
|
||||
Assert::true($this->reader->hasProperty($authServer, 'oauth2:supportsGrantType'),
|
||||
"Authorization Server must support at least one grant type.");
|
||||
Assert::true($this->reader->hasProperty($this->authServer, 'oauth2:usesClientIDFrom'),
|
||||
"Authorization Server must define client ID property.");
|
||||
Assert::true($this->reader->hasProperty($this->authServer, 'oauth2:usesClientSecretFrom'),
|
||||
"Authorization Server must define client secret property.");
|
||||
try {
|
||||
Assert::true($this->reader->hasProperty($authServer, 'oauth2:hasTokenEndpoint'),
|
||||
"Authorization Server must have a token endpoint.");
|
||||
Assert::startsWith($this->reader->getFirstValueString($authServer, 'oauth2:hasTokenEndpoint'),
|
||||
"https://",
|
||||
"Token endpoint must be an https:// URL.");
|
||||
Assert::true($this->reader->hasProperty($authServer, 'oauth2:supportsGrantType'),
|
||||
"Authorization Server must support at least one grant type.");
|
||||
Assert::true($this->reader->hasProperty($authServer, 'oauth2:usesClientIDFrom'),
|
||||
"Authorization Server must define client ID property.");
|
||||
Assert::true($this->reader->hasProperty($authServer, 'oauth2:usesClientSecretFrom'),
|
||||
"Authorization Server must define client secret property.");
|
||||
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new InvalidObjectConfigurationException($e->getMessage());
|
||||
}
|
||||
|
||||
$this->authServer = $authServer;
|
||||
}
|
||||
|
||||
private function assertClientCredentialPropertiesExist() : void {
|
||||
|
||||
$this->cacheAndLog = $cacheAndLog;
|
||||
}
|
||||
|
||||
public function configureConsumer(Node $consumer) : void {
|
||||
$this->assertClientCredentialPropertiesExist();
|
||||
$clientIDProperty = $this->reader->getFirstValueString($this->authServer,
|
||||
'oauth2:usesClientIDFrom');
|
||||
$clientSecretProperty = $this->reader->getFirstValueString($this->authServer,
|
||||
'oauth2:usesClientSecretFrom');
|
||||
try {
|
||||
Assert::notNull($this->authServer, "Object wasn't initialized correctly.");
|
||||
Assert::notNull($this->cacheAndLog, "Object wasn't initialized correctly.");
|
||||
|
||||
Assert::true($this->reader->hasProperty($consumer, $clientIDProperty),
|
||||
"Namespace must have Client ID");
|
||||
Assert::true($this->reader->hasProperty($consumer, $clientSecretProperty),
|
||||
"Namespace must have Client Secret");
|
||||
$clientIDProperty = $this->reader->getFirstValueString($this->authServer,
|
||||
'oauth2:usesClientIDFrom');
|
||||
$clientSecretProperty = $this->reader->getFirstValueString($this->authServer,
|
||||
'oauth2:usesClientSecretFrom');
|
||||
|
||||
Assert::true($this->reader->hasProperty($consumer, $clientIDProperty),
|
||||
"Namespace must have Client ID");
|
||||
Assert::true($this->reader->hasProperty($consumer, $clientSecretProperty),
|
||||
"Namespace must have Client Secret");
|
||||
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new InvalidObjectConfigurationException($e->getMessage());
|
||||
}
|
||||
|
||||
if ($this->reader->hasPropertyValue($this->authServer,
|
||||
'oauth2:supportsGrantType', 'oauth2:ClientCredentials')) {
|
||||
'oauth2:supportsGrantType', 'oauth2:ClientCredentials'))
|
||||
{
|
||||
// No additional conditions for "client_credentials" flow
|
||||
$this->grantType = 'client_credentials';
|
||||
} else {
|
||||
throw new Exception("No flow/grant_type found.");
|
||||
throw new InvalidObjectConfigurationException("No flow/grant_type found.");
|
||||
}
|
||||
|
||||
$this->consumer = $consumer;
|
||||
@@ -74,10 +88,18 @@ class OAuth2AuthServer {
|
||||
}
|
||||
|
||||
public function getAccessToken() {
|
||||
Assert::notNull($this->consumer, "Missing consumer.");
|
||||
Assert::notNull($this->grantType, "Missing grant_type.");
|
||||
Assert::notNull($this->clientId, "Missing client_id.");
|
||||
Assert::notNull($this->clientSecret, "Missing client_secret.");
|
||||
try {
|
||||
Assert::notNull($this->authServer, "Object wasn't initialized correctly.");
|
||||
Assert::notNull($this->cacheAndLog, "Object wasn't initialized correctly.");
|
||||
|
||||
Assert::notNull($this->consumer, "Missing consumer.");
|
||||
Assert::notNull($this->grantType, "Missing grant_type.");
|
||||
Assert::notNull($this->clientId, "Missing client_id.");
|
||||
Assert::notNull($this->clientSecret, "Missing client_secret.");
|
||||
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new InvalidObjectConfigurationException($e->getMessage());
|
||||
}
|
||||
|
||||
$client = new Client;
|
||||
$tokenEndpointUrl = $this->reader->getFirstValueString($this->authServer, 'oauth2:hasTokenEndpoint');
|
||||
@@ -90,15 +112,31 @@ class OAuth2AuthServer {
|
||||
switch ($this->grantType) {
|
||||
case "client_credentials":
|
||||
// no additional params needed
|
||||
break;
|
||||
default:
|
||||
throw new Exception("No flow/grant_type found.");
|
||||
throw new SDKException("No flow/grant_type found.");
|
||||
}
|
||||
|
||||
$tokenResponse = json_decode($client->post($tokenEndpointUrl, [
|
||||
'form_params' => $params
|
||||
])->getBody(true));
|
||||
$grantCacheKey = sha1(json_encode($params));
|
||||
|
||||
Assert::keyExists($tokenResponse, 'access_token');
|
||||
$ts = microtime(true);
|
||||
$tokenResponse = json_decode($this->cacheAndLog->getFromCacheCustom($grantCacheKey), true);
|
||||
|
||||
if (isset($tokenResponse)) {
|
||||
$this->cacheAndLog->logInfoWithTime("Reused access token for <".$this->authServer->getId()."> from cache.", $ts);
|
||||
} else {
|
||||
// Nothing cached, fetch from server
|
||||
$tokenResponse = json_decode($client->post($tokenEndpointUrl, [
|
||||
'form_params' => $params
|
||||
])->getBody(true), true);
|
||||
|
||||
Assert::keyExists($tokenResponse, 'access_token');
|
||||
|
||||
$expiry = isset($tokenResponse['expires_in']) ? $tokenResponse['expires_in'] : 84600;
|
||||
|
||||
$this->cacheAndLog->logInfoWithTime("Retrieved access token for <".$this->authServer->getId()."> from token endpoint and will cache for ".$expiry." seconds.", $ts);
|
||||
$this->cacheAndLog->putIntoCacheCustom($grantCacheKey, json_encode($tokenResponse), $expiry);
|
||||
}
|
||||
|
||||
return $tokenResponse['access_token'];
|
||||
}
|
||||
|
||||
@@ -2,66 +2,95 @@
|
||||
|
||||
[](https://packagist.org/packages/cloudobjects/sdk) [](https://packagist.org/packages/cloudobjects/sdk)
|
||||
|
||||
[](https://app.buddy.works/cloudobjects/php-sdk/repository/branch/main)
|
||||
|
||||
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.
|
||||
[](https://eu.buddy.works/cloudobjects/php-sdk/pipelines/pipeline/213082)
|
||||
|
||||
## 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
|
||||
{
|
||||
"require": {
|
||||
"cloudobjects/sdk" : ">=0.7"
|
||||
}
|
||||
}
|
||||
````
|
||||
Install with the following command:
|
||||
|
||||
## 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.
|
||||
|
||||
````php
|
||||
use ML\IRI\IRI;
|
||||
You can use the high-level `CloudObjects\SDK\CloudObject` interface or the lower-level `ML\JsonLD\Node` interface (from the [sweetrdf/json-ld](https://packagist.org/packages/sweetrdf/json-ld) package) to work with the parsed JSON-LD representation of the object.
|
||||
|
||||
### Using the ObjectRetriever directly
|
||||
|
||||
```php
|
||||
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();
|
||||
$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.";
|
||||
````
|
||||
When no options are passed to the `ObjectRetriever`, it will initialize with default settings and no authentication, which means that it can only retrieve public triples from public objects. You can authenticate with a CloudObjects namespace (domain name) and the shared secret between this namespace and CloudObjects.
|
||||
|
||||
### Configuration
|
||||
```php
|
||||
use CloudObjects\SDK\ObjectRetriever;
|
||||
use ML\IRI\IRI;
|
||||
|
||||
You can pass an array of configuration options to the ObjectRetriever's constructor:
|
||||
$retriever = new ObjectRetriever([
|
||||
'auth_ns' => 'example.com',
|
||||
'auth_secret' => 'YOUR_SHARED_SECRET',
|
||||
]);
|
||||
|
||||
| Option | Description | Default |
|
||||
|---|---|---|
|
||||
| `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` |
|
||||
$object = $retriever->getCloudObject(new IRI('coid://example.com/PrivateObject'));
|
||||
|
||||
#### For `redis` cache:
|
||||
// Retrieve the object describing the authenticating namespace itself
|
||||
$namespace = $retriever->getAuthenticatingNamespaceCloudObject();
|
||||
echo $namespace->getLabel();
|
||||
```
|
||||
|
||||
| Option | Description | Default |
|
||||
|---|---|---|
|
||||
| `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` |
|
||||
Any production usage should configure a cache; [all the caching-related features are documented here](docs/caching.md). All [configuration options are documented here](docs/options.md).
|
||||
|
||||
#### For `file` cache:
|
||||
### Using the static facade
|
||||
|
||||
| Option | Description | Default |
|
||||
|---|---|---|
|
||||
| `cache_provider.file.directory` | The directory to store cache data in. | The system's temporary directory. |
|
||||
```php
|
||||
use CloudObjects\SDK\ObjectRetriever;
|
||||
use CloudObjects\SDK\ObjectRetrieverFacade;
|
||||
use ML\IRI\IRI;
|
||||
|
||||
ObjectRetrieverFacade::setObjectRetriever(new ObjectRetriever);
|
||||
|
||||
$object = ObjectRetrieverFacade::getCloudObject(new IRI('coid://cloudobjects.io'));
|
||||
echo $object->getLabel();
|
||||
```
|
||||
|
||||
### Using the global function
|
||||
|
||||
The global function uses the static facade, so the facade must be initialized first.
|
||||
|
||||
```php
|
||||
use CloudObjects\SDK\ObjectRetriever;
|
||||
use CloudObjects\SDK\ObjectRetrieverFacade;
|
||||
|
||||
ObjectRetrieverFacade::setObjectRetriever(new ObjectRetriever);
|
||||
|
||||
$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
|
||||
|
||||
|
||||
+17
-14
@@ -2,16 +2,16 @@
|
||||
"name": "cloudobjects/sdk",
|
||||
"description": "CloudObjects SDK for PHP for working with COIDs and object descriptions from CloudObjects.",
|
||||
"keywords": ["cloudobjects", "sdk"],
|
||||
"homepage": "https://github.com/CloudObjects/CloudObjects-PHP-SDK",
|
||||
"homepage": "https://codeberg.org/CloudObjects/CloudObjects-PHP-SDK",
|
||||
"license": "MPL-2.0",
|
||||
"require" : {
|
||||
"ml/json-ld": ">=1.0.7",
|
||||
"doctrine/common" : ">=2.6.1",
|
||||
"doctrine/cache" : "1.*",
|
||||
"sweetrdf/json-ld": ">=1.3",
|
||||
"guzzlehttp/guzzle" : ">=6.0",
|
||||
"psr/cache": ">=1.0",
|
||||
"psr/log": ">=1.1",
|
||||
"kevinrob/guzzle-cache-middleware": "^3.2",
|
||||
"webmozart/assert": "^1.6"
|
||||
"kevinrob/guzzle-cache-middleware": "^7.0.0",
|
||||
"webmozart/assert": "^1.6",
|
||||
"psr/simple-cache": "^3.0"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
@@ -19,21 +19,24 @@
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"CloudObjects\\SDK" : ""
|
||||
}
|
||||
"psr-4": {
|
||||
"CloudObjects\\SDK\\" : "CloudObjects/SDK/"
|
||||
},
|
||||
"files": [
|
||||
"functions.php"
|
||||
]
|
||||
},
|
||||
"require-dev" : {
|
||||
"phpunit/phpunit": ">=4.8.0,<5.0",
|
||||
"phpunit/phpunit": "^10",
|
||||
"symfony/http-foundation" : ">=4.0",
|
||||
"symfony/psr-http-message-bridge" : ">=1.1.0",
|
||||
"nyholm/psr7" : "~1.5.1",
|
||||
"nyholm/psr7" : "^1.5",
|
||||
"defuse/php-encryption" : "^2.2"
|
||||
},
|
||||
"suggest" : {
|
||||
"symfony/http-foundation" : "Required to use parseSymfonyRequest() in AccountContext.",
|
||||
"symfony/psr-http-message-bridge" : "Required to use parseSymfonyRequest() in AccountContext.",
|
||||
"nyholm/psr7" : "Required to use parseSymfonyRequest() in AccountContext.",
|
||||
"symfony/http-foundation" : "Required to use fromSymfonyRequest() in AccountContext.",
|
||||
"symfony/psr-http-message-bridge" : "Required to use fromSymfonyRequest() in AccountContext.",
|
||||
"nyholm/psr7" : "Required to use fromSymfonyRequest() in AccountContext.",
|
||||
"defuse/php-encryption": "Required to use CryptoHelper"
|
||||
}
|
||||
}
|
||||
Generated
+1457
-1460
File diff suppressed because it is too large.
Load diff
+210
@@ -0,0 +1,210 @@
|
||||
## 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.
|
||||
|
||||
```php
|
||||
use CloudObjects\SDK\ObjectRetriever;
|
||||
|
||||
// Keep objects in memory for 5 minutes
|
||||
$retriever = new ObjectRetriever([
|
||||
'cache_ttl' => 300,
|
||||
]);
|
||||
```
|
||||
|
||||
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`.
|
||||
|
||||
All keys written to the external cache are prefixed with the value of the `cache_prefix` option (default: `clobj:`), so you can share a cache with other parts of your application.
|
||||
|
||||
The following examples use the [Symfony Cache component](https://symfony.com/doc/current/components/cache.html), but any library implementing one of the interfaces above works.
|
||||
|
||||
Using a PSR-6 cache pool, e.g. on the filesystem:
|
||||
|
||||
```php
|
||||
use CloudObjects\SDK\ObjectRetriever;
|
||||
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
|
||||
|
||||
$retriever = new ObjectRetriever([
|
||||
'cache_storage' => new FilesystemAdapter('cloudobjects', 0, '/var/cache/myapp'),
|
||||
'cache_ttl' => 300,
|
||||
]);
|
||||
```
|
||||
|
||||
Using a PSR-16 simple cache, e.g. in Redis:
|
||||
|
||||
```php
|
||||
use CloudObjects\SDK\ObjectRetriever;
|
||||
use Symfony\Component\Cache\Adapter\RedisAdapter;
|
||||
use Symfony\Component\Cache\Psr16Cache;
|
||||
|
||||
$redis = RedisAdapter::createConnection('redis://localhost');
|
||||
|
||||
$retriever = new ObjectRetriever([
|
||||
'cache_storage' => new Psr16Cache(new RedisAdapter($redis)),
|
||||
'cache_prefix' => 'myapp:clobj:',
|
||||
]);
|
||||
```
|
||||
|
||||
### Fallback Store
|
||||
|
||||
Fallback store is a mechanism that can be used by applications that require high availability but may lose connectivity to CloudObjects Core. With fallback store, a copy of an object is saved in a directory. If the API fails with a 5xx error, times out, or the request fails without a response, an `ObjectRetriever` will return the stored copy instead. Be advised that fallback storage will, once configured, result in your application not noticing when the Object API is unreachable - at least for objects in the store - and will continue to deliver the saved copy even if the original in CloudObjects Core is changed or removed.
|
||||
|
||||
It is possible for the fallback store to be configured for all objects or only for objects in specified namespaces. Since the SDK updates stored copies whenever it retrieves an object successfully but never deletes them, not even when the object has been removed from CloudObjects Core, the fallback store can grow large; hence it should be limited to specific namespaces in applications where many random objects may be retrieved. **Use this feature with caution and disable it if it causes any problems.**
|
||||
|
||||
To enable fallback store, set the `fallback_store_directory` option to a writeable path on the filesystem. Also, set `fallback_store_scope` to either the string `*` to save all retrieved objects, or a list of namespaces (domains only without `coid://` prefix); the list of namespaces can be provided as a comma-separated string or an array of strings.
|
||||
|
||||
Storing all retrieved objects:
|
||||
|
||||
```php
|
||||
use CloudObjects\SDK\ObjectRetriever;
|
||||
|
||||
$retriever = new ObjectRetriever([
|
||||
'fallback_store_directory' => '/var/lib/myapp/cloudobjects-fallback',
|
||||
'fallback_store_scope' => '*',
|
||||
]);
|
||||
```
|
||||
|
||||
Storing only objects from specific namespaces, provided as an array:
|
||||
|
||||
```php
|
||||
use CloudObjects\SDK\ObjectRetriever;
|
||||
|
||||
$retriever = new ObjectRetriever([
|
||||
'fallback_store_directory' => '/var/lib/myapp/cloudobjects-fallback',
|
||||
'fallback_store_scope' => [ 'example.com', 'cloudobjects.io' ],
|
||||
]);
|
||||
```
|
||||
|
||||
Or as a comma-separated string, which is convenient when the value comes from an environment variable:
|
||||
|
||||
```php
|
||||
use CloudObjects\SDK\ObjectRetriever;
|
||||
|
||||
$retriever = new ObjectRetriever([
|
||||
'fallback_store_directory' => getenv('FALLBACK_STORE_DIRECTORY'),
|
||||
'fallback_store_scope' => 'example.com,cloudobjects.io',
|
||||
]);
|
||||
```
|
||||
|
||||
The fallback store can be combined with an external cache. The cache is still consulted first; the fallback store only comes into play when an object is neither cached nor retrievable from the API.
|
||||
|
||||
```php
|
||||
use CloudObjects\SDK\ObjectRetriever;
|
||||
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
|
||||
|
||||
$retriever = new ObjectRetriever([
|
||||
'cache_storage' => new FilesystemAdapter('cloudobjects', 0, '/var/cache/myapp'),
|
||||
'cache_ttl' => 300,
|
||||
'fallback_store_directory' => '/var/lib/myapp/cloudobjects-fallback',
|
||||
'fallback_store_scope' => 'example.com',
|
||||
]);
|
||||
```
|
||||
|
||||
### Permanent Store
|
||||
|
||||
Permanent store is a mechanism that can be used by applications that want to minimize connections to CloudObjects Core, may go offline after initialization, or want to work with the same version of an object for the lifetime of their run without getting updates. With permanent store, an `ObjectRetriever` saves a copy of an object in a directory the first time it retrieves it from the Object API. Once an object is in the permanent store, the API will _never_ be contacted for this object again. Be advised that permanent storage will, once configured, result in your application continuing to deliver the saved copy even if the original in CloudObjects Core is changed or removed, and not noticing when the Object API is unreachable - at least for objects in the store.
|
||||
|
||||
It is possible for the permanent store to be configured for all objects or only for objects in specified namespaces. Since the permanent store is never purged or refreshed by the SDK, it can grow large; hence it should be limited to specific namespaces in applications where many random objects may be retrieved. To get the latest version of an object, delete its copy from the directory, or clear the directory entirely. **Use this feature with caution and disable it if it causes any problems.**
|
||||
|
||||
To enable permanent store, set the `permanent_store_directory` option to a writeable path on the filesystem. Also, set `permanent_store_scope` to either the string `*` to save all retrieved objects, or a list of namespaces (domains only without `coid://` prefix); the list of namespaces can be provided as a comma-separated string or an array of strings.
|
||||
|
||||
Storing all retrieved objects:
|
||||
|
||||
```php
|
||||
use CloudObjects\SDK\ObjectRetriever;
|
||||
|
||||
$retriever = new ObjectRetriever([
|
||||
'permanent_store_directory' => '/var/lib/myapp/cloudobjects-permanent',
|
||||
'permanent_store_scope' => '*',
|
||||
]);
|
||||
```
|
||||
|
||||
Storing only objects from specific namespaces, provided as an array:
|
||||
|
||||
```php
|
||||
use CloudObjects\SDK\ObjectRetriever;
|
||||
|
||||
$retriever = new ObjectRetriever([
|
||||
'permanent_store_directory' => '/var/lib/myapp/cloudobjects-permanent',
|
||||
'permanent_store_scope' => [ 'example.com', 'cloudobjects.io' ],
|
||||
]);
|
||||
```
|
||||
|
||||
Or as a comma-separated string, which is convenient when the value comes from an environment variable:
|
||||
|
||||
```php
|
||||
use CloudObjects\SDK\ObjectRetriever;
|
||||
|
||||
$retriever = new ObjectRetriever([
|
||||
'permanent_store_directory' => getenv('PERMANENT_STORE_DIRECTORY'),
|
||||
'permanent_store_scope' => 'example.com,cloudobjects.io',
|
||||
]);
|
||||
```
|
||||
|
||||
The permanent store can be combined with an external cache. The cache is still consulted first, as it may be faster than the filesystem; the permanent store only comes into play when an object is not cached, and the API is only contacted when the object is in neither.
|
||||
|
||||
```php
|
||||
use CloudObjects\SDK\ObjectRetriever;
|
||||
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
|
||||
|
||||
$retriever = new ObjectRetriever([
|
||||
'cache_storage' => new FilesystemAdapter('cloudobjects', 0, '/var/cache/myapp'),
|
||||
'cache_ttl' => 300,
|
||||
'permanent_store_directory' => '/var/lib/myapp/cloudobjects-permanent',
|
||||
'permanent_store_scope' => 'example.com',
|
||||
]);
|
||||
```
|
||||
|
||||
Permanent store and fallback store can be combined, for example to pin objects from your own namespace while keeping a fallback copy of everything else. Both stores must use separate directories, otherwise the `ObjectRetriever` throws an `InvalidSDKConfigurationException`. If an object is in scope of both, the permanent store takes precedence.
|
||||
|
||||
```php
|
||||
use CloudObjects\SDK\ObjectRetriever;
|
||||
|
||||
$retriever = new ObjectRetriever([
|
||||
'permanent_store_directory' => '/var/lib/myapp/cloudobjects-permanent',
|
||||
'permanent_store_scope' => 'example.com',
|
||||
'fallback_store_directory' => '/var/lib/myapp/cloudobjects-fallback',
|
||||
'fallback_store_scope' => '*',
|
||||
]);
|
||||
```
|
||||
|
||||
Unlike the [static object directory](#static-objects), which contains object descriptions that you provide yourself, the permanent store is filled automatically by the SDK from the Object API.
|
||||
|
||||
### Static Objects
|
||||
|
||||
The static object directory is a mechanism that is mostly intended for testing. It allows you to provide object descriptions yourself, for example as fixtures in your test suite, so that your code can be tested without connecting to CloudObjects Core, or with objects that differ from or do not yet exist in CloudObjects Core. It should usually not be configured in production.
|
||||
|
||||
To enable it, set the `static_object_directory` option to a path on the filesystem. Object descriptions are read from `<directory>/<namespace>/<path of COID>/object.jsonld`, which is the same layout used by the fallback and permanent stores. For example, the object `coid://example.com/MyObject` is read from `<directory>/example.com/MyObject/object.jsonld`, and the namespace object `coid://example.com` itself is read from `<directory>/example.com/object.jsonld`. Each file must contain the object description in JSON-LD format, with an `@id` that matches the COID.
|
||||
|
||||
```json
|
||||
{
|
||||
"@id": "coid://example.com/MyObject",
|
||||
"http://www.w3.org/2000/01/rdf-schema#label": "My Object"
|
||||
}
|
||||
```
|
||||
|
||||
The static object directory is consulted before the external cache, the permanent store and the API. Objects that are not found in the directory are retrieved as usual, so you can override only some objects and let the SDK fetch the rest:
|
||||
|
||||
```php
|
||||
use CloudObjects\SDK\ObjectRetriever;
|
||||
|
||||
$retriever = new ObjectRetriever([
|
||||
'static_object_directory' => __DIR__.'/fixtures/objects',
|
||||
]);
|
||||
```
|
||||
|
||||
Static objects are only used by `getObjectNode()` and the methods built on it; methods that fetch multiple objects from a namespace, such as `fetchAllObjectsInNamespace()`, always use the API. The SDK never writes to the static object directory, so it is not necessary to specify a scope; any COIDs will be considered for the static store.
|
||||
|
||||
In earlier versions of the SDK, this option was called `static_config_path`. The old name is deprecated but still supported.
|
||||
@@ -0,0 +1,20 @@
|
||||
## Options
|
||||
|
||||
The `ObjectRetriever` constructor accepts an associative array of options. All options are optional; any option not specified uses its default value.
|
||||
|
||||
| Key | Description | Default value |
|
||||
| --- | --- | --- |
|
||||
| `cache_storage` | External cache storage; must implement `Kevinrob\GuzzleCache\Storage\CacheStorageInterface`, `Psr\Cache\CacheItemPoolInterface` or `Psr\SimpleCache\CacheInterface`. See [caching](caching.md). | `null` |
|
||||
| `cache_prefix` | Prefix for all keys written to the external cache. | `'clobj:'` |
|
||||
| `cache_ttl` | Number of seconds that object descriptions are cached, both in memory and in the external cache. | `60` |
|
||||
| `static_object_directory` | Directory with static object descriptions, which are used instead of the API; mostly useful for testing. Objects are read from `<directory>/<namespace>/<path of COID>/object.jsonld`. Replaces the deprecated `static_config_path`. See [caching](caching.md#static-objects). | `null` |
|
||||
| `auth_ns` | Hostname of the namespace to authenticate as, e.g. `example.com`. | `null` |
|
||||
| `auth_secret` | Shared secret between the authenticating namespace and CloudObjects. | `null` |
|
||||
| `api_base_url` | Base URL of the CloudObjects Core Object API. If not set, `https://od.coid.link/` is used. | `null` |
|
||||
| `logger` | Logger implementing `Psr\Log\LoggerInterface`, which receives info messages about object retrieval. | `null` |
|
||||
| `timeout` | Total timeout for API requests, in seconds. | `20` |
|
||||
| `connect_timeout` | Timeout for establishing a connection to the API, in seconds. | `5` |
|
||||
| `fallback_store_directory` | Writeable directory in which copies of retrieved objects are saved and returned when the API fails with a 5xx error, times out, or the request fails without a response. Requires `fallback_store_scope`. See [caching](caching.md#fallback-store). | `null` |
|
||||
| `fallback_store_scope` | Which objects are saved in the fallback store: `'*'` for all objects, or a list of namespaces (domains without `coid://` prefix) as a comma-separated string or an array of strings. | `null` |
|
||||
| `permanent_store_directory` | Writeable directory in which copies of retrieved objects are saved; once an object is stored there, it is always returned from the store and the API is never contacted for it again. Requires `permanent_store_scope` and must differ from `fallback_store_directory`. See [caching](caching.md#permanent-store). | `null` |
|
||||
| `permanent_store_scope` | Which objects are saved in the permanent store: `'*'` for all objects, or a list of namespaces (domains without `coid://` prefix) as a comma-separated string or an array of strings. | `null` |
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
syntaxCheck="false"
|
||||
bootstrap="./tests/bootstrap.php">
|
||||
<testsuites>
|
||||
<testsuite name="OfflineTests">
|
||||
|
||||
@@ -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;
|
||||
|
||||
class AAUIDParserTest extends \PHPUnit_Framework_TestCase {
|
||||
class AAUIDParserTest extends \PHPUnit\Framework\TestCase {
|
||||
|
||||
public function testValidAccountAAUID() {
|
||||
$aauid = new IRI('aauid:abcd1234abcd1234');
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace CloudObjects\SDK\AccountGateway;
|
||||
use GuzzleHttp\Psr7\Request as GuzzlePsrRequest;
|
||||
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
|
||||
|
||||
class AccountContextParseTest extends \PHPUnit_Framework_TestCase {
|
||||
class AccountContextParseTest extends \PHPUnit\Framework\TestCase {
|
||||
|
||||
public function testParsePsrRequest() {
|
||||
$request = new GuzzlePsrRequest('GET', '/', [
|
||||
|
||||
@@ -8,11 +8,11 @@ namespace CloudObjects\SDK\AccountGateway;
|
||||
|
||||
use ML\IRI\IRI;
|
||||
|
||||
class AccountContextTest extends \PHPUnit_Framework_TestCase {
|
||||
class AccountContextTest extends \PHPUnit\Framework\TestCase {
|
||||
|
||||
private $context;
|
||||
|
||||
protected function setUp() {
|
||||
protected function setUp(): void {
|
||||
$this->context = new AccountContext(new IRI('aauid:aaaabbbbccccdddd'), 'DUMMY');
|
||||
}
|
||||
|
||||
|
||||
@@ -8,123 +8,175 @@ namespace CloudObjects\SDK;
|
||||
|
||||
use ML\IRI\IRI;
|
||||
|
||||
class COIDParserTest extends \PHPUnit_Framework_TestCase {
|
||||
class COIDParserTest extends \PHPUnit\Framework\TestCase {
|
||||
|
||||
public function testRootCOID() {
|
||||
$coid = new IRI('coid://example.com');
|
||||
$this->assertEquals(COIDParser::COID_ROOT, COIDParser::getType($coid));
|
||||
}
|
||||
public function testRootCOID() {
|
||||
$coid = new IRI('coid://example.com');
|
||||
$this->assertEquals(COIDParser::COID_ROOT, COIDParser::getType($coid));
|
||||
|
||||
public function testInvalidRootCOID() {
|
||||
$coid = new IRI('coid://example');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
$coid = new IRI('coid://exämple.com');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
$coid = new IRI('coid://ex&mple.com');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
}
|
||||
$coid = new IRI('coid://subdomain.example.com');
|
||||
$this->assertEquals(COIDParser::COID_ROOT, COIDParser::getType($coid));
|
||||
|
||||
public function testInvalidCOID() {
|
||||
$coid = new IRI('http://example.com');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
$coid = new IRI('example.com');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
$coid = new IRI('COID://example.com');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
$coid = new IRI('Coid://example.com');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
$coid = new IRI('coid://EXAMPLE.COM');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
$coid = new IRI('coid://exAMPle.CoM');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
}
|
||||
$coid = new IRI('coid://anotherlevel.subdomain.example.com');
|
||||
$this->assertEquals(COIDParser::COID_ROOT, COIDParser::getType($coid));
|
||||
}
|
||||
|
||||
public function testUnversionedCOID() {
|
||||
$coid = new IRI('coid://example.com/Example');
|
||||
$this->assertEquals(COIDParser::COID_UNVERSIONED, COIDParser::getType($coid));
|
||||
}
|
||||
public function testInvalidRootCOID() {
|
||||
$coid = new IRI('coid://example');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
|
||||
public function testInvalidUnversionedCOID() {
|
||||
$coid = new IRI('coid://example.com/Exümple');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
$coid = new IRI('coid://example.com/Examp%e');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
}
|
||||
$coid = new IRI('coid://exämple.com');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
|
||||
public function testVersionedCOID() {
|
||||
$coid = new IRI('coid://example.com/Example/1.0');
|
||||
$this->assertEquals(COIDParser::COID_VERSIONED, COIDParser::getType($coid));
|
||||
$coid = new IRI('coid://example.com/Example/alpha');
|
||||
$this->assertEquals(COIDParser::COID_VERSIONED, COIDParser::getType($coid));
|
||||
}
|
||||
$coid = new IRI('coid://ex&mple.com');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
}
|
||||
|
||||
public function testInvalidVersionedCOID() {
|
||||
$coid = new IRI('coid://example.com/Example/1.$');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
}
|
||||
public function testInvalidCOID() {
|
||||
$coid = new IRI('http://example.com');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
|
||||
public function testVersionWildcardCOID() {
|
||||
$coid = new IRI('coid://example.com/Example/^1.0');
|
||||
$this->assertEquals(COIDParser::COID_VERSION_WILDCARD, COIDParser::getType($coid));
|
||||
$coid = new IRI('coid://example.com/Example/~1.0');
|
||||
$this->assertEquals(COIDParser::COID_VERSION_WILDCARD, COIDParser::getType($coid));
|
||||
$coid = new IRI('coid://example.com/Example/1.*');
|
||||
$this->assertEquals(COIDParser::COID_VERSION_WILDCARD, COIDParser::getType($coid));
|
||||
}
|
||||
$coid = new IRI('example.com');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
|
||||
public function testInvalidVersionWildcardCOID() {
|
||||
$coid = new IRI('coid://example.com/Example/^1.*');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
$coid = new IRI('coid://example.com/Example/1.a.*');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
}
|
||||
$coid = new IRI('COID://example.com');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
|
||||
public function testIRICaseSensitivity() {
|
||||
$coid1 = new IRI('coid://example.com/example/1.0');
|
||||
$coid2 = new IRI('coid://example.com/Example/1.0');
|
||||
$this->assertFalse($coid1->equals($coid2));
|
||||
}
|
||||
$coid = new IRI('Coid://example.com');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
|
||||
public function testRootFromString() {
|
||||
$coid1 = new IRI('coid://example.com');
|
||||
$coid2 = COIDParser::fromString('coid://example.com');
|
||||
$coid3 = COIDParser::fromString('example.com');
|
||||
$this->assertTrue($coid1->equals($coid2));
|
||||
$this->assertTrue($coid1->equals($coid3));
|
||||
}
|
||||
$coid = new IRI('coid://EXAMPLE.COM');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
|
||||
public function testUnversionedFromString() {
|
||||
$coid1 = new IRI('coid://example.com/Example');
|
||||
$coid2 = COIDParser::fromString('coid://example.com/Example');
|
||||
$coid3 = COIDParser::fromString('example.com/Example');
|
||||
$this->assertTrue($coid1->equals($coid2));
|
||||
$this->assertTrue($coid1->equals($coid3));
|
||||
}
|
||||
$coid = new IRI('coid://exAMPle.CoM');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
|
||||
public function testVersionedFromString() {
|
||||
$coid1 = new IRI('coid://example.com/Example/1.0');
|
||||
$coid2 = COIDParser::fromString('coid://example.com/Example/1.0');
|
||||
$coid3 = COIDParser::fromString('example.com/Example/1.0');
|
||||
$this->assertTrue($coid1->equals($coid2));
|
||||
$this->assertTrue($coid1->equals($coid3));
|
||||
}
|
||||
$coid = new IRI('coid://example.com#hash=something');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
|
||||
public function testNormalizeRootFromString() {
|
||||
$coid1 = new IRI('coid://example.com');
|
||||
$coid2 = COIDParser::fromString('COID://example.com');
|
||||
$coid3 = COIDParser::fromString('ExAmple.COM');
|
||||
$this->assertTrue($coid1->equals($coid2));
|
||||
$this->assertTrue($coid1->equals($coid3));
|
||||
}
|
||||
$coid = new IRI('coid://example.com?query=something');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
|
||||
public function testNormalizeNonRootFromString() {
|
||||
$coid1 = new IRI('coid://example.com/Example');
|
||||
$coid2 = COIDParser::fromString('COID://example.com/Example');
|
||||
$coid3 = COIDParser::fromString('ExAmple.COM/Example');
|
||||
$coid4 = COIDParser::fromString('ExAmple.COM/EXample');
|
||||
$this->assertTrue($coid1->equals($coid2));
|
||||
$this->assertTrue($coid1->equals($coid3));
|
||||
$this->assertFalse($coid1->equals($coid4));
|
||||
}
|
||||
$coid = new IRI('coid://example.com/Example/1.0?query=something');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
|
||||
$coid = new IRI('coid://example.com/Example#hash');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
|
||||
$coid = new IRI('coid://example.com?');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
|
||||
$coid = new IRI('coid://example.com#');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
}
|
||||
|
||||
public function testUnversionedCOID() {
|
||||
$coid = new IRI('coid://subdomain.example.com/Example');
|
||||
$this->assertEquals(COIDParser::COID_UNVERSIONED, COIDParser::getType($coid));
|
||||
}
|
||||
|
||||
public function testInvalidUnversionedCOID() {
|
||||
$coid = new IRI('coid://example.com/Exümple');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
|
||||
$coid = new IRI('coid://example.com/Examp%e');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
}
|
||||
|
||||
public function testVersionedCOID() {
|
||||
$coid = new IRI('coid://anotherlevel.subdomain.example.com/Example/1.0');
|
||||
$this->assertEquals(COIDParser::COID_VERSIONED, COIDParser::getType($coid));
|
||||
|
||||
$coid = new IRI('coid://subdomain.example.com/Example/alpha');
|
||||
$this->assertEquals(COIDParser::COID_VERSIONED, COIDParser::getType($coid));
|
||||
}
|
||||
|
||||
public function testInvalidVersionedCOID() {
|
||||
$coid = new IRI('coid://example.com/Example/1.$');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
}
|
||||
|
||||
public function testVersionWildcardCOID() {
|
||||
$coid = new IRI('coid://example.com/Example/^1.0');
|
||||
$this->assertEquals(COIDParser::COID_VERSION_WILDCARD, COIDParser::getType($coid));
|
||||
|
||||
$coid = new IRI('coid://example.com/Example/~1.0');
|
||||
$this->assertEquals(COIDParser::COID_VERSION_WILDCARD, COIDParser::getType($coid));
|
||||
|
||||
$coid = new IRI('coid://example.com/Example/1.*');
|
||||
$this->assertEquals(COIDParser::COID_VERSION_WILDCARD, COIDParser::getType($coid));
|
||||
}
|
||||
|
||||
public function testInvalidVersionWildcardCOID() {
|
||||
$coid = new IRI('coid://example.com/Example/^1.*');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
|
||||
$coid = new IRI('coid://example.com/Example/1.a.*');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
}
|
||||
|
||||
public function testIRICaseSensitivity() {
|
||||
$coid1 = new IRI('coid://example.com/example/1.0');
|
||||
$coid2 = new IRI('coid://example.com/Example/1.0');
|
||||
$this->assertFalse($coid1->equals($coid2));
|
||||
}
|
||||
|
||||
public function testRootFromString() {
|
||||
$coid1 = new IRI('coid://example.com');
|
||||
$coid2 = COIDParser::fromString('coid://example.com');
|
||||
$coid3 = COIDParser::fromString('example.com');
|
||||
$this->assertTrue($coid1->equals($coid2));
|
||||
$this->assertTrue($coid1->equals($coid3));
|
||||
}
|
||||
|
||||
public function testUnversionedFromString() {
|
||||
$coid1 = new IRI('coid://example.com/Example');
|
||||
$coid2 = COIDParser::fromString('coid://example.com/Example');
|
||||
$coid3 = COIDParser::fromString('example.com/Example');
|
||||
$this->assertTrue($coid1->equals($coid2));
|
||||
$this->assertTrue($coid1->equals($coid3));
|
||||
}
|
||||
|
||||
public function testVersionedFromString() {
|
||||
$coid1 = new IRI('coid://example.com/Example/1.0');
|
||||
$coid2 = COIDParser::fromString('coid://example.com/Example/1.0');
|
||||
$coid3 = COIDParser::fromString('example.com/Example/1.0');
|
||||
$this->assertTrue($coid1->equals($coid2));
|
||||
$this->assertTrue($coid1->equals($coid3));
|
||||
}
|
||||
|
||||
public function testNormalizeRootFromString() {
|
||||
$coid1 = new IRI('coid://example.com');
|
||||
$coid2 = COIDParser::fromString('COID://example.com');
|
||||
$coid3 = COIDParser::fromString('ExAmple.COM');
|
||||
$this->assertTrue($coid1->equals($coid2));
|
||||
$this->assertTrue($coid1->equals($coid3));
|
||||
}
|
||||
|
||||
public function testNormalizeNonRootFromString() {
|
||||
$coid1 = new IRI('coid://example.com/Example');
|
||||
$coid2 = COIDParser::fromString('COID://example.com/Example');
|
||||
$coid3 = COIDParser::fromString('ExAmple.COM/Example');
|
||||
$coid4 = COIDParser::fromString('ExAmple.COM/EXample');
|
||||
$this->assertTrue($coid1->equals($coid2));
|
||||
$this->assertTrue($coid1->equals($coid3));
|
||||
$this->assertFalse($coid1->equals($coid4));
|
||||
}
|
||||
|
||||
public function testQueryAndFragmentFromString() {
|
||||
$coid = COIDParser::fromString('example.com/Example?query=something');
|
||||
$this->assertEquals('coid://example.com/Example?query=something', (string)$coid);
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
|
||||
$coid = COIDParser::fromString('COID://Example.com/Example/1.0#hash');
|
||||
$this->assertEquals('coid://example.com/Example/1.0#hash', (string)$coid);
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
|
||||
$coid = COIDParser::fromString('example.com?');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
|
||||
$coid = COIDParser::fromString('example.com#');
|
||||
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,7 +11,7 @@ use GuzzleHttp\Client, GuzzleHttp\Handler\MockHandler,
|
||||
GuzzleHttp\HandlerStack, GuzzleHttp\Psr7\Response;
|
||||
use CloudObjects\SDK\ObjectRetriever;
|
||||
|
||||
class CryptoHelperTest extends \PHPUnit_Framework_TestCase {
|
||||
class CryptoHelperTest extends \PHPUnit\Framework\TestCase {
|
||||
|
||||
private $retriever;
|
||||
private $graph;
|
||||
@@ -22,7 +22,7 @@ class CryptoHelperTest extends \PHPUnit_Framework_TestCase {
|
||||
$this->retriever->setClient(new Client(['handler' => $handler]));
|
||||
}
|
||||
|
||||
public function setUp() {
|
||||
protected function setUp(): void {
|
||||
$this->retriever = new ObjectRetriever([
|
||||
'auth_ns' => 'test.cloudobjects.io',
|
||||
'auth_secret' => 'TEST'
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,12 +10,12 @@ use InvalidArgumentException;
|
||||
use ML\JsonLD\JsonLD;
|
||||
use CloudObjects\SDK\ObjectRetriever;
|
||||
|
||||
class SchemaValidatorTest extends \PHPUnit_Framework_TestCase {
|
||||
class SchemaValidatorTest extends \PHPUnit\Framework\TestCase {
|
||||
|
||||
private $schemaValidator;
|
||||
private $graph;
|
||||
|
||||
public function setUp() {
|
||||
protected function setUp(): void {
|
||||
$this->schemaValidator = new SchemaValidator(new ObjectRetriever);
|
||||
$this->graph = JsonLD::getDocument('{}')->getGraph();
|
||||
}
|
||||
@@ -24,10 +24,11 @@ class SchemaValidatorTest extends \PHPUnit_Framework_TestCase {
|
||||
$node = $this->graph->createNode();
|
||||
$node->setType($this->graph->createNode('coid://json.co-n.net/String'));
|
||||
$this->schemaValidator->validateAgainstNode("Test", $node);
|
||||
$this->addToAssertionCount(1);
|
||||
}
|
||||
|
||||
public function testNotString() {
|
||||
$this->setExpectedException(InvalidArgumentException::class);
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
|
||||
$node = $this->graph->createNode();
|
||||
$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->setType($this->graph->createNode('coid://json.co-n.net/Number'));
|
||||
$this->schemaValidator->validateAgainstNode(3.5, $node);
|
||||
$this->addToAssertionCount(1);
|
||||
}
|
||||
|
||||
public function testNotNumber() {
|
||||
$this->setExpectedException(InvalidArgumentException::class);
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
|
||||
$node = $this->graph->createNode();
|
||||
$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->setType($this->graph->createNode('coid://json.co-n.net/Integer'));
|
||||
$this->schemaValidator->validateAgainstNode(12, $node);
|
||||
$this->addToAssertionCount(1);
|
||||
}
|
||||
|
||||
public function testNotInteger() {
|
||||
$this->setExpectedException(InvalidArgumentException::class);
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
|
||||
$node = $this->graph->createNode();
|
||||
$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->setType($this->graph->createNode('coid://json.co-n.net/Array'));
|
||||
$this->schemaValidator->validateAgainstNode([ 1, 2, "foo" ], $node);
|
||||
$this->addToAssertionCount(1);
|
||||
}
|
||||
|
||||
public function testNotArray() {
|
||||
$this->setExpectedException(InvalidArgumentException::class);
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
|
||||
$node = $this->graph->createNode();
|
||||
$node->setType($this->graph->createNode('coid://json.co-n.net/Array'));
|
||||
@@ -83,10 +87,11 @@ class SchemaValidatorTest extends \PHPUnit_Framework_TestCase {
|
||||
'a' => 'A',
|
||||
'b' => 'B'
|
||||
], $node);
|
||||
$this->addToAssertionCount(1);
|
||||
}
|
||||
|
||||
public function testNotObject() {
|
||||
$this->setExpectedException(InvalidArgumentException::class);
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
|
||||
$node = $this->graph->createNode();
|
||||
$node->setType($this->graph->createNode('coid://json.co-n.net/Object'));
|
||||
@@ -105,10 +110,11 @@ class SchemaValidatorTest extends \PHPUnit_Framework_TestCase {
|
||||
'a' => 'A',
|
||||
'b' => 'B'
|
||||
], $node);
|
||||
$this->addToAssertionCount(1);
|
||||
}
|
||||
|
||||
public function testObjectWithPropertyTypeError() {
|
||||
$this->setExpectedException(InvalidArgumentException::class);
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
|
||||
$stringNode = $this->graph->createNode();
|
||||
$stringNode->setProperty('coid://json.co-n.net/hasKey', 'a');
|
||||
@@ -136,10 +142,11 @@ class SchemaValidatorTest extends \PHPUnit_Framework_TestCase {
|
||||
'a' => 'A',
|
||||
'b' => 'B'
|
||||
], $node);
|
||||
$this->addToAssertionCount(1);
|
||||
}
|
||||
|
||||
public function testObjectWithRequiredPropertyTypeError() {
|
||||
$this->setExpectedException(InvalidArgumentException::class);
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
|
||||
$stringNode = $this->graph->createNode();
|
||||
$stringNode->setProperty('coid://json.co-n.net/hasKey', 'a');
|
||||
@@ -156,7 +163,7 @@ class SchemaValidatorTest extends \PHPUnit_Framework_TestCase {
|
||||
}
|
||||
|
||||
public function testObjectWithRequiredPropertyMissing() {
|
||||
$this->setExpectedException(InvalidArgumentException::class);
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
|
||||
$stringNode = $this->graph->createNode();
|
||||
$stringNode->setProperty('coid://json.co-n.net/hasKey', 'a');
|
||||
|
||||
@@ -7,150 +7,189 @@
|
||||
namespace CloudObjects\SDK;
|
||||
|
||||
use ML\IRI\IRI;
|
||||
use ML\JsonLD\Node;
|
||||
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 $reader;
|
||||
private $retriever;
|
||||
private $reader;
|
||||
|
||||
private function setMockResponse(Response $response) {
|
||||
$mock = new MockHandler([$response]);
|
||||
$handler = HandlerStack::create($mock);
|
||||
$this->retriever->setClient(new Client(['handler' => $handler]));
|
||||
}
|
||||
private function setMockResponse(Response $response) {
|
||||
$mock = new MockHandler([$response]);
|
||||
$handler = HandlerStack::create($mock);
|
||||
$this->retriever->setClient(new Client(['handler' => $handler]));
|
||||
}
|
||||
|
||||
private function useRootResourceMock() {
|
||||
$this->setMockResponse(new Response(200,
|
||||
['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"}'));
|
||||
}
|
||||
private function useRootResourceMock() {
|
||||
$this->setMockResponse(new Response(200,
|
||||
['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"}'
|
||||
));
|
||||
}
|
||||
|
||||
protected function setUp() {
|
||||
$this->retriever = new ObjectRetriever;
|
||||
$this->reader = new NodeReader([
|
||||
'prefixes' => [
|
||||
'co' => 'coid://cloudobjects.io/',
|
||||
'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#'
|
||||
]
|
||||
]);
|
||||
}
|
||||
protected function setUp(): void {
|
||||
$this->retriever = new ObjectRetriever;
|
||||
$this->reader = new NodeReader([
|
||||
'prefixes' => [
|
||||
'co' => 'coid://cloudobjects.io/',
|
||||
'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#'
|
||||
]
|
||||
]);
|
||||
$this->retriever->setDefaultReader($this->reader);
|
||||
}
|
||||
|
||||
public function testHasType1() {
|
||||
$coid = new IRI('coid://cloudobjects.io');
|
||||
$this->useRootResourceMock();
|
||||
$object = $this->retriever->getObject($coid);
|
||||
public function testHasType1() {
|
||||
$coid = new IRI('coid://cloudobjects.io');
|
||||
$this->useRootResourceMock();
|
||||
$object = $this->retriever->getObjectNode($coid);
|
||||
|
||||
$this->assertTrue($this->reader->hasType($object, 'coid://cloudobjects.io/Namespace'));
|
||||
$this->assertTrue($this->reader->hasType($object, 'co:Namespace'));
|
||||
$this->assertFalse($this->reader->hasType($object, 'coid://cloudobjects.io/MemberRole'));
|
||||
$this->assertFalse($this->reader->hasType($object, 'co:MemberRole'));
|
||||
}
|
||||
$this->assertTrue($this->reader->hasType($object, 'coid://cloudobjects.io/Namespace'));
|
||||
$this->assertTrue($this->reader->hasType($object, 'co:Namespace'));
|
||||
$this->assertFalse($this->reader->hasType($object, 'coid://cloudobjects.io/MemberRole'));
|
||||
$this->assertFalse($this->reader->hasType($object, 'co:MemberRole'));
|
||||
}
|
||||
|
||||
public function testHasPropertyValue1() {
|
||||
$coid = new IRI('coid://cloudobjects.io');
|
||||
$this->useRootResourceMock();
|
||||
$object = $this->retriever->getObject($coid);
|
||||
public function testHasPropertyValue1() {
|
||||
$coid = new IRI('coid://cloudobjects.io');
|
||||
$this->useRootResourceMock();
|
||||
$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, 'rdfs: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'));
|
||||
}
|
||||
|
||||
public function testGetFirstValueString1() {
|
||||
$coid = new IRI('coid://cloudobjects.io');
|
||||
$this->useRootResourceMock();
|
||||
$object = $this->retriever->getObject($coid);
|
||||
public function testGetFirstValueString1() {
|
||||
$coid = new IRI('coid://cloudobjects.io');
|
||||
$this->useRootResourceMock();
|
||||
$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, 'rdfs: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->assertNull($this->reader->getFirstValueString($object, 'coid://cloudobjects.io/makesTriplesVisibleTo'));
|
||||
$this->assertNull($this->reader->getFirstValueString($object, 'co:makesTriplesVisibleTo'));
|
||||
$this->assertNull($this->reader->getFirstValueString($object, 'coid://cloudobjects.io/makesTriplesVisibleTo'));
|
||||
$this->assertNull($this->reader->getFirstValueString($object, 'co:makesTriplesVisibleTo'));
|
||||
|
||||
$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, 'coid://cloudobjects.io/makesTriplesVisibleTo', 'theDefaultValue'));
|
||||
$this->assertEquals('theDefaultValue', $this->reader->getFirstValueString($object, 'co:makesTriplesVisibleTo', 'theDefaultValue'));
|
||||
|
||||
public function testGetFirstValueIRI1() {
|
||||
$coid = new IRI('coid://cloudobjects.io');
|
||||
$this->useRootResourceMock();
|
||||
$object = $this->retriever->getObject($coid);
|
||||
$object = $this->retriever->getCloudObject($coid);
|
||||
|
||||
$this->assertInstanceOf('ML\IRI\IRI', $this->reader->getFirstValueIRI($object, 'coid://cloudobjects.io/isVisibleTo'));
|
||||
$this->assertInstanceOf('ML\IRI\IRI', $this->reader->getFirstValueIRI($object, 'co:isVisibleTo'));
|
||||
$this->assertEquals('CloudObjects', $object->getString('http://www.w3.org/2000/01/rdf-schema#label'));
|
||||
$this->assertEquals('CloudObjects', $object->getString('rdfs:label'));
|
||||
|
||||
$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->assertNull($object->getString('coid://cloudobjects.io/makesTriplesVisibleTo'));
|
||||
$this->assertNull($object->getString('co:makesTriplesVisibleTo'));
|
||||
|
||||
public function testGetFirstValueNode1() {
|
||||
$coid = new IRI('coid://cloudobjects.io');
|
||||
$this->useRootResourceMock();
|
||||
$object = $this->retriever->getObject($coid);
|
||||
$this->assertEquals('theDefaultValue', $object->getString('coid://cloudobjects.io/makesTriplesVisibleTo', 'theDefaultValue'));
|
||||
$this->assertEquals('theDefaultValue', $object->getString('co:makesTriplesVisibleTo', 'theDefaultValue'));
|
||||
}
|
||||
|
||||
$this->assertInstanceOf('ML\JsonLD\Node', $this->reader->getFirstValueNode($object, 'coid://cloudobjects.io/isVisibleTo'));
|
||||
$this->assertInstanceOf('ML\JsonLD\Node', $this->reader->getFirstValueNode($object, 'co:isVisibleTo'));
|
||||
public function testGetFirstValueIRI1() {
|
||||
$coid = new IRI('coid://cloudobjects.io');
|
||||
$this->useRootResourceMock();
|
||||
$object = $this->retriever->getObjectNode($coid);
|
||||
|
||||
$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->assertInstanceOf(IRI::class, $this->reader->getFirstValueIRI($object, 'coid://cloudobjects.io/isVisibleTo'));
|
||||
$this->assertInstanceOf(IRI::class, $this->reader->getFirstValueIRI($object, 'co:isVisibleTo'));
|
||||
|
||||
public function testGetAllValuesString1() {
|
||||
$coid = new IRI('coid://cloudobjects.io');
|
||||
$this->useRootResourceMock();
|
||||
$object = $this->retriever->getObject($coid);
|
||||
$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->assertCount(1, $this->reader->getAllValuesString($object, 'http://www.w3.org/2000/01/rdf-schema#label'));
|
||||
$this->assertCount(1, $this->reader->getAllValuesString($object, 'rdfs:label'));
|
||||
$object = $this->retriever->getCloudObject($coid);
|
||||
|
||||
$this->assertEquals('CloudObjects', $this->reader->getAllValuesString($object, 'http://www.w3.org/2000/01/rdf-schema#label')[0]);
|
||||
$this->assertEquals('CloudObjects', $this->reader->getAllValuesString($object, 'rdfs:label')[0]);
|
||||
$this->assertInstanceOf(IRI::class, $object->getIRI('coid://cloudobjects.io/isVisibleTo'));
|
||||
$this->assertInstanceOf(IRI::class, $object->getIRI('co:isVisibleTo'));
|
||||
|
||||
$this->assertCount(0, $this->reader->getAllValuesString($object, 'coid://cloudobjects.io/makesTriplesVisibleTo'));
|
||||
$this->assertCount(0, $this->reader->getAllValuesString($object, 'co:makesTriplesVisibleTo'));
|
||||
$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'));
|
||||
}
|
||||
|
||||
$this->assertCount(2, $this->reader->getAllValuesString($object, '@type'));
|
||||
}
|
||||
public function testGetFirstValueNode1() {
|
||||
$coid = new IRI('coid://cloudobjects.io');
|
||||
$this->useRootResourceMock();
|
||||
$object = $this->retriever->getObjectNode($coid);
|
||||
|
||||
public function testGetAllValuesIRI1() {
|
||||
$coid = new IRI('coid://cloudobjects.io');
|
||||
$this->useRootResourceMock();
|
||||
$object = $this->retriever->getObject($coid);
|
||||
$this->assertInstanceOf(Node::class, $this->reader->getFirstValueNode($object, 'coid://cloudobjects.io/isVisibleTo'));
|
||||
$this->assertInstanceOf(Node::class, $this->reader->getFirstValueNode($object, 'co:isVisibleTo'));
|
||||
|
||||
$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->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->assertCount(1, $this->reader->getAllValuesIRI($object, 'coid://cloudobjects.io/isVisibleTo'));
|
||||
$this->assertCount(1, $this->reader->getAllValuesIRI($object, 'co:isVisibleTo'));
|
||||
$object = $this->retriever->getCloudObject($coid);
|
||||
|
||||
$this->assertCount(2, $this->reader->getAllValuesIRI($object, '@type'));
|
||||
$this->assertInstanceOf(Node::class, $object->getNode('coid://cloudobjects.io/isVisibleTo'));
|
||||
$this->assertInstanceOf(Node::class, $object->getNode('co:isVisibleTo'));
|
||||
|
||||
$this->assertInstanceOf('ML\IRI\IRI', $this->reader->getAllValuesIRI($object, 'coid://cloudobjects.io/isVisibleTo')[0]);
|
||||
$this->assertInstanceOf('ML\IRI\IRI', $this->reader->getAllValuesIRI($object, 'co:isVisibleTo')[0]);
|
||||
$this->assertEquals('coid://cloudobjects.io/Public', $object->getNode('coid://cloudobjects.io/isVisibleTo')->getId());
|
||||
$this->assertEquals('coid://cloudobjects.io/Public', $object->getNode('co:isVisibleTo')->getId());
|
||||
}
|
||||
|
||||
$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]);
|
||||
}
|
||||
public function testConstants() {
|
||||
$coid = new IRI('coid://cloudobjects.io');
|
||||
$this->useRootResourceMock();
|
||||
$object = $this->retriever->getCloudObject($coid);
|
||||
|
||||
public function testGetAllValuesNode1() {
|
||||
$coid = new IRI('coid://cloudobjects.io');
|
||||
$this->useRootResourceMock();
|
||||
$object = $this->retriever->getObject($coid);
|
||||
$this->assertEquals('6-fbea0c90b2c5e5300e4039ed99be9b2d', $object->getRevision());
|
||||
$this->assertEquals('CloudObjects', $object->getLabel());
|
||||
}
|
||||
|
||||
$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'));
|
||||
public function testGetAllValuesString1() {
|
||||
$coid = new IRI('coid://cloudobjects.io');
|
||||
$this->useRootResourceMock();
|
||||
$object = $this->retriever->getObjectNode($coid);
|
||||
|
||||
$this->assertCount(1, $this->reader->getAllValuesNode($object, 'coid://cloudobjects.io/isVisibleTo'));
|
||||
$this->assertCount(1, $this->reader->getAllValuesNode($object, 'co:isVisibleTo'));
|
||||
$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(2, $this->reader->getAllValuesNode($object, '@type'));
|
||||
$this->assertEquals('CloudObjects', $this->reader->getAllValuesString($object, 'http://www.w3.org/2000/01/rdf-schema#label')[0]);
|
||||
$this->assertEquals('CloudObjects', $this->reader->getAllValuesString($object, 'rdfs:label')[0]);
|
||||
|
||||
$this->assertInstanceOf('ML\JsonLD\Node', $this->reader->getAllValuesNode($object, 'coid://cloudobjects.io/isVisibleTo')[0]);
|
||||
$this->assertInstanceOf('ML\JsonLD\Node', $this->reader->getAllValuesNode($object, 'co:isVisibleTo')[0]);
|
||||
$this->assertCount(0, $this->reader->getAllValuesString($object, 'coid://cloudobjects.io/makesTriplesVisibleTo'));
|
||||
$this->assertCount(0, $this->reader->getAllValuesString($object, 'co:makesTriplesVisibleTo'));
|
||||
|
||||
$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->assertCount(2, $this->reader->getAllValuesString($object, '@type'));
|
||||
}
|
||||
|
||||
public function testGetAllValuesIRI1() {
|
||||
$coid = new IRI('coid://cloudobjects.io');
|
||||
$this->useRootResourceMock();
|
||||
$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, 'rdfs:label'));
|
||||
|
||||
$this->assertCount(1, $this->reader->getAllValuesIRI($object, 'coid://cloudobjects.io/isVisibleTo'));
|
||||
$this->assertCount(1, $this->reader->getAllValuesIRI($object, 'co:isVisibleTo'));
|
||||
|
||||
$this->assertCount(2, $this->reader->getAllValuesIRI($object, '@type'));
|
||||
|
||||
$this->assertInstanceOf(IRI::class, $this->reader->getAllValuesIRI($object, 'coid://cloudobjects.io/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, 'co:isVisibleTo')[0]);
|
||||
}
|
||||
|
||||
public function testGetAllValuesNode1() {
|
||||
$coid = new IRI('coid://cloudobjects.io');
|
||||
$this->useRootResourceMock();
|
||||
$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, 'rdfs:label'));
|
||||
|
||||
$this->assertCount(1, $this->reader->getAllValuesNode($object, 'coid://cloudobjects.io/isVisibleTo'));
|
||||
$this->assertCount(1, $this->reader->getAllValuesNode($object, 'co:isVisibleTo'));
|
||||
|
||||
$this->assertCount(2, $this->reader->getAllValuesNode($object, '@type'));
|
||||
|
||||
$this->assertInstanceOf(Node::class, $this->reader->getAllValuesNode($object, 'coid://cloudobjects.io/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, '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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,33 +8,473 @@ namespace CloudObjects\SDK;
|
||||
|
||||
use ML\IRI\IRI;
|
||||
use GuzzleHttp\Client, GuzzleHttp\Handler\MockHandler,
|
||||
GuzzleHttp\HandlerStack, GuzzleHttp\Psr7\Response;
|
||||
GuzzleHttp\HandlerStack, GuzzleHttp\Psr7\Request, GuzzleHttp\Psr7\Response;
|
||||
use GuzzleHttp\Exception\ConnectException, GuzzleHttp\Exception\RequestException;
|
||||
use CloudObjects\SDK\Exceptions\CoreAPIException;
|
||||
|
||||
class ObjectRetrieverMockTest extends \PHPUnit_Framework_TestCase {
|
||||
class ObjectRetrieverMockTest extends \PHPUnit\Framework\TestCase {
|
||||
|
||||
private $retriever;
|
||||
private $retriever;
|
||||
|
||||
private function setMockResponse(Response $response) {
|
||||
$mock = new MockHandler([$response]);
|
||||
$handler = HandlerStack::create($mock);
|
||||
$this->retriever->setClient(new Client(['handler' => $handler]));
|
||||
}
|
||||
/**
|
||||
* @param Response|\Throwable ...$responses Responses in the order they are returned,
|
||||
* or exceptions to throw instead.
|
||||
*/
|
||||
private function setMockResponse(...$responses) : MockHandler {
|
||||
$mock = new MockHandler($responses);
|
||||
$handler = HandlerStack::create($mock);
|
||||
$this->retriever->setClient(new Client(['handler' => $handler]));
|
||||
return $mock;
|
||||
}
|
||||
|
||||
protected function setUp() {
|
||||
$this->retriever = new ObjectRetriever;
|
||||
}
|
||||
protected function setUp(): void {
|
||||
$this->retriever = new ObjectRetriever;
|
||||
}
|
||||
|
||||
public function testGetRootResource() {
|
||||
$this->setMockResponse(new Response(200,
|
||||
['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"}'));
|
||||
public function testGetRootResource() {
|
||||
$this->setMockResponse(new Response(200,
|
||||
['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"}'
|
||||
));
|
||||
|
||||
$coid = new IRI('coid://cloudobjects.io');
|
||||
$object = $this->retriever->getObject($coid);
|
||||
$this->assertNotNull($object);
|
||||
$this->assertEquals((string)$coid, $object->getID());
|
||||
$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());
|
||||
}
|
||||
$coid = new IRI('coid://cloudobjects.io');
|
||||
|
||||
// Test node interface first
|
||||
$object = $this->retriever->getObjectNode($coid);
|
||||
$this->assertNotNull($object);
|
||||
$this->assertEquals((string)$coid, $object->getId());
|
||||
$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->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);
|
||||
}
|
||||
|
||||
public function testWhenAPITimesOut() {
|
||||
$this->setMockResponse(new ConnectException(
|
||||
'cURL error 28: Operation timed out',
|
||||
new Request('GET', 'cloudobjects.io')
|
||||
));
|
||||
|
||||
$coid = new IRI('coid://cloudobjects.io');
|
||||
|
||||
try {
|
||||
$this->retriever->getObjectNode($coid);
|
||||
$this->fail('Expected CoreAPIException was not thrown.');
|
||||
} catch (CoreAPIException $e) {
|
||||
$this->assertInstanceOf(ConnectException::class, $e->getPrevious());
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetAttachment() {
|
||||
$mock = $this->setMockResponse(
|
||||
new Response(200, ['Content-Type' => 'application/ld+json'],
|
||||
'{"@id":"coid://cloudobjects.io/Test","coid://cloudobjects.io/isAtRevision":"1-abc"}'),
|
||||
new Response(200, ['Content-Type' => 'text/plain'], 'Attachment content')
|
||||
);
|
||||
|
||||
$content = $this->retriever->getAttachment(new IRI('coid://cloudobjects.io/Test'), 'file.txt');
|
||||
$this->assertEquals('Attachment content', $content);
|
||||
$this->assertStringEndsWith('cloudobjects.io/Test/file.txt', $mock->getLastRequest()->getUri()->getPath());
|
||||
}
|
||||
|
||||
public function testGetMissingAttachment() {
|
||||
$this->setMockResponse(
|
||||
new Response(200, ['Content-Type' => 'application/ld+json'],
|
||||
'{"@id":"coid://cloudobjects.io/Test","coid://cloudobjects.io/isAtRevision":"1-abc"}'),
|
||||
new Response(404)
|
||||
);
|
||||
|
||||
$content = $this->retriever->getAttachment(new IRI('coid://cloudobjects.io/Test'), 'missing.txt');
|
||||
$this->assertNull($content);
|
||||
}
|
||||
|
||||
public function testWhenAPIThrowsErrorForAttachment() {
|
||||
$this->setMockResponse(
|
||||
new Response(200, ['Content-Type' => 'application/ld+json'],
|
||||
'{"@id":"coid://cloudobjects.io/Test","coid://cloudobjects.io/isAtRevision":"1-abc"}'),
|
||||
new Response(500)
|
||||
);
|
||||
|
||||
$this->expectException(CoreAPIException::class);
|
||||
$this->retriever->getAttachment(new IRI('coid://cloudobjects.io/Test'), 'file.txt');
|
||||
}
|
||||
|
||||
public function testWhenAPITimesOutForAttachment() {
|
||||
$this->setMockResponse(
|
||||
new Response(200, ['Content-Type' => 'application/ld+json'],
|
||||
'{"@id":"coid://cloudobjects.io/Test","coid://cloudobjects.io/isAtRevision":"1-abc"}'),
|
||||
new ConnectException(
|
||||
'cURL error 28: Operation timed out',
|
||||
new Request('GET', 'cloudobjects.io')
|
||||
)
|
||||
);
|
||||
|
||||
try {
|
||||
$this->retriever->getAttachment(new IRI('coid://cloudobjects.io/Test'), 'file.txt');
|
||||
$this->fail('Expected CoreAPIException was not thrown.');
|
||||
} catch (CoreAPIException $e) {
|
||||
$this->assertInstanceOf(ConnectException::class, $e->getPrevious());
|
||||
}
|
||||
}
|
||||
|
||||
private function createFallbackStoreDirectory() : string {
|
||||
$directory = sys_get_temp_dir().DIRECTORY_SEPARATOR.'clobj-fallback-'.uniqid();
|
||||
mkdir($directory);
|
||||
return $directory;
|
||||
}
|
||||
|
||||
private function removeDirectory(string $directory) {
|
||||
$files = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS),
|
||||
\RecursiveIteratorIterator::CHILD_FIRST
|
||||
);
|
||||
foreach ($files as $file)
|
||||
$file->isDir() ? rmdir($file->getPathname()) : unlink($file->getPathname());
|
||||
rmdir($directory);
|
||||
}
|
||||
|
||||
public function testFallbackStoreSavesObjectInScope() {
|
||||
$directory = $this->createFallbackStoreDirectory();
|
||||
$this->retriever = new ObjectRetriever([
|
||||
'fallback_store_directory' => $directory,
|
||||
'fallback_store_scope' => 'example.com, cloudobjects.io',
|
||||
]);
|
||||
$body = '{"@id":"coid://cloudobjects.io/Test","http://www.w3.org/2000/01/rdf-schema#label":"Test"}';
|
||||
$this->setMockResponse(new Response(200, ['Content-Type' => 'application/ld+json'], $body));
|
||||
|
||||
try {
|
||||
$this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
||||
|
||||
$location = $directory.'/cloudobjects.io/Test/object.jsonld';
|
||||
$this->assertFileExists($location);
|
||||
$this->assertEquals($body, file_get_contents($location));
|
||||
} finally {
|
||||
$this->removeDirectory($directory);
|
||||
}
|
||||
}
|
||||
|
||||
public function testFallbackStoreSkipsObjectOutOfScope() {
|
||||
$directory = $this->createFallbackStoreDirectory();
|
||||
$this->retriever = new ObjectRetriever([
|
||||
'fallback_store_directory' => $directory,
|
||||
'fallback_store_scope' => [ 'example.com' ],
|
||||
]);
|
||||
$this->setMockResponse(new Response(200, ['Content-Type' => 'application/ld+json'],
|
||||
'{"@id":"coid://cloudobjects.io/Test"}'));
|
||||
|
||||
try {
|
||||
$this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
||||
$this->assertFileDoesNotExist($directory.'/cloudobjects.io/Test/object.jsonld');
|
||||
} finally {
|
||||
$this->removeDirectory($directory);
|
||||
}
|
||||
}
|
||||
|
||||
public function testFallbackStoreSavesAllObjectsWithWildcard() {
|
||||
$directory = $this->createFallbackStoreDirectory();
|
||||
$this->retriever = new ObjectRetriever([
|
||||
'fallback_store_directory' => $directory,
|
||||
'fallback_store_scope' => '*',
|
||||
]);
|
||||
$this->setMockResponse(new Response(200, ['Content-Type' => 'application/ld+json'],
|
||||
'{"@id":"coid://cloudobjects.io"}'));
|
||||
|
||||
try {
|
||||
$this->retriever->getObjectNode(new IRI('coid://cloudobjects.io'));
|
||||
$this->assertFileExists($directory.'/cloudobjects.io/object.jsonld');
|
||||
} finally {
|
||||
$this->removeDirectory($directory);
|
||||
}
|
||||
}
|
||||
|
||||
private function createRetrieverWithStoredObject(string $directory) {
|
||||
$this->retriever = new ObjectRetriever([
|
||||
'fallback_store_directory' => $directory,
|
||||
'fallback_store_scope' => '*',
|
||||
]);
|
||||
mkdir($directory.'/cloudobjects.io/Test', 0777, true);
|
||||
file_put_contents($directory.'/cloudobjects.io/Test/object.jsonld',
|
||||
'{"@id":"coid://cloudobjects.io/Test","http://www.w3.org/2000/01/rdf-schema#label":"Stored"}');
|
||||
}
|
||||
|
||||
public function testFallbackStoreUsedWhenAPIReturns500() {
|
||||
$directory = $this->createFallbackStoreDirectory();
|
||||
$this->createRetrieverWithStoredObject($directory);
|
||||
$this->setMockResponse(new Response(500));
|
||||
|
||||
try {
|
||||
$object = $this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
||||
$this->assertNotNull($object);
|
||||
$this->assertEquals('Stored', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue());
|
||||
} finally {
|
||||
$this->removeDirectory($directory);
|
||||
}
|
||||
}
|
||||
|
||||
public function testFallbackStoreUsedWhenAPITimesOut() {
|
||||
$directory = $this->createFallbackStoreDirectory();
|
||||
$this->createRetrieverWithStoredObject($directory);
|
||||
$this->setMockResponse(new ConnectException(
|
||||
'cURL error 28: Operation timed out',
|
||||
new Request('GET', 'cloudobjects.io')
|
||||
));
|
||||
|
||||
try {
|
||||
$object = $this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
||||
$this->assertNotNull($object);
|
||||
$this->assertEquals('Stored', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue());
|
||||
} finally {
|
||||
$this->removeDirectory($directory);
|
||||
}
|
||||
}
|
||||
|
||||
public function testFallbackStoreUsedWhenRequestFailsWithoutResponse() {
|
||||
$directory = $this->createFallbackStoreDirectory();
|
||||
$this->createRetrieverWithStoredObject($directory);
|
||||
$this->setMockResponse(new RequestException(
|
||||
'Request failed',
|
||||
new Request('GET', 'cloudobjects.io')
|
||||
));
|
||||
|
||||
try {
|
||||
$object = $this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
||||
$this->assertNotNull($object);
|
||||
$this->assertEquals('Stored', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue());
|
||||
} finally {
|
||||
$this->removeDirectory($directory);
|
||||
}
|
||||
}
|
||||
|
||||
public function testErrorWhenRequestFailsWithoutResponseAndObjectNotInFallbackStore() {
|
||||
$directory = $this->createFallbackStoreDirectory();
|
||||
$this->retriever = new ObjectRetriever([
|
||||
'fallback_store_directory' => $directory,
|
||||
'fallback_store_scope' => '*',
|
||||
]);
|
||||
$this->setMockResponse(new RequestException(
|
||||
'Request failed',
|
||||
new Request('GET', 'cloudobjects.io')
|
||||
));
|
||||
|
||||
try {
|
||||
$this->expectException(CoreAPIException::class);
|
||||
$this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
||||
} finally {
|
||||
$this->removeDirectory($directory);
|
||||
}
|
||||
}
|
||||
|
||||
public function testFallbackStoreNotUsedOnClientError() {
|
||||
$directory = $this->createFallbackStoreDirectory();
|
||||
$this->createRetrieverWithStoredObject($directory);
|
||||
$this->setMockResponse(new Response(403));
|
||||
|
||||
try {
|
||||
$this->expectException(CoreAPIException::class);
|
||||
$this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
||||
} finally {
|
||||
$this->removeDirectory($directory);
|
||||
}
|
||||
}
|
||||
|
||||
public function testErrorWhenAPIThrowsErrorAndObjectNotInFallbackStore() {
|
||||
$directory = $this->createFallbackStoreDirectory();
|
||||
$this->retriever = new ObjectRetriever([
|
||||
'fallback_store_directory' => $directory,
|
||||
'fallback_store_scope' => '*',
|
||||
]);
|
||||
$this->setMockResponse(new Response(500));
|
||||
|
||||
try {
|
||||
$this->expectException(CoreAPIException::class);
|
||||
$this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
||||
} finally {
|
||||
$this->removeDirectory($directory);
|
||||
}
|
||||
}
|
||||
|
||||
private function createRetrieverWithPermanentObject(string $directory, array $options = []) {
|
||||
$this->retriever = new ObjectRetriever(array_merge([
|
||||
'permanent_store_directory' => $directory,
|
||||
'permanent_store_scope' => '*',
|
||||
], $options));
|
||||
mkdir($directory.'/cloudobjects.io/Test', 0777, true);
|
||||
file_put_contents($directory.'/cloudobjects.io/Test/object.jsonld',
|
||||
'{"@id":"coid://cloudobjects.io/Test","http://www.w3.org/2000/01/rdf-schema#label":"Stored"}');
|
||||
}
|
||||
|
||||
public function testPermanentStoreSavesObjectInScope() {
|
||||
$directory = $this->createFallbackStoreDirectory();
|
||||
$this->retriever = new ObjectRetriever([
|
||||
'permanent_store_directory' => $directory,
|
||||
'permanent_store_scope' => 'example.com, cloudobjects.io',
|
||||
]);
|
||||
$body = '{"@id":"coid://cloudobjects.io/Test","http://www.w3.org/2000/01/rdf-schema#label":"Test"}';
|
||||
$this->setMockResponse(new Response(200, ['Content-Type' => 'application/ld+json'], $body));
|
||||
|
||||
try {
|
||||
$this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
||||
|
||||
$location = $directory.'/cloudobjects.io/Test/object.jsonld';
|
||||
$this->assertFileExists($location);
|
||||
$this->assertEquals($body, file_get_contents($location));
|
||||
} finally {
|
||||
$this->removeDirectory($directory);
|
||||
}
|
||||
}
|
||||
|
||||
public function testPermanentStoreSkipsObjectOutOfScope() {
|
||||
$directory = $this->createFallbackStoreDirectory();
|
||||
$this->retriever = new ObjectRetriever([
|
||||
'permanent_store_directory' => $directory,
|
||||
'permanent_store_scope' => [ 'example.com' ],
|
||||
]);
|
||||
$this->setMockResponse(new Response(200, ['Content-Type' => 'application/ld+json'],
|
||||
'{"@id":"coid://cloudobjects.io/Test"}'));
|
||||
|
||||
try {
|
||||
$this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
||||
$this->assertFileDoesNotExist($directory.'/cloudobjects.io/Test/object.jsonld');
|
||||
} finally {
|
||||
$this->removeDirectory($directory);
|
||||
}
|
||||
}
|
||||
|
||||
public function testPermanentStoreUsedWithoutContactingAPI() {
|
||||
$directory = $this->createFallbackStoreDirectory();
|
||||
$this->createRetrieverWithPermanentObject($directory);
|
||||
$mock = new MockHandler([ new Response(500) ]);
|
||||
$this->retriever->setClient(new Client(['handler' => HandlerStack::create($mock)]));
|
||||
|
||||
try {
|
||||
$object = $this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
||||
$this->assertNotNull($object);
|
||||
$this->assertEquals('Stored', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue());
|
||||
// The mocked response has not been consumed
|
||||
$this->assertCount(1, $mock);
|
||||
} finally {
|
||||
$this->removeDirectory($directory);
|
||||
}
|
||||
}
|
||||
|
||||
public function testPermanentStoreNotOverwritten() {
|
||||
$directory = $this->createFallbackStoreDirectory();
|
||||
$this->createRetrieverWithPermanentObject($directory);
|
||||
$this->setMockResponse(new Response(200, ['Content-Type' => 'application/ld+json'],
|
||||
'[{"@id":"coid://cloudobjects.io/Test","http://www.w3.org/2000/01/rdf-schema#label":"Updated"}]'));
|
||||
|
||||
try {
|
||||
$this->retriever->fetchAllObjectsInNamespace(new IRI('coid://cloudobjects.io'));
|
||||
$this->assertStringContainsString('Stored',
|
||||
file_get_contents($directory.'/cloudobjects.io/Test/object.jsonld'));
|
||||
} finally {
|
||||
$this->removeDirectory($directory);
|
||||
}
|
||||
}
|
||||
|
||||
public function testPermanentStoreTakesPrecedenceOverFallbackStore() {
|
||||
$permanentDirectory = $this->createFallbackStoreDirectory();
|
||||
$fallbackDirectory = $this->createFallbackStoreDirectory();
|
||||
$this->createRetrieverWithPermanentObject($permanentDirectory, [
|
||||
'fallback_store_directory' => $fallbackDirectory,
|
||||
'fallback_store_scope' => '*',
|
||||
]);
|
||||
mkdir($fallbackDirectory.'/cloudobjects.io/Test', 0777, true);
|
||||
file_put_contents($fallbackDirectory.'/cloudobjects.io/Test/object.jsonld',
|
||||
'{"@id":"coid://cloudobjects.io/Test","http://www.w3.org/2000/01/rdf-schema#label":"Fallback"}');
|
||||
$this->setMockResponse(new Response(500));
|
||||
|
||||
try {
|
||||
$object = $this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
||||
$this->assertEquals('Stored', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue());
|
||||
} finally {
|
||||
$this->removeDirectory($permanentDirectory);
|
||||
$this->removeDirectory($fallbackDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
private function createStaticObjectDirectory() : string {
|
||||
$directory = $this->createFallbackStoreDirectory();
|
||||
mkdir($directory.'/cloudobjects.io/Test', 0777, true);
|
||||
file_put_contents($directory.'/cloudobjects.io/Test/object.jsonld',
|
||||
'{"@id":"coid://cloudobjects.io/Test","http://www.w3.org/2000/01/rdf-schema#label":"Static"}');
|
||||
return $directory;
|
||||
}
|
||||
|
||||
public function testStaticObjectDirectoryUsedWithoutContactingAPI() {
|
||||
$directory = $this->createStaticObjectDirectory();
|
||||
$this->retriever = new ObjectRetriever([
|
||||
'static_object_directory' => $directory,
|
||||
]);
|
||||
$mock = new MockHandler([ new Response(500) ]);
|
||||
$this->retriever->setClient(new Client(['handler' => HandlerStack::create($mock)]));
|
||||
|
||||
try {
|
||||
$object = $this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
||||
$this->assertEquals('Static', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue());
|
||||
// The mocked response has not been consumed
|
||||
$this->assertCount(1, $mock);
|
||||
} finally {
|
||||
$this->removeDirectory($directory);
|
||||
}
|
||||
}
|
||||
|
||||
public function testStaticObjectDirectoryWithDeprecatedOptionName() {
|
||||
$directory = $this->createStaticObjectDirectory();
|
||||
$this->retriever = new ObjectRetriever([
|
||||
'static_config_path' => $directory,
|
||||
]);
|
||||
$this->setMockResponse(new Response(500));
|
||||
|
||||
try {
|
||||
$object = $this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
||||
$this->assertEquals('Static', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue());
|
||||
} finally {
|
||||
$this->removeDirectory($directory);
|
||||
}
|
||||
}
|
||||
|
||||
public function testStaticObjectDirectoryFallsThroughToAPI() {
|
||||
$directory = $this->createStaticObjectDirectory();
|
||||
$this->retriever = new ObjectRetriever([
|
||||
'static_object_directory' => $directory,
|
||||
]);
|
||||
$this->setMockResponse(new Response(200, ['Content-Type' => 'application/ld+json'],
|
||||
'{"@id":"coid://cloudobjects.io/Other","http://www.w3.org/2000/01/rdf-schema#label":"API"}'));
|
||||
|
||||
try {
|
||||
$object = $this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Other'));
|
||||
$this->assertEquals('API', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue());
|
||||
} finally {
|
||||
$this->removeDirectory($directory);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?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\InvalidSDKConfigurationException;
|
||||
|
||||
class ObjectRetrieverTest extends \PHPUnit\Framework\TestCase {
|
||||
|
||||
public function testFallbackStoreWithExistingDirectory() {
|
||||
$retriever = new ObjectRetriever([
|
||||
'fallback_store_directory' => sys_get_temp_dir(),
|
||||
'fallback_store_scope' => '*',
|
||||
]);
|
||||
$this->assertInstanceOf(ObjectRetriever::class, $retriever);
|
||||
}
|
||||
|
||||
public function testFallbackStoreWithMissingDirectory() {
|
||||
$this->expectException(InvalidSDKConfigurationException::class);
|
||||
new ObjectRetriever([
|
||||
'fallback_store_directory' => sys_get_temp_dir().'/does-not-exist-'.uniqid(),
|
||||
'fallback_store_scope' => '*',
|
||||
]);
|
||||
}
|
||||
|
||||
public function testPermanentStoreWithExistingDirectory() {
|
||||
$retriever = new ObjectRetriever([
|
||||
'permanent_store_directory' => sys_get_temp_dir(),
|
||||
'permanent_store_scope' => '*',
|
||||
]);
|
||||
$this->assertInstanceOf(ObjectRetriever::class, $retriever);
|
||||
}
|
||||
|
||||
public function testPermanentStoreWithMissingDirectory() {
|
||||
$this->expectException(InvalidSDKConfigurationException::class);
|
||||
new ObjectRetriever([
|
||||
'permanent_store_directory' => sys_get_temp_dir().'/does-not-exist-'.uniqid(),
|
||||
'permanent_store_scope' => '*',
|
||||
]);
|
||||
}
|
||||
|
||||
public function testPermanentStoreWithSameDirectoryAsFallbackStore() {
|
||||
$this->expectException(InvalidSDKConfigurationException::class);
|
||||
new ObjectRetriever([
|
||||
'fallback_store_directory' => sys_get_temp_dir(),
|
||||
'fallback_store_scope' => '*',
|
||||
'permanent_store_directory' => sys_get_temp_dir(),
|
||||
'permanent_store_scope' => '*',
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,11 +10,11 @@ use InvalidArgumentException;
|
||||
use ML\IRI\IRI;
|
||||
use CloudObjects\SDK\ObjectRetriever;
|
||||
|
||||
class SchemaValidatorPublicTest extends \PHPUnit_Framework_TestCase {
|
||||
class SchemaValidatorPublicTest extends \PHPUnit\Framework\TestCase {
|
||||
|
||||
private $schemaValidator;
|
||||
|
||||
public function setUp() {
|
||||
protected function setUp(): void {
|
||||
$this->schemaValidator = new SchemaValidator(new ObjectRetriever);
|
||||
}
|
||||
|
||||
@@ -24,10 +24,11 @@ class SchemaValidatorPublicTest extends \PHPUnit_Framework_TestCase {
|
||||
'region' => 'Hessen',
|
||||
'country-name' => 'Germany'
|
||||
], new IRI('coid://json.co-n.net/Address'));
|
||||
$this->addToAssertionCount(1);
|
||||
}
|
||||
|
||||
public function testNotAddress() {
|
||||
$this->setExpectedException(InvalidArgumentException::class);
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
|
||||
$this->schemaValidator->validateAgainstCOID([
|
||||
'region' => 'Hessen',
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
<?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());
|
||||
}
|
||||
|
||||
public function testAttachmentCacheInRuntimeStorage() {
|
||||
$cacheStorage = new VolatileRuntimeStorage;
|
||||
$logger = new InMemoryLogger;
|
||||
|
||||
$coid = new IRI('coid://cloudobjects.io/Test');
|
||||
$cacheId = (string)$coid.'#file.txt';
|
||||
|
||||
$retriever = new ObjectRetriever([
|
||||
'cache_storage' => $cacheStorage,
|
||||
'logger' => $logger
|
||||
]);
|
||||
|
||||
$content1 = $retriever->getAttachment($coid, 'file.txt');
|
||||
|
||||
$this->assertEquals('Test file content', trim($content1));
|
||||
$this->assertNotNull($cacheStorage->fetch($retriever->getCacheKey($cacheId)));
|
||||
$this->assertStringContainsString('from Core API', $logger->getLastLogMessage());
|
||||
|
||||
// Reinitialize retriever with same cache storage to verify that cache is used
|
||||
$retriever = new ObjectRetriever([
|
||||
'cache_storage' => $cacheStorage,
|
||||
'logger' => $logger
|
||||
]);
|
||||
|
||||
$content2 = $retriever->getAttachment($coid, 'file.txt');
|
||||
|
||||
$this->assertEquals('Test file content', trim($content2));
|
||||
$this->assertStringContainsString('from object cache', $logger->getLastLogMessage());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,11 +8,11 @@ namespace CloudObjects\SDK;
|
||||
|
||||
use ML\IRI\IRI;
|
||||
|
||||
class ObjectRetrieverTest extends \PHPUnit_Framework_TestCase {
|
||||
class ObjectRetrieverPublicTest extends \PHPUnit\Framework\TestCase {
|
||||
|
||||
private $retriever;
|
||||
|
||||
protected function setUp() {
|
||||
protected function setUp(): void {
|
||||
$this->retriever = new ObjectRetriever;
|
||||
}
|
||||
|
||||
@@ -26,13 +26,34 @@ class ObjectRetrieverTest extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
public function testGetRootObject() {
|
||||
$coid = new IRI('coid://cloudobjects.io');
|
||||
$object = $this->retriever->getObject($coid);
|
||||
$object = $this->retriever->getObjectNode($coid);
|
||||
$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->assertEquals('CloudObjects', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue());
|
||||
}
|
||||
|
||||
public function testGetTestObject() {
|
||||
$coid = new IRI('coid://cloudobjects.io/Test');
|
||||
$object = $this->retriever->getObjectNode($coid);
|
||||
$this->assertNotNull($object);
|
||||
$this->assertEquals((string)$coid, $object->getId());
|
||||
$this->assertNotNull($object->getProperty('http://www.w3.org/2000/01/rdf-schema#label'));
|
||||
$this->assertEquals('Test Object', $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() {
|
||||
$coid = new IRI('coid://cloudobjects.io');
|
||||
$list = $this->stringifyItems(
|
||||
|
||||
Reference in new issue
Block a user