forked from raoul2000/simpleWorkflow
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSWNode.php
273 lines (252 loc) · 8.47 KB
/
SWNode.php
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
<?php
namespace niekoost\simpleWorkflow;
/**
* This class implements a graph node for the simpleWorkflow extension.
*/
class SWNode extends \yii\base\Component
{
/**
* @var string workflow identifier
*/
private $_workflowId;
/**
* @var string node identifier which must be unique within the workflow
*/
private $_id;
/**
* @var string user friendly node name. If not provided at construction, the string
* 'workflowId/nodeId' will be used.
*/
private $_label;
/**
* @var string expression evaluated in the context of an CActiveRecord object. It must returns
* a boolean value that is used to allow access to this node.
*/
private $_constraint = array();
/**
* @var array
*/
private $_metadata = array();
/**
* @var array array of transitions that exist between this node and other nodes
*/
private $_tr=array();
/**
* Creates a workflow node instance.
* If no workflowId is specified in the nodeId, then the $defaultWorkflowId is used.<br/>
* Note that both workflow and node id must begin with a alphabetic character followed by aplha-numeric
* characters : all other characters are not accepted and cause an exception to be thrown (see {@link SWNode::parseNodeId()})
*
* @param mixed $node If a string is passed as argument, it can be both in format workflowId/NodeId
* or simply 'nodeId'. In this last case, argument $defaultWorkflowIs must be provided, otherwise it is
* ignored. <br/>
* The $node argument may also be provided as an associative array, with the following structure :<br/>
* <pre>
* {
* 'id' => string, // mandatory
* 'label' => string , // optional
* 'constraint' => string, // optional
* 'transition' => array, // optional
* 'metadata' => array, // optional
* }
* </pre>
* Again, the 'id' value may contain a workflow id (e.g 'workflowId/nodeId') but if it's not the case then
* the second argument $defaultWorkflowId must be provided.
* @param string defaultWorkflowId workflow Id that is used each time a workflow is needed to complete
* a status name.
*/
public function __construct($node, $defaultWorkflowId=null)
{
if($node==null || empty($node))
throw new SWException('illegal argument exception : $node cannot be empty', SWException::SW_ERR_CREATE_NODE);
$st=array();
if( $node instanceof SWNode )
{
// copy constructor : does not copy transitions, constraints and metadata
$this->_workflowId = $node->getWorkflowId();
$this->_id = $node->getId();
$this->_label = $node->getLabel();
$this->_metadata = $node->getMetadata();
}
else {
if( is_array($node))
{
if(!isset($node['id']))
throw new SWException('missing node id',SWException::SW_ERR_MISSING_NODE_ID);
// set node id -----------------------
$st=$this->parseNodeId($node['id'],$defaultWorkflowId);
if(isset($node['label'])){
$this->_label=$node['label'];
}
if(isset($node['constraint'])){
$this->_constraint=$node['constraint'];
}
if(isset($node['transition'])){
$this->_loadTransition($node['transition'],$st['workflow']);
}
if(isset($node['metadata'])){
$this->_metadata = $node['metadata'];
}
}
elseif(is_string($node))
{
$st=$this->parseNodeId($node,$defaultWorkflowId);
}
if(!Array_key_existS('workflow', $st)) {
throw new \Exception('workflow missing');
}
$this->_workflowId = $st['workflow'];
$this->_id = $st['node'];
if(!isset($this->_label))
$this->_label=$this->_id;
}
}
/**
* Parse a status name and return it as an array. The string passed as argument
* may be a complete status name (e.g workflowId/nodeId) and if no workflowId is
* specified, then an exception is thrown. Both workflow and node ids must match
* following pattern:
* <pre>
* /^[[:alpha:]][[:alnum:]_]*$/
* </pre>
* For instance :
* <ul>
* <li>ready : matches</li>
* <li>to_process : matches</li>
* <li>priority_1 : matches</li>
* <li>2_level : does not match</li>
* <li>to-production : does not match</li>
* <li>enable/disable : does not match</li>
*</ul>
* @param string status status name (wfId/nodeId or nodeId)
* @return array the complete status (e.g array ( [workflow] => 'a' [node] => 'b' ))
*/
public function parseNodeId($status,$workflowId)
{
$nodeId=$wfId=null;
if(strstr($status,'/')){
if(preg_match('/^([[:alpha:]][[:alnum:]_]*)\/([[:alpha:]][[:alnum:]_]*)$/',$status,$matches) == 1){
$wfId = $matches[1];
$nodeId = $matches[2];
}
}
else{
if(preg_match('/^[[:alpha:]][[:alnum:]_]*$/',$status) == 1){
$nodeId = $status;
if(preg_match('/^[[:alpha:]][[:alnum:]_]*$/',$workflowId) == 1){
$wfId = $workflowId;
}
}
}
if( $wfId == null || $nodeId == null){
throw new SWException('failed to create node from node Id = '.$status.', workflow Id = '.$workflowId, SWException::SW_ERR_CREATE_NODE);
}
return array('workflow'=>$wfId,'node'=>$nodeId);
}
/**
* Overrides the default magic method defined at the CComponent level in order to
* return a metadata value if parent method fails.
*
* @see CComponent::__get()
*/
public function __get($name)
{
try{
return parent::__get($name);
}catch(CException $e){
if(isset($this->_metadata[$name])){
return $this->_metadata[$name];
}else{
throw new SWException('Property "'.$name.'" is not found.',SWException::SW_ERR_ATTR_NOT_FOUND);
}
}
}
/**
* Loads the set of transitions passed as argument.
*
* @param mixed $tr if provided as a string, it is a comma separated list of SWNodes id,
* This list can also be provided as an array
* @param string $defWfId Default workflow Id if nodes have no workflow id, this value is used
* as their workflow id.
*/
private function _loadTransition($tr, $defWfId)
{
if( is_string($tr))
{
$trAr=explode(',',$tr);
foreach($trAr as $aTr)
{
$objNode=new SWNode(trim($aTr),$defWfId);
$this->_tr[$objNode->toString()]=null;
}
}
elseif( is_array($tr))
{
foreach($tr as $key => $value){
if( is_string($key)){
$objNode=new SWNode(trim($key),$defWfId);
if($value!=null)
$this->_tr[$objNode->toString()]=$value;
else
$this->_tr[$objNode->toString()]=null;
}else {
$objNode=new SWNode(trim($value),$defWfId);
$this->_tr[$objNode->toString()]=null;
}
}
}else {
throw new SWException(__FUNCTION__. 'incorrect arg type : string or array expected');
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// accessors
public function getWorkflowId() {return $this->_workflowId;}
public function getId() {return $this->_id;}
public function getLabel() {return $this->_label;}
public function getNext() {return $this->_tr;}
public function getConstraint() {return $this->_constraint;}
public function getMetadata() {return $this->_metadata;}
public function getNextNodeIds() {return array_keys($this->_tr);}
/**
* @returns String the task for this transition or NULL if no task is defined
* @param mixed $endNode SWNode instance or string that will be converted to SWNode instance (e.g 'workflowId/nodeId')
* @throws SWException
*/
public function getTransitionTask($endNode){
if( ! $endNode instanceof SWNode ){
$endNode = new SWNode($endNode, $this->getWorkflowId());
}
$endNodeId = $endNode->toString();
return ( isset($this->_tr[$endNodeId])
? $this->_tr[$endNodeId]
: null
);
}
public function __toString(){
return $this->getWorkflowId().'/'.$this->getId();
}
public function toString(){
return $this->__toString();
}
/**
* SWnode comparator method. Note that only the node and the workflow id
* members are compared.
*
* @param mixed SWNode object or string. If a string is provided it is used to create
* a new SWNode object.
*/
public function equals($status){
if( $status instanceof SWNode )
{
return $status->toString() == $this->toString();
}
else try{
$other=new SWNode($status,$this->getWorkflowId());
return $other->equals($this);
}catch(Exception $e)
{
throw new SWException('comparaison error - the value passed as argument (value='.$status.') cannot be converted into a SWNode',$e->getCode());
}
}
}
?>