PHPackages                             centamiv/concrete - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. centamiv/concrete

ActiveLibrary[Database &amp; ORM](/categories/database)

centamiv/concrete
=================

A simple and solid ORM for PHP applications.

0.0.3(1w ago)20[3 issues](https://github.com/centamiv/concrete/issues)MITPHPPHP &gt;=7.4

Since Jun 26Pushed 6mo ago1 watchersCompare

[ Source](https://github.com/centamiv/concrete)[ Packagist](https://packagist.org/packages/centamiv/concrete)[ RSS](/packages/centamiv-concrete/feed)WikiDiscussions main Synced today

READMEChangelog (1)Dependencies (1)Versions (3)Used By (0)

Concrete ORM
============

[](#concrete-orm)

**Concrete** is a simple and solid ORM for PHP applications. It provides an elegant Active Record implementation and a fluent Query Builder to interact with your database.

Installation
------------

[](#installation)

```
composer require centamiv/concrete
```

Configuration
-------------

[](#configuration)

Initialize the database connection once, typically in your bootstrap file.

### MySQL

[](#mysql)

```
use Concrete\Database;
use Concrete\Connection\MysqlDriver;

Database::init(new MysqlDriver(), '127.0.0.1', 'my_database', 'root', 'password');
```

### SQLite

[](#sqlite)

```
use Concrete\Database;
use Concrete\Connection\SqliteDriver;

// Second parameter is the file path (or ':memory:' for in-memory)
Database::init(new SqliteDriver(), '/path/to/database.sqlite');
```

### PostgreSQL

[](#postgresql)

```
use Concrete\Database;
use Concrete\Connection\PostgresDriver;

Database::init(new PostgresDriver(), '127.0.0.1', 'my_database', 'postgres', 'password');
```

### SQL Server

[](#sql-server)

```
use Concrete\Database;
use Concrete\Connection\SqlServerDriver;

Database::init(new SqlServerDriver(), 'localhost', 'my_database', 'sa', 'password');
```

---

Defining Models
---------------

[](#defining-models)

Extend `Concrete\Model` and declare the `TABLE` constant. The primary key defaults to `id`.

```
namespace App\Models;

use Concrete\Model;

class User extends Model
{
    public const TABLE = 'users';

    // Optional: column name constants for type-safe references
    public const COL_ID         = 'id';
    public const COL_NAME       = 'name';
    public const COL_EMAIL      = 'email';
    public const COL_AGE        = 'age';
    public const COL_ACTIVE     = 'active';
    public const COL_ROLE_ID    = 'role_id';
    public const COL_CREATED_AT = 'created_at';
}
```

### Custom &amp; Composite Primary Keys

[](#custom--composite-primary-keys)

```
class Order extends Model
{
    public const TABLE       = 'orders';
    public const PRIMARY_KEY = 'order_code'; // custom single key
}

class OrderItem extends Model
{
    public const TABLE       = 'order_items';
    public const PRIMARY_KEY = ['order_id', 'product_id']; // composite key
}
```

---

CRUD
----

[](#crud)

### Create

[](#create)

```
$user = new User();
$user->set(User::COL_NAME, 'Mario Rossi')
     ->set(User::COL_EMAIL, 'mario@example.com')
     ->save();

echo $user->get(User::COL_ID); // auto-incremented ID
```

### Read

[](#read)

```
// Single primary key
$user = User::find(1);
echo $user?->get(User::COL_NAME);

// Composite primary key
$item = OrderItem::find(['order_id' => 10, 'product_id' => 5]);
```

### Update

[](#update)

```
$user = User::find(1);
$user->set(User::COL_EMAIL, 'new@example.com');
$user->save(); // only dirty attributes are sent to the database
```

### Delete

[](#delete)

```
$user = User::find(1);
$user?->delete();
```

---

Query Builder
-------------

[](#query-builder)

Access the builder via `Model::query()`.

### Filtering

[](#filtering)

#### `where`

[](#where)

```
$users = User::query()
    ->where(User::col(User::COL_ACTIVE), '=', 1)
    ->where(User::col(User::COL_AGE), '>', 18)
    ->get();
```

Supported operators: `=` `!=` `` `` `=` `LIKE` `NOT LIKE` `IN` `NOT IN` `IS` `IS NOT`

#### `whereIn` / `whereNotIn`

[](#wherein--wherenotin)

Pass a plain array or a **subquery Builder** as the second argument.

```
// Array literal
$users = User::query()
    ->whereIn(User::col(User::COL_ROLE_ID), [1, 2, 3])
    ->get();

// Subquery
$users = User::query()
    ->whereIn(
        User::col(User::COL_ID),
        Order::query()
            ->select('user_id')
            ->where(Order::col('status'), '=', 'paid')
    )
    ->get();

// NOT IN
$users = User::query()
    ->whereNotIn(User::col(User::COL_ID), [10, 20])
    ->get();
```

> An empty array in `whereIn` generates `1 = 0` (always false); in `whereNotIn` it generates `1 = 1` (always true).

#### `whereExists` / `whereNotExists`

[](#whereexists--wherenotexists)

```
$users = User::query()
    ->whereExists(
        Order::query()
            ->whereColumn(Order::col('user_id'), '=', User::col(User::COL_ID))
            ->where(Order::col('status'), '=', 'paid')
    )
    ->get();
```

#### `whereColumn`

[](#wherecolumn)

Compare two column identifiers without parameterization — used for **correlated subqueries**.

```
Order::query()->whereColumn(Order::col('user_id'), '=', User::col(User::COL_ID));
```

Supported operators: `=` `!=` `` `` `=`

---

### Ordering

[](#ordering)

```
$users = User::query()
    ->orderBy(User::col(User::COL_CREATED_AT), 'DESC')
    ->get();
```

---

### Joins

[](#joins)

```
$users = User::query()
    ->select(User::col('*'), Role::colAs('name', 'role_name'))
    ->join(Role::TABLE, User::col(User::COL_ROLE_ID), '=', Role::col('id'))
    ->get();

// Left / right join
User::query()->leftJoin(Role::TABLE, User::col(User::COL_ROLE_ID), '=', Role::col('id'));
User::query()->rightJoin(Role::TABLE, User::col(User::COL_ROLE_ID), '=', Role::col('id'));
```

---

### Conditional Selection — `CASE`

[](#conditional-selection--case)

Build `CASE WHEN … THEN … ELSE … END` expressions with `When`. Pass them directly to `select()`.

```
use Concrete\Query\When;

$users = User::query()
    ->select(
        User::col(User::COL_ID),
        User::col(User::COL_NAME),
        When::make()
            ->when(User::col(User::COL_ACTIVE), '=', 1)->then('Active')
            ->when(User::col(User::COL_ACTIVE), '=', 0)->then('Inactive')
            ->else('Unknown')
            ->as('status_label'),
        When::make()
            ->when(User::col(User::COL_AGE), '>=', 18)->then(1)
            ->else(0)
            ->as('is_adult')
    )
    ->get();
```

**Result type rules for `then()` / `else()`:**

PHP typeSQL`int` / `float`embedded literal (`1`, `3.14`)`null``NULL` literal`string`bound as PDO named parameter---

### Scalar Subqueries in `select()`

[](#scalar-subqueries-in-select)

Use `Subquery` to embed a correlated scalar subquery as a column.

```
use Concrete\Query\Subquery;

$users = User::query()
    ->select(
        User::col(User::COL_ID),
        User::col(User::COL_NAME),
        Subquery::make(
            Order::query()
                ->select('COUNT(*)')
                ->whereColumn(Order::col('user_id'), '=', User::col(User::COL_ID))
        )->as('order_count')
    )
    ->get();
// → SELECT users.id, users.name,
//     (SELECT COUNT(*) FROM orders WHERE orders.user_id = users.id) AS order_count
//   FROM users
```

---

### UNION

[](#union)

Combine the results of two or more queries with `union()` (distinct rows) or `unionAll()` (all rows including duplicates).

```
$activeUsers = User::query()
    ->select(User::col(User::COL_ID), User::col(User::COL_NAME))
    ->where(User::col(User::COL_ACTIVE), '=', 1);

$adminUsers = User::query()
    ->select(User::col(User::COL_ID), User::col(User::COL_NAME))
    ->where(User::col(User::COL_ROLE_ID), '=', 99);

// Distinct rows
$result = $activeUsers
    ->union($adminUsers)
    ->orderBy(User::col(User::COL_NAME), 'ASC')
    ->take(20)
    ->get();

// Including duplicates
$result = $activeUsers->unionAll($adminUsers)->get();
```

`orderBy()`, `take()`, and `skip()` called on the **outermost** builder apply to the whole union result.

---

### Limiting Results

[](#limiting-results)

```
$users = User::query()
    ->take(10)  // LIMIT 10
    ->skip(20)  // OFFSET 20
    ->get();
```

---

### Aggregates &amp; Helpers

[](#aggregates--helpers)

```
// Total count
$total = User::query()->where(User::col(User::COL_ACTIVE), '=', 1)->count();

// First matching record
$user = User::query()->where(User::col(User::COL_EMAIL), '=', 'mario@example.com')->first();

// Check existence
$exists = User::query()->where(User::col(User::COL_AGE), '
