この記事は Symfony Advent Calendar 2016 15日目の記事です。
はじめに
ステータスを管理する時などに便利なSymfony/Workflowコンポーネントを紹介したいと思います。
インストール
Composerで楽々インストール出来ます。
$ composer require symfony/workflow
今回のゴール
以下のPull Requestのワークフローのステータス管理を実現したいと思います。
準備
PullRequestクラスの作成
<?php
class PullRequest {
private $marking = 'ready';
public function getMarking()
{
return $this->marking;
}
public function setMarking($marking)
{
$this->marking = $marking;
}
}
ステータス情報を保持するmarking
プロパティだけを持ったシンプルなクラスになります。
marking
プロパティは、自分で任意の名前に変更可能です。
ちなみにGetter
とSetter
が無いと怒られます。
Placeの定義
今回のステータス情報に当たるものは、WorkflowコンポーネントではPlace
と表現されています。
<?php
// use Symfony\Component\Workflow\DefinitionBuilder;
$builder = new DefinitionBuilder();
$builder->addPlaces(['ready', 'wip', 'in-review', 'merged']);
DefinitionBuilder::addPlaces()
メソッドで、ready
, wip
, in-review
, merged
の4つのステータスを定義していることになります。
Transitionの定義
どのようにステータス(Place
)が遷移していくかをTransition
クラスで定義します。
<?php
// use Symfony\Component\Workflow\Transition;
$builder->addTransition(new Transition('start-work', 'ready', 'wip'));
$builder->addTransition(new Transition('request-review', 'wip', 'in-review'));
$builder->addTransition(new Transition('feedback', 'in-review', 'wip'));
$builder->addTransition(new Transition('merge', 'in-review', 'merged'));
今回の場合は、start-work
Transitionは、ready
からwip
Placeへの遷移を表現していることになります。
Workflow(StateMachine)インスタンスの作成
Workflow
(StateMachine
)のインスタンスを作成します。
今回はStateMachine
を作成しています。
<?php
// use Symfony\Component\Workflow\DefinitionBuilder;
// use Symfony\Component\Workflow\Transition;
// use Symfony\Component\Workflow\StateMachine;
$builder = new DefinitionBuilder();
$builder->addPlaces(['ready', 'wip', 'in-review', 'merged']);
$builder->addTransition(new Transition('start-work', 'ready', 'wip'));
$builder->addTransition(new Transition('request-review', 'wip', 'in-review'));
$builder->addTransition(new Transition('feedback', 'in-review', 'wip'));
$builder->addTransition(new Transition('merge', 'in-review', 'merged'));
$definition = $builder->build();
$workflow = new StateMachine($definition);
WorkflowとStateMachineの違い
デフォルトでセットされるMarkingStore
が異なるだけで他に違いはありません。
特にMarkingStore
を指定しない場合、StateMachine
ではSingleMarkingStore
がセットされ、Workflow
ではMultipleMarkingStore
がセットされます。
ですので、以下のようにコードを変更しても完全に同じ振る舞いをします。
-$workflow = new StateMachine($definition);
+$marking = new SingleStateMarkingStore();
+$workflow = new StateMachine($definition, $marking);
SingleMarkingStoreとMultipleMarkingStoreの違い
SingleMarkingStore
は名前の通りmarking
に値を1つしか保持出来ませんが、MultipleMarkingStore
は同時に複数保持することが可能です。
今回の場合は1つで事足りると思いますので、SingleMarkingStore
をデフォルトで採用しているStateMachine
を利用しています。
実行
それでは実行してみます。
<?php
$pr = new PullRequest();
$workflow->can($pr, 'hoge'); // Thow Symfony\Component\Workflow\Exception\LogicException
$workflow->can($pr, 'merge'); // False
$workflow->apply($pr, 'merge'); // Thow Symfony\Component\Workflow\Exception\LogicException
$workflow->can($pr, 'start-work'); // True
$workflow->apply($pr, 'start-work');
$workflow->can($pr, 'request-review'); // True
$workflow->getEnabledTransitions($pr); // ['feedback', 'merge']
定義したルールに添っていないと例外が投げられたり、False
が返ってきたりして分かりやすいですね。
ちなみにTwig_Extension
も用意されているので、
{% if workflow_can(pr, 'merge') %}
<a href="...">Merge</a>
{% endif %}
みたいに書くことも出来ます、便利ですね!
おわりに
ステータス管理にSymfony/Workflowコンポーネントを利用すると「どのように遷移すべきか」「どういった遷移を想定しているか」を明示的に定義することが出来ます。
特に外部サービスのAPIなどを利用している場合は、想定外のレスポンスやステータス遷移を引き起こしてしまう可能性もあるかと思いますので、このように宣言的に設定出来るのは良いプラクティスではないでしょうか。
Symfonyで使う場合は、ワークフローの定義をYamlファイルなどの外部ファイルで簡単に管理出来ますので、一度試してみては如何でしょうか。
補足資料
最終的に実行したコード