drupal8文档
The Queue API in Drupal allows us to handle a number of tasks at a later stage. What this means is that we can place items into a queue which will run some time in the future and process each individual item at that point and at least once. Usually, this happens on CRON runs, and Drupal 8 allows for a quick set up for cronjob based queues. It doesn’t necessarily have to be CRON, however.
Drupal中的Queue API使我们可以在稍后阶段处理许多任务。 这意味着我们可以将项目放入队列中,该队列将在将来的某个时间运行,并在那时至少处理一次单个项目。 通常,这发生在CRON运行中,Drupal 8允许快速设置基于cronjob的队列。 但是,不一定必须是CRON。
In this article, we will look at using the Queue API in Drupal 8 by exploring two simple examples. The first will see the queue triggered by Cron while the second will allow us to manually do so ourselves. However, the actual processing will be handled by a similar worker. If you want to follow along, clone this git repository where you can find the npq
module we will write in this article.
在本文中,我们将通过探讨两个简单的示例来研究在Drupal 8中使用Queue API。 第一个将看到Cron触发的队列,第二个将允许我们自己手动执行此操作。 但是,实际处理将由类似的工人进行。 如果您想继续,请克隆此git存储库 ,您可以在其中找到我们将在本文中编写的npq
模块。
The module we’ll work with is called Node Publisher Queue and it automatically adds newly created nodes that are saved unpublished to a queue to be published later on. We will see how later on can be the next CRON run or a manual action triggered by the site’s administrator. First, let’s understand some basic concepts about queues in Drupal 8.
我们将使用的模块称为Node Publisher Queue ,它会自动将未发布保存的新创建节点添加到队列中,以便稍后发布。 我们将看到以后如何下次运行CRON或由站点管理员触发手动操作。 首先,让我们了解有关Drupal 8中队列的一些基本概念。
理论 (The theory)
There are a few components that make up the Queue API in Drupal 8.
Drupal 8中有一些组成Queue API的组件。
The most important role in this API is played by the QueueInterface
implementation which represents the queue. The default queue type Drupal 8 ships with is currently the DatabaseQueue
which is a type of reliable queue that makes sure all its items are processed at least once and in their original order (FIFO). This is in contrast to unreliable queues which only do their best to achieve this (something for which valid use cases do exist).
该API中最重要的角色是代表队列的QueueInterface
实现。 Drupal 8附带的默认队列类型当前是DatabaseQueue
,这是一种可靠的队列,可确保其所有项目至少按其原始顺序( FIFO )处理一次。 这与不可靠的队列相反,后者只能尽力做到这一点(确实存在有效的用例)。
The typical role of the queue object is to create items, later claim them from the queue and delete them when they have been processed. In addition, it can release items if processing is either not finished or another worker needs to process them again before deletion.
队列对象的典型作用是创建项目,随后从队列中声明它们,并在处理完它们后将其删除。 另外,如果处理未完成或另一个工作人员需要在删除之前再次处理它们,它可以释放项目。
The QueueInterface
implementation is instantiated with the help of a general QueueFactory
. In the case of the DatabaseQueue
, the former uses the DatabaseQueueFactory
as well. Queues also need to be created before they can be used. However, the DatabaseQueue
is already created when Drupal is first installed so no additional setup is required.
借助通用QueueFactory
实例化QueueInterface
实现。 对于DatabaseQueue
,前者也使用DatabaseQueueFactory
。 在使用队列之前,还需要创建队列。 但是,首次安装Drupal时已经创建了DatabaseQueue
,因此不需要其他设置。
The Queue Workers are responsible for processing queue items as they receive them. In Drupal 8 these are QueueWorker
plugins that implement the QueueWorkerInterface
. Using the QueueWorkerManager
, we create instances of these plugins and process the items whenever the queue needs to be run.
队列工作人员负责在接收队列项目时对其进行处理。 在Drupal 8中,这些是实现QueueWorkerInterface
QueueWorker
插件。 使用QueueWorkerManager
,我们创建了这些插件的实例,并在需要运行队列时处理项目。
节点发布队列模块 (The Node Publish Queue module)
Now that we’ve covered the basic concepts of the Queue API in Drupal 8, let’s get our hands dirty and create the functionality described in the introduction. Our npq.info.yml file can be simple:
现在我们已经介绍了Drupal 8中Queue API的基本概念,让我们动手实践一下,并创建引言中描述的功能。 我们的npq.info.yml文件可以很简单:
name: Node Publish Queue
description: Demo module illustrating the Queue API in Drupal 8
core: 8.x
type: module
队列项目创建 (Queue item creation)
Inside the npq.module
file we take care of the logic for creating queue items whenever a node is saved and not published:
在npq.module
文件中,我们负责保存节点但不发布节点时创建队列项的逻辑:
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Queue\QueueFactory;
use Drupal\Core\Queue\QueueInterface;
/**
* Implements hook_entity_insert().
*/
function npq_entity_insert(EntityInterface $entity) {
if ($entity->getEntityTypeId() !== 'node') {
return;
}
if ($entity->isPublished()) {
return;
}
/** @var QueueFactory $queue_factory */
$queue_factory = \Drupal::service('queue');
/** @var QueueInterface $queue */
$queue = $queue_factory->get('cron_node_publisher');
$item = new \stdClass();
$item->nid = $entity->id();
$queue->createItem($item);
}
Inside this basic hook_entity_insert()
implementation we do a very simple task. We first retrieve the QueueFactoryInterface
object from the service container and use it to get a queue called cron_node_publisher
. If we track things down, we notice that the get()
method on the DatabaseQueueFactory
simply creates a new DatabaseQueue
instance with the name we pass to it.
在这个基本的hook_entity_insert()
实现中,我们执行了一个非常简单的任务。 我们首先从服务容器中检索QueueFactoryInterface
对象,并使用它获取名为cron_node_publisher
的队列。 如果我们往下看,我们会注意到DatabaseQueueFactory
上的get()
方法只是创建了一个新的DatabaseQueue
实例,并带有我们传递给它的名称。
Lastly, we create a small PHP object containing the node ID and create an item in the queue with that data. Simple.
最后,我们创建一个包含节点ID的小型PHP对象,并使用该数据在队列中创建一个项目。 简单。
CRON队列工作者 (The CRON queue worker)
Next, let’s create a QueueWorker
plugin that will process the queue items whenever Cron is run. However, because we know that we will also need one for manual processing that does the same thing, we will add most of the logic in a base abstract class. So inside the Plugin/QueueWorker
namespace of our module we can have the NodePublishBase
class:
接下来,让我们创建一个QueueWorker
插件,该插件将在Cron运行时处理队列项。 但是,因为我们知道对于相同的手动处理也将需要一个逻辑,所以我们将在基本抽象类中添加大多数逻辑。 因此,在我们模块的Plugin/QueueWorker
命名空间内,我们可以拥有NodePublishBase
类:
/**
* @file
* Contains Drupal\npq\Plugin\QueueWorker\NodePublishBase.php
*/
namespace Drupal\npq\Plugin\QueueWorker;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Queue\QueueWorkerBase;
use Drupal\node\NodeInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides base functionality for the NodePublish Queue Workers.
*/
abstract class NodePublishBase extends QueueWorkerBase implements ContainerFactoryPluginInterface {
/**
* The node storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $nodeStorage;
/**
* Creates a new NodePublishBase object.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $node_storage
* The node storage.
*/
public function __construct(EntityStorageInterface $node_storage) {
$this->nodeStorage = $node_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$container->get('entity.manager')->getStorage('node')
);
}
/**
* Publishes a node.
*
* @param NodeInterface $node
* @return int
*/
protected function publishNode($node) {
$node->setPublished(TRUE);
return $node->save();
}
/**
* {@inheritdoc}
*/
public function processItem($data) {
/** @var NodeInterface $node */
$node = $this->nodeStorage->load($data->nid);
if (!$node->isPublished() && $node instanceof NodeInterface) {
return $this->publishNode($node);
}
}
}
Right off the bat we can see that we are using dependency injection to inject the NodeStorage
into our class. For more information about dependency injection and the service container, feel free to check out my article on the topic.
马上,我们可以看到我们正在使用依赖注入将NodeStorage
注入到我们的类中。 有关依赖注入和服务容器的更多信息,请随时阅读我关于该主题的文章 。
In this base class we have two methods: publishNode()
and the obligatory processItem()
. The former publishes and saves a node that is passed to it. The latter loads the node using the node ID contained in the $data
object and publishes it if it’s unpublished.
在此基类中,我们有两种方法: publishNode()
和必需的processItem()
。 前者发布并保存传递给它的节点。 后者使用$data
对象中包含的节点ID加载节点,如果未发布,则将其发布。
Now, let’s create a CronNodePublisher
plugin that will use this logic on Cron runs:
现在,让我们创建一个CronNodePublisher
插件,该插件将在Cron运行时使用以下逻辑:
namespace Drupal\npq\Plugin\QueueWorker;
/**
* A Node Publisher that publishes nodes on CRON run.
*
* @QueueWorker(
* id = "cron_node_publisher",
* title = @Translation("Cron Node Publisher"),
* cron = {"time" = 10}
* )
*/
class CronNodePublisher extends NodePublishBase {}
And that is all. We don’t need any other logic than what already is in our base class. Notice that, in the annotation, we are telling Drupal that this worker needs to be used by Cron to process as many items as it can within 10 seconds. How does this happen?
仅此而已。 除了基类中已经存在的逻辑外,我们不需要任何其他逻辑。 请注意,在批注中,我们告诉Drupal,Cron需要使用此工作程序在10秒内处理尽可能多的项目。 这是怎么发生的?
Whenever Cron runs, it uses the QueueWorkerManager
to load all its plugin definitions. Then, if any of them have the cron
key in their annotation, a Queue with the same name as the ID of the worker is loaded for processing. Lastly, each item in the queue is claimed and processed by the worker until the specified time has elapsed.
每当Cron运行时,它都使用QueueWorkerManager
加载其所有插件定义。 然后,如果其中任何一个在其批注中具有cron
键,则将加载与工作程序ID相同名称的Queue进行处理。 最后,工作人员要求对队列中的每个项目进行索取和处理,直到经过指定的时间为止。
If we now save an unpublished node, it will most likely become published at the next Cron run.
如果我们现在保存一个未发布的节点,则很有可能在下一次Cron运行时将其发布。
体力工人 (The manual worker)
Let’s create also the possibility for the Queue to be processed manually. First, let’s adapt the hook_entity_insert()
implementation from before and change this line:
让我们还创建手动处理队列的可能性。 首先,让我们从以前改编hook_entity_insert()
实现并更改此行:
$queue = $queue_factory->get('cron_node_publisher');
to this:
对此:
$queue = $queue_factory->get('manual_node_publisher');
You can of course provide an admin screen for configuring which type of node publisher the application should use.
您当然可以提供一个管理屏幕,用于配置应用程序应使用哪种类型的节点发布者。
Second, let’s create our ManualNodePublisher
plugin:
其次,让我们创建我们的ManualNodePublisher
插件:
namespace Drupal\npq\Plugin\QueueWorker;
/**
* A Node Publisher that publishes nodes via a manual action triggered by an admin.
*
* @QueueWorker(
* id = "manual_node_publisher",
* title = @Translation("Manual Node Publisher"),
* )
*/
class ManualNodePublisher extends NodePublishBase {}
This is almost the same as with the CRON example but without the cron
key.
这几乎与CRON示例相同,但没有cron
密钥。
Third, let’s create a form where we can see how many items are in the manual_node_publisher
queue and process them all by the press of a button. Inside npq.routing.yml
in the module root folder:
第三,让我们创建一个表单,在其中可以查看manual_node_publisher
队列中有多少个项目,并通过按一下按钮来处理所有项目。 在模块根文件夹的npq.routing.yml
中:
demo.form:
path: '/npq'
defaults:
_form: '\Drupal\npq\Form\NodePublisherQueueForm'
_title: 'Node Publisher'
requirements:
_permission: 'administer site configuration'
We define a path at /npq
which should use the specified form that lives in that namespace and that we can define as such:
我们在/npq
处定义一个路径,该路径应使用该名称空间中的指定格式,并且可以这样定义:
/**
* @file
* Contains \Drupal\npq\Form\NodePublisherQueueForm.
*/
namespace Drupal\npq\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Queue\QueueFactory;
use Drupal\Core\Queue\QueueInterface;
use Drupal\Core\Queue\QueueWorkerInterface;
use Drupal\Core\Queue\QueueWorkerManagerInterface;
use Drupal\Core\Queue\SuspendQueueException;
use Symfony\Component\DependencyInjection\ContainerInterface;
class NodePublisherQueueForm extends FormBase {
/**
* @var QueueFactory
*/
protected $queueFactory;
/**
* @var QueueWorkerManagerInterface
*/
protected $queueManager;
/**
* {@inheritdoc}
*/
public function __construct(QueueFactory $queue, QueueWorkerManagerInterface $queue_manager) {
$this->queueFactory = $queue;
$this->queueManager = $queue_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('queue'),
$container->get('plugin.manager.queue_worker')
);
}
/**
* {@inheritdoc}.
*/
public function getFormId() {
return 'demo_form';
}
/**
* {@inheritdoc}.
*/
public function buildForm(array $form, FormStateInterface $form_state) {
/** @var QueueInterface $queue */
$queue = $this->queueFactory->get('node_publisher');
$form['help'] = array(
'#type' => 'markup',
'#markup' => $this->t('Submitting this form will process the Manual Queue which contains @number items.', array('@number' => $queue->numberOfItems())),
);
$form['actions']['#type'] = 'actions';
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Process queue'),
'#button_type' => 'primary',
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
/** @var QueueInterface $queue */
$queue = $this->queueFactory->get('manual_node_publisher');
/** @var QueueWorkerInterface $queue_worker */
$queue_worker = $this->queueManager->createInstance('manual_node_publisher');
while($item = $queue->claimItem()) {
try {
$queue_worker->processItem($item->data);
$queue->deleteItem($item);
}
catch (SuspendQueueException $e) {
$queue->releaseItem($item);
break;
}
catch (\Exception $e) {
watchdog_exception('npq', $e);
}
}
}
}
We are again using dependency injection to inject the QueueFactory
and the manager for QueueWorker
plugins. Inside buildForm()
we are creating a basic form structure and using the numberOfItems()
method on the queue to tell the user how many items they are about to process. And finally, inside the submitForm()
method we take care of the processing. But how do we do that?
我们再次使用依赖项注入来注入QueueFactory
和QueueWorker
插件的管理器。 在buildForm()
内部,我们正在创建一个基本的表单结构,并在队列上使用numberOfItems()
方法来告诉用户他们将要处理多少个项目。 最后,在submitForm()
方法内部,我们负责处理。 但是我们该怎么做呢?
First, we load the Queue and instantiate a Queue worker (in both cases we use the manual_node_publisher
id). Then we run a while
loop until all the items have been processed. The claimItem()
method is responsible for blocking a queue item from being claimed by another queue and returning it for processing. After it gets processed by the worker, we delete it. In the next iteration, the next item is returned and on like this until no items are left.
首先,我们加载队列并实例化队列工作器(在两种情况下,我们都使用manual_node_publisher
id)。 然后我们运行一个while
循环,直到所有项目都已处理完毕。 claimItem()
方法负责阻止一个队列项目被另一个队列声明并返回它进行处理。 在工作人员对其进行处理后,我们将其删除。 在下一次迭代中,将返回下一个项目,依此类推,直到没有剩余项目为止。
Although we have not used it, the SuspendQueueException
is meant to indicate that during the processing of the item, the worker found a problem that would most likely make all other items in the queue fail as well. And for this reason it is pointless to continue to the next item so we break out of the loop. However, we also release the item so that when we try again later, the item is available. Other exceptions are also caught and logged to the watchdog.
尽管我们没有使用过SuspendQueueException
,但它旨在表明在处理项目期间,工作程序发现了一个问题,很可能会使队列中的所有其他项目也失败。 因此,继续下一个项目毫无意义,因此我们跳出了循环。 但是,我们也会释放该项目,以便稍后再试时该项目可用。 其他异常也会被捕获并记录到看门狗。
Now if we create a couple of nodes and don’t publish them, we’ll see their count inside the message if we navigate to /npq
. By clicking the submit button we process (publish) them all one by one.
现在,如果我们创建几个节点并且不发布它们,那么如果导航到/npq
,我们将在消息中看到它们的数量。 通过单击提交按钮,我们将一一处理(发布)它们。
This has been a demonstration example only. It’s always important to take into account the potential load of processing a large number of items and either limit that so your request doesn’t time out or use the Batch API to split them into multiple requests.
这仅是一个演示示例。 考虑到处理大量项目的潜在负担,并限制您的请求不会超时或使用Batch API将它们拆分为多个请求,这始终很重要。
结论 (Conclusion)
In this article we’ve looked at the Queue API in Drupal 8. We’ve learned some basic concepts about how it is built and how it works, but we’ve also seen some examples of how we can work with it. Namely, we’ve played with two use cases by which we can publish unpublished nodes either during Cron runs or manually via an action executed by the user.
在本文中,我们研究了Drupal 8中的Queue API。我们学习了一些有关如何构建它以及如何工作的基本概念,但是我们也看到了一些如何使用它的示例。 即,我们已经研究了两个用例,通过它们我们可以在Cron运行期间或通过用户执行的操作手动发布未发布的节点。
Have you tried out the Queue API in Drupal 8? Let us know how it went!
您是否在Drupal 8中试用了Queue API? 让我们知道进展如何!
翻译自: https://www.sitepoint.com/drupal-8-queue-api-powerful-manual-and-cron-queueing/
drupal8文档