1
|
|
|
<?php |
2
|
|
|
namespace Agavi\Database; |
3
|
|
|
|
4
|
|
|
// +---------------------------------------------------------------------------+ |
5
|
|
|
// | This file is part of the Agavi package. | |
6
|
|
|
// | Copyright (c) 2005-2011 the Agavi Project. | |
7
|
|
|
// | | |
8
|
|
|
// | For the full copyright and license information, please view the LICENSE | |
9
|
|
|
// | file that was distributed with this source code. You can also view the | |
10
|
|
|
// | LICENSE file online at http://www.agavi.org/LICENSE.txt | |
11
|
|
|
// | vi: set noexpandtab: | |
12
|
|
|
// | Local Variables: | |
13
|
|
|
// | indent-tabs-mode: t | |
14
|
|
|
// | End: | |
15
|
|
|
// +---------------------------------------------------------------------------+ |
16
|
|
|
use Agavi\Exception\DatabaseException; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* A database adapter for the Doctrine ORM. |
20
|
|
|
* |
21
|
|
|
* @package agavi |
22
|
|
|
* @subpackage database |
23
|
|
|
* |
24
|
|
|
* @author Ross Lawley <[email protected]> |
25
|
|
|
* @author David Zülke <[email protected]> |
26
|
|
|
* @author TANAKA Koichi <[email protected]> |
27
|
|
|
* @copyright Authors |
28
|
|
|
* @copyright The Agavi Project |
29
|
|
|
* |
30
|
|
|
* @since 0.11.0 |
31
|
|
|
* |
32
|
|
|
* @version $Id$ |
33
|
|
|
*/ |
34
|
|
|
class DoctrineDatabase extends Database |
35
|
|
|
{ |
36
|
|
|
/** |
37
|
|
|
* @var Doctrine_Manager The Doctrine Manager instance we should use. |
38
|
|
|
*/ |
39
|
|
|
protected $doctrineManager; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* Connect to the database. |
43
|
|
|
* |
44
|
|
|
* @throws <b>AgaviDatabaseException</b> If a connection could not be |
45
|
|
|
* created. |
46
|
|
|
* |
47
|
|
|
* @author David Zülke <[email protected]> |
48
|
|
|
* @since 0.11.0 |
49
|
|
|
*/ |
50
|
|
|
public function connect() |
51
|
|
|
{ |
52
|
|
|
// this doesn't do anything, Doctrine is handling the lazy connection stuff |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* Retrieve a raw database resource associated with this Database |
57
|
|
|
* implementation. |
58
|
|
|
* |
59
|
|
|
* @return mixed A database resource. |
60
|
|
|
* |
61
|
|
|
* @throws <b>AgaviDatabaseException</b> If no resource could be retrieved |
62
|
|
|
* |
63
|
|
|
* @author David Zülke <[email protected]> |
64
|
|
|
* @since 0.11.0 |
65
|
|
|
*/ |
66
|
|
|
public function getResource() |
67
|
|
|
{ |
68
|
|
|
return $this->getConnection()->getDbh(); |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* Initialize Doctrine set the autoloading |
73
|
|
|
* |
74
|
|
|
* @param DatabaseManager $databaseManager The database manager of this instance. |
75
|
|
|
* @param array $parameters An assoc array of initialization params. |
76
|
|
|
* |
77
|
|
|
* @author David Zülke <[email protected]> |
78
|
|
|
* @author Ross Lawley <[email protected]> |
79
|
|
|
* @author TANAKA Koichi <[email protected]> |
80
|
|
|
* @since 0.11.0 |
81
|
|
|
*/ |
82
|
|
|
public function initialize(DatabaseManager $databaseManager, array $parameters = array()) |
83
|
|
|
{ |
84
|
|
|
parent::initialize($databaseManager, $parameters); |
85
|
|
|
|
86
|
|
|
$name = $this->getName(); |
87
|
|
|
|
88
|
|
|
// try to autoload doctrine |
89
|
|
|
if (!class_exists('Doctrine')) { |
90
|
|
|
// okay that didn't work. last resort: include it. we assume it's on the include path by default |
91
|
|
|
require('Doctrine.php'); |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
$is12 = version_compare(Doctrine::VERSION, '1.2', '>='); |
95
|
|
|
|
96
|
|
|
if (!$is12) { |
97
|
|
|
trigger_error('Support for Doctrine versions older than 1.2 is deprecated and will be removed in Agavi 1.2.', E_USER_DEPRECATED); |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
// in any case, it's loaded now. maybe we need to register the autoloading stuff for it! |
101
|
|
|
// we need this list further down |
102
|
|
|
$splAutoloadFunctions = spl_autoload_functions(); |
103
|
|
View Code Duplication |
if (!in_array(array('Doctrine', 'autoload'), $splAutoloadFunctions) && !in_array(array('Doctrine_Core', 'autoload'), $splAutoloadFunctions)) { |
|
|
|
|
104
|
|
|
// we do |
105
|
|
|
spl_autoload_register(array('Doctrine', 'autoload')); |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
// cool. Assign the Doctrine Manager instance |
109
|
|
|
$this->doctrineManager = Doctrine_Manager::getInstance(); |
110
|
|
|
|
111
|
|
|
// now we're in business. we will set up connections right away, as Doctrine is handling the lazy-connecting stuff for us. |
112
|
|
|
// that way, you can just start using classes in your code |
113
|
|
|
try { |
114
|
|
|
$dsn = $this->getParameter('dsn'); |
115
|
|
|
|
116
|
|
|
if ($dsn === null) { |
117
|
|
|
// missing required dsn parameter |
118
|
|
|
$error = 'Database configuration specifies method "dsn", but is missing dsn parameter'; |
119
|
|
|
throw new DatabaseException($error); |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
$this->connection = $this->doctrineManager->openConnection($dsn, $name); |
123
|
|
|
// do not assign the resource here. that would connect to the database |
124
|
|
|
// $this->resource = $this->connection->getDbh(); |
|
|
|
|
125
|
|
|
|
126
|
|
|
// set our event listener that, on connect, sets the configured charset and runs init queries |
127
|
|
|
$cel = $this->getParameter('connection_event_listener_class', 'AgaviDoctrineDatabaseEventListener'); |
128
|
|
|
$this->connection->setListener(new $cel($this)); |
129
|
|
|
|
130
|
|
|
// set the context instance as a connection parameter |
131
|
|
|
$this->connection->setParam('context', $databaseManager->getContext(), 'org.agavi'); |
132
|
|
|
|
133
|
|
|
// date format |
134
|
|
|
if ($this->hasParameter('date_format')) { |
135
|
|
|
$this->connection->setDateFormat($this->getParameter('date_format')); |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
// options |
139
|
|
|
foreach ((array)$this->getParameter('options') as $optionName => $optionValue) { |
140
|
|
|
$this->connection->setOption($optionName, $optionValue); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
foreach (array( |
144
|
|
|
'manager_attributes' => $this->doctrineManager, |
145
|
|
|
'attributes' => $this->connection, |
146
|
|
|
) as $attributesKey => $attributesDestination) { |
147
|
|
|
foreach ((array)$this->getParameter($attributesKey, array()) as $attributeName => $attributeValue) { |
148
|
|
|
if ($is12) { |
149
|
|
|
if (!strpos($attributeName, '::')) { |
150
|
|
|
throw new DatabaseException(sprintf('For Doctrine 1.2 and newer, attribute names (and, if desired to be resolved against a constant, values) must be fully qualified, e.g. "Doctrine_Core::ATTR_VALIDATE" and "Doctrine_Core::VALIDATE_NONE". Given attribute with name "%s" in collection "%s" does not match this condition.', $attributeName, $attributesKey)); |
151
|
|
|
} |
152
|
|
|
if (!defined($attributeName)) { |
153
|
|
|
throw new DatabaseException(sprintf('Unknown Attribute "%s"', $attributeName)); |
154
|
|
|
} |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
// resolve from constant if possible |
158
|
|
|
if (strpos($attributeName, '::') && defined($attributeName)) { |
159
|
|
|
$attributeName = constant($attributeName); |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
if (strpos($attributeValue, '::') && defined($attributeValue)) { |
163
|
|
|
// resolve from constant if possible |
164
|
|
|
$attributeValue = constant($attributeValue); |
165
|
|
|
} elseif (ctype_digit($attributeValue)) { |
166
|
|
|
// cast numeric type to int |
167
|
|
|
$attributeValue = (int)$attributeValue; |
168
|
|
|
} elseif (($attributeName == Doctrine::ATTR_QUERY_CACHE || $attributeName == Doctrine::ATTR_RESULT_CACHE) && (is_string($attributeValue) || (is_array($attributeValue) && isset($attributeValue['class'])))) { |
169
|
|
|
// handle special case for query and result caches, where the attribute value needs to be an instance of Doctrine_Cache_Driver |
170
|
|
|
// we only allow basic cases where the ctor argument array for options requires scalar values |
171
|
|
|
// if people want to use e.g. Doctrine_Cache_Db, which requires an instance of Doctrine_Connection as the argument, they should use a custom connection event listener |
172
|
|
|
$driverClass = is_string($attributeValue) ? $attributeValue : $attributeValue['class']; |
173
|
|
|
$driverOptions = is_array($attributeValue) && isset($attributeValue['options']) && is_array($attributeValue['options']) ? $attributeValue['options'] : array(); |
174
|
|
|
$attributeValue = new $driverClass($driverOptions); |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
$attributesDestination->setAttribute($attributeName, $attributeValue); |
178
|
|
|
} |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
foreach ((array)$this->getParameter('impls', array()) as $templateName => $className) { |
182
|
|
|
$this->connection->setImpl($templateName, $className); |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
foreach ((array)$this->getParameter('manager_impls', array()) as $templateName => $className) { |
186
|
|
|
$this->doctrineManager->setImpl($templateName, $className); |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
// load models (that'll just work with empty values too) |
190
|
|
|
Doctrine::loadModels($this->getParameter('load_models')); |
191
|
|
|
|
192
|
|
|
// for 1.2, handle model autoloading and base paths |
193
|
|
|
if ($is12 && ($this->hasParameter('load_models') || $this->hasParameter('models_directory'))) { |
194
|
|
View Code Duplication |
if (!in_array(array('Doctrine', 'modelsAutoload'), $splAutoloadFunctions) && !in_array(array('Doctrine_Core', 'modelsAutoload'), $splAutoloadFunctions)) { |
|
|
|
|
195
|
|
|
spl_autoload_register(array('Doctrine_Core', 'modelsAutoload')); |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
if ($this->hasParameter('models_directory')) { |
199
|
|
|
Doctrine_Core::setModelsDirectory($this->getParameter('models_directory')); |
200
|
|
|
} |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
// for 1.2, handle extension autoloading, base paths and registration |
204
|
|
|
if ($is12 && ($this->hasParameter('extensions_path') || $this->hasParameter('register_extensions'))) { |
205
|
|
View Code Duplication |
if (!in_array(array('Doctrine', 'extensionsAutoload'), $splAutoloadFunctions) && !in_array(array('Doctrine_Core', 'extensionsAutoload'), $splAutoloadFunctions)) { |
|
|
|
|
206
|
|
|
spl_autoload_register(array('Doctrine_Core', 'extensionsAutoload')); |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
if ($this->hasParameter('extensions_path')) { |
210
|
|
|
Doctrine_Core::setExtensionsPath($this->getParameter('extensions_path')); |
211
|
|
|
} |
212
|
|
|
foreach ((array)$this->getParameter('register_extensions', array()) as $extensionName) { |
213
|
|
|
if (is_array($extensionName)) { |
214
|
|
|
call_user_func_array(array($this->doctrineManager, 'registerExtension'), $extensionName); |
215
|
|
|
} else { |
216
|
|
|
$this->doctrineManager->registerExtension($extensionName); |
217
|
|
|
} |
218
|
|
|
} |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
foreach ((array)$this->getParameter('bind_components', array()) as $componentName) { |
222
|
|
|
$this->doctrineManager->bindComponent($componentName, $name); |
223
|
|
|
} |
224
|
|
|
} catch (Doctrine_Exception $e) { |
|
|
|
|
225
|
|
|
// the connection's foobar'd |
226
|
|
|
throw new DatabaseException($e->getMessage(), 0, $e); |
227
|
|
|
} |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* Execute the shutdown procedure. |
232
|
|
|
* |
233
|
|
|
* @throws <b>AgaviDatabaseException</b> If an error occurs while shutting |
234
|
|
|
* down this database. |
235
|
|
|
* |
236
|
|
|
* @author David Zülke <[email protected]> |
237
|
|
|
* @since 0.11.0 |
238
|
|
|
*/ |
239
|
|
|
public function shutdown() |
240
|
|
|
{ |
241
|
|
|
if ($this->connection !== null) { |
242
|
|
|
$this->doctrineManager->closeConnection($this->connection); |
243
|
|
|
$this->connection = null; |
244
|
|
|
$this->resource = null; |
245
|
|
|
} |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
/** |
249
|
|
|
* Get the Doctrine Manager instance. |
250
|
|
|
* |
251
|
|
|
* @return Doctrine_Manager The Doctrine Manager instance. |
252
|
|
|
* |
253
|
|
|
* @author David Zülke <[email protected]> |
254
|
|
|
* @since 0.11.0 |
255
|
|
|
*/ |
256
|
|
|
public function getDoctrineManager() |
257
|
|
|
{ |
258
|
|
|
return $this->doctrineManager; |
259
|
|
|
} |
260
|
|
|
} |
261
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.