-
Notifications
You must be signed in to change notification settings - Fork 173
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow loading entities by any field. #334
Changes from all commits
29f812f
83d768e
6705cc1
d43aa4c
68a0e21
9c9db08
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -86,7 +86,7 @@ public static function controllersInfo() { | |
// POST | ||
\RestfulInterface::POST => 'createEntity', | ||
), | ||
'^(\d+,)*\d+$' => array( | ||
'^.*$' => array( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. seems we are loosing an important validation here, although I'm not sure how smart we want to be about it. Any ideas? (anyway this isn't a blocker) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it's a big deal, since there's still validation when trying to load the entity. |
||
\RestfulInterface::GET => 'viewEntities', | ||
\RestfulInterface::HEAD => 'viewEntities', | ||
\RestfulInterface::PUT => 'putEntity', | ||
|
@@ -145,12 +145,12 @@ public function getList() { | |
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function viewEntities($entity_ids_string) { | ||
$entity_ids = array_unique(array_filter(explode(',', $entity_ids_string))); | ||
public function viewEntities($ids_string) { | ||
$ids = array_unique(array_filter(explode(',', $ids_string))); | ||
$output = array(); | ||
|
||
foreach ($entity_ids as $entity_id) { | ||
$output[] = $this->viewEntity($entity_id); | ||
foreach ($ids as $id) { | ||
$output[] = $this->viewEntity($id); | ||
} | ||
return $output; | ||
} | ||
|
@@ -270,7 +270,8 @@ protected function getQueryResultForAutocomplete() { | |
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function viewEntity($entity_id) { | ||
public function viewEntity($id) { | ||
$entity_id = $this->getEntityIdByFieldId($id); | ||
$request = $this->getRequest(); | ||
|
||
$cached_data = $this->getRenderedCache(array( | ||
|
@@ -397,7 +398,7 @@ protected function getValueFromProperty(\EntityMetadataWrapper $wrapper, \Entity | |
protected function getValueFromFieldFormatter(\EntityMetadataWrapper $wrapper, \EntityMetadataWrapper $sub_wrapper, array $info) { | ||
$property = $info['property']; | ||
|
||
if (!field_info_field($property)) { | ||
if (!static::propertyIsField($property)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. over abstraction? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel that it makes semantic sense. A contributor or a module extending restful doesn't need to know about Field API to discern between an entity property and a field. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not a big fan, but I can live with that ;) |
||
// Property is not a field. | ||
throw new \RestfulServerConfigurationException(format_string('@property is not a configurable field, so it cannot be processed using field API formatter', array('@property' => $property))); | ||
} | ||
|
@@ -564,7 +565,8 @@ public function deleteEntity($entity_id) { | |
/** | ||
* {@inheritdoc} | ||
*/ | ||
protected function updateEntity($entity_id, $null_missing_fields = FALSE) { | ||
protected function updateEntity($id, $null_missing_fields = FALSE) { | ||
$entity_id = $this->getEntityIdByFieldId($id); | ||
$this->isValidEntity('update', $entity_id); | ||
|
||
$wrapper = entity_metadata_wrapper($this->entityType, $entity_id); | ||
|
@@ -581,7 +583,6 @@ protected function updateEntity($entity_id, $null_missing_fields = FALSE) { | |
return array($this->viewEntity($wrapper->getIdentifier())); | ||
} | ||
|
||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
|
@@ -1472,4 +1473,92 @@ public function clearResourceRenderedCacheEntity($id) { | |
$this->cacheInvalidate($cid); | ||
} | ||
|
||
/** | ||
* Get the entity ID based on the ID provided in the request. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe worth giving an example, since it might not be obvious without this PR as context. maybe:
|
||
* | ||
* As any field may be used as the ID, we convert it to the numeric internal | ||
* ID of the entity | ||
* | ||
* @param mixed $id | ||
* The provided ID. | ||
* | ||
* @throws RestfulBadRequestException | ||
* @throws RestfulUnprocessableEntityException | ||
* | ||
* @return int | ||
* The entity ID. | ||
*/ | ||
protected function getEntityIdByFieldId($id) { | ||
$request = $this->getRequest(); | ||
if (empty($request['loadByFieldName'])) { | ||
// The regular entity ID was provided. | ||
return $id; | ||
} | ||
$public_property_name = $request['loadByFieldName']; | ||
// We need to get the internal field/property from the public name. | ||
$public_fields = $this->getPublicFields(); | ||
if ((!$public_field_info = $public_fields[$public_property_name]) || empty($public_field_info['property'])) { | ||
throw new \RestfulBadRequestException(format_string('Cannot load an entity using the field "@name"', array( | ||
'@name' => $public_property_name, | ||
))); | ||
} | ||
$query = $this->getEntityFieldQuery(); | ||
$query->range(0, 1); | ||
// Find out if the provided ID is a Drupal field or an entity property. | ||
if (static::propertyIsField($public_field_info['property'])) { | ||
$query->fieldCondition($public_field_info['property'], $public_field_info['column'], $id); | ||
} | ||
else { | ||
$query->propertyCondition($public_field_info['property'], $id); | ||
} | ||
|
||
// Execute the query and gather the results. | ||
$result = $query->execute(); | ||
if (empty($result[$this->getEntityType()])) { | ||
throw new RestfulUnprocessableEntityException(format_string('The entity ID @id by @name for @resource cannot be loaded.', array( | ||
'@id' => $id, | ||
'@resource' => $this->getPluginKey('label'), | ||
'@name' => $public_property_name, | ||
))); | ||
} | ||
|
||
// There is nothing that guarantees that there is only one result, since | ||
// this is user input data. Return the first ID. | ||
$entity_id = key($result[$this->getEntityType()]); | ||
|
||
// REST requires a canonical URL for every resource. | ||
$this->addHttpHeaders('Link', $this->versionedUrl($entity_id, array(), FALSE) . '; rel="canonical"'); | ||
|
||
return $entity_id; | ||
} | ||
|
||
/** | ||
* Initialize an EntityFieldQuery (or extending class). | ||
* | ||
* @return \EntityFieldQuery | ||
* The initialized query with the basics filled in. | ||
*/ | ||
protected function getEntityFieldQuery() { | ||
$query = new \EntityFieldQuery(); | ||
$query->entityCondition('entity_type', $this->getEntityType()); | ||
if ($bundle = $this->getBundle()) { | ||
$query->entityCondition('bundle', $bundle); | ||
} | ||
return $query; | ||
} | ||
|
||
/** | ||
* Checks if a given string represents a Field API field. | ||
* | ||
* @param string $name | ||
* The name of the field/property. | ||
* | ||
* @return bool | ||
* TRUE if it's a field. FALSE otherwise. | ||
*/ | ||
public static function propertyIsField($name) { | ||
$field_info = field_info_field($name); | ||
return !empty($field_info); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -158,6 +158,21 @@ class RestfulViewEntityTestCase extends RestfulCurlBaseTestCase { | |
$this->assertEqual($result, $expected_result, 'Entity view has correct result for "main" resource v1.1 with empty entity reference.'); | ||
|
||
|
||
// Load an entity by an alternate field. | ||
$entity4 = entity_create('entity_test', array('name' => 'main', 'uid' => $user1->uid)); | ||
$wrapper = entity_metadata_wrapper('entity_test', $entity4); | ||
$text = $this->randomName(); | ||
$wrapper->text_single->set($text); | ||
$wrapper->save(); | ||
|
||
$request = array('loadByFieldName' => 'text_single'); | ||
$result = $handler->get($text, $request); | ||
$this->assertNotNull($result[0]); | ||
|
||
// Make sure canonical header is added. | ||
$headers = $handler->getHttpHeaders(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good stuff! :) |
||
$this->assertEqual($headers['Link'], $handler->versionedUrl($wrapper->getIdentifier(), array(), FALSE) . '; rel="canonical"'); | ||
|
||
// v1.2 - "callback" and "process callback". | ||
$handler = restful_get_restful_handler('main', 1, 2); | ||
$base_expected_result['self'] = $handler->versionedUrl($id); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when don't we want to add the version string? I think we always want to refer to it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When having the URL in the
Link
header along with theX-API-Version: v1.5
header. It's sloppy and error prone (what if we haveX-API-Version: v1.5
andhttps://www.example.org/api/v1.4/…
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, makes sense