PageRenderTime 27ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/tests/fixtures/class.FixtureBuilder.php

https://github.com/dagda/ThinkUp
PHP | 346 lines | 194 code | 23 blank | 129 comment | 45 complexity | 9343317d79076a650dac66a6cf2a228b MD5 | raw file
  1. <?php
  2. /**
  3. * FixtureBuilder
  4. *
  5. * Data Fixture builder for test data generation. Will auto generate values if not defined.
  6. *
  7. * Table data gets truncated when the builder object goes out of scope.
  8. *
  9. * Currently only tested with mysql.
  10. *
  11. * Example use:
  12. *
  13. * <code>
  14. * // populate a table named "table_name" with two columns: "name", "email"
  15. * $builder = FixtureBuilder::build('table_name');
  16. * $name_value = $builder->columns['name'];
  17. * $email_value = $builder->columns['email'];
  18. *
  19. * // you can also set values
  20. * $builder = FixtureBuilder::build('table_name', array( 'name' => 'Mojo Jojo', 'email' => 'mojo@jojo.info' ));
  21. * $name_value = $builder->columns['name'];
  22. * $email_value = $builder->columns['email']
  23. *
  24. * // you can set date values by string
  25. * $builder = FixtureBuilder::build('table_name', array( 'date_added' => '2010-06-21 20:34:13' ));
  26. *
  27. * // or you can set dates by + or - n days, hours, minutes or seconds
  28. * // 1 hour ahead
  29. * $builder = FixtureBuilder::build('table_name', array( 'date_added' => '+1h' ));
  30. * // 3 days behind
  31. * $builder = FixtureBuilder::build('table_name', array( 'date_added' => '-3d' ));
  32. * // 600 seconds behind
  33. * $builder = FixtureBuilder::build('table_name', array( 'date_added' => '-300s' ));
  34. *
  35. * // to truncate the data in a table, just set the builder to null
  36. * // and __destruct will call 'truncate table $tablename', ie:
  37. * $builder = FixtureBuilder::build('table_name', array( 'date_added' => '-300s' ));
  38. * $builder = null;
  39. * </code>
  40. *
  41. * @author Mark Wilkie <mwilkie[at]gmail[dot]com>
  42. */
  43. class FixtureBuilder {
  44. /*
  45. * @var bool Debugging flag
  46. */
  47. var $DEBUG = false;
  48. /*
  49. * @var array Default lengths for data
  50. */
  51. var $DATA_DEFAULTS = array(
  52. 'bigint' => 1000000000,
  53. 'int' => 1000000,
  54. 'smallint' => 10000,
  55. 'tinyint' => 10,
  56. 'text' => 50, //20 cahrs
  57. 'mediumtext' => 40, //10 chars
  58. 'tinytext' => 30, // 5 chars
  59. 'varchar' => 20, //20 chars
  60. 'char' => 10, //10 chars
  61. 'float' => 1000
  62. );
  63. /*
  64. * @var PDO our db handle
  65. */
  66. static $pdo;
  67. /*
  68. * our Constructor
  69. */
  70. public function __construct($debug = false) {
  71. $this->DEBUG = $debug ? $debug : $this->DEBUG;
  72. $this->config = Config::getInstance();
  73. if(is_null(self::$pdo)) {
  74. self::$pdo = $this->connect();
  75. }
  76. }
  77. /**
  78. * Builds our data
  79. * @param str table name (without prefix)
  80. * @param array hash args of column values (optional)
  81. * @param bool debug (defaults to false)
  82. * @return FixtureBuilder our builder object with column values
  83. */
  84. public static function build($table, $args = null, $debug = false) {
  85. $builder = new FixtureBuilder($debug);
  86. $builder->buildData($table, $args);
  87. $builder->table = $table;
  88. return $builder;
  89. }
  90. /*
  91. * Connect to db using PDO
  92. * @return PDO
  93. */
  94. private function connect() {
  95. $db_string = sprintf("mysql:dbname=%s;host=%s", $this->config->getValue('db_name'),
  96. $this->config->getValue('db_host'));
  97. if($this->DEBUG) { echo "DEBUG: Connecting to $db_string\n"; }
  98. $db_socket = $this->config->getValue('db_socket');
  99. if ( $db_socket) {
  100. $db_string.=";unix_socket=".$db_socket;
  101. }
  102. $pdo = new PDO($db_string, $this->config->getValue('db_user'), $this->config->getValue('db_password'));
  103. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  104. return $pdo;
  105. }
  106. /*
  107. * Truncate a table by name
  108. * @param str Table name (without prefix)
  109. */
  110. public static function truncateTable($table) {
  111. $table = Config::getInstance()->getValue('table_prefix') . $table;
  112. try {
  113. self::$pdo->query('truncate table ' . $table);
  114. } catch(Exception $e) {
  115. throw new FixtureBuilderException('Unable to truncate table "' . $table . '" - ' . $e->getMessage());
  116. }
  117. }
  118. /*
  119. * Describes a table
  120. * @param str A table name (without prefix)
  121. * @return array A list of table columns
  122. */
  123. public function describeTable($table) {
  124. $columns = array();
  125. $table = $this->config->getValue('table_prefix') . $table;
  126. try {
  127. $stmt = self::$pdo->query('desc ' . $table);
  128. while ($row = $stmt->fetch()) {
  129. $columns[$row['Field']] = $row;
  130. }
  131. } catch(Exception $e) {
  132. throw new FixtureBuilderException('Unable to describe table "' . $table . '" - ' . $e->getMessage());
  133. }
  134. return $columns;
  135. }
  136. /*
  137. * Build our data
  138. * @param str A table name: note, without prefix
  139. * @param array Column values (optional)
  140. * @return array Our columns with data
  141. */
  142. public function buildData($table, $args = null) {
  143. $columns = $this->describeTable($table);
  144. $this->columns = array();
  145. $sql = "INSERT INTO " . $this->config->getValue('table_prefix') . $table;
  146. foreach( $columns as $column) {
  147. $field_value = (! is_null($args)) && isset( $args[ $column['Field'] ]) ? $args[ $column['Field'] ] : null;
  148. if( isset($column['Key']) && $column['Key'] == 'UNI' && ! $field_value) {
  149. throw new FixtureBuilderException($column['Field'] .
  150. ' has a unique key constraint, a value must be defined for this column');
  151. }
  152. if( isset($column['Extra']) && $column['Extra'] == 'auto_increment' && ! $field_value ) {
  153. continue;
  154. }
  155. if(isset($field_value)) {
  156. if(preg_match('/^(times|date)/', $column['Type'])) {
  157. $column['value'] = $this->genDate($field_value);
  158. } else {
  159. $column['value'] = $field_value;
  160. }
  161. } else if(isset($args) && array_search($column['Field'], array_keys($args)) !== false) {
  162. // Column value was specified, but is null; we just don't want to specify a value for that column
  163. continue;
  164. } else if (isset($column['Default']) && $column['Default'] != ''
  165. && $column['Default'] != 'CURRENT_TIMESTAMP') {
  166. $column['value'] = $column['Default'];
  167. } else {
  168. if(preg_match('/^enum/', $column['Type'])) {
  169. $column['value'] = $this->genEnum( $column['Type'] );
  170. } else if(preg_match('/^decimal/', $column['Type'])) {
  171. $column['value'] = $this->genDecimal($column['Type']);
  172. } else if(preg_match('/^(int|tinyint)/', $column['Type'])) {
  173. $column['value'] = $this->genInt();
  174. } else if(preg_match('/^bigint/', $column['Type'])) {
  175. $column['value'] = $this->genBigint();
  176. } else if(preg_match('/^(times|date)/', $column['Type'])) {
  177. $column['value'] = $this->genDate();
  178. } else if(preg_match('/^(varchar|text|tinytext|mediumtext|longtext|blob)/', $column['Type'])) {
  179. $column['value'] = $this->genVarchar();
  180. }
  181. }
  182. $this->columns[ $column['Field'] ] = $column['value'];
  183. }
  184. $sql .= sprintf(" (%s) VALUES", join(',', array_keys($this->columns) ));
  185. $values = array_values($this->columns);
  186. array_shift($values);
  187. $sql .= sprintf(" (?%s)", str_repeat(",?", count($values)));
  188. $stmt = self::$pdo->prepare($sql);
  189. $stmt->execute(array_values($this->columns));
  190. $last_insert_id = self::$pdo->lastInsertId();
  191. if(isset($last_insert_id)) {
  192. $this->columns['last_insert_id'] = $last_insert_id;
  193. }
  194. }
  195. /*
  196. * Generates a varchar value
  197. * @param int Length (optional)
  198. * @return str
  199. */
  200. public function genVarchar($length = 0) {
  201. $length = $length > 0 ? $length : $this->DATA_DEFAULTS['varchar'];
  202. return $this->genString($length);
  203. }
  204. /*
  205. * Generates a string value
  206. * @param int Length (optional)
  207. * @return str
  208. */
  209. public function genString($length = 0) {
  210. $characters = array(
  211. "a","b","c","d","e","f","g","h","j","k","l","m",
  212. "n","p","q","r","s","t","u","v","w","x","y","z",
  213. "A","B","C","D","E","F","G","H","J","K","L","M",
  214. "N","P","Q","R","S","T","U","V","W","X","Y","Z",
  215. "1","2","3","4","5","6","7","8","9", "0", " ");
  216. $length = $length > 0 ? $length : $this->DATA_DEFAULTS['varchar'];
  217. $length = rand(1, $length);
  218. $string = '';
  219. for($i = 0; $i < $length; $i++) {
  220. $string .= $characters[mt_rand(0, count($characters)-1)];
  221. }
  222. return $string;
  223. }
  224. /*
  225. * Generates an int value
  226. * @param int Length (optional)
  227. * @return int
  228. */
  229. public function genInt($length = 0, $unsigned = false) {
  230. $length = $length > 0 ? $length : $this->DATA_DEFAULTS['int'];
  231. $start = $unsigned ? ($length * -1) : 1;
  232. return rand($start, $length);
  233. }
  234. /*
  235. * Generates a big int value
  236. * @param int Length (optional)
  237. * @return int
  238. */
  239. public function genBigInt($length = 0, $unsigned = false) {
  240. $length = $length > 0 ? $length : $this->DATA_DEFAULTS['bigint'];
  241. return $this->genInt($length, $unsigned);
  242. }
  243. /*
  244. * Generates a tiny int value
  245. * @param int Length (optional)
  246. * @return int
  247. */
  248. public function genTinyInt($length = 0, $unsigned = false) {
  249. $length = $length > 0 ? $length : $this->DATA_DEFAULTS['tinyint'];
  250. return $this->genInt($length, $unsigned);
  251. }
  252. /*
  253. * Generates a float value
  254. * @param int Length (optional)
  255. * @return float
  256. */
  257. public function genFloat($length) {
  258. $int = rand(0, $length);
  259. }
  260. /*
  261. * Generates an enum value
  262. * @param str An 'emun(...)' description
  263. * @return string
  264. */
  265. public function genEnum($values) {
  266. $values = preg_replace("/enum\\(|\\)/i", '', $values);
  267. $values = preg_split('/,/', $values);
  268. $value = $values[mt_rand(0, count($values)-1)];
  269. $value = preg_replace("/^'|'$/", '', $value);
  270. return $value;
  271. }
  272. /*
  273. * Generates decimal value
  274. * @param str A 'decimal(M,D)' description
  275. * @return float
  276. */
  277. public function genDecimal($values) {
  278. $values = preg_replace("/(decimal)\\(|\\)/i", '', $values);
  279. $values = preg_split('/,/', $values);
  280. $left = mt_rand(0, pow(10, $values[0]) - 1);
  281. $right = mt_rand(1, pow(10, $values[1]) - 1);
  282. $value = $left . '.' . $right;
  283. $value = $value + 0; // cast to a float;
  284. return $value;
  285. }
  286. /*
  287. * Generates a mysql date
  288. * @param str A date increment or decrement (+3d, -1h, +7m, -2m), or a mysql date string '2010-06-20 16:22:25'
  289. * @return str
  290. */
  291. public function genDate($value = null) {
  292. $time_inc_map = array('h' => 'HOUR', 'd' => 'DAY', 'm' => 'MINUTE', 's' => 'SECOND');
  293. $sql = 'select now() - interval rand()*100000000 second';
  294. if($value) {
  295. if(preg_match('/^(\+|\-)(\d+)(s|m|h|d)/', $value, $matches)) {
  296. $sql = "select now() $matches[1] interval $matches[2] " . $time_inc_map[$matches[3]];
  297. } else {
  298. $sql = null;
  299. }
  300. }
  301. if($sql) {
  302. $stmt = self::$pdo->query( $sql . ' as FDATE' );
  303. $data = $stmt->fetch();
  304. return $data[0];
  305. } else {
  306. return $value;
  307. }
  308. }
  309. /*
  310. * Our destructor
  311. * truncates the fixture table
  312. */
  313. function __destruct() {
  314. if(isset($this->table)) {
  315. $table = Config::getInstance()->getValue('table_prefix') . $this->table;
  316. try {
  317. self::$pdo->query('truncate table ' . $table);
  318. } catch(Exception $e) {
  319. throw new FixtureBuilderException('Unable to truncate table "' . $table . '" - ' . $e->getMessage());
  320. }
  321. }
  322. }
  323. }