Nette Database

repository·master·Indexed 19 days ago

https://github.com/nette/database

A powerful database access layer for PHP providing a wrapper around PDO. It features a Core layer for direct SQL execution via Nette\Database\Connection and an Explorer layer for optimized, relationship-aware data fetching using ActiveRow objects to avoid the N+1 query problem. Supports PHP 8.3 to 8.5 and includes comprehensive tools for transaction management, type conversion, and detailed exception handling for connection issues and constraint violations.

Tokens
8.7K
Snippets
35
Records
40
Agent score
68%

What's inside nette-database

  1. How Nette Database Explorer works with relationships

    master

    The Nette\Database\Explorer layer optimizes data fetching by avoiding the 'N+1 query problem'. Instead of running a new query for every single row in a loop, it fetches related data in bulk using IN (...) clauses.

    When you access a relationship (e.g., $book->author), the Explorer identifies the necessary IDs from the current collection and executes a single query to fetch all related records at once. If caching is enabled (default), it further optimizes by only selecting the specific columns that have been accessed.

    // Example of efficient relationship fetching
    $books = $explorer->table('book');
    
    foreach ($books as $book) {
        // Accessing 1:N relationship (author)
        echo $book->author->name;
    
        // Accessing M:N relationship via a junction table
        foreach ($book->related('book_tag') as $bookTag) {
            echo $bookTag->tag->name;
        }
    }
  2. Run tests for Nette Database

    master

    To run tests against the default SQLite setup:

    composer run tester

    To test against MySQL or PostgreSQL using Docker:

    docker compose up -d
    cp tests/databases.docker.ini tests/Database/databases.ini
    composer run tester
  3. Use GroupedSelection for grouped table queries

    master

    A GroupedSelection represents a table that has been filtered and grouped by a referencing table. It is typically used when you want to work with a subset of rows that all belong to a specific parent record (the "active group").

    Key behaviors:

    • Automatic Column Inclusion: When calling select() or order(), the grouping column is automatically prepended to the query to ensure correct grouping and index utilization.
    • Grouped Operations: You can perform insert(), update(), and delete() operations that are automatically scoped to the current active group using the grouping column.
    • Aggregations: It provides an aggregation() method to calculate aggregate values (like SUM, COUNT, AVG) specifically for the current group.
    • Data Refreshing: Use refreshData() to invalidate cached data and force a reload from the database on the next access.
  4. Inspect database schema with Structure

    master

    The Nette\Database\Structure class provides metadata about your database schema, including tables, columns, primary keys, and relationships. It uses a caching mechanism to avoid expensive database queries on every request.

    Note: This class is marked as @internal in the source, meaning it is intended for use by the Nette Framework itself, but it can be used by developers for schema inspection tasks.

  5. Iterate over a database Result set

    master

    A Nette\Database\Result object can be iterated directly using a foreach loop. Each iteration yields a Nette\Database\Row object.

    Important: The result set is a single-pass iterator. Once you have iterated through the results (e.g., via foreach or fetchAll()), attempting to iterate again will throw a Nette\InvalidStateException.

    foreach ($result as $row) {
        echo $row->column_name;
    }
  6. Manage database rows with ActiveRow

    master

    An ActiveRow represents a single row from a database table. It provides an object-oriented interface to access column data, navigate relationships, and perform CRUD operations.

    Key behaviors:

    • Read-only access: You cannot use $row->column = 'value' because ActiveRow is read-only. Instead, use the update() method to persist changes.
    • Array-like access: You can access columns as properties (e.g., $row->name) or via array syntax (e.g., $row['name']).
    • Relationship navigation: It supports traversing database relationships using ref() and related().
    • Automatic refreshing: If you access a column that wasn't part of the initial selection, ActiveRow can automatically refetch the row from the database to ensure data integrity.
    // Assuming $row is an instance of ActiveRow
    $name = $row->name; // Access column
    $id = $row->getPrimary(); // Get primary key
    
    // Updating data
    $row->update(['name' => 'New Name']);
    
    // Deleting the row
    $row->delete();
  7. How Selection and ActiveRow handle relationships

    master

    The Selection class provides methods to navigate database relationships (belongs-to and has-many) using the Explorer's conventions.

    Belongs-to (Referenced Rows)

    Use getReferencedTable(ActiveRow $row, ?string $table = null, ?string $column = null) to find the parent row of a relationship. If $table or $column are not provided, the Explorer uses defined conventions to identify the relationship.

    Has-many (Referencing Rows)

    Use getReferencingTable(string $table, ?string $column = null, int|string|null $active = null) to get a GroupedSelection of child rows. This is useful for retrieving all items that belong to a specific parent.

    Note: These methods rely on the Explorer having correctly configured database conventions.

    // Assuming a 'book' belongs to an 'author'
    $book = $explorer->table('book')->get(1);
    $author = $explorer->table('book')->getReferencedTable($book, 'author');
    
    // Assuming an 'author' has many 'books'
    $author = $explorer->table('author')->get(1);
    $books = $explorer->table('author')->getReferencingTable($author, 'book');
  8. Set up local database environments with Docker Compose

    master

    The repository provides a docker-compose.yml file to quickly spin up various database engines (MySQL, PostgreSQL, and MSSQL) for testing or development purposes. Each service is configured with specific ports and environment variables to ensure a consistent testing environment.

    To use these services, run:

    docker-compose up -d
    services:
        mysql:
            image: mysql:8.0
            ports:
                - "3307:3306"
            environment:
                MYSQL_ROOT_PASSWORD: root
                MYSQL_DATABASE: nette_test
    
        postgres96:
            image: postgres:9.6
            ports:
                - "5433:5432"
            environment:
                POSTGRES_USER: postgres
                POSTGRES_PASSWORD: postgres
                POSTGRES_DB: nette_test
    
        postgres13:
            image: postgres:13
            ports:
                - "5434:5432"
            environment:
                POSTGRES_USER: postgres
                POSTGRES_PASSWORD: postgres
                POSTGRES_DB: nette_test
    
        mssql:
            image: mcr.microsoft.com/mssql/server:2022-latest
            ports:
                - "1434:1433"
            environment:
                ACCEPT_EULA: "Y"
                SA_PASSWORD: "YourStrong!Passw0rd"
                MSSQL_PID: Developer
  9. Use Nette Database Core for direct queries

    master

    The Nette\Database\Connection class (and the Explorer wrapper) allows you to execute SQL queries using the query method. You can pass arrays or DateTime objects as parameters, and the library handles the binding. It also supports file resources for inserting binary data.

    $database = new Nette\Database\Explorer($dsn, $user, $password);
    
    // Insert with array parameters and DateTime
    $database->query('INSERT INTO users', [
    	'name' => 'Jim',
    	'created' => new DateTime,
    	'avatar' => fopen('image.gif', 'r'),
    ]);
    
    // Update using named or positional parameters
    $database->query('UPDATE users SET ? WHERE id=?', $data, $id);
    
    // Select and dump results
    $database->query('SELECT * FROM categories WHERE id=?', 123)->dump();
  10. Fetch data using Nette Database Explorer

    master

    Use the table() method on a Nette\Database\Explorer instance to start a selection. You can iterate over the selection to get ActiveRow instances, or use get() to retrieve a single specific row by its identifier.

    $explorer = new Nette\Database\Explorer($dsn, $user, $password);
    
    // Iterate over all rows in a table
    $books = $explorer->table('book');
    foreach ($books as $book) {
    	// Access columns as properties
    	echo $book->title;
    }
    
    // Get a single specific row
    $book = $explorer->table('book')->get(2); // returns book with id 2
    echo $book->title;
  11. Fetch rows from a Result set

    master

    The Result class provides several methods to retrieve data depending on the desired format:

    • fetch(): Returns the next row as a Nette\Database\Row object (allows property access like $row->name). Returns null when no more rows exist.
    • fetchAssoc(?string $path = null): Returns the next row as an associative array. If a $path is provided, it restructures all rows into an associative array using that path as the key.
    • fetchList(): Returns the next row as an indexed array (list).
    • fetchAll(): Returns all remaining rows as an array of Row objects. This caches the results internally.
    • fetchPairs(...): Returns all rows as an associative array. You can specify a key column, a value column, or a callback to define the key-value pairs.
    // Fetch as Row objects
    while ($row = $result->fetch()) {
        echo $row->id;
    }
    
    // Fetch all as an associative array of pairs (e.g., id => name)
    $pairs = $result->fetchPairs('id', 'name');
    
    // Fetch all as an array of Row objects
    $rows = $result->fetchAll();