Package · cdo

Quickstart

In five minutes you’ll go from credentials to a full round-trip: define a config, open a connection, insert a user and read back its id, update it, query it with a condition, and delete it. Every value is bound — you never concatenate SQL.

Time ~5 minLevel BeginnerPrereqs PHP 8.3+, a database

What you’ll build

A tiny lifecycle over a users table: connect → insert → update → query → delete. We’ll use PostgreSQL, but the only line that changes for MySQL/MariaDB is the config base class — the DML calls are identical.

Assume a table like:

sql
CREATE TABLE users (
  id    SERIAL PRIMARY KEY,
  name  TEXT NOT NULL,
  email TEXT UNIQUE NOT NULL,
  age   INT
);

Step 1 — Define a config

app/Db/AppDb.php
<?php

namespace App\Db;

use Flytachi\Winter\Cdo\Config\PgDbConfig;

class AppDb extends PgDbConfig
{
  public function setUp(): void
  {
      $this->host     = '127.0.0.1';
      $this->port     = 5432;
      $this->database = 'myapp';
      $this->username = 'postgres';
      $this->password = 'secret';
  }
}

Step 2 — Get a connection

php
use Flytachi\Winter\Cdo\ConnectionPool;
use App\Db\AppDb;

$cdo = ConnectionPool::db(AppDb::class);

ConnectionPool builds the config once and opens the PDO socket lazily on first use. Call it again anywhere and you get the same connection back.

Step 3 — Insert and get the id

insert() returns the generated primary key. On PostgreSQL and MariaDB it uses INSERT ... RETURNING; on MySQL it falls back to lastInsertId().

php
$id = $cdo->insert('users', [
  'name'  => 'Alice',
  'email' => 'alice@example.com',
  'age'   => 30,
]);

echo $id; // e.g. 1

NULL columns are skipped

Any key whose value is null is dropped from the INSERT, letting the column take its database default (or auto-generated id). Pass only what you mean to set.

Step 4 — Update with a condition

The third argument is a Qb condition for the WHERE clause. update() returns the number of affected rows.

php
use Flytachi\Winter\Cdo\Qb;

$affected = $cdo->update('users',
  ['name' => 'Alice Smith'],
  Qb::eq('id', $id)
);

echo $affected; // 1

Step 5 — Query with the underlying PDO

CDO is a PDO, so use prepare / query / fetchAll directly. Pair a Qb fragment with its binds:

php
$where = Qb::and(
  Qb::gte('age', 18),
  Qb::like('email', '%@example.com'),
);

$stmt = $cdo->prepare("SELECT * FROM users WHERE " . $where->getQuery());
foreach ($where->getBinds() as $bind) {
  $stmt->bindValue($bind->getName(), $bind->getValue());
}
$stmt->execute();

$rows = $stmt->fetchAll(); // default fetch mode is FETCH_ASSOC

Step 6 — Delete

delete() also takes a Qb and returns the deleted row count:

php
$deleted = $cdo->delete('users', Qb::eq('id', $id));

echo $deleted; // 1

The whole thing

demo.php
<?php

require 'vendor/autoload.php';

use Flytachi\Winter\Cdo\ConnectionPool;
use Flytachi\Winter\Cdo\Qb;
use App\Db\AppDb;

$cdo = ConnectionPool::db(AppDb::class);

// create
$id = $cdo->insert('users', [
  'name'  => 'Alice',
  'email' => 'alice@example.com',
  'age'   => 30,
]);

// update
$cdo->update('users', ['name' => 'Alice Smith'], Qb::eq('id', $id));

// read
$stmt = $cdo->prepare('SELECT * FROM users WHERE ' . Qb::eq('id', $id)->getQuery());
foreach (Qb::eq('id', $id)->getBinds() as $b) {
  $stmt->bindValue($b->getName(), $b->getValue());
}
$stmt->execute();
$user = $stmt->fetch();

// delete
$cdo->delete('users', Qb::eq('id', $id));

Next steps