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