Chapter 130 · Integration Laravel
Subchapter 130.5
references/EXAMPLE.mdMarkdown80 KBView on GitHub
Repository: https://github.com/PostHog/context-mill Path: basics/laravel
A Laravel application demonstrating PostHog integration for analytics, feature flags, and error tracking using Livewire for reactive UI components.
Note: This is a minimal implementation demonstrating PostHog integration. For a production application, you would need to install Laravel via Composer and set up additional dependencies.
Install dependencies:
composer installSet up environment:
cp .env.example .env
# Edit .env with your PostHog project tokenConfigure PostHog in .env:
POSTHOG_PROJECT_TOKEN=your_posthog_project_token
POSTHOG_HOST=https://us.i.posthog.com
POSTHOG_DISABLED=falseGenerate application key:
php artisan key:generateCreate database and run migrations:
touch database/database.sqlite
php artisan migrate --seedStart the development server:
php artisan serveOpen http://localhost:8000 (opens in a new tab) and either:
admin@example.com / adminThe PostHogService class (app/Services/PostHogService.php) wraps the PostHog PHP SDK and provides:
| Method | Description |
|---|---|
identify($distinctId, $properties) | Identify a user with properties |
capture($distinctId, $event, $properties) | Capture custom events |
captureException($exception, $distinctId) | Capture exceptions with stack traces |
isFeatureEnabled($key, $distinctId, $properties) | Check feature flag status |
getFeatureFlagPayload($key, $distinctId) | Get feature flag payload |
All methods check config('posthog.disabled') and return early if PostHog is disabled.
New users are identified and tracked on signup:
$posthog->identify($user->email, $user->getPostHogProperties());
$posthog->capture($user->email, 'user_signed_up', [
'signup_method' => 'form',
]);Users are identified on login with their properties:
$posthog->identify($user->email, $user->getPostHogProperties());
$posthog->capture($user->email, 'user_logged_in', [
'login_method' => 'password',
]);Logout events are tracked:
$posthog->capture($user->email, 'user_logged_out');Dashboard and profile views are tracked (app/Http/Livewire/Dashboard.php, app/Http/Livewire/Profile.php):
$posthog->capture($user->email, 'dashboard_viewed', [
'is_staff' => $user->is_staff,
]);
$posthog->capture($user->email, 'profile_viewed');The burrito tracker demonstrates custom event capture:
$posthog->identify($user->email, $user->getPostHogProperties());
$posthog->capture($user->email, 'burrito_considered', [
'total_considerations' => $this->burritoCount,
]);The dashboard demonstrates feature flag checking:
$this->showNewFeature = $posthog->isFeatureEnabled(
'new-dashboard-feature',
$user->email,
$user->getPostHogProperties()
) ?? false;
$this->featureConfig = $posthog->getFeatureFlagPayload(
'new-dashboard-feature',
$user->email
);Manual exception capture is demonstrated in multiple places:
Livewire Components (app/Http/Livewire/Dashboard.php, app/Http/Livewire/Profile.php):
try {
throw new \Exception('This is a test error for PostHog tracking');
} catch (\Exception $e) {
$errorId = $posthog->captureException($e, $user->email);
$this->successMessage = "Error captured in PostHog! Error ID: {$errorId}";
}API Endpoint (app/Http/Controllers/Api/ErrorTestController.php):
try {
throw new \Exception('Test exception from critical operation');
} catch (\Throwable $e) {
if ($shouldCapture) {
$posthog->identify($user->email, $user->getPostHogProperties());
$eventId = $posthog->captureException($e, $user->email);
return response()->json([
'error' => 'Operation failed',
'error_id' => $eventId,
'message' => "Error captured in PostHog. Reference ID: {$eventId}",
], 500);
}
}The /api/test-error endpoint demonstrates manual exception capture. Use ?capture=true to capture in PostHog, or ?capture=false to skip tracking.
| Route | Component | PostHog Events |
|---|---|---|
/ | Login | user_logged_in |
/register | Register | user_signed_up |
/dashboard | Dashboard | dashboard_viewed, feature flag checks |
/burrito | BurritoTracker | burrito_considered |
/profile | Profile | profile_viewed |
/logout | (route) | user_logged_out |
basics/laravel/
├── app/
│ ├── Http/
│ │ ├── Controllers/
│ │ │ └── Api/
│ │ │ ├── BurritoController.php # Burrito API endpoint
│ │ │ └── ErrorTestController.php # Error testing endpoint
│ │ └── Livewire/
│ │ ├── Auth/
│ │ │ ├── Login.php # Login component
│ │ │ └── Register.php # Registration component
│ │ ├── BurritoTracker.php # Burrito tracker component
│ │ ├── Dashboard.php # Dashboard with feature flags
│ │ └── Profile.php # User profile component
│ ├── Models/
│ │ └── User.php # User model with PostHog properties
│ └── Services/
│ └── PostHogService.php # PostHog wrapper service
├── database/
│ ├── migrations/ # Database migrations
│ └── seeders/
│ └── DatabaseSeeder.php # Seeds admin user
├── resources/
│ └── views/
│ ├── components/
│ │ └── layouts/
│ │ ├── app.blade.php # Authenticated layout
│ │ └── guest.blade.php # Guest layout
│ ├── errors/
│ │ ├── 404.blade.php # Not found page
│ │ └── 500.blade.php # Server error page
│ └── livewire/
│ ├── auth/
│ │ ├── login.blade.php # Login form
│ │ └── register.blade.php # Registration form
│ ├── burrito-tracker.blade.php # Burrito tracker UI
│ ├── dashboard.blade.php # Dashboard UI
│ └── profile.blade.php # Profile UI
├── routes/
│ ├── web.php # Web routes (auth, pages)
│ └── api.php # API routes
└── config/
└── posthog.php # PostHog configuration# Start development server
php artisan serve
# Run migrations
php artisan migrate
# Seed database
php artisan migrate:fresh --seed
# Clear caches
php artisan optimize:clearAPP_NAME="PostHog Laravel Example"
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost:8000
DB_CONNECTION=sqlite
# DB_DATABASE will use default database/database.sqlite
CACHE_DRIVER=file
CACHE_STORE=file
SESSION_DRIVER=file
SESSION_LIFETIME=120
POSTHOG_PROJECT_TOKEN=your_posthog_project_token_here
POSTHOG_HOST=https://us.i.posthog.com
POSTHOG_DISABLED=false
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Services\PostHogService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class BurritoController extends Controller
{
public function consider(Request $request, PostHogService $posthog): JsonResponse
{
$user = Auth::user();
// Increment session counter
$burritoCount = session('burrito_count', 0) + 1;
session(['burrito_count' => $burritoCount]);
// PostHog: Track event
$posthog->identify($user->email, $user->getPostHogProperties());
$posthog->capture($user->email, 'burrito_considered', [
'total_considerations' => $burritoCount,
]);
return response()->json([
'success' => true,
'count' => $burritoCount,
]);
}
}
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Services\PostHogService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class ErrorTestController extends Controller
{
public function test(Request $request, PostHogService $posthog): JsonResponse
{
$shouldCapture = $request->query('capture', 'true') === 'true';
$user = Auth::user();
try {
throw new \Exception('Test exception from critical operation');
} catch (\Throwable $e) {
if ($shouldCapture) {
// Capture in PostHog
$posthog->identify($user->email, $user->getPostHogProperties());
$eventId = $posthog->captureException($e, $user->email);
return response()->json([
'error' => 'Operation failed',
'error_id' => $eventId,
'message' => "Error captured in PostHog. Reference ID: {$eventId}",
], 500);
}
return response()->json([
'error' => $e->getMessage(),
], 500);
}
}
}
<?php
namespace App\Http\Controllers;
abstract class Controller
{
//
}
<?php
namespace App\Http\Livewire\Auth;
use App\Services\PostHogService;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
class Login extends Component
{
public string $email = '';
public string $password = '';
public bool $remember = false;
protected $rules = [
'email' => 'required|email',
'password' => 'required',
];
public function login(PostHogService $posthog)
{
$this->validate();
if (Auth::attempt(['email' => $this->email, 'password' => $this->password], $this->remember)) {
$user = Auth::user();
// PostHog: Identify and track login
$posthog->identify($user->email, $user->getPostHogProperties());
$posthog->capture($user->email, 'user_logged_in', [
'login_method' => 'password',
]);
session()->regenerate();
return redirect()->intended(route('dashboard'));
}
$this->addError('email', 'Invalid credentials');
}
public function render()
{
return view('livewire.auth.login')
->layout('components.layouts.guest');
}
}
<?php
namespace App\Http\Livewire\Auth;
use App\Models\User;
use App\Services\PostHogService;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
class Register extends Component
{
public string $email = '';
public string $password = '';
public string $password_confirmation = '';
protected $rules = [
'email' => 'required|email|unique:users,email',
'password' => 'required|min:6|confirmed',
];
public function register(PostHogService $posthog)
{
$validated = $this->validate();
$user = User::create([
'email' => $validated['email'],
'password' => bcrypt($validated['password']),
'is_staff' => false,
]);
// PostHog: Identify new user and track signup
$posthog->identify($user->email, $user->getPostHogProperties());
$posthog->capture($user->email, 'user_signed_up', [
'signup_method' => 'form',
]);
Auth::login($user);
session()->flash('success', 'Account created successfully!');
return redirect()->route('dashboard');
}
public function render()
{
return view('livewire.auth.register')
->layout('components.layouts.guest');
}
}
<?php
namespace App\Http\Livewire;
use App\Services\PostHogService;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
class BurritoTracker extends Component
{
public int $burritoCount = 0;
public function mount()
{
$this->burritoCount = session('burrito_count', 0);
}
public function considerBurrito(PostHogService $posthog)
{
$this->burritoCount++;
session(['burrito_count' => $this->burritoCount]);
// PostHog: Track burrito consideration
$user = Auth::user();
$posthog->identify($user->email, $user->getPostHogProperties());
$posthog->capture($user->email, 'burrito_considered', [
'total_considerations' => $this->burritoCount,
]);
$this->dispatch('burrito-considered');
}
public function render()
{
return view('livewire.burrito-tracker')
->layout('components.layouts.app');
}
}
<?php
namespace App\Http\Livewire;
use App\Services\PostHogService;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
class Dashboard extends Component
{
public bool $showNewFeature = false;
public $featureConfig = null;
public ?string $errorMessage = null;
public ?string $successMessage = null;
public function mount(PostHogService $posthog)
{
$user = Auth::user();
// PostHog: Track dashboard view
$posthog->capture($user->email, 'dashboard_viewed', [
'is_staff' => $user->is_staff,
]);
// Check feature flag
$this->showNewFeature = $posthog->isFeatureEnabled(
'new-dashboard-feature',
$user->email,
$user->getPostHogProperties()
) ?? false;
// Get feature flag payload
$this->featureConfig = $posthog->getFeatureFlagPayload(
'new-dashboard-feature',
$user->email
);
}
public function testErrorWithCapture(PostHogService $posthog)
{
$user = Auth::user();
try {
// Simulate an error
throw new \Exception('This is a test error for PostHog tracking');
} catch (\Exception $e) {
// Capture the exception in PostHog
$errorId = $posthog->captureException($e, $user->email);
$this->successMessage = "Error captured in PostHog! Error ID: {$errorId}";
$this->errorMessage = null;
}
}
public function testErrorWithoutCapture()
{
try {
// Simulate an error without capturing
throw new \Exception('This error was NOT sent to PostHog');
} catch (\Exception $e) {
$this->errorMessage = "Error occurred but NOT captured in PostHog: " . $e->getMessage();
$this->successMessage = null;
}
}
public function render()
{
return view('livewire.dashboard')
->layout('components.layouts.app');
}
}
<?php
namespace App\Http\Livewire;
use App\Services\PostHogService;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
class Profile extends Component
{
public ?string $errorMessage = null;
public ?string $successMessage = null;
public function mount(PostHogService $posthog)
{
$user = Auth::user();
// PostHog: Track profile view
$posthog->capture($user->email, 'profile_viewed');
}
public function testErrorWithCapture(PostHogService $posthog)
{
$user = Auth::user();
try {
// Simulate an error
throw new \Exception('This is a test error for PostHog tracking');
} catch (\Exception $e) {
// Capture the exception in PostHog
$errorId = $posthog->captureException($e, $user->email);
$this->successMessage = "Error captured in PostHog! Error ID: {$errorId}";
$this->errorMessage = null;
}
}
public function testErrorWithoutCapture()
{
try {
// Simulate an error without capturing
throw new \Exception('This error was NOT sent to PostHog');
} catch (\Exception $e) {
$this->errorMessage = "Error occurred but NOT captured in PostHog: " . $e->getMessage();
$this->successMessage = null;
}
}
public function render()
{
return view('livewire.profile')
->layout('components.layouts.app');
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use HasFactory, Notifiable;
protected $fillable = [
'email',
'password',
'is_staff',
];
protected $hidden = [
'password',
'remember_token',
];
protected function casts(): array
{
return [
'password' => 'hashed',
'is_staff' => 'boolean',
];
}
/**
* Get PostHog person properties for this user.
*/
public function getPostHogProperties(): array
{
return [
'email' => $this->email,
'is_staff' => $this->is_staff,
'date_joined' => $this->created_at->toISOString(),
];
}
}
<?php
namespace App\Services;
use PostHog\PostHog;
use Illuminate\Support\Facades\Auth;
class PostHogService
{
protected static $initialized = false;
public function __construct()
{
if (config('posthog.disabled')) {
return;
}
// Initialize PostHog once
if (!self::$initialized) {
PostHog::init(
config('posthog.api_key'),
[
'host' => config('posthog.host'),
'debug' => config('posthog.debug'),
]
);
self::$initialized = true;
}
}
public function identify(string $distinctId, array $properties = []): void
{
if (config('posthog.disabled')) {
return;
}
PostHog::identify([
'distinctId' => $distinctId,
'properties' => $properties,
]);
}
public function capture(string $distinctId, string $event, array $properties = []): void
{
if (config('posthog.disabled')) {
return;
}
PostHog::capture([
'distinctId' => $distinctId,
'event' => $event,
'properties' => $properties,
]);
}
public function captureException(\Throwable $exception, ?string $distinctId = null): ?string
{
if (config('posthog.disabled')) {
return null;
}
$distinctId = $distinctId ?? Auth::user()?->email ?? 'anonymous';
$eventId = uniqid('error_', true);
$this->capture($distinctId, '$exception', [
'error_id' => $eventId,
'exception_type' => get_class($exception),
'exception_message' => $exception->getMessage(),
'exception_file' => $exception->getFile(),
'exception_line' => $exception->getLine(),
'stack_trace' => $exception->getTraceAsString(),
]);
return $eventId;
}
public function isFeatureEnabled(string $key, string $distinctId, array $properties = []): ?bool
{
if (config('posthog.disabled')) {
return false;
}
return PostHog::isFeatureEnabled($key, $distinctId, $properties);
}
public function getFeatureFlagPayload(string $key, string $distinctId)
{
if (config('posthog.disabled')) {
return null;
}
return PostHog::getFeatureFlagPayload($key, $distinctId);
}
}
#!/usr/bin/env php
<?php
define('LARAVEL_START', microtime(true));
/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
*/
require __DIR__.'/vendor/autoload.php';
$app = require_once __DIR__.'/bootstrap/app.php';
/*
|--------------------------------------------------------------------------
| Run The Artisan Application
|--------------------------------------------------------------------------
*/
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
$status = $kernel->handle(
$input = new Symfony\Component\Console\Input\ArgvInput,
new Symfony\Component\Console\Output\ConsoleOutput
);
$kernel->terminate($input, $status);
exit($status);
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
//
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();
<?php return array (
'livewire/livewire' =>
array (
'aliases' =>
array (
'Livewire' => 'Livewire\\Livewire',
),
'providers' =>
array (
0 => 'Livewire\\LivewireServiceProvider',
),
),
'nesbot/carbon' =>
array (
'providers' =>
array (
0 => 'Carbon\\Laravel\\ServiceProvider',
),
),
'nunomaduro/termwind' =>
array (
'providers' =>
array (
0 => 'Termwind\\Laravel\\TermwindServiceProvider',
),
),
);<?php return array (
'providers' =>
array (
0 => 'Illuminate\\Auth\\AuthServiceProvider',
1 => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
2 => 'Illuminate\\Bus\\BusServiceProvider',
3 => 'Illuminate\\Cache\\
<?php
return [
'name' => env('APP_NAME', 'PostHog Laravel Example'),
'env' => env('APP_ENV', 'production'),
'debug' => (bool) env('APP_DEBUG', false),
'url' => env('APP_URL', 'http://localhost'),
'timezone' => 'UTC',
'locale' => 'en',
'fallback_locale' => 'en',
'key' => env('APP_KEY'),
'cipher' => 'AES-256-CBC',
'providers' => [
// Laravel Framework Service Providers
Illuminate\Auth\AuthServiceProvider::class,
Illuminate\Broadcasting\BroadcastServiceProvider::class,
Illuminate\Bus\BusServiceProvider::class,
Illuminate\Cache\CacheServiceProvider::class,
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
Illuminate\Cookie\CookieServiceProvider::class,
Illuminate\Database\DatabaseServiceProvider::class,
Illuminate\Encryption\EncryptionServiceProvider::class,
Illuminate\Filesystem\FilesystemServiceProvider::class,
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
Illuminate\Hashing\HashServiceProvider::class,
Illuminate\Mail\MailServiceProvider::class,
Illuminate\Notifications\NotificationServiceProvider::class,
Illuminate\Pagination\PaginationServiceProvider::class,
Illuminate\Pipeline\PipelineServiceProvider::class,
Illuminate\Queue\QueueServiceProvider::class,
Illuminate\Redis\RedisServiceProvider::class,
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
Illuminate\Session\SessionServiceProvider::class,
Illuminate\Translation\TranslationServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
],
'aliases' => [
'App' => Illuminate\Support\Facades\App::class,
'Auth' => Illuminate\Support\Facades\Auth::class,
'Blade' => Illuminate\Support\Facades\Blade::class,
'Cache' => Illuminate\Support\Facades\Cache::class,
'Config' => Illuminate\Support\Facades\Config::class,
'DB' => Illuminate\Support\Facades\DB::class,
'Hash' => Illuminate\Support\Facades\Hash::class,
'Request' => Illuminate\Support\Facades\Request::class,
'Route' => Illuminate\Support\Facades\Route::class,
'Schema' => Illuminate\Support\Facades\Schema::class,
'Session' => Illuminate\Support\Facades\Session::class,
'View' => Illuminate\Support\Facades\View::class,
],
];
<?php
return [
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
],
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_reset_tokens',
'expire' => 60,
'throttle' => 60,
],
],
'password_timeout' => 10800,
];
<?php
return [
'default' => env('DB_CONNECTION', 'sqlite'),
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'url' => env('DATABASE_URL'),
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
],
'mysql' => [
'driver' => 'mysql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],
],
'migrations' => 'migrations',
];
<?php
return [
'api_key' => env('POSTHOG_PROJECT_TOKEN', ''),
'host' => env('POSTHOG_HOST', 'https://us.i.posthog.com'),
'disabled' => env('POSTHOG_DISABLED', false),
'debug' => env('APP_DEBUG', false),
];
<?php
return [
'driver' => env('SESSION_DRIVER', 'file'),
'lifetime' => env('SESSION_LIFETIME', 120),
'expire_on_close' => false,
'encrypt' => false,
'files' => storage_path('framework/sessions'),
'connection' => null,
'table' => 'sessions',
'store' => null,
'lottery' => [2, 100],
'cookie' => env('SESSION_COOKIE', 'laravel_session'),
'path' => '/',
'domain' => env('SESSION_DOMAIN'),
'secure' => env('SESSION_SECURE_COOKIE'),
'http_only' => true,
'same_site' => 'lax',
];
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('email')->unique();
$table->string('password');
$table->boolean('is_staff')->default(false);
$table->rememberToken();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('users');
}
};
<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
User::firstOrCreate(
['email' => 'admin@example.com'],
[
'password' => bcrypt('admin'),
'is_staff' => true,
]
);
}
}
This document summarizes the implementation of the Laravel PostHog example application, ported from the Flask version.
Models & Database
is_staff fieldPostHog Integration
config/posthog.php)identify() - User identificationcapture() - Event trackingcaptureException() - Error trackingisFeatureEnabled() - Feature flag checkinggetFeatureFlagPayload() - Feature flag payload retrievalAuthentication (Livewire Components)
Core Features (Livewire Components)
API Controllers
Views & Layouts
Routes
Configuration
Documentation
new-dashboard-feature)?capture=true/false)| Feature | Location | PostHog Method |
|---|---|---|
| User Login | Login.php:23-27 | identify() + capture() |
| User Signup | Register.php:29-32 | identify() + capture() |
| User Logout | web.php:25 | capture() |
| Dashboard View | Dashboard.php:18 | capture() |
| Feature Flag Check | Dashboard.php:21-25 | isFeatureEnabled() |
| Feature Flag Payload | Dashboard.php:28-31 | getFeatureFlagPayload() |
| Burrito Tracking | BurritoTracker.php:22-24 | identify() + capture() |
| Profile View | Profile.php:14 | capture() |
| Error Capture | ErrorTestController.php:22-24 | identify() + captureException() |
basics/laravel/
├── app/
│ ├── Http/
│ │ ├── Controllers/
│ │ │ ├── Controller.php
│ │ │ └── Api/
│ │ │ ├── BurritoController.php
│ │ │ └── ErrorTestController.php
│ │ └── Livewire/
│ │ ├── Auth/
│ │ │ ├── Login.php
│ │ │ └── Register.php
│ │ ├── Dashboard.php
│ │ ├── BurritoTracker.php
│ │ └── Profile.php
│ ├── Models/
│ │ └── User.php
│ └── Services/
│ └── PostHogService.php
├── config/
│ ├── app.php
│ ├── auth.php
│ ├── database.php
│ ├── posthog.php
│ └── session.php
├── database/
│ ├── migrations/
│ │ └── 2024_01_01_000000_create_users_table.php
│ └── seeders/
│ └── DatabaseSeeder.php
├── resources/
│ └── views/
│ ├── components/
│ │ └── layouts/
│ │ ├── app.blade.php
│ │ └── guest.blade.php
│ ├── livewire/
│ │ ├── auth/
│ │ │ ├── login.blade.php
│ │ │ └── register.blade.php
│ │ ├── dashboard.blade.php
│ │ ├── burrito-tracker.blade.php
│ │ └── profile.blade.php
│ └── errors/
│ ├── 404.blade.php
│ └── 500.blade.php
├── routes/
│ ├── api.php
│ └── web.php
├── .env.example
├── .gitignore
├── composer.json
├── IMPLEMENTATION.md
└── README.md| Flask Component | Laravel Equivalent |
|---|---|
| Flask-Login | Laravel Auth + Livewire |
| Flask-SQLAlchemy | Eloquent ORM |
| Jinja2 Templates | Blade Templates + Livewire |
| Blueprint routes | Route definitions |
| @app.route decorators | Route::get/post |
| session | session() helper |
| flash() | session()->flash() |
| @login_required | Route::middleware(‘auth’) |
| request.form | Livewire properties |
| render_template() | view() or Livewire render() |
| jsonify() | response()->json() |
| SQLAlchemy models | Eloquent models |
To make this a production-ready application:
php artisan key:generatephp artisan migrate --seedImplementation Date: January 2026 Laravel Version: 11.x Livewire Version: 3.x PostHog PHP SDK: 3.x
<?php
use Illuminate\Http\Request;
define('LARAVEL_START', microtime(true));
// Suppress PHP 8.5 deprecation warnings for development
error_reporting(E_ALL & ~E_DEPRECATED);
// Determine if the application is in maintenance mode...
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
require $maintenance;
}
// Register the Composer autoloader...
require __DIR__.'/../vendor/autoload.php';
// Bootstrap Laravel and handle the request...
(require_once __DIR__.'/../bootstrap/app.php')
->handleRequest(Request::capture());
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ $title ?? 'PostHog Laravel Example' }}</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
line-height: 1.6;
background-color: #f5f5f5;
color: #333;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.card {
background: white;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
h1, h2, h3 {
margin-bottom: 15px;
color: #1d4ed8;
}
button, .btn {
background: #1d4ed8;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
display: inline-block;
text-decoration: none;
width: 100%;
}
button:hover, .btn:hover {
background: #1e40af;
}
input {
width: 100%;
padding: 10px;
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 14px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
.error {
color: #dc2626;
font-size: 13px;
margin-top: -10px;
margin-bottom: 10px;
}
.text-sm {
font-size: 14px;
}
.text-gray {
color: #666;
}
a {
color: #1d4ed8;
}
code {
background: #f3f4f6;
padding: 2px 6px;
border-radius: 3px;
font-family: monospace;
font-size: 13px;
}
pre {
background: #1e293b;
color: #e2e8f0;
padding: 16px;
border-radius: 8px;
overflow-x: auto;
font-size: 13px;
margin-top: 10px;
}
ul {
margin-left: 20px;
}
</style>
@livewireStyles
</head>
<body>
<div class="container">
{{ $slot }}
</div>
@livewireScripts
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Page Not Found - PostHog Laravel Example</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
line-height: 1.6;
background-color: #f5f5f5;
color: #333;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.card {
background: white;
border-radius: 8px;
padding: 40px;
margin-top: 50px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
text-align: center;
}
h1 {
font-size: 72px;
color: #1d4ed8;
margin-bottom: 15px;
}
h2 {
font-size: 24px;
color: #333;
margin-bottom: 15px;
}
p {
color: #666;
margin-bottom: 25px;
}
.btn {
background: #1d4ed8;
color: white;
border: none;
padding: 12px 24px;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
text-decoration: none;
display: inline-block;
}
.btn:hover {
background: #1e40af;
}
</style>
</head>
<body>
<div class="container">
<div class="card">
<h1>404</h1>
<h2>Page Not Found</h2>
<p>The page you're looking for doesn't exist.</p>
<a href="/" class="btn">Go Home</a>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Server Error - PostHog Laravel Example</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
line-height: 1.6;
background-color: #f5f5f5;
color: #333;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.card {
background: white;
border-radius: 8px;
padding: 40px;
margin-top: 50px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
text-align: center;
}
h1 {
font-size: 72px;
color: #dc2626;
margin-bottom: 15px;
}
h2 {
font-size: 24px;
color: #333;
margin-bottom: 15px;
}
p {
color: #666;
margin-bottom: 25px;
}
.btn {
background: #1d4ed8;
color: white;
border: none;
padding: 12px 24px;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
text-decoration: none;
display: inline-block;
}
.btn:hover {
background: #1e40af;
}
</style>
</head>
<body>
<div class="container">
<div class="card">
<h1>500</h1>
<h2>Internal Server Error</h2>
<p>Something went wrong on our end.</p>
<a href="/" class="btn">Go Home</a>
</div>
</div>
</body>
</html>
<div>
<div class="card">
<h1>Welcome to PostHog Laravel Example</h1>
<p class="text-gray mb-4">This example demonstrates how to integrate PostHog with a Laravel application.</p>
<form wire:submit="login">
<label for="email">Email</label>
<input
type="email"
id="email"
wire:model="email"
required
>
@error('email') <div class="error">{{ $message }}</div> @enderror
<label for="password">Password</label>
<input
type="password"
id="password"
wire:model="password"
required
>
@error('password') <div class="error">{{ $message }}</div> @enderror
<div style="margin-bottom: 15px;">
<label style="display: inline; font-weight: normal;">
<input type="checkbox" wire:model="remember" style="width: auto; margin-right: 5px;">
Remember me
</label>
</div>
<button type="submit">Login</button>
</form>
<p style="margin-top: 16px;" class="text-sm text-gray">
Don't have an account? <a href="{{ route('register') }}">Sign up here</a>
</p>
<p class="text-sm text-gray">
<strong>Tip:</strong> Default credentials are admin@example.com/admin
</p>
</div>
<div class="card">
<h2>Features Demonstrated</h2>
<ul class="text-gray">
<li>User registration and identification</li>
<li>Event tracking</li>
<li>Feature flags</li>
<li>Error tracking</li>
</ul>
</div>
</div>
<div>
<div class="card">
<h1>Create an Account</h1>
<p class="text-gray mb-4">Sign up to explore the PostHog Laravel integration example.</p>
<form wire:submit="register">
<label for="email">Email *</label>
<input
type="email"
id="email"
wire:model="email"
required
>
@error('email') <div class="error">{{ $message }}</div> @enderror
<label for="password">Password *</label>
<input
type="password"
id="password"
wire:model="password"
required
>
@error('password') <div class="error">{{ $message }}</div> @enderror
<label for="password_confirmation">Confirm Password *</label>
<input
type="password"
id="password_confirmation"
wire:model="password_confirmation"
required
>
<button type="submit">Sign Up</button>
</form>
<p style="margin-top: 16px;" class="text-sm text-gray">
Already have an account? <a href="{{ route('login') }}">Login here</a>
</p>
</div>
<div class="card">
<h2>PostHog Integration</h2>
<p class="text-gray">When you sign up, the following PostHog events are captured:</p>
<ul class="text-gray" style="margin-top: 10px;">
<li><code>identify()</code> - Associates your email with the user</li>
<li><code>capture()</code> - Sets person properties (email, etc.)</li>
<li><code>user_signed_up</code> event - Tracks the signup action</li>
</ul>
<h3 style="margin-top: 20px;">Code Example</h3>
<pre>// After creating the user
$posthog->identify($user->email, $user->getPostHogProperties());
$posthog->capture($user->email, 'user_signed_up', [
'signup_method' => 'form'
]);</pre>
</div>
</div>
<div>
<div class="card">
<h1>Burrito Consideration Tracker</h1>
<p class="text-gray mb-4">This page demonstrates custom event tracking with PostHog.</p>
<div class="count">{{ $burritoCount }}</div>
<p style="text-align: center; color: #666; margin-bottom: 20px;">Times you've considered a burrito</p>
<div style="text-align: center;">
<button
wire:click="considerBurrito"
wire:loading.attr="disabled"
>
<span wire:loading.remove>Consider a Burrito</span>
<span wire:loading>Considering...</span>
</button>
</div>
</div>
<div class="card">
<h3>Code Example</h3>
<pre>// Livewire component method
public function considerBurrito(PostHogService $posthog)
{
$this->burritoCount++;
session(['burrito_count' => $this->burritoCount]);
$user = Auth::user();
$posthog->identify($user->email, $user->getPostHogProperties());
$posthog->capture($user->email, 'burrito_considered', [
'total_considerations' => $this->burritoCount,
]);
}</pre>
</div>
</div>
<div>
<div class="card">
<h1>Dashboard</h1>
<p class="text-gray">Welcome back, {{ auth()->user()->email }}!</p>
</div>
<div class="card">
<h2>Error Tracking Demo</h2>
<p class="text-gray">Test manual exception capture in PostHog. These buttons trigger errors in the context of your logged-in user.</p>
@if($successMessage)
<div style="background: #d4edda; border: 1px solid #c3e6cb; color: #155724; padding: 12px; border-radius: 4px; margin: 15px 0;">
{{ $successMessage }}
</div>
@endif
@if($errorMessage)
<div style="background: #f8d7da; border: 1px solid #f5c6cb; color: #721c24; padding: 12px; border-radius: 4px; margin: 15px 0;">
{{ $errorMessage }}
</div>
@endif
<div style="display: flex; gap: 10px; margin-top: 15px;">
<button wire:click="testErrorWithCapture" class="btn" style="background: #dc3545; color: white;">
Capture Error in PostHog
</button>
<button wire:click="testErrorWithoutCapture" class="btn" style="background: #c82333; color: white;">
Skip PostHog Capture
</button>
</div>
<h3 style="margin-top: 20px;">Code Example</h3>
<pre>try {
// Critical operation that might fail
processPayment();
} catch (\Throwable $e) {
// Manually capture this specific exception
$errorId = $posthog->captureException($e, $user->email);
return response()->json([
'error' => 'Operation failed',
'error_id' => $errorId
], 500);
}</pre>
<p class="text-gray" style="margin-top: 10px;">This demonstrates manual exception capture where you have control over whether errors are sent to PostHog.</p>
</div>
<div class="card">
<h2>Feature Flags</h2>
@if($showNewFeature)
<div class="feature-flag">
<strong>New Feature Enabled!</strong>
<p style="margin-top: 10px;">You're seeing this because the <code>new-dashboard-feature</code> flag is enabled for you.</p>
@if($featureConfig)
<p style="margin-top: 15px;"><strong>Feature Configuration:</strong></p>
<pre>{{ json_encode($featureConfig, JSON_PRETTY_PRINT) }}</pre>
@endif
</div>
@else
<p class="text-gray">The <code>new-dashboard-feature</code> flag is not enabled for your account.</p>
@endif
<h3 style="margin-top: 20px;">Code Example</h3>
<pre>// Check if feature flag is enabled
$showNewFeature = $posthog->isFeatureEnabled(
'new-dashboard-feature',
$user->email,
$user->getPostHogProperties()
);
// Get feature flag payload
$featureConfig = $posthog->getFeatureFlagPayload(
'new-dashboard-feature',
$user->email
);</pre>
</div>
</div>
<div>
<div class="card">
<h1>Your Profile</h1>
<p class="text-gray mb-4">This page demonstrates error tracking with PostHog.</p>
<table>
<tr>
<th>Email</th>
<td>{{ auth()->user()->email }}</td>
</tr>
<tr>
<th>Date Joined</th>
<td>{{ auth()->user()->created_at->format('Y-m-d H:i') }}</td>
</tr>
<tr>
<th>Staff Status</th>
<td>{{ auth()->user()->is_staff ? 'Yes' : 'No' }}</td>
</tr>
</table>
</div>
<div class="card">
<h2>Error Tracking Demo</h2>
<p class="text-gray">Test manual exception capture in PostHog. These buttons trigger errors in the context of your logged-in user.</p>
@if($successMessage)
<div style="background: #d4edda; border: 1px solid #c3e6cb; color: #155724; padding: 12px; border-radius: 4px; margin: 15px 0;">
{{ $successMessage }}
</div>
@endif
@if($errorMessage)
<div style="background: #f8d7da; border: 1px solid #f5c6cb; color: #721c24; padding: 12px; border-radius: 4px; margin: 15px 0;">
{{ $errorMessage }}
</div>
@endif
<div style="display: flex; gap: 10px; margin-top: 15px;">
<button wire:click="testErrorWithCapture" class="btn" style="background: #dc3545; color: white;">
Capture Error in PostHog
</button>
<button wire:click="testErrorWithoutCapture" class="btn" style="background: #c82333; color: white;">
Skip PostHog Capture
</button>
</div>
<p class="text-gray" style="margin-top: 15px;">
This demonstrates manual exception capture where you have control over whether errors are sent to PostHog.
</p>
</div>
<div class="card">
<h3>Code Example</h3>
<pre>try {
throw new \Exception('Test exception from critical operation');
} catch (\Throwable $e) {
// Capture exception with user context
$posthog->identify($user->email, $user->getPostHogProperties());
$eventId = $posthog->captureException($e, $user->email);
return response()->json([
'error' => 'Operation failed',
'error_id' => $eventId,
'message' => "Error captured in PostHog. Reference ID: {$eventId}"
], 500);
}</pre>
</div>
</div>
<?php
use App\Http\Controllers\Api\BurritoController;
use App\Http\Controllers\Api\ErrorTestController;
use Illuminate\Support\Facades\Route;
Route::middleware('auth:sanctum')->group(function () {
Route::post('/burrito/consider', [BurritoController::class, 'consider']);
Route::post('/test-error', [ErrorTestController::class, 'test']);
});
<?php
use App\Http\Livewire\Auth\Login;
use App\Http\Livewire\Auth\Register;
use App\Http\Livewire\BurritoTracker;
use App\Http\Livewire\Dashboard;
use App\Http\Livewire\Profile;
use App\Services\PostHogService;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;
// Guest routes
Route::middleware('guest')->group(function () {
Route::get('/', Login::class)->name('login');
Route::get('/register', Register::class)->name('register');
});
// Authenticated routes
Route::middleware('auth')->group(function () {
Route::get('/dashboard', Dashboard::class)->name('dashboard');
Route::get('/burrito', BurritoTracker::class)->name('burrito');
Route::get('/profile', Profile::class)->name('profile');
Route::post('/logout', function (PostHogService $posthog) {
$user = Auth::user();
// PostHog: Track logout
$posthog->capture($user->email, 'user_logged_out');
Auth::logout();
request()->session()->invalidate();
request()->session()->regenerateToken();
return redirect('/');
})->name('logout');
});
This file