[WIP] Implement interface for lazy-loading foreign nodes

This commit is contained in:
Jeremy Dormitzer 2019-04-27 19:08:47 -04:00
parent a210752d7f
commit 4913dfdf43
3 changed files with 122 additions and 29 deletions

View File

@ -5,13 +5,13 @@ namespace ActivityPub\JsonLd\Dereferencer;
use ActivityPub\JsonLd\Exceptions\NodeNotFoundException; use ActivityPub\JsonLd\Exceptions\NodeNotFoundException;
use ActivityPub\JsonLd\JsonLdNode; use stdClass;
interface DereferencerInterface interface DereferencerInterface
{ {
/** /**
* @param string $iri The IRI to dereference. * @param string $iri The IRI to dereference.
* @return JsonLdNode The dereferenced node. * @return stdClass|array The dereferenced node.
* @throws NodeNotFoundException If a node with the IRI could not be found. * @throws NodeNotFoundException If a node with the IRI could not be found.
*/ */
public function dereference( $iri ); public function dereference( $iri );

View File

@ -2,27 +2,42 @@
namespace ActivityPub\JsonLd; namespace ActivityPub\JsonLd;
use ActivityPub\JsonLd\Dereferencer\DereferencerInterface;
use ActivityPub\JsonLd\Exceptions\PropertyNotDefinedException; use ActivityPub\JsonLd\Exceptions\PropertyNotDefinedException;
use ArrayAccess; use ArrayAccess;
use InvalidArgumentException;
use ML\JsonLD\Graph;
use ML\JsonLD\JsonLD; use ML\JsonLD\JsonLD;
use ML\JsonLD\Node; use ML\JsonLD\Node;
use ML\JsonLD\Value; use ML\JsonLD\Value;
use stdClass;
/** /**
* Class JsonLdNode * Class JsonLdNode
* @package ActivityPub\JsonLd * @package ActivityPub\JsonLd
* *
* A representation of a node in a JSON-LD graph. Supports lazy-loading linked nodes and persisting RDF triples to * A representation of a node in a JSON-LD graph. Supports lazy-loading linked nodes.
* a storage backend.
*/ */
class JsonLdNode implements ArrayAccess class JsonLdNode implements ArrayAccess
{ {
/** /**
* The internal representation of the node. * The Node within $this->graph that represents this JsonLdNode.
* @var Node * @var Node
*/ */
private $node; private $node;
/**
* The portion of the JSON-LD graph that this node knows about.
* @var Graph
*/
private $graph;
/**
* The factory used to construct this node.
* @var JsonLdNodeFactory
*/
private $factory;
/** /**
* The JSON-LD context that should be used when getting/setting properties on this node. * The JSON-LD context that should be used when getting/setting properties on this node.
* @var array|\stdClass|string * @var array|\stdClass|string
@ -30,20 +45,31 @@ class JsonLdNode implements ArrayAccess
private $context; private $context;
/** /**
* JsonLdNode constructor. * The dereferencer, used to dereference foreign nodes based on their IRIs.
* @param \stdClass $jsonLd The JSON-LD input as a stdClass. * @var DereferencerInterface
* @param string $context This node's JSON-LD context.
*/ */
public function __construct( $jsonLd, $context ) private $dereferencer;
/**
* JsonLdNode constructor.
* @param Node|\stdClass $jsonLd The JSON-LD input as a stdClass or an existing \ML\JsonLD\Node instance.
* @param string $context This node's JSON-LD context.
* @param DereferencerInterface $dereferencer
*/
public function __construct( $jsonLd, $context, JsonLdNodeFactory $factory, DereferencerInterface $dereferencer )
{ {
$doc = JsonLD::getDocument( $jsonLd ); $this->factory = $factory;
$graph = $doc->getGraph(); $this->dereferencer = $dereferencer;
$id = empty( $doc->getIri() ) ? '_:b0' : $doc->getIri();
$this->node = $graph->getNode( $id );
if ( is_null( $this->node ) ) {
$this->node = $graph->createNode();
}
$this->context = $context; $this->context = $context;
if ( $jsonLd instanceof Node ) {
$this->node = $jsonLd;
$this->graph = $jsonLd->getGraph();
} else {
$doc = JsonLD::getDocument( $jsonLd );
$this->graph = $doc->getGraph();
$nodes = $this->graph->getNodes();
$this->node = count( $nodes ) > 0 ? $nodes[0] : $this->graph->createNode();
}
} }
/** /**
@ -117,9 +143,20 @@ class JsonLdNode implements ArrayAccess
return $property->getValue(); return $property->getValue();
} else if ( is_array( $property ) ) { } else if ( is_array( $property ) ) {
return array_map( array( $this, 'resolveProperty' ), $property ); return array_map( array( $this, 'resolveProperty' ), $property );
} else if ( $property instanceof Node ) {
if ( count( $property->getProperties() ) > 0 ) {
return $this->factory->newNode( $property );
} else {
// dereference the node to get its properties, then update $property's props with the retrieved values
$dereferenced = $this->dereferencer->dereference( $property->getId() );
$newNode = JsonLD::getDocument( $dereferenced )->getGraph()->getNode( $property->getId() );
foreach ( $newNode->getProperties() as $name => $value ) {
$property->setProperty( $name, $value );
}
return $this->factory->newNode( $property );
}
} }
// TODO handle lazy-loading linked nodes here // TODO figure out what to do about as:items -- the vocab says it should be a node but the JsonLD lib
// also, figure out what to do about as:items -- the vocab says it should be a node but the JsonLD lib
// seems to resolve it to an array if it comes in as an array of string values... // seems to resolve it to an array if it comes in as an array of string values...
} }
@ -132,12 +169,18 @@ class JsonLdNode implements ArrayAccess
public function setProperty( $name, $value ) public function setProperty( $name, $value )
{ {
$expandedName = $this->expand_name( $name ); $expandedName = $this->expand_name( $name );
if ( $value instanceof \stdClass || is_array( $value ) ) { if ( is_array( $value ) ) {
// TODO handle adding a new linked node here $this->clearProperty( $expandedName );
// should instantiate a new JsonLdNode and recursively call __set foreach ( $value as $v ) {
$this->addPropertyValue( $expandedName, $v );
}
} else if ( $value instanceof stdClass ) {
$newDoc = JsonLD::getDocument( $value );
$newNodes = $newDoc->getGraph()->getNodes();
$newNode = count( $newNodes ) > 0 ? $newNodes[0] : $this->graph->createNode();
$this->node->setProperty( $expandedName, $newNode );
} else if ( $value instanceof JsonLdNode ) { } else if ( $value instanceof JsonLdNode ) {
// TODO handle adding a new linked node here $this->setProperty( $expandedName, $value->asObject() );
// by getting the \ML\JsonLD\Node instance from the $value and calling $this->node->addPropertyValue()
} else { } else {
$this->node->setProperty( $expandedName, $value ); $this->node->setProperty( $expandedName, $value );
} }
@ -159,17 +202,21 @@ class JsonLdNode implements ArrayAccess
* If the property already exists, the new value is added onto the existing values rather than * If the property already exists, the new value is added onto the existing values rather than
* overwriting them. * overwriting them.
* @param string $name * @param string $name
* @param string|\stdClass|array $value * @param string|stdClass $value
*/ */
public function addPropertyValue( $name, $value ) public function addPropertyValue( $name, $value )
{ {
$expandedName = $this->expand_name( $name ); $expandedName = $this->expand_name( $name );
if ( $value instanceof \stdClass || is_array( $value ) ) { if ( is_array( $value ) ) {
// TODO handle adding a new linked node here $err = "Can't add array value to a property. To add multiple values call addPropertyValue multiple times or use setProperty";
// should instantiate a new JsonLdNode and recursively call __set throw new InvalidArgumentException( $err );
} else if ( $value instanceof stdClass ) {
$newDoc = JsonLD::getDocument( $value );
$newNodes = $newDoc->getGraph()->getNodes();
$newNode = count( $newNodes ) > 0 ? $newNodes[0] : $this->graph->createNode();
$this->node->addPropertyValue( $expandedName, $newNode );
} else if ( $value instanceof JsonLdNode ) { } else if ( $value instanceof JsonLdNode ) {
// TODO handle adding a new linked node here $this->addPropertyValue( $expandedName, $value->asObject() );
// by getting the \ML\JsonLD\Node instance from the $value and calling $this->node->addPropertyValue()
} else { } else {
$this->node->addPropertyValue( $expandedName, $value ); $this->node->addPropertyValue( $expandedName, $value );
} }
@ -184,6 +231,11 @@ class JsonLdNode implements ArrayAccess
return $this->setProperty( $name, null ); return $this->setProperty( $name, null );
} }
public function asObject()
{
return $this->node->toJsonLd();
}
/** /**
* Resolves $name to a full IRI given the JSON-LD context of this node. * Resolves $name to a full IRI given the JSON-LD context of this node.
* @param string $name The name of the property to resolve. * @param string $name The name of the property to resolve.

View File

@ -0,0 +1,41 @@
<?php
namespace ActivityPub\JsonLd;
use ActivityPub\JsonLd\Dereferencer\DereferencerInterface;
/**
* A factory class for constructing JsonLdNode instances
* Class JsonLdNodeFactory
* @package ActivityPub\JsonLd
*/
class JsonLdNodeFactory
{
/**
* The JSON-LD context to give to new JsonLdNode instances.
* @var array|\stdClass|string
*/
private $context;
/**
* The dereferencer to pass to new JsonLdNode instances.
* @var DereferencerInterface
*/
private $dereferencer;
public function __construct( $context, DereferencerInterface $dereferencer )
{
$this->context = $context;
$this->dereferencer = $dereferencer;
}
/**
* Construct and return a new JsonLdNode.
* @param Node|\stdClass $jsonLd The JSON-LD object input
* @return JsonLdNode
*/
public function newNode( $jsonLd )
{
return new JsonLdNode( $jsonLd, $this->context, $this, $this->dereferencer );
}
}