1. <?php
2. /**
3. * File containing the ezcDbFactory class.
4. *
5. * @package Database
6. * @version 1.4
7. * @copyright Copyright (C) 2005-2008 eZ systems as. All rights reserved.
8. * @license http://ez.no/licenses/new_bsd New BSD License
9. */
10.
11. /**
12. * ezcDbFactory manages the list of known database drivers
13. * and is used to create their instances.
14. *
15. * Example:
16. * <code>
17. * $dbparams = array(
18. * 'type' => 'mysql',
19. * 'dbname' => 'test',
20. * 'user' => 'john',
21. * 'pass' => 'topsecret' );
22. * $db = ezcDbFactory::create( $dbparams );
23. * $db->query( 'SELECT * FROM tbl' );
24. * </code>
25. *
26. * Instead of passing an array with those parameters, you can also pass a DSN:
27. *
28. * <code>
29. * $dsn = "mysql://root@localhost/geolocation";
30. * $db = ezcDbFactory::create( $dsn );
31. * </code>
32. *
33. * Other examples of DSNs are:
34. * <code>
35. * $dsn = "sqlite:///tmp/ezc.sqlite"; // Disk based databases for SQLite.
36. * $dsn = "sqlite://:memory:"; // In memory databases for SQLite.
37. * </code>
38. *
39. * Note that this class does not deal with character sets automatically, you
40. * have to make sure that you do that yourself. For MySQL that means running
41. * a query "SET NAMES" for example. See the tutorial for some hints on this.
42. *
43. * @see create()
44. *
45. * @package Database
46. * @version 1.4
47. */
48. class ezcDbFactory
49. {
50. /**
51. * List of supported database implementations.
52. *
53. * The list is an array with the form 'dbname' => 'HandlerClassName'
54. * This list may be extended using {@link addImplementation()}.
55. *
56. * @var array(string=>string)
57. */
58. static private $implementations = array( 'mysql' => 'ezcDbHandlerMysql',
59. 'pgsql' => 'ezcDbHandlerPgsql',
60. 'oracle' => 'ezcDbHandlerOracle',
61. 'sqlite' => 'ezcDbHandlerSqlite',
62. 'mssql' => 'ezcDbHandlerMssql', );
63.
64. /**
65. * Adds a database implementation to the list of known implementations.
66. *
67. * $implementationName is the name of the implemenation. This name should
68. * be short and uniquely identify the database. $className is the class name
69. * of the class that implements the handler for this database.
70. *
71. * Example:
72. * <code>
73. * class DB2Handler
74. * {
75. * }
76. * ezcDbFactory::addImplementation( 'db2', 'DB2Handler' );
77. * // ...
78. * $dbparams = array( 'handler' => 'db2', ... );
79. * $db = ezcDbFactory::create( $dbparams );
80. * </code>
81. *
82. * @param string $implementationName
83. * @param string $className
84. * @return void
85. */
86. static public function addImplementation( $implementationName, $className )
87. {
88. self::$implementations[$implementationName] = $className;
89. }
90.
91. /**
92. * Returns a list with supported database implementations.
93. *
94. * Example:
95. * <code>
96. * ezcDbFactory::getImplementations();
97. * </code>
98. *
99. * @return array(string)
100. */
101. static public function getImplementations()
102. {
103. $list = array();
104. foreach ( self::$implementations as $name => $className )
105. {
106. $list[] = $name;
107. }
108. return $list;
109. }
110.
111. /**
112. * Creates and returns an instance of the specified ezcDbHandler implementation.
113. *
114. * Supported database parameters are:
115. * - phptype|type|handler|driver: Database implementation
116. * - user|username: Database user name
117. * - pass|password: Database user password
118. * - dbname|database: Database name
119. * - host|hostspec: Name of the host database is running on
120. * - port: TCP port
121. * - charset: Client character set
122. * - socket: UNIX socket path
123. *
124. * The list above is actually driver-dependent and may be extended in the future.
125. * You can specify any parameters your database handler supports.
126. *
127. * @throws ezcDbHandlerNotFoundException if the requested database handler could not be found.
128. * @param mixed $dbParams Database parameters
129. * (driver, host, port, user, pass, etc).
130. * May be specified either as array (key => val ....) or as DSN string.
131. * Format of the DSN is the same as accepted by PEAR::DB::parseDSN().
132. * @return ezcDbHandler
133. */
134. static public function create( $dbParams )
135. {
136. if ( is_string( $dbParams ) )
137. {
138. $dbParams = self::parseDSN( $dbParams );
139. }
140.
141. $impName = null; // implementation name
142.
143. foreach ( $dbParams as $key => $val )
144. {
145. if ( in_array( $key, array( 'phptype', 'type', 'handler', 'driver' ) ) )
146. {
147. $impName = $val;
148. break;
149. }
150. }
151.
152. if ( $impName === null || !array_key_exists( $impName, self::$implementations ) )
153. {
154. throw new ezcDbHandlerNotFoundException( $impName, array_keys( self::$implementations ) );
155. }
156.
157. $className = self::$implementations[$impName];
158. $instance = new $className( $dbParams );
159.
160. return $instance;
161. }
162.
163. /**
164. * Returns the Data Source Name as a structure containing the various parts of the DSN.
165. *
166. * Additional keys can be added by appending a URI query string to the
167. * end of the DSN.
168. *
169. * The format of the supplied DSN is in its fullest form:
170. * <code>
171. * phptype(dbsyntax)://username:password@protocol+hostspec/database?option=8&another=true
172. * </code>
173. *
174. * Most variations are allowed:
175. * <code>
176. * phptype://username:password@protocol+hostspec:110//usr/db_file.db?mode=0644
177. * phptype://username:password@hostspec/database_name
178. * phptype://username:password@hostspec
179. * phptype://username@hostspec
180. * phptype://hostspec/database
181. * phptype://hostspec
182. * phptype(dbsyntax)
183. * phptype
184. * </code>
185. *
186. * This function is 'borrowed' from PEAR /DB.php .
187. *
188. * @param string $dsn Data Source Name to be parsed
189. *
190. * @return array an associative array with the following keys:
191. * + phptype: Database backend used in PHP (mysql, odbc etc.)
192. * + dbsyntax: Database used with regards to SQL syntax etc.
193. * + protocol: Communication protocol to use (tcp, unix etc.)
194. * + hostspec: Host specification (hostname[:port])
195. * + database: Database to use on the DBMS server
196. * + username: User name for login
197. * + password: Password for login
198. */
199. public static function parseDSN( $dsn )
200. {
201. $parsed = array(
202. 'phptype' => false,
203. 'dbsyntax' => false,
204. 'username' => false,
205. 'password' => false,
206. 'protocol' => false,
207. 'hostspec' => false,
208. 'port' => false,
209. 'socket' => false,
210. 'database' => false,
211. );
212.
213. if ( is_array( $dsn ) )
214. {
215. $dsn = array_merge( $parsed, $dsn );
216. if ( !$dsn['dbsyntax'] )
217. {
218. $dsn['dbsyntax'] = $dsn['phptype'];
219. }
220. return $dsn;
221. }
222.
223. // Find phptype and dbsyntax
224. if ( ( $pos = strpos( $dsn, '://' ) ) !== false )
225. {
226. $str = substr( $dsn, 0, $pos );
227. $dsn = substr( $dsn, $pos + 3 );
228. }
229. else
230. {
231. $str = $dsn;
232. $dsn = null;
233. }
234.
235. // Get phptype and dbsyntax
236. // $str => phptype(dbsyntax)
237. if ( preg_match( '|^(.+?)\((.*?)\)$|', $str, $arr ) )
238. {
239. $parsed['phptype'] = $arr[1];
240. $parsed['dbsyntax'] = !$arr[2] ? $arr[1] : $arr[2];
241. }
242. else
243. {
244. $parsed['phptype'] = $str;
245. $parsed['dbsyntax'] = $str;
246. }
247.
248. if ( !count( $dsn ) )
249. {
250. return $parsed;
251. }
252.
253. // Get (if found): username and password
254. // $dsn => username:password@protocol+hostspec/database
255. if ( ( $at = strrpos( (string) $dsn, '@' ) ) !== false )
256. {
257. $str = substr( $dsn, 0, $at );
258. $dsn = substr( $dsn, $at + 1 );
259. if ( ( $pos = strpos( $str, ':' ) ) !== false )
260. {
261. $parsed['username'] = rawurldecode( substr( $str, 0, $pos ) );
262. $parsed['password'] = rawurldecode( substr( $str, $pos + 1 ) );
263. }
264. else
265. {
266. $parsed['username'] = rawurldecode( $str );
267. }
268. }
269.
270. // Find protocol and hostspec
271.
272. if ( preg_match( '|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match ) )
273. {
274. // $dsn => proto(proto_opts)/database
275. $proto = $match[1];
276. $proto_opts = $match[2] ? $match[2] : false;
277. $dsn = $match[3];
278. }
279. else
280. {
281. // $dsn => protocol+hostspec/database (old format)
282. if ( strpos( $dsn, '+' ) !== false )
283. {
284. list( $proto, $dsn ) = explode( '+', $dsn, 2 );
285. }
286. if ( strpos( $dsn, '/' ) !== false )
287. {
288. list( $proto_opts, $dsn ) = explode( '/', $dsn, 2 );
289. }
290. else
291. {
292. $proto_opts = $dsn;
293. $dsn = null;
294. }
295. }
296.
297. // process the different protocol options
298. $parsed['protocol'] = ( !empty( $proto ) ) ? $proto : 'tcp';
299. $proto_opts = rawurldecode( $proto_opts );
300. if ( $parsed['protocol'] == 'tcp' )
301. {
302. if ( strpos( $proto_opts, ':' ) !== false )
303. {
304. list( $parsed['hostspec'], $parsed['port'] ) = explode( ':', $proto_opts );
305. }
306. else
307. {
308. $parsed['hostspec'] = $proto_opts;
309. }
310. }
311. elseif ( $parsed['protocol'] == 'unix' )
312. {
313. $parsed['socket'] = $proto_opts;
314. }
315.
316. // Get dabase if any
317. // $dsn => database
318. if ( $dsn )
319. {
320. if ( ( $pos = strpos( $dsn, '?' ) ) === false )
321. {
322. // /database
323. $parsed['database'] = rawurldecode( $dsn );
324. }
325. else
326. {
327. // /database?param1=value1¶m2=value2
328. $parsed['database'] = rawurldecode( substr( $dsn, 0, $pos ) );
329. $dsn = substr( $dsn, $pos + 1 );
330. if ( strpos( $dsn, '&') !== false )
331. {
332. $opts = explode( '&', $dsn );
333. }
334. else
335. { // database?param1=value1
336. $opts = array( $dsn );
337. }
338. foreach ( $opts as $opt )
339. {
340. list( $key, $value ) = explode( '=', $opt );
341. if ( !isset( $parsed[$key] ) )
342. {
343. // don't allow params overwrite
344. $parsed[$key] = rawurldecode( $value );
345. }
346. }
347. }
348. }
349. return $parsed;
350. }
351. }
352. ?>