Contents


Build and deploy an investment tracking application in the cloud with IBM Bluemix, Part 2

Use financial data from your PHP app for investment portfolio valuation

Comments

This tutorial was written using a previous version of the IBM Bluemix® interface. Given the rapid evolution of technology, some steps and illustrations may have changed.

In Part 1 of this series, you saw how to connect and retrieve financial data using an open API. And now in Part 2, you'll see how to use the financial data in a PHP application (developed in Part 1) to provide an instant, accurate valuation of an investment portfolio. A mobile-compliant interface framework enables the application to work equally well on desktop computers and mobile devices. And hosting it on IBM Bluemix® ensures reliability and scalability.

The sample application described in this series allows users to search for, and add, one or more stocks to their online investment portfolio.

What you'll need for your app

See Part 1 for the prerequisite knowledge and software, along with API requirements.

Run the sample appGet the code for the app

Download all the code and experiment!

You can download all the code implemented in this series from its GitHub repository, together with the configuration files for the PHP buildpack used here. I recommend that you get the code, start playing with it, and try adding new features to it. I guarantee you won't break anything, and it will definitely add to your learning. Have fun!

The Bluemix platform and its wide variety of services, combined with the Cloud Foundry PHP buildpack, gives you cutting-edge technical infrastructure designed for scalability and reliability.

Step 1. Initialize and configure Cloudant

In Part 1, I showed you how to set up a page template that included an Add button, linked to the /add URL route. The next logical step is to flesh out that functionality by allowing the user to add stocks to his or her portfolio. This requires a persistent data store: in this case, a Cloudant (or CouchDB) database.

  1. The easiest way to get started with Cloudant is to log in to your Bluemix account and add a new Cloudant service instance. Initially, this Cloudant service instance runs in an unbound state; this allows the application to be developed locally with the database itself hosted in the Bluemix cloud. At a later point, once the application is complete, you'll deploy it to Bluemix and bind the Cloudant service instance to the deployed application instance.
  2. To create a new Cloudant database, log in to your Bluemix account and, from the dashboard, click the USE SERVICES OR APIS button. Screen capture showing Use Services or APIs button
    Screen capture showing Use Services or APIs button
  3. From the resulting list of services, select Cloudant NoSQL DB. Screen capture showing Cloudant NoSQL DB
    Screen capture showing Cloudant NoSQL DB
  4. Review the description of the service and click USE to launch it. Ensure that the "App" field is set to "Leave unbound." Screen capture showing leaving the unbound setting
    Screen capture showing leaving the unbound setting

    The Cloudant database service instance will now be initialized and you will see a service information page. Click Launch on this page to launch the Cloudant dashboard. Here is what you should see on the Cloudant dashboard for your instance:

    Screen capture showing the Cloudant dashboard
    Screen capture showing the Cloudant dashboard
  5. Your first task here is to create a new database. Click the Add New Database button in the top menu bar, and enter the name of the new database as portfolios. Click Create to create the database. Screen capture showing Add New Database button
    Screen capture showing Add New Database button

    Your database is now created.

  6. Because each application user can have a separate portfolio, every document stored by the application must necessarily include a user identifier. The identifier that's readily available, thanks to HybridAuth, is the user's email address. So, the next step is to add a search index to Cloudant that can be used to find and return all documents matching a specified email address.

    To do this, select your newly created database in the list of available databases and, on the resulting page, select the option to create a New Search Index.

    Screen capture showing New Search Index
    Screen capture showing New Search Index
  7. Name the design doc users and the search index searchByUID. Use the following code for the search index function:
    function (doc) { 
     index("default", doc.uid);
    }
    Screen capture showing the search index function
    Screen capture showing the search index function
  8. Click Save and Build Index to save the index to the system. You can now use this searchByUID() function to retrieve all documents matching a specified email address in the document's 'uid' property. More on this later.
  9. Next, browse back to your Bluemix dashboard and select the new Cloudant service instance in the list of available services. On the resulting page, click the arrow in the left navigation bar and select Service Credentials from the resulting menu. Screen capture showing Service Credentials button
    Screen capture showing Service Credentials button
  10. Copy and paste the URL from the JSON credentials block into your application's configuration file, in the 'db_uri' key. At the same time, update the 'db_name' key to reflect the name of the new database you just created (in this example, "portfolios"). Here's what it might look like:
    <?php
    // config.php
    $config = [
      'quandl_key'    => 'YOUR-QUANDL-API-KEY',
      'oauth_id'      => 'YOUR-OAUTH-ID',
      'oauth_secret'  => 'YOUR-OAUTH-SECRET',
      'db_uri'        => 'https://username:password@instancexyz-bluemix.cloudant.com',
      'db_name'       => 'portfolios'
    ];
  11. This is a good time to update your application code in $APP_ROOT/index.php and configure the Guzzle client to use this database URI as its base URI for all requests.
    <?php
    // application initialization and other routes – snipped!
    
    // initialize HTTP client
    $guzzle = new GuzzleHttp\Client([
      'base_uri' => $app->config['db_uri'] . '/'
    ]);

Your Cloudant database instance is now active, and your application is configured to use it. You can use this database to store details for a user's investment portfolio, which is what the rest of this part is all about.

Step 2. Add stocks to a portfolio

You now have a persistent data store in place. The next step is to add a form and form processor so that users can start adding stocks to their portfolio from the search screen. Here's a simple form, which should be saved as $APP_ROOT/views/add.twig:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jquerymobile/1.4.5/jquery.mobile.min.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquerymobile/1.4.5/jquery.mobile.min.js"></script>    
  </head>
  <body>

    <div data-role="dialog">

      <div data-role="content">	
        <div>
          <h2 class="ui-bar ui-bar-a">Add to Portfolio: {{symbol}}</h2>
          <div class="ui-body">
            <form method="post" action="{{ app.request.basepath }}/add" data-ajax="false">
              <input type="hidden" name="symbol" value="{{symbol}}" />
              <label for="units">Number of shares:</label>
              <input type="text" id="units" name="units" />
              <input type="submit" name="submit" value="Add" />
            </form>
          </div>      
        </div>        
      </div>
      
    </div>
      
  </body>
</html>

This is a web form that will be styled by jQuery Mobile as a dialog, by virtue of the data-role="dialog" attribute in the top-level container. The dialog itself is fairly simple: It consists of a form with a field for the user to enter the number of shares held in the specified company, and a Submit button. The stock symbol for the company (which you will remember is passed to the page as a URL parameter) is included with the form as a hidden input.

The Silex callback for the /add route is equally simple: It renders the form, passing along the stock symbol as a template variable so that it can be interpolated into the hidden field in the form. Here's the callback, which should be added to $APP_ROOT/index.php:

<?php
// application initialization and other routes – snipped!

$app->get('/add/{symbol}', function ($symbol) use ($app) {
  return $app['twig']->render('add.twig', array('symbol' => $symbol));
})
->before($authenticate);

Once the user submits the form, a separate callback receives the user input, validates it, and saves it to the Cloudant database via REST. Here's the necessary callback, which handles the form POST submission:

<?php
// application initialization and other routes – snipped!

// form submission handler
$app->post('/add', function (Request $request) use ($app, $guzzle) {
  $symbol = strip_tags($request->get('symbol'));
  $units = (int)$request->get('units');
  if ($units <= 0) {
    throw new Exception('Invalid input');
  }
  $doc = [
    'uid' => $_SESSION['uid'],
    'symbol' => $symbol,
    'units' => $units
  ];
  $guzzle->post($app->config['db_name'], [ 'json' => $doc ]);
  return $app->redirect($app["url_generator"]->generate('index') . '#manage');
})
->before($authenticate);

Here, a Silex callback receives the form submission as a POST request and extracts the individual values submitted by the user. Basic input validation and sanitizing is performed (for example, checking that the quantity entered is a number greater than zero), and the resulting values are then formatted into a JSON document suitable for insertion into Cloudant. The Guzzle HTTP client is then used to formulate and send a POST request containing the JSON document to the Cloudant REST API; this creates and saves a new document in the Cloudant database. Finally, the client browser is redirected back to the application's index page.

To see this in action, try selecting and adding a stock through the application. You should now see something like this:

Screen capture showing selection  of new stock
Screen capture showing selection of new stock

Once you enter a value and click Add, you are redirected back to the index page. If you then browse back to the Bluemix console, launch the Cloudant dashboard, and view all documents in your database, you should see a new record containing the details for your newly added portfolio item.

Screen capture showing details of a newly added portfolio item
Screen capture showing details of a newly added portfolio item

Step 3. Update portfolio values

At this point, the application is capable of supporting data entry for a portfolio of stocks. All that's left is to calculate the value of each item in the portfolio by multiplying the quantity by the current price. For the current price, we'll reach again for the Quandl API's WIKI dataset, which (as you'll remember from Step 4 in Part 1) provides end-of-day prices for US stocks.

To do this, the application needs to connect to the Cloudant database, retrieve all available documents (each document representing a stock) for the logged-in user, and then, for each such document, connect to the Quandl API to retrieve the current price for the corresponding stock symbol. This information, together with the final value calculation, can then be provided to the template for display.

Here's the updated code for the /index route, which performs all the steps above:

<?php
// application initialization and other routes – snipped!

$app->get('/index', function () use ($app, $guzzle) {
  $uid = $_SESSION['uid'];
  // get all stocks in user's portfolio
  $response = $guzzle->get($app->config['db_name'] . '/_design/users/_search/searchByUID?include_docs=true&q='.urlencode($uid));
  $list = json_decode((string)$response->getBody());
  $symbols = [];
  $data = [];
  // extract unique list of stocks
  foreach ($list->rows as $row) {
    $symbol = $row->doc->symbol;
    $symbols[$symbol] = 0;
  }
  // get closing price for each stock
  foreach ($symbols as $key => &$value) {
    $url = "https://www.quandl.com/api/v1/datasets/WIKI/$key.json?api_key=" .$app->config['quandl_key'] . "&rows=1";
    $response = $guzzle->get($url);
    $eod = json_decode((string)$response->getBody());
    $idx = array_search('Close', $eod->column_names);
    $price = $eod->data[0][$idx];
    $value = $price;
  }
  // create structured array of stocks, prices and valuations
  foreach ($list->rows as $row) {
    $id = $row->doc->_id;
    $rev = $row->doc->_rev;
    $rid = "$id.$rev";
    $symbol = $row->doc->symbol;
    $units = $row->doc->units;
    $price = $symbols[$symbol];
    $data[$rid] = [
      'symbol' => $symbol,
      'units' => $units,
      'price' => $price,
      'value' => $units * $price,
    ];
  }
  return $app['twig']->render('index.twig', array('data' => $data, 'uid' => $uid));
})
->before($authenticate)
->bind('index');

The /index callback uses the searchByUID() method created earlier to retrieve all the documents belonging to the currently logged-in user. It then iterates over those documents to extract the list of stocks and, for each stock, generates a request to the Quandl API to retrieve the latest price. The stock price from the API response and value calculation are then combined into an array, which is passed to the template for display.

You can now also update the index page template at $APP_ROOT/views/index.twig, as shown below:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jquerymobile/1.4.5/jquery.mobile.min.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquerymobile/1.4.5/jquery.mobile.min.js"></script>    
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
    <script>      
      var myApp = angular.module('myApp', []);
      
      myApp.controller("myAppController", function ($scope, $http) {
        $scope.items = {};
        $scope.items.results = [];
        $scope.items.query = '';
        
        $scope.search = function() {
          if ($scope.items.query != '') {
            $http({
                method: 'GET',
                url: '{{ app.request.basepath }}/search/' + $scope.items.query,
              }).
              success(function(data) {
                $scope.items.results = data.docs;
              });
          } else {
            $scope.items.results = [];
          }
        };
      });
    </script>    
  </head>
  <body>

    <div data-role="page">

      <div data-role="header">
        <h1>Portfolio Tracker</h1>
        <a data-ajax="false" href="{{ app.request.basepath }}/logout" data-role="button" class="ui-btn-right">Sign out</a>
        <div class="ui-bar ui-bar-a" style="text-align: center">
          {{ uid }}
        </div>
      </div>

      <div data-role="content">
        <div data-role="tabs">
        
          <div id="navbar" data-role="navbar">
            <ul>
              <li><a id="tab-search" href="#search" data-theme="a" class="ui-btn-active">Search</a></li>
              <li><a id="tab-manage" href="#manage" data-theme="a" class="">Manage</a></li>
            </ul>
          </div>
          
          <div id="search" ng-app="myApp" ng-controller="myAppController">
            <h2 class="ui-bar ui-bar-a">Stock Search</h2>
            <div class="ui-body">
                <input type="search" name="query" ng-model="items.query" />
                <button ng-click="search()">Search</button>
            </div>      
            <h2 class="ui-bar ui-bar-a">Search Results</h2>   
            <div class="ui-body">
              <ul data-role="listview" data-split-theme="d">
                <li ng-repeat="r in items.results">
                {% verbatim %}
                  <a>{{r.name}}</a>
                {% endverbatim %}
                  <a href="{{ app.request.basepath }}{% verbatim %}/add/{{r.code}}{% endverbatim %}" data-ajax="false" data-inline="true" data-role="button" data-icon="plus" data-theme="a">Add</a>                
                </li>
              </ul>                    
            </div>          
          </div>

          <div id="manage">
            <h2 class="ui-bar ui-bar-a">Portfolio Summary</h2>
            <div class="ui-body">
              <ul data-role="listview">          
              {% for id, item in data %}
                <li>
                  <a href="#">
                    <div class="ui-grid-a">
                      <div class="ui-block-a">
                        {{ item.symbol }}
                      </div>
                      <div class="ui-block-b">
                         <span class="ui-li-count">${{ item.value }}</span>                
                      </div>
                    </div>
                    <div>
                      <p>{{ item.units }} units at ${{ item.price}}</p>
                    </div>
                  </a>
                  <a href="{{ app.request.basepath }}/delete/{{ id }}" data-ajax="false" data-inline="true" data-role="button" data-icon="minus" data-theme="a">Remove</a>                 
                </li>
              {% endfor %}
              </ul>
            </div>          
          </div>

        </div>        
      </div>

      <div data-role="footer">
      </div>

    </div>
      
  </body>
</html>

This listing adds a new "Portfolio Summary" section to the "Manage" tab of the index page. This section lists each stock in the portfolio, together with the number of units and the current value. Here's what it looks like:

Screen capture showing listing of stock in the portfolio

Screen capture showing listing of stock in the portfolio

Step 4. Delete stocks from a portfolio

From the preceding image and code listings, it's clear that each stock listed in the portfolio summary page includes a Delete button, linked to the /delete route and with the Cloudant document and revision identifier passed to the route callback as URL parameters. With everything you've learned so far, it should now be trivial for you to program a Silex callback for this route, which uses the information provided to send a DELETE request to the Cloudant REST API and remove the document from the database.

Here's the additional code:

<?php
// application initialization and other routes – snipped!

$app->get('/delete/{rid}', function ($rid) use ($app, $guzzle) {
  $arr = explode('.', $rid);
  $id = $arr[0];
  $rev = $arr[1];
  $guzzle->delete($app->config['db_name'] . '/' . $id . '?rev=' . $rev);
  return $app->redirect($app["url_generator"]->generate('index') . '#manage');
})
->before($authenticate);

This is also a good time to add an error handler to the application:

<?php
// application initialization and other routes – snipped!

// error page handler
$app->error(function (\Exception $e, $code) use ($app) {
  return $app['twig']->render('error.twig', array('error' => $e->getMessage()));
});

This simple error handler intercepts all exceptions and renders an error template containing the exception message. You can find the code for the error template in the source code repository for the application.

Step 5. Deploy to IBM Bluemix

Now that you've got the application coded, the final step is to deploy it. Of course, if you're deploying locally, you're all done; you should be able to use the application normally. However, if you're deploying on Bluemix, you'll need a Bluemix account and the Cloud Foundry command-line tool. Follow these steps to complete the deployment process.

  1. Update the application code for the Bluemix environment.

    When deploying to Bluemix, you will need to attach, or bind, a Cloudant service instance to your application. You already provisioned this via Bluemix in Step 1 above, but you needed to manually retrieve the credentials for the database instance in order to use them. However, the same credentials are exposed in the Bluemix environment via the special VCAP_SERVICES environment variable. Therefore, it's a good idea to update the application code to read this variable if available and retrieve credentials from it, as this makes your application more portable and lets you easily "swap in" a new Cloudant service instance if needed.

    Make the following addition to your $APP_ROOT/index.php file:

    <?php
    // use Composer autoloader
    require 'vendor/autoload.php';
    require 'config.php';
    
    // load classes – snipped!
    
    // initialization – snipped!
    
    // load configuration from file – snipped!
    
    // add configuration for HybridAuth – snipped!
    
    // register various providers – snipped!
    
    // if Bluemix VCAP_SERVICES environment available
    // overwrite local database credentials with Bluemix credentials
    if ($services = getenv("VCAP_SERVICES")) {
      $services_json = json_decode($services, true);
      $app->config['db_uri'] = $services_json['cloudantNoSQLDB'][0]['credentials']['url'];
    }
    
    // start session
    session_start();
    
    // application route handlers – snipped!
    
    $app->run();
  2. Create your application manifest.

    The application manifest file tells Bluemix how to deploy your application. In particular, it specifies the PHP runtime environment ("buildpack") that should be used. Create this file at $APP_ROOT/manifest.yml, filling it with the information below.

    ---
    applications:
    - name: portfolio-tracker-[your-initials]
    memory: 256M
    instances: 1
    host: portfolio-tracker-[your-initials]
    buildpack: https://github.com/cloudfoundry/php-buildpack.git
    stack: cflinuxfs2

    Remember to update the host and application name to make it unique, either by changing it or by appending a random string or number to it (such as your initials). I'm using the Cloud Foundry PHP buildpack, although alternatives are also available.

  3. Connect to Bluemix and deploy the application.

    Use the cf command-line tool to log in to Bluemix using your IBM user name and password.

    shell> cf api https://api.ng.bluemix.net
    shell> cf login

    Change to the $APP_ROOT directory and push the application to Bluemix.

    shell> cf push

    Here's a sample of what you will see during this process:

    Screen capture showing process in the command line
    Screen capture showing process in the command line
  4. Bind the Cloudant service to your application.

    Your application is now deployed, but you still need to connect it with a Cloudant database instance. Because you already provisioned this in Step 1 above, it's fairly easy to do. Just go to your Bluemix dashboard and log in with your IBM user name and password. You should see your application listed. Click the application name to arrive at the application details page.

    Screen capture showing your application details page
    Screen capture showing your application details page

    Click the BIND A SERVICE OR API button and, on the resulting page, select the Cloudant database instance you provisioned in Step 1 above, and click ADD to bind it to your application.

    Screen capture showing the Add button to bind the application

    Screen capture showing the Add button to bind the application
    You are prompted to restage your application. Do so, and you should now see the Cloudant service instance bound to your application in the Bluemix dashboard.

  5. Update your redirect URI in the Google Developers Console.

    The final step is to revisit the Google Developers Console and update the list of authorized redirect URLs to include the host name of your Bluemix application. Without this change, your OAuth redirection will fail and users will be unable to log in to the application.

  6. Start using your application.

    Once your application is deployed, you can start using it by browsing to the host you specified in your application manifest, for example: http://portfolio-tracker-[your-initials].mybluemix.net. Alternatively, you can click Run the sample app at the top of this tutorial to try out a live demo of the application. If you see a blank page or other errors, see "Debugging PHP Errors on IBM Bluemix" to debug your PHP code and find out where things are going wrong.

For more detail on using the manifest.yml file for PHP applications on Bluemix, read Carl Osipov's post "Deploy a Hello World PHP Application to Zend Server on IBM Bluemix."

Learn how to set up URL rewriting on Bluemix in my post "Configure URL Rewriting for Framework-Based PHP Applications on IBM Bluemix." (For our sample app, URL rewriting is already done in the GitHub codebase in the .htaccess file.)

Conclusion

With the widespread availability of financial data through open APIs, it's easy to create a mobile web application that leverages that data to help users make sense of their financial lives. The Bluemix platform and its wide variety of services, combined with the Cloud Foundry PHP buildpack, gives you cutting-edge technical infrastructure designed for scalability and reliability. Add in open-source components like Silex, jQuery Mobile, and HybridAuth, and you've got a full-featured toolkit to design, build, and test your applications in the cloud.


Downloadable resources


Related topics


Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Cloud computing, Mobile development, Web development
ArticleID=1023261
ArticleTitle=Build and deploy an investment tracking application in the cloud with IBM Bluemix, Part 2
publish-date=12072015