1: <?php
2: /**
3: * DataSource base class
4: *
5: * PHP versions 4 and 5
6: *
7: * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
8: * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
9: *
10: * Licensed under The MIT License
11: * Redistributions of files must retain the above copyright notice.
12: *
13: * @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
14: * @link http://cakephp.org CakePHP(tm) Project
15: * @package cake
16: * @subpackage cake.cake.libs.model.datasources
17: * @since CakePHP(tm) v 0.10.5.1790
18: * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
19: */
20:
21: /**
22: * DataSource base class
23: *
24: * @package cake
25: * @subpackage cake.cake.libs.model.datasources
26: */
27: class DataSource extends Object {
28:
29: /**
30: * Are we connected to the DataSource?
31: *
32: * @var boolean
33: * @access public
34: */
35: var $connected = false;
36:
37: /**
38: * Print full query debug info?
39: *
40: * @var boolean
41: * @access public
42: */
43: var $fullDebug = false;
44:
45: /**
46: * Error description of last query
47: *
48: * @var unknown_type
49: * @access public
50: */
51: var $error = null;
52:
53: /**
54: * String to hold how many rows were affected by the last SQL operation.
55: *
56: * @var string
57: * @access public
58: */
59: var $affected = null;
60:
61: /**
62: * Number of rows in current resultset
63: *
64: * @var int
65: * @access public
66: */
67: var $numRows = null;
68:
69: /**
70: * Time the last query took
71: *
72: * @var int
73: * @access public
74: */
75: var $took = null;
76:
77: /**
78: * The starting character that this DataSource uses for quoted identifiers.
79: *
80: * @var string
81: * @access public
82: */
83: var $startQuote = null;
84:
85: /**
86: * The ending character that this DataSource uses for quoted identifiers.
87: *
88: * @var string
89: * @access public
90: */
91: var $endQuote = null;
92:
93: /**
94: * Result
95: *
96: * @var array
97: * @access protected
98: */
99: var $_result = null;
100:
101: /**
102: * Queries count.
103: *
104: * @var int
105: * @access protected
106: */
107: var $_queriesCnt = 0;
108:
109: /**
110: * Total duration of all queries.
111: *
112: * @var unknown_type
113: * @access protected
114: */
115: var $_queriesTime = null;
116:
117: /**
118: * Log of queries executed by this DataSource
119: *
120: * @var unknown_type
121: * @access protected
122: */
123: var $_queriesLog = array();
124:
125: /**
126: * Maximum number of items in query log
127: *
128: * This is to prevent query log taking over too much memory.
129: *
130: * @var int Maximum number of queries in the queries log.
131: * @access protected
132: */
133: var $_queriesLogMax = 200;
134:
135: /**
136: * Caches serialzed results of executed queries
137: *
138: * @var array Maximum number of queries in the queries log.
139: * @access protected
140: */
141: var $_queryCache = array();
142:
143: /**
144: * The default configuration of a specific DataSource
145: *
146: * @var array
147: * @access protected
148: */
149: var $_baseConfig = array();
150:
151: /**
152: * Holds references to descriptions loaded by the DataSource
153: *
154: * @var array
155: * @access private
156: */
157: var $__descriptions = array();
158:
159: /**
160: * Holds a list of sources (tables) contained in the DataSource
161: *
162: * @var array
163: * @access protected
164: */
165: var $_sources = null;
166:
167: /**
168: * A reference to the physical connection of this DataSource
169: *
170: * @var array
171: * @access public
172: */
173: var $connection = null;
174:
175: /**
176: * The DataSource configuration
177: *
178: * @var array
179: * @access public
180: */
181: var $config = array();
182:
183: /**
184: * The DataSource configuration key name
185: *
186: * @var string
187: * @access public
188: */
189: var $configKeyName = null;
190:
191: /**
192: * Whether or not this DataSource is in the middle of a transaction
193: *
194: * @var boolean
195: * @access protected
196: */
197: var $_transactionStarted = false;
198:
199: /**
200: * Whether or not source data like available tables and schema descriptions
201: * should be cached
202: *
203: * @var boolean
204: * @access public
205: */
206: var $cacheSources = true;
207:
208: /**
209: * Constructor.
210: *
211: * @param array $config Array of configuration information for the datasource.
212: * @return void.
213: */
214: function __construct($config = array()) {
215: parent::__construct();
216: $this->setConfig($config);
217: }
218:
219: /**
220: * Caches/returns cached results for child instances
221: *
222: * @param mixed $data
223: * @return array Array of sources available in this datasource.
224: * @access public
225: */
226: function listSources($data = null) {
227: if ($this->cacheSources === false) {
228: return null;
229: }
230:
231: if ($this->_sources !== null) {
232: return $this->_sources;
233: }
234:
235: $key = ConnectionManager::getSourceName($this) . '_' . $this->config['database'] . '_list';
236: $key = preg_replace('/[^A-Za-z0-9_\-.+]/', '_', $key);
237: $sources = Cache::read($key, '_cake_model_');
238:
239: if (empty($sources)) {
240: $sources = $data;
241: Cache::write($key, $data, '_cake_model_');
242: }
243:
244: $this->_sources = $sources;
245: return $sources;
246: }
247:
248: /**
249: * Convenience method for DboSource::listSources(). Returns source names in lowercase.
250: *
251: * @param boolean $reset Whether or not the source list should be reset.
252: * @return array Array of sources available in this datasource
253: * @access public
254: */
255: function sources($reset = false) {
256: if ($reset === true) {
257: $this->_sources = null;
258: }
259: return array_map('strtolower', $this->listSources());
260: }
261:
262: /**
263: * Returns a Model description (metadata) or null if none found.
264: *
265: * @param Model $model
266: * @return array Array of Metadata for the $model
267: * @access public
268: */
269: function describe(&$model) {
270: if ($this->cacheSources === false) {
271: return null;
272: }
273: $table = $model->tablePrefix . $model->table;
274:
275: if (isset($this->__descriptions[$table])) {
276: return $this->__descriptions[$table];
277: }
278: $cache = $this->__cacheDescription($table);
279:
280: if ($cache !== null) {
281: $this->__descriptions[$table] =& $cache;
282: return $cache;
283: }
284: return null;
285: }
286:
287: /**
288: * Begin a transaction
289: *
290: * @return boolean Returns true if a transaction is not in progress
291: * @access public
292: */
293: function begin(&$model) {
294: return !$this->_transactionStarted;
295: }
296:
297: /**
298: * Commit a transaction
299: *
300: * @return boolean Returns true if a transaction is in progress
301: * @access public
302: */
303: function commit(&$model) {
304: return $this->_transactionStarted;
305: }
306:
307: /**
308: * Rollback a transaction
309: *
310: * @return boolean Returns true if a transaction is in progress
311: * @access public
312: */
313: function rollback(&$model) {
314: return $this->_transactionStarted;
315: }
316:
317: /**
318: * Converts column types to basic types
319: *
320: * @param string $real Real column type (i.e. "varchar(255)")
321: * @return string Abstract column type (i.e. "string")
322: * @access public
323: */
324: function column($real) {
325: return false;
326: }
327:
328: /**
329: * Used to create new records. The "C" CRUD.
330: *
331: * To-be-overridden in subclasses.
332: *
333: * @param Model $model The Model to be created.
334: * @param array $fields An Array of fields to be saved.
335: * @param array $values An Array of values to save.
336: * @return boolean success
337: * @access public
338: */
339: function create(&$model, $fields = null, $values = null) {
340: return false;
341: }
342:
343: /**
344: * Used to read records from the Datasource. The "R" in CRUD
345: *
346: * To-be-overridden in subclasses.
347: *
348: * @param Model $model The model being read.
349: * @param array $queryData An array of query data used to find the data you want
350: * @return mixed
351: * @access public
352: */
353: function read(&$model, $queryData = array()) {
354: return false;
355: }
356:
357: /**
358: * Update a record(s) in the datasource.
359: *
360: * To-be-overridden in subclasses.
361: *
362: * @param Model $model Instance of the model class being updated
363: * @param array $fields Array of fields to be updated
364: * @param array $values Array of values to be update $fields to.
365: * @return boolean Success
366: * @access public
367: */
368: function update(&$model, $fields = null, $values = null) {
369: return false;
370: }
371:
372: /**
373: * Delete a record(s) in the datasource.
374: *
375: * To-be-overridden in subclasses.
376: *
377: * @param Model $model The model class having record(s) deleted
378: * @param mixed $conditions The conditions to use for deleting.
379: * @access public
380: */
381: function delete(&$model, $conditions = null) {
382: return false;
383: }
384:
385: /**
386: * Returns the ID generated from the previous INSERT operation.
387: *
388: * @param unknown_type $source
389: * @return mixed Last ID key generated in previous INSERT
390: * @access public
391: */
392: function lastInsertId($source = null) {
393: return false;
394: }
395:
396: /**
397: * Returns the number of rows returned by last operation.
398: *
399: * @param unknown_type $source
400: * @return integer Number of rows returned by last operation
401: * @access public
402: */
403: function lastNumRows($source = null) {
404: return false;
405: }
406:
407: /**
408: * Returns the number of rows affected by last query.
409: *
410: * @param unknown_type $source
411: * @return integer Number of rows affected by last query.
412: * @access public
413: */
414: function lastAffected($source = null) {
415: return false;
416: }
417:
418: /**
419: * Check whether the conditions for the Datasource being available
420: * are satisfied. Often used from connect() to check for support
421: * before establishing a connection.
422: *
423: * @return boolean Whether or not the Datasources conditions for use are met.
424: * @access public
425: */
426: function enabled() {
427: return true;
428: }
429:
430: /**
431: * Returns true if the DataSource supports the given interface (method)
432: *
433: * @param string $interface The name of the interface (method)
434: * @return boolean True on success
435: * @access public
436: */
437: function isInterfaceSupported($interface) {
438: static $methods = false;
439: if ($methods === false) {
440: $methods = array_map('strtolower', get_class_methods($this));
441: }
442: return in_array(strtolower($interface), $methods);
443: }
444:
445: /**
446: * Sets the configuration for the DataSource.
447: * Merges the $config information with the _baseConfig and the existing $config property.
448: *
449: * @param array $config The configuration array
450: * @return void
451: * @access public
452: */
453: function setConfig($config = array()) {
454: $this->config = array_merge($this->_baseConfig, $this->config, $config);
455: }
456:
457: /**
458: * Cache the DataSource description
459: *
460: * @param string $object The name of the object (model) to cache
461: * @param mixed $data The description of the model, usually a string or array
462: * @return mixed
463: * @access private
464: */
465: function __cacheDescription($object, $data = null) {
466: if ($this->cacheSources === false) {
467: return null;
468: }
469:
470: if ($data !== null) {
471: $this->__descriptions[$object] =& $data;
472: }
473:
474: $key = ConnectionManager::getSourceName($this) . '_' . $object;
475: $cache = Cache::read($key, '_cake_model_');
476:
477: if (empty($cache)) {
478: $cache = $data;
479: Cache::write($key, $cache, '_cake_model_');
480: }
481:
482: return $cache;
483: }
484:
485: /**
486: * Replaces `{$__cakeID__$}` and `{$__cakeForeignKey__$}` placeholders in query data.
487: *
488: * @param string $query Query string needing replacements done.
489: * @param array $data Array of data with values that will be inserted in placeholders.
490: * @param string $association Name of association model being replaced
491: * @param unknown_type $assocData
492: * @param Model $model Instance of the model to replace $__cakeID__$
493: * @param Model $linkModel Instance of model to replace $__cakeForeignKey__$
494: * @param array $stack
495: * @return string String of query data with placeholders replaced.
496: * @access public
497: * @todo Remove and refactor $assocData, ensure uses of the method have the param removed too.
498: */
499: function insertQueryData($query, $data, $association, $assocData, &$model, &$linkModel, $stack) {
500: $keys = array('{$__cakeID__$}', '{$__cakeForeignKey__$}');
501:
502: foreach ($keys as $key) {
503: $val = null;
504: $type = null;
505:
506: if (strpos($query, $key) !== false) {
507: switch ($key) {
508: case '{$__cakeID__$}':
509: if (isset($data[$model->alias]) || isset($data[$association])) {
510: if (isset($data[$model->alias][$model->primaryKey])) {
511: $val = $data[$model->alias][$model->primaryKey];
512: } elseif (isset($data[$association][$model->primaryKey])) {
513: $val = $data[$association][$model->primaryKey];
514: }
515: } else {
516: $found = false;
517: foreach (array_reverse($stack) as $assoc) {
518: if (isset($data[$assoc]) && isset($data[$assoc][$model->primaryKey])) {
519: $val = $data[$assoc][$model->primaryKey];
520: $found = true;
521: break;
522: }
523: }
524: if (!$found) {
525: $val = '';
526: }
527: }
528: $type = $model->getColumnType($model->primaryKey);
529: break;
530: case '{$__cakeForeignKey__$}':
531: foreach ($model->__associations as $id => $name) {
532: foreach ($model->$name as $assocName => $assoc) {
533: if ($assocName === $association) {
534: if (isset($assoc['foreignKey'])) {
535: $foreignKey = $assoc['foreignKey'];
536: $assocModel = $model->$assocName;
537: $type = $assocModel->getColumnType($assocModel->primaryKey);
538:
539: if (isset($data[$model->alias][$foreignKey])) {
540: $val = $data[$model->alias][$foreignKey];
541: } elseif (isset($data[$association][$foreignKey])) {
542: $val = $data[$association][$foreignKey];
543: } else {
544: $found = false;
545: foreach (array_reverse($stack) as $assoc) {
546: if (isset($data[$assoc]) && isset($data[$assoc][$foreignKey])) {
547: $val = $data[$assoc][$foreignKey];
548: $found = true;
549: break;
550: }
551: }
552: if (!$found) {
553: $val = '';
554: }
555: }
556: }
557: break 3;
558: }
559: }
560: }
561: break;
562: }
563: if (empty($val) && $val !== '0') {
564: return false;
565: }
566: $query = str_replace($key, $this->value($val, $type), $query);
567: }
568: }
569: return $query;
570: }
571:
572: /**
573: * To-be-overridden in subclasses.
574: *
575: * @param Model $model Model instance
576: * @param string $key Key name to make
577: * @return string Key name for model.
578: * @access public
579: */
580: function resolveKey(&$model, $key) {
581: return $model->alias . $key;
582: }
583:
584: /**
585: * Closes the current datasource.
586: *
587: * @return void
588: * @access public
589: */
590: function __destruct() {
591: if ($this->_transactionStarted) {
592: $null = null;
593: $this->rollback($null);
594: }
595: if ($this->connected) {
596: $this->close();
597: }
598: }
599: }
600: