nette/database
Nette Database is a lightweight PHP database layer with a safe, fluent SQL builder, easy connection and result handling, and handy helpers for queries and transactions. Designed to work smoothly with the Nette framework while usable standalone.
## Getting Started
### Minimal Setup
1. **Installation**:
```bash
composer require nette/database
Add to your config/neon (or Laravel config/database.php):
services:
database.connection:
factory: Nette\Database\Connection
arguments:
dsn: 'mysql:host=localhost;dbname=test'
username: 'user'
password: 'pass'
First Query:
use Nette\Database\Connection;
$connection = app('database.connection');
$users = $connection->table('users')->fetchAll();
Basic CRUD:
// Insert
$connection->table('users')->insert(['name' => 'John', 'email' => 'john@example.com']);
// Update
$connection->table('users')->where('id', 1)->update(['name' => 'Jane']);
// Delete
$connection->table('users')->where('id', 1)->delete();
Fluent Query Building:
$activeUsers = $connection
->table('users')
->where('active', true)
->order('created_at DESC')
->limit(10)
->fetchAll();
ActiveRow for Single Records:
$user = $connection->table('users')->get(1);
$user->name = 'Updated Name';
$user->update();
Joins:
$postsWithAuthors = $connection
->table('posts')
->join('users', 'posts.author_id = users.id')
->fetchAll();
Leverage Explorer (formerly Context) for table-agnostic operations:
class UserRepository
{
public function __construct(private Connection $connection) {}
public function findByEmail(string $email): ?ActiveRow
{
return $this->connection
->table('users')
->where('email', $email)
->fetch();
}
}
Use transaction() for atomic operations:
$this->connection->transaction(function (Connection $conn) {
$conn->table('orders')->insert(['user_id' => 1, 'amount' => 100]);
$conn->table('user_balance')->where('user_id', 1)->update(['balance' => 900]);
});
Combine Selection and SqlBuilder for complex queries:
$selection = $this->connection->table('orders');
$selection->where('status', 'pending')
->orWhere('created_at > ?', new DateTime('-7 days'));
$results = $selection->fetchAll();
Inspect database structure dynamically:
$table = $this->connection->getStructure()->getTable('users');
foreach ($table->columns as $column) {
echo $column->name . ' (' . $column->type . ')' . PHP_EOL;
}
Register the connection in AppServiceProvider:
public function register()
{
$this->app->singleton('database.connection', function ($app) {
return new Connection(
$app['config']['database.connections.mysql']['dsn'],
$app['config']['database.connections.mysql']['username'],
$app['config']['database.connections.mysql']['password']
);
});
}
Use nette/database for complex queries while keeping Eloquent for ORM:
// Complex query with nette/database
$rawResults = $this->connection
->table('users')
->select('id, name, COUNT(orders.id) as order_count')
->leftJoin('orders', 'users.id = orders.user_id')
->group('users.id')
->fetchAll();
// Convert to Eloquent collection
$collection = collect($rawResults)->map(function ($item) {
return (new User())->fill($item);
});
Cache frequent queries using Laravel's cache:
$cacheKey = 'active_users_' . now()->format('Y-m-d');
$users = cache($cacheKey, function () {
return $this->connection->table('users')->where('active', true)->fetchAll();
}, now()->addHour());
Listen to ConnectionLostException for reconnection logic:
$this->connection->getConnection()->getPdo()->setAttribute(
PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION
);
$this->connection->getConnection()->on('exception', function ($e) {
if ($e instanceof ConnectionLostException) {
// Reconnect logic
}
});
Extend Row behavior:
$connection->getConnection()->setRowNormalizer(function (array $row) {
$row['created_at'] = Carbon::parse($row['created_at']);
return $row;
});
Efficient inserts/updates:
// Bulk insert
$this->connection->table('logs')->insert([
['user_id' => 1, 'message' => 'Login', 'created_at' => now()],
['user_id' => 2, 'message' => 'Logout', 'created_at' => now()],
]);
// Bulk update
$this->connection->table('users')->where('active', false)->update([
'last_seen' => now(),
'status' => 'inactive',
]);
Use SqlBuilder for raw SQL while maintaining parameter binding:
$sql = $this->connection->getSqlBuilder()
->select('u.*', 'COUNT(o.id) as order_count')
->from('users', 'u')
->leftJoin('orders', 'o.user_id = u.id')
->where('u.active', true)
->group('u.id');
$results = $this->connection->query($sql)->fetchAll();
Leverage Explorer for cross-table operations:
$explorer = $this->connection->getExplorer();
$users = $explorer->table('users');
$orders = $explorer->table('orders');
// Complex join with aggregations
$report = $explorer->table('users')
->select('users.id', 'users.name', 'COUNT(orders.id) as orders')
->leftJoin('orders', 'users.id = orders.user_id')
->group('users.id')
->fetchAll();
IN Clauses: Direct arrays in IN() bypass binding. Use named parameters:
// ❌ Avoid (bypasses binding)
$selection->where('id IN ?', [1, 2, 3]);
// ✅ Use named parameters
$selection->where('id IN (:ids)', ['ids' => [1, 2, 3]]);
NULL Values: Explicitly pass null for NULL checks:
$selection->where('column IS NULL'); // Correct
$selection->where('column', null); // Incorrect (becomes 'column = NULL')
$this->connection->transaction(function ($conn) {
$conn->transaction(function ($nestedConn) { // ❌ Won't work as expected
$nestedConn->table('x')->insert(...);
});
});
Use savepoint for nested atomicity or flatten transactions.LIMIT must come after ORDER BY (enforced by applyLimit).OFFSET for large datasets (uses ROW_NUMBER() internally).getIdentity() for auto-increment columns:
$id = $this->connection->getDriver()->getIdentity();
Lazy Loading: ActiveRow properties trigger queries on access:
$user = $this->connection->table('users')->get(1);
$user->posts; // ❌ Triggers N+1 query unless eager-loaded
Use with() for eager loading:
$user = $this->connection->table('users')->with('posts')->get(1);
Detached Rows: Modified rows detach from the database:
$user = $this->connection->table('users')->get(1);
$
How can I help you explore Laravel packages today?