This is a Laravel 10 application for CSV file upload and processing. It uses Redis-backed queues and caching, Laravel WebSockets for real-time progress updates, and Horizon for queue monitoring.
app/
├── Console/ # Console commands and kernel
├── Enums/ # PHP enums (e.g., FileStatus)
├── Events/ # Broadcast events for WebSocket updates
├── Exceptions/ # Custom exception handlers
├── Http/
│ ├── Controllers/ # HTTP controllers
│ ├── Middleware/ # Request middleware
│ ├── Requests/ # Form request validation classes
│ └── Kernel.php # HTTP kernel configuration
├── Jobs/ # Queue jobs (implement ShouldQueue)
├── Listeners/ # Event listeners
├── Models/ # Eloquent models
├── Providers/ # Service providers
└── Services/ # Business logic services
database/
├── factories/ # Model factories for testing
├── migrations/ # Database migrations
└── seeders/ # Database seeders
resources/
├── css/ # Frontend styles
├── js/ # Frontend JavaScript (Vite entry points)
└── views/ # Blade templates and error pages
routes/
├── web.php # Web routes
├── api.php # API routes
├── channels.php # Broadcasting channels
└── console.php # Console commands
tests/
├── Feature/ # Feature tests (HTTP, integration)
└── Unit/ # Unit tests (isolated class tests)
# Install PHP dependencies
composer install
# Install frontend dependencies
npm install
# Copy environment file
cp .env.example .env
# Generate application key
php artisan key:generate
# Run database migrations
php artisan migrate# Copy Docker environment file
cp .env.docker .env
# Start all services (Laravel, MySQL, Redis, queue worker, WebSocket server)
docker compose up -d --build
# Install dependencies inside the app container
docker compose exec laravel composer install
docker compose exec laravel npm install
# Generate application key and run migrations
docker compose exec laravel php artisan key:generate
docker compose exec laravel php artisan migrate
# View container logs
docker compose logs -f
# Stop all containers
docker compose down
# Run tests or artisan commands in container
docker compose exec laravel php artisan test
docker compose exec laravel php artisan migrate# PHP development server
php artisan serve
# Frontend hot reload (Vite)
npm run dev
# Queue worker
php artisan queue:work
# WebSocket server for real-time updates
php artisan websockets:serve
# Horizon dashboard / worker alternative
php artisan horizon# Build frontend assets for production
npm run build# Format PHP code (must run before commits)
./vendor/bin/pint
# Check code style without modifying
./vendor/bin/pint --test# Run all tests
php artisan test
# OR
./vendor/bin/phpunit
# Run only feature tests
php artisan test --testsuite=Feature
# Run only unit tests
php artisan test --testsuite=Unit
# Run a specific test file
php artisan test tests/Feature/FileUploadTest.php
# Run a specific test method
php artisan test --filter test_file_upload_success
# Run tests with parallel execution
php artisan test --parallel
# Run tests with coverage report
php artisan test --coverage# Run pending migrations
php artisan migrate
# Rollback last migration
php artisan migrate:rollback
# Clear application cache
php artisan config:clear
php artisan cache:clear
# Clear all caches (config, route, view)
php artisan optimize:clear
# Restart queue workers after job code changes
php artisan queue:restart- Indentation: 4spaces (no tabs)
- Opening brace: Same line for classes/methods
- Namespace: One blank line after
<?php - Use statements: One per line, alphabetically sorted
<?php
namespace App\Services;
use App\Enums\FileStatus;
use App\Models\File;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
class FileService
{
// ...
}- Group imports by type: framework first, then project classes
- Always import classes at the top of the file
- Use fully qualified names for facades only when necessary
use App\Enums\FileStatus; // Project classes first
use App\Models\File;
use Illuminate\Bus\Batch; // Framework classes second
use Illuminate\Support\Facades\Log;| Type | Convention | Example |
|---|---|---|
| Classes | PascalCase | FileController |
| Methods | camelCase | processFile() |
| Properties | camelCase | $fileName |
| Constants | UPPER_SNAKE | MAX_ATTEMPTS |
| Variables | camelCase | $uploadedFile |
| Database tables | snake_case | files, products |
| Migrations | snake_case with timestamp | 2024_01_15_create_files_table |
| Jobs | Descriptive noun + verb | ProcessUpdate |
| Events | Past tense or noun | CsvFileUploaded, CsvUploadProgress |
| Enums | PascalCase case names | FileStatus::Processing |
- Always use return type declarations
- Use parameter types in method signatures
- Use union types when needed (
int|false) - Use
readonlyfor constructor property promotion in PHP 8.1+
public function __construct(
private readonly array $rows,
private readonly array $header
) {}
public function headerIndex(string $columnName): int|false
{
return array_search($columnName, $this->header);
}- Use try-catch in controllers to catch exceptions and log errors
- Log errors with context using
Log::error() - Return user-friendly error messages, never expose stack traces
public function store(FileUploadRequest $request): RedirectResponse
{
try {
$file = $this->fileService->processAndStoreFile($request->file('file'));
return redirect()->route('files.index')->with('success-msg', 'Success!');
} catch (\Throwable $e) {
Log::error('File upload failed: ' . $e->getMessage(), ['exception' => $e]);
return redirect()->route('files.index')->with('error-msg', 'Upload failed.');
}
}- Services handle business logic and should be injected via constructor
- Use constructor property promotion for dependency injection
- Return typed values from service methods
class FileService
{
public function __construct(
protected JobBatchService $jobBatchService
) {}
public function getFiles(int $perPage = 15): LengthAwarePaginator
{
return File::latest()->paginate($perPage);
}
}- Use Form Request classes for validation, not inline validation
- Separate
authorize()andrules()methods
class FileUploadRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'file' => 'required|file|mimes:csv,xlsx,xls',
];
}
}- Implement
ShouldQueuefor async processing - Use
Batchabletrait for job batches - Check if batch is cancelled before processing
class ProcessUpdate implements ShouldQueue
{
use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function handle(): void
{
if ($this->batch()?->cancelled()) {
return;
}
// Process job...
}
}- Use events for decoupled communication
- Events broadcasting progress should implement
ShouldBroadcastNow - Use base event classes for shared functionality
abstract class BaseCsvUploadEvent implements ShouldBroadcastNow
{
public function broadcastWith(): array
{
return ['batch' => $this->batch_arr];
}
}- Never commit
.envor.env.*files - Use Form Request classes for validation and authorization
- Sanitize file uploads (check mime types, size limits)
- Never expose internal errors to users
- Always validate file uploads with
mimesand size rules - Use CSRF protection (built into Laravel web routes)
- Restart queue workers after changing job code:
php artisan queue:restart - WebSocket server must be running for real-time progress:
php artisan websockets:serve - Use Redis for queue driver in production (
QUEUE_CONNECTION=redis) - Redis must be running before starting queue workers
- In Docker, Supervisor starts the HTTP server, queue worker, S3 notification poller, and WebSocket server in the
laravelcontainer
- Use conventional commits format:
feat:,fix:,refactor:,docs:,test: - Keep commits atomic and focused
- Reference issue numbers when applicable
- Run
./vendor/bin/pintbefore committing
Key environment variables for this project:
DB_HOST,DB_DATABASE,DB_USERNAME,DB_PASSWORD- Database connectionREDIS_HOST- Redis connection for queuesQUEUE_CONNECTION=redis- Queue driverBROADCAST_DRIVER=pusher- Broadcasting driverPUSHER_*- WebSocket/Pusher configurationVITE_PUSHER_*- Frontend WebSocket configuration