Coverage for Doctrine_Manager

Back to coverage report

1 <?php
2 /*
3  *  $Id: Manager.php 4094 2008-03-23 00:05:14Z jwage $
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 Doctrine::autoload('Doctrine_Configurable');
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: 4094 $
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     /**
68      * constructor
69      *
70      * this is private constructor (use getInstance to get an instance of this class)
71      */
72     private function __construct()
73     {
74         $this->_root = dirname(__FILE__);
75
76         Doctrine_Locator_Injectable::initNullObject(new Doctrine_Null);
77     }
78
79     /**
80      * setDefaultAttributes
81      * sets default attributes
82      *
83      * @todo I do not understand the flow here. Explain or refactor?
84      * @return boolean
85      */
86     public function setDefaultAttributes()
87     {
88         static $init = false;
89         if ( ! $init) {
90             $init = true;
91             $attributes = array(
92                         Doctrine::ATTR_CACHE                    => null,
93                         Doctrine::ATTR_RESULT_CACHE             => null,
94                         Doctrine::ATTR_QUERY_CACHE              => null,
95                         Doctrine::ATTR_LOAD_REFERENCES          => true,
96                         Doctrine::ATTR_LISTENER                 => new Doctrine_EventListener(),
97                         Doctrine::ATTR_RECORD_LISTENER          => new Doctrine_Record_Listener(),
98                         Doctrine::ATTR_THROW_EXCEPTIONS         => true,
99                         Doctrine::ATTR_VALIDATE                 => Doctrine::VALIDATE_NONE,
100                         Doctrine::ATTR_QUERY_LIMIT              => Doctrine::LIMIT_RECORDS,
101                         Doctrine::ATTR_IDXNAME_FORMAT           => "%s_idx",
102                         Doctrine::ATTR_SEQNAME_FORMAT           => "%s_seq",
103                         Doctrine::ATTR_TBLNAME_FORMAT           => "%s",
104                         Doctrine::ATTR_QUOTE_IDENTIFIER         => false,
105                         Doctrine::ATTR_SEQCOL_NAME              => 'id',
106                         Doctrine::ATTR_PORTABILITY              => Doctrine::PORTABILITY_ALL,
107                         Doctrine::ATTR_EXPORT                   => Doctrine::EXPORT_ALL,
108                         Doctrine::ATTR_DECIMAL_PLACES           => 2,
109                         Doctrine::ATTR_DEFAULT_PARAM_NAMESPACE  => 'doctrine',
110                         Doctrine::ATTR_AUTOLOAD_TABLE_CLASSES   => true,
111                         ); 
112             foreach ($attributes as $attribute => $value) {
113                 $old = $this->getAttribute($attribute);
114                 if ($old === null) {
115                     $this->setAttribute($attribute,$value);
116                 }
117             }
118             return true;
119         }
120         return false;
121     }
122
123     /**
124      * returns the root directory of Doctrine
125      *
126      * @return string
127      */
128     final public function getRoot()
129     {
130         return $this->_root;
131     }
132
133     /**
134      * getInstance
135      * returns an instance of this class
136      * (this class uses the singleton pattern)
137      *
138      * @return Doctrine_Manager
139      */
140     public static function getInstance()
141     {
142         static $instance;
143         if ( ! isset($instance)) {
144             $instance = new self();
145         }
146         return $instance;
147     }
148
149     /**
150      * getQueryRegistry
151      *
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         } else if (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             $driverName = $parts['scheme'];
278             $adapter = $parts;
279         }
280
281         // Decode adapter information
282         if (is_array($adapter)) {
283             foreach ($adapter as $key => $value) {
284                 $adapter[$key]  = urldecode($value);  
285             }
286         }
287
288         // initialize the default attributes
289         $this->setDefaultAttributes();
290
291         if ($name !== null) {
292             $name = (string) $name;
293             if (isset($this->_connections[$name])) {
294                 if ($setCurrent) {
295                     $this->_currIndex = $name;
296                 }
297                 return $this->_connections[$name];
298             }
299         } else {
300             $name = $this->_index;
301             $this->_index++;
302         }
303
304         $drivers = array('mysql'    => 'Doctrine_Connection_Mysql',
305                          'sqlite'   => 'Doctrine_Connection_Sqlite',
306                          'pgsql'    => 'Doctrine_Connection_Pgsql',
307                          'oci'      => 'Doctrine_Connection_Oracle',
308                          'oci8'     => 'Doctrine_Connection_Oracle',
309                          'oracle'   => 'Doctrine_Connection_Oracle',
310                          'mssql'    => 'Doctrine_Connection_Mssql',
311                          'dblib'    => 'Doctrine_Connection_Mssql',
312                          'firebird' => 'Doctrine_Connection_Firebird',
313                          'informix' => 'Doctrine_Connection_Informix',
314                          'mock'     => 'Doctrine_Connection_Mock');
315
316         if ( ! isset($drivers[$driverName])) {
317             throw new Doctrine_Manager_Exception('Unknown driver ' . $driverName);
318         }
319
320         $className = $drivers[$driverName];
321         $conn = new $className($this, $adapter);
322         $conn->setName($name);
323
324         $this->_connections[$name] = $conn;
325
326         if ($setCurrent) {
327             $this->_currIndex = $name;
328         }
329         return $this->_connections[$name];
330     }
331     
332     /**
333      * parsePdoDsn
334      *
335      * @param array $dsn An array of dsn information
336      * @return array The array parsed
337      * @todo package:dbal
338      */
339     public function parsePdoDsn($dsn)
340     {
341         $parts = array();
342
343         $names = array('dsn', 'scheme', 'host', 'port', 'user', 'pass', 'path', 'query', 'fragment');
344
345         foreach ($names as $name) {
346             if ( ! isset($parts[$name])) {
347                 $parts[$name] = null;
348             }
349         }
350
351         $e = explode(':', $dsn);
352         $parts['scheme'] = $e[0];
353         $parts['dsn'] = $dsn;
354
355         $e = explode(';', $e[1]);
356         foreach ($e as $string) {
357             if ($string) {
358                 $e2 = explode('=', $string);
359
360                 if (isset($e2[0]) && isset($e2[1])) {
361                     list($key, $value) = $e2;
362                     $parts[$key] = $value;
363                 }
364             }
365         }
366
367         return $parts;
368     }
369
370     /**
371      * buildDsnPartsArray
372      *
373      * @param string $dsn 
374      * @return array $parts
375      */
376     public function buildDsnPartsArray($dsn)
377     {
378         // fix sqlite dsn so that it will parse correctly
379         $dsn = str_replace("////", "/", $dsn);
380         $dsn = str_replace("\\", "/", $dsn);
381         $dsn = preg_replace("/\/\/\/(.*):\//", "//$1:/", $dsn);
382
383         // silence any warnings
384         $parts = @parse_url($dsn);
385
386         $names = array('dsn', 'scheme', 'host', 'port', 'user', 'pass', 'path', 'query', 'fragment');
387
388         foreach ($names as $name) {
389             if ( ! isset($parts[$name])) {
390                 $parts[$name] = null;
391             }
392         }
393
394         if (count($parts) == 0 || ! isset($parts['scheme'])) {
395             throw new Doctrine_Manager_Exception('Could not parse dsn');
396         }
397
398         return $parts;
399     }
400
401     /**
402      * parseDsn
403      *
404      * @param string $dsn
405      * @return array Parsed contents of DSN
406      * @todo package:dbal
407      */
408     public function parseDsn($dsn)
409     {
410         $parts = $this->buildDsnPartsArray($dsn);
411
412         switch ($parts['scheme']) {
413             case 'sqlite':
414             case 'sqlite2':
415             case 'sqlite3':
416                 if (isset($parts['host']) && $parts['host'] == ':memory') {
417                     $parts['database'] = ':memory:';
418                     $parts['dsn']      = 'sqlite::memory:';
419                 } else {
420                     //fix windows dsn we have to add host: to path and set host to null
421                     if (isset($parts['host'])) {
422                         $parts['path'] = $parts['host'] . ":" . $parts["path"];
423                         $parts['host'] = null;
424                     }
425                     $parts['database'] = $parts['path'];
426                     $parts['dsn'] = $parts['scheme'] . ':' . $parts['path'];
427                 }
428
429                 break;
430
431             case 'mssql':
432             case 'dblib':
433                 if ( ! isset($parts['path']) || $parts['path'] == '/') {
434                     throw new Doctrine_Manager_Exception('No database available in data source name');
435                 }
436                 if (isset($parts['path'])) {
437                     $parts['database'] = substr($parts['path'], 1);
438                 }
439                 if ( ! isset($parts['host'])) {
440                     throw new Doctrine_Manager_Exception('No hostname set in data source name');
441                 }
442
443                 $parts['dsn'] = $parts['scheme'] . ':host='
444                               . $parts['host'] . (isset($parts['port']) ? ':' . $parts['port']:null) . ';dbname='
445                               . $parts['database'];
446
447                 break;
448
449             case 'mysql':
450             case 'informix':
451             case 'oci8':
452             case 'oci':
453             case 'firebird':
454             case 'pgsql':
455             case 'odbc':
456             case 'mock':
457             case 'oracle':
458                 if ( ! isset($parts['path']) || $parts['path'] == '/') {
459                     throw new Doctrine_Manager_Exception('No database available in data source name');
460                 }
461                 if (isset($parts['path'])) {
462                     $parts['database'] = substr($parts['path'], 1);
463                 }
464                 if ( ! isset($parts['host'])) {
465                     throw new Doctrine_Manager_Exception('No hostname set in data source name');
466                 }
467
468                 $parts['dsn'] = $parts['scheme'] . ':host='
469                               . $parts['host'] . (isset($parts['port']) ? ';port=' . $parts['port']:null) . ';dbname='
470                               . $parts['database'];
471
472                 break;
473             default:
474                 throw new Doctrine_Manager_Exception('Unknown driver '.$parts['scheme']);
475         }
476
477         return $parts;
478     }
479
480     /**
481      * getConnection
482      * @param integer $index
483      * @return object Doctrine_Connection
484      * @throws Doctrine_Manager_Exception   if trying to get a non-existent connection
485      */
486     public function getConnection($name)
487     {
488         if ( ! isset($this->_connections[$name])) {
489             throw new Doctrine_Manager_Exception('Unknown connection: ' . $name);
490         }
491
492         return $this->_connections[$name];
493     }
494
495     /**
496      * getComponentAlias
497      * retrieves the alias for given component name
498      * if the alias couldn't be found, this method returns the given
499      * component name
500      *
501      * @param string $componentName
502      * @return string                   the component alias
503      */
504     public function getComponentAlias($componentName)
505     {
506         if (isset($this->componentAliases[$componentName])) {
507             return $this->componentAliases[$componentName];
508         }
509
510         return $componentName;
511     }
512
513     /**
514      * sets an alias for given component name
515      * very useful when building a large framework with a possibility
516      * to override any given class
517      *
518      * @param string $componentName         the name of the component
519      * @param string $alias
520      * @return Doctrine_Manager
521      */
522     public function setComponentAlias($componentName, $alias)
523     {
524         $this->componentAliases[$componentName] = $alias;
525
526         return $this;
527     }
528
529     /**
530      * getConnectionName
531      *
532      * @param Doctrine_Connection $conn     connection object to be searched for
533      * @return string                       the name of the connection
534      */
535     public function getConnectionName(Doctrine_Connection $conn)
536     {
537         return array_search($conn, $this->_connections, true);
538     }
539
540     /**
541      * bindComponent
542      * binds given component to given connection
543      * this means that when ever the given component uses a connection
544      * it will be using the bound connection instead of the current connection
545      *
546      * @param string $componentName
547      * @param string $connectionName
548      * @return boolean
549      */
550     public function bindComponent($componentName, $connectionName)
551     {
552         $this->_bound[$componentName] = $connectionName;
553     }
554
555     /**
556      * getConnectionForComponent
557      *
558      * @param string $componentName
559      * @return Doctrine_Connection
560      */
561     public function getConnectionForComponent($componentName)
562     {
563         Doctrine::autoload($componentName);
564
565         if (isset($this->_bound[$componentName])) {
566             return $this->getConnection($this->_bound[$componentName]);
567         }
568
569         return $this->getCurrentConnection();
570     }
571     
572     /**
573      * hasConnectionForComponent
574      *
575      * @param string $componentName
576      * @return boolean
577      */
578     public function hasConnectionForComponent($componentName = null)
579     {
580         return isset($this->_bound[$componentName]);
581     }
582
583     /**
584      * getTable
585      * this is the same as Doctrine_Connection::getTable() except
586      * that it works seamlessly in multi-server/connection environment
587      *
588      * @see Doctrine_Connection::getTable()
589      * @param string $componentName
590      * @return Doctrine_Table
591      */
592     public function getTable($componentName)
593     {
594         return $this->getConnectionForComponent($componentName)->getTable($componentName);
595     }
596
597     /**
598      * table
599      * this is the same as Doctrine_Connection::getTable() except
600      * that it works seamlessly in multi-server/connection environment
601      *
602      * @see Doctrine_Connection::getTable()
603      * @param string $componentName
604      * @return Doctrine_Table
605      */
606     public static function table($componentName)
607     {
608         $manager = Doctrine_Manager::getInstance();
609         return $manager->getConnectionForComponent($componentName)->getTable($componentName);
610     }
611
612     /**
613      * closes the connection
614      *
615      * @param Doctrine_Connection $connection
616      * @return void
617      */
618     public function closeConnection(Doctrine_Connection $connection)
619     {
620         $connection->close();
621
622         $key = array_search($connection, $this->_connections, true);
623
624         if ($key !== false) {
625             unset($this->_connections[$key]);
626         }
627         $this->_currIndex = key($this->_connections);
628
629         unset($connection);
630     }
631
632     /**
633      * getConnections
634      * returns all opened connections
635      *
636      * @return array
637      */
638     public function getConnections()
639     {
640         return $this->_connections;
641     }
642
643     /**
644      * setCurrentConnection
645      * sets the current connection to $key
646      *
647      * @param mixed $key                        the connection key
648      * @throws InvalidKeyException
649      * @return void
650      */
651     public function setCurrentConnection($key)
652     {
653         $key = (string) $key;
654         if ( ! isset($this->_connections[$key])) {
655             throw new InvalidKeyException();
656         }
657         $this->_currIndex = $key;
658     }
659
660     /**
661      * contains
662      * whether or not the manager contains specified connection
663      *
664      * @param mixed $key                        the connection key
665      * @return boolean
666      */
667     public function contains($key)
668     {
669         return isset($this->_connections[$key]);
670     }
671
672     /**
673      * count
674      * returns the number of opened connections
675      *
676      * @return integer
677      */
678     public function count()
679     {
680         return count($this->_connections);
681     }
682
683     /**
684      * getIterator
685      * returns an ArrayIterator that iterates through all connections
686      *
687      * @return ArrayIterator
688      */
689     public function getIterator()
690     {
691         return new ArrayIterator($this->_connections);
692     }
693
694     /**
695      * getCurrentConnection
696      * returns the current connection
697      *
698      * @throws Doctrine_Connection_Exception       if there are no open connections
699      * @return Doctrine_Connection
700      */
701     public function getCurrentConnection()
702     {
703         $i = $this->_currIndex;
704         if ( ! isset($this->_connections[$i])) {
705             throw new Doctrine_Connection_Exception('There is no open connection');
706         }
707         return $this->_connections[$i];
708     }
709
710     /**
711      * createDatabases
712      *
713      * Creates databases for connections
714      *
715      * @param string $specifiedConnections Array of connections you wish to create the database for
716      * @return void
717      * @todo package:dbal
718      */
719     public function createDatabases($specifiedConnections = array())
720     {
721         if ( ! is_array($specifiedConnections)) {
722             $specifiedConnections = (array) $specifiedConnections;
723         }
724
725         $results = array();
726
727         foreach ($this as $name => $connection) {
728             if ( ! empty($specifiedConnections) && ! in_array($name, $specifiedConnections)) {
729                 continue;
730             }
731
732             $results[$name] = $connection->createDatabase();
733         }
734
735         return $results;
736     }
737
738     /**
739      * dropDatabases
740      *
741      * Drops databases for connections
742      *
743      * @param string $specifiedConnections Array of connections you wish to drop the database for
744      * @return void
745      * @todo package:dbal
746      */
747     public function dropDatabases($specifiedConnections = array())
748     {
749         if ( ! is_array($specifiedConnections)) {
750             $specifiedConnections = (array) $specifiedConnections;
751         }
752
753         $results = array();
754
755         foreach ($this as $name => $connection) {
756             if ( ! empty($specifiedConnections) && ! in_array($name, $specifiedConnections)) {
757                 continue;
758             }
759
760             $results[$name] = $connection->dropDatabase();
761         }
762
763         return $results;
764     }
765
766     /**
767      * __toString
768      * returns a string representation of this object
769      *
770      * @return string
771      */
772     public function __toString()
773     {
774         $r[] = "<pre>";
775         $r[] = "Doctrine_Manager";
776         $r[] = "Connections : ".count($this->_connections);
777         $r[] = "</pre>";
778         return implode("\n",$r);
779     }
780 }