Website Backend Development with PHP (Laravel)
Laravel is the most mature PHP framework with a complete stack of tools: Eloquent ORM, Artisan CLI, queues, events, scheduler, notifications, storage, caching, real-time broadcasting. All in one package with consistent documentation.
Laravel occupies a reasonable niche: when you need to be fast, reliable, and have room to grow. Among PHP frameworks, it's practically the standard for new projects.
Application Structure
Laravel follows MVC, but proper organization requires a bit more structure — a service layer for business logic:
app/
Http/
Controllers/
Api/V1/
ProductController.php
UserController.php
OrderController.php
Requests/
CreateProductRequest.php
UpdateOrderRequest.php
Resources/
ProductResource.php
ProductCollection.php
Middleware/
EnsureRole.php
Models/
Product.php
User.php
Order.php
Services/
ProductService.php
OrderService.php
PaymentService.php
Repositories/
ProductRepository.php
Jobs/
SendOrderConfirmation.php
ProcessPayment.php
Events/
OrderPlaced.php
Listeners/
NotifyAdminOnOrder.php
routes/
api.php
web.php
Eloquent Models
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Builder;
class Product extends Model
{
use SoftDeletes;
protected $fillable = ['name', 'slug', 'price', 'category_id', 'attributes', 'is_active'];
protected $casts = [
'price' => 'decimal:2',
'attributes' => 'array',
'is_active' => 'boolean',
];
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
public function variants(): HasMany
{
return $this->hasMany(ProductVariant::class);
}
public function scopeActive(Builder $query): Builder
{
return $query->where('is_active', true);
}
public function scopeWithCategory(Builder $query): Builder
{
return $query->with('category:id,name,slug');
}
}
Complex queries via Eloquent or Query Builder:
$products = Product::query()
->active()
->withCategory()
->when($request->category_id, fn($q, $id) => $q->where('category_id', $id))
->when($request->search, fn($q, $s) => $q->where('name', 'like', "%{$s}%"))
->orderByDesc('created_at')
->paginate($request->per_page ?? 20);
API Resources
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class ProductResource extends JsonResource
{
public function toArray($request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'slug' => $this->slug,
'price' => (float) $this->price,
'category' => $this->whenLoaded('category', fn() => [
'id' => $this->category->id,
'name' => $this->category->name,
]),
'attributes' => $this->attributes,
'created_at' => $this->created_at->toISOString(),
];
}
}
Form Requests and Validation
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class CreateProductRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()->hasRole('admin');
}
public function rules(): array
{
return [
'name' => ['required', 'string', 'min:2', 'max:255'],
'price' => ['required', 'numeric', 'min:0.01'],
'category_id' => ['nullable', 'exists:categories,id'],
'attributes' => ['nullable', 'array'],
'description' => ['nullable', 'string'],
];
}
public function messages(): array
{
return [
'name.required' => 'Name is required',
'price.min' => 'Price must be greater than zero',
'category_id.exists' => 'Category not found',
];
}
}
Authentication via Laravel Sanctum
Sanctum serves both SPA (cookie-based) and mobile apps (token-based) with one mechanism:
// routes/api.php
Route::post('/auth/login', [AuthController::class, 'login']);
Route::post('/auth/logout', [AuthController::class, 'logout'])->middleware('auth:sanctum');
Route::middleware('auth:sanctum')->group(function () {
Route::get('/user', fn(Request $req) => $req->user());
Route::apiResource('products', ProductController::class);
});
// AuthController
public function login(Request $request): JsonResponse
{
$credentials = $request->validate([
'email' => 'required|email',
'password' => 'required|string',
]);
if (!Auth::attempt($credentials)) {
return response()->json(['message' => 'Invalid credentials'], 401);
}
$user = Auth::user();
$token = $user->createToken('api-token', $user->getAbilities())->plainTextToken;
return response()->json([
'token' => $token,
'user' => new UserResource($user),
]);
}
Queues and Events
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class SendOrderConfirmation implements ShouldQueue
{
use Queueable, InteractsWithQueue;
public int $tries = 3;
public int $backoff = 60;
public function __construct(private readonly Order $order) {}
public function handle(MailService $mailService): void
{
$mailService->sendOrderConfirmation($this->order);
}
public function failed(\Throwable $exception): void
{
Notification::route('slack', config('services.slack.webhook'))
->notify(new JobFailed($this->order->id, $exception->getMessage()));
}
}
// Dispatch
SendOrderConfirmation::dispatch($order)->delay(now()->addSeconds(5));
// Event + listeners
event(new OrderPlaced($order));
Caching
use Illuminate\Support\Facades\Cache;
// Tagged cache (redis)
$products = Cache::tags(['products', "category:{$categoryId}"])
->remember("products:cat:{$categoryId}:page:{$page}", 300, function () use ($categoryId, $page) {
return Product::active()->where('category_id', $categoryId)->paginate(20, ['*'], 'page', $page);
});
// Invalidate on change
Cache::tags(['products', "category:{$product->category_id}"])->flush();
Scheduler
// app/Console/Kernel.php
protected function schedule(Schedule $schedule): void
{
$schedule->job(new SyncInventory)->hourly();
$schedule->command('app:send-reminders')->dailyAt('09:00');
$schedule->call(function () {
DB::table('sessions')->where('last_activity', '<', now()->subDays(7))->delete();
})->weekly();
}
Development Timelines
- Project + migrations + models — 3–5 days
- API + Resources + Requests — 1–2 weeks
- Auth + Permissions (Spatie) — 3–5 days
- Queues + events — 3–5 days
- Integrations — 1–3 weeks
- Tests (Feature + Unit) — 1 week
Medium-scale corporate website or web app: 5–10 weeks. Laravel is a mature choice with a huge package ecosystem and predictable behavior.







