/** * Save a workflow definition to the database. * * @param ezcWorkflow $workflow * @throws ezcWorkflowDefinitionStorageException */ public function save(\ezcWorkflow $workflow) { // Verify the workflow. $workflow->verify(); if (strlen($workflow->name) == 0) { throw new \ezcWorkflowDefinitionStorageException(); } $platform = $this->conn->getDatabasePlatform(); // what mode of saving should it be? Update or Re-Generate? // // Conditions that an update sufficies: // 1. No node has been deleted // 2. No node has changed its meaning (action class or type) // 3. For simplicitly only zero or one new nodes will be created. $hasExistingNodeIds = array(); $newNodes = 0; foreach ($workflow->nodes as $node) { $oid = spl_object_hash($node); if (!isset($this->nodeMap[$oid])) { $newNodes++; } else { $hasExistingNodeIds[] = $this->nodeMap[$oid]; } } $canBeUpdate = false; if ($newNodes < 2 && count(array_diff($hasExistingNodeIds, $this->workflowNodeIds[$workflow->id])) == 0 && $workflow->id) { $canBeUpdate = true; } $this->workflowNodeIds[$workflow->id] = array(); try { $this->conn->beginTransaction(); $workflowVersion = $this->getCurrentVersion($workflow->name) + 1; $this->conn->update($this->options->workflowTable(), array('workflow_outdated' => 1), array('workflow_name' => $workflow->name)); $date = new \DateTime("now"); if ($canBeUpdate) { $this->conn->update($this->options->workflowTable(), array('workflow_version' => $workflowVersion, 'workflow_created' => $date->format($platform->getDateTimeFormatString())), array('workflow_id' => $workflow->id)); } else { $data = array('workflow_name' => $workflow->name, 'workflow_version' => $workflowVersion, 'workflow_created' => $date->format($platform->getDateTimeFormatString()), 'workflow_outdated' => 0); // For sequences: get id before insert if ($platform->prefersSequences()) { $id = (int) $this->conn->fetchColumn($platform->getSequenceNextValSQL($this->options->workflowSequence())); $data['workflow_id'] = $id; $workflow->id = $id; } $this->conn->insert($this->options->workflowTable(), $data); if ($platform->prefersIdentityColumns()) { $workflow->id = (int) $this->conn->lastInsertId(); } $workflow->definitionStorage = $this; } // Write node table rows. $nodeMap = array(); foreach ($workflow->nodes as $node) { /* @var $node \ezcWorkflowNode */ $oid = spl_object_hash($node); if ($canBeUpdate && isset($this->nodeMap[$oid])) { $nodeId = (int) $this->nodeMap[$oid]; $this->conn->update($this->options->nodeTable(), array('node_configuration' => $this->options->getSerializer()->serialize($node->getConfiguration())), array('node_id' => $nodeId)); } else { $data = array('workflow_id' => (int) $workflow->id, 'node_class' => get_class($node), 'node_configuration' => $this->options->getSerializer()->serialize($node->getConfiguration())); if ($platform->prefersSequences()) { $nodeId = (int) $this->conn->fetchColumn($platform->getSequenceNextValSQL($this->options->nodeSequence())); $data['node_id'] = $nodeId; } $this->conn->insert($this->options->nodeTable(), $data); if ($platform->prefersIdentityColumns()) { $nodeId = (int) $this->conn->lastInsertId(); } } $nodeMap[$nodeId] = $node; $this->workflowNodeIds[$workflow->id][] = $nodeId; $this->nodeMap[$oid] = $nodeId; } if ($canBeUpdate) { // Delete all the node connections, NodeMap Keys are casted to (int) so usage here is safe. $query = "DELETE FROM " . $this->options->nodeConnectionTable() . " " . "WHERE incoming_node_id IN (" . implode(",", array_keys($nodeMap)) . ") OR " . "outgoing_node_id IN (" . implode(",", array_keys($nodeMap)) . ")"; $this->conn->executeUpdate($query); } foreach ($workflow->nodes as $node) { foreach ($node->getOutNodes() as $outNode) { $incomingNodeId = null; $outgoingNodeId = null; foreach ($nodeMap as $_id => $_node) { if ($_node === $node) { $incomingNodeId = $_id; } else { if ($_node === $outNode) { $outgoingNodeId = $_id; } } if ($incomingNodeId !== NULL && $outgoingNodeId !== NULL) { break; } } $data = array('incoming_node_id' => $incomingNodeId, 'outgoing_node_id' => $outgoingNodeId); if ($platform->prefersSequences()) { $id = (int) $this->conn->fetchColumn($platform->getSequenceNextValSQL($this->options->nodeConnectionSequence())); $data['id'] = $id; } $this->conn->insert($this->options->nodeConnectionTable(), $data); } } unset($nodeMap); if ($canBeUpdate) { $this->conn->delete($this->options->variableHandlerTable(), array('workflow_id' => (int) $workflow->id)); } foreach ($workflow->getVariableHandlers() as $variable => $class) { $this->conn->insert($this->options->variableHandlerTable(), array('workflow_id' => (int) $workflow->id, 'variable' => $variable, 'class' => $class)); } $this->conn->commit(); } catch (\Exception $e) { $this->conn->rollBack(); throw new \ezcWorkflowDefinitionStorageException("Error while persisting workflow: " . $e->getMessage()); } }
public function getWorkflowSchema(WorkflowOptions $options) { $schema = new \Doctrine\DBAL\Schema\Schema(); $workflowTable = $schema->createTable($options->workflowTable()); $columnOptions = $this->_handlePrimaryKey($schema, $options->workflowTable(), $options->workflowSequence()); $workflowTable->addColumn('workflow_id', 'integer', $columnOptions); $workflowTable->addColumn('workflow_name', 'string'); $workflowTable->addColumn('workflow_version', 'integer'); $workflowTable->addColumn('workflow_outdated', 'integer'); $workflowTable->addColumn('workflow_created', 'datetime'); $workflowTable->setPrimaryKey(array('workflow_id')); $workflowTable->addUniqueIndex(array('workflow_name', 'workflow_version')); $nodeTable = $schema->createTable($options->nodeTable()); $columnOptions = $this->_handlePrimaryKey($schema, $options->nodeTable(), $options->nodeSequence()); $nodeTable->addColumn('node_id', 'integer', $columnOptions); $nodeTable->addColumn('workflow_id', 'integer'); $nodeTable->addColumn('node_class', 'string'); $nodeTable->addColumn('node_configuration', 'text', array('notnull' => false, "length" => null)); $nodeTable->setPrimaryKey(array('node_id')); $nodeTable->addIndex(array('workflow_id')); $nodeTable->addForeignKeyConstraint($options->workflowTable(), array('workflow_id'), array('workflow_id'), array('onDelete' => 'CASCADE')); $connectionTable = $schema->createTable($options->nodeConnectionTable()); $columnOptions = $this->_handlePrimaryKey($schema, $options->nodeConnectionTable(), $options->nodeConnectionSequence()); $connectionTable->addColumn('id', 'integer', $columnOptions); $connectionTable->addColumn('incoming_node_id', 'integer'); $connectionTable->addColumn('outgoing_node_id', 'integer'); $connectionTable->setPrimaryKey(array('id')); $connectionTable->addForeignKeyConstraint($options->nodeTable(), array('incoming_node_id'), array('node_id'), array('onDelete' => 'CASCADE')); $connectionTable->addForeignKeyConstraint($options->nodeTable(), array('outgoing_node_id'), array('node_id'), array('onDelete' => 'CASCADE')); $variableHandlerTable = $schema->createTable($options->variableHandlerTable()); $variableHandlerTable->addColumn('workflow_id', 'integer'); $variableHandlerTable->addColumn('variable', 'string'); $variableHandlerTable->addColumn('class', 'string'); $variableHandlerTable->setPrimaryKey(array('workflow_id', 'variable')); $variableHandlerTable->addForeignKeyconstraint($options->workflowTable(), array('workflow_id'), array('workflow_id')); $executionTable = $schema->createTable($options->executionTable()); $columnOptions = $this->_handlePrimaryKey($schema, $options->executionTable(), $options->executionSequence()); $executionTable->addColumn('execution_id', 'integer', $columnOptions); $executionTable->addColumn('workflow_id', 'integer'); $executionTable->addColumn('execution_parent', 'integer', array('notnull' => false)); $executionTable->addColumn('execution_started', 'datetime'); $executionTable->addColumn('execution_suspended', 'datetime', array('notnull' => false)); $executionTable->addColumn('execution_variables', 'text', array('notnull' => false, "length" => null)); $executionTable->addColumn('execution_waiting_for', 'text', array('notnull' => false, "length" => null)); $executionTable->addColumn('execution_threads', 'text', array('notnull' => false, "length" => null)); $executionTable->addColumn('execution_next_thread_id', 'integer'); $executionTable->addColumn('execution_next_poll_date', 'datetime', array('notnull' => false)); $executionTable->addIndex(array('execution_next_poll_date')); $executionTable->setPrimaryKey(array('execution_id')); $executionTable->addIndex(array('execution_parent')); $executionTable->addForeignKeyConstraint($options->workflowTable(), array('workflow_id'), array('workflow_id')); $executionTable->addForeignKeyConstraint($options->executionTable(), array('execution_parent'), array('execution_id')); $executionStateTable = $schema->createTable($options->executionStateTable()); $executionStateTable->addColumn('execution_id', 'integer'); $executionStateTable->addColumn('node_id', 'integer'); $executionStateTable->addColumn('node_state', 'text', array('notnull' => false, "length" => null)); $executionStateTable->addColumn('node_activated_from', 'text', array('notnull' => false, "length" => null)); $executionStateTable->addColumn('node_thread_id', 'integer'); $executionStateTable->setPrimaryKey(array('execution_id', 'node_id')); $executionStateTable->addForeignKeyConstraint($options->executionTable(), array('execution_id'), array('execution_id')); $executionStateTable->addForeignKeyConstraint($options->nodeTable(), array('node_id'), array('node_id')); return $schema; }