FRED™  3.0
FRED™: Framework for Rapid and Easy Development
Fred.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Rsi;
4 
5 ini_set('display_errors',false);
6 error_reporting(E_ALL);
7 
8 /**
9  * Framework for Rapid and Easy Development.
10  *
11  * The Fred object is at the core of the framework. From this object all components will be initialized. This is done in a
12  * 'lazy' manner. That is: only when the component is specificly called (through the component() function or with a magic
13  * __get()).
14  *
15  * This object also does autoloading and error handling. The magic __call() method is mapped to the item() function of the
16  * entity component.
17  */
18 class Fred extends Thing{
19 
20  const EVENT_HALT = 'fred:halt';
21  const EVENT_EXTERNAL_ERROR = 'fred:externalError';
22  const EVENT_SHUTDOWN = 'fred:shutdown';
23 
24  public $debug = false; //!< True for debug modus.
25  public $autoloadCacheKey = 'fred:autoloadCache'; //!< Session key for classnames cache.
26  public $autoloadMissingKey = 'fred:autoloadMissing'; //!< Session key for missing classnames cache.
27  public $defaultComponentNamespace = __CLASS__; //!< Default namespace for components. If there is no class name defined for a
28  // component, then the framework will try to load the class in this namespace with the same name (ucfirst-ed).
29 
30  public $templatePath = __DIR__ . '/../../template/'; //!< Path for the framework templates.
31  public $version = null; //!< Project version.
32  public $ignoreErrors = '/^SOAP-ERROR/'; //!< Errors to ignore on shutdown (regex).
33  public $stripObjectsMemoryLimit = null;
34  public $stripObjectsMaxDepth = 10;
35 
36  protected $_startTime = null; //!< Time at which the request started.
37  protected $_initialized = false; //!< True if the framework is initialised.
38  protected $_config = []; //!< The configuration.
39  protected $_internalError = false; //!< True if an internal error has (already) occured.
40  protected $_errorHash = null; //!< Hash for the latest caught error.
41  protected $_errorBacktrace = null; //!< Backtrace for the latest caught error.
42  protected $_timeLimit = null; //!< Execution cut-off timestamp.
43 
44  protected $_autoloadNamespaces = []; //!< Autoload namespace prefix (key) en paths (value).
45  protected $_autoloadClasses = []; //!< Autoload classes (key) and files (value).
46  protected $_autoloadFiles = []; //!< Autoload files (if not covered by the previous options).
47  protected $_autoloadCache = []; //!< Register direct location of class files.
48  protected $_autoloadCacheLimit = 250; //!< Size limit for the autoload cache.
49  protected $_autoloadMissing = []; //!< Register missing autoload classes (prevent double checking).
50  protected $_sharedCacheFile = null; //!< Shared cache file.
51  protected $_sharedCacheTime = null; //!< Creation time of the shared cache.
52  protected $_sharedCacheTtl = 600; //!< Time-to-live for shared cache.
53  protected $_sharedCacheTtlSpread = 10; //!< Random spread for TTL.
54  protected $_components = []; //!< Initialized components (key = component name, value = component).
55 
56  protected $_releaseNotesFile = __DIR__ . '/../../doc/pages/notes.php';
57  protected $_releaseNotesKey = 'fred:releaseNotesTime'; //!< Session key for release notes change time.
58 
59  protected $_maintenanceTime = null; //!< Date and time set for maintenance (site inaccessible) in 'Y-m-d H:i:s' format.
60  protected $_maintenanceMessage = []; //!< Messages to show before maintenance starts. Key = seconds before start to show
61  // message (once per session), from shortest to longer. Add a colon to specify message type, e.g. '30:error' (defaults to
62  // warning). Value = message (translated, use 'time' tag to insert maintenance time, 'delta' for time left in minutes; time
63  // 'limit' and message 'type' are also available).
64  protected $_maintenanceKey = 'fred:maintenanceMessage'; //!< Session key for last maintenance message.
65 
66  /**
67  * Initialize the framework.
68  * @param string|array $config The configuration. In case of a string, the file with this name will be included.
69  */
70  public function __construct($config){
71  try{
72  $this->_startTime = $_SERVER['REQUEST_TIME_FLOAT'] ?? microtime(true);
73  $this->_config = is_array($config) ? $config : require($config);
74  spl_autoload_register([$this,'autoload'],true,true);
75  $this->init();
76  }
77  catch(\Exception $e){
78  $this->exceptionHandler($e);
79  }
80  }
81  /**
82  * Initialize the framework.
83  */
84  protected function init(){
85  $this->configure($this->_config);
86  ini_set('display_errors',$this->debug);
87  set_error_handler([$this,'errorHandler']);
88  set_exception_handler([$this,'exceptionHandler']);
89  register_shutdown_function([$this,'shutdownFunction']);
90  $this->publish(['startTime' => self::READABLE,'autoloadNamespaces' => self::READABLE,'autoloadClasses' => self::READABLE]);
91  try{
92  if(session_status() == PHP_SESSION_NONE) session_start();
93  if($this->_sharedCacheFile) try{
94  include($this->_sharedCacheFile);
95  if((($delta = $this->_startTime - $this->_sharedCacheTime - $this->_sharedCacheTtl) > 0) && ($delta > rand(0,1000) / 1000 * $this->_sharedCacheTtlSpread)){
96  $this->_autoloadCache = $this->_autoloadMissing = [];
97  $this->_sharedCacheTime = null;
98  }
99  }
100  catch(\Throwable $e){
101  \Rsi\File::unlink($this->_sharedCacheFile);
102  $this->log->info($e);
103  }
104  if(array_key_exists($this->autoloadCacheKey,$_SESSION))
105  $this->_autoloadCache = array_merge($this->_autoloadCache,$_SESSION[$this->autoloadCacheKey]);
106  if(array_key_exists($this->autoloadMissingKey,$_SESSION))
107  $this->_autoloadMissing = array_merge($this->_autoloadMissing,$_SESSION[$this->autoloadMissingKey]);
108  }
109  catch(\Exception $e){
110  if($this->debug) throw $e;
111  }
112  if($this->debug){
113  $this->log->debug('FRED™ framework initialized',__FILE__,__LINE__);
114  $this->releaseNotes();
115  }
116  else $this->maintenance();
117  $this->_initialized = true;
118  }
119 
120  protected function saveSharedCache(){
121  if($this->_initialized && $this->_sharedCacheFile) try{
122  file_put_contents($temp = $this->_sharedCacheFile . uniqid('-',true) . '.tmp',"<?php\n" .
123  '$this->_sharedCacheTime = ' . ($this->_sharedCacheTime ?: $this->_startTime) . '; //modified ' . date('Y-m-d H:i:s') . "\n" .
124  '$this->_autoloadCache = ' . var_export($this->_autoloadCache,true) . ";\n" .
125  '$this->_autoloadMissing = ' . var_export(count($this->_autoloadMissing) > $this->_autoloadCacheLimit ? [] : $this->_autoloadMissing,true) . ';'
126  );
127  if(!rename($temp,$this->_sharedCacheFile)) throw new \Exception("Could not rename('$temp','{$this->_sharedCacheFile}')");
128  chmod($this->_sharedCacheFile,0666);
129  }
130  catch(\Exception $e){
131  $this->log->info($e);
132  try{
133  unlink($temp);
134  }
135  catch(\Exception $e){}
136  }
137  else{
138  $_SESSION[$this->autoloadCacheKey] = array_slice($this->_autoloadCache,0,$this->_autoloadCacheLimit,true);
139  $_SESSION[$this->autoloadMissingKey] = array_slice($this->_autoloadMissing,0,$this->_autoloadCacheLimit);
140  }
141  }
142  /**
143  * Autoloader.
144  * The autoloader tries to load a class in 3 steps:
145  * - If the class is specificly mentioned in the $_autoloadClasses, then the corresponding file will be loaded.
146  * - If the class name starts with a prefix from the $_autoloadNamespaces, then the rest of the class name (namespace
147  * separator = directory separator) and the path corresponding path will be used to create the filename.
148  * - If both these options fail, all files in the $_autoloadFiles will be loaded (once) (hoping the sought class will be in
149  * there).
150  */
151  public function autoload($class_name){
152  if($this->debug && $this->_initialized) $this->log->debug(__CLASS__ . "::autoload('$class_name')",__FILE__,__LINE__,['className' => $class_name]);
153  if(array_key_exists($class_name,$this->_autoloadCache)) require($this->_autoloadCache[$class_name]);
154  elseif(array_key_exists($class_name,$this->_autoloadClasses)) return require($this->_autoloadClasses[$class_name]);
155  elseif(!in_array($class_name,$this->_autoloadMissing)){
156  $prefix_match = false;
157  foreach($this->_autoloadNamespaces as $prefix => $paths) if(substr($class_name,0,strlen($prefix)) == $prefix){
158  $prefix_match = true;
159  foreach($paths as $path) if(is_file($filename = $path . str_replace(['_','\\'],'/',substr($class_name,strlen(rtrim($prefix,'\\')))) . '.php')){
160  $this->_autoloadCache[$class_name] = $filename;
161  $this->saveSharedCache();
162  return require($filename);
163  }
164  }
165  if(!$prefix_match && ($files = $this->_autoloadFiles)){
166  $this->_autoloadFiles = false;
167  foreach($files as $filename) require($filename);
168  }
169  else{
170  $this->_autoloadMissing[] = $class_name;
171  $this->saveSharedCache();
172  }
173  }
174  }
175  /**
176  * End the request.
177  * @param int|string $status Value to pass to the exit() function.
178  */
179  public function halt($status = null){
180  if($this->event->trigger(self::EVENT_HALT,$this,$status) !== false) exit($status);
181  }
182  /**
183  * Separate objects.
184  * @param mixed $item Item to check on (recursive for arrays).
185  * @param array $objects Array to store objects in. Item will be replaced with name of key.
186  * @param int $level Current nesting level (stops at 10).
187  */
188  protected function stripObjects(&$item,&$objects,$level = 0){
189  if(is_object($item)) try{
190  if($item instanceof \Closure){
191  $reflect = new \ReflectionFunction($item);
192  $filename = $reflect->getFileName();
193  $line_no = $reflect->getStartLine();
194  $item =
195  "<closure file='$filename' line='$line_no'>\n" .
196  implode(array_slice(file($filename),$line_no - 1,$reflect->getEndLine() - $line_no + 1)) .
197  '</closure>';
198  }
199  else{
200  if(!$this->stripObjectsMemoryLimit) $this->stripObjectsMemoryLimit = \Rsi::memoryLimit() / 5;
201  if(memory_get_usage() > $this->stripObjectsMemoryLimit) $item = 'out of memory';
202  $objects[$key = '@@object_' . md5(print_r($item,true)) . '@@'] = $item;
203  $item = $key;
204  }
205  }
206  catch(\Exception $e){
207  $item = '@@' . $e->getMessage() . '@@';
208  }
209  elseif(is_array($item) && ($level < $this->stripObjectsMaxDepth)) foreach($item as &$sub) $this->stripObjects($sub,$objects,$level + 1);
210  unset($sub);
211  }
212  /**
213  * Separate objects from a trace.
214  * @param array $trace
215  * @param array $objects Array to store objects in. Item will be replaced with name of key.
216  */
217  protected function stripTraceObjects(&$trace,&$objects){
218  foreach($trace as &$step){
219  if(array_key_exists('object',$step)) $this->stripObjects($step['object'],$objects);
220  if(array_key_exists('args',$step) && $step['args']) $this->stripObjects($step['args'],$objects);
221  else unset($step['args']);
222  }
223  unset($step);
224  }
225  /**
226  * Handle an (deliberately caused) external error.
227  */
228  public function externalError($message,$context = null){
229  if($this->debug){
230  print($message . "\n\n");
231  if(!\Rsi::commandLine()){
232  $trace = debug_backtrace();
233  $objects = [];
234  $this->stripTraceObjects($trace,$objects);
235  print_r($context);
236  print_r($trace);
237  print_r($objects);
238  }
239  $this->halt('External error');
240  }
241  $this->log->notice('External error: ' . $message,$context);
242  if($this->event->trigger(self::EVENT_EXTERNAL_ERROR,$this,$message) !== false){
243  http_response_code(400); //Bad Request
244  $_SESSION = [];
245  $this->halt();
246  }
247  }
248  /**
249  * Handle an internal error.
250  * @param string $message Error message.
251  * @param string $filename File in which the error occured.
252  * @param int $line_no Line at which the error occured.
253  * @param array $trace Backtrace of the moment the error occured.
254  */
255  public function internalError($message,$filename = null,$line_no = null,$trace = null){
256  try{
257  if(ob_get_length()) ob_clean();
258  $objects = [];
259  if($this->debug && (strpos($message,'Undefined variable: ') === 0)){
260  $this->_internalError = true;
261  $trace = false;
262  }
263  else{
264  if(!$trace) $trace = debug_backtrace();
265  $this->log->error($message,$filename,$line_no);
266  $this->stripTraceObjects($trace,$objects);
267  }
268  if($this->_internalError){
269  if($this->debug){
270  if(\Rsi::commandLine()){
271  print("$message ($filename:$line_no)\n");
272  print_r($trace);
273  }
274  else{
275  print("<code>$message ($filename:$line_no)<code><br><br>");
276  $dump = $this->dump();
277  print($dump->head() . $dump->source($filename,$line_no));
278  if($trace) print($dump->var('trace',$trace));
279  if($objects) print($dump->var('objects',$objects));
280  }
281  }
282  exit('Internal error');
283  }
284  $this->_internalError = true;
285 
286  if(!$this->debug) try{
287  $this->log->emergency($message,array_filter([
288  'filename' => $filename,
289  'lineNo' => $line_no,
290  'trace' => $trace,
291  'objects' => $objects,
292  'headers' => function_exists('getallheaders') ? getallheaders() : null,
293  'args' => $_SERVER['argv'] ?? null,
294  'GET' => $_GET,
295  'POST' => $_POST,
296  'COOKIE' => $_COOKIE,
297  'SESSION' => isset($_SESSION) ? $_SESSION : null
298  ]));
299  usleep(rand(0,10000000));
300  http_response_code(500);
301  if(is_file($template = $this->templatePath . 'error.php')) require($template);
302  }
303  catch(\Exception $e){
304  print('An unexpected error has occurred');
305  }
306  elseif(!\Rsi::commandLine() && is_file($template = $this->templatePath . 'debug.php')) require($template);
307  else print($message . ($filename ? " ($filename" . ($line_no ? "@$line_no" : null) . ")" : null) . "\n\n");
308  $this->halt();
309  }
310  catch(\Exception $e){ //the default exception handler is not called again on an unhandled exception
311  $this->internalError($e->getMessage(),$e->getFile(),$e->getLine(),$e->getTrace());
312  }
313  }
314 
315  protected function errorHash($message,$filename,$line_no){
316  return md5("errorHash($message,$filename,$line_no)");
317  }
318  /**
319  * Error handler.
320  * Throws an exception to get a unified error handling. Errors that are suppressed are only logged.
321  */
322  public function errorHandler($error_no,$message,$filename,$line_no){
323  if(error_reporting()){
324  $this->_errorHash = $this->errorHash($message,$filename,$line_no);
325  $this->_errorBacktrace = debug_backtrace();
326  throw new \ErrorException($message,$error_no,0,$filename,$line_no);
327  }
328  elseif($this->_initialized) $this->log->info($message,$filename,$line_no);
329  }
330  /**
331  * Exception handler.
332  * @param Exception $exception
333  */
334  public function exceptionHandler($exception){
335  $message = $exception->getMessage();
336  $filename = $exception->getFile();
337  $line_no = $exception->getLine();
338  if(!($exception instanceof \ErrorException) || ($this->errorHash($message,$filename,$line_no) != $this->_errorHash)) $this->_errorBacktrace = null;
339  $this->internalError($message,$filename,$line_no,$this->_errorBacktrace ?: $exception->getTrace());
340  }
341  /**
342  * Shutdown function.
343  * If a (non catched) error is the reason for the shutdown (e.g. a timeout), then the shutdown will be processed as an
344  * internal error.
345  * @see internalError()
346  */
347  public function shutdownFunction(){
348  if(
349  $this->_initialized && !$this->_internalError &&
350  ($error = error_get_last()) && !preg_match($this->ignoreErrors,$error['message']) &&
351  ($this->event->trigger(self::EVENT_SHUTDOWN,$this,$error) !== false)
352  ) $this->internalError($error['message'],$error['file'],$error['line']);
353  }
354  /**
355  * Version without hash.
356  * @param string $hash Hash from version string.
357  * @return string Version number.
358  */
359  public function version(&$hash = null){
360  list($version,$hash) = explode('-',$this->version . '-',2);
361  return $version;
362  }
363  /**
364  * Add new release notes to messages and log.
365  */
366  protected function releaseNotes(){
367  if(
368  $this->_releaseNotesFile &&
369  ($time = File::mtime($this->_releaseNotesFile)) &&
370  ($time != Record::get($_SESSION,$this->_releaseNotesKey)) &&
371  preg_match_all('/\\n- ([\\d\\.]+): (.*)/',file_get_contents($this->_releaseNotesFile),$matches,PREG_SET_ORDER)
372  ){
373  $messages = [];
374  foreach($matches as $match){
375  $version = $match[1] . '-' . str_replace(' ','-',strtolower(\Rsi\Str::codeName(crc32($match[2]))));
376  if($this->version == $version) $messages = [];
377  else $messages[] = "FRED™ version $version: " . str_replace('\\\\','\\',$match[2]);
378  }
379  if($messages){
380  foreach($messages as $message){
381  $this->message->warning($message);
382  $this->log->warning($message,__FILE__,__LINE__);
383  }
384  $this->log->notice("Upgraded to FRED™ version $version.",__FILE__,__LINE__);
385  }
386  else $_SESSION[$this->_releaseNotesKey] = $time;
387  }
388  }
389  /**
390  * Check for maintenance.
391  */
392  protected function maintenance(){
393  if($this->_maintenanceTime){
394  if(($delta = (strtotime($this->_maintenanceTime)) - $this->_startTime) <= 0){
395  if(is_file($template = $this->templatePath . 'maintenance.php')) require($template);
396  http_response_code(503); //Service Unavailable
397  $_SESSION = [];
398  $this->halt();
399  }
400  else foreach($this->_maintenanceMessage as $limit => $message){
401  list($limit,$type) = explode(':',$limit . ':warning');
402  if($delta <= $limit){
403  if($limit != \Rsi\Record::get($_SESSION,$this->_maintenanceKey)){
404  $this->message->add($type,$message,['time' => $this->_maintenanceTime,'delta' => round($delta / 60)] + compact('limit','type'));
405  $_SESSION[$this->_maintenanceKey] = $limit;
406  }
407  break;
408  }
409  }
410  }
411  }
412  /**
413  * Replace config keys with variables.
414  * @param mixed $config For arrays, values with keys starting with a '@' the value is replaced by the variable with that
415  * name (and the '@' is removed from the key).
416  * @return mixed
417  */
418  public function replaceVars($config){
419  if(!is_array($config)) return $config;
420  $result = [];
421  foreach($config as $key => $value) if(substr($key,0,1) == '@')
422  $result[substr($key,1)] = (substr($value,0,1) == '{') && (substr($value,-1) == '}')
423  ? json_decode($this->vars->value(substr($value,1,-1)),true)
424  : $this->vars->value($value);
425  else $result[$key] = $this->replaceVars($value);
426  return $result;
427  }
428  /**
429  * Get a dumper.
430  * @return \\Rsi\\Dump
431  */
432  public function dump(){
433  $config = $this->config('dump');
434  $class_name = $config['className'] ?? 'Rsi\\Dump';
435  return new $class_name($config);
436  }
437  /**
438  * Get a value from the configuration.
439  * @param string|array $key Key to get the value from the configuration (array = nested).
440  * @param mixed $default Default value if the key does not exist.
441  * @return mixed Found value, or default value if the key does not exist.
442  */
443  public function config($key,$default = null){
444  return $this->replaceVars(Record::get($this->_config,$key,$default));
445  }
446 
447  protected function defaultComponentClassName($name){
448  return $this->defaultComponentNamespace . '\\' . ucfirst($name);
449  }
450  /**
451  * Get a component.
452  * @param string $name Name of the component.
453  * @return Fred\\Component
454  */
455  public function component($name){
456  if(!$this->has($name)){
457  $config = $this->config($name);
458  if($config && !is_array($config)){
459  if(is_callable($config)) $config = call_user_func($config,$name);
460  if(is_string($config)) $config = require($config);
461  }
462  if(!class_exists($class_name = Record::get($config,'className') ?: $this->defaultComponentClassName($name)))
463  throw new \Exception("Unknown component '$name' ($class_name)");
464  $this->_components[$name] = new $class_name($this,array_merge(['name' => $name],$config ?: []));
465  }
466  return $this->_components[$name];
467  }
468  /**
469  * Get a component if there is a configuration entry for it.
470  * @param string $name Name of the component.
471  * @return Fred\\Component False if there is no configuration.
472  */
473  public function may($name){
474  return array_key_exists($name,$this->_components) || array_key_exists($name,$this->_config) || class_exists($this->defaultComponentClassName($name))
475  ? $this->component($name)
476  : false;
477  }
478  /**
479  * Get a component if it already exists.
480  * @param string $name Name of the component.
481  * @return Fred\\Component False if it did not exist.
482  */
483  public function has($name){
484  return $this->_components[$name] ?? false;
485  }
486  /**
487  * Public configuration.
488  * @return array Public configuration for all components (key = component name, value = public component configuration).
489  */
490  public function clientConfig(){
491  $config = [];
492  if($this->debug) $config['debug'] = true;
493  foreach($this->_components as $name => $component) $config[$name] = $component->clientConfig();
494  return array_filter($config);
495  }
496 
497  protected function setAutoloadNamespaces($namespaces){
498  $this->_autoloadNamespaces = array_merge($this->_autoloadNamespaces,$namespaces);
499  }
500 
501  protected function setAutoloadClasses($classes){
502  $this->_autoloadClasses = array_merge($this->_autoloadClasses,$classes);
503  }
504 
505  protected function setAutoloadFiles($classes){
506  $this->_autoloadFiles = array_merge($this->_autoloadFiles,$classes);
507  }
508 
509  protected function setTimeLimit($value){
510  set_time_limit($value);
511  $this->_timeLimit = microtime(true) + $value;
512  }
513 
514  protected function getTimeLimit(){
515  if($this->_timeLimit === null) $this->_timeLimit = $this->_startTime + ini_get('max_execution_time');
516  return max(0,$this->_timeLimit - microtime(true));
517  }
518 
519  protected function _get($key){
520  return $this->component($key);
521  }
522 
523  public function __call($func_name,$params){
524  return call_user_func_array([$this->entity,'item'],array_merge([$func_name],$params));
525  }
526 
527 }
has($name)
Get a component if it already exists.
Definition: Fred.php:483
_get($key)
Definition: Fred.php:519
config($key, $default=null)
Get a value from the configuration.
Definition: Fred.php:443
clientConfig()
Public configuration.
Definition: Fred.php:490
init()
Initialize the framework.
Definition: Fred.php:84
Basic object.
Definition: Thing.php:13
defaultComponentClassName($name)
Definition: Fred.php:447
maintenance()
Check for maintenance.
Definition: Fred.php:392
may($name)
Get a component if there is a configuration entry for it.
Definition: Fred.php:473
version(&$hash=null)
Version without hash.
Definition: Fred.php:359
exceptionHandler($exception)
Exception handler.
Definition: Fred.php:334
dump()
Get a dumper.
Definition: Fred.php:432
saveSharedCache()
Definition: Fred.php:120
__construct($config)
Initialize the framework.
Definition: Fred.php:70
__call($func_name, $params)
Definition: Fred.php:523
shutdownFunction()
Shutdown function.
Definition: Fred.php:347
setAutoloadClasses($classes)
Definition: Fred.php:501
Framework for Rapid and Easy Development.
Definition: Fred.php:18
externalError($message, $context=null)
Handle an (deliberately caused) external error.
Definition: Fred.php:228
replaceVars($config)
Replace config keys with variables.
Definition: Fred.php:418
stripTraceObjects(&$trace, &$objects)
Separate objects from a trace.
Definition: Fred.php:217
errorHash($message, $filename, $line_no)
Definition: Fred.php:315
setAutoloadNamespaces($namespaces)
Definition: Fred.php:497
setTimeLimit($value)
Definition: Fred.php:509
releaseNotes()
Add new release notes to messages and log.
Definition: Fred.php:366
setAutoloadFiles($classes)
Definition: Fred.php:505
errorHandler($error_no, $message, $filename, $line_no)
Error handler.
Definition: Fred.php:322
component($name)
Get a component.
Definition: Fred.php:455
halt($status=null)
End the request.
Definition: Fred.php:179
stripObjects(&$item, &$objects, $level=0)
Separate objects.
Definition: Fred.php:188
internalError($message, $filename=null, $line_no=null, $trace=null)
Handle an internal error.
Definition: Fred.php:255
getTimeLimit()
Definition: Fred.php:514
autoload($class_name)
Autoloader.
Definition: Fred.php:151