diff --git a/core/includes/entity.inc b/core/includes/entity.inc
index b651e32..2b504aa 100644
--- a/core/includes/entity.inc
+++ b/core/includes/entity.inc
@@ -47,6 +47,7 @@ function entity_get_info($entity_type = NULL) {
'fieldable' => FALSE,
'entity class' => 'Drupal\Core\Entity\Entity',
'controller class' => 'Drupal\Core\Entity\DatabaseStorageController',
+ 'list controller class' => 'Drupal\Core\Entity\EntityListController',
'form controller class' => array(
'default' => 'Drupal\Core\Entity\EntityFormController',
),
@@ -530,3 +531,21 @@ function entity_form_submit_build_entity($entity_type, $entity, $form, &$form_st
field_attach_submit($entity_type, $entity, $form, $form_state);
}
}
+
+/**
+ * Returns an entity list controller for a given entity type.
+ *
+ * @param string $entity_type
+ * The type of the entity.
+ *
+ * @return Drupal\Core\Entity\EntityListControllerInterface
+ * An entity list controller.
+ *
+ * @see hook_entity_info()
+ */
+function entity_list_controller($entity_type) {
+ $storage = entity_get_controller($entity_type);
+ $entity_info = entity_get_info($entity_type);
+ $class = $entity_info['list controller class'];
+ return new $class($entity_type, $storage);
+}
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityListController.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityListController.php
new file mode 100644
index 0000000..3bd4b82
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityListController.php
@@ -0,0 +1,27 @@
+entityType = $entity_type;
+ $this->storage = $storage;
+ $this->entityInfo = entity_get_info($this->entityType);
+ }
+
+ /**
+ * Implements Drupal\Core\Entity\EntityListControllerInterface::getStorageController().
+ */
+ public function getStorageController() {
+ return $this->storage;
+ }
+
+ /**
+ * Implements Drupal\Core\Entity\EntityListControllerInterface::load().
+ */
+ public function load() {
+ return $this->storage->load();
+ }
+
+ /**
+ * Implements Drupal\Core\Entity\EntityListControllerInterface::getOperations().
+ */
+ public function getOperations(EntityInterface $entity) {
+ $uri = $entity->uri();
+ $operations['edit'] = array(
+ 'title' => t('Edit'),
+ 'href' => $uri['path'] . '/edit',
+ 'options' => $uri['options'],
+ 'weight' => 10,
+ );
+ $operations['delete'] = array(
+ 'title' => t('Delete'),
+ 'href' => $uri['path'] . '/delete',
+ 'options' => $uri['options'],
+ 'weight' => 100,
+ );
+ return $operations;
+ }
+
+ /**
+ * Retrieves the entity list path from the entity information.
+ *
+ * @return string
+ * The internal system path where the entity list will be rendered.
+ *
+ * @todo What is this method for, other than fetching the list path? Is this
+ * for http://drupal.org/node/1783964 ? Should it be on the interface?
+ */
+ public function getPath() {
+ return $this->entityInfo['list path'];
+ }
+
+ /**
+ * Builds the header row.
+ *
+ * @return array
+ * An array of header strings.
+ */
+ public function buildHeader() {
+ $row['label'] = t('Label');
+ $row['id'] = t('Machine name');
+ $row['operations'] = t('Operations');
+ return $row;
+ }
+
+ /**
+ * Builds an array of data for each row.
+ *
+ * @param Drupal\Core\Entity\EntityInterface $entity
+ * The entity for this row of the list.
+ *
+ * @return array
+ * An array of fields to use for this entity.
+ */
+ public function buildRow(EntityInterface $entity) {
+ $row['label'] = $entity->label();
+ $row['id'] = $entity->id();
+ $operations = $this->buildOperations($entity);
+ $row['operations'] = drupal_render($operations);
+ return $row;
+ }
+
+ /**
+ * Renders a list of operation links.
+ *
+ * @param Drupal\Core\Entity\EntityInterface $entity
+ * The entity on which the linked operations will be performed.
+ *
+ * @return array
+ * A renderable array of operation links.
+ */
+ public function buildOperations(EntityInterface $entity) {
+ // Retrieve and sort operations.
+ $operations = $this->getOperations($entity);
+ uasort($operations, 'drupal_sort_weight');
+ $build = array(
+ '#theme' => 'links',
+ '#links' => $operations,
+ );
+ return $build;
+ }
+
+ /**
+ * Implements Drupal\Core\Entity\EntityListControllerInterface::render().
+ */
+ public function render() {
+ $build = array(
+ '#theme' => 'table',
+ '#header' => $this->buildHeader(),
+ '#rows' => array(),
+ '#empty' => t('There is no @label yet. Add one.', array(
+ '@label' => $this->entityInfo['label'],
+ '@add-url' => url($this->getPath() . '/add'),
+ )),
+ );
+ foreach ($this->load() as $entity) {
+ $build['#rows'][$entity->id()] = $this->buildRow($entity);
+ }
+ return $build;
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityListControllerInterface.php b/core/lib/Drupal/Core/Entity/EntityListControllerInterface.php
new file mode 100644
index 0000000..85790c3
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityListControllerInterface.php
@@ -0,0 +1,58 @@
+ 'Configuration entity list',
+ 'description' => 'Tests the listing of configuration entities.',
+ 'group' => 'Configuration',
+ );
+ }
+
+ /**
+ * Tests that the entity list controller loads a valid list of entities.
+ */
+ function testList() {
+ $controller = entity_list_controller('config_test');
+
+ // Get a list of ConfigTest entities and confirm that it contains the
+ // ConfigTest entity provided by the config_test module.
+ // @see config_test.dynamic.default.yml
+ $list = $controller->load();
+ $this->assertEqual(count($list), 1, '1 ConfigTest entity found.');
+ $this->assertTrue(!empty($list['default']), '"Default" ConfigTest entity ID found.');
+ $this->assertTrue($list['default'] instanceof ConfigTest, '"Default" ConfigTest entity is an instance of ConfigTest.');
+ }
+
+ /**
+ * Tests the listing UI.
+ */
+ function testListUI() {
+ $page = $this->drupalGet('admin/structure/config_test');
+ $this->assertText('Test configuration');
+
+ // Verify that the default ConfigTest configuration appears.
+ $this->assertText('default');
+ $this->assertText('Default');
+
+ // Verify that the expected operation links work.
+ foreach (array('Edit', 'Delete') as $link) {
+ $this->drupalSetContent($page);
+ $this->assertLink($link);
+ $this->clickLink($link);
+ $this->assertResponse(200);
+ }
+ }
+
+}
diff --git a/core/modules/config/tests/config_test/config_test.module b/core/modules/config/tests/config_test/config_test.module
index 61d92df..439d6a7 100644
--- a/core/modules/config/tests/config_test/config_test.module
+++ b/core/modules/config/tests/config_test/config_test.module
@@ -82,6 +82,8 @@ function config_test_entity_info() {
'label' => 'Test configuration',
'controller class' => 'Drupal\Core\Config\Entity\ConfigStorageController',
'entity class' => 'Drupal\config_test\ConfigTest',
+ 'list controller class' => 'Drupal\Core\Config\Entity\ConfigEntityListController',
+ 'list path' => 'admin/structure/config_test',
'uri callback' => 'config_test_uri',
'config prefix' => 'config_test.dynamic',
'entity keys' => array(
@@ -176,36 +178,8 @@ function config_test_delete($id) {
* Page callback; Lists available ConfigTest objects.
*/
function config_test_list_page() {
- $entities = entity_load_multiple('config_test');
- uasort($entities, 'Drupal\Core\Config\Entity\ConfigEntityBase::sort');
-
- $rows = array();
- foreach ($entities as $config_test) {
- $uri = $config_test->uri();
- $row = array();
- $row['name']['data'] = array(
- '#type' => 'link',
- '#title' => $config_test->label(),
- '#href' => $uri['path'],
- '#options' => $uri['options'],
- );
- $row['delete']['data'] = array(
- '#type' => 'link',
- '#title' => t('Delete'),
- '#href' => $uri['path'] . '/delete',
- '#options' => $uri['options'],
- );
- $rows[] = $row;
- }
- $build = array(
- '#theme' => 'table',
- '#header' => array('Name', 'Operations'),
- '#rows' => $rows,
- '#empty' => format_string('No test configuration defined. Add some', array(
- '@add-url' => url('admin/structure/config_test/add'),
- )),
- );
- return $build;
+ $controller = entity_list_controller('config_test');
+ return $controller->render();
}
/**