Skip to content

Commit

Permalink
Add Notification channel (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
slashequip authored Jul 26, 2023
1 parent 8951b8c commit 61d3f23
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 1 deletion.
47 changes: 46 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.

Expand Down
2 changes: 2 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ parameters:

# Level 9 is the highest level
level: 6

checkGenericClassInNonGenericObjectType: false
8 changes: 8 additions & 0 deletions src/Contracts/CanNotifyViaSegment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace SlashEquip\LaravelSegment\Contracts;

interface CanNotifyViaSegment
{
public function toSegment(CanBeIdentifiedForSegment $notifiable): CanBeSentToSegment;
}
9 changes: 9 additions & 0 deletions src/Contracts/SegmentServiceContract.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,19 @@ interface SegmentServiceContract
{
public function setGlobalUser(CanBeIdentifiedForSegment $globalUser): void;

/**
* @param array<string, mixed> $globalContext
*/
public function setGlobalContext(array $globalContext): void;

/**
* @param array<string, mixed> $eventData
*/
public function track(string $event, array $eventData = null): void;

/**
* @param array<string, mixed> $identifyData
*/
public function identify(array $identifyData = null): void;

public function forUser(CanBeIdentifiedForSegment $user): PendingUserSegment;
Expand Down
10 changes: 10 additions & 0 deletions src/Exceptions/NotifiableCannotBeIdentifiedForSegmentException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace SlashEquip\LaravelSegment\Exceptions;

use RuntimeException;

class NotifiableCannotBeIdentifiedForSegmentException extends RuntimeException
{
//
}
20 changes: 20 additions & 0 deletions src/Facades/Fakes/SegmentFake.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,39 @@ class SegmentFake implements SegmentServiceContract
{
private CanBeIdentifiedForSegment $user;

/** @var array<string, mixed> */
private ?array $context = [];

/** @var array<int, SimpleSegmentEvent> */
private array $events = [];

/** @var array<int, SimpleSegmentIdentify> */
private array $identities = [];

public function setGlobalUser(CanBeIdentifiedForSegment $globalUser): void
{
$this->user = $globalUser;
}

/**
* @param array<string, mixed> $globalContext
*/
public function setGlobalContext(array $globalContext): void
{
$this->context = $globalContext;
}

/**
* @param array<string, mixed> $identifyData
*/
public function identify(?array $identifyData = []): void
{
$this->identities[] = new SimpleSegmentIdentify($this->user, $identifyData);
}

/**
* @param array<string, mixed> $eventData
*/
public function track(string $event, array $eventData = null): void
{
$this->events[] = new SimpleSegmentEvent($this->user, $event, $eventData);
Expand Down Expand Up @@ -158,6 +170,14 @@ public function assertNothingTracked(): void
PHPUnit::assertEmpty($events, $events->count().' events were found unexpectedly.');
}

/**
* @return array<string, mixed>
*/
public function getContext(): ?array
{
return $this->context;
}

private function identities(Closure $callback = null): Collection
{
$identities = collect($this->identities);
Expand Down
20 changes: 20 additions & 0 deletions src/Notifications/SegmentChannel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace SlashEquip\LaravelSegment\Notifications;

use SlashEquip\LaravelSegment\Contracts\CanBeIdentifiedForSegment;
use SlashEquip\LaravelSegment\Contracts\CanNotifyViaSegment;
use SlashEquip\LaravelSegment\Exceptions\NotifiableCannotBeIdentifiedForSegmentException;
use SlashEquip\LaravelSegment\Facades\Segment;

class SegmentChannel
{
public function send(object $notifiable, CanNotifyViaSegment $notification): void
{
if (! $notifiable instanceof CanBeIdentifiedForSegment) {
throw new NotifiableCannotBeIdentifiedForSegmentException();
}

Segment::push($notification->toSegment($notifiable));
}
}
36 changes: 36 additions & 0 deletions tests/Stubs/SegmentTestNotification.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace SlashEquip\LaravelSegment\Tests\Stubs;

use Illuminate\Notifications\Notification;
use Illuminate\Support\Str;
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 SegmentTestNotification extends Notification implements CanNotifyViaSegment
{
public function __construct(
private int $number
) {
}

public function via(object $notifiable): array
{
return [SegmentChannel::class];
}

public function toSegment(CanBeIdentifiedForSegment $notifiable): CanBeSentToSegment
{
return new SimpleSegmentEvent(
$notifiable,
Str::of(class_basename(static::class))
->snake()
->replace('_', ' ')
->title(),
['some' => 'thing'],
);
}
}
3 changes: 3 additions & 0 deletions tests/Stubs/SegmentTestUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
) {
Expand Down
33 changes: 33 additions & 0 deletions tests/Unit/SegmentNotificationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

use SlashEquip\LaravelSegment\Contracts\SegmentServiceContract;
use SlashEquip\LaravelSegment\SimpleSegmentEvent;
use SlashEquip\LaravelSegment\Tests\Stubs\SegmentTestNotification;
use SlashEquip\LaravelSegment\Tests\Stubs\SegmentTestUser;

it('can send a notification to a notifiable entity', function () {
// Given we have a notifiable entity
$notifiable = new SegmentTestUser('123456');

// And we are spying on the service
$service = test()->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();
});

0 comments on commit 61d3f23

Please sign in to comment.