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