Rocket is a modern ORM (Object-Relational Mapping) for the Luxid Framework. It uses PHP 8 attributes for configuration, providing a clean, intuitive, and type-safe way to work with your database.
Rocket is included by default in Luxid Framework projects. When you create a new Luxid project, Rocket is automatically installed and configured.
composer create-project luxid/framework my-app
cd my-app
php juice db:migrate
php juice seedRocket uses your existing Luxid database configuration. The database connection is automatically configured from your .env file:
# Database Configuration
DB_DSN=mysql:host=127.0.0.1;dbname=myapp
DB_USER=root
DB_PASSWORD=secret
# Platform-specific (Linux/Arch with MariaDB)
# DB_DSN=mysql:unix_socket=/run/mysqld/mysqld.sock;dbname=myapp
# macOS with MAMP
# DB_DSN=mysql:unix_socket=/Applications/MAMP/tmp/mysql/mysql.sock;dbname=myappEntities are PHP classes that represent database tables. Use PHP 8 attributes to define the table structure, columns, and validation rules.
<?php
namespace App\Entities;
use Rocket\ORM\Entity;
use Rocket\Attributes\Entity as EntityAttr;
use Rocket\Attributes\Column;
use Rocket\Attributes\Rules\Required;
use Rocket\Attributes\Rules\Email;
use Rocket\Attributes\Rules\Min;
use Rocket\Attributes\Rules\Unique;
#[EntityAttr(table: 'users')]
class User extends Entity
{
#[Column(primary: true, autoIncrement: true)]
public int $id = 0;
#[Column]
#[Required]
#[Email]
#[Unique]
public string $email = '';
#[Column(hidden: true)]
#[Required]
#[Min(8)]
public string $password = '';
#[Column]
#[Required]
public string $firstname = '';
#[Column]
#[Required]
public string $lastname = '';
#[Column(autoCreate: true)]
public string $created_at = '';
#[Column(autoCreate: true, autoUpdate: true)]
public string $updated_at = '';
}| Attribute | Description |
|---|---|
#[Column(primary: true)] |
Sets column as primary key |
#[Column(autoIncrement: true)] |
Auto-incrementing integer |
#[Column(hidden: true)] |
Excludes from JSON serialization |
#[Column(autoCreate: true)] |
Automatically sets on create (like timestamps) |
#[Column(autoUpdate: true)] |
Automatically updates on every save |
#[Column(nullable: true)] |
Allows NULL values |
#[Column(default: value)] |
Sets default value |
| Rule | Description | Example |
|---|---|---|
#[Required] |
Field cannot be empty | #[Required] |
#[Email] |
Must be valid email | #[Email] |
#[Min(8)] |
Minimum length/value | #[Min(8)] |
#[Max(100)] |
Maximum length/value | #[Max(100)] |
#[Unique] |
Must be unique in table | #[Unique] |
#[In(['a', 'b'])] |
Must be in allowed values | #[In(['pending', 'completed'])] |
$user = new User();
$user->email = 'john@example.com';
$user->password = 'password123';
$user->firstname = 'John';
$user->lastname = 'Doe';
if ($user->save()) {
echo "User created! ID: {$user->id}";
} else {
print_r($user->getErrors()); // Show validation errors
}// Find by ID
$user = User::find(1);
// Find one by conditions
$user = User::findOne(['email' => 'john@example.com']);
// Find all with conditions
$users = User::findAll(['is_active' => true], ['created_at' => 'DESC'], 10);
// Using Query Builder
$users = User::query()
->where('email', 'LIKE', '%@gmail.com')
->orderBy('created_at', 'DESC')
->limit(10)
->all();
// Count records
$count = User::query()->where('is_active', '=', true)->count();
// Get total count
$total = User::count();
// Check if any records exist
if (User::exists()) {
echo "Users found!";
}
// Get first record
$firstUser = User::first();
// Get last record
$lastUser = User::last();
// Get random records
$randomUsers = User::random(3);$user = User::find(1);
$user->firstname = 'Jonathan';
$user->save(); // Automatically updates updated_at timestamp// Delete a single record
$user = User::find(1);
$user->delete();
// Delete all records
User::deleteAll();
// Truncate table (delete all and reset auto-increment)
User::truncate();The query builder provides a fluent interface for building complex queries.
use App\Entities\User;
// Basic queries
$users = User::query()
->select(['id', 'name', 'email'])
->where('is_active', '=', true)
->where('age', '>=', 18)
->orderBy('created_at', 'DESC')
->limit(10)
->offset(20)
->all();
// Where IN
$users = User::query()
->whereIn('id', [1, 2, 3])
->all();
// Where NULL
$users = User::query()
->whereNull('deleted_at')
->all();
// Where NOT NULL
$users = User::query()
->whereNotNull('email_verified_at')
->all();Use the get prefix to create computed properties that aren't stored in the database:
class User extends Entity
{
// ... columns
public function getFullName(): string
{
return $this->firstname . ' ' . $this->lastname;
}
public function getDisplayName(): string
{
return $this->fullName ?? $this->email;
}
public function getInitials(): string
{
return strtoupper(substr($this->firstname, 0, 1) . substr($this->lastname, 0, 1));
}
}
// Usage
$user = User::find(1);
echo $user->fullName; // "John Doe"
echo $user->displayName; // "John Doe"
echo $user->initials; // "JD"Override these methods to add custom logic at specific points:
class User extends Entity
{
protected function beforeSave(): void
{
// Hash password before saving
if (!empty($this->password) && !$this->isPasswordHashed()) {
$this->password = password_hash($this->password, PASSWORD_DEFAULT);
}
// Auto-set timestamps
if ($this->isNew && empty($this->created_at)) {
$this->created_at = date('Y-m-d H:i:s');
}
$this->updated_at = date('Y-m-d H:i:s');
}
protected function afterSave(): void
{
// Clear cache, send welcome email, etc.
Cache::forget('user_' . $this->id);
}
protected function beforeDelete(): void
{
// Soft delete related records
$this->posts()->update(['deleted_at' => now()]);
}
protected function afterDelete(): void
{
// Log deletion
Log::info("User {$this->id} was deleted");
}
private function isPasswordHashed(): bool
{
return password_get_info($this->password)['algo'] !== 0;
}
}One-to-one relationship where the current entity has one related entity.
use Rocket\Attributes\Relations\HasOne;
class User extends Entity
{
#[HasOne(Profile::class, 'user_id', 'id')]
protected $profile;
}
class Profile extends Entity
{
#[Column]
public string $bio = '';
#[Column]
public int $user_id = 0;
}
// Usage
$user = User::find(1);
echo $user->profile->bio; // Loads profile automaticallyOne-to-many relationship where the current entity has many related entities.
use Rocket\Attributes\Relations\HasMany;
class User extends Entity
{
#[HasMany(Post::class, 'user_id', 'id')]
protected $posts;
}
class Post extends Entity
{
#[Column]
public string $title = '';
#[Column]
public string $content = '';
#[Column]
public int $user_id = 0;
}
// Usage
$user = User::find(1);
foreach ($user->posts as $post) {
echo $post->title;
}
// Create related record
$user->posts()->create(['title' => 'New Post', 'content' => '...']);Inverse of HasOne/HasMany. The related entity belongs to the current entity.
use Rocket\Attributes\Relations\BelongsTo;
class Post extends Entity
{
#[BelongsTo(User::class, 'user_id', 'id')]
protected $author;
}
// Usage
$post = Post::find(1);
echo $post->author->name; // Loads the author automaticallyRocket provides several convenient helper methods on all entities:
| Method | Description | Example |
|---|---|---|
count() |
Get total record count | User::count() |
exists() |
Check if any records exist | User::exists() |
first() |
Get the first record | User::first() |
last() |
Get the last record | User::last() |
random($limit) |
Get random records | User::random(5) |
deleteAll() |
Delete all records | User::deleteAll() |
truncate() |
Delete all records and reset auto-increment | User::truncate() |
// Count users
$totalUsers = User::count();
// Check if there are any active users
if (User::exists()) {
echo "There are users!";
}
// Get the newest user
$newestUser = User::last();
// Get 3 random products for a "You might also like" section
$randomProducts = Product::random(3);
// Reset the users table for testing
User::truncate();
// Clean up old records
User::where('last_login', '<', date('Y-m-d', strtotime('-1 year')))->deleteAll();php juice make:migration create_users_table
php juice make:migration add_email_to_users
php juice make:migration create_products_table<?php
use Rocket\Migration\Migration;
use Rocket\Migration\Rocket;
class m00001_create_users_table extends Migration
{
public function up(): void
{
Rocket::table('users', function($column) {
$column->id('id');
$column->string('email')->unique();
$column->string('password')->hidden();
$column->string('firstname');
$column->string('lastname');
$column->timestamps();
});
}
public function down(): void
{
Rocket::drop('users');
}
}| Method | Description | Example |
|---|---|---|
id() |
Auto-incrementing primary key | $column->id('id') |
string() |
VARCHAR column | $column->string('email') |
text() |
TEXT column | $column->text('content') |
integer() |
INT column | $column->integer('age') |
float() |
FLOAT column | $column->float('rating') |
decimal() |
DECIMAL column | $column->decimal('price', 10, 2) |
boolean() |
BOOLEAN column | $column->boolean('is_active') |
datetime() |
DATETIME column | $column->datetime('published_at') |
timestamps() |
created_at + updated_at | $column->timestamps() |
softDeletes() |
deleted_at column | $column->softDeletes() |
| Modifier | Description | Example |
|---|---|---|
->unique() |
Add unique constraint | $column->string('email')->unique() |
->nullable() |
Allow NULL values | $column->string('bio')->nullable() |
->default(value) |
Set default value | $column->boolean('active')->default(true) |
->index() |
Add index | $column->integer('status')->index() |
->hidden() |
Hide from serialization | $column->string('password')->hidden() |
$column->foreign('user_id')
->references('id')
->on('users')
->cascadeOnDelete();Actions:
cascadeOnDelete()- Delete child records when parent is deletedsetNullOnDelete()- Set foreign key to NULL when parent is deletedrestrictOnDelete()- Prevent deletion if child records existcascadeOnUpdate()- Update foreign key when parent is updated
# Run pending migrations
php juice migrate
# Rollback last batch
php juice migrate:rollback
# Rollback all migrations
php juice migrate:reset
# Reset and re-run all migrations (with optional seed)
php juice migrate:fresh
php juice migrate:fresh --seedphp juice make:factory UserFactory<?php
namespace Seeds;
use Rocket\Seed\Factory;
use Rocket\Seed\Faker;
use App\Entities\User;
class UserFactory extends Factory
{
protected static function getEntityClass(): string
{
return User::class;
}
protected function definition(): array
{
return [
'name' => Faker::name(),
'email' => Faker::unique()->email(),
'password' => 'password123',
'is_active' => true,
];
}
public function admin(): self
{
return $this->state([
'name' => 'Admin User',
'email' => 'admin@example.com',
'is_admin' => true,
]);
}
public function inactive(): self
{
return $this->state([
'is_active' => false,
]);
}
}php juice make:seeder UserSeeder<?php
namespace Seeds;
use Rocket\Seed\Seeder;
use App\Entities\User;
class UserSeeder extends Seeder
{
public function run(): void
{
echo "π± Seeding users...\n";
// Clean the table before seeding (no raw SQL!)
User::truncate();
// Create admin user
$admin = new User();
$admin->email = 'admin@example.com';
$admin->password = 'admin123';
$admin->firstname = 'Admin';
$admin->lastname = 'User';
$admin->save();
echo " β Created admin user\n";
// Create 10 regular users
for ($i = 1; $i <= 10; $i++) {
$user = new User();
$user->email = "user{$i}@example.com";
$user->password = 'password123';
$user->firstname = "User";
$user->lastname = "{$i}";
$user->save();
}
echo " β Created 10 regular users\n";
echo "β
User seeding completed!\n";
}
}The DatabaseSeeder runs all seeders in order:
<?php
namespace Seeds;
use Rocket\Seed\Seeder;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
$this->call(UserSeeder::class);
$this->call(PostSeeder::class);
$this->call(CommentSeeder::class);
}
}# Run all seeders
php juice seed
# Run specific seeder
php juice seed UserSeeder
# Fresh migrate and seed
php juice db:fresh --seedValidation runs automatically when calling save(). You can also validate manually:
$user = new User();
$user->email = 'invalid-email';
if (!$user->validate()) {
foreach ($user->getErrors() as $field => $errors) {
echo "{$field}: " . implode(', ', $errors);
}
}Create custom validation by extending the base validation:
class User extends Entity
{
public function validate(): bool
{
// Run default validation first
if (!parent::validate()) {
return false;
}
// Add custom validation
if (strpos($this->email, 'example.com') !== false) {
$this->addError('email', 'Email cannot be from example.com');
return false;
}
if (strlen($this->password) < 8) {
$this->addError('password', 'Password must be at least 8 characters');
return false;
}
return true;
}
}The toArray() method automatically handles hidden columns:
class User extends Entity
{
#[Column(hidden: true)]
public string $password = '';
#[Column(hidden: true)]
public string $remember_token = '';
}
$user = User::find(1);
return Response::json($user->toArray());
// Output: {'id': 1, 'email': 'john@example.com', 'name': 'John Doe', 'created_at': '...'}
// Password and remember_token are excluded# Database
php juice db:create # Create database
php juice db:migrate # Run migrations
php juice db:rollback # Rollback last batch
php juice db:reset # Rollback all migrations
php juice db:fresh # Drop all tables and re-migrate
php juice db:fresh --seed # Fresh migrate and seed
php juice seed # Run all seeders
php juice seed UserSeeder # Run specific seeder
# Code Generation
php juice make:entity Product # Create entity
php juice make:migration # Create migration
php juice make:seeder # Create seeder
php juice make:factory # Create factory
# Info
php juice status # Check application status
php juice routes # List all routes
php juice version # Show version-
Always set default values for typed properties to avoid uninitialized errors
public int $id = 0; public string $name = '';
-
Use hidden columns for sensitive data like passwords and tokens
#[Column(hidden: true)] public string $password = '';
-
Add validation rules to ensure data integrity before saving
#[Required] #[Email] #[Unique] public string $email = '';
-
Use lifecycle hooks for side effects like password hashing and timestamps
protected function beforeSave(): void { if (!empty($this->password) && !$this->isPasswordHashed()) { $this->password = password_hash($this->password, PASSWORD_DEFAULT); } if ($this->isNew && empty($this->created_at)) { $this->created_at = date('Y-m-d H:i:s'); } $this->updated_at = date('Y-m-d H:i:s'); }
-
Leverage relationships to keep your code clean and expressive
$user = User::find(1); foreach ($user->posts as $post) { echo $post->title; }
-
Use helper methods instead of raw SQL
// Instead of: $this->db->execute("TRUNCATE TABLE users"); User::truncate(); // Instead of: $this->db->execute("DELETE FROM users"); User::deleteAll();
-
Use factories and seeders for consistent test data
php juice make:factory UserFactory php juice seed
-
Keep migrations version-controlled for team collaboration
git add migrations/ git commit -m "Add users table migration" -
Use query builder for complex queries instead of raw SQL
$products = Product::query() ->where('price', '>', 100) ->where('stock', '>', 0) ->orderBy('name') ->limit(10) ->all();
-
Use computed properties for derived values
public function getTotalPrice(): float { return $this->quantity * $this->unit_price; }
"Typed property must not be accessed before initialization"
- Add default values to all typed properties:
public int $id = 0;
"Class not found"
- Run
composer dump-autoloadto regenerate the autoloader
"Migration not found"
- Ensure migration files are in the
migrations/directory with correct naming (m00001_*.php)
"Validation failed"
- Check
$entity->getErrors()for detailed error messages - Example:
print_r($user->getErrors());
"Connection failed"
- Verify your
.envdatabase configuration - Check if MySQL/MariaDB is running:
systemctl status mariadb(Linux) orbrew services list(macOS)
"Table already exists"
- Run
php juice db:rollbackto revert, then fix migration and runphp juice db:migrate
"Foreign key constraint fails"
- Ensure referenced table exists and has the referenced column
- Use
->nullable()if the foreign key can be null