Skip to content

Commit

Permalink
main: Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
christian committed Mar 24, 2023
0 parents commit 4266d41
Show file tree
Hide file tree
Showing 12 changed files with 390 additions and 0 deletions.
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.idea
.phpunit.cache
build
composer.lock
coverage
docs
node_modules
phpstan.neon
phpunit.xml
testbench.yaml
vendor
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Changelog

## 24-03-2023

Initial release
88 changes: 88 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Laravel Posthog implementation

---
This package provides a simple integration of Posthog in Laravel applications.

The small package covers both Identify as Capture (events) requests which can be triggered manual or automatically using an Event Listener.

This package uses the [PostHog / posthog-php](https://github.com/PostHog/posthog-php) package. For more information about Posthog, check their [documentation](https://posthog.com/docs).

## Installation

You can install the package via composer:

```bash
composer require qodenl/laravel-posthog
```

You can publish the config file with:

```bash
php artisan vendor:publish --provider="QodeNL\LaravelPosthog\PosthogServiceProvider"
```

After publishing the content, set your API key and Host in your .env file:

```bash
POSTHOG_KEY=
POSTHOG_HOST=https://posthog.com
POSTHOG_ENABLED=true
```

Make sure you copy the correct host from Posthog.

Posthog is enabled by default, but you can disable it with the POSTHOG_ENABLED env variable.

Make sure to disable Posthog for local/testing environments.

## Usage

### Manual events

```php
use QodeNL\LaravelPosthog\Facades\Posthog;

Posthog::capture('event name', ['property' => 'value']);
```

### Automatic events

You can add the `PosthogListener::class` listener to your `EventServiceProvider`. The package will create an capture automatically when the event happens.

By default, all `fillable` attributes from a model (available in the event) will be sent to Posthog as properties.

You can specify which attributes you want to send to Posthog by adding a `PosthogAttributes` property to your Model.

```php
public $posthogAttributes = [
'first_name',
'last_name',
];
```

Attributes in the `hidden` property will always be ignored.

### Identify

Events will be sent to Posthog with a unique ID for anonymous users. When the user is recognized (usually on log in),
you should trigger the `identify` method to link the unique ID to the user.

You can pass additional information about the user to be stored in his profile.

```php
use QodeNL\LaravelPosthog\Facades\Posthog;

Posthog::identify('email@user.com', ['first_name' => 'John', 'last_name' => 'Doe']);
```

### Queue / jobs

All above actions will be executed by jobs. Be sure you've enabled and configured [queues](https://laravel.com/docs/10.x/queues) for your applications.

## Changelog

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

## Credits

- Christian Schoenmakers (Qode BV - Netherlands) (https://github.com/christianschoenmakers)
39 changes: 39 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "qodenl/laravel-posthog",
"description": "Laravel implementation for Posthog",
"autoload": {
"psr-4": {
"QodeNL\\LaravelPosthog\\": "src/"
}
},
"authors": [
{
"name": "Christian Schoenmakers",
"email": "info@qode.io"
}
],
"keywords": [
"posthog",
"laravel"
],
"require": {
"php": "^8.0",
"illuminate/support": "^9.0|^10.0",
"posthog/posthog-php": "^3.0"
},
"require-dev": {
"roave/security-advisories": "dev-latest"
},
"extra": {
"laravel": {
"providers": [
"QodeNL\\LaravelPosthog\\PosthogServiceProvider"
],
"aliases": {
"Posthog": "QodeNL\\LaravelPosthog\\Facades\\Posthog"
}
}
},
"minimum-stability": "dev",
"prefer-stable": true
}
7 changes: 7 additions & 0 deletions config/posthog.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

return [
'enabled' => env('POSTHOG_ENABLED', true),
'key' => env('POSTHOG_KEY', ''),
'host' => env('POSTHOG_HOST', 'https://app.posthog.com'),
];
13 changes: 13 additions & 0 deletions src/Facades/Posthog.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace QodeNL\LaravelPosthog\Facades;

use Illuminate\Support\Facades\Facade;

class Posthog extends Facade
{
protected static function getFacadeAccessor()
{
return 'LaravelPosthog';
}
}
21 changes: 21 additions & 0 deletions src/Jobs/PosthogBaseJob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace QodeNL\LaravelPosthog\Jobs;

use Exception;
use Illuminate\Support\Facades\Log;
use PostHog\PostHog;

class PosthogBaseJob
{
public function init(): void
{
try {
PostHog::init(config('posthog.key'),
['host' => config('posthog.host')]
);
} catch (Exception $e) {
Log::error('Posthog initialization failed: ' . $e->getMessage());
}
}
}
37 changes: 37 additions & 0 deletions src/Jobs/PosthogCaptureJob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace QodeNL\LaravelPosthog\Jobs;

use Exception;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Bus\Queueable;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use PostHog\PostHog;

class PosthogCaptureJob extends PosthogBaseJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

public function __construct(private string $sessionId, private string $event, private array $properties = [])
{
}

public function handle(): void
{
$this->init();

try {
Posthog::capture([
'distinctId' => $this->sessionId,
'event' => $this->event,
'properties' => $this->properties,
]);
} catch (Exception $e) {
Log::info('Posthog capture call failed:' . $e->getMessage());
}
}

}
36 changes: 36 additions & 0 deletions src/Jobs/PosthogIdentifyJob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace QodeNL\LaravelPosthog\Jobs;

use Exception;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Bus\Queueable;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use PostHog\PostHog;

class PosthogIdentifyJob extends PosthogBaseJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

public function __construct(private string $sessionId, private string $email, private array $properties = [])
{
}

public function handle(): void
{
$this->init();

try {
Posthog::identify([
'distinctId' => $this->sessionId,
'properties' => ['email' => $this->email] + $this->properties,
]);
} catch (Exception $e) {
Log::info('Posthog identify call failed:' . $e->getMessage());
}
}

}
46 changes: 46 additions & 0 deletions src/LaravelPosthog.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace QodeNL\LaravelPosthog;

use Log;
use QodeNL\LaravelPosthog\Jobs\PosthogCaptureJob;
use QodeNL\LaravelPosthog\Jobs\PosthogIdentifyJob;

class LaravelPosthog
{

protected string $sessionId;

public function __construct()
{
$this->sessionId = sha1(session()->getId());
}

private function posthogEnabled(): bool
{
if (!config('posthog.enabled')) {
return false;
}

return true;
}

public function identify(string $email, array $properties = []): void
{
if ($this->posthogEnabled()) {
PosthogIdentifyJob::dispatch($this->sessionId, $email, $properties);
} else {
Log::debug('PosthogIdentifyJob not dispatched because posthog is disabled');
}
}

public function capture(string $event, array $properties = []): void
{
if ($this->posthogEnabled()) {
PosthogCaptureJob::dispatch($this->sessionId, $event, $properties);
} else {
Log::debug('PosthogCaptureJob not dispatched because posthog is disabled');
}
}

}
65 changes: 65 additions & 0 deletions src/Listeners/PosthogListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

namespace QodeNL\LaravelPosthog\Listeners;

use Illuminate\Database\Eloquent\Model;
use QodeNL\LaravelPosthog\LaravelPosthog;
use ReflectionClass;
use ReflectionNamedType;

class PosthogListener
{
public function handle($event): void
{
$eventParameters = [];

$reflectionClass = new ReflectionClass(get_class($event));
$reflectionClassProps = $reflectionClass->getConstructor()->getParameters();

if (is_array($reflectionClassProps) && count($reflectionClassProps) > 0) {
foreach ($reflectionClassProps as $property) {

$parameterName = $property->getName();

$classType = $property->getType();
if (!$classType instanceof ReflectionNamedType) {
continue;
}

$className = $classType->getName();

if (!$className) {
continue;
}
$class = new $className();

if (!$class || !is_subclass_of($class, Model::class)) {
continue;
}

$modelAttributes = collect();
if (property_exists($class, 'posthogAttributes')) {
$modelAttributes = collect($class->posthogAttributes);
} elseif (method_exists($class, 'getFillable')) {
$modelAttributes = collect($class->getFillable());
}

if (method_exists($class, 'getHidden')) {
$hidden = collect($class->getHidden());
$modelAttributes = $modelAttributes->diff($hidden);
}

if ($modelAttributes->count() > 0) {
$eventParameters[$parameterName] = $event->$parameterName->only($modelAttributes->toArray()) ?? [];
}
}
}

$posthog = new LaravelPosthog();
$posthog->capture(
get_class($event),
$eventParameters
);

}
}
Loading

0 comments on commit 4266d41

Please sign in to comment.