diff --git a/app/Http/Controllers/MicropubController.php b/app/Http/Controllers/MicropubController.php index be44b371..a1e81e36 100644 --- a/app/Http/Controllers/MicropubController.php +++ b/app/Http/Controllers/MicropubController.php @@ -45,7 +45,9 @@ class MicropubController extends Controller try { $handler = $this->handlerRegistry->getHandler($type); - $result = $handler->handle($request->getMicropubData()); + $dataClass = $handler->dataClass(); + $data = $dataClass::fromArray($request->getMicropubData()); + $result = $handler->handle($data); if ($result['response'] === 'updated') { return response()->json([ diff --git a/app/Providers/MicropubServiceProvider.php b/app/Providers/MicropubServiceProvider.php index e217827f..b8ff479c 100644 --- a/app/Providers/MicropubServiceProvider.php +++ b/app/Providers/MicropubServiceProvider.php @@ -4,10 +4,10 @@ declare(strict_types=1); namespace App\Providers; -use App\Services\Micropub\CardHandler; -use App\Services\Micropub\EntryHandler; +use App\Services\Micropub\Handlers\CardHandler; +use App\Services\Micropub\Handlers\EntryHandler; +use App\Services\Micropub\Handlers\UpdateHandler; use App\Services\Micropub\MicropubHandlerRegistry; -use App\Services\Micropub\UpdateHandler; use Illuminate\Support\ServiceProvider; class MicropubServiceProvider extends ServiceProvider diff --git a/app/Services/Micropub/Data/CardData.php b/app/Services/Micropub/Data/CardData.php new file mode 100644 index 00000000..cee41109 --- /dev/null +++ b/app/Services/Micropub/Data/CardData.php @@ -0,0 +1,44 @@ + $this->tokenData, + 'name' => $this->name, + 'description' => $this->description, + 'geo' => $this->geo, + 'location' => $this->location, + 'latitude' => $this->latitude, + 'longitude' => $this->longitude, + ]; + } +} diff --git a/app/Services/Micropub/Data/EntryData.php b/app/Services/Micropub/Data/EntryData.php new file mode 100644 index 00000000..cf87fdc5 --- /dev/null +++ b/app/Services/Micropub/Data/EntryData.php @@ -0,0 +1,65 @@ + $this->tokenData, + 'content' => $this->content, + 'in-reply-to' => $this->inReplyTo, + 'published' => $this->published, + 'location' => $this->location, + 'bookmark-of' => $this->bookmarkOf, + 'like-of' => $this->likeOf, + 'mp-syndicate-to' => $this->mpSyndicateTo, + 'name' => $this->name, + 'description' => $this->description, + 'geo' => $this->geo, + 'checkin' => $this->checkin, + 'syndication' => $this->syndication, + 'photos' => $this->photos, + ]; + } +} diff --git a/app/Services/Micropub/Data/MicropubData.php b/app/Services/Micropub/Data/MicropubData.php new file mode 100644 index 00000000..3fabcadc --- /dev/null +++ b/app/Services/Micropub/Data/MicropubData.php @@ -0,0 +1,12 @@ + $this->tokenData, + 'update_url' => $this->updateUrl, + 'update_replace' => $this->updateReplace, + 'update_add' => $this->updateAdd, + 'update_delete' => $this->updateDelete, + ]; + } +} diff --git a/app/Services/Micropub/CardHandler.php b/app/Services/Micropub/Handlers/CardHandler.php similarity index 61% rename from app/Services/Micropub/CardHandler.php rename to app/Services/Micropub/Handlers/CardHandler.php index 12e283be..02e3a066 100644 --- a/app/Services/Micropub/CardHandler.php +++ b/app/Services/Micropub/Handlers/CardHandler.php @@ -2,20 +2,28 @@ declare(strict_types=1); -namespace App\Services\Micropub; +namespace App\Services\Micropub\Handlers; use App\Exceptions\InvalidTokenScopeException; +use App\Services\Micropub\Data\CardData; +use App\Services\Micropub\Data\MicropubData; use App\Services\PlaceService; class CardHandler implements MicropubHandlerInterface { + public function dataClass(): string + { + return CardData::class; + } + /** * @throws InvalidTokenScopeException */ - public function handle(array $data): array + public function handle(MicropubData $data): array { - // Handle h-card requests - $scopes = $data['token_data']['scope']; + assert($data instanceof CardData); + + $scopes = $data->tokenData['scope']; if (is_string($scopes)) { $scopes = explode(' ', $scopes); } @@ -24,7 +32,7 @@ class CardHandler implements MicropubHandlerInterface throw new InvalidTokenScopeException; } - $location = resolve(PlaceService::class)->createPlace($data)->uri; + $location = resolve(PlaceService::class)->createPlace($data->toArray())->uri; return [ 'response' => 'created', diff --git a/app/Services/Micropub/EntryHandler.php b/app/Services/Micropub/Handlers/EntryHandler.php similarity index 51% rename from app/Services/Micropub/EntryHandler.php rename to app/Services/Micropub/Handlers/EntryHandler.php index 9cdbe789..ef9740f2 100644 --- a/app/Services/Micropub/EntryHandler.php +++ b/app/Services/Micropub/Handlers/EntryHandler.php @@ -2,22 +2,31 @@ declare(strict_types=1); -namespace App\Services\Micropub; +namespace App\Services\Micropub\Handlers; use App\Exceptions\InvalidTokenScopeException; use App\Services\ArticleService; use App\Services\BookmarkService; use App\Services\LikeService; +use App\Services\Micropub\Data\EntryData; +use App\Services\Micropub\Data\MicropubData; use App\Services\NoteService; class EntryHandler implements MicropubHandlerInterface { + public function dataClass(): string + { + return EntryData::class; + } + /** * @throws InvalidTokenScopeException */ - public function handle(array $data) + public function handle(MicropubData $data): array { - $scopes = $data['token_data']['scope']; + assert($data instanceof EntryData); + + $scopes = $data->tokenData['scope']; if (is_string($scopes)) { $scopes = explode(' ', $scopes); } @@ -26,11 +35,12 @@ class EntryHandler implements MicropubHandlerInterface throw new InvalidTokenScopeException; } + $dataArray = $data->toArray(); $location = match (true) { - isset($data['like-of']) => resolve(LikeService::class)->create($data)->url, - isset($data['bookmark-of']) => resolve(BookmarkService::class)->create($data)->uri, - isset($data['name']) => resolve(ArticleService::class)->create($data)->link, - default => resolve(NoteService::class)->create($data)->uri, + isset($dataArray['like-of']) => resolve(LikeService::class)->create($dataArray)->url, + isset($dataArray['bookmark-of']) => resolve(BookmarkService::class)->create($dataArray)->uri, + isset($dataArray['name']) => resolve(ArticleService::class)->create($dataArray)->link, + default => resolve(NoteService::class)->create($dataArray)->uri, }; return [ diff --git a/app/Services/Micropub/Handlers/MicropubHandlerInterface.php b/app/Services/Micropub/Handlers/MicropubHandlerInterface.php new file mode 100644 index 00000000..6afddc5f --- /dev/null +++ b/app/Services/Micropub/Handlers/MicropubHandlerInterface.php @@ -0,0 +1,44 @@ + + */ + public function dataClass(): string; + + /** + * Process the request and return a result array with at minimum a + * 'response' key ('created' or 'updated') and a 'url' key pointing to + * the affected resource. + */ + public function handle(MicropubData $data): array; +} diff --git a/app/Services/Micropub/UpdateHandler.php b/app/Services/Micropub/Handlers/UpdateHandler.php similarity index 80% rename from app/Services/Micropub/UpdateHandler.php rename to app/Services/Micropub/Handlers/UpdateHandler.php index d4558547..fb633c8b 100644 --- a/app/Services/Micropub/UpdateHandler.php +++ b/app/Services/Micropub/Handlers/UpdateHandler.php @@ -2,26 +2,34 @@ declare(strict_types=1); -namespace App\Services\Micropub; +namespace App\Services\Micropub\Handlers; use App\Exceptions\InvalidTokenScopeException; use App\Exceptions\MicropubHandlerException; use App\Exceptions\MicropubUnsupportedModelException; use App\Models\Media; use App\Models\Note; -use Illuminate\Support\Arr; +use App\Services\Micropub\Data\MicropubData; +use App\Services\Micropub\Data\UpdateData; use Illuminate\Support\Str; class UpdateHandler implements MicropubHandlerInterface { + public function dataClass(): string + { + return UpdateData::class; + } + /** * @throws InvalidTokenScopeException * @throws MicropubUnsupportedModelException * @throws MicropubHandlerException */ - public function handle(array $data): array + public function handle(MicropubData $data): array { - $scopes = $data['token_data']['scope']; + assert($data instanceof UpdateData); + + $scopes = $data->tokenData['scope']; if (is_string($scopes)) { $scopes = explode(' ', $scopes); } @@ -30,7 +38,7 @@ class UpdateHandler implements MicropubHandlerInterface throw new InvalidTokenScopeException; } - $urlPath = parse_url(Arr::get($data, 'update_url'), PHP_URL_PATH); + $urlPath = parse_url($data->updateUrl, PHP_URL_PATH); if (mb_substr($urlPath, 1, 5) !== 'notes') { throw new MicropubUnsupportedModelException('This implementation currently only supports the updating of notes'); @@ -38,8 +46,8 @@ class UpdateHandler implements MicropubHandlerInterface $note = Note::nb60(basename($urlPath))->firstOrFail(); - if (Arr::get($data, 'update_replace')) { - foreach (Arr::get($data, 'update_replace') as $property => $value) { + if ($data->updateReplace !== null) { + foreach ($data->updateReplace as $property => $value) { if ($property === 'content') { $note->note = $value[0]; } @@ -55,8 +63,8 @@ class UpdateHandler implements MicropubHandlerInterface ]; } - if (Arr::get($data, 'update_add')) { - foreach (Arr::get($data, 'update_add') as $property => $value) { + if ($data->updateAdd !== null) { + foreach ($data->updateAdd as $property => $value) { if ($property === 'syndication') { $this->applySyndication($note, $value); } diff --git a/app/Services/Micropub/MicropubHandlerInterface.php b/app/Services/Micropub/MicropubHandlerInterface.php deleted file mode 100644 index 82040be9..00000000 --- a/app/Services/Micropub/MicropubHandlerInterface.php +++ /dev/null @@ -1,10 +0,0 @@ -dataClass()::fromArray($rawData) + * → $handler->handle($dataObject) + * + * Handlers are registered in MicropubServiceProvider. To support a new + * Micropub post type, create a handler in App\Services\Micropub\Handlers, a + * matching data class in App\Services\Micropub\Data, and register the handler + * here with its type string. + */ class MicropubHandlerRegistry { /** @@ -13,6 +33,9 @@ class MicropubHandlerRegistry */ protected array $handlers = []; + /** + * Register a handler for a given Micropub type string. + */ public function register(string $type, MicropubHandlerInterface $handler): self { $this->handlers[$type] = $handler; @@ -21,6 +44,8 @@ class MicropubHandlerRegistry } /** + * Retrieve the handler for a given type, or throw if none is registered. + * * @throws MicropubHandlerException */ public function getHandler(string $type): MicropubHandlerInterface