<?php
/**
*
* The Solar arch-class provides static methods needed throughout the
* framework environment.
*
* @category Solar
*
* @package Solar Foundation classes for all of Solar.
*
* @author Paul M. Jones <hide@address.com>
*
* @version $Id: Solar.php 4551 2010-05-04 21:38:04Z pmjones $
*
* @license http://opensource.org/licenses/bsd-license.php BSD
*
* Copyright (c) 2005-2007, Paul M. Jones. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* * Neither the name of the Solar project nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
class Solar
{
/**
*
* Default config values for the Solar arch-class.
*
* @config array ini_set An array of key-value pairs where the key is an
* [[php::ini_set | ]] key, and the value is the value for that setting.
*
* @config array registry_set An array of key-value pairs to use in pre-setting registry
* objects. The key is a registry name to use. The value is either
* a string class name, or is a sequential array where element 0 is
* a string class name and element 1 is a configuration array for that
* class. Cf. [[Solar_Registry::set()]].
*
* @config array start Run these scripts at the end of Solar::start().
*
* @config array stop Run these scripts in Solar::stop().
*
* @config string system The system directory path.
*
* @var array
*
*/
protected static $_Solar = array(
'ini_set' => array(),
'registry_set' => array(),
'start' => array(),
'stop' => array(),
'system' => null,
);
/**
*
* The Solar system root directory.
*
* @var string
*
*/
public static $system = null;
/**
*
* Status flag (whether Solar has started or not).
*
* @var bool
*
*/
protected static $_status = false;
/**
*
* Constructor is disabled to enforce a singleton pattern.
*
*/
final private function __construct() {}
/**
*
* Starts Solar: loads configuration values and and sets up the environment.
*
* Note that this method is overloaded; you can pass in different
* value types for the $config parameter.
*
* * `null|false` -- This will not load any new configuration values;
* you will get only the default values defined in the Solar class.
*
* * `string` -- The string is treated as a path to a Solar.config.php
* file; the return value from that file will be used for [[Solar_Config::load()]].
*
* * `array` -- This will use the passed array for the [[Solar_Config::load()]]
* values.
*
* * `object` -- The passed object will be cast as an array, and those
* values will be used for [[Solar_Config::load()]].
*
* Here are some examples of starting with alternative configuration parameters:
*
* {{code: php
* require_once 'Solar.php';
*
* // don't load any config values at all
* Solar::start();
*
* // point to a config file (which returns an array)
* Solar::start('/path/to/another/config.php');
*
* // use an array as the config source
* $config = array(
* 'Solar' => array(
* 'ini_set' => array(
* 'error_reporting' => E_ALL,
* ),
* ),
* );
* Solar::start($config);
*
* // use an object as the config source
* $config = new StdClass;
* $config->Solar = array(
* 'ini_set' => array(
* 'error_reporting' => E_ALL,
* ),
* );
* Solar::start($config);
* }}
*
* @param mixed $config The configuration source value.
*
* @return void
*
* @see Solar::cleanGlobals()
*
*/
public static function start($config = null)
{
// don't re-start if we're already running.
if (Solar::$_status) {
return;
}
// make sure these classes are loaded
$list = array(
'Base',
'Class',
'Config',
'File',
);
$dir = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'Solar';
foreach ($list as $name) {
// don't use the autoloader when checking for existence
if (! class_exists('Solar_' . $name, false)) {
require $dir . DIRECTORY_SEPARATOR . "$name.php";
}
}
// register autoloader
spl_autoload_register(array('Solar_Class', 'autoload'));
// clear out registered globals
if (ini_get('register_globals')) {
Solar::cleanGlobals();
}
// load config values
Solar_Config::load($config);
// make sure we have the Solar arch-class configs
$arch_config = Solar_Config::get('Solar');
if (! $arch_config) {
Solar_Config::set('Solar', null, Solar::$_Solar);
} else {
Solar_Config::set('Solar', null, array_merge(
Solar::$_Solar,
(array) $arch_config
));
}
// set the system directory
Solar::$system = Solar_Config::get('Solar', 'system');
// process ini settings from config file
$settings = Solar_Config::get('Solar', 'ini_set', array());
foreach ($settings as $key => $val) {
ini_set($key, $val);
}
// user-defined registry entries
$register = Solar_Config::get('Solar', 'registry_set', array());
foreach ($register as $name => $list) {
// make sure we have the class-name and a config
$list = array_pad((array) $list, 2, null);
list($spec, $config) = $list;
// register the item
Solar_Registry::set($name, $spec, $config);
}
// Solar itself needs these default objects registered ...
$name_class = array(
'inflect' => 'Solar_Inflect',
'locale' => 'Solar_Locale',
'rewrite' => 'Solar_Uri_Rewrite',
'request' => 'Solar_Request',
'response' => 'Solar_Http_Response',
);
// ... but only if not already registered by the user.
foreach ($name_class as $name => $class) {
if (! Solar_Registry::exists($name)) {
Solar_Registry::set($name, $class);
}
}
// run any 'start' hooks
$hooks = Solar_Config::get('Solar', 'start', array());
Solar::callbacks($hooks);
// and we're done!
Solar::$_status = true;
}
/**
*
* Stops Solar: runs stop scripts and cleans up the Solar environment.
*
* @return void
*
*/
public static function stop()
{
// run any 'stop' hook methods
$hooks = Solar_Config::get('Solar', 'stop', array());
Solar::callbacks($hooks);
// unregister autoloader
spl_autoload_unregister(array('Solar_Class', 'autoload'));
// reset the status flag, and we're done.
Solar::$_status = false;
}
/**
*
* Runs a series of callbacks using call_user_func_array().
*
* The callback array looks like this:
*
* {{code: php
* $callbacks = array(
* // static method call
* array('Class_Name', 'method', $param1, $param2, ...),
*
* // instance method call on a registry object
* array('registry-key', 'method', $param1, $param2, ...),
*
* // instance method call
* array($object, 'method', $param1, $param2, ...),
*
* // function call
* array(null, 'function', $param1, $param2, ...),
*
* // file include, as in previous versions of Solar
* 'path/to/file.php',
* );
* }}
*
* @param array $callbacks The array of callbacks.
*
* @return void
*
* @see start()
*
* @see stop()
*
*/
public static function callbacks($callbacks)
{
foreach ((array) $callbacks as $params) {
// include a file as in previous versions of Solar
if (is_string($params)) {
Solar_File::load($params);
continue;
}
// $spec is an object instance, class name, or registry key
settype($params, 'array');
$spec = array_shift($params);
if (! is_object($spec)) {
// not an object, so treat as a class name ...
$spec = (string) $spec;
// ... unless it's a registry key.
if (Solar_Registry::exists($spec)) {
$spec = Solar_Registry::get($spec);
}
}
// the method to call on $spec
$func = array_shift($params);
// make the call
if ($spec) {
call_user_func_array(array($spec, $func), $params);
} else {
call_user_func_array($func, $params);
}
}
}
/**
*
* Returns the API version for Solar.
*
* @return string A PHP-standard version number.
*
*/
public static function apiVersion()
{
return '1.1.1';
}
/**
*
* Convenience method to instantiate and configure an object.
*
* @param string $class The class name.
*
* @param array $config Configuration value overrides, if any.
*
* @return object A new instance of the requested class.
*
*/
public static function factory($class, $config = null)
{
Solar_Class::autoload($class);
$obj = new $class($config);
// is it an object factory?
if ($obj instanceof Solar_Factory) {
// return an instance from the object factory
return $obj->factory();
}
// return the object itself
return $obj;
}
/**
*
* Combination dependency-injection and service-locator method; returns
* a dependency object as passed, or an object from the registry, or a
* new factory instance.
*
* @param string $class The dependency object should be an instance of
* this class. Technically, this is more a hint than a requirement,
* although it will be used as the class name if [[Solar::factory()]]
* gets called.
*
* @param mixed $spec If an object, check to make sure it's an instance
* of $class. If a string, treat as a [[Solar_Registry::get()]] key.
* Otherwise, use this as a config param to [[Solar::factory()]] to
* create a $class object.
*
* @return object The dependency object.
*
*/
public static function dependency($class, $spec)
{
// is it an object already?
if (is_object($spec)) {
return $spec;
}
// check for registry objects
if (is_string($spec)) {
return Solar_Registry::get($spec);
}
// not an object, not in registry.
// try to create an object with $spec as the config
return Solar::factory($class, $spec);
}
/**
*
* Generates a simple exception, but does not throw it.
*
* This method attempts to automatically load an exception class
* based on the error code, falling back to parent exceptions
* when no specific exception classes exist. For example, if a
* class named 'Vendor_Example' extended from 'Vendor_Base' throws an
* exception or error coded as 'ERR_FILE_NOT_FOUND', the method will
* attempt to return these exception classes in this order ...
*
* 1. Vendor_Example_Exception_FileNotFound (class specific)
*
* 2. Vendor_Base_Exception_FileNotFound (parent specific)
*
* 3. Vendor_Example_Exception (class generic)
*
* 4. Vendor_Base_Exception (parent generic)
*
* 5. Vendor_Exception (generic for all of vendor)
*
* The final fallback is always the generic Solar_Exception class.
*
* Note that this method only generates the object; it does not
* throw the exception.
*
* {{code: php
* $class = 'My_Example_Class';
* $code = 'ERR_SOMETHING_WRONG';
* $text = 'Something is wrong.';
* $info = array('foo' => 'bar');
* $exception = Solar::exception($class, $code, $text, $info);
* throw $exception;
* }}
*
* In general, you shouldn't need to use this directly in classes
* extended from [[Solar_Base]]. Instead, use
* [[Solar_Base::_exception() | $this->_exception()]] for automated
* picking of the right exception class from the $code, and
* automated translation of the error message.
*
* @param string|object $spec The class name (or object) that generated
* the exception.
*
* @param mixed $code A scalar error code, generally a string.
*
* @param string $text Any error message text.
*
* @param array $info Additional error information in an associative
* array.
*
* @return Solar_Exception
*
*/
public static function exception($spec, $code, $text = '',
$info = array())
{
// is the spec an object?
if (is_object($spec)) {
// yes, find its class
$class = get_class($spec);
} else {
// no, assume the spec is a class name
$class = (string) $spec;
}
// drop 'ERR_' and 'EXCEPTION_' prefixes from the code
// to get a suffix for the exception class
$suffix = $code;
if (strpos($suffix, 'ERR_') === 0) {
$suffix = substr($suffix, 4);
} elseif (strpos($suffix, 'EXCEPTION_') === 0) {
$suffix = substr($suffix, 10);
}
// convert "STUDLY_CAP_SUFFIX" to "Studly Cap Suffix" ...
$suffix = ucwords(strtolower(str_replace('_', ' ', $suffix)));
// ... then convert to "StudlyCapSuffix"
$suffix = str_replace(' ', '', $suffix);
// build config array from params
$config = array(
'class' => $class,
'code' => $code,
'text' => $text,
'info' => (array) $info,
);
// get all parent classes, including the class itself
$stack = array_reverse(Solar_Class::parents($class, true));
// add the vendor namespace to the stack as a fallback, even though
// it's not strictly part of the hierarchy, for generic vendor-wide
// exceptions.
$vendor = Solar_Class::vendor($class);
if ($vendor != 'Solar') {
$stack[] = $vendor;
}
// add Solar as the final fallback
$stack[] = 'Solar';
// track through class stack and look for specific exceptions
foreach ($stack as $class) {
try {
$obj = Solar::factory("{$class}_Exception_$suffix", $config);
return $obj;
} catch (Exception $e) {
// do nothing
}
}
// track through class stack and look for generic exceptions
foreach ($stack as $class) {
try {
$obj = Solar::factory("{$class}_Exception", $config);
return $obj;
} catch (Exception $e) {
// do nothing
}
}
// last resort: a generic Solar exception
return Solar::factory('Solar_Exception', $config);
}
/**
*
* Dumps a variable to output.
*
* Essentially, this is an alias to the Solar_Debug_Var::dump()
* method, which buffers the [[php::var_dump | ]] for a variable,
* applies some simple formatting for readability, [[php::echo | ]]s
* it, and prints with an optional label. Use this for
* debugging variables to see exactly what they contain.
*
* @param mixed $var The variable to dump.
*
* @param string $label A label for the dumped output.
*
* @return void
*
*/
public static function dump($var, $label = null)
{
$obj = Solar::factory('Solar_Debug_Var');
$obj->display($var, $label);
}
/**
*
* Cleans the global scope of all variables that are found in other
* super-globals.
*
* This code originally from Richard Heyes and Stefan Esser.
*
* @return void
*
*/
public static function cleanGlobals()
{
$list = array(
'GLOBALS',
'_POST',
'_GET',
'_COOKIE',
'_REQUEST',
'_SERVER',
'_ENV',
'_FILES',
);
// Create a list of all of the keys from the super-global values.
// Use array_keys() here to preserve key integrity.
$keys = array_merge(
array_keys($_ENV),
array_keys($_GET),
array_keys($_POST),
array_keys($_COOKIE),
array_keys($_SERVER),
array_keys($_FILES),
// $_SESSION is null if you have not started the session yet.
// This insures that a check is performed regardless.
isset($_SESSION) && is_array($_SESSION) ? array_keys($_SESSION) : array()
);
// Unset the globals.
foreach ($keys as $key) {
if (isset($GLOBALS[$key]) && ! in_array($key, $list)) {
unset($GLOBALS[$key]);
}
}
}
}