Coverage for Doctrine_Manager

Back to coverage report

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