6
\$\begingroup\$

We have a geocoding samples github repository. The snippet below came through in an email and I am not very familiar with PHP. I'd like to add this as the PHP example in the samples repository but would like someone to review it first. The idea is that some other php dev can use this as a starting point and it will be considered good code.

Basically what it does, along with the other samples in the repository, is to geocodes an address against a web service.

Right off the bat, I am not sure I like that the referer header is being set and not added by the browser. What would you change about this? If you want, pull request against the samples repo or post answers here or both!

/**
 * Access API from http://api.mapserv.utah.gov
 * uses curl library
 */
class geocoder {

    private $apiKey = '';

    private $apiUrl = 'http://api.mapserv.utah.gov/api/v1/geocode/%s/%s';

    private $options = array(
            CURLOPT_RETURNTRANSFER => true, //return the transfer as string instead of writing directly
            CURLOPT_HTTPHEADER => array('Content-type: application/json'), //specify content type (json)
            CURLOPT_REFERER => 'http://www.example.com/'    //status 400 "Referrer http header is missing" without this line, but it needs to be the originating website 
        );

    /**
     *  Class constructor / 
     */
    public function __construct($userApiKey) {

        $this->apiKey = $userApiKey;    
    }

    /**
     * curl processes the request as a function so that it can be reused with multiple mapserv api requests
     * @param object $options
     * @return 
     */
    private function curl ($options){

        $cUrl = curl_init();
        curl_setopt_array( $cUrl, $options );
        $response = curl_exec( $cUrl );
        curl_close( $cUrl );    

        return $response;   
    }

    /**
     * locate returns x/y coordinate based on address and zone (and possibly other parameters)
     * @param object $address
     * @param object $zone
     * @param object $parameters [optional]
     * @return 
     */
    public function locate ($address, $zone, $parameters = array()){

        $parameters['apiKey'] = $this->apiKey;

        $paramArray = array();
        foreach($parameters as $k => $v){
            $paramArray[] = $k.'='.$v;
        }
        $parameterStr = '?'.implode('&', $paramArray);

        $url = sprintf($this->apiUrl, rawurlencode($address), rawurlencode($zone) ).$parameterStr;

        $this->options[CURLOPT_URL] = $url;

        $response = $this->curl($this->options);

            $decoded = json_decode( $response );

        if($decoded->status != 200){
            $decoded->result = 'Error status = '.$decoded->status;
        }

        return $decoded->result; 
    }

}

//Usage

//generate private apiKey at https://developer.mapserv.utah.gov/secure/KeyManagement
$geocoder = new geocoder(‘insert your api key here');  
$result = $geocoder->locate('123 South Main Street', 'SLC', array('acceptScore' => 90, 'spatialReference' => 4326));

echo '<pre>'.print_r($result,1).'</pre>;
\$\endgroup\$

1 Answer 1

5
\$\begingroup\$

There are a number of concerns - from code styling to error handling (echoing out an error is not good. Use Exceptions or a custom Error handler).

<?

/**
 * Access API from http://api.mapserv.utah.gov
 * 
 * @require cURL
 * @internal Create your api key at https://developer.mapserv.utah.gov/secure/KeyManagement
 */
class Geocoder {
    private $api_key;
    private $api_url;
    private $options;

    public function __construct($user_api_key, $curl_ref = '') {
        if(empty($user_api_key)){
            throw new InvalidArgumentException("Expecting API key, none received in Geocoder::__construct");
        }

        $this->api_key = $user_api_key;    
        $this->api_url = 'http://api.mapserv.utah.gov/api/v1/geocode/%s/%s';
        $this->options = array(
            # Return the transfer as string
            CURLOPT_RETURNTRANSFER => true,

            # Specify content type (json)
            CURLOPT_HTTPHEADER => array('Content-type: application/json'),

            # Status 400 "Referrer http header is missing" without this line, but it needs to be the originating website 
            CURLOPT_REFERER => (empty($curl_ref) ? $curl_ref : $_SERVER['HTTP_REFERER'])
        );
    }

    /**
     * Wrap our cURL call in a function.
     *
     * @param array $options
     * @return 
     */
    private function curl ($options){
        $cUrl = curl_init();
        curl_setopt_array( $cUrl, $options );
        $response = curl_exec( $cUrl );
        curl_close( $cUrl );
        return $response;
    }

    /**
     * Locate returns {X, Y} coordinates based on address, zone and any other parameters
     *
     * @param string $address
     * @param string $zone
     * @param array $parameters [optional]
     * @return 
     */
    public function locate ($address, $zone, $parameters = array()){
        $paramArray = array();
        $parameters['api_key'] = $this->api_key;

        foreach($parameters as $k => $v){
            $paramArray[] = $k . '=' . $v;
        }

        $this->options[CURLOPT_URL] = sprintf($this->api_url, rawurlencode($address), rawurlencode($zone) ) . 
                                      '?' . implode('&', $paramArray);

        $response = $this->curl($this->options);
        $decoded = json_decode( $response );

        if($decoded->status != 200){
            throw new RuntimeException("Error status: " . $decoded->status, $decoded->status);
        }

        return $decoded->result; 
    }
}

Then to initialize and use it, as follows:

try{
    $Geocoder = new Geocoder('insert-api-key-here');
    $result = $Geocoder->locate('123 South Main Street', 'SLC', array('acceptScore' => 90, 'spatialReference' => 4326));

    echo '<pre>' . print_r($result, 1) . '</pre>';
} catch(Exception $e){
    if ($e instanceof InvalidArgumentException OR $e instanceof RuntimeException){
        die($e->getMessage());
    } else {
        die("Caught unrecognized exception: " . $e->getMessage());
    }
}
\$\endgroup\$
7
  • \$\begingroup\$ The sample is supposed to print out the result for the person running the sample to see. It looks like you took that out? \$\endgroup\$
    – Steve
    Mar 6, 2014 at 16:52
  • \$\begingroup\$ You mean the print_r() call? I did take that out (I'd imagine a developer would want to access the $result data rather than print_r it out, but can be put in right after $result is set. \$\endgroup\$
    – jsanc623
    Mar 6, 2014 at 16:54
  • \$\begingroup\$ well the idea is github.com/agrc/GeocodingSample/blob/master/README.md \$\endgroup\$
    – Steve
    Mar 6, 2014 at 17:02
  • 1
    \$\begingroup\$ I put in the print_r again. \$\endgroup\$
    – jsanc623
    Mar 6, 2014 at 17:06
  • \$\begingroup\$ what are your thoughts on using curl to make the request and setting the http referrer header? Is that legit? \$\endgroup\$
    – Steve
    Mar 6, 2014 at 17:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.