Stores - evansims/openfga-php GitHub Wiki
Stores can be thought of as your authorization database. They contain your permission rules, user relationships, and everything needed to answer "can this user do that?" Every OpenFGA operation happens within a store, making them the foundation of your authorization system.
The examples in this guide assume you have the following setup:
<?php
declare(strict_types=1);
use OpenFGA\Client;
$client = new Client(
url: $_ENV['FGA_API_URL'] ?? 'http://localhost:8080',
);
For a typical application, create one store per environment:
use OpenFGA\Responses\CreateStoreResponse;
use function OpenFGA\{failure, success};
$result = $client->createStore(name: 'my-app-store');
failure($result, function (Throwable $e): void {
throw new RuntimeException("Error creating store: {$e->getMessage()}");
});
success($result, function (CreateStoreResponse $store): void {
echo "Created store: {$store->getId()}\n";
echo "Store name: {$store->getName()}\n";
});
Save the store ID in your environment configuration. You'll need it for future API calls.
For SaaS applications, create a store per customer to ensure complete data isolation:
use function OpenFGA\store;
final class TenantStoreManager
{
public function __construct(
private readonly Client $client,
private array $cache = [],
) {
}
public function getStoreForTenant(string $tenantId): string
{
if (! array_key_exists($tenantId, $this->cache)) {
$this->cache[$tenantId] = store("tenant-{$tenantId}", $this->client);
}
return $this->cache[$tenantId];
}
}
$tenantManager = new TenantStoreManager($client);
$acmeStoreId = $tenantManager->getStoreForTenant('acme-corp');
echo "Store for ACME Corp: {$acmeStoreId}\n";
Keep your environments completely isolated:
use RuntimeException;
use Throwable;
use function OpenFGA\failure;
// Environment separation
function createEnvironmentStore(Client $client, string $environment): string
{
$result = $client->createStore(
name: sprintf('app-%s-%s', $environment, date('Y-m-d')),
);
failure($result, function (Throwable $e): void {
throw new RuntimeException('Failed to create store: ' . $e->getMessage());
});
return $result->unwrap()->getId();
}
Finding and managing existing stores:
$result = $client->listStores();
if ($result->succeeded()) {
$stores = $result->unwrap()->getStores();
echo "Available stores:\n";
foreach ($stores as $store) {
echo "- {$store->getName()} (ID: {$store->getId()})\n";
echo " Created: {$store->getCreatedAt()->format('Y-m-d H:i:s')}\n";
echo " Updated: {$store->getUpdatedAt()->format('Y-m-d H:i:s')}\n";
}
}
For pagination with many stores:
// Pagination with continuation tokens
$stores = [];
$continuationToken = null;
do {
$result = $client->listStores(
pageSize: 10,
continuationToken: $continuationToken,
);
if ($result->succeeded()) {
$response = $result->unwrap();
foreach ($response->getStores() as $store) {
$stores[] = $store;
}
$continuationToken = $response->getContinuationToken();
} else {
break;
}
} while (null !== $continuationToken);
echo 'Total stores: ' . count($stores) . "\n";
When to use multiple stores:
- Different environments (dev/staging/production)
- Different customers in SaaS apps
- Different applications with no shared permissions
- Compliance requirements
When to use a single store:
- Different user roles (use authorization models instead)
- Different features in the same app (use object types)
- A/B testing (use different object IDs)
Pro tips:
- Start with one store per environment
- Save store IDs in your configuration
- Test your app works with store switching
- Document which team owns each store