Forecast the Weather With PHP and OpenWeatherMap
Time to read: 9 minutes
Forecast the Weather With PHP and OpenWeatherMap
No matter what we do, weather is all around us and forms an intimate part of our lives. Whether you live in a colder climate, such as Canada, where it can get as low as -63C/-81.4F, or a warmer climate, such as 50C/122F in Australia, it's important to stay abreast of what's coming by forecasting the weather.
And what better way to do that, than by making your own weather app!
Okay, you could just download any one of the myriad apps available for iOS and Android. Alternatively, you could use sites such as the Australian Bureau of Meteorology, WeatherCAN, or the Meteorological Service Singapore.
But, why do that when you can build your own? Especially since — as developers — we love to learn, explore, grow, and build.
So, in this tutorial, you're going to learn how to do just that, by building a simplistic weather forecast website using PHP and the OpenWeatherMap API.
What you'll need
To follow along with this tutorial, you will need the following:
- PHP 8.3 with the SimpleXML, PCRE, LibXML, and JSON extensions. Ensure that your PHP runtime does not auto-start sessions .
- Composer installed globally
- A free or paid OpenWeatherMap account
- Your preferred IDE or text editor
- Your preferred browser
- Some prior experience with the Slim Framework and Twig templates would be ideal, but not necessary
How does the app work?
Before diving in too deeply, let's look at how the application will work. Built around the Slim Framework, it lets a user search for the current weather in any city or town around the world and, if found, displays:
- A general description of the weather for the day, such as "overcast clouds"
- The current temperature in either Celsius or Fahrenheit
- The current wind speed in kilometres or miles per/hour
- The humidity, UV index, and time of sunrise and sunset
The app will have two routes, as you can see in the sitemap above, a route to render the search form with the desired city or town name, and a route to process a submitted search form.
The Search Form route will render the search form along with any form errors if there are any. The Search Form Processing route will process form submissions. If there are errors, it will redirect the user back to the search form. If there are none, it will retrieve the weather data for the given location and render it (rather stylishly, I think).
Let's begin!
Create the project's structure
We'll start by creating the project's directory structure and changing into the project's top-level directory, by running the commands below:
The commands will create a directory structure that looks like this:
Install the required dependencies
With the directory structure in place, the next step is to install the required dependencies. There aren't many, just the following:
To install them, run the following command:
Set the required environment variables
You only need one environment variable for this app, your OpenWeatherMap API key. First, though, create a new file named .env in the project's top-level directory, and in that file paste the code below:
Then, if you already have an OpenWeatherMap API key, after logging into your OpenWeatherMap account, retrieve it from the My API keys section. If you don't have one, from My API keys, in the Create key section enter a name for the key and click Generate. Then, copy the key and paste it in .env in place of <<OPENWEATHERMAP_APIKEY>>
.
Download the static assets
Now, download the static assets, starting with the application's stylesheet, styles.css. Store it in the public/css directory as styles.css. Then, download a zip archive containing the application's images, and extract its contents in the public/img directory.
Create the search form route
This route will render a small page containing just a search form where the user can input the city that they want the weather forecast for. To do that, create a new file in the public directory named index.php. In that file, past the code below:
The code starts by using PHP Dotenv to load the OpenWeatherMap API key from .env in the project's top-level directory. Then, it initialises a DI container ($container
) with two services: view
and flash
.
The view
service returns a Twig instance which retrieves its templates from the templates directory in the project's top-level directory. The flash
service returns a new Slim\Flash\Messages
object which can store messages between requests, allowing for messages to be passed from one request to another, such as error messages.
The code finishes up by registering the search form route (/
) along with an anonymous function to handle requests to the route. The function renders the search form ( /templates/home.html.twig). If any form errors are set in a flash message they are retrieved and rendered in the template as well.
Create the base template
Each of the application's two routes have a template that renders its specific content. However, the elements common to both templates, such as the header and footer, will be stored in a separate base template, which the route-specific templates will extend.
So, in the templates directory, create a new file named base.html.twig, and in that file paste the code below:
Most of the code should be pretty self-explanatory. It's a minimalist HTML file with a small head and body. The block directives are what is most important, though. These allow for the route-specific templates to set the content in these sections, when they extend it.
Create the route's template
With the base template in place, let's create the search form route's template. In the templates directory, create a new file named home.html.twig. Then, in that file, add the code below:
The template starts by extending templates/base.html.twig. Then, it sets the content of the title
and h1
tags, along with the page's core content. The core content is composed of two parts:
- Firstly, a
div
that renders an error message, should there be a form error after submission. - Secondly, the embedded search form. It's included from a separate template as it's part of the templates for both routes, plus the error page. There's no sense in duplicating the code needlessly.
Create the form template
Next, create a new file in the templates directory named search-form.html.twig. In the file, paste the code below:
The form contains a text box for the desired city, two radio buttons to set the desired unit of measurement (imperial or metric), and a button to submit the form.
If you choose Imperial as the unit of measure, the weather data will use degrees Fahrenheit and miles per/hour. Otherwise, by choosing Metric it will use degrees Celsius and kilometres per/hour.
Create the form processing route
Now, it's time to create the route that processes form submissions. To do that, in public/index.php, add the following code after the call to $app->get()
:
Then, update the use
statements at the top of the file to match the following code:
The code adds a route that accepts POST requests to /
. The form's handler function checks if the POST data in the request's body contains a valid city
parameter. If not, it sets an error message in the flash message and redirects the user to the search form. Otherwise, it uses OpenWeatherMap, with the help of Guzzle, to retrieve the weather and UV index data.
If exceptions are thrown during the request, an HttpNotFoundException
is thrown. This short-circuits execution, rendering an error template, which you'll also see later.
If there were no exceptions, templates/weather-report.html.twig is rendered with the retrieved weather data. Finally, the application's default error handler is set. This is a new class, HtmlErrorRenderer
, which I'll cover in just a moment.
Create the route's template
Before diving into HtmlErrorRenderer
, let's first create the route's template. Create a new file named weather-report.html.twig in the templates directory. In that file, post the code below:
The template starts by rendering the search form, making it simple for the user to get a forecast for a different location. Then, it renders the various pieces of the retrieved weather forecast, using various Twig functions and filters to massage the data so that it renders as desired.
It also uses a custom Twig function, getWeatherIcon()
, to render a weather icon indicative of the overall forecast. To create the function, in src/Twig/Extension, create a new file named WeatherIconExtension.php. Then, paste the code below into the file:
The class defines two functions:
Function | Description |
---|---|
getFunctions() | This defines the Twig function names that this extension provides, along with the class method to call, when the function is used in a Twig template. |
getIcon() | This function is what is called when the function is used in a Twig template. The function takes a weather condition code, returned in the weather.id element of the response from OpenWeatherMap. It then maps the code to a human-readable string. This string is the weather icon name, minus the file extension. You can see some examples below. |
Now, you have to register the new extension with the Twig instance. To do that, update the view
container service definition, in public/index.php, so that it matches the code below:
Finally, you have to update Composer's autoloader to load the extension. To do that, add the following to composer.json:
Then, run the following command to have Composer regenerate its autoload files:
There's one final thing to do, and that's to customise the error page. You can see an example of Slim's default error page above. There's nothing wrong with it. But let's style it so that it's consistent with the look and feel of the rest of the application.
To do this you're going to create two Twig templates and a custom class, HtmlErrorRenderer
, which were mentioned earlier. In the templates directory, create a new file named 404.html.twig. In the file, paste the code below:
This template will be rendered if there is an issue retrieving weather data from the OpenWeatherMap API. As with the other templates, it extends templates/base.html.twig. It sets the title, and the h2 tag in the body, saying that the city could not be found. It then renders the search form in the body so that the user can quickly search for a different city.
Next, in templates, create a second template, named error.html.twig, and in that file paste the following code:
This template will be rendered for any other exception or error within the application, such as a missing class. If that happens, the details of the exception, including the message, title, line, and code will be rendered in the template, such as in the screenshot below:
Then, in src/Error, create a new file named HtmlErrorRenderer.php. In the file, paste the code below:
The class is initialised with a Twig
object, so that it can access and render the applicable error template. Then, based on the exception type, it sets the template to render, and renders it with the appropriate template variables.
Finally, at the end of public/index.php, add the following before $app->run();
:
Then, add the following to the use
statements at the top of the file:
These lines add Slim's built-in error middleware to the application's middleware stack, and set our new class, HtmlErrorRenderer
, as the application's error handler.
Test that the application works
Before we can test that the app works, start it using PHP's built-in webserver, by running the following command:
Then, in your browser of choice open http://localhost:8080. It should look like the screenshot below:
Enter the name of a city such as Dubbo (in New South Wales, Australia), choose Metric as the weather display format, and click Search. You should see it render the weather information, as in the screenshot below:
If you normally use Fahrenheit, enter another city, such as Raleigh (in North Carolina, USA), choose Imperial for the weather display format, and click Search.
Now, let's check that the errors work. So, without entering a city name click Search. You'll see it render an error message, via flash messages, as in the screenshot below:
Now, for one final test. Let's see how it handles non-existent locations. Enter a string of random letters, or a non-existent city name. You should then see it display the error page, as below, indicating that the city could not be found:
That's how to create a simple weather app with PHP
While there was a bit to do to build the application, it wasn't all that complicated. Now, you can find the weather where you are, where your friends and family are, all powered by open source.
If you'd like to go further, check out the paid plans that OpenWeatherMap offers. There's loads more functionality available then.
Otherwise, what would you do to extend the application? I'd love to hear your ideas.
Lastly, the application's weather icons are available on FlatIcon. Credits provided below.
Matthew Setter is the PHP Editor in the Twilio Voices team (plus Go and Rust) and a PHP and Go developer. He’s also the author of Deploy With Docker Compose. You can find him at msetter[at]twilio.com, on LinkedIn and GitHub.
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.