diff --git a/examples.module b/examples.module
index 7b816b0..9c9b091 100644
--- a/examples.module
+++ b/examples.module
@@ -53,6 +53,7 @@ function examples_toolbar() {
'phpunit_example' => 'phpunit_example_description',
'plugin_type_example' => 'plugin_type_example.description',
'simpletest_example' => 'simpletest_example_description',
+ 'queue_example' => 'queue_example',
'tablesort_example' => 'tablesort_example_description',
'tour_example' => 'tour_example_description',
);
diff --git a/queue_example/queue_example.info.yml b/queue_example/queue_example.info.yml
new file mode 100644
index 0000000..e7d94ff
--- /dev/null
+++ b/queue_example/queue_example.info.yml
@@ -0,0 +1,5 @@
+name: Queue example
+type: module
+description: Examples of using the Drupal Queue API.
+package: Example modules
+core: 8.x
diff --git a/queue_example/queue_example.links.menu.yml b/queue_example/queue_example.links.menu.yml
new file mode 100644
index 0000000..29b8614
--- /dev/null
+++ b/queue_example/queue_example.links.menu.yml
@@ -0,0 +1,3 @@
+queue_example:
+ title: Queue Example
+ route_name: queue_example
diff --git a/queue_example/queue_example.module b/queue_example/queue_example.module
new file mode 100644
index 0000000..891c93c
--- /dev/null
+++ b/queue_example/queue_example.module
@@ -0,0 +1,37 @@
+queueFactory = $queue_factory;
+ $this->database = $database;
+ $this->cron = $cron;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static($container->get('queue'), $container->get('database'), $container->get('cron'));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFormID() {
+ // Return a string that is the unique ID of our form. Best practice here is
+ // to namespace the form based on your module's name.
+ return 'queue_example';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildForm(array $form, FormStateInterface $form_state) {
+ // Simple counter that makes it possible to put auto-incrementing default
+ // string into the string to insert.
+ if (empty($form_state->get('insert_counter'))) {
+ $form_state->set('insert_counter', 1);
+ }
+
+ $queue_name = $form_state->getValue('queue_name') ?: 'queue_example_first_queue';
+ $items = $this->retrieveQueue($queue_name);
+
+ $form['help'] = array(
+ '#type' => 'markup',
+ '#markup' => '
' . $this->t('This page is an interface on the Drupal queue API. You can add new items to the queue, "claim" one (retrieve the next item and keep a lock on it), and delete one (remove it from the queue). Note that claims are not expired until cron runs, so there is a special button to run cron to perform any necessary expirations.') . '
',
+ );
+
+ $queue_names = array('queue_example_first_queue', 'queue_example_second_queue');
+ $form['queue_name'] = array(
+ '#type' => 'select',
+ '#title' => $this->t('Choose queue to examine'),
+ '#options' => array_combine($queue_names, $queue_names),
+ '#default_value' => $queue_name,
+ );
+
+ $form['queue_show'] = array(
+ '#type' => 'submit',
+ '#value' => $this->t('Show queue'),
+ '#submit' => array(array($this, 'submitShowQueue')),
+ );
+
+ $form['status_fieldset'] = array(
+ '#type' => 'fieldset',
+ '#title' => $this->t('Queue status for @name', array('@name' => $queue_name)),
+ '#collapsible' => TRUE,
+ );
+
+ if (count($items) > 0) {
+ $form['status_fieldset']['status'] = array(
+ '#theme' => 'table',
+ '#header' => array(
+ t('Item ID'),
+ t('Claimed/Expiration'),
+ t('Created'),
+ t('Content/Data'),
+ ),
+ '#rows' => array_map(array($this, 'processQueueItemForTable'), $items),
+ );
+ }
+ else {
+ $form['status_fieldset']['status'] = array(
+ '#type' => 'markup',
+ '#markup' => $this->t('There are no items in the queue.'),
+ );
+ }
+
+ $form['insert_fieldset'] = array(
+ '#type' => 'fieldset',
+ '#title' => $this->t('Insert into @name', array('@name' => $queue_name)),
+ );
+
+ $form['insert_fieldset']['string_to_add'] = array(
+ '#type' => 'textfield',
+ '#size' => 10,
+ '#default_value' => $this->t('item @counter', array('@counter' => $form_state->get('insert_counter'))),
+ );
+
+ $form['insert_fieldset']['add_item'] = array(
+ '#type' => 'submit',
+ '#value' => $this->t('Insert into queue'),
+ '#submit' => array(array($this, 'submitAddQueueItem')),
+ );
+
+ $form['claim_fieldset'] = array(
+ '#type' => 'fieldset',
+ '#title' => $this->t('Claim from queue'),
+ '#collapsible' => TRUE,
+ );
+
+ $form['claim_fieldset']['claim_time'] = array(
+ '#type' => 'radios',
+ '#title' => $this->t('Claim time, in seconds'),
+ '#options' => array(
+ 0 => $this->t('none'),
+ 5 => $this->t('5 seconds'),
+ 60 => $this->t('60 seconds'),
+ ),
+ '#description' => $this->t('This time is only valid if cron runs during this time period. You can run cron manually below.'),
+ '#default_value' => $form_state->getValue('claim_time') ?: 5,
+ );
+
+ $form['claim_fieldset']['claim_item'] = array(
+ '#type' => 'submit',
+ '#value' => $this->t('Claim the next item from the queue'),
+ '#submit' => array(array($this, 'submitClaimItem')),
+ );
+
+ $form['claim_fieldset']['claim_and_delete_item'] = array(
+ '#type' => 'submit',
+ '#value' => $this->t('Claim the next item and delete it'),
+ '#submit' => array(array($this, 'submitClaimDeleteItem')),
+ );
+
+ $form['claim_fieldset']['run_cron'] = array(
+ '#type' => 'submit',
+ '#value' => $this->t('Run cron manually to expire claims'),
+ '#submit' => array(array($this, 'submitRunCron')),
+ );
+
+ $form['delete_queue'] = array(
+ '#type' => 'submit',
+ '#value' => $this->t('Delete the queue and items in it'),
+ '#submit' => array(array($this, 'submitDeleteQueue')),
+ );
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ }
+
+ /**
+ * Retrieves the queue from the database for display purposes only.
+ *
+ * It is not recommended to access the database directly, and this is only
+ * here so that the user interface can give a good idea of what's going on in the
+ * queue.
+ *
+ * @param $queue_name
+ * The name of the queue from which to fetch items.
+ *
+ * @return array
+ */
+ public function retrieveQueue($queue_name) {
+ $items = array();
+
+ $result = $this->database->query('SELECT item_id, data, expire, created FROM queue WHERE name = :name ORDER BY item_id',
+ array(':name' => $queue_name),
+ array('fetch' => \PDO::FETCH_ASSOC));
+ foreach ($result as $item) {
+ $items[] = $item;
+ }
+
+ return $items;
+ }
+
+ /**
+ * Submit function for the show-queue button.
+ *
+ * @param array $form
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ */
+ public function submitShowQueue(array &$form, FormStateInterface $form_state) {
+ $queue = $this->queueFactory->get($form_state->getValue('queue_name'));
+ // There is no harm in trying to recreate existing.
+ $queue->createQueue();
+
+ // Get the number of items.
+ $count = $queue->numberOfItems();
+
+ // Update the form item counter.
+ $form_state->set('insert_counter', $count + 1);
+
+ // Unset the string_to_add textbox.
+ $form_state->unsetValue('string_to_add');
+
+ $form_state->setRebuild();
+ }
+
+ /**
+ * Submit function for the insert-into-queue button.
+ *
+ * @param array $form
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ */
+ public function submitAddQueueItem(array &$form, FormStateInterface $form_state) {
+ // Get a queue (of the default type) called 'queue_example_queue'.
+ // If the default queue class is SystemQueue this creates a queue that
+ // stores its items in the database.
+ $queue = $this->queueFactory->get($form_state->getValue('queue_name'));
+ // There is no harm in trying to recreate existing.
+ $queue->createQueue();
+
+ // Queue the string.
+ $queue->createItem($form_state->getValue('string_to_add'));
+ $count = $queue->numberOfItems();
+ drupal_set_message($this->t('Queued your string (@string_to_add). There are now @count items in the queue.', array('@count' => $count, '@string_to_add' => $form_state->getValue('string_to_add'))));
+ // Allows us to keep information in $form_state.
+ $form_state->setRebuild();
+
+ // Unsetting the string_to_add allows us to set the incremented default
+ // value for the user so they don't have to type anything.
+ $form_state->unsetValue('string_to_add');
+ $form_state->set('insert_counter', $count + 1);
+ }
+
+ /**
+ * Submit function for the "claim" button.
+ * Claims (retrieves) an item from the queue and reports the results.
+ *
+ * @param array $form
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ */
+ public function submitClaimItem(array &$form, FormStateInterface $form_state) {
+ $queue = $this->queueFactory->get($form_state->getValue('queue_name'));
+ // There is no harm in trying to recreate existing.
+ $queue->createQueue();
+ $item = $queue->claimItem($form_state->getValue('claim_time'));
+ $count = $queue->numberOfItems();
+ if (!empty($item)) {
+ drupal_set_message($this->t('Claimed item id=@item_id string=@string for @seconds seconds. There are @count items in the queue.', array('@count' => $count, '@item_id' => $item->item_id, '@string' => $item->data, '@seconds' => $form_state->getValue('claim_time'))));
+ }
+ else {
+ drupal_set_message($this->t('There were no items in the queue available to claim. There are @count items in the queue.', array('@count' => $count)));
+ }
+ $form_state->setRebuild();
+ }
+
+ /**
+ * Submit function for "Claim and delete" button.
+ *
+ * @param array $form
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ */
+ public function submitClaimDeleteItem(array &$form, FormStateInterface $form_state) {
+ $queue = $this->queueFactory->get($form_state->getValue('queue_name'));
+ // There is no harm in trying to recreate existing.
+ $queue->createQueue();
+ $count = $queue->numberOfItems();
+ $item = $queue->claimItem(60);
+ if (!empty($item)) {
+ drupal_set_message($this->t('Claimed and deleted item id=@item_id string=@string for @seconds seconds. There are @count items in the queue.', array(
+ '@count' => $count,
+ '@item_id' => $item->item_id,
+ '@string' => $item->data,
+ '@seconds' => $form_state->getValue('claim_time'),
+ )));
+ $queue->deleteItem($item);
+ $count = $queue->numberOfItems();
+ drupal_set_message($this->t('There are now @count items in the queue.', array('@count' => $count)));
+ }
+ else {
+ $count = $queue->numberOfItems();
+ drupal_set_message($this->t('There were no items in the queue available to claim/delete. There are currently @count items in the queue.', array('@count' => $count)));
+ }
+ $form_state->setRebuild();
+ }
+
+ /**
+ * Submit function for "run cron" button.
+ *
+ * Runs cron (to release expired claims) and reports the results.
+ *
+ * @param array $form
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ */
+ public function submitRunCron(array &$form, FormStateInterface $form_state) {
+ $this->cron->run();
+ $queue = $this->queueFactory->get($form_state->getValue('queue_name'));
+ // @see https://www.drupal.org/node/2705809
+ if ($queue instanceof QueueGarbageCollectionInterface) {
+ $queue->garbageCollection();
+ }
+ // There is no harm in trying to recreate existing.
+ $queue->createQueue();
+ $count = $queue->numberOfItems();
+ drupal_set_message($this->t('Ran cron. If claimed items expired, they should be expired now. There are now @count items in the queue', array('@count' => $count)));
+ $form_state->setRebuild();
+ }
+
+ /**
+ * Submit handler for clearing/deleting the queue.
+ *
+ * @param array $form
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ */
+ public function submitDeleteQueue(array &$form, FormStateInterface $form_state) {
+ $queue = $this->queueFactory->get($form_state->getValue('queue_name'));
+ $queue->deleteQueue();
+ drupal_set_message($this->t('Deleted the @queue_name queue and all items in it', array('@queue_name' => $form_state->getValue('queue_name'))));
+ }
+
+ /**
+ * Helper method to format a queue item for display in a summary table.
+ *
+ * @param array $item
+ * Queue item array with keys for item_id, expire, created, and data.
+ *
+ * @return array
+ * An array with the queue properties in the right order for display in a
+ * summary table.
+ */
+ private function processQueueItemForTable(array $item) {
+ if ($item['expire'] > 0) {
+ $item['expire'] = $this->t('Claimed: expires %expire', array('%expire' => date('r', $item['expire'])));
+ }
+ else {
+ $item['expire'] = $this->t('Unclaimed');
+ }
+ $item['created'] = date('r', $item['created']);
+ $item['content'] = Html::escape(unserialize($item['data']));
+ unset($item['data']);
+
+ return $item;
+ }
+
+}
diff --git a/queue_example/src/Tests/QueueExampleTest.php b/queue_example/src/Tests/QueueExampleTest.php
new file mode 100644
index 0000000..626bf94
--- /dev/null
+++ b/queue_example/src/Tests/QueueExampleTest.php
@@ -0,0 +1,80 @@
+ 'Queue Example functionality',
+ 'description' => 'Test Queue Example functionality',
+ 'group' => 'Examples',
+ );
+ }
+
+ /**
+ * Login user, create an example node, and test blog functionality through the
+ * admin and user interfaces.
+ */
+ public function testQueueExampleBasic() {
+ $this->drupalGet('examples/queue_example');
+ // Load the queue with 5 items.
+ for ($i = 1; $i <= 5; $i++) {
+ $edit = array('queue_name' => 'queue_example_first_queue', 'string_to_add' => 'boogie' . $i);
+ $this->drupalPostForm(NULL, $edit, t('Insert into queue'));
+ $this->assertText(t('There are now @number items in the queue', array('@number' => $i)));
+ }
+ // Claim each of the 5 items with a claim time of 0 seconds.
+ for ($i = 1; $i <= 5; $i++) {
+ $edit = array('queue_name' => 'queue_example_first_queue', 'claim_time' => 0);
+ $this->drupalPostForm(NULL, $edit, t('Claim the next item from the queue'));
+ $this->assertPattern(t('%Claimed item id=.*string=@string for 0 seconds.%', array('@string' => 'boogie' . $i)));
+ }
+ $edit = array('queue_name' => 'queue_example_first_queue', 'claim_time' => 0);
+ $this->drupalPostForm(NULL, $edit, t('Claim the next item from the queue'));
+ $this->assertText(t('There were no items in the queue available to claim'));
+
+ // Sleep a second so we can make sure that the timeouts actually time out.
+ // Local systems work fine with this but apparently the PIFR server is so
+ // fast that it needs a sleep before the cron run.
+ sleep(1);
+
+ // Run cron to release expired items.
+ $this->drupalPostForm(NULL, array(), t('Run cron manually to expire claims'));
+
+ // Claim and delete each of the 5 items which should now be available.
+ for ($i = 1; $i <= 5; $i++) {
+ $edit = array('queue_name' => 'queue_example_first_queue', 'claim_time' => 0);
+ $this->drupalPostForm(NULL, $edit, t('Claim the next item and delete it'));
+ $this->assertPattern(t('%Claimed and deleted item id=.*string=@string for 0 seconds.%', array('@string' => 'boogie' . $i)));
+ }
+ // Verify that nothing is left to claim.
+ $edit = array('queue_name' => 'queue_example_first_queue', 'claim_time' => 0);
+ $this->drupalPostForm(NULL, $edit, t('Claim the next item from the queue'));
+ $this->assertText(t('There were no items in the queue available to claim'));
+ }
+
+}