{"id":24054549,"url":"https://github.com/rikudousage/activitypub","last_synced_at":"2025-05-09T01:48:27.181Z","repository":{"id":271558543,"uuid":"912996408","full_name":"RikudouSage/ActivityPub","owner":"RikudouSage","description":"A strongly typed, validated and developer-friendly ActivityPub implementation in PHP","archived":false,"fork":false,"pushed_at":"2025-05-02T15:25:05.000Z","size":117,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-09T01:48:19.857Z","etag":null,"topics":["activitypub","activitypub-php","php"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RikudouSage.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-01-06T20:24:37.000Z","updated_at":"2025-05-02T15:24:42.000Z","dependencies_parsed_at":"2025-01-08T14:35:00.246Z","dependency_job_id":"0b9b53b4-233a-49c0-b955-f6644a8ddec2","html_url":"https://github.com/RikudouSage/ActivityPub","commit_stats":null,"previous_names":["rikudousage/activitypub"],"tags_count":46,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RikudouSage%2FActivityPub","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RikudouSage%2FActivityPub/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RikudouSage%2FActivityPub/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RikudouSage%2FActivityPub/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RikudouSage","download_url":"https://codeload.github.com/RikudouSage/ActivityPub/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253176440,"owners_count":21866142,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["activitypub","activitypub-php","php"],"created_at":"2025-01-09T03:47:53.392Z","updated_at":"2025-05-09T01:48:27.153Z","avatar_url":"https://github.com/RikudouSage.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ActivityPub for PHP\n\nA strongly typed and developer friendly ActivityPub implementation. All Core and Extended types are implemented.\nAlso some widely used unofficial extensions.\n\n## Table of contents\n\n\u003c!-- TOC --\u003e\n* [ActivityPub for PHP](#activitypub-for-php)\n  * [Table of contents](#table-of-contents)\n  * [Installation](#installation)\n  * [Objects](#objects)\n    * [Naming](#naming)\n    * [Objects and activities](#objects-and-activities)\n    * [Validations](#validations)\n    * [Parsing JSON into types](#parsing-json-into-types)\n    * [Creating your own types](#creating-your-own-types)\n  * [Server](#server)\n    * [Request signing](#request-signing)\n    * [Request validating](#request-validating)\n    * [Fetching objects](#fetching-objects)\n  * [Symfony usage](#symfony-usage)\n\u003c!-- TOC --\u003e\n\n## Installation\n\n`composer require rikudou/activity-pub`\n\n## Objects\n\n### Naming\n\nAll object names are the same as in the ActivityPub/ActivityStreams specifications, with the sole exception of the\nbase `Object` which is called `BaseObject` because PHP disallows having a class called `Object`.\n\n### Objects and activities\n\nTo construct an object, simply create it as you normally would, for example, let's construct a note:\n\n```php\n\u003c?php\n\nuse Rikudou\\ActivityPub\\Vocabulary\\Extended\\Object\\Note;\nuse Rikudou\\ActivityPub\\Dto\\Source\\MarkdownSource;\n\n$note = new Note();\n$note-\u003eid = 'https://example.com/notes/123';\n$note-\u003econtent = 'Hello \u003cstrong\u003ethere\u003c/strong\u003e!';\n$note-\u003eattributedTo = 'https://example.com/user/some-actor';\n$note-\u003eto = 'https://example.com/user/some-other-actor';\n$note-\u003einReplyTo = 'https://example.com/notes/120';\n$note-\u003epublished = new DateTimeImmutable();\n$note-\u003esource = new MarkdownSource('Hello **there**');\n\necho json_encode($note, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);\n```\n\nThis prints:\n\n```json\n{\n    \"type\": \"Note\",\n    \"@context\": \"https://www.w3.org/ns/activitystreams\",\n    \"id\": \"https://example.com/notes/123\",\n    \"attributedTo\": \"https://example.com/user/some-actor\",\n    \"content\": \"Hello \u003cstrong\u003ethere\u003c/strong\u003e!\",\n    \"inReplyTo\": \"https://example.com/notes/120\",\n    \"published\": \"2025-01-06T22:52:11+01:00\",\n    \"to\": [\n        \"https://example.com/user/some-other-actor\"\n    ],\n    \"source\": {\n        \"content\": \"Hello **there**\",\n        \"mediaType\": \"text/markdown\"\n    }\n}\n```\n\n### Validations\n\nAll property assignments are validated using various set of rules depending on the type of the property and object.\nThere are multiple modes of validation:\n\n- **none** - no validation takes place\n- **lax** - not as strict as the **strict** mode, leaves out some stuff that is required by the specification\n  but isn't required in real-world scenarios\n- **strict** - strict adherence to the ActivityPub/ActivityStreams specifications\n- **recommended** - a custom opinionated set of rules, stricter than **strict**, but should prevent you making\n  some mistakes which are technically correct but make no real sense. Some bugs are possible for edge cases.\n\nFor example:\n\n```php\n\u003c?php\n\nuse Rikudou\\ActivityPub\\Vocabulary\\Extended\\Object\\Note;\n\n$note = new Note();\n$note-\u003eid = '123';\n```\n\nWhen running the snippet above, you get this exception:\n\n`Uncaught Rikudou\\ActivityPub\\Exception\\InvalidPropertyValueException: The value for property 'id' is not valid: string(123): the value must be a valid uri`\n\nNow let's do the same with changing the validation level to none:\n\n```php\n\u003c?php\n\nuse Rikudou\\ActivityPub\\Enum\\ValidatorMode;\nuse Rikudou\\ActivityPub\\Vocabulary\\Extended\\Object\\Note;\n\n$note = new Note();\n$note-\u003evalidatorMode = ValidatorMode::None;\n$note-\u003eid = '123';\n\necho json_encode($note, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);\n```\n\nThis prints the following JSON:\n\n```json\n{\n    \"type\": \"Note\",\n    \"@context\": \"https://www.w3.org/ns/activitystreams\",\n    \"id\": \"123\"\n}\n```\n\nIf you don't want to change the validator mode for every object individually, you can also use the `GlobalSettings` class:\n\n```php\n\u003c?php\n\nuse Rikudou\\ActivityPub\\Enum\\ValidatorMode;\nuse Rikudou\\ActivityPub\\GlobalSettings;\nuse Rikudou\\ActivityPub\\Vocabulary\\Extended\\Object\\Note;\n\nGlobalSettings::$validatorMode = ValidatorMode::None;\n\n$note = new Note();\n$note-\u003eid = '123';\n\necho json_encode($note, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);\n```\n\nThe above code prints the same JSON. Note that if you change the validator mode for an individual object, the global\nsetting doesn't have any effect anymore, until you manually set it back to null.\n\nThe last option is to use the `runInNoValidationContext` function:\n\n```php\n\u003c?php\n\nuse Rikudou\\ActivityPub\\Vocabulary\\Extended\\Object\\Note;\nuse function Rikudou\\ActivityPub\\runInNoValidationContext;\n\nrequire_once __DIR__ . '/vendor/autoload.php';\n\n$note = new Note();\nrunInNoValidationContext(\n    fn () =\u003e $note-\u003eid = '123',\n);\n\necho json_encode($note, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);\n```\n\nThe same caveats as for changing the global mode exist (because all this function does is it changes the global mode,\nruns your function, changes it back to the original value).\n\n### Non-standard properties\n\nIf you wish to use non-standard properties, you can use the setter:\n\n```php\n\u003c?php\n\nuse Rikudou\\ActivityPub\\Vocabulary\\Extended\\Object\\Note;\n\n$note = new Note();\n$note-\u003eid = 'https://example.com/note/1';\n$note-\u003eset('customProperty', 'customValue');\n\necho json_encode($note, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), PHP_EOL;\n```\n\nNote that unless you disable validation, custom properties are not allowed, so the above needs to run in\nthe no-validation context:\n\n```php\n\u003c?php\n\nuse Rikudou\\ActivityPub\\Vocabulary\\Extended\\Object\\Note;\nuse function Rikudou\\ActivityPub\\runInNoValidationContext;\n\nrequire __DIR__ . '/vendor/autoload.php';\n\n$note = new Note();\n$note-\u003eid = 'https://example.com/note/1';\nrunInNoValidationContext(fn () =\u003e $note-\u003eset('customProperty', 'customValue'));\n\necho json_encode($note, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), PHP_EOL;\n```\n\n### Parsing JSON into types\n\nWhile exporting ActivityPub objects to JSON is great, you'll need the exact opposite if you want to handle incoming\nactivities!\n\nLuckily for us, there's a `TypeParser` (more specifically, a class implementing the interface, `DefaultTypeParser`).\nLet's take the previous example as our input:\n\n```php\n\u003c?php\n\nuse Rikudou\\ActivityPub\\Vocabulary\\Extended\\Object\\Note;\nuse Rikudou\\ActivityPub\\Vocabulary\\Parser\\DefaultTypeParser;\n\n$parser = new DefaultTypeParser();\n\n$json = \u003c\u003c\u003c'JSON'\n{\n    \"type\": \"Note\",\n    \"@context\": \"https://www.w3.org/ns/activitystreams\",\n    \"id\": \"https://example.com/notes/123\",\n    \"attributedTo\": \"https://example.com/user/some-actor\",\n    \"content\": \"Hello \u003cstrong\u003ethere\u003c/strong\u003e!\",\n    \"inReplyTo\": \"https://example.com/notes/120\",\n    \"published\": \"2025-01-06T22:52:11+01:00\",\n    \"to\": [\n        \"https://example.com/user/some-other-actor\"\n    ],\n    \"source\": {\n        \"content\": \"Hello **there**\",\n        \"mediaType\": \"text/markdown\"\n    }\n}\nJSON;\n\n$note = $parser-\u003eparseJson($json);\n\n// all the following assertions are true\nassert($note instanceof Note);\nassert($note-\u003econtext === \"https://www.w3.org/ns/activitystreams\");\nassert($note-\u003eid === \"https://example.com/notes/123\");\nassert((string) $note-\u003eattributedTo === \"https://example.com/user/some-actor\");\nassert($note-\u003econtent === \"Hello \u003cstrong\u003ethere\u003c/strong\u003e!\");\nassert((string) $note-\u003einReplyTo === \"https://example.com/notes/120\");\nassert($note-\u003epublished-\u003eformat('c') === \"2025-01-06T22:52:11+01:00\");\nassert(count($note-\u003eto) === 1);\nassert((string) $note-\u003eto[0] === \"https://example.com/user/some-other-actor\");\nassert($note-\u003esource-\u003econtent === \"Hello **there**\");\nassert($note-\u003esource-\u003emediaType === \"text/markdown\");\n```\n\n### Creating your own types\n\nAll the ActivityPub objects can be extended by your own classes. The built-in ones use property hooks to automatically \nvalidate the values, but you can do it any other way, just make sure the properties are publicly readable.\n\nLet's create a custom type:\n\n```php\n\u003c?php\n\nuse Rikudou\\ActivityPub\\Vocabulary\\Core\\BaseObject;\n\nfinal class Cat extends BaseObject\n{\n    public string $type {\n        get =\u003e 'Cat';\n    }\n}\n\n```\n\nAdding a property is easy:\n\n```php\n\u003c?php\n\nuse Rikudou\\ActivityPub\\Vocabulary\\Core\\BaseObject;\n\nfinal class Cat extends BaseObject\n{\n    public string $type {\n        get =\u003e 'Cat';\n    }\n\n    public ?int $lives = null;\n}\n```\n\nNow, if you create your cat, you can check out the response JSON:\n\n```php\n\u003c?php\n\n$cat = new Cat();\n$cat-\u003eid = 'https://example.com/meow';\n$cat-\u003elives = 9;\n\necho json_encode($cat, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT), PHP_EOL;\n```\n\n```json\n{\n    \"type\": \"Cat\",\n    \"lives\": 9,\n    \"@context\": \"https://www.w3.org/ns/activitystreams\",\n    \"id\": \"https://example.com/meow\"\n}\n```\n\nNow, if you want to make sure your cat always has some lives, you can mark the property as required:\n\n```php\n\u003c?php\n\nuse Rikudou\\ActivityPub\\Attribute\\RequiredProperty;\nuse Rikudou\\ActivityPub\\Enum\\ValidatorMode;\nuse Rikudou\\ActivityPub\\Vocabulary\\Core\\BaseObject;\n\nfinal class Cat extends BaseObject\n{\n    public string $type {\n        get =\u003e 'Cat';\n    }\n\n    #[RequiredProperty(ValidatorMode::Lax)]\n    public ?int $lives = null;\n}\n```\n\nYou also need to specify the minimum validator mode that it's required on. If you set it to `Lax`, it will be required\non `Lax`, `Strict` and `Recommended`. If you set it to `Strict`, it will be required on `Strict` and `Recommended`.\n\nAnd now creating our cat throws this exception:\n\n```php\n\u003c?php\n\n$cat = new Cat();\n$cat-\u003eid = 'https://example.com/meow';\n\necho json_encode($cat, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT), PHP_EOL;\n\n// Uncaught Rikudou\\ActivityPub\\Exception\\MissingRequiredPropertyException: The property \"Cat:lives\" is required when running in \"Strict\" validator mode.\n```\n\nNow, let's get fancy and create our cat! And announce it to the world!\n\n```php\n\u003c?php\n\nuse Rikudou\\ActivityPub\\Attribute\\RequiredProperty;\nuse Rikudou\\ActivityPub\\Enum\\ValidatorMode;\nuse Rikudou\\ActivityPub\\GlobalSettings;\nuse Rikudou\\ActivityPub\\Vocabulary\\Core\\BaseObject;\nuse Rikudou\\ActivityPub\\Vocabulary\\Core\\Link;\nuse Rikudou\\ActivityPub\\Vocabulary\\Extended\\Activity\\Announce;\nuse Rikudou\\ActivityPub\\Vocabulary\\Extended\\Activity\\Create;\nuse Rikudou\\ActivityPub\\Vocabulary\\Extended\\Actor\\Person;\n\nfinal class Cat extends BaseObject\n{\n    public string $type {\n        get =\u003e 'Cat';\n    }\n\n    #[RequiredProperty(ValidatorMode::Lax)]\n    public ?int $lives = null;\n}\n\n$cat = new Cat();\n$cat-\u003eid = 'https://example.com/meow';\n$cat-\u003elives = 9;\n$cat-\u003ename = 'Meowth';\n\n$me = new Person();\n$me-\u003eid = 'https://example.com/me';\n$me-\u003ename = 'James';\n$me-\u003einbox = 'https://example.com/inbox';\n$me-\u003eoutbox = 'https://example.com/outbox';\n$me-\u003efollowing = 'https://example.com/following';\n$me-\u003efollowers = 'https://example.com/following';\n\n$create = new Create();\n$create-\u003eid = 'https://example.com/create/meow';\n$create-\u003eactor = $me;\n$create-\u003eobject = $cat;\n$create-\u003eto = Link::publicAudienceLink(); // a special link that indicates that the target is public\n\n$announcer = new Person();\n$announcer-\u003eid = 'https://example.com/not-me';\n$announcer-\u003ename = 'Jessie';\n$announcer-\u003einbox = 'https://example.com/inbox-jessie';\n$announcer-\u003eoutbox = 'https://example.com/outbox-jessie';\n$announcer-\u003efollowing = 'https://example.com/following-jessie';\n$announcer-\u003efollowers = 'https://example.com/following-jessie';\n\n$announce = new Announce();\n$announce-\u003eid = 'https://example.com/announce/create/meow';\n$announce-\u003eto = $create-\u003eto;\n$announce-\u003eactor = $announcer;\n$announce-\u003eobject = $create;\n\necho json_encode($announce, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT), PHP_EOL;\n```\n\nAll this prints this complicated-looking ActivityPub activity which can be sent to every ActivityPub server in the whole world!\n\n```json\n{\n  \"type\": \"Announce\",\n  \"actor\": {\n    \"type\": \"Person\",\n    \"inbox\": \"https://example.com/inbox-jessie\",\n    \"outbox\": \"https://example.com/outbox-jessie\",\n    \"following\": \"https://example.com/following-jessie\",\n    \"followers\": \"https://example.com/following-jessie\",\n    \"@context\": \"https://www.w3.org/ns/activitystreams\",\n    \"id\": \"https://example.com/not-me\",\n    \"name\": \"Jessie\"\n  },\n  \"object\": {\n    \"type\": \"Create\",\n    \"actor\": {\n      \"type\": \"Person\",\n      \"inbox\": \"https://example.com/inbox\",\n      \"outbox\": \"https://example.com/outbox\",\n      \"following\": \"https://example.com/following\",\n      \"followers\": \"https://example.com/following\",\n      \"@context\": \"https://www.w3.org/ns/activitystreams\",\n      \"id\": \"https://example.com/me\",\n      \"name\": \"James\"\n    },\n    \"object\": {\n      \"type\": \"Cat\",\n      \"lives\": 9,\n      \"@context\": \"https://www.w3.org/ns/activitystreams\",\n      \"id\": \"https://example.com/meow\",\n      \"name\": \"Meowth\"\n    },\n    \"@context\": \"https://www.w3.org/ns/activitystreams\",\n    \"id\": \"https://example.com/create/meow\",\n    \"to\": [\n      \"https://www.w3.org/ns/activitystreams#Public\"\n    ]\n  },\n  \"@context\": \"https://www.w3.org/ns/activitystreams\",\n  \"id\": \"https://example.com/announce/create/meow\",\n  \"to\": [\n    \"https://www.w3.org/ns/activitystreams#Public\"\n  ]\n}\n```\n\nNow, if your Cat object ever becomes so popular that everything using ActivityPub sends them back and forth,\nyou might want to register the type in the parser, otherwise it would just throw an exception saying that it\ndoesn't know about the Cat object.\n\n```php\n\u003c?php\n\nuse Rikudou\\ActivityPub\\Attribute\\RequiredProperty;\nuse Rikudou\\ActivityPub\\Enum\\ValidatorMode;\nuse Rikudou\\ActivityPub\\Vocabulary\\Core\\BaseObject;\nuse Rikudou\\ActivityPub\\Vocabulary\\Parser\\DefaultTypeParser;\n\nfinal class Cat extends BaseObject\n{\n    public string $type {\n        get =\u003e 'Cat';\n    }\n\n    #[RequiredProperty(ValidatorMode::Lax)]\n    public ?int $lives = null;\n}\n\n$parser = new DefaultTypeParser();\n\n$parser-\u003eregisterType('Cat', Cat::class);\n\n$catJson = \u003c\u003c\u003c'JSON'\n{\n    \"type\": \"Cat\",\n    \"lives\": 9,\n    \"@context\": \"https://www.w3.org/ns/activitystreams\",\n    \"id\": \"https://example.com/meow\",\n    \"name\": \"Meowth\"\n}\nJSON;\n\n$reconstructedCat = $parser-\u003eparseJson($catJson);\n\n// all the following are true\nassert($reconstructedCat instanceof Cat);\nassert($reconstructedCat-\u003elives === 9);\nassert($reconstructedCat-\u003ename === 'Meowth');\nassert($reconstructedCat-\u003eid === 'https://example.com/meow');\n\n```\n\n## Server\n\nIn addition to the ActivityPub object, there are also various helpers for implementing ActivityPub in your server.\nAll of them rely on the PSR abstractions, so it should be easy to use them with your favourite http client or\na framework of choice.\n\n### Request signing\n\nWhile not part of the ActivityPub protocol itself, you won't get far in the Fediverse without signing your request - almost no\nmainstream software accepts activities that are unsigned. For signing to work, each actor must be publicly accessible at the URL\npointed to in its ID and have a `publicKey` property with the public key defined.\n\nFor this reason, this package includes two things:\n\n1. A non-standard `publicKey` property available for all actors\n   - If you use the `Recommended` validator mode, this property is required for all actors\n2. An `ActorKeyGenerator` service which generates a private and public key-pair that should be stored in a database\n  for all actors.\n\nExample:\n\n```php\n\u003c?php\n\nuse Rikudou\\ActivityPub\\Dto\\KeyPair;\nuse Rikudou\\ActivityPub\\Dto\\PublicKey;\nuse Rikudou\\ActivityPub\\Server\\KeyGenerator\\OpenSslActorKeyGenerator;\nuse Rikudou\\ActivityPub\\Vocabulary\\Contract\\ActivityPubActor;\nuse Rikudou\\ActivityPub\\Vocabulary\\Extended\\Actor\\Person;\n\nfunction storePrivateKeyInDatabase(ActivityPubActor $actor, KeyPair $keyPair): void\n{\n    $privateKey = $keyPair-\u003eprivateKey;\n    // todo store it somewhere securely\n}\n\n// create a minimal valid Actor object\n$me = new Person();\n$me-\u003eid = 'https://example.com/person/1';\n$me-\u003einbox = 'https://example.com/person/1/inbox';\n$me-\u003eoutbox = 'https://example.com/person/1/outbox';\n$me-\u003efollowing = 'https://example.com/person/1/following';\n$me-\u003efollowers = 'https://example.com/person/1/followers';\n\n// instantiate a specific implementation of the KeyGenerator interface, there's currently only this one\n$keyGenerator = new OpenSslActorKeyGenerator();\n\n// generate the private and public key-pair\n// if you provide an instance of actor as the first parameter, it will automatically create\n$keyPair = $keyGenerator-\u003egenerate($me);\n\n// alternatively, you can assign the public key manually\n$keyPair = $keyGenerator-\u003egenerate();\n$me-\u003epublicKey = new PublicKey(\n    // adding #main-key is a convention, and it's not really important what exactly is there, important is that you can fetch\n    // the public key at that URL and that it's unique (thus it cannot be the same as the owner id)\n    id: $me-\u003eid . '#main-key',\n    owner: $me-\u003eid,\n    publicKeyPem: $keyPair-\u003epublicKey,\n);\n```\n\nNow you have an actor who can send signed requests!\n\nNow let's take a look at a hypothetical service that sends your requests:\n\n```php\n\u003c?php\n\nuse GuzzleHttp\\Psr7\\Utils;\nuse Psr\\Http\\Client\\ClientInterface;\nuse Psr\\Http\\Message\\RequestFactoryInterface;\nuse Rikudou\\ActivityPub\\Server\\Signing\\RequestSigner;\nuse Rikudou\\ActivityPub\\Vocabulary\\Contract\\ActivityPubActivity;\nuse Rikudou\\ActivityPub\\Vocabulary\\Contract\\ActivityPubActor;\n\nrequire_once __DIR__ . '/vendor/autoload.php';\n\nclass ActivitySender\n{\n    public function __construct(\n        private RequestFactoryInterface $requestFactory,\n        // this is the service used for signing requests, more specifically this is an interface implemented by RequestSignerAndValidator\n        private RequestSigner $requestSigner,\n        private ClientInterface $httpClient,\n    ) {\n    }\n\n    public function sendOutgoingActivity(\n        ActivityPubActor $actor,\n        #[SensitiveParameter]\n        string $actorPrivateKey,\n        ActivityPubActivity $activity,\n    ): void {\n        // you should send the activity to other fields as well, this is just for illustration\n        $recipients = $activity-\u003eto;\n\n        foreach ($recipients as $recipient) {\n            // for simplicity, let's assume this is always an actor, but it can also be a link\n            assert($recipient instanceof ActivityPubActor);\n\n            // you need to specify at least the request method,\n            $request = $this-\u003erequestFactory\n                -\u003ecreateRequest('POST', $recipient-\u003einbox)\n                -\u003ewithBody(\n                    Utils::streamFor(\n                        json_encode($activity),\n                    ),\n                )\n            ;\n\n            // now let's sign it!\n            $request = $this-\u003erequestSigner-\u003esignRequest(\n                $request,\n                $actor-\u003epublicKey-\u003eid,\n                $actorPrivateKey,\n            );\n\n            $response = $this-\u003ehttpClient-\u003esendRequest($request);\n            if ($response-\u003egetStatusCode() \u003c 200 || $response-\u003egetStatusCode() \u003e= 300) {\n                // todo handle bad responses in some way\n            }\n        }\n    }\n}\n```\n\n### Request validating\n\nOf course the reverse, validating an incoming request, is also possible!\n\n```php\n\u003c?php\n\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Psr\\Http\\Message\\ServerRequestInterface;\nuse Rikudou\\ActivityPub\\Server\\Signing\\RequestValidator;\n\nclass IncomingActivityHandler\n{\n    public function __construct(\n        // Just like before, an interface that's implemented by RequestSignerAndValidator\n        private RequestValidator $requestValidator,\n    ) {\n    }\n\n    public function handle(\n        ServerRequestInterface $incomingRequest,\n    ): ResponseInterface {\n        if ($incomingRequest-\u003egetMethod() !== 'POST') {\n            // todo return 405\n        }\n\n        if (!$this-\u003erequestValidator-\u003eisRequestValid($incomingRequest)) {\n            // todo return 403 or something\n        }\n\n        // todo handle the request\n    }\n}\n\n```\n\n### Fetching objects\n\nFetching remote objects can be handled using the `ObjectFetcher` and `WebFinger` services (implemented by `ActivityPubObjectFetcher`\nand `DefaultWebFinger` respectively).\n\n```php\n\u003c?php\n\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Rikudou\\ActivityPub\\Exception\\ActivityPubException;\nuse Rikudou\\ActivityPub\\Exception\\ResourceException;\nuse Rikudou\\ActivityPub\\Exception\\WebFingerException;\nuse Rikudou\\ActivityPub\\Server\\ObjectFetcher\\ObjectFetcher;\nuse Rikudou\\ActivityPub\\Server\\ObjectFetcher\\WebFinger;\nuse Rikudou\\ActivityPub\\Vocabulary\\Extended\\Actor\\Person;\nuse Rikudou\\ActivityPub\\Vocabulary\\Extended\\Object\\Article;\n\nclass SomeController\n{\n    public function __construct(\n        private WebFinger $webFinger,\n        private ObjectFetcher $objectFetcher,\n    ) {\n    }\n\n    public function someMethod(): ResponseInterface\n    {\n        $account = 'me@example.com';\n\n        try {\n            $webFingerResponse = $this-\u003ewebFinger-\u003efind($account);\n            $object = $this-\u003eobjectFetcher-\u003efetch($webFingerResponse);\n            assert($object instanceof Person);\n            // todo do something with the object\n        } catch (WebFingerException $e) {\n            // todo handle that something went wrong\n        } catch (ResourceException $e) {\n            // todo handle that something went wrong with fetching the person\n        } catch (ActivityPubException $e) {\n            // todo handle all other exceptions thrown by the package\n        }\n    }\n\n    public function anotherMethod(): ResponseInterface\n    {\n        $resource = 'https://example.com/posts/1';\n        $object = $this-\u003eobjectFetcher-\u003efetch($resource);\n        assert($object instanceof Article);\n    }\n}\n```\n\n## Symfony usage\n\nTo use this library in Symfony, simply configure the [PSR-7 Bridge](https://symfony.com/doc/current/components/psr7.html)\nand create the following file in `config/packages/activity_pub.yaml`:\n\n```yaml\nservices:\n  Rikudou\\ActivityPub\\Server\\KeyGenerator\\ActorKeyGenerator:\n    class: Rikudou\\ActivityPub\\Server\\KeyGenerator\\OpenSslActorKeyGenerator\n\n  Rikudou\\ActivityPub\\Server\\ObjectFetcher\\ObjectFetcher:\n    class: Rikudou\\ActivityPub\\Server\\ObjectFetcher\\ActivityPubObjectFetcher\n    arguments:\n      $typeParser: '@Rikudou\\ActivityPub\\Vocabulary\\Parser\\TypeParser'\n      $requestFactory: '@Psr\\Http\\Message\\RequestFactoryInterface'\n      $httpClient: '@Psr\\Http\\Client\\ClientInterface'\n\n  Rikudou\\ActivityPub\\Vocabulary\\Parser\\TypeParser:\n    class: Rikudou\\ActivityPub\\Vocabulary\\Parser\\DefaultTypeParser\n\n  Rikudou\\ActivityPub\\Server\\ObjectFetcher\\WebFinger:\n    class: Rikudou\\ActivityPub\\Server\\ObjectFetcher\\DefaultWebFinger\n    arguments:\n      $httpClient: '@Psr\\Http\\Client\\ClientInterface'\n      $requestFactory: '@Psr\\Http\\Message\\RequestFactoryInterface'\n\n  Rikudou\\ActivityPub\\Server\\Signing\\RequestSignerAndValidator:\n    arguments:\n      $requestFactory: '@Psr\\Http\\Message\\RequestFactoryInterface'\n      $httpClient: '@Psr\\Http\\Client\\ClientInterface'\n      $typeParser: '@Rikudou\\ActivityPub\\Vocabulary\\Parser\\TypeParser'\n      \n  Rikudou\\ActivityPub\\Server\\CollectionResolver\\CollectionResolver:\n    class: Rikudou\\ActivityPub\\Server\\CollectionResolver\\DefaultCollectionResolver\n    arguments:\n      $objectFetcher: '@Rikudou\\ActivityPub\\Server\\ObjectFetcher\\ObjectFetcher'\n\n  Rikudou\\ActivityPub\\Server\\Signing\\RequestSigner: '@Rikudou\\ActivityPub\\Server\\Signing\\RequestSignerAndValidator'\n  Rikudou\\ActivityPub\\Server\\Signing\\RequestValidator: '@Rikudou\\ActivityPub\\Server\\Signing\\RequestSignerAndValidator'\n```\n\nIf you also want to use the built-in activity sender, you need to create a service implementing `Rikudou\\ActivityPub\\Server\\Abstraction\\LocalActorResolver`\nand add the following to the above yaml:\n\n```yaml\n  Rikudou\\ActivityPub\\Server\\ActivitySender\\ActivitySender:\n    class: Rikudou\\ActivityPub\\Server\\ActivitySender\\DefaultActivitySender\n    arguments:\n      $objectFetcher: '@Rikudou\\ActivityPub\\Server\\ObjectFetcher\\ObjectFetcher'\n      $collectionResolver: '@Rikudou\\ActivityPub\\Server\\CollectionResolver\\CollectionResolver'\n      $requestFactory: '@Psr\\Http\\Message\\RequestFactoryInterface'\n      $httpClient: '@Psr\\Http\\Client\\ClientInterface'\n      $streamFactory: '@Psr\\Http\\Message\\StreamFactoryInterface'\n      $requestSigner: '@Rikudou\\ActivityPub\\Server\\Signing\\RequestSigner'\n      $localActorResolver: '@Rikudou\\ActivityPub\\Server\\Abstraction\\LocalActorResolver'\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frikudousage%2Factivitypub","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frikudousage%2Factivitypub","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frikudousage%2Factivitypub/lists"}