PHPackages                             apigopro/slim-database - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. apigopro/slim-database

ActiveLibrary[Database &amp; ORM](/categories/database)

apigopro/slim-database
======================

Lazy PDO database connection management for Slim Framework 4, with dual-credential (auth/app) support and PostgreSQL, MySQL, and Oracle drivers.

v1.0.0(1mo ago)01MITPHPPHP ^8.5

Since Jul 19Pushed 1mo agoCompare

[ Source](https://github.com/apigopro/slim-database)[ Packagist](https://packagist.org/packages/apigopro/slim-database)[ RSS](/packages/apigopro-slim-database/feed)WikiDiscussions main Synced 2w ago

READMEChangelogDependencies (4)Versions (2)Used By (0)

apigopro/slim-database
======================

[](#apigoproslim-database)

Lazy PDO database connection management for **Slim Framework 4**, requiring **PHP 8.5**.

Supports **PostgreSQL**, **MySQL/MariaDB**, and **Oracle** through one consistent API. Built around a dual-credential pattern: separate, distinctly-typed connections for authentication data vs. the rest of the application, so least-privilege database roles are enforced both by Postgres/MySQL/ Oracle GRANTs *and* by PHP's type system (you can't accidentally inject the wrong connection into the wrong place without it being visible in the constructor signature).

Install
-------

[](#install)

Published on Packagist

```
composer require apigopro/slim-database
```

Requirements
------------

[](#requirements)

- PHP 8.5
- The `pdo` extension (always required)
- **One** driver-specific PDO extension, matching whichever database you actually use:
    - `pdo_pgsql` for PostgreSQL
    - `pdo_mysql` for MySQL/MariaDB
    - `pdo_oci` for Oracle (see below — this one needs manual setup, not a package manager)
- [`vlucas/phpdotenv`](https://github.com/vlucas/phpdotenv) if you're loading config from a `.env`file rather than real process environment variables
- A PSR-11 DI container (this README uses [PHP-DI](https://php-di.org/)) — not strictly required by this package's code, but the intended usage pattern relies on one for wiring things together

### PostgreSQL

[](#postgresql)

```
sudo apt install php8.3-pgsql   # adjust for your installed PHP version
```

### MySQL / MariaDB

[](#mysql--mariadb)

```
sudo apt install php8.3-mysql
```

**Named timezones require a one-time setup step.** MySQL doesn't ship with timezone data loaded by default — connecting with `DB_TIMEZONE=Europe/Sarajevo` (or any named zone) fails with `Unknown or incorrect time zone` until you load it once per server:

```
mysql_tzinfo_to_sql /usr/share/zoneinfo | sudo mysql -u root mysql
```

This is a MySQL server-level gotcha, not something this package can work around — it affects any application connecting with a named timezone, not just this one.

### Oracle

[](#oracle)

This is the one that isn't a simple package-manager install — Oracle requires a **licensed manual download**, no `apt`/`pecl` one-liner can fetch it.

1. **Create a free Oracle account** and download **Instant Client Basic** and **Instant Client SDK**for your platform from Oracle's site (search "Oracle Instant Client downloads" — the exact URL path changes over Oracle's site restructures often enough that it's not worth hardcoding here).
2. **Extract and register the shared libraries:**

    ```
    sudo mkdir -p /opt/oracle
    sudo unzip instantclient-basic-linux.x64-*.zip -d /opt/oracle
    sudo unzip instantclient-sdk-linux.x64-*.zip -d /opt/oracle

    echo /opt/oracle/instantclient_21_1 | sudo tee /etc/ld.so.conf.d/oracle-instantclient.conf
    sudo ldconfig
    ```

    (adjust the `instantclient_21_1` directory name to whatever version you actually downloaded)
3. **Install build tools and the PECL extensions:**

    ```
    sudo apt install php8.3-dev php-pear build-essential libaio1
    ```

    `pdo_oci` has, at different points in PHP's history, shipped bundled and also existed as a separate PECL package — check what's actually available for your PHP version before assuming either way:

    ```
    pecl list-all | grep -i oci
    ```

    Typical PECL install, pointing at your Instant Client directory:

    ```
    sudo pecl install oci8
    # when prompted for the Instant Client location:
    # instantclient,/opt/oracle/instantclient_21_1
    ```

    For non-interactive installs:

    ```
    echo "instantclient,/opt/oracle/instantclient_21_1" | sudo pecl install oci8
    ```
4. **Enable the extension(s)** in `php.ini` (or a file under `conf.d/`):

    ```
    extension=oci8.so
    extension=pdo_oci.so
    ```

    Then restart PHP-FPM:

    ```
    sudo systemctl restart php8.3-fpm
    ```
5. **Verify:**

    ```
    php -m | grep -i oci
    ```

I wasn't able to test the Oracle path against a real server while building this package — Oracle's download requires accepting their license through a browser session, which isn't something available in an automated environment. Everything else in this README (PostgreSQL, MySQL, the DSN templating, the connection classes themselves) was verified against real running servers. If you hit something that doesn't match reality during Oracle setup, it's worth double-checking against Oracle's current official docs for your specific Instant Client version.

How the DSN template works
--------------------------

[](#how-the-dsn-template-works)

`DatabaseCredentials->dns` is a `printf`-style template. `AbstractDatabaseConnection` builds the final DSN with:

```
sprintf($credentials->dns, $credentials->host, $credentials->port, $credentials->name)
```

So whatever you put in `DB_DNS` must have exactly three placeholders, in that order: host, port, name. Examples per driver are in `.env.example`. If your DSN ever needs a literal `%` character for some other reason, escape it as `%%`.

`.env` setup
------------

[](#env-setup)

Copy `.env.example` to `.env` and fill in real values — **pick exactly one driver block** (Postgres, MySQL, or Oracle) and leave the others commented out. `DB_DNS` and `DB_ENCODING` are only meant to be set once; if more than one block is active, later values silently overwrite earlier ones in the same `.env` file (dotenv doesn't merge repeated keys).

Load it early in your bootstrap, before building your container:

```
use Dotenv\Dotenv;

if (file_exists('/etc/myapp/.env')) {
    Dotenv::createImmutable('/etc/myapp')->load();
}
```

The two-connection pattern
--------------------------

[](#the-two-connection-pattern)

`DatabaseConfiguration` reads two separate sets of credentials from your env — `DB_AUTH_USER`/ `DB_AUTH_PASSWORD` and `DB_APP_USER`/`DB_APP_PASSWORD` — sharing the same host/port/database name. The intended setup is two actual database roles with different grants:

```
-- PostgreSQL example
CREATE ROLE auth_user WITH LOGIN PASSWORD '...';
GRANT SELECT ON auth_credentials TO auth_user;

CREATE ROLE app_user WITH LOGIN PASSWORD '...';
GRANT SELECT, INSERT, UPDATE, DELETE ON users TO app_user;
-- deliberately no grants on auth_credentials for app_user
```

`AuthDatabaseConnection` and `AppDatabaseConnection` are distinct PHP classes (both extending `AbstractDatabaseConnection`), not just two instances of the same class — so a constructor type-hint like `AuthDatabaseConnection $db` is self-documenting, and using the wrong one is a visible mistake in code, on top of the database itself enforcing the real boundary via GRANTs.

Wiring into a DI container (PHP-DI example)
-------------------------------------------

[](#wiring-into-a-di-container-php-di-example)

```
use DI\Container;
use SlimDatabase\DatabaseConfiguration;
use SlimDatabase\AuthDatabaseConnection;
use SlimDatabase\AppDatabaseConnection;
use SlimDatabase\Middleware\CloseDatabaseConnectionsMiddleware;

$container = new Container();

$container->set(DatabaseConfiguration::class, function () {
    return new DatabaseConfiguration();
});

$container->set(AuthDatabaseConnection::class, function (Container $c) {
    return new AuthDatabaseConnection($c->get(DatabaseConfiguration::class)->authDb);
});

$container->set(AppDatabaseConnection::class, function (Container $c) {
    return new AppDatabaseConnection($c->get(DatabaseConfiguration::class)->appDb);
});
```

Wiring the closing middleware
-----------------------------

[](#wiring-the-closing-middleware)

Add it **first**, before any other middleware, so it's the outermost layer — Slim's middleware stack is nested, and the first middleware added is the *last* to finish on the way out, which is what guarantees the connections close only after the route handler and every other middleware have fully completed:

```
$app->add($container->get(CloseDatabaseConnectionsMiddleware::class));
$app->add(new JwtAuthMiddleware([/* ... */]));
// ... routes
```

Usage in an action class
------------------------

[](#usage-in-an-action-class)

```
use SlimDatabase\AppDatabaseConnection;

final class ListUsersAction
{
    public function __construct(private readonly AppDatabaseConnection $db) {}

    public function __invoke($request, $response, array $args)
    {
        $stmt = $this->db->get()->query('SELECT id, name FROM users');
        // ...
    }
}
```

```
use SlimDatabase\AuthDatabaseConnection;

final class LoginAction
{
    public function __construct(private readonly AuthDatabaseConnection $db) {}

    // Only this class (and anything else that legitimately needs it)
    // should ever type-hint AuthDatabaseConnection.
}
```

`DatabaseCredentials` reference
-------------------------------

[](#databasecredentials-reference)

PropertyMeaning`dns`printf-style DSN template — see "How the DSN template works" above.`host`Database host.`port`Database port.`name`Database/service name.`user`Connection username.`password`Connection password.`timezone`Session timezone. Applied via `SET TIME ZONE` (Postgres), `SET time_zone` (MySQL), or `ALTER SESSION SET TIME_ZONE` (Oracle).`encoding`Client character encoding. Applied via `SET client_encoding` (Postgres) or `SET NAMES` (MySQL). For Oracle, this is instead baked directly into the DSN's `;charset=` parameter (see `.env.example`) rather than set via a session command.`persistent`Whether to use `PDO::ATTR_PERSISTENT`. See "Design notes" below before enabling this on a busy server.`collation`MySQL-only: session collation, applied via `SET collation_connection`. Unused by Postgres/Oracle.`autocommit`MySQL/Oracle-only: applied via `PDO::ATTR_AUTOCOMMIT`. Unused by Postgres (which doesn't expose this as a settable session attribute the same way).Design notes
------------

[](#design-notes)

- **`PDO::ATTR_ORACLE_NULLS` is applied unconditionally, for every driver.** Despite the name, this isn't Oracle-specific — it converts empty strings to `NULL` on fetch, for whichever driver is active. If your Postgres/MySQL tables rely on distinguishing an empty string from `NULL`, this default may not be what you want; it's currently not configurable per-connection.
- **Connections are lazy.** Nothing connects to the database until `->get()` is actually called, so injecting these classes broadly (even into code paths that never touch the DB) costs nothing.
- **`close()` has no effect on connections referenced elsewhere.** If `->get()`'s return value gets stashed in another variable/property, that reference keeps the connection alive regardless of calling `close()` on the wrapper — PDO has no real `close()` method, only "destroy every reference and let PHP's refcounting close it."
- **Persistent connections (`DB_PERSISTENT=true`)** get pooled/reused by PHP across requests on the same worker rather than truly closing — useful under load, but every long-lived worker holding a persistent connection open adds up against your database's connection limit. Left off by default.

Testing
-------

[](#testing)

```
composer install
composer test
```

The included tests cover connection lifecycle (`get()`/`close()`/`isConnected()`), the `DatabaseConfiguration` env-var parsing, and the closing middleware's success/exception paths against a real PostgreSQL instance. MySQL's session-configuration SQL was independently verified against a live MySQL 8.0 server during development (including the named-timezone table dependency noted above). Oracle's SQL/DSN construction was checked for correctness but not run against a live Oracle instance — see "Oracle setup" above for why.

License
-------

[](#license)

MIT.

###  Health Score

39

—

LowBetter than 84% of packages

Maintenance90

Actively maintained with recent releases

Popularity1

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity51

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

45d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/52b826a6fec331f780189b1319e7be4fedadf89d17a6087b09fbe7dee0308df9?d=identicon)[vuckoo81](/maintainers/vuckoo81)

---

Top Contributors

[![apigopro](https://avatars.githubusercontent.com/u/10329971?v=4)](https://github.com/apigopro "apigopro (5 commits)")

---

Tags

middlewaredatabaseslimmysqlpostgresqlpdooracle

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/apigopro-slim-database/health.svg)

```
[![Health](https://phpackages.com/badges/apigopro-slim-database/health.svg)](https://phpackages.com/packages/apigopro-slim-database)
```

###  Alternatives

[cakephp/cakephp

The CakePHP framework

8.9k20.4M1.9k](/packages/cakephp-cakephp)[typo3/cms

TYPO3 CMS is a free open source Content Management Framework initially created by Kasper Skaarhoj and licensed under GNU/GPL.

1.2k1.9M122](/packages/typo3-cms)[mevdschee/php-crud-api

Single file PHP script that adds a REST API to a SQL database.

3.7k68.1k10](/packages/mevdschee-php-crud-api)[typo3/cms-core

TYPO3 CMS Core

3714.0M5.9k](/packages/typo3-cms-core)[mcp/sdk

Model Context Protocol SDK for Client and Server applications in PHP

1.6k3.0M161](/packages/mcp-sdk)[cakephp/authentication

Authentication plugin for CakePHP

1184.6M126](/packages/cakephp-authentication)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
