root/fCore.php

Revision 388, 25.3 kB (checked in by wbond, 2 years ago)

BackwardsCompatibilityBreak - fCore::toss() was removed and all exceptions are now normally thrown with thrown new ExceptionName?. fCore::registerTossCallback() was renamed to fPrintableException::registerCallback(), fGrammar::compose() was renamed to fText::compose() and fGrammar::registerComposeCallback() was renamed to fText::registerComposeCallback()

LineHide Line Numbers
1 <?php
2 /**
3  * Provides low-level debugging, error and exception functionality
4  *
5  * @copyright  Copyright (c) 2007-2008 William Bond
6  * @author     William Bond [wb] <will@flourishlib.com>
7  * @license    http://flourishlib.com/license
8  *
9  * @package    Flourish
10  * @link       http://flourishlib.com/fCore
11  *
12  * @version    1.0.0b
13  * @changes    1.0.0b  The initial implementation [wb, 2007-09-25]
14  */
15 class fCore
16 {
17     // The following constants allow for nice looking callbacks to static methods
18     const backtrace               = 'fCore::backtrace';
19     const debug                   = 'fCore::debug';
20     const dump                    = 'fCore::dump';
21     const enableDebugging         = 'fCore::enableDebugging';
22     const enableDynamicConstants  = 'fCore::enableDynamicConstants';
23     const enableErrorHandling     = 'fCore::enableErrorHandling';
24     const enableExceptionHandling = 'fCore::enableExceptionHandling';
25     const expose                  = 'fCore::expose';
26     const getOS                   = 'fCore::getOS';
27     const getPHPVersion           = 'fCore::getPHPVersion';
28     const handleError             = 'fCore::handleError';
29     const handleException         = 'fCore::handleException';
30     const reset                   = 'fCore::reset';
31     const sendMessagesOnShutdown  = 'fCore::sendMessagesOnShutdown';
32     const stringlike              = 'fCore::stringlike';
33     const trigger                 = 'fCore::trigger';
34    
35    
36     /**
37     * If global debugging is enabled
38     *
39     * @var boolean
40     */
41     static private $debug = NULL;
42    
43     /**
44     * If dynamic constants should be created
45     *
46     * @var boolean
47     */
48     static private $dynamic_constants = FALSE;
49    
50     /**
51     * Error destination
52     *
53     * @var string
54     */
55     static private $error_destination = 'html';
56    
57     /**
58     * An array of errors to be send to the destination upon page completion
59     *
60     * @var array
61     */
62     static private $error_message_queue = array();
63    
64     /**
65     * Exception destination
66     *
67     * @var string
68     */
69     static private $exception_destination = 'html';
70    
71     /**
72     * Exception handler callback
73     *
74     * @var mixed
75     */
76     static private $exception_handler_callback = NULL;
77    
78     /**
79     * Exception handler callback parameters
80     *
81     * @var array
82     */
83     static private $exception_handler_parameters = array();
84    
85     /**
86     * The message generated by the uncaught exception
87     *
88     * @var string
89     */
90     static private $exception_message = NULL;
91    
92     /**
93     * If this class is handling errors
94     *
95     * @var boolean
96     */
97     static private $handles_errors = FALSE;
98    
99    
100     /**
101     * Creates a nicely formatted backtrace to the the point where this method is called
102     *
103     * @param  integer $remove_lines  The number of trailing lines to remove from the backtrace
104     * @return string  The formatted backtrace
105     */
106     static public function backtrace($remove_lines=0)
107     {
108         if ($remove_lines !== NULL && !is_numeric($remove_lines)) {
109             $remove_lines = 0;
110         }
111        
112         settype($remove_lines, 'integer');
113        
114         $doc_root  = realpath($_SERVER['DOCUMENT_ROOT']);
115         $doc_root .= (substr($doc_root, -1) != '/' && substr($doc_root, -1) != '\\') ? '/' : '';
116        
117         $backtrace = debug_backtrace();
118        
119         while ($remove_lines > 0) {
120             array_shift($backtrace);
121             $remove_lines--;
122         }
123        
124         $backtrace = array_reverse($backtrace);
125        
126         $bt_string = '';
127         $i = 0;
128         foreach ($backtrace as $call) {
129             if ($i) {
130                 $bt_string .= "\n";
131             }
132             if (isset($call['file'])) {
133                 $bt_string .= str_replace($doc_root, '{doc_root}/', $call['file']) . '(' . $call['line'] . '): ';
134             } else {
135                 $bt_string .= '[internal function]: ';
136             }
137             if (isset($call['class'])) {
138                 $bt_string .= $call['class'] . $call['type'];
139             }
140             if (isset($call['class']) || isset($call['function'])) {
141                 $bt_string .= $call['function'] . '(';
142                     $j = 0;
143                     if (!isset($call['args'])) {
144                         $call['args'] = array();
145                     }
146                     foreach ($call['args'] as $arg) {
147                         if ($j) {
148                             $bt_string .= ', ';
149                         }
150                         if (is_bool($arg)) {
151                             $bt_string .= ($arg) ? 'true' : 'false';
152                         } elseif (is_null($arg)) {
153                             $bt_string .= 'NULL';
154                         } elseif (is_array($arg)) {
155                             $bt_string .= 'Array';
156                         } elseif (is_object($arg)) {
157                             $bt_string .= 'Object(' . get_class($arg) . ')';
158                         } elseif (is_string($arg)) {
159                             // Shorten the UTF-8 string if it is too long
160                             if (strlen(utf8_decode($arg)) > 18) {
161                                 preg_match('#^(.{0,15})#us', $arg, $short_arg);
162                                 $arg = $short_arg[1] . '...';
163                             }
164                             $bt_string .= "'" . $arg . "'";
165                         } else {
166                             $bt_string .= (string) $arg;
167                         }
168                         $j++;
169                     }
170                 $bt_string .= ')';
171             }
172             $i++;
173         }
174        
175         return $bt_string;
176     }
177    
178    
179     /**
180     * Performs a [http://php.net/call_user_func call_user_func()], while translating PHP 5.2 static callback syntax for PHP 5.1 and 5.0
181     *
182     * Parameters can be passed either as a single array of parameters or as
183     * multiple parameters.
184     *
185     * {{{
186     * #!php
187     * // Passing multiple parameters in a normal fashion
188     * fCore::call('Class::method', TRUE, 0, 'test');
189     *
190     * // Passing multiple parameters in a parameters array
191     * fCore::call('Class::method', array(TRUE, 0, 'test'));
192     * }}}
193     *
194     * To pass parameters by reference they must be assigned to an
195     * array by reference and the function/method being called must accept those
196     * parameters by reference. If either condition is not met, the parameter
197     * will be passed by value.
198     *
199     * {{{
200     * #!php
201     * // Passing parameters by reference
202     * fCore::call('Class::method', array(&$var1, &$var2));
203     * }}}
204     *
205     * @param  callback $callback    The function or method to call
206     * @param  array    $parameters  The parameters to pass to the function/method
207     * @return mixed  The return value of the called function/method
208     */
209     static public function call($callback, $parameters=array())
210     {
211         // Fix PHP 5.0 and 5.1 static callback syntax
212         if (is_string($callback) && self::getPHPVersion() < '5.2.0' && strpos($callback, '::') !== FALSE) {
213             $callback = explode('::', $callback);
214         }
215        
216         $parameters = array_slice(func_get_args(), 1);
217         if (sizeof($parameters) == 1 && is_array($parameters[0])) {
218             $parameters = $parameters[0];   
219         }
220        
221         return call_user_func_array($callback, $parameters);
222     }
223    
224    
225     /**
226     * Translates a Class::method style static method callback to array style for compatibility with PHP 5.0 and 5.1 and built-in PHP functions
227     *
228     * @param  callback $callback  The callback to translate
229     * @return array  The translated callback
230     */
231     static public function callback($callback)
232     {
233         if (is_string($callback) || strpos($callback, '::') !== FALSE) {
234             return explode('::', $callback);   
235         }
236        
237         return $callback;
238     }
239    
240    
241     /**
242     * Checks an error/exception destination to make sure it is valid
243     *
244     * @param  string $destination  The destination for the exception. An email, file or the string `'html'`.
245     * @return string|boolean  `'email'`, `'file'`, `'html'` or `FALSE`
246     */
247     static private function checkDestination($destination)
248     {
249         if ($destination == 'html') {
250             return 'html';
251         }
252        
253         if (preg_match('#[a-z0-9_.\-\']+@([a-z0-9\-]+\.){1,}([a-z]{2,})#i', $destination)) {
254             return 'email';
255         }
256        
257         $path_info     = pathinfo($destination);
258         $dir_exists    = file_exists($path_info['dirname']);
259         $dir_writable  = ($dir_exists) ? is_writable($path_info['dirnam']) : FALSE;
260         $file_exists   = file_exists($destination);
261         $file_writable = ($file_exists) ? is_writable($destination) : FALSE;
262        
263         if (!$dir_exists || ($dir_exists && ((!$file_exists && !$dir_writable) || ($file_exists && !$file_writable)))) {
264             return FALSE;
265         }
266            
267         return 'file';
268     }
269    
270    
271     /**
272     * Composes text using fText if loaded
273     *
274     * @param  string  $message    The message to compose
275     * @param  mixed   $component  A string or number to insert into the message
276     * @param  mixed   ...
277     * @return string  The composed and possible translated message
278     */
279     static private function compose($message)
280     {
281         $args = array_slice(func_get_args(), 1);
282        
283         if (class_exists('fText', FALSE)) {
284             return call_user_func_array(
285                 array('fText', 'compose'),
286                 array($message, $args)
287             );
288         } else {
289             return vsprintf($message, $args);
290         }
291     }
292    
293    
294     /**
295     * Prints a debugging message if global or code-specific debugging is enabled
296     *
297     * @param  string  $message  The debug message
298     * @param  boolean $force    If debugging should be forced even when global debugging is off
299     * @return void
300     */
301     static public function debug($message, $force)
302     {
303         if ($force || self::$debug) {
304             self::expose($message, FALSE);
305         }
306     }
307    
308    
309     /**
310     * Creates a string representation of any variable using predefined strings for booleans, `NULL` and empty strings
311     *
312     * The string output format of this method is very similar to the output of
313     * [http://php.net/print_r print_r()] except that the following values
314     * are represented as special strings:
315     *   
316     *  - `TRUE`: `'{true}'`
317     *  - `FALSE`: `'{false}'`
318     *  - `NULL`: `'{null}'`
319     *  - `''`: `'{empty_string}'`
320     *
321     * @param  mixed $data  The value to dump
322     * @return string  The string representation of the value
323     */
324     static public function dump($data)
325     {
326         if (is_bool($data)) {
327             return ($data) ? '{true}' : '{false}';
328        
329         } elseif (is_null($data)) {
330             return '{null}';
331        
332         } elseif ($data === '') {
333             return '{empty_string}';
334        
335         } elseif (is_array($data) || is_object($data)) {
336            
337             ob_start();
338             var_dump($data);
339             $output = ob_get_contents();
340             ob_end_clean();
341            
342             // Make the var dump more like a print_r
343             $output = preg_replace('#=>\n(  )+(?=[a-zA-Z]|&)#m', ' => ', $output);
344             $output = str_replace('string(0) ""', '{empty_string}', $output);
345             $output = preg_replace('#=> (&)?NULL#', '=> \1{null}', $output);
346             $output = preg_replace('#=> (&)?bool\((false|true)\)#', '=> \1{\2}', $output);
347             $output = preg_replace('#string\(\d+\) "#', '', $output);
348             $output = preg_replace('#"(\n(  )*)(?=\[|\})#', '\1', $output);
349             $output = preg_replace('#(?:float|int)\((-?\d+(?:.\d+)?)\)#', '\1', $output);
350             $output = preg_replace('#((?:  )+)\["(.*?)"\]#', '\1[\2]', $output);
351             $output = preg_replace('#(?:&)?array\(\d+\) \{\n((?:  )*)((?:  )(?=\[)|(?=\}))#', "Array\n\\1(\n\\1\\2", $output);
352             $output = preg_replace('/object\((\w+)\)#\d+ \(\d+\) {\n((?:  )*)((?:  )(?=\[)|(?=\}))/', "\\1 Object\n\\2(\n\\2\\3", $output);
353             $output = preg_replace('#^((?:  )+)}(?=\n|$)#m', "\\1)\n", $output);
354             $output = substr($output, 0, -2) . ')';
355            
356             // Fix indenting issues with the var dump output
357             $output_lines = explode("\n", $output);
358             $new_output = array();
359             $stack = 0;
360             foreach ($output_lines as $line) {
361                 if (preg_match('#^((?:  )*)([^ ])#', $line, $match)) {
362                     $spaces = strlen($match[1]);
363                     if ($spaces && $match[2] == '(') {
364                         $stack += 1;
365                     }
366                     $new_output[] = str_pad('', ($spaces)+(4*$stack)) . $line;
367                     if ($spaces && $match[2] == ')') {
368                         $stack -= 1;
369                     }
370                 } else {
371                     $new_output[] = str_pad('', ($spaces)+(4*$stack)) . $line;
372                 }
373             }
374            
375             return join("\n", $new_output);
376            
377         } else {
378             return (string) $data;
379         }
380     }
381    
382    
383     /**
384     * Enables debug messages globally, i.e. they will be shown for any call to ::debug()
385     *
386     * @param  boolean $flag  If debugging messages should be shown
387     * @return void
388     */
389     static public function enableDebugging($flag)
390     {
391         self::$debug = (boolean) $flag;
392     }
393    
394    
395     /**
396     * Turns on a feature where undefined constants are automatically created with the string value equivalent to the name
397     *
398     * This functionality only works if ::enableErrorHandling() has been
399     * called first. This functionality may have a very slight performance
400     * impact since a `E_STRICT` error message must be captured and then a
401     * call to [http://php.net/define define()] is made.
402     *
403     * @return void
404     */
405     static public function enableDynamicConstants()
406     {
407         if (!self::$handles_errors) {
408             throw new fProgrammerException(
409                 'Dynamic constants can not be enabled unless error handling has been enabled via %s',
410                 __CLASS__ . '::enableErrorHandling()'
411             );
412         }
413         self::$dynamic_constants = TRUE;
414     }
415    
416    
417     /**
418     * Turns on developer-friendly error handling that includes context information including a backtrace and superglobal dumps
419     *
420     * All errors that match the current
421     * [http://php.net/error_reporting error_reporting()] level will be
422     * redirected to the destination and will include a full backtrace. In
423     * addition, dumps of the following superglobals will be made to aid in
424     * debugging:
425     *
426     *  - `$_SERVER`
427     *  - `$_POST`
428     *  - `$_GET`
429     *  - `$_SESSION`
430     *  - `$_FILES`
431     *  - `$_COOKIE`
432     *
433     * The superglobal dumps are only done once per page, however a backtrace
434     * in included for each error.
435     *
436     * If an email address is specified for the destination, only one email
437     * will be sent per script execution. If both error and
438     * [enableExceptionHandling() exception handling] are set to the same
439     * email address, the email will contain both errors and exceptions.
440     *
441     * @param  string $destination  The destination for the errors and context information - an email address, a file path or the string `'html'`
442     * @return void
443     */
444     static public function enableErrorHandling($destination)
445     {
446         if (!self::checkDestination($destination)) {
447             return;
448         }
449         self::$error_destination = $destination;
450         self::$handles_errors    = TRUE;
451         set_error_handler(self::callback(self::handleError));
452     }
453    
454    
455     /**
456     * Turns on developer-friendly uncaught exception handling that includes context information including a backtrace and superglobal dumps
457     *
458     * Any uncaught exception will be redirected to the destination specified,
459     * and the page will execute the `$closing_code` callback before exiting.
460     * The destination will receive a message with the exception messaage, a
461     * full backtrace and dumps of the following superglobals to aid in
462     * debugging:
463     *
464     *  - `$_SERVER`
465     *  - `$_POST`
466     *  - `$_GET`
467     *  - `$_SESSION`
468     *  - `$_FILES`
469     *  - `$_COOKIE`
470     *
471     * The superglobal dumps are only done once per page, however a backtrace
472     * in included for each error.
473     *
474     * If an email address is specified for the destination, only one email
475     * will be sent per script execution.
476     *
477     * If an email address is specified for the destination, only one email
478     * will be sent per script execution. If both exception and
479     * [enableErrorHandling() error handling] are set to the same
480     * email address, the email will contain both exceptions and errors.
481     *
482     * @param  string   $destination   The destination for the exception and context information - an email address, a file path or the string `'html'`
483     * @param  callback $closing_code  This callback will happen after the exception is handled and before page execution stops. Good for printing a footer.
484     * @param  array    $parameters    The parameters to send to `$closing_code`
485     * @return void
486     */
487     static public function enableExceptionHandling($destination, $closing_code=NULL, $parameters=array())
488     {
489         if (!self::checkDestination($destination)) {
490             return;
491         }
492         self::$exception_destination        = $destination;
493         self::$exception_handler_callback   = $closing_code;
494         if (!is_object($parameters)) {
495             settype($parameters, 'array');
496         } else {
497             $parameters = array($parameters);   
498         }
499         self::$exception_handler_parameters = $parameters;
500         set_exception_handler(self::callback(self::handleException));
501     }
502    
503    
504     /**
505     * Prints the ::dump() of a value in a pre tag with the class `exposed`
506     *
507     * @param  mixed $data  The value to show
508     * @return void
509     */
510     static public function expose($data)
511     {
512         echo '<pre class="exposed">' . htmlspecialchars((string) self::dump($data)) . '</pre>';
513     }
514    
515    
516     /**
517     * Generates some information about the context of an error or exception
518     *
519     * @return string  A string containing `$_SERVER`, `$_GET`, `$_POST`, `$_FILES`, `$_SESSION` and `$_COOKIE`
520     */
521     static private function generateContext()
522     {
523         return self::compose('Context') . "\n-------" .
524             "\n\n\$_SERVER\n"  . self::dump($_SERVER) .
525             "\n\n\$_POST\n" . self::dump($_POST) .
526             "\n\n\$_GET\n" . self::dump($_GET) .
527             "\n\n\$_FILES\n"   . self::dump($_FILES) .
528             "\n\n\$_SESSION\n" . self::dump((isset($_SESSION)) ? $_SESSION : NULL) .
529             "\n\n\$_COOKIE\n" . self::dump($_COOKIE);
530     }
531    
532    
533     /**
534     * Returns the (generalized) operating system the code is currently running on
535     *
536     * @return string  Either `'windows'`, `'solaris'` or `'linux/unix'` (linux, *BSD)
537     */
538     static public function getOS()
539     {
540         $uname = php_uname('s');
541        
542         if (stripos($uname, 'linux') !== FALSE) {
543             return 'linux/unix';
544         }
545         if (stripos($uname, 'bsd') !== FALSE) {
546             return 'linux/unix';
547         }
548         if (stripos($uname, 'solaris') !== FALSE || stripos($uname, 'sunos') !== FALSE) {
549             return 'solaris';
550         }
551         if (stripos($uname, 'windows') !== FALSE) {
552             return 'windows';
553         }
554        
555         self::trigger(
556             'warning',
557             self::compose(
558                 'Unable to reliably determine the server OS. Defaulting to %s.',
559                 "'linux/unix'"
560             )
561         );
562        
563         return 'linux/unix';
564     }
565    
566    
567     /**
568     * Returns the version of PHP running, ignoring any information about the OS
569     *
570     * @return string  The PHP version in the format major.minor.version
571     */
572     static public function getPHPVersion()
573     {
574         static $version = NULL;
575        
576         if ($version === NULL) {
577             $version = phpversion();
578             $version = preg_replace('#^(\d+\.\d+\.\d+).*$#', '\1', $version);
579         }
580        
581         return $version;
582     }
583    
584    
585     /**
586     * Handles an error, creating the necessary context information and sending it to the specified destination
587     *
588     * @internal
589      *
590     * @param  integer $error_number   The error type
591     * @param  string  $error_string   The message for the error
592     * @param  string  $error_file     The file the error occured in
593     * @param  integer $error_line     The line the error occured on
594     * @param  array   $error_context  A references to all variables in scope at the occurence of the error
595     * @return void
596     */
597     static public function handleError($error_number, $error_string, $error_file=NULL, $error_line=NULL, $error_context=NULL)
598     {
599         if (self::$dynamic_constants && $error_number == E_NOTICE) {
600             if (preg_match("#^Use of undefined constant (\w+) - assumed '\w+'\$#", $error_string, $matches)) {
601                 define($matches[1], $matches[1]);
602                 return;
603             }       
604         }
605        
606         if ((error_reporting() & $error_number) == 0) {
607             return;
608         }
609        
610         $doc_root  = realpath($_SERVER['DOCUMENT_ROOT']);
611         $doc_root .= (substr($doc_root, -1) != '/' && substr($doc_root, -1) != '\\') ? '/' : '';
612        
613         $error_file = str_replace($doc_root, '{doc_root}/', $error_file);
614        
615         $backtrace = self::backtrace(1);
616        
617         $error_string = preg_replace('# \[<a href=\'.*?</a>\]: #', ': ', $error_string);
618        
619         $error   = self::compose('Error') . "\n-----\n" . $backtrace . "\n" . $error_string;
620        
621         self::sendMessageToDestination('error', $error);
622     }
623    
624    
625     /**
626     * Handles an uncaught exception, creating the necessary context information, sending it to the specified destination and finally executing the closing callback
627     *
628     * @internal
629      *
630     * @param  object $exception  The uncaught exception to handle
631     * @return void
632     */
633     static public function handleException($exception)
634     {
635         if ($exception instanceof fPrintableException) {
636             $message = $exception->formatTrace() . "\n" . $exception->getMessage();
637         } else {
638             $message = $exception->getTraceAsString() . "\n" . $exception->getMessage();
639         }
640         $message = self::compose("Uncaught Exception") . "\n------------------\n" . trim($message);
641        
642         if (self::$exception_destination != 'html' && $exception instanceof fPrintableException) {
643             $exception->printMessage();
644         }
645                
646         self::sendMessageToDestination('exception', $message);
647        
648         if (self::$exception_handler_callback === NULL) {
649             return;
650         }
651                
652         try {
653             self::call(self::$exception_handler_callback, self::$exception_handler_parameters);
654         } catch (Exception $e) {
655             self::trigger(
656                 'error',
657                 self::compose(
658                     'An exception was thrown in the %s closing code callback',
659                     'setExceptionHandling()'
660                 )
661             );
662         }
663     }
664    
665    
666     /**
667     * Resets the configuration of the class
668     *
669     * @internal
670      *
671     * @return void
672     */
673     static public function reset()
674     {
675         restore_error_handler();
676         restore_exception_handler();
677        
678         self::$debug                        = NULL;
679         self::$dynamic_constants            = FALSE;
680         self::$error_destination            = 'html';
681         self::$error_message_queue          = array();
682         self::$exception_destination        = 'html';
683         self::$exception_handler_callback   = NULL;
684         self::$exception_handler_parameters = array();
685         self::$exception_message            = NULL;
686         self::$handles_errors               = FALSE;
687         self::$toss_callbacks               = array();
688     }
689    
690    
691     /**
692     * Sends an email or writes a file with messages generated during the page execution
693     *
694     * This method prevents multiple emails from being sent or a log file from
695     * being written multiple times for one script execution.
696     *
697     * @internal
698      *
699     * @return void
700     */
701     static public function sendMessagesOnShutdown()
702     {
703         $subject = self::compose(
704             '[%1$s] One or more errors or exceptions occured at %2$s',
705             $_SERVER['SERVER_NAME'],
706             date('Y-m-d H:i:s')
707         );
708        
709         $messages = array();
710        
711         if (self::$error_message_queue) {
712             $message = join("\n\n", self::$error_message_queue);
713             $messages[self::$error_destination] = $message;
714         }
715        
716         if (self::$exception_message) {
717             if (isset($messages[self::$exception_destination])) {
718                 $messages[self::$exception_destination] .= "\n\n";
719             } else {
720                 $messages[self::$exception_destination] = '';
721             }
722             $messages[self::$exception_destination] .= self::$exception_message;
723         }
724        
725         foreach ($messages as $destination => $message) {
726             if (self::checkDestination($destination) == 'email') {
727                 mail($destination, $subject, $message . "\n\n" . self::generateContext());
728            
729             } else {
730                 $handle = fopen($destination, 'a');
731                 fwrite($handle, $subject . "\n\n");
732                 fwrite($handle, $message . "\n\n");
733                 fwrite($handle, self::generateContext() . "\n\n");
734                 fclose($handle);
735             }
736         }
737     }
738    
739    
740     /**
741     * Handles sending a message to a destination
742     *
743     * If the destination is an email address or file, the messages will be
744     * spooled up until the end of the script execution to prevent multiple
745     * emails from being sent or a log file being written to multiple times.
746     *
747     * @param  string $type     If the message is an error or an exception
748     * @param  string $message  The message to send to the destination
749     * @return void
750     */
751     static private function sendMessageToDestination($type, $message)
752     {
753         $destination = ($type == 'exception') ? self::$exception_destination : self::$error_destination;
754        
755         if ($destination == 'html') {
756             static $shown_context = FALSE;
757             if (!$shown_context) {
758                 self::expose(self::generateContext());
759                 $shown_context = TRUE;
760             }
761             self::expose($message);
762             return;
763         }
764  
765         static $registered_function = FALSE;
766         if (!$registered_function) {
767             register_shutdown_function(self::callback(self::sendMessagesOnShutdown));
768             $registered_function = TRUE;
769         }
770        
771         if ($type == 'error') {
772             self::$error_message_queue[] = $message;
773         } else {
774             self::$exception_message = $message;
775         }
776     }
777    
778    
779     /**
780     * Returns `TRUE` for non-empty strings, numbers, objects, empty numbers and string-like numbers (such as `0`, `0.0`, `'0'`)
781     *
782     * @param  mixed $value  The value to check
783     * @return boolean  If the value is string-like
784     */
785     static public function stringlike($value)
786     {
787         if (!$value && !is_numeric($value)) {
788             return FALSE;
789         }
790        
791         if (is_resource($value) || is_array($value) || $value === TRUE) {
792             return FALSE;
793         }
794        
795         if (is_string($value) && !trim($value)) {
796             return FALSE;   
797         }
798        
799         return TRUE;
800     }
801    
802    
803     /**
804     * Triggers a user-level error
805     *
806     * The default error handler in PHP will show the line number of this
807     * method as the triggering code. To get a full backtrace, use
808     * ::enableErrorHandling().
809     *
810     * @param  string $error_type  The type of error to trigger: `'error'`, `'warning'`, `'notice'`
811     * @param  string $message     The error message
812     * @return void
813     */
814     static public function trigger($error_type, $message)
815     {
816         $valid_error_types = array('error', 'warning', 'notice');
817         if (!in_array($error_type, $valid_error_types)) {
818             self::toss(
819                 'fProgrammerException',
820                 self::compose(
821                     'Invalid error type, %1$s, specified. Must be one of: %2$s.',
822                     self::dump($error_type),
823                     join(', ', $valid_error_types)
824                 )
825             );
826         }
827        
828         static $error_type_map = array(
829             'error'   => E_USER_ERROR,
830             'warning' => E_USER_WARNING,
831             'notice'  => E_USER_NOTICE
832         );
833        
834         trigger_error($message, $error_type_map[$error_type]);
835     }
836    
837    
838     /**
839     * Forces use as a static class
840     *
841     * @return fCore
842     */
843     private function __construct() { }
844 }
845  
846  
847  
848 /**
849  * Copyright (c) 2007-2008 William Bond <will@flourishlib.com>
850  *
851  * Permission is hereby granted, free of charge, to any person obtaining a copy
852  * of this software and associated documentation files (the "Software"), to deal
853  * in the Software without restriction, including without limitation the rights
854  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
855  * copies of the Software, and to permit persons to whom the Software is
856  * furnished to do so, subject to the following conditions:
857  *
858  * The above copyright notice and this permission notice shall be included in
859  * all copies or substantial portions of the Software.
860  *
861  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
862  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
863  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
864  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
865  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
866  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
867  * THE SOFTWARE.
868  */