Commit 3f4d8bc4 authored by Dries's avatar Dries

Issue #1826688 by klausi | mitchell: Added REST module: PATCH/update.

parent 63fa77ec
......@@ -368,7 +368,7 @@ public function __isset($name) {
return isset($this->values[$name]);
}
elseif ($this->getPropertyDefinition($name)) {
return (bool) count($this->get($name));
return $this->get($name)->valueIsSet();
}
}
......@@ -380,7 +380,7 @@ public function __unset($name) {
unset($this->values[$name]);
}
elseif ($this->getPropertyDefinition($name)) {
$this->get($name)->setValue(array());
$this->get($name)->unsetValue();
}
}
......
......@@ -50,6 +50,13 @@ class Field extends TypedData implements IteratorAggregate, FieldInterface {
*/
protected $list = array();
/**
* Flag to indicate if this field has been set.
*
* @var bool
*/
protected $isset = FALSE;
/**
* Implements TypedDataInterface::getValue().
*/
......@@ -68,6 +75,7 @@ public function getValue() {
* An array of values of the field items.
*/
public function setValue($values) {
$this->isset = TRUE;
if (isset($values) && $values !== array()) {
// Support passing in only the value of the first item.
if (!is_array($values) || !is_numeric(current(array_keys($values)))) {
......@@ -100,6 +108,14 @@ public function setValue($values) {
}
}
/**
* Mark this field as not set.
*/
public function unsetValue() {
$this->list = array();
$this->isset = FALSE;
}
/**
* Returns a string representation of the field.
*
......@@ -256,13 +272,14 @@ public function get($property_name) {
*/
public function __set($property_name, $value) {
$this->offsetGet(0)->__set($property_name, $value);
$this->isset = TRUE;
}
/**
* Delegate.
*/
public function __isset($property_name) {
return $this->offsetGet(0)->__isset($property_name);
return $this->isset && $this->offsetGet(0)->__isset($property_name);
}
/**
......@@ -284,6 +301,15 @@ public function isEmpty() {
return TRUE;
}
/**
* Determines if this field has been set.
*
* @return bool
*/
public function valueIsSet() {
return $this->isset;
}
/**
* Implements a deep clone.
*/
......
......@@ -79,11 +79,17 @@ public function denormalize($data, $class, $format = null) {
if ($fieldName[0] === '@') {
continue;
}
// If the incoming value is an empty array we set the property to mark it
// for deletion.
if (empty($incomingFieldValues) && is_array($incomingFieldValues)) {
$entity->{$fieldName} = array();
}
// Figure out the designated class for this field type, which is used by
// the Serializer to determine which Denormalizer to use.
// @todo Is there a better way to get the field type's associated class?
$fieldItemClass = get_class($entity->get($fieldName)->offsetGet(0));
// Iterate through the language keyed values and add them to the entity.
// The vnd.drupal.ld+json mime type will always use language keys, per
// http://drupal.org/node/1838700.
......
......@@ -85,6 +85,49 @@ public function post($id, EntityInterface $entity) {
}
}
/**
* Responds to entity PATCH requests.
*
* @param mixed $id
* The entity ID.
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity.
*
* @return \Drupal\rest\ResourceResponse
* The HTTP response object.
*
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
*/
public function patch($id, EntityInterface $entity) {
if (empty($id)) {
throw new NotFoundHttpException();
}
$definition = $this->getDefinition();
if ($entity->entityType() != $definition['entity_type']) {
throw new BadRequestHttpException('Invalid entity type');
}
$original_entity = entity_load($definition['entity_type'], $id);
// We don't support creating entities with PATCH, so we throw an error if
// there is no existing entity.
if ($original_entity == FALSE) {
throw new NotFoundHttpException();
}
// Overwrite the received properties.
foreach ($entity->getProperties() as $name => $property) {
if (isset($entity->{$name})) {
$original_entity->{$name} = $property;
}
}
try {
$original_entity->save();
// Update responses have an empty body.
return new ResourceResponse(NULL, 204);
}
catch (EntityStorageException $e) {
throw new HttpException(500, 'Internal Server Error', $e);
}
}
/**
* Responds to entity DELETE requests.
*
......
......@@ -48,7 +48,7 @@ public function testCreate() {
$entity = entity_create($entity_type, $entity_values);
$serialized = $serializer->serialize($entity, 'drupal_jsonld');
// Create the entity over the web API.
$response = $this->httpRequest('entity/' . $entity_type, 'POST', $serialized, 'application/vnd.drupal.ld+json');
$this->httpRequest('entity/' . $entity_type, 'POST', $serialized, 'application/vnd.drupal.ld+json');
$this->assertResponse('201', 'HTTP response code is correct.');
// Get the new entity ID from the location header and try to read it from
......@@ -63,13 +63,14 @@ public function testCreate() {
// entity references is implemented.
unset($entity_values['user_id']);
foreach ($entity_values as $property => $value) {
$actual_value = $loaded_entity->get($property);
$this->assertEqual($value, $actual_value->value, 'Created property ' . $property . ' expected: ' . $value . ', actual: ' . $actual_value->value);
$actual_value = $loaded_entity->get($property)->value;
$send_value = $entity->get($property)->value;
$this->assertEqual($send_value, $actual_value, 'Created property ' . $property . ' expected: ' . $send_value . ', actual: ' . $actual_value);
}
// Try to create an entity without proper permissions.
$this->drupalLogout();
$response = $this->httpRequest('entity/' . $entity_type, 'POST', $serialized, 'application/vnd.drupal.ld+json');
$this->httpRequest('entity/' . $entity_type, 'POST', $serialized, 'application/vnd.drupal.ld+json');
$this->assertResponse(403);
// Try to create a resource which is not web API enabled.
......
......@@ -68,6 +68,17 @@ protected function httpRequest($url, $method, $body = NULL, $format = 'applicati
);
break;
case 'PATCH':
$curl_options = array(
CURLOPT_HTTPGET => FALSE,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_POSTFIELDS => $body,
CURLOPT_URL => url($url, array('absolute' => TRUE)),
CURLOPT_NOBODY => FALSE,
CURLOPT_HTTPHEADER => array('Content-Type: ' . $format),
);
break;
case 'DELETE':
$curl_options = array(
CURLOPT_HTTPGET => FALSE,
......@@ -127,7 +138,11 @@ protected function entityCreate($entity_type) {
protected function entityValues($entity_type) {
switch ($entity_type) {
case 'entity_test':
return array('name' => $this->randomName(), 'user_id' => 1);
return array(
'name' => $this->randomName(),
'user_id' => 1,
'field_test_text' => array(0 => array('value' => $this->randomString())),
);
case 'node':
return array('title' => $this->randomString());
case 'user':
......
<?php
/**
* @file
* Contains Drupal\rest\test\UpdateTest.
*/
namespace Drupal\rest\Tests;
use Drupal\rest\Tests\RESTTestBase;
/**
* Tests resource updates on test entities.
*/
class UpdateTest extends RESTTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('rest', 'entity_test');
public static function getInfo() {
return array(
'name' => 'Update resource',
'description' => 'Tests the update of resources.',
'group' => 'REST',
);
}
/**
* Tests several valid and invalid partial update requests on test entities.
*/
public function testPatchUpdate() {
$serializer = drupal_container()->get('serializer');
// @todo once EntityNG is implemented for other entity types test all other
// entity types here as well.
$entity_type = 'entity_test';
$this->enableService('entity:' . $entity_type);
// Create a user account that has the required permissions to create
// resources via the web API.
$account = $this->drupalCreateUser(array('restful patch entity:' . $entity_type));
$this->drupalLogin($account);
// Create an entity and save it to the database.
$entity = $this->entityCreate($entity_type);
$entity->save();
// Create a second stub entity for overwriting a field.
$patch_values['field_test_text'] = array(0 => array('value' => $this->randomString()));
$patch_entity = entity_create($entity_type, $patch_values);
// We don't want to overwrite the UUID.
unset($patch_entity->uuid);
$serialized = $serializer->serialize($patch_entity, 'drupal_jsonld');
// Update the entity over the web API.
$this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'PATCH', $serialized, 'application/vnd.drupal.ld+json');
$this->assertResponse(204);
// Re-load updated entity from the database.
$entity = entity_load($entity_type, $entity->id(), TRUE);
$this->assertEqual($entity->field_test_text->value, $patch_entity->field_test_text->value, 'Field was successfully updated.');
// Try to empty a field.
$normalized = $serializer->normalize($patch_entity, 'drupal_jsonld');
$normalized['field_test_text'] = array();
$serialized = $serializer->encode($normalized, 'jsonld');
// Update the entity over the web API.
$this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'PATCH', $serialized, 'application/vnd.drupal.ld+json');
$this->assertResponse(204);
// Re-load updated entity from the database.
$entity = entity_load($entity_type, $entity->id(), TRUE);
$this->assertNull($entity->field_test_text->value, 'Test field has been cleared.');
// Try to update a non-existing entity with ID 9999.
$this->httpRequest('entity/' . $entity_type . '/9999', 'PATCH', $serialized, 'application/vnd.drupal.ld+json');
$this->assertResponse(404);
$loaded_entity = entity_load($entity_type, 9999, TRUE);
$this->assertFalse($loaded_entity, 'Entity 9999 was not created.');
// Try to update an entity without proper permissions.
$this->drupalLogout();
$this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'PATCH', $serialized, 'application/vnd.drupal.ld+json');
$this->assertResponse(403);
// Try to update a resource which is not web API enabled.
$this->enableService(FALSE);
// Reset cURL here because it is confused from our previously used cURL
// options.
unset($this->curlHandle);
$this->drupalLogin($account);
$this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'PATCH', $serialized, 'application/vnd.drupal.ld+json');
$this->assertResponse(404);
}
}
......@@ -128,6 +128,12 @@ public function testReadWrite() {
$this->assertFalse(isset($entity->name[0]->value), 'Name is not set.');
$this->assertFalse(isset($entity->name->value), 'Name is not set.');
$entity->name = array();
$this->assertTrue(isset($entity->name), 'Name field is set.');
$this->assertFalse(isset($entity->name[0]), 'Name field item is not set.');
$this->assertFalse(isset($entity->name[0]->value), 'First name item value is not set.');
$this->assertFalse(isset($entity->name->value), 'Name value is not set.');
$entity->name->value = 'a value';
$this->assertTrue(isset($entity->name->value), 'Name is set.');
unset($entity->name);
......
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