1: <?php
2: /**
3: * DataSource base class
4: *
5: * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
6: * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
7: *
8: * Licensed under The MIT License
9: * For full copyright and license information, please see the LICENSE.txt
10: * Redistributions of files must retain the above copyright notice.
11: *
12: * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
13: * @link http://cakephp.org CakePHP(tm) Project
14: * @package Cake.Model.Datasource
15: * @since CakePHP(tm) v 0.10.5.1790
16: * @license http://www.opensource.org/licenses/mit-license.php MIT License
17: */
18:
19: /**
20: * DataSource base class
21: *
22: * DataSources are the link between models and the source of data that models represent.
23: *
24: * @link http://book.cakephp.org/2.0/en/models/datasources.html#basic-api-for-datasources
25: * @package Cake.Model.Datasource
26: */
27: class DataSource extends Object {
28:
29: /**
30: * Are we connected to the DataSource?
31: *
32: * @var boolean
33: */
34: public $connected = false;
35:
36: /**
37: * The default configuration of a specific DataSource
38: *
39: * @var array
40: */
41: protected $_baseConfig = array();
42:
43: /**
44: * Holds references to descriptions loaded by the DataSource
45: *
46: * @var array
47: */
48: protected $_descriptions = array();
49:
50: /**
51: * Holds a list of sources (tables) contained in the DataSource
52: *
53: * @var array
54: */
55: protected $_sources = null;
56:
57: /**
58: * The DataSource configuration
59: *
60: * @var array
61: */
62: public $config = array();
63:
64: /**
65: * Whether or not this DataSource is in the middle of a transaction
66: *
67: * @var boolean
68: */
69: protected $_transactionStarted = false;
70:
71: /**
72: * Whether or not source data like available tables and schema descriptions
73: * should be cached
74: *
75: * @var boolean
76: */
77: public $cacheSources = true;
78:
79: /**
80: * Constructor.
81: *
82: * @param array $config Array of configuration information for the datasource.
83: */
84: public function __construct($config = array()) {
85: parent::__construct();
86: $this->setConfig($config);
87: }
88:
89: /**
90: * Caches/returns cached results for child instances
91: *
92: * @param mixed $data
93: * @return array Array of sources available in this datasource.
94: */
95: public function listSources($data = null) {
96: if ($this->cacheSources === false) {
97: return null;
98: }
99:
100: if ($this->_sources !== null) {
101: return $this->_sources;
102: }
103:
104: $key = ConnectionManager::getSourceName($this) . '_' . $this->config['database'] . '_list';
105: $key = preg_replace('/[^A-Za-z0-9_\-.+]/', '_', $key);
106: $sources = Cache::read($key, '_cake_model_');
107:
108: if (empty($sources)) {
109: $sources = $data;
110: Cache::write($key, $data, '_cake_model_');
111: }
112:
113: return $this->_sources = $sources;
114: }
115:
116: /**
117: * Returns a Model description (metadata) or null if none found.
118: *
119: * @param Model|string $model
120: * @return array Array of Metadata for the $model
121: */
122: public function describe($model) {
123: if ($this->cacheSources === false) {
124: return null;
125: }
126: if (is_string($model)) {
127: $table = $model;
128: } else {
129: $table = $model->tablePrefix . $model->table;
130: }
131:
132: if (isset($this->_descriptions[$table])) {
133: return $this->_descriptions[$table];
134: }
135: $cache = $this->_cacheDescription($table);
136:
137: if ($cache !== null) {
138: $this->_descriptions[$table] =& $cache;
139: return $cache;
140: }
141: return null;
142: }
143:
144: /**
145: * Begin a transaction
146: *
147: * @return boolean Returns true if a transaction is not in progress
148: */
149: public function begin() {
150: return !$this->_transactionStarted;
151: }
152:
153: /**
154: * Commit a transaction
155: *
156: * @return boolean Returns true if a transaction is in progress
157: */
158: public function commit() {
159: return $this->_transactionStarted;
160: }
161:
162: /**
163: * Rollback a transaction
164: *
165: * @return boolean Returns true if a transaction is in progress
166: */
167: public function rollback() {
168: return $this->_transactionStarted;
169: }
170:
171: /**
172: * Converts column types to basic types
173: *
174: * @param string $real Real column type (i.e. "varchar(255)")
175: * @return string Abstract column type (i.e. "string")
176: */
177: public function column($real) {
178: return false;
179: }
180:
181: /**
182: * Used to create new records. The "C" CRUD.
183: *
184: * To-be-overridden in subclasses.
185: *
186: * @param Model $model The Model to be created.
187: * @param array $fields An Array of fields to be saved.
188: * @param array $values An Array of values to save.
189: * @return boolean success
190: */
191: public function create(Model $model, $fields = null, $values = null) {
192: return false;
193: }
194:
195: /**
196: * Used to read records from the Datasource. The "R" in CRUD
197: *
198: * To-be-overridden in subclasses.
199: *
200: * @param Model $model The model being read.
201: * @param array $queryData An array of query data used to find the data you want
202: * @param integer $recursive Number of levels of association
203: * @return mixed
204: */
205: public function read(Model $model, $queryData = array(), $recursive = null) {
206: return false;
207: }
208:
209: /**
210: * Update a record(s) in the datasource.
211: *
212: * To-be-overridden in subclasses.
213: *
214: * @param Model $model Instance of the model class being updated
215: * @param array $fields Array of fields to be updated
216: * @param array $values Array of values to be update $fields to.
217: * @param mixed $conditions
218: * @return boolean Success
219: */
220: public function update(Model $model, $fields = null, $values = null, $conditions = null) {
221: return false;
222: }
223:
224: /**
225: * Delete a record(s) in the datasource.
226: *
227: * To-be-overridden in subclasses.
228: *
229: * @param Model $model The model class having record(s) deleted
230: * @param mixed $conditions The conditions to use for deleting.
231: * @return boolean Success
232: */
233: public function delete(Model $model, $conditions = null) {
234: return false;
235: }
236:
237: /**
238: * Returns the ID generated from the previous INSERT operation.
239: *
240: * @param mixed $source
241: * @return mixed Last ID key generated in previous INSERT
242: */
243: public function lastInsertId($source = null) {
244: return false;
245: }
246:
247: /**
248: * Returns the number of rows returned by last operation.
249: *
250: * @param mixed $source
251: * @return integer Number of rows returned by last operation
252: */
253: public function lastNumRows($source = null) {
254: return false;
255: }
256:
257: /**
258: * Returns the number of rows affected by last query.
259: *
260: * @param mixed $source
261: * @return integer Number of rows affected by last query.
262: */
263: public function lastAffected($source = null) {
264: return false;
265: }
266:
267: /**
268: * Check whether the conditions for the Datasource being available
269: * are satisfied. Often used from connect() to check for support
270: * before establishing a connection.
271: *
272: * @return boolean Whether or not the Datasources conditions for use are met.
273: */
274: public function enabled() {
275: return true;
276: }
277:
278: /**
279: * Sets the configuration for the DataSource.
280: * Merges the $config information with the _baseConfig and the existing $config property.
281: *
282: * @param array $config The configuration array
283: * @return void
284: */
285: public function setConfig($config = array()) {
286: $this->config = array_merge($this->_baseConfig, $this->config, $config);
287: }
288:
289: /**
290: * Cache the DataSource description
291: *
292: * @param string $object The name of the object (model) to cache
293: * @param mixed $data The description of the model, usually a string or array
294: * @return mixed
295: */
296: protected function _cacheDescription($object, $data = null) {
297: if ($this->cacheSources === false) {
298: return null;
299: }
300:
301: if ($data !== null) {
302: $this->_descriptions[$object] =& $data;
303: }
304:
305: $key = ConnectionManager::getSourceName($this) . '_' . $object;
306: $cache = Cache::read($key, '_cake_model_');
307:
308: if (empty($cache)) {
309: $cache = $data;
310: Cache::write($key, $cache, '_cake_model_');
311: }
312:
313: return $cache;
314: }
315:
316: /**
317: * Replaces `{$__cakeID__$}` and `{$__cakeForeignKey__$}` placeholders in query data.
318: *
319: * @param string $query Query string needing replacements done.
320: * @param array $data Array of data with values that will be inserted in placeholders.
321: * @param string $association Name of association model being replaced
322: * @param array $assocData
323: * @param Model $model Instance of the model to replace $__cakeID__$
324: * @param Model $linkModel Instance of model to replace $__cakeForeignKey__$
325: * @param array $stack
326: * @return string String of query data with placeholders replaced.
327: */
328: public function insertQueryData($query, $data, $association, $assocData, Model $model, Model $linkModel, $stack) {
329: $keys = array('{$__cakeID__$}', '{$__cakeForeignKey__$}');
330:
331: foreach ($keys as $key) {
332: $val = null;
333: $type = null;
334:
335: if (strpos($query, $key) !== false) {
336: switch ($key) {
337: case '{$__cakeID__$}':
338: if (isset($data[$model->alias]) || isset($data[$association])) {
339: if (isset($data[$model->alias][$model->primaryKey])) {
340: $val = $data[$model->alias][$model->primaryKey];
341: } elseif (isset($data[$association][$model->primaryKey])) {
342: $val = $data[$association][$model->primaryKey];
343: }
344: } else {
345: $found = false;
346: foreach (array_reverse($stack) as $assoc) {
347: if (isset($data[$assoc]) && isset($data[$assoc][$model->primaryKey])) {
348: $val = $data[$assoc][$model->primaryKey];
349: $found = true;
350: break;
351: }
352: }
353: if (!$found) {
354: $val = '';
355: }
356: }
357: $type = $model->getColumnType($model->primaryKey);
358: break;
359: case '{$__cakeForeignKey__$}':
360: foreach ($model->associations() as $name) {
361: foreach ($model->$name as $assocName => $assoc) {
362: if ($assocName === $association) {
363: if (isset($assoc['foreignKey'])) {
364: $foreignKey = $assoc['foreignKey'];
365: $assocModel = $model->$assocName;
366: $type = $assocModel->getColumnType($assocModel->primaryKey);
367:
368: if (isset($data[$model->alias][$foreignKey])) {
369: $val = $data[$model->alias][$foreignKey];
370: } elseif (isset($data[$association][$foreignKey])) {
371: $val = $data[$association][$foreignKey];
372: } else {
373: $found = false;
374: foreach (array_reverse($stack) as $assoc) {
375: if (isset($data[$assoc]) && isset($data[$assoc][$foreignKey])) {
376: $val = $data[$assoc][$foreignKey];
377: $found = true;
378: break;
379: }
380: }
381: if (!$found) {
382: $val = '';
383: }
384: }
385: }
386: break 3;
387: }
388: }
389: }
390: break;
391: }
392: if (empty($val) && $val !== '0') {
393: return false;
394: }
395: $query = str_replace($key, $this->value($val, $type), $query);
396: }
397: }
398: return $query;
399: }
400:
401: /**
402: * To-be-overridden in subclasses.
403: *
404: * @param Model $model Model instance
405: * @param string $key Key name to make
406: * @return string Key name for model.
407: */
408: public function resolveKey(Model $model, $key) {
409: return $model->alias . $key;
410: }
411:
412: /**
413: * Returns the schema name. Override this in subclasses.
414: *
415: * @return string schema name
416: */
417: public function getSchemaName() {
418: return null;
419: }
420:
421: /**
422: * Closes a connection. Override in subclasses
423: *
424: * @return boolean
425: */
426: public function close() {
427: return $this->connected = false;
428: }
429:
430: /**
431: * Closes the current datasource.
432: */
433: public function __destruct() {
434: if ($this->_transactionStarted) {
435: $this->rollback();
436: }
437: if ($this->connected) {
438: $this->close();
439: }
440: }
441:
442: }
443: