tpetry/laravel-postgresql-enhanced
Adds PostgreSQL-specific power to Laravel beyond the “lowest common denominator”: enhanced migrations (zero-downtime, extensions, functions, triggers, views/materialized views), advanced indexes (concurrent, partial, include, full-text, temporal), domains and table options.
## Getting Started
### First Steps
1. **Installation**: Require the package via Composer:
```bash
composer require tpetry/laravel-postgresql-enhanced
Publish the config (if needed) with:
php artisan vendor:publish --provider="Tpetry\PostgresqlEnhanced\PostgresqlEnhancedServiceProvider"
Verify Setup: Ensure your config/database.php uses pgsql as the connection driver.
First Use Case: Use PostgreSQL-specific features in migrations. For example, create a materialized view:
use Tpetry\PostgresqlEnhanced\Schema\Blueprint;
use Tpetry\PostgresqlEnhanced\Support\Facades\Schema;
Schema::createMaterializedView('user_stats', function (Blueprint $table) {
$table->from('users');
$table->select(['id', 'name', 'created_at']);
});
Schema facade methods (e.g., createExtension, createFunction) are the most commonly used entry points.Zero-Downtime Migrations:
ZeroDowntimeMigration trait for critical schema changes:
use Tpetry\PostgresqlEnhanced\Schema\Concerns\ZeroDowntimeMigration;
class UpdateUserTable extends Migration {
use ZeroDowntimeMigration;
public function up() {
Schema::table('users', function (Blueprint $table) {
$table->string('email', 255)->change();
});
}
}
$timeout = 5.0 to abort if the migration exceeds 5 seconds.Extension Management:
Schema::createExtension('uuid-ossp');
Schema::createExtensionIfNotExists('pg_trgm');
down() methods.Functional Indexes:
$table->index('(lower(name))', 'idx_name_lower');
Full-Text Search:
->whereFullText() builder method:
$results = User::whereFullText('name', 'John Doe')->get();
$results = User::where('name', 'plainto_tsquery', 'John & Doe')->get();
CTEs (Common Table Expressions):
$query = DB::table(DB::raw('
WITH RECURSIVE category_tree AS (
SELECT id, name, parent_id, 1 AS level FROM categories WHERE parent_id IS NULL
UNION ALL
SELECT c.id, c.name, c.parent_id, ct.level + 1
FROM categories c
JOIN category_tree ct ON c.parent_id = ct.id
)
SELECT * FROM category_tree
'))->get();
Lateral Joins:
joinLateral() for correlated subqueries:
$query = User::select('users.*')
->joinLateral(function ($join) {
$join->select('order_id')
->from('orders')
->whereColumn('users.id', 'orders.user_id')
->orderBy('orders.created_at', 'desc')
->limit(1);
}, 'latest_order');
Custom Casts:
jsonb, uuid):
protected $casts = [
'metadata' => 'jsonb',
'id' => 'uuid',
];
->asPostgresType() helper for custom types:
protected $casts = [
'ip_address' => 'ip_address:asPostgresType',
];
Refresh Data on Save:
use Tpetry\PostgresqlEnhanced\Eloquent\Concerns\RefreshesDataOnSave;
class User extends Model {
use RefreshesDataOnSave;
protected $refreshOnSave = ['updated_at', 'last_login'];
}
IDE Autocomplete:
php artisan ide-helper:generate
phpstan.neon:
includes:
- vendor/tpetry/laravel-postgresql-enhanced/phpstan-extension.neon
Function Creation:
sql:expression for simple functions can lead to poor performance.sql:expression for immutable, inlineable logic:
Schema::createFunction('calculate_tax', ['amount' => 'numeric'], 'numeric', 'sql:expression', 'amount * 0.08');
Trigger Conflicts:
trigger_update_user_status) and scope to tables:
$table->trigger('trigger_update_user_status_after_insert', 'update_user_status()', 'AFTER INSERT');
Zero-Downtime Timeouts:
5.0 seconds and adjust based on testing.Partial Indexes:
WHERE clause is too broad.EXPLAIN ANALYZE:
DB::select('EXPLAIN ANALYZE SELECT * FROM users WHERE active = true');
Query Explanation:
->toSqlWithBindings() to inspect raw SQL:
$sql = User::whereFullText('name', 'John')->toSqlWithBindings();
EXPLAIN:
DB::select('EXPLAIN ANALYZE ' . $sql);
Migration Rollback:
psql -c "SELECT * FROM pg_locks WHERE relation = 'users'::regclass;"
Extension Conflicts:
psql -c "\dx"
Schema::dropExtension('pg_trgm');
Schema::createExtension('pg_trgm');
Custom Types:
// app/Providers/PostgresqlEnhancedServiceProvider.php
public function boot() {
\Tpetry\PostgresqlEnhanced\Eloquent\Casts\PostgresType::macro('customType', function ($value) {
return new CustomType($value);
});
}
Query Builder Macros:
DB::macro('whereJsonContains', function ($key, $value) {
return $this->whereRaw("jsonb_typeof({$key}) = 'object' AND {$key} ? '{$value}'");
});
Schema Blueprints:
Blueprint class for custom schema operations:
class CustomBlueprint extends \Tpetry\PostgresqlEnhanced\Schema\Blueprint {
public function customIndex($columns, $name = null) {
$this->index($columns, $name)->using('gin');
}
}
AppServiceProvider:
Schema::extend(function ($schema, $connection) {
return new CustomBlueprint($schema, $connection);
});
TimescaleDB:
timescaledb extension is enabled in config/postgresql-enhanced.php:
'extensions' => [
'timescaledb' => true,
],
timescale/timescaledb PHP extension.Case-Insensitive Text:
->asPostgresType('citext') for case-insensitive strings:
$table->string('slug')->asPostgresType('cite
How can I help you explore Laravel packages today?