Implement backreferences

This commit is contained in:
Jeremy Dormitzer 2019-04-28 10:34:04 -04:00
parent 3f0892a4a8
commit 3e7ead903d
3 changed files with 138 additions and 9 deletions

View File

@ -3,6 +3,7 @@
namespace ActivityPub\JsonLd;
use ActivityPub\JsonLd\Dereferencer\DereferencerInterface;
use ActivityPub\JsonLd\Exceptions\NodeNotFoundException;
use ActivityPub\JsonLd\Exceptions\PropertyNotDefinedException;
use ArrayAccess;
use BadMethodCallException;
@ -47,7 +48,14 @@ class JsonLdNode implements ArrayAccess
*/
private $graph;
// TODO support backreferences
/**
* An array mapping property names to JsonLdNodes, where each entry
* represents a backreference from the node in the array to this node
* via named property (e.g. a backreference from "as:inReplyTo" => nodeA means
* that nodeA["as:inReplyTo"] == this node).
* @var array
*/
private $backreferences;
/**
* JsonLdNode constructor.
@ -57,7 +65,12 @@ class JsonLdNode implements ArrayAccess
* @param DereferencerInterface $dereferencer
* @param JsonLdGraph $graph The JSON-LD graph this node is a part of.
*/
public function __construct( $jsonLd, $context, JsonLdNodeFactory $factory, DereferencerInterface $dereferencer, JsonLdGraph $graph )
public function __construct( $jsonLd,
$context,
JsonLdNodeFactory $factory,
DereferencerInterface $dereferencer,
JsonLdGraph $graph,
$backreferences = array() )
{
$this->factory = $factory;
$this->dereferencer = $dereferencer;
@ -73,6 +86,7 @@ class JsonLdNode implements ArrayAccess
$this->context = $context;
$this->graph = $graph;
$this->graph->addNode( $this );
$this->backreferences = $backreferences;
}
/**
@ -103,12 +117,13 @@ class JsonLdNode implements ArrayAccess
* @param string $name The property name to get.
* @return mixed A single property value.
* @throws PropertyNotDefinedException If no property named $name exists.
* @throws NodeNotFoundException If lazy-loading a linked node fails.
*/
public function get( $name )
{
$expandedName = $this->expandName( $name );
if ( property_exists( $this->expanded, $expandedName ) ) {
return $this->resolveProperty( $this->expanded->$expandedName[0] );
return $this->resolveProperty( $expandedName, $this->expanded->$expandedName[0] );
}
throw new PropertyNotDefinedException( $name );
}
@ -119,12 +134,13 @@ class JsonLdNode implements ArrayAccess
* @param string $name The property name to get.
* @return mixed A single property value.
* @throws PropertyNotDefinedException If no property named $name exists.
* @throws NodeNotFoundException If lazy-loading a linked node fails.
*/
public function getMany( $name )
{
$expandedName = $this->expandName( $name );
if ( property_exists( $this->expanded, $expandedName ) ) {
return $this->resolveProperty( $this->expanded->$expandedName );
return $this->resolveProperty( $expandedName, $this->expanded->$expandedName );
}
throw new PropertyNotDefinedException( $name );
}
@ -140,15 +156,28 @@ class JsonLdNode implements ArrayAccess
return $this->get( $name );
}
private function resolveProperty( &$property )
/**
* Takes a raw value from $this->expanded and turns it into something useful.
* If its a node, lazy-load the node and wrap it in a JsonLdNode. If it's a value object,
* return the value. If it's an array, recursively resolve the values in the array.
* @param string $expandedName The name of the property we are resolving.
* @param mixed $property The property value.
* @return JsonLdNode|array|mixed
* @throws NodeNotFoundException If lazy-loading the node fails because the node does not exist.
*/
private function resolveProperty( $expandedName, &$property )
{
if ( is_array( $property ) ) {
return array_map( array( $this, 'resolveProperty'), $property );
for ( $i = 0; $i < count( $property); $i += 1) {
$names[] = $expandedName;
}
return array_map( array( $this, 'resolveProperty'), $names, $property );
} else if ( $property instanceof stdClass && property_exists( $property, '@id') ) {
// Only dereference if @id is the only property present
if ( count( get_object_vars( $property ) ) > 1 ) {
return $property;
}
// Otherwise lazy-load the referenced node
$idProp = '@id';
$iri = $property->$idProp;
$dereferenced = $this->dereferencer->dereference( $iri );
@ -156,7 +185,8 @@ class JsonLdNode implements ArrayAccess
$property = $expanded;
$referencedNode = $this->graph->getNode( $property->$idProp );
if ( is_null( $referencedNode) ) {
$referencedNode = $this->factory->newNode( $property, $this->graph );
$backrefs = array( $expandedName => array( $this ) );
$referencedNode = $this->factory->newNode( $property, $this->graph, $backrefs );
}
return $referencedNode;
} else if ( $property instanceof stdClass && property_exists( $property, '@value' ) ) {
@ -183,6 +213,7 @@ class JsonLdNode implements ArrayAccess
throw new InvalidArgumentException( 'This node already has an id.' );
}
$expandedValue = $this->expandValue( $expandedName, $value );
$this->addNewValueToGraph( $expandedName, $expandedValue );
$this->expanded->$expandedName = $expandedValue;
if ( $expandedName === '@id' ) {
$this->graph->nameBlankNode( $this->getId(), $expandedValue );
@ -197,6 +228,7 @@ class JsonLdNode implements ArrayAccess
throw new InvalidArgumentException( 'Cannot add to the @id property.' );
}
$expandedValue = $this->expandValue( $expandedName, $value );
$this->addNewValueToGraph( $expandedName, $expandedValue );
if ( property_exists( $this->expanded, $expandedName ) ) {
$this->expanded->$expandedName = array_merge( $this->expanded->$expandedName, $expandedValue );
} else {
@ -204,6 +236,26 @@ class JsonLdNode implements ArrayAccess
}
}
private function addNewValueToGraph( $expandedName, $expandedValue )
{
if ( is_array( $expandedValue ) ) {
for ( $i = 0; $i < count( $expandedValue ); $i += 1 ) {
$names[] = $expandedName;
}
array_map( array( $this, 'addNewValueToGraph' ), $names, $expandedValue );
} else if ( $expandedValue instanceof stdClass && property_exists( $expandedValue, '@id' ) ) {
$idProp = '@id';
$id = $expandedValue->$idProp;
$referencedNode = $this->graph->getNode( $id );
if ( is_null( $referencedNode ) ) {
$backrefs = array( $expandedName => array( $this ) );
$referencedNode = $this->factory->newNode( $expandedValue, $this->graph, $backrefs );
} else {
$referencedNode->addBackReference( $expandedName, $this );
}
}
}
/**
* Convenience wrapper around $this->set().
* If the property already exists, the new value overwrites the old value(s).
@ -263,6 +315,11 @@ class JsonLdNode implements ArrayAccess
return property_exists( $this->expanded, '@id' );
}
public function addBackReference( $expandedName, JsonLdNode $referencingNode )
{
$this->backreferences[$expandedName][] = $referencingNode;
}
/**
* Whether a offset exists
* @link https://php.net/manual/en/arrayaccess.offsetexists.php

View File

@ -33,13 +33,14 @@ class JsonLdNodeFactory
* Construct and return a new JsonLdNode.
* @param Node|\stdClass $jsonLd The JSON-LD object input.
* @param JsonLdGraph|null $graph The JSON-LD graph.
* @param array $backreferences Backreferences to instantiate the new node with.
* @return JsonLdNode
*/
public function newNode( $jsonLd, $graph = null )
public function newNode( $jsonLd, $graph = null, $backreferences = array() )
{
if ( is_null( $graph ) ) {
$graph = new JsonLdGraph();
}
return new JsonLdNode( $jsonLd, $this->context, $this, $this->dereferencer, $graph );
return new JsonLdNode( $jsonLd, $this->context, $this, $this->dereferencer, $graph, $backreferences );
}
}

View File

@ -2,6 +2,7 @@
namespace ActivityPub\Test\JsonLd;
use ActivityPub\JsonLd\Exceptions\NodeNotFoundException;
use ActivityPub\JsonLd\Exceptions\PropertyNotDefinedException;
use ActivityPub\JsonLd\JsonLdNode;
use ActivityPub\JsonLd\JsonLdNodeFactory;
@ -316,6 +317,70 @@ class JsonLdNodeTest extends APTestCase
'type' => 'Note',
),
),
array(
(object) array(
'@context' => array( 'https://www.w3.org/ns/activitystreams' ),
'type' => 'Announce',
'object' => 'https://example.org/objects/1',
),
$this->asContext,
array(),
'object',
null,
NodeNotFoundException::class,
),
array(
(object) array(
'@context' => array( 'https://www.w3.org/ns/activitystreams' ),
'type' => 'Announce',
'object' => 'https://example.org/objects/1',
),
$this->asContext,
array(
'https://example.org/objects/1' => (object) array(
'@context' => array( 'https://www.w3.org/ns/activitystreams' ),
'id' => 'https://example.org/objects/1',
'type' => 'Note',
'inReplyTo' => (object) array(
'id' => 'https://example.org/articles/1',
'type' => 'Article',
),
),
),
'object',
(object) array(
'@context' => array( 'https://www.w3.org/ns/activitystreams' ),
'id' => 'https://example.org/objects/1',
'type' => 'Note',
'inReplyTo' => (object) array(
'id' => 'https://example.org/articles/1',
'type' => 'Article',
),
),
),
array(
(object) array(
'@context' => array( 'https://www.w3.org/ns/activitystreams' ),
'type' => 'Announce',
'object' => 'https://example.org/objects/1',
),
$this->asContext,
array(
'https://example.org/objects/1' => (object) array(
'@context' => array( 'https://www.w3.org/ns/activitystreams' ),
'id' => 'https://example.org/objects/1',
'type' => 'Note',
'inReplyTo' => 'https://example.org/articles/1',
),
),
'object',
(object) array(
'@context' => array( 'https://www.w3.org/ns/activitystreams' ),
'id' => 'https://example.org/objects/1',
'type' => 'Note',
'inReplyTo' => 'https://example.org/articles/1',
),
),
);
}
@ -335,6 +400,12 @@ class JsonLdNodeTest extends APTestCase
$this->assertEquals( $expectedValue, $actualValue );
}
public function testBackreferences()
{
// TODO implement me
$this->assertTrue( false );
}
private function makeJsonLdNode( $inputObj, $context, $nodeGraph = array() )
{
$factory = new JsonLdNodeFactory( $context, new TestDereferencer( $nodeGraph ) );