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)
-
README.md (5 min)
- Project overview, tech stack, features
-
AGENTS.md (15 min)
- Code standards, testing conventions, patterns
-
Domains Overview (10 min)
- What each domain does, dependencies
-
Domain README (5 min)
- Your assigned domain, its models, contracts, services
-
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