gnugat/pomm-foundation-bundle
Symfony bundle that integrates Pomm, the PostgreSQL object model manager, providing service configuration and connection setup so applications can use Pomm’s model manager and query tools within Symfony.
Requirements first. v0.7.0 bumped the floor to PHP 8.0 and Symfony 6. That is the whole release: no new features, no config changes, no API changes — just the constraint bump. So the practical rule is:
^0.7.^0.6 line, or skip this bundle and wire pomm-project/pomm-bundle / pomm-project/foundation yourself.Because the bump is constraints-only, any code, service ids and YAML you already had on 0.6 will keep working after the upgrade — the upgrade is a Composer resolution problem, not a code-migration problem.
Install and register (no Flex recipe — do it manually).
composer require gnugat/pomm-foundation-bundle:^0.7
Add to config/bundles.php (Symfony 6 has no AppKernel):
Gnugat\PommFoundationBundle\GnugatPommFoundationBundle::class => ['all' => true],
Configure one or more PostgreSQL connections.
# config/packages/pomm_foundation.yaml
pomm_foundation:
connections:
default:
database: '%env(PG_DATABASE)%'
host: '%env(PG_HOST)%'
port: '%env(int:PG_PORT)%'
user: '%env(PG_USER)%'
password: '%env(PG_PASSWORD)%'
The bundle's job is narrow: it turns each entry into a Pomm session and registers the Pomm service. You then pull a session and use Pomm Foundation's query managers, converters, pager and transaction services directly.
First sensible use case — a thin repository that resolves a session and runs a parameterised query:
namespace App\Repository;
use PommProject\Foundation\Pomm;
final class ArticleRepository
{
public function __construct(private Pomm $pomm) {}
public function findOne(int $id): ?array
{
$result = $this->pomm
->getSession('default') // or getDefaultSession()
->getQueryManager()
->query('SELECT * FROM article WHERE id = $*', [$id]);
return $result->fetchRow() ?: null;
}
}
Where to look first: bin/console debug:container pomm and debug:container --parameter=pomm_foundation to confirm the service id, session names and config shape the bundle actually produced. On Symfony 6 also run bin/console lint:container after registering the bundle — a hard failure there almost always means the bundle's service definitions don't match your installed Symfony components.
Always go through a repository/service, never inject sessions into controllers. Pomm is a long-lived registry of sessions; Session holds a live connection and transaction state. Inject Pomm (a singleton) and resolve the session inside the method call. This keeps your code unit-testable (mock Pomm, return a fake session/result) and avoids sharing a mutable connection across services.
final class ReportingRepository
{
public function __construct(
private Pomm $pomm,
private string $sessionName = 'reporting',
) {}
private function qm()
{
return $this->pomm->getSession($this->sessionName)->getQueryManager();
}
}
Multiple named connections. Configure default plus any read-replica or reporting DB and switch with getSession('reporting'). This is the bundle's main value over raw pomm-project/foundation — you don't hand-build Pomm and SessionBuilder in bootstrap code.
Custom query managers. Pomm sessions resolve query managers by service name. Implement PommProject\Foundation\QueryManager\QueryManagerInterface (or extend SimpleQueryManager) in a real Symfony service — constructor-inject whatever dependencies you need into it — then register it on the session builder so $session->getQueryManager('articles') returns it. If the bundle doesn't expose a session-builder option, decorate the pomm service or define your own SessionBuilder service and pass it in via a compiler pass. This is the cleanest extension point for .sql-file-backed or cached query managers.
$session->getQueryManager('articles')->query(
'SELECT * FROM article WHERE published_at > $* ORDER BY published_at DESC',
[$since]
);
Transactions. Wrap multi-statement work explicitly:
$session = $this->pomm->getSession('default');
$transaction = $session->getTransaction(); // verify namespace on your installed version
$transaction->begin();
try {
$qm = $session->getQueryManager();
$qm->query('UPDATE account SET balance = balance - $* WHERE id = $*', [$amount, $from]);
$qm->query('UPDATE account SET balance = balance + $* WHERE id = $*', [$amount, $to]);
$transaction->commit();
} catch (\Throwable $e) {
$transaction->rollback();
throw $e;
}
Note Pomm's placeholder style is $* (expanded positionally into $1, $2, …) or explicit $1, $2. It is not PDO ? or named :param.
Pagination. Use PommProject\Foundation\Pager\Pager for LIMIT/OFFSET + total-count pages rather than hand-rolling COUNT(*) queries; it emits a proper page structure you can render directly.
Type conversion. Pomm converts PostgreSQL types (JSON, arrays, timestamps, numeric) via its converter pool on the way out. If you have a custom PG type (e.g. ltree, enums), implement a converter and register it in the converter pool rather than casting strings in PHP.
Pair with Doctrine Migrations. This bundle is a data-access layer, not a schema tool. Keep doctrine/doctrine-migrations-bundle for DDL and use Pomm only for queries. Do not add Doctrine ORM "just for models" — you won't get an identity map, lazy loading, or DQL from Pomm.
Stay PHP-8.0-safe if you support 8.0. The package now requires 8.0, not 8.1+, so anything you ship as a shared library alongside it can't rely on readonly properties, enums, or never. Application code on 8.1+ is free to use them; just don't assume the bundle's own types do.
Upgrading from 0.6 is a Composer problem, not a code problem. The v0.7.0 changelog is only "bumped requirement to PHP 8.0" and "bumped requirements to Symfony 6". If composer require refuses to move, check composer why-not gnugat/pomm-foundation-bundle 0.7.0 before touching code — you're almost certainly blocked by a transitive constraint, or you're still on Symfony 5.4/PHP 7.4, in which case stay on ^0.6.
Autowiring is probably not set up. If Pomm $pomm fails to inject, alias it yourself:
services:
PommProject\Foundation\Pomm: '@pomm'
Lazy connections mean late failures. Pomm and sessions construct without opening a socket; connection errors surface on the first query. Health checks should run SELECT 1 through the query manager, not just "is the service instantiable". A container that lints clean on Symfony 6 tells you nothing about whether PostgreSQL is reachable.
Persistent workers (RoadRunner, Swoole, FrankenPHP). A long-lived Pomm singleton keeps a live PG connection across requests, and an unfinished transaction or open cursor will leak into the next request. Reset the session/transaction per request, or rebuild Pomm in a per-request scope.
Transactions must be committed explicitly — an uncommitted BEGIN holds row locks and blocks other writers. Add a finally or a decorator that rolls back if commit() was never called.
Don't inject Session or query managers as singletons. They carry transaction and cursor state. Injecting Pomm and resolving per call is the safe pattern.
SQL injection via string-concatenated builders. Pomm's where/projection helpers compose SQL strings. Always pass values as parameters to query(), never interpolate them into the SQL.
Config quirks. Env vars arrive as strings; use %env(int:PG_PORT)% for port. Empty password: use ~ rather than '' in some versions. A missing database key produces a confusing connection failure, not a config error — validate config in a boot test.
Debugging slow queries. Attach a PSR-3 logger to Pomm's client (or wrap the query manager in a decorator that logs SQL + params + duration), and set log_min_duration_statement at the PostgreSQL level. debug:container will show you whether the bundle wires a logger by default — usually it does not. On Symfony 6, PSR-3 loggers are autowired via LoggerInterface, so the decorator approach is the least invasive.
Model-manager features are not here. If you expected findAll(), model classes and generated getters, that's pomm-project/model-manager plus pomm-project/pomm-bundle. This bundle is the thinner foundation-only integration; add the model manager as a separate layer if you want it, wired on top of the sessions this bundle builds. The 0.7.0 bump does not change that scope.
Extension points, ranked: custom QueryManagerInterface services via the session builder → custom converters in the converter pool → PommProject\Foundation\Listener implementations for query/connection events → decorating the pomm service. Keep those in App\Pomm\* so a future swap to the official Pomm bundle is a wiring change, not a rewrite.
How can I help you explore Laravel packages today?