From 4266d4156ab29e723b277fe1afab84992e11efb7 Mon Sep 17 00:00:00 2001 From: christian Date: Fri, 24 Mar 2023 12:05:39 +0100 Subject: [PATCH] main: Initial commit --- .gitignore | 11 ++++ CHANGELOG.md | 5 ++ README.md | 88 +++++++++++++++++++++++++++++++ composer.json | 39 ++++++++++++++ config/posthog.php | 7 +++ src/Facades/Posthog.php | 13 +++++ src/Jobs/PosthogBaseJob.php | 21 ++++++++ src/Jobs/PosthogCaptureJob.php | 37 +++++++++++++ src/Jobs/PosthogIdentifyJob.php | 36 +++++++++++++ src/LaravelPosthog.php | 46 ++++++++++++++++ src/Listeners/PosthogListener.php | 65 +++++++++++++++++++++++ src/PosthogServiceProvider.php | 22 ++++++++ 12 files changed, 390 insertions(+) create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 README.md create mode 100644 composer.json create mode 100644 config/posthog.php create mode 100644 src/Facades/Posthog.php create mode 100644 src/Jobs/PosthogBaseJob.php create mode 100644 src/Jobs/PosthogCaptureJob.php create mode 100644 src/Jobs/PosthogIdentifyJob.php create mode 100644 src/LaravelPosthog.php create mode 100644 src/Listeners/PosthogListener.php create mode 100644 src/PosthogServiceProvider.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..35d5cd5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.idea +.phpunit.cache +build +composer.lock +coverage +docs +node_modules +phpstan.neon +phpunit.xml +testbench.yaml +vendor \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4b74abf --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## 24-03-2023 + +Initial release \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2116618 --- /dev/null +++ b/README.md @@ -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) diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..7e39e29 --- /dev/null +++ b/composer.json @@ -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 +} diff --git a/config/posthog.php b/config/posthog.php new file mode 100644 index 0000000..39ce75b --- /dev/null +++ b/config/posthog.php @@ -0,0 +1,7 @@ + env('POSTHOG_ENABLED', true), + 'key' => env('POSTHOG_KEY', ''), + 'host' => env('POSTHOG_HOST', 'https://app.posthog.com'), +]; diff --git a/src/Facades/Posthog.php b/src/Facades/Posthog.php new file mode 100644 index 0000000..a367de9 --- /dev/null +++ b/src/Facades/Posthog.php @@ -0,0 +1,13 @@ + config('posthog.host')] + ); + } catch (Exception $e) { + Log::error('Posthog initialization failed: ' . $e->getMessage()); + } + } +} \ No newline at end of file diff --git a/src/Jobs/PosthogCaptureJob.php b/src/Jobs/PosthogCaptureJob.php new file mode 100644 index 0000000..56cb131 --- /dev/null +++ b/src/Jobs/PosthogCaptureJob.php @@ -0,0 +1,37 @@ +init(); + + try { + Posthog::capture([ + 'distinctId' => $this->sessionId, + 'event' => $this->event, + 'properties' => $this->properties, + ]); + } catch (Exception $e) { + Log::info('Posthog capture call failed:' . $e->getMessage()); + } + } + +} \ No newline at end of file diff --git a/src/Jobs/PosthogIdentifyJob.php b/src/Jobs/PosthogIdentifyJob.php new file mode 100644 index 0000000..6ec3c58 --- /dev/null +++ b/src/Jobs/PosthogIdentifyJob.php @@ -0,0 +1,36 @@ +init(); + + try { + Posthog::identify([ + 'distinctId' => $this->sessionId, + 'properties' => ['email' => $this->email] + $this->properties, + ]); + } catch (Exception $e) { + Log::info('Posthog identify call failed:' . $e->getMessage()); + } + } + +} \ No newline at end of file diff --git a/src/LaravelPosthog.php b/src/LaravelPosthog.php new file mode 100644 index 0000000..00b4d97 --- /dev/null +++ b/src/LaravelPosthog.php @@ -0,0 +1,46 @@ +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'); + } + } + +} \ No newline at end of file diff --git a/src/Listeners/PosthogListener.php b/src/Listeners/PosthogListener.php new file mode 100644 index 0000000..47f7271 --- /dev/null +++ b/src/Listeners/PosthogListener.php @@ -0,0 +1,65 @@ +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 + ); + + } +} \ No newline at end of file diff --git a/src/PosthogServiceProvider.php b/src/PosthogServiceProvider.php new file mode 100644 index 0000000..b0da9bd --- /dev/null +++ b/src/PosthogServiceProvider.php @@ -0,0 +1,22 @@ +app->bind('LaravelPosthog', function ($app) { + return new LaravelPosthog(); + }); + } + + public function boot(): void + { + $this->publishes([ + __DIR__ . '/../config/posthog.php' => config_path('posthog.php'), + ]); + } +}