| name | nette-database |
| description | Provides Nette Database conventions and API reference. Use when working with database entities, Selection API, ActiveRow, SQL queries, or database configuration in Nette. |
Database
Uses Nette Database with MySQL 8.4+ as the backend.
composer require nette/database
For complete Explorer API, see explorer.md. For SQL queries, see sql-way.md.
Database Conventions
- Table names use singular form (e.g.,
usernotusers) - Use TINYINT(1) for booleans
- Use
idfor primary keys - Character encoding should be:
utf8mb4_cs_0900_ai_cifor Czech-language applicationsutf8mb4_0900_ai_cifor English-language applications
- Standard timestamp fields:
created_atDATETIME DEFAULT CURRENT_TIMESTAMP- never use TIMESTAMP for date/time fields
Database Explorer
Extends Nette Database Explorer to automatically map database tables to typed entity classes.
Core benefit: Zero-configuration entity mapping with full IDE support. How it works: Converts table names (snake_case) to entity class names (PascalCase + Row suffix).
Entity Design Strategy
All entities in App\Entity with consistent Row suffix:
producttable →ProductRoworder_itemtable →OrderItemRowvariant_expirationtable →VariantExpirationRow
Why flat: Entities are data structures that cross domain boundaries. ProductRow used in catalog, orders, inventory, and reporting contexts.
Entity Organization
All entities in single App\Entity namespace - avoid domain subdivision:
app/Entity/
├── ProductRow.php ← Core business entities
├── OrderItemRow.php ← Relationship entities
└── StockTransferRow.php ← Operational entities
Entity Documentation Patterns
/**
* @property-read int $id
* @property-read string $title
* @property-read bool $active
* @property-read ?CategoryRow $category ← nullable relationship
* @property-read UserRow $author ← required relationship
*/
final class ProductRow extends Table\ActiveRow
{
}
Documentation rules:
- Document ALL accessible properties (including inherited id)
- Use nullable types for optional foreign keys
- Include relationship properties for IDE navigation
- Match database schema exactly
Entity Relationships in phpDoc
Foreign key patterns:
@property-read ?CategoryRow $categoryfor optional relationships@property-read UserRow $authorfor required relationships@property-read Selection<OrderItemRow> $order_itemsfor back-references
Naming convention: Follow Nette Database relationship naming (foreign key without _id suffix).
When to Use Selection API
Use for:
- Simple filtering and sorting
- Standard CRUD operations
- Queries that benefit from lazy loading
- When you need to chain conditions dynamically
return $this->db->table('product')
->where('active', true)
->where('category_id', $categoryId)
->order('name');
When to Use Raw SQL
Use for:
- Complex analytics and reporting
- Recursive queries (WITH RECURSIVE)
- Performance-critical queries
- Complex joins that are awkward in Selection API
return $this->db->query('
WITH RECURSIVE category_tree AS (...)
SELECT ...
', $params)->fetchAll();
Query Building Patterns
Progressive refinement - start with base methods, refine with conditions:
Always use generic types for Selection returns:
/** @return Selection<ProductRow> */
public function getProducts(): Selection
{
return $this->db->table('product');
}
/** @return Selection<ProductRow> */
public function getActiveProducts(): Selection
{
return $this->getProducts()->where('active', true);
}
/** @return Selection<ProductRow> */
public function getProductsInCategory(int $categoryId): Selection
{
return $this->getActiveProducts()
->where(':product_category.category_id', $categoryId);
}
Benefits: Reusable base queries, clear evolution of filtering logic, easy testing. Benefits: Full IDE support, type safety, clear contracts.
Relationship Navigation
Use colon notation for efficient joins:
// Forward relationship (via foreign key)
->where('category.slug', $categorySlug)
// Back-reference (reverse relationship)
->where(':order_item.quantity >', 1)
// Deep relationships
->where('category.parent.name', 'Root Category')
Fetching Strategies by Use Case
Single optional result: ->fetch()
All results as array: ->fetchAll()
Key-value pairs: ->fetchPairs('key_column', 'value_column')
Structured data: ->fetchAssoc('key_column->')
Count only: ->count('*')
Schema Management
Use direct SQL migrations rather than ORM-style migrations:
- Store schema in
sql/db.sql - Manual migration scripts for schema changes
- Version control captures schema evolution
Database Constraints
Rely on database constraints for data integrity:
- Foreign key constraints for relationships
- Unique constraints for business rules
- Check constraints for data validation
Handle constraint violations in services with meaningful business exceptions.
Anti-Patterns to Avoid
Don't create separate Repository classes - combine data access with business logic in services. Don't use ActiveRecord for complex queries - raw SQL is cleaner for analytics and reporting. Don't fetch more data than needed - use appropriate fetching methods and SELECT only required columns for large datasets.
Error Handling Strategy
Service Level Error Handling
Transform database errors to business exceptions:
try {
$customer->update(['email' => $newEmail]);
} catch (Nette\Database\UniqueConstraintViolationException) {
throw new EmailAlreadyExistsException();
}
Handle at service boundary - presenters should receive business exceptions, not database exceptions.
Database Configuration
database:
dsn: 'mysql:host=127.0.0.1;dbname=myapp'
user: root
password: secret
options:
lazy: true # Connect on first query
charset: utf8mb4 # Default
convertBoolean: true # TINYINT(1) to bool
newDateTime: true # Return DateTimeImmutable
Multiple connections:
database:
main:
dsn: 'mysql:host=127.0.0.1;dbname=app'
user: root
password: secret
logs:
dsn: 'mysql:host=127.0.0.1;dbname=logs'
user: logs
password: secret
autowired: false # Must reference explicitly
Reference non-autowired connection:
services:
- LogService(@database.logs.connection)
Online Documentation
For detailed information, fetch from doc.nette.org:
- Database Core - SQL queries
- Database Explorer - ORM-like API
- Configuration - connection setup