PHPackages                             rougin/ezekiel - 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. rougin/ezekiel

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

rougin/ezekiel
==============

An expressive SQL query builder in PHP.

v0.1.3(1w ago)15992MITPHPPHP &gt;=5.3.0CI passing

Since Sep 22Pushed 1mo ago1 watchersCompare

[ Source](https://github.com/rougin/ezekiel)[ Packagist](https://packagist.org/packages/rougin/ezekiel)[ Docs](https://roug.in/ezekiel/)[ RSS](/packages/rougin-ezekiel/feed)WikiDiscussions master Synced today

READMEChangelog (4)Dependencies (4)Versions (7)Used By (2)

Ezekiel
=======

[](#ezekiel)

[![Latest Version on Packagist](https://camo.githubusercontent.com/4d8b94b2cb80ee4e359b562780f6d2302da42a7d6d33f70930c19e82e3069f3e/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f726f7567696e2f657a656b69656c2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/rougin/ezekiel)[![Software License](https://camo.githubusercontent.com/55c0218c8f8009f06ad4ddae837ddd05301481fcf0dff8e0ed9dadda8780713e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d627269676874677265656e2e7376673f7374796c653d666c61742d737175617265)](https://github.com/rougin/ezekiel/blob/master/LICENSE.md)[![Build Status](https://camo.githubusercontent.com/cf83250a3269e773586daeef9950282fc13eb7097cf0219aeac0516eb9ec2e63/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f726f7567696e2f657a656b69656c2f6275696c642e796d6c3f7374796c653d666c61742d737175617265)](https://github.com/rougin/ezekiel/actions)[![Coverage Status](https://camo.githubusercontent.com/e851b06e26048a77052b7b597d5e5c0ebd5b516cfe4daa1bdc21634514840d4a/68747470733a2f2f696d672e736869656c64732e696f2f636f6465636f762f632f6769746875622f726f7567696e2f657a656b69656c3f7374796c653d666c61742d737175617265)](https://app.codecov.io/gh/rougin/ezekiel)[![Total Downloads](https://camo.githubusercontent.com/41523b0e508b05393c172cc6e17d9df85e87e1dfd47e4280a045d609f4f1fe9e/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f726f7567696e2f657a656b69656c2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/rougin/ezekiel)

An expressive SQL query builder in PHP. This package is previously known as [Windstorm](https://github.com/rougin/ezekiel/tree/c95c77506087db19033997d1e752ce01c9294056).

Why
---

[](#why)

I tried to unify [Doctrine](https://www.doctrine-project.org/) and [Eloquent](https://laravel.com/docs/5.0/eloquent) into a single interface for them to be swappable. Unfortunately the implementation is not possible because of the different core design patterns ([data mapper](https://en.wikipedia.org/wiki/Data_mapper_pattern) for Doctrine while [active record](https://en.wikipedia.org/wiki/Active_record_pattern) for Eloquent). I realized later that the one thing common for both is their query builder and it was also common on all existing ORM packages and SQL query builders.

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

[](#installation)

Install `Ezekiel` through [Composer](https://getcomposer.org/):

```
$ composer require rougin/ezekiel
```

Basic usage
-----------

[](#basic-usage)

Use the `Query` class to create SQL queries:

```
use Rougin\Ezekiel\Query;

$query = (new Query)
    ->select(array('u.id', 'u.name', 'p.name as position'))
    ->from('users u')
    ->leftJoin('positions p')->on('p.id', 'u.position_id')
    ->where('u.name')->like('%winds%')
    ->having('u.id')->greaterThan(0)
    ->orderBy('u.created_at')->desc();

// SELECT u.id, u.name, p.name as position
// FROM users u
// LEFT JOIN positions p ON p.id = u.position_id
// WHERE u.name LIKE ? HAVING u.id > ?
// ORDER BY u.created_at DESC
$sql = $query->toSql();

// array('name' => '%winds%', 'id' => 0)
$binds = $query->getBinds();
```

After creating the query, use the `Result` class to return its contents:

```
use Rougin\Ezekiel\Query;
use Rougin\Ezekiel\Result;

$query = (new Query)
    ->select(array('u.id', 'u.name'))
    ->from('users', 'u')
    ->where('name')->like('%winds%')
    ->orderBy('created_at')->desc();

$pdo = /** returns a PDO instance */;

$result = new Result($pdo);

$items = $result->items($query);

echo json_encode($items);
```

```
[
  {
    "id": 2,
    "name": "Windsor",
    "created_at": "2018-10-15 23:09:47",
    "updated_at": null
  },
  {
    "id": 1,
    "name": "Windstorm",
    "created_at": "2018-10-15 23:06:28",
    "updated_at": null
  },
  {
    "id": 3,
    "name": "Windsy",
    "created_at": "2018-10-15 23:14:45",
    "updated_at": null
  }
]
```

For returning only one item from the result, use the `first` method instead:

```
// ...

use Rougin\Ezekiel\Result;

// ...

$result = new Result($pdo);

$items = $result->first($query);

echo json_encode($items);
```

```
{
  "id": 2,
  "name": "Windsor",
  "created_at": "2018-10-15 23:09:47",
  "updated_at": null
}
```

Using entities
--------------

[](#using-entities)

For mapping query results into an entity object, the entity can be extended to the `Entity` class:

```
// src/Entities/User.php

namespace Test\Entities;

use Rougin\Ezekiel\Entity;

class User extends Entity
{
    protected $id;

    protected $name;

    public function getId()
    {
        return $this->id;
    }

    public function getName()
    {
        return $this->name;
    }

    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }
}
```

If an `Entity` is passed to the `Result` class, the results will be automatically to new instances of that `Entity`:

```
use Rougin\Ezekiel\Query;
use Rougin\Ezekiel\Result;
use Test\Entities\User;

$user = (new User)
    ->select('id, name')->from('users')
    ->where('name')->equals('Windsor');

$pdo = /** returns a PDO instance */ ;

$result = new Result($pdo);

/** @var \Rougin\Ezekiel\Fixture\Entities\User[] */
$users = $result->items($user);

foreach ($users as $user)
{
    echo 'Hello ' . $user->getName() . '!';
}
```

Using "Active"
--------------

[](#using-active)

The `Active` namespace provides a lightweight active-record implementation on top of Ezekiel's query builder. It serves as a drop-in replacement for [Eloquent](https://laravel.com/docs/eloquent) models without external dependencies:

```
namespace App\Models;

use Rougin\Ezekiel\Active\Model;

class User extends Model
{
    /**
     * @var array
     */
    protected $casts = array('age' => 'integer');

    /**
     * @var string[]
     */
    protected $fillable = array('name', 'age');

    /**
     * @param string $value
     *
     * @return string
     */
    public function getNameAttribute($value)
    {
        return strtoupper($value);
    }

    /**
     * @return \Rougin\Ezekiel\Active\Relations\HasMany
     */
    public function posts()
    {
        return $this->hasMany(__NAMESPACE__ . '\Post');
    }
}
```

To setup a model instance, use the existing `PDO` instance and set it to the same model (e.g., `User`):

```
$pdo = new \PDO('sqlite::memory:');

$user = new User;

$user->setPdo($pdo);
```

Then use the available Eloquent methods for doing CRUD operations:

```
// ...

// Create a new user -----------
$data = array('name' => 'John');

$data['age'] = 25;

$item = $user->create($data);
// -----------------------------

// Find or show all users ------------
$items = $user->get();

$item = $user->find(1);

$adults = $user->where('age', '>', 18)
    ->get();
// -----------------------------------

// Update the current user -----
$data = array('age' => 26);

$user->update($user->id, $data);
// -----------------------------

// Delete the specified user ---------
$model = $user->where('name', 'John');

$model = $model->first()->delete();
// -----------------------------------
```

### Query builder methods

[](#query-builder-methods)

The `Builder` class wraps Ezekiel's `Query` and provides an Eloquent-compatible fluent interface:

MethodDescription`where($column, $value)`Basic where clause (`$operator` optional as second argument)`orWhere($column, $value)`OR where clause (`$operator` optional as second argument)`whereIn($column, $values)`WHERE IN clause`whereLike($column, $value)`WHERE LIKE clause`orWhereLike($column, $value)`OR WHERE LIKE clause`whereNull($column)`WHERE IS NULL`whereNotNull($column)`WHERE IS NOT NULL`orWhereNull($column)`OR WHERE IS NULL`orWhereNotNull($column)`OR WHERE IS NOT NULL`orderBy($column, $direction)`ORDER BY clause`limit($value)`LIMIT clause`offset($value)`OFFSET clause`groupBy($columns)`GROUP BY clause`with($relations)`Eager load relationships`select($columns)`Override selected columns`distinct()`SELECT DISTINCT`get($columns)`Execute and return models`first($columns)`Return first model or null`find($id, $columns)`Find by primary key`findOrFail($id, $columns)`Find or throw RuntimeException`firstOrFail()`First model or throw`count()`Return row count`all()`Alias for `get()``exists()`Returns `true` if query matches`save()`Insert or update model`toArray()`Returns attributes as array`create($attrs)`Insert and return model`update($values)`Bulk update matching rows`delete()`Delete matching rows### Accessors and mutators

[](#accessors-and-mutators)

Accessors follow Eloquent's `getFooAttribute($value)` convention. The column name in `snake_case` is transformed to `StudlyCase` internally:

```
class User extends Model
{
    public function getFullNameAttribute($value)
    {
        return $this->first_name . ' ' . $this->last_name;
    }

    public function setEmailAttribute($value)
    {
        $this->attributes['email'] = strtolower($value);
    }
}

echo $user->full_name; // triggers getFullNameAttribute

$user->email = 'JOHN@EXAMPLE.COM'; // triggers setEmailAttribute
```

### Attribute casting

[](#attribute-casting)

The `$casts` property automatically converts attributes to their PHP types:

```
protected $casts = array(

    'age' => 'integer',
    'active' => 'boolean',
    'price' => 'float',
    'notes' => 'string',

);
```

### Mass assignment protection

[](#mass-assignment-protection)

Use `$fillable` to whitelist or `$guarded` to blacklist attributes:

```
class User extends Model
{
    /**
     * Only these keys can be mass-assigned.
     *
     * @var string[]
     */
    protected $fillable = array('name', 'email');

    /**
     * These keys are blocked (default: '*' blocks all).
     *
     * @var string[]
     */
    protected $guarded = array('password', 'is_admin');
}
```

### Timestamps

[](#timestamps)

By default, `created_at` and `updated_at` are managed automatically. Use `$timestamps = false` to disable it in a specified `Model`:

```
protected $timestamps = false;
```

### Soft deletes

[](#soft-deletes)

Activate soft deleting by setting `$softDelete = true`. Instead of removing rows, the `deleted_at` column is set:

```
class User extends Model
{
    /**
     * @var boolean
     */
    protected $softDelete = true;
}

// Sets "deleted_at", row remains
$user->delete();

// Returns "true" if soft-deleted
$user->trashed();

// Sets "deleted_at" as "null"
$user->restore();

// Permanently removes the row
$user->forceDelete();
```

### Relationships

[](#relationships)

`Active` supports `hasMany`, `hasOne`, `belongsTo`, and `belongsToMany`:

```
// One-to-many
class User extends Model
{
    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

// Inverse
class Post extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

// Many-to-many
class Post extends Model
{
    public function tags()
    {
        return $this->belongsToMany(Tag::class, 'post_tag', 'post_id', 'tag_id')
            ->withPivot('extra');
    }
}
```

Lazy loading and eager loading are both supported:

```
// Lazy
$user = $user->find(1);

foreach ($user->posts as $post)
{
    // ...
}

// Eager
$users = $user->with('posts')->get();
```

Pivot data is stored as `\stdClass` on the loaded model:

```
$tags = $post->tags;

echo $tags[0]->pivot->extra;
```

While the `attach` and `detach` methods manage pivot table rows:

```
$post->tags()->attach($tagId, array('extra' => 'value'));

$post->tags()->detach($tagId);
```

Pivot table timestamps can be enabled with `withTimestamps`:

```
public function tags()
{
    return $this->belongsToMany(Tag::class, 'post_tag', 'post_id', 'tag_id')
        ->withTimestamps();
}
```

### Table name derivation

[](#table-name-derivation)

When `$table` is not set, the table name is derived from the class name by converting `PascalCase` to `snake_case` and pluralising:

```
class UserProfile extends Model
{
    // If not specified, it will be "user_profiles"
}

class User extends Model
{
    /**
     * If not specified, it will be "users".
     *
     * @var string
     */
    protected $table = 'app_users';
}
```

### Model metadata

[](#model-metadata)

The following methods provide introspective access to a model's configuration:

```
$user = new User;

$table = $user->getTable(); // 'users'

$pk = $user->getPrimaryKey(); // 'id'

$fk = $user->getForeignKey(); // 'user_id'

$pdo = $user->getPdo(); // \PDO instance

$conn = $user->getConnectionName(); // 'default'
```

`joiningTable($class)` computes the alphabetical pivot table name from two model tables:

```
$user = new User;

// 'roles_users'
$table = $user->joiningTable(Role::class);
```

The static method `Model::setPdo($name, $pdo)` registers a named `\PDO` connection for all models sharing that connection name:

```
User::setPdo('default', $pdo);
```

Available methods
-----------------

[](#available-methods)

All available SQL statements should be supported by `Ezekiel`. These includes `DELETE FROM`, `INSERT INTO`, `SELECT`, and `UPDATE`:

### DELETE

[](#delete)

```
use Rougin\Ezekiel\Query;

$query = (new Query)
    ->deleteFrom('users')
    ->where('id')->equals(12);

// DELETE FROM users WHERE id = ?
$sql = $query->toSql();

// array('id' => 12)
$binds = $query->getBinds();
```

### INSERT

[](#insert)

```
use Rougin\Ezekiel\Query;

$query = (new Query)
    ->insertInto('users')
    ->values(array('name' => 'Ezekiel', 'age' => 20));

// INSERT INTO users (name, age) VALUES (?, ?)
$sql = $query->toSql();

// array('name' => 'Ezekiel', 'age' => 20)
$binds = $query->getBinds();
```

For batch inserting multiple rows, pass an array of associative arrays to `VALUES`:

```
$query = (new Query)
    ->insertInto('users')
    ->values(array(
        array('name' => 'Alice', 'age' => 25),
        array('name' => 'Bob', 'age' => 30),
    ));

// INSERT INTO users (name, age) VALUES (?, ?), (?, ?)
$sql = $query->toSql();

// array('Alice', 25, 'Bob', 30)
$binds = $query->getBinds();
```

### SELECT

[](#select)

```
$query = (new Query)
    ->select(array('u.id', 'u.name'))
    ->from('users u')
    ->where('u.name')->like('%winds%')
    ->orderBy('u.created_at')->desc();

// SELECT u.id, u.name FROM users u
// WHERE u.name LIKE ?
// ORDER BY u.created_at DESC
$sql = $query->toSql();

// array('name' => '%winds%')
$binds = $query->getBinds();
```

To select distinct values, call `DISTINCT` on the select builder before `FROM`:

```
$query = (new Query)
    ->select('name')->distinct()
    ->from('users');

// SELECT DISTINCT name FROM users
$sql = $query->toSql();
```

For granular conditions, use the `whereGroup` method to enclose multiple conditions in parentheses:

```
$query = (new Query)
    ->select('*')->from('users')
    ->where('status')->equals(1)
    ->whereGroup(function (Query $query)
    {
        $query->where('name')->equals('Alice')
            ->orWhere('name')->equals('Bob');
    });

// SELECT * FROM users
// WHERE status = ? AND (name = ? OR name = ?)
$sql = $query->toSql();

// array('status' => 1, 'name' => array('Alice', 'Bob'))
$binds = $query->getBinds();
```

In addition to the standard comparison operators, `BETWEEN` and `NOTBETWEEN` are also supported:

```
$query = (new Query)
    ->select('*')->from('users')
    ->where('age')->between(6, 7);

// SELECT * FROM users WHERE age BETWEEN ? AND ?
$sql = $query->toSql();

// array('age' => array(6, 7))
$binds = $query->getBinds();
```

`INNER JOIN` and `RIGHT JOIN` are also available alongside the `LEFT JOIN` shown in basic usage:

```
// SELECT * FROM users u INNER JOIN profiles p ON p.user_id = u.id
$query = (new Query)
    ->select('*')->from('users u')
    ->innerJoin('profiles p')->on('p.user_id', 'u.id');

// SELECT * FROM users u RIGHT JOIN orders o ON o.user_id = u.id
$query = (new Query)
    ->select('*')->from('users u')
    ->rightJoin('orders o')->on('o.user_id', 'u.id');
```

For wrapping grouped conditions with explicit `AND`/`OR` connectors, use `andWhereGroup` and `orWhereGroup`:

```
$fn = function (Query $q)
{
    $q->where('age')->greaterThan(18)
        ->orWhere('role')->equals('admin');
};

$query = (new Query)
    ->select('*')->from('users')
    ->where('status')->equals(1)
    ->andWhereGroup($fn);

// SELECT * FROM users
// WHERE status = ? AND (age > ? OR role = ?)
$sql = $query->toSql();
```

### UPDATE

[](#update)

```
use Rougin\Ezekiel\Query;

$query = (new Query)
    ->update('users')
    ->set('name', 'Ezekiel')
    ->where('id')->equals(12);

// UPDATE users SET name = ? WHERE id = ?
$sql = $query->toSql();

// array('name' => 'Ezekiel', 'id' => 12)
$binds = $query->getBinds();
```

Subqueries
----------

[](#subqueries)

`Ezekiel` supports subqueries in `WHERE` clauses and as derived tables in the `FROM` clause.

### WHERE IN subquery

[](#where-in-subquery)

```
$sub = (new Query)
    ->select('user_id')->from('posts')
    ->where('status')->equals(1);

$query = (new Query)
    ->select('*')->from('users')
    ->where('id')->in($sub);

// SELECT * FROM users
// WHERE id IN (SELECT user_id FROM posts WHERE status = ?)
$sql = $query->toSql();
```

### WHERE scalar comparison

[](#where-scalar-comparison)

```
$sub = (new Query)
    ->select('MAX(age)')->from('users');

$query = (new Query)
    ->select('*')->from('users')
    ->where('age')->equals($sub);

// SELECT * FROM users
// WHERE age = (SELECT MAX(age) FROM users)
$sql = $query->toSql();
```

### Derived table (FROM subquery)

[](#derived-table-from-subquery)

```
$sub = (new Query)
    ->select('*')->from('users')
    ->where('active')->equals(1);

$query = (new Query)
    ->select('*')->from($sub, 'active_users');

// SELECT * FROM (SELECT * FROM users WHERE active = ?) active_users
$sql = $query->toSql();
```

Schema builder
--------------

[](#schema-builder)

The `Table` class provides a fluent interface for building DDL statements such as `CREATE TABLE` and `DROP TABLE`:

```
use Rougin\Ezekiel\Schema\Table;
use Rougin\Ezekiel\Schema\Design;

$table = new Table;

$table->create('users', function (Design $d)
{
    $d->increments('id');
    $d->string('name', 100);
    $d->string('email')->unique();
    $d->integer('age')->defaultValue(0);
    $d->text('bio')->nullable();
    $d->timestamps();
});

// CREATE TABLE `users` (
//   `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
//   `name` VARCHAR(100) NOT NULL,
//   `email` VARCHAR(255) NOT NULL UNIQUE,
//   `age` INT NOT NULL DEFAULT 0,
//   `bio` TEXT,
//   `created_at` TIMESTAMP,
//   `updated_at` TIMESTAMP
// )
$sql = $table->toSql();
```

### Column types

[](#column-types)

TypeSQLNotes`string(name, length)``VARCHAR(n)`Default length: 255`integer(name, length)``INT(n)``bigInteger(name)``BIGINT``tinyInteger(name)``TINYINT``boolean(name)``TINYINT(1)``float(name)``FLOAT``decimal(name, prec, scale)``DECIMAL(p, s)`Default: `(8, 2)``text(name)``TEXT``date(name)``DATE``dateTime(name)``DATETIME``timestamp(name)``TIMESTAMP``increments(name)``INT AUTO_INCREMENT`Also sets `NOT NULL PRIMARY KEY`Note

SQL shown is for MySQL (the default dialect). Types are translated per dialect: e.g., `boolean()` becomes `BOOLEAN` in PostgreSQL, `BIT` in MSSQL, `INTEGER` in SQLite; `increments()` becomes `SERIAL` in PostgreSQL, `IDENTITY(1,1)` in MSSQL, `INTEGER AUTOINCREMENT` in SQLite.

### Column modifiers

[](#column-modifiers)

ModifierSQL`nullable()`Omits `NOT NULL``defaultValue(value)``DEFAULT value``unique()``UNIQUE``primary()``PRIMARY KEY``autoIncrement()``AUTO_INCREMENT`### Table-level constraints

[](#table-level-constraints)

MethodSQL`primary('col')``PRIMARY KEY (`col`)``primary(array('a', 'b'))``PRIMARY KEY (`a`, `b`)``unique('col')``UNIQUE (`col`)``unique(array('a', 'b'))``UNIQUE (`a`, `b`)``index('col')``INDEX (`col`)``index(array('a', 'b'))``INDEX (`a`, `b`)`### Convenience methods

[](#convenience-methods)

MethodAdds`timestamps()``created_at` and `updated_at` (`TIMESTAMP`, nullable)`softDeletes()``deleted_at` (`TIMESTAMP`, nullable)### Drop operations

[](#drop-operations)

```
$table = new Table;
$table->drop('users');
// DROP TABLE `users`

$table->dropIfExists('users');
// DROP TABLE IF EXISTS `users`
```

### Alter table

[](#alter-table)

Use the `table` method to add columns to an existing table:

```
$table = new Table;

$fn = function (Design $d)
{
    $d->string('phone', 20)->nullable();

    $d->text('notes')->nullable();

    $d->softDeletes();
};

$table->table('users', $fn);

// ALTER TABLE `users`
// ADD `phone` VARCHAR(20),
// ADD `notes` TEXT,
// ADD `deleted_at` TIMESTAMP
$sql = $table->toSql();
```

Snake case
----------

[](#snake-case)

All methods can be called in either `camelCase` or `snake_case`:

```
$query = (new Query)
    ->select(array('u.id', 'u.name'))
    ->from('users u')
    ->where('u.name')->like('%winds%')
    ->order_by('u.created_at')->desc();

$sql = $query->to_sql();

$binds = $query->get_binds();
```

Dialects
--------

[](#dialects)

`Ezekiel` generates SQL that adapts to the database platform with MySQL (`MysqlDialect`) as its default dialect. To target a different database, use the `setDialect` method:

```
use Rougin\Ezekiel\Dialect\PgsqlDialect;

$query = (new Query)->setDialect(new PgsqlDialect)
    ->select('*')->from('users')->limit(10);

// SELECT * FROM "users" LIMIT 10 OFFSET 0
$sql = $query->toSql();
```

When using `Result`, the dialect is automatically detected from the `PDO` connection:

```
$pdo = new \PDO('pgsql:host=localhost;dbname=test');

$result = new Result($pdo);

// Dialect is automatically set to PgsqlDialect on the Query
$items = $result->items($query);
```

To create a custom dialect, implement the class in `DialectInterface` or by extending it to `AbstractDialect`:

```
use Rougin\Ezekiel\Dialect\AbstractDialect;

class OracleDialect extends AbstractDialect
{
    public function getName()
    {
        return 'oracle';
    }

    public function getOpenQuoteChar()
    {
        return '"';
    }

    public function toLimit($limit, $offset)
    {
        return '';
    }
}
```

Custom dialects can also override `toColumn`, `toAlterTable`, `toCreateTable`, `toDropTable`, and `toDropTableIfExists` for generating platform-specific DDL statements (e.g., type translation, `ADD` vs `ADD COLUMN`, `AUTO_INCREMENT` vs `IDENTITY(1,1)`).

To auto-detect a dialect from a `\PDO` connection, use the static factory:

```
use Rougin\Ezekiel\Dialect;

$pdo = new \PDO('sqlite::memory:');

// Returns a SqliteDialect instance
$dialect = Dialect::fromPdo($pdo);
```

Note

Available built-in dialects for `Ezekiel` include `MysqlDialect`, `PgsqlDialect`, `SqliteDialect`, and `MssqlDialect`.

Renaming from `Windstorm`
-------------------------

[](#renaming-from-windstorm)

As being renamed from `Windstorm`, this will introduce [backward compatibility](https://en.wikipedia.org/wiki/Backward_compatibility) (BC) breaks through out the source code. This was done to increase extensibility, simplicity and maintainbility and was discussed in one of [my blog post](https://roug.in/hello-world-again/) which aims to solve overengineering of my own open source packages:

> I also want to extend this plan to my personal packages as well like [Staticka](https://github.com/staticka/staticka) and [Transcribe](https://github.com/rougin/transcribe). With this, I will introduce backward compatibility breaks to them initially as it is hard to migrate their codebase due to minimal to no documentation being provided in its basic usage and its internals. As I checked their code, I realized that they are also over engineered, which is a mistake that I needed to atone for when updating my packages in the future.

Please see the [UPGRADING](https://github.com/rougin/ezekiel/blob/master/UPGRADING.md) page for the specified breaking changes.

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more recent changes.

Contributing
------------

[](#contributing)

See [CONTRIBUTING](CONTRIBUTING.md) on how to contribute.

License
-------

[](#license)

The MIT License (MIT). Please see [LICENSE](https://github.com/rougin/ezekiel/blob/master/LICENSE.md) for more information.

###  Health Score

39

—

LowBetter than 84% of packages

Maintenance95

Actively maintained with recent releases

Popularity19

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity25

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~11 days

Total

4

Last Release

8d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/c7721589a958a1fbcef1b527c794d820d75b82988f14b2ed86a1c6904d76a59e?d=identicon)[rougin](/maintainers/rougin)

---

Top Contributors

[![rougin](https://avatars.githubusercontent.com/u/6078637?v=4)](https://github.com/rougin "rougin (171 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/rougin-ezekiel/health.svg)

```
[![Health](https://phpackages.com/badges/rougin-ezekiel/health.svg)](https://phpackages.com/packages/rougin-ezekiel)
```

###  Alternatives

[jdorn/sql-formatter

a PHP SQL highlighting library

3.9k117.2M118](/packages/jdorn-sql-formatter)[propel/propel1

Propel is an open-source Object-Relational Mapping (ORM) for PHP5.

8351.6M87](/packages/propel-propel1)[pgvector/pgvector

pgvector support for PHP

198741.5k12](/packages/pgvector-pgvector)[jfelder/oracledb

Oracle DB driver for Laravel

11518.4k](/packages/jfelder-oracledb)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
