PEAR and CakePHP

This post is about my experience with loading in PEAR to a CakePHP 1.2.x application. This may be the right way or the wrong way, but I got it to work throughout the application. I had to do some changes, and if there is a better way of doing this, please let me know.
First off, here is the issue. I needed to be able to export a group of records from the database to an excel spreadsheet. I have tried to use the Excel Spreadsheet add in that is listed on the Bakery. It works nice, and I had to do some modification for 1.2, but it worked. But not the way I wanted it. I have used the PEAR library Spreadsheet_Excel_Writer before and I like the type of control that I wanted, over the cells, the formatting, the merging, etc etc etc. It provides the type of control that I wanted. So here is what I did to get this to work with the CakePHP framework.
First, I have to download the PEAR library and the Spreadsheet_Excel_Writer libraries to use. Since I use a local system to help develop, I could download these libraries to the local system and transport these over to the CakePHP application. So I went to PEAR site to get the libraries. To download these I ran the following commands:

pear install PEAR-1.8.1
pear install OLE-1.0.0RC1
pear install Spreadsheet_Excel_Writer-0.9.1

URL’s are listed below:
http://pear.php.net/package/PEAR/download
http://pear.php.net/package/Spreadsheet_Excel_Writer/download
http://pear.php.net/package/OLE/download

These downloaded the to local drives and I copied them over to the CakePHP area. Here is where it gets a little tricky. And I thank “brian” who helped me on the Cake Google group to get past this when I got into a problem. So here we go, diving in to this.
First off, the PEAR libraries need to be put in the vendors directory. If you look at the directory structure for Cake, it appears like this:
/app
/cake
/vendors
Inside of the /app directory, there is another vendors directory. This is where I put the PEAR libraries. This causes problems, because in the /cake/config/paths.php file, there is a path defined for VENDORS, and for PEAR:

if (!defined('VENDORS')) {
define('VENDORS', CAKE_CORE_INCLUDE_PATH.DS.'vendors'.DS);
}

define('PEAR', VENDORS.'Pear'.DS);

Now, I have put the PEAR libraries in the top level vendor directory. If you choose to put it in the app/vendors directory, then you may need to change a core file, which is not advisable, because you would need to change the path above to

if (!defined('VENDORS')) {
define('VENDORS', CAKE_CORE_INCLUDE_PATH.DS.'app/vendors'.DS);
}

So back to the PEAR libraries. Here is what I needed to do. I moved the PEAR directories to the /vendors directory. So here is what that directory looks like:

/vendors
/css
/js
/Pear <--- Look at this, case sensitive based on those paths above
/OLE
/OS
/PEAR
/scripts
/Spreadsheet
INSTALL
LICENSE
OLE.php
package.dtd
PEAR.php
PEAR5.php
README
System.php
template.spec
/shells

This is part of the PEAR install we need to do. Now we need to update the paths in some of these files so that CakePHP can find them and include them.
**** ORIGINAL ENTRY THAT HAS BEEN EDITED/DEPRECATED ********************
**** EDIT: Based on the original post this is the method I originally used. This way works, but requires a
**** little too much overhead and editing the files in the PEAR libraries which is never really a good idea
**** and should only be used sparingly. To see the way that it should be done, please see below this
**** section.
****
**** In the file /vendors/Pear/Spreadsheet/Excel/Writer.php there is 2 requires
**** require_once 'PEAR.php';
**** require_once 'Spreadsheet/Excel/Writer/Workbook.php';
****
**** In order for the Cake App to see these, at least in my set up, I needed to change these to the
**** following:
**** require_once 'PEAR.php';
**** require_once PEAR . 'Spreadsheet/Excel/Writer/Workbook.php';
****
**** I needed to add "PEAR . " to the require_once call. Now I needed to add this to the following files:
**** /vendors/Pear/Spreadsheet/Excel/Writer.php
**** require_once 'PEAR.php';
**** require_once PEAR . 'Spreadsheet/Excel/Writer/Workbook.php';
****
**** /vendors/Pear/Spreadsheet/Excel/Writer/Workbook.php
**** require_once PEAR . 'Spreadsheet/Excel/Writer/Format.php';
**** require_once PEAR . 'Spreadsheet/Excel/Writer/BIFFwriter.php';
**** require_once PEAR . 'Spreadsheet/Excel/Writer/Worksheet.php';
**** require_once PEAR . 'Spreadsheet/Excel/Writer/Parser.php';
**** require_once PEAR . 'OLE/PPS/Root.php';
**** require_once PEAR . 'OLE/PPS/File.php';
****
**** /vendors/Pear/OLE/PPS.php
**** require_once 'PEAR.php';
**** require_once PEAR . 'OLE.php';
****
**** This had helped the application find my PEAR libraries when trying to do this
**** END ORIGINAL ENTRY ***********************************************
Now, a word about the edit. The above method works, but is not a preferred method. The best method for this, so there is no need to edit PEAR library files is the following. And a big thanks to Daniel Hofstetter for pointing this out.
I put this at the top of my controller file. I am sure there is better places for this, probably even the app_controller file so that all controllers get the needed include path set. Here is what I did.
I needed to append the include path so that the new PEAR path would be found. After the php opening, I added this line:

ini_set("include_path", PEAR . PATH_SEPARATOR . ini_get("include_path"));

I am going to go over this just a little. First off, the ini_set is called to set the include_path. But we do not want to destroy any other include paths that are set up as well. So when we add the PEAR path, we need to also include the other paths as well. So the initial include_path was as follows (given in example form only, where the directory "test" is where I have CakePHP installed)
ini_get("include_path") =

/www/htdocs/html/test:/www/htdocs/html/test/app/:.:/usr/local/php5/lib/php

Since CakePHP already defines the variable for the PEAR path as "PEAR", we can use that to add to the include path, like shown above. After setting the path using ini_set(), we run ini_get("include_path") it would =

/www/htdocs/html/test/vendors/Pear/:/www/htdocs/html/test:/www/htdocs/html/test/app/:.:/usr/local/php5/lib/php

By doing it this way, there is no need to edit the PEAR library files, and we can add new PEAR libraries without having to worry about editing those files as well.
Now, I needed to make the controller aware of the vendor library. In my controller file I added this line before the class declaration:

App::import('vendor', 'Spreadsheet_Excel_Writer', array('file' => '../vendors/Pear/Spreadsheet/Excel/Writer.php'));

In Cake 1.2, this is how the vendor's are imported. The vendor() declaration has been deprecated. This imports a vendor, gives the class a name (I choose the base one that it is usually called), and the location of the of the file. In my set up, I needed to add the "../", you may not have to.
In the function, (I called "export"), I did not want to have a "view" page for it. The first thing I did was grab the information I needed. For this example, I needed all users that signed up for a conference. So I grabbed that information and put it in an array $registrations.

function export ($id = null){
// I only want to get a specific conference, not all of them
if ( $id == 'all' ){
$this->Session->setFlash('Please select a specific conference to export the registrations.');
$this->redirect(array('action' => 'index'));
}

// Now get the registrations for the conference
$registrations = $this->Registration->find('all',
array(
'conditions' => array('conference_id' => $id),
'fields' => array('*'),
'recursive' => '-1',
'order' => array('Registration.created'),
)
);

Now comes the fun part, building the column heading array, and then instantiating the writer

// Set up the header array
$titles = array(
'Name' => 15,
'Address' => 20,
'City' => 20,
'State' => 7,
'Zip Code' => 10,
'Email' => 20,
'Phone' => 13,
);

$rn = 0; // row number
// Build the XLS file using PEAR
$xlsBook = new Spreadsheet_Excel_Writer();
$xlsBook->send("registrations.xls");
$xls =& $xlsBook->addWorksheet('Registrations');

Everything else is now just as the same as it would be with the Spreadsheet-Excel_writer. Create the formats as you would like, for text, numerics, specialized strings, colors, etc. Write the sheet headings, if you so desire

/* Create styles for the spreadsheet */
$format_bold =& $xlsBook->addFormat();
$format_bold->setBold();

$main =& $xlsBook->addFormat(
array('Size' => 14,
'Align' => 'center',
'Color' => 'black',
'Bold' => 'true'
));
$main->setBold();

$formatText =& $xlsBook->addFormat(array('Size' => 11));

$cn = 0;
$xls->write($rn, 0, "CONFERENCE REGISTRATIONS", $main);
$xls->mergeCells($rn,0,$rn,11);
$rn++;

As you can see, just use the writer calls to write the data, format it, and do what you need. To get more information on this, please check the PEAR documentation for this library.
Finish up the column headings by doing a quick little loop

// Set up the headings of the columns
foreach ( $titles as $t => $val){
$xls->setColumn($cn, $cn, $val);
$xls->write($rn, $cn++, $t, $format_bold);
}
$rn++;
// reset the column num
$cn = 0;

Now you can do the actual rows in a loop:

foreach ( $registrations as $r ){
$xls->write($rn, $cn++, $r['Registration']['name'], $formatText);
$xls->write($rn, $cn++, $r['Registration']['address'], $formatText);
$xls->write($rn, $cn++, $r['Registration']['city'], $formatText);
$xls->write($rn, $cn++, $r['Registration']['state'], $formatText);
$xls->write($rn, $cn++, $r['Registration']['zip_code'], $formatText);
$xls->write($rn, $cn++, $r['Registration']['email'], $formatText);
$xls->write($rn, $cn++, $r['Registration']['contact_phone1'], $formatText);
// cycle to the next row
$rn++;
// Reset the column
$cn = 0;
}

$xlsBook->close();
exit();
} // end of function

Now, this does the work for me on my code. To call it in the view, I have a page that shows all registrations on the page, the function name is "registrations". I set a variable in this function for the ID number to be passed to the view. In the view for this function, I have put the following:

if ( $param != 'all') {
echo "      - - ";
echo $html->link(__('EXPORT DATA', true), array('action' => 'export', $param) );
echo " - - ";
}

So I want a specific conference. If there is not one, and they are viewing all registrations for all conferences, then it does not show the link. But if it is a specific id, then it shows the link to export with the corresponding parameter for the ID.
And there it is. Using the Spreadsheet_Excel_Writer PEAR library with CakePHP 1.2.
Again, this works for me, and there may be a better way of doing things, and if so, please feel free to tell me. I am always looking for new things to learn.