Coverage for Doctrine_Manager

Back to coverage report

1 <?php
2 /*
3  *  $Id: Manager.php 3055 2007-11-01 22:52:40Z zYne $
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.com>.
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.com
31  * @since       1.0
32  * @version     $Revision: 3055 $
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      * @return boolean
86      */
87     public function setDefaultAttributes()
88     {
89         static $init = false;
90         if ( ! $init) {
91             $init = true;
92             $attributes = array(
93                         Doctrine::ATTR_CACHE                    => null,
94                         Doctrine::ATTR_LOAD_REFERENCES          => true,
95                         Doctrine::ATTR_LISTENER                 => new Doctrine_EventListener(),
96                         Doctrine::ATTR_RECORD_LISTENER          => new Doctrine_Record_Listener(),
97                         Doctrine::ATTR_THROW_EXCEPTIONS         => true,
98                         Doctrine::ATTR_VALIDATE                 => Doctrine::VALIDATE_NONE,
99                         Doctrine::ATTR_QUERY_LIMIT              => Doctrine::LIMIT_RECORDS,
100                         Doctrine::ATTR_IDXNAME_FORMAT           => "%s_idx",
101                         Doctrine::ATTR_SEQNAME_FORMAT           => "%s_seq",
102                         Doctrine::ATTR_TBLNAME_FORMAT           => "%s",
103                         Doctrine::ATTR_QUOTE_IDENTIFIER         => false,
104                         Doctrine::ATTR_SEQCOL_NAME              => 'id',
105                         Doctrine::ATTR_PORTABILITY              => Doctrine::PORTABILITY_ALL,
106                         Doctrine::ATTR_EXPORT                   => Doctrine::EXPORT_ALL,
107                         Doctrine::ATTR_DECIMAL_PLACES           => 2,
108                         Doctrine::ATTR_DEFAULT_PARAM_NAMESPACE  => 'doctrine',
109                         );
110             foreach ($attributes as $attribute => $value) {
111                 $old = $this->getAttribute($attribute);
112                 if ($old === null) {
113                     $this->setAttribute($attribute,$value);
114                 }
115             }
116             return true;
117         }
118         return false;
119     }
120
121     /**
122      * returns the root directory of Doctrine
123      *
124      * @return string
125      */
126     final public function getRoot()
127     {
128         return $this->_root;
129     }
130
131     /**
132      * getInstance
133      * returns an instance of this class
134      * (this class uses the singleton pattern)
135      *
136      * @return Doctrine_Manager
137      */
138     public static function getInstance()
139     {
140         static $instance;
141         if ( ! isset($instance)) {
142             $instance = new self();
143         }
144         return $instance;
145     }
146
147     /**
148      * getQueryRegistry
149      * lazy-initializes the query registry object and returns it
150      *
151      * @return Doctrine_Query_Registry
152      */
153     public function getQueryRegistry()
154     {
155      if ( ! isset($this->_queryRegistry)) {
156         $this->_queryRegistry = new Doctrine_Query_Registry;
157      }
158         return $this->_queryRegistry;
159     }
160
161     /**
162      * setQueryRegistry
163      * sets the query registry
164      *
165      * @return Doctrine_Manager     this object
166      */
167     public function setQueryRegistry(Doctrine_Query_Registry $registry)
168     {
169         $this->_queryRegistry = $registry;
170         
171         return $this;
172     }
173
174     /**
175      * fetch
176      * fetches data using the provided queryKey and 
177      * the associated query in the query registry
178      *
179      * if no query for given queryKey is being found a 
180      * Doctrine_Query_Registry exception is being thrown
181      *
182      * @param string $queryKey      the query key
183      * @param array $params         prepared statement params (if any)
184      * @return mixed                the fetched data
185      */
186     public function find($queryKey, $params = array(), $hydrationMode = Doctrine::HYDRATE_RECORD)
187     {
188         return Doctrine_Manager::getInstance()
189                             ->getQueryRegistry()
190                             ->get($queryKey)
191                             ->execute($params, $hydrationMode);
192     }
193
194     /**
195      * fetchOne
196      * fetches data using the provided queryKey and 
197      * the associated query in the query registry
198      *
199      * if no query for given queryKey is being found a 
200      * Doctrine_Query_Registry exception is being thrown
201      *
202      * @param string $queryKey      the query key
203      * @param array $params         prepared statement params (if any)
204      * @return mixed                the fetched data
205      */
206     public function findOne($queryKey, $params = array(), $hydrationMode = Doctrine::HYDRATE_RECORD)
207     {
208         return Doctrine_Manager::getInstance()
209                             ->getQueryRegistry()
210                             ->get($queryKey)
211                             ->fetchOne($params, $hydrationMode);
212     }
213
214     /**
215      * connection
216      *
217      * if the adapter parameter is set this method acts as
218      * a short cut for Doctrine_Manager::getInstance()->openConnection($adapter, $name);
219      *
220      * if the adapter paramater is not set this method acts as
221      * a short cut for Doctrine_Manager::getInstance()->getCurrentConnection()
222      *
223      * @param PDO|Doctrine_Adapter_Interface $adapter   database driver
224      * @param string $name                              name of the connection, if empty numeric key is used
225      * @throws Doctrine_Manager_Exception               if trying to bind a connection with an existing name
226      * @return Doctrine_Connection
227      */
228     public static function connection($adapter = null, $name = null)
229     {
230         if ($adapter == null) {
231             return Doctrine_Manager::getInstance()->getCurrentConnection();
232         } else {
233             return Doctrine_Manager::getInstance()->openConnection($adapter, $name);
234         }
235     }
236
237     /**
238      * openConnection
239      * opens a new connection and saves it to Doctrine_Manager->connections
240      *
241      * @param PDO|Doctrine_Adapter_Interface $adapter   database driver
242      * @param string $name                              name of the connection, if empty numeric key is used
243      * @throws Doctrine_Manager_Exception               if trying to bind a connection with an existing name
244      * @throws Doctrine_Manager_Exception               if trying to open connection for unknown driver
245      * @return Doctrine_Connection
246      */
247     public function openConnection($adapter, $name = null, $setCurrent = true)
248     {
249         if (is_object($adapter)) {
250             if ( ! ($adapter instanceof PDO) && ! in_array('Doctrine_Adapter_Interface', class_implements($adapter))) {
251                 throw new Doctrine_Manager_Exception("First argument should be an instance of PDO or implement Doctrine_Adapter_Interface");
252             }
253
254             $driverName = $adapter->getAttribute(Doctrine::ATTR_DRIVER_NAME);
255         } elseif (is_array($adapter)) {
256             if ( ! isset($adapter[0])) {
257                 throw new Doctrine_Manager_Exception('Empty data source name given.');
258             }
259             $e = explode(':', $adapter[0]);
260
261             if ($e[0] == 'uri') {
262                 $e[0] = 'odbc';
263             }
264
265             $parts['dsn']    = $adapter[0];
266             $parts['scheme'] = $e[0];
267             $parts['user']   = (isset($adapter[1])) ? $adapter[1] : null;
268             $parts['pass']   = (isset($adapter[2])) ? $adapter[2] : null;
269             
270             $driverName = $e[0];
271             $adapter = $parts;
272         } else {
273             $parts = $this->parseDsn($adapter);
274             
275             $driverName = $parts['scheme'];
276             
277             $adapter = $parts;
278         }
279
280         // initialize the default attributes
281         $this->setDefaultAttributes();
282
283         if ($name !== null) {
284             $name = (string) $name;
285             if (isset($this->_connections[$name])) {
286                 return $this->_connections[$name];
287             }
288         } else {
289             $name = $this->_index;
290             $this->_index++;
291         }
292
293
294         $drivers = array('mysql'    => 'Doctrine_Connection_Mysql',
295                          'sqlite'   => 'Doctrine_Connection_Sqlite',
296                          'pgsql'    => 'Doctrine_Connection_Pgsql',
297                          'oci'      => 'Doctrine_Connection_Oracle',
298                          'oci8'     => 'Doctrine_Connection_Oracle',
299                          'oracle'   => 'Doctrine_Connection_Oracle',
300                          'mssql'    => 'Doctrine_Connection_Mssql',
301                          'dblib'    => 'Doctrine_Connection_Mssql',
302                          'firebird' => 'Doctrine_Connection_Firebird',
303                          'informix' => 'Doctrine_Connection_Informix',
304                          'mock'     => 'Doctrine_Connection_Mock');
305         if ( ! isset($drivers[$driverName])) {
306             throw new Doctrine_Manager_Exception('Unknown driver ' . $driverName);
307         }
308         
309         $className = $drivers[$driverName];
310         $conn = new $className($this, $adapter);
311
312         $this->_connections[$name] = $conn;
313
314         if ($setCurrent) {
315             $this->_currIndex = $name;
316         }
317         return $this->_connections[$name];
318     }
319     
320     public function parsePdoDsn($dsn)
321     {
322         $parts = array();
323         
324         $names = array('dsn', 'scheme', 'host', 'port', 'user', 'pass', 'path', 'query', 'fragment');
325
326         foreach ($names as $name) {
327             if ( ! isset($parts[$name])) {
328                 $parts[$name] = null;
329             }
330         }
331         
332         $e = explode(':', $dsn);
333         $parts['scheme'] = $e[0];
334         $parts['dsn'] = $dsn;
335         
336         $e = explode(';', $e[1]);
337         foreach ($e as $string) {
338             list($key, $value) = explode('=', $string);
339             $parts[$key] = $value;
340         }
341         
342         return $parts;
343     }
344
345     /**
346      * parseDsn
347      *
348      * @param string $dsn
349      * @return array Parsed contents of DSN
350      */
351     public function parseDsn($dsn)
352     {
353         // silence any warnings
354         $parts = @parse_url($dsn);
355
356         $names = array('dsn', 'scheme', 'host', 'port', 'user', 'pass', 'path', 'query', 'fragment');
357
358         foreach ($names as $name) {
359             if ( ! isset($parts[$name])) {
360                 $parts[$name] = null;
361             }
362         }
363
364         if (count($parts) == 0 || ! isset($parts['scheme'])) {
365             throw new Doctrine_Manager_Exception('Empty data source name');
366         }
367
368         switch ($parts['scheme']) {
369             case 'sqlite':
370             case 'sqlite2':
371             case 'sqlite3':
372                 if (isset($parts['host']) && $parts['host'] == ':memory') {
373                     $parts['database'] = ':memory:';
374                     $parts['dsn']      = 'sqlite::memory:';
375                 } else {
376                     $parts['database'] = $parts['path'];
377                     $parts['dsn'] = $parts['scheme'] . ':' . $parts['path'];
378                 }
379
380                 break;
381             
382             case 'mssql':
383             case 'dblib':
384                 if ( ! isset($parts['path']) || $parts['path'] == '/') {
385                     throw new Doctrine_Manager_Exception('No database available in data source name');
386                 }
387                 if (isset($parts['path'])) {
388                     $parts['database'] = substr($parts['path'], 1);
389                 }
390                 if ( ! isset($parts['host'])) {
391                     throw new Doctrine_Manager_Exception('No hostname set in data source name');
392                 }
393                 
394                 if (isset(self::$driverMap[$parts['scheme']])) {
395                     $parts['scheme'] = self::$driverMap[$parts['scheme']];
396                 }
397
398                 $parts['dsn'] = $parts['scheme'] . ':host='
399                               . $parts['host'] . (isset($parts['port']) ? ':' . $parts['port']:null) . ';dbname='
400                               . $parts['database'];
401                 
402                 break;
403
404             case 'mysql':
405             case 'informix':
406             case 'oci8':
407             case 'oci':
408             case 'firebird':
409             case 'pgsql':
410             case 'odbc':
411             case 'mock':
412             case 'oracle':
413                 if ( ! isset($parts['path']) || $parts['path'] == '/') {
414                     throw new Doctrine_Manager_Exception('No database available in data source name');
415                 }
416                 if (isset($parts['path'])) {
417                     $parts['database'] = substr($parts['path'], 1);
418                 }
419                 if ( ! isset($parts['host'])) {
420                     throw new Doctrine_Manager_Exception('No hostname set in data source name');
421                 }
422                 
423                 if (isset(self::$driverMap[$parts['scheme']])) {
424                     $parts['scheme'] = self::$driverMap[$parts['scheme']];
425                 }
426
427                 $parts['dsn'] = $parts['scheme'] . ':host='
428                               . $parts['host'] . (isset($parts['port']) ? ';port=' . $parts['port']:null) . ';dbname='
429                               . $parts['database'];
430                 
431                 break;
432             default:
433                 throw new Doctrine_Manager_Exception('Unknown driver '.$parts['scheme']);
434         }
435
436
437         return $parts;
438     }
439
440     /**
441      * getConnection
442      * @param integer $index
443      * @return object Doctrine_Connection
444      * @throws Doctrine_Manager_Exception   if trying to get a non-existent connection
445      */
446     public function getConnection($name)
447     {
448         if ( ! isset($this->_connections[$name])) {
449             throw new Doctrine_Manager_Exception('Unknown connection: ' . $name);
450         }
451
452         return $this->_connections[$name];
453     }
454
455     /**
456      * getComponentAlias
457      * retrieves the alias for given component name
458      * if the alias couldn't be found, this method returns the given
459      * component name
460      *
461      * @param string $componentName
462      * @return string                   the component alias
463      */
464     public function getComponentAlias($componentName)
465     {
466         if (isset($this->componentAliases[$componentName])) {
467             return $this->componentAliases[$componentName];
468         }
469
470         return $componentName;
471     }
472
473     /**
474      * sets an alias for given component name
475      * very useful when building a large framework with a possibility
476      * to override any given class
477      *
478      * @param string $componentName         the name of the component
479      * @param string $alias
480      * @return Doctrine_Manager
481      */
482     public function setComponentAlias($componentName, $alias)
483     {
484         $this->componentAliases[$componentName] = $alias;
485
486         return $this;
487     }
488
489     /**
490      * getConnectionName
491      *
492      * @param Doctrine_Connection $conn     connection object to be searched for
493      * @return string                       the name of the connection
494      */
495     public function getConnectionName(Doctrine_Connection $conn)
496     {
497         return array_search($conn, $this->_connections, true);
498     }
499
500     /**
501      * bindComponent
502      * binds given component to given connection
503      * this means that when ever the given component uses a connection
504      * it will be using the bound connection instead of the current connection
505      *
506      * @param string $componentName
507      * @param string $connectionName
508      * @return boolean
509      */
510     public function bindComponent($componentName, $connectionName)
511     {
512         $this->_bound[$componentName] = $connectionName;
513     }
514
515     /**
516      * getConnectionForComponent
517      *
518      * @param string $componentName
519      * @return Doctrine_Connection
520      */
521     public function getConnectionForComponent($componentName = null)
522     {
523         if (isset($this->_bound[$componentName])) {
524             return $this->getConnection($this->_bound[$componentName]);
525         }
526         return $this->getCurrentConnection();
527     }
528
529     /**
530      * getTable
531      * this is the same as Doctrine_Connection::getTable() except
532      * that it works seamlessly in multi-server/connection environment
533      *
534      * @see Doctrine_Connection::getTable()
535      * @param string $componentName
536      * @return Doctrine_Table
537      */
538     public function getTable($componentName)
539     {
540         return $this->getConnectionForComponent($componentName)->getTable($componentName);
541     }
542
543     /**
544      * table
545      * this is the same as Doctrine_Connection::getTable() except
546      * that it works seamlessly in multi-server/connection environment
547      *
548      * @see Doctrine_Connection::getTable()
549      * @param string $componentName
550      * @return Doctrine_Table
551      */
552     public static function table($componentName)
553     {
554         return Doctrine_Manager::getInstance()
555                ->getConnectionForComponent($componentName)
556                ->getTable($componentName);
557     }
558
559     /**
560      * closes the connection
561      *
562      * @param Doctrine_Connection $connection
563      * @return void
564      */
565     public function closeConnection(Doctrine_Connection $connection)
566     {
567         $connection->close();
568
569         $key = array_search($connection, $this->_connections, true);
570
571         if ($key !== false) {
572             unset($this->_connections[$key]);
573         }
574         $this->_currIndex = key($this->_connections);
575
576         unset($connection);
577     }
578
579     /**
580      * getConnections
581      * returns all opened connections
582      *
583      * @return array
584      */
585     public function getConnections()
586     {
587         return $this->_connections;
588     }
589
590     /**
591      * setCurrentConnection
592      * sets the current connection to $key
593      *
594      * @param mixed $key                        the connection key
595      * @throws InvalidKeyException
596      * @return void
597      */
598     public function setCurrentConnection($key)
599     {
600         $key = (string) $key;
601         if ( ! isset($this->_connections[$key])) {
602             throw new InvalidKeyException();
603         }
604         $this->_currIndex = $key;
605     }
606
607     /**
608      * contains
609      * whether or not the manager contains specified connection
610      *
611      * @param mixed $key                        the connection key
612      * @return boolean
613      */
614     public function contains($key)
615     {
616         return isset($this->_connections[$key]);
617     }
618
619     /**
620      * count
621      * returns the number of opened connections
622      *
623      * @return integer
624      */
625     public function count()
626     {
627         return count($this->_connections);
628     }
629
630     /**
631      * getIterator
632      * returns an ArrayIterator that iterates through all connections
633      *
634      * @return ArrayIterator
635      */
636     public function getIterator()
637     {
638         return new ArrayIterator($this->_connections);
639     }
640
641     /**
642      * getCurrentConnection
643      * returns the current connection
644      *
645      * @throws Doctrine_Connection_Exception       if there are no open connections
646      * @return Doctrine_Connection
647      */
648     public function getCurrentConnection()
649     {
650         $i = $this->_currIndex;
651         if ( ! isset($this->_connections[$i])) {
652             throw new Doctrine_Connection_Exception();
653         }
654         return $this->_connections[$i];
655     }
656
657     /**
658      * __toString
659      * returns a string representation of this object
660      *
661      * @return string
662      */
663     public function __toString()
664     {
665         $r[] = "<pre>";
666         $r[] = "Doctrine_Manager";
667         $r[] = "Connections : ".count($this->_connections);
668         $r[] = "</pre>";
669         return implode("\n",$r);
670     }
671 }