Kubernetes with OpenShift World Tour: Get hands-on experience and build applications fast! Find a workshop!

Monitor and correlate personal asthma readings with environmental data, Part 1

For people suffering from asthma, a peak flow meter is a useful, manually-operated device to help them keep track of how well their airways are functioning. By noting peak flow readings on a regular basis, people with asthma can get an idea of their “normal” lung capacity and can gain insight into how they are affected by changes in weather or season.

Like many people with asthma, I use a peak flow meter, and, until recently, I kept track of my readings using a paper chart given to me by my physician. The problem with paper charts, though, is that after you have a significant number of readings, it’s hard to analyze them efficiently. I thought about building a cloud application to both record my peak flow meter readings electronically and help me visualize and analyze them better. This tutorial shows you the steps I followed to create this application.

Learning objectives

The example application shown in this tutorial allows users to input and save peak flow meter readings into a database. Each time a reading is added, the application automatically retrieves current external weather conditions, such as temperature and humidity, and enhances the reading with this additional metadata. The users can see visualizations and charts of the saved data, such as a bar chart of peak flow averages grouped by month, or a scatter plot of peak flow values versus temperature.

Behind the scenes, the application uses the IBM Cloud Cloudant service to save data to a Cloudant database in the cloud and the IBM Cloud Weather Company service to retrieve current weather conditions. It also uses the Slim PHP micro-framework to process requests, Bootstrap to create a mobile-optimized user experience, and Chart.js to create different types of charts and graphs from the saved data.

After completing this tutorial, you can complete the following tasks:

  • Develop a PHP-based web application for IBM Cloud.
  • Integrate data from a third-party REST API using the IBM Cloud Cloudant service.
  • Create and interact with the Cloudant database.
  • Integrate data from Weather Company’s Data API with your IBM Cloud application.
  • Create different types of HTML5 charts to visualize data.
  • Deploy your final application code on IBM Cloud and debug any runtime errors.

Although this IBM Cloud application uses PHP with the Cloudant and Weather Company Data services, you can apply similar logic to other languages and other IBM Cloud services.

The complete source code for this application is available on GitHub at /vvaswani/bluemix-asthma-tracker/.

Prerequisites

Before starting, make sure you have the following tools and skills:

NOTE: Any application that uses the Cloudant service and Weather Company Data service on IBM Cloud must comply with the corresponding Terms of Service at IBM Cloudant for IBM Cloud (11-2018) and Weather Company Data for IBM Cloud (10-2018). Similarly, any application that uses the IBM Cloud must comply with their respective terms of use, described on the IBM Cloud. Before beginning your project, spend a few minutes reading these terms and ensuring that your application complies with them.

Estimated time

This tutorial consists of two parts. It should take you approximately 60 minutes to complete each part.

Steps

In the first part of this tutorial series, you complete the following steps:

  1. Create the application skeleton.
  2. Create the application initialization script.
  3. Integrate the Twig template engine.
  4. Create the application base template.
  5. initialize a new Cloudant database.
  6. Accept and save user input to the database.

Step 1: Create the application skeleton

Slim is a lightweight PHP framework that adds structure to your code and makes it easy to integrate other components and libraries. In this tutorial, you use the Slim micro-framework as the basis for the application.

  1. To start, initialize a basic application with the Slim PHP micro-framework. Create the directories $APP_ROOT/public for all web-accessible files and $APP_ROOT/templates for all page templates. Here, $APP_ROOT refers to your local working directory, which is named myapp in the following example.

     cd myapp
     mkdir public templates
    

If you have an Apache and PHP7 development environment (optional), you should point your Apache Web server’s document root to $APP_ROOT/public.

  1. Next, create the following Composer configuration file, and save it to $APP_ROOT/composer.json:

     {
      "require": {
      "php": ">=7.0.0",
      "slim/slim": "^3.11",
      "slim/twig-view": "^2.3.0",
      "guzzlehttp/guzzle": "6.3.3"
      }
     }
    
  2. Use Composer to install Slim and other required components with the following command:

     composer install
    
  3. Finally, create the file $APP_ROOT/config.php file with the following information (you add more parameters to it later in the tutorial):

     <?php
     // config.php
     $config = [
      'settings' => [
      'displayErrorDetails' => true,
      ]
     ];
    

Step 2: Create the application initialization script

Slim works by defining application routes and callbacks for each route. When an incoming request is matched to a defined application route, the corresponding callback code is run, and the generated response is sent back to the requesting client. The routes and callbacks are defined in a PHP script.

  1. Create this script at $APP_ROOT/public/index.php with the following content:

     <?php
     use \Psr\Http\Message\ServerRequestInterface as Request;
     use \Psr\Http\Message\ResponseInterface as Response;
    
     // autoload files
     require '../vendor/autoload.php';
     require '../config.php';
    
     // configure Slim application instance
     // initialize application
     $app = new \Slim\App($config);
    
     // initialize dependency injection container
     $container = $app->getContainer();
    
     $app->run();
    

    This script does not currently contain any routes or callbacks, but these will be filled in as you progress through the tutorial.

  2. Add the following Apache configuration as $APP_ROOT/public/.htaccess so that Apache redirects all requests to the previous script:

     <IfModule mod_rewrite.c>
      RewriteEngine On
      RewriteCond %{REQUEST_FILENAME} !-f
      RewriteCond %{REQUEST_FILENAME} !-d
      RewriteRule ^ index.php [QSA,L]
     </IfModule>
    

Step 3: Integrate the Twig template engine

Slim provides two templating components to help with response generation. This tutorial uses the Twig component, because it provides several powerful features. Integrate Twig into the application code by adding it to the Slim dependency injection (DI) container as shown in the following example:

<?php
// index.php
// ...

// add view renderer to DI container
$container['view'] = function ($container) {
  $view = new \Slim\Views\Twig("../templates/");
  $router = $container->get('router');
  $uri = \Slim\Http\Uri::createFromEnvironment(new \Slim\Http\Environment($_SERVER));
  $view->addExtension(new \Slim\Views\TwigExtension($router, $uri));
  return $view;
};

// ...
$app->run();

This code initializes the Slim Twig extension and configures it to look in the $APP_ROOT/templates directory for template files. With this code in place, the Twig engine can be accessed from any of the Slim callback handlers with $this->view.

Step 4: Create the application base template

Now you create a template for the application home page, which also serves as the base template for other application pages.

  1. Use the following content in the template for the application home page at $APP_ROOT/templates/home.twig with the following content:

     <!DOCTYPE html>
     <html lang="en">
       <head>
         <meta charset="utf-8">
         <meta http-equiv="X-UA-Compatible" content="IE=edge">
         <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
         <title>Home</title>
         <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css">
         <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap-theme.min.css">
         <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
         <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
         <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
       </head>
       <body style="padding-top: 95px">
    
         <div class="container">
           <div class="row">
             <nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top justify-content-between">
               <a class="navbar-brand" href="{{ path_for('home') }}">Asthma Diary</a>
               {% block links %}
               {% endblock %}
             </nav>
           </div>
         </div>
    
         <!-- content area -->
         <div class="container">
         {% block content %}
           <div class="card-deck">
             <div class="card" style="text-align: center">
               <div class="card-header bg-primary text-white">Add data</div>
               <div class="card-body">
                 <p class="card-text">Add a new peak flow meter reading.</p>
                 <a href="#" class="btn btn-primary btn-lg">Go</a>
               </div>
             </div>
             <div class="card" style="text-align: center">
               <div class="card-header bg-primary text-white">View data</div>
               <div class="card-body">
                 <p class="card-text">View reports and visualizations of previous peak flow meter readings.</p>
                 <a href="#" class="btn btn-primary btn-lg">Go</a>
               </div>
             </div>
           </div>
         {% endblock %}
         </div>
         <!-- content area ends-->
    
         <!-- footer -->
         <div class="container">
         </div>
         <!-- footer ends -->
    
       </body>
     </html>
    

    This file contains a simple Bootstrap-based user interface with a navigation bar, and footer. In particular, note that In particular, note that both the navigation bar and the content area contain placeholder areas, marked with {% block %}...{% endblock %} markers, which you can override from child templates. This structure makes it easy to reuse the same HTML skeleton for different pages of the application.

  2. Define a route and callback function for this page. Add the following code to $APP_ROOT/public/index.php:

     <?php
     // index.php
     // ...
    
     // welcome page controller
     $app->get('/', function (Request $request, Response $response) {
       return $response->withHeader('Location', $this->router->pathFor('home'));
     });
    
     $app->get('/home', function (Request $request, Response $response) {
      $response = $this->view->render($response, 'home.twig', [
        'router' => $this->router
       ]);
       return $response;
     })->setName('home');
    
     // ...
     $app->run();
    

The code shown in the previous listing sets up two callbacks (you add more callbacks in upcoming tutorial steps).

  • The first callback is a simple redirection, which redirects all requests for the / route to the /home route.
  • The second callback is the /home route itself, which renders the content of the $APP_ROOT/views/home.twig template that you created earlier.

If you are using a local Apache and PHP7 development environment, you can browse to http://localhost/home to see the rendered version of the template. To confirm that you are proceeding correctly, check that you see a page resembling the following screen capture:

Screen capture

Step 5: Initialize a new Cloudant database

As previously discussed, the application needs a database to store its readings: in this case, a Cloudant database running on IBM Cloud.

  1. To create the database, log in to your IBM Cloud account and, from the service catalog, under Databases, select the Cloudant service. Review the description of the service and click Create to start it. Then select Use both legacy credentials and IAM as the authentication method and the free Lite plan. A new Cloudant database service is initialized, and the Cloudant service information page opens.

  2. Click Launch Cloudant Dashboard to open the Cloudant dashboard. You are redirected to the management page for your Cloudant instance.

  3. Create a database by clicking the Add New Database button in the top menu bar and entering the name of the new database as pfr. Then click Create.

  4. Go back to the service information page and under Service credentials, click the New credential button. Then, click View credentials to review the credentials for the instance, as shown in the following screen capture.

    Screen capture of credentials

  5. Copy the URL from the JSON credentials block into your application’s configuration file at $APP_ROOT/config.php, as shown in the following example:

     <?php
    /    / config.php
     $config = [
       'settings' => [
         'displayErrorDetails' => true,
         'db' => [
           'uri' => 'DB-URI',
           'name' => 'pfr'
         ],
       ]
     ];
    

Cloudant is a document-oriented database that offers an HTTP REST API to create, modify, and delete databases and documents. By convention, you use a POST request to create a new document, a PUT request to update a document, and a DELETE request to delete a document. Retrieving a document or a collection of documents requires a GET request.

To make these requests from the PHP application, you need an HTTP client. For this tutorial, you use the Guzzle HTTP client, which was already installed earlier with Composer.

You initialize the Guzzle HTTP client and add the client object to application code through the Slim dependency injection container with the following code in the initialization script at $APP_ROOT/public/index.php:

<?php
// index.php
// ...

// configure and add Cloudant client to DI container
$container['cloudant'] = function ($container) use ($config) {
  return new Client([
    'base_uri' => $config['settings']['db']['uri'] . '/',
    'timeout'  => 6000,
    'verify' => false,    // set to true in production
  ]);
};
// ...
$app->run();

With this Slim dependency injection, you can access the Cloudant API client from any handler in the script by using $this->cloudant. Notice that this client uses the Cloudant database URL (including credentials) that you added earlier to the application configuration file as its base URL for all operations.

Step 6: Accept and save user input to the database

Now you add a form for the users of the app to enter peak flow meter readings.

  1. Create the HTML code for the form in a new template file at $APP_ROOT/views/save.twig. Notice that this template extends the home page template, but overrides the content block to render a form instead of the default starter links:

     {% extends 'home.twig' %}
    
     {% block content %}
     <h3 class="display-6">Add peak flow reading</h3>
     <form method="post" action="{{ path_for('save') }}">
       <div class="form-group">
         <label for="hours">Value (L/min)</label>
         <input type="text" class="form-control" id="value" name="value">
       </div>
       <div class="form-group">
         <label for="comment">Note</label>
         <input type="text" class="form-control" id="note" name="note">
       </div>
       <div class="form-group">
         <button type="submit" name="submit" class="btn btn-success">Save</button>
       </div>
     </form>  
     {% endblock %}
    
  2. Add a callback to render this form when the application receives requests for the /save route in $APP_ROOT/public/index.php:

     <?php
     // index.php
     // ...
    
     // addition handlers
     // display form
     $app->get('/save', function (Request $request, Response $response) {
       $response = $this->view->render($response, 'save.twig', [
         'router' => $this->router, 'method' => $request->getMethod()
       ]);
       return $response;
     })->setName('save');
    
     // ...
     $app->run();
    
  3. When the user enters a reading into the form, the application must validate it and save it to the Cloudant database. Add the following code to accomplish these tasks as a separate callback in the $APP_ROOT/public/index.php script:

     <?php
     // index.php
     // ...
    
     // process form
     $app->post('/save', function (Request $request, Response $response, $args) {
       $config = $this->get('settings');
       $params = $request->getParams();
    
       // validate input
       $value = filter_var($params['value'], FILTER_SANITIZE_NUMBER_INT);
       if (!(filter_var($value, FILTER_VALIDATE_INT))) {
         throw new Exception('ERROR: Value is not valid');
       }
       $note = filter_var($params['note'], FILTER_SANITIZE_STRING);
    
       // create document with reading
       $doc = [
         'value' => $value,
         'note' => $note,
         'ts' => mktime()
       ];
    
       // save document to Cloudant
       $cloudantResponse = $this->cloudant->post($config['db']['name'], [
         'json' => $doc
       ]);
       if ($cloudantResponse->getStatusCode() == 201) {
         return $response->withHeader('Location', $this->router->pathFor('home'));
       } else {
         throw new Exception('ERROR: Document could not be created');
       }
     })->setName('save');
    
     // ...
     $app->run();
    

This callback function receives the input from the form, validates it and then creates a PHP array containing the input values and an auto-generated timestamp. It then uses the Guzzle HTTP client to send a POST request with the document to the Cloudant API and save the document to the Cloudant database. On successful creation, the Cloudant API responds with a Created (HTTP 201) response code. The callback checks for this response code and either redirects back to the home page or displays an error message.

To see this in action, browse to http://localhost/save, where you should see the following input form:

Screen capture of Asthma Diary input form

Enter a value and note into the form and save it. The document should open in the Cloudant dashboard, as shown in the following screen capture:

Screen capture of Cloudant dashboard

Summary

This tutorial discussed specific tools, technologies, and APIs. Although these tools and APIs might change and evolve over time, the general principles remain valid and should serve as guidance for other projects you undertake. As you go on to work with APIs and cloud computing in Part 2 of this series, and in your own work, take the following three guidelines with you:

  • A micro-framework that supports dependency injection helps you create a lightweight, decoupled application and lets you invoke specific libraries only when necessary.
  • A secure, stable and extensible HTTP client library that works independently of system libraries makes it easy to integrate your application with any third-party REST API.
  • A template engine lets you separate your business logic from your interface. If it supports inheritance, it helps create a consistent user experience, by defining a base layout that can be reused everywhere but can also be overridden for specific scenarios.

At the end of this first tutorial in the series, you have a working PHP application that is integrated with the Cloudant database service on IBM Cloud and is able to accept, validate, and save peak flow meter readings to the database. You also have a basic understanding of how routing, dependency injection, page templates, and third-party API integration works with Slim PHP applications.

The second part of this tutorial builds on what you have learned by adding more functions to the application, integrating it with a weather data service in the IBM Cloud, creating HTML5 charts and graphs from the data, and then deploying the result to IBM Cloud using a CloudFoundry PHP buildpack.

Vikram Vaswani