The following is a transcription of the Youtube video below. See for visual references.
Hello everyone, my name is Cory and I’m a Content and Support Engineer here at Geocodio. Today, we’re going to build an application that will demonstrate how to integrate a PHP project with the Geocodio API.
To accomplish this, we’re going to use our first-party PHP library, which I’ve provided a link to down below. This library will help streamline the process of calling the API to retrieve data.
The application we’re going to build will take in an address and return information about that location’s US Congressional district - and specifically its legislators. You can use this tutorial as a general guide, but it could be referenced to build a number of different tools, depending on the type of dataset you choose to return. Feel free to play around with what will best fit your needs.
It should be noted that this tutorial assumes that you already have PHP and Composer installed on your computer.
Let’s get started!
I’ve gone ahead and configured a few files to begin with.
First up, I have an empty file called index.php which will act as our controller.
To accompany our controller, I’ve also created a view, index.view.php, which will house the HTML that is rendered to our users.
Next up, I’ve built out a composer.json file that will contain all the necessary packages that will be used in our application. Make sure that you’ve required the following packages in this configuration file so that you can access them while we work.
geocodio-library-php is our in-house library that contains functions that will make the process of accessing the Geocodio API simpler and more efficient.
phpdotenv is a package that will allow us to house our API Key in a secure .env file. This is crucial, especially if you plan on uploading your repo to Github.
I also have access to the illuminate/collections package, but that may not be fully necessary for this particular project.
Run composer install in your terminal and these dependencies should automatically be downloaded, updated and stored in your new vendors directory.
With access to the phpdotenv package, I’ve also created a .gitignore file and .env file. .env houses a secure API_KEY variable that I’m not going to show you at this moment, but make sure to assign your own API Key to a similar variable here. Then, I place the .env file inside .gitignore to keep it from being loaded to Github when I push my repo.
With everything properly configured, hop back into index.php so that we can start writing some code.
First, make sure that this file can read your API_KEY from the .env file. You’re going to need that value to retrieve data from Geocodio’s API.
Make sure to require 'vendor/autoload' so that you’ll have easy access to the packages we configured in our composer.json file.
require 'vendor/autoload.php';
Once that’s done, set up a variable called $dotenv and assign it to the following namespace:
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
Then, call the function load() on $dotenv.
$dotenv->load();
This will load our .env file so that we can access any variables contained within it.
I’ve also added a debugging tool to help verify the results of our API calls in our console. This isn’t necessary, but I found it useful when I was building the application the first time.
Create a function called debug_to_console() and pass in $data. Then, create a variable called $output and set it to our $data. If $output is an array, then implode it with a comma and echo a script that logs to console. We’ll pass in our newly updated $output variable.
function debug_to_console($data) {
$output = $data;
if (is_array($output))
$output = implode(',', $output);
echo "<script>console.log('Debug Objects: " . $output . "' );</script>";
}
We also want to verify that the API Key exists and return an error if it does not. As such, create a conditional statement. If our API_KEY is empty, then die()) and return the message: “Please enter an API Key”.
You can see that we passed $_ENV(“API_KEY”) into our function. Since we’ve already set up our Dotenv configuration, this will grab the API_KEY variable from our environment file to authorize it.
if (empty($_ENV['API_KEY'])) {
die('Please enter an API Key.');
}
Now that we’ve got some of our foundations built, let’s set up the geocodio library to make a call to the API. We’ll start by hard coding in an address for testing, but in a little while, we’ll take in a dynamic input from a form.
We’ll start by initializing a new instance of Geocodio\Geocodio() class - assigning it to a variable called $geocoder. This will give us access to use the Geocodio PHP library.
$geocoder = new Geocodio\Geocodio();
To authorize our Geocodio instance, we’ll run the setApiKey() function on $geocoder and pass in our API KEY using $_ENV[‘API_KEY’]. This is a required step. Without the API Key, you will receive a 404 forbidden error that will prevent you from receiving results.
$geocoder->setApiKey($_ENV['API_KEY']);
For testing purposes, let’s create an $input variable and set it to an address string. I’ll use the address of the White House: “1600 Pennsylvania Ave. Washington, DC”. We’ll update this soon enough.
$input = "1600 Pennsylvania Ave. Washington, DC"
With an input ready to go, we can run our geocoder and receive an output. As such, create another variable called $output and assign it to run the geocode function on our $geocoder. Then, pass in our new $input variable.
This would return a basic geocode, including parsed address, latitude and longitude, as well as source and accuracy information. That might be enough for your project, but in this case, we also want to append Congressional district data.
To add data appends, add an additional parameter to your geocode function, passing in a string of the field append code into square brackets. If you’d like to append more than one dataset, you can pass in multiple codes to the array - separated by commas. In this case, we only need one: ‘cd’.
$output = $geocoder->geocode($input, ['cd']);
As one final step, I’m going to dive a little deeper into the JSON returned via our output variable and specifically grab data pertaining to Congressional districts. Create a variable called $district and assign it to the following:
Start with our new $output and from within it, grab the first result in the results array. From that, grab fields and then the first element in the congressional_districts array. If you need to see the structure of the returned JSON, check out the sample code in our API documentation.
Just in case, let’s also return null if there is no $output variable.
$district = $output->results[0]->fields->congressional_districts[0] ?? null;
Since we’ll be displaying the returned information in our view, let’s require ‘index.view.php’ at the bottom of our index.php file.
require 'index.view.php'
To verify that we’re actually calling the API, let’s set up a simple view that will allow us to display the data we’ve requested. In our index.view.php file, we’ll add the following code:
<!DOCTYPE html>
<html lang="en" class="h-full bg-gray-100">
<head>
<meta charset="UTF-8">
<title>Congress Search: Sample Geocodio PHP Site</title>
<script src="https://cdn.tailwindcss.com?plugins=forms"></script>
</head>
<body class="h-full">
<div class="min-h-full">
</div>
</body>
</html>
Within our head, I’ve set the title of the application to: "Congress Search - Sample Geocodio PHP Site". I’ve also added a script that gives us access to tailwind.css for styling purposes.
In the only div class currently available, I’m going to insert some php code. Since we’ve required the view in index.php, I should have access to the variables we created in that file. As such, I’m going to var_dump() our $output variable to see if we successfully called Geocodio and retrieved data. The reason we utilize var_dump() is because the returned object will be an stdClass object that can’t be accessed with print() or echo.
<?php var_dump($output) ?>
To check out our view start a PHP server using:
php -S localhost:8005
Then, in your browser, go to localhost:8005 to see what we’ve got.
You’ll see the stdClass object printed for you to review. There is a lot to look at here, so we won’t go over it all, but if you need an idea of how the structure of the JSON is broken down, you can see the key-value pairs displayed.
You can also take a look at our $district variable by switching it with $output.
<?php var_dump($district) ?>
Now that we know for sure that we’re calling our API, let’s build out our view so that we can display our results - and eventually add a search bar that will allow us to dynamically enter an address and return data.
To avoid too much clutter in my index view, I like to break down the HTML code into partial segments.
I’m going to create a directory called partials that we’ll store these files in.
Next, I’ll create a file called header.php and copy and paste the header from our index view into it.
I’ll do the same with the footer in a file I’ll call footer.php.
To access these partials in the view, require('partials/header.php') at the top of the file and partials/footer.php at the bottom, surrounding your var_dump(). We’ll also wrap the var_dump() in a main tag.
<?php require('partials/header.php') ?>
<main>
<?= var_dump($district) ?>
</main>
<?php require('partials/footer.php') ?>
If we save these changes and refresh our browser, you should still see the stdClass object displayed. Everything is still working.
For aesthetic purposes, we’re probably going to want a Navigation Bar at the top of the screen that contains the name of our application. I’m using Tailwind CSS to style my website, but you can style it however you choose.
I’ll create a new file called nav.php and enter the following HTML. I’ll add some text that refers to its name: Congress Search and style it to the color white.
<nav class="bg-gray-800">
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div class="flex h-16 items-center justify-between">
<div class="flex items-center">
<div>
<p style="color:white">Congress Search</p>
</div>
</div>
</div>
</div>
</nav>
Just like with my previous partials, I’ll then require('partials/nav.php') where I think it would make the most sense visually - in this case just below the header.
<?php require('partials/nav.php') ?>
If we refresh our browser again, we should see our new nav bar just above the rest of our data.
We’ll also want to add a search bar to take in an input of an address for us to geocode and then append Congressional District data.
Again, we’ll create a file, this time called form.php. We’ll add the following code, ensuring that the form’s method is a POST request. The form only needs two elements: a search input and a submit button.
<div>
<form method="POST">
<label for="default-search" class="mb-2 text-sm font-medium text-gray-900 sr-only dark:text-white">Search</label>
<div class="relative">
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<svg class="w-4 h-4 text-gray-500 dark:text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"/>
</svg>
</div>
<input type="search" name="search" id="default-search" class="block w-full p-4 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="Address..." required>
<button type="submit" class="text-white absolute right-2.5 bottom-2.5 bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Search</button>
</div>
</form>
</div>
Require this new form partial in our index file, placing it within the main tag but above the var_dump().
<main>
<?php require('partials/form.php') ?>
<?= var_dump($district) ?>
</main>
Let’s refresh our browser real quick so that we can verify that the form is rendering correctly.
It looks like it is. Awesome!
Now, when we input a string into the form and click submit, we want to grab the address provided, pass it into our geocoder instead of the hardcoded string we’re currently using.
Returning to our index file, I’m going to move our $input variable a little higher up in the file, delete the existing string and replace it with the following: $_POST[“search”]. “search” in this case is referencing our search bar form input, which is named “search”.
As such, our $input is now whatever we submit into the form. If nothing has been submitted, we’ll return null instead.
Keep in mind that this is not the most secure way to retrieve inputted form data. However, since we are not working with any personal information such as an email address or a password, this is an okay option for now. You may want to look into other methods of form input retrieval for your own projects.
If we return to the browser, we can test to see if the updated $input value is working. I’m going to use the address for the Empire State Building: "20 W 34th St., New York, NY 10001" - and click submit.
It works! You can see that the $district variable dumps out new JSON in our sample. The same would be true if we tried a different input. For example, if we used a simpler city state format and input: "Los Angeles, CA" we get the following results.
This is all great news, but it’s not the most digestible way to display this information to our user. We probably want to render some of this data from our JSON beneath the search bar.
We can start small by rendering the address and the district name. That way the customer associates the address they input with that district.
I’m going to get rid of our var_dump($district) and replace the code with the following:
<div class="flex items-center justify-center">
<p style="font-size:26px"><i><?= $output->input->formatted_address ?? "No input found." ?></i></p>
</div>
<div class="flex items-center justify-center">
<p style="font-size:30px"><b><?= $output->results[0]->address_components->state?> <?= $district->name ?? "No district found." ?></b></p>
</div>
Two div classes that will allow us to justify the results to the center of the screen.
Within the first div, we’ll add a paragraph at a font-size of about 26px. This will contain our $output variable, but then we’ll dig a little deeper to grab the formatted_address from input. If not $output is found we’ll return the string: “No input found”. I’ve also italicized this text to make it stand out a bit.
In the second div, we’ll do the same, but make the text slightly larger, since that is new information for the user. We’ll also bold the text, then grab the state information from the address_components in the first element of the results key of our $output data. Then we’ll put a space and also add the name of the $district.
This will mean the final format should be: the address followed by the state name and district where that address is located.
Let’s try the White House’s address again.
You can see that we now render the address and "DC Delegate District (at Large)" - the name of the district where the White House resides. We’re really making progress here!
But we want more than just this little but of information. The real useful aspect of this application is being able to display to the users the names of their representative and senators, as well as the contact information for these legislators.
As such, let’s run a loop through our $district variable and render cards that contain all the useful information for each legislator.
We’ll create a new partial file called cards.php. You might be wondering why we don’t just hard code three separate cards into our index file - one for the district representative and two for the state’s senators - as that might seem a bit easier. While its true that this would work for the vast majority of districts, some regions and territories are only represented by a single delegate - such as our address in Washington, DC. We want to make sure that we account for this when we render our results.
In cards.php, we’ll start a foreach() loop through each one of the current_legislators in our $district variable. Each of these will be set to a variable within the loop as $legislator. I’ll also go ahead and close our loop with endforeach just so I don’t forget later.
In this card, we’re going to grab a bunch of data from each $legislator.
As well as buttons that lead you to their:
There’s a lot of code to write here, so I’m going to speed through this, then come back and explain some of the decisions I’ve made.
<?php foreach($district->current_legislators as $legislator): ?>
<div class="max-w-sm rounded overflow-hidden shadow-lg p-10 m-2">
<p style="font-size:24px"><b><?=$legislator->bio->first_name ?? 'N/A'?> <?=$legislator->bio->last_name ?? 'N/A' ?></b></p>
<p><i><?= ucfirst($legislator->type) ?? 'N/A' ?> - <?= $legislator->bio->party ?? 'N/A' ?></i></p>
<br />
<p><?= "<b>Birthday: </b>" . $legislator->bio->birthday ?? 'N/A' ?></p>
<?php if ($legislator->bio->gender === "M"): ?>
<p><b>Gender:</b> Male</p>
<?php elseif ($legislator->bio->gender === "F"): ?>
<p><b>Gender:</b> Female</p>
<?php else: ?>
<p><b>Gender:</b> N/A</p>
<?php endif ?>
<br />
<p><?= "<b>Address: </b><br/>" . $legislator->contact->address ?></p>
<br />
<?= "<b>Phone: </b>" . $legislator->contact->phone ?>
<br />
<br />
<div class="flex flex-col space-y-4">
<button class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded-full"><a href=<?= $legislator->contact->url ?>>Website</a></button>
<button class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded-full"><a href=<?= "https://www.twitter.com/" . $legislator->social->twitter ?>>Twitter</a></button>
<button class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded-full"><a href=<?= "https://www.facebook.com/" . $legislator->social->facebook ?>>Facebook</a></button>
<button class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded-full"><a href=<?= "https://www.youtube.com/channel/" . $legislator->social->youtube_id ?>>Youtube</a></button>
</div>
</div>
<br />
<br />
<?php endforeach; ?>
Okay, the first thing you might notice is that in each of the values I return from the $district variable, I also include a simple conditional statement where if that variable is empty, I return “N/A” or Not Applicable. The reason I’ve elected to do this is because there is guarantee that every single legislator will have a completed value for every datapoint. I want to account for what to render when a value has no data.
You may also notice that I have a broader conditional statement when trying to determine what values to render for the gender category. This is because our data source returns either an “M” for Male, an “F” for Female or null if no option was provided. I wanted to be a bit more explicit than a single letter. As such, if the $legislator variable’s gender value comes back as “M”, I render "Gender: Male", if it returns “F”, I render "Gender: Female", and if it returns null, I return "Gender: Not Applicable".
Finally, I’ve also included a collection of buttons at the bottom of the card. When you click on these buttons, I’ve set them to link to any of the social media links provided for the legislator. Most of these values only return with the custom URL tag for the associated service, so I append that value to the basic URL for the relevant website. For example, "facebook.com/" and then I append the username provided in the $legislator facebook value.
The rest of the code you see here is mostly to help style it to my liking. You can either use the same styling or try something different.
Now that we have the cards.php file configured, let’s go back to our index view and render the partial so that our new code appears on the page.
Below the district name, I’m going to add a few line breaks, as well as a rounder, to help visually differentiate our cards from the information above them. Then, I’m going to create a div so that we can style the cards with some Tailwind code. Finally, I’ll require ('partials/cards.php') within the div.
<br />
<hr class="rounder" />
<br />
<div class="flex mb-4 flex items-center justify-center">
<?php require('partials/cards.php') ?>
</div>
The good news is that if I search for an address, we’ll see one of our cards rendered! And it looks great! All of the data for this legislator is contained in the card and we have successfully rendered information from our API Call.
However, there are a few things we may want to account for. If you were to load the application without an address already input, it is possible that you might receive an error message.
This would occur because we are calling the API without having submitted an address through our $input variable yet. As such, we need to account for this and only call the API if an $input variable is present. We may also want to clean up how this error might appear to the user so that it is a bit more digestible - but we’ll handle that later.
Go back to our controller, index.php. A few tweaks here should fix this problem.
Let’s take the code that calls the Geocodio API and returns the $output and $district variables - and place it within a conditional statement. If there is an input, then we’ll call the API. This should fix any potential issues, because if there is no input, we won’t call the API at all and thus won’t run into the 422 error we just saw.
if ($input) {
$geocoder = new Geocodio\Geocodio();
$geocoder->setApiKey($_ENV['API_KEY']);
$output = $geocoder->geocode($input, ['cd']);
$district = $output->results[0]->fields->congressional_districts[0] ?? null;
}
Let’s make one last adjustment, because at this point, we still don’t have the $output or $district variables available in our view when we start our application. As such, warnings might be rendered where we expect our results to be.
This is because the $output variable - which we call a number of times in our view - isn’t made available to the view until we’ve confirmed that there’s an $input.
We’ll need to make a few tweaks to fix this.
First, let’s wrap the $output and $district variables in a try-catch conditional so that will throw an exception if the code block is not executed.
$output in $district will go within try and if we catch() an Exception, then we’ll set the $district variable to null. We’ll also set the $district value to null outside of our if() statement for reasons I’ll explain in just a minute.
$district = null;
if ($input) {
$geocoder = new Geocodio\Geocodio();
$geocoder->setApiKey($_ENV['API_KEY']);
try {
$output = $geocoder->geocode($input, ['cd']);
$district = $output->results[0]->fields->congressional_districts[0] ?? null;
} catch(Exception) {
$district = null;
}
}
Switching back to our view file, we want to add a conditional statement that will display a 422 forbidden error if no $district variable is present - because that means there will be no legislator data to display. This is why we placed the $district variable outside of the if() statement in our controller, because it will act as a default when the Geocodio API hasn’t been called yet.
If the $district variable is present and we have data returned from our API call, then we will render that information in the same way that we did before.
As such, beneath our form partial, let’s start our conditional. If there is no $district, we’ll render a 422 error by requiring partials/422.php. We haven’t built out this file yet, but we will shortly.
In case that statement does not return as true, we’ll create an else that will house all of our original code - then wrap that code into an endif statement to close out the conditional. This should all be contained within our main tags.
<?php require('partials/header.php') ?>
<?php require('partials/nav.php') ?>
<main>
<?php require('partials/form.php') ?>
<?php if (! $district): ?>
<?php require('partials/422.php') ?>
<?php else: ?>
<div>
<br />
<div class="flex items-center justify-center">
<p style="font-size:26px"><i><?= $output->input->formatted_address ?? "No input found." ?></i></p>
</div>
<div class="flex items-center justify-center">
<p style="font-size:30px"><b><?= $output->results[0]->address_components->state?> <?= $district->name ?? "No district found." ?></b></p>
</div>
<br />
<hr class="rounder" />
<br />
<div class="flex mb-4 flex items-center justify-center">
<?php require('partials/cards.php') ?>
</div>
</div>
<?php endif ?>
</main>
<?php require('partials/footer.php') ?>
Now all we need to do is create that 422 partial file. In it, we’ll put some simple HTML code and a little bit of Tailwind styling. To make the text feel significant, I’ve housed it in an h1 tag. It reads “Please enter valid or existing address.”
This will have a secondary benefit of acting as instructions when the user first starts using the application, as well as an alert or warning if the address they provide does not return a result.
<div class="mx-auto max-w-7xl py-6 sm:px-6 lg:px-8 flex items-center justify-center">
<h1 class="text-2xl font-bold">Please enter valid or existing address.</h1>
</div>
Finally, we can go back to our browser and double check our work.
Now when we load our application, we won’t see any potential errors or warnings. Instead, we render instructions to enter an address. Let’s try a couple of different addresses to make sure that we’re able to receive results based on our input.
When we enter “1600 Pennsylvania Ave Washington, DC”, we get information pertaining to the delegate for that district: Eleanor Holmes Norton.
When we enter the address for the Empire State Building ("20 W 34th St., New York, NY 10001"), we get information pertaining to Representative Jerrold Nadler, Senator Kirsten Gillebrand and Senator Chuck Schumer.
Finally, we’ll try a more general location like "Los Angeles, CA". Even this will return a result for the most likely possible legislators.
Our application is up and running! We were able to utilize the Geocodio PHP library to call the Geocodio API, retrieve information about the address’ Congressional District and Legislators, then render that information for the user to review. Amazing work!
Obviously, this could still use a little cleaning up and you can customize its usage to your own needs. But I hope this tutorial has been helpful in getting you up to speed with how to implement the PHP library. I’ve included all of this sample code in a Github link down below if you’d like to take a look at it in more detail.
Please feel free to reach out to support@geocod.io with any questions you might have. Until next time, happy geocoding!