PageRenderTime 68ms CodeModel.GetById 37ms RepoModel.GetById 1ms app.codeStats 0ms

/Pipe/src/execution/database.php

https://github.com/memonews/ymc-components
PHP | 483 lines | 324 code | 78 blank | 81 comment | 23 complexity | 5d0013089036d896906498884f9bb9ae MD5 | raw file
  1. <?php
  2. class ymcPipeExecutionDatabase extends ymcPipeExecutionSuspendable
  3. {
  4. /**
  5. * YYYY-MM-DD HH:MM:SS
  6. */
  7. const DB_DATETIMEFORMAT = 'U';
  8. protected $pipeName;
  9. protected $pipeVersion;
  10. protected $db;
  11. protected $created;
  12. /**
  13. * Temporary variable to cache node states fetched from the DB until the pipe is created.
  14. *
  15. * @var array( ( int )nodeId => ( array )nodeVariables )
  16. */
  17. protected $nodeStates = array();
  18. /**
  19. * Prefix for DB tables.
  20. *
  21. * @var string
  22. */
  23. protected $prefix = '';
  24. /**
  25. * id of this execution;
  26. *
  27. * @var integer
  28. */
  29. protected $id;
  30. /**
  31. * inTransaction
  32. *
  33. * @var bool
  34. */
  35. protected $inTransaction = false;
  36. public function __construct( ezcDbHandler $db, $executionId = Null )
  37. {
  38. $this->db = $db;
  39. if( is_integer( $executionId ) )
  40. {
  41. $this->id = $executionId;
  42. $this->load();
  43. }
  44. elseif( NULL === $executionId )
  45. {
  46. $this->created = new DateTime( 'now' );
  47. $this->executionState = self::NOT_STARTED;
  48. }
  49. else
  50. {
  51. throw new Exception( 'executionId must be of type integer.' );
  52. }
  53. }
  54. /**
  55. * Deletes this execution from the Database.
  56. *
  57. */
  58. public function delete()
  59. {
  60. self::deleteById( $this->db, $this->id, $this->prefix );
  61. if( $this->inTransaction )
  62. {
  63. //$this->db->commit();
  64. $this->inTransaction = false;
  65. }
  66. }
  67. public static function deleteById( ezcDbHandler $db, $id, $prefix='' )
  68. {
  69. //$db->beginTransaction();
  70. $q = $db->createDeleteQuery();
  71. $q->deleteFrom( $db->quoteIdentifier( $prefix.'pipe_execution' ) )
  72. ->where( $q->expr->eq( $db->quoteIdentifier( 'id' ), $q->bindValue( (int)$id ) ) );
  73. $stmt = $q->prepare();
  74. $stmt->execute();
  75. $q = $db->createDeleteQuery();
  76. $q->deleteFrom( $db->quoteIdentifier( $prefix.'pipe_execution_state' ) )
  77. ->where( $q->expr->eq( $db->quoteIdentifier( 'execution_id' ), $q->bindValue( (int)$id ) ) );
  78. $stmt = $q->prepare();
  79. $stmt->execute();
  80. //$db->commit();
  81. }
  82. /**
  83. * Loads the execution from the database.
  84. *
  85. */
  86. protected function load()
  87. {
  88. //$this->db->beginTransaction();
  89. $this->inTransaction = true;
  90. $q = $this->db->createSelectQuery();
  91. $q->select( '*' )
  92. ->from( $this->db->quoteIdentifier( $this->prefix.'pipe_execution' ) )
  93. ->where( $q->expr->eq( $this->db->quoteIdentifier( 'id' ),
  94. $q->bindValue( (int)$this->id ) ) );
  95. $stmt = $q->prepare();
  96. $stmt->execute();
  97. $result = $stmt->fetchAll( PDO::FETCH_ASSOC );
  98. if ( empty( $result ) )
  99. {
  100. //@todo better Exception
  101. throw new Exception(
  102. 'No state information for execution '.$this->id.'.'
  103. );
  104. }
  105. if ( $result === false )
  106. {
  107. //@todo better Exception
  108. throw new Exception(
  109. 'DB error loading state of execution '.$this->id.'.'
  110. );
  111. }
  112. // There can be only one result row
  113. $result = array_pop( $result );
  114. $this->pipeName = $result['pipe_name'];
  115. $this->pipeVersion = ( int )$result['pipe_version'];
  116. $this->executionState = ( int )$result['state'];
  117. //$this->parent = $result['parent'];
  118. $this->created = new DateTime( '@'.$result['created'] );
  119. // Load variables of this execution and of all nodes
  120. $q = $this->db->createSelectQuery();
  121. $q->select( '*' )
  122. ->from( $this->db->quoteIdentifier( $this->prefix.'pipe_execution_state' ) )
  123. ->where( $q->expr->eq( $this->db->quoteIdentifier( 'execution_id' ),
  124. $q->bindValue( (int)$this->id ) ) );
  125. $stmt = $q->prepare();
  126. $stmt->execute();
  127. $result = $stmt->fetchAll( PDO::FETCH_ASSOC );
  128. //@todo Check result
  129. $nodeStates = array();
  130. foreach( $result as $row )
  131. {
  132. $nodeId = ( int )$row['node_id'];
  133. if( 0 === $nodeId )
  134. {
  135. // This is the state of the execution
  136. $this->unserializeState( $row['state'] );
  137. }
  138. else
  139. {
  140. // It's a node's state
  141. $nodeStates[$nodeId] = $row['state'];
  142. }
  143. }
  144. $this->nodeStates = $nodeStates;
  145. }
  146. protected function serializeState()
  147. {
  148. return serialize(
  149. array( 'v' => $this->variables,
  150. 'e' => $this->exception,
  151. 'a' => array_keys( $this->activatedNodes )
  152. ) );
  153. }
  154. protected function unserializeState( $state )
  155. {
  156. $s = unserialize( $state );
  157. $this->variables = $s['v'];
  158. $this->exception = $s['e'];
  159. foreach( $s['a'] as $activatedNodeId )
  160. {
  161. $this->activatedNodes[(int)$activatedNodeId] = TRUE;
  162. }
  163. }
  164. /**
  165. * Stores the current execution state do the database.
  166. *
  167. */
  168. public function store()
  169. {
  170. if( $this->isPersistent() )
  171. {
  172. $this->update();
  173. }
  174. else
  175. {
  176. $this->insert();
  177. }
  178. }
  179. /**
  180. * Prepares this execution for starting and calls parent::start().
  181. *
  182. * After completion you must decide whether to call suspend() or delete()!
  183. * @todo save a static flac to avoid the start of a second pipe during a transaction.
  184. *
  185. */
  186. public function start( $parentId = NULL )
  187. {
  188. // Make sure the pipe is set up.
  189. $this->__get( 'pipe' );
  190. parent::start( $parentId );
  191. }
  192. protected function insert()
  193. {
  194. //$this->db->beginTransaction();
  195. $q = $this->db->createInsertQuery();
  196. $q->insertInto( $this->db->quoteIdentifier( $this->prefix . 'pipe_execution' ) )
  197. ->set( 'pipe_name', $q->bindValue( (string)$this->__get( 'pipeName' ) ) )
  198. ->set( 'pipe_version', $q->bindValue( (int)$this->__get( 'pipeVersion' ) ) )
  199. ->set( 'state', $q->bindValue( (int)$this->executionState ) )
  200. ->set( 'created', $q->bindValue( $this->created->format( self::DB_DATETIMEFORMAT ) ) )
  201. ->set( 'parent', $q->bindValue(0) );
  202. $statement = $q->prepare();
  203. $statement->execute();
  204. $this->id = (int)$this->db->lastInsertId( 'execution_id_seq' );
  205. // Save execution variables
  206. $this->insertState( null, $this->serializeState() );
  207. // save node states
  208. if( $this->pipe instanceof ymcPipe )
  209. {
  210. foreach( $this->pipe->nodes as $node )
  211. {
  212. $id = $node->id;
  213. if( !is_integer( $id ) )
  214. {
  215. throw new Exception( 'Node has no id!' );
  216. }
  217. $this->insertState( $node->id, $node->serializeState() );
  218. }
  219. }
  220. //$this->db->commit();
  221. }
  222. protected function insertState( $nodeId, $state )
  223. {
  224. $q = $this->db->createInsertQuery();
  225. $q->insertInto( $this->db->quoteIdentifier( $this->prefix . 'pipe_execution_state' ) )
  226. ->set( 'execution_id', $q->bindValue( (int)$this->id ) )
  227. ->set( 'node_id', $q->bindValue( (int)$nodeId ) )
  228. ->set( 'state', $q->bindValue( $state ) );
  229. $statement = $q->prepare();
  230. $statement->execute();
  231. }
  232. protected function updateState( $nodeId, $state )
  233. {
  234. $q = $this->db->createUpdateQuery();
  235. $q->update( $this->db->quoteIdentifier( $this->prefix . 'pipe_execution_state' ) )
  236. ->set( 'state', $q->bindValue( $state ) )
  237. ->where( $q->expr->eq( 'node_id', $q->bindValue( (int)$nodeId ) ) )
  238. ->where( $q->expr->eq( 'execution_id', $q->bindValue( (int)$this->id ) ) );
  239. $statement = $q->prepare();
  240. $statement->execute();
  241. }
  242. protected function update()
  243. {
  244. $this->updateState( null, $this->serializeState() );
  245. // Update the state column in pipe_execution
  246. // @todo: remember the state from load() and update only if necessary
  247. $q = $this->db->createUpdateQuery();
  248. $q->update( $this->db->quoteIdentifier( $this->prefix . 'pipe_execution' ) )
  249. ->set( 'state', $q->bindValue( ( int )$this->executionState ) )
  250. ->where( $q->expr->eq( 'id', $q->bindValue( (int)$this->id ) ) );
  251. $statement = $q->prepare();
  252. $statement->execute();
  253. if( $this->pipe instanceof ymcPipe )
  254. {
  255. foreach( $this->pipe->nodes as $node )
  256. {
  257. $id = $node->id;
  258. $serialized = $node->serializeState();
  259. if( !array_key_exists( $id, $this->nodeStates ) )
  260. {
  261. if( !is_integer( $id ) )
  262. {
  263. throw new Exception( 'Node has no id.' );
  264. }
  265. $this->insertState( $id, $serialized );
  266. }
  267. else
  268. {
  269. // Only update, if sth. has changed.
  270. if( $serialized !== $this->nodeStates[$id] )
  271. {
  272. $this->updateState( $id, $serialized );
  273. }
  274. }
  275. }
  276. }
  277. //$this->db->commit();
  278. $this->inTransaction = false;
  279. }
  280. protected function isPersistent()
  281. {
  282. return NULL !== $this->id;
  283. }
  284. /**
  285. * Indicates the pipe to be executed.
  286. *
  287. * This is intended to allow the initialization of a pipe execution without instantiating the
  288. * pipe. So the execution can be set up, saved and started later by another process.
  289. *
  290. * The version can remain undefined and will be set the first time the pipe is loaded.
  291. *
  292. * @param string $name
  293. * @param integer $version optional, defaults to the most recent.
  294. * @return void
  295. */
  296. public function setPipe( $name, $version = NULL )
  297. {
  298. if( !is_string( $name ) || ( !is_integer( $version ) && ( NULL !== $version ) ) )
  299. {
  300. throw new Exception( sprintf( 'Pipe name must be string and pipe version integer. Given %s and %s.', gettype( $name ), gettype( $version ) ) );
  301. }
  302. $this->pipeName = $name;
  303. $this->pipeVersion = $version;
  304. }
  305. /**
  306. * Loads the pipe indicated by setPipe() and puts it in $this->pipe.
  307. *
  308. */
  309. protected function loadPipe()
  310. {
  311. if( NULL === $this->pipeName )
  312. {
  313. throw new Exception( 'You must specify a pipe with setPipe() first!' );
  314. }
  315. $pipe = $this->__get( 'definitionStorage' )->loadByName( $this->pipeName, $this->pipeVersion );
  316. if( !$pipe instanceof ymcPipe )
  317. {
  318. throw new Exception(
  319. sprintf( 'Could not load pipe %s, version %s with definitionStorage of type %s.',
  320. $this->pipeName,
  321. $this->pipeVersion ? $this->pipeVersion : '(undefined)',
  322. get_class( $this->__get( 'definitionStorage' ) ) )
  323. );
  324. }
  325. $nodeStates = $this->nodeStates;
  326. foreach( $pipe->nodes as $node )
  327. {
  328. $id = $node->id;
  329. if( array_key_exists( $id, $nodeStates ) )
  330. {
  331. $node->unserializeState( $nodeStates[$id] );
  332. }
  333. }
  334. $this->pipe = $pipe;
  335. $this->pipeVersion = $pipe->version;
  336. }
  337. public function __get( $name )
  338. {
  339. switch( $name )
  340. {
  341. case 'executionState':
  342. case 'id':
  343. case 'prefix':
  344. case 'created':
  345. case 'exception':
  346. return $this->$name;
  347. //@todo check whether $this->pipe is loaded and get values from there in this case.
  348. case 'pipeName':
  349. case 'pipeVersion':
  350. return $this->$name;
  351. case 'pipe':
  352. if( !$this->pipe instanceof ymcPipe )
  353. {
  354. $this->loadPipe();
  355. }
  356. return $this->pipe;
  357. case 'definitionStorage':
  358. if( !isset( $this->definitionStorage ) )
  359. {
  360. $this->definitionStorage = new ymcPipeDefinitionStorageDatabase( $this->db );
  361. }
  362. return $this->definitionStorage;
  363. default:
  364. return parent::__get( $name );
  365. }
  366. }
  367. public function __set( $name, $value )
  368. {
  369. switch( $name )
  370. {
  371. case 'prefix':
  372. break;
  373. case 'definitionStorage':
  374. if ( !( $value instanceof ymcPipeDefinitionStorage ) )
  375. {
  376. throw new ezcBaseValueException(
  377. $name,
  378. $value,
  379. 'ymcPipeDefinitionStorage'
  380. );
  381. }
  382. break;
  383. case 'created':
  384. if ( !( $value instanceof DateTime ) )
  385. {
  386. throw new ezcBaseValueException(
  387. $name,
  388. $value,
  389. 'DateTime'
  390. );
  391. }
  392. break;
  393. default:
  394. parent::__set( $name, $value );
  395. }
  396. $this->$name = $value;
  397. }
  398. public function __destruct()
  399. {
  400. if( $this->inTransaction )
  401. {
  402. //$this->db->rollback();
  403. }
  404. }
  405. }