adaiasmagdiel / rubik
A lightweight and intuitive ORM for PHP with support for SQLite and MySQL/MariaDB.
Installs: 58
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 1
Forks: 0
Open Issues: 0
pkg:composer/adaiasmagdiel/rubik
Requires
- php: >=8.1
- ext-pdo: *
Requires (Dev)
- pestphp/pest: ^3.7
README
Rubik ORM is a lightweight and intuitive Object-Relational Mapping (ORM) library designed for SQLite and MySQL/MariaDB databases. Inspired by the simplicity and structure of a Rubik's Cube, Rubik provides a clean and efficient way to interact with databases using both Active Record and Query Builder patterns. It offers robust support for CRUD operations, relationships, and schema management while maintaining a minimal footprint.
Table of Contents
- Features
- Use Cases
- Installation
- Getting Started
- Basic Examples
- Advanced Examples
- SQLite and MySQL Examples
- Models and Active Records
- Query Builder
- Relationships
- API Reference
- Contributing
- License
Features
- Active Record Pattern: Define models with schema mappings and perform CRUD operations directly on model instances.
- Query Builder: Construct complex SQL queries fluently with methods like
where,select,join,limit,whereInandpaginate. - Database Support: Optimized for SQLite and MySQL/MariaDB, with driver-specific configurations and foreign key support.
- Relationships: Supports
belongsToandhasManyrelationships for easy data modeling. - Schema Management: Define table schemas programmatically and create tables with custom field types (e.g., INTEGER, TEXT, BOOLEAN).
- Lightweight: Minimal dependencies, requiring only PHP 8.1+ and PDO.
- Type Safety: Uses enums (
FieldEnum) for field types and strict typing for better code reliability. - Flexible Connections: Singleton-like PDO connection management with support for custom configurations.
Use Cases
Rubik ORM is ideal for:
- Small to Medium Projects: Perfect for applications needing a lightweight ORM without the overhead of larger frameworks like Laravel's Eloquent.
- SQLite-Driven Applications: Mobile apps, desktop tools, or embedded systems using SQLite databases.
- MySQL/MariaDB Projects: Web applications requiring relational database interactions with MySQL or MariaDB.
- Rapid Prototyping: Quickly set up database interactions with minimal configuration.
- Educational Purposes: Learn ORM concepts with a simple, transparent implementation.
Installation
Prerequisites
- PHP 8.1 or higher
- PDO extension enabled (included by default in most PHP installations)
- Composer (for dependency management)
Install via Composer
Run the following command to install Rubik ORM:
composer require adaiasmagdiel/rubik
Alternatively, add the following to your composer.json and run composer update:
{
"require": {
"adaiasmagdiel/rubik": "^1.0"
}
}
Getting Started
Follow these steps to start using Rubik ORM in your project:
-
Set Up the Database Connection: Configure and establish a connection using the
Rubikclass. For SQLite, specify a file path or use:memory:for an in-memory database. For MySQL/MariaDB, provide host, database, and credentials.use AdaiasMagdiel\Rubik\Rubik; // SQLite connection Rubik::connect([ 'driver' => 'sqlite', 'path' => 'path/to/database.sqlite' ]); // MySQL connection Rubik::connect([ 'driver' => 'mysql', 'host' => 'localhost', 'database' => 'myapp', 'username' => 'user', 'password' => 'password', 'charset' => 'utf8mb4' ]);
-
Define a Model: Create a model by extending the
Modelclass and defining the table schema in thefields()method.use AdaiasMagdiel\Rubik\Model; class User extends Model { protected static string $table = 'users'; protected static function fields(): array { return [ 'id' => self::Int(autoincrement: true, primaryKey: true), 'name' => self::Text(notNull: true), 'email' => self::Text(unique: true, notNull: true), 'created_at' => self::DateTime(default: 'CURRENT_TIMESTAMP') ]; } }
-
Create the Table: Use the
createTablemethod to generate the database table based on the model's schema.User::createTable(ifNotExists: true);
-
Perform CRUD Operations: Use Active Record methods to interact with the database.
// Create a new user $user = new User(); $user->name = 'John Doe'; $user->email = 'john@example.com'; $user->save(); // Retrieve a user $user = User::find(1); echo $user->name; // John Doe // Update a user $user->name = 'Jane Doe'; $user->save(); // Delete a user $user->delete();
Basic Examples
Creating a Record
$user = new User(); $user->name = 'Alice Smith'; $user->email = 'alice@example.com'; $user->save(); // Inserts the record into the users table
Retrieving Records
// Find a user by ID $user = User::find(1); // Find all users $users = User::all(); // Find a user by email $user = User::findOneBy('email', 'alice@example.com');
Updating a Record
$user = User::find(1); $user->name = 'Alice Johnson'; $user->save(); // Updates the record
Deleting a Record
$user = User::find(1); $user->delete(); // Deletes the record
Advanced Examples
Query Builder
The Query Builder allows you to construct complex queries fluently.
// Select specific fields with conditions $users = User::query() ->select(['name', 'email']) ->where('created_at', '2023-01-01', '>') ->orderBy('name', 'ASC') ->limit(10) ->all(); // Complex query with WHERE IN $emails = ['alice@example.com', 'bob@example.com']; $users = User::query() ->whereIn('email', $emails) ->all(); // Paginate results $results = User::query() ->paginate(page: 1, perPage: 20); // Paginate results with where clause $results = User::query() ->where('created_at', '2023-01-01', '>') ->paginate(page: 1, perPage: 20); // Update multiple records User::query() ->where('created_at', '2023-01-01', '<') ->update(['name' => 'Archived User']);
Relationships
Define relationships between models using belongsTo and hasMany.
use AdaiasMagdiel\Rubik\Model; use AdaiasMagdiel\Rubik\Relationship; class Post extends Model { protected static string $table = 'posts'; protected static function fields(): array { return [ 'id' => self::Int(autoincrement: true, primaryKey: true), 'user_id' => self::Int(notNull: true), 'title' => self::Text(notNull: true), 'content' => self::Text() ]; } public function user(): Relationship { return $this->belongsTo(User::class, 'user_id'); } } class User extends Model { // ... fields() as defined earlier public function posts(): Relationship { return $this->hasMany(Post::class, 'user_id'); } } // Create tables User::createTable(ifNotExists: true); Post::createTable(ifNotExists: true); // Create a user and posts $user = new User(); $user->name = 'Bob'; $user->email = 'bob@example.com'; $user->save(); $post1 = new Post(); $post1->user_id = $user->id; $post1->title = 'First Post'; $post1->content = 'Hello, world!'; $post1->save(); $post2 = new Post(); $post2->user_id = $user->id; $post2->title = 'Second Post'; $post2->content = 'Another post.'; $post2->save(); // Access relationships $user = User::find(1); $posts = $user->posts; // Array of Post instances foreach ($posts as $post) { echo $post->title . "\n"; } $post = Post::find(1); $user = $post->user; // User instance echo $user->name; // Bob
Bulk Insert
Insert multiple records efficiently using insertMany.
$users = [ ['name' => 'Alice', 'email' => 'alice@example.com'], ['name' => 'Bob', 'email' => 'bob@example.com'], ['name' => 'Charlie', 'email' => 'charlie@example.com'] ]; User::insertMany($users);
SQLite and MySQL Examples
SQLite Example
Using an in-memory SQLite database for testing:
use AdaiasMagdiel\Rubik\Rubik; use AdaiasMagdiel\Rubik\Model; Rubik::connect([ 'driver' => 'sqlite', 'path' => ':memory:' ]); // Define and create a model class Product extends Model { protected static string $table = 'products'; protected static function fields(): array { return [ 'id' => self::Int(autoincrement: true, primaryKey: true), 'name' => self::Text(notNull: true), 'price' => self::Real(notNull: true), 'is_active' => self::Boolean(default: true) ]; } } Product::createTable(); // Insert a product $product = new Product(); $product->name = 'Laptop'; $product->price = 999.99; $product->is_active = true; $product->save(); // Query products $products = Product::query() ->where('price', 500, '>') ->all(); foreach ($products as $product) { echo "{$product->name}: \${$product->price}\n"; }
MySQL/MariaDB Example
Using a MySQL database for a web application:
use AdaiasMagdiel\Rubik\Rubik; Rubik::connect([ 'driver' => 'mysql', 'host' => 'localhost', 'database' => 'shop', 'username' => 'user', 'password' => 'password', 'charset' => 'utf8mb4' ]); // Define and create a model class Order extends Model { protected static string $table = 'orders'; protected static function fields(): array { return [ 'id' => self::Int(autoincrement: true, primaryKey: true), 'user_id' => self::Int(notNull: true), 'total' => self::Real(notNull: true), 'created_at' => self::DateTime(default: 'CURRENT_TIMESTAMP') ]; } } Order::createTable(ifNotExists: true); // Insert an order $order = new Order(); $order->user_id = 1; $order->total = 149.99; $order->save(); // Query orders $orders = Order::query() ->where('total', 100, '>') ->orderBy('created_at', 'DESC') ->limit(5) ->all(); foreach ($orders as $order) { echo "Order #{$order->id}: \${$order->total}\n"; }
Models and Active Records
Defining Models
Models represent database tables and are defined by extending the Model class. The fields() method specifies the table schema using field types from FieldEnum (e.g., INTEGER, TEXT, REAL).
class Book extends Model { protected static string $table = 'books'; protected static function fields(): array { return [ 'id' => self::Int(autoincrement: true, primaryKey: true), 'title' => self::Text(notNull: true), 'author' => self::Text(), 'price' => self::Real(default: 0.0), 'published' => self::Boolean(default: false) ]; } }
Active Record Operations
The Active Record pattern allows you to manipulate records as objects:
- Create: Instantiate a model, set properties, and call
save(). - Read: Use
find,findOneBy,findAllBy, orallto retrieve records. - Update: Modify properties and call
save()to update the record. - Delete: Call
delete()on a model instance.
Example:
$book = new Book(); $book->title = 'PHP Essentials'; $book->author = 'Jane Doe'; $book->price = 29.99; $book->published = true; $book->save(); $book = Book::find(1); $book->price = 34.99; $book->save(); $book->delete();
Query Builder
The Query Builder provides a fluent interface for constructing SQL queries. It supports:
- SELECT Queries:
select,where,whereIn,orderBy,limit,offset. - JOIN Operations:
join,leftJoin,rightJoin. - UPDATE and DELETE:
update,delete,exec. - Aggregation:
groupBy,having. - Pagination:
paginate.
Example:
$users = User::query() ->select(['id', 'name']) ->where('id', 5, '>') ->leftJoin('posts', 'users.id', '=', 'posts.user_id') ->groupBy('users.id') ->having('COUNT(posts.id) > 0') ->orderBy('name', 'ASC') ->limit(10) ->all();
Or paginate results:
$res = User::query() ->paginate(page: 2, perPage: 10); $users = $res["data"]; $currentPage = $res["current_page"]; $perPage = $res["per_page"]; $total = $res["total"]; $lastPage = $res["last_page"];
Relationships
Rubik ORM supports belongsTo and hasMany relationships, allowing you to model one-to-one and one-to-many associations.
BelongsTo
A model can belong to another model via a foreign key.
class Comment extends Model { protected static string $table = 'comments'; protected static function fields(): array { return [ 'id' => self::Int(autoincrement: true, primaryKey: true), 'post_id' => self::Int(notNull: true), 'content' => self::Text(notNull: true) ]; } public function post(): Relationship { return $this->belongsTo(Post::class, 'post_id'); } }
HasMany
A model can have multiple related models.
class Post extends Model { // ... fields() as defined earlier public function comments(): Relationship { return $this->hasMany(Comment::class, 'post_id'); } }
Using Relationships
Access related data dynamically:
$post = Post::find(1); $comments = $post->comments; // Array of Comment instances $comment = Comment::find(1); $post = $comment->post; // Post instance
API Reference
Class: FieldEnum
An enumeration defining SQLite and MySQL/MariaDB field data types.
- Cases:
INTEGER: Represents SQLite/MySQL INTEGER type (string).TEXT: Represents SQLite/MySQL TEXT type (string).REAL: Represents SQLite/MySQL REAL/FLOAT type (string).BLOB: Represents SQLite/MySQL BLOB type (string).NUMERIC: Represents SQLite/MySQL NUMERIC type (string).BOOLEAN: Represents SQLite/MySQL BOOLEAN type, stored as 0 or 1 (string).DATETIME: Represents SQLite/MySQL DATETIME type (string).
Class: Rubik
Main entry point for managing database connections.
public static function connect(array $config): voidpublic static function getConn(): PDO|nullpublic static function disconnect(): voidpublic static function isConnected(): bool
Class: Model (Abstract)
Abstract base class for database models, implementing Active Record pattern.
public function __set(string $key, mixed $value): voidpublic function __get(string $key): mixedpublic static function query(): Querypublic function save(bool $ignore = false): boolpublic static function insertMany(array $records): boolpublic function update(): boolpublic function delete(): boolpublic static function all(array|string $fields = '*'): arraypublic static function find(mixed $pk): ?staticpublic static function findOneBy(string $key, mixed $value, string $op = '='): ?staticpublic static function findAllBy(string $key, mixed $value, string $op = '='): arraypublic static function paginate(int $page, int $perPage, array|string $fields = '*'): \stdClasspublic static function createTable(bool $ifNotExists = false): boolpublic function belongsTo(string $related, string $foreignKey): Relationshippublic function hasMany(string $related, string $foreignKey): Relationshippublic static function primaryKey(): stringpublic static function getTableName(): stringprotected static function fields(): arrayprotected static function getFieldString(array $field): stringprotected static function escapeDefaultValue(mixed $value): stringpublic static function Int(bool $autoincrement = false, bool $primaryKey = false, bool $unique = false, bool $notNull = false, ?int $default = null): arraypublic static function Text(bool $unique = false, bool $notNull = false, bool $primaryKey = false, ?string $default = null): arraypublic static function Real(bool $unique = false, bool $notNull = false, bool $primaryKey = false, ?float $default = null): arraypublic static function Blob(bool $unique = false, bool $notNull = false, mixed $default = null): arraypublic static function Numeric(bool $unique = false, bool $notNull = false, bool $primaryKey = false, int|float|null $default = null): arraypublic static function Boolean(bool $notNull = false, ?bool $default = null): arraypublic static function DateTime(bool $notNull = false, ?string $default = null): array
Class: Query
Query builder for constructing and executing SQL queries.
public function setTable(string $table): selfpublic function setModel(string $model): selfpublic function select(string|array $fields = '*'): selfpublic function where(string $key, mixed $value, string $op = '='): selfpublic function orWhere(string $key, mixed $value, string $op = '='): selfpublic function whereIn(string $key, array $values): selfpublic function join(string $table, string $first, string $operator, string $second, string $type = 'INNER'): selfpublic function leftJoin(string $table, string $first, string $operator, string $second): selfpublic function rightJoin(string $table, string $first, string $operator, string $second): selfpublic function orderBy(string $column, string $direction = 'ASC'): selfpublic function groupBy(string|array $columns): selfpublic function having(string $condition): selfpublic function limit(int $limit): selfpublic function offset(int $offset): selfpublic function delete(): selfpublic function update(array $data): boolpublic function all(): arraypublic function first(): ?objectpublic function exec(): boolpublic function paginate(int $page, int $perPage): arraypublic function getSql(): string
Class: Relationship
Represents relationships between models (belongsTo and hasMany).
public function __construct(string $type, string $parentModel, string $relatedModel, string $foreignKey, ?object $parentInstance = null)public function getResults(): mixed
Contributing
Contributions are welcome! To contribute:
- Fork the repository.
- Create a feature branch (
git checkout -b feature/your-feature). - Commit your changes (
git commit -m 'Add your feature'). - Push to the branch (
git push origin feature/your-feature). - Open a Pull Request.
Please include tests and update the documentation as needed.
License
Rubik ORM is licensed under the GNU General Public License v3.0 (GPL-3.0).
See the LICENSE and COPYRIGHT files for details.