PageRenderTime 40ms CodeModel.GetById 11ms RepoModel.GetById 1ms app.codeStats 0ms

/scar.php

https://github.com/Jakobo/scar
PHP | 337 lines | 182 code | 61 blank | 94 comment | 24 complexity | f62a65dd5ccccfc26000af2d06282143 MD5 | raw file
Possible License(s): JSON
  1. <?php
  2. define('SCAR_BASE', dirname(__FILE__).DIRECTORY_SEPARATOR);
  3. define('SCAR_LIB', SCAR_BASE.'lib'.DIRECTORY_SEPARATOR);
  4. define('SCAR_DB', SCAR_BASE.'db'.DIRECTORY_SEPARATOR);
  5. include SCAR_LIB . 'exceptions.php';
  6. include SCAR_LIB . 'inflector.php';
  7. include SCAR_LIB . 'constants.php';
  8. include SCAR_LIB . 'sql.php';
  9. include SCAR_LIB . 'datastore.php';
  10. include SCAR_LIB . 'repository.php';
  11. include SCAR_LIB . 'scargeneric.php';
  12. /**
  13. * SCAR Factory and Bridge Class
  14. * The SCAR Object is a static object that:
  15. * - creates new SCAR_Generic Objects
  16. * - runs queries for SCAR_Generic Objects
  17. * - can retrieve tables and columns for databases
  18. * - can escape strings
  19. * - serves as a bridge for multiple DB types
  20. *
  21. * It should be used as the start of any SCAR retrieval chain, using the format
  22. * SCAR::get('tables') and from that point forward, you will be working with
  23. * SCAR_Generic objects. To trigger an insert, use the format SCAR::make('object')
  24. * to get a SCAR_Generic object geared towards inserting data.
  25. **/
  26. class SCAR {
  27. /**
  28. * contains an array of timings for events as they happen
  29. * useful for benchmarking
  30. **/
  31. public static $timings = array();
  32. /**
  33. * Specify a config path for the application or retrieve the config path
  34. * @param $cfg string a string to set the config path to
  35. * @return string
  36. **/
  37. public static function config($cfg = null) {
  38. static $config;
  39. if (!isset($config)) {
  40. $config = null;
  41. }
  42. if ($cfg === null) {
  43. return $config;
  44. }
  45. $config = $cfg;
  46. return $config;
  47. }
  48. /**
  49. * Record a timestamp for benchmarking
  50. * if $finish is provided, then it will be the time lapsed since
  51. * mark was called last for $method without $finish.
  52. * @param string $method the token we are timing
  53. * @param boolean $finish if true, the time lapse will be recorded to self::$timings
  54. **/
  55. public static function mark($method, $finish = false) {
  56. static $timings;
  57. if (!isset($timings)) {
  58. $timings = array();
  59. }
  60. $time = microtime(true);
  61. $uuid = 't'.$method;
  62. if (!$finish) {
  63. $timings[$uuid] = $time;
  64. return;
  65. }
  66. $end_time = floatval($time - $timings[$uuid]);
  67. SCAR::$timings[] = array('m' => $method, 't' => $end_time);
  68. }
  69. /**
  70. * Create a SCAR_Generic object from a class name
  71. * this function is useful when you know the class name
  72. * and want to create an object (for insert).
  73. * @param $name string a class name, ie Author
  74. * @return SCAR_Generic
  75. **/
  76. public static function make($name) {
  77. return SCAR::get(Inflector::tableize($name));
  78. }
  79. /**
  80. * Get a collection of objects, via a SCAR object from a table
  81. * this function creates a SCAR object based on the table
  82. * @param $name string a name of a table to create an object for
  83. * @param $columns string [optional] a list of columns to limit to
  84. * @return SCAR_Generic
  85. * @throws SCAR_Object_Not_Found_Exception
  86. **/
  87. public static function get($name, $columns = null) {
  88. static $repository;
  89. $datastore = SCAR::datastore();
  90. $klass = Inflector::classify($name);
  91. if (class_exists($klass)) {
  92. $k = new $klass();
  93. $k->columns($columns);
  94. $k->datastore($datastore);
  95. return $k;
  96. }
  97. // class does not exist, check the repository
  98. if (!isset($repository)) {
  99. self::mark('repository');
  100. $repository = new SCAR_Repository(SCAR::config());
  101. self::mark('repository', true);
  102. }
  103. $k = $repository->get($klass);
  104. if ($k === null) {
  105. throw new SCAR_Object_Not_Found_Exception(Inflector::camelize($name));
  106. }
  107. $k->columns($columns);
  108. $k->datastore($datastore);
  109. return $k;
  110. }
  111. /**
  112. * Loads a Sub-Scheme object.
  113. * This isolates DB specific methods into static classes which SCAR
  114. * can then call. This serves as a factory method, attempting to load
  115. * schemes out of the db/ directory
  116. * @param $scheme string The scheme type to load, ie 'mysql'
  117. * @throws SCAR_Scheme_Not_Found_Exception
  118. **/
  119. public static function load($scheme) {
  120. $klass = 'SCAR_'.$scheme;
  121. if (!class_exists($klass)) {
  122. $file = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'db' . DIRECTORY_SEPARATOR . strtolower($scheme) . '.php';
  123. if (!file_exists($file)) {
  124. throw new SCAR_Scheme_Not_Found_Exception($scheme);
  125. }
  126. include $file;
  127. if (!class_exists($klass)) {
  128. throw new SCAR_Scheme_Not_Found_Exception($scheme);
  129. }
  130. }
  131. }
  132. /**
  133. * Makes the SCAR Datastore a Singleton to the SCAR class
  134. * This is a retrieval point for the SCAR_Datastore object,
  135. * ensuring one persistent copy exists within a deployment of
  136. * SCAR.
  137. * @return SCAR_Datastore
  138. **/
  139. public static function datastore() {
  140. static $datastore;
  141. if (!isset($datastore)) {
  142. self::mark('datastore');
  143. $datastore = new SCAR_Datastore();
  144. self::mark('datastore', true);
  145. }
  146. return $datastore;
  147. }
  148. /**
  149. * Opens a connection to a database via a subscheme
  150. * @param $dsn string a scheme://user:pass@host/db formatted string
  151. * @return a connection object reference for that db combination
  152. * @throws SCAR_Connection_Exception
  153. **/
  154. public static function connect($dsn) {
  155. static $connectors;
  156. if (!isset($connectors)) {
  157. $connectors = array();
  158. }
  159. $uri = parse_url($dsn);
  160. if (!isset($uri['port'])) {
  161. $uri['port'] = null;
  162. }
  163. SCAR::load($uri['scheme']);
  164. $hash = md5(strtolower(serialize(ksort($uri))));
  165. if (!isset($connectors[$hash])) {
  166. self::mark('connect');
  167. $connector = call_user_func_array(array('SCAR_'.$uri['scheme'], 'connect'), array($uri));
  168. if (!$connector) {
  169. throw new SCAR_Connection_Exception($dsn);
  170. }
  171. $connectors[$hash] = $connector;
  172. self::mark('connect', true);
  173. }
  174. return $connectors[$hash];
  175. }
  176. /**
  177. * Calls a scheme specific escape function
  178. * @param $dsn string a formatted scheme://user:pass@host/db string
  179. * @param $string string what we want to escape for SQL
  180. * @return string
  181. **/
  182. public static function escape($dsn, $string) {
  183. $uri = parse_url($dsn);
  184. SCAR::load($uri['scheme']);
  185. return call_user_func_array(array('SCAR_'.$uri['scheme'], 'escape'), array($string));
  186. }
  187. /**
  188. * Runs a scheme specific query and normalizes the results for SCAR objects
  189. * @param $dsn string a scheme://user:pass@host/db formatted string
  190. * @param $sql string the SQL to run
  191. * @param $pk_list array the primary key list for the object
  192. * @return array
  193. * @throws SCAR_Query_Exception
  194. **/
  195. public static function query($dsn, $sql, $pk_list) {
  196. // var_dump($sql);
  197. self::mark($sql);
  198. $i_am = substr($sql, 0, 6);
  199. $is_update = ($i_am == 'UPDATE');
  200. $is_select = ($i_am == 'SELECT');
  201. $is_insert = ($i_am == 'INSERT');
  202. $db = SCAR::connect($dsn);
  203. // var_dump('QUERY: '.$sql);
  204. $uri = parse_url($dsn);
  205. SCAR::load($uri['scheme']);
  206. $rs = call_user_func_array(array('SCAR_'.$uri['scheme'], 'query'), array($db, $sql));
  207. if (!$rs) { throw new SCAR_Query_Exception($dsn, $sql); }
  208. $results = array();
  209. $keys = array();
  210. if ($is_select) {
  211. while ($row = call_user_func_array(array('SCAR_'.$uri['scheme'], 'row'), array($rs))) {
  212. $store_pk = implode(',', $pk_list);
  213. $pk_val = '';
  214. foreach ($pk_list as $pk) {
  215. $pk_val .= $row[$pk].',';
  216. }
  217. $pk_val = trim($pk_val, ',');
  218. $keys[$store_pk][] = $pk_val;
  219. $results[$pk_val] = $row;
  220. }
  221. }
  222. self::mark($sql, true);
  223. return array(
  224. 'keys' => $keys,
  225. 'data' => $results,
  226. 'rows' => ($is_select) ? call_user_func_array(array('SCAR_'.$uri['scheme'], 'count'), array($rs)) : false,
  227. 'next' => ($is_insert) ? call_user_func_array(array('SCAR_'.$uri['scheme'], 'nextid'), array($db)) : false,
  228. 'affected' => ($is_update || $is_insert) ? call_user_func_array(array('SCAR_'.$uri['scheme'], 'affected'), array($db)) : false,
  229. );
  230. }
  231. /**
  232. * Calls a scheme specific show tables command
  233. * @param $dsn string a scheme://user:pass@host/db formatted string
  234. * @return array
  235. **/
  236. public static function showTables($dsn) {
  237. $db = SCAR::connect($dsn);
  238. $uri = parse_url($dsn);
  239. SCAR::load($uri['scheme']);
  240. self::mark('showtables: '.$dsn);
  241. $sql = call_user_func_array(array('SCAR_'.$uri['scheme'], 'showTables'), array());
  242. $rs = call_user_func_array(array('SCAR_'.$uri['scheme'], 'query'), array($db, $sql));
  243. $tables = array();
  244. while ($row = call_user_func_array(array('SCAR_'.$uri['scheme'], 'row'), array($rs))) {
  245. $tables[] = $row;
  246. }
  247. self::mark('showtables: '.$dsn, true);
  248. self::mark('parsetables: '.$dsn);
  249. $results = call_user_func_array(array('SCAR_'.$uri['scheme'], 'parseShowTables'), array($tables));
  250. self::mark('parsetables: '.$dsn, true);
  251. return $results;
  252. }
  253. /**
  254. * Calls a scheme specific version of showing the columns in a database
  255. * @param $dsn string a scheme://user:pass@host/db formatted string
  256. * @param $table string a table name to get the columns for
  257. * @return array
  258. **/
  259. public static function showColumns($dsn, $table) {
  260. $db = SCAR::connect($dsn);
  261. $uri = parse_url($dsn);
  262. SCAR::load($uri['scheme']);
  263. self::mark('showcolumns: '.$dsn.' tbl: '.$table);
  264. $sql = call_user_func_array(array('SCAR_'.$uri['scheme'], 'showColumns'), array($table));
  265. $rs = call_user_func_array(array('SCAR_'.$uri['scheme'], 'query'), array($db, $sql));
  266. $columns = array();
  267. while ($row = call_user_func_array(array('SCAR_'.$uri['scheme'], 'row'), array($rs))) {
  268. $columns[] = $row;
  269. }
  270. self::mark('showcolumns: '.$dsn.' tbl: '.$table, true);
  271. self::mark('parsecolumns: '.$dsn.' tbl: '.$table);
  272. $results = call_user_func_array(array('SCAR_'.$uri['scheme'], 'parseShowColumns'), array($columns));
  273. self::mark('parsecolumns: '.$dsn.' tbl: '.$table, true);
  274. return $results;
  275. }
  276. }