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 * The base core class of Doctrine
24 *
25 * @package     Doctrine
26 * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
27 * @author      Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
28 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
29 * @link        www.doctrine-project.org
30 * @since       1.0
31 * @version     $Revision$
32 */
33class Doctrine_Core
34{
35    /**
36     * VERSION
37     */
38    const VERSION                   = '1.2.4';
39
40    /**
41     * ERROR CONSTANTS
42     */
43    const ERR                       = -1;
44    const ERR_SYNTAX                = -2;
45    const ERR_CONSTRAINT            = -3;
46    const ERR_NOT_FOUND             = -4;
47    const ERR_ALREADY_EXISTS        = -5;
48    const ERR_UNSUPPORTED           = -6;
49    const ERR_MISMATCH              = -7;
50    const ERR_INVALID               = -8;
51    const ERR_NOT_CAPABLE           = -9;
52    const ERR_TRUNCATED             = -10;
53    const ERR_INVALID_NUMBER        = -11;
54    const ERR_INVALID_DATE          = -12;
55    const ERR_DIVZERO               = -13;
56    const ERR_NODBSELECTED          = -14;
57    const ERR_CANNOT_CREATE         = -15;
58    const ERR_CANNOT_DELETE         = -16;
59    const ERR_CANNOT_DROP           = -17;
60    const ERR_NOSUCHTABLE           = -18;
61    const ERR_NOSUCHFIELD           = -19;
62    const ERR_NEED_MORE_DATA        = -20;
63    const ERR_NOT_LOCKED            = -21;
64    const ERR_VALUE_COUNT_ON_ROW    = -22;
65    const ERR_INVALID_DSN           = -23;
66    const ERR_CONNECT_FAILED        = -24;
67    const ERR_EXTENSION_NOT_FOUND   = -25;
68    const ERR_NOSUCHDB              = -26;
69    const ERR_ACCESS_VIOLATION      = -27;
70    const ERR_CANNOT_REPLACE        = -28;
71    const ERR_CONSTRAINT_NOT_NULL   = -29;
72    const ERR_DEADLOCK              = -30;
73    const ERR_CANNOT_ALTER          = -31;
74    const ERR_MANAGER               = -32;
75    const ERR_MANAGER_PARSE         = -33;
76    const ERR_LOADMODULE            = -34;
77    const ERR_INSUFFICIENT_DATA     = -35;
78    const ERR_CLASS_NAME            = -36;
79
80    /**
81     * PDO derived constants
82     */
83    const CASE_LOWER = 2;
84    const CASE_NATURAL = 0;
85    const CASE_UPPER = 1;
86    const CURSOR_FWDONLY = 0;
87    const CURSOR_SCROLL = 1;
88    const ERRMODE_EXCEPTION = 2;
89    const ERRMODE_SILENT = 0;
90    const ERRMODE_WARNING = 1;
91    const FETCH_ASSOC = 2;
92    const FETCH_BOTH = 4;
93    const FETCH_BOUND = 6;
94    const FETCH_CLASS = 8;
95    const FETCH_CLASSTYPE = 262144;
96    const FETCH_COLUMN = 7;
97    const FETCH_FUNC = 10;
98    const FETCH_GROUP = 65536;
99    const FETCH_INTO = 9;
100    const FETCH_LAZY = 1;
101    const FETCH_NAMED = 11;
102    const FETCH_NUM = 3;
103    const FETCH_OBJ = 5;
104    const FETCH_ORI_ABS = 4;
105    const FETCH_ORI_FIRST = 2;
106    const FETCH_ORI_LAST = 3;
107    const FETCH_ORI_NEXT = 0;
108    const FETCH_ORI_PRIOR = 1;
109    const FETCH_ORI_REL = 5;
110    const FETCH_SERIALIZE = 524288;
111    const FETCH_UNIQUE = 196608;
112    const NULL_EMPTY_STRING = 1;
113    const NULL_NATURAL = 0;
114    const NULL_TO_STRING         = NULL;
115    const PARAM_BOOL = 5;
116    const PARAM_INPUT_OUTPUT = -2147483648;
117    const PARAM_INT = 1;
118    const PARAM_LOB = 3;
119    const PARAM_NULL = 0;
120    const PARAM_STMT = 4;
121    const PARAM_STR = 2;
122
123    /**
124     * ATTRIBUTE CONSTANTS
125     */
126
127    /**
128     * PDO derived attributes
129     */
130    const ATTR_AUTOCOMMIT           = 0;
131    const ATTR_PREFETCH             = 1;
132    const ATTR_TIMEOUT              = 2;
133    const ATTR_ERRMODE              = 3;
134    const ATTR_SERVER_VERSION       = 4;
135    const ATTR_CLIENT_VERSION       = 5;
136    const ATTR_SERVER_INFO          = 6;
137    const ATTR_CONNECTION_STATUS    = 7;
138    const ATTR_CASE                 = 8;
139    const ATTR_CURSOR_NAME          = 9;
140    const ATTR_CURSOR               = 10;
141    const ATTR_ORACLE_NULLS         = 11;
142    const ATTR_PERSISTENT           = 12;
143    const ATTR_STATEMENT_CLASS      = 13;
144    const ATTR_FETCH_TABLE_NAMES    = 14;
145    const ATTR_FETCH_CATALOG_NAMES  = 15;
146    const ATTR_DRIVER_NAME          = 16;
147    const ATTR_STRINGIFY_FETCHES    = 17;
148    const ATTR_MAX_COLUMN_LEN       = 18;
149
150    /**
151     * Doctrine constants
152     */
153    const ATTR_LISTENER             = 100;
154    const ATTR_QUOTE_IDENTIFIER     = 101;
155    const ATTR_FIELD_CASE           = 102;
156    const ATTR_IDXNAME_FORMAT       = 103;
157    const ATTR_SEQNAME_FORMAT       = 104;
158    const ATTR_SEQCOL_NAME          = 105;
159    const ATTR_CMPNAME_FORMAT       = 118;
160    const ATTR_DBNAME_FORMAT        = 117;
161    const ATTR_TBLCLASS_FORMAT      = 119;
162    const ATTR_TBLNAME_FORMAT       = 120;
163    const ATTR_FKNAME_FORMAT        = 171;
164    const ATTR_EXPORT               = 140;
165    const ATTR_DECIMAL_PLACES       = 141;
166
167    const ATTR_PORTABILITY          = 106;
168    const ATTR_VALIDATE             = 107;
169    const ATTR_COLL_KEY             = 108;
170    const ATTR_QUERY_LIMIT          = 109;
171    const ATTR_DEFAULT_TABLE_TYPE   = 112;
172    const ATTR_DEF_TEXT_LENGTH      = 113;
173    const ATTR_DEF_VARCHAR_LENGTH   = 114;
174    const ATTR_DEF_TABLESPACE       = 115;
175    const ATTR_EMULATE_DATABASE     = 116;
176    const ATTR_USE_NATIVE_ENUM      = 117;
177    const ATTR_DEFAULT_SEQUENCE     = 133;
178
179    const ATTR_FETCHMODE                    = 118;
180    const ATTR_NAME_PREFIX                  = 121;
181    const ATTR_CREATE_TABLES                = 122;
182    const ATTR_COLL_LIMIT                   = 123;
183
184    const ATTR_CACHE                        = 150;
185    const ATTR_RESULT_CACHE                 = 150;
186    const ATTR_CACHE_LIFESPAN               = 151;
187    const ATTR_RESULT_CACHE_LIFESPAN        = 151;
188    const ATTR_LOAD_REFERENCES              = 153;
189    const ATTR_RECORD_LISTENER              = 154;
190    const ATTR_THROW_EXCEPTIONS             = 155;
191    const ATTR_DEFAULT_PARAM_NAMESPACE      = 156;
192    const ATTR_QUERY_CACHE                  = 157;
193    const ATTR_QUERY_CACHE_LIFESPAN         = 158;
194    const ATTR_AUTOLOAD_TABLE_CLASSES       = 160;
195    const ATTR_MODEL_LOADING                = 161;
196    const ATTR_RECURSIVE_MERGE_FIXTURES     = 162;
197    const ATTR_USE_DQL_CALLBACKS            = 164;
198    const ATTR_AUTO_ACCESSOR_OVERRIDE       = 165;
199    const ATTR_AUTO_FREE_QUERY_OBJECTS      = 166;
200    const ATTR_DEFAULT_TABLE_CHARSET        = 167;
201    const ATTR_DEFAULT_TABLE_COLLATE        = 168;
202    const ATTR_DEFAULT_IDENTIFIER_OPTIONS   = 169;
203    const ATTR_DEFAULT_COLUMN_OPTIONS       = 170;
204    const ATTR_HYDRATE_OVERWRITE            = 172;
205    const ATTR_QUERY_CLASS                  = 173;
206    const ATTR_CASCADE_SAVES                = 174;
207    const ATTR_COLLECTION_CLASS             = 175;
208    const ATTR_TABLE_CLASS                  = 176;
209    const ATTR_USE_NATIVE_SET               = 177;
210    const ATTR_MODEL_CLASS_PREFIX           = 178;
211    const ATTR_TABLE_CLASS_FORMAT           = 179;
212    const ATTR_MAX_IDENTIFIER_LENGTH        = 180;
213    const ATTR_USE_TABLE_REPOSITORY         = 181;
214    const ATTR_USE_TABLE_IDENTITY_MAP       = 182;
215    const ATTR_TABLE_CACHE                  = 183;
216    const ATTR_TABLE_CACHE_LIFESPAN         = 184;
217
218
219    /**
220     * LIMIT CONSTANTS
221     */
222
223    /**
224     * constant for row limiting
225     */
226    const LIMIT_ROWS       = 1;
227    const QUERY_LIMIT_ROWS = 1;
228
229    /**
230     * constant for record limiting
231     */
232    const LIMIT_RECORDS       = 2;
233    const QUERY_LIMIT_RECORDS = 2;
234
235    /**
236     * FETCHMODE CONSTANTS
237     */
238
239
240    /**
241     * PORTABILITY CONSTANTS
242     */
243
244    /**
245     * Portability: turn off all portability features.
246     * @see self::ATTR_PORTABILITY
247     */
248    const PORTABILITY_NONE          = 0;
249
250    /**
251     * Portability: convert names of tables and fields to case defined in the
252     * "field_case" option when using the query*(), fetch*() methods.
253     * @see self::ATTR_PORTABILITY
254     */
255    const PORTABILITY_FIX_CASE      = 1;
256
257    /**
258     * Portability: right trim the data output by query*() and fetch*().
259     * @see self::ATTR_PORTABILITY
260     */
261    const PORTABILITY_RTRIM         = 2;
262
263    /**
264     * Portability: force reporting the number of rows deleted.
265     * @see self::ATTR_PORTABILITY
266     */
267    const PORTABILITY_DELETE_COUNT  = 4;
268
269    /**
270     * Portability: convert empty values to null strings in data output by
271     * query*() and fetch*().
272     * @see self::ATTR_PORTABILITY
273     */
274    const PORTABILITY_EMPTY_TO_NULL = 8;
275
276    /**
277     * Portability: removes database/table qualifiers from associative indexes
278     * @see self::ATTR_PORTABILITY
279     */
280    const PORTABILITY_FIX_ASSOC_FIELD_NAMES = 16;
281
282    /**
283     * Portability: makes Doctrine_Expression throw exception for unportable RDBMS expressions
284     * @see self::ATTR_PORTABILITY
285     */
286    const PORTABILITY_EXPR          = 32;
287
288    /**
289     * Portability: turn on all portability features.
290     * @see self::ATTR_PORTABILITY
291     */
292    const PORTABILITY_ALL           = 63;
293
294    /**
295     * LOCKMODE CONSTANTS
296     */
297
298    /**
299     * mode for optimistic locking
300     */
301    const LOCK_OPTIMISTIC       = 0;
302
303    /**
304     * mode for pessimistic locking
305     */
306    const LOCK_PESSIMISTIC      = 1;
307
308    /**
309     * EXPORT CONSTANTS
310     */
311
312    /**
313     * EXPORT_NONE
314     */
315    const EXPORT_NONE               = 0;
316
317    /**
318     * EXPORT_TABLES
319     */
320    const EXPORT_TABLES             = 1;
321
322    /**
323     * EXPORT_CONSTRAINTS
324     */
325    const EXPORT_CONSTRAINTS        = 2;
326
327    /**
328     * EXPORT_PLUGINS
329     */
330    const EXPORT_PLUGINS            = 4;
331
332    /**
333     * EXPORT_ALL
334     */
335    const EXPORT_ALL                = 7;
336
337    /**
338     * HYDRATION CONSTANTS
339     */
340
341    /**
342     * HYDRATE_RECORD
343     */
344    const HYDRATE_RECORD            = 2;
345
346    /**
347     * HYDRATE_ARRAY
348     */
349    const HYDRATE_ARRAY             = 3;
350
351    /**
352     * HYDRATE_NONE
353     */
354    const HYDRATE_NONE              = 4;
355
356    /**
357     * HYDRATE_SCALAR
358     */
359    const HYDRATE_SCALAR            = 5;
360
361    /**
362     * HYDRATE_SINGLE_SCALAR
363     */
364    const HYDRATE_SINGLE_SCALAR     = 6;
365
366    /**
367     * HYDRATE_ON_DEMAND
368     */
369    const HYDRATE_ON_DEMAND         = 7;
370
371    /**
372     * HYDRATE_ARRAY_HIERARCHY
373     */
374    const HYDRATE_ARRAY_HIERARCHY   = 8;
375
376    /**
377     * HYDRATE_RECORD_HIERARCHY
378     */
379    const HYDRATE_RECORD_HIERARCHY  = 9;
380
381    /**
382     * HYDRATE_ARRAY_SHALLOW
383     */
384    const HYDRATE_ARRAY_SHALLOW     = 10;
385
386    /**
387     * VALIDATION CONSTANTS
388     */
389    const VALIDATE_NONE             = 0;
390
391    /**
392     * VALIDATE_LENGTHS
393     */
394    const VALIDATE_LENGTHS          = 1;
395
396    /**
397     * VALIDATE_TYPES
398     */
399    const VALIDATE_TYPES            = 2;
400
401    /**
402     * VALIDATE_CONSTRAINTS
403     */
404    const VALIDATE_CONSTRAINTS      = 4;
405
406    /**
407     * VALIDATE_ALL
408     */
409    const VALIDATE_ALL              = 7;
410
411    /**
412     * VALIDATE_USER
413     */
414    const VALIDATE_USER             = 8;
415
416    /**
417     * IDENTIFIER_AUTOINC
418     *
419     * constant for auto_increment identifier
420     */
421    const IDENTIFIER_AUTOINC        = 1;
422
423    /**
424     * IDENTIFIER_SEQUENCE
425     *
426     * constant for sequence identifier
427     */
428    const IDENTIFIER_SEQUENCE       = 2;
429
430    /**
431     * IDENTIFIER_NATURAL
432     *
433     * constant for normal identifier
434     */
435    const IDENTIFIER_NATURAL        = 3;
436
437    /**
438     * IDENTIFIER_COMPOSITE
439     *
440     * constant for composite identifier
441     */
442    const IDENTIFIER_COMPOSITE      = 4;
443
444    /**
445     * MODEL_LOADING_AGGRESSIVE
446     *
447     * Constant for agressive model loading
448     * Will require_once() all found model files
449     */
450    const MODEL_LOADING_AGGRESSIVE   = 1;
451
452    /**
453     * MODEL_LOADING_CONSERVATIVE
454     *
455     * Constant for conservative model loading
456     * Will not require_once() found model files inititally instead it will build an array
457     * and reference it in autoload() when a class is needed it will require_once() it
458     */
459    const MODEL_LOADING_CONSERVATIVE = 2;
460
461    /**
462     * MODEL_LOADING_PEAR
463     *
464     * Constant for pear model loading
465     * Will simply store the path passed to Doctrine_Core::loadModels()
466     * and Doctrine_Core::autoload() will check there
467     */
468    const MODEL_LOADING_PEAR = 3;
469
470    /**
471     * Path to Doctrine root
472     *
473     * @var string $path            doctrine root directory
474     */
475    private static $_path;
476
477    /**
478     * Path to the Doctrine extensions directory
479     *
480     * @var string $extensionsPath
481     */
482    private static $_extensionsPath;
483
484    /**
485     * Debug bool true/false option
486     *
487     * @var boolean $_debug
488     */
489    private static $_debug = false;
490
491    /**
492     * Array of all the loaded models and the path to each one for autoloading
493     *
494     * @var array
495     */
496    private static $_loadedModelFiles = array();
497
498    /**
499     * Array of all the loaded validators
500     *
501     * @var array
502     */
503    private static $_validators = array();
504
505    /**
506     * Path to the models directory
507     *
508     * @var string
509     */
510    private static $_modelsDirectory;
511
512    /**
513     * __construct
514     *
515     * @return void
516     * @throws Doctrine_Exception
517     */
518    public function __construct()
519    {
520        throw new Doctrine_Exception('Doctrine is static class. No instances can be created.');
521    }
522
523    /**
524     * Returns an array of all the loaded models and the path where each of them exists
525     *
526     * @return array
527     */
528    public static function getLoadedModelFiles()
529    {
530        return self::$_loadedModelFiles;
531    }
532
533    /**
534     * Turn on/off the debugging setting
535     *
536     * @param string $bool
537     * @return void
538     */
539    public static function debug($bool = null)
540    {
541        if ($bool !== null) {
542            self::$_debug = (bool) $bool;
543        }
544
545        return self::$_debug;
546    }
547
548    /**
549     * Set the path to your core Doctrine libraries
550     *
551     * @param string $path The path to your Doctrine libraries
552     * @return void
553     */
554    public static function setPath($path)
555    {
556        self::$_path = $path;
557    }
558
559    /**
560     * Get the root path to Doctrine
561     *
562     * @return string
563     */
564    public static function getPath()
565    {
566        if ( ! self::$_path) {
567            self::$_path = realpath(dirname(__FILE__) . '/..');
568        }
569
570        return self::$_path;
571    }
572
573    /**
574     * Set the path to autoload extension classes from
575     *
576     * @param string $extensionsPath
577     * @return void
578     */
579    public static function setExtensionsPath($extensionsPath)
580    {
581        self::$_extensionsPath = $extensionsPath;
582    }
583
584    /**
585     * Get the path to load extension classes from
586     *
587     * @return string $extensionsPath
588     */
589    public static function getExtensionsPath()
590    {
591        return self::$_extensionsPath;
592    }
593
594    /**
595     * Load an individual model name and path in to the model loading registry
596     *
597     * @return null
598     */
599    public static function loadModel($className, $path = null)
600    {
601        self::$_loadedModelFiles[$className] = $path;
602    }
603
604    /**
605     * Set the directory where your models are located for PEAR style
606     * naming convention autoloading.
607     *
608     * @param string $directory
609     * @return void
610     */
611    public static function setModelsDirectory($directory)
612    {
613        self::$_modelsDirectory = $directory;
614    }
615
616    /**
617     * Get the directory where your models are located for PEAR style naming
618     * convention autoloading
619     *
620     * @return void
621     * @author Jonathan Wage
622     */
623    public static function getModelsDirectory()
624    {
625        return self::$_modelsDirectory;
626    }
627
628    /**
629     * Recursively load all models from a directory or array of directories
630     *
631     * @param  string   $directory      Path to directory of models or array of directory paths
632     * @param  integer  $modelLoading   Pass value of Doctrine_Core::ATTR_MODEL_LOADING to force a certain style of model loading
633     *                                  Allowed Doctrine_Core::MODEL_LOADING_AGGRESSIVE(default) or Doctrine_Core::MODEL_LOADING_CONSERVATIVE
634     * @param  string  $classPrefix     The class prefix of the models to load. This is useful if the class name and file name are not the same
635     */
636    public static function loadModels($directory, $modelLoading = null, $classPrefix = null)
637    {
638        $manager = Doctrine_Manager::getInstance();
639
640        $modelLoading = $modelLoading === null ? $manager->getAttribute(Doctrine_Core::ATTR_MODEL_LOADING) : $modelLoading;
641        $classPrefix = $classPrefix === null ? $manager->getAttribute(Doctrine_Core::ATTR_MODEL_CLASS_PREFIX) : $classPrefix;
642
643        $loadedModels = array();
644
645        if ($directory !== null) {
646            foreach ((array) $directory as $dir) {
647                $dir = rtrim($dir, '/');
648                if ( ! is_dir($dir)) {
649                    throw new Doctrine_Exception('You must pass a valid path to a directory containing Doctrine models');
650                }
651
652                $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir),
653                                                        RecursiveIteratorIterator::LEAVES_ONLY);
654
655                foreach ($it as $file) {
656                    $e = explode('.', $file->getFileName());
657
658                    if (end($e) === 'php' && strpos($file->getFileName(), '.inc') === false) {
659                        if ($modelLoading == Doctrine_Core::MODEL_LOADING_PEAR) {
660                            $className = str_replace($dir . DIRECTORY_SEPARATOR, null, $file->getPathName());
661                            $className = str_replace(DIRECTORY_SEPARATOR, '_', $className);
662                            $className = substr($className, 0, strpos($className, '.'));
663                        } else {
664                            $className = $e[0];
665                        }
666
667                        if ($classPrefix && $classPrefix != substr($className, 0, strlen($classPrefix))) {
668                            $className = $classPrefix . $className;
669                        }
670
671                        if ( ! class_exists($className, false)) {
672                            if ($modelLoading == Doctrine_Core::MODEL_LOADING_CONSERVATIVE || $modelLoading == Doctrine_Core::MODEL_LOADING_PEAR) {
673                                self::loadModel($className, $file->getPathName());
674
675                                $loadedModels[$className] = $className;
676                            } else {
677                                $declaredBefore = get_declared_classes();
678                                require_once($file->getPathName());
679                                $declaredAfter = get_declared_classes();
680
681                                if (defined('HHVM_VERSION')) {
682                                    // on HHVM get_declared_classes() returns in a different order, array_diff() works, so we have to use it
683                                    $foundClasses = array_diff($declaredAfter, $declaredBefore);
684                                } else {
685                                    // Using array_slice because array_diff is broken is some PHP versions
686                                    // https://bugs.php.net/bug.php?id=47643
687                                    $foundClasses = array_slice($declaredAfter, count($declaredBefore));
688                                }
689
690                                if ($foundClasses) {
691                                    foreach ($foundClasses as $className) {
692                                        if (self::isValidModelClass($className)) {
693                                            $loadedModels[$className] = $className;
694
695                                            self::loadModel($className, $file->getPathName());
696                                        }
697                                    }
698                                }
699
700                                $previouslyLoaded = array_keys(self::$_loadedModelFiles, $file->getPathName());
701
702                                if ( ! empty($previouslyLoaded)) {
703                                    $previouslyLoaded = array_combine(array_values($previouslyLoaded), array_values($previouslyLoaded));
704                                    $loadedModels = array_merge($loadedModels, $previouslyLoaded);
705                                }
706                            }
707                        } else if (self::isValidModelClass($className)) {
708                            $loadedModels[$className] = $className;
709                        }
710                    }
711                }
712            }
713        }
714
715        asort($loadedModels);
716
717        return $loadedModels;
718    }
719
720    /**
721     * Get all the loaded models, you can provide an array of classes or it will use get_declared_classes()
722     *
723     * Will filter through an array of classes and return the Doctrine_Records out of them.
724     * If you do not specify $classes it will return all of the currently loaded Doctrine_Records
725     *
726     * @param classes  Array of classes to filter through, otherwise uses get_declared_classes()
727     * @return array   $loadedModels
728     */
729    public static function getLoadedModels($classes = null)
730    {
731        if ($classes === null) {
732            $classes = get_declared_classes();
733            $classes = array_merge($classes, array_keys(self::$_loadedModelFiles));
734        }
735
736        return self::filterInvalidModels($classes);
737    }
738
739    /**
740     * Initialize all models so everything is present and loaded in to memory
741     * This will also inheritently initialize any model behaviors and add
742     * the models generated by Doctrine generators and add them to the $models
743     * array
744     *
745     * @param string $models
746     * @return array $models
747     */
748    public static function initializeModels($models)
749    {
750        $models = self::filterInvalidModels($models);
751
752        foreach ($models as $model) {
753            $declaredBefore = get_declared_classes();
754            Doctrine_Core::getTable($model);
755
756            $declaredAfter = get_declared_classes();
757            if (defined('HHVM_VERSION')) {
758                // on HHVM get_declared_classes() returns in a different order, array_diff() works, so we have to use it
759                $foundClasses = array_diff($declaredAfter, $declaredBefore);
760            } else {
761                // Using array_slice because array_diff is broken is some PHP versions
762                // https://bugs.php.net/bug.php?id=47643
763                $foundClasses = array_slice($declaredAfter, count($declaredBefore) - 1);
764            }
765            foreach ($foundClasses as $class) {
766                if (self::isValidModelClass($class)) {
767                    $models[] = $class;
768                }
769            }
770        }
771
772        $models = self::filterInvalidModels($models);
773
774        return $models;
775    }
776
777    /**
778     * Filter through an array of classes and return all the classes that are valid models.
779     * This will inflect the class, causing it to be loaded in to memory.
780     *
781     * @param classes  Array of classes to filter through, otherwise uses get_declared_classes()
782     * @return array   $loadedModels
783     */
784    public static function filterInvalidModels($classes)
785    {
786        $validModels = array();
787
788        foreach ((array) $classes as $name) {
789            if (self::isValidModelClass($name) && ! in_array($name, $validModels)) {
790                $validModels[] = $name;
791            }
792        }
793
794        return $validModels;
795    }
796
797    /**
798     * Checks if what is passed is a valid Doctrine_Record
799     * Will load class in to memory in order to inflect it and find out information about the class
800     *
801     * @param   mixed   $class Can be a string named after the class, an instance of the class, or an instance of the class reflected
802     * @return  boolean
803     */
804    public static function isValidModelClass($class)
805    {
806        if ($class instanceof Doctrine_Record) {
807            $class = get_class($class);
808        }
809
810        if (is_string($class) && class_exists($class)) {
811            $class = new ReflectionClass($class);
812        }
813
814        if ($class instanceof ReflectionClass) {
815            // Skip the following classes
816            // - abstract classes
817            // - not a subclass of Doctrine_Record
818            if ( ! $class->isAbstract() && $class->isSubclassOf('Doctrine_Record')) {
819
820                return true;
821            }
822        }
823
824        return false;
825    }
826
827    /**
828     * Get the connection object for a table by the actual table name
829     * FIXME: I think this method is flawed because a individual connections could have the same table name
830     *
831     * @param string $tableName
832     * @return Doctrine_Connection
833     */
834    public static function getConnectionByTableName($tableName)
835    {
836        $loadedModels = self::getLoadedModels();
837
838        foreach ($loadedModels as $name) {
839            $table = Doctrine_Core::getTable($name);
840
841            if ($table->getTableName() == $tableName) {
842               return $table->getConnection();
843            }
844        }
845
846        return Doctrine_Manager::connection();
847    }
848
849    /**
850     * Method for importing existing schema to Doctrine_Record classes
851     *
852     * @param string $directory Directory to write your models to
853     * @param array $connections Array of connection names to generate models for
854     * @param array $options Array of options
855     * @return boolean
856     * @throws Exception
857     */
858    public static function generateModelsFromDb($directory, array $connections = array(), array $options = array())
859    {
860        return Doctrine_Manager::connection()->import->importSchema($directory, $connections, $options);
861    }
862
863    /**
864     * Generates models from database to temporary location then uses those models to generate a yaml schema file.
865     * This should probably be fixed. We should write something to generate a yaml schema file directly from the database.
866     *
867     * @param string $yamlPath Path to write oyur yaml schema file to
868     * @param array $connections Array of connection names to generate yaml for
869     * @param array  $options Array of options
870     * @return void
871     */
872    public static function generateYamlFromDb($yamlPath, array $connections = array(), array $options = array())
873    {
874        $directory = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'tmp_doctrine_models';
875
876        $options['generateBaseClasses'] = isset($options['generateBaseClasses']) ? $options['generateBaseClasses']:false;
877        $result = Doctrine_Core::generateModelsFromDb($directory, $connections, $options);
878
879        if ( empty($result) && ! is_dir($directory)) {
880            throw new Doctrine_Exception('No models generated from your databases');
881        }
882
883        $export = new Doctrine_Export_Schema();
884
885        $result = $export->exportSchema($yamlPath, 'yml', $directory, array(), Doctrine_Core::MODEL_LOADING_AGGRESSIVE);
886
887        Doctrine_Lib::removeDirectories($directory);
888
889        return $result;
890    }
891
892    /**
893     * Generate a yaml schema file from an existing directory of models
894     *
895     * @param string $yamlPath Path to your yaml schema files
896     * @param string $directory Directory to generate your models in
897     * @param array  $options Array of options to pass to the schema importer
898     * @return void
899     */
900    public static function generateModelsFromYaml($yamlPath, $directory, $options = array())
901    {
902        $import = new Doctrine_Import_Schema();
903        $import->setOptions($options);
904
905        return $import->importSchema($yamlPath, 'yml', $directory);
906    }
907
908    /**
909     * Creates database tables for the models in the specified directory
910     *
911     * @param string $directory Directory containing your models
912     * @return void
913     */
914    public static function createTablesFromModels($directory = null)
915    {
916        return Doctrine_Manager::connection()->export->exportSchema($directory);
917    }
918
919    /**
920     * Creates database tables for the models in the supplied array
921     *
922     * @param array $array An array of models to be exported
923     * @return void
924     */
925    public static function createTablesFromArray($array)
926    {
927        return Doctrine_Manager::connection()->export->exportClasses($array);
928    }
929
930    /**
931     * Generate a array of sql for the passed array of models
932     *
933     * @param  array $array
934     * @return array $sql
935     */
936    public static function generateSqlFromArray($array)
937    {
938        return Doctrine_Manager::connection()->export->exportClassesSql($array);
939    }
940
941    /**
942     * Generate a sql string to create the tables from all loaded models
943     * or the models found in the passed directory.
944     *
945     * @param  string $directory
946     * @return string $build  String of sql queries. One query per line
947     */
948    public static function generateSqlFromModels($directory = null)
949    {
950        $conn = Doctrine_Manager::connection();
951        $sql = $conn->export->exportSql($directory);
952
953        $build = '';
954        foreach ($sql as $query) {
955            $build .= $query.$conn->sql_file_delimiter;
956        }
957
958        return $build;
959    }
960
961    /**
962     * Generate yaml schema file for the models in the specified directory
963     *
964     * @param string $yamlPath Path to your yaml schema files
965     * @param string $directory Directory to generate your models in
966     * @return void
967     */
968    public static function generateYamlFromModels($yamlPath, $directory)
969    {
970        $export = new Doctrine_Export_Schema();
971
972        return $export->exportSchema($yamlPath, 'yml', $directory);
973    }
974
975    /**
976     * Creates databases for connections
977     *
978     * @param string $specifiedConnections Array of connections you wish to create the database for
979     * @return void
980     */
981    public static function createDatabases($specifiedConnections = array())
982    {
983        return Doctrine_Manager::getInstance()->createDatabases($specifiedConnections);
984    }
985
986    /**
987     * Drops databases for connections
988     *
989     * @param string $specifiedConnections Array of connections you wish to drop the database for
990     * @return void
991     */
992    public static function dropDatabases($specifiedConnections = array())
993    {
994        return Doctrine_Manager::getInstance()->dropDatabases($specifiedConnections);
995    }
996
997    /**
998     * Dump data to a yaml fixtures file
999     *
1000     * @param string $yamlPath Path to write the yaml data fixtures to
1001     * @param string $individualFiles Whether or not to dump data to individual fixtures files
1002     * @return void
1003     */
1004    public static function dumpData($yamlPath, $individualFiles = false)
1005    {
1006        $data = new Doctrine_Data();
1007
1008        return $data->exportData($yamlPath, 'yml', array(), $individualFiles);
1009    }
1010
1011    /**
1012     * Load data from a yaml fixtures file.
1013     * The output of dumpData can be fed to loadData
1014     *
1015     * @param string $yamlPath Path to your yaml data fixtures
1016     * @param string $append Whether or not to append the data
1017     * @return void
1018     */
1019    public static function loadData($yamlPath, $append = false, $charset = 'UTF-8')
1020    {
1021        $data = new Doctrine_Data();
1022
1023        return $data->importData($yamlPath, 'yml', array(), $append, $charset);
1024    }
1025
1026    /**
1027     * Migrate database to specified $to version. Migrates from current to latest if you do not specify.
1028     *
1029     * @param string $migrationsPath Path to migrations directory which contains your migration classes
1030     * @param string $to Version you wish to migrate to.
1031     * @return bool true
1032     * @throws new Doctrine_Migration_Exception
1033     */
1034    public static function migrate($migrationsPath, $to = null)
1035    {
1036        $migration = new Doctrine_Migration($migrationsPath);
1037
1038        return $migration->migrate($to);
1039    }
1040
1041    /**
1042     * Generate new migration class skeleton
1043     *
1044     * @param string $className Name of the Migration class to generate
1045     * @param string $migrationsPath Path to directory which contains your migration classes
1046     */
1047    public static function generateMigrationClass($className, $migrationsPath)
1048    {
1049        $builder = new Doctrine_Migration_Builder($migrationsPath);
1050
1051        return $builder->generateMigrationClass($className);
1052    }
1053
1054    /**
1055     * Generate a set of migration classes from an existing database
1056     *
1057     * @param string $migrationsPath
1058     * @return void
1059     * @throws new Doctrine_Migration_Exception
1060     */
1061    public static function generateMigrationsFromDb($migrationsPath)
1062    {
1063        $builder = new Doctrine_Migration_Builder($migrationsPath);
1064
1065        return $builder->generateMigrationsFromDb();
1066    }
1067
1068    /**
1069     * Generate a set of migration classes from an existing set of models
1070     *
1071     * @param string  $migrationsPath Path to your Doctrine migration classes
1072     * @param string  $modelsPath     Path to your Doctrine model classes
1073     * @param integer $modelLoading   Style of model loading to use for loading the models in order to generate migrations
1074     * @return void
1075     */
1076    public static function generateMigrationsFromModels($migrationsPath, $modelsPath = null, $modelLoading = null)
1077    {
1078        $builder = new Doctrine_Migration_Builder($migrationsPath);
1079
1080        return $builder->generateMigrationsFromModels($modelsPath, $modelLoading);
1081    }
1082
1083    /**
1084     * Generate a set of migration classes by generating differences between two sets
1085     * of schema information
1086     *
1087     * @param  string $migrationsPath   Path to your Doctrine migration classes
1088     * @param  string $from             From schema information
1089     * @param  string $to               To schema information
1090     * @return array $changes
1091     */
1092    public static function generateMigrationsFromDiff($migrationsPath, $from, $to)
1093    {
1094        $diff = new Doctrine_Migration_Diff($from, $to, $migrationsPath);
1095
1096        return $diff->generateMigrationClasses();
1097    }
1098
1099    /**
1100     * Get the Doctrine_Table object for the passed model
1101     *
1102     * @param string $componentName
1103     * @return Doctrine_Table
1104     */
1105    public static function getTable($componentName)
1106    {
1107        return Doctrine_Manager::getInstance()->getConnectionForComponent($componentName)->getTable($componentName);
1108    }
1109
1110    /**
1111     * Method for making a single file of most used doctrine runtime components
1112     * including the compiled file instead of multiple files (in worst
1113     * cases dozens of files) can improve performance by an order of magnitude
1114     *
1115     * @param string $target
1116     * @param array  $includedDrivers
1117     * @throws Doctrine_Exception
1118     * @return void
1119     */
1120    public static function compile($target = null, $includedDrivers = array())
1121    {
1122        return Doctrine_Compiler::compile($target, $includedDrivers);
1123    }
1124
1125    /**
1126     * simple autoload function
1127     * returns true if the class was loaded, otherwise false
1128     *
1129     * @param string $className
1130     * @return boolean
1131     */
1132    public static function autoload($className)
1133    {
1134        if (strpos($className, 'sfYaml') === 0) {
1135            require dirname(__FILE__) . '/Parser/sfYaml/' . $className . '.php';
1136
1137            return true;
1138        }
1139
1140        if (0 !== stripos($className, 'Doctrine') || class_exists($className, false) || interface_exists($className, false)) {
1141            return false;
1142        }
1143
1144        $class = self::getPath() . DIRECTORY_SEPARATOR . str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
1145
1146        if (file_exists($class)) {
1147            require $class;
1148
1149            return true;
1150        }
1151
1152        return false;
1153    }
1154
1155    public static function modelsAutoload($className)
1156    {
1157        if (class_exists($className, false) || interface_exists($className, false)) {
1158            return false;
1159        }
1160
1161        if ( ! self::$_modelsDirectory) {
1162            $loadedModels = self::$_loadedModelFiles;
1163
1164            if (isset($loadedModels[$className]) && file_exists($loadedModels[$className])) {
1165                require $loadedModels[$className];
1166
1167                return true;
1168            }
1169        } else {
1170            $class = self::$_modelsDirectory . DIRECTORY_SEPARATOR . str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
1171
1172            if (file_exists($class)) {
1173                require $class;
1174
1175                return true;
1176            }
1177        }
1178
1179        return false;
1180    }
1181
1182    /**
1183     * Load classes from the Doctrine extensions directory/path
1184     *
1185     * @param string $className
1186     * @return boolean
1187     */
1188    public static function extensionsAutoload($className)
1189    {
1190        if (class_exists($className, false) || interface_exists($className, false)) {
1191            return false;
1192        }
1193
1194        $extensions = Doctrine_Manager::getInstance()
1195            ->getExtensions();
1196
1197        foreach ($extensions as $name => $path) {
1198            $class = $path . DIRECTORY_SEPARATOR . str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
1199
1200            if (file_exists($class)) {
1201                require $class;
1202
1203                return true;
1204            }
1205        }
1206
1207        return false;
1208    }
1209
1210    /**
1211     * dumps a given variable
1212     *
1213     * @param mixed $var        a variable of any type
1214     * @param boolean $output   whether to output the content
1215     * @param string $indent    indention string
1216     * @return void|string
1217     */
1218    public static function dump($var, $output = true, $indent = "")
1219    {
1220        $ret = array();
1221        switch (gettype($var)) {
1222            case 'array':
1223                $ret[] = 'Array(';
1224                $indent .= "    ";
1225                foreach ($var as $k => $v) {
1226
1227                    $ret[] = $indent . $k . ' : ' . self::dump($v, false, $indent);
1228                }
1229                $indent = substr($indent,0, -4);
1230                $ret[] = $indent . ")";
1231                break;
1232            case 'object':
1233                $ret[] = 'Object(' . get_class($var) . ')';
1234                break;
1235            default:
1236                $ret[] = var_export($var, true);
1237        }
1238
1239        if ($output) {
1240            print implode("\n", $ret);
1241        }
1242
1243        return implode("\n", $ret);
1244    }
1245}
1246