whilesmart / eloquent-projects
Polymorphic project and version management package for Laravel applications
dev-main
2026-03-16 11:00 UTC
Requires
- php: ^8.2
- laravel/framework: ^10.0|^11.0|^12.0
Requires (Dev)
- fakerphp/faker: ^1.24
- laravel/pint: ^1.25
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpunit/phpunit: ^10.0|^11.0
Suggests
- whilesmart/eloquent-roles: For permission-based project management
- whilesmart/eloquent-taxonomy: For tagging and categorizing projects
- whilesmart/eloquent-workspaces: For workspace-scoped projects
This package is auto-updated.
Last update: 2026-03-16 11:03:45 UTC
README
Polymorphic project container for Laravel applications.
Features
- Polymorphic Ownership: Any model can own projects (workspaces, teams, orgs, users)
- Polymorphic Creator: Track who or what created a project (users, agents, systems)
- Polymorphic Assignments: Assign any entity to a project with a role
- Configurable Statuses: Define project lifecycle states via database
- Archiving: Soft archive/unarchive without deletion
- Lifecycle Events: Hooks for created, updated, deleted, archived, assigned, etc.
- Middleware Hooks: Before/after hooks on all controller actions
- Customizable Models: Override Project, ProjectStatus, ProjectAssignment via config
- Child Model Support: Any model can belong to a project via
BelongsToProjecttrait - Laravel 10+, 11+, 12+ Support
Installation
composer require whilesmart/eloquent-projects
Publish Configuration (Optional)
php artisan vendor:publish --tag=projects-config
Run Migrations
php artisan migrate
Quick Start
Owning Projects
Add the HasProjects trait to any model that should own projects:
use Whilesmart\Projects\Traits\HasProjects; class Workspace extends Model { use HasProjects; }
$workspace->createProject('My Design', $user); $workspace->createProject('Campaign Q2', $aiAgent, 'Q2 marketing assets'); $workspace->projects; // all projects owned by this workspace
Child Models
Add BelongsToProject to models that live inside a project:
use Whilesmart\Projects\Traits\BelongsToProject; class DesignVersion extends Model { use BelongsToProject; }
$version->project; // parent project DesignVersion::forProject($projectId)->get(); // all versions for a project
Assignments
Assign any entity (user, agent, team) to a project with a role:
use Whilesmart\Projects\Traits\AssignableToProject; class User extends Model { use AssignableToProject; }
// Assign $project->assign($user, 'editor'); $project->assign($aiAgent, 'generator', $admin); $project->assign($team, 'stakeholder', null, ['notify' => true]); // Query $project->assignments; // all assignments $project->assignees('editor')->get(); // editors only $project->isAssigned($user, 'editor'); // true // From the assignable side $user->assignedProjects()->get(); // all projects $user->assignedProjects('editor')->get(); // projects where user is editor $user->isAssignedToProject($project); // true // Unassign $project->unassign($user, 'editor'); // remove specific role $project->unassign($user); // remove all roles
Statuses
use Whilesmart\Projects\Models\ProjectStatus; ProjectStatus::create(['name' => 'Active', 'slug' => 'active', 'color' => '#22c55e']); ProjectStatus::create(['name' => 'Paused', 'slug' => 'paused', 'color' => '#f59e0b']); ProjectStatus::create(['name' => 'Completed', 'slug' => 'completed', 'color' => '#3b82f6']); $project->update(['status_id' => $status->id]); $project->status->name; // "Active" Project::withStatus('active')->get();
Archiving
$project->archive(); $project->isArchived(); // true $project->unarchive(); Project::active()->get(); // non-archived Project::archived()->get(); // archived only
Metadata
$project = Project::create([ 'owner_type' => Workspace::class, 'owner_id' => $ws->id, 'title' => 'Campaign', 'metadata' => ['budget' => 5000, 'deadline' => '2026-04-01'], ]); $project->getMetadata('budget'); // 5000 $project->getMetadata('missing', 'default'); // 'default' $project->setMetadata('priority', 'high'); $project->save();
Events
| Event | Fired when |
|---|---|
ProjectCreated |
Project is created |
ProjectUpdated |
Project is updated |
ProjectDeleted |
Project is deleted |
ProjectArchived |
Project is archived |
ProjectUnarchived |
Project is unarchived |
ProjectAssigned |
Entity is assigned to a project |
ProjectUnassigned |
Entity is unassigned from a project |
Configuration
// config/projects.php return [ 'models' => [ 'project' => \Whilesmart\Projects\Models\Project::class, 'project_status' => \Whilesmart\Projects\Models\ProjectStatus::class, 'project_assignment' => \Whilesmart\Projects\Models\ProjectAssignment::class, ], 'register_routes' => true, 'route_prefix' => '', 'route_middleware' => ['auth:sanctum'], 'middleware_hooks' => [], ];
Custom Models
class Project extends \Whilesmart\Projects\Models\Project { // custom logic, relationships, scopes }
// config/projects.php 'models' => [ 'project' => App\Models\Project::class, ],
Middleware Hooks
use Whilesmart\Projects\Interfaces\MiddlewareHookInterface; class ProjectHook implements MiddlewareHookInterface { public function before(Request $request, string $action): ?Request { return $request; } public function after(Request $request, JsonResponse $response, string $action): JsonResponse { return $response; } }
API Routes
GET /projects List projects (filter: ?owner_type=, ?owner_id=, ?status=, ?archived=)
POST /projects Create project
GET /projects/{id} Show project
PUT /projects/{id} Update project
DELETE /projects/{id} Delete project
POST /projects/{id}/archive Archive project
POST /projects/{id}/unarchive Unarchive project
Database Schema
projects
| Column | Type | Description |
|---|---|---|
| owner_type / owner_id | morph | Polymorphic owner |
| creator_type / creator_id | morph (nullable) | Polymorphic creator (immutable) |
| status_id | FK (nullable) | References project_statuses |
| title | string | Project title |
| description | text (nullable) | Description |
| thumbnail_url | string (nullable) | Preview image |
| metadata | JSON (nullable) | Flexible data |
| archived_at | timestamp (nullable) | Archive timestamp |
project_statuses
| Column | Type | Description |
|---|---|---|
| name | string | Display name |
| slug | string (unique) | Identifier |
| description | string (nullable) | Description |
| color | string (nullable) | Hex color |
| sort_order | integer | Display ordering |
project_assignments
| Column | Type | Description |
|---|---|---|
| project_id | FK | References projects |
| assignable_type / assignable_id | morph | Entity assigned |
| role | string | Assignment role |
| assigned_by_type / assigned_by_id | morph (nullable) | Who assigned |
| metadata | JSON (nullable) | Flexible data |
Unique constraint: (project_id, assignable_type, assignable_id, role)
Testing
make test # Run tests via Docker make pint # Run code formatter make check # Run all checks (pint + tests)
Requirements
- PHP 8.2+
- Laravel 10.0, 11.0, or 12.0
License
MIT License
Credits
Developed by the Whilesmart Team