PHP code example of gimucco / atproto-php

1. Go to this page and download the library: Download gimucco/atproto-php library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

gimucco / atproto-php example snippets




use Gimucco\Atproto\ClientConfig;
use Gimucco\Atproto\OAuthClient;
use Gimucco\Atproto\Storage\FileSessionStore;
use Gimucco\Atproto\Storage\FileStateStore;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;

// 1. Configure
$config = new ClientConfig(
    clientId: 'https://your-app.com/client-metadata.json',
    redirectUri: 'https://your-app.com/callback',
    scope: 'atproto transition:generic',
    clientName: 'My App',
    jwksUri: 'https://your-app.com/jwks.json',
    privateKey: file_get_contents('/path/to/private.pem'),
);

// 2. Create the OAuth client
$factory = new HttpFactory();
$oauth = new OAuthClient(
    config: $config,
    sessionStore: new FileSessionStore('/var/app/sessions', 'encryption-passphrase'),
    stateStore: new FileStateStore('/var/app/states', 'encryption-passphrase'),
    httpClient: new Client(),
    requestFactory: $factory,
    streamFactory: $factory,
);

// 3. Start login — redirect the user
$authUrl = $oauth->beginAuthorization('alice.bsky.social');
header('Location: '.$authUrl);

// 4. Handle callback (in your callback endpoint)
$session = $oauth->completeAuthorization($_GET['code'], $_GET['state'], $_GET['iss']);

// 5. Make authenticated requests
$response = $session->authenticatedRequest(
    'GET',
    $session->pdsUrl.'/xrpc/com.atproto.server.getSession',
);

echo $response->getBody(); // {"did":"did:plc:...","handle":"alice.bsky.social",...}

// config.php for bin/generate-metadata
return [
    'client_id' => 'https://your-app.com/client-metadata.json',
    'redirect_uri' => [
        'https://your-app.com/callback/login',
        'https://your-app.com/callback/link',
    ],
    // …
];

$loginConfig = new ClientConfig(
    clientId: 'https://your-app.com/client-metadata.json',
    redirectUri: 'https://your-app.com/callback/login',
    privateKey: $pem,
    encryptionPassphrase: $passphrase,
    // …
);

$linkConfig = new ClientConfig(
    clientId: 'https://your-app.com/client-metadata.json',
    redirectUri: 'https://your-app.com/callback/link',
    privateKey: $pem,                  // same
    encryptionPassphrase: $passphrase, // same
    // …
);

$metaConfig = new ClientConfig(
    clientId: 'https://your-app.com/client-metadata.json',
    redirectUri: 'https://your-app.com/callback/login',
    additionalRedirectUris: ['https://your-app.com/callback/link'],
    // …
);

file_put_contents('public/client-metadata.json', json_encode(
    ClientMetadataBuilder::fromConfig($metaConfig)
));

$authUrl = $oauth->beginAuthorization('alice.bsky.social');
// Redirect the user's browser
header('Location: '.$authUrl);
exit;

$authUrl = $oauth->beginAuthorization();   // no handle
header('Location: '.$authUrl);
exit;

// Per-call override
$authUrl = $oauth->beginAuthorization(
    handleOrDid: null,
    authorizationServer: 'https://auth.example.com',
);

// Or set the project-wide default once in ClientConfig:
$config = new ClientConfig(
    // ...
    defaultAuthorizationServer: 'https://auth.example.com',
);

// The authorization server redirects back with code, state, and iss
$session = $oauth->completeAuthorization(
    code: $_GET['code'],
    state: $_GET['state'],
    iss: $_GET['iss'],
);

// $session->did    — "did:plc:..."
// $session->handle — "alice.bsky.social"
// $session->pdsUrl — "https://bsky.social" (or wherever their PDS is)

// GET request
$response = $session->authenticatedRequest(
    'GET',
    $session->pdsUrl.'/xrpc/com.atproto.server.getSession',
);

// POST request with JSON body
$response = $session->authenticatedRequest(
    'POST',
    $session->pdsUrl.'/xrpc/com.atproto.repo.createRecord',
    [
        'repo' => $session->did,
        'collection' => 'app.bsky.feed.post',
        'record' => [
            'text' => 'Hello from atproto-php!',
            'createdAt' => date('c'),
        ],
    ],
);

// Upload an image as a blob (returns a blob ref you can embed in a post)
$bytes = file_get_contents('/path/to/photo.jpg');

$response = $session->authenticatedRawRequest(
    method: 'POST',
    url: $session->pdsUrl.'/xrpc/com.atproto.repo.uploadBlob',
    body: $bytes,
    contentType: 'image/jpeg',
);

$blob = json_decode((string) $response->getBody(), true)['blob'];
// $blob is now { "$type": "blob", "ref": {...}, "mimeType": "image/jpeg", "size": ... }
// — embed it in an app.bsky.feed.post record's `embed.images[].image` field

use Gimucco\Atproto\Storage\InMemorySessionStore;
use Gimucco\Atproto\Storage\InMemoryStateStore;

$sessionStore = new InMemorySessionStore();
$stateStore = new InMemoryStateStore();

use Gimucco\Atproto\Storage\FileSessionStore;
use Gimucco\Atproto\Storage\FileStateStore;

$sessionStore = new FileSessionStore(
    directory: '/var/app/sessions',
    passphrase: 'your-strong-passphrase', // encrypts tokens at rest
);
$stateStore = new FileStateStore(
    directory: '/var/app/states',
    passphrase: 'your-strong-passphrase',
);

use Gimucco\Atproto\Storage\PdoSessionStore;
use Gimucco\Atproto\Storage\PdoStateStore;
use Gimucco\Atproto\Storage\Pdo\Schema;

$pdo = new PDO('mysql:host=localhost;dbname=myapp', 'user', 'pass');

// Create tables (run once)
$sql = Schema::createTablesSql('mysql');
$pdo->exec($sql['sessions']);
$pdo->exec($sql['states']);

$sessionStore = new PdoSessionStore($pdo, passphrase: 'your-strong-passphrase');
$stateStore = new PdoStateStore($pdo, passphrase: 'your-strong-passphrase');

$sql = Schema::createTablesSql('mysql');   // or 'pgsql' or 'sqlite'

$session->refresh();

if ($session->isExpired()) {
    // Token has expired
}

$expiresAt = $session->expiresAt(); // DateTimeImmutable

use Gimucco\Atproto\Exception\ResolutionException;
use Gimucco\Atproto\Exception\AuthorizationException;
use Gimucco\Atproto\Exception\TokenException;
use Gimucco\Atproto\Exception\DpopException;
use Gimucco\Atproto\Exception\SessionException;
use Gimucco\Atproto\Exception\ConfigurationException;
use Gimucco\Atproto\Exception\NetworkException;

try {
    $authUrl = $oauth->beginAuthorization($handle);
} catch (ResolutionException $e) {
    // Handle/DID/PDS could not be resolved
} catch (AuthorizationException $e) {
    // PAR request failed
} catch (NetworkException $e) {
    // HTTP transport failure
}

try {
    $session = $oauth->completeAuthorization($code, $state, $iss);
} catch (TokenException $e) {
    // Token endpoint returned an error
    echo $e->error;            // e.g., "invalid_grant"
    echo $e->errorDescription; // Human-readable message
} catch (AuthorizationException $e) {
    // State/issuer/sub mismatch
}

use Monolog\Logger;
use Monolog\Handler\StreamHandler;

$logger = new Logger('atproto');
$logger->pushHandler(new StreamHandler('/var/log/atproto.log'));

$oauth = new OAuthClient(
    config: $config,
    sessionStore: $sessionStore,
    stateStore: $stateStore,
    httpClient: $httpClient,
    requestFactory: $factory,
    streamFactory: $factory,
    logger: $logger,
);

$httpClient = new \GuzzleHttp\Client(['verify' => false]);
bash
bin/generate-metadata --config=examples/config.php --output=examples/public
bash
cd examples
cp config.example.php config.php
# Edit config.php with your settings
php -S localhost:8080 -t public