# Coverage for Doctrine_Manager

Back to coverage report

1 | <?php
2 | /*
3 | * $Id: Manager.php 3223 2007-11-25 19:07:30Z romanb $
4 | *
5 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16 | *
17 | * This software consists of voluntary contributions made by many individuals
18 | * and is licensed under the LGPL. For more information, see
19 | * <http://www.phpdoctrine.org>.
20 | */
21 |
22 | /**
23 | *
24 | * Doctrine_Manager is the base component of all doctrine based projects.
25 | * It opens and keeps track of all connections (database connections).
26 | *
27 | * @package Doctrine
28 | * @subpackage Manager
29 | * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
30 | * @link www.phpdoctrine.org
31 | * @since 1.0
32 | * @version $Revision: 3223 $
33 | * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
34 | */
35 | class Doctrine_Manager extends Doctrine_Configurable implements Countable, IteratorAggregate
36 | {
37 | /**
38 | * @var array $connections an array containing all the opened connections
39 | */
40 | protected $_connections = array();
41 |
42 | /**
43 | * @var array $bound an array containing all components that have a bound connection
44 | */
45 | protected $_bound = array();
46 |
47 | /**
48 | * @var integer $index the incremented index
49 | */
50 | protected $_index = 0;
51 |
52 | /**
53 | * @var integer $currIndex the current connection index
54 | */
55 | protected $_currIndex = 0;
56 |
57 | /**
58 | * @var string $root root directory
59 | */
60 | protected $_root;
61 |
62 | /**
63 | * @var Doctrine_Query_Registry the query registry
64 | */
65 | protected $_queryRegistry;
66 |
67 | protected static $driverMap = array('oci' => 'oracle');
68 |
69 | /**
70 | * constructor
71 | *
72 | * this is private constructor (use getInstance to get an instance of this class)
73 | */
74 | private function __construct()
75 | {
76 | $this->_root = dirname(__FILE__);
77 |
78 | Doctrine_Locator_Injectable::initNullObject(new Doctrine_Null);
79 | }
80 |
81 | /**
82 | * setDefaultAttributes
83 | * sets default attributes
84 | *
85 | * @todo I do not understand the flow here. Explain or refactor?
86 | * @return boolean
87 | */
88 | public function setDefaultAttributes()
89 | {
90 | static $init = false;
91 | if ( ! $init) {
92 | $init = true;
93 | $attributes = array(
94 | Doctrine::ATTR_CACHE => null,
95 | Doctrine::ATTR_RESULT_CACHE => null,
96 | Doctrine::ATTR_QUERY_CACHE => null,
97 | Doctrine::ATTR_LOAD_REFERENCES => true,
98 | Doctrine::ATTR_LISTENER => new Doctrine_EventListener(),
99 | Doctrine::ATTR_RECORD_LISTENER => new Doctrine_Record_Listener(),
100 | Doctrine::ATTR_THROW_EXCEPTIONS => true,
101 | Doctrine::ATTR_VALIDATE => Doctrine::VALIDATE_NONE,
102 | Doctrine::ATTR_QUERY_LIMIT => Doctrine::LIMIT_RECORDS,
103 | Doctrine::ATTR_IDXNAME_FORMAT => "%s_idx",
104 | Doctrine::ATTR_SEQNAME_FORMAT => "%s_seq",
105 | Doctrine::ATTR_TBLNAME_FORMAT => "%s",
106 | Doctrine::ATTR_QUOTE_IDENTIFIER => false,
107 | Doctrine::ATTR_SEQCOL_NAME => 'id',
108 | Doctrine::ATTR_PORTABILITY => Doctrine::PORTABILITY_ALL,
109 | Doctrine::ATTR_EXPORT => Doctrine::EXPORT_ALL,
110 | Doctrine::ATTR_DECIMAL_PLACES => 2,
111 | Doctrine::ATTR_DEFAULT_PARAM_NAMESPACE => 'doctrine',
112 | );
113 | foreach ($attributes as $attribute => $value) {
114 | $old = $this->getAttribute($attribute);
115 | if ($old === null) {
116 | $this->setAttribute($attribute,$value);
117 | }
118 | }
119 | return true;
120 | }
121 | return false;
122 | }
123 |
124 | /**
125 | * returns the root directory of Doctrine
126 | *
127 | * @return string
128 | */
129 | final public function getRoot()
130 | {
131 | return $this->_root;
132 | }
133 |
134 | /**
135 | * getInstance
136 | * returns an instance of this class
137 | * (this class uses the singleton pattern)
138 | *
139 | * @return Doctrine_Manager
140 | */
141 | public static function getInstance()
142 | {
143 | static $instance;
144 | if ( ! isset($instance)) {
145 | $instance = new self();
146 | }
147 | return $instance;
148 | }
149 |
150 | /**
151 | * getQueryRegistry
152 | * lazy-initializes the query registry object and returns it
153 | *
154 | * @return Doctrine_Query_Registry
155 | */
156 | public function getQueryRegistry()
157 | {
158 | if ( ! isset($this->_queryRegistry)) {
159 | $this->_queryRegistry = new Doctrine_Query_Registry;
160 | }
161 | return $this->_queryRegistry;
162 | }
163 |
164 | /**
165 | * setQueryRegistry
166 | * sets the query registry
167 | *
168 | * @return Doctrine_Manager this object
169 | */
170 | public function setQueryRegistry(Doctrine_Query_Registry $registry)
171 | {
172 | $this->_queryRegistry = $registry;
173 |
174 | return $this;
175 | }
176 |
177 | /**
178 | * fetch
179 | * fetches data using the provided queryKey and
180 | * the associated query in the query registry
181 | *
182 | * if no query for given queryKey is being found a
183 | * Doctrine_Query_Registry exception is being thrown
184 | *
185 | * @param string $queryKey the query key
186 | * @param array $params prepared statement params (if any)
187 | * @return mixed the fetched data
188 | */
189 | public function find($queryKey, $params = array(), $hydrationMode = Doctrine::HYDRATE_RECORD)
190 | {
191 | return Doctrine_Manager::getInstance()
192 | ->getQueryRegistry()
193 | ->get($queryKey)
194 | ->execute($params, $hydrationMode);
195 | }
196 |
197 | /**
198 | * fetchOne
199 | * fetches data using the provided queryKey and
200 | * the associated query in the query registry
201 | *
202 | * if no query for given queryKey is being found a
203 | * Doctrine_Query_Registry exception is being thrown
204 | *
205 | * @param string $queryKey the query key
206 | * @param array $params prepared statement params (if any)
207 | * @return mixed the fetched data
208 | */
209 | public function findOne($queryKey, $params = array(), $hydrationMode = Doctrine::HYDRATE_RECORD)
210 | {
211 | return Doctrine_Manager::getInstance()
212 | ->getQueryRegistry()
213 | ->get($queryKey)
214 | ->fetchOne($params, $hydrationMode);
215 | }
216 |
217 | /**
218 | * connection
219 | *
220 | * if the adapter parameter is set this method acts as
221 | * a short cut for Doctrine_Manager::getInstance()->openConnection($adapter, $name);
222 | *
223 | * if the adapter paramater is not set this method acts as
224 | * a short cut for Doctrine_Manager::getInstance()->getCurrentConnection()
225 | *
226 | * @param PDO|Doctrine_Adapter_Interface $adapter database driver
227 | * @param string $name name of the connection, if empty numeric key is used
228 | * @throws Doctrine_Manager_Exception if trying to bind a connection with an existing name
229 | * @return Doctrine_Connection
230 | */
231 | public static function connection($adapter = null, $name = null)
232 | {
233 | if ($adapter == null) {
234 | return Doctrine_Manager::getInstance()->getCurrentConnection();
235 | } else {
236 | return Doctrine_Manager::getInstance()->openConnection($adapter, $name);
237 | }
238 | }
239 |
240 | /**
241 | * openConnection
242 | * opens a new connection and saves it to Doctrine_Manager->connections
243 | *
244 | * @param PDO|Doctrine_Adapter_Interface $adapter database driver
245 | * @param string $name name of the connection, if empty numeric key is used
246 | * @throws Doctrine_Manager_Exception if trying to bind a connection with an existing name
247 | * @throws Doctrine_Manager_Exception if trying to open connection for unknown driver
248 | * @return Doctrine_Connection
249 | */
250 | public function openConnection($adapter, $name = null, $setCurrent = true)
251 | {
252 | if (is_object($adapter)) {
253 | if ( ! ($adapter instanceof PDO) && ! in_array('Doctrine_Adapter_Interface', class_implements($adapter))) {
254 | throw new Doctrine_Manager_Exception("First argument should be an instance of PDO or implement Doctrine_Adapter_Interface");
255 | }
256 |
257 | $driverName = $adapter->getAttribute(Doctrine::ATTR_DRIVER_NAME);
258 | } elseif (is_array($adapter)) {
259 | if ( ! isset($adapter[0])) {
260 | throw new Doctrine_Manager_Exception('Empty data source name given.');
261 | }
262 | $e = explode(':', $adapter[0]);
263 |
264 | if ($e[0] == 'uri') {
265 | $e[0] = 'odbc';
266 | }
267 |
268 | $parts['dsn'] = $adapter[0];
269 | $parts['scheme'] = $e[0];
270 | $parts['user'] = (isset($adapter[1])) ? $adapter[1] : null;
271 | $parts['pass'] = (isset($adapter[2])) ? $adapter[2] : null;
272 |
273 | $driverName = $e[0];
274 | $adapter = $parts;
275 | } else {
276 | $parts = $this->parseDsn($adapter);
277 |
278 | $driverName = $parts['scheme'];
279 |
280 | $adapter = $parts;
281 | }
282 |
283 | // initialize the default attributes
284 | $this->setDefaultAttributes();
285 |
286 | if ($name !== null) {
287 | $name = (string) $name;
288 | if (isset($this->_connections[$name])) {
289 | return $this->_connections[$name];
290 | }
291 | } else {
292 | $name = $this->_index;
293 | $this->_index++;
294 | }
295 |
296 |
297 | $drivers = array('mysql' => 'Doctrine_Connection_Mysql',
298 | 'sqlite' => 'Doctrine_Connection_Sqlite',
299 | 'pgsql' => 'Doctrine_Connection_Pgsql',
300 | 'oci' => 'Doctrine_Connection_Oracle',
301 | 'oci8' => 'Doctrine_Connection_Oracle',
302 | 'oracle' => 'Doctrine_Connection_Oracle',
303 | 'mssql' => 'Doctrine_Connection_Mssql',
304 | 'dblib' => 'Doctrine_Connection_Mssql',
305 | 'firebird' => 'Doctrine_Connection_Firebird',
306 | 'informix' => 'Doctrine_Connection_Informix',
307 | 'mock' => 'Doctrine_Connection_Mock');
308 | if ( ! isset($drivers[$driverName])) {
309 | throw new Doctrine_Manager_Exception('Unknown driver ' . $driverName);
310 | }
311 |
312 | $className = $drivers[$driverName];
313 | $conn = new $className($this, $adapter);
314 |
315 | $this->_connections[$name] = $conn;
316 |
317 | if ($setCurrent) {
318 | $this->_currIndex = $name;
319 | }
320 | return $this->_connections[$name];
321 | }
322 |
323 | /**
324 | * parsePdoDsn
325 | *
326 | * @param array $dsn An array of dsn information
327 | * @return array The array parsed
328 | */
329 | public function parsePdoDsn($dsn)
330 | {
331 | $parts = array();
332 |
333 | $names = array('dsn', 'scheme', 'host', 'port', 'user', 'pass', 'path', 'query', 'fragment');
334 |
335 | foreach ($names as $name) {
336 | if ( ! isset($parts[$name])) {
337 | $parts[$name] = null;
338 | }
339 | }
340 |
341 | $e = explode(':', $dsn);
342 | $parts['scheme'] = $e[0];
343 | $parts['dsn'] = $dsn;
344 |
345 | $e = explode(';', $e[1]);
346 | foreach ($e as $string) {
347 | list($key, $value) = explode('=', $string);
348 | $parts[$key] = $value;
349 | }
350 |
351 | return $parts;
352 | }
353 |
354 | /**
355 | * parseDsn
356 | *
357 | * @param string $dsn
358 | * @return array Parsed contents of DSN
359 | */
360 | public function parseDsn($dsn)
361 | {
362 |
363 |
364 | //fix linux sqlite dsn so that it will parse correctly
365 | $dsn = str_replace("///", "/", $dsn);
366 |
367 | // silence any warnings
368 | $parts = @parse_url($dsn);
369 |
370 | $names = array('dsn', 'scheme', 'host', 'port', 'user', 'pass', 'path', 'query', 'fragment');
371 |
372 | foreach ($names as $name) {
373 | if ( ! isset($parts[$name])) {
374 | $parts[$name] = null;
375 | }
376 | }
377 |
378 | if (count($parts) == 0 || ! isset($parts['scheme'])) {
379 | throw new Doctrine_Manager_Exception('Empty data source name');
380 | }
381 |
382 | switch ($parts['scheme']) {
383 | case 'sqlite':
384 | case 'sqlite2':
385 | case 'sqlite3':
386 | if (isset($parts['host']) && $parts['host'] == ':memory') {
387 | $parts['database'] = ':memory:';
388 | $parts['dsn'] = 'sqlite::memory:';
389 | } else {
390 | //fix windows dsn we have to add host: to path and set host to null
391 | if (isset($parts['host'])) {
392 | $parts['path'] = $parts['host'] . ":" . $parts["path"];
393 | $parts["host"] = null;
394 | }
395 | $parts['database'] = $parts['path'];
396 | $parts['dsn'] = $parts['scheme'] . ':' . $parts['path'];
397 | }
398 |
399 | break;
400 |
401 | case 'mssql':
402 | case 'dblib':
403 | if ( ! isset($parts['path']) || $parts['path'] == '/') {
404 | throw new Doctrine_Manager_Exception('No database available in data source name');
405 | }
406 | if (isset($parts['path'])) {
407 | $parts['database'] = substr($parts['path'], 1);
408 | }
409 | if ( ! isset($parts['host'])) {
410 | throw new Doctrine_Manager_Exception('No hostname set in data source name');
411 | }
412 |
413 | if (isset(self::$driverMap[$parts['scheme']])) {
414 | $parts['scheme'] = self::$driverMap[$parts['scheme']];
415 | }
416 |
417 | $parts['dsn'] = $parts['scheme'] . ':host='
418 | . $parts['host'] . (isset($parts['port']) ? ':' . $parts['port']:null) . ';dbname='
419 | . $parts['database'];
420 |
421 | break;
422 |
423 | case 'mysql':
424 | case 'informix':
425 | case 'oci8':
426 | case 'oci':
427 | case 'firebird':
428 | case 'pgsql':
429 | case 'odbc':
430 | case 'mock':
431 | case 'oracle':
432 | if ( ! isset($parts['path']) || $parts['path'] == '/') {
433 | throw new Doctrine_Manager_Exception('No database available in data source name');
434 | }
435 | if (isset($parts['path'])) {
436 | $parts['database'] = substr($parts['path'], 1);
437 | }
438 | if ( ! isset($parts['host'])) {
439 | throw new Doctrine_Manager_Exception('No hostname set in data source name');
440 | }
441 |
442 | if (isset(self::$driverMap[$parts['scheme']])) {
443 | $parts['scheme'] = self::$driverMap[$parts['scheme']];
444 | }
445 |
446 | $parts['dsn'] = $parts['scheme'] . ':host='
447 | . $parts['host'] . (isset($parts['port']) ? ';port=' . $parts['port']:null) . ';dbname='
448 | . $parts['database'];
449 |
450 | break;
451 | default:
452 | throw new Doctrine_Manager_Exception('Unknown driver '.$parts['scheme']);
453 | }
454 |
455 |
456 | return $parts;
457 | }
458 |
459 | /**
460 | * getConnection
461 | * @param integer $index
462 | * @return object Doctrine_Connection
463 | * @throws Doctrine_Manager_Exception if trying to get a non-existent connection
464 | */
465 | public function getConnection($name)
466 | {
467 | if ( ! isset($this->_connections[$name])) {
468 | throw new Doctrine_Manager_Exception('Unknown connection: ' . $name);
469 | }
470 |
471 | return $this->_connections[$name];
472 | }
473 |
474 | /**
475 | * getComponentAlias
476 | * retrieves the alias for given component name
477 | * if the alias couldn't be found, this method returns the given
478 | * component name
479 | *
480 | * @param string $componentName
481 | * @return string the component alias
482 | */
483 | public function getComponentAlias($componentName)
484 | {
485 | if (isset($this->componentAliases[$componentName])) {
486 | return $this->componentAliases[$componentName];
487 | }
488 |
489 | return $componentName;
490 | }
491 |
492 | /**
493 | * sets an alias for given component name
494 | * very useful when building a large framework with a possibility
495 | * to override any given class
496 | *
497 | * @param string $componentName the name of the component
498 | * @param string $alias
499 | * @return Doctrine_Manager
500 | */
501 | public function setComponentAlias($componentName, $alias)
502 | {
503 | $this->componentAliases[$componentName] = $alias;
504 |
505 | return $this;
506 | }
507 |
508 | /**
509 | * getConnectionName
510 | *
511 | * @param Doctrine_Connection $conn connection object to be searched for
512 | * @return string the name of the connection
513 | */
514 | public function getConnectionName(Doctrine_Connection $conn)
515 | {
516 | return array_search($conn, $this->_connections, true);
517 | }
518 |
519 | /**
520 | * bindComponent
521 | * binds given component to given connection
522 | * this means that when ever the given component uses a connection
523 | * it will be using the bound connection instead of the current connection
524 | *
525 | * @param string $componentName
526 | * @param string $connectionName
527 | * @return boolean
528 | */
529 | public function bindComponent($componentName, $connectionName)
530 | {
531 | $this->_bound[$componentName] = $connectionName;
532 | }
533 |
534 | /**
535 | * getConnectionForComponent
536 | *
537 | * @param string $componentName
538 | * @return Doctrine_Connection
539 | */
540 | public function getConnectionForComponent($componentName = null)
541 | {
542 | if (isset($this->_bound[$componentName])) {
543 | return $this->getConnection($this->_bound[$componentName]);
544 | }
545 | return $this->getCurrentConnection();
546 | }
547 |
548 | /**
549 | * getTable
550 | * this is the same as Doctrine_Connection::getTable() except
551 | * that it works seamlessly in multi-server/connection environment
552 | *
553 | * @see Doctrine_Connection::getTable()
554 | * @param string $componentName
555 | * @return Doctrine_Table
556 | */
557 | public function getTable($componentName)
558 | {
559 | return $this->getConnectionForComponent($componentName)->getTable($componentName);
560 | }
561 |
562 | /**
563 | * table
564 | * this is the same as Doctrine_Connection::getTable() except
565 | * that it works seamlessly in multi-server/connection environment
566 | *
567 | * @see Doctrine_Connection::getTable()
568 | * @param string $componentName
569 | * @return Doctrine_Table
570 | */
571 | public static function table($componentName)
572 | {
573 | return Doctrine_Manager::getInstance()
574 | ->getConnectionForComponent($componentName)
575 | ->getTable($componentName);
576 | }
577 |
578 | /**
579 | * closes the connection
580 | *
581 | * @param Doctrine_Connection $connection
582 | * @return void
583 | */
584 | public function closeConnection(Doctrine_Connection $connection)
585 | {
586 | $connection->close();
587 |
588 | $key = array_search($connection, $this->_connections, true);
589 |
590 | if ($key !== false) {
591 | unset($this->_connections[$key]);
592 | }
593 | $this->_currIndex = key($this->_connections);
594 |
595 | unset($connection);
596 | }
597 |
598 | /**
599 | * getConnections
600 | * returns all opened connections
601 | *
602 | * @return array
603 | */
604 | public function getConnections()
605 | {
606 | return $this->_connections;
607 | }
608 |
609 | /**
610 | * setCurrentConnection
611 | * sets the current connection to $key
612 | *
613 | * @param mixed $key the connection key
614 | * @throws InvalidKeyException
615 | * @return void
616 | */
617 | public function setCurrentConnection($key)
618 | {
619 | $key = (string) $key;
620 | if ( ! isset($this->_connections[$key])) {
621 | throw new InvalidKeyException();
622 | }
623 | $this->_currIndex = $key;
624 | }
625 |
626 | /**
627 | * contains
628 | * whether or not the manager contains specified connection
629 | *
630 | * @param mixed $key the connection key
631 | * @return boolean
632 | */
633 | public function contains($key)
634 | {
635 | return isset($this->_connections[$key]);
636 | }
637 |
638 | /**
639 | * count
640 | * returns the number of opened connections
641 | *
642 | * @return integer
643 | */
644 | public function count()
645 | {
646 | return count($this->_connections);
647 | }
648 |
649 | /**
650 | * getIterator
651 | * returns an ArrayIterator that iterates through all connections
652 | *
653 | * @return ArrayIterator
654 | */
655 | public function getIterator()
656 | {
657 | return new ArrayIterator($this->_connections);
658 | }
659 |
660 | /**
661 | * getCurrentConnection
662 | * returns the current connection
663 | *
664 | * @throws Doctrine_Connection_Exception if there are no open connections
665 | * @return Doctrine_Connection
666 | */
667 | public function getCurrentConnection()
668 | {
669 | $i = $this->_currIndex;
670 | if ( ! isset($this->_connections[$i])) {
671 | throw new Doctrine_Connection_Exception();
672 | }
673 | return $this->_connections[$i];
674 | }
675 |
676 | /**
677 | * __toString
678 | * returns a string representation of this object
679 | *
680 | * @return string
681 | */
682 | public function __toString()
683 | {
684 | $r[] = "<pre>";
685 | $r[] = "Doctrine_Manager";
686 | $r[] = "Connections : ".count($this->_connections);
687 | $r[] = "</pre>";
688 | return implode("\n",$r);
689 | }
690 | }