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 }