Skip to content

Commit 67e2ee8

Browse files
Handle pooled Postgres connections for Laravel Cloud (#54346)
* wip * work on migrations on pooled connections * Apply fixes from StyleCI * add test * formatting --------- Co-authored-by: StyleCI Bot <[email protected]>
1 parent 62044a5 commit 67e2ee8

File tree

5 files changed

+174
-35
lines changed

5 files changed

+174
-35
lines changed

src/Illuminate/Database/Migrations/Migrator.php

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Illuminate\Database\Migrations;
44

5+
use Closure;
56
use Illuminate\Console\View\Components\BulletList;
67
use Illuminate\Console\View\Components\Info;
78
use Illuminate\Console\View\Components\Task;
@@ -50,6 +51,13 @@ class Migrator
5051
*/
5152
protected $resolver;
5253

54+
/**
55+
* The custom connection resolver callback.
56+
*
57+
* @var \Closure|null
58+
*/
59+
protected static $connectionResolverCallback;
60+
5361
/**
5462
* The name of the default connection.
5563
*
@@ -641,7 +649,26 @@ public function setConnection($name)
641649
*/
642650
public function resolveConnection($connection)
643651
{
644-
return $this->resolver->connection($connection ?: $this->connection);
652+
if (static::$connectionResolverCallback) {
653+
return call_user_func(
654+
static::$connectionResolverCallback,
655+
$this->resolver,
656+
$connection ?: $this->connection
657+
);
658+
} else {
659+
return $this->resolver->connection($connection ?: $this->connection);
660+
}
661+
}
662+
663+
/**
664+
* Set a connection resolver callback.
665+
*
666+
* @param \Closure $callback
667+
* @return void
668+
*/
669+
public static function resolveConnectionsUsing(Closure $callback)
670+
{
671+
static::$connectionResolverCallback = $callback;
645672
}
646673

647674
/**

src/Illuminate/Foundation/Application.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ public function __construct($basePath = null)
223223
$this->registerBaseBindings();
224224
$this->registerBaseServiceProviders();
225225
$this->registerCoreContainerAliases();
226+
$this->registerLaravelCloudServices();
226227
}
227228

228229
/**
@@ -303,6 +304,28 @@ protected function registerBaseServiceProviders()
303304
$this->register(new RoutingServiceProvider($this));
304305
}
305306

307+
/**
308+
* Register any services needed for Laravel Cloud.
309+
*
310+
* @return void
311+
*/
312+
protected function registerLaravelCloudServices()
313+
{
314+
if (! laravel_cloud()) {
315+
return;
316+
}
317+
318+
$this['events']->listen(
319+
'bootstrapping: *',
320+
fn ($bootstrapper) => Cloud::bootstrapperBootstrapping($this, Str::after($bootstrapper, 'bootstrapping: '))
321+
);
322+
323+
$this['events']->listen(
324+
'bootstrapped: *',
325+
fn ($bootstrapper) => Cloud::bootstrapperBootstrapped($this, Str::after($bootstrapper, 'bootstrapped: '))
326+
);
327+
}
328+
306329
/**
307330
* Run the given array of bootstrap classes.
308331
*

src/Illuminate/Foundation/Bootstrap/HandleExceptions.php

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@
88
use Illuminate\Contracts\Foundation\Application;
99
use Illuminate\Log\LogManager;
1010
use Illuminate\Support\Env;
11-
use Monolog\Formatter\JsonFormatter;
1211
use Monolog\Handler\NullHandler;
13-
use Monolog\Handler\SocketHandler;
1412
use PHPUnit\Runner\ErrorHandler;
1513
use Symfony\Component\Console\Output\ConsoleOutput;
1614
use Symfony\Component\ErrorHandler\Error\FatalError;
@@ -55,10 +53,6 @@ public function bootstrap(Application $app)
5553
if (! $app->environment('testing')) {
5654
ini_set('display_errors', 'Off');
5755
}
58-
59-
if (laravel_cloud()) {
60-
$this->configureCloudLogging($app);
61-
}
6256
}
6357

6458
/**
@@ -251,34 +245,6 @@ protected function fatalErrorFromPhpError(array $error, $traceOffset = null)
251245
return new FatalError($error['message'], 0, $error, $traceOffset);
252246
}
253247

254-
/**
255-
* Configure the Laravel Cloud log channels.
256-
*
257-
* @param \Illuminate\Contracts\Foundation\Application $app
258-
* @return void
259-
*/
260-
protected function configureCloudLogging(Application $app)
261-
{
262-
$app['config']->set('logging.channels.stderr.formatter_with', [
263-
'includeStacktraces' => true,
264-
]);
265-
266-
$app['config']->set('logging.channels.laravel-cloud-socket', [
267-
'driver' => 'monolog',
268-
'handler' => SocketHandler::class,
269-
'formatter' => JsonFormatter::class,
270-
'formatter_with' => [
271-
'includeStacktraces' => true,
272-
],
273-
'with' => [
274-
'connectionString' => $_ENV['LARAVEL_CLOUD_LOG_SOCKET'] ??
275-
$_SERVER['LARAVEL_CLOUD_LOG_SOCKET'] ??
276-
'unix:///tmp/cloud-init.sock',
277-
'persistent' => true,
278-
],
279-
]);
280-
}
281-
282248
/**
283249
* Forward a method call to the given method if an application instance exists.
284250
*

src/Illuminate/Foundation/Cloud.php

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
3+
namespace Illuminate\Foundation;
4+
5+
use Illuminate\Database\Migrations\Migrator;
6+
use Illuminate\Foundation\Bootstrap\LoadConfiguration;
7+
use Monolog\Formatter\JsonFormatter;
8+
use Monolog\Handler\SocketHandler;
9+
10+
class Cloud
11+
{
12+
/**
13+
* Handle a bootstrapper that is bootstrapping.
14+
*/
15+
public static function bootstrapperBootstrapping(Application $app, string $bootstrapper): void
16+
{
17+
//
18+
}
19+
20+
/**
21+
* Handle a bootstrapper that has bootstrapped.
22+
*/
23+
public static function bootstrapperBootstrapped(Application $app, string $bootstrapper): void
24+
{
25+
(match ($bootstrapper) {
26+
LoadConfiguration::class => function () use ($app) {
27+
static::configureUnpooledPostgresConnection($app);
28+
static::ensureMigrationsUseUnpooledConnection($app);
29+
},
30+
HandleExceptions::class => function () use ($app) {
31+
static::configureCloudLogging($app);
32+
},
33+
default => fn () => true,
34+
})();
35+
}
36+
37+
/**
38+
* Adjust the database configuration for pooled Laravel Postgres.
39+
*/
40+
public static function configureUnpooledPostgresConnection(Application $app): void
41+
{
42+
$host = $app['config']->get('database.connections.pgsql.host', '');
43+
44+
if (str_contains($host, 'pg.laravel.cloud') &&
45+
str_contains($host, '-pooler')) {
46+
$app['config']->set(
47+
'database.connections.pgsql-unpooled',
48+
array_merge($app['config']->get('database.connections.pgsql'), [
49+
'host' => str_replace('-pooler', '', $host),
50+
])
51+
);
52+
}
53+
}
54+
55+
/**
56+
* Ensure that migrations use the unpooled Postgres connection if applicable.
57+
*/
58+
public static function ensureMigrationsUseUnpooledConnection(Application $app): void
59+
{
60+
if (! is_array($app['config']->get('database.connections.pgsql-unpooled'))) {
61+
return;
62+
}
63+
64+
Migrator::resolveConnectionsUsing(function ($resolver, $connection) use ($app) {
65+
$connection = $connection ?? $app['config']->get('database.default');
66+
67+
return $resolver->connection(
68+
$connection === 'pgsql' ? 'pgsql-unpooled' : $connection
69+
);
70+
});
71+
}
72+
73+
/**
74+
* Configure the Laravel Cloud log channels.
75+
*/
76+
public static function configureCloudLogging(Application $app): void
77+
{
78+
$app['config']->set('logging.channels.stderr.formatter_with', [
79+
'includeStacktraces' => true,
80+
]);
81+
82+
$app['config']->set('logging.channels.laravel-cloud-socket', [
83+
'driver' => 'monolog',
84+
'handler' => SocketHandler::class,
85+
'formatter' => JsonFormatter::class,
86+
'formatter_with' => [
87+
'includeStacktraces' => true,
88+
],
89+
'with' => [
90+
'connectionString' => $_ENV['LARAVEL_CLOUD_LOG_SOCKET'] ??
91+
$_SERVER['LARAVEL_CLOUD_LOG_SOCKET'] ??
92+
'unix:///tmp/cloud-init.sock',
93+
'persistent' => true,
94+
],
95+
]);
96+
}
97+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace Illuminate\Tests\Integration\Foundation;
4+
5+
use Illuminate\Foundation\Cloud;
6+
use Orchestra\Testbench\TestCase;
7+
8+
class CloudTest extends TestCase
9+
{
10+
public function test_it_can_resolve_core_container_aliases()
11+
{
12+
$this->app['config']->set('database.connections.pgsql', [
13+
'host' => 'test-pooler.pg.laravel.cloud',
14+
'username' => 'test-username',
15+
'password' => 'test-password',
16+
]);
17+
18+
Cloud::configureUnpooledPostgresConnection($this->app);
19+
20+
$this->assertEquals([
21+
'host' => 'test.pg.laravel.cloud',
22+
'username' => 'test-username',
23+
'password' => 'test-password',
24+
], $this->app['config']->get('database.connections.pgsql-unpooled'));
25+
}
26+
}

0 commit comments

Comments
 (0)