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.
You can install the package via composer:
composer require centamiv/concreteTo start using Concrete, you need to initialize the database connection. Typically, you would do this in your application's bootstrap file (e.g., index.php).
use Concrete\Database;
use Concrete\Connection\MysqlDriver;
require 'vendor/autoload.php';
// Initialize the database connection
Database::init(
new MysqlDriver(),
'127.0.0.1', // Host
'my_database', // Database Name
'root', // User
'password' // Password
);use Concrete\Database;
use Concrete\Connection\SqliteDriver;
require 'vendor/autoload.php';
// Initialize the database connection
// For SQLite, the second parameter is the database file path
Database::init(
new SqliteDriver(),
'/path/to/database.sqlite', // Database file path (or ':memory:' for in-memory)
);use Concrete\Database;
use Concrete\Connection\PostgresDriver;
require 'vendor/autoload.php';
// Initialize the database connection
Database::init(
new PostgresDriver(),
'127.0.0.1', // Host
'my_database', // Database Name
'postgres', // User
'password' // Password
);use Concrete\Database;
use Concrete\Connection\SqlServerDriver;
require 'vendor/autoload.php';
// Initialize the database connection
Database::init(
new SqlServerDriver(),
'localhost', // Host (Server)
'my_database', // Database Name
'sa', // User
'password' // Password
);Create a model class that extends Concrete\Model. You need to define the TABLE constant. By default, the primary key is assumed to be id.
namespace App\Models;
use Concrete\Model;
class User extends Model
{
public const TABLE = 'users';
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';
}If your table uses a primary key other than id, you can override the PRIMARY_KEY constant. Concrete also supports composite primary keys.
class Order extends Model
{
public const TABLE = 'orders';
// Simple Primary Key
public const PRIMARY_KEY = 'id';
}
class OrderItem extends Model
{
public const TABLE = 'order_items';
// Composite Primary Key
public const PRIMARY_KEY = ['order_id', 'product_id'];
}To create a new record, instantiate your model, set attributes, and call save().
$user = new User();
$user->set(User::COL_NAME, 'Mario Rossi')
->set(User::COL_EMAIL, '[email protected]')
->save();
// Access the auto-incremented ID (if applicable)
echo $user->get(User::COL_ID); Use the find() static method to retrieve a record by its primary key.
// Find by single primary key
$user = User::find(1);
if ($user) {
echo $user->get(User::COL_NAME);
}
// Find by composite primary key
$item = OrderItem::find(['order_id' => 10, 'product_id' => 5]);Retrieve a record, modify its attributes, and call save() again.
$user = User::find(1);
$user->set(User::COL_EMAIL, '[email protected]');
$user->save();Retrieve a record and call delete().
$user = User::find(1);
if ($user) {
$user->delete();
}Concrete provides a fluent Query Builder for more complex queries. You can access it via the query() static method on your models.
$users = User::query()
->where(User::col(User::COL_ACTIVE), '=', 1)
->where(User::col(User::COL_AGE), '>', 18)
->get();
foreach ($users as $user) {
echo $user->get(User::COL_NAME);
}$users = User::query()
->where(User::col(User::COL_ACTIVE), '=', 1)
->orderBy(User::col(User::COL_CREATED_AT), 'DESC')
->get();You can join other tables using the join or leftJoin methods.
// Assuming Role model exists with constants
$users = User::query()
->select(User::col('*'), Role::colAs(Role::COL_NAME, 'role_name'))
->join(Role::TABLE, User::col(User::COL_ROLE_ID), '=', Role::col(Role::COL_ID))
->get();You can also use rightJoin:
$users = User::query()
->rightJoin(Role::TABLE, User::col(User::COL_ROLE_ID), '=', Role::col(Role::COL_ID))
->get();$affected = User::query()
->where(User::col(User::COL_ACTIVE), '=', 0)
->update([User::col(User::COL_ACTIVE) => 1]);$deleted = User::query()
->where(User::col(User::COL_AGE), '<', 18)
->delete();$users = User::query()
->take(10) // LIMIT 10
->skip(5) // OFFSET 5
->get();count(): Returns the number of records matching the query.first(): Returns the first model instance ornull.exists(): Returnstrueif any records match the query.
$count = User::query()->where(User::col(User::COL_ACTIVE), '=', 1)->count();
$user = User::query()->where(User::col(User::COL_EMAIL), '=', '[email protected]')->first();
if (User::query()->where(User::col(User::COL_AGE), '<', 18)->exists()) {
// ...
}If you prefer to work with raw associative arrays instead of Model instances, you can use getRows() or firstRow().
// Returns an array of associative arrays
$users = User::query()->where('active', '=', 1)->getRows();
foreach ($users as $user) {
echo $user['name']; // Access as array
}
// Returns a single associative array or null
$user = User::query()->find(1)->firstRow();
if ($user) {
echo $user['email'];
}exists(): Checks if the model instance exists in the database (based on primary key presence).fill(array $data): Mass assign attributes from an array.col(string $column, ?string $alias = null): Helper to get fully qualified column names (e.g.,users.name).colAs(string $column, string $columnAs, ?string $tableAlias = null): Helper to get fully qualified column names with an alias (e.g.,users.name as user_name).
You can inspect the generated SQL query using the sql() method on the builder.
$query = User::query()
->where(User::col(User::COL_ACTIVE), '=', 1);
echo $query->sql();
// Outputs: SELECT users.* FROM users WHERE users.active = :users_active0For complex operations not supported by the Query Builder, you can access the underlying PDO connection.
use Concrete\Database;
$db = Database::getConnection();
// Complex Raw Query
$stmt = $db->query("SELECT count(*) as count FROM users GROUP BY age");
$stats = $stmt->fetchAll();While Model::query() is the preferred way, you can use the Builder directly.
use Concrete\Query\Builder;
use App\Models\User;
$builder = new Builder();
$users = $builder->table(User::class)
->where('active', '=', 1)
->get();- PHP >= 7.4
ext-pdoextension- For MySQL:
ext-pdo_mysql - For SQLite:
ext-pdo_sqlite - For PostgreSQL:
ext-pdo_pgsql - For SQL Server:
ext-pdo_sqlsrv
If you want to use Concrete within a Laravel application and reuse the existing Laravel database connection, you can use the initFromPDO method.
In your app/Providers/AppServiceProvider.php, initialize Concrete in the boot method.
use Concrete\Database;
use Concrete\Connection\MysqlDriver;
// Or use: PostgresDriver, SqliteDriver, SqlServerDriver
use Illuminate\Support\Facades\DB;
public function boot()
{
// Initialize using the existing Laravel PDO connection
// Choose the appropriate driver based on your Laravel database configuration
Database::initFromPDO(
DB::connection()->getPdo(),
new MysqlDriver() // Use PostgresDriver, SqliteDriver, or SqlServerDriver as needed
);
}Now you can use Concrete models alongside Eloquent models.
namespace App\Http\Controllers;
use App\Models\Concrete\User; // Your Concrete Model
class UserController extends Controller
{
public function index()
{
// Use Concrete to fetch users using Laravel's connection
$users = User::query()
->where(User::col(User::COL_ACTIVE), '=', 1)
->get();
return view('users.index', ['users' => $users]);
}
}This library is open-sourced software licensed under the MIT license.