Comprehensive guide for developers working on AutoClean.
- Development Environment Setup
- Development Workflow
- Coding Standards
- Creating Features
- Database Migrations
- Testing
- Debugging
- Git Workflow
Install the following:
- PHP 8.2+ with required extensions
- Composer 2.x
- MySQL/MariaDB
- Node.js 18+ and npm
- Git
# Clone repository
git clone https://github.com/yourusername/autoclean.git
cd autoclean
# Install dependencies
composer install
npm install
# Setup environment
cp .env.example .env
php artisan key:generate
# Configure database in .env
# DB_CONNECTION=mysql
# DB_DATABASE=autoclean_dev
# Run migrations with seeders
php artisan migrate:fresh --seed
# Start development server
composer devThe composer dev command starts:
- Laravel development server (http://localhost:8000)
- Vite dev server with hot reload
- Queue worker
- Laravel Pail (log viewer)
Extensions:
- PHP Intelephense
- Laravel Extension Pack
- Tailwind CSS IntelliSense
- Livewire Language Support
- EditorConfig
Settings (.vscode/settings.json):
{
"php.suggest.basic": false,
"intelephense.files.maxSize": 5000000,
"editor.formatOnSave": true,
"editor.defaultFormatter": "bmewburn.vscode-intelephense-client",
"tailwindCSS.includeLanguages": {
"blade": "html"
}
}- Enable Laravel plugin
- Enable Livewire plugin
- Configure PHP interpreter
- Setup database connection
- Enable Tailwind CSS support
# Full stack (recommended)
composer dev
# Or individual services
php artisan serve # Terminal 1
npm run dev # Terminal 2
php artisan queue:listen # Terminal 3
php artisan pail # Terminal 4-
Create Feature Branch
git checkout -b feature/your-feature-name
-
Make Changes
- Write code following Coding Standards
- Write tests for new features
- Update documentation
-
Run Tests
composer test -
Format Code
./vendor/bin/pint
-
Commit Changes
git add . git commit -m "Add: your feature description"
-
Push and Create PR
git push origin feature/your-feature-name
AutoClean uses Laravel Pint for code formatting.
Run Pint:
# Fix all files
./vendor/bin/pint
# Check without fixing
./vendor/bin/pint --test
# Fix specific file
./vendor/bin/pint app/Models/Task.phpConfiguration: pint.json
{
"preset": "laravel"
}Classes:
// Models: Singular, PascalCase
class Task extends Model {}
class TaskSchedule extends Model {}
// Controllers: Singular, PascalCase + Controller
class TaskController extends Controller {}
// Livewire: Nested namespaces, PascalCase
class Admin\Tasks\Create extends Component {}
// Services: Descriptive name + Service
class RecurrenceCalculator {}
class SettingsService {}Database:
// Tables: Plural, snake_case
tasks, task_schedules, time_logs
// Columns: snake_case
due_date, completed_at, station_id
// Foreign keys: singular_table_id
station_id, user_id, task_schedule_id
// Pivot tables: alphabetically ordered, singular
station_user (not user_station)Routes:
// Resource routes: plural, kebab-case
Route::resource('task-schedules', TaskScheduleController::class);
// Livewire routes: descriptive, kebab-case
Route::get('/admin/tasks/create', Create::class);namespace App\Livewire\Admin\Tasks;
use Livewire\Component;
use Livewire\Attributes\Validate;
class Create extends Component
{
// Public properties (reactive)
#[Validate('required|string|max:255')]
public string $name = '';
#[Validate('required|exists:stations,id')]
public int $station_id;
// Computed properties (cached)
public function stations()
{
return Station::active()->get();
}
// Lifecycle hooks
public function mount(): void
{
$this->station_id = auth()->user()->stations->first()?->id;
}
// Public methods (callable from view)
public function save(): void
{
$this->validate();
Task::create([
'name' => $this->name,
'station_id' => $this->station_id,
]);
$this->redirect(route('tasks.index'));
}
// Render method
public function render()
{
return view('livewire.admin.tasks.create');
}
}Always use typed properties:
// Good
public string $name = '';
public int $quantity = 0;
public ?Carbon $dueDate = null;
public array $selectedItems = [];
// Bad
public $name;
public $quantity;
public $dueDate;Use #[Validate] attribute:
use Livewire\Attributes\Validate;
#[Validate('required|email')]
public string $email = '';
#[Validate('required|min:8')]
public string $password = '';Or rules() method for dynamic validation:
protected function rules(): array
{
return [
'email' => ['required', 'email', Rule::unique('users')->ignore($this->user)],
'password' => 'required|min:8',
];
}namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class Task extends Model
{
use SoftDeletes;
// 1. Properties
protected $fillable = [
'name',
'description',
'station_id',
'due_date',
'completed',
];
protected $casts = [
'due_date' => 'date',
'completed' => 'boolean',
'completed_at' => 'datetime',
];
// 2. Relationships
public function station(): BelongsTo
{
return $this->belongsTo(Station::class);
}
public function completedBy(): BelongsTo
{
return $this->belongsTo(User::class, 'completed_by');
}
// 3. Scopes
public function scopeCompleted($query)
{
return $query->where('completed', true);
}
public function scopeOverdue($query)
{
return $query->where('due_date', '<', now())
->where('completed', false);
}
// 4. Accessors
public function getIsOverdueAttribute(): bool
{
return $this->due_date->isPast() && !$this->completed;
}
// 5. Methods
public function markAsCompleted(User $user): void
{
$this->update([
'completed' => true,
'completed_at' => now(),
'completed_by' => $user->id,
]);
}
// 6. Boot method
protected static function booted(): void
{
static::created(function (Task $task) {
// Log task creation
});
}
}Eager Loading (avoid N+1 queries):
// Good
$tasks = Task::with(['station', 'completedBy'])->get();
// Bad
$tasks = Task::all();
foreach ($tasks as $task) {
echo $task->station->name; // N+1 query
}Select Only Needed Columns:
// Good
$tasks = Task::select('id', 'name', 'due_date')->get();
// Avoid
$tasks = Task::all(); // Selects everythingUse Chunking for Large Datasets:
Task::chunk(100, function ($tasks) {
foreach ($tasks as $task) {
// Process task
}
});1. Create Component:
php artisan make:livewire Admin/Tasks/CreateThis creates:
app/Livewire/Admin/Tasks/Create.phpresources/views/livewire/admin/tasks/create.blade.php
2. Define Route:
// routes/web.php
use App\Livewire\Admin\Tasks\Create;
Route::middleware(['auth'])->group(function () {
Route::get('/admin/tasks/create', Create::class)->name('tasks.create');
});3. Implement Component Logic:
// app/Livewire/Admin/Tasks/Create.php
class Create extends Component
{
public string $name = '';
public int $station_id;
public function save(): void
{
$this->validate([
'name' => 'required|string|max:255',
'station_id' => 'required|exists:stations,id',
]);
Task::create([
'name' => $this->name,
'station_id' => $this->station_id,
]);
$this->redirect(route('tasks.index'));
}
public function render()
{
return view('livewire.admin.tasks.create');
}
}4. Create View:
{{-- resources/views/livewire/admin/tasks/create.blade.php --}}
<div>
<form wire:submit="save">
<flux:input
wire:model="name"
label="Task Name"
placeholder="Enter task name"
/>
<flux:select wire:model="station_id" label="Station">
@foreach(Station::all() as $station)
<option value="{{ $station->id }}">{{ $station->name }}</option>
@endforeach
</flux:select>
<flux:button type="submit">Create Task</flux:button>
</form>
</div>1. Create Model and Migration:
php artisan make:model Category -m2. Define Migration:
// database/migrations/xxxx_create_categories_table.php
public function up(): void
{
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description')->nullable();
$table->boolean('active')->default(true);
$table->timestamps();
});
}3. Define Model:
// app/Models/Category.php
class Category extends Model
{
protected $fillable = ['name', 'description', 'active'];
protected $casts = [
'active' => 'boolean',
];
public function scopeActive($query)
{
return $query->where('active', true);
}
}4. Create Factory:
php artisan make:factory CategoryFactory// database/factories/CategoryFactory.php
public function definition(): array
{
return [
'name' => $this->faker->words(3, true),
'description' => $this->faker->sentence,
'active' => true,
];
}5. Run Migration:
php artisan migrate1. Create Service Class:
// app/Services/NotificationService.php
namespace App\Services;
class NotificationService
{
public function sendTaskReminder(Task $task): void
{
// Send notification logic
}
public function sendLowStockAlert(Station $station, InventoryItem $item): void
{
// Send alert logic
}
}2. Register Service (Optional):
// app/Providers/AppServiceProvider.php
public function register(): void
{
$this->app->singleton(NotificationService::class);
}3. Use Service:
// In a Livewire component or controller
use App\Services\NotificationService;
class TaskController
{
public function __construct(
private NotificationService $notifications
) {}
public function sendReminder(Task $task)
{
$this->notifications->sendTaskReminder($task);
}
}# Create table migration
php artisan make:migration create_categories_table
# Modify table migration
php artisan make:migration add_priority_to_tasks_table
# Migration with model
php artisan make:model Category -mUse Descriptive Names:
// Good
xxxx_create_task_schedules_table.php
xxxx_add_completed_by_to_tasks_table.php
// Bad
xxxx_update_tasks.php
xxxx_new_fields.phpAlways Include Down Method:
public function up(): void
{
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('categories');
}Use Foreign Keys:
Schema::create('tasks', function (Blueprint $table) {
$table->id();
$table->foreignId('station_id')->constrained()->cascadeOnDelete();
$table->foreignId('completed_by')->nullable()->constrained('users')->nullOnDelete();
$table->timestamps();
});Add Indexes:
$table->index('due_date');
$table->index(['station_id', 'completed']);
$table->unique('email');# Run pending migrations
php artisan migrate
# Rollback last batch
php artisan migrate:rollback
# Rollback all migrations
php artisan migrate:reset
# Fresh migration (drop all + migrate)
php artisan migrate:fresh
# Fresh with seeders
php artisan migrate:fresh --seed
# Migration status
php artisan migrate:statusphp artisan pail
# Filter by level
php artisan pail --level=error
# Filter by user
php artisan pail --user=1// Dump and die
dd($variable);
// Dump
dump($variable);
// Ray (if installed)
ray($variable);
// Log
Log::info('User logged in', ['user_id' => $user->id]);
Log::error('Task creation failed', ['error' => $e->getMessage()]);{{-- In Blade view --}}
@dump($variable)
{{-- Component state --}}
<pre>{{ json_encode($this->all(), JSON_PRETTY_PRINT) }}</pre>// Enable query log
\DB::enableQueryLog();
// Run queries
$tasks = Task::with('station')->get();
// Dump queries
dd(\DB::getQueryLog());php artisan tinker// Test queries
>>> Task::count()
=> 42
>>> User::first()
=> App\Models\User {#1234}
// Create records
>>> Task::factory()->create()
// Test services
>>> app(RecurrenceCalculator::class)->calculateNextOccurrence($schedule)feature/task-templates
bugfix/clock-out-validation
hotfix/security-patch
refactor/recurrence-calculator
docs/api-reference
Format:
Type: Brief description (50 chars max)
Detailed explanation if needed (wrap at 72 chars)
Types:
Add: New featureFix: Bug fixUpdate: Modification to existing featureRefactor: Code restructuringDocs: Documentation changesTest: Test additions or changesStyle: Code style changes (formatting)
Examples:
Add: Task template management
Fix: Clock out validation error
Update: Improve recurrence calculation performance
Refactor: Extract notification service
Docs: Add API reference for models
Test: Add task completion tests
Before submitting PR:
- All tests pass (
composer test) - Code formatted with Pint (
./vendor/bin/pint) - No debug code (dd(), dump(), console.log())
- Documentation updated
- Database migrations include down() method
- Foreign keys and indexes added
- Validation rules present
- Error handling implemented
- Security considerations addressed
- Responsive design (mobile-friendly)
# Development
composer dev # Start full dev stack
php artisan serve # Laravel server
npm run dev # Vite dev server
# Database
php artisan migrate # Run migrations
php artisan db:seed # Run seeders
php artisan migrate:fresh --seed # Reset & seed
# Testing
composer test # Run tests
./vendor/bin/pest --coverage # With coverage
# Code Quality
./vendor/bin/pint # Format code
./vendor/bin/pint --test # Check formatting
# Cache
php artisan optimize:clear # Clear all caches
php artisan config:cache # Cache config
php artisan route:cache # Cache routes
php artisan view:cache # Cache views
# Livewire
php artisan livewire:make # Create component
php artisan livewire:delete # Delete component
php artisan livewire:copy # Copy component
# Queue
php artisan queue:work # Process queue
php artisan queue:listen # Process with reload
php artisan queue:restart # Restart workers
# Logs
php artisan pail # View logs
php artisan log:clear # Clear logs- Review Testing Guide for writing tests
- Check Contributing Guide for PR process
- See API Reference for component details
Navigation: ← API Reference | Back to Documentation | Testing →