:: Archive for the 'PHP' Category ::

Getting stock quotes with Yahoo and the Zend Framework

Posted on April 23rd, 2006 in PHP by Greg

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!


gCards 1.46 released due to security issues

Posted on March 27th, 2006 in gCards, PHP by Greg

A reader posted some comments on my blog this morning, pointing me to a website listing some security vulnerabilities in gCards and also to the fact that this had been exploited on my website! Not good, not good. I’ve fixed these problems and posted gCards 1.46 – you can download it at the regular location.

If you’re using gCards (any version), you must upgrade to this version or you risk someone gaining control of your site and executing arbitrary code.

The challenge with me for gCards is that I wrote it so long ago and it’s such a big mess of spaghetti code, that it’s difficult for me to confidently say how secure it is. I would rewrite it from scratch, but that requires a bunch of time, and there’s so much built into it already to handle the complexities on running in so many different PHP environments. If anyone hears of any other security vulnerabilities or finds any, please let me know…

Zdbform: Simple database admin forms with the Zend Framework

Posted on March 25th, 2006 in PHP, Web Development by Greg

I’ve put together a database admin application using the database functionality provided by the Zend Framework. Basically, it’s a generic set of classes that allows people to quickly add, edit, delete, and view data in a database table. The nice thing is that this uses the Zend_DB_Table classes, so it should presumably work with just about any database supported by the Zend Framework. I’ve tried this on mySQL and sqlite, and it worked just fine on both. There are some limitations of course. Right now this will only work on tables that have auto-increment primary keys. I don’t quite know how this would work with databases that don’t have auto-increment such as Oracle – maybe this could have support for defining the sequence that should be used when inserting data. This is a PHP5 only class due to it’s reliance on the Zend Framework.

The intention of Zdbform is to have a quick way to put up a database admin page for webapp control panel that is easier to use than tools such as phpMyAdmin. Something that can provide validation, customization of the view, integration within other pages,

You can see an example of this running or check out the API docs.

Zdbform has the following features:

  • Simple maintenance of database tables
  • View table data with pagination, searching, and sorting of columns
  • Ability to change the display names of columns
  • Ability to hide columns in view, add, and edit
  • Extendable set of widgets to show data in table / forms
  • Extendable set of validators to validate column data
  • Ability to add row validators that validate the full contents of a newly inserted or edited row

Other items that I would like to add in the future are:

  • Custom callbacks on add / edit / delete
  • Think about defining Zdbform as extension of Zend_DB_Form
  • If definition of class settings is externalized it could open up opportunities for export of data, ajax editing, ajax validation, etc.
  • Rename classes to fit within naming standards of Zend Framework. Think about how this fits into the MVC parts of the Zend Framework.

The page that defined the example is shown below:

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php
require_once 'Zend/Db.php';
require_once 'Zend/Db/Table.php';
require_once 'zdbform.class.php';
require_once 'zdbform_widgets.class.php';
require_once 'zdbform_validations.php';
$params = array (
    'host'     => 'localhost',
    'username' => 'test',
    'password' => 'test',
    'dbname'   => 'delicious.db'
);
$db = Zend_Db::factory('pdoSqlite', $params);
Zend_Db_Table::setDefaultAdapter($db);
class Posts extends Zend_Db_Table {}
 
$dbform = new Zdbform('Posts');
$dbform->setWidget('posted', 'timestamp');
$dbform->setWidget('description', 'html');
$dbform->setWidget('href', 'link');
$dbform->setDisplayName('id', 'ID');
$dbform->setDisplayName('href', 'Link');
$dbform->hideColumns('posted');
$dbform->setWidgetOption('href','width',70);
$dbform->setHelpText('posted', 'm/d/y format');
$dbform->setHelpText('tags', 'Enter one or more tags, separated by commas');
$dbform->tableTitle = "Backed up delicious links";
$dbform->editTitle = "Edit link information";
$dbform->addTitle = "Add new link";
$dbform->addColumnValidator('tags','validate_minlength',array('minlength' => 4));
$dbform->addColumnValidator('href,description','validate_required',array());
$dbform->rowsPerPage = 15;
$dbform->processForms();
 
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>ZDBFORM: Manage Posts Table</title>
<link type="text/css" rel="stylesheet" href="zdbform.css" />
</head>
 
<body>
<?php
$dbform->showForms();
$dbform->showTable();
?>
</body>
</html>

Here are the main steps:

  • Lines 2-6 include the required libraries for this to work. It’s important to note that you need to have the Zend Framework classes in your include path for PHP. Alternatively, you can use the set_include_path at the top of the script.
  • Lines 7-13 sets up database parameters and then creates a Zend DB Adapter using the options provided. In this case it’s a sqlite database. Typically this stuff would probably just be defined in one place in your application, and then included, but for this case I’ve put everything on one page
  • Lines 14-15 set the newly created Zend DB Adapter to be the default adapter for Zend DB Table and then defines ‘Posts’ a class that extends Zend_DB_Table. The name of the class is important – it is the name of the database table we want to manage.
  • Line 17 is the first place where we see Zdbform. Here, we create an instance of Zdbform, passing it the name of the table we want to manage.
  • Lines 18-32 set various options for our table. For example, Line 18 sets the posted column’s widget to timestamp, which will show a dropdown for selection of a date. Line 19 sets the description column’s widget to html, which will use FCKeditor (a JavaScript HTML editor) when adding and editing data. Lines 30 and 31 set various validators that will validate data on edit or insert. All of these steps are optional and only enhance the behavior / look and feel of Zdbform.
  • Line 33 calls Zdbform::processForms(), which will process any data that has been submitted for add, edit, or delete
  • Finally, lines 47 and 48 display the forms and display the table of data

Download the code and example

gCards 1.45 released

Posted on March 9th, 2006 in gCards, PHP by Greg

I just uploaded gCards version 1.45 – you can get it here. This should hopefully fix all of the issues people had with the authentication. Basically when it was written I used a mySQL specific function called ‘password’ for creation of and validation of the users password. MySQL changed this in one of there versions – 4.x I think, so anyone using newer versions of mySQL couldn’t quite login to the application. I’ve updated gCards to store the password as an md5 hash of the real password, so that should hopefully stand the test of time, as its not specific to any mySQL version…

For users upgrading…it is very important to note that upon running the setup.php file, all passwords will be set to the same as the username. For example, if i had gCards 1.44 with a username of bobbyjoe and a password of schweenard, after running setup.php I would login with bobbyjoe/bobbyjoe. Makes sense?

I did very minimal testing, but it all seems to work just fine on my home computer and at my web host (which runs mySQL 5). Let me know here if you find any problems.

Enjoy

Greg

Zend Framework Alpha Released

Posted on March 5th, 2006 in PHP by Greg

The folks at Zend have released version 0.1.1 of the Zend FrameworkAndi Gutman’s annoucement also mentions the new Zend Developer Zone, which seems to be starting off with some high quality articles – including one on connecting PHP with Macromedia Flex.

I downloaded the new framework, but haven’t had much time to look through all the docs or really get going with it in earnest. From first glance, it seems to be closer to a component library right now – there aren’t any full examples that go through using it as a true ‘framework’. That being said, it looks like most of the building blocks are there.

On the database access side of things, it’s actually quite confusing now – the documentations refers to Zend_Db_DataObject, which seems to be the Active Record implementation seen in the php|archictect webcast, but the code doesn’t exist with the framework. Andi clarified that this code doesn’t exist and the docs were accidentally included – it’s not clear, however, whether this will eventually be added or not. Zend_Db_Table seems similar in some ways, but is not as straightforward as the Active Record approach. I was able to put together a quick example of pulling data from a database, and here’s what it looked like:

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
<?php
require_once 'Zend/Db.php';
$params = array (
    'host'     => 'localhost',
    'username' => 'test',
    'password' => '********',
    'dbname'   => 'delicious'
);
 
$db = Zend_Db::factory('pdo Mysql', $params);
 
require_once 'Zend/Db/Table.php';
Zend_Db_Table::setDefaultAdapter($db);
 
class Posts extends Zend_Db_Table {}
 
$table = new Posts();
$posts = $table->fetchAll(null, null, 10);
foreach($posts as $post) {
	?>
	<a href="<?=$post->href;?>"><?=$post->description;?></a>
	(Posted <?=Date("m/d/y",$post->posted);?>)<br>
	<?
}
 
Zend::dump($db->describeTable('posts'));
?>

In this example, I create a new PDO mySQL adapter (line 10), passing in the database connection properties. Set this database adapter to be the default one used for Zend_Db_Table (line 13). Create a new class that extends Zend_Db_Table (line 15) – the name of the class is the same as the DB table name, and this is all Zend_Db_Table needs. Lines 17 and 18 get an instance of this class, and retrieve the first 10 rows from this table. Lines 19-24 print various columns from the returned rows out on the screen. Lastly, on line 26 I print out a description of the table, which is an array that lists the column names, their types, length, etc.

All in all, it seems pretty easy to use, and it also seems easy to add and update rows. What’s missing from the capabilities included in the Ruby on Rails implementation of Active Record is the ability to create relationships between tables – this is a truly great feature that simplifies the life of a developer quite a bit, and I hope that Zend can evolve in this direction.

Another person who has obvious checked this stuff out is John Lim, creator of ADODB, the finest DB abstraction library for PHP. John put together a quick Active Record implementation using ADODB. The big benefits of this implementation is that it works in both PHP4 and PHP5 (Zend framework is PHP5 only and the DB stuff on works well with PDO), and its running on top of the very well tested ADODB library that already has support for just about every database under the sun.

Hopefully the continued innovation and competition will lead to better tools that are easier to use!