Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions documentation/components/bridges/symfony-postgresql-bundle.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ flow_postgresql:
profiler: false # do not record queries in selected connection (default: true)
```

When `migrations` are enabled, a separate **Flow Migrations** panel reports each connection's
When `migrations` are enabled, a separate **Flow Migrations** panel reports the migrations connection's
executed, pending and unavailable migrations (with execution time) — like the Doctrine Migrations
bundle's panel. It queries the database on every profiled request; set `profiler.migrations: false`
to disable it while keeping the query panel:
Expand All @@ -163,8 +163,9 @@ flow_postgresql:

### Migrations

Migrations are configured at the top level, not per connection. Use `--connection` to target a specific connection
when running migration commands.
Migrations are configured at the top level, not per connection, and always run against a single connection.
Set `connection` to choose which one; when omitted it defaults to the default (first) connection. Every migration
command operates on that connection — there is no per-command connection override.

```yaml
flow_postgresql:
Expand All @@ -174,6 +175,7 @@ flow_postgresql:

migrations:
enabled: true
connection: ~ # Connection migrations run against (default: the first/default connection)
directory: "%kernel.project_dir%/migrations" # Where migration files are stored
namespace: "App\\Migrations" # PHP namespace for generated migrations
table_name: "flow_migrations" # Database table tracking executed migrations
Expand Down Expand Up @@ -418,7 +420,8 @@ These commands are always available, regardless of migration configuration.
| `flow:database:drop` | Drop the configured database (requires `--force`) |
| `flow:sql:run` | Execute SQL directly on the database |

All commands accept `--connection` (`-c`) to target a specific connection.
The commands above accept `--connection` (`-c`) to target a specific connection. Migration commands do not —
they always run against the configured migrations connection.

### Migration Commands

Expand All @@ -440,7 +443,6 @@ These commands are available when `migrations.enabled: true` for at least one co

| Option | Description |
|-----------------------|-------------------------------------------------------|
| `--connection` (`-c`) | Target a specific connection |
| `--dry-run` | Preview changes without applying (migrate, execute) |
| `--all-or-nothing` | Wrap all migrations in a single transaction (migrate) |
| `--up` / `--down` | Migration direction (execute) |
Expand Down
17 changes: 17 additions & 0 deletions documentation/upgrading.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,23 @@ Handle failures via the `ErrorHandler` (default `ErrorLogHandler`) instead of tr
bundle injects the exporter's configured `error_handler` into the transport automatically. Failover behavior
(`FailoverTransportException`) is unchanged.

### 7) `flow-php/symfony-postgresql-bundle` - migrations run against a single configured connection

| Before | After |
|---------------------------------------------------------|----------------------------------------------------------------------------------|
| `flow:migrations:* --connection=<name>` (`-c`) | removed — every migration command uses the configured migrations connection |
| migrator stack registered for every connection | registered only for the migrations connection |
| — | `flow_postgresql.migrations.connection: <name>` (defaults to the first connection)|

To run migrations against a non-default connection, set `migrations.connection` instead of passing `-c`:

```yaml
flow_postgresql:
migrations:
enabled: true
connection: analytics
```

---

## Upgrading from 0.39.x to 0.40.x
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,30 @@

namespace Flow\Bridge\Monolog\Telemetry;

use BackedEnum;
use DateTimeInterface;
use Flow\Telemetry\Logger\LogRecord as TelemetryLogRecord;
use Monolog\LogRecord;
use Throwable;
use UnitEnum;

use function array_keys;
use function gettype;
use function is_array;
use function is_object;
use function is_scalar;
use function json_encode;
use function method_exists;
use function str_contains;
use function strtr;

/**
* Convert Monolog LogRecord to Telemetry LogRecord with proper attribute mapping.
*
* This class handles the conversion of Monolog log records to Telemetry log records,
* mapping Monolog's severity levels and applying appropriate prefixes to context
* and extra attributes.
*/
final readonly class LogRecordConverter
{
private const string INTERPOLATION_DATE_FORMAT = 'Y-m-d\TH:i:s.uP';

public function __construct(
private SeverityMapper $severityMapper = new SeverityMapper(),
private ValueNormalizer $valueNormalizer = new ValueNormalizer(),
Expand All @@ -26,7 +37,7 @@ public function convert(LogRecord $record): TelemetryLogRecord
{
$telemetryRecord = new TelemetryLogRecord(
severity: $this->severityMapper->map($record->level),
body: $record->message,
body: $this->interpolate($record->message, $record->context),
);

return $this->applyAttributes($telemetryRecord, $record);
Expand Down Expand Up @@ -65,4 +76,65 @@ private function applyAttributes(TelemetryLogRecord $telemetryRecord, LogRecord

return $telemetryRecord;
}

/**
* @param array<array-key, mixed> $context
*/
private function interpolate(string $message, array $context): string
{
if (!str_contains($message, '{')) {
return $message;
}

$replacements = [];

foreach (array_keys($context) as $key) {
$placeholder = '{' . $key . '}';

if (!str_contains($message, $placeholder)) {
continue;
}

$replacements[$placeholder] = $this->renderForInterpolation($context[$key]);
}

return strtr($message, $replacements);
}

private function renderForInterpolation(mixed $value): string
{
if ($value === null) {
return '';
}

if (is_scalar($value)) {
return (string) $value;
}

if (is_object($value) && method_exists($value, '__toString')) {
return (string) $value;
}

if ($value instanceof DateTimeInterface) {
return $value->format(self::INTERPOLATION_DATE_FORMAT);
}

if ($value instanceof BackedEnum) {
return (string) $value->value;
}

if ($value instanceof UnitEnum) {
return $value->name;
}

if (is_object($value)) {
return '[object ' . $value::class . ']';
}

if (is_array($value)) {
return 'array' . (json_encode($value) ?: '');
}

return '[' . gettype($value) . ']';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace Flow\Bridge\Monolog\Telemetry\Tests\Fixtures;

enum InterpolationBackedEnumFixture: string
{
case Active = 'active';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace Flow\Bridge\Monolog\Telemetry\Tests\Fixtures;

enum InterpolationUnitEnumFixture
{
case First;
}
Loading
Loading