Commit 5a8e7bd7 authored by catch's avatar catch

Issue #1361232 by sun, Berdir, aspilicious, fago, klausi, amateescu: Make the...

Issue #1361232 by sun, Berdir, aspilicious, fago, klausi, amateescu: Make the taxonomy entities classed objects.
parent 99269244
...@@ -403,9 +403,18 @@ function drupal_write_record($table, &$record, $primary_keys = array()) { ...@@ -403,9 +403,18 @@ function drupal_write_record($table, &$record, $primary_keys = array()) {
continue; continue;
} }
// Skip fields that are not provided, default values are already known
// by the database. property_exists() allows to explicitly set a value to
// NULL.
if (!property_exists($object, $field)) { if (!property_exists($object, $field)) {
// Skip fields that are not provided, default values are already known $default_fields[] = $field;
// by the database. continue;
}
// However, if $object is an entity class instance, then class properties
// always exist, as they cannot be unset. Therefore, if $field is a serial
// type and the value is NULL, skip it.
// @see http://php.net/manual/en/function.property-exists.php
if ($info['type'] == 'serial' && !isset($object->$field)) {
$default_fields[] = $field; $default_fields[] = $field;
continue; continue;
} }
......
...@@ -196,7 +196,7 @@ protected function preSave(EntityInterface $comment) { ...@@ -196,7 +196,7 @@ protected function preSave(EntityInterface $comment) {
/** /**
* Overrides EntityDatabaseStorageController::postSave(). * Overrides EntityDatabaseStorageController::postSave().
*/ */
protected function postSave(EntityInterface $comment) { protected function postSave(EntityInterface $comment, $update) {
// Update the {node_comment_statistics} table prior to executing the hook. // Update the {node_comment_statistics} table prior to executing the hook.
$this->updateNodeStatistics($comment->nid); $this->updateNodeStatistics($comment->nid);
if ($comment->status == COMMENT_PUBLISHED) { if ($comment->status == COMMENT_PUBLISHED) {
......
...@@ -33,11 +33,30 @@ public function id(); ...@@ -33,11 +33,30 @@ public function id();
/** /**
* Returns whether the entity is new. * Returns whether the entity is new.
* *
* Usually an entity is new if no ID exists for it yet. However, entities may
* be enforced to be new with existing IDs too.
*
* @return * @return
* TRUE if the entity is new, or FALSE if the entity has already been saved. * TRUE if the entity is new, or FALSE if the entity has already been saved.
*
* @see EntityInterface::enforceIsNew()
*/ */
public function isNew(); public function isNew();
/**
* Enforces an entity to be new.
*
* Allows migrations to create entities with pre-defined IDs by forcing the
* entity to be new before saving.
*
* @param bool $value
* (optional) Whether the entity should be forced to be new. Defaults to
* TRUE.
*
* @see EntityInterface::isNew()
*/
public function enforceIsNew($value = TRUE);
/** /**
* Returns the type of the entity. * Returns the type of the entity.
* *
...@@ -147,6 +166,13 @@ class Entity implements EntityInterface { ...@@ -147,6 +166,13 @@ class Entity implements EntityInterface {
*/ */
protected $bundleKey; protected $bundleKey;
/**
* Boolean indicating whether the entity should be forced to be new.
*
* @var bool
*/
protected $enforceIsNew;
/** /**
* Constructs a new entity object. * Constructs a new entity object.
*/ */
...@@ -179,9 +205,14 @@ public function id() { ...@@ -179,9 +205,14 @@ public function id() {
* Implements EntityInterface::isNew(). * Implements EntityInterface::isNew().
*/ */
public function isNew() { public function isNew() {
// We support creating entities with pre-defined IDs to ease migrations. return !empty($this->enforceIsNew) || empty($this->{$this->idKey});
// For that the "is_new" property may be set to TRUE. }
return !empty($this->is_new) || empty($this->{$this->idKey});
/**
* Implements EntityInterface::enforceIsNew().
*/
public function enforceIsNew($value = TRUE) {
$this->enforceIsNew = $value;
} }
/** /**
......
...@@ -403,6 +403,18 @@ protected function cacheSet($entities) { ...@@ -403,6 +403,18 @@ protected function cacheSet($entities) {
*/ */
interface EntityStorageControllerInterface extends DrupalEntityControllerInterface { interface EntityStorageControllerInterface extends DrupalEntityControllerInterface {
/**
* Constructs a new entity object, without permanently saving it.
*
* @param $values
* An array of values to set, keyed by property name. If the entity type has
* bundles the bundle key has to be specified.
*
* @return EntityInterface
* A new entity object.
*/
public function create(array $values);
/** /**
* Deletes permanently saved entities. * Deletes permanently saved entities.
* *
...@@ -441,6 +453,14 @@ class EntityStorageException extends Exception { } ...@@ -441,6 +453,14 @@ class EntityStorageException extends Exception { }
*/ */
class EntityDatabaseStorageController extends DrupalDefaultEntityController implements EntityStorageControllerInterface { class EntityDatabaseStorageController extends DrupalDefaultEntityController implements EntityStorageControllerInterface {
/**
* Implements EntityStorageControllerInterface::create().
*/
public function create(array $values) {
$class = isset($this->entityInfo['entity class']) ? $this->entityInfo['entity class'] : 'Entity';
return new $class($values, $this->entityType);
}
/** /**
* Implements EntityStorageControllerInterface::delete(). * Implements EntityStorageControllerInterface::delete().
*/ */
...@@ -496,18 +516,21 @@ public function save(EntityInterface $entity) { ...@@ -496,18 +516,21 @@ public function save(EntityInterface $entity) {
if (!$entity->isNew()) { if (!$entity->isNew()) {
$return = drupal_write_record($this->entityInfo['base table'], $entity, $this->idKey); $return = drupal_write_record($this->entityInfo['base table'], $entity, $this->idKey);
$this->resetCache(array($entity->{$this->idKey})); $this->resetCache(array($entity->{$this->idKey}));
$this->postSave($entity); $this->postSave($entity, TRUE);
$this->invokeHook('update', $entity); $this->invokeHook('update', $entity);
} }
else { else {
$return = drupal_write_record($this->entityInfo['base table'], $entity); $return = drupal_write_record($this->entityInfo['base table'], $entity);
$this->postSave($entity); // Reset general caches, but keep caches specific to certain entities.
$this->resetCache(array());
$entity->enforceIsNew(FALSE);
$this->postSave($entity, FALSE);
$this->invokeHook('insert', $entity); $this->invokeHook('insert', $entity);
} }
// Ignore slave server temporarily. // Ignore slave server temporarily.
db_ignore_slave(); db_ignore_slave();
unset($entity->is_new);
unset($entity->original); unset($entity->original);
return $return; return $return;
...@@ -531,8 +554,12 @@ protected function preSave(EntityInterface $entity) { } ...@@ -531,8 +554,12 @@ protected function preSave(EntityInterface $entity) { }
* *
* Used after the entity is saved, but before invoking the insert or update * Used after the entity is saved, but before invoking the insert or update
* hook. * hook.
*
* @param $update
* (bool) TRUE if the entity has been updated, or FALSE if it has been
* inserted.
*/ */
protected function postSave(EntityInterface $entity) { } protected function postSave(EntityInterface $entity, $update) { }
/** /**
* Acts on entities before they are deleted. * Acts on entities before they are deleted.
......
...@@ -270,7 +270,7 @@ function entity_delete_multiple($entity_type, $ids) { ...@@ -270,7 +270,7 @@ function entity_delete_multiple($entity_type, $ids) {
} }
/** /**
* Constructs a new entity object, without saving it to the database. * Constructs a new entity object, without permanently saving it.
* *
* @param $entity_type * @param $entity_type
* The type of the entity. * The type of the entity.
...@@ -282,13 +282,13 @@ function entity_delete_multiple($entity_type, $ids) { ...@@ -282,13 +282,13 @@ function entity_delete_multiple($entity_type, $ids) {
* A new entity object. * A new entity object.
*/ */
function entity_create($entity_type, array $values) { function entity_create($entity_type, array $values) {
$info = entity_get_info($entity_type) + array('entity class' => 'Entity'); return entity_get_controller($entity_type)->create($values);
$class = $info['entity class'];
return new $class($values, $entity_type);
} }
/** /**
* Gets the entity controller class for an entity type. * Gets the entity controller class for an entity type.
*
* @return EntityStorageControllerInterface
*/ */
function entity_get_controller($entity_type) { function entity_get_controller($entity_type) {
$controllers = &drupal_static(__FUNCTION__, array()); $controllers = &drupal_static(__FUNCTION__, array());
......
...@@ -245,22 +245,22 @@ class EntityCrudHookTestCase extends DrupalWebTestCase { ...@@ -245,22 +245,22 @@ class EntityCrudHookTestCase extends DrupalWebTestCase {
* Tests hook invocations for CRUD operations on taxonomy terms. * Tests hook invocations for CRUD operations on taxonomy terms.
*/ */
public function testTaxonomyTermHooks() { public function testTaxonomyTermHooks() {
$vocabulary = (object) array( $vocabulary = entity_create('taxonomy_vocabulary', array(
'name' => 'Test vocabulary', 'name' => 'Test vocabulary',
'machine_name' => 'test', 'machine_name' => 'test',
'langcode' => LANGUAGE_NOT_SPECIFIED, 'langcode' => LANGUAGE_NOT_SPECIFIED,
'description' => NULL, 'description' => NULL,
'module' => 'entity_crud_hook_test', 'module' => 'entity_crud_hook_test',
); ));
taxonomy_vocabulary_save($vocabulary); taxonomy_vocabulary_save($vocabulary);
$term = (object) array( $term = entity_create('taxonomy_term', array(
'vid' => $vocabulary->vid, 'vid' => $vocabulary->vid,
'name' => 'Test term', 'name' => 'Test term',
'langcode' => LANGUAGE_NOT_SPECIFIED, 'langcode' => LANGUAGE_NOT_SPECIFIED,
'description' => NULL, 'description' => NULL,
'format' => 1, 'format' => 1,
); ));
$_SESSION['entity_crud_hook_test'] = array(); $_SESSION['entity_crud_hook_test'] = array();
taxonomy_term_save($term); taxonomy_term_save($term);
...@@ -305,13 +305,13 @@ class EntityCrudHookTestCase extends DrupalWebTestCase { ...@@ -305,13 +305,13 @@ class EntityCrudHookTestCase extends DrupalWebTestCase {
* Tests hook invocations for CRUD operations on taxonomy vocabularies. * Tests hook invocations for CRUD operations on taxonomy vocabularies.
*/ */
public function testTaxonomyVocabularyHooks() { public function testTaxonomyVocabularyHooks() {
$vocabulary = (object) array( $vocabulary = entity_create('taxonomy_vocabulary', array(
'name' => 'Test vocabulary', 'name' => 'Test vocabulary',
'machine_name' => 'test', 'machine_name' => 'test',
'langcode' => LANGUAGE_NOT_SPECIFIED, 'langcode' => LANGUAGE_NOT_SPECIFIED,
'description' => NULL, 'description' => NULL,
'module' => 'entity_crud_hook_test', 'module' => 'entity_crud_hook_test',
); ));
$_SESSION['entity_crud_hook_test'] = array(); $_SESSION['entity_crud_hook_test'] = array();
taxonomy_vocabulary_save($vocabulary); taxonomy_vocabulary_save($vocabulary);
......
...@@ -158,11 +158,11 @@ class FieldUIManageFieldsTestCase extends FieldUITestCase { ...@@ -158,11 +158,11 @@ class FieldUIManageFieldsTestCase extends FieldUITestCase {
$this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article')); $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
// Create a vocabulary named "Tags". // Create a vocabulary named "Tags".
$vocabulary = (object) array( $vocabulary = entity_create('taxonomy_vocabulary', array(
'name' => 'Tags', 'name' => 'Tags',
'machine_name' => 'tags', 'machine_name' => 'tags',
'langcode' => LANGUAGE_NOT_SPECIFIED, 'langcode' => LANGUAGE_NOT_SPECIFIED,
); ));
taxonomy_vocabulary_save($vocabulary); taxonomy_vocabulary_save($vocabulary);
$field = array( $field = array(
......
...@@ -79,7 +79,9 @@ function forum_form_submit($form, &$form_state) { ...@@ -79,7 +79,9 @@ function forum_form_submit($form, &$form_state) {
$type = t('forum'); $type = t('forum');
} }
$term = (object) $form_state['values']; // @todo Set explicit entity properties instead of arbitrary form values.
form_state_values_clean($form_state);
$term = entity_create('taxonomy_term', $form_state['values']);
$status = taxonomy_term_save($term); $status = taxonomy_term_save($term);
switch ($status) { switch ($status) {
case SAVED_NEW: case SAVED_NEW:
......
...@@ -30,7 +30,7 @@ function forum_enable() { ...@@ -30,7 +30,7 @@ function forum_enable() {
// Create the forum vocabulary if it does not exist. // Create the forum vocabulary if it does not exist.
$vocabulary = taxonomy_vocabulary_load(variable_get('forum_nav_vocabulary', 0)); $vocabulary = taxonomy_vocabulary_load(variable_get('forum_nav_vocabulary', 0));
if (!$vocabulary) { if (!$vocabulary) {
$edit = array( $vocabulary = entity_create('taxonomy_vocabulary', array(
'name' => t('Forums'), 'name' => t('Forums'),
'machine_name' => 'forums', 'machine_name' => 'forums',
'langcode' => language_default()->langcode, 'langcode' => language_default()->langcode,
...@@ -38,8 +38,7 @@ function forum_enable() { ...@@ -38,8 +38,7 @@ function forum_enable() {
'hierarchy' => 1, 'hierarchy' => 1,
'module' => 'forum', 'module' => 'forum',
'weight' => -10, 'weight' => -10,
); ));
$vocabulary = (object) $edit;
taxonomy_vocabulary_save($vocabulary); taxonomy_vocabulary_save($vocabulary);
variable_set('forum_nav_vocabulary', $vocabulary->vid); variable_set('forum_nav_vocabulary', $vocabulary->vid);
} }
...@@ -61,14 +60,13 @@ function forum_enable() { ...@@ -61,14 +60,13 @@ function forum_enable() {
field_create_field($field); field_create_field($field);
// Create a default forum so forum posts can be created. // Create a default forum so forum posts can be created.
$edit = array( $term = entity_create('taxonomy_term', array(
'name' => t('General discussion'), 'name' => t('General discussion'),
'langcode' => language_default()->langcode, 'langcode' => language_default()->langcode,
'description' => '', 'description' => '',
'parent' => array(0), 'parent' => array(0),
'vid' => $vocabulary->vid, 'vid' => $vocabulary->vid,
); ));
$term = (object) $edit;
taxonomy_term_save($term); taxonomy_term_save($term);
// Create the instance on the bundle. // Create the instance on the bundle.
......
...@@ -468,7 +468,7 @@ function forum_permission() { ...@@ -468,7 +468,7 @@ function forum_permission() {
/** /**
* Implements hook_taxonomy_term_delete(). * Implements hook_taxonomy_term_delete().
*/ */
function forum_taxonomy_term_delete(stdClass $term) { function forum_taxonomy_term_delete(TaxonomyTerm $term) {
// For containers, remove the tid from the forum_containers variable. // For containers, remove the tid from the forum_containers variable.
$containers = variable_get('forum_containers', array()); $containers = variable_get('forum_containers', array());
$key = array_search($term->tid, $containers); $key = array_search($term->tid, $containers);
......
...@@ -266,7 +266,7 @@ function path_form_taxonomy_form_term_alter(&$form, $form_state) { ...@@ -266,7 +266,7 @@ function path_form_taxonomy_form_term_alter(&$form, $form_state) {
/** /**
* Implements hook_taxonomy_term_insert(). * Implements hook_taxonomy_term_insert().
*/ */
function path_taxonomy_term_insert($term) { function path_taxonomy_term_insert(TaxonomyTerm $term) {
if (isset($term->path)) { if (isset($term->path)) {
$path = $term->path; $path = $term->path;
$path['alias'] = trim($path['alias']); $path['alias'] = trim($path['alias']);
...@@ -283,7 +283,7 @@ function path_taxonomy_term_insert($term) { ...@@ -283,7 +283,7 @@ function path_taxonomy_term_insert($term) {
/** /**
* Implements hook_taxonomy_term_update(). * Implements hook_taxonomy_term_update().
*/ */
function path_taxonomy_term_update($term) { function path_taxonomy_term_update(TaxonomyTerm $term) {
if (isset($term->path)) { if (isset($term->path)) {
$path = $term->path; $path = $term->path;
$path['alias'] = trim($path['alias']); $path['alias'] = trim($path['alias']);
...@@ -304,7 +304,7 @@ function path_taxonomy_term_update($term) { ...@@ -304,7 +304,7 @@ function path_taxonomy_term_update($term) {
/** /**
* Implements hook_taxonomy_term_delete(). * Implements hook_taxonomy_term_delete().
*/ */
function path_taxonomy_term_delete($term) { function path_taxonomy_term_delete(TaxonomyTerm $term) {
// Delete all aliases associated with this term. // Delete all aliases associated with this term.
path_delete(array('source' => 'taxonomy/term/' . $term->tid)); path_delete(array('source' => 'taxonomy/term/' . $term->tid));
} }
...@@ -230,11 +230,11 @@ class PathTaxonomyTermTestCase extends PathTestCase { ...@@ -230,11 +230,11 @@ class PathTaxonomyTermTestCase extends PathTestCase {
parent::setUp(array('taxonomy')); parent::setUp(array('taxonomy'));
// Create a Tags vocabulary for the Article node type. // Create a Tags vocabulary for the Article node type.
$vocabulary = (object) array( $vocabulary = entity_create('taxonomy_vocabulary', array(
'name' => t('Tags'), 'name' => t('Tags'),
'machine_name' => 'tags', 'machine_name' => 'tags',
); ));
taxonomy_vocabulary_save($vocabulary); $vocabulary->save();
// Create and login user. // Create and login user.
$web_user = $this->drupalCreateUser(array('administer url aliases', 'administer taxonomy', 'access administration pages')); $web_user = $this->drupalCreateUser(array('administer url aliases', 'administer taxonomy', 'access administration pages'));
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
/** /**
* Implements hook_taxonomy_term_load(). * Implements hook_taxonomy_term_load().
*/ */
function taxonomy_test_taxonomy_term_load($terms) { function taxonomy_test_taxonomy_term_load(array $terms) {
foreach ($terms as $term) { foreach ($terms as $term) {
$antonym = taxonomy_test_get_antonym($term->tid); $antonym = taxonomy_test_get_antonym($term->tid);
if ($antonym) { if ($antonym) {
...@@ -20,7 +20,7 @@ function taxonomy_test_taxonomy_term_load($terms) { ...@@ -20,7 +20,7 @@ function taxonomy_test_taxonomy_term_load($terms) {
/** /**
* Implements hook_taxonomy_term_insert(). * Implements hook_taxonomy_term_insert().
*/ */
function taxonomy_test_taxonomy_term_insert($term) { function taxonomy_test_taxonomy_term_insert(TaxonomyTerm $term) {
if (!empty($term->antonym)) { if (!empty($term->antonym)) {
db_insert('taxonomy_term_antonym') db_insert('taxonomy_term_antonym')
->fields(array( ->fields(array(
...@@ -34,7 +34,7 @@ function taxonomy_test_taxonomy_term_insert($term) { ...@@ -34,7 +34,7 @@ function taxonomy_test_taxonomy_term_insert($term) {
/** /**
* Implements hook_taxonomy_term_update(). * Implements hook_taxonomy_term_update().
*/ */
function taxonomy_test_taxonomy_term_update($term) { function taxonomy_test_taxonomy_term_update(TaxonomyTerm $term) {
if (!empty($term->antonym)) { if (!empty($term->antonym)) {
db_merge('taxonomy_term_antonym') db_merge('taxonomy_term_antonym')
->key(array('tid' => $term->tid)) ->key(array('tid' => $term->tid))
...@@ -48,7 +48,7 @@ function taxonomy_test_taxonomy_term_update($term) { ...@@ -48,7 +48,7 @@ function taxonomy_test_taxonomy_term_update($term) {
/** /**
* Implements hook_taxonomy_term_delete(). * Implements hook_taxonomy_term_delete().
*/ */
function taxonomy_test_taxonomy_term_delete($term) { function taxonomy_test_taxonomy_term_delete(TaxonomyTerm $term) {
db_delete('taxonomy_term_antonym') db_delete('taxonomy_term_antonym')
->condition('tid', $term->tid) ->condition('tid', $term->tid)
->execute(); ->execute();
......
...@@ -298,6 +298,10 @@ abstract class UpgradePathTestCase extends DrupalWebTestCase { ...@@ -298,6 +298,10 @@ abstract class UpgradePathTestCase extends DrupalWebTestCase {
drupal_load('module', $module); drupal_load('module', $module);
} }
// Re-register autoload functions.
spl_autoload_register('drupal_autoload_class');
spl_autoload_register('drupal_autoload_interface');
// Reload hook implementations // Reload hook implementations
module_implements_reset(); module_implements_reset();
......
...@@ -101,31 +101,27 @@ function theme_taxonomy_overview_vocabularies($variables) { ...@@ -101,31 +101,27 @@ function theme_taxonomy_overview_vocabularies($variables) {
/** /**
* Form builder for the vocabulary editing form. * Form builder for the vocabulary editing form.
* *
* @param TaxonomyVocabulary|null $vocabulary
* (optional) The taxonomy vocabulary entity to edit. If NULL or omitted, the
* form creates a new vocabulary.
*
* @ingroup forms * @ingroup forms
* @see taxonomy_form_vocabulary_submit() * @see taxonomy_form_vocabulary_submit()
* @see taxonomy_form_vocabulary_validate() * @see taxonomy_form_vocabulary_validate()
*/ */
function taxonomy_form_vocabulary($form, &$form_state, $edit = array()) { function taxonomy_form_vocabulary($form, &$form_state, TaxonomyVocabulary $vocabulary = NULL) {
// During initial form build, add the entity to the form state for use // During initial form build, add the entity to the form state for use
// during form building and processing. During a rebuild, use what is in the // during form building and processing. During a rebuild, use what is in the
// form state. // form state.
if (!isset($form_state['vocabulary'])) { if (!isset($form_state['vocabulary'])) {
$vocabulary = is_object($edit) ? $edit : (object) $edit; // Create a new TaxonomyVocabulary entity for the add form.
$defaults = array( if (!isset($vocabulary)) {
'name' => '', $vocabulary = entity_create('taxonomy_vocabulary', array(
'machine_name' => '', // Default the new vocabulary to the site's default language. This is
'description' => '', // the most likely default value until we have better flexible settings.
'hierarchy' => TAXONOMY_HIERARCHY_DISABLED, // @todo See http://drupal.org/node/258785 and followups.
'weight' => 0, 'langcode' => language_default()->langcode,
// Default the new vocabulary to the site's default language. This is ));
// the most likely default value until we have better flexible settings.
// @todo See http://drupal.org/node/258785 and followups.
'langcode' => language_default()->langcode,
);
foreach ($defaults as $key => $value) {
if (!isset($vocabulary->$key)) {
$vocabulary->$key = $value;
}
} }
$form_state['vocabulary'] = $vocabulary; $form_state['vocabulary'] = $vocabulary;
} }
...@@ -156,10 +152,6 @@ function taxonomy_form_vocabulary($form, &$form_state, $edit = array()) { ...@@ -156,10 +152,6 @@ function taxonomy_form_vocabulary($form, &$form_state, $edit = array()) {
'exists' => 'taxonomy_vocabulary_machine_name_load', 'exists' => 'taxonomy_vocabulary_machine_name_load',
), ),
); );
$form['old_machine_name'] = array(
'#type' => 'value',
'#value' => $vocabulary->machine_name,
);
$form['description'] = array( $form['description'] = array(
'#type' => 'textfield', '#type' => 'textfield',