1
+ <?php
2
+ namespace cornernote \workflow \manager \components ;
3
+
4
+ use raoul2000 \workflow \base \Status ;
5
+ use raoul2000 \workflow \base \Transition ;
6
+ use raoul2000 \workflow \base \Workflow ;
7
+ use raoul2000 \workflow \base \WorkflowException ;
8
+ use raoul2000 \workflow \source \IWorkflowSource ;
9
+ use Yii ;
10
+ use yii \base \Object ;
11
+ use yii \helpers \Inflector ;
12
+
13
+ /**
14
+ * WorkflowDbSource component is dedicated to read workflow definition from DB.
15
+ *
16
+ * It doesn't implement many features available in the WorkflowFileSource component
17
+ * released with yii2-workflow but can be used as a starting point to develop
18
+ * a production ready component.
19
+ *
20
+ * Among missing features :
21
+ * - Status, Transition and Workflow class mapping to allow usage of custom classes for those objects
22
+ * - metadata
23
+ * - short id usage (in this version canonical status ids must be used)
24
+ * - robust test for method arguments (in particular ids)
25
+ *
26
+ * The underlying DB schema is also very simple and only include those columns
27
+ * which are required by yii2-workflow.
28
+ *
29
+ */
30
+ class WorkflowDbSource extends Object implements IWorkflowSource
31
+ {
32
+ const SEPARATOR_STATUS_NAME = '/ ' ;
33
+ /**
34
+ * @var Workflow[] list of workflow instances indexed by workflow id
35
+ */
36
+ private $ _w = [];
37
+ /**
38
+ * @var Status[] list status instances indexed by their id
39
+ */
40
+ private $ _s = [];
41
+ private $ _allStatusLoaded = false ;
42
+ /**
43
+ * @var Transition[] list of out-going Transition instances indexed by the start status id
44
+ */
45
+ private $ _t = [];
46
+
47
+ /**
48
+ * @see \raoul2000\workflow\source\IWorkflowSource::getStatus()
49
+ * @param mixed $id
50
+ * @param null $model
51
+ * @return Status|\raoul2000\workflow\base\StatusInterface
52
+ * @throws WorkflowException
53
+ * @throws \yii\base\InvalidConfigException
54
+ */
55
+ public function getStatus ($ id , $ model = null )
56
+ {
57
+ list ($ wId , $ stId ) = $ this ->parseStatusId ($ id );
58
+
59
+ $ canonicalStId = $ wId . self ::SEPARATOR_STATUS_NAME . $ stId ;
60
+
61
+ // TODO : implement status class map
62
+ if (!array_key_exists ($ canonicalStId , $ this ->_s )) {
63
+
64
+ $ statusModel = \cornernote \workflow \manager \models \Status::findOne ([
65
+ 'workflow_id ' => $ wId ,
66
+ 'id ' => $ stId
67
+ ]);
68
+ if ($ statusModel == null ) {
69
+ throw new WorkflowException ('No status found with id ' . $ id );
70
+ }
71
+ $ this ->_s [$ canonicalStId ] = Yii::createObject ([
72
+ 'class ' => 'raoul2000\workflow\base\Status ' ,
73
+ 'id ' => $ canonicalStId ,
74
+ 'workflowId ' => $ statusModel ->workflow_id ,
75
+ 'label ' => isset ($ statusModel ->label ) ? $ statusModel ->label : Inflector::camel2words ($ stId , true ),
76
+ 'source ' => $ this
77
+ ]);
78
+ }
79
+ return $ this ->_s [$ canonicalStId ];
80
+ }
81
+
82
+ /**
83
+ * @see \raoul2000\workflow\source\IWorkflowSource::getAllStatuses()
84
+ * @param string $workflowId
85
+ * @return \raoul2000\workflow\base\Status[]|\raoul2000\workflow\base\StatusInterface[]
86
+ * @throws \yii\base\InvalidConfigException
87
+ */
88
+ public function getAllStatuses ($ workflowId )
89
+ {
90
+ if (!$ this ->_allStatusLoaded ) {
91
+
92
+ $ loadedStatusIds = array_keys ($ this ->_s );
93
+
94
+ $ dbStatus = \cornernote \workflow \manager \models \Status::find ()
95
+ ->where (['workflow_id ' => $ workflowId ])
96
+ ->andWhere (['NOT IN ' , 'id ' , $ loadedStatusIds ])
97
+ ->all ();
98
+
99
+ foreach ($ dbStatus as $ status ) {
100
+ $ canonicalStId = $ status ->workflow_id . self ::SEPARATOR_STATUS_NAME . $ status ->id ;
101
+
102
+ $ this ->_s [$ canonicalStId ] = Yii::createObject ([
103
+ 'class ' => 'raoul2000\workflow\base\Status ' ,
104
+ 'id ' => $ canonicalStId ,
105
+ 'workflowId ' => $ status ->workflow_id ,
106
+ 'label ' => isset ($ status ->label ) ? $ status ->label : Inflector::camel2words ($ status ->id , true ),
107
+ 'source ' => $ this
108
+ ]);
109
+ }
110
+ $ this ->_allStatusLoaded = true ;
111
+ }
112
+ return $ this ->_s ;
113
+ }
114
+
115
+ /**
116
+ * @see \raoul2000\workflow\source\IWorkflowSource::getTransitions()
117
+ * @param mixed $startStatusId
118
+ * @param null $model
119
+ * @return Transition|\raoul2000\workflow\base\TransitionInterface[]
120
+ * @throws WorkflowException
121
+ * @throws \yii\base\InvalidConfigException
122
+ */
123
+ public function getTransitions ($ startStatusId , $ model = null )
124
+ {
125
+ list ($ wId , $ stId ) = $ this ->parseStatusId ($ startStatusId );
126
+ $ startId = $ wId . self ::SEPARATOR_STATUS_NAME . $ stId ;
127
+
128
+ if (!array_key_exists ($ startId , $ this ->_t )) {
129
+
130
+ $ transInstance = [];
131
+ $ transitions = \cornernote \workflow \manager \models \Transition::findAll ([
132
+ 'start_status_id ' => $ stId ,
133
+ 'start_status_workflow_id ' => $ wId
134
+ ]);
135
+ foreach ($ transitions as $ transition ) {
136
+ // TODO : implement transition class map
137
+ $ endId = $ transition ->end_status_workflow_id . self ::SEPARATOR_STATUS_NAME . $ transition ->end_status_id ;
138
+ $ transInstance [] = Yii::createObject ([
139
+ 'class ' => 'raoul2000\workflow\base\Transition ' ,
140
+ 'start ' => $ this ->getStatus ($ startId ),
141
+ 'end ' => $ this ->getStatus ($ endId ),
142
+ 'source ' => $ this
143
+ ]);
144
+ }
145
+ $ this ->_t [$ startId ] = $ transInstance ;
146
+ }
147
+ return $ this ->_t [$ startId ];
148
+ }
149
+
150
+ /**
151
+ * @see \raoul2000\workflow\source\IWorkflowSource::getTransition()
152
+ * @param string $startId
153
+ * @param string $endId
154
+ * @param null $defaultWorkflowId
155
+ * @return null|\raoul2000\workflow\base\TransitionInterface
156
+ */
157
+ public function getTransition ($ startId , $ endId , $ defaultWorkflowId = null )
158
+ {
159
+ $ tr = $ this ->getTransitions ($ startId , $ defaultWorkflowId );
160
+ if (count ($ tr ) > 0 ) {
161
+ foreach ($ tr as $ aTransition ) {
162
+ if ($ aTransition ->getEndStatus ()->getId () == $ endId ) {
163
+ return $ aTransition ;
164
+ }
165
+ }
166
+ }
167
+ return null ;
168
+ }
169
+
170
+ /**
171
+ * @see \raoul2000\workflow\source\IWorkflowSource::getWorkflow()
172
+ * @param mixed $id
173
+ * @return Workflow|\raoul2000\workflow\base\WorkflowInterface
174
+ * @throws WorkflowException
175
+ * @throws \yii\base\InvalidConfigException
176
+ */
177
+ public function getWorkflow ($ id )
178
+ {
179
+
180
+ // TODO : validate that initial status is valid
181
+ // TODO : implement status class map
182
+
183
+ if (!array_key_exists ($ id , $ this ->_w )) {
184
+ $ workflowModel = \cornernote \workflow \manager \models \Workflow::findOne ([
185
+ 'id ' => $ id
186
+ ]);
187
+
188
+ if ($ workflowModel == null ) {
189
+ throw new WorkflowException ('No workflow found with id ' . $ id );
190
+ }
191
+ $ initialStatusId = $ workflowModel ->id . self ::SEPARATOR_STATUS_NAME . $ workflowModel ->initial_status_id ;
192
+ $ this ->_w [$ id ] = Yii::createObject ([
193
+ 'class ' => 'raoul2000\workflow\base\Workflow ' ,
194
+ 'id ' => $ id ,
195
+ 'initialStatusId ' => $ initialStatusId ,
196
+ 'source ' => $ this
197
+ ]);
198
+ }
199
+ return $ this ->_w [$ id ];
200
+ }
201
+
202
+ /**
203
+ *
204
+ * @param string $val canonical id (e.g. myWorkflow/myStatus)
205
+ * @return array:
206
+ */
207
+ public function parseStatusId ($ val )
208
+ {
209
+
210
+ // TODO : validate $val and once splitted in workflow_id and status_id
211
+ // ensure they are both valid
212
+
213
+ $ tokens = array_map ('trim ' , explode (self ::SEPARATOR_STATUS_NAME , $ val ));
214
+ return $ tokens ;
215
+ }
216
+ }
0 commit comments