It would be very useful to be able to package many classes into a single file and still use Yii's import declarations. This would also be in keeping with Yii's ability to dramatically increase performance using APC and the stated purpose of yiilite.
I have been hacking on it and have a potential solution, if you think it would be beneficial. I'll use the tired example of the class nobody has ever really built: Animal!
external class file (apppath/something/Animal.php)
<?php
class Animal {
//snip
}
class Duck extends Animal {
//snip
}
class MutantDuck extends Duck implements Mutant {
//snip
}
?>
controller:
<?php
class AwesomeController extends CController
{
function actionDoWonderfulThings()
{
if (isset($_REQUEST['inHell']))
$ohDear = new MutantDuck();
else
$sweet = new WonderfulThing();
}
}
?>
If I put the appropriate import declarations at the top of the controller, I currently have to specify the force include parameter:
<?php Yii::import("application.something.Animal", true); ?>
in order to expect that the MutantDuck will be available in the event that $_REQUEST[‘inHell’] is set. That means I’m loading a whole bunch of classes I might not necessarily need - if we’re not in hell, I can’t imagine there would be too many mutant ducks! ![]()
The idea I had to solve the problem is to use a different character to distinguish between a path (which we're using '.' for), and a class name which is the child of a file (for which i thought we could use '/').
The following specifies that the class MutantDuck, present in the file Animal, should be imported:
<?php Yii::import("application.something.Animal/MutantDuck"); ?>
The disadvantage is that we need to have an import declaration for each child class we want to be autoloadable… for example, if the controller needed to instantiate a regular Duck under certain circumstances, we would need to import both:
<?php
Yii::import("application.something.Animal/Duck");
Yii::import("application.something.Animal/MutantDuck");
?>
The following changes can be made to YiiBase::import() and YiiBase::getPathOfAlias() to support it (but i'm sure there will be a better way to do it). The changes are not huge, but I have not thoroughly tested them yet:
<?php
public static function import($alias,$forceInclude=false)
{
if(isset(self::$_imports[$alias])) // previously imported
return self::$_imports[$alias];
$classPos=strrpos($alias,'.');
$childPos=strrpos($alias,'/');
if(isset(self::$_coreClasses[$alias]) || ($classPos===false && $childPos===false)) // a simple class name
{
self::$_imports[$alias]=$alias;
if($forceInclude && !class_exists($alias,false))
{
if(isset(self::$_coreClasses[$alias])) // a core class
require_once(YII_PATH.self::$_coreClasses[$alias]);
else
require_once($alias.'.php');
}
return $alias;
}
if(($className=(string)substr($alias,max($classPos,$childPos)+1))!=='*' && class_exists($className,false))
return self::$_imports[$alias]=$className;
if(($path=self::getPathOfAlias($alias))!==false)
{
if($className!=='*')
{
self::$_imports[$alias]=$className;
if($forceInclude)
require_once($path.'.php');
else
self::$_classes[$className]=$path.'.php';
return $className;
}
else // a directory
{
set_include_path(get_include_path().PATH_SEPARATOR.$path);
return self::$_imports[$alias]=$path;
}
}
else
throw new CException(Yii::t('yii#Alias "{alias}" is invalid. Make sure it points to an existing directory or file.',
array('{alias}'=>$alias)));
}
public static function getPathOfAlias($alias)
{
if(isset(self::$_aliases[$alias]))
return self::$_aliases[$alias];
else if(($pos=strpos($alias,'.'))!==false)
{
$pathAlias=$alias;
// remove the child class portion of the alias
if(($childPos=strrpos($alias,"/"))!==false)
$pathAlias = substr($alias,0,$childPos);
$rootAlias=substr($alias,0,$pos);
if(isset(self::$_aliases[$rootAlias]))
return self::$_aliases[$alias]=rtrim(self::$_aliases[$rootAlias].DIRECTORY_SEPARATOR.str_replace('.',DIRECTORY_SEPARATOR,substr($pathAlias,$pos+1)),'*'.DIRECTORY_SEPARATOR);
}
return self::$_aliases[$alias]=false;
}
?>