Last.fm and Google Charts PHP Class

Coding Critique is the place to post source code for peer review by other members of DevNetwork. Any kind of code can be posted. Code posted does not have to be limited to PHP. All members are invited to contribute constructive criticism with the goal of improving the code. Posted code should include some background information about it and what areas you specifically would like help with.

Popular code excerpts may be moved to "Code Snippets" by the moderators.

Moderator: General Moderators

Post Reply
Ollie
Forum Newbie
Posts: 4
Joined: Sun Jan 20, 2008 5:46 pm
Location: Nottingham, UK

Last.fm and Google Charts PHP Class

Post by Ollie »

I've written a php class that uses the Google Chart API and the Audioscrobbler webservices to produce charts showing my listening habits. It's my first real attempt at OOP with PHP.

Obviously it's fairly restricted in terms of function at present, but I think at this stage it would be good to have critique of coding style, especially on the OOP front.

Here's the class:

Code: Select all

<?php
 
class lastfmChart {
    
    var $dataFile = '';                         // location of xml file
    
    var $chartDataType = 'pc';                  // type of data needed for chart (percentage [pc] or number [num])
    var $chartDataPoints = '15';                // number of data points
    
    var $chartTitle = 'Chart Title';
    var $chartType = 'pie';
    var $chartSize = '400x400';
    var $chartColors = '';
    
    var $chartLabels = '';
    var $chartValues = '';
    var $maxPlays = 0;                          // highest number of plays
    
    var $chartURI = 'http://chart.apis.google.com/chart?';
 
    function lastfmChart() {
    }
    
    function setDataFile($value) {
        $this->dataFile = $value;
    }
    
    function setDataPoints($value) {
        $this->chartDataPoints = $value;
    }
    
    function setTitle($value) {
        $this->chartTitle = $value;
    }
    
    function setSize($value) {
        $this->chartSize = $value;
    }
    
    function setColors($value) {
        $this->chartColors = $value;
    }
    
    function setMaxPlays($value) {
        $this->maxPlays = $value;
    }
    
    function setType($value) {
    // set the type of chart and some other required / default parameters
        
        switch ($value) {
            case 'pie':
                $value = 'p';
                $type = 'pc';           // pie chart requires percentage values
                $size = '550x200';
                break;
            case 'bar':
                $value = 'bhg';
                $type = 'num';          // bar chart requires absolute numbers
                $size = '400x400';
                break;
            default:
                $value = $value;
                $type = $type;
                break;
        }
        
        $this->chartType = $value;
        $this->chartDataType = $type;
        $this->setSize($size);
    }
    
    function getData($xml) {
    // get data from xml
        
        // read in xml
        $music = simplexml_load_file($xml);
        
        // initiate some variables
        $listens = array();
        $tot_plays = 0;
        $counter = 0;
        
        // blank labels and values
        $this->chartLabels = '';
        $this->chartValues = '';
        
        // string for encoding - these 62 characters have increasing value
        $simpleEncoding = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
        
        // for each artist in the xml
        foreach ($music->artist as $artist) { 
            
            // add the artist name to the label string up to the number of data points required
            if ($counter < $this->chartDataPoints) {
                $this->chartLabels .= $artist->name . '|';
            }
            
            // store the number of plays for this artist, add the plays to the total and increment the counter
            $plays[$counter] = $artist->playcount;
            $tot_plays += $artist->playcount;
            $counter++;
            
        } 
        
        // for each plays value
        foreach ($plays as $play) {
            
            // get a number that works with charts
            if ($this->chartDataType == 'pc') {
                $play = $play / $tot_plays * 61;
            } elseif ($this->chartDataType == 'num') {
                $play = $play / $plays[0] * 61;
            }
            
            // encode these values
            $this->chartValues .= substr($simpleEncoding,$play,1);
            
        }
        
        // trim values to number of data points required
        $this->chartValues = substr($this->chartValues, 0, $this->chartDataPoints);
        
        // trim trailing separator from labels and encode for use in a url
        $this->chartLabels = substr($this->chartLabels, 0, strlen($this->chartLabels) - 1);
        $this->chartLabels = urlencode($this->chartLabels);
        
        // if this is a bar chart, reverse the chart values
        if ($this->chartType == 'bhg') {
            $this->chartValues = strrev($this->chartValues);
        }
        
        // store the highest number of plays
        $this->setMaxPlays($plays[0]);
        
    }
    
    function doChart() {
        
        // get the data
        $this->getData($this->dataFile);
        
        // start appending to url
        $this->chartURI .= 'chtt=' . urlencode($this->chartTitle) . '&cht=' . $this->chartType . '&chs=' . $this->chartSize . '&chco=' . $this->chartColors . '&chd=s:' . $this->chartValues;
        
        // chart type specifics
        if ($this->chartType == 'p') {
            $this->chartURI .= '&chl=' .  $this->chartLabels;
        } elseif ($this->chartType == 'bhg') {
            $this->chartURI .= '&chxt=x,y&chxl=0:|0|' . $this->maxPlays . '|1:|' . $this->chartLabels;
        }
        
        // output into img tag
        echo '<img src="' . $this->chartURI . '" alt="' . $this->chartTitle . '"/>';
        
    }
 
}
?>
The code to output the chart:

Code: Select all

<?php
// initiate a chart
$chart = new lastfmChart();
 
// choose a data source
$chart->setDataFile('http://ws.audioscrobbler.com/1.0/user/mister-brown/weeklyartistchart.xml');
 
// set type of chart and number of segments/bars
$chart->setType('pie');
$chart->setDataPoints('10');
 
// set the size, title, color
// defaults to 400x400 for bar, 550x200 for pie; max total number of pixels is 300,000
$chart->setSize('600x150');
$chart->setTitle('Weekly Artist Shares');
$chart->setColors('0000FF');
 
// output the chart
$chart->doChart();
?>
All of which produces something like:

Image
User avatar
Kieran Huggins
DevNet Master
Posts: 3635
Joined: Wed Dec 06, 2006 4:14 pm
Location: Toronto, Canada
Contact:

Re: Last.fm and Google Charts PHP Class

Post by Kieran Huggins »

Looks pretty good to me - well done! Any comments would be purely about best practice and style.

Best practice: there's no error checking / recovery. You may want to look into throwing and catching errors (what if the feed doesn't load, etc...)

You may want to get used to declaring your methods and variables as either public, private or static.

Your "string encoding" string is a good idea, but lowercase letters traditionally have a lower value than uppercase. Numbers are typically the lowest of all. Also, if you add the characters / and = to the end you'll be compatible with "base64" encoding, which there are compiled functions for in PHP ( base64_[en|de]code() ). Compiled functions are considerably faster, and those are in PHP core by default. Does Google require this mapping or something?

I'd prefer the object output a URL for the chart, rather than an image tag, might make it more flexible. Also, I'd make the URL the return value of the __toString() magic method, but that's just me. In templates you could just echo the object where you wanted the URL. That being said, there's certainly nothing wrong with a $chart->to_a() method that outputs an xhtml anchor or a $chart->to_img() method that outputs an img tag.

Coding style: I like using magic methods and chaining for things like setting properties. It's just how I roll. Saves typing and I think it's easier to read:

Code: Select all

// for a url:
echo $chart->size('500x300')->type->('pie')->data(array('slice 1'=>'23','slice 2'=>'56','slice3'=>'35'));
 
// for an img tag:
$chart->size('500x300')->type->('pie')->datafile('http://bob.com/stats.xml')->to_img();
 
// maybe even make it a singleton:
chart::new()->size('500x300')->type->('pie')->datafile('http://bob.com/stats.xml')->to_img();
Generally you seem to have a really good handle on OOP - keep going!
Ollie
Forum Newbie
Posts: 4
Joined: Sun Jan 20, 2008 5:46 pm
Location: Nottingham, UK

Re: Last.fm and Google Charts PHP Class

Post by Ollie »

Belated thanks for the critique, Kieran. Much appreciated.

Will have to look into public/private/static variables (i think i pretty much know what they're about) and magic methods (any good reading material on these?).

You're right about catching errors.

The bizarre encoding was Google's idea. Having read their documentation more thoroughly, you can also pass decimal numbers in the url, so this might be a better way forward.

Thanks again,
User avatar
Kieran Huggins
DevNet Master
Posts: 3635
Joined: Wed Dec 06, 2006 4:14 pm
Location: Toronto, Canada
Contact:

Re: Last.fm and Google Charts PHP Class

Post by Kieran Huggins »

Overloading, including __call(): http://ca.php.net/manual/en/language.oo ... oading.php

Magic Methods, including __toString(): http://ca.php.net/manual/en/language.oop5.magic.php

Exceptions (error handling): http://ca.php.net/manual/en/language.exceptions.php

That would make your class one sexy piece of work. Damn sexy.
Post Reply