Resolve AT Protocol DIDs, handles, and schemas with intelligent caching for Laravel
1<?php
2
3namespace SocialDept\AtpResolver;
4
5use SocialDept\AtpResolver\Contracts\CacheStore;
6use SocialDept\AtpResolver\Contracts\DidResolver;
7use SocialDept\AtpResolver\Contracts\HandleResolver;
8use SocialDept\AtpResolver\Data\DidDocument;
9use SocialDept\AtpResolver\Exceptions\DidResolutionException;
10use SocialDept\AtpResolver\Exceptions\HandleResolutionException;
11use SocialDept\AtpResolver\Support\Concerns\HasConfig;
12use SocialDept\AtpResolver\Support\Identity;
13
14class Resolver
15{
16 use HasConfig;
17
18 /**
19 * Create a new Resolver instance.
20 */
21 public function __construct(
22 protected DidResolver $didResolver,
23 protected HandleResolver $handleResolver,
24 protected CacheStore $cache
25 ) {
26 }
27
28 /**
29 * Resolve a DID to a DID Document.
30 *
31 * @param string $did
32 * @param bool $useCache
33 * @return DidDocument
34 * @throws DidResolutionException
35 */
36 public function resolveDid(string $did, bool $useCache = true): DidDocument
37 {
38 $cacheKey = "did:{$did}";
39
40 if ($useCache && $this->cache->has($cacheKey)) {
41 $cached = $this->cache->get($cacheKey);
42
43 if ($cached instanceof DidDocument) {
44 return $cached;
45 }
46 }
47
48 $document = $this->didResolver->resolve($did);
49
50 if ($useCache) {
51 $ttl = $this->getConfig('resolver.cache.did_ttl', 3600);
52 $this->cache->put($cacheKey, $document, $ttl);
53 }
54
55 return $document;
56 }
57
58 /**
59 * Convert a handle to its DID.
60 *
61 * @param string $handle
62 * @param bool $useCache
63 * @return string
64 * @throws HandleResolutionException
65 */
66 public function handleToDid(string $handle, bool $useCache = true): string
67 {
68 $cacheKey = "handle:{$handle}";
69
70 if ($useCache && $this->cache->has($cacheKey)) {
71 return $this->cache->get($cacheKey);
72 }
73
74 $did = $this->handleResolver->resolve($handle);
75
76 if ($useCache) {
77 $ttl = $this->getConfig('resolver.cache.handle_ttl', 3600);
78 $this->cache->put($cacheKey, $did, $ttl);
79 }
80
81 return $did;
82 }
83
84 /**
85 * Resolve a handle to its DID Document.
86 *
87 * @param string $handle
88 * @param bool $useCache
89 * @return DidDocument
90 * @throws DidResolutionException
91 * @throws HandleResolutionException
92 */
93 public function resolveHandle(string $handle, bool $useCache = true): DidDocument
94 {
95 $did = $this->handleToDid($handle, $useCache);
96
97 return $this->resolveDid($did, $useCache);
98 }
99
100 /**
101 * Resolve an identity (DID or handle) to its DID Document.
102 *
103 * @param string $actor A DID or handle
104 * @param bool $useCache
105 * @return DidDocument
106 * @throws DidResolutionException
107 * @throws HandleResolutionException
108 */
109 public function resolveIdentity(string $actor, bool $useCache = true): DidDocument
110 {
111 return Identity::isDid($actor)
112 ? $this->resolveDid($actor, $useCache)
113 : $this->resolveHandle($actor, $useCache);
114 }
115
116 /**
117 * Clear cached data for a DID.
118 *
119 * @param string $did
120 */
121 public function clearDidCache(string $did): void
122 {
123 $this->cache->forget("did:{$did}");
124 }
125
126 /**
127 * Clear cached data for a handle.
128 *
129 * @param string $handle
130 */
131 public function clearHandleCache(string $handle): void
132 {
133 $this->cache->forget("handle:{$handle}");
134 }
135
136 /**
137 * Clear all cached data.
138 */
139 public function clearCache(): void
140 {
141 $this->cache->flush();
142 }
143
144 /**
145 * Resolve a DID or handle to its PDS endpoint.
146 *
147 * @param string $actor A DID (e.g., "did:plc:abc123") or handle (e.g., "user.bsky.social")
148 * @param bool $useCache
149 * @return string|null The PDS endpoint URL or null if not found
150 * @throws DidResolutionException
151 * @throws HandleResolutionException
152 */
153 public function resolvePds(string $actor, bool $useCache = true): ?string
154 {
155 $cacheKey = "pds:{$actor}";
156
157 if ($useCache && $this->cache->has($cacheKey)) {
158 return $this->cache->get($cacheKey);
159 }
160
161 // Determine if input is a DID or handle
162 $document = $this->resolveIdentity($actor, $useCache);
163
164 $pdsEndpoint = $document->getPdsEndpoint();
165
166 if ($useCache && $pdsEndpoint !== null) {
167 $ttl = $this->getConfig('resolver.cache.pds_ttl', 3600);
168 $this->cache->put($cacheKey, $pdsEndpoint, $ttl);
169 }
170
171 return $pdsEndpoint;
172 }
173
174 /**
175 * Clear cached PDS endpoint for a DID or handle.
176 *
177 * @param string $actor
178 */
179 public function clearPdsCache(string $actor): void
180 {
181 $this->cache->forget("pds:{$actor}");
182 }
183}