Coverage for Doctrine_Manager

Back to coverage report

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