From 61d3f2371513ada9907f0c3c90bb60de30e3d0ed Mon Sep 17 00:00:00 2001 From: Sam Jones Date: Wed, 26 Jul 2023 19:30:23 +0100 Subject: [PATCH] Add Notification channel (#13) --- README.md | 47 ++++++++++++++++++- phpstan.neon.dist | 2 + src/Contracts/CanNotifyViaSegment.php | 8 ++++ src/Contracts/SegmentServiceContract.php | 9 ++++ ...eCannotBeIdentifiedForSegmentException.php | 10 ++++ src/Facades/Fakes/SegmentFake.php | 20 ++++++++ src/Notifications/SegmentChannel.php | 20 ++++++++ tests/Stubs/SegmentTestNotification.php | 36 ++++++++++++++ tests/Stubs/SegmentTestUser.php | 3 ++ tests/Unit/SegmentNotificationTest.php | 33 +++++++++++++ 10 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 src/Contracts/CanNotifyViaSegment.php create mode 100644 src/Exceptions/NotifiableCannotBeIdentifiedForSegmentException.php create mode 100644 src/Notifications/SegmentChannel.php create mode 100644 tests/Stubs/SegmentTestNotification.php create mode 100644 tests/Unit/SegmentNotificationTest.php diff --git a/README.md b/README.md index b0bbf0d..de60f01 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,51 @@ Segment::identify([ ]); ``` +### Laravel Notifications +This package includes an out-of-the-box notification channel, to allow you to use Laravel's built-in notification +feature. To send Segment events to users as notifications, generate your notification as normal; + +``` +php artisan make:notification UserSubscribed +``` + +You must ensure your notification implements the `CanNotifyViaSegment` interface, and add the required `toSegment` +method. Then you can configure the `via` method to include the `SegmentChannel` class. + +You can then adjust the `toSegment` method to return the event you'd like. + +``` +use Illuminate\Notifications\Notification; +use SlashEquip\LaravelSegment\Contracts\CanBeIdentifiedForSegment; +use SlashEquip\LaravelSegment\Contracts\CanBeSentToSegment; +use SlashEquip\LaravelSegment\Contracts\CanNotifyViaSegment; +use SlashEquip\LaravelSegment\Notifications\SegmentChannel; +use SlashEquip\LaravelSegment\SimpleSegmentEvent; + +class UserSubscribed extends Notification implements CanNotifyViaSegment +{ + use Notifiable; + + public function __construct( + ) { + } + + public function via(object $notifiable): array + { + return [SegmentChannel::class]; + } + + public function toSegment(CanBeIdentifiedForSegment $notifiable): CanBeSentToSegment + { + return new SimpleSegmentEvent( + $notifiable, + 'User Subscribed', + ['some' => 'thing'], + ); + } +} +``` + ## Misc ### Deferring @@ -167,7 +212,7 @@ through-out the request or process and then send them in batch after your applic happens during the Laravel termination. ### Safe mode -By default safe-mode is turned on. When safe-mode is active it will swallow any exceptions thrown when making the HTTP +By default, safe-mode is turned on. When safe-mode is active it will swallow any exceptions thrown when making the HTTP request to Segmenta and report them automatically to the exception handler, allow your app to continue running. When disabled then the exception will be thrown. diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 5dc0732..c4ff6f5 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -8,3 +8,5 @@ parameters: # Level 9 is the highest level level: 6 + + checkGenericClassInNonGenericObjectType: false diff --git a/src/Contracts/CanNotifyViaSegment.php b/src/Contracts/CanNotifyViaSegment.php new file mode 100644 index 0000000..f41f163 --- /dev/null +++ b/src/Contracts/CanNotifyViaSegment.php @@ -0,0 +1,8 @@ + $globalContext + */ public function setGlobalContext(array $globalContext): void; + /** + * @param array $eventData + */ public function track(string $event, array $eventData = null): void; + /** + * @param array $identifyData + */ public function identify(array $identifyData = null): void; public function forUser(CanBeIdentifiedForSegment $user): PendingUserSegment; diff --git a/src/Exceptions/NotifiableCannotBeIdentifiedForSegmentException.php b/src/Exceptions/NotifiableCannotBeIdentifiedForSegmentException.php new file mode 100644 index 0000000..a3a1c40 --- /dev/null +++ b/src/Exceptions/NotifiableCannotBeIdentifiedForSegmentException.php @@ -0,0 +1,10 @@ + */ private ?array $context = []; + /** @var array */ private array $events = []; + /** @var array */ private array $identities = []; public function setGlobalUser(CanBeIdentifiedForSegment $globalUser): void @@ -27,16 +30,25 @@ public function setGlobalUser(CanBeIdentifiedForSegment $globalUser): void $this->user = $globalUser; } + /** + * @param array $globalContext + */ public function setGlobalContext(array $globalContext): void { $this->context = $globalContext; } + /** + * @param array $identifyData + */ public function identify(?array $identifyData = []): void { $this->identities[] = new SimpleSegmentIdentify($this->user, $identifyData); } + /** + * @param array $eventData + */ public function track(string $event, array $eventData = null): void { $this->events[] = new SimpleSegmentEvent($this->user, $event, $eventData); @@ -158,6 +170,14 @@ public function assertNothingTracked(): void PHPUnit::assertEmpty($events, $events->count().' events were found unexpectedly.'); } + /** + * @return array + */ + public function getContext(): ?array + { + return $this->context; + } + private function identities(Closure $callback = null): Collection { $identities = collect($this->identities); diff --git a/src/Notifications/SegmentChannel.php b/src/Notifications/SegmentChannel.php new file mode 100644 index 0000000..fd49831 --- /dev/null +++ b/src/Notifications/SegmentChannel.php @@ -0,0 +1,20 @@ +toSegment($notifiable)); + } +} diff --git a/tests/Stubs/SegmentTestNotification.php b/tests/Stubs/SegmentTestNotification.php new file mode 100644 index 0000000..dbc4750 --- /dev/null +++ b/tests/Stubs/SegmentTestNotification.php @@ -0,0 +1,36 @@ +snake() + ->replace('_', ' ') + ->title(), + ['some' => 'thing'], + ); + } +} diff --git a/tests/Stubs/SegmentTestUser.php b/tests/Stubs/SegmentTestUser.php index d90d12f..12cd295 100644 --- a/tests/Stubs/SegmentTestUser.php +++ b/tests/Stubs/SegmentTestUser.php @@ -2,10 +2,13 @@ namespace SlashEquip\LaravelSegment\Tests\Stubs; +use Illuminate\Notifications\Notifiable; use SlashEquip\LaravelSegment\Contracts\CanBeIdentifiedForSegment; class SegmentTestUser implements CanBeIdentifiedForSegment { + use Notifiable; + public function __construct( private string $id ) { diff --git a/tests/Unit/SegmentNotificationTest.php b/tests/Unit/SegmentNotificationTest.php new file mode 100644 index 0000000..e35f0f9 --- /dev/null +++ b/tests/Unit/SegmentNotificationTest.php @@ -0,0 +1,33 @@ +spy(SegmentServiceContract::class); + + // When we notify the entity + $notifiable->notify(new SegmentTestNotification(987654)); + + // Then the service was called appropriately + $service->shouldHaveReceived('push') + ->with(Mockery::on(function ($arg) { + if (! $arg instanceof SimpleSegmentEvent) { + return false; + } + + $payload = $arg->toSegment(); + + return $payload->user->getSegmentIdentifier() === '123456' + && $payload->event === 'Segment Test Notification' + && count($payload->data) === 1 + && $payload->data['some'] === 'thing'; + })) + ->once(); +});