Esc

    PHP SDK

    PHP client for InferaDB with Laravel and Symfony integration.

    Coming soon. The PHP SDK is under active development. The API surface shown here is based on the Rust SDK and may change before release.

    Typed client for InferaDB’s authorization APIs. Requires PHP 8.2+. Works with Laravel, Symfony, and any PSR-18 HTTP client.

    Installation

    composer require inferadb/inferadb-php
    

    Authentication

    Three authentication methods:

    Method Use Case Security
    Client Credentials (Ed25519 JWT) Service-to-service High
    Bearer Token User sessions, OAuth Medium
    API Key Testing, simple integrations Basic
    use InferaDB\InferaDB;
    use InferaDB\Auth\Ed25519PrivateKey;
    use InferaDB\Auth\ClientCredentials;
    
    $client = InferaDB::builder()
        ->url('https://engine.inferadb.com')
        ->credentials(new ClientCredentials(
            clientId: 'my-client',
            privateKey: Ed25519PrivateKey::fromPemFile('client.pem'),
            certificateId: 'cert-id',
        ))
        ->build();
    

    Bearer Token

    $client = InferaDB::builder()
        ->url('https://engine.inferadb.com')
        ->bearerToken(getenv('INFERADB_TOKEN'))
        ->build();
    

    API Key

    $client = InferaDB::builder()
        ->url('https://engine.inferadb.com')
        ->apiKey(getenv('INFERADB_API_KEY'))
        ->build();
    

    Permission Checks

    $vault = $client->organization('my-org')->vault('production');
    
    // Simple check
    $allowed = $vault->check('user:alice', 'can_edit', 'document:readme');
    

    With ABAC Context

    $allowed = $vault->check('user:alice', 'can_view', 'document:readme', [
        'context' => ['ip_address' => '10.0.0.1'],
    ]);
    

    Require — Throws on Deny

    use InferaDB\Exceptions\AccessDeniedException;
    
    // Throws AccessDeniedException if permission is denied
    $vault->require('user:alice', 'can_edit', 'document:readme');
    

    With Consistency Token

    $allowed = $vault->check('user:alice', 'can_view', 'document:readme', [
        'atLeastAsFresh' => $revisionToken,
    ]);
    

    Batch Check

    use InferaDB\CheckRequest;
    
    $results = $vault->checkBatch([
        new CheckRequest('user:alice', 'can_edit', 'document:readme'),
        new CheckRequest('user:bob', 'can_view', 'document:readme'),
    ]);
    
    if ($results->allAllowed()) {
        // all checks passed
    }
    

    Relationships

    Write

    use InferaDB\Relationship;
    
    // Returns a revision token
    $token = $vault->relationships()->write(
        new Relationship('document:readme', 'editor', 'user:alice')
    );
    

    Batch Write

    $vault->relationships()->writeBatch([
        new Relationship('document:readme', 'editor', 'user:alice'),
        new Relationship('document:readme', 'viewer', 'user:bob'),
    ]);
    

    List

    $rels = $vault->relationships()
        ->list()
        ->resource('document:readme')
        ->collect();
    

    Delete

    $vault->relationships()
        ->deleteWhere()
        ->resource('document:readme')
        ->relation('viewer')
        ->subject('user:bob')
        ->execute();
    

    Lookups

    // What resources can Alice view?
    $resources = $vault->resources()
        ->accessibleBy('user:alice')
        ->withPermission('can_view')
        ->resourceType('document')
        ->collect();
    
    // Who can edit this document?
    $subjects = $vault->subjects()
        ->withPermission('can_edit')
        ->onResource('document:readme')
        ->collect();
    

    Testing

    MockClient (Fastest)

    use InferaDB\Testing\MockClient;
    
    $client = MockClient::builder()
        ->onCheck('user:alice', 'can_edit', 'document:readme')->allow()
        ->onCheck('user:bob', 'can_edit', 'document:readme')->deny()
        ->onCheckAnySubject('can_view', 'document:readme')->allow()
        ->defaultDeny()
        ->verifyOnDestruct(true) // asserts all expectations were invoked on destruct
        ->build();
    

    InMemoryClient (Full Policy Evaluation)

    use InferaDB\Testing\InMemoryClient;
    
    $client = InMemoryClient::withSchemaAndData(
        schema: <<<'IPL'
        type document {
            relation viewer
            relation editor
            relation can_view = viewer | editor
        }
        IPL,
        data: [
            new Relationship('document:readme', 'editor', 'user:alice'),
            new Relationship('document:readme', 'viewer', 'user:bob'),
        ],
    );
    

    TestVault (Real Instance)

    use InferaDB\Testing\TestVault;
    
    $vault = TestVault::create($org, schema: $schemaIpl);
    // vault auto-cleans up on __destruct
    // call $vault->preserve() to keep data for debugging
    

    PHPUnit Integration

    use PHPUnit\Framework\TestCase;
    use InferaDB\Testing\InMemoryClient;
    
    class DocumentAuthorizationTest extends TestCase
    {
        private InMemoryClient $client;
    
        protected function setUp(): void
        {
            $this->client = InMemoryClient::withSchemaAndData(
                schema: '...',
                data: [new Relationship('document:readme', 'editor', 'user:alice')],
            );
        }
    
        public function testAliceCanEdit(): void
        {
            $vault = $this->client->organization('test')->vault('test');
            $this->assertTrue($vault->check('user:alice', 'can_edit', 'document:readme'));
        }
    }
    

    Error Handling

    use InferaDB\Exceptions\AccessDeniedException;
    use InferaDB\Exceptions\InferaDBException;
    
    try {
        $vault->require('user:alice', 'can_edit', 'document:readme');
    } catch (AccessDeniedException $e) {
        // permission denied
    } catch (InferaDBException $e) {
        if ($e->isRetriable()) {
            // retry after $e->getRetryAfter() seconds
        }
        error_log(sprintf('Authorization error: kind=%s, requestId=%s',
            $e->getKind()->value, $e->getRequestId()));
    }
    

    ErrorKind enum: Unauthorized, Forbidden, NotFound, RateLimited, SchemaViolation, Unavailable, Timeout, InvalidArgument.

    Framework Integrations

    Laravel Middleware

    // app/Http/Middleware/InferaDBAuthorize.php
    namespace App\Http\Middleware;
    
    use Closure;
    use Illuminate\Http\Request;
    use InferaDB\VaultClient;
    use InferaDB\Exceptions\AccessDeniedException;
    
    class InferaDBAuthorize
    {
        public function __construct(private VaultClient $vault) {}
    
        public function handle(Request $request, Closure $next, string $permission): mixed
        {
            $userId = $request->user()->id;
            $documentId = $request->route('document');
    
            try {
                $this->vault->require(
                    "user:{$userId}",
                    $permission,
                    "document:{$documentId}",
                );
            } catch (AccessDeniedException) {
                abort(403);
            }
    
            return $next($request);
        }
    }
    
    Route::get('/documents/{document}', [DocumentController::class, 'show'])
        ->middleware('inferadb:can_view');
    
    Route::put('/documents/{document}', [DocumentController::class, 'update'])
        ->middleware('inferadb:can_edit');
    

    Laravel Gate

    // app/Providers/AuthServiceProvider.php
    use Illuminate\Support\Facades\Gate;
    use InferaDB\VaultClient;
    
    Gate::define('view-document', function ($user, $document) {
        return app(VaultClient::class)->check(
            "user:{$user->id}",
            'can_view',
            "document:{$document->id}",
        );
    });
    

    Symfony Voter

    // src/Security/InferaDBVoter.php
    namespace App\Security;
    
    use InferaDB\VaultClient;
    use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
    use Symfony\Component\Security\Core\Authorization\Voter\Voter;
    
    class InferaDBVoter extends Voter
    {
        public function __construct(private VaultClient $vault) {}
    
        protected function supports(string $attribute, mixed $subject): bool
        {
            return str_starts_with($attribute, 'INFERADB_');
        }
    
        protected function voteOnAttribute(
            string $attribute,
            mixed $subject,
            TokenInterface $token,
        ): bool {
            $user = $token->getUser();
            $permission = strtolower(str_replace('INFERADB_', '', $attribute));
    
            return $this->vault->check(
                "user:{$user->getUserIdentifier()}",
                $permission,
                "document:{$subject->getId()}",
            );
        }
    }