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; } ?>