http://dropbucket.org/node/659

This snippet shows a way you can alter the response data of a restws call so that it returns the fully loaded items that are being referenced instead of just the resource type, identifier and link. I saw another thread making mention of the fact that this approach isn't REST-ful by design as the rationale for not being included in the default response but it helps (a lot) when working with field collections.

Example request is node/1.json?deep-load-refs and returned data would include the node + the fully loaded field collections. This is useful if your end-point has information about the node stored but really needs to know information about the field collections included with the node.

Leaving this here for example purposes

Comments

klausi’s picture

Yep, in-lining certain reference fields is surely a valid feature request. We would need to make it configurable which fields should be in-lined on which entity bundle.

Note that your approach has security implications, as you are not checking field access at all and just delivering the whole entity object. So you need to be careful who your consumers are and what they are allowed to see. Field access might not be an issue for you, but as you are overriding the response for any entity type and bundle this looks dangerous.

btopro’s picture

ty for the quick response. Yes I had feared that. I'm not using it as an open API and my rest user account is IP locked so it basically has perms to defeat access restrictions. That said, I'd definitely like it to take into account the security implication but wanted to at least see if the inlining vs ref was a conversation worth having or if I should stick to alter based implementations like this. Is there an easy way of scrubbing an object based on field / access permissions to that object? I could do entity_access but seems that would only limit loading to ability to see it which still doesn't solve the issue of potential field access permissions.

klausi’s picture

If you use entity metadata wrappers (from Entity API) then you can check access on each field. See restws_property_access_filter() for an example.

btopro’s picture

Thanks for the example. I've modified my original code to follow a format that I think will be taking into account the wrapper functionality for stripping off fields accurately based on access.

// specific modifications based common request type
  if ($function == 'viewResource' && $formatName == 'json') {
    // allow for deep loading of resources
    if (isset($_GET['deep-load-refs'])) {
      foreach ($response as $key => &$val) {
        // check for a single resource verses many
        if (is_array($val) && isset($val[0]['id'])) {
          // loop through items loading them in
          foreach ($val as &$item) {
            // load the entity
            $entity = entity_load_single($item['resource'], $item['id']);
            // ensure they can view this specific item
            if (entity_access('view', $item['resource'], $entity)) {
              // create a meta wrapper to act on for entity
              $wrapper = entity_metadata_wrapper($item['resource'], $entity);
              // filter out these values
              $wrap = restws_property_access_filter($wrapper);
              $eary = (array) $entity;
              foreach ($eary as $property => $value) {
                // value needs to be removed as it didn't pass wrapper validation
                if (!isset($wrap[$property])) {
                  unset($eary[$property]);
                }
              }
              // add values based on wrapper passing correctly
              $item[$item['resource']] = $eary;
            }
          }
        }
        elseif (is_array($val) && isset($val['id'])) {
          // load the entity
          $entity = entity_load_single($val['resource'], $val['id']);
          // ensure they can view this specific item
          if (entity_access('view', $val['resource'], $entity)) {
            // create a meta wrapper to act on for entity
            $wrapper = entity_metadata_wrapper($val['resource'], $entity);
            // filter out fields
            $wrap = restws_property_access_filter($wrapper);
            // typecast entity as array for property evaluation
            $eary = (array) $entity;
            foreach ($eary as $property => $value) {
              // value needs to be removed as it didn't pass wrapper validation
              if (!isset($wrap[$property])) {
                unset($eary[$property]);
              }
            }
            // add values based on wrapper passing correctly
            $val[$val['resource']] = $eary;
          }
        }
      }
    }
  }
JamesAn’s picture

Issue summary: View changes

This code snippet appears to no longer work with the current hook_restws_response_alter(). The $response object is now a DOMDocument class object, not an array reference, so the foreach/if statement doesn't work. I've filed #2394869: Return entity reference fields recursively as full-fledged entity objects to capture this new change rather than re-open this issue.

I'm also trying to immortalise this feature of recursively loading entityreference entities in a sandbox module. More to come on that shortly.

JamesAn’s picture

Status: Active » Closed (duplicate)

Continue updated conversation on related thread: #2394869: Return entity reference fields recursively as full-fledged entity objects, rather than revive this year-old issue.

JamesAn’s picture

Media Crumb’s picture

Sorry to reopen this, but I'm having trouble tracking which patch to use or if this code snipet still in fact works. 2 questions:

Does the code snipet above in fact work or should i be using something more recent?

This doesn't seem to work with files/image resources, am I missing something?

btopro’s picture

See the referenced through https://www.drupal.org/node/2394869

Media Crumb’s picture

Thanks, Looks like the link you sent your snippet has change a bit. Does it work when just grabbing all nodes of one type.

IE:

www.site.com/node.json?type=blogs&deep-load-refs

Currently I'm getting 412 Precondition Failed: Not a valid filter: deep-load-refs

I assume it wont grab rel on just the node.json, but i have an instance where I'm returning the last 10 items from www.site.com/node.json?type=blogs and would like to save the amount of requests i have to make for each item.

Also, was this rolled into the latest release? I seem to see reference that it was, but I can't tell.

Cosy’s picture

Can anyone clarify whether this patch has been rolled out yet?
Or at least where the latest functioning patch is ?

Trying to get image file_entity fields such as tag/description returned in a call to the node that references it!

Thanks..

Cosy’s picture

Just realised
function hook_restws_meta_controls_alter(&$controls) {
$controls['deep-load-refs'] = 'deep-load-refs';
}
was added to restws but am I right in understanding this just allows the patch to work? But I still need to apply the patch?

Or should I replace the entire restws module with https://github.com/elmsln/elmsln/tree/master/core/dslmcode/shared/drupal... ?

Currently calling examaple.com/node/1.json?deep-load-refs doesnt appear to change the output.

and as above calling node.json?deep-load-refs returns the 412

Cosy’s picture

Additionally, Is there any way to include an image format in the response?
i.e rather than reference the original file, spit out medium, thumb formats etc

btopro’s picture

deep-load-refs is NOT in restws, it is in elmsln in the files you found.

/**
 * Alter the list of allowed meta controls.
 *
 * @param array $controls
 *   A list of allowed meta controlers
 */
function hook_restws_meta_controls_alter(&$controls) {
  $controls['deep-load-refs'] = 'deep-load-refs';
}

This from RestWS is in the API as an example of how to use it.

need to use deep-load-refs like this
deep-load-refs=node,user

will load the resource, then check and load all node entity references and all user entity references

Cosy’s picture

Thanks for swift response & time bto.
Apologies, but my Drupal knowledge is a bit ~

So to clarify.

I would need to make a module (lets call it cosy_rest_alter)
In that module, a basic cosy_rest_alter.info file containing something like

name = Cosy Rest alter
description = ...
core = 7.x
dependencies[] = restws
features[user_permission][] = access resource node

2
a cosy_rest_alter.module file containing:
http://dropbucket.org/node/659 (with the module name replacing "YOUR MODULE"

3
file cosy_rest_alter.features.user_permission containing:
function cosy_rest_alter_restws_user_default_permissions() {
$permissions = array();

// Exported permission: 'access resource node'.
$permissions['access resource node'] = array(
'name' => 'access resource node',
'roles' => array(
'SERVICE ACCOUNT' => 'SERVICE ACCOUNT',
'administrator' => 'administrator',
),
'module' => 'restws',
);

return $permissions;
}

And this alongside restws would be sufficient to implement?

USAGE:

http://eaxample.com/node/25.json?deep-load-refs=node,user ?
http://eaxample.com/node.json?deep-load-refs=node,user ?

btopro’s picture

take https://github.com/elmsln/elmsln/blob/master/core/dslmcode/shared/drupal...

replace cis_service_restws with YOURMODULE or whatever

Then add this

/**
 * Implements hook_restws_meta_controls_alter().
 */
function YOURMODULE_restws_meta_controls_alter(&$controls) {
  $controls['deep-load-refs'] = 'deep-load-refs';
  $controls['xml-out'] = 'xml-out';
  $controls['role-filter'] = 'role-filter';
  $controls['count'] = 'count';
  $controls['display_mode'] = 'display_mode';
}

This will give you the 5 additional methods we use on elmsln; deep resource loading, ability to kick json out as xml, role filtering for users, returning counts of results and rendering entities in certain display modes.

Cosy’s picture

Nice one bto!

I'm having some success with this but cant quite get the deep-load to work with files..

I can call node/25?xml-out => works

DISPLAY-MODES: awesome! that's a nice extra i can make use of at some point.
Calling node/25?display_mode=teaser => works

node/25?deep-load-refs=user => works
node/25?deep-load-refs=taxonomy_term => also works

but calling tried node/25?deep-load-refs=file doesn't seem to do anything.

I note files are in an array, but sit inside a "file" object..
Could this be causing problems?

-Having said that, I just looked over the .module & it appears you are accounting for this file: {id:*} structure, correct?

> Is this potentially a permissions issue?
I thought probably not since I'm using a full administrator level account & appear to have all permissions on.

My images fields looks like:
"field_project_images": [
{
"file": {
"uri": "http://eg.com/file/34",
"id": "34",
"resource": "file"
}
},
{
"file": {
"uri": "http://eg.com/file/33",
"id": "33",
"resource": "file"
}
}
]

Cosy’s picture

Notes:

Definatley seems to be having problems with an array..
When the field is limited to 1 - deep-load-refs=file works great.

"field_single_file": {
    "file": {
      "uri": "http://ex.com/file/39",
      "id": "39",
      "resource": "file"
    }
  } 

expands correctly but

"field_multiple_file": [
    {
      "file": {
        "uri": "http://ex.com/file/40",
        "id": "40",
        "resource": "file"
      }
    }
  ] 

does not ?!

seems odd because the module file says elseif (is_array($val) && isset($val['file']['id']))

Some sort of mis-translation between json and php ?

btopro’s picture

Hmm.. I get a url; there's really nothing to do for file deep loads as a lot of that information should already be there. url is the only thing you need that it doesn't generate at least in what I'm seeing.

Shouldn't be any permissions issues as we're doing the same entity checks that the rest of it is. Also if restws blocked a file field from showing the ID in the first place then you wouldn't be able to dig down into it. Drupal doesn't let you know about something if you can't then turn around and access it (with the exception of field_permissions module but this also takes it into account).

Cosy’s picture

Correctly expanded single file looks like:

{
    "file": {
      "fid": "39",
      "uid": "2",
      "filename": "tangible-1.png",
      "uri": "public://tangible-1.png",
      "filemime": "image/png",
      "filesize": "46607",
      "status": "1",
      "timestamp": "1457964817",
      "type": "image",
      "field_file_title": {
        "und": [
          {
            "value": "test image",
            "format": null,
            "safe_value": "test image"
          }
        ]
      },
      "field_file_body": {
        "und": [
          {
            "value": "<p>blah blah</p>\r\n",
            "format": "filtered_html",
            "safe_value": "<p>blah blah</p>"
          }
        ]
      },
      "field_file_tags": [],
      "field_file_dimensions_print": [],
      "field_file_dimensions_original": [],
      "url": "http://ex.com/sites/default/files/tangible-1.png"
    }

... im just wondering if this expands ok, when the module is asking "is_array($val)" when $val in json is an object.
Then... for multiple files in a json [] array , does the test "is_array($val)" fail for this reason ?
or is all arrays in php until rendered as json?

btopro’s picture

i guess you are using file entity which we don't so I have no idea

Cosy’s picture

Thinking I might try and use "Distill" to within a similar structure to your "display_mode" implementation as I'm going to need to render each image too with different image formats in one response, then pick the appropriate image format from my angular frontend depending on breakpoints..

i.e IDEAL GOAL is to add an additional field to the node output along the lines of:

 {
 (... node fields),
 images_rendered: [
 {
mobile:'http://ex.com/sites/default/files/styles/mobile/tangible-1.png'
tablet:'http://ex.com/sites/default/files/styles/tablet/tangible-1.png'
desktop:'http://ex.com/sites/default/files/styles/desktop/tangible-1.png'
},
{
mobile:'http://ex.com/sites/default/files/styles/mobile/tangible-2.png'
tablet:'http://ex.com/sites/default/files/styles/tablet/tangible-2.png'
desktop:'http://ex.com/sites/default/files/styles/desktop/tangible-2.png'
},
 ]
}
Cosy’s picture

OH.
Bugger, I see.
Odd that the single file from file entity does expand with the additional fields though.
Hmm. Okay, I guess if its ok, I'll just have to use your module as a starting block to try and flesh out something a bit more specific to my needs.

If I come up with anything useful, Ill post

Cosy’s picture

Solved:

Added the following code to the for each response to handle arrays of images (almost exact copy of what you were doing to handle multiples of other entities

// special case for ARRAY of file entities
    elseif (is_array($val) && isset($val[0]['file']['id'])) {
      // loop through items loading them in
      foreach ($val as &$item) {
        if (in_array($item['file']['resource'], $allowed)) {
          // load the entity
          $entity = entity_load_single($item['file']['resource'], $item['file']['id']);
          // ensure they can view this specific item
          if (entity_access('view', $item['file']['resource'], $entity)) {
            // create a meta wrapper to act on for entity
            $wrapper = entity_metadata_wrapper($item['file']['resource'], $entity);
            // filter out fields
            $wrap = restws_property_access_filter($wrapper);
            // typecast entity as array for property evaluation
            $eary = (array) $entity;
            // these properties don't cause security issues but file entity is
            // really tricky, especially without file_entity... and even then with
            // it it can be a pain
            $file_fix = array(
              'fid' => 'fid',
              'uid' => 'uid',
              'uri' => 'uri',
              'filename' => 'filename',
              'filemime' => 'filemime',
              'filesize' => 'filesize',
              'status' => 'status',
              'timestamp' => 'timestamp',
            );
            foreach ($eary as $property => $value) {
              // value needs to be removed as it didn't pass wrapper validation
              if (!isset($wrap[$property]) && !isset($file_fix[$property])) {
                unset($eary[$property]);
              }
            }
            $eary['url'] = file_create_url($eary['uri']);
            // make sure we don't have a service path delivery for files
            $eary['url'] = str_replace('/services/', '/', $eary['url']);
            // add values based on wrapper passing correctly
            $item['file'] = $eary;
          }
        }
      }
    }
Cosy’s picture

For those also interested in my above problem: how to get formatted versions of each file within the return...

I just added:

//after this line
$eary['url'] = str_replace('/services/', '/', $eary['url']);

//Modified from here
$eary['formatted-thumb'] = image_style_url('thumbnail', $eary['uri']);
// etc etc

$val['file'] = $eary;

to the end of the code block that builds the image entities :)

npacker’s picture

Thanks to btopro for getting the ball rolling on this.

As there was clearly a lot of code duplication within the solutions so far, I have adapted it to use the existing restws classes, which are used internally for building the response data. A lot of the work can be off-loaded to those classes, ensuring both security and consistency of the data.

Full module is here: https://bitbucket.org/wwuweb/restws_entityreference

The module provides an option to filter which entity types are expanded, and an option to set the maximum recursion depth.

Cosy’s picture

interesting...
but link is dead

nvexler’s picture

Marko B’s picture

I also find this module very useful for adding extra info to JSONs output https://www.drupal.org/project/restws_embed

Cosy’s picture

Well done nvexler.

Even if youre name isn't pronouncable without spitting on my shirt.. that is a really tasty encapsulation of bto's work

Very useful. Thanks

Cosy’s picture

As an example for anyone trying to use nvxler's module.

I've got a node with a field collection of nodes, each with a gallery field.

All I had to do was call example.com/node/4?load-entity-refs=node,file,field_collection_item&max-depth=3 et voila, I had everything I needed in one call to make an awesome Angular powered page with a dynamic gallery based off what section was being viewed!

The 2 parameters the module adds to a basic restws call are:

load-entity-refs: a list of entity types to allow inclusion of
max-depth: a maximum recursion depth.

rcodina’s picture

@Cosy Many thanks for sharing how you did it. It works for me too. Thanks to @nvexler too!

rcodina’s picture

@npacker Could restws_entityreference module be integrated on restws module as a submodule? See #2845908: Add instructions on README file of module on how to enable entity deep load. Thanks for doing this utility module!

jigarius’s picture

I had faced a similar problem with Drupal 7 services. I had resorted to a solution by sending a parameter to define which entity references to expand. Example: ?expand[]=user.field_image&expand[]=node.author, etc. I also had a parameter to state the max depth to expand to avoid circular references and infinite loops.