Commit 8a63860c authored by Mateu Aguiló Bosch's avatar Mateu Aguiló Bosch

Allow nested trees

parent 90edcc2d
......@@ -2,15 +2,21 @@
namespace Drupal\subrequests\Blueprint;
use Drupal\Component\Serialization\Json;
use Rs\Json\Pointer;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Contains the hierarchical information of the requests.
*/
class RequestTree {
const ROOT_TREE_ID = '#ROOT#';
const SUBREQUEST_TREE = '_subrequests_tree_object';
const SUBREQUEST_ID = '_subrequests_content_id';
const SUBREQUEST_PARENT_ID = '_subrequests_parent_id';
const SUBREQUEST_DONE = '_subrequests_is_done';
/**
......@@ -20,6 +26,7 @@ class RequestTree {
/**
* If this tree sprouts from another requests, save the request id here.
*
* @var string
*/
protected $parentId;
......@@ -78,6 +85,7 @@ class RequestTree {
$trees = array_map(function (Request $request) {
return $request->attributes->get(static::SUBREQUEST_TREE);
}, $this->getRequests());
return array_filter($trees);
}
......@@ -104,11 +112,13 @@ class RequestTree {
if (!$sub_tree = $request->attributes->get(static::SUBREQUEST_TREE)) {
return FALSE;
}
return $sub_tree->getDescendant($request_id);
});
if (count($found)) {
return reset($found);
}
return NULL;
}
......@@ -125,6 +135,59 @@ class RequestTree {
}, TRUE);
}
/**
* Resolves the JSON Pointer references.
*
* @todo For now we are forcing the use of JSON Pointer as the only format to
* reference properties in existing responses. Allow pluggability, this step
* should probably be better placed in the subrequest normalizer.
*
* @param \Symfony\Component\HttpFoundation\Response[] $responses
* Previous responses available.
*/
public function dereference(array $responses) {
$this->requests = array_map(function (Request $request) use ($responses) {
$subrequest_id = $request->attributes->get(static::SUBREQUEST_ID);
// Detect {{/foo#/bar}}
$pattern = '/\{\{\/([^\{\}]+)@(\/[^\{\}]+)\}\}/';
// Allow replacement tokens in:
// 1. The body.
// 2. The path.
// 3. The query string values.
$matches = [];
if (preg_match($pattern, $request->getContent(), $matches)) {
// Do the magic.
$matches;
}
$matches = [];
if (preg_match($pattern, $request->getRequestUri(), $matches)) {
$replacement = static::findReplacement($responses, $matches[1], $matches[2]);
$new_uri = preg_replace($pattern, $replacement, $request->getRequestUri());
$request->server->set('REQUEST_URI', $new_uri);
// We need to duplicate the request to force recomputing the internal
// caches.
$request = Request::create(
$new_uri,
$request->getMethod(),
(array) $request->query->getIterator(),
(array) $request->cookies->getIterator(),
(array) $request->files->getIterator(),
(array) $request->server->getIterator(),
$request->getContent()
);
$request->headers->set('Content-ID', sprintf('<%s>', $subrequest_id));
}
foreach ($request->query as $key => $value) {
$matches = [];
if (preg_match($pattern, $request->getUri(), $matches)) {
// Do the magic.
$matches;
}
}
return $request;
}, $this->getRequests());
}
/**
* Check if a request and all its possible children are done.
*
......@@ -146,7 +209,19 @@ class RequestTree {
return FALSE;
}
}
return TRUE;
}
protected static function findReplacement($responses, $id, $json_pointer_path) {
/** @var \Symfony\Component\HttpFoundation\Response $response */
$response = array_filter($responses, function (Response $response) use ($id) {
return $response->headers->get('Content-ID') === sprintf('<%s>', $id);
})[0];
// Find the data in the response output.
$pointer = new Pointer($response->getContent());
return $pointer->get($json_pointer_path);
}
}
......@@ -52,6 +52,11 @@ class FrontController extends ControllerBase {
$trees = [$root_tree];
// Handle all the sub-requests.
while (!$root_tree->isDone()) {
// Requests in the current level may have references to older responses.
// This step resolves those.
array_walk($trees, function (RequestTree $tree) use ($responses) {
$tree->dereference($responses);
});
// Get all the requests in the trees for the previous pass.
$requests = array_reduce($trees, function (array $carry, RequestTree $tree) {
return array_merge($carry, $tree->getRequests());
......
......@@ -35,7 +35,33 @@ class JsonBlueprintDenormalizer implements DenormalizerInterface, SerializerAwar
$requests = array_map(function ($item) use ($format, $context) {
return $this->serializer->denormalize($item, Request::class, $format, $context);
}, $data);
return new RequestTree($requests);
// We want to create one tree per parent, but for that we need to identify
// the parents first.
$requests_per_parent = array_reduce($requests, function (array $carry, Request $request) {
$parent_id = $request->attributes
->get(RequestTree::SUBREQUEST_PARENT_ID, RequestTree::ROOT_TREE_ID);
if (empty($carry[$parent_id])) {
$carry[$parent_id] = [];
}
$carry[$parent_id][] = $request;
return $carry;
}, []);
// Now get all the requests for the root parent to create the root tree.
$root_tree = new RequestTree($requests_per_parent[RequestTree::ROOT_TREE_ID]);
unset($requests_per_parent[RequestTree::ROOT_TREE_ID]);
// Iterate through all the parents to find them in the tree. The attach the
// sub-tree to the root.
// TODO: If a tree hangs from a parent that is not attached to the root, then this process may fail.
foreach ($requests_per_parent as $parent_id => $children_requests) {
$parent_request = $root_tree->getDescendant($parent_id);
$parent_request->attributes->set(
RequestTree::SUBREQUEST_TREE,
new RequestTree($children_requests)
);
}
return $root_tree;
}
/**
......
......@@ -3,11 +3,12 @@
namespace Drupal\subrequests\Normalizer;
use Drupal\Component\Utility\NestedArray;
use Drupal\subrequests\Blueprint\Parser;
use Drupal\subrequests\Blueprint\RequestTree;
use Symfony\Component\HttpFoundation\HeaderBag;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Drupal\Component\Utility\NestedArray;
class JsonSubrequestDenormalizer implements DenormalizerInterface {
/**
......@@ -58,6 +59,12 @@ class JsonSubrequestDenormalizer implements DenormalizerInterface {
? md5(serialize($data))
: $data['requestId'];
$request->headers->set('Content-ID', '<' . $content_id . '>');
$request->attributes->set(RequestTree::SUBREQUEST_ID, $content_id);
// If there is a parent, then add the ID to construct the tree.
if (!empty($data['waitFor'])) {
$request->attributes
->set(RequestTree::SUBREQUEST_PARENT_ID, $data['waitFor']);
}
return $request;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment