raoul2000 / yii2-workflow

A simple workflow engine for Yii2
BSD 3-Clause "New" or "Revised" License
171 stars 48 forks source link

WorkflowDbSource #13

Closed feor58 closed 8 years ago

feor58 commented 9 years ago

Hi! I would like a WorkflowDbSource class. Which files should extend according to best practice?

Thanks.

raoul2000 commented 9 years ago

Hi, What you should do is to implement the IWorkflowSource interface and extend yii\base\Object, You can use WorkflowFileSource as an example. Then next step is to create and return the three basic objects types :

You can also create your own basic classes for these 3 ones by simply implementing StatusInterface, TransitionInterface and WorkflowInterface.

If you have other questions, feel free to ask ...

ciao

philippfrenzel commented 9 years ago

Hi, if you have a db source up an running i would appreciate it if you share?!

feor58 commented 9 years ago

Hi Raoul, Thanks for your quick answer. I see the next schema: class WorkflowSource (new class) implements iWorkflowSource class WorkflowFileSource extends WorkflowSource class WorkflowDbSource (this is my target class) extends WorkflowSource What is your opion from this? Thanks.

feor58 commented 9 years ago

WorkflowSource an abstract class, of course.

raoul2000 commented 9 years ago

Yes, it seems ok ... however, I don't understand why you need this WorkflowSource abstract class and why not create WorkflowDbSource to implement the IWorkflowSource interfrace ?


use yii\base\Object;
use raoul2000\workflow\source\IWorkflowSource;
use raoul2000\workflow\base\Status;
use raoul2000\workflow\base\Transition;
use raoul2000\workflow\base\Workflow;
use raoul2000\workflow\base\WorkflowException;

class WorkflowDbSource extends Object implements IWorkflowSource
{
// your DB implementation here ....
}
feor58 commented 9 years ago

Yes, I understand this, however, i sew my WorkflowDbSource class have nearly same methods as the WorkflowFileSource class. That's why i thought the abstract WorkflowSource class. At the same time WorkflowFileSource and WorkflowDbSource are logically same level for me, so i didn't want to extend the WorkflowFileSource class.

raoul2000 commented 9 years ago

i sew my WorkflowDbSource class have nearly same methods as the WorkflowFileSource class.

That is correct, they should both implementing the IWorkflowSource interface.

At the same time WorkflowFileSource and WorkflowDbSource are logically same level for me

That's correct again, they are at the same level, they both implement the IWorkflowSource interface, and that's why In my opinion there is no need for an intermediate absrtact class, but that's up to you ....

feor58 commented 9 years ago

I see WorkflowFileSource class have further methods beyond IWorkflowSource methods which are nearly same as in my planned WorkflowDbSource class. These are: getDefinitionLoader() ??? getDefinitionCache() getClassMap() getClassMapByType() parseStatusId() isValidStatusId() isValidWorkflowId() isValidStatusLocalId() addWorkflowDefinition() validateWorkflowDefinition() I think to put these methods into WorkflowSource class. Ultimately, in my WorkflowDbSource.php can be class WorkflowDbSource extends WorkflowSource implements IWorkflowSource {} Of course the following is possible, too class WorkflowDbSource extends WorkflowFileSource, however because my opion WorkflowFileSource and WorkflowDbSource are same level so the former is more friendly for me. What think you?

feor58 commented 9 years ago

I suggest the following file structure:

.../src/source/ IWorkflowDefinitionProvider.php IWorkflowSource.php WorkflowArrayParser.php WorkflowDefinitionLoader.php WorkflowSource.php

.../src/source/file/ GraphmlLoader.php PhpArrayLoader.php PhpClassLoader.php WorkflowFileSource.php

.../src/source/db/ DbLoader.php WorkflowDbSource.php

raoul2000 commented 9 years ago

ok I understand now .

However, in your proposed structure I don't think that IWorkflowDefinitionProvider, WorkflowArrayParserand WorkflowDefinitionLoadershould be moved to src/source. These classes and interfaces are very specific to the WorkflowFileSourceand related components. They are responsible for loading file (plain php, php class or gaphml files) and parse the workflow definition into a format expected by WorkflowFileSource. For a DB source, they are not needed ...

Again, this is my opinion but if you are more comfortable with this structure, go with it !!

feor58 commented 9 years ago

Thanks for the useful info. I thought forward. I am able to get the same definition array from database as that PhpArrayLoader provide. I think from here my way would be same (parsing, getting obejcts - Workflow, Status, Transition) as in case of WorkflowFileSource. I find the class structure for this. I wouldn't like to copy the full WorkflowFileSource class into WorkflowDbSource. Help me, please.

raoul2000 commented 9 years ago

do you mean that you want to store in DB, the workflow definition as PHP array ? ... you want to replace file storage with DB storage but keep the same structure ?

feor58 commented 9 years ago

My base tables are: CREATE TABLE IF NOT EXISTS w_workflows ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL, status varchar(50) NOT NULL, is_systemtype tinyint(1) NOT NULL DEFAULT '1', initial_node varchar(50) NOT NULL, max_open_instances tinyint(3) DEFAULT NULL, ngsf_tid bigint(20) unsigned DEFAULT NULL, ngsf_arid bigint(20) unsigned DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk (ngsf_tid,ngsf_arid,name), KEY ngsf_arid (ngsf_arid), KEY ngsf_tid (ngsf_tid) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='{"alias":"w"}' AUTO_INCREMENT=1 ; CREATE TABLE IF NOT EXISTS w_nodes ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, wid bigint(20) unsigned NOT NULL, name varchar(50) NOT NULL, label varchar(40) DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk (wid,name), KEY wid (wid) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='{"alias":"wn"}' AUTO_INCREMENT=1 ; CREATE TABLE IF NOT EXISTS w_edges ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, wnid_from bigint(20) unsigned DEFAULT NULL, wnid_to bigint(20) unsigned DEFAULT NULL, label varchar(40) DEFAULT NULL, menu_url varchar(128) DEFAULT NULL, menu_linkoptions varchar(128) DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk (wnid_from,wnid_to), KEY wtnid_from (wnid_from), KEY wnid_to (wnid_to) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='{"alias":"we"}' AUTO_INCREMENT=1 ;

Yes, from these tables i can to build the same php definition array as that the PhpArrayLoader produce (this is the DbLoader class).

raoul2000 commented 9 years ago

I guesses these table are designed to meet your specific needs as there are plenty of columns which are not required by yii2-workflow. I think that creating a PHP array from table rows (with your DbLoader) and pass it to the workflow source is not the simplest way to go, but it can work fine.

feor58 commented 9 years ago

i don't understand what would be even simpler?

raoul2000 commented 9 years ago

ok, I have created an example of something simple to implement WorkflowDbSource. It took me less than 2 hours so you can ilmagine it is not ready to be used in production, however it's a good prototype of how I would implement a WorkflowDbSource component. I ran simple tests with no problem.

The DB schema is basic :

db schema

You can find the source code in Gist :

The main component is the WorkflowDbsource

It is meant to be a generic implementation and it misses many features that a production-ready component should include (optimisation and verifications), but as you can see, I just had to implement the IWorkflowSource interface to be able to use DB as a source (I've used 3 models generated automatically by Gii to avoid writting SQL queries .. yes I'm lazy sometime).

I hope it will clarify what I mean by simpler, but remember that I don't know anything about your specific requirements and so I don't have the same problem to solve thay you may have with your (future) app.

:sunglasses:

ps: I have also created a UML schema so you can see how yii2-workflow classes architecture. Hope it helps ...

feor58 commented 9 years ago

Yes, it helps. This is a very cool comment for a very cool project. Thanks.

philippfrenzel commented 8 years ago

Hi, as I would need this for a project, did you enhance that dbsource? I would be interested in how you manage the ui in detail? ;)

philippfrenzel commented 8 years ago

Hey, thanks for reopening! Have a pretty raw version of the management because of your sample code... actually if you can support this - i would at least invite you to a wine!

raoul2000 commented 8 years ago

Hi, as I don't currently need such component I didn't enhaced it up to now (and I don't know if @feor58 did something about it).

Regarding the UI, same thing .... I played a little with visjs on yii2-workflow-view but nothing related with the DB up to now.

Of course I'm ready to help/support (and not only because of the wine :smirk: ) so fell free to ask...

philippfrenzel commented 8 years ago

Thanks a lot - i just put the latest version of the module online - open - would be great if @feor58 has something done already! And thanks for the workflow view! it's awesome and great for "sales" ;)

Repo:

https://github.com/FrenzelGmbH/cm-workflow

philippfrenzel commented 8 years ago

So, after setting all up, I tried to include the workflow like this:

public function behaviors()
    {
        return [
            TimestampBehavior::className(),
            'softDeleteBehavior' => [
                'class' => SoftDeleteBehavior::className(),
                'softDeleteAttributeValues' => [
                    'deleted_at' => time()
                ],
                'replaceRegularDelete' => true,
            ],
            'customerWorkflow' => [            
                'class' => \raoul2000\workflow\base\SimpleWorkflowBehavior::className(),
                'source' => \app\modules\workflow\components\WorkflowDbSource::className(),
            ]
        ];
    }

But it fails with message: image

How do I need to "assign the workflow"? THANKS!

philippfrenzel commented 8 years ago

Btw: management looks like this:

image

image

image

raoul2000 commented 8 years ago

The parameter source must contain the name of an application component (the workflow source) to use with the behavior, and not (like you wrote) the class name. So what you should do is :

$config = [
    // ....
    'components' => [
        // declare your source component and give it a name
        'myDbSource' => [
          'class' => \app\modules\workflow\components\WorkflowDbSource::className()
        ]
   // ...
namespace app\models;
class MyModelextends \yii\db\ActiveRecord
{
    public function behaviors()
    {
        return [
            [
                'class' => \raoul2000\workflow\base\SimpleWorkflowBehavior::className(),
                'source' => 'myDbSource'
            ]
        ];
    }
}
raoul2000 commented 8 years ago

For a personal project I render workflow status using the bootstrap label component (the column Etat) .. I felt it thats it brings a little bit of color and readability.

2015-10-03_11-43-07

Then of course, one solution would be to CRUD the workflow through a 100% graphicla way .... with visjs for instance (but I think we already agreed that it would require quite a lot of work).

philippfrenzel commented 8 years ago

Thanks, that's awesome but how do you seperate several workflows stored within one table... e.g. CustomerWorkflow, UnitWorkflow, ProspectingWorkflow, etc...

btw. are you managing Tennis Competitions?

raoul2000 commented 8 years ago

well in my case that's true, all items in the grid belong to the same workflow .... If that's not the case, I'm afraid it is required to also display the workflow name. Maybe but 2 labels side by side (one for the workflow name, one for the status) ? ... humm I'm not sure (I have poor designer skills).

The webapp I'm working on is dedicated to manage any type of competition so it will adapt to any type of organisation (layout) : tournament, division, etc ... well, at least that's the goal. I'm making heavy uses of yii2-workflow and up to now it I'm happy with it :smile:

philippfrenzel commented 8 years ago

great app - and for the workflow, i think about passing over the "workflow id" to the dbsource so you can filter down to one of the models...?!

raoul2000 commented 8 years ago

Maybe you don't have to do that but just play with the gridview and add 2 computed columns : one for the workflow ID (e.g. CustomerWF) and another one for the status Id (e.g. Active).

Something like this :

        GridView::widget([
            'dataProvider'      => $dataProvider,
            'filterModel'       => $searchModel,
            'columns'           => [
                'id',   
                'name',
                [
                    'attribute' => 'workflow_id',
                    'format'    => 'html',
                    'value'     => function($model, $key, $index, $column) {
                        return substr($model->status,0,strpos($model->status,'/'));
                    }
                ],       
                [
                    'attribute' => 'status_id',
                    'format'    => 'html',
                    'value'     => function($model, $key, $index, $column) {
                        return substr($model->status,strpos($model->status,'/')+1);
                    }
                ],                  
                'status',   
                ['class' => 'yii\grid\ActionColumn'],
            ],
        ]); 

By modifying the ModelSearch class, you should be able to implement filtering these 2 calculated columns. For the sorting I'm not sure .. maybe sorting on a substring ?

That's just an idea and of course it requires a little bit of work to be validated as an acceptable solution ....

philippfrenzel commented 8 years ago

great concept - will add it too - any idea about the passing wfID to the dbsource? thanks for your help

On Oct 6, 2015, at 10:44 PM, raoul notifications@github.com wrote:

Maybe you don't have to do that but just play with the gridview and add 2 computed columns : one for the workflow ID (e.g. CustomerWF) and another one for the status Id (e.g. Active).

Something like this :

    GridView::widget([
        'dataProvider'      => $dataProvider,
        'filterModel'       => $searchModel,
        'columns'           => [
            'id',   
            'name',
            [
                'attribute' => 'workflow_id',
                'format'    => 'html',
                'value'     => function($model, $key, $index, $column) {
                    return substr($model->status,0,strpos($model->status,'/'));
                }
            ],       
            [
                'attribute' => 'status_id',
                'format'    => 'html',
                'value'     => function($model, $key, $index, $column) {
                    return substr($model->status,strpos($model->status,'/')+1);
                }
            ],                  
            'status',   
            ['class' => 'yii\grid\ActionColumn'],
        ],
    ]); 

By modifying the ModelSearch class, you should be able to implement filtering these 2 calculated columns. For the sorting I'm not sure .. maybe sorting on a substring ?

That's just an idea and of course it requires a little bit of work to be validated as an acceptable solution ....

— Reply to this email directly or view it on GitHub https://github.com/raoul2000/yii2-workflow/issues/13#issuecomment-145995380.

raoul2000 commented 8 years ago

Sorry I thought your question was related to display multi workflow statuses in the grid...

I don't understand why you would need to passe a workflow Id to the dbSource component... To which method do you want to pass it ? to the constructor ? Is it related to your question ("how do you seperate several workflows stored within one table ?")

philippfrenzel commented 8 years ago

Well maybe I don't get it, how can i only lookup transitions for the customer workflow if I have a unit workflow stored in the same tables? I don't wanna have a set of three tables for each workflow I might use;)

raoul2000 commented 8 years ago

Maybe that's me who don't get it :smirk: (my english skills are ... average)

A status name is made of a workflow id and a status id separated with a slash char (example : customer/active). In the three tables (_swworkflow, _swstatus, _swtransitions) you'll always find a column for workflow id, and one for status id. So for instance if you want to get all transitions for workflow customer you can write someting like :

$transitions = SwTransition::findAll([
    'start_status_workflow_id' => 'customer',
    'end_status_workflow_id' => 'customer'
]);

(Actually the example above is not appropriate if you have cross workflow transitions)

With the three tables (_swworkflow, _swstatus, _swtransitions) it should be possible to store many workflows/status/transitions. However, note that this DB schema is just one solution among others (maybe better ones).

philippfrenzel commented 8 years ago

ah, so i guess it’s my fault! the “id” is the “key” ;) sorry I’m not a good coder, so I should have had the chance to get it from the code… Thanks for your support!

On Oct 7, 2015, at 10:44 PM, raoul notifications@github.com wrote:

Maybe that's me who don't get it (my english skills are ... average)

A status name is made of a workflow id and a status id separated with a slash char (example : customer/active). In the three tables (sw_workflow, sw_status, sw_transitions) you'll always find a column for workflow id, and one for status id. So for instance if you want to get all transitions for workflow customer you can write someting like :

$transitions = SwTransition::findAll([ 'start_status_workflow_id' => 'customer', 'end_status_workflow_id' => 'customer' ]); (Actually the example above is not appropriate if you have cross workflow transitions)

With the three tables (sw_workflow, sw_status, sw_transitions) it should be possible to store many workflows/status/transitions. However, note that this DB schema is just one solution among others (maybe better ones).

— Reply to this email directly or view it on GitHub https://github.com/raoul2000/yii2-workflow/issues/13#issuecomment-146322181.

raoul2000 commented 8 years ago

no problem ... :smile:

philippfrenzel commented 8 years ago

thanks for support again! ;)