Documentation menu
On this page
Components · v1.x
Database
The Database component gives PDO connections to MySQL / MariaDB, PostgreSQL and SQLite and runs SQL queries with typed parameters and nested transactions. It is not an ORM: entities, repositories, migrations and the query builder belong to the ORM package (see the ORM documentation).
Requirements
It requires the pdo extension and the driver of the database:
| Driver | Extension | URL schemes |
|---|---|---|
| MySQL / MariaDB | pdo_mysql |
mysql://, mariadb:// |
| PostgreSQL | pdo_pgsql |
postgresql://, postgres://, pgsql:// |
| SQLite | pdo_sqlite |
sqlite:// |
Configuration
config/framework/database.yaml:
default: default
connections:
default:
url: '%env(DATABASE_URL)%'
.env:
# DATABASE_URL="mysql://user:password@127.0.0.1:3306/app?charset=utf8mb4"
# DATABASE_URL="postgresql://user:password@127.0.0.1:5432/app?charset=utf8"
DATABASE_URL="sqlite:///%kernel.root_path%/var/data.db"
A connection is configured with a url, with separate parameters, or both: the parameters override the parts of the URL.
default: default
connections:
default:
url: '%env(DATABASE_URL)%'
analytics:
driver: pgsql
host: '%env(ANALYTICS_HOST)%'
port: 5432
dbname: analytics
user: '%env(ANALYTICS_USER)%'
password: '%env(ANALYTICS_PASSWORD)%'
charset: utf8
cache:
driver: sqlite
path: var/cache.db
options:
ATTR_TIMEOUT: 5
| Parameter | Description |
|---|---|
url |
driver://user:password@host:port/dbname?charset=...; special characters of the user and the password are URL-encoded (@ is %40) |
driver |
mysql (or mariadb), pgsql (or postgresql), sqlite |
host, port, dbname, user, password |
server connection (MySQL and PostgreSQL) |
unix_socket |
socket used instead of host and port |
charset |
utf8mb4 by default for MySQL, utf8 for PostgreSQL |
collation |
MySQL collation used by database:create |
sslmode |
PostgreSQL SSL mode |
path |
SQLite file, relative to the project root or absolute; sqlite:///:memory: for an in-memory database |
foreign_keys |
SQLite: set to false to disable PRAGMA foreign_keys = ON |
options |
PDO attributes, by name (ATTR_TIMEOUT) or number |
With a URL, sqlite:///var/data.db is relative to the project root and sqlite:///%kernel.root_path%/var/data.db is absolute.
The connections are opened on the first query. PDO throws exceptions and fetches associative arrays by default.
Queries
In a controller, getConnection() returns the default connection, getConnection('analytics') another one:
#[Route('/posts/{id}', name: 'post_show')]
public function show(int $id): Response
{
$post = $this->getConnection()->fetchAssociative('SELECT * FROM post WHERE id = :id', ['id' => $id]);
if ($post === null) {
throw $this->createNotFoundException('Post not found.');
}
return $this->render('post/show.php', ['post' => $post]);
}
In a service, inject ConnectionInterface (default connection), a named connection with #[Autowire(service: 'database.connection.<name>')], or DatabaseInterface to use all the connections:
<?php
declare(strict_types=1);
namespace App\Repository;
use NeoPHP\Component\Container\Attribute\Autowire;
use NeoPHP\Component\Database\Contract\ConnectionInterface;
class PostRepository
{
public function __construct(
protected ConnectionInterface $connection,
#[Autowire(service: 'database.connection.analytics')] protected ConnectionInterface $analytics,
) {
}
public function findPublished(): array
{
return $this->connection->fetchAllAssociative('SELECT * FROM post WHERE published = ? ORDER BY id DESC', [true]);
}
}
| Method | Returns |
|---|---|
executeQuery($sql, $params) |
a Result |
executeStatement($sql, $params) |
the number of affected rows |
fetchAssociative() / fetchNumeric() / fetchObject() |
the first row, or null |
fetchOne() |
the first column of the first row, or null |
fetchAllAssociative() / fetchAllNumeric() / fetchAllObjects() |
all the rows |
fetchFirstColumn() |
the first column of every row |
fetchAllKeyValue() |
[first column => second column] |
fetchAllAssociativeIndexed() |
the rows indexed by their first column |
iterateAssociative() |
a generator, row by row |
insert($table, $data) |
the number of inserted rows |
update($table, $data, $criteria) |
the number of updated rows |
delete($table, $criteria) |
the number of deleted rows |
lastInsertId(?$sequence) |
the last inserted id (PostgreSQL: the sequence name, post_id_seq) |
quote($value) / quoteIdentifier($name) |
a quoted value / identifier |
getPdo() |
the PDO instance |
$connection->insert('post', ['title' => 'Hello', 'status' => Status::Published, 'created_at' => new DateTimeImmutable()]);
$id = $connection->lastInsertId();
$connection->update('post', ['title' => 'Hello world'], ['id' => $id]);
$connection->delete('post', ['status' => [Status::Draft, Status::Archived]]);
update() and delete() require criteria: a null criterion becomes IS NULL, an array becomes IN (...).
The connection also provides getName(), getDriver(), getParams(), getDatabase(), isConnected(), close(), getServerVersion(), inTransaction() and getTransactionLevel(). fetchObject() and fetchAllObjects() accept a class as third argument (stdClass by default).
Result
executeQuery() returns a NeoPHP\Component\Database\Result\Result, iterable row by row:
$result = $connection->executeQuery('SELECT id, title FROM post WHERE views > ?', [100]);
foreach ($result as $row) {
echo $row['title'];
}
| Method | Description |
|---|---|
fetchAssociative(), fetchNumeric(), fetchObject($class, $arguments), fetchOne() |
next row or null |
fetchAllAssociative(), fetchAllNumeric(), fetchAllObjects($class, $arguments), fetchFirstColumn(), fetchAllKeyValue(), fetchAllAssociativeIndexed() |
all the rows |
iterateAssociative(), iterateNumeric() |
generators |
rowCount(), columnCount(), getColumnNames() |
information |
free(), getStatement() |
closes the cursor, returns the PDOStatement |
Database service
NeoPHP\Component\Database\Contract\DatabaseInterface (implemented by DatabaseManager) manages the connections:
| Method | Description |
|---|---|
connection($name = null) |
a connection (the default one without name) |
hasConnection($name), getConnectionNames(), getDefaultConnectionName(), getConnections() |
connections |
addConnection($name, $config) |
adds a connection from a URL or an array of parameters |
getParams($name = null) |
resolved parameters of a connection |
getDriver($name), addDriver($name, $driver) |
drivers (DriverInterface object or class) |
close($name = null) |
closes one or all the connections |
A driver implements Contract\DriverInterface (getName(), getExtension(), isAvailable(), getDsn(), connect(), quoteIdentifier(), databaseExists(), createDatabase(), dropDatabase()), usually by extending Contract\AbstractDriver. The framework ships MysqlDriver, PgsqlDriver and SqliteDriver.
Parameters
Parameters are positional (?) or named (:name), not both in the same query. The values are bound with their type: int, bool, null, float, backed enums (their value), DateTimeInterface (Y-m-d H:i:s), Stringable.
An array is expanded, for IN clauses:
$connection->fetchAllAssociative('SELECT * FROM post WHERE id IN (:ids)', ['ids' => [1, 2, 3]]);
$connection->fetchAllAssociative('SELECT * FROM post WHERE id IN (?) AND views > ?', [[1, 2, 3], 10]);
An empty array becomes NULL, so IN (NULL) matches nothing.
Transactions
$connection->transactional(function (ConnectionInterface $connection): void {
$connection->insert('order', ['reference' => 'A-001']);
$connection->insert('order_line', ['order_id' => $connection->lastInsertId(), 'quantity' => 2]);
});
transactional() commits and returns the value of the callback, or rolls back and rethrows the exception. beginTransaction(), commit() and rollBack() can be called directly. Nested transactions use savepoints.
Commands
| Command | Alias | Description |
|---|---|---|
database:create [-c connection] [--if-not-exists] |
db:create |
creates the database of the connection |
database:drop [-c connection] [--if-exists] [--force] |
db:drop |
drops the database; asks for a confirmation, --force skips it (required with --no-interaction) |
database:query <sql> [-c connection] |
db:query |
runs a SQL query and displays the result; the query is asked when missing |
php bin/neo database:create --if-not-exists
php bin/neo db:query "SELECT * FROM post LIMIT 5"
php bin/neo database:drop --force --if-exists
Errors
| Exception | Thrown when |
|---|---|
ConnectionException |
the connection fails, or the PDO extension of the driver is missing |
QueryException |
a query fails; getSql() and getParams() return the query and its parameters |
DatabaseException |
the configuration is invalid, a connection does not exist... (parent of the two others) |
They extend FrameworkException (see the Exception documentation).
Profiler
Every connection accepts a query logger (Contract\QueryLoggerInterface). None is attached by default: without logger, queries run exactly as before.
use NeoPHP\Component\Database\Logger\QueryLogger;
$logger = new QueryLogger();
$connection->setQueryLogger($logger);
$database->setQueryLogger($logger);
$logger->getQueries();
Each entry holds the SQL, the parameters, the duration in ms, the connection name, the affected rows (statements without result set), the caller (first file outside the framework) and the error message when the query failed. BEGIN, COMMIT, ROLLBACK and savepoints are logged with the transaction type. QueryLogger keeps at most 1000 queries and counts the others.
DatabaseProvider registers a QueryLogger singleton (database.query_logger) and attaches it to the connections only when the Web Profiler is enabled (web_profiler.config with enabled: true), checked when the first connection is created. When a stopwatch service exists, each query is also added to the timeline with the database category.
The Helper/Profiler/DatabaseProfiler element adds a Database item to the toolbar (number of queries, warning on duplicated queries or more than 50 queries, danger on error) and a panel: queries, time, duplicated queries (same SQL several times: possible N+1), slow queries (more than 100 ms), per-connection statistics and errors.
Changelog
- v1.25.1 — profiler integration:
QueryLoggerInterface,QueryLogger,setQueryLogger()on connections and on the manager, transaction logging, Stopwatch events, Database panel of the Web Profiler. - v1.17.0 —
database:queryasks for the query when it is missing. - v1.15.0 — commands rewritten for the new console (
db:*aliases). - v1.11.0 — Database component: connections by URL or parameters, several connections, query and fetch methods,
insert()/update()/delete(), expanded array parameters, nested transactions,getConnection()in controllers,database:create,database:dropanddatabase:querycommands.