kbrw/riak-bundle
A Symfony bundle that integrates Riak into PHP applications, providing configuration and service wiring for connecting to and working with Riak data stores.
Treat this bundle as two things: a thin Symfony service around a Riak HTTP client, and a lightweight ODM on top of it. Most teams get value from the first and only reach for the second once they have a real document model.
Start by proving the cluster is reachable and understood before touching PHP:
curl -s http://127.0.0.1:8098/ping # expect: OK
curl -s http://127.0.0.1:8098/types # bucket types, if enabled
curl -s "http://127.0.0.1:8098/buckets?buckets=true"
Then install and register:
composer require kbrw/riak-bundle
If Composer refuses due to symfony/* constraints, stop there — that is the assessment result. This is a Symfony 2/3-era bundle, so check its composer.json against your framework version before designing anything around it. Register Kbrw\RiakBundle\KbrwRiakBundle in AppKernel (or config/bundles.php on newer skeletons).
Minimal configuration — the exact keys live in the bundle's DependencyInjection/Configuration.php, so confirm with bin/console debug:config riak after adding it:
# config/packages/riak.yaml
riak:
clients:
default:
host: "%env(RIAK_HOST)%"
port: 8098
document_managers:
default:
client: default
mapping:
prefix: App\Document
dir: "%kernel.project_dir%/src/Document"
Service IDs are the first thing to discover, not guess:
bin/console debug:container riak
You are looking for the client service (commonly riak.client or riak.client.default) and the document manager (riak.document_manager). Inject the client into a throwaway command or controller and do the smallest possible round trip: write one object, read it back, list the bucket. That single use case validates host, port, bucket type, serialization, and your assumptions about the ODM in one pass.
A good first real use case is a read path, not a write path: take an existing Riak bucket your application already depends on, fetch one key through the bundle, and render it. Writes come second, once you know how the client represents vector clocks and siblings.
Raw client for existing buckets. The lowest-risk integration is to skip the ODM entirely and treat the client as a bucket gateway. This is what most day-to-day code looks like in a legacy app:
final class SessionStore
{
private $client;
public function __construct($riakClient)
{
$this->client = $riakClient;
}
public function get(string $key): ?array
{
$bucket = $this->client->getBucket('sessions');
$object = $bucket->get($key);
if ($object->get404()) {
return null;
}
return json_decode($object->getData(), true);
}
}
Wire it explicitly in services.yaml with the discovered service ID rather than $this->get(); private services and the removal of container access make the old style brittle.
ODM for owned entities. For documents your application controls end to end, map them with the bundle's annotations (the bundle ships RiakBucket, RiakKey, and RiakIndex-style annotations; verify class names under Annotation/ for your version):
namespace App\Document;
use Kbrw\RiakBundle\Annotation\RiakBucket;
use Kbrw\RiakBundle\Annotation\RiakKey;
/**
* @RiakBucket(name="users")
*/
class User
{
/** @RiakKey */
private $username;
private $email;
private $createdAt;
}
The key annotation is the important one: Riak has no generated identifiers, so the document must supply its own key from a business value you are willing to treat as immutable. If you need lookup by email, add a secondary index annotation and set it at write time rather than scanning.
Repository wrappers. Do not let controllers talk to the document manager directly. Wrap it:
final class UserRepository
{
private $dm;
public function __construct($documentManager)
{
$this->dm = $documentManager;
}
public function find(string $username): ?User
{
return $this->dm->getRepository(User::class)->find($username);
}
public function save(User $user): void
{
$this->dm->persist($user);
$this->dm->flush();
}
}
Keeping this behind an interface means you can swap to direct HTTP calls or a maintained client later without touching domain code — worth doing given the package's age.
Secondary index queries for "find by field". 2i is the pragmatic query mechanism when Search is not configured. Index writes must set the index name and value on the object before storing; queries then use the bucket/index/value form. Two habits matter: index values are compared as strings, and result sets need max_results/pagination handling explicitly. Do not assume an unpaginated query returns everything.
MapReduce and Search only if the cluster already runs them. MapReduce is deprecated in Riak 2.2+ and frequently disabled; if a cluster has it off, the bundle's MapReduce model will fail at runtime, not at configuration time. Search requires a Solr index and schema created outside the bundle — an annotation will not provision it. If you inherit a cluster that uses either, wrap calls in a service with clear failure handling so you can move them to 2i later.
Integration tips. Version your key namespaces (users:v2:<id>) so a serialization change is a migration, not a data-loss event. Batch high-volume writes at the HTTP layer rather than through a flush() loop; per-object round trips dominate cost. For tests, run a real Riak container and mark those tests as integration; there is no in-memory substitute, and mocking the client only tests your mock.
Siblings are the pitfall that bites in production. Riak can return multiple values for one key after concurrent writes. The ODM may surface only one, silently dropping siblings. Check how the client exposes vclock and sibling arrays, and implement an explicit merge policy for any document written from more than one path. Reads that "randomly" return the older value are almost always unresolved siblings.
No transactions, and flush() is not atomic. It is a loop of HTTP writes with partial-failure semantics. If flush() throws, some documents persisted and some did not. Design writes to be idempotent and retryable; never model multi-document invariants on a single flush.
Eventual consistency. Default read/write quorums do not give read-your-writes. Do not put a Riak read immediately after a Riak write in a user-facing flow and expect the new value. If you must, set pw/pr at the cost of availability.
Annotation mapping is silently forgiving. A misspelled or missing @RiakKey can produce writes under a null/empty key, overwriting data. Validate that the property is populated before persisting and add a test that asserts the generated key string.
Bucket types and content negotiation. If the cluster uses non-default bucket types, every request must include the type. Older bundle versions may not model this; if you see blanket 404s on a bucket you know exists, check curl with the type path before blaming your mapping.
Timeouts and handoff errors. Configure connect and request timeouts explicitly, and expect transient 503 insufficient_vnodes during rebalancing. A single retry with backoff handles most of it; without it you will see sporadic failures during cluster maintenance.
Serialization is a contract. The bundle encodes documents in a specific format (typically JSON). Changing the serializer, adding a field, or switching from object to array storage breaks reads of existing keys. Version the payload or the key namespace, never both silently.
Debug by looking at the wire. Enable HTTP logging or point the client at a proxy, then compare the exact method, path, headers, and body against a manual curl. This resolves nearly every "the bundle is doing something weird" question faster than reading the bundle source.
Extension points worth knowing. Custom repository classes via the mapping configuration, custom serializer/normalizer services, and service-definition overrides in your app's services.yaml are the safe hooks. Avoid editing the bundle in vendor/; fork and use a VCS repository entry if you genuinely need a patch.
Assess the exit path. Riak's original vendor is gone, the ecosystem is maintenance-mode, and this bundle has a small user base and no dependents. Isolate it behind one or two services, keep raw HTTP calls available as a fallback, and budget for migration. The bundle is a reasonable adapter for an existing cluster in an existing Symfony app; it is a poor foundation for a new system.
How can I help you explore Laravel packages today?