root/trunk/lib/casco/class.casco.php

Revision 2144, 31.2 kB (checked in by Peter, 3 years ago)

Get rid of the last two globals in the config file.
Get rid of silly getters (they weren't even working!); just access the static vars directly from casco class
We should really get rid of the global debugging switch; either you want to debug the finder, or the cache, or the sql, or.... but not *all of it at once*.

Line 
1 <?php
2
3 /**
4  * Casco: the factory class
5  *
6  * This class is responsible for bootstrapping the framework, managing
7  * core strategies and registration/lookup of modules. It also has
8  * control over the connection pool, instantiating new connection
9  * objects on demand.
10  *
11  * Furthermore it is the place where all PHP language extensions (like
12  * dynamic variables/context pattern and dynamic class
13  * replacement/subclassing) are collected.
14  *
15  * @todo This is truly "the class that knew too much". We should
16  *    separate some responsibilities into other classes.
17  *
18  * @package casco
19  */
20 class casco extends module
21 {
22         private static $MODULES     = array();
23         private static $SUBCLASSES  = array();
24         private static $COMPONENTS  = array();
25         private static $CONNECTIONS = array();
26         private static $CONTEXT     = array();
27         private static $CONTEXT_NESTING = 0;
28        
29         static $finder;
30         static $translator;
31         static $cache;
32         static $url;
33         static $templator;
34         static $composer;
35         static $automator;
36         static $bouncer;
37
38         /** The document root (on disk) of the CASCO application */
39         static $docroot;
40         /** The HTTP root (an URL) of the CASCO application */
41         static $httproot;
42         /**
43          * Use {@link http://wush.net/trac/casco/wiki/Metasync
44          * metasync}?  This is the global setting, which may be
45          * overridden for individual connections.
46          *
47          * @see casco::register_connection()
48          */
49         static $metasync = true;
50
51         /**
52          * Enable caching?
53          *
54          * @todo Perhaps make this a static var of the cache class?
55          */
56         static $caching = true;
57        
58         /**
59          * Show debugging info?
60          *
61          * @todo Remove this; it's silly to have one giant switch
62          * that affects *all* components that might have something
63          * to say for debugging.  Typically you're only interested
64          * in debugging info of one particular component/module.
65          */
66         static $debug = false;
67        
68         ///////////////////////////////////////////////////////////
69         // initialization
70         ///////////////////////////////////////////////////////////
71
72         /**
73          * Initialize the CASCO framework.
74          *
75          * This causes all the bootstrapping to be performed that is
76          * required for a fully working Casco.
77          *
78          * Steps that are undertaken include the following:
79          * 1. All core strategy classes are instantiated in their
80          *     vanilla implementations.
81          * 2. For each registered module, the {@link module::subclassing()}
82          *     function is invoked.
83          * 3. Strategies are re-instantiated with (possibly) new implementations
84          *     as determined by the subclassing() functions.
85          * 4. For each registered module, the {@link module::init()}
86          *     function is invoked.
87          * 5. Finally, shutdown functions are registered.
88          */
89         static function init()
90         {
91                         // Start with vanilla classes (with one exception)
92                 self::$finder     = new finder();
93                 self::$cache      = new cache(self::$caching);
94                 self::$finder     = new finder_cached();        //it is better for performance to use the finder_cached as soon as possible. That's the reason why it is in core.
95                 self::$translator = new translator();
96                 self::$url        = new url();
97                 self::$templator  = new templator();
98                 self::$composer   = new composer();
99                 self::$automator  = new automator();
100                 self::$bouncer    = new bouncer();
101
102                 foreach (self::get_registered_modules() as $mod)
103                 {
104                         $mod_class = self::get_module_class($mod['module']);
105                         if ($mod_class) $mod_class::subclassing();
106                 }
107                
108                         //initialize the cache
109                 self::$cache = lib::create('cache', array(self::$caching));
110
111                         //replace the vanilla finder with the one we REALLY want according to the configuration
112                 self::$finder = lib::create('finder', array(false));
113                
114                         //initialize the translator class
115                 self::$translator = lib::create('translator');
116                
117                         //initialize the url class
118                 self::$url = lib::create('url');
119                 $_GET = self::$url->decode($_GET);
120                
121                         //initialize the templator (template engine) class
122                 self::$templator = lib::create('templator');
123
124                         //initialize the composer (composition engine) class
125                 self::$composer = lib::create('composer', array(self::composer()->get_overrides()));
126                
127                 self::$automator = lib::create('automator');
128
129                 self::$bouncer = lib::create('bouncer');
130                
131                 foreach (self::get_registered_modules() as $mod)
132                 {
133                         $mod_class = self::get_module_class($mod['module']);
134                         if ($mod_class) $mod_class::init();
135                 }
136                 register_shutdown_function(array('controller', 'destroy_expired_form_cookies'));
137         }
138
139        
140         ///////////////////////////////////////////////////////////
141         // db connections
142         ///////////////////////////////////////////////////////////
143
144         /**
145          * Register a database connection in the CASCO connection pool.
146          *
147          * A connection configuration is an associative array with options
148          * for the connection.  The following keys are mandatory:
149          *
150          * - <code>$config['class']</code>: The class that will be
151          *    instantiated when the connection is requested with
152          *    {@link casco::get_connection()}
153          *
154          * The following keys are optional:
155          * - <code>$config['name']</code>: The name of the connection,
156          *    used with {@link casco::get_connection()} to get the connection.
157          * - <code>$config['instance']</code>: A pre-instantiated
158          *    object to use for managing the connection.  If it does
159          *    not exist, the class is automatically instantiated when
160          *    the connection is first requested.
161          *
162          * NOTE: Do not use this function directly!
163          * For user code, it is advisable to register connections
164          * through the module class of the module that manages the
165          * connection. This will allow the module to set some useful
166          * defaults before registering the connection with the CASCO
167          * connection pool.
168          *
169          * @param array $config The configuration for this connection.
170          */
171         static function register_connection($config)
172         {
173                 $config = self::get_config_defaults($config);
174                 self::$CONNECTIONS[$config['name']] = $config;
175         }
176
177         /**
178          * Set the defaults for a connection configuration.
179          *
180          * @param array $config The input config.
181          * @return array The config with optionally added keys.
182          */
183         protected static function get_config_defaults($config)
184         {
185                 if(!isset($config['name']))
186                 {
187                         $config['name'] = 'default';
188                 }
189                 return $config;
190         }
191
192         /**
193          * Retrieve a connection from the connection pool by name.
194          *
195          * If the connection was not yet established, the connection
196          * managing class is instantiated (establishing a new connection)
197          * and this object is returned. Otherwise, a pre-existing
198          * connection manager is recycled.
199          *
200          * @param string $name (optional, defaults to 'default') The name of
201          *                     the connection to retrieve.
202          * @return mixed The connection object
203          */
204         static function get_connection($name = 'default')
205         {
206                 if (!isset(self::$CONNECTIONS[$name]))
207                 {
208                         throw new argument_exception('Unknown connection name: "'.$name.'"');
209                 }
210                 else if (!isset(self::$CONNECTIONS[$name]['instance']))
211                 {
212                         $classname      = self::$CONNECTIONS[$name]['class'];
213                         $config         = self::$CONNECTIONS[$name];
214                         self::$CONNECTIONS[$name]['instance'] = lib::create($classname, array($config));
215                 }
216                 return self::$CONNECTIONS[$name]['instance'];
217         }
218        
219         ///////////////////////////////////////////////////////////
220         // module stuff
221         ///////////////////////////////////////////////////////////
222
223         /**
224          * Get an instance for all models from all registered modules.
225          *
226          * @todo Why does this return instances? Why not just names?
227          *       If anyone needs an instance, he can just instantiate
228          *       these classes himself.
229          *
230          * @return array A list of instances of all models.
231          */
232         public static function get_models($module = '')
233         {
234                 $models = array();
235                 if($module == '')
236                 {
237                         foreach (self::get_registered_modules() as $module)
238                         {
239                                 $models = self::append_models_from_module($models, $module);
240                         }
241                 }
242                 else
243                 {
244                         $models = self::append_models_from_module($models, $module);
245                 }
246                 return $models;
247         }
248
249         /**
250          * Find all models in the given module.
251          *
252          * @param array $models  The models found up till now.
253          * @param string $module The name of the module to look in.
254          * @return array The models found up till now, including this module.
255          */
256         static private function append_models_from_module($models, $module)
257         {
258                 $filepath = self::get_local_path($module) . '/model/';
259                 if(is_dir($filepath))
260                 {
261                         $d = dir($filepath);
262                         while (false !== ($entry = $d->read()))
263                         {
264                                 if(is_dir($filepath.$entry) && $entry != '.' && $entry != '..' && $entry != '.svn')
265                                 {
266                                                 //models can have their own subdirectories, in which exists class.foobar.php
267                                         $subd = dir($filepath.$entry);
268                                         while (false !== ($subentry = $subd->read()))
269                                         {
270                                                 $model = self::get_model_by_path_name($filepath.$entry.'/', $subentry);
271                                                 if($model !== false)
272                                                 {
273                                                         $models[] = $model;     
274                                                 }
275                                         }
276                                         $subd->close();                 
277                                 }
278                                 else
279                                 {
280                                                 //models can be defined in files such as class.foobar.php inside the models directory
281                                         $model = self::get_model_by_path_name($filepath, $entry);
282                                         if($model !== false)
283                                         {
284                                                 $models[] = $model;     
285                                         }
286                                 }
287                         }
288                         $d->close();                   
289                 }
290                 return $models;
291         }
292
293         /**
294          * Given a pathname, determine the model, load it and instantiate it.
295          *
296          * @param string $filepath The path to the model file.
297          * @param string $name     The name of the model file.
298          * @return An instance of that model.
299          */
300         static private function get_model_by_path_name($filepath, $name)
301         {
302                 if(is_file($filepath.$name))
303                 {
304                         $modelname_array = explode('.', $name);
305                         if($modelname_array[0] == 'class')
306                         {
307                                 self::require_model('model');
308                                 $modelbase = new ReflectionClass('model');
309                                
310                                 require_once($filepath.$name);
311                                 $modelname = $modelname_array[1];
312                                 $modelreflection = new ReflectionClass($modelname);
313                                 if($modelreflection->isSubclassOf($modelbase))
314                                 {
315                                         return new $modelname();                       
316                                 }
317                         }
318                 }
319                 return false;
320         }
321
322         /**
323          * Register a module.
324          *
325          * @param string $module The name of the module to register
326          * @param string $localpath The path to the module directory
327          *     on disk.  The name will be appended to this, so
328          *     $localpath.$module is the directory in which will be looked
329          *     for classes and assets etc.  Defaults to  casco::$docroot.'/lib'
330          * @param string $remotepath The path to the module directory
331          *     over HTTP. Same semantics as $localpath. Defaults to
332          *     casco::$httproot.'/lib'.
333          * @param string $dir The directory name of the module, if this
334          *     differs from the name of the module. This is the name that
335          *     will be appended to $localpath and $remotepath.
336          */
337         static function register_module($module, $localpath = '', $remotepath = '', $dir = '')
338         {
339                 $config = array();
340                 $config['module'] = $module;
341                
342                         //if a containing path is specified, store it, else store defaults
343                 if($localpath != '')
344                 {
345                         $config['local_path'] = $localpath;
346                         $config['remote_path'] = $remotepath;
347                 }
348                 else
349                 {
350                         $config['local_path'] = casco::$docroot.'lib/';
351                         $config['remote_path'] = casco::$httproot.'lib/';
352                 }
353                         //if the module directory is specified store it, else store the default (the module name itself)
354                 if($dir == '')
355                 {
356                         $config['dir'] = $module;
357                 }
358                 else
359                 {
360                         $config['dir'] = $dir;
361                 }
362                 if (!is_dir($config['local_path'].$config['dir']))
363                 {
364                         throw new casco_exception('Could not find module "'.$module.'". Expected it in dir "'.$config['dir'].'" under "'.$config['local_path'].'". Check your config!');
365                 }
366                         //store the module configuration data in the casco modules array
367                 self::$MODULES[$module] = $config;
368                
369                 if (file_exists($config['local_path'].$config['dir'].'/init.php'))
370                 {
371                         require_once($config['local_path'].$config['dir'].'/init.php');
372                 }
373         }
374
375         /**
376          * Register a module only for a test.
377          *
378          * This is useful when a module has some test models or other
379          * classes that it needs to load just for its tests, but not
380          * in regular operation.
381          *
382          * The name of the module is determined by appending the name
383          * of the test to the string 'module_'.
384          *
385          * Example: If you're in the test class file
386          * <code>foo_module/model/my_model/test/class.test_my_model.php</code>
387          * you can call
388          * <code>register_test_module('foo_module', 'model', 'my_model')</code>
389          * and it will load
390          * <code>foo_module/model/my_model/test/module_test_my_model</code>
391          *
392          * Note that it is possible to load a test module from the test for
393          * another class.
394          *
395          * @todo We don't need the parent_module arg because we can
396          *     look up the test class, whose path looks like this:
397          *     <casco_local_path>/<parent_module>/<classtype>/<classname>/test/test_<classname>
398          *     Strip out casco_local_path and take the remaining first
399          *     path component.
400          *
401          * @param string $parent_module The module in which the test
402          *     lives.
403          * @param string $classtype The major CASCO classtype of the
404          *     class you are testing.
405          * @param string $name The name of the class you are testing.
406          */
407         static function register_test_module($parent_module, $classtype, $name)
408         {
409                 $dir = self::get_test_dir($classtype, $name);
410                 $module = 'module_test_'.$name;
411                 return self::register_module($module, casco::get_local_path($parent_module).$dir, casco::get_remote_path($parent_module).$dir);
412         }
413
414         /**
415          * Get the name of the module class for the named module.
416          *
417          * @param string $module The name of the module you want
418          *     to load the class from.
419          *
420          * @return string|false The name of the module class, or false
421          *     if the named module has no module class.
422          *
423          * @see casco::register_module_class()
424          */
425         static function get_module_class($module)
426         {
427                 if (isset(self::$MODULES[$module]['class']))
428                 {
429                         return self::$MODULES[$module]['class'];
430                 }
431                 else
432                 {
433                         return false;
434                 }
435         }
436
437         /**
438          * Register the module class for a given module.
439          *
440          * You must call this function in your module's
441          * <code>init.php</code> if you define a module class there.
442          *
443          * It is advised to name your module classes
444          * <code>mod_foo</code>, if the module name is
445          * <code>foo</code>. In the future, this may be enforced
446          * automatically in code.
447          *
448          * @param string $module The name of the module.
449          * @param string $class  The name of the module class.
450          */
451         static function set_module_class($module, $class)
452         {
453                 self::$MODULES[$module]['class'] = $class;
454         }
455
456         /**
457          * Retrieve a list of all registered modules.
458          *
459          * @return array The names of the registered modules.
460          */
461         static function get_registered_modules()
462         {
463                 return self::$MODULES;
464         }
465
466         /**
467          * Check if the named module has been registered.
468          *
469          * @param string $module The name of the module to check.
470          * @return boolean True if the module was registered, false if not.
471          */
472         static function is_registered($module)
473         {
474                 if(isset(self::$MODULES[$module]))
475                 {
476                         return true;
477                 }
478                 else
479                 {
480                         return false;
481                 }
482         }
483
484         /**
485          * Get the module configuration array for a module.
486          *
487          * The array contains the following entries:
488          * - 'local_path': The local path (on-disk) to the module,
489          *    excluding the module name itself.
490          * - 'remote_path': The remote path (HTTP) to the module,
491          *    excluding the module name itself.
492          * - 'dir': The directory name of the module.
493          *
494          * @param string $module The name of the module.
495          * @return array The configuration.
496          *
497          * @see casco::register_module()
498          */
499         protected static function get_module($module)
500         {
501                 $mod = self::$MODULES[$module];
502                 if (!isset($mod)) throw new argument_exception('Unknown module "'.$module.'"');
503                 return $mod;
504         }
505
506         /**
507          * Get the local (on-disk) path to a module's root directory.
508          *
509          * {@internal If we force all modules to have a module class
510          * (or generate one for them on-the-fly if it doesn't have
511          * one) we can move these functions to the module's class.
512          * the casco class would know less, which is good.}
513          *
514          * @param string $module The name of the module.
515          * @return string The path up to that module's root dir.
516          *
517          * @see casco::get_remote_path()
518          */
519         static function get_local_path($module)
520         {
521                 $mod = self::get_module($module);
522                 return $mod['local_path'].$mod['dir'].'/';
523         }
524
525         /**
526          * Get the remote (HTTP) path to a module's root directory.
527          *
528          * @param string $module The name of the module.
529          * @return string The URL up to that module's root dir.
530          *
531          * @see casco::get_local_path()
532          */
533         static function get_remote_path($module)
534         {
535                 $mod = self::get_module($module);
536                 return $mod['remote_path'].$mod['dir'].'/';
537         }
538        
539         ///////////////////////////////////////////////////////////
540         // Accessors
541         ///////////////////////////////////////////////////////////
542
543         /**
544          * Retrieve the current implementation for CASCO's {@link cache} strategy.
545          *
546          * @return cache The cache object.
547          */
548         static function cache()
549         {
550                 return self::$cache;
551         }
552
553         /**
554          * Retrieve the current implementation for CASCO's {@link finder} strategy.
555          *
556          * @return finder The finder object.
557          */
558         static function finder()
559         {
560                 return self::$finder;
561         }
562
563         /**
564          * Retrieve the current implementation for CASCO's {@link composer} strategy.
565          *
566          * @return composer The composer object.
567          */
568         static function composer()
569         {
570                 return self::$composer;
571         }
572
573         /**
574          * Retrieve the current implementation for CASCO's {@link templator} strategy.
575          *
576          * @return templator The templator object.
577          */
578         static function templator()
579         {
580                 return self::$templator;
581         }
582
583         /**
584          * Retrieve the current implementation for CASCO's {@link automator} strategy.
585          *
586          * @return automator The automator object.
587          */
588         static function automator()
589         {
590                 return self::$automator;
591         }
592
593         /**
594          * Retrieve the current implementation for CASCO's {@link url} strategy.
595          *
596          * @return url The url object.
597          */
598         static function url()
599         {
600                 return self::$url;
601         }
602
603         /**
604          * Retrieve the current implementation for CASCO's {@link translator} strategy.
605          *
606          * @return translator The translator object.
607          */
608         static function translator()
609         {
610                 return self::$translator;
611         }
612        
613         /**
614          * Retrieve the current implementation for CASCO's {@link bouncer} strategy.
615          *
616          * @return bouncer The bouncer object.
617          */
618         static function bouncer()
619         {
620                 return self::$bouncer;
621         }
622        
623         ///////////////////////////////////////////////////////////
624         // global class override (subclass)
625         ///////////////////////////////////////////////////////////
626
627         /**
628          * Define a class override.
629          *
630          * Using CASCO's
631          * {@link http://wush.net/trac/casco/wiki/CookbookSubclassing
632          *  subclassing mechanism}, override a class with another one.
633          *
634          * Simply put, this means that whenever anyone instantiates or
635          * loads a class using {@link model::create()}, {@link lib::create()},
636          * {@link model::load()}, {@link lib::load()},
637          * or any of the other helper functions for {@link casco::req()}
638          * or {@link casco::factory()}, CASCO will load another class
639          * of your choice instead.
640          *
641          * @param $class    The name of the class that will be requested.
642          * @param $subclass The name of the class to load instead.
643          */
644         static function subclass($class, $subclass)
645         {
646                 self::$SUBCLASSES[$class] = $subclass;
647         }
648
649         /**
650          * Get the real name of the class to load.
651          *
652          * Given any class name, return the class that would be loaded
653          * when this class is requested. Usually it will just return
654          * the same name you give as input, but when subclassing is
655          * active (see {@link casco::subclass()}), another class name
656          * might be returned instead.
657          *
658          * @param string $name The name of the originally requested class.
659          * @return string The name of the class that actually will be loaded.
660          */
661         static function get_real_classname($name)
662         {
663                 if(isset(self::$SUBCLASSES[$name]))
664                 {
665                         return self::$SUBCLASSES[$name];
666                 }
667                 else
668                 {
669                         return $name;
670                 }
671         }
672
673         /**
674          * Retrieve an associative array of all classes that have been
675          * overridden using the subclassing mechanism.
676          *
677          * @return array The mapping of class name to subclass name.
678          *
679          * @see casco::subclass()
680          */
681         static function get_subclasses()
682         {
683                 return self::$SUBCLASSES;
684         }
685        
686         ///////////////////////////////////////////////////////////
687         // factory functions
688         ///////////////////////////////////////////////////////////
689         ///////////////////////////////////////////////////////////
690
691         ///////////////////////////////////////////////////////////
692         /// Require functions
693         ///////////////////////////////////////////////////////////
694
695         /**
696          * Require a class to be loaded.
697          *
698          * It's recommended to use the convenience functions like
699          * {@link lib::load()}, {@link model::load()}
700          * etc instead of directly using this function.
701          *
702          * <b>Always</b> use this function or, better, one of the
703          * convenience functions to load CASCO classes.
704          * <b>Never</b> use any of the built-in PHP functions like
705          * {@link require()} or {@link require_once()} to load CASCO
706          * classes. The reason for this is that using this function
707          * allows CASCO to invoke its special subclassing system. See
708          * {@link casco::subclass()} for more info.
709          *
710          * Only if you are loading <b>non-CASCO classes</b> from external
711          * libraries, you should use {@link require_once()}.
712          *
713          * @param string $type The major CASCO type of the class to load.
714          *     This can be one of 'model', 'controller', 'view',
715          *     'lib', 'plugin' or any additional major CASCO types
716          *     introduced by a module (for example: 'skin' if
717          *     casco_core_skinning is loaded).
718          * @param string $name The name of the class to load.
719          */
720         static function req($name, $type)
721         {
722                 if(!class_exists($name, false)) // class not already loaded?
723                 {
724                         $path = self::$finder->find_class($name, $type);
725                         if($path === false)
726                                 throw(new casco_exception('Could not load given class: '.$name.' of type: '.$type));
727                         require_once($path);
728                 }
729         }
730
731
732         /**
733          * Require a test class for another CASCO class to be loaded.
734          *
735          * This function is slightly different in that it does not
736          * require you to pass it the name of the test class, but the
737          * name of the class <i>for which you are loading the
738          * test</i>.
739          *
740          * @param $classtype The major CASCO class type of the class for
741          *          which we want to load the test.
742          * @param $name The name of the class for which we want to load
743          *          the test.
744          * @param string $name The name of the class to load.
745          * @return boolean True if the class was loaded, false if it could
746          *     not be found.
747          *
748          * @todo move this?
749          */
750         static function require_test($classtype, $name)
751         {
752                 return casco::req('test_'.$name, self::get_test_dir($classtype, $name));
753         }
754        
755         ///////////////////////////////////////////////////////////
756         /// Instantiation functions ///
757         ///////////////////////////////////////////////////////////
758        
759         /**
760          * Load and instantiate a class.
761          *
762          * Like {@link casco::req()}, but also instantiate the class.
763          *
764          * It's recommended to use the convenience functions like
765          * {@link lib::create()}, {@link model::create()}
766          * etc instead of directly using this function.
767          *
768          * @param string $type The major CASCO type of the class.
769          *
770          * @param string $name The name of the class to load.
771          *
772          * @param array  $arguments A list of arguments to pass to
773          *     the constructor of the class. (This is limited to 10
774          *     arguments because of technical limitations)
775          *
776          * @param boolean $allow_subclass Allow casco to substitute
777          *     an instance of another class for the requested one,
778          *     if one was defined by {@link casco::subclass()}.
779          *
780          * @return mixed An instance of the requested class.
781          * @throws casco_exception if the class could not be found
782          *     or instantiated.
783          */
784         static function factory($name, $type, $arguments = array(), $allow_subclass = true)
785         {
786                 if($allow_subclass === true)
787                 {
788                         $name = casco::get_real_classname($name);
789                 }
790
791                 // We check here for class_exists to avoid the overhead of unneccessarily calling req()
792                 if (!class_exists($name, false))
793                 {
794                         casco::req($name, $type);
795                 }
796
797                 // A sort call_user_func_array-workalike for constructing objects
798                 if ($arguments === array()) { // newInstanceArgs doesn't work for
799                         return new $name;
800                 } else {
801                         $rc = new ReflectionClass($name);
802                         return $rc->newInstanceArgs($arguments);
803                 }
804                 // Alternatively (but slightly slower):
805                 // return call_user_func_array(array(new ReflectionClass($name), 'newInstance'), $arguments);
806         }
807
808         /**
809          * Instantiate a test class for another CASCO class.
810          *
811          * This function is slightly different in that it does not
812          * require you to pass it the name of the test class, but the
813          * name of the class <i>for which you are loading the
814          * test</i>.
815          *
816          * @param $classtype The major CASCO class type of the class for
817          *          which we want to instantiate the test.
818          * @param $name The name of the class for which we want to
819          *          instantiate the test.
820          *
821          * @param array  $arguments A list of arguments to pass to
822          *     the constructor of the class. (This is limited to 10
823          *     arguments because of technical limitations)
824          *
825          * @param boolean $allow_subclass Allow casco to substitute
826          *     an instance of another class for the requested one,
827          *     if one was defined by {@link casco::subclass()}.
828          *
829          * @return mixed An instance of the requested class.
830          * @throws casco_exception if the class could not be found
831          *     or instantiated.
832          *
833          * @todo move this?
834          */
835         static function test($classtype, $name, $arguments = array(), $allow_subclass = true)
836         {
837                 return casco::factory('test_'.$name, self::get_test_dir($classtype, $name), $arguments, $allow_subclass);
838         }
839
840         protected static function get_test_dir($classtype, $name)
841         {
842                 return $classtype.'/'.$name.'/test/';
843         }
844
845         ///////////////////////////////////////////////////////////
846         // Context functions
847         ///////////////////////////////////////////////////////////
848
849         /**
850          * Call function with modified context.
851          *
852          * Add or modify keys in the context, then call the given
853          * function with the supplied arguments list. When the
854          * function returns, the context is restored to what it was
855          * before {@link casco::call_with_altered_context()} was invoked.
856          *
857          * Example:
858          * <code>
859          * // Assume context for 'foo' was previously set to 'bar'...
860          * echo casco::get_context('foo');
861          * casco::call_with_altered_context(array('foo' => 'qux'), 'my_func');
862          * echo casco::get_context('foo');
863          *
864          * function my_func()
865          * {
866          *     echo casco::get_context('foo');
867          * }
868          * </code>
869          * This outputs:
870          * <pre>
871          * bar
872          * qux
873          * bar
874          * </pre>
875          *
876          * For more information, please see
877          * {@link http://wush.net/trac/casco/wiki/ContextPattern The wiki
878          *  page about the "Context Pattern"}.
879          *
880          * @param array $alterations The new key/value pairs to
881          *                            set/add to the context
882          * @param string|array $func The function to call, as per {@link call_user_func()}
883          * @return mixed The value returned by $func
884          */
885         static function call_with_altered_context($alterations, $func, $args=array())
886         {
887                 foreach($alterations as $key => $value)
888                 {
889                         if (!isset(self::$CONTEXT[$key]))
890                                 $old_value = null;  // XXX Fix this! isset() abuse!
891                         else
892                                 $old_value = self::$CONTEXT[$key];
893                         self::$CONTEXT[$key] = $value;
894                         $alterations[$key] = $old_value;
895                 }
896                 try
897                 {
898                         $retval = call_user_func_array($func, $args);
899                 }
900                 catch(Exception $e)
901                 {
902                         foreach($alterations as $key => $value)
903                         {
904                                 if (isset($alterations[$key]))
905                                         self::$CONTEXT[$key] = $alterations[$key];
906                                 else
907                                         unset(self::$CONTEXT[$key]);
908                         }
909                         throw($e);
910                 }
911                 foreach($alterations as $key => $value)
912                 {
913                         if (isset($alterations[$key]))
914                                 self::$CONTEXT[$key] = $alterations[$key];
915                         else
916                                 unset(self::$CONTEXT[$key]);
917                 }
918                 return $retval;
919         }
920
921         /**
922          * Get a value from the current context.
923          *
924          * @param  string $key The name of the value to get.
925          * @param  mixed  $default The default value, if context is not set
926          * @return mixed  The value stored in the context.
927          *                $default if the key has no matching value.
928          */
929         static function get_context($key, $default=null)
930         {
931                 if(isset(self::$CONTEXT[$key]))
932                 {
933                         return self::$CONTEXT[$key];
934                 }
935                 else
936                 {
937                         return $default;
938                 }
939         }
940
941         /**
942          * Set a context value in-place.
943          *
944          * This "leaks out" of the current scope, so be extremely careful in using this.
945          *
946          * @param  string $key The name of the value to get.
947          * @param  mixed  $value The value to set the key to
948          */
949         static function set_context($key, $value)
950         {
951                 self::$CONTEXT[$key] = $value;
952         }
953        
954         static function debug_context($print = true)
955         {
956                 _debug_array(self::$CONTEXT, $print);
957         }
958
959         ///////////////////////////////////////////////////////////
960         // Miscellaneous functions
961         ///////////////////////////////////////////////////////////
962
963         /**
964          * Sort an array using insertion sort.
965          *
966          * Sort an array using the
967          * {@link http://en.wikipedia.org/wiki/Insertion_sort Insertion Sort}
968          * algorithm.
969          *
970          * @param array $array The array to sort.
971          * @return array The same array as $array, but sorted.
972          */
973         static function insertion_sort($array)
974         {
975                         // Sorts a given array by insertion sort (Decrease and conquer technique)
976                 for($i = 1; $i < (count($array)+1); $i++)
977                 {
978                         $j = $i-1;
979                         if($key == '')
980                         {
981                                         // sort based on the value
982                                 while($j >= 0 && $array[$j] > $array[$j+1] && count($array) > $j+1)
983                                 {
984                                                 // swap the items
985                                         $val = $array[$j];
986                                         $array[$j] = $array[$j+1];
987                                         $array[$j+1] = $val;
988                                         $j--;
989                                 }                               
990                         }
991                         else
992                         {
993                                         // sort based on a key
994                                 while($j[$key] >= 0 && $array[$j][$key] > $array[$j+1][$key] && count($array) > $j+1)
995                                 {
996                                                 // swap the items
997                                         $val = $array[$j];
998                                         $array[$j] = $array[$j+1];
999                                         $array[$j+1] = $val;
1000                                         $j--;
1001                                 }                                       
1002                         }
1003        
1004                 }
1005                 return $array;
1006         }
1007
1008         /**
1009          * Get the parent class for another class by name.
1010          *
1011          * If the class is not known to PHP yet, this loads the file
1012          * and retries.
1013          *
1014          * You should always use this function if you want to know
1015          * the parent class of a class, because the class may not
1016          * be loaded yet in some situations.
1017          *
1018          * @param string $classtype The major CASCO classtype.
1019          * @param string $classname The name of the class for which we
1020          *     want to know the parent.
1021          * @param string The name of the parent class.
1022          */
1023         static function get_parent_class($classtype, $classname)
1024         {
1025                 if(!class_exists($classname))
1026                 {
1027                         $deffile = self::$finder->find_class($classname, $classtype, false);
1028                         if($deffile !== false)
1029                         {
1030                                 require_once($deffile);
1031                         }
1032                 }
1033                 return get_parent_class($classname);
1034         }
1035
1036         /**
1037          * Escape a string so that it can be embedded in eval()ed code.
1038          *
1039          * Example: casco::php_escape_string('foo\'$bar') => 'foo\\\'$bar'
1040          * You will need to wrap _single_ (not double!) quotes around this
1041          * string when you want to eval it (this function mirrors
1042          * functions like mysql_escape_string, pg_escape_string etc)
1043          *
1044          * @param  string $str The string to escape
1045          * @return string The escaped equivalent of the input string
1046          */
1047         static function php_escape_string($str)
1048         {
1049                 // addslashes also escapes double quotes, which means
1050                 // extra backslashes will creep into the string.
1051                 return str_replace('\\"', '"', addslashes($str));
1052         }
1053
1054         /**
1055          * Redirect to a URL and stop processing.
1056          *
1057          * It is highly recommended to use this function and not the
1058          * default PHP header('Location: '), because this function
1059          * takes care to discard all output buffers and stop any
1060          * further processing. This abandons the script.
1061          *
1062          * This function is used, for example, in 'permission denied'
1063          * cases, so that the user does not get to see any output
1064          * rendered up to the point where it is determined he's not
1065          * supposed to see anything.
1066          *
1067          * @param array $url The URL to redirect to
1068          * @param array $response_code The HTTP response code to use (default = 302)
1069          */
1070         static function redirect($url, $response_code = 302)
1071         {
1072                 while(ob_get_level() > 0) ob_end_clean();
1073
1074                 if(is_string($url))
1075                         $url_string = $url;
1076                 else
1077                         $url_string = url($url);
1078                 header('Location: '.$url_string, true, $response_code);
1079                 die('See <a href="'.$url_string.'">'.$url_string.'</a>');
1080         }
1081 }
1082
1083 ?>
Note: See TracBrowser for help on using the browser.