Problem/Motivation
The HAL REST API response's _embedded and _links relationship Uris are prepended with a fixed Hostname from url(). This can cause issues in development/qa/staging environments where the host differs.
In addition contrib modules like default_content (http://github.com/larowlan/default_content) have to modify the service definition and swap in their own implementation of the TypeLinkManagerInterface and the RelationLinkManagerInterface in order to make these URIs generic enough to support moving content between different sites. However this is a global change and means that all REST uris are updated to use the new format.
A way is needed to make this host configurable but also context specific.
Proposed resolution
We already have $context argument to the serializer/normaliser operations but we don't pass that along to the link managers (TypeLinkManagerInterface and RelationLinkManagerInterface).
This patch passes that context along and also makes the base-url configurable via config.
The context and the generated URI are then sent through two new alter hooks hook_rest_type_uri_alter() and hook_rest_relation_uri_alter() for each respective manager. This takes the generated URI and the context as arguments and allows contrib projects to conditionally modify the generated URIs based on the serializer/normalizer context. For example default content would set a flag in this context before deserializing/normalizing and then ensure it only alters URIs for its own serializer operations, not globally.
How to test
- Check current value
drush @drupal.d8 config-get rest.settings.relation_domain - Run a get on node/1
curl --user admin:admin --header 'Accept: application/hal+json' --request GET http://drupal.d8/node/1 - Check the response on the domains used.
- Set relation domain
drush @drupal.d8 config-set rest.settings relation_domain http://x.y.z - Check the set value
drush @drupal.d8 config-get rest.settings.relation_domain - Rerun a get on node/1
curl --user admin:admin --header 'Accept: application/hal+json' --request GET http://drupal.d8/node/1 - Recheck the response on the domains used.
Remaining tasks
Reviews
API Changes
RelationLinkManagerInterface::getRelationUri now has optional $context param - will impact on classes implementing that interface TypeLinkManagerInterface::getTypeUri now has optional $context param - will impact on classes implementing that interface.
Two new alter hooks hook_rest_type_uri_alter() and hook_rest_relation_uri_alter()
The interface change will impact file_entity and default_content in contrib, I'm (@larowlan) the maintainer of one - and require this change for required functionality. Also willing to commit a patch to the other. Have spoken to @berdir who's maintaining the D8 port of file_entity.
new interface:
interface ConfigurableLinkManagerInterface {
public function setLinkDomain($domain);
}
RelationLinkManager, LinkManager and TypeLinkManager implement this interface.
New abstract base class BaseLinkManager which RelationLinkManager and TypeLinkManager extend from
Beta phase evaluation
| Issue category | Bug because hard-coding isn't the Drupal way |
|---|---|
| Issue priority | Major because contrib has no option but to swap the whole service for something that should be configurable |
| Prioritized changes | The main goal of this issue is flexibility. This is not a prioritized change for the beta phase. But it causes known issues in contrib *already* |
| Disruption | Possibly disruptive to contrib- but the impacts will be on file-entity and default-content in contrib and both maintainers of the current D8 work are involved in this issue and behind the change |
| Comment | File | Size | Author |
|---|---|---|---|
| #68 | rest-relation-domain-2336247.68.patch | 32.85 KB | larowlan |
| #68 | interdiff.txt | 935 bytes | larowlan |
| #64 | rest-relation-domain-2336247.64.patch | 32.81 KB | larowlan |
| #64 | interdiff.txt | 1.07 KB | larowlan |
| #62 | rest-relation-domain-2336247.62.patch | 32.75 KB | larowlan |
Comments
Comment #1
Anonymous (not verified) commentedWith this patch, the base path can be configured with the
relation_link_pathparameter inrest.settings.Comment #3
Anonymous (not verified) commentedThis should fix the tests; The rest.settings schema needed updating
Comment #4
Anonymous (not verified) commentedComment #5
Anonymous (not verified) commentedUpdated patch to work with beta1
Comment #7
Anonymous (not verified) commentedFixed Url calls that broke tests
Comment #8
Anonymous (not verified) commentedComment #9
larowlanPlease inject the config factory instead of calling out to the global singleton
Please inject the service instead of using the global singleton
I don't think this goes far enough. In default_content (http://github.com/larowlan/default_content) we have to override the type link manager and the relation link manager to provide an alternate domain so that content can be exported from one site and imported into another. However due to the design of this system this is a global override. So other modules might wish to use the actual url, or the url as configured by this patch, but cannot. I think a more flexible approach would be to inject the module handler and fire an alter hook on the generated links.
Comment #10
clemens.tolboomCode duplications. This suggests to use a Trait.
Remove the ltrim by removing the '/' in $base.
[edit]And add it @ appropriate places[/edit]
Comment #11
larowlanIt might not always be there
Comment #12
clemens.tolboom@larowlan what did I miss? New code should be
Comment #13
larowlanNew patch fixes #9 and #10 - passes the $context from the normalize/serialize around and then two new hooks to allow other modules to alter.
To-do: RelationLinkManagerInterface::getRelationInternalIds doesn't exist but is referenced in docblock of RelationLinkManager - quite a few missing public methods on interface.
Comment #15
larowlanMissed a spot
Comment #16
larowlanNew issue summary detailing scope/changes/justifications
Comment #18
larowlanComment #20
larowlanComment #21
larowlanFiled #2355977: Code references RelationLinkManagerInterface::getRelationInternalIds but no such method for out-of-scope bug found
Comment #22
clemens.tolboomhook_rest_resource_alter seems an unrelated but very useful addition right?
Following the How to test steps the resulting _links.type.hrefand link names like http://x.y.z/rest/relation/node/article/uid are changed accordingly.
Reading the specs http://stateless.co/hal_specification.html I'm wondering whether curries wouldn't be a better solution. Is this issue blocking implementing curries? Or is this pretty close to implementing curries?
I guess this is RTBC
apart from the added untested rest_resource_alter. Guess that should be in another issue.Comment #23
larowlanThe relation alter is for the links in embedded, ie entity reference fields
Comment #24
clemens.tolboom@larowlan I'm talking about hook_rest_resource_alter. That has no test, seems unrelated to this issue as I haven't found any usage either but is a nice addition so deserves its own issue.
My question about curries isn't answered either.
Comment #25
larowlanThis is the title of the issue 'make relation domains configurable', although the solution also deals with the type domain too (so updated title) - note that both TypeLinkManager and RelationLinkManager have a todo to make the domain configurable - those todos are the point of this issue.
There is test implementation, test coverage and invocation as follows:
Here is where it is invoked
Here is the test coverage
Here is the test implementation
I've never heard of curies before, but after reading the linked document - it seems that they are very useful - but are more applicable to sub-resources or related resources? Eg node/1 could have curies for node/1/comments and node/1/revisions. Am I reading that correctly? I'm not sure that will work because the links aren't sub-resources, they're not in the same relative path. Eg relation URIs are
rest/relation/$entity_type/$bundle/$field_nameand type URIs arerest/type/$entity_type/$bundlewhereas entity URIs are/entity/{entity_type}/{entity}- however what this patch does allow is for a contrib project to implement curies and refactor all of these URIs to suit. This isn't possible in HEAD as it stands.Comment #26
larowlanSetting back for more eyes
Comment #27
clemens.tolboom(Forget the discussion about hook_rest_resource_alter above. (I had a weird .gitignore so assumed to much))
Darn. I missed the upgrade route. Are we now required to support beta upgrades? I guess so so 'Needs work'.
curl --user admin:admin --header 'Accept: application/hal+json' --request GET http://drupal.d8/node/1http://drupal.d8/rest/relationRoot cause is
which should have a value.
Comment #28
larowlanNo, an upgrade path isn't supported yet - see #2341575: [meta] Provide a beta to beta/rc upgrade path
Comment #29
clemens.tolboomBased on feedback from #28 this is RTBC to me.
Comment #30
alexpottIt would be good to have at least an @see to where the context is set and what are the possible values here.
I don't think this is going to be correct enough of the time - if you install on a dev server and then push config to live this automatically set value is likely to be incorrect. Therefore I think we should have a hook_requirements warning to advise the user to set it - or an automatic fallback. Discussed this with @larowlan on IRC and he agreed.
Not used.
This is a string not an array.
Comment #31
larowlanFixes #30 1 » 4
In the case of 2 » if the config value isn't set we default to the
<front>page URI.But then takes it further - adds a new interface thus:
Makes RelationLinkManager, LinkManager and TypeLinkManager implement this interface by way of a new abstract base class BaseLinkManager which RelationLinkManager and TypeLinkManager extend from.
This new interface makes it easier for modules that need to call ->deserialize/normalize/denormalize/serialize themself as they can set the domain before hand and then set it back afterwards instead of relying on the hook.
Comment #32
larowlanComment #33
wim leersHigh-level review.
If required to be a string, could/should be type-hinted. Here and elsewhere.
s/rest/REST/
?
Here and elsewhere.
Link domain or relation domain?
Comment #35
larowlanDo you mean array?
It applies to relation links and type links, so used the generic term link - noticed that many places in the patch refer to 'relation domain' so cleaned them up, including the config key name to use 'link domain' and link_domain consistently.
Comment #36
larowlanreroll
Comment #37
dixon_Code looks really good. Below are a few minor nit-picks:
Since when are we using the short
[]array syntax in core? I've got nothing against it, I'm just curious :-) (Edit: We could perhaps add this as an acceptable thing here: https://www.drupal.org/coding-standards#array)Don't we usually suffix
Baseon abstract classes, rather than prefixing?Since we're extending the interface with an optional parameter it could be worth to either A) add phpDoc on the LinkManager to explain what $context is/does or B) actually add the $context parameter to the LinkManagerInterface itself, include that in the interface's phpDoc and change these lines to
/** {@inheritdoc} */.Comment #38
larowlan1. we're using it for most new code - as long as it's consistent (ie don't mix the two)
2. Fixed
3. LinkManagerInterface extends TypeLinkManagerInterface and RelationLinkManagerInterface, both of which contain the phpdoc. The 'Implements Interface::method' docblocks are as per HEAD, I think changing them here is out of scope.
Comment #40
larowlanmissed the file rename
Comment #41
dixon_Ok, that makes sense.
Patch is green and I think the code is really solid. Ready to go IMO.
Comment #44
larowlanlooks like a random fail
Comment #45
larowlantwas a random fail
Comment #46
alexpottI thought we agreed to remove this? Especially give that this is what we fallback to anyway. See #30
Comment #47
larowlanSure, sorry
Boldly sticks back to rtbc.
Comment #49
larowlan@alexpott - looks like we can't - it causes a recursive router rebuild in views serializer style plugin.
Comment #50
jibranBack to RTBC for @alexpott to review.
Comment #51
alexpottWell doesn't that mean that the fallback is not going work then?
Comment #52
larowlanRe-roll and solves the recursion issue, removes the hook_install as requested.
Also, I think this is major - hardcoded is not the Drupal way.
Comment #53
larowlanBacked out the hook_install but not the test :)
Comment #55
larowlanback to rtbc anyone?
Comment #56
larowlanreroll
Comment #58
dawehnerIts quite annoying that these kernel tests don't get services from the container.
Usually we don't use composable interface but rather extend existing ones, is there a reason why we don't extend? If you don't extend typehinting gets a little bit tricky.
So you wanted to get the base url + base path?
It would be great to avoid calling to the config factory on constructor time, given that this could always lead somehow by accident to circular dependencies or simply slow down things. You already have a method: getLinkDomain, so you can call it there.
Comment #59
larowlanComment #60
larowlanRe-roll
1. yep
2. RelationLinkManagerInterface extends ConfigurableLinkManagerInterface, as does TypeLinkManagerInterface - I think this composition is pre-existing in rest module where we have the common LinkManager which wraps TypeLinkManager and RelationLinkManager, but not a common base class
3. Injected request stack instead, much cleaner
4. fixed
Comment #62
larowlanTest fixes
Comment #64
larowlanComment #65
larowlanThis has been rtbc three times already, any takers for number four?
Comment #66
jibranYeah sure.
Comment #67
alexpottDo we need a CR? Maybe there are other affect contrib or custom modules?
Let's make this fluent and return $this.
Comment #68
larowlanFixed #67
Draft CR is https://www.drupal.org/node/2484399
Comment #69
jibranBack to RTBC.
Comment #70
alexpottCommitted 20add38 and pushed to 8.0.x. Thanks!
Thanks for adding the beta evaluation.
Removed unused use.