PHP code example of socialdept / atp-orm

1. Go to this page and download the library: Download socialdept/atp-orm 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/ */

    

socialdept / atp-orm example snippets


use App\Remote\Post;

// List a user's posts
$posts = Post::for('alice.bsky.social')->limit(10)->get();

foreach ($posts as $post) {
    echo $post->text;
    echo $post->createdAt;
}

// Paginate through all posts
while ($posts->hasMorePages()) {
    $posts = $posts->nextPage();
}

// Find a specific post
$post = Post::for('did:plc:ewvi7nxzyoun6zhxrhs64oiz')->find('3mdtrzs7kts2p');
echo $post->text;

// Find by AT-URI
$post = Post::for('alice.bsky.social')
    ->findByUri('at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/app.bsky.feed.post/3mdtrzs7kts2p');

namespace App\Remote;

use SocialDept\AtpOrm\RemoteRecord;
use SocialDept\AtpSchema\Generated\App\Bsky\Feed\Post as PostData;

class Post extends RemoteRecord
{
    protected string $collection = 'app.bsky.feed.post';
    protected string $recordClass = PostData::class;
    protected int $cacheTtl = 300;
}

use App\Remote\Post;

// Basic listing
$posts = Post::for('alice.bsky.social')->get();

// With options
$posts = Post::for('did:plc:ewvi7nxzyoun6zhxrhs64oiz')
    ->limit(25)
    ->reverse()
    ->get();

// By record key
$post = Post::for('alice.bsky.social')->find('3mdtrzs7kts2p');

// Throws RecordNotFoundException if not found
$post = Post::for('alice.bsky.social')->findOrFail('3mdtrzs7kts2p');

// By full AT-URI
$post = Post::for('alice.bsky.social')
    ->findByUri('at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/app.bsky.feed.post/3mdtrzs7kts2p');

$posts = Post::for($did)->limit(50)->get();

echo $posts->cursor(); // Pagination cursor

while ($posts->hasMorePages()) {
    $posts = $posts->nextPage();

    foreach ($posts as $post) {
        // Process each page...
    }
}

$firstPage = Post::for($did)->limit(50)->get();
$secondPage = Post::for($did)->limit(50)->after($firstPage->cursor())->get();

$post = Post::for($did)->find($rkey);

// Property access
$post->text;
$post->createdAt;

// Array access
$post['text'];

// Method access
$post->getAttribute('text');

// Record metadata
$post->getUri();    // "at://did:plc:.../app.bsky.feed.post/..."
$post->getRkey();   // "3mdtrzs7kts2p"
$post->getCid();    // "bafyreic3..."
$post->getDid();    // "did:plc:..."

// Convert to atp-schema DTO
$dto = $post->toDto();

// Convert to array
$data = $post->toArray();

// Use model's default TTL
$posts = Post::for($did)->get();

// Custom TTL for this query (seconds)
$posts = Post::for($did)->remember(600)->get();

// Bypass cache entirely
$posts = Post::for($did)->fresh()->get();

// Reload a single record from remote
$post = $post->fresh();

// Invalidate all cached data for a scope
Post::for($did)->invalidate();

// config/atp-orm.php
'cache' => [
    'invalidation' => [
        'enabled' => true,
        'collections' => null, // null = all collections
        'dids' => null,        // null = all DIDs
    ],
],

$post = Post::as($authenticatedDid)->create([
    'text' => 'Hello from ORM!',
    'createdAt' => now()->toIso8601String(),
]);

echo $post->getUri(); // "at://did:plc:.../app.bsky.feed.post/..."

$post = Post::as($did)->for($did)->find($rkey);

$post->text = 'Updated text';
$post->save();

// Or in one call
$post->update(['text' => 'Updated text']);

$post = Post::as($did)->for($did)->find($rkey);
$post->delete();

$post = Post::for($did)->find($rkey);

$post->isDirty();        // false
$post->text = 'New text';
$post->isDirty();        // true
$post->isDirty('text');  // true
$post->getDirty();       // ['text' => 'New text']
$post->getOriginal('text'); // Original value

// Requires socialdept/atp-signals
$allPosts = Post::for($did)->fromRepo()->get();

$post = Post::for('did:plc:abc')->find('rk1');

// Get all likes on this post
$likes = $post->backlinks()->likes();

echo $likes->total();  // 2852
echo $likes->count();  // Items in this page

foreach ($likes as $ref) {
    echo $ref->did;    // Who liked it
    echo $ref->uri();  // at://did/app.bsky.feed.like/rkey
}

$post->backlinks()->likes();      // app.bsky.feed.like -> subject.uri
$post->backlinks()->quotes();     // app.bsky.feed.post -> embed.record.uri
$post->backlinks()->replies();    // app.bsky.feed.post -> reply.parent.uri
$post->backlinks()->reposts();    // app.bsky.feed.repost -> subject.uri
$post->backlinks()->mentions();   // app.bsky.feed.post -> facets[...].features[...mention].did
$post->backlinks()->followers();  // app.bsky.graph.follow -> subject

// Find all records in a custom collection that link to this post
$backlinks = $post->backlinks()
    ->source('com.example.bookmark', 'post.uri')
    ->limit(50)
    ->reverse()
    ->get();

use SocialDept\AtpOrm\Backlinks\BacklinkQuery;

// Find followers of a DID
$followers = BacklinkQuery::for('did:plc:abc')
    ->source('app.bsky.graph.follow', 'subject')
    ->get();

// Get a count
$likeCount = BacklinkQuery::for('at://did:plc:abc/app.bsky.feed.post/rk1')
    ->source('app.bsky.feed.like', 'subject.uri')
    ->count();

$summary = $post->backlinks()->all();

// Returns LinkSummary with nested structure:
// app.bsky.feed.like -> .subject.uri -> { records: 2852, distinct_dids: 2852 }
// app.bsky.feed.post -> .embed.record.uri -> { records: 1143, distinct_dids: 1123 }
// app.bsky.feed.repost -> .subject.uri -> { records: 320, distinct_dids: 320 }

$summary->total();                              // 7205
$summary->forCollection('app.bsky.feed.like');  // Filter to a single collection

$likes = $post->backlinks()->likes();

while ($likes->hasMorePages()) {
    $likes = $likes->nextPage();
}

$hydrated = $post->backlinks()
    ->source('app.bsky.feed.like', 'subject.uri')
    ->hydrate(Like::class);

// Returns RemoteCollection of Like instances
foreach ($hydrated as $like) {
    echo $like->subject; // Full record data
}

$likes = $post->backlinks()->likes();

$likes->uris();     // Collection of AT-URIs
$likes->dids();     // Collection of unique DIDs
$likes->toArray();  // Array of {did, collection, rkey, uri}
$likes->filter(fn ($ref) => $ref->did === 'did:plc:abc');

// Per-query
$post = Post::for('did:plc:abc')->viaSlingshot()->find('rk1');

// config/atp-orm.php
'record_source' => env('ATP_ORM_RECORD_SOURCE', 'pds'), // 'pds' or 'slingshot'

use SocialDept\AtpOrm\Events\RecordCreated;

Event::listen(RecordCreated::class, function (RecordCreated $event) {
    logger()->info('Record created', [
        'uri' => $event->record->getUri(),
    ]);
});

'events' => [
    'enabled' => false,
],

use SocialDept\AtpOrm\Support\AtUri;

$uri = AtUri::parse('at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/app.bsky.feed.post/3mdtrzs7kts2p');

$uri->did;        // "did:plc:ewvi7nxzyoun6zhxrhs64oiz"
$uri->collection; // "app.bsky.feed.post"
$uri->rkey;       // "3mdtrzs7kts2p"

// Build a URI
$uri = AtUri::make($did, 'app.bsky.feed.post', $rkey);
echo (string) $uri; // "at://did/app.bsky.feed.post/rkey"

$posts = Post::for($did)->get();

$posts->count();
$posts->isEmpty();
$posts->isNotEmpty();
$posts->first();
$posts->last();
$posts->pluck('text');
$posts->filter(fn ($post) => str_contains($post->text, 'hello'));
$posts->map(fn ($post) => $post->text);
$posts->each(fn ($post) => logger()->info($post->text));
$posts->toArray();
$posts->toCollection(); // Convert to Laravel Collection

return [
    // Cache provider class (LaravelCacheProvider, FileCacheProvider, or ArrayCacheProvider)
    'cache_provider' => \SocialDept\AtpOrm\Providers\LaravelCacheProvider::class,

    // Record source: 'pds' (default) or 'slingshot' (Microcosm cache)
    'record_source' => env('ATP_ORM_RECORD_SOURCE', 'pds'),

    'cache' => [
        'default_ttl' => 300,      // 5 minutes (0 = no caching)
        'prefix' => 'atp-orm',
        'store' => null,           // Laravel cache store (null = default)
        'file_path' => storage_path('app/atp-orm-cache'), // FileCacheProvider storage path

        // Per-collection TTL overrides
        'ttls' => [
            'app.bsky.feed.post' => 600,
            'app.bsky.graph.follow' => 3600,
        ],

        // Automatic invalidation via firehose (

use SocialDept\AtpOrm\Exceptions\ReadOnlyException;
use SocialDept\AtpOrm\Exceptions\RecordNotFoundException;

try {
    $post = Post::for($did)->findOrFail('nonexistent');
} catch (RecordNotFoundException $e) {
    // "Record not found: at://did/app.bsky.feed.post/nonexistent"
}

try {
    // Attempting write without ::as()
    Post::for($did)->create(['text' => 'Hello']);
} catch (ReadOnlyException $e) {
    // "Cannot write without an authenticated DID. Use ::as($did) for write operations."
}

// config/atp-orm.php (in testing environment)
'cache_provider' => \SocialDept\AtpOrm\Providers\ArrayCacheProvider::class,
bash
php artisan vendor:publish --tag=atp-orm-config
bash
php artisan make:remote-record Post --collection=app.bsky.feed.post