Contents


Build a notepad application with PHP, MongoDB, and IBM Bluemix

Store and search free-form text notes in the cloud on a mobile or desktop web browser

Comments

Users no longer want to confine their content — photos, music, and documents — to a single desktop or laptop computer. Instead, they want it to be available everywhere, equally accessible from their mobile devices and their office desktop. To satisfy this need, there's been an explosion in the number and variety of cloud storage services, each offering storage and sync facilities so that users have access to their data at any time, from anywhere.

If you're a developer thinking about building such a service, now is a great time to get started. Cloud infrastructure has become more economical and developer-friendly without any loss of stability or scalability. Plus, the widespread availability of tools for building native mobile and mobile web applications means that developing, testing, and deploying new mobile-friendly applications are much simpler tasks than they used to be.

In this tutorial, I'll walk you through the process of building a simple notepad application that lets users store and search free-form text notes in the cloud using a mobile or desktop web browser. I'll also show you how to deploy and run the application on the IBM Bluemix® cloud platform.

What you'll need

The example notepad application allows users to create and enter an unlimited number of text notes, and also edit, search, and delete notes. In addition, users can color code notes for easy categorization or identification.

On the client, I'll use Bootstrap to create a mobile-friendly user interface for the application. On the server, I'll use Slim, a PHP micro-framework, to manage the application flow and connect and retrieve data from MongoDB.

To follow the steps in this article, you will need:

This notepad application uses MongoDB for fast and scalable document storage, the Slim PHP micro-framework for business logic, and Bootstrap for a responsive, mobile-friendly user interface.

Step 1. Create the bare application

  1. The first step is to create a bare application containing the Slim PHP micro-framework. You can download and install it by using Composer, the PHP dependency manager. Use this Composer configuration file, which should be saved to <$APP_ROOT>/composer.json (<$APP_ROOT> refers to your project directory):
    {
        "require": {
            "slim/slim": "2.*"
        }
    }
  2. Install Slim using Composer with the following command:
    shell> php composer.phar install
  3. Next, set up the main control script for the application. This script will load the Slim framework and initialize the Slim application. It will also contain callbacks for each of the application's routes, with each callback defining the code to be executed when the route is matched to an incoming request. Because the application must support listing, viewing, adding, editing, deleting, and searching notes, you can define the URL routes /index, /view, /save, and /delete as shown below. Save this script as <$APP_ROOT>/index.php.
    <?php
    // use Composer autoloader
    require 'vendor/autoload.php';
    require 'config.php';
    
    // configure Slim application instance
    // initialize application
    $app = new \Slim\Slim(array(
      'debug' => true,
      'templates.path' => './views'
    ));
    
    $app->config = $config;
    
    // index page handlers
    $app->get('/', function () use ($app) {
      $app->redirect($app->urlFor('index'));
    });
    
    // handler to list available notes in database
    // if query string included
    // filter results to match query string
    $app->get('/index', function () use ($app) {
      // code here
    })->name('index');
    
    // handler to display add/edit form
    $app->get('/save(/:id)', function ($id = null) use ($app) {
      // code here
    });
    
    // handler to process form input
    // save note content to database
    $app->post('/save', function () use ($app) {
      // code here
    });
    
    // handler to delete specified note
    $app->get('/delete/:id', function ($id) use ($app) {
      // code here
    });
    
    // handler to display specified note
    $app->get('/view/:id', function ($id) use ($app) {
      // code here
    });
    
    
    // hook to add request URI path as template variable
    $app->hook('slim.before.dispatch', function() use ($app) {
      $app->view()->appendData(array(
        'baseUri' => $app->request()->getRootUri()
      ));
    });
    
    $app->run();
  4. Notice the 'slim.before.dispatch' hook, which retrieves the current request URL (including any sub-directory paths) and makes it available as a template variable named $baseUri. This maximizes portability, as it allows you to move your application to a different directory path on the web server without needing to rewrite the URL paths in your views. You can see this in action in the various templates in the source code repository.
  5. You will also need to construct a base user interface that can be used for the various views rendered by the app. Here's an example:
    <!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">
        <title>Cloud Notepad</title>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
        <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
        <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
        <!--[if lt IE 9]>
          <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
          <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
        <![endif]-->    
      </head>
      <body>
      
        <div class="panel panel-default">
          <div class="panel-heading clearfix">
            <h4 class="pull-left">Notes</h4>
          </div>
        </div>  
    
        <!-- page content here -->
    
      
        <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
      </body>
    </html>

With all the pieces in place, you can now start building out the application itself.

Step 2. Add notes

Essentially, a note consists of three properties: a 'title', a 'body', and a 'color'. These values will be provided by the user. Each note will also include two additional properties: a unique 'id', which identifies the note in the MongoDB collection, and an 'updated' property, which stores the time that the note was last modified.

It's easy enough to build a form to match these properties. Here's what it looks like:

    <form method="post" action="<?php echo $this->data['baseUri']; ?>/save">
      <input name="id" type="hidden" value="<?php echo $this->data['note']['_id']; ?>" />
      <div class="form-group">
        <label for="title">Title</label>
        <input type="title" class="form-control" id="title" name="title" placeholder="Title" value="<?php echo htmlspecialchars($this->data['note']['title']); ?>">
      </div>
      <div class="form-group">
        <label for="color">Color</label>
        <input type="color" class="form-control" id="color" name="color" placeholder="Color" value="<?php echo$this->data['note']['color']; ?>">
      </div>
      <div class="form-group">
        <label for="body">Content</label>
        <textarea name="body" id="body" class="form-control" rows="3"><?php echo htmlspecialchars($this->data['note']['body']); ?></textarea>
      </div>
      <div class="form-group">
        <button type="submit" class="btn btn-default">Save</button>
      </div>
    </form>

Notice that the form uses the new HTML5 'color' input type, which automatically produces a color palette or color slider, allowing the user to select from a range of colors for each note. The selected color is returned as a hexadecimal value.

To reduce duplication, it makes sense to reuse this form for editing existing notes. That's why the form includes a hidden 'id' field, which will remain empty for new notes. The system can use the presence or absence of this identifier to determine whether to create a new note in the database or to update an existing one.

Here's the code fragment that initializes the database connection, using information sourced from the <$APP_ROOT>/config.php file:

<?php
// attach configuration to application
$app->config = $config;

// extract database name from URI
// initialize PHP Mongo client
$dbn = substr(parse_url($app->config['db_uri'], PHP_URL_PATH), 1);
$mongo = new MongoClient($app->config['db_uri'], array("connectTimeoutMS" => 30000));
$db = $mongo->selectDb($dbn);

Once the form is submitted to the /save endpoint, the form processor must first validate and sanitize the POST input, and then save it to the database. Here's what the callback function for that process looks like:

<?php
// handler to process form input 
// save note content to database
$app->post('/save', function () use ($app, $db) {
  $collection = $db->notes;  
  $id = trim(strip_tags($app->request->post('id')));
  $note = new stdClass;
  $note->title = trim(strip_tags($app->request->post('title')));
  $note->body = trim(strip_tags($app->request->post('body')));
  $note->color = trim(strip_tags($app->request->post('color')));
  $note->updated = time();
  if (!empty($id)) {
    $note->_id = new MongoId($id);
  }
  $collection->save($note);
  $app->redirect($app->urlFor('index'));

The code above sanitizes the POST input and sets the value of the 'updated' property to the current time. Depending on whether the POST input contains an identifier, it either creates a new MongoDB document for the note or uses the identifier to update an existing document with the revised content.

Here's an example of what adding a new note looks like:

Screen capture showing an example of a new note
Screen capture showing an example of a new note

Step 3. List and search notes

Being able to add and update notes is just part of the picture; you also need to be able to list and search for notes. Listing them is trivial: Simply update the callback for the /index route to retrieve all the documents in the collection using the MongoDB client's find() method and hand them to the view, sorted with the most recently updated first.

<?php
// handler to list available notes in database
$app->get('/index', function () use ($app, $db) {
  $collection = $db->notes;  
  $notes = $collection->find()->sort(array('updated' => -1));
  $app->render('index.tpl.php', array('notes' => $notes));
})->name('index');

Here's an example of what the output looks like:

Screen capture showing sample output
Screen capture showing sample output

If you have a large number of notes, listing them all isn't very practical. Ideally, you also want a way to search note content for one or more keywords, so that you can quickly find the information you're looking for.

  1. The first step is to update the listing template with an additional search field, as shown below:
        <div class="panel panel-default">
          <form method="get" action="<?php echo $this->data['baseUri']; ?>/index">
            <div class="input-group">
              <input type="text" name="q" class="form-control" placeholder="Search for...">
              <span class="input-group-btn">
                <button type="submit" class="btn btn-default">Go!</button>
              </span>
            </div>  
          </form>
        </div>
  2. When a user enters a search term into the field, it's necessary to modify the generic "find all documents" handler and retrieve only those documents where the 'title' or 'body' properties contain a match to the search term. Here's the revised code:
    <?php
    // handler to list available notes in database
    // if query string included
    // filter results to match query string
    $app->get('/index', function () use ($app, $db) {
      $collection = $db->notes;  
      $q = trim(strip_tags($app->request->get('q')));
      $where = array();
      if (!empty($q)) {
        $where = array(
          '$or' =>
            array(
              array(
                'title' => array('$regex' => new MongoRegex("/$q/i"))),
              array(
                'body' => array('$regex' => new MongoRegex("/$q/i")))
            )
        );  
      }
      $notes = $collection->find($where)->sort(array('updated' => -1));
      $app->render('index.tpl.php', array('notes' => $notes));
    })->name('index');

As the code above illustrates, when requests to the /index route include a query string, the handler will generate an additional condition that returns only notes containing the search term (expressed as a PHP MongoRegex object) in their title or body. This additional condition is expressed in the $where variable, which is passed to the find() method as an additional argument. The resulting data is transferred to the view and presented as before.

Here's an example of it in action:

Screen capture showing example of search results
Screen capture showing example of search results

Step 4. View and delete notes

You'll notice, from the previous image, that each note in the list includes a View button. This button is hyperlinked to the /view route and includes the document ID for the corresponding note as a request parameter. The /view callback handler merely needs to retrieve the specified note from the database using the MongoDB client's findOne() method and display it, as shown in the code below:

<?php
// handler to display specified note
$app->get('/view/:id', function ($id) use ($app, $db) {
  $collection = $db->notes;
  $note = $collection->findOne(array('_id' => new MongoId($id)));
  $app->render('view.tpl.php', array('note' => $note));
});

Here's an example of what the output looks like:

Screen capture showing example of a view
Screen capture showing example of a view

Similarly, the /delete handler receives a document ID as a request parameter and uses the MongoDB client's remove() method to delete the corresponding note from the database.

<?php
// handler to delete specified note
$app->get('/delete/:id', function ($id) use ($app, $db) {
  $collection = $db->notes;
  $collection->remove(array('_id' => new MongoId($id)));
  $app->redirect($app->urlFor('index'));
});

Step 5. Deploy to Bluemix

  1. At this point, the application is complete and can be deployed to Bluemix. First, update the application configuration file and modify the database credentials so that they point to your remote MongoDB database deployment. Then, create the application manifest file, remembering to use a unique host and application name by appending a random string to it (such as your initials).
    ---
    applications:
    - name: notes-[initials]
    memory: 256M
    instances: 1
    host: notes-[initials]
    buildpack: https://github.com/cloudfoundry/php-buildpack.git
    stack: cflinuxfs2
  2. The Cloud Foundry PHP buildpack doesn't include the PHP MongoDB extension by default, so you must configure the buildpack to enable this extension during deployment. Create a <$APP_ROOT>/.bp-config/options.json file with the following content:
    {
        "WEB_SERVER": "httpd",
        "PHP_EXTENSIONS": ["bz2", "zlib", "curl", "mcrypt", "mongo"]
    }
  3. You can now go ahead and push the application to Bluemix.
    shell> cf api https://api.ng.bluemix.net
    shell> cf login
    shell> cf push
  4. You can start using the application by browsing to the host specified in the application manifest (for example, http://notes-<initials>.mybluemix.net). If you see a blank page or other errors, see "Debugging PHP Errors on IBM Bluemix" to find out where things are going wrong.

Conclusion

It's never been easier to build desktop or mobile web applications backed by cloud infrastructure and storage. By combining the Bluemix PaaS infrastructure with MongoDB, PHP, the Slim framework, and Bootstrap, you have a complete set of tools to create, deploy, and scale your own cloud-based applications quickly and efficiently.


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, Web development, Mobile development
ArticleID=1024330
ArticleTitle=Build a notepad application with PHP, MongoDB, and IBM Bluemix
publish-date=12152015