22

PHPExcel in Yii

by MrSoundless 23. April 2011 23:35

A little while, I had to build a .xls export function into a Yii application. I decided to use PHPExcel for this, since I had used it before. It's also probably the best Excel importer/exporter for PHP. I soon figured that this didn't go as smooth as I expected.

The problem

Yii kept throwing an Exception about it not being able to find the file to import. Of course it couldn't find the file because PHPExcel's importer was supposed to handle that. I searched around and found a couple of posts like this one. This failed for me because between the spl_autoload_unregister and spl_autoload_register calls, I had to get info from a Model through another Model. When you try to reach a related Model, Yii actually tries to autoload that Model. But since we just unregistered the yii autoloader by calling spl_autoload_unregister, the Yii import was never called and so autoloading the Model failed.

The solution

So I went on and figured out that the reason they unregistered the Yii autoloader, was because the Yii autoloader was called before the PHPExcel autoloader. Since the Yii autoloader throws an Exception when it can't find a file and PHPExcel doesn't, the solution was simple: switch the order in which the autoloaders get registered. I decided to do this by changing the PHPExcel Autoloader's register method from this:

 

<?php
	public static function Register() {
		return spl_autoload_register(array('PHPExcel_Autoloader', 'Load'));
	}	//	function Register()

 

To this:

 

<?php
	public static function Register() {
		$functions = spl_autoload_functions();
		
		foreach($functions as $function)
			spl_autoload_unregister($function);
		
		$functions=array_merge(array(array('PHPExcel_Autoloader', 'Load')), $functions);
		
		foreach($functions as $function)
			$x = spl_autoload_register($function);
		
		return $x;
	}	//	function Register()

 

So this is what happens:

  1. All registered autoload functions are saved in $functions.
  2. Then they all are unregistered.
  3. Next we put PHPExcel_Autoloader in front of array we named $functions.
  4. We register all of them in that same order.
  5. And we return the $functions array

The thing with this is, you can use this everywhere. So if you change the PHPExcel autoloader and you decide to use it later in a project which is not built with Yii, it'll still work fine! From now on PHPExcel's AutoLoader will never be a problem again, no matter which framework you use!

All I have to do now is create a patch and submit it to http://phpexcel.codeplex.com/ so no one will ever have that problem again. Or maybe I should just wait till I have at least 100 comments before I do that Wink

 

Click the link below to download the fixed Autoloader.php for PHPExcel 1.7.3c

Autoloader for PHPExcel 1.7.3c Autoloader.php (1.84 kb)

Autoloader for PHPExcel 1.7.6  Autoloader.php (2.15 kb)

Tags: , ,

PHP | Yii

Comments (22) -

Sue
Sue
6/12/2011 2:38:06 AM #

Valuable info. Exactly what I was looking for. i'll check back soon again.

Angel
Angel
6/15/2011 12:02:51 AM #

So, how does your solution affect the controller code (oposed to the solution in www.yiiframework.com/.../)? Are the calls to the spl_load_unregister/register functions still needed in the controller?

I have tried your solution with and without calling to spl_load_* in my controller, but I still had no success :-(

Any help will be appreciated.

MrSoundless
MrSoundless
6/15/2011 3:33:54 PM #

Hi Angel,
If you use this solution, you don't have to do anything in Yii except import PHPExcel.
If you do this and still have problems, feel free to let me know what is actually going wrong.

Angel
Angel
6/15/2011 5:16:09 PM #

Thanks for your attention.

I can't figure out what's happening. This is is the fragment of source where I get the error:

public function comprovaFitxerDeCarrega($fitxer) {
  $errors = array();

  $phpExcelPath = Yii::getPathOfAlias('ext.slt.phpexcel');
  include($phpExcelPath . DIRECTORY_SEPARATOR . 'PHPExcel.php');
    
  $o = new PHPExcel();
  CVarDumper::dump("hello", 1, true);

  try {
    // Identificar el tipus de fitxer
    $inputFileType = PHPExcel_IOFactory::identify($fitxer);
    CVarDumper::dump("debug2", 1, true);
  } catch (Exception $e) {
    CVarDumper::dump("debug3",1,true);
    array_push($errors, "Error: " . $e->getMessage() );
  }
  return $errors;
}

"comprovaFitxerDeCarrega" is a function called from my controller's actionIndex() method. I get this error: http://i.imgur.com/ZfKIk.png

As you see, the line "$o = new PHPExcel();" (i included this line just for test; I don't use the $o variable) executes successfully (so the PHPExcel class is found by the loader), but the line

   $inputFileType = PHPExcel_IOFactory::identify($fitxer);

throws an error. I'm using PHP 5.3 and PHPExcel 1.7.6 (I haven't tried with version 1.7.3c yet, the version you use). Any ideas about what steps could I follow to find the error?

Thanks in advance.

MrSoundless
MrSoundless
6/15/2011 7:45:30 PM #

First you can replace this:

  $phpExcelPath = Yii::getPathOfAlias('ext.slt.phpexcel');
  include($phpExcelPath . DIRECTORY_SEPARATOR . 'PHPExcel.php');

with this:
  Yii:import('ext.slt.PHPExcel.PHPExcel');


I also attached a new Autoloader.php file for PHPExcel 1.7.6 . Please make sure you use the Autoloader for the right version.

Angel
Angel
6/16/2011 10:55:25 PM #

I really appreciate your help, MrSoundless, I finally got it working thanks to your advice.

BUT I have detected a really strange behaviour: if I remove the line

$o = new PHPExcel();

from my function, I still get the same exception when calling to PHPExcel_IOfactory::identify(). So It seems to me that this sentence does some "magic" loading some classes or libraries which are not loaded if I call directly to PHPExcel_IOFactory::identify().

I am a PHP newbie, as you can see. Does this make any sense to you, or am I going crazy? Smile

Anyway, thank you very much for your time!

MrSoundless
MrSoundless
6/17/2011 12:46:21 AM #

I'm glad to hear that!

What you said there makes complete sense:
The reason that PHPExcel_IOFactory is not recognized is because the PHPExcel Autoloader is needed to import the PHPExcel_IOFActory class. At this time the autoloader is not loaded yet, which means it also can't import anything. The PHPExcel Autoloader is loaded as soon as the PHPExcel.php file is included. If you check out that file, you'll see that it includes the Autoloader in there.
The Yii::import function, includes a class ONLY when necessary. So until you call new PHPExcel, it won't load in the PHPExcel file which means the PHPExcel autoloader is never called.

So to fix this, you could just create an instance of PHPExcel like you did. You could also use include instead of Yii::import (as you showed in an earlier comment)

So yeah this sounds like a good reason to use include instead of import.

Dipsy
Dipsy
6/28/2011 11:45:46 AM #

it s magic! I love your patch ^)

nonomare
nonomare
7/9/2011 12:23:21 AM #

@Angle
You could also use
Yii::import('blah.blah', true);

which includes the files right away.

chikaboom
chikaboom
7/14/2011 11:12:00 AM #

Thank you It is very useful.

aluminium section
aluminium section
7/22/2011 12:09:58 PM #

Was looking for this.. thanks

real estate bangalore
real estate bangalore
8/8/2011 2:10:28 PM #

Great share , Thanks

aramaki
aramaki
8/18/2011 11:45:04 AM #

hi, thanks for share!

why you don`t use Yii::registerAutoloader()?

Yii::import('....PHPExcel',true);
Yii::registerAutoloader(array('PHPExcel_Autoloader', 'Load'),true);

thanks  a lot again

MrSoundless
MrSoundless
8/18/2011 4:30:56 PM #

Oh wow, I didn't know about registerAutoloader. That is indeed the best solution I've seen till now!

aramaki
aramaki
8/18/2011 5:10:58 PM #

yep, but the problem is that we need to load
Autoloader.php (require or via yii::import() )
( or PHPExcel.php, which load it)
before call registerAutoloader.
And if it`s happened we`ll get error with

PHPExcel_Shared_ZipStreamWrapper::register();

I feel, that the another solution is near, but little bit tired to search it  now ((

also, may be you clear with me, why does we need to unregister firstly Yii`s autoloader?

as saw it many times: in your example and in the www.yiiframework.com/.../ and more results from yii`s forum

thanks in advance, good luck


MrSoundless
MrSoundless
8/18/2011 7:22:41 PM #

When you add an autoloader it gets stacked.
So if you just have Yii your autoloader array will look like this:
   element 0 => Yii's Autoloader

And if you add PHPExcel's Autoloader to it it'll become this:
   element 0 => Yii's Autoloader
   element 1 => PHPExcel's Autoloader

So if php tries to autoload a class it first checks with Yii's autoloader. Then PHPExcel's autoloader. The problem with Yii's autoloader is that it returns an exception if it cna't find anything. This means it'll never reach PHPExcel's Autoloader..

By unregistering Yii's Autoloader first, we clear the array, then add PHPExcel's Autoloader, then add Yii's Autoloader. After doing this the array will look like this:
   0 => PHPExcel's Autoloader
   1 => Yii's Autoloader

So now if PHP tries to autoload a class, it first tries with PHPExcel's Autoloader. Since PHPExcel's Autoloader does not throw an exception if it can't find anything this will work fine. If it can't find the class, it'll just move on to Yii's Autoloader.

That's pretty much the reason why Yii's Autoloader is unregistered before others are registered.

Hope this helps.

shark555
shark555
1/5/2012 10:57:32 AM #

Thanks!! I definitely owe you one ;)

George Pligor
George Pligor
1/23/2012 1:05:52 PM #

one more comment from me only because you are counting! haha!
thanks man!

MrSoundless
MrSoundless
1/25/2012 8:07:03 AM #

Haha, thanks. Awesome Laughing

Kalo
Kalo
4/4/2012 5:55:00 PM #

Awesome. Thanks dude. Helps a lot!

Kalo
Kalo
4/4/2012 5:55:27 PM #

Awesome. Thanks dude, helps a lot!

trending online
trending online
5/15/2012 1:11:27 PM #

Can I just say what a relief to find someone who actually knows what theyre talking about on the internet

Add comment




biuquote
  • Comment
  • Preview
Loading


Powered by BlogEngine.NET 2.0.0.36
Original Design by Laptop Geek, Adapted by onesoft