lastguest/murmurhash
A fast, dependency-free PHP implementation of MurmurHash (MurmurHash3) for non-cryptographic hashing. Generate consistent 32/128-bit hashes for sharding, caching keys, bloom filters, and partitioning. Simple API, Composer-ready, and easy to drop into Laravel or any PHP project.
Install
composer require lastguest/murmurhash
There is no service provider, config file, or facade — it is a plain static class. Nothing to publish or boot.
Look first at
vendor/lastguest/murmurhash/README.md — the canonical usage example (class name and method signatures) is here; confirm the exact static methods (Murmur::hash() for the 32-bit variant, and the 128-bit helper if you need it) before wiring it in, because the API is a single class with static methods and no interface contracts.vendor/lastguest/murmurhash/src/ — the implementation is small enough to read in one sitting. Skim it once to confirm it is the x86 32-bit MurmurHash3 variant and how it treats the seed, since cross-language parity hinges on that.First use case
Assign users/entities deterministically to buckets — the classic Laravel need for A/B tests, feature-flag rollout percentages, and cache sharding.
use lastguest\Murmur;
$bucket = hexdec(Murmur::hash('user:'.$userId, 1234)) % 100; // 0–99
if ($bucket < 10) {
// 10% of users, stable for a given seed
}
That is the entire value proposition: a fast, stable, seedable bucket key. Do not reach for it when you need security (see Gotchas).
Wrap it in a service, not static calls everywhere
Raw static calls scatter an unversioned, magic-seeded algorithm across your codebase. Bind a small service so the seed lives in config and is changeable in tests.
namespace App\Support;
use lastguest\Murmur;
class Bucketer
{
public function __construct(private int $seed = 0) {}
public function bucket(string $key, int $buckets = 100): int
{
// 7 hex chars = 28 bits, safe on 32-bit PHP too
$n = hexdec(substr(Murmur::hash($key, $this->seed), 0, 7));
return $n % $buckets;
}
}
// app/Providers/AppServiceProvider.php
$this->app->singleton(Bucketer::class, fn () =>
new Bucketer((int) config('services.experiments.seed', 0))
);
Feature rollout / experiment assignment
Seed per experiment so assignments are independent (feature:new-checkout + user:123 + experiment seed). One seed for everything collapses every experiment's buckets onto the same cohort, which silently invalidates your A/B results.
$inTreatment = $bucketer->bucket("exp:new-checkout:user:{$user->id}", 100)
% 2 === 1;
Cache/shard keying
Use the hash to pick a shard or to compress long cache keys:
$shard = $bucketer->bucket($cacheKey, config('cache.shards'));
cache()->store("redis_{$shard}")->remember($cacheKey, $ttl, $callback);
Or shorten unbounded keys:
$key = 'rpt:'.Murmur::hash($reportDefinition, 0); // stable, fixed-length
Log sampling
Cheap, stable correlation sampling at the request/job level:
if ($bucketer->bucket($requestId, 1000) < 10) { // 1%
Log::debug('sampled', ['request_id' => $requestId]);
}
Idempotency and deduplication keys
Derive a stable fingerprint from a job payload so retries collapse:
$key = 'job:'.Murmur::hash(json_encode($payload), 0);
Use this for routing or deduping, not for proving payload uniqueness — 32-bit collisions are reachable.
Model events / jobs
Apply it wherever determinism matters more than security: deterministic avatar colors, consistent ordering, tenant→worker assignment in queued jobs. Prefer passing the already-computed bucket through the job payload rather than recomputing, so a config/seed change mid-flight cannot split a user across treatments.
Not a cryptographic hash — ever
MurmurHash3 is fast and predictable by design. Never use it for passwords, tokens, signatures, or anything an attacker controls. Use Hash::make() / hash_hmac() / random_bytes() for those. If a reviewer sees Murmur::hash($password), that is a bug.
32-bit collisions are real
Murmur::hash() returns an 8-hex-character (32-bit) string. Birthday collisions become likely in the tens of thousands of distinct keys, so do not treat the value as a unique ID. For collision-resistant stable IDs over large sets, use the 128-bit helper (if present in your version) or xxh128/hash('sha256', ...).
hexdec() and 32-bit PHP
hexdec() on a full 8-char hash exceeds PHP_INT_MAX on 32-bit PHP and yields a float, which breaks % bucketing silently. Truncating to 7 hex chars (28 bits), as above, keeps the arithmetic integer-only. On 64-bit PHP the full 8 chars are fine, but the truncated version is portable — prefer it.
Seed portability across languages
Seeds and the exact variant matter. PHP's implementation is the 32-bit x86 variant; Guava/Java's murmur3_32 should agree, but the 128-bit variants and things like the Kafka partitioner (which uses murmur2, not murmur3) will not. If hashes must match another system, write a cross-system fixture test with literal expected hex values and pin them.
Package is frozen (last release 2021)
It is stable, tiny, and MIT, but unmaintained — no Laravel 12 tags to wait on because there is nothing to adapt. If you need patches, fork it or vendor the single class. Do not build a critical contract on it without a snapshot test.
Pin the algorithm with tests
Seeds and method changes are silent behavior changes. Assert literal outputs:
$this->assertSame('5e928f0f', Murmur::hash('hello', 0));
This catches accidental swaps to crc32, md5, or a different seed during refactors.
Bias and bucket math
$n % 100 from a 28-bit space has negligible bias for typical bucket counts, but avoid tiny ranges (e.g. % 3) if statistical fairness is audited — use the full hash or a larger modulus, then scale.
Extension points
There are none in the package itself — no interfaces, no config, no container hooks. Your extension point is your own wrapper service: put the seed in config, add a sample(string $key, float $rate): bool helper, and keep the static class behind that boundary so you can swap implementations later without touching call sites.
How can I help you explore Laravel packages today?