Coverage for Doctrine_Manager

Back to coverage report

1 <?php
2 /*
3  *  $Id: Manager.php 3071 2007-11-03 20:34:19Z meus $
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: 3071 $
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
354
355         //fix linux sqlite dsn so that it will parse correctly
356         $dsn = str_replace("///", "/", $dsn);
357
358         // silence any warnings
359         $parts = @parse_url($dsn);
360
361         $names = array('dsn', 'scheme', 'host', 'port', 'user', 'pass', 'path', 'query', 'fragment');
362
363         foreach ($names as $name) {
364             if ( ! isset($parts[$name])) {
365                 $parts[$name] = null;
366             }
367         }
368
369         if (count($parts) == 0 || ! isset($parts['scheme'])) {
370             throw new Doctrine_Manager_Exception('Empty data source name');
371         }
372
373         switch ($parts['scheme']) {
374             case 'sqlite':
375             case 'sqlite2':
376             case 'sqlite3':
377                 if (isset($parts['host']) && $parts['host'] == ':memory') {
378                     $parts['database'] = ':memory:';
379                     $parts['dsn']      = 'sqlite::memory:';
380                 } else {
381                     //fix windows dsn we have to add host: to path and set host to null
382                     if (isset($parts['host'])) {
383                         $parts['path'] = $parts['host'] . ":" . $parts["path"];
384                         $parts["host"] = null;
385                     }
386                     $parts['database'] = $parts['path'];
387                     $parts['dsn'] = $parts['scheme'] . ':' . $parts['path'];
388                 }
389
390                 break;
391             
392             case 'mssql':
393             case 'dblib':
394                 if ( ! isset($parts['path']) || $parts['path'] == '/') {
395                     throw new Doctrine_Manager_Exception('No database available in data source name');
396                 }
397                 if (isset($parts['path'])) {
398                     $parts['database'] = substr($parts['path'], 1);
399                 }
400                 if ( ! isset($parts['host'])) {
401                     throw new Doctrine_Manager_Exception('No hostname set in data source name');
402                 }
403                 
404                 if (isset(self::$driverMap[$parts['scheme']])) {
405                     $parts['scheme'] = self::$driverMap[$parts['scheme']];
406                 }
407
408                 $parts['dsn'] = $parts['scheme'] . ':host='
409                               . $parts['host'] . (isset($parts['port']) ? ':' . $parts['port']:null) . ';dbname='
410                               . $parts['database'];
411                 
412                 break;
413
414             case 'mysql':
415             case 'informix':
416             case 'oci8':
417             case 'oci':
418             case 'firebird':
419             case 'pgsql':
420             case 'odbc':
421             case 'mock':
422             case 'oracle':
423                 if ( ! isset($parts['path']) || $parts['path'] == '/') {
424                     throw new Doctrine_Manager_Exception('No database available in data source name');
425                 }
426                 if (isset($parts['path'])) {
427                     $parts['database'] = substr($parts['path'], 1);
428                 }
429                 if ( ! isset($parts['host'])) {
430                     throw new Doctrine_Manager_Exception('No hostname set in data source name');
431                 }
432                 
433                 if (isset(self::$driverMap[$parts['scheme']])) {
434                     $parts['scheme'] = self::$driverMap[$parts['scheme']];
435                 }
436
437                 $parts['dsn'] = $parts['scheme'] . ':host='
438                               . $parts['host'] . (isset($parts['port']) ? ';port=' . $parts['port']:null) . ';dbname='
439                               . $parts['database'];
440                 
441                 break;
442             default:
443                 throw new Doctrine_Manager_Exception('Unknown driver '.$parts['scheme']);
444         }
445
446
447         return $parts;
448     }
449
450     /**
451      * getConnection
452      * @param integer $index
453      * @return object Doctrine_Connection
454      * @throws Doctrine_Manager_Exception   if trying to get a non-existent connection
455      */
456     public function getConnection($name)
457     {
458         if ( ! isset($this->_connections[$name])) {
459             throw new Doctrine_Manager_Exception('Unknown connection: ' . $name);
460         }
461
462         return $this->_connections[$name];
463     }
464
465     /**
466      * getComponentAlias
467      * retrieves the alias for given component name
468      * if the alias couldn't be found, this method returns the given
469      * component name
470      *
471      * @param string $componentName
472      * @return string                   the component alias
473      */
474     public function getComponentAlias($componentName)
475     {
476         if (isset($this->componentAliases[$componentName])) {
477             return $this->componentAliases[$componentName];
478         }
479
480         return $componentName;
481     }
482
483     /**
484      * sets an alias for given component name
485      * very useful when building a large framework with a possibility
486      * to override any given class
487      *
488      * @param string $componentName         the name of the component
489      * @param string $alias
490      * @return Doctrine_Manager
491      */
492     public function setComponentAlias($componentName, $alias)
493     {
494         $this->componentAliases[$componentName] = $alias;
495
496         return $this;
497     }
498
499     /**
500      * getConnectionName
501      *
502      * @param Doctrine_Connection $conn     connection object to be searched for
503      * @return string                       the name of the connection
504      */
505     public function getConnectionName(Doctrine_Connection $conn)
506     {
507         return array_search($conn, $this->_connections, true);
508     }
509
510     /**
511      * bindComponent
512      * binds given component to given connection
513      * this means that when ever the given component uses a connection
514      * it will be using the bound connection instead of the current connection
515      *
516      * @param string $componentName
517      * @param string $connectionName
518      * @return boolean
519      */
520     public function bindComponent($componentName, $connectionName)
521     {
522         $this->_bound[$componentName] = $connectionName;
523     }
524
525     /**
526      * getConnectionForComponent
527      *
528      * @param string $componentName
529      * @return Doctrine_Connection
530      */
531     public function getConnectionForComponent($componentName = null)
532     {
533         if (isset($this->_bound[$componentName])) {
534             return $this->getConnection($this->_bound[$componentName]);
535         }
536         return $this->getCurrentConnection();
537     }
538
539     /**
540      * getTable
541      * this is the same as Doctrine_Connection::getTable() except
542      * that it works seamlessly in multi-server/connection environment
543      *
544      * @see Doctrine_Connection::getTable()
545      * @param string $componentName
546      * @return Doctrine_Table
547      */
548     public function getTable($componentName)
549     {
550         return $this->getConnectionForComponent($componentName)->getTable($componentName);
551     }
552
553     /**
554      * table
555      * this is the same as Doctrine_Connection::getTable() except
556      * that it works seamlessly in multi-server/connection environment
557      *
558      * @see Doctrine_Connection::getTable()
559      * @param string $componentName
560      * @return Doctrine_Table
561      */
562     public static function table($componentName)
563     {
564         return Doctrine_Manager::getInstance()
565                ->getConnectionForComponent($componentName)
566                ->getTable($componentName);
567     }
568
569     /**
570      * closes the connection
571      *
572      * @param Doctrine_Connection $connection
573      * @return void
574      */
575     public function closeConnection(Doctrine_Connection $connection)
576     {
577         $connection->close();
578
579         $key = array_search($connection, $this->_connections, true);
580
581         if ($key !== false) {
582             unset($this->_connections[$key]);
583         }
584         $this->_currIndex = key($this->_connections);
585
586         unset($connection);
587     }
588
589     /**
590      * getConnections
591      * returns all opened connections
592      *
593      * @return array
594      */
595     public function getConnections()
596     {
597         return $this->_connections;
598     }
599
600     /**
601      * setCurrentConnection
602      * sets the current connection to $key
603      *
604      * @param mixed $key                        the connection key
605      * @throws InvalidKeyException
606      * @return void
607      */
608     public function setCurrentConnection($key)
609     {
610         $key = (string) $key;
611         if ( ! isset($this->_connections[$key])) {
612             throw new InvalidKeyException();
613         }
614         $this->_currIndex = $key;
615     }
616
617     /**
618      * contains
619      * whether or not the manager contains specified connection
620      *
621      * @param mixed $key                        the connection key
622      * @return boolean
623      */
624     public function contains($key)
625     {
626         return isset($this->_connections[$key]);
627     }
628
629     /**
630      * count
631      * returns the number of opened connections
632      *
633      * @return integer
634      */
635     public function count()
636     {
637         return count($this->_connections);
638     }
639
640     /**
641      * getIterator
642      * returns an ArrayIterator that iterates through all connections
643      *
644      * @return ArrayIterator
645      */
646     public function getIterator()
647     {
648         return new ArrayIterator($this->_connections);
649     }
650
651     /**
652      * getCurrentConnection
653      * returns the current connection
654      *
655      * @throws Doctrine_Connection_Exception       if there are no open connections
656      * @return Doctrine_Connection
657      */
658     public function getCurrentConnection()
659     {
660         $i = $this->_currIndex;
661         if ( ! isset($this->_connections[$i])) {
662             throw new Doctrine_Connection_Exception();
663         }
664         return $this->_connections[$i];
665     }
666
667     /**
668      * __toString
669      * returns a string representation of this object
670      *
671      * @return string
672      */
673     public function __toString()
674     {
675         $r[] = "<pre>";
676         $r[] = "Doctrine_Manager";
677         $r[] = "Connections : ".count($this->_connections);
678         $r[] = "</pre>";
679         return implode("\n",$r);
680     }
681 }