1<?php
2/*
3 *  $Id$
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.doctrine-project.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.doctrine-project.org
31 * @since       1.0
32 * @version     $Revision$
33 * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
34 */
35class 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 Doctrine_Query_Registry     the query registry
59     */
60    protected $_queryRegistry;
61
62    /**
63     * @var array                       Array of registered validators
64     */
65    protected $_validators = array();
66
67    /**
68     * @var array                       Array of registered hydrators
69     */
70    protected $_hydrators = array(
71        Doctrine_Core::HYDRATE_ARRAY            => 'Doctrine_Hydrator_ArrayDriver',
72        Doctrine_Core::HYDRATE_RECORD           => 'Doctrine_Hydrator_RecordDriver',
73        Doctrine_Core::HYDRATE_NONE             => 'Doctrine_Hydrator_NoneDriver',
74        Doctrine_Core::HYDRATE_SCALAR           => 'Doctrine_Hydrator_ScalarDriver',
75        Doctrine_Core::HYDRATE_SINGLE_SCALAR    => 'Doctrine_Hydrator_SingleScalarDriver',
76        Doctrine_Core::HYDRATE_ON_DEMAND        => 'Doctrine_Hydrator_RecordDriver',
77        Doctrine_Core::HYDRATE_ARRAY_HIERARCHY  => 'Doctrine_Hydrator_ArrayHierarchyDriver',
78        Doctrine_Core::HYDRATE_RECORD_HIERARCHY => 'Doctrine_Hydrator_RecordHierarchyDriver',
79        Doctrine_Core::HYDRATE_ARRAY_SHALLOW    => 'Doctrine_Hydrator_ArrayShallowDriver',
80    );
81
82    protected $_connectionDrivers = array(
83        'db2'      => 'Doctrine_Connection_Db2',
84        'mysql'    => 'Doctrine_Connection_Mysql',
85        'mysqli'   => 'Doctrine_Connection_Mysql',
86        'sqlite'   => 'Doctrine_Connection_Sqlite',
87        'pgsql'    => 'Doctrine_Connection_Pgsql',
88        'oci'      => 'Doctrine_Connection_Oracle',
89        'oci8'     => 'Doctrine_Connection_Oracle',
90        'oracle'   => 'Doctrine_Connection_Oracle',
91        'mssql'    => 'Doctrine_Connection_Mssql',
92        'dblib'    => 'Doctrine_Connection_Mssql',
93        'odbc'     => 'Doctrine_Connection_Mssql',
94        'mock'     => 'Doctrine_Connection_Mock'
95    );
96
97    protected $_extensions = array();
98
99    /**
100     * @var boolean                     Whether or not the default validators have been loaded
101     */
102    protected $_loadedDefaultValidators = false;
103
104    protected static $_instance;
105
106    private $_initialized = false;
107
108    /**
109     * constructor
110     *
111     * this is private constructor (use getInstance to get an instance of this class)
112     */
113    private function __construct()
114    {
115        $null = new Doctrine_Null;
116        Doctrine_Locator_Injectable::initNullObject($null);
117        Doctrine_Record_Iterator::initNullObject($null);
118    }
119
120    /**
121     * Sets default attributes values.
122     *
123     * This method sets default values for all null attributes of this
124     * instance. It is idempotent and can only be called one time. Subsequent
125     * calls does not alter the attribute values.
126     *
127     * @return boolean      true if inizialization was executed
128     */
129    public function setDefaultAttributes()
130    {
131        if ( ! $this->_initialized) {
132            $this->_initialized = true;
133            $attributes = array(
134                        Doctrine_Core::ATTR_CACHE                        => null,
135                        Doctrine_Core::ATTR_RESULT_CACHE                 => null,
136                        Doctrine_Core::ATTR_QUERY_CACHE                  => null,
137                        Doctrine_Core::ATTR_TABLE_CACHE                  => null,
138                        Doctrine_Core::ATTR_LOAD_REFERENCES              => true,
139                        Doctrine_Core::ATTR_LISTENER                     => new Doctrine_EventListener(),
140                        Doctrine_Core::ATTR_RECORD_LISTENER              => new Doctrine_Record_Listener(),
141                        Doctrine_Core::ATTR_THROW_EXCEPTIONS             => true,
142                        Doctrine_Core::ATTR_VALIDATE                     => Doctrine_Core::VALIDATE_NONE,
143                        Doctrine_Core::ATTR_QUERY_LIMIT                  => Doctrine_Core::LIMIT_RECORDS,
144                        Doctrine_Core::ATTR_IDXNAME_FORMAT               => "%s_idx",
145                        Doctrine_Core::ATTR_SEQNAME_FORMAT               => "%s_seq",
146                        Doctrine_Core::ATTR_TBLNAME_FORMAT               => "%s",
147                        Doctrine_Core::ATTR_FKNAME_FORMAT                => "%s",
148                        Doctrine_Core::ATTR_QUOTE_IDENTIFIER             => false,
149                        Doctrine_Core::ATTR_SEQCOL_NAME                  => 'id',
150                        Doctrine_Core::ATTR_PORTABILITY                  => Doctrine_Core::PORTABILITY_NONE,
151                        Doctrine_Core::ATTR_EXPORT                       => Doctrine_Core::EXPORT_ALL,
152                        Doctrine_Core::ATTR_DECIMAL_PLACES               => 2,
153                        Doctrine_Core::ATTR_DEFAULT_PARAM_NAMESPACE      => 'doctrine',
154                        Doctrine_Core::ATTR_AUTOLOAD_TABLE_CLASSES       => false,
155                        Doctrine_Core::ATTR_USE_DQL_CALLBACKS            => false,
156                        Doctrine_Core::ATTR_AUTO_ACCESSOR_OVERRIDE       => false,
157                        Doctrine_Core::ATTR_AUTO_FREE_QUERY_OBJECTS      => false,
158                        Doctrine_Core::ATTR_DEFAULT_IDENTIFIER_OPTIONS   => array(),
159                        Doctrine_Core::ATTR_DEFAULT_COLUMN_OPTIONS       => array(),
160                        Doctrine_Core::ATTR_HYDRATE_OVERWRITE            => true,
161                        Doctrine_Core::ATTR_QUERY_CLASS                  => 'Doctrine_Query',
162                        Doctrine_Core::ATTR_COLLECTION_CLASS             => 'Doctrine_Collection',
163                        Doctrine_Core::ATTR_TABLE_CLASS                  => 'Doctrine_Table',
164                        Doctrine_Core::ATTR_CASCADE_SAVES                => true,
165                        Doctrine_Core::ATTR_TABLE_CLASS_FORMAT           => '%sTable',
166                        Doctrine_Core::ATTR_USE_TABLE_REPOSITORY         => true,
167                        Doctrine_Core::ATTR_USE_TABLE_IDENTITY_MAP       => true,
168                        );
169            foreach ($attributes as $attribute => $value) {
170                $old = $this->getAttribute($attribute);
171                if ($old === null) {
172                    $this->setAttribute($attribute,$value);
173                }
174            }
175            return true;
176        }
177        return false;
178    }
179
180    /**
181     * Returns an instance of this class
182     * (this class uses the singleton pattern)
183     *
184     * @return Doctrine_Manager
185     */
186    public static function getInstance()
187    {
188        if ( ! isset(self::$_instance)) {
189            self::$_instance = new self();
190        }
191        return self::$_instance;
192    }
193
194    /**
195     * Reset the internal static instance
196     *
197     * @return void
198     */
199    public static function resetInstance()
200    {
201        if (self::$_instance) {
202            self::$_instance->reset();
203            self::$_instance = null;
204        }
205    }
206
207    /**
208     * Reset this instance of the manager
209     *
210     * @return void
211     */
212    public function reset()
213    {
214        foreach ($this->_connections as $conn) {
215            $conn->close();
216        }
217        $this->_connections = array();
218        $this->_queryRegistry = null;
219        $this->_extensions = array();
220        $this->_bound = array();
221        $this->_validators = array();
222        $this->_loadedDefaultValidators = false;
223        $this->_index = 0;
224        $this->_currIndex = 0;
225        $this->_initialized = false;
226    }
227
228    /**
229     * Lazy-initializes the query registry object and returns it
230     *
231     * @return Doctrine_Query_Registry
232     */
233    public function getQueryRegistry()
234    {
235      	if ( ! isset($this->_queryRegistry)) {
236      	   $this->_queryRegistry = new Doctrine_Query_Registry();
237      	}
238        return $this->_queryRegistry;
239    }
240
241    /**
242     * Sets the query registry
243     *
244     * @return Doctrine_Manager     this object
245     */
246    public function setQueryRegistry(Doctrine_Query_Registry $registry)
247    {
248        $this->_queryRegistry = $registry;
249
250        return $this;
251    }
252
253    /**
254     * Open a new connection. If the adapter parameter is set this method acts as
255     * a short cut for Doctrine_Manager::getInstance()->openConnection($adapter, $name);
256     *
257     * if the adapter paramater is not set this method acts as
258     * a short cut for Doctrine_Manager::getInstance()->getCurrentConnection()
259     *
260     * @param PDO|Doctrine_Adapter_Interface $adapter   database driver
261     * @param string $name                              name of the connection, if empty numeric key is used
262     * @throws Doctrine_Manager_Exception               if trying to bind a connection with an existing name
263     * @return Doctrine_Connection
264     */
265    public static function connection($adapter = null, $name = null)
266    {
267        if ($adapter == null) {
268            return Doctrine_Manager::getInstance()->getCurrentConnection();
269        } else {
270            return Doctrine_Manager::getInstance()->openConnection($adapter, $name);
271        }
272    }
273
274    /**
275     * Opens a new connection and saves it to Doctrine_Manager->connections
276     *
277     * @param PDO|Doctrine_Adapter_Interface $adapter   database driver
278     * @param string $name                              name of the connection, if empty numeric key is used
279     * @throws Doctrine_Manager_Exception               if trying to bind a connection with an existing name
280     * @throws Doctrine_Manager_Exception               if trying to open connection for unknown driver
281     * @return Doctrine_Connection
282     */
283    public function openConnection($adapter, $name = null, $setCurrent = true)
284    {
285        if (is_object($adapter)) {
286            if ( ! ($adapter instanceof PDO) && ! in_array('Doctrine_Adapter_Interface', class_implements($adapter))) {
287                throw new Doctrine_Manager_Exception("First argument should be an instance of PDO or implement Doctrine_Adapter_Interface");
288            }
289
290            $driverName = $adapter->getAttribute(Doctrine_Core::ATTR_DRIVER_NAME);
291        } else if (is_array($adapter)) {
292            if ( ! isset($adapter[0])) {
293                throw new Doctrine_Manager_Exception('Empty data source name given.');
294            }
295            $e = explode(':', $adapter[0]);
296
297            if ($e[0] == 'uri') {
298                $e[0] = 'odbc';
299            }
300
301            $parts['dsn']    = $adapter[0];
302            $parts['scheme'] = $e[0];
303            $parts['user']   = (isset($adapter[1])) ? $adapter[1] : null;
304            $parts['pass']   = (isset($adapter[2])) ? $adapter[2] : null;
305            $driverName = $e[0];
306            $adapter = $parts;
307        } else {
308            $parts = $this->parseDsn($adapter);
309            $driverName = $parts['scheme'];
310            $adapter = $parts;
311        }
312
313        // Decode adapter information
314        if (is_array($adapter)) {
315            foreach ($adapter as $key => $value) {
316                $adapter[$key]  = $value ? urldecode($value):null;
317            }
318        }
319
320        // initialize the default attributes
321        $this->setDefaultAttributes();
322
323        if ($name !== null) {
324            $name = (string) $name;
325            if (isset($this->_connections[$name])) {
326                if ($setCurrent) {
327                    $this->_currIndex = $name;
328                }
329                return $this->_connections[$name];
330            }
331        } else {
332            $name = $this->_index;
333            $this->_index++;
334        }
335
336        if ( ! isset($this->_connectionDrivers[$driverName])) {
337            throw new Doctrine_Manager_Exception('Unknown driver ' . $driverName);
338        }
339
340        $className = $this->_connectionDrivers[$driverName];
341        $conn = new $className($this, $adapter);
342        $conn->setName($name);
343
344        $this->_connections[$name] = $conn;
345
346        if ($setCurrent) {
347            $this->_currIndex = $name;
348        }
349        return $this->_connections[$name];
350    }
351
352    /**
353     * Parse a pdo style dsn in to an array of parts
354     *
355     * @param array $dsn An array of dsn information
356     * @return array The array parsed
357     * @todo package:dbal
358     */
359    public function parsePdoDsn($dsn)
360    {
361        $parts = array();
362
363        $names = array('dsn', 'scheme', 'host', 'port', 'user', 'pass', 'path', 'query', 'fragment', 'unix_socket');
364
365        foreach ($names as $name) {
366            if ( ! isset($parts[$name])) {
367                $parts[$name] = null;
368            }
369        }
370
371        $e = explode(':', $dsn);
372        $parts['scheme'] = $e[0];
373        $parts['dsn'] = $dsn;
374
375        $e = explode(';', $e[1]);
376        foreach ($e as $string) {
377            if ($string) {
378                $e2 = explode('=', $string);
379
380                if (isset($e2[0]) && isset($e2[1])) {
381                    if (count($e2) > 2)
382                    {
383                        $key = $e2[0];
384                        unset($e2[0]);
385                        $value = implode('=', $e2);
386                    } else {
387                        list($key, $value) = $e2;
388                    }
389                    $parts[$key] = $value;
390                }
391            }
392        }
393
394        return $parts;
395    }
396
397    /**
398     * Build the blank dsn parts array used with parseDsn()
399     *
400     * @see parseDsn()
401     * @param string $dsn
402     * @return array $parts
403     */
404    protected function _buildDsnPartsArray($dsn)
405    {
406        // fix sqlite dsn so that it will parse correctly
407        $dsn = str_replace("////", "/", $dsn);
408        $dsn = str_replace("\\", "/", $dsn);
409        $dsn = preg_replace("/\/\/\/(.*):\//", "//$1:/", $dsn);
410
411        // silence any warnings
412        $parts = @parse_url($dsn);
413
414        $names = array('dsn', 'scheme', 'host', 'port', 'user', 'pass', 'path', 'query', 'fragment');
415
416        foreach ($names as $name) {
417            if ( ! isset($parts[$name])) {
418                $parts[$name] = null;
419            }
420        }
421
422        if (count($parts) == 0 || ! isset($parts['scheme'])) {
423            throw new Doctrine_Manager_Exception('Could not parse dsn');
424        }
425
426        return $parts;
427    }
428
429    /**
430     * Parse a Doctrine style dsn string in to an array of parts
431     *
432     * @param string $dsn
433     * @return array Parsed contents of DSN
434     * @todo package:dbal
435     */
436    public function parseDsn($dsn)
437    {
438        $parts = $this->_buildDsnPartsArray($dsn);
439
440        switch ($parts['scheme']) {
441            case 'sqlite':
442            case 'sqlite2':
443            case 'sqlite3':
444                if (isset($parts['host']) && $parts['host'] == ':memory') {
445                    $parts['database'] = ':memory:';
446                    $parts['dsn']      = 'sqlite::memory:';
447                } else {
448                    //fix windows dsn we have to add host: to path and set host to null
449                    if (isset($parts['host'])) {
450                        $parts['path'] = $parts['host'] . ":" . $parts["path"];
451                        $parts['host'] = null;
452                    }
453                    $parts['database'] = $parts['path'];
454                    $parts['dsn'] = $parts['scheme'] . ':' . $parts['path'];
455                }
456
457                break;
458
459            case 'mssql':
460            case 'dblib':
461                if ( ! isset($parts['path']) || $parts['path'] == '/') {
462                    throw new Doctrine_Manager_Exception('No database available in data source name');
463                }
464                if (isset($parts['path'])) {
465                    $parts['database'] = substr($parts['path'], 1);
466                }
467                if ( ! isset($parts['host'])) {
468                    throw new Doctrine_Manager_Exception('No hostname set in data source name');
469                }
470
471                $parts['dsn'] = $parts['scheme'] . ':host='
472                              . $parts['host'] . (isset($parts['port']) ? ':' . $parts['port']:null) . ';dbname='
473                              . $parts['database'];
474
475                break;
476
477            case 'mysql':
478            case 'oci8':
479            case 'oci':
480            case 'pgsql':
481            case 'odbc':
482            case 'mock':
483            case 'oracle':
484                if ( ! isset($parts['path']) || $parts['path'] == '/') {
485                    throw new Doctrine_Manager_Exception('No database available in data source name');
486                }
487                if (isset($parts['path'])) {
488                    $parts['database'] = substr($parts['path'], 1);
489                }
490                if ( ! isset($parts['host'])) {
491                    throw new Doctrine_Manager_Exception('No hostname set in data source name');
492                }
493
494                $parts['dsn'] = $parts['scheme'] . ':host='
495                              . $parts['host'] . (isset($parts['port']) ? ';port=' . $parts['port']:null) . ';dbname='
496                              . $parts['database'];
497
498                break;
499            default:
500                $parts['dsn'] = $dsn;
501        }
502
503        return $parts;
504    }
505
506    /**
507     * Get the connection instance for the passed name
508     *
509     * @param string $name                  name of the connection, if empty numeric key is used
510     * @return Doctrine_Connection
511     * @throws Doctrine_Manager_Exception   if trying to get a non-existent connection
512     */
513    public function getConnection($name)
514    {
515        if ( ! isset($this->_connections[$name])) {
516            throw new Doctrine_Manager_Exception('Unknown connection: ' . $name);
517        }
518
519        return $this->_connections[$name];
520    }
521
522    /**
523     * Get the name of the passed connection instance
524     *
525     * @param Doctrine_Connection $conn     connection object to be searched for
526     * @return string                       the name of the connection
527     */
528    public function getConnectionName(Doctrine_Connection $conn)
529    {
530        return array_search($conn, $this->_connections, true);
531    }
532
533    /**
534     * Binds given component to given connection
535     * this means that when ever the given component uses a connection
536     * it will be using the bound connection instead of the current connection
537     *
538     * @param string $componentName
539     * @param string $connectionName
540     * @return boolean
541     */
542    public function bindComponent($componentName, $connectionName)
543    {
544        $this->_bound[$componentName] = $connectionName;
545    }
546
547    /**
548     * Get the connection instance for the specified component
549     *
550     * @param string $componentName
551     * @return Doctrine_Connection
552     */
553    public function getConnectionForComponent($componentName)
554    {
555        Doctrine_Core::modelsAutoload($componentName);
556
557        if (isset($this->_bound[$componentName])) {
558            return $this->getConnection($this->_bound[$componentName]);
559        }
560
561        return $this->getCurrentConnection();
562    }
563
564    /**
565     * Check if a component is bound to a connection
566     *
567     * @param string $componentName
568     * @return boolean
569     */
570    public function hasConnectionForComponent($componentName = null)
571    {
572        return isset($this->_bound[$componentName]);
573    }
574
575    /**
576     * Closes the specified connection
577     *
578     * @param Doctrine_Connection $connection
579     * @return void
580     */
581    public function closeConnection(Doctrine_Connection $connection)
582    {
583        $connection->close();
584
585        $key = array_search($connection, $this->_connections, true);
586
587        if ($key !== false) {
588            unset($this->_connections[$key]);
589
590            if ($key === $this->_currIndex) {
591                $key = key($this->_connections);
592                $this->_currIndex = ($key !== null) ? $key : 0;
593            }
594        }
595
596        unset($connection);
597    }
598
599    /**
600     * Returns all opened connections
601     *
602     * @return array
603     */
604    public function getConnections()
605    {
606        return $this->_connections;
607    }
608
609    /**
610     * Sets the current connection to $key
611     *
612     * @param mixed $key                        the connection key
613     * @throws Doctrine_Manager_Exception
614     * @return void
615     */
616    public function setCurrentConnection($key)
617    {
618        $key = (string) $key;
619        if ( ! isset($this->_connections[$key])) {
620            throw new Doctrine_Manager_Exception("Connection key '$key' does not exist.");
621        }
622        $this->_currIndex = $key;
623    }
624
625    /**
626     * Whether or not the manager contains specified connection
627     *
628     * @param mixed $key                        the connection key
629     * @return boolean
630     */
631    public function contains($key)
632    {
633        return isset($this->_connections[$key]);
634    }
635
636    /**
637     * Returns the number of opened connections
638     *
639     * @return integer
640     */
641    public function count()
642    {
643        return count($this->_connections);
644    }
645
646    /**
647     * Returns an ArrayIterator that iterates through all connections
648     *
649     * @return ArrayIterator
650     */
651    public function getIterator()
652    {
653        return new ArrayIterator($this->_connections);
654    }
655
656    /**
657     * Get the current connection instance
658     *
659     * @throws Doctrine_Connection_Exception       if there are no open connections
660     * @return Doctrine_Connection
661     */
662    public function getCurrentConnection()
663    {
664        $i = $this->_currIndex;
665        if ( ! isset($this->_connections[$i])) {
666            throw new Doctrine_Connection_Exception('There is no open connection');
667        }
668        return $this->_connections[$i];
669    }
670
671    /**
672     * Creates databases for all existing connections
673     *
674     * @param string $specifiedConnections Array of connections you wish to create the database for
675     * @return void
676     * @todo package:dbal
677     */
678    public function createDatabases($specifiedConnections = array())
679    {
680        if ( ! is_array($specifiedConnections)) {
681            $specifiedConnections = (array) $specifiedConnections;
682        }
683
684        foreach ($this as $name => $connection) {
685            if ( ! empty($specifiedConnections) && ! in_array($name, $specifiedConnections)) {
686                continue;
687            }
688
689            $connection->createDatabase();
690        }
691    }
692
693    /**
694     * Drops databases for all existing connections
695     *
696     * @param string $specifiedConnections Array of connections you wish to drop the database for
697     * @return void
698     * @todo package:dbal
699     */
700    public function dropDatabases($specifiedConnections = array())
701    {
702        if ( ! is_array($specifiedConnections)) {
703            $specifiedConnections = (array) $specifiedConnections;
704        }
705
706        foreach ($this as $name => $connection) {
707            if ( ! empty($specifiedConnections) && ! in_array($name, $specifiedConnections)) {
708                continue;
709            }
710
711            $connection->dropDatabase();
712        }
713    }
714
715    /**
716     * Returns a string representation of this object
717     *
718     * @return string
719     */
720    public function __toString()
721    {
722        $r[] = "<pre>";
723        $r[] = "Doctrine_Manager";
724        $r[] = "Connections : ".count($this->_connections);
725        $r[] = "</pre>";
726        return implode("\n",$r);
727    }
728
729    /**
730     * Get available doctrine validators
731     *
732     * @return array $validators
733     */
734    public function getValidators()
735    {
736        if ( ! $this->_loadedDefaultValidators) {
737            $this->_loadedDefaultValidators = true;
738
739            $this->registerValidators(array(
740                'unique',
741                'past',
742                'range',
743                'ip',
744                'notblank',
745                'unsigned',
746                'errorstack',
747                'nospace',
748                'creditcard',
749                'regexp',
750                'exception',
751                'time',
752                'future',
753                'notnull',
754                'driver',
755                'readonly',
756                'htmlcolor',
757                'date',
758                'timestamp',
759                'minlength',
760                'usstate',
761                'email',
762                'country',
763            ));
764        }
765
766        return $this->_validators;
767    }
768
769    /**
770     * Register validators so that Doctrine is aware of them
771     *
772     * @param  mixed $validators Name of validator or array of validators
773     * @return void
774     */
775    public function registerValidators($validators)
776    {
777        $validators = (array) $validators;
778        foreach ($validators as $validator) {
779            if ( ! in_array($validator, $this->_validators)) {
780                $this->_validators[] = $validator;
781            }
782        }
783    }
784
785    /**
786     * Register a new driver for hydration
787     *
788     * @return void
789     */
790    public function registerHydrator($name, $class)
791    {
792        $this->_hydrators[$name] = $class;
793    }
794
795    /**
796     * Get all registered hydrators
797     *
798     * @return array $hydrators
799     */
800    public function getHydrators()
801    {
802        return $this->_hydrators;
803    }
804
805    /**
806     * Register a custom connection driver
807     *
808     * @return void
809     */
810    public function registerConnectionDriver($name, $class)
811    {
812        $this->_connectionDrivers[$name] = $class;
813    }
814
815    /**
816     * Get all the available connection drivers
817     *
818     * @return array $connectionDrivers
819     */
820    public function getConnectionDrivers()
821    {
822        return $this->_connectionDrivers;
823    }
824
825    /**
826     * Register a Doctrine extension for extensionsAutoload() method
827     *
828     * @param string $name
829     * @param string $path
830     * @return void
831     */
832    public function registerExtension($name, $path = null)
833    {
834        if (is_null($path)) {
835            $path = Doctrine_Core::getExtensionsPath() . '/' . $name . '/lib';
836        }
837        $this->_extensions[$name] = $path;
838    }
839
840    /**
841     * Get all registered Doctrine extensions
842     *
843     * @return $extensions
844     */
845    public function getExtensions()
846    {
847        return $this->_extensions;
848    }
849}
850