npx claudepluginhub nette/claude-code --plugin netteThis skill uses the workspace's default tool permissions.
Uses Nette Database, typically with MySQL, PostgreSQL or SQLite as the backend.
Conducts multi-round deep research on GitHub repos via API and web searches, generating markdown reports with executive summaries, timelines, metrics, and Mermaid diagrams.
Dynamically discovers and combines enabled skills into cohesive, unexpected delightful experiences like interactive HTML or themed artifacts. Activates on 'surprise me', inspiration, or boredom cues.
Generates images from structured JSON prompts via Python script execution. Supports reference images and aspect ratios for characters, scenes, products, visuals.
Uses Nette Database, typically with MySQL, PostgreSQL or SQLite as the backend.
composer require nette/database
See the Explorer API reference for the full ActiveRow/Selection API. See the SQL query reference for direct SQL queries.
user not users)id for primary keysutf8mb4 with appropriate collation (e.g. utf8mb4_0900_ai_ci, or utf8mb4_cs_0900_ai_ci for Czech)created_at DATETIME DEFAULT CURRENT_TIMESTAMPExtends 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).
All entities in App\Entity with consistent Row suffix:
product table → ProductRoworder_item table → OrderItemRowvariant_expiration table → VariantExpirationRowWhy flat: Entities are data structures that cross domain boundaries. A ProductRow might be used in catalog, orders, inventory, and reporting contexts. Subdividing entities by domain forces you to either pick one arbitrary "home" domain or duplicate references.
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
use Nette\Database\Table;
/**
* @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:
Foreign key patterns:
@property-read ?CategoryRow $category for optional relationships@property-read UserRow $author for required relationships@property-read Selection<OrderItemRow> $order_items for back-referencesNaming convention: Follow Nette Database relationship naming (foreign key without _id suffix).
Use for:
return $this->db->table('product')
->where('active', true)
->where('category_id', $categoryId)
->order('name');
Use for:
return $this->db->query('
WITH RECURSIVE category_tree AS (...)
SELECT ...
', $params)->fetchAll();
Build queries by progressive refinement – start with a base method, then add 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. Full IDE support, type safety, clear contracts.
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')
Single optional result: ->fetch()
All results as array: ->fetchAll()
Key-value pairs: ->fetchPairs('key_column', 'value_column')
Single scalar value: ->fetchField() (first column of first row)
Count only: ->count('*')
Structured data with fetchAssoc:
// Key by column value
$byId = $db->table('product')->fetchAssoc('id');
// [1 => ProductRow, 2 => ProductRow, ...]
// Group by column
$byCategory = $db->table('product')->fetchAssoc('category_id[]');
// [5 => [ProductRow, ProductRow], 8 => [ProductRow, ...]]
// Nested grouping
$nested = $db->table('product')->fetchAssoc('category_id|active');
// [5 => [true => ProductRow, false => ProductRow], ...]
The path string uses [] for array grouping, | for nested keys, and = to extract a single value.
Use direct SQL migrations rather than ORM-style migrations – store schema in sql/db.sql with manual migration scripts. Rely on database constraints (foreign keys, unique, check) for data integrity and handle constraint violations in services with meaningful business exceptions.
Wrap multi-step writes in transactions to ensure consistency:
$this->db->transaction(function () use ($data, $items) {
$order = $this->db->table('order')->insert($data);
foreach ($items as $item) {
$order->related('order_item')->insert($item);
}
});
The callback approach automatically commits on success and rolls back on exception.
Don't create separate Repository classes – in Nette, services combine data access with business logic. A separate repository layer adds indirection without benefit because Nette Database Explorer already provides a clean query API. The service IS the repository.
Don't use Selection API for complex queries – raw SQL is cleaner for analytics, reporting, and recursive queries. Selection API excels at CRUD and simple filtering; forcing complex JOINs through it creates hard-to-read code.
Don't fetch more data than needed – use appropriate fetching methods (fetchPairs for dropdowns, count('*') for pagination) and SELECT only required columns for large datasets.
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. This keeps the UI layer independent of the storage layer and produces meaningful error messages.
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)
For detailed information, use WebFetch on these URLs: