1: <?php
2: /**
3: * DataSource base class
4: *
5: * PHP 5
6: *
7: * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
8: * Copyright 2005-2011, 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-2011, 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: * @return mixed
201: */
202: public function read(Model $model, $queryData = array()) {
203: return false;
204: }
205:
206: /**
207: * Update a record(s) in the datasource.
208: *
209: * To-be-overridden in subclasses.
210: *
211: * @param Model $model Instance of the model class being updated
212: * @param array $fields Array of fields to be updated
213: * @param array $values Array of values to be update $fields to.
214: * @return boolean Success
215: */
216: public function update(Model $model, $fields = null, $values = null) {
217: return false;
218: }
219:
220: /**
221: * Delete a record(s) in the datasource.
222: *
223: * To-be-overridden in subclasses.
224: *
225: * @param Model $model The model class having record(s) deleted
226: * @param mixed $conditions The conditions to use for deleting.
227: * @return void
228: */
229: public function delete(Model $model, $id = null) {
230: return false;
231: }
232:
233: /**
234: * Returns the ID generated from the previous INSERT operation.
235: *
236: * @param mixed $source
237: * @return mixed Last ID key generated in previous INSERT
238: */
239: public function lastInsertId($source = null) {
240: return false;
241: }
242:
243: /**
244: * Returns the number of rows returned by last operation.
245: *
246: * @param mixed $source
247: * @return integer Number of rows returned by last operation
248: */
249: public function lastNumRows($source = null) {
250: return false;
251: }
252:
253: /**
254: * Returns the number of rows affected by last query.
255: *
256: * @param mixed $source
257: * @return integer Number of rows affected by last query.
258: */
259: public function lastAffected($source = null) {
260: return false;
261: }
262:
263: /**
264: * Check whether the conditions for the Datasource being available
265: * are satisfied. Often used from connect() to check for support
266: * before establishing a connection.
267: *
268: * @return boolean Whether or not the Datasources conditions for use are met.
269: */
270: public function enabled() {
271: return true;
272: }
273:
274: /**
275: * Sets the configuration for the DataSource.
276: * Merges the $config information with the _baseConfig and the existing $config property.
277: *
278: * @param array $config The configuration array
279: * @return void
280: */
281: public function setConfig($config = array()) {
282: $this->config = array_merge($this->_baseConfig, $this->config, $config);
283: }
284:
285: /**
286: * Cache the DataSource description
287: *
288: * @param string $object The name of the object (model) to cache
289: * @param mixed $data The description of the model, usually a string or array
290: * @return mixed
291: */
292: protected function _cacheDescription($object, $data = null) {
293: if ($this->cacheSources === false) {
294: return null;
295: }
296:
297: if ($data !== null) {
298: $this->_descriptions[$object] =& $data;
299: }
300:
301: $key = ConnectionManager::getSourceName($this) . '_' . $object;
302: $cache = Cache::read($key, '_cake_model_');
303:
304: if (empty($cache)) {
305: $cache = $data;
306: Cache::write($key, $cache, '_cake_model_');
307: }
308:
309: return $cache;
310: }
311:
312: /**
313: * Replaces `{$__cakeID__$}` and `{$__cakeForeignKey__$}` placeholders in query data.
314: *
315: * @param string $query Query string needing replacements done.
316: * @param array $data Array of data with values that will be inserted in placeholders.
317: * @param string $association Name of association model being replaced
318: * @param array $assocData
319: * @param Model $model Instance of the model to replace $__cakeID__$
320: * @param Model $linkModel Instance of model to replace $__cakeForeignKey__$
321: * @param array $stack
322: * @return string String of query data with placeholders replaced.
323: * @todo Remove and refactor $assocData, ensure uses of the method have the param removed too.
324: */
325: public function insertQueryData($query, $data, $association, $assocData, Model $model, Model $linkModel, $stack) {
326: $keys = array('{$__cakeID__$}', '{$__cakeForeignKey__$}');
327:
328: foreach ($keys as $key) {
329: $val = null;
330: $type = null;
331:
332: if (strpos($query, $key) !== false) {
333: switch ($key) {
334: case '{$__cakeID__$}':
335: if (isset($data[$model->alias]) || isset($data[$association])) {
336: if (isset($data[$model->alias][$model->primaryKey])) {
337: $val = $data[$model->alias][$model->primaryKey];
338: } elseif (isset($data[$association][$model->primaryKey])) {
339: $val = $data[$association][$model->primaryKey];
340: }
341: } else {
342: $found = false;
343: foreach (array_reverse($stack) as $assoc) {
344: if (isset($data[$assoc]) && isset($data[$assoc][$model->primaryKey])) {
345: $val = $data[$assoc][$model->primaryKey];
346: $found = true;
347: break;
348: }
349: }
350: if (!$found) {
351: $val = '';
352: }
353: }
354: $type = $model->getColumnType($model->primaryKey);
355: break;
356: case '{$__cakeForeignKey__$}':
357: foreach ($model->associations() as $id => $name) {
358: foreach ($model->$name as $assocName => $assoc) {
359: if ($assocName === $association) {
360: if (isset($assoc['foreignKey'])) {
361: $foreignKey = $assoc['foreignKey'];
362: $assocModel = $model->$assocName;
363: $type = $assocModel->getColumnType($assocModel->primaryKey);
364:
365: if (isset($data[$model->alias][$foreignKey])) {
366: $val = $data[$model->alias][$foreignKey];
367: } elseif (isset($data[$association][$foreignKey])) {
368: $val = $data[$association][$foreignKey];
369: } else {
370: $found = false;
371: foreach (array_reverse($stack) as $assoc) {
372: if (isset($data[$assoc]) && isset($data[$assoc][$foreignKey])) {
373: $val = $data[$assoc][$foreignKey];
374: $found = true;
375: break;
376: }
377: }
378: if (!$found) {
379: $val = '';
380: }
381: }
382: }
383: break 3;
384: }
385: }
386: }
387: break;
388: }
389: if (empty($val) && $val !== '0') {
390: return false;
391: }
392: $query = str_replace($key, $this->value($val, $type), $query);
393: }
394: }
395: return $query;
396: }
397:
398: /**
399: * To-be-overridden in subclasses.
400: *
401: * @param Model $model Model instance
402: * @param string $key Key name to make
403: * @return string Key name for model.
404: */
405: public function resolveKey(Model $model, $key) {
406: return $model->alias . $key;
407: }
408:
409: /**
410: * Closes the current datasource.
411: *
412: */
413: public function __destruct() {
414: if ($this->_transactionStarted) {
415: $this->rollback();
416: }
417: if ($this->connected) {
418: $this->close();
419: }
420: }
421: }
422: