Getting Yii to play nice with PHP 5.3 namespaces

I have been struggling with using PHP 5.3 namespaces with Yii. The Yii autoloader is passed the namespace AND the class name, so it is not seeing \foo\bar\class as an imported class.

I have tried creating my own autoloader and registered it using Yii::registerAutoloader(), but for some reason the Yii autoloader is running first some of the time.

If anyone has solved the problem, or knows of a strategy that works, I would appreciate it if you would share it with me.

What registerAutoloader does is adding another autoloader. What you need is to replace it completely, I think:




spl_autoload_unregister(array('YiiBase','autoload'));

spl_autoload_register($callback);



btw., it will be great if you will share your autoloader along with some usage examples.

You gave me a new angle to try, since the Yii autoloader loads first sometimes, unregistering should hopefully fix that.

I will write my own autoloader that detects namespaces and loads them, otherwise it will call the Yii::autoload() explicitly instead of letting SPL fall through.

I will post the results of my tests, with code.

After poking around some more I came across Symfony’s Universal Class Loader which is working great. I am loading and configuring it before I include yii.php and it just works, and Yii’s autoloader continues to work well also.

This more advanced autoloader looks so simple, it would be great if Yii had something like it for those of us that use PHP 5.3 and/or components from more than one framework. (Yii is my core framework, but I am a hardcore DRY guy.)

Here is what Symfony’s autoloader looks like if anyone wants to use it or port it.

http://github.com/fabpot/symfony/blob/master/src/Symfony/Framework/UniversalClassLoader.php

I just spent close to two hours trying everything in creation to get some namespaced classes to autoload. No dice.

My classes are in two-level namespaces, e.g. "vendor\package\Class", standard PSR-2 conventions - I cannot for the life of me get these to autoload.

The documentation on this subject is shifty - 3 paragraphs talking about how to autoload “application\components\GoogleMap”, but then the example shown at the end of that whole discussion is a simple 1-level namespace. And yes, that works - I can map a root-level namespace to a folder and get that to autoload. But I have two packages from the same vendor, so that’s not good enough.

Has this problem not been addressed in later versions of Yii?

Do I really have to circumvent Yii’s autoloader and pull in the one from Zend or Symfony like I’ve seen it explained in various blog posts??

Okay, I created my own very simple autoloader - this handles only namespaces (and class-names) that have been explicitly registered with it, so it doesn’t replace (or interfere with) the Yii autoloader.

I find it astounding that Yii to this day still doesn’t have an actual autoloader - a class you can extend.

Bottom line is, using namespaces with Yii is painful and takes WAY too much work - this is an ordinary, basic requirement for any developer these days, nearly all modern third-party packages use namespaces by now.

This should not require any work or thought at all, and certainly should not involve pulling third-party packages from other frameworks.

What. The. Hell.

@mindplay, this is a godsend! Like you I wasted hours trying to import a third-party library that uses extensive namespacing to no avail. Your solution with GAutoloader.php works like a charm. Thank you!

FYI, I used it to import the Balanced Payments library that I had previously posted a forum question about here:

http://www.yiiframework.com/forum/index.php/topic/38152-integrating-balanced-payments-php-library-uses-namespaces-in-yii/

Just to clarify, your custom yii.php file goes in the core yii dir (not docroot) and replaces the standard yii.php file that docroot/index.php includes right? Is there a “pure” solution that doesn’t involve hacking core (if not, I can certainly live with this)? Also, the constant YII_PATH is defined in YiiBase.php so how are you using it in yii.php before YiiBase.php is included?

You don’t need to hack the core - just create your own “yii.php” somewhere outside the code. If you look at the standard Yii class, it’s just an empty class that extends YiiBase. It’s designed this way, so that you can extend YiiBase with your own Yii class, which enables you to add or override methods in the static Yii class.

So you don’t need to hack any of the files in the actual Yii codebase, just ignore the standard Yii-class and load your own instead.

That’s an artifact of the way I set up my own Yii apps, you can substitute that as needed. (I have a separate environment setup, configuration and bootstrapping system that loads before Yii does…)

I went down this path… Unfortunately because of the poorly coded YiiBase.php (i.e. use of self:: and [size=2]Yii:: instead of [/size][size=2]static::) no overridden methods ever get called.[/size]

[size=2]

[/size]

[size=2]My solution was to just to yank the Yii autoloader completely and use the composer one. I’ve almost got it working and will share my results.[/size]

[size=2]

[/size]

[size=2]I’m having issues now where the autoloader is trying to prefix a namespace to global PHP classes used inside Yii (i.e. \Iterator in CListIterator)[/size]

I agree, it’s not sound, but late static binding was only introduced in PHP 5.3, Yii still targets PHP 5.1, and unfortunately has not gotten the maintenance it should have since it’s initial release.

Having made the (also questionable) design choice of using a static class, they did the best they could under the circumstances, which was to have this vestigial Yii-class in place, so that at least you can override certain methods by loading a different implementation of that class.

For most common purposes, such as overriding createWebApplication(), it doesn’t cause any problems, because no code ever calls YiiBase::anything() - all calls are made to Yii::something() which by default will bubble up to the YiiBase parent class, unless you provide your own implementation. But yes, any method that references self::something() (or worse, static private variables) from inside the YiiBase class, will have to be completely replaced (cut and paste!) if you want to “override” the default implementation.

The solution I posted does work - I’m using it in production on two different sites by now.

It’s worth noting that support for autoloading namespaces has been added to YiiBase.php by Qiang in the 1.1.13 release:

  • Enh #1481: Added support for autoloading namespaced classes (Qiang)

(change log - http://static.yiiframework.com/files/CHANGELOG-1.1.13.txt)

Actually that was added in 1.1.5, but it never seemed to work for anybody.

I know that the post is very old but maybe it will help somebody.

There is no need for write any complicated autoloaders. Yii has support for namespaced classes but it is well hidden :)

The solution is described here: http://shiki.me/blog…-yii-framework/

Personally I prefer a mixture of above solution with Yii application configuration. I’m going to explain it basing on example.

Example

I would like to add Complexify class (https://github.com/m...php-complexify/) which is using namespace Complexify.

The first step is to update aliases parameter of the application in the configuration file to register Complexify alias and point it to a directory where our namespaced class is:





      'aliases' => array(

        'common' => $root . DIRECTORY_SEPARATOR . 'common',

        'Complexify' => $root . DIRECTORY_SEPARATOR . 'common' . DIRECTORY_SEPARATOR . 'extensions' . DIRECTORY_SEPARATOR . 'complexify'

      ),



Note that:

  • $root points to my root directory

  • aliasses do not point the class ifself but only the folder where the namespaced class is stored;

Now I can create the class using namespace:




$check = new \Complexify\Complexify();



thanks ;)