I’ve been experimenting with various pieces of the Zend Framework to get a better idea of how they work. I decided to put together a simple project using a few of them. What I wanted to do was create a class that allows the user to get stock quotes (and other stock information) from Yahoo! Finance, modeled after the Zend_Service_* classes. Yahoo provides stock information in a CSV format. I used the following components of the Zend Framework:

  • Zend_Http_Client – provides a class for sending HTTP requests and getting the response. I need this in order to fetch the CSV file from Yahoo!
  • Zend_Cache – currently in the ‘incubator’, this class allows me to cache the response so that the application doesn’t need to get data from Yahoo on every page request.
  • Zend_Json – allows for automatic serialization of objects, arrays, and strings into JSON format, perfect for using with AJAX applications. This isn’t actually used in the library, but there’s an example of how it can be used with the result of the library.

On my way through putting this together, I came across another couple of things I haven’t had much/any experience with:

The calling ‘application’

My test page for this class is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?
function __autoLoad($className) {
	Zend::loadClass($className);
}
require('Zend.php');
 
$stocks = new Gregphoto_Service_Yahoo_Stocks();
$stocks->addStocks(array('GOOG','YHOO','MSFT','AMD','INTC'));
$stocks->setSymbols(array('s','n','l1','c1'));
$stockResult = $stocks->getStockInfo();
?>
<html>
<head>
<title>Yahoo Stocks with Zend Framework: Example</title>
</head>
<body>
<?
foreach($stockResult as $stock) {
	echo "{$stock->s}: {$stock->c1}<br>";
}
?>
 
<p>And here is a JSON string representation of the results:</p>
 
<?=Zend_Json::encode($stockResult->getArray());?>
</body>
</html>

Key points:

  • Lines 2-4 setup an autoLoad function to take care of loading the Zend Framework files whenever they’re requested
  • Line 7 instantiates the Gregphoto_Service_Yahoo_Stocks class
  • On line 8 we pass in an array of stocks we’d like information on
  • On line 9 we send in a list of symbols, indicating what information we would like on these stocks. For a detailed list of all the information that is available, check out the following link: http://www.gummy-stuff.org/Yahoo-data.htm
  • Line 10 sends the request and gets back a Gregphoto_Service_Yahoo_Stocks_ResultSet object back. This class implements SeekableIterator, allowing simple iteration over the results.
  • Lines 18 – 20 loop through the results, printing two symbols out: s and c1. These are the stock symbol and the change on last day of trading
  • Line 25 uses the Zend_Json class to encode the results in JSON format. This would be perfect for sending back to a page in an Ajax environment

The stocks class

I’ll focus here on a couple of the key methods of this class – most of them are fairly basic. You can download all the files to see the whole thing if you want.

The first function we’ll look at is the getStockInfo method. Here’s what it looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function getStockInfo() {
	if(!is_object($this->cacheObj)) {
		$this->getCacheObj();
	}
	$cache = $this->cacheObj;
	$url = $this->generateYahooURL();
	$id = md5($url);
	if (!($cache->test($id))) {
		$stockArray = $this->getStockArray($url);
		$cache->save($stockArray);
	} else {
		$stockArray = $cache->get($id);
	}
	return new Gregphoto_Service_Yahoo_Stocks_ResultSet($stockArray);
}

This performs the following steps:

  • Gets a cache object if it doesn’t already exist
  • Calls a method to generate the URL for the stock request
  • Creates an md5 hash of the url to use as the cache identifier for the request
  • If too old, it calls the getStockArray method and saves its result to the cache, otherwise it loads this information from the cache
  • It returns a new Gregphoto_Service_Yahoo_Stocks_ResultSet object to the user

This of course, left out the most critical part – getting the stock quote CSV data from Yahoo! and converting it into an associative array. That takes place in the getStockArray method, shown below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private function getStockArray($url) {
	$http = new Zend_Http_Client($url);
	$response = $http->get();
	if($response->isSuccessful()) {
		require_once(dirname(__FILE__).'/csvstream.php');
		$stockArray = array();
		$GLOBALS['Gregphoto_Yahoo_Stocks_Stream'] = $response->getBody();
		$fp = fope.n('csvstr://Gregphoto_Yahoo_Stocks_Stream', 'r+');		
		while (($row = fgetcsv($fp, 2000, ",")) !== FALSE) {
			$stockArray[] = array_combine($this->symbols,$row);
		}
		unset($GLOBALS['Gregphoto_Yahoo_Stocks_Stream']);
		return $stockArray;
	} else {
		throw new Exception("Unable to get data from Yahoo Finance");
	}
}

Note: On line 8, I couldn’t get this damn thing to save unless I got rid of ‘fopen’, so I changed it to ‘fope.n’

My webhost, Dreamhost, doesn’t support allow_url_fopen, meaning I can’t use standard file functions such as file, fopen, or file_get_contents to get the CSV file from Yahoo. Instead, here I use the Zend_Http_Client class to get stock quotes CSV from Yahoo!. PHP has a built in function, fgetcsv, for converting CSVs to arrays…unfortunately it only works on file resources, like the ones you’d get if you opened the file with fopen…a function I can’t use to open a remote file. I scratched my head for a while on this one. One solution would be to save the contents of the response as a file on the filesystem and then use fgetcsv on the file. I found an alternative after many a google search – stream_wrapper_register – a function that lets you define a class that represents data as a stream…that can be used with all functions requiring a file resource. Not only did I find the idea in the PHP manual, there was actually an example of using it for CSV data in the comments – now that’s service. So here’s the steps:

  • Instantiate a new Zend_Http_Client with the URL passed in from getStockInfo
  • Get the reponse object and if it was successful, continue, otherwise throw an exception
  • Require the csvstream.php class which has the csvstream class and the following line: stream_wrapper_register(“csvstr”, “csvstream”); This registers the class for all urls beginning with csvstr://
  • Set a global variable with the value of the CSV file, the body of the HTTP response. The csvstream class looks for the value to be converted to a stream in a global variable.
  • Use the fopen function to open csvstr://Gregphoto_Yahoo_Stocks_Stream, the second part of the URL being the name of the global variable I set
  • After this, we loop through the file and use the fgetcsv function to create an array, combine it with the symbols chosen to make a more user-friendly associative array.
  • Lastly, we unset the hack-ish global variable we had to set and return the array

And that’s really the meat of it. The rest of it is very straightforward. The Gregphoto_Service_Yahoo_Stocks_ResultSet does nothing much more than implementing the SeekableIterator function and creating new Gregphoto_Service_Yahoo_Stocks_Result instances. The Gregphoto_Service_Yahoo_Stocks_Result class itself is very simple, using the magical __get method to allow easier access to the array values, i.e. $stock->s instead of $stock[‘s’]. I could easily have skipped the resultset and result classes, but I decided to create them so that it would work in a similar fashion to the Zend_Service classes.

All in all, I’ve found it to be very easy to work with the Zend Framework classes, though I’m still holding off on the controller/view classes until they firm up a bit and add native support for applications in subdirectories (supposedly in the next dot release).

Hope you found this interesting…you can download the code here, but don’t expect production worthy code, much documentation, or much support!