/** * Returns a the contents of a graphviz .dot file. * * @return boolean * @ignore */ public function __toString() { $dot = 'digraph ' . $this->workflowName . " {\n"; foreach ($this->nodes as $key => $data) { $dot .= sprintf("node%s [label=\"%s\", color=\"%s\"]\n", $key, $data['label'], $data['color']); } $dot .= "\n"; foreach ($this->edges as $fromNode => $toNodes) { foreach ($toNodes as $toNode) { $dot .= sprintf("node%s -> node%s%s\n", $fromNode, $toNode[0], $toNode[1]); } } if (!empty($this->options['workflowVariables'])) { $dot .= 'variables [shape=none, label=<<table>'; foreach ($this->options['workflowVariables'] as $name => $value) { $dot .= sprintf('<tr><td>%s</td><td>%s</td></tr>', $name, htmlspecialchars(ezcWorkflowUtil::variableToString($value))); } $dot .= "</table>>]\n"; } return $dot . "}\n"; }
/** * Generate node configuration from XML representation. * * @param DOMElement $element * @return array * @ignore */ public static function configurationFromXML(DOMElement $element) { $configuration = array('class' => $element->getAttribute('serviceObjectClass'), 'arguments' => array()); $childNode = ezcWorkflowUtil::getChildNode($element); if ($childNode->tagName == 'arguments') { foreach (ezcWorkflowUtil::getChildNodes($childNode) as $argument) { $configuration['arguments'][] = ezcWorkflowDefinitionStorageXml::xmlToVariable($argument); } } return $configuration; }
/** * Called after a variable has been set. * * @param ezcWorkflowExecution $execution * @param string $variableName * @param mixed $value */ public function afterVariableSet(ezcWorkflowExecution $execution, $variableName, $value) { $this->notifyListeners(sprintf('Set variable "%s" to "%s" for execution #%d of workflow "%s" (version %d).', $variableName, ezcWorkflowUtil::variableToString($value), $execution->getId(), $execution->workflow->name, $execution->workflow->version), ezcWorkflowExecutionListener::DEBUG); }
/** * Activates a node and returns true if it was activated, false if not. * * The node will only be activated if the node is executable. * See {@link ezcWorkflowNode::isExecutable()}. * * @param ezcWorkflowNode $node * @param bool $notifyPlugins * @return bool * @ignore */ public function activate(ezcWorkflowNode $node, $notifyPlugins = true) { // Only activate the node when // - the execution of the workflow has not been cancelled, // - the node is ready to be activated, // - and the node is not already activated. if ($this->cancelled || !$node->isExecutable() || ezcWorkflowUtil::findObject($this->activatedNodes, $node) !== false) { return false; } $activateNode = true; foreach ($this->plugins as $plugin) { $activateNode = $plugin->beforeNodeActivated($this, $node); if (!$activateNode) { // @codeCoverageIgnoreStart break; // @codeCoverageIgnoreEnd } } if ($activateNode) { // Add node to list of activated nodes. $this->activatedNodes[] = $node; $this->numActivatedNodes++; if ($node instanceof ezcWorkflowNodeEnd) { $this->numActivatedEndNodes++; } if ($notifyPlugins) { foreach ($this->plugins as $plugin) { $plugin->afterNodeActivated($this, $node); } } return true; } else { // @codeCoverageIgnoreStart return false; // @codeCoverageIgnoreEnd } }
/** * Returns a textual representation of this node. * * @return string * @ignore */ public function __toString() { $buffer = array(); foreach ($this->configuration as $variable => $value) { $buffer[] = $variable . ' = ' . ezcWorkflowUtil::variableToString($value); } return implode(', ', $buffer); }
/** * Returns true when the $node belongs to an ELSE condition. * * @param ezcWorkflowNode $node * @return bool * @ignore */ public function isElse(ezcWorkflowNode $node) { return isset($this->configuration['else'][ezcWorkflowUtil::findObject($this->outNodes, $node)]); }
/** * * @param int $workflowId * @param string $workflowName * @param int $workflowVersion * @return \ezcWorkflow */ protected function loadWorkflow($workflowId, $workflowName, $workflowVersion) { $workflowId = (int) $workflowId; $sql = "SELECT node_id, node_class, node_configuration FROM " . $this->options->nodeTable() . " WHERE workflow_id = ?"; $stmt = $this->conn->prepare($sql); $stmt->bindParam(1, $workflowId); $stmt->execute(); $result = $stmt->fetchAll(\PDO::FETCH_ASSOC); $this->workflowNodeIds[$workflowId] = array(); // Create node objects. foreach ($result as $node) { $node = array_change_key_case($node, \CASE_LOWER); $configuration = $this->options->getSerializer()->unserialize($node['node_configuration'], null); if (is_null($configuration)) { $configuration = \ezcWorkflowUtil::getDefaultConfiguration($node['node_class']); } $nodes[$node['node_id']] = $this->options->getWorkflowFactory()->createNode($node['node_class'], $configuration); $nodes[$node['node_id']]->setId($node['node_id']); $this->nodeMap[spl_object_hash($nodes[$node['node_id']])] = $node['node_id']; $this->workflowNodeIds[$workflowId][] = $node['node_id']; if ($nodes[$node['node_id']] instanceof \ezcWorkflowNodeFinally && !isset($finallyNode)) { $finallyNode = $nodes[$node['node_id']]; } else { if ($nodes[$node['node_id']] instanceof \ezcWorkflowNodeEnd && !isset($defaultEndNode)) { $defaultEndNode = $nodes[$node['node_id']]; } else { if ($nodes[$node['node_id']] instanceof \ezcWorkflowNodeStart && !isset($startNode)) { $startNode = $nodes[$node['node_id']]; } } } } if (!isset($startNode) || !isset($defaultEndNode)) { throw new \ezcWorkflowDefinitionStorageException('Could not load workflow definition.'); } $sql = "SELECT nc.outgoing_node_id, nc.incoming_node_id FROM " . $this->options->nodeConnectionTable() . " nc " . "INNER JOIN " . $this->options->nodeTable() . " n ON n.node_id = nc.incoming_node_id WHERE n.workflow_id = ?"; $stmt = $this->conn->prepare($sql); $stmt->bindParam(1, $workflowId); $stmt->execute(); $connections = $stmt->fetchAll(\PDO::FETCH_ASSOC); foreach ($connections as $connection) { $connection = array_change_key_case($connection, \CASE_LOWER); $nodes[$connection['incoming_node_id']]->addOutNode($nodes[$connection['outgoing_node_id']]); } if (!isset($finallyNode) || count($finallyNode->getInNodes()) > 0) { $finallyNode = null; } // Create workflow object and add the node objects to it. $workflowClassName = $this->options->workflowClassName(); $workflow = new $workflowClassName($workflowName, $startNode, $defaultEndNode, $finallyNode); $workflow->definitionStorage = $this; $workflow->id = (int) $workflowId; $workflow->version = (int) $workflowVersion; $sql = "SELECT variable, class FROM " . $this->options->variableHandlerTable() . " WHERE workflow_id = ?"; $stmt = $this->conn->prepare($sql); $stmt->bindParam(1, $workflowId); $stmt->execute(); $result = $stmt->fetchAll(\PDO::FETCH_ASSOC); $nodes = array(); if ($result !== false) { foreach ($result as $variableHandler) { $workflow->addVariableHandler($variableHandler['variable'], $variableHandler['class']); } } // Verify the loaded workflow. $workflow->verify(); return $workflow; }
/** * "Convert" an DOMElement object into a PHP variable. * * @param DOMElement $element * @return mixed */ public static function xmlToVariable(DOMElement $element) { $variable = null; switch ($element->tagName) { case 'array': $variable = array(); foreach ($element->getElementsByTagName('element') as $element) { $value = self::xmlToVariable(ezcWorkflowUtil::getChildNode($element)); if ($element->hasAttribute('key')) { $variable[(string) $element->getAttribute('key')] = $value; } else { $variable[] = $value; } } break; case 'object': $className = $element->getAttribute('class'); if ($element->hasChildNodes()) { $arguments = ezcWorkflowUtil::getChildNodes(ezcWorkflowUtil::getChildNode($element)); $constructorArgs = array(); foreach ($arguments as $argument) { if ($argument instanceof DOMElement) { $constructorArgs[] = self::xmlToVariable($argument); } } $class = new ReflectionClass($className); $variable = $class->newInstanceArgs($constructorArgs); } else { $variable = new $className(); } break; case 'boolean': $variable = $element->nodeValue == 'true' ? true : false; break; case 'integer': case 'double': case 'string': $variable = $element->nodeValue; settype($variable, $element->tagName); } return $variable; }
/** * Removes a node from the outgoing nodes of this node. * * Automatically removes $this as an in node of $node. * * @param ezcWorkflowNode $node The node that is to be removed as outgoing node. * @throws ezcWorkflowInvalidWorkflowException if the operation violates the constraints of the nodes involved. * @return boolean */ public function removeOutNode(ezcWorkflowNode $node) { $index = ezcWorkflowUtil::findObject($this->outNodes, $node); if ($index !== false) { // Remove this node as an incoming node from the other node. if (!self::$internalCall) { self::$internalCall = true; $node->removeInNode($this); } else { self::$internalCall = false; } unset($this->outNodes[$index]); $this->numOutNodes--; return true; } return false; }
/** * Generate node configuration from XML representation. * * @param DOMElement $element * @return array * @ignore */ public static function configurationFromXML(DOMElement $element) { $configuration = array(); foreach ($element->getElementsByTagName('variable') as $variable) { $configuration[$variable->getAttribute('name')] = ezcWorkflowDefinitionStorageXml::xmlToCondition(ezcWorkflowUtil::getChildNode($variable)); } return $configuration; }
/** * Load a workflow definition by ID. * * Providing the name of the workflow that is to be loaded as the * optional second parameter saves a database query. * * @param int $workflowId * @param string $workflowName * @param int $workflowVersion * @return ezcWorkflow * @throws ezcWorkflowDefinitionStorageException * @throws ezcDbException */ public function loadById($workflowId, $workflowName = '', $workflowVersion = 0) { // Query the database for the name and version of the workflow. if (empty($workflowName) || $workflowVersion == 0) { $query = $this->db->createSelectQuery(); $query->select($this->db->quoteIdentifier('workflow_name'))->select($this->db->quoteIdentifier('workflow_version'))->from($this->db->quoteIdentifier($this->options['prefix'] . 'workflow'))->where($query->expr->eq($this->db->quoteIdentifier('workflow_id'), $query->bindValue((int) $workflowId))); $stmt = $query->prepare(); $stmt->execute(); $result = $stmt->fetchAll(PDO::FETCH_ASSOC); if ($result !== false && isset($result[0])) { $workflowName = $result[0]['workflow_name']; $workflowVersion = $result[0]['workflow_version']; } else { throw new ezcWorkflowDefinitionStorageException('Could not load workflow definition.'); } } // Query the database for the nodes of the workflow to be loaded. $query = $this->db->createSelectQuery(); $query->select($this->db->quoteIdentifier('node_id'))->select($this->db->quoteIdentifier('node_class'))->select($this->db->quoteIdentifier('node_configuration'))->from($this->db->quoteIdentifier($this->options['prefix'] . 'node'))->where($query->expr->eq($this->db->quoteIdentifier('workflow_id'), $query->bindValue((int) $workflowId))); $stmt = $query->prepare(); $stmt->execute(); $result = $stmt->fetchAll(PDO::FETCH_ASSOC); $nodes = array(); // Create node objects. foreach ($result as $node) { $configuration = ezcWorkflowDatabaseUtil::unserialize($node['node_configuration'], null); if (is_null($configuration)) { $configuration = ezcWorkflowUtil::getDefaultConfiguration($node['node_class']); } $nodes[$node['node_id']] = new $node['node_class']($configuration); if ($nodes[$node['node_id']] instanceof ezcWorkflowNodeFinally && !isset($finallyNode)) { $finallyNode = $nodes[$node['node_id']]; } else { if ($nodes[$node['node_id']] instanceof ezcWorkflowNodeEnd && !isset($defaultEndNode)) { $defaultEndNode = $nodes[$node['node_id']]; } else { if ($nodes[$node['node_id']] instanceof ezcWorkflowNodeStart && !isset($startNode)) { $startNode = $nodes[$node['node_id']]; } } } } if (!isset($startNode) || !isset($defaultEndNode)) { throw new ezcWorkflowDefinitionStorageException('Could not load workflow definition.'); } // Connect node objects. $query = $this->db->createSelectQuery(); $query->select($query->alias('node_connection.incoming_node_id', $this->db->quoteIdentifier('incoming_node_id')))->select($query->alias('node_connection.outgoing_node_id', $this->db->quoteIdentifier('outgoing_node_id')))->from($query->innerJoin($this->db->quoteIdentifier($this->options['prefix'] . 'node_connection'), $this->db->quoteIdentifier($this->options['prefix'] . 'node'), 'node_connection.incoming_node_id', 'node.node_id'))->where($query->expr->eq('node.workflow_id', $query->bindValue((int) $workflowId)))->orderBy($this->db->quoteIdentifier('node_connection_id')); $stmt = $query->prepare(); $stmt->execute(); $connections = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($connections as $connection) { $nodes[$connection['incoming_node_id']]->addOutNode($nodes[$connection['outgoing_node_id']]); } if (!isset($finallyNode) || count($finallyNode->getInNodes()) > 0) { $finallyNode = null; } // Create workflow object and add the node objects to it. $workflow = new ezcWorkflow($workflowName, $startNode, $defaultEndNode, $finallyNode); $workflow->definitionStorage = $this; $workflow->id = (int) $workflowId; $workflow->version = (int) $workflowVersion; // Query the database for the variable handlers. $query = $this->db->createSelectQuery(); $query->select($this->db->quoteIdentifier('variable'))->select($this->db->quoteIdentifier('class'))->from($this->db->quoteIdentifier($this->options['prefix'] . 'variable_handler'))->where($query->expr->eq($this->db->quoteIdentifier('workflow_id'), $query->bindValue((int) $workflowId))); $stmt = $query->prepare(); $stmt->execute(); $result = $stmt->fetchAll(PDO::FETCH_ASSOC); $nodes = array(); if ($result !== false) { foreach ($result as $variableHandler) { $workflow->addVariableHandler($variableHandler['variable'], $variableHandler['class']); } } // Verify the loaded workflow. $workflow->verify(); return $workflow; }