10 Commits
14 changed files with 335 additions and 39 deletions
@@ -40,7 +40,7 @@ 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");
+13 -6
View File
@@ -85,7 +85,7 @@ class CloudObject {
* 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 {
public function getString($property, ?string $default = null) : ?string {
return $this->getReader()->getFirstValueString($this->node, $property, $default);
}
@@ -93,7 +93,7 @@ class CloudObject {
* 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 {
public function getInt($property, ?int $default = null) : ?int {
return $this->getReader()->getFirstValueInt($this->node, $property, $default);
}
@@ -101,7 +101,7 @@ class CloudObject {
* 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 {
public function getFloat($property, ?float $default = null) : ?float {
return $this->getReader()->getFirstValueFloat($this->node, $property, $default);
}
@@ -109,7 +109,7 @@ class CloudObject {
* 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 {
public function getBool($property, ?bool $default = null) : ?bool {
return $this->getReader()->getFirstValueBool($this->node, $property, $default);
}
@@ -117,7 +117,7 @@ class CloudObject {
* 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 {
public function getIRI($property, ?IRI $default = null) : ?IRI {
return $this->getReader()->getFirstValueIRI($this->node, $property, $default);
}
@@ -125,7 +125,7 @@ class CloudObject {
* 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 {
public function getNode($property, ?Node $default = null) : ?Node {
return $this->getReader()->getFirstValueNode($this->node, $property, $default);
}
@@ -166,4 +166,11 @@ class CloudObject {
return $this->getString(Constants::PROPERTY_REVISION);
}
/**
* Get the label of the object.
*/
public function getLabel() : ?string {
return $this->getString(Constants::RDFS_LABEL);
}
}
+1 -1
View File
@@ -53,7 +53,7 @@ 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.");
+1
View File
@@ -9,5 +9,6 @@ namespace CloudObjects\SDK;
class Constants {
const PROPERTY_REVISION = 'coid://cloudobjects.io/isAtRevision';
const RDFS_LABEL = 'http://www.w3.org/2000/01/rdf-schema#label';
}
+38 -4
View File
@@ -7,6 +7,7 @@
namespace CloudObjects\SDK\Helpers;
use Exception;
use CloudObjects\SDK\Exceptions\InvalidSDKConfigurationException;
use CloudObjects\SDK\NodeReader, CloudObjects\SDK\ObjectRetriever;
/**
@@ -27,20 +28,53 @@ class SDKLoader {
$this->reader = new NodeReader;
}
/**
* Initialize the SDK with the given name and options; used for
* SDKs that are initialized with a function and not a class name.
*
* @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 Exception If the SDK is not supported or cannot be initialized.
*/
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 Exception("No rules defined to initialize SDK with name <".$sdkName.">.");
}
}
/**
* Initialize and return the SDK with the given classname.
* Throws Exception if the SDK is not supported.
*
* @param $classname Classname for the SDK's main class
* @param array $options Additional options for the SDK (if necessary)
* @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 Exception If the SDK is not supported or cannot be initialized.
*/
public function get($classname, array $options) {
public function get(string $classname, array $options) : mixed {
if (!class_exists($classname))
throw new Exception("<".$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
+18 -18
View File
@@ -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 = null, $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 = null, $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 = null, $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 = null, $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 = null, $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 = null, $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 = null, $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 = null, $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 = null, $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 = null, $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 = null, $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 = null, $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 = null, $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 = null, $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 = null, $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 = null, $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 = null, $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 = null, $property) {
$allValues = $this->getAllValues($node, $property);
$output = [];
foreach ($allValues as $a)
+5 -5
View File
@@ -206,7 +206,7 @@ class ObjectRetriever implements CustomCacheAndLogInterface {
/**
* Get an object description and return a CloudObject.
*/
public function getCloudObject(IRI $coid) : CloudObject {
public function getCloudObject(IRI $coid) : ?CloudObject {
$node = $this->getObjectNode($coid);
if (!$node) {
// Object not found
@@ -239,7 +239,7 @@ class ObjectRetriever implements CustomCacheAndLogInterface {
* @param IRI $coid COID of the object
* @return Node|null
*/
public function getObjectNode(IRI $coid) : Node {
public function getObjectNode(IRI $coid) : ?Node {
if (!COIDParser::isValidCOID($coid))
throw new Exception("Not a valid COID.");
@@ -536,7 +536,7 @@ class ObjectRetriever implements CustomCacheAndLogInterface {
* @deprecated Use getAuthenticatingNamespaceObjectNode() instead
* @return Node
*/
public function getAuthenticatingNamespaceObject() : Node {
public function getAuthenticatingNamespaceObject() : ?Node {
return $this->getObject($this->assertAuthenticatingNamespaceAndGetId());
}
@@ -546,7 +546,7 @@ class ObjectRetriever implements CustomCacheAndLogInterface {
*
* @return Node
*/
public function getAuthenticatingNamespaceObjectNode() : Node {
public function getAuthenticatingNamespaceObjectNode() : ?Node {
return $this->getObject($this->assertAuthenticatingNamespaceAndGetId());
}
@@ -556,7 +556,7 @@ class ObjectRetriever implements CustomCacheAndLogInterface {
*
* @return CloudObject
*/
public function getAuthenticatingNamespaceCloudObject() : CloudObject {
public function getAuthenticatingNamespaceCloudObject() : ?CloudObject {
return $this->getCloudObject($this->assertAuthenticatingNamespaceAndGetId());
}
@@ -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 Exception;
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 Exception('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();
}
}
+1 -1
View File
@@ -217,7 +217,7 @@ 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->getObjectNode($namespaceCoid)
+6 -3
View File
@@ -19,9 +19,12 @@
}
],
"autoload": {
"psr-0": {
"CloudObjects\\SDK" : ""
}
"psr-4": {
"CloudObjects\\SDK\\" : "CloudObjects/SDK/"
},
"files": [
"functions.php"
]
},
"require-dev" : {
"phpunit/phpunit": "^10",
+23
View File
@@ -0,0 +1,23 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use ML\IRI\IRI;
use CloudObjects\SDK\CloudObject,
CloudObjects\SDK\ObjectRetrieverFacade;
if (!function_exists('cloudobjects_get_object')) {
/**
* Retrieve a CloudObject by COID. Accepts a COID as an IRI object or a string.
*/
function cloudobjects_get_object($coid) : ?CloudObject {
if (is_string($coid))
$coid = new IRI($coid);
elseif (!($coid instanceof IRI))
throw new InvalidArgumentException('COID must be a string or an IRI object.');
return ObjectRetrieverFacade::getCloudObject($coid);
}
}
+63
View File
@@ -0,0 +1,63 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK;
use ML\IRI\IRI;
use ReflectionProperty,
InvalidArgumentException;
class FunctionsTest extends \PHPUnit\Framework\TestCase {
protected function setUp(): void {
$prop = new ReflectionProperty(ObjectRetrieverFacade::class, 'instance');
$prop->setAccessible(true);
$prop->setValue(null, null);
}
private function makeMockRetrieverReturning(?CloudObject $object) : ObjectRetriever {
$mock = $this->createMock(ObjectRetriever::class);
$mock->method('getCloudObject')->willReturn($object);
return $mock;
}
public function testAcceptsIRI(): void {
$coid = new IRI('coid://cloudobjects.io');
$mockObject = $this->createMock(CloudObject::class);
$mockRetriever = $this->createMock(ObjectRetriever::class);
$mockRetriever->expects($this->once())
->method('getCloudObject')
->with($coid)
->willReturn($mockObject);
ObjectRetrieverFacade::setObjectRetriever($mockRetriever);
$this->assertSame($mockObject, cloudobjects_get_object($coid));
}
public function testConvertsStringToIRI(): void {
$mockObject = $this->createMock(CloudObject::class);
$mockRetriever = $this->createMock(ObjectRetriever::class);
$mockRetriever->expects($this->once())
->method('getCloudObject')
->with($this->callback(fn($arg) => $arg instanceof IRI && (string)$arg === 'coid://cloudobjects.io'))
->willReturn($mockObject);
ObjectRetrieverFacade::setObjectRetriever($mockRetriever);
$this->assertSame($mockObject, cloudobjects_get_object('coid://cloudobjects.io'));
}
public function testThrowsOnInvalidArgumentType(): void {
ObjectRetrieverFacade::setObjectRetriever($this->makeMockRetrieverReturning(null));
$this->expectException(InvalidArgumentException::class);
cloudobjects_get_object(42);
}
}
@@ -126,6 +126,15 @@ class NodeReaderMockTest extends \PHPUnit\Framework\TestCase {
$this->assertEquals('coid://cloudobjects.io/Public', $object->getNode('co:isVisibleTo')->getId());
}
public function testConstants() {
$coid = new IRI('coid://cloudobjects.io');
$this->useRootResourceMock();
$object = $this->retriever->getCloudObject($coid);
$this->assertEquals('6-fbea0c90b2c5e5300e4039ed99be9b2d', $object->getRevision());
$this->assertEquals('CloudObjects', $object->getLabel());
}
public function testGetAllValuesString1() {
$coid = new IRI('coid://cloudobjects.io');
$this->useRootResourceMock();
@@ -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));
}
}