* * @license http://opensource.org/licenses/bsd-license.php BSD * * @version $Id$ * */ class Solar_Class { /** * * Parent hierarchy for all classes. * * We keep track of this so configs, locale strings, etc. can be * inherited properly from parent classes, and so we don't need to * recalculate it on each request. * * @var array * */ protected static $_parents = array(); /** * * Loads a class or interface file from the include_path. * * Thanks to Robert Gonzalez for the report leading to this method. * * @param string $name A Solar (or other) class or interface name. * * @return void * * @todo Add localization for errors * */ public static function autoload($name) { // did we ask for a non-blank name? if (trim($name) == '') { throw Solar::exception( 'Solar_Class', 'ERR_AUTOLOAD_EMPTY', 'No class or interface named for loading.', array('name' => $name) ); } // pre-empt further searching for the named class or interface. // do not use autoload, because this method is registered with // spl_autoload already. $exists = class_exists($name, false) || interface_exists($name, false); if ($exists) { return; } // convert the class name to a file path $file = Solar_Class::nameToFile($name); // include the file and check for failure. we use Solar_File::load() // instead of require() so we can see the exception backtrace. Solar_File::load($file); // if the class or interface was not in the file, we have a problem. // do not use autoload, because this method is registered with // spl_autoload already. $exists = class_exists($name, false) || interface_exists($name, false); if (! $exists) { throw Solar::exception( 'Solar_Class', 'ERR_AUTOLOAD_FAILED', 'Class or interface does not exist in loaded file', array('name' => $name, 'file' => $file) ); } } /** * * Converts a namespace-and-classname to a file path. * * Implements PSR-0 as defined by the PHP Project Interoperability Group. * * * * @param string $spec The namespace-and-classname. * * @return string The converted file path. * */ public static function nameToFile($spec) { // using namespaces? (look for last namespace separator) $pos = strrpos($spec, '\\'); if ($pos === false) { // no namespace, class portion only $namespace = ''; $class = $spec; } else { // pre-convert namespace portion to file path $namespace = substr($spec, 0, $pos); $namespace = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR; // class portion $class = substr($spec, $pos + 1); } // convert class underscores, and done return $namespace . str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php'; } /** * * Returns an array of the parent classes for a given class. * * @param string|object $spec The class or object to find parents * for. * * @param bool $include_class If true, the class name is element 0, * the parent is element 1, the grandparent is element 2, etc. * * @return array * */ public static function parents($spec, $include_class = false) { if (is_object($spec)) { $class = get_class($spec); } else { $class = $spec; } // do we need to load the parent stack? if (empty(Solar_Class::$_parents[$class])) { // use SPL class_parents(), which uses autoload by default. use // only the array values, not the keys, since that messes up BC. $parents = array_values(class_parents($class)); Solar_Class::$_parents[$class] = array_reverse($parents); } // get the parent stack $stack = Solar_Class::$_parents[$class]; // add the class itself? if ($include_class) { $stack[] = $class; } // done return $stack; } /** * * Returns the directory for a specific class, plus an optional * subdirectory path. * * @param string|object $spec The class or object to find parents * for. * * @param string $sub Append this subdirectory. * * @return string The class directory, with optional subdirectory. * */ public static function dir($spec, $sub = null) { if (is_object($spec)) { $class = get_class($spec); } else { $class = $spec; } // convert the class to a base directory to stem from $base = str_replace('_', DIRECTORY_SEPARATOR, $class); // does the directory exist? $dir = Solar_Dir::exists($base); if (! $dir) { throw Solar::exception( 'Solar_Class', 'ERR_NO_DIR_FOR_CLASS', 'Directory does not exist', array('class' => $class, 'base' => $base) ); } else { return Solar_Dir::fix($dir . DIRECTORY_SEPARATOR. $sub); } } /** * * Returns the path to a file under a specific class. * * @param string|object $spec The class or object to use as the base path. * * @param string $file Append this file path. * * @return string The path to the file under the class. * */ public static function file($spec, $file) { $dir = Solar_Class::dir($spec); return Solar_File::exists($dir . $file); } /** * * Find the vendor name of a given class or object; this is effectively * the part of the class name that comes before the first underscore. * * @param mixed $spec An object, or a class name. * * @return string The vendor name of the class or object. * */ public static function vendor($spec) { if (is_object($spec)) { $class = get_class($spec); } else { $class = $spec; } // find the first underscore $pos = strpos($class, '_'); if ($pos !== false) { // return the part up to the first underscore return substr($class, 0, $pos); } else { // no underscores, must be an arch-class return $class; } } /** * * Find the vendors of a given class or object and its parents. * * @param mixed $spec An object, or a class name. * * @return array The vendor names of the class or object hierarchy. * */ public static function vendors($spec) { // vendor listing $stack = array(); // get the list of parents $parents = Solar_Class::parents($spec, true); // look through vendor names $old = null; foreach ($parents as $class) { $new = Solar_Class::vendor($class); if ($new != $old) { // not the same, add the current vendor name and suffix $stack[] = $new; } // retain old vendor for next loop $old = $new; } return $stack; } }