Maintain local ⭤ remote in sync with automatic AT Protocol parity for Laravel (alpha & unstable)
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 363 lines 6.6 kB view raw view rendered
1# Model Traits 2 3Parity provides two traits to add AT Protocol awareness to your Eloquent models. 4 5## HasAtpRecord 6 7The base trait for models that store AT Protocol record references. 8 9### Setup 10 11```php 12<?php 13 14namespace App\Models; 15 16use Illuminate\Database\Eloquent\Model; 17use SocialDept\AtpParity\Concerns\HasAtpRecord; 18 19class Post extends Model 20{ 21 use HasAtpRecord; 22 23 protected $fillable = [ 24 'content', 25 'published_at', 26 'atp_uri', 27 'atp_cid', 28 ]; 29} 30``` 31 32### Database Migration 33 34```php 35Schema::create('posts', function (Blueprint $table) { 36 $table->id(); 37 $table->text('content'); 38 $table->timestamp('published_at'); 39 $table->string('atp_uri')->nullable()->unique(); 40 $table->string('atp_cid')->nullable(); 41 $table->timestamps(); 42}); 43``` 44 45### Available Methods 46 47#### `getAtpUri(): ?string` 48 49Returns the stored AT Protocol URI. 50 51```php 52$post->getAtpUri(); 53// "at://did:plc:abc123/app.bsky.feed.post/xyz789" 54``` 55 56#### `getAtpCid(): ?string` 57 58Returns the stored content identifier. 59 60```php 61$post->getAtpCid(); 62// "bafyreib2rxk3rjnlvzj..." 63``` 64 65#### `getAtpDid(): ?string` 66 67Extracts the DID from the URI. 68 69```php 70$post->getAtpDid(); 71// "did:plc:abc123" 72``` 73 74#### `getAtpCollection(): ?string` 75 76Extracts the collection (lexicon NSID) from the URI. 77 78```php 79$post->getAtpCollection(); 80// "app.bsky.feed.post" 81``` 82 83#### `getAtpRkey(): ?string` 84 85Extracts the record key from the URI. 86 87```php 88$post->getAtpRkey(); 89// "xyz789" 90``` 91 92#### `hasAtpRecord(): bool` 93 94Checks if the model has been synced to AT Protocol. 95 96```php 97if ($post->hasAtpRecord()) { 98 // Model exists on AT Protocol 99} 100``` 101 102#### `getAtpMapper(): ?RecordMapper` 103 104Gets the registered mapper for this model class. 105 106```php 107$mapper = $post->getAtpMapper(); 108``` 109 110#### `toAtpRecord(): ?Data` 111 112Converts the model to an AT Protocol record DTO. 113 114```php 115$record = $post->toAtpRecord(); 116$data = $record->toArray(); // Ready for API calls 117``` 118 119### Query Scopes 120 121#### `scopeWithAtpRecord($query)` 122 123Query only models that have been synced. 124 125```php 126$syncedPosts = Post::withAtpRecord()->get(); 127``` 128 129#### `scopeWithoutAtpRecord($query)` 130 131Query only models that have NOT been synced. 132 133```php 134$localOnlyPosts = Post::withoutAtpRecord()->get(); 135``` 136 137#### `scopeWhereAtpUri($query, string $uri)` 138 139Find a model by its AT Protocol URI. 140 141```php 142$post = Post::whereAtpUri('at://did:plc:xxx/app.bsky.feed.post/abc')->first(); 143``` 144 145## SyncsWithAtp 146 147Extended trait for bidirectional synchronization tracking. Includes all `HasAtpRecord` functionality plus sync timestamps and conflict detection. 148 149### Setup 150 151```php 152<?php 153 154namespace App\Models; 155 156use Illuminate\Database\Eloquent\Model; 157use SocialDept\AtpParity\Concerns\SyncsWithAtp; 158 159class Post extends Model 160{ 161 use SyncsWithAtp; 162 163 protected $fillable = [ 164 'content', 165 'published_at', 166 'atp_uri', 167 'atp_cid', 168 'atp_synced_at', 169 ]; 170 171 protected $casts = [ 172 'published_at' => 'datetime', 173 'atp_synced_at' => 'datetime', 174 ]; 175} 176``` 177 178### Database Migration 179 180```php 181Schema::create('posts', function (Blueprint $table) { 182 $table->id(); 183 $table->text('content'); 184 $table->timestamp('published_at'); 185 $table->string('atp_uri')->nullable()->unique(); 186 $table->string('atp_cid')->nullable(); 187 $table->timestamp('atp_synced_at')->nullable(); 188 $table->timestamps(); 189}); 190``` 191 192### Additional Methods 193 194#### `getAtpSyncedAtColumn(): string` 195 196Returns the column name for the sync timestamp. Override to customize. 197 198```php 199public function getAtpSyncedAtColumn(): string 200{ 201 return 'last_synced_at'; // Default: 'atp_synced_at' 202} 203``` 204 205#### `getAtpSyncedAt(): ?DateTimeInterface` 206 207Returns when the model was last synced. 208 209```php 210$syncedAt = $post->getAtpSyncedAt(); 211// Carbon instance or null 212``` 213 214#### `markAsSynced(string $uri, string $cid): void` 215 216Marks the model as synced with the given metadata. Does not save. 217 218```php 219$post->markAsSynced($uri, $cid); 220$post->save(); 221``` 222 223#### `hasLocalChanges(): bool` 224 225Checks if the model has been modified since the last sync. 226 227```php 228if ($post->hasLocalChanges()) { 229 // Local changes exist that haven't been pushed 230} 231``` 232 233This compares `updated_at` with `atp_synced_at`. 234 235#### `updateFromRecord(Data $record, string $uri, string $cid): void` 236 237Updates the model from a remote record. Does not save. 238 239```php 240$post->updateFromRecord($record, $uri, $cid); 241$post->save(); 242``` 243 244## Practical Examples 245 246### Checking Sync Status 247 248```php 249$post = Post::find(1); 250 251if (!$post->hasAtpRecord()) { 252 echo "Not yet published to AT Protocol"; 253} elseif ($post->hasLocalChanges()) { 254 echo "Has unpushed local changes"; 255} else { 256 echo "In sync with AT Protocol"; 257} 258``` 259 260### Finding Related Records 261 262```php 263// Get all posts from the same author 264$authorDid = $post->getAtpDid(); 265$authorPosts = Post::withAtpRecord() 266 ->get() 267 ->filter(fn($p) => $p->getAtpDid() === $authorDid); 268``` 269 270### Building an AT Protocol URL 271 272```php 273$post = Post::find(1); 274 275if ($post->hasAtpRecord()) { 276 $bskyUrl = sprintf( 277 'https://bsky.app/profile/%s/post/%s', 278 $post->getAtpDid(), 279 $post->getAtpRkey() 280 ); 281} 282``` 283 284### Sync Status Dashboard 285 286```php 287// Get sync statistics 288$stats = [ 289 'total' => Post::count(), 290 'synced' => Post::withAtpRecord()->count(), 291 'pending' => Post::withoutAtpRecord()->count(), 292 'with_changes' => Post::withAtpRecord() 293 ->get() 294 ->filter(fn($p) => $p->hasLocalChanges()) 295 ->count(), 296]; 297``` 298 299## Custom Column Names 300 301Both traits respect the global column configuration: 302 303```php 304// config/parity.php 305return [ 306 'columns' => [ 307 'uri' => 'at_protocol_uri', 308 'cid' => 'at_protocol_cid', 309 ], 310]; 311``` 312 313For the sync timestamp column, override the method in your model: 314 315```php 316class Post extends Model 317{ 318 use SyncsWithAtp; 319 320 public function getAtpSyncedAtColumn(): string 321 { 322 return 'last_synced_at'; 323 } 324} 325``` 326 327## Event Hooks 328 329The `SyncsWithAtp` trait includes a boot method you can extend: 330 331```php 332class Post extends Model 333{ 334 use SyncsWithAtp; 335 336 protected static function bootSyncsWithAtp(): void 337 { 338 parent::bootSyncsWithAtp(); 339 340 static::updating(function ($model) { 341 // Custom logic before updates 342 }); 343 } 344} 345``` 346 347## Combining with Other Traits 348 349The traits work alongside other Eloquent features: 350 351```php 352use Illuminate\Database\Eloquent\Model; 353use Illuminate\Database\Eloquent\SoftDeletes; 354use SocialDept\AtpParity\Concerns\SyncsWithAtp; 355 356class Post extends Model 357{ 358 use SoftDeletes; 359 use SyncsWithAtp; 360 361 // Both traits work together 362} 363```