Разработка бэкенда сайта на PHP (Laravel)
Laravel — наиболее зрелый PHP-фреймворк с полным стеком инструментов: Eloquent ORM, Artisan CLI, очереди (Queue), события (Events), планировщик (Scheduler), уведомления (Notifications), файловое хранилище (Storage), кеш, трансляция событий в реальном времени (Broadcasting). Всё это в одном пакете с согласованной документацией.
Laravel занимает разумную нишу: когда нужно быстро, надёжно и с возможностью роста. Среди PHP-фреймворков это практически стандарт для новых проектов.
Структура приложения
Laravel следует MVC, но правильная организация требует чуть больше структуры — сервисный слой для бизнес-логики:
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
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');
}
}
Сложные запросы — через Eloquent или 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 и валидация
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' => 'Название обязательно',
'price.min' => 'Цена должна быть больше нуля',
'category_id.exists' => 'Категория не найдена',
];
}
}
Аутентификация через Laravel Sanctum
Sanctum обслуживает SPA (cookie-based) и мобильные приложения (token-based) единым механизмом:
// 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),
]);
}
Очереди и события
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
{
// Уведомление об ошибке в Slack/Telegram
Notification::route('slack', config('services.slack.webhook'))
->notify(new JobFailed($this->order->id, $exception->getMessage()));
}
}
// Диспетчеризация
SendOrderConfirmation::dispatch($order)->delay(now()->addSeconds(5));
// Событие + листенеры
event(new OrderPlaced($order));
Кеширование
use Illuminate\Support\Facades\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);
});
// Инвалидация при изменении
Cache::tags(['products', "category:{$product->category_id}"])->flush();
Планировщик
// 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();
}
Сроки разработки
- Проект + миграции + модели — 3–5 дней
- API + Resources + Requests — 1–2 недели
- Auth + Permissions (Spatie) — 3–5 дней
- Очереди + события — 3–5 дней
- Интеграции — 1–3 недели
- Тесты (Feature + Unit) — 1 неделя
Средний корпоративный сайт или веб-приложение: 5–10 недель. Laravel — зрелый выбор с огромной экосистемой пакетов и предсказуемым поведением.







