PageRenderTime 57ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/tests/fixtures/class.FixtureBuilder.php

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