Як використовувати Workflow
Як використовувати Workflow
Робочий потік - це процес життєвого циклу, через який проходять ваші об'єкти. Кожен крок або етап процесу називається місцем. Ви також визначаєте переходи, які описують дію переходу з одного місця в інше.
Набір місць і переходів створює визначення. Робочому потоку необхідно Definition
(визначення) і спосіб написати стани об'єктів (наприклад, сутність класу
MarkingStoreInterface).
Розгляньте наступний приклад для запису блогу. Запис може мати такі місця: "чернетка", "огляд", "відхилений", "опублікований". Ви можете визначити робочий потік таким чином:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
framework:
workflows:
blog_publishing:
type: 'workflow' # або 'state_machine'
marking_store:
type: 'multiple_state' # або 'single_state'
arguments:
- 'currentPlace'
supports:
- AppBundle\Entity\BlogPost
places:
- draft
- review
- rejected
- published
transitions:
to_review:
from: draft
to: review
publish:
from: review
to: published
reject:
from: review
to: rejected
1 2 3 4 5 6 7
class BlogPost
{
// Ця властивість використовується сховищем маркування
public $currentPlace;
public $title;
public $content;
}
Note
Тип сховища маркування може бути множинним і єдиним станом ("multiple_state" або "single_state"). Єдиний стан не підтримує модель, яка знаходиться в декількох місцях одночасно.
Tip
Атрибути type
(значення за замовчуванням single_state
) і arguments
(значення
за замовчуванням marking
) опції marking_store
необов'язкові. Якщо їх опустити,
будуть використані їхні значення за замовчуванням.
З цим робочим потоком, названим blog_publishing
, ви можете отримати допомогу у вирішенні
того, які дії дозволені в записі блогу:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
$post = new \AppBundle\Entity\BlogPost();
$workflow = $this->container->get('workflow.blog_publishing');
$workflow->can($post, 'publish'); // False
$workflow->can($post, 'to_review'); // True
// Оновити currentState поста
try {
$workflow->apply($post, 'to_review');
} catch (LogicException $e) {
// ...
}
// Дивіться всі доступні переходи для запису у поточному стані
$transitions = $workflow->getEnabledTransitions($post);
Використання подій
Щоб зробити ваші робочі потоки ще більш потужними, ви можете побудувати об'єкт
Workflow
з EventDispatcher
. Тепер ви можете створювати слухачів подій
для блокування переходів (тобто залежно від даних у записі блогу). Оголошуються
такі події:
workflow.leave
workflow.[workflow name].leave
workflow.[workflow name].leave.[place name]
workflow.transition
workflow.[workflow name].transition
workflow.[workflow name].transition.[transition name]
workflow.enter
workflow.[workflow name].enter
workflow.[workflow name].enter.[place name]
workflow.entered
workflow.[workflow name].entered
workflow.[workflow name].entered.[place name]
workflow.announce
workflow.[workflow name].announce
workflow.[workflow name].announce.[transition name]
Ось приклад того, як включати логування кожен раз, коли робочий потік "blog_publishing" залишає місце:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Workflow\Event\Event;
class WorkflowLogger implements EventSubscriberInterface
{
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function onLeave(Event $event)
{
$this->logger->alert(sprintf(
'Blog post (id: "%s") performed transaction "%s" from "%s" to "%s"',
$event->getSubject()->getId(),
$event->getTransition()->getName(),
implode(', ', array_keys($event->getMarking()->getPlaces())),
implode(', ', $event->getTransition()->getTos())
));
}
public static function getSubscribedEvents()
{
return array(
'workflow.blog_publishing.leave' => 'onLeave',
);
}
}
Захисні події
Існує особливий тип подій, під назвою "захисні події". Їхні слухачі
подій викликаються щоразу, коли виконується виклик Workflow::can
, Workflow::apply
або Workflow::getEnabledTransitions
. Із захисними подіями ви можете додавати користувацьку
логіку для того, щоб вирішити, які переходи валідні, а які - ні. Ось список імен
захисних подій:
workflow.guard
workflow.[workflow name].guard
workflow.[workflow name].guard.[transition name]
Дивіться приклад, щоб переконатися, що жоден запис блогу без заголовку не буде перенесено у стан "огляду":
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
use Symfony\Component\Workflow\Event\GuardEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class BlogPostReviewListener implements EventSubscriberInterface
{
public function guardReview(GuardEvent $event)
{
/** @var \AppBundle\Entity\BlogPost $post */
$post = $event->getSubject();
$title = $post->title;
if (empty($title)) {
// Записи без заголовків не повинні бути допущені
$event->setBlocked(true);
}
}
public static function getSubscribedEvents()
{
return array(
'workflow.blogpost.guard.to_review' => array('guardReview'),
);
}
}
Методи подій
Кожна подія робочого потоку - це екземпляр Event. Це означає, що кожна подія має доступ до такої інформації:
- getMarking()
- Повертає Marking робочого потоку.
- getSubject()
- Повертає обʼєкт, який оголошує подію.
- getTransition()
- Повертає Transition, який оголошує подію.
- getWorkflowName()
-
Повертає рядок з іменем робочого потоку, який викликав подію.
3.3
Метод
getWorkflowName()
було представлено в Symfony 3.3.
Для захисних подій існує розширений клас GuardEvent. Цей клас має ще два методи:
- isBlocked()
- Повертається, якщо перехід заблоковано.
- setBlocked()
- Встановлює заблоковане значення.
Використання в Twig
Symfony визначає кілька функцій Twig для управління робочими потоками та зменшення необхідності логіки домену у ваших шаблонах:
workflow_can()
-
Повертає
true
, якщо даний об'єкт може виконати даний перехід. workflow_transitions()
- Повертає масив з усіма переходами, включеними в даному об'єкті.
workflow_marked_places()
- Повертає масив з іменами місць даного маркування.
workflow_has_marked_place()
-
Повертає
true
, якщо маркування даного об'єкта має даний стан. - .. versionadded:: 3.3
-
Функції
workflow_marked_places()
таworkflow_has_marked_place()
були представлені в Symfony 3.3.
Наступний приклад ілюструє ці функції в дії:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
<h3>Actions</h3>
{% if workflow_can(post, 'publish') %}
<a href="...">Publish article</a>
{% endif %}
{% if workflow_can(post, 'to_review') %}
<a href="...">Submit to review</a>
{% endif %}
{% if workflow_can(post, 'reject') %}
<a href="...">Reject article</a>
{% endif %}
{# Або закільцювати через включені переходи #}
{% for transition in workflow_transitions(post) %}
<a href="...">{{ transition.name }}</a>
{% else %}
No actions available.
{% endfor %}
{# Перевірити, чи знаходиться обʼєкт в якомусь особливому місці #}
{% if workflow_has_marked_place(post, 'to_review') %}
<p>This post is ready for review.</p>
{% endif %}
{# Перевірити, чи було якесь місце марковано в обʼєкті #}
{% if 'waiting_some_approval' in workflow_marked_places(post) %}
<span class="label">PENDING</span>
{% endif %}