Skip to content

Commit

Permalink
Merge branch 'master' of git://github.com/raoul2000/yii2-workflow.git
Browse files Browse the repository at this point in the history
  • Loading branch information
raoul2000 committed Jul 3, 2015
2 parents 7ed1c4d + 570cb5a commit bac4557
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 9 deletions.
9 changes: 8 additions & 1 deletion CHANGE.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
#version 0.0.14
- improve leave workflow management

The action to **delete** the owner model is now considered as *leaving the workflow* : the leave workflow event
sequence is fired. Previously, the only way for a model to leave a workflow was by assigning NULL to the status attribute and saving
the model (or by calling `sendToStatus(null)`);

#version 0.0.13
- the *SimpleWorkflowBehavior* can now be safely attached to any object that inherits from \yii\base\Object.

**warning** : The *SimpleWorkflowBehavior* has been first designed to be attached to an `ActiveRecord` instance and thus integrates in the life cycle
of such objects. By installing event handlers on various `ActiveRecord` events, it automatically handles status persistence and restore. If the behavior
of such objects. By installing event handlers on various `ActiveRecord` events, it automatically handles status persistence. If the behavior
is attached to another type of object, the developer must understand and (possibly) implement all the features that otherwise would be already available.


Expand Down
39 changes: 31 additions & 8 deletions src/base/SimpleWorkflowBehavior.php
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,8 @@ public function events()
ActiveRecord::EVENT_BEFORE_UPDATE => 'beforeSaveStatus',
ActiveRecord::EVENT_AFTER_UPDATE => 'afterSaveStatus',
ActiveRecord::EVENT_AFTER_INSERT => 'afterSaveStatus',
ActiveRecord::EVENT_BEFORE_DELETE => 'beforeDelete',
ActiveRecord::EVENT_AFTER_DELETE => 'afterDelete',
];
}
/**
Expand Down Expand Up @@ -376,6 +378,24 @@ public function beforeSaveStatus($event)
$event->isValid = $this->sendToStatusInternal($this->getOwnerStatus(), true);
}

/**
* Handle the case where the owner model is leaving the workflow
* @param yii\base\Event $event
*/
public function beforeDelete($event)
{
$event->isValid = $this->sendToStatusInternal(null, true);
}

/**
* Fires pending events, once the owner model has been successfully deleted.
*
* @param yii\base\Event $event
*/
public function afterDelete($event)
{
$this->firePendingEvents();
}
/**
* Send the owner model into the status passed as argument.
*
Expand All @@ -397,20 +417,23 @@ public function sendToStatus($status)
/**
* Performs status change and event fire.
*
* This method is called when the status is changed during a save event, or directly through the sendToStatus method.
* Based on the current value of the owner model status attribute, and the behavior status, it checks if a transition
* This method is called when the owner model is about to change status. This occurs when it is saved, deleted, or when
* a call is done to `sendToStatus()`
*
* Based on the current value of the owner model status attribute and the internal behavior status, it checks if a transition
* is about to occur. If that's the case, this method fires all "before" events provided by the event sequence component
* and then updates status attributes values (both internal and at the owner model level).
* Finallly it fires the "after" events, or if we are in a save operation, store them for as pending events that are fired
* on the *afterSave" event.
* Finallly it fires the "after" events, or if we are in a save or delete operation, store them as pending events that are fired
* on the *afterSave" or afterDelete event.
* Note that if an event handler attached to a "before" event sets the event instance as invalid, all remaining handlers
* are ingored and the method returns immediately.
*
* @param unknown $status
* @param boolean $onSave
* @param mixed $status the target status or NULL when leaving the workflow
* @param boolean $delayed if TRUE, all 'after' events are not fire but stored for being fired in AfterSave or afterDelete. This occurs
* when the transition is performed on a save or delete action.
* @return boolean
*/
private function sendToStatusInternal($status, $onSave)
private function sendToStatusInternal($status, $delayed)
{
$this->_pendingEvents = [];

Expand Down Expand Up @@ -439,7 +462,7 @@ private function sendToStatusInternal($status, $onSave)
$this->setStatusInternal($newStatus);

if ( ! empty($events['after']) ) {
if ( $onSave ) {
if ( $delayed ) {
$this->_pendingEvents = $events['after'];
} else {
foreach ($events['after'] as $eventAfter) {
Expand Down
24 changes: 24 additions & 0 deletions tests/codeception/unit/models/Item06Behavior.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ class Item06Behavior extends Behavior
public $corrected = false;
public $canBeArchived = false;

public $canLeaveWorkflow = true;

public static $maxPostCount = 2;
public static $countPost = 0;
public static $countPostToCorrect = 0;
public static $countPostCorrected = 0;
public static $countLeaveWorkflow = 0;

public function events()
{
Expand All @@ -26,8 +29,25 @@ public function events()
WorkflowEvent::beforeLeaveStatus('Item06Workflow/correction') => "postCorrected",
WorkflowEvent::beforeEnterStatus('Item06Workflow/published') => "checkCanBePublished",
WorkflowEvent::beforeChangeStatus('Item06Workflow/published', 'Item06Workflow/archive') => "canBeArchived",
WorkflowEvent::beforeLeaveWorkflow('Item06Workflow') => 'beforeLeaveWorkflow',
WorkflowEvent::afterLeaveWorkflow('Item06Workflow') => 'afterLeaveWorkflow',
];
}
public function afterLeaveWorkflow($event)
{
self::$countLeaveWorkflow++;
}
public function beforeLeaveWorkflow($event)
{
if( $this->canLeaveWorkflow == false) {
$event->invalidate('item cannot be deleted');
return false;
} else {
return true;
}
}


public function beforeNew($event)
{
if(self::$countPost >= self::$maxPostCount) {
Expand Down Expand Up @@ -74,4 +94,8 @@ public function markAsCandidateForArchive()
{
$this->canBeArchived = true;
}
public function canLeaveWorkflow($bool)
{
$this->canLeaveWorkflow = $bool;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,60 @@ protected function tearDown()
parent::tearDown();
}

public function testLeaveWorkflowOnAssignNULL()
{
$post = new Item06();
$post->name ='post name';
$post->enterWorkflow();

verify($post->save())->true();
verify($post->getWorkflowStatus()->getId())->equals('Item06Workflow/new');
$post->status = null;

Item06Behavior::$countLeaveWorkflow= 0;
expect($post->save())->equals(true);
verify(Item06Behavior::$countLeaveWorkflow)->equals(1);

$post->enterWorkflow();
verify($post->save())->true();
verify($post->getWorkflowStatus()->getId())->equals('Item06Workflow/new');

$post->canLeaveWorkflow(false);

$post->status = null;
expect_not($post->save());
}

public function testLeaveWorkflowOnDelete()
{
$post = new Item06();
$post->name ='post name';
$post->enterWorkflow();

verify($post->save())->true();
verify($post->getWorkflowStatus()->getId())->equals('Item06Workflow/new');

Item06Behavior::$countLeaveWorkflow= 0;
$post->canLeaveWorkflow(true);
expect_that($post->delete());
verify(Item06Behavior::$countLeaveWorkflow)->equals(1);


$post = new Item06();
$post->name ='post name';
$post->enterWorkflow();

verify($post->save())->true();
verify($post->getWorkflowStatus()->getId())->equals('Item06Workflow/new');


$post->canLeaveWorkflow(false); // refuse leave workflow

// Now, the handler attached to the beforeLeaveWorkflow Event (see Item06Behavior)
// will invalidate the event and return false (preventing the DELETE operation)
expect_not($post->delete());
}

public function testEnterWorkflowSuccess()
{
$post = new Item06();
Expand Down

0 comments on commit bac4557

Please sign in to comment.