Green Framework 3.0 is a major release focused on making common backend work more expressive without hiding how the application runs.
This release adds PHP attribute-based relations, first-class polymorphic relations, transactional database seeders, and a lightweight queue system. It also completes an important configuration redesign: configuration is now assembled once during bootstrap and remains read-only at runtime.
The theme is simple: less runtime mutation, clearer declarations, and predictable infrastructure.
Green Core is the reusable runtime library behind Green Framework. To start an application, use the Green skeleton project.
Define table relations with PHP attributes
Relations can now live directly on a Table class through the repeatable #[RelatesTo] attribute:
use YasserElgammal\Green\Database\Relations\BelongsTo;
use YasserElgammal\Green\Database\Relations\HasMany;
use YasserElgammal\Green\Database\Relations\RelatesTo;
#[RelatesTo('posts', new HasMany(Post::class))]
#[RelatesTo('author', new BelongsTo(User::class))]
final class UserTable extends Table
{
// ...
}
This gives relation definitions a compact, discoverable home while keeping them as explicit Relation objects.
Existing projects can migrate gradually. Green 3.0 supports all three definition styles and resolves duplicate names in a deterministic order:
-
#[RelatesTo]attributes - Entries returned by
relations() - The legacy
$relationsproperty
The higher item in that list wins. Existing relation declarations therefore continue to work while new code can adopt attributes one table at a time.
Model polymorphic relationships without N+1 queries
Green 3.0 introduces MorphTo, MorphMany, and MorphOne. A comment, for example, can belong to either a post or a video through the same commentable_type and commentable_id columns.
Models declare stable database aliases with #[MorphAlias]:
use YasserElgammal\Green\Database\Attributes\MorphAlias;
#[MorphAlias('post')]
final class Post extends Model
{
}
#[MorphAlias('video')]
final class Video extends Model
{
}
The related tables can then define both sides of the relationship:
use YasserElgammal\Green\Database\Relations\MorphMany;
use YasserElgammal\Green\Database\Relations\MorphTo;
use YasserElgammal\Green\Database\Relations\RelatesTo;
#[RelatesTo('comments', new MorphMany(Comment::class, 'commentable'))]
final class PostTable extends Table
{
}
#[RelatesTo(
'commentable',
new MorphTo('commentable', models: [Post::class, Video::class]),
)]
final class CommentTable extends Table
{
}
MorphMap discovers aliases from the model attributes, with manual registration available when an application needs explicit control. Dedicated eager-loading strategies batch each relationship type, avoiding an N+1 query for every parent model.
Seed databases with predictable execution
The new seeding subsystem gives applications a standard way to create initial, development, or test data.
Generate a seeder:
php green create:seeder UserSeeder
Green writes seeders to database/seeders/ with an auto-incrementing numeric prefix. Files run alphabetically, so prefixes such as 001_, 002_, and 003_ make dependencies visible and execution repeatable.
A seeder stays deliberately straightforward:
use App\Tables\UserTable;
use YasserElgammal\Green\Database\Seeders\Seeder;
final class UserSeeder extends Seeder
{
protected array $truncate = ['users'];
public function run(): void
{
(new UserTable())->insert([
'name' => 'Admin',
'email' => 'admin@example.com',
]);
}
}
Run every seeder, target one class, or request a fresh pass:
php green db:seed
php green db:seed --class=UserSeeder
php green db:seed --fresh
Each seeder runs inside its own transaction. If it fails, Green rolls it back and stops the run. Declared table names are validated before truncation, and --fresh clears all tables declared by the selected seeders before execution.
Move work out of the request with queues
The v3.0.0 tag also ships Green's first queue subsystem, with synchronous and database-backed drivers.
A queued job implements one small contract:
use YasserElgammal\Green\Queue\Contracts\JobInterface;
final class SendWelcomeEmail implements JobInterface
{
public function __construct(private readonly int $userId)
{
}
public function handle(): void
{
// Resolve the user and send the message.
}
public function maxAttempts(): int
{
return 3;
}
public function retryDelay(): int
{
return 10;
}
}
Dispatch it through QueueManager:
$queue->dispatch(new SendWelcomeEmail($user->id));
For local execution, the sync driver processes work immediately. The database driver persists jobs for a worker:
php green queue:work
php green queue:work --queue=emails --connection=database --sleep=2
php green queue:work --max-jobs=100
Workers delete successful jobs and release failed jobs according to the job's attempt and retry settings. Custom queue drivers can be registered through QueueManager::extend().
This is intentionally a small queue foundation. In 3.0, a job that exhausts its attempts is discarded rather than moved to a failed-jobs store, so production applications should account for that behavior in monitoring and job design.
Configuration is read-only by construction
In 3.0, the lifecycle is simpler:
Unloaded -> Ready
That creates a clearer boundary: configuration belongs to bootstrap; application services consume the finished snapshot.
Final Thoughts
Green Framework 3.0.0 is a major step forward, but the goal has not changed.
The focus is still on building a framework that stays explicit, understandable, and easy to trace while gradually adding the tools developers need in real applications.
This release brings more expressive relationships, polymorphic associations, deterministic database seeders, a lightweight queue system, and a stricter configuration lifecycle ā all while keeping the core architecture visible instead of hiding it behind magic.
Green is still evolving, and there is more to build.
If you try v3.0.0, Iād love to hear your feedback, ideas, and contributions.
Build simpler. Understand more. Go further. š±
Projects using legacy relation arrays do not have to rewrite them immediately. They remain supported, with the precedence rules described above.
A more explicit foundation
Green 3.0 makes several daily workflows easier, but the larger direction matters more: relations are declared close to their tables, polymorphic types have stable aliases, seed execution has deterministic ordering and transaction boundaries, queues use focused contracts, and configuration stops changing once runtime begins.
That is the kind of framework evolution Green is aiming for: useful conveniences built on behavior you can trace.
If you try Green 3.0, feedback and contributions are welcome.
Top comments (0)