Database

LacePHP’s database layer gives you a simple, consistent API for connecting to your database and running queries—without writing raw PDO code in every controller. It consists of:

  • ConnectionManager: returns a shared PDO instance based on your config(‘database’) settings

  • QueryBuilder: a fluent, chainable builder for SELECT, INSERT, UPDATE and DELETE

Why this matters

  • DRY and safe: One place to configure connection options (DSN, charset, timezone) and error modes.

  • Readable queries: Chainable methods (where(), orderBy(), limit()) read like English.

  • SQL injection protection: Automatic parameter binding keeps you secure.

Configuration

Your database connection is driven by config/lace.php under the database key. For example:

'database' => [
    'driver'   => env('DB_DRIVER', 'sqlite'),
    'sqlite'   => [ 'database_file' => env('DB_FILE', __DIR__.'/../database.sqlite') ],
    'mysql'    => [
        'host'     => env('DB_HOST', '127.0.0.1'),
        'port'     => env('DB_PORT', '3306'),
        'database' => env('DB_DATABASE', 'lace'),
        'username' => env('DB_USERNAME', 'lace'),
        'password' => env('DB_PASSWORD', ''),
        // ... charset, collation, options
    ],
],

ConnectionManager

Use ConnectionManager::getConnection() anywhere to get the shared PDO:

use Lacebox\Sole\Cobble\ConnectionManager;

$pdo = ConnectionManager::getConnection();
$stmt = $pdo->query('SELECT NOW()');
$now  = $stmt->fetchColumn();

LacePHP caches the PDO instance, reusing it if the DSN and credentials haven’t changed. It also sets your application timezone at connection time.

QueryBuilder

For most operations, you will use QueryBuilder. Start by specifying a table:

use Lacebox\Sole\Cobble\QueryBuilder;

// Fetch all rows from 'posts' table
$rows = QueryBuilder::table('posts')->get();

Basic SELECT

// SELECT * FROM posts WHERE user_id = ? ORDER BY created_at DESC LIMIT 5
$recent = QueryBuilder::table('posts')
    ->where('user_id', '=', 42)
    ->orderBy('created_at', 'desc')
    ->limit(5)
    ->get();

Note

Each ? placeholder is automatically bound to the value you pass to where(), protecting you from SQL injection.

First Row

// Fetch one row
$row = QueryBuilder::table('posts')
    ->where('id', '=', $id)
    ->first();

Inserting and Getting ID

// INSERT INTO posts (...) VALUES (...)
// returns the new record's primary key
$newId = QueryBuilder::table('posts')
    ->insertGetId([
        'title'   => 'Hello LacePHP',
        'body'    => 'Welcome aboard!',
        'user_id' => 1,
    ]);

Updating Rows

// UPDATE posts SET title = ? WHERE id = ?
$count = QueryBuilder::table('posts')
    ->where('id', '=', $id)
    ->update(['title' => 'Updated title']);

Deleting Rows

// DELETE FROM posts WHERE id = ?
$deleted = QueryBuilder::table('posts')
    ->where('id', '=', $id)
    ->delete();

As Class Instances

If you prefer working with your Model classes, chain asClass():

use Weave\Models\Post;

/** @var Post[] $posts */
$posts = QueryBuilder::table('posts')
    ->asClass(Post::class)
    ->where('user_id', '=', 42)
    ->get();

// each item in $posts is a Post object

Controller Example

Here is how you might use both Model and QueryBuilder in a controller:

<?php
namespace Weave\Controllers;

use Lacebox\Sole\Cobble\QueryBuilder;
use Weave\Models\Post;
use function kickback;
use function sole_request;

class PostController
{
    public function stats(): string
    {
        // 1. Count all posts by a user
        $userId = sole_request()->input('user_id', 0);
        $count  = QueryBuilder::table('posts')
            ->where('user_id', '=', $userId)
            ->count();  # if count() is implemented

        // 2. Fetch latest 3 as Post objects
        $latest = Post::query()
            ->where('user_id', '=', $userId)
            ->orderBy('created_at', 'desc')
            ->limit(3)
            ->get();

        return kickback()->json([
            'total_posts' => $count,
            'recent'      => $latest,
        ]);
    }
}

Best Practices

  • Use Models (Model::query()) when you want object hydration, mass assignment and relations.

  • Use QueryBuilder directly for simple or dynamic queries (reports, analytics).

  • Always bind parameters via where()—never concatenate user input into SQL.

  • Cache heavy queries with ShoeCacheKnots to improve performance.

By leveraging ConnectionManager and QueryBuilder, junior developers can write clean, secure database code without wrestling with raw PDO or SQL strings.