PHP Riak Client

Taste of Riak: PHP

If you haven’t set up a Riak Node and started it, please visit the Prerequisites first.

Client Setup

Here is a Composer example:

$ composer require "php-riak/riak-client"

If you are using a single local Riak node, use the following to create a new client instance, assuming that the node is running on localhost port 8087 Protocol Buffers or port 8098 for http:

The easiest way to get started with the client is using a RiakClientBuilder :

<?php
use Riak\Client\RiakClientBuilder;

$builder = new RiakClientBuilder();
$client  = $builder
    ->withNodeUri('http://192.168.1.1:8098')
    ->withNodeUri('proto://192.168.1.2:8087')
    ->build();

Once you have a $client we are now ready to start interacting with Riak.

Creating Objects in Riak

The first object that we create is a very basic object with a content type of text/plain. Once that object is created, we create a StoreValue operation that will store the object later on down the line:

<?php
use Riak\Client\Command\Kv\StoreValue;
use Riak\Client\Core\Query\RiakObject;
use Riak\Client\Core\Query\RiakLocation;
use Riak\Client\Core\Query\RiakNamespace;

$object    = new RiakObject();
$namespace = new RiakNamespace('default', 'quotes');
$location  = new RiakLocation($namespace, 'icemand');

$object->setValue("You're dangerous, Maverick");
$object->setContentType('text/plain');

// store object
$store  = StoreValue::builder($location, $object)
    ->withPw(1)
    ->withW(2)
    ->build();

// Use our client object to execute the store operation
$client->execute($store);

Reading Objects from Riak

After that, we check to make sure that the stored object has the same value as the object that we created. This requires us to fetch the object by way of a FetchValue operation:

<?php
use Riak\Client\Command\Kv\FetchValue;
use Riak\Client\Core\Query\RiakLocation;
use Riak\Client\Core\Query\RiakNamespace;

$namespace = new RiakNamespace('default', 'quotes');
$location  = new RiakLocation($namespace, 'icemand');

// fetch object
$fetch  = FetchValue::builder($location)
    ->withNotFoundOk(true)
    ->withR(1)
    ->build();

/** @var $result \Riak\Client\Command\Kv\Response\FetchValueResponse */
/** @var $object \Riak\Client\Core\Query\RiakObject */
$result = $client->execute($fetch);
$object = $result->getValue();

echo $object->getValue();
// You're dangerous, Maverick

Deleting Objects from Riak

Now that we’ve stored and then fetched the object, we can delete it by creating and executing a DeleteValue operation:

<?php
use Riak\Client\Command\Kv\DeleteValue;
use Riak\Client\Core\Query\RiakLocation;
use Riak\Client\Core\Query\RiakNamespace;

$namespace = new RiakNamespace('default', 'quotes');
$location  = new RiakLocation($namespace, 'icemand');

// delete object
$delete  = DeleteValue::builder($location)
    ->withPw(1)
    ->withW(2)
    ->build();

$this->client->execute($delete);

Working With Complex Objects

Since the world is a little more complicated than simple integers and bits of strings, let’s see how we can work with more complex objects. Take for example, this plain PHP object that encapsulates some knowledge about a book.

<?php
class Book implements \JsonSerializable
{
    private $title;
    private $author;
    private $body;
    private $isbn;
    private $copiesOwned;

    // getter and setters.

    public function jsonSerialize()
    {
        return [
            'body'        => $this->body,
            'title'       => $this->title,
            'author'      => $this->author,
            'copiesOwned' => $this->copiesOwned,
        ];
    }
}

By default, the PHP Riak client serializes PHP Objets as JSON. Let’s create a new Book object to store:

<?php
$mobyDick = new Book();

$modyDick->setTitle("Moby Dick");
$mobyDick->setAuthor("Herman Melville");
$mobyDick->setBody("Call me Ishmael. Some years ago...");
$mobyDick->setIsbn("11119799723");
$mobyDick->setCopiesOwned(3);

Now we can store that Object object just like we stored the riak object earlier:

<?php
use Riak\Client\Command\Kv\StoreValue;
use Riak\Client\Core\Query\RiakLocation;
use Riak\Client\Core\Query\RiakNamespace;

$namespace = new RiakNamespace('default', 'books');
$location  = new RiakLocation($namespace, 'moby_dick');

/** @var $mobyDick \Book */
$store  = StoreValue::builder($location, $mobyDick)
    ->withPw(1)
    ->withW(2)
    ->build();

$client->execute($store);

If we fetch the object using the same method we showed up above, we should get the following:

<?php
use Riak\Client\Command\Kv\FetchValue;
use Riak\Client\Core\Query\RiakLocation;
use Riak\Client\Core\Query\RiakNamespace;

$namespace = new RiakNamespace('default', 'books');
$location  = new RiakLocation($namespace, 'moby_dick');

// fetch object
$fetch  = FetchValue::builder($location)
    ->withNotFoundOk(true)
    ->withR(1)
    ->build();

/** @var $result \Riak\Client\Command\Kv\Response\FetchValueResponse */
/** @var $object \Riak\Client\Core\Query\RiakObject */
$result = $client->execute($fetch);
$book   = $result->getValue('Book');
$object = $result->getValue();

echo $book->getTitle();
//  "Moby Dick"

echo $object->getValue();
/*
{
  "title": "Moby Dick",
  "author": "Herman Melville",
  "body": "Call me Ishmael. Some years ago...",
  "isbn": "1111979723",
  "copiesOwned": 3
}
*/

Development

All development is done on Github. Use Issues to report problems or submit contributions.

This tutorial documentation its based on the Basho Taste of Riak Docs.

Contents

Riak Client

The easiest way to get started with the client is using a RiakClientBuilder :

<?php
use Riak\Client\RiakClientBuilder;

$builder = new RiakClientBuilder();
$client  = $builder
    ->withNodeUri('http://192.168.1.1:8098')
    ->withNodeUri('proto://192.168.1.2:8087')
    ->build();

Once you have a $client, commands from the RiakClientCommand* namespace are built then executed by the client.

<?php
use Riak\Client\Command\Kv\FetchValue;
use Riak\Client\Core\Query\RiakObject;
use Riak\Client\Core\Query\RiakLocation;
use Riak\Client\Core\Query\RiakNamespace;

$namespace = new RiakNamespace('bucket_type', 'bucket_name');
$location  = new RiakLocation($namespace, 'object_key');

// fetch object
$fetch  = FetchValue::builder($location)
    ->withNotFoundOk(true)
    ->withR(1)
    ->build();

$result = $client->execute($fetch);
$object = $result->getValue();
RiakCommand classes
  • Fetching, storing and deleting objects

    • Riak\Client\Command\Kv\FetchValue
    • Riak\Client\Command\Kv\StoreValue
    • Riak\Client\Command\Kv\UpdateValue
    • Riak\Client\Command\Kv\DeleteValue
  • Fetching and storing datatypes (CRDTs)

    • Riak\Client\Command\DataType\FetchCounter
    • Riak\Client\Command\DataType\FetchSet
    • Riak\Client\Command\DataType\FetchMap
    • Riak\Client\Command\DataType\StoreCounter
    • Riak\Client\Command\DataType\StoreSet
    • Riak\Client\Command\DataType\StoreMap
  • Querying and modifying buckets

    • Riak\Client\Command\Bucket\FetchBucketProperties
    • Riak\Client\Command\Bucket\StoreBucketProperties
  • Secondary index (2i)

    • Riak\Client\Command\Index\BinIndexQuery
    • Riak\Client\Command\Index\IntIndexQuery
  • Yokozuna Search

    • Riak\Client\Command\Search\DeleteIndex
    • Riak\Client\Command\Search\FetchIndex
    • Riak\Client\Command\Search\StoreIndex
    • Riak\Client\Command\Search\StoreSchema
    • Riak\Client\Command\Search\FetchSchema
    • Riak\Client\Command\Search\Search

Buckets & Bucket Types

For more information on how bucket types work and how to managing bucket types see : Basho Bucket Types Docs.

In versions of Riak prior to 2.0, all queries are made to a bucket/key pair, as in the following example read request:

<?php
use Riak\Client\Command\Kv\FetchValue;
use Riak\Client\Core\Query\RiakLocation;
use Riak\Client\Core\Query\RiakNamespace;

$namespace = new RiakNamespace('<bucket_type>', '<bucket_name>');
$location  = new RiakLocation($namespace, '<key>');

// fetch object
$fetch  = FetchValue::builder($location)
    ->withNotFoundOk(true)
    ->withR(1)
    ->build();

/** @var $result \Riak\Client\Command\Kv\Response\FetchValueResponse */
$result = $client->execute($fetch);

To modify the properties of a bucket in Riak:

<?php
use Riak\Client\Command\Bucket\StoreBucketProperties;
use Riak\Client\Core\Query\RiakNamespace;

$namespace = new RiakNamespace('<bucket_type>', '<bucket_name>');
$store     =StoreBucketProperties::builder($namespace)
    ->withLinkwalkFunction($linkwalkFunction)
    ->withChashkeyFunction($chashkeyFunction)
    ->withPostcommitHook($postcommitFunction)
    ->withPrecommitHook($precommitFunction)
    ->withSearchIndex($searchIndex)
    ->withBasicQuorum($basicQuorum)
    ->withLastWriteWins($wins)
    ->withBackend($backend)
    ->withAllowMulti($allow)
    ->withNotFoundOk($ok)
    ->withSmallVClock($smallVClock)
    ->withYoungVClock($youngVClock)
    ->withOldVClock($ldVClock)
    ->withBigVClock($bigVClock)
    ->withNVal($nVal)
    ->withRw($rw)
    ->withDw($dw)
    ->withPr($pr)
    ->withPw($pw)
    ->withW($w)
    ->withR($w);

$this->client->execute($store);

We can simply retrieve the bucket properties :

<?php
use Riak\Client\Command\Bucket\FetchBucketProperties;
use Riak\Client\Core\Query\RiakNamespace;

$namespace = new RiakNamespace('<bucket_type>', '<bucket_name>');
$fetch     = FetchBucketProperties::builder()
        ->withNamespace($namespace)
        ->build();

/** @var $response \Riak\Client\Command\Bucket\Response\FetchBucketPropertiesResponse */
/** @var $props \Riak\Client\Core\Query\BucketProperties */
$response = $this->client->execute($fetch);
$props    = $response->getProperties();

echo $props->getNVal();
// 3

This tutorial documentation its based on the Basho Bucket Types Docs.

Key/Value commands

StoreValue
<?php
use Riak\Client\Command\Kv\StoreValue;
use Riak\Client\Core\Query\RiakObject;
use Riak\Client\Core\Query\RiakLocation;
use Riak\Client\Core\Query\RiakNamespace;

$namespace = new RiakNamespace('bucket_type', 'bucket_name');
$location  = new RiakLocation($namespace, 'object_key');
$object    = new RiakObject();

$object->setContentType('application/json');
$object->setValue('{"name": "FabioBatSilva"}');

// store object
$store  = StoreValue::builder($location)
    ->withReturnBody(true)
    ->withPw(1)
    ->withW(2)
    ->build();

$result = $client->execute($store);
$object = $result->getValue();
FetchValue
<?php
use Riak\Client\Command\Kv\FetchValue;
use Riak\Client\Core\Query\RiakObject;
use Riak\Client\Core\Query\RiakLocation;
use Riak\Client\Core\Query\RiakNamespace;

$namespace = new RiakNamespace('bucket_type', 'bucket_name');
$location  = new RiakLocation($namespace, 'object_key');

// fetch object
$fetch  = FetchValue::builder($location)
    ->withNotFoundOk(true)
    ->withR(1)
    ->build();

$result = $client->execute($fetch);
$object = $result->getValue();
DeleteValue
<?php
use Riak\Client\Command\Kv\DeleteValue;
use Riak\Client\Core\Query\RiakObject;
use Riak\Client\Core\Query\RiakLocation;
use Riak\Client\Core\Query\RiakNamespace;

$namespace = new RiakNamespace('bucket_type', 'bucket_name');
$location  = new RiakLocation($namespace, 'object_key');

// delete object
$delete  = DeleteValue::builder($location)
    ->withPw(1)
    ->withW(2)
    ->build();

$client->execute($delete);
ListKeys
<?php
use Riak\Client\Command\Kv\ListKeys;
use Riak\Client\Core\Query\RiakNamespace;

$namespace = new RiakNamespace('bucket_type', 'bucket_name');
$location  = new RiakLocation($namespace, 'object_key');

/** @var $result \Riak\Client\Command\Kv\Response\ListKeysResponse */
/** @var $locations \Riak\Client\Core\Query\RiakLocation[] */
$result    = $client->execute($command);
$locations = $result->getLocations();

echo $locations[0]->getKey();
// object_key

Data Types

This tutorial documentation its based on the Basho CRDT Docs. If you are not familiar with crdt in Riak Before you start take a look at Basho CRDT Docs for more details.

In versions 2.0 and greater, Riak users can make use of a variety of Riak-specific data types inspired by research on convergent replicated data types, more commonly known as CRDTs.

Location

Here is the general syntax for setting up a bucket type/bucket/key combination to handle a data type:

<?php
use Riak\Client\Core\Query\RiakLocation;
use Riak\Client\Core\Query\RiakNamespace;

$namespace = new RiakNamespace('<bucket_type>', '<bucket_name>');
$location  = new RiakLocation($namespace, '<key>');
Counters

Counters are a bucket-level Riak Data Type that can be used either by themselves, i.e. associated with a bucket/key pair, or within a map. The examples in this section will show you how to use counters on their own.

Let’s say that we want to create a counter called traffic_tickets in our counters bucket to keep track of our legal misbehavior. We can create this counter and ensure that the counters bucket will use our counters bucket type like this:

<?php
use Riak\Client\Core\Query\RiakLocation;
use Riak\Client\Core\Query\RiakNamespace;

$namespace = new RiakNamespace('counters', 'counters');
$location  = new RiakLocation($namespace, 'traffic_tickets');

Now that our client knows which bucket/key pairing to use for our counter, traffic_tickets will start out at 0 by default. If we happen to get a ticket that afternoon, we would need to increment the counter

use Riak\Client\Command\DataType\StoreCounter;

<?php
// Increment the counter by 1 and fetch the current value
$store = StoreCounter::builder()
    ->withReturnBody(true)
    ->withLocation($location)
    ->withDelta(1)
    ->build();

$result  = $client->execute($store);
$counter = $result->getDatatype();
$value   = $counter->getValue();

echo $value;
// 1

If we’re curious about how many tickets we have accumulated, we can simply retrieve the value of the counter at any time:

use Riak\Client\Command\DataType\FetchCounter;

<?php
// fetch counter
$fetch = FetchCounter::builder()
    ->withLocation($location)
    ->withR(1)
    ->build();

$result  = $client->execute($store);
$counter = $result->getDatatype();
$value   = $counter->getValue();

For a counter to be useful, you need to be able to decrement it in addition to incrementing it. Riak counters enable you to do precisely that. Let’s say that we hire an expert lawyer who manages to get one of our traffic tickets stricken from our record:

use Riak\Client\Command\DataType\StoreCounter;

<?php
$store = StoreCounter::builder()
    ->withLocation($location)
    ->withDelta(-1)
    ->build();

$client->execute($store);
Sets

As with counters (and maps, as shown below), using sets involves setting up a bucket/key pair to house a set and running set-specific operations on that pair.

Here is the general syntax for setting up a bucket type/bucket/key combination to handle a set:

<?php
use Riak\Client\Core\Query\RiakLocation;
use Riak\Client\Core\Query\RiakNamespace;

$namespace = new RiakNamespace('sets', 'travel');
$location  = new RiakLocation($namespace, 'cities');

Let’s say that we read a travel brochure saying that Toronto and Montreal are nice places to go. Let’s add them to our cities set:

<?php

use Riak\Client\RiakOption;
use Riak\Client\Command\DataType\StoreSet;

// Store new cities and return the current value
$store = StoreCounter::builder()
    ->withLocation($location)
    ->withReturnBody(true)
    ->build();

$store->add("Toronto")
$store->add("Montreal")

$result = $client->execute($store);
$set    = $set->getDatatype();
$value  = $counter->getValue();

var_dump($value);
// ["Toronto", "Montreal"]

Later on, we hear that Hamilton and Ottawa are nice cities to visit in Canada, but if we visit them, we won’t have time to visit Montreal, so we need to remove it from the list. It needs to be noted here that removing an element from a set is a bit tricker than adding elements.

<?php

use Riak\Client\RiakOption;
use Riak\Client\Command\DataType\StoreSet;
use Riak\Client\Command\DataType\FecthSet;

$fetch = FecthSet::builder()
    ->withLocation($location)
    ->build();

$fetchResult  = $client->execute($store);
$fetchContext = $result->getContext();

$store = StoreCounter::builder()
    ->withContext($fetchContext)
    ->withLocation($location)
    ->withReturnBody(true)
    ->build();

$store->add("Ottawa");
$store->add("Vancouver");
$store->remove("Montreal");

$result  = $client->execute($store);
$set     = $result->getDatatype();
$value   = $set->getValue();

var_dump($value);
// ["Ottawa","Vancouver","Toronto"]
Maps

The map is in many ways the richest of the Riak Data Types because all of the other Data Types can be embedded within them, including maps themselves, to create arbitrarily complex custom Data Types out of a few basic building blocks

Let’s say that we want to use Riak to store information about our company’s customers. We’ll use the bucket customers to do so. Each customer’s data will be contained in its own key in the customers bucket. Let’s create a map for the user Ahmed (ahmed_info) in our bucket and simply call it map for simplicity’s sake:

<?php
use Riak\Client\Core\Query\RiakLocation;
use Riak\Client\Core\Query\RiakNamespace;

$namespace = new RiakNamespace('maps', 'customers');
$location  = new RiakLocation($namespace, 'ahmed_info');
Register & Flags Within Maps

The first piece of info we want to store in our map is Ahmed’s name and phone number, both of which are best stored as registers We’ll also create an enterprise_customer flag to track whether Ahmed has signed up for the new plan:

<?php

use Riak\Client\RiakOption;
use Riak\Client\Command\DataType\StoreMap;

$store = StoreMap::builder($location)
    ->updateRegister('first_name', 'Ahmed')
    ->updateRegister('phone_number', '5551234567')
    ->updateFlag('enterprise_customer', false)
    ->withReturnBody(true)
    ->build();

$result = $client->execute($store);
$map    = $result->getDatatype();

echo $map->get('first_name');
// Ahmed
echo $map->get('phone_number');
// 5551234567
echo $map->get('enterprise_customer');
// false

We can retrieve the value of that flag at any time:

<?php

use Riak\Client\RiakOption;
use Riak\Client\Command\DataType\FetchMap;

$fetch = FetchMap::builder()
    ->withLocation($location)
    ->build();

$result = $client->execute($fetch);
$map    = $result->getDatatype();
$value  = $map->getValue();

echo $map->get('first_name');
echo $map->get('phone_number');
echo $map->get('enterprise_customer');
Counters Within Maps

We also want to know how many times Ahmed has visited our website. We’ll use a page_visits counter for that and run the following operation when Ahmed visits our page for the first time:

<?php

use Riak\Client\RiakOption;
use Riak\Client\Command\DataType\StoreMap;

$store = StoreMap::builder()
    ->withLocation($location)
    ->updateCounter('page_visits', 1)
    ->build();

$client->execute($store);
Sets Within Maps

We’d also like to know what Ahmed’s interests are so that we can better design a user experience for him. Through his purchasing decisions, we find out that Ahmed likes robots, opera, and motorcyles. We’ll store that information in a set inside of our map:

<?php

use Riak\Client\Command\DataType\StoreMap;

$store = StoreMap::builder()
    ->withLocation($location)
    ->updateSet('interests', ['robots', 'opera' , 'motorcycles'])
    ->build();

$client->execute($store);

We learn from a recent purchasing decision that Ahmed actually doesn’t seem to like opera. He’s much more keen on indie pop. Let’s change the interests set to reflect that:

<?php

use Riak\Client\Command\DataType\FetchMap;
use Riak\Client\Command\DataType\StoreMap;
use Riak\Client\Command\DataType\SetUpdate;

$fetch = FetchMap::builder()
    ->withLocation($location)
    ->build();

$fetchResult  = $client->execute($fetch);
$fetchContext = $fetchResult->getContext();
$setUpdate    = new SetUpdate();

$setUpdate->remove('opera');

$store = StoreMap::builder()
    ->withLocation($location)
    ->withContext($fetchContext)
    ->updateSet('interests', $setUpdate)
    ->withReturnBody(true)
    ->build();

$result = $client->execute($store);
$map    = $result->getDatatype();

var_dump($map->get('interests'));
// ['robots', 'motorcycles']
Maps Within Maps

We’ve stored a wide of variety of information—of a wide variety of types—within the ahmed_info map thus far, but we have yet to explore recursively storing maps within maps (which can be nested as deeply as you wish).

Our company is doing well and we have lots of useful information about Ahmed, but now we want to store information about Ahmed’s contacts as well. We’ll start with storing some information about Ahmed’s colleague Annika inside of a map called annika_info.

First, we’ll store Annika’s first name, last name, and phone number in registers:

<?php

use Riak\Client\Command\DataType\FetchMap;
use Riak\Client\Command\DataType\StoreMap;

$fetch = FetchMap::builder()
    ->withLocation($location)
    ->build();

$fetchResult  = $client->execute($fetch);
$fetchContext = $fetchResult->getContext();

$store = StoreMap::builder()
    ->withReturnBody(true)
    ->withContext($fetchContext)
    ->withLocation($location)
    ->updateMap('annika_info', [
        'first_name'   => 'Annika',
        'last_name'    => 'Weiss',
        'phone_number' => '5559876543'
    ])
    ->build();

$result     = $client->execute($store);
$map        = $result->getDatatype();
$annikaInfo = $map->get('annika_info');

echo $annikaInfo['first_name'];
// Annika

Map values can also be removed:

<?php

use Riak\Client\Command\DataType\FetchMap;
use Riak\Client\Command\DataType\StoreMap;
use Riak\Client\Command\DataType\MapUpdate;

$fetch = FetchMap::builder()
    ->withLocation($location)
    ->build();

$fetchResult  = $client->execute($fetch);
$fetchContext = $fetchResult->getContext();
$mapUpdate    = new MapUpdate();

$mapUpdate->removeRegister('first_name');

$store = StoreMap::builder()
    ->updateMap('annika_info', $mapUpdate)
    ->withContext($fetchContext)
    ->withLocation($location)
    ->build();

$client->execute($store);

Now, we’ll store whether Annika is subscribed to a variety of plans within the company as well:

<?php

use Riak\Client\Command\DataType\FetchMap;
use Riak\Client\Command\DataType\StoreMap;
use Riak\Client\Command\DataType\MapUpdate;

$fetch = FetchMap::builder()
    ->withLocation($location)
    ->build();

$fetchResult  = $client->execute($fetch);
$fetchContext = $fetchResult->getContext();
$mapUpdate    = new MapUpdate();

$mapUpdate
    ->updateFlag('enterprise_plan', false)
    ->updateFlag('family_plan', false)
    ->updateFlag('free_plan', true);

$store = StoreMap::builder()
    ->updateMap('annika_info', $mapUpdate)
    ->withContext($fetchContext)
    ->withLocation($location)
    ->build();

$client->execute($store);

The value of a flag can be retrieved at any time:

<?php

use Riak\Client\RiakOption;
use Riak\Client\Command\DataType\FetchMap;

$fetch = FetchMap::builder()
    ->withLocation($location)
    ->build();

$result     = $client->execute($fetch);
$map        = $result->getDatatype();
$annikaInfo = $map->get('annika_info');

echo $annikaInfo['enterprise_plan'];
// false

It’s also important to track the number of purchases that Annika has made with our company. Annika just made her first widget purchase, w’ll also store Annika’s interests in a set:

<?php

use Riak\Client\Command\DataType\FetchMap;
use Riak\Client\Command\DataType\StoreMap;
use Riak\Client\Command\DataType\MapUpdate;
use Riak\Client\Command\DataType\SetUpdate;

$fetch = FetchMap::builder()
    ->withLocation($location)
    ->withIncludeContext(true)
    ->build();

$fetchResult  = $client->execute($fetch);
$fetchContext = $fetchResult->getContext();
$mapUpdate    = new MapUpdate();
$setUpdate    = new SetUpdate();

$setUpdate
    ->add("tango dancing");

$mapUpdate
    ->updateCounter('widget_purchases', 1)
    ->updateCounter('interests', $setUpdate);

$store = StoreMap::builder()
    ->updateMap('annika_info', $mapUpdate)
    ->withContext($fetchContext)
    ->withLocation($location)
    ->build();

$client->execute($store);

If we wanted to add store information about one of Annika’s specific purchases, we could do so within a map:

<?php

use Riak\Client\Command\DataType\FetchMap;
use Riak\Client\Command\DataType\StoreMap;
use Riak\Client\Command\DataType\MapUpdate;

$fetch = FetchMap::builder()
    ->withLocation($location)
    ->build();

$fetchResult  = $client->execute($fetch);
$fetchContext = $fetchResult->getContext();
$mapUpdate    = new MapUpdate();

$mapUpdate
    ->updateMap('purchase', [
        'first_purchase' => true,             // flag
        'amount'         => "1271",           // register
        'items'          => ["large widget"], // set
    ]);

$store = StoreMap::builder()
    ->updateMap('annika_info', $mapUpdate)
    ->withContext($fetchContext)
    ->withLocation($location)
    ->build();

$client->execute($store);

Secondary Indexes

This tutorial documentation its based on the Basho Secondary Indexes Docs. If you are not familiar with search in Riak Before you start take a look at Basho Secondary Indexes Docs for more details.

Secondary indexing (2i) in Riak gives developers the ability to tag an object stored in Riak, at write time, with one or more queryable values. Those values can be either a binary or string, such as sensor_1_data or admin_user or click_event, or an integer, such as 99 or 141121.

Since key/value data is completely opaque to 2i, applications must attach metadata to objects that tell Riak 2i exactly which attribute(s) to index and what the values of those indexes should be.

Riak Search serves analogous purposes but is quite different because it parses key/value data itself and builds indexes on the basis of Solr schemas.

Store Object with Secondary Indexes

In this example, the key john_smith is used to store user data in the bucket users, which bears the default bucket type. Let’s say that an application would like add a Twitter handle and an email address to this object as secondary indexes.

<?php
use Riak\Client\Command\Kv\StoreValue;
use Riak\Client\Core\Query\RiakObject;
use Riak\Client\Core\Query\RiakLocation;
use Riak\Client\Core\Query\RiakNamespace;
use Riak\Client\Core\Query\Index\RiakIndexBin;

$namespace = new RiakNamespace('default', 'users');
$location  = new RiakLocation($namespace, 'john_smith');
$object    = new RiakObject();

$object->setContentType('application/json');
$object->setValue('{"name": "FabioBatSilva"}');
$object->addIndex(new RiakIndexBin('twitter', ['jsmith123']));
$object->addIndex(new RiakIndexBin('email', ['jsmith@basho.com']));

$command = StoreValue::builder($location)
    ->withValue($object)
    ->withW(3)
    ->build();

// store object
$client->execute($command);

This has accomplished the following :

  • The object has been stored with a primary bucket/key of users/john_smith
  • The object now has a secondary index called twitter_bin with a value of jsmith123
  • The object now has a secondary index called email_bin with a value of jsmith@basho.com
Querying Objects with Secondary Indexes

Let’s query the users bucket on the basis of Twitter handle to make sure that we can find our stored object:

<?php

use Riak\Client\Core\Query\RiakNamespace;
use Riak\Client\Command\Index\BinIndexQuery;

$namespace = new RiakNamespace('default', 'users');
$command   = BinIndexQuery::builder()
    ->withNamespace($namespace)
    ->withIndexName('twitter')
    ->withMatch('jsmith123')
    ->build();

/** @var $result \Riak\Client\Command\Index\Response\IndexQueryResponse */
/** @var $entries array */
$result  = $this->client->execute($command);
$entries = $result->getEntries();

echo $entries[0]->getLocation()->getKey();
// john_smith
Querying
Exact Match

The following examples perform an exact match index query.

Query a binary index:

<?php

use Riak\Client\Core\Query\RiakNamespace;
use Riak\Client\Command\Index\BinIndexQuery;

$namespace = new RiakNamespace('bucket-type', 'bucket-name');
$command   = BinIndexQuery::builder()
    ->withNamespace($namespace)
    ->withIndexName('index-name')
    ->withMatch('index-val')
    ->build();

/** @var $result \Riak\Client\Command\Index\Response\IndexQueryResponse */
$result  = $this->client->execute($command);

Query an integer index:

<?php

use Riak\Client\Core\Query\RiakNamespace;
use Riak\Client\Command\Index\IntIndexQuery;

$namespace = new RiakNamespace('bucket-type', 'bucket-name');
$command   = IntIndexQuery::builder()
    ->withNamespace($namespace)
    ->withIndexName('index-name')
    ->withMatch(101)
    ->build();

/** @var $result \Riak\Client\Command\Index\Response\IndexQueryResponse */
$result  = $this->client->execute($command);
Range

The following examples perform a range query:

Query a binary index :

<?php

use Riak\Client\Core\Query\RiakNamespace;
use Riak\Client\Command\Index\BinIndexQuery;

$namespace = new RiakNamespace('bucket-type', 'bucket-name');
$command   = BinIndexQuery::builder()
    ->withNamespace($namespace)
    ->withIndexName('index-name')
    ->withStart('val1')
    ->withEnd('val9')
    ->build();

/** @var $result \Riak\Client\Command\Index\Response\IndexQueryResponse */
$result  = $this->client->execute($command);

Query a integer index :

<?php

use Riak\Client\Core\Query\RiakNamespace;
use Riak\Client\Command\Index\BinIndexQuery;

$namespace = new RiakNamespace('bucket-type', 'bucket-name');
$command   = BinIndexQuery::builder()
    ->withNamespace($namespace)
    ->withIndexName('index-name')
    ->withStart(1)
    ->withEnd(100)
    ->build();

/** @var $result \Riak\Client\Command\Index\Response\IndexQueryResponse */
$result  = $this->client->execute($command);
Range with terms

When performing a range query, it is possible to retrieve the matched index values alongside the Riak keys using return_terms=true. An example from a small sampling of Twitter data with indexed hash tags:

Query a binary index :

<?php

use Riak\Client\Core\Query\RiakNamespace;
use Riak\Client\Command\Index\BinIndexQuery;

$namespace = new RiakNamespace('bucket-type', 'bucket-name');
$command   = BinIndexQuery::builder()
    ->withNamespace($namespace)
    ->withIndexName('index-name')
    ->withReturnTerms(true)
    ->withStart('val1')
    ->withEnd('val9')
    ->build();

/** @var $result \Riak\Client\Command\Index\Response\IndexQueryResponse */
$result  = $this->client->execute($command);

Map Reduce

This tutorial documentation its based on the Basho MapReduce Docs.

How it Works

The MapReduce framework helps developers divide a query into steps, divide the dataset into chunks, and then run those step/chunk pairs in separate physical hosts.

In Riak, MapReduce is one of the primary methods for non-primary-key-based querying in Riak, alongside secondary indexes. Riak allows you to run MapReduce jobs using Erlang or JavaScript.

There are two steps in a MapReduce query:

  • Map — The data collection phase, which breaks up large chunks of work into smaller ones and then takes action on each chunk. Map phases consist of a function and a list of objects on which the map operation will operate.
  • Reduce — The data collation or processing phase, which combines the results from the map step into a single output. The reduce phase is optional.

Riak MapReduce queries have two components:

  • A list of inputs
  • A list of phases

The elements of the input list are object locations as specified by bucket type, bucket, and key. The elements of the phases list are chunks of information related to a map, a reduce, or a link function.

MapReduce Commands
BucketMapReduce

Command used to perform a Map Reduce operation over a bucket in Riak.

Here is the general syntax for setting up a bucket map reduce combination to handle a range of keys:

<?php
use Riak\Client\Command\MapReduce\BucketMapReduce;
use Riak\Client\Core\Query\Func\NamedJsFunction;
use Riak\Client\Command\MapReduce\KeyFilters;
use Riak\Client\Core\Query\RiakNamespace;

$map       = new NamedJsFunction('Riak.mapValuesJson');
$namespace = new RiakNamespace('bucket_type', 'bucket_name');
$filter    = KeyFilters::filter()->between('key1', 'key9', false);
$command   = BucketMapReduce::builder()
    ->withMapPhase($map, null, true)
    ->withNamespace($namespace)
    ->withKeyFilter($filter)
    ->build();

/* @var $result \Riak\Client\Command\MapReduce\Response\BucketMapReduceResponse */
$result = $this->client->execute($command);
$values = $result->getResultForPhase(0);

echo $values[0];
// ... first element response

See Basho KeyFilters Docs. for more details on filters

BucketKeyMapReduce

Command used to perform a map reduce operation over a specific set of keys in a bucket.

Here is the general syntax for setting up a bucket map over a specific set of keys:

<?php
use Riak\Client\Command\MapReduce\BucketKeyMapReduce;
use Riak\Client\Core\Query\Func\AnonymousJsFunction;
use Riak\Client\Core\Query\Func\ErlangFunction;
use Riak\Client\Core\Query\RiakNamespace;
use Riak\Client\Core\Query\RiakLocation;

$reduce = new ErlangFunction('riak_kv_mapreduce', 'reduce_sum');
$map    = new AnonymousJsFunction('function(entry) {
    return [JSON.parse(entry.values[0].data)];
}');

$namespace = new RiakNamespace('bucket_type', 'bucket_name');
$command   = BucketKeyMapReduce::builder([])
    ->withMapPhase($map)
    ->withReducePhase($reduce, null, true)
    ->withLocation(new RiakLocation($namespace, 'key1'))
    ->withLocation(new RiakLocation($namespace, 'key2'))
    ->withLocation(new RiakLocation($namespace, 'key3'))
    ->build();

/* @var $result \Riak\Client\Command\MapReduce\Response\BucketKeyMapReduceResponse */
$result = $this->client->execute($command);
$values = $result->getResultForPhase(1);

echo $values[0];
// 10
IndexMapReduce

Command used to perform a map reduce operation using a secondary index (2i) as input.

Here is the general syntax for setting up a bin secondary index map-reduce:

<?php
use Riak\Client\Command\MapReduce\IndexMapReduce;
use Riak\Client\Core\Query\Func\AnonymousJsFunction;
use Riak\Client\Core\Query\Func\ErlangFunction;
use Riak\Client\Core\Query\RiakNamespace;

$reduce = new ErlangFunction('riak_kv_mapreduce', 'reduce_sort');
$map    = new AnonymousJsFunction('function(entry) {
    return [JSON.parse(entry.values[0].data).email];
}');

$namespace = new RiakNamespace('bucket_type', 'bucket_name');
$command   = IndexMapReduce::builder()
    ->withMapPhase($map)
    ->withReducePhase($reduce, null, true)
    ->withNamespace($namespace)
    ->withIndexBin('department_index')
    ->withMatchValue('dev')
    ->build();

/* @var $result \Riak\Client\Command\MapReduce\Response\IndexMapReduceResponse */
$result = $this->client->execute($command);
$values = $result->getResultsFromAllPhases();

echo implode(",", $values);
// fabio.bat.silva@gmail.com,dev@gmail.com,riak@basho.com,...

For a int secondary index map-reduce:

<?php
use Riak\Client\Command\MapReduce\IndexMapReduce;
use Riak\Client\Core\Query\Func\AnonymousJsFunction;
use Riak\Client\Core\Query\Func\ErlangFunction;
use Riak\Client\Core\Query\RiakNamespace;

$reduce = new ErlangFunction('riak_kv_mapreduce', 'reduce_sort');
$map    = new AnonymousJsFunction('function(entry) {
    return [JSON.parse(entry.values[0].data).email];
}');

$namespace = new RiakNamespace('bucket_type', 'bucket_name');
$command   = IndexMapReduce::builder()
    ->withMapPhase($map)
    ->withReducePhase($reduce, null, true)
    ->withNamespace($namespace)
    ->withIndexInt('year')
    ->withRange(2010, 2015)
    ->build();

/* @var $result \Riak\Client\Command\MapReduce\Response\IndexMapReduceResponse */
$result = $this->client->execute($command);
SearchMapReduce
<?php
use Riak\Client\Command\MapReduce\SearchMapReduce;
use Riak\Client\Core\Query\Func\AnonymousJsFunction;
use Riak\Client\Core\Query\Func\ErlangFunction;
use Riak\Client\Core\Query\RiakNamespace;

$reduce = new ErlangFunction('riak_kv_mapreduce', 'reduce_sort');
$map    = new AnonymousJsFunction('function(entry) {
    return [JSON.parse(entry.values[0].data).email];
}');

$namespace = new RiakNamespace('cats_type', 'cats_bucket');
$command   = SearchMapReduce::builder()
    ->withMapPhase($map)
    ->withReducePhase($reduce, null, true)
    ->withQuery('name_s:Snarf')
    ->withIndex('famous')
    ->build();

/* @var $result \Riak\Client\Command\MapReduce\Response\SearchMapReduceResponse */
/* @var $iterator \Iterator*/
$result   = $this->client->execute($command);
$iterator = $result->getIterator();

/** @var $entry \Riak\Client\Command\MapReduce\Response\MapReduceEntry */
foreach ($iterator as $entry) {
    echo $entry->getPhase();
    // 1

    echo $entry->getResponse();
    // ["Snarf"]
}

Note

Map-reduce operations are always made using streaming, Response#getIterator() will return a stream iterator that can be used to iterate over the response entries.

Notice that is not possible to rewind a stream iterator, If you need to re-use the result use Response#getResults() instead.

Merging siblings

Siblings are an important feature in Riak, for this purpose we have the interface Riak\Client\Resolver\ConflictResolver, you implement this interface to resolve siblings into a single object.

Below is a simple example that will just merge the content of siblings :

Assuming we have the following domain :

<?php
use Riak\Client\Annotation as Riak;

class MyDomainObject
{
    /**
     * @var string
     *
     * @Riak\ContentType
     */
    private $contentType = 'application/json';

    /**
     * @var string
     *
     * @Riak\VClock
     */
    private $vClock;

    /**
     * @var string
     */
    private $value;

    // getters and setters
}

By implementing the interface Riak\Client\Resolver\ConflictResolver we can merge siblings,

The method resolve($siblings) will be called every time we have siblings and invoke Riak\Client\Command\Kv\Response\FetchValueResponse#getValue('MyDomainObject') :

<?php
use \Riak\Client\Resolver\ConflictResolver;
use \Riak\Client\Core\Query\RiakObject;
use \Riak\Client\Core\Query\RiakList;

class MySimpleResolver implements ConflictResolver
{
    /**
     * {@inheritdoc}
     */
    public function resolve(RiakList $siblings)
    {
        $result  = new MyDomainObject();
        $content = "";

        /** @var $object \MyDomainObject */
        foreach ($siblings as $object) {
            $content .= $object->getValue();
        }

        $result->setValue($content);

        return $result;
    }
}

Register your resolver during the application start up :

<?php
/** @var $builder \Riak\Client\RiakClientBuilder */
$client = $builder
    ->withConflictResolver('MyDomainObject' new MySimpleResolver())
    ->withNodeUri('http://localhost:8098')
    ->build();

Finally whe can fetch the object with siblings and resolve any possible conflict :

<?php
$namespace = new RiakNamespace('bucket_type', 'bucket_name');
$location  = new RiakLocation($namespace, 'object_key');
$fetch     = FetchValue::builder($location)
    ->withNotFoundOk(true)
    ->build();

/** @var $domain \MyDomainObject */
$result = $client->execute($fetch);
$domain = $result->getValue('MyDomainObject');

echo $result->getNumberOfValues();
// 2

See Siblings for more details on conflict resolution on riak

Performance

This library is faster than most riak clients written in php, It is about 50% percent faster is some cases, mostly because it uses protocol buffer and and iterators every where it is possible.

Fetch/Get Operation
https://raw.githubusercontent.com/FabioBatSilva/riak-clients-performance-comparison/master/graphs/fetch-top.jpg
Fetch/Get Operation Iterations Average Time Ops/second
php_riak extension 1,000 0.0005543117523 1,804.03897
riak-client (proto) 1,000 0.0007790112495 1,283.67851
basho/riak 1,000 0.0017048845291 586.54999
Store/Put Operation
https://raw.githubusercontent.com/FabioBatSilva/riak-clients-performance-comparison/master/graphs/store-top.jpg
Store/Put Operation Iterations Average Time Ops/second
php_riak extension 1,000 0.0010141553879 986.04219
riak-client (proto) 1,000 0.0013224580288 756.16767
basho/riak 1,000 0.0025912058353 385.92071

For more details and riak clients performance comparison see : https://github.com/FabioBatSilva/riak-clients-performance-comparison