v2

Developer Onboarding Guide

Duration: ~4 hours for full onboarding
Target: New developers joining Loreax team
Goal: Be productive with your first PR in one day


🎯 Welcome to Loreax!

Loreax is a creator-economy platform (MPESA-first for Kenya) where creators monetize content and fans access it through subscriptions, purchases, or free engagement.

Key Facts:

  • 14 domains organized by responsibility (not by feature)
  • Double-entry ledger ensures financial integrity
  • Type-safe code with Larastan level 8 compliance
  • 533 tests covering all critical logic (≥80% coverage)
  • Real money handling — correctness is non-negotiable

⏱️ Hour 1: Environment Setup

Step 1: Clone & Install (15 min)

# Clone repository
git clone git@github.com:bervant/loreax-core.git
cd loreax-core

# Follow Installation Guide
# See: docs/wikis/01-INSTALLATION.md for full steps

Step 2: Verify Everything Works (10 min)

# Check all systems
./vendor/bin/phpunit                    # 533/533 tests passing
./vendor/bin/pint --test                # 703/703 files clean
./vendor/bin/phpstan analyse            # 0 errors (level 8)
curl http://localhost:8000/health       # {"status": "ok"}

Step 3: Set Up IDE

PHPStorm/IntelliJ:

✅ PHP Language Level: 8.4
✅ Enable "Analyze on the fly"
✅ Enable "Inspect on Save"
✅ Install: Laravel Plugin

VS Code:

# Install extensions
code --install-extension felixbecker.php-debug
code --install-extension felixbecker.php-intellisense
code --install-extension eamodio.gitlens

Step 4: Configure Git

# Set your identity
git config user.name "Your Name"
git config user.email "you@example.com"

# Create feature branch from master
git checkout -b feat/first-feature

⏱️ Hour 2: Architecture Concepts

Core Mental Model: Domains

Loreax is organized into 14 semi-independent domains, not by file type (models, controllers) but by business responsibility.

Example: User Registration Flow
  ↓
  Identity domain owns "User" concept
  ↓
  RegisterUserAction (business logic)
  ↓
  RegisterUserController (thin HTTP adapter)
  ↓
  Ledger domain gives welcome bonus
  ↓
  Notifications domain sends welcome email

Why? Easy to understand, reuse, and test. Each domain is a "mini application."

Key Patterns (Copy These)

1. Action Pattern

All business logic goes here:

// ✅ app/Domain/Actions/VerbNounAction.php
final class RegisterUserAction
{
    use AsAction;

    public function run(RegisterUserData $data): User
    {
        // Validate
        if (User::where('email', $data->email)->exists()) {
            throw new EmailAlreadyRegisteredException($data->email);
        }

        // Execute in transaction
        return DB::transaction(function () use ($data): User {
            $user = User::create([...]);
            
            // Side effects AFTER commit
            DB::afterCommit(fn () => UserRegistered::dispatch($user));
            
            return $user;
        });
    }
}

2. Thin Controller Pattern

Controller = I/O adapter only:

// ✅ app/Domain/Http/Controllers/VerbNounController.php
final class RegisterUserController extends Controller
{
    public function __construct(
        private readonly RegisterUserAction $registerUser,
    ) {}

    public function __invoke(RegisterUserRequest $request): JsonResponse
    {
        $data = RegisterUserData::from($request->validated());
        $user = $this->registerUser->run($data);

        return UserResource::make($user)
            ->response()
            ->setStatusCode(201);
    }
}

3. Ledger Pattern

ALL money moves through one place:

// ✅ Only way to move money:
$this->ledger->post(
    type: LedgerTransactionType::PostPurchase,
    entries: [
        ['account' => $buyer->cashAccount(), 'amount' => -$price],
        ['account' => $creator->holdingsAccount(), 'amount' => $creatorEarnings],
        ['account' => LedgerAccount::platformRevenue(), 'amount' => $fee],
    ],
    metadata: ['purchase_id' => $purchase->id],
);

❌ NEVER do:

// ❌ Direct wallet manipulation — will fail ledger tests
$wallet->balance -= $amount;
$wallet->save();

4. Exception Pattern

// ✅ app/Domain/Exceptions/SpecificDomainException.php
final class EmailAlreadyRegisteredException extends DomainException
{
    public function errorCode(): ErrorCode
    {
        return ErrorCode::EmailAlreadyRegistered;
    }

    public function httpStatus(): int
    {
        return 430;  // Business rule violation
    }
}

Add error code to enum:

// ✅ app/Core/Exceptions/ErrorCode.php
enum ErrorCode: string
{
    case EmailAlreadyRegistered = 'EMAIL_ALREADY_REGISTERED';
    // ...
}

API Response Shapes (Critical)

2xx Success (Shape A):

{
  "message": "Created",
  "data": { "id": "01HQ...", "email": "user@loreax.app" },
  "meta": { "requestId": "01HQ...", "timestamp": "..." }
}

422 Validation (Shape B):

{
  "message": "Validation failed",
  "errors": {
    "email": ["The email field is required."],
    "password": ["Must be at least 8 characters."]
  },
  "meta": { ... }
}

430+ Business Error (Shape C):

{
  "errorCode": "EMAIL_ALREADY_REGISTERED",
  "message": "This email is already registered.",
  "meta": { ... }
}

⚠️ Never include "success": true/false — the shape IS the status.

Authorization: Scopes

Admin operations use scopes (not roles):

// ✅ Use enum
$this->identity->authorizeScope(Scope::PostReadWrite);

// ❌ Never raw strings
$this->identity->authorizeScope('post:write');  // WRONG

Scopes are in App\Core\Authorization\Scope enum.

Code Standards (Non-Negotiable)

Every PHP file starts with:

<?php

declare(strict_types=1);

namespace App\Domain\Subdomain;

Classes are final by default:

// ✅
final class UserService implements IUserService {}

// ❌ Non-final without reason
class UserService implements IUserService {}

Interfaces are I-prefixed:

// ✅
interface IUserService {}
final class UserService implements IUserService {}

// ❌ No prefix
interface UserService {}
class UserServiceImpl implements UserService {}

⏱️ Hour 3: Read the Documentation

Essential Reading (in order)

  1. README.md (5 min)

    • Project overview, tech stack, features
  2. AGENTS.md (15 min)

    • Code standards, testing conventions, patterns
  3. Domains Overview (10 min)

    • What each domain does, dependencies
  4. Domain README (5 min)

    • Your assigned domain, its models, contracts, services
  5. File Structure Guide (10 min)

    • Where files live, why, naming conventions

⏱️ Hour 4: Your First Task

Pick a Beginner-Friendly Task

Choose from IMPLEMENTATION_PLAN.md — look for tasks marked:

  • Status: Ready (dependencies complete)
  • Difficulty: Easy or Medium
  • 📊 PR Coverage: < 200 lines (small, reviewable)

Example tasks:

  • Add a new field to a model + test
  • Create a new exception + test
  • Implement a small action
  • Write missing tests
  • Fix a Larastan warning

Follow the Implementation Checklist

Step 1: Create Feature Branch

git checkout -b feat/domain-task-description

Step 2: Understand What to Build

  • Read the task description in IMPLEMENTATION_PLAN.md
  • Check domain README for context
  • Find similar implementations in the codebase

Step 3: Implement Following Patterns

  • Action for business logic
  • Tests (Unit + Feature)
  • OpenAPI annotations (if HTTP endpoint)
  • PHPDoc on public methods

Step 4: Validate

./vendor/bin/phpunit              # All tests pass
./vendor/bin/pint                 # Style clean
./vendor/bin/phpstan analyse      # Level 8 compliant
php artisan l5-swagger:generate   # Docs generate

Step 5: Commit & Push

git add .
git commit -m "feat: domain: short description"
git push origin feat/domain-task-description

Step 6: Open Pull Request

  • Reference the task from IMPLEMENTATION_PLAN.md
  • Link related issues
  • Request review from tech lead

Step 7: Address Feedback

  • Code review may request changes
  • Iterate until approved
  • Merge when CI passes and approved

🎓 Common First-Task Flows

Example 1: Add a Field to User Model

# 1. Create feature branch
git checkout -b feat/identity-add-phone-field

# 2. Create migration
php artisan make:migration add_phone_to_users_table

# 3. Edit migration: add phone column

# 4. Run migration
php artisan migrate:fresh

# 5. Update User model
# - Add $fillable = [..., 'phone']
# - Add validation rule to DTO
# - Update FormRequest

# 6. Write tests
# tests/Feature/Identity/UserPhoneTest.php
#   - Happy path test
#   - Validation tests

# 7. Verify
./vendor/bin/phpunit tests/Feature/Identity/
./vendor/bin/phpstan analyse

# 8. Commit and push
git add .
git commit -m "feat: identity: add phone field to users"
git push origin feat/identity-add-phone-field

# 9. Create PR

Example 2: Implement a New Action

# 1. Create feature branch
git checkout -b feat/payments-validate-withdrawal

# 2. Create Action class
# app/Payments/Actions/ValidateWithdrawalAction.php
# - Constructor injection of dependencies
# - run() method returns User or throws DomainException
# - Add PHPDoc with pre/postconditions

# 3. Create DTO
# app/Payments/Data/ValidateWithdrawalData.php
# - Properties with validation attributes

# 4. Create Exception (if needed)
# app/Payments/Exceptions/InvalidWithdrawalAmountException.php

# 5. Write Unit tests
# tests/Unit/Payments/Actions/ValidateWithdrawalActionTest.php
# - Happy path
# - Each business rule violation

# 6. Write Feature tests
# tests/Feature/Payments/WithdrawalTest.php
# - Happy path with HTTP
# - Error paths with HTTP
# - Assert response shape

# 7. Integrate into controller (if HTTP endpoint)

# 8. Verify coverage >= 80%
./vendor/bin/phpunit --coverage-text

# 9. Commit and push
git commit -m "feat: payments: implement withdrawal validation action"
git push origin feat/payments-validate-withdrawal

🚀 Your First Week

Day 1 (Today):

  • ✅ Environment setup
  • ✅ Read core documentation
  • ✅ Start first task

Day 2:

  • Complete first task + PR
  • Get comfortable with codebase
  • Review another PR

Day 3:

  • Pick second task (slightly harder)
  • Understand domain dependencies
  • Write more tests

Day 4:

  • Work on assigned domain tasks
  • Pair program with a teammate
  • Ask questions (no such thing as "dumb question")

Day 5:

  • Review others' PRs
  • Learn by reading code
  • Submit 2-3 PRs

🔧 Key Commands (Bookmarks These)

# Development
php artisan serve                     # Start server (port 8000)
php artisan horizon                   # Start job worker
npm run dev                           # Compile assets

# Testing
./vendor/bin/phpunit                  # All tests
./vendor/bin/phpunit tests/Feature/   # Feature tests only
./vendor/bin/phpunit --filter=Login   # Tests matching "Login"
./vendor/bin/phpunit --coverage-text  # Coverage report

# Code Quality
./vendor/bin/pint                     # Fix style
./vendor/bin/phpstan analyse          # Static analysis
php artisan l5-swagger:generate       # Update API docs

# Database
php artisan migrate:fresh             # Reset DB
php artisan migrate:fresh --seed      # Reset + seed
php artisan tinker                    # Interactive shell

# Git
git branch -a                         # List branches
git status                            # Check changes
git diff                              # View changes
git log --oneline | head -10          # Recent commits

📚 Documentation Map

Need Resource
First day This page + AGENTS.md
How to code AGENTS.md §1-6
Domain info Domain README or wiki
API specs loreax-technical-design.md or Swagger at /docs/api
Test patterns AGENTS.md §2, then see existing tests
Database schema Run migrations, check database/migrations/
Admin panel Admin BackOffice domain README
Deployment loreax-technical-design.md §23

❓ FAQ

Q: Where do I ask questions?
A: Slack #loreax-dev, or ask your team lead. No question is too basic.

Q: What if tests fail?
A: Read the error, check AGENTS.md, compare with similar passing tests, ask for help.

Q: How do I know if my code is right?
A: Passes: phpunit (tests), pint (style), phpstan (analysis), and code review ✅

Q: Can I modify a model migration after running it?
A: Create a new migration. Never edit old migrations (they're in production).

Q: How long should my PR be?
A: < 400 lines ideally. Big features get broken into multiple PRs.

Q: Do I need to write documentation?
A: PHPDoc on public methods, yes. Long explanations go in domain README.

Q: What if I break something?
A: Happens to everyone. CI will catch it. Fix and push. Ask for help if stuck.

Q: How do I run a single test?
A: ./vendor/bin/phpunit tests/Feature/Domain/TestNameTest.php

Q: Can I use var_dump() for debugging?
A: Yes, but delete before pushing. Consider adding proper logging instead.

Q: What's the difference between Unit and Feature tests?
A: Unit tests check pure logic (no DB/HTTP). Feature tests run full HTTP flow with DB.


📞 Getting Help

Immediate Issues

  • Can't start server: Check docker-compose ps, ensure services running
  • Tests failing: Run ./vendor/bin/phpunit --filter=<test> to isolate
  • Composer issues: composer clear-cache && composer install
  • Git issues: Ask on Slack, don't force push

Learning Resources

  • Code patterns: Look at similar domain (e.g., if building payments, study Identity domain)
  • API format: Check loreax-technical-design.md §3.8
  • Testing: See AGENTS.md §2, then look at existing tests in same domain
  • Validation: Check Laravel's form request validation docs + AGENTS.md examples

Escalation

  • Code review rejected: Ask reviewer for specific feedback, clarify requirements
  • Architecture unclear: Pair with tech lead on similar feature
  • Performance issue: Profile with Debugbar, discuss optimization strategy

✅ Onboarding Checklist

  • Environment setup complete (Docker, PHP, Composer)
  • All tests passing (./vendor/bin/phpunit)
  • Read AGENTS.md (code standards)
  • Read domain README (your assigned domain)
  • Read IMPLEMENTATION_PLAN.md (assigned tasks)
  • Understand Action + Controller patterns
  • Understand Response Shapes (A/B/C)
  • Understand Ledger pattern
  • First task started
  • First PR created
  • First PR reviewed and merged

🎉 Welcome!

You're now part of the Loreax team. Your code will eventually handle real money for real creators. Quality matters. Take your time, ask questions, and enjoy building something meaningful.

Next step: Pick your first task from IMPLEMENTATION_PLAN.md and start coding! 🚀


Last Updated: April 25, 2026