Introduction

This guide provides information for developers who want to use the Couchbase PHP SDK to build applications that use Couchbase Server.

Getting Started

We will use PHP and access the Couchbase server as part of this exercise; the Couchbase Server can be part of a cluster or it can be stand-alone. This exercise will show you how to:

  • Download, install and configure the Couchbase PHP SDK

  • How to use Couchbase and PHP to generate a general page access counter and last-accessed date indicator script

This section assumes that you have the following items set up for your development environment:

  • PHP 5.3 and above installed. For more information, see PHP Manual,

  • Web server installed and configured to serve PHP pages,

  • Couchbase Server 2.0 installed, and available from your development environment. See Couchbase Downloads

This section also assumes you have downloaded and set up a compatible version of Couchbase Server and have at least one instance of Couchbase Server and one data bucket established. If you need to set up these items, you can do with the Couchbase Administrative Console, or Couchbase Command-Line Interface (CLI), or the Couchbase REST API. For information and instructions, see:

The TCP/IP port allocation on Windows by default includes a restricted number of ports available for client communication. For more information on this issue, including information on how to adjust the configuration and increase the available ports, see [ MSDN: Avoiding TCP/IP Port Exhaustion.

After you have your Couchbase Server set up and you have installed the Couchbase SDK, you can compile and run the following basic program.

Downloading and Installation

Follow these steps to install and set up the PHP SDK:

  1. Get, Install and Start Couchbase Server. Come back when you are done.

  2. Get and install the C Library. The C SDK is a requirement for the PHP library.

  3. Download the PHP SDK for your system at Develop with Couchbase.

  4. Unpack the archive containing the PHP SDK in the directory of your choice:

    shell> tar xzf php-ext-couchbase-$system-$arch.tar.gz
    

    This creates the directory php-ext-couchbase. This directory includes a file couchbase.so which is the PHP extension for the SDK. Note the path to this file as you will use it to update the PHP configuration file with this path.

  5. Open the php.ini file for your PHP interpreter. This is the initializer for PHP which you will edit. You can get the path with this command:

    shell> php -i | grep ini
    

    The output provided will have the path to the configuration for PHP, for instance:

    Loaded Configuration File => /private/etc/php.ini
    
  6. Open the php.ini file and add the path to the couchbase.so file:

    extension=/path/to/couchbase.so
    

Depending on the platform you are using, you may also need to reference the JSON library in your PHP configuration file.

If you are using the Couchbase PHP SDK on Red Hat/CentOS or their derivatives, be aware that JSON encoding for PHP is by default not available to other extensions. As a result you will receive an error resolving the php_json_encode symbol. The solution is to edit the php.ini file to load the JSON library and also load the Couchbase library. For instance, if your extensions are at /etc/php.ini, add the following two lines, in this order, to the file:

extension=/path/to/json.so
extension=/path/to/couchbase.so

The reference to the two extensions must be in this specific order.

Now you are ready to verify your install by creating a small sample script using the PHP SDK.

Verifying the PHP SDK

To verify you have correctly set up the PHP SDK and the underlying C SDK follow these steps:

  1. Create a new file named test.php and place the following test code it:

    <?php
         $cb = new Couchbase("127.0.0.1:8091", "username", "password", "default");
         $cb->set("a", 101);
         var_dump($cb->get("a"));
     ?>
    
  2. Start your Couchbase Server. Note the ip address and port for Couchbase Server and any username and password.

  3. If you need to, update the IP address, username and password in test.php with your Couchbase Server information.

  4. Save the new script as test.php.

  5. Run your test script:

    shell> php test.php
    

    Couchbase Server returns 101.

If you are using the PHP SDK on a Linux distribution such as Red Hat/CentOS, be aware that JSON encoding for PHP is by default not available to other extensions. As a result you will receive an error resolving the php_json_encode symbol. The solution is to edit the PHP configuration file as we described earlier to include the JSON extension and then the Couchbase extension. For instance, if your PHP config file is /etc/php.ini, add the following two lines to the file:

extension=/path/to/json.so
extension=/path/to/couchbase.so

The references to your JSON library and the PHP SDK need to be provided in this order.

Now you are ready to ready to develop with the PHP SDK and Couchbase Server 2.0. For more information about methods available in this SDK, see Couchbase Client Library: PHP 1.1. For learning more in general about developing application on Couchbase Server, see Couchbase Developer’s Guide 2.0.

Accessing Couchbase from PHP

To demonstrate how to use Couchbase PHP SDK with Couchbase Server we will built a web page counter. The counter will record the last access date for a web page. we’ll implement those features in PHP using the Couchbase library.

To store data in Couchbase Server, you provide information in a way that is similar to a PHP Array. You can store a value with a key, then retrieve the value by the key. In this exercise, we will store and retrieve integer and string data.

In the first part of our PHP script, we create a connection with Couchbase Server:

<?php
#Create the Couchbase object
$cb_obj = new Couchbase("127.0.0.1:8091", "user", "pass", "default");

In the next section of our script, we find out the name of the script currently running on the web server. Typically the name would be for the counter PHP script itself:

#determine the name of the script currently running
$script_name=$_SERVER["SCRIPT_NAME"];

In this case we set the $script_name variable to be the script name that is stored in a PHP environment variable, SCRIPT_NAME. Then we try to retrieve an existing record for the key $script_name from Couchbase Server. This will tell us if the key already exists or not and wheter we need to create a new record with the key:

#if the script name doesn't exist as a Couchbase key then add

$script_access_count=$cb_obj->get($script_name);

if($cb_obj->getResultCode() == COUCHBASE_KEY_ENOENT){
    #the add will fail if it has already been added
   $cb_obj->add($script_name,0);
}

Then we use one of the Couchbase SDKs increment methods to add one to the page access count. We provide the key, which is the $script_name, to increase the counter by one. We also output the access count in a string:

#increment the integer associated with the script name
$access_count = $cb_obj->increment($script_name);

#print the current access count
echo "this page ($script_name) accessed $access_count times<br>";

In this final part of our access counter script, we retrieve the last date and time that the script had been accessed. Again, we use the key as a parameter to get the value from Couchbase Server:

#retrieve the last access date/time of the script.
#the key name is the script name prefixed with DATE::
$last_access_date=$cb_obj->get("DATE::" . $script_name);

#handle the case where this is the first access to the script
#and that key doesn't yet exist
if($last_access_date == NULL){
     $last_access_date = "never";
}
echo "this page last accessed: " . $last_access_date;

#save the current access date/time in a script
$cb_obj->set("DATE::" . $script_name,date("F j, Y, g:i:s a "));
?>

You can save this sample can be saved into a PHP file in a directory on your web server, such as test.php

When you load test.php page you’ll see something like this on the first load:

this page (/test.php) accessed 1 times
this page last accessed: never

And like this on subsequent loads, with the access count incrementing and the date/time increasing:

this page (/test.php) accessed 2 times
this page last accessed: June 1, 2011, 10:06:04 am

Reload the script a few times in rapid succession and you’ll see a spike in traffic in the Couchbase Web UI. The first line of the script creates a Couchbase client instance, ( $cb_obj ). The parameters define a Couchbase server hostname and port as well as user credentials and the default bucket name to the server pool that we’ll be accessing.

If you are running Couchbase on a single node, you specify its hostname and port. If you are running a multi-node cluster, you only need to point to a single node in a cluster as the PHP extension will transparently determine any cluster topology and route requests to the right servers as well as react on topolgy changes.

$cb_obj = new Couchbase("localhost", "user", "pass", "default");

We then get the "SCRIPT_NAME" value from the $_SERVER Array, which tells us in which script this PHP code is currently running.

$script_name=$_SERVER["SCRIPT_NAME"];

Now we connect to Couchbase to retrieve the integer access count associated with this script using the script name (determined above) as the key.

$script_access_count=$cb_obj->get($script_name);

If there is no such key in Couchbase (because the page has not previously been accessed), the request returns a NULL value, and the result code of the call to the get method is the Couchbase constant COUCHBASE_KEY_ENOENT (resource not found). In this case we use the add method to set the value to zero. If, between the failed get and add method calls, the key has already been added by another process, the add will fail and we will not overwrite the added value. This sort of attention to concurrency issues is overkill for the current application, as a missed counting of a single script access is unlikely to be critical; however, it illustrates how to write Couchbase code to avoid unintentionally overwriting keys:

if($cb_obj->getResultCode() == COUCHBASE_KEY_ENOENT){
       $cb_obj->add($script_name,0);
}

The key associated with the script name count is then incremented by one for the current script access, and that incremented value is returned and printed out. The increment method is atomic, consisting of both an increment and retrieval of the resultant value. If the Couchbase server receives two such requests, they will be queued for action in the order they were received and return the correct count to each requesting process.

$access_count = $cb_obj->increment($script_name);
echo "this page ($script_name) accessed $access_count times<br>";

To retrieve and update the last access date of the script we’re doing something similar to what we did for the script access count, however, instead of adding and incrementing an integer, we’re adding and updating a string.

As a key for the date any given script was accessed, we’ve prepended the string "DATE::" to the beginning of the script name; thus the key for the last access date of our test.php script is "DATE::test.php". We first try to get the date of last access and assign it to the $last_access_date variable. If this fails (the get method returns a Boolean NULL value and the subsequent getResultCode call returns COUCHBASE_KEY_ENOENT ) that means it hasn’t yet been set, so the script has not previously been accessed. In that case we’ll set $last_access_date to “never”. We then print out the value of $last_access_date.

$last_access_date=$cb_obj->get("DATE::" . $script_name);
 if($cb_obj->getResultCode() == COUCHBASE_KEY_ENOENT){
    $last_access_date = "never";
}
echo "this page last accessed: " . $last_access_date;

We now set the access date for this access of the script for retrieval on the next access.

$cb_obj->set("DATE::" . $script_name,date("F j, Y, g:i:s a "));

And that’s the end of the script. There is no method in the Couchbase library to close the connection to the server, however it will automatically be closed at the end of script execution.

Conclusion

This has been a brief outline of some basic features of the Couchbase API in PHP. We’ve used the add, get, set, increment and getResultCode methods of the Couchbase class to store and retrieve integers and strings. There are a number of other methods that allow for scalable development in a multiprocess and multi-server environment.

Tutorial

Building on the foundations of the Getting Started guide, this tutorial will show you how to build a full-blown web application on top of Couchbase Server 2.0. We’ll make use of a very powerful toolchain, consisting of Composer, Silex and Twig to build a solid web application fast while not losing the focus on showing how to work with the PHP SDK.

We’ll make use of the beer-sample dataset to display and manage beers and breweries. Along the way we’ll pickup concepts on querying Couchbase Server 2.0 by keys or through views.

The full code sample is available on GitHub. You can clone the repository and work from there if you would simply like to see the finished application.

  • While we’ll cover the installation process in detail, make sure to have the following dependencies in place:PHP 5.3 or later: The PHP SDK itself requires 5.3 or later, nearly all of the dependencies used here require 5.3.

  • Couchbase PHP SDK 1.1 : Install the appropriate.so or.dll file, depending on your platform. Earlier versions are not capable enough, because we make use of the brand new view-related functionality.

  • Composer : An excellent dependency manager for PHP and supported by major libraries and frameworks like Doctrine or Symfony.

Preparations

Before we can start coding our application logic, we need to prepare both the database and the application. We’ll import the beer-sample bucket, prepare some views and make sure everything works correctly. We’ll also get our application skeleton in line so we are ready to code.

Setting up Couchbase Server

If you haven’t already, download and install Couchbase Server 2.0. While you’re at it, make sure to install the beer-sample sample bucket on the fly. If you already have the server installed and the beer-sample bucket is not in place, head over to Settings->Sample Buckets and install it. Give it a few seconds until the notificaton box disappears. You may need to shrink the size of some of your existing buckets to make room for the beer-sample database.

The beer-sample bucket comes with a small set of views already predefined, but to make our application function correctly we need some more. This is also a very good chance to explore the view management possibilities inside the Web UI.

Since we want to list beers and breweries by name, we need to define one view for each. Head over to the Web UI and click on the Views menu. Select beer-sample from the dropdown list to switch to the correct bucket. Now click on Development Views and then Create Development View to define your first view. You need to give it the name of both the design document and the actual view. Insert the following names:

  • Design Document Name: _design/dev_beer

  • View Name: by_name

The next step is to define the map and (optional) reduce functions. In our examples, we won’t use have a reduce function, but you can play around if you would like to. Insert the following JavaScript map function and click Save.

function (doc, meta) {
  if(doc.type && doc.type == "beer") {
    emit(doc.name, null);
  }
}

Every map function takes the full document ( doc ) and (optionally) its associated metadata ( meta ) as the arguments. You are then free to inspect this data and emit a result when you want to have it in your view. Views are always sorted by key, so by emitting information, we are in effect creating an index. In our case we emit the name of the beer ( doc.name ) when the document both has a type field and the type is beer. We don’t need to emit a value - that’s because we are using null here. It’s always advisable to keep the view entry as small as possible. Resist the urge to include the full document through emit(meta.id, doc), because it will increase the size of your views. If you need to access the full document (or large parts), then retrieve the document via the returned id in the view result. Note: at this time PHP does not have a way to include docs, though some other Couchbase SDKs do.

Now we need to do (nearly) the same for our breweries. Since you already know how to do this, here is all the information you need to create it:

  • Design Document Name: _design/dev_brewery

  • View Name: by_name

  • Map Function:

    function (doc, meta) {
       if(doc.type && doc.type == "brewery") {
         emit(doc.name, null);
       }
     }
    

The final step that you need to do is to push the design documents in production. While the design documents are in development, the index is only applied to a subset of the data. Since we want to have the index on the whole dataset, click the Publish button on both design documents (and accept any info popup that warns you from overriding the old one). See the view section of the Couchbase Server manual for more information on how you may use this development and production workflow to your advantage when developing a large application.

Installing the Application Dependencies

Now that Couchbase Server is ready to use, we need to set up the skeleton of our application. Since we’re using composer, all we need to get the dependencies is to create a composer.json file with the following content:

{
  "require": {
    "silex/silex": "1.0.x-dev",
    "twig/twig": ">=1.8,<2.0-dev"
  }
}

Place that file inside the /beersample-php directory of your webroot (depending on your setup, it is often located under /var/www/ ). We also need to create a few more directories to keep the application organized.

Create directories with the following structure:

/beersample-php
    /templates
        /beers
        /breweries
    /assets
        /css
        /js

We’ll fill the template directories later, but the assets can be added immediately. Please locate and download the following JavaScript and CSS files so they are in place. We make use of the fabulous Twitter Bootstrap library to make the application look good without much effort.

Also, we’re using pretty URLs in our application. Here is a .htaccess file you can place inside your root directory to make it work properly when using Apache HTTPD. Please refer to the Silex documentation on how to add one for different web servers.

<IfModule mod_rewrite.c>
    Options -MultiViews

    RewriteEngine On
    RewriteBase /beersample-php
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
</IfModule>

To install the dependencies, we’re now able to run php composer.phar install (or update ). It should install all needed dependencies and we’re ready to go afterwards:

Loading composer repositories with package information
Installing dependencies
  - Installing twig/twig (v1.11.1)
    Downloading: 100%
...

  - Installing silex/silex (dev-master 0e69dc2)
    Cloning 0e69dc22400293f9364f8b918d008f3f6b634a47

symfony/routing suggests installing symfony/config (2.1.*)
...
Writing lock file
Generating autoload files

Bootstrapping the Application

With all the dependencies installed and ready to go, we can start out by adding our index.php and doing the basic bootstrap.

Create a index.php file inside your /beersample-php directory and add the following. We’ll discuss it afterwards:

<?php
use Symfony\Component\HttpFoundation\Request;
use Silex\Application;
use Silex\Provider\TwigServiceProvider;

// Config Settings
define("SILEX_DEBUG", true);
define("COUCHBASE_HOSTS", "127.0.0.1");
define("COUCHBASE_BUCKET", "beer-sample");
define("COUCHBASE_PASSWORD", "");
define("COUCHBASE_CONN_PERSIST", true);
define("INDEX_DISPLAY_LIMIT", 20);

// Autoloader
require_once __DIR__.'/vendor/autoload.php';

// Silex-Application Bootstrap
$app = new Application();
$app['debug'] = SILEX_DEBUG;

// Connecting to Couchbase
$cb = new Couchbase(COUCHBASE_HOSTS, "beer-sample", COUCHBASE_PASSWORD, COUCHBASE_BUCKET, COUCHBASE_CONN_PERSIST);

// Register the Template Engine
$app->register(new TwigServiceProvider(), array('twig.path' => __DIR__.'/templates'));

// Run the Application
$app->run();
?>

The first part defines some constants to make config settings easy to change. Of course this is not needed, but makes it easy for you to change the configuration later in different environments. Afterwards, the composer autoloader is included. This is needed to make sure the use statements are available to us without having to require all PHP files by hand.

The new Application(); initializes a new Silex application. To make sure that we see all errors during development, we can set debug to true.

Now it gets interesting. We connect to our Couchbase cluster by constructing a new Couchbase object. We pass in all required information (easy to grasp with the name of the constants). This object will then be passed into all controller actions and used from there.

Because we’re using the Twig template engine, we can register a TwigServiceProvider which helps us to automatially locate and load them. You’ll see later how these are renderd and how we can pass data to them.

Finally, we run the application through $app->run();. The actual actions are implemented between the Twig registration call ( $app->register(new TwigServiceProvider()... ) and the final run method ( $app->run() ), so remeber to put them in there.

If you now run the application in your browser, you should see the following Exception showing up: "Sorry, the page you are looking for could not be found.". This is actually great, because Silex is at work, but can’t find the route for the / URL. Let’s fix this now. Add the following snippet between the TwigServiceProvider and the run() call:

$app->get('/', function() use ($app) {
    return $app['twig']->render('welcome.twig.html');
});

This action is called when a GET request comes in for the / URL. We don’t need to fetch any data from Couchbase here, so we just instruct Silex to render a Twig template named welcome.twig.html. Since we haven’t created it yet, go ahead and place the file inside the “templates” directory:

{% extends "layout.twig.html" %}

{% block content %}
    <div class="span6">
      <div class="span12">
        <h4>Browse all Beers</h4>
        <a href="/beersample-php/beers" class="btn btn-warning">Show me all beers</a>
        <hr />
      </div>
      <div class="span12">
        <h4>Browse all Breweries</h4>
        <a href="/beersample-php/breweries" class="btn btn-info">Take me to the breweries</a>
      </div>
    </div>
    <div class="span6">
      <div class="span12">
        <h4>About this App</h4>
        <p>Welcome to Couchbase!</p>
        <p>This application helps you to get started on application
            development with Couchbase. It shows how to create, update and
            delete documents and how to work with JSON documents.</p>
        <p>The official tutorial can be found
            <a href="http://www.couchbase.com/docs/couchbase-sdk-php-1.1/tutorial.html">here</a>!</p>
      </div>
    </div>
{% endblock %}

There is nothing fancy here, we’re just showing some basic information to the user and guiding them to the real application functionality. Also, note the {% extends "layout.twig.html" %} and {% block content %} twig elements on top of the page.

Since we don’t want to repeat the HTML layout part on every template, we can define a layout and each template is a block that is loaded into that template. Since we haven’t created the layout.twig.html, do that in the same directory as the welcome.twig.html :

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Couchbase PHP Beer-Sample</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="The Couchbase PHP Beer-Sample App">
    <meta name="author" content="Couchbase, Inc. 2012">

    <link href="/beersample-php/assets/css/bootstrap.min.css" rel="stylesheet">
    <link href="/beersample-php/assets/css/beersample.css" rel="stylesheet">
    <link href="/beersample-php/assets/css/bootstrap-responsive.min.css" rel="stylesheet">

    <!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
    <!--[if lt IE 9]>
      <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
    <![endif]-->
  </head>
  <body>
    <div class="container-narrow">
      <div class="masthead">
        <ul class="nav nav-pills pull-right">
          <li><a href="/beersample-php">Home</a></li>
          <li><a href="/beersample-php/beers">Beers</a></li>
          <li><a href="/beersample-php/breweries">Breweries</a></li>
        </ul>
        <h2 class="muted">Couchbase Beer-Sample</h2>
      </div>
      <hr>
      <div class="row-fluid">
        <div class="span12">
            {% block content %}{% endblock %}
        </div>
      </div>
      <hr>
      <div class="footer">
        <p>&copy; Couchbase, Inc. 2012</p>
      </div>
    </div>
    <script src="/beersample-php/assets/js/jquery.min.js"></script>
    <script src="/beersample-php/assets/js/bootstrap.min.js"></script>
    <script src="/beersample-php/assets/js/beersample.js"></script>
  </body>
</html>

The {% block content %}{% endblock %} is responsible for loading the appropriate block later (the other markup is again just HTML boilerplate to help with a nice layout for Twitter Bootstrap ).

If you load the page, you should see the welcome page loading! If not, you may need to look at your webserver logs to see what kind of error messages have been generated when running the scripts. Assuming all is working well, we’re now ready to implement the actual functionality.

Managing Beers

The first thing we’re going to implement is to show a list of beers in a table. The table itself will contain the name of the beer and links to the brewery as well as buttons to edit or delete the beer. We’ll implement interactive filtering on the table later as well. The following code should be inserted after the / action to keep everything in order.

$app->get('/beers', function() use ($app, $cb) {
    // Load all beers from the beer/by_name view
    $results = $cb->view("beer", "by_name", array(
        'limit' => INDEX_DISPLAY_LIMIT
    ));

    $beers = array();
    // Iterate over the returned rows
    foreach($results['rows'] as $row) {
        // Load the full document by the ID
        $doc = $cb->get($row['id']);
        if($doc) {
            // Decode the JSON string into a PHP array
            $doc = json_decode($doc, true);
            $beers[] = array(
                'name' => $doc['name'],
                'brewery' => $doc['brewery_id'],
                'id' => $row['id']
            );
        }

    }

    // Render the template and pass on the beers array
    return $app['twig']->render('beers/index.twig.html', compact('beers'));
});

We’re making use of our previously defined view beer/by_name. We also pass in a limit option to make sure we don’t load all documents returned by the view. The results variable stores the view response and contains the actual data inside the rows element. We can then iterate over the dataset, but since the view only returns the document ID and we need more information, we fetch the full document through the get() method. If it actually finds a document by the given ID, we convert the JSON string to a PHP array and add it to the list of beers. The list is then passed on to the template to display it.

The corresponding template beers/index.twig.html looks like this:

{% extends "layout.twig.html" %}

{% block content %}
<h3>Browse Beers</h3>

<form class="navbar-search pull-left">
  <input id="beer-search" type="text" class="search-query" placeholder="Search for Beers">
</form>

<table id="beer-table" class="table table-striped">
  <thead>
    <tr>
      <th>Name</th>
      <th>Brewery</th>
      <th></th>
    </tr>
  </thead>
  <tbody>
      {% for beer in beers %}
        <tr>
          <td><a href="/beersample-php/beers/show/{{beer.id}}">{{beer.name}}</a></td>
          <td><a href="/beersample-php/breweries/show/{{beer.brewery}}">To Brewery</a></td>
          <td>
            <a class="btn btn-small btn-warning" href="/beersample-php/beers/edit/{{beer.id}}">Edit</a>
            <a class="btn btn-small btn-danger" href="/beersample-php/beers/delete/{{beer.id}}">Delete</a>
          </td>
        </tr>
      {% endfor %}
    </tbody>
  </table>
{% endblock %}

Aside from normal HTML table markup, we make use of the {% for beer in beers %} block to loop over the beers array. We then print out each row and show the name of the beer, the link to the brewery and also buttons to edit and delete the beer. We’ll implement these methods in a minute.

The next action we’re going to implement is the show action. When you click on a beer, it should display all attributes from the JSON document in a table so we can inspect them properly. Since everything is stored in one document, we just need to fetch it by the given ID, decode it from the JSON string and pass it on to the view. Very straightforward and performant:

$app->get('/beers/show/{id}', function($id) use ($app, $cb) {
    // Get the beer by its ID
    $beer = $cb->get($id);
    if($beer) {
       // If a document was found, decode it
       $beer = json_decode($beer, true);
       $beer['id'] = $id;
    } else {
       // Redirect if no document was found
       return $app->redirect('/beers');
    }

    // Render the template and pass the beer to it
    return $app['twig']->render(
        'beers/show.twig.html',
        compact('beer')
    );
});

The template iterates over the JSON attributes and prints their name and value accordingly. Note that some documents can contain nested values which is not covered here.

{% extends "layout.twig.html" %}

{% block content %}
<h3>Show Details for Beer "{{beer.name}}"</h3>
<table class="table table-striped">
    <tbody>
       {% for key,attribute in beer %}
        <c:forEach items="${beer}" var="item">
            <tr>
                <td><strong>{{key}}</strong></td>
                <td>{{attribute}}</td>
            </tr>
          </c:forEach>
          {% endfor %}
    </tbody>
</table>
{% endblock %}

The next action we’re going to implement is the delete action.

$app->get('/beers/delete/{id}', function($id) use ($app, $cb) {
    // Delete the Document by its ID
    $cb->delete($id);
    // Redirect to the Index action
    return $app->redirect('/beersample-php/beers');
});

As you can see, the delete call is very similar to the previous get method. After the document has been deleted, we redirect to the index action. If we’d like to, we could get more sophisticated in here. For example, good practice would be to fetch the document first and check if the document type is beer to make sure only beers are deleted here. Also, it would be appropriate to return a error message if the document didn’t exist previously. Note that there is no template needed because we redirect immediately after deleting the document.

Since we can now show and delete beers, its about time to make them editable as well. We now need to implement two different actions here. One to load the dataset and one to actually handle the POST response. Take note that this demo code is not really suited for production. You really want to add validation here to make sure only valid data is stored - but it should give you a solid idea on how to implement the basics with Couchbase.

// Show the beer form
$app->get('/beers/edit/{id}', function($id) use ($app, $cb) {
    // Fetch the document
    $beer = $cb->get($id);
    if($beer) {
        // Decode the document
       $beer = json_decode($beer, true);
       $beer['id'] = $id;
    } else {
        // Redirect if no document was found
       return $app->redirect('/beers');
    }

    // Pass the document on to the template
    return $app['twig']->render(
        'beers/edit.twig.html',
        compact('beer')
    );
});

// Store submitted Beer Data (POST /beers/edit/<ID>)
$app->post('/beers/edit/{id}', function(Request $request, $id) use ($app, $cb) {
    // Extract the POST form data out of the request
    $data = $request->request;

    $newbeer = array();
    // Iterate over the POSTed fields and extract their content.
    foreach($data as $name => $value) {
        $name = str_replace('beer_', '', $name);
        $newbeer[$name] = $value;
    }

    // Add the type field
    $newbeer['type'] = 'beer';

    // Encode it to a JSON string and save it back
    $cb->set($id, json_encode($newbeer));

    // Redirect to show the beers details
    return $app->redirect('/beersample-php/beers/show/' . $id);
});

The missing link between the GET and POST handlers is the form itself. The template is called edit.twig.html and looks like this:

{% extends "layout.twig.html" %}

{% block content %}
<h3>Edit Beer</h3>

<form method="post" action="/beersample-php/beers/edit/{{beer.id}}">
    <fieldset>
      <legend>General Info</legend>
      <div class="span12">
        <div class="span6">
          <label>Name</label>
          <input type="text" name="beer_name" placeholder="The name of the beer." value="{{beer.name}}">

          <label>Description</label>
          <input type="text" name="beer_description" placeholder="A short description." value="{{beer.description}}">
        </div>
        <div class="span6">
          <label>Style</label>
          <input type="text" name="beer_style" placeholder="Bitter? Sweet? Hoppy?" value="{{beer.style}}">

          <label>Category</label>
          <input type="text" name="beer_category" placeholder="Ale? Stout? Lager?" value="{{beer.category}}">
        </div>
      </div>
    </fieldset>
    <fieldset>
        <legend>Details</legend>
        <div class="span12">
            <div class="span6">
              <label>Alcohol (ABV)</label>
              <input type="text" name="beer_abv" placeholder="The beer's ABV" value="{{beer.abv}}">

              <label>Biterness (IBU)</label>
              <input type="text" name="beer_ibu" placeholder="The beer's IBU" value="{{beer.ibu}}">
            </div>
            <div class="span6">
              <label>Beer Color (SRM)</label>
              <input type="text" name="beer_srm" placeholder="The beer's SRM" value="{{beer.srm}}">

              <label>Universal Product Code (UPC)</label>
              <input type="text" name="beer_upc" placeholder="The beer's UPC" value="{{beer.upc}}">
            </div>
        </div>
    </fieldset>
    <fieldset>
        <legend>Brewery</legend>
        <div class="span12">
            <div class="span6">
              <label>Brewery</label>
              <input type="text" name="beer_brewery_id" placeholder="The brewery" value="{{beer.brewery_id}}">
            </div>
        </div>
    </fieldset>
    <div class="form-actions">
        <button type="submit" class="btn btn-primary">Save changes</button>
    </div>
</form>
{% endblock %}

The only special part in the form are the Twig blocks like {{beer.brewery_id}}. They allow us to easily include the actual value from the field (when there is one). You can now change the values in the input fields, hit Save changes and see the updated document either in the web application or through the Couchbase Admin UI.

There is one last thing we want to implement here. You may have noticed that the index page lists all beers but also has a search box on the top. Currently, it won’t work because the backend is not yet implemented. The JavaScript is already in place in the assets/js/beersample.js file, so look through it if you are interested. It just does an AJAX request against the server with the given search value, expects a JSON response and iterates over it while replacing the original table rows with the new ones.

We need to implement nearly the same view code as in the index action, but this time we make use of two more view query params that allow us to only return the range of documents we need:

$app->get('/beers/search', function(Request $request) use ($app, $cb) {
    // Extract the search value
    $input = strtolower($request->query->get('value'));

    // Define the Query options
    $options = array(
      'limit' => INDEX_DISPLAY_LIMIT, // Limit the number of returned documents
      'startkey' => $input, // Start the search at the given search input
      'endkey' => $input . '\uefff' // End the search with a special character (see explanation below)
    );

    // Query the view
    $results = $cb->view("beer", "by_name", $options);

    $beers = array();
    // Iterate over the resulting rows
    foreach($results['rows'] as $row) {
        // Load the corresponding document
        $doc = $cb->get($row['id']);
        if($doc) {
            // If the doc is found, decode it.
            $doc = json_decode($doc, true);
            $beers[] = array(
                'name' => $doc['name'],
                'brewery' => $doc['brewery_id'],
                'id' => $row['id']
            );
        }

    }
    // Return a JSON formatted response of all beers for the JavaScript code.
    return $app->json($beers, 200);
});

The two new query parameters we make use of are: startkey and endkey. They allow us to define the key where the search should begin and the key where the search should end. We use the special character '\uefff' here which means “end”. That way, we only get results which correctly begin with the given search string. This is a little trick that comes in very handy from time to time.

The rest is very similar to the index action so we’ll skip the discussion for that. Also, we don’t need a template here because we can return the JSON response directly. Very straightforward.

For more information about using views for indexing and querying from Couchbase Server, here are some useful resources:

Wrapping Up

If you look through the actual code on GitHub, you will notice that there is much more to it than described here. For example, some is the same but just for breweries. There is also some additional code not covered here. We recommend you look through it to become more familiar with it. Then move on to the API documentation and further examples.

If you wish to view further samples using the PHP client, please take a look at some of our DeveloperDay samples!

Couchbase Server and the PHP SDK provids a boatload of useful methods that you can use in your day-to-day work. You should now be ready to explore thos on your own, so have fun coding with Couchbase!

Using the APIs

For convenience, all distributions include a stub API reference file that contains the definitions for all the different methods and constants supported. The API information is provided in the couchbase-api.php file. Additionally, a link to the current API reference is available here.

Finding Data with Views

In Couchbase 2.0 you can index and query JSON documents using views. Views are functions written in JavaScript that can serve several purposes in your application. You can use them to:

  • Find all the documents in your database that you need for a particular process,

  • Create a copy of data in a document and present it in a specific order,

  • Create an index to efficiently find documents by a particular value or by a particular structure in the document,

  • Represent relationships between documents, and

  • Perform calculations on data contained in documents.

For more information on views, see Couchbase Developer Guide, Finding Data with Views, and Couchbase Sever 2.0: Views and Indexes.

Error Handling

Error handling with the Couchbase PHP interface is currently handled using a combination of return values, exceptions and error codes. Return values are used for operations such as get(), which returns the corresponding document value if successful, or false if the document could not be found.

An explicit error code system is available when you want to identify specific errors, but the methods to obtain these must be called immediately after each operation to identify a specific failure. These methods are deprecated in place of the PHP exception mechanism and may be removed in a future release.

All the interface calls support exceptions and will raise a specific or generic Couchbase exception if an operation fails. For more information on the exceptions supported, see Exceptions. Operations also raise a result code which can be obtained by calling the getResultCode() method immediately after an operation. Constants are provided to identify specific error conditions. See Error Codes and Constants for more information.

Exceptions

Exceptions are used within the PHP Couchbase client library to raise an error within an operation. The exceptions are all inherited from the base CouchbaseException class, or you can trap for specific exceptions. The error string associated with the exception will provide more information about the error.

For example:

try {
    $oo->touch("spoon", 1);
} catch (CouchbaseException $e) {
    echo "Error: " + $e;
}

The supported exceptions are listed below:

  • CouchbaseException

    This is the base class of all of the exceptions thrown from the extension.

  • CouchbaseIllegalKeyException

    The key provided to the operation is not legal (i.e. empty).

  • CouchbaseAuthenticationException

    Authentication to the Couchbase cluster failed.

  • CouchbaseLibcouchbaseException

    An error occurred within libcouchbase which is used by the PHP extension to communicate with the cluster.

  • CouchbaseServerException

    An error occurred somewhere in the Couchbase Cluster.

Error Codes and Constants

The PHP client library defines the following error codes and constants. These can be used against the return value for most operations to determine whether they succeeded, or failed, and in the event of a failure, what the underlying cause was. The constants are distributed between both generic interface errors, and operation specific errors. Check the corresponding API reference for information on which errors are raised by each operation.

You can use the getResultCode() and getResultMessage() methods to obtain the error code and message for each operation. You can use these in combination with the error constants to determine a specific course of action when an operation fails, for example:

$cb->replace("fork", "Hello World!", 10);

if ($cb->getResultCode() === COUCHBASE_KEY_ENOENT) {
  // Doesn't exist, add it first
  $cb->add("fork", "Hello World!", 10);
  echo "Key added (didn't exist)\n";
} elseif ($cb->getResultCode() === SUCCESS) {
  echo "Key updated\n";
} else {
  echo "Error occurred: ", $cb->getResultMessage(), "<br/>";
}

In the above example, an update operation is attempted on a key which must already exist. If Couchbase Server returns an error that the key didn’t exist, add() is used to create the key. If the operation was successful, we output a success message. For all other errors, the error message is generated.

Note: Deprecations

Note that the getResultCode() and getResultMessage() methods are now deprecated and will be removed in a future release. The PHP exception system will replace the functionality of these two methods.

Advanced Usage

Timeouts

The PHP SDK currently implements a timeout for Couchbase operations which is defaulted to 2.5 seconds. This timeout can be adjusted via the getTimeout and setTimeout methods. The following is an example = of this:

<?php
// Connect to our cluster
$cb = new Couchbase("192.168.1.200:8091");

// Adjust our timeout to 100ms rather than the default of 2500ms
$cb->setTimeout(100);

// The following operation will timeout if not completed within 100ms
$cb->add('test_key', 'test_value');

Configuration Cache

Note, this feature is currently experimental!

Due to the highly maleable nature of a Couchbase cluster, the configuration information which holds a list of all cluster nodes along with various other important pieces of cluster meta-data needs to be downloaded when a connection to the cluster is established. This is a ‘heavy’ call to make to a Couchbase cluster, and because of this, we have implemented a system that allows temporary cacheing of this data such that requests that are executed simultaneously, or close together will not incur the configuration lookup penalty.

In order to enable this feature, you must specify the couchbase.config_cache option somewhere in your php configuration. The value of this option should be a path to a directory where you would like to store the cached data.

See the example couchbase ini for further information.

API Reference

The API reference for the PHP client is available at http://www.couchbase.com/autodocs/couchbase-php-client-1.1.5/index.html

Appendix: Release Notes

The following sections provide release notes for individual release versions of Couchbase Client Library PHP. To browse or submit new issaues, see Couchbase Client Library PHP Issues Tracker.

Release Notes for Couchbase Client Library PHP 1.1.0 GA (12 December 2012)

This is the second release of the Couchbase PHP SDK which is compatible with Couchbase Server 1.8 and Couchbase Server 2.0. It is intended to be compatible with version 1.0 of the SDK. New features for this SDK include:

  • New functions for writing and accessing data using pessimistic locking, which are also known as get-and-lock functions.

  • Specify durability. You can now indicate how many replica nodes an item will be written to; you can also request a return value if Couchbase Server has successfully persisted an item to disk.

  • Indexing and querying. Ability to query a view and handle result sets returned by Couchbase Server.

To browse existing issues and fixes in this release, see PHP SDK Issues Tracking.

New Features and Behavior Changes in 1.1.0

  • Warn for incorrect view parameters.

    Issues : PCBC-13

  • Add support for unlock command.

    Issues : PCBC-52

Fixes in 1.1.0

  • Completely document constants used as options and result codes for the PHP SDK. A list of constants and result codes are available at Error Codes and Constants.

    PHP Client also now catches and processes ‘object too large’ errors coming from underlying C library. For a complete list of errors, including this one, see Error Codes and Constants.

    Issues : PCBC-92

  • Depending on the platform you are using, you may also need to reference the JSON library in your PHP configuration file:

    If you are using the Couchbase PHP SDK on Red Hat/CentOS or their derivatives, be aware that JSON encoding for PHP is by default not available to other extensions. As a result you will receive an error resolving the php_json_encode symbol. The solution is to edit the php.ini file to load the JSON library and also load the Couchbase library. Please note that you should provide these two extensions in the order shown below:

    extension=/path/to/json.so
     extension=/path/to/couchbase.so
    

    Issues : PCBC-141

  • Improve method signature for get() : move the $cas_token parameter to the second parameter, and improve handling of callback function provided as parameter.

    Issues : PCBC-73

  • Documentation incorrectly stated that version returns the version of the server; this in fact returns the version of the library. This has been fixed.

    Issues : PCBC-108

  • Provide tarball releases for PHP SDKs.

    Issues : PCBC-79

  • When doing a query against a view, the php application segfaulted. This is fixed.

    Issues : PCBC-147

Known Issues in 1.1.0

  • PHP SDK 1.1.0 is not yet available on Windows as a supported library. There is however an experimental build of the PHP SDK 1.1.0 which you can download and preview on a development system.

    Issues : PCBC-53

Release Notes for Couchbase Client Library PHP 1.1.0-dp2 Developer Preview (7 June 2012)

This is a preview release of the Couchbase PHP SDK with preliminary support for Views and Couchbase Server 2.0. Version “-dp1” was a faulty release and is thus not mentioned here.

This versions includes all fixes of the 1.0.x branch, up to the 1.0.4 release.

New Features and Behavior Changes in 1.1.0-dp2

  • Implement Couchbase::View.