30 վայրկյանում
Laravel-ը PHP framework է, որը «տուփից» տալիս է routing, տվյալների բազայի շերտ (Eloquent), վալիդացիա, queue-ներ, scheduling և authentication։ Դու նկարագրում ես, թե ինչ պետք է կատարվի յուրաքանչյուր URL-ի համար, իսկ Laravel-ը հոգում է շրջապատող ողջ «սանտեխնիկան»։ Laravel 13-ը (2026-ի մարտ) պահանջում է PHP 8.3+ և 12-ից թեթև թարմացում է։
Ինչու եմ օգտագործում
- Օգտագործում եմ, որովհետև մեկ framework-ը ծածկում է իրական պրոդուկտի ամբողջ backend-ը՝ API, ադմին, queue-ներ, ժամանակացույցով job-եր, email-ներ և auth, այնպիսի conventions-ով, որոնք ցանկացած նոր ծրագրավորող արդեն գիտի։
- Օգտագործում եմ, որովհետև Eloquent-ն ու FormRequest-ները թույլ են տալիս արագ թողարկել ֆիչեր՝ առանց կառուցվածքից հրաժարվելու, ու կոդը երկու տարի անց էլ լավ է կարդացվում։
- Օգտագործում եմ, որովհետև queue-ներն ու scheduler-ը ներկառուցված են, ինչը կարևոր է commerce-ի ծանր մասերի համար՝ ERP sync, email-ներ, սեգմենտներ և հաշվետվություններ։
- Օգտագործում եմ, որովհետև նրա թեստավորման գործիքները (HTTP թեստեր, factory-ներ, fake-եր) backend թեստերն այնքան էժան են դարձնում, որ թիմն իսկապես գրում է դրանք։
Ինչպես է աշխատում
01Request-ի կյանքի ցիկլը
Յուրաքանչյուր request մտնում է public/index.php-ով։ Laravel-ը boot է անում հավելվածը (service provider-ները գրանցում են binding-ներն ու config-ը), հետո request-ն անցկացնում middleware-ների շարքով, ինչպես օդանավակայանի ստուգման կետերով։ Router-ը URL-ն ու HTTP մեթոդը համապատասխանեցնում է route-ին, որը կանչում է controller-ի մեթոդը։ Controller-ը վերադարձնում է ինչ-որ բան (model, զանգված, Resource, view), և Laravel-ը դա վերածում է HTTP response-ի, որը հետ է գնում նույն middleware-ներով։ Laravel 11+-ում այս ամբողջ կապակցումն ապրում է bootstrap/app.php-ում․ route-ները, middleware-ներն ու exception handling-ը գրանցում ես այնտեղ՝ առանձին Kernel class-ի փոխարեն։ Այս հերթականությունն իմանալը բացատրում է բագերի մեծ մասը․ եթե auth-ը ձախողվում է, նայիր middleware-ներին, եթե տվյալներն են սխալ՝ controller-ին և ավելի ներքև։
- Service provider՝ class, որը boot-ի ժամանակ բաներ է գրանցում service container-ում։
- Service container՝ Laravel-ի dependency injection համակարգը․ այն քո փոխարեն կառուցում է class-ները և դրանց կախվածությունները։
- Middleware՝ կոդ, որն աշխատում է controller-ից առաջ կամ հետո (auth, throttling, լոկալ, CORS)։
02Routing, middleware և «բարակ» controller-ներ
Route-ները URL-ները կապում են կոդի հետ։ Խմբավորիր դրանք ըստ լսարանի և middleware-ը կցիր խմբին, որպեսզի ներսի յուրաքանչյուր route ժառանգի authentication, rate limit-եր կամ լոկալ։ Controller-ները պահիր բարակ․ նրանց գործը վալիդացված input-ը կարդալն է, իրական աշխատանքն անող service-ը կանչելը և response վերադարձնելը։ Բիզնես կանոնները (ինչպես է գինը հաշվարկվում, երբ կարելի է պատվերը չեղարկել) պատկանում են service-ներին կամ action-ներին, իսկ տվյալների հասանելիությունը՝ model-ներին։ Այս բաժանումը՝ controller-ից service, service-ից model, նշանակում է, որ նույն տրամաբանությունը կարող ես կանչել API endpoint-ից, Artisan command-ից կամ queue job-ից՝ առանց կոդը կրկնօրինակելու։
// routes/api.php
use App\Http\Controllers\Api\OrderController;
use Illuminate\Support\Facades\Route;
Route::middleware(['auth:sanctum', 'throttle:api'])
->prefix('v1')
->group(function () {
Route::get('orders', [OrderController::class, 'index']);
Route::post('orders', [OrderController::class, 'store']);
Route::get('orders/{order}', [OrderController::class, 'show']);
});
03Վալիդացիա FormRequest-ներով, ելք՝ API Resource-ներով
Վալիդացրու եզրին։ FormRequest class-ը պահում է մեկ action-ի կանոններն ու authorize() ստուգումը․ եթե վալիդացիան չանցնի, Laravel-ը կվերադարձնի 422 response՝ դաշտերի սխալներով, նախքան controller-ի աշխատելը։ Controller-ի ներսում $request->validated()-ը տալիս է միայն ստուգումն անցած դաշտերը, ինչը պաշտպանում է mass assignment-ից։ Ելքի ճանապարհին API Resource-ը model-ը վերածում է հենց այն JSON ձևի, որն ակնկալում են քո client-ները։ Սա բազայի սյունակներն անջատում է քո հրապարակային պայմանագրից․ կարող ես վերանվանել սյունակը՝ առանց մոբայլ հավելվածը կոտրելու։ Resource-ները հեշտացնում են նաև պայմանական դաշտերը, օրինակ՝ relation-ներն ավելացնել միայն այն դեպքում, երբ դրանք բեռնված են։
class StoreOrderRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()->can('create', Order::class);
}
public function rules(): array
{
return [
'items' => ['required', 'array', 'min:1'],
'items.*.product_id' => ['required', 'integer', 'exists:products,id'],
'items.*.qty' => ['required', 'integer', 'between:1,99'],
'note' => ['nullable', 'string', 'max:500'],
];
}
}
04Eloquent, relationship-ներ և eager loading
Eloquent-ը Laravel-ի ORM-ն է․ յուրաքանչյուր model class համապատասխանում է մեկ աղյուսակի, իսկ relationship-ները (hasMany, belongsTo, belongsToMany, morphMany) model-ի մեթոդներ են։ Դասական թակարդը N+1 խնդիրն է․ բեռնում ես 50 պատվեր, հետո ցիկլում դիմում $order->customer-ին, և Eloquent-ը կատարում է 50 լրացուցիչ query։ with()-ով eager loading-ը relation-ը բերում է մեկ լրացուցիչ query-ով։ Development-ում կանչիր Model::preventLazyLoading(), որպեսզի lazy load-երը exception նետեն, և N+1-ը վաղ բռնես։ Քանակների համար օգտագործիր withCount(), մեծ ցուցակների համար ընտրիր միայն անհրաժեշտ սյունակները, և pagination արա ամեն ինչի համար, ինչը օգտատերը կարող է scroll անել։ Laravel 13-ը թույլ է տալիս նաև model-ի շատ հատկություններ կարգավորել PHP attribute-ներով, բայց դասական property-ները շարունակում են աշխատել։
// AppServiceProvider::boot()
Model::preventLazyLoading(! app()->isProduction());
// One query for orders, one for customers, one for items
$orders = Order::query()
->with(['customer:id,name', 'items.product:id,name,price'])
->withCount('items')
->where('status', OrderStatus::Paid)
->latest()
->paginate(20);
return OrderResource::collection($orders);
05Queue-ներ, scheduler և authentication
Ամեն դանդաղ կամ անվստահելի բան (email-ներ, SMS, վճարման callback-ներ, export-ներ, ERP sync) գնում է queue job-ի մեջ․ օգտատերն արագ response է ստանում, իսկ worker պրոցեսը ֆոնում կատարում է աշխատանքը՝ retry-ներով։ Scheduler-ը cron գրառումների կույտը փոխարինում է մեկով․ cron-ն ամեն րոպե գործարկում է php artisan schedule:run, իսկ քո ժամանակացույցը սահմանվում է routes/console.php-ում։ Authentication-ի համար Sanctum-ը լռելյայն ընտրությունն է՝ cookie session-ներ նույն top-level domain-ի վրա գտնվող քո SPA-ի համար, գումարած պարզ personal access token-ներ մոբայլ հավելվածների համար։ Passport-ը լիարժեք OAuth2 սերվեր է․ ընտրիր այն, երբ երրորդ կողմերին OAuth flow-եր են պետք, կամ արդեն կախված ես նրա token մոդելից։ Socialite-ը դրանցից ցանկացածի վրա ավելացնում է Google, Apple և այլ սոցիալական մուտքեր։
// routes/console.php
use Illuminate\Support\Facades\Schedule;
Schedule::command('subscriptions:expire')
->dailyAt('02:00')
->withoutOverlapping()
->onOneServer();
Schedule::job(new SyncPricesFromErp)->hourly();
// crontab: * * * * * cd /var/www/app && php artisan schedule:run >> /dev/null 2>&1
06Production խորհուրդներ Laravel 13-ի դարաշրջանի համար
Աշխատիր PHP 8.3-ի կամ ավելի նոր տարբերակի վրա և ամենուր օգտագործիր enum-ներ, readonly property-ներ և տիպավորված signature-ներ։ Deploy-ի ժամանակ գործարկիր php artisan optimize՝ config-ը, route-ները, event-ներն ու view-երը cache անելու համար, և երբեք մի կանչիր env() config ֆայլերից դուրս, որովհետև cache արված config-ը այն անտեսում է։ Migration-ները գործարկիր որպես deploy-ի առանձին քայլ։ Queue worker-ները պահիր process manager-ի տակ և ամեն deploy-ի ժամանակ վերագործարկիր php artisan queue:restart-ով, այլապես նրանք կշարունակեն աշխատեցնել հին կոդը։ Լոկալ debug-ի համար օգտագործիր Telescope կամ Pail, իսկ production-ում՝ structured log-եր և error tracking։ Laravel 13-ը բերում է նաև first-party AI SDK և JSON:API resource-ներ․ ընդունիր դրանք, երբ իրական խնդիր են լուծում, ոչ թե պարզապես նոր լինելու համար։
- Deploy՝ composer install --no-dev --optimize-autoloader, migrate --force, optimize, queue:restart։
- Production-ում դրիր APP_DEBUG=false․ debug էջերը գաղտնիքներ են արտահոսեցնում։
- Բազմաքայլ գրառումները փաթաթիր database transaction-ներով, իսկ job-երը dispatch արա afterCommit-ով։
Օգտագործեք, երբ
- Բիզնես հավելվածներ, որոնց կենտրոնում տվյալների բազան է՝ e-commerce, CRM-ներ, ամրագրումներ, back office-ներ։
- Մեկ API, որը պետք է սպասարկի կայքը, մոբայլ հավելվածը և ադմին պանելը։
- Թիմեր, որոնք ուզում են ամուր conventions, արագ onboarding և «ամեն ինչ ներառված»։
- Պրոդուկտներ, որոնք շատ ֆոնային աշխատանք ունեն՝ email-ներ, import-ներ, export-ներ, ժամանակացույցով sync-եր։
Մի՛ օգտագործեք, երբ
- Հազարավոր երկարատև կապեր մեկ պրոցեսում կամ ծանր streaming, որտեղ Node-ը կամ Go-ն ավելի բնական են (կամ Laravel-ի կողքին օգտագործիր Reverb)։
- Փոքրիկ, մեկ նպատակի ֆունկցիաներ, որտեղ serverless handler-ն ավելի պարզ է, քան ամբողջական framework-ը։
- Թիմ, որը PHP փորձ չունի, բայց ունի ուժեղ, արդեն գոյություն ունեցող TypeScript backend։
Այլընտրանքներ
- NestJS
TypeScript, մոդուլներ և DI Node-ի վրա․ հիանալի է, երբ ամբողջ stack-ը TypeScript է։
- Symfony
Ավելի բացահայտ և կարգավորելի PHP framework․ Laravel-ի շատ կոմպոնենտներ կառուցված են դրա վրա։
- Django
Python-ի համարժեքը՝ ներկառուցված ադմինով․ լավ է, երբ թիմն ապրում է Python-ում։
- Ruby on Rails
Framework, որը ոգեշնչել է Laravel-ի մեծ մասը․ նման արտադրողականություն, Ruby էկոհամակարգ։
Լավագույն պրակտիկաներ
- Controller-ները պահիր բարակ․ բիզնես տրամաբանությունը դիր service-ների կամ մեկ նպատակի action class-ների մեջ։
- Յուրաքանչյուր input վալիդացրու FormRequest-ներով և տվյալները վերադարձրու API Resource-ներով։
- Relation-ները eager load արա և production-ից դուրս միացրու preventLazyLoading-ը։
- Ստատուսների և տիպերի համար օգտագործիր enum-ներ՝ «կախարդական» string-երի փոխարեն։
- Բազմաքայլ գրառումները փաթաթիր DB::transaction-ով, իսկ job-երը dispatch արա commit-ից հետո։
- Deploy-ի ժամանակ cache արա config-ն ու route-ները և ամեն release-ի ժամանակ վերագործարկիր queue worker-ները։
- Feature թեստեր գրիր յուրաքանչյուր endpoint-ի համար, որը փող է շարժում կամ վիճակ է փոխում։
Տարածված սխալներ
- «Գեր» controller-ներ՝ բիզնես տրամաբանության հարյուրավոր տողերով, որոնք հնարավոր չէ վերօգտագործել կամ թեստավորել։
- N+1 query-ներ, որոնք թաքնված են Blade view-երում կամ Resource-ներում։
- $request->all()-ի օգտագործումը create()-ի հետ, ինչը դուռ է բացում mass assignment-ի առաջ։
- env()-ի կանչը հավելվածի կոդում, որը config-ը cache անելուց հետո null է վերադարձնում։
- Deploy-ից հետո worker-ները վերագործարկել մոռանալը, ու job-երը հին կոդն են աշխատեցնում նոր սխեմայի դեմ։
Խորը վերլուծություն
07Service container, scope-եր և երկար աշխատող worker-ներ
Դասական PHP-FPM-ը Laravel-ը բեռնում է յուրաքանչյուր request-ի համար և հետո ամեն ինչ դեն նետում, ուստի անփույթ state-ը հազվադեպ է վնասում։ Queue worker-ները և Octane-ը (FrankenPHP, Swoole կամ RoadRunner) application-ը բեռնում են մեկ անգամ, իսկ հետո նույն հիշողությունից սպասարկում են հազարավոր request-ներ կամ job-եր։ Դա փոխում է կանոնները։ bind()-ով գրանցված binding-ը ամեն resolve-ի ժամանակ տալիս է նոր օբյեկտ։ singleton()-ը տալիս է մեկ օբյեկտ ամբողջ process-ի կյանքի ընթացքում, ինչը ճիշտ է stateless client-ների համար, օրինակ՝ վճարային gateway-ի HTTP client-ի։ scoped()-ը տալիս է մեկ օբյեկտ յուրաքանչյուր request-ի կամ job-ի համար և դրանց միջև reset է արվում․ սա հենց այն է, ինչ պետք է ընթացիկ user-ը, tenant-ը կամ locale-ը պահող ամեն ինչի համար։ Request-ի տվյալները singleton-ի կամ static property-ի մեջ դնելը Octane-ի դասական bug-ն է․ մի հաճախորդ կարճ ժամանակով տեսնում է մեկ ուրիշի context-ը։
// AppServiceProvider::register()
// Stateless client: safe to share for the whole process
$this->app->singleton(PaymentGateway::class, fn () => new GatewayClient(
config('services.gateway.key'),
));
// Holds per-request data: reset for every request and every queued job
$this->app->scoped(CurrentTenant::class, fn () => new CurrentTenant());
// Cheap builder: a new instance on every resolve
$this->app->bind(ReportBuilder::class);
08Eloquent-ը մեծ աղյուսակների վրա․ chunking, lazy collection-ներ և strict mode
Order::all()-ը երկու միլիոն տող ունեցող աղյուսակի վրա հիշողություն է բեռնում երկու միլիոն model և սպանում process-ը։ Batch աշխատանքի համար օգտագործեք chunkById(), որը էջավորում է ըստ primary key-ի և ճիշտ է մնում նույնիսկ այն դեպքում, երբ թարմացնում եք հենց այն տողերը, որոնց վրայով անցնում եք (offset-ով սովորական chunk()-ը այդ դեպքում տողեր է բաց թողնում)։ Export-ների համար lazyById()-ը վերադարձնում է LazyCollection, որը stream է անում տողերը գրեթե հաստատուն հիշողությամբ։ Զգույշ եղեք whereHas()-ի հետ մեծ աղյուսակների վրա․ այն compile է լինում correlated EXISTS subquery-ի, որը կարող է դանդաղ լինել առանց ճիշտ composite index-ի, և երբեմն join-ը կամ subselect-ով whereIn-ը շատ ավելի արագ են։ Model::shouldBeStrict()-ը միանգամից միացնում է երեք պաշտպանություն՝ արգելում է lazy loading-ը, exception է նետում, երբ սահմանում եք ոչ fillable attribute՝ այն լուռ դեն նետելու փոխարեն, և exception է նետում, երբ կարդում եք attribute, որը select չի արվել։
// Backfill without loading everything
Order::query()
->where('status', OrderStatus::Paid)
->whereNull('invoice_number')
->chunkById(500, function ($orders) {
foreach ($orders as $order) {
GenerateInvoice::dispatch($order->id);
}
});
// Stream an export with near-constant memory
foreach (Order::query()->select(['id', 'total', 'created_at'])->lazyById(1000) as $order) {
$csv->insertOne([$order->id, $order->total, $order->created_at]);
}
09Race condition-ներ․ atomic update-ներ, row lock-եր և distributed lock-եր
Երկու հաճախորդ միաժամանակ գնում են վերջին ապրանքը։ Երկու request-ն էլ կարդում են stock = 1, երկուսն էլ անցնում են ստուգումը, երկուսն էլ նվազեցնում են, և stock-ը դառնում է -1։ Կարդալը, PHP-ում ստուգելը և հետո գրելը concurrency-ի պայմաններում երբեք անվտանգ չէ։ Ամենաէժան լուծումը atomic պայմանական update-ն է․ թողեք, որ database-ը ստուգի և փոխի մեկ statement-ով, և նայեք ազդված տողերի քանակին։ Երբ պետք է, որ մի քանի կարդալ և գրել համաձայնեցված մնան, օգտագործեք transaction lockForUpdate()-ով, որը row lock-ը պահում է մինչև commit․ այդպիսի transaction-ները կարճ պահեք և տողերը միշտ նույն հերթականությամբ lock արեք՝ deadlock-ներից խուսափելու համար։ DB::transaction()-ը ընդունում է փորձերի քանակ և deadlock-ի դեպքում կրկնում է։ Process-ների միջև տարածվող աշխատանքի համար, օրինակ՝ սերվերների միջև միաժամանակ միայն մեկ ERP import, օգտագործեք Cache::lock() Redis կամ database store-ով։ Unique index-ը մնում է պաշտպանության վերջին գիծը։
DB::transaction(function () use ($productId, $qty) {
$affected = Product::whereKey($productId)
->where('stock', '>=', $qty)
->decrement('stock', $qty);
if ($affected === 0) {
throw new OutOfStockException($productId);
}
}, attempts: 3);
// One import at a time across all servers (needs a shared cache store)
Cache::lock('erp-import', 600)->block(5, function () {
app(ErpImporter::class)->run();
});
10Laravel API-ի ամրացում
Laravel-ի անվտանգության խնդիրների մեծ մասը կոնֆիգուրացիա և սովորություններ են, ոչ թե framework-ի bug-եր։ Սահմանեք անվանված rate limiter-ներ ըստ լսարանի՝ առատաձեռն մուտք գործած user-ների համար, խիստ անանուն traffic-ի համար, շատ խիստ OTP-ի և password reset-ի համար, key-ով, որը միավորում է user-ը, հեռախոսը և IP-ն։ Nested route binding-ները scope արեք scopeBindings()-ով, որպեսզի /shops/1/orders/99-ը ձախողվի, երբ 99 պատվերը պատկանում է այլ խանութի։ Զգայուն սյուների համար, ինչպիսիք են tax id-ները կամ token-ները, օգտագործեք encrypted cast-եր և հիշեք, որ APP_KEY-ի rotation-ը աջակցվում է APP_PREVIOUS_KEYS-ի միջոցով, ուստի հին արժեքները դեռ decrypt են լինում, իսկ նորերն օգտագործում են նոր key-ը։ Email-ների հղումների համար (unsubscribe, download) օգտագործեք signed URL-ներ՝ սեփական token-ներ հորինելու փոխարեն։ Gate::before-ի super-admin shortcut-ները նեղ պահեք, քանի որ դրանք շրջանցում են բոլոր policy-ները։ Եվ վերջապես CI-ում գործարկեք composer audit՝ հայտնի խոցելիություններով package-ները բռնելու համար։
// AppServiceProvider::boot()
RateLimiter::for('api', function (Request $request) {
return $request->user()
? Limit::perMinute(120)->by('user:'.$request->user()->id)
: Limit::perMinute(30)->by('ip:'.$request->ip());
});
RateLimiter::for('otp', fn (Request $request) => [
Limit::perMinute(1)->by('otp-phone:'.$request->input('phone')),
Limit::perDay(20)->by('otp-ip:'.$request->ip()),
]);
// routes: ->middleware('throttle:otp')
Իրական իրավիճակներ
Պատվերների ցուցակը դանդաղեց մեկ տարվա աճից հետո
Խնդիր
Admin-ի պատվերների էջը գործարկման ժամանակ բացվում էր 200 ms-ում, իսկ հիմա՝ 6 վայրկյանում։ Աղյուսակը հասել է մի քանի միլիոն տողի, և հաճախորդները բողոքում են նաև API-ից։
Ինչպես լուծել
Չափեք՝ նախքան որևէ բան փոխելը։ Լոկալ բացեք Telescope կամ Debugbar և հաշվեք query-ները, production-ում միացրեք database-ի slow query log-ը։ Սովորաբար գտնում եք երկու բան՝ N+1 Resource-ի ներսում և filter առանց index-ի։ Ավելացրեք eager loading և preventLazyLoading, ապա ավելացրեք composite index, որը համապատասխանում է query-ին, օրինակ՝ (status, created_at), երբ ֆիլտրում եք ըստ status-ի և դասավորում ըստ ամսաթվի։ Խորը էջերում offset pagination-ը փոխարինեք cursorPaginate()-ով, քանի որ OFFSET 200000-ը միևնույն է scan է անում բաց թողնված տողերը։ Select արեք միայն այն սյուները, որոնք ցուցակը ցույց է տալիս։ Կրկին չափեք, ապա ավելացրեք feature test, որը ստուգում է query-ների քանակը, որպեսզի խնդիրը չվերադառնա։
Scheduled job-ը գործարկվում է երկու անգամ երկրորդ սերվերն ավելացնելուց հետո
Խնդիր
Թիմը load balancer-ի հետևում ավելացրեց երկրորդ app սերվեր։ Այդ պահից հաճախորդները ամենօրյա հիշեցման SMS-ը ստանում են երկու անգամ, իսկ գիշերային հաշվետվությունը գեներացվում է երկու անգամ։
Ինչպես լուծել
Երկու սերվերն էլ գործարկում են նույն cron գրառումը, ուստի երկուսն էլ ամեն րոպե գործարկում են schedule:run։ Ավելացրեք onOneServer() յուրաքանչյուր task-ի, որը պետք է գործարկվի մեկ անգամ, և համոզվեք, որ default cache store-ը ընդհանուր է, օրինակ՝ Redis, database կամ Memcached, քանի որ lock-ը հենց այնտեղ է ապրում․ file կամ array cache-ը յուրաքանչյուր սերվերին տալիս է իր սեփական lock-ը և ոչինչ չի պաշտպանում։ Երկար task-երի համար ավելացրեք withoutOverlapping()։ Որպես այլընտրանք՝ scheduler-ը գործարկեք միայն մեկ առանձին worker host-ի վրա։ Ապա job-երն իրենք դարձրեք idempotent, օրինակ՝ գրանցելով, որ տվյալ բաժանորդագրության և ամսաթվի հիշեցումն արդեն ուղարկվել է, որպեսզի ապագա սխալ կոնֆիգուրացիան չկարողանա կրկնակի ուղարկել։
Կենդանի պրոդուկտի թարմացում Laravel 12-ից 13
Խնդիր
Ծանրաբեռնված e-commerce API-ն աշխատում է Laravel 12-ով PHP 8.2-ի վրա։ Բիզնեսն ուզում է, որ թարմացումն արվի առանց feature-ների վրա աշխատանքը սառեցնելու և առանց downtime-ի։
Ինչպես լուծել
Սկսեք PHP-ից, քանի որ Laravel 13-ին պետք է 8.3 կամ ավելի նոր․ թարմացրեք PHP-ն staging-ում, գործարկեք ամբողջ test suite-ը և PHPStan-ը, ապա նոր PHP տարբերակը roll out արեք production՝ դեռ մնալով Laravel 12-ի վրա։ Հետո առանձին branch-ում composer.json-ում բարձրացրեք laravel/framework-ը և first-party package-ները, կետ առ կետ կարդացեք պաշտոնական upgrade guide-ը և ուղղեք deprecation-ները։ Գործարկեք test-երը, deploy արեք staging, այնտեղ մեկ օր աշխատեցրեք queue worker-ները և scheduler-ը և ստուգեք log-երը։ Թողարկեք սովորական zero-downtime deploy-ով և restart արեք worker-ները։ Branch-ը կարճ կյանքով պահեք և ամեն օր rebase արեք, որպեսզի feature-ների վրա աշխատանքը երբեք չկանգնի։
Արագ ուղեցույց
- Ցույց տալ տարբերակները, driver-ները և cache-ի վիճակը
php artisan about- Route-ների ցուցակը իրենց middleware-ներով
php artisan route:list -v --except-vendor- Մաքրել բոլոր cache արված ֆայլերը վատ deploy-ից հետո
php artisan optimize:clear- Model migration-ով, factory-ով և policy-ով
php artisan make:model Order -mfp- Դիտել աղյուսակի սյուներն ու index-ները
php artisan db:table orders- Application-ի log-երը հետևել իրական ժամանակում
php artisan pail --filter="OrderController"- Տեսնել scheduled task-երը և հաջորդ գործարկման ժամերը
php artisan schedule:list- Միացնել Eloquent-ի բոլոր պաշտպանությունները
Model::shouldBeStrict(! app()->isProduction());- Արագ pagination խորը կամ անվերջ ցուցակների համար
Order::orderBy('id')->cursorPaginate(50);- Ստուգել dependency-ները հայտնի խոցելիությունների համար
composer audit
Իմ իրական նախագծերում
- Ռեստորանային ցանցի պատվերների պլատֆորմ եմ կառուցել որպես մեկ Laravel API (մոտ 330 route տող), controller-ները բաժանված Shared, Ui, Mobile և Admin խմբերի, որպեսզի վեբն ու հավելվածը վերօգտագործեն նույն հիմնական տրամաբանությունը։
- Կենդանիների ապրանքների պլատֆորմում (Laravel 12) backend-ն ունի մոտ 110 service class domain model-ների վրա և 95 backend թեստային ֆայլ․ ERP sync-ն աշխատում է որպես ժամանակացույցով, feature-flag-ով կառավարվող job-եր՝ health alert-ներով։
- Գրեթե միայնակ կառուցել եմ մարզասրահների ցանցի CRM Laravel 13-ով և PHP 8.3-ով՝ հերթականությամբ, չհամընկնող ժամանակացույցով job-երով (unfreeze, հետո expire, հետո activate) և հավելվածի auth-ի համար Passport-ով։
- Production սերվերներում Laravel queue worker-ներն աշխատեցնում եմ supervisor-ի տակ, իսկ scheduler-ը՝ cron-ով։
Հարցեր և պատասխաններ
Q1Բացատրիր Laravel-ի request-ի կյանքի ցիկլը։
Request-ը հասնում է public/index.php, որը բեռնում է autoloader-ը և ստեղծում հավելվածը։ Service provider-ները գրանցվում և boot են լինում, որպեսզի container-ը իմանա, թե ինչպես կառուցել ամեն ինչ։ Հետո request-ն անցնում է գլոբալ middleware-ներով, router-ը գտնում է համապատասխան route-ը, և աշխատում են route middleware-ները (auth, throttle, bindings)։ Controller-ի մեթոդը resolve է արվում container-ի միջոցով, այնպես որ նրա կախվածությունները inject են լինում։ Ինչ էլ որ այն վերադարձնի, վերածվում է response-ի, որը հետ է անցնում middleware-ներով, և վերջում՝ response-ն ուղարկելուց հետո, աշխատում է terminable middleware-ը։ Laravel 11-ում և հետագա տարբերակներում route-ները, middleware-ներն ու exception handling-ը կարգավորվում են bootstrap/app.php-ում։
Q2Ի՞նչ է N+1 խնդիրը և ինչպե՞ս ես խուսափում դրանից Eloquent-ում։
N+1-ը տեղի է ունենում, երբ ցուցակի համար կատարում ես մեկ query, հետո յուրաքանչյուր տողի համար ևս մեկ query՝ relation-ը բերելու համար, օրինակ՝ բեռնում ես 50 պատվեր, հետո lazy կերպով յուրաքանչյուր պատվերի customer-ը։ Դա երկուսի փոխարեն 51 query է։ Eloquent-ում դա ուղղում ես eager loading-ով՝ Order::with('customer')->get(), կամ արդեն գոյություն ունեցող collection-ի վրա load(), իսկ քանակների համար՝ withCount()։ Վաղ բռնելու համար production-ից դուրս միացնում եմ Model::preventLazyLoading(), որպեսզի ցանկացած lazy load exception նետի։ Code review-ի ժամանակ նայում եմ նաև Resource-ներին և view-երին, որովհետև հենց այնտեղ է սովորաբար թաքնված լինում relation-ների հասանելիությունը։
Q3Sanctum, թե՞ Passport․ որն ես ընտրում։
Պրոդուկտների մեծ մասի համար՝ Sanctum։ Այն սպասարկում է cookie-ի վրա հիմնված session auth նույն top-level domain-ի վրա գտնվող first-party SPA-ի համար և պարզ personal access token-ներ մոբայլ հավելվածների համար, իսկ ability-ները ծառայում են որպես թեթև scope-ներ։ Passport-ը league/oauth2-server-ի վրա կառուցված լիարժեք OAuth2 սերվեր է․ այն ճիշտ ընտրություն է, երբ արտաքին ծրագրավորողներին կամ գործընկեր համակարգերին ստանդարտ OAuth flow-եր են պետք, օրինակ՝ authorization code PKCE-ով կամ client credentials, կամ երբ գոյություն ունեցող համակարգն արդեն կախված է դրանից։ Passport-ը բերում է ավելի շատ շարժվող մասեր՝ key-եր, client-ներ, token աղյուսակներ և refresh-ի մշակում։ Եթե ընկերությունից դուրս ոչ ոքի OAuth պետք չէ, Sanctum-ն ավելի պարզ է և ավելի հեշտ է ապահովել։
Q4Laravel հավելվածում որտե՞ղ ես դնում բիզնես տրամաբանությունը։
Ոչ controller-ներում և ոչ էլ model-ներով ցրված։ Controller-ները կարդում են վալիդացված input-ը և վերադարձնում Resource։ Տրամաբանությունն ապրում է service class-ներում կամ մեկ նպատակի action-ներում, ինչպիսիք են PlaceOrder-ը կամ CalculateDeliveryFee-ն, որոնք ընդունում են պարզ տվյալներ և model-ներ ու կարող են կանչվել controller-ից, job-ից կամ command-ից։ Model-ները պահում են relationship-ները, cast-երը, scope-ները և փոքր domain helper-ները։ Բարդ կանոնների համար օգտագործում եմ value object-ներ, օրինակ՝ Money կամ Quantity տիպ, որպեսզի կանոնները թեստավորվեն մեկուսացված։ Սա controller-ները կարճ է պահում, տրամաբանությունը վերօգտագործելի դարձնում վեբի, մոբայլի և ադմինի միջև, և unit testing-ը պարզեցնում։
Q5Ինչպե՞ս ես production-ում աշխատեցնում queue-ներն ու scheduler-ը։
Scheduler-ին պետք է մեկ cron գրառում, որն ամեն րոպե գործարկում է php artisan schedule:run․ ժամանակացույցն ինքնին ապրում է կոդում՝ withoutOverlapping-ով և onOneServer-ով այն job-երի համար, որոնք չպետք է երկու անգամ աշխատեն։ Queue worker-ները գործարկում են php artisan queue:work-ը process manager-ի տակ, ինչպիսին է supervisor-ը կամ systemd-ն, որը վերագործարկում է դրանք, եթե crash լինեն։ Յուրաքանչյուր job-ի համար սահմանում եմ tries, backoff և timeout-ներ, job-երը դարձնում եմ idempotent և հետևում failed_jobs աղյուսակին։ Ամեն deploy-ի ժամանակ գործարկում եմ queue:restart, որպեսզի worker-ները նոր կոդը վերցնեն։ Երբ driver-ը Redis-ն է, Horizon-ը տալիս է dashboard և queue-ների միջև բալանսավորում։
Q6Ի՞նչ է փոխվել Laravel 13-ում, և արժե՞ թարմացնել։
Laravel 13-ը թողարկվեց 2026-ի մարտին։ Հիմնական գործնական փոփոխությունը PHP-ի նվազագույն տարբերակն է․ այն պահանջում է PHP 8.3 կամ ավելի նոր։ Այն ավելացնում է PHP attribute-ների ավելի լայն աջակցություն model-ների և այլ class-ների կարգավորման համար, first-party AI SDK տեքստի գեներացիայի, embedding-ների և agent-ների համար, ինչպես նաև first-party JSON:API resource-ներ։ Պաշտոնական upgrade guide-ը այն նկարագրում է որպես 12-ից համեմատաբար փոքր թարմացում։ Ակտիվ նախագծի համար թարմացնում եմ մի քանի ամսվա ընթացքում․ նախ բարձրացնում եմ PHP-ն, թարմացնում first-party փաթեթները, գործարկում թեստերը և ուղղում deprecation-ները։ Աջակցվող տարբերակների վրա մնալը կարևոր է հիմնականում անվտանգության ուղղումների համար։
Սենիոր մակարդակի հարցեր
S1Ինչպե՞ս կմասշտաբավորեիք Laravel application-ը traffic-ի մեծ աճի համար։
Նախ app սերվերները դարձնում եմ stateless՝ session-ները, cache-ը և lock-երը Redis-ում, ֆայլերը object storage-ում, որպեսզի կարողանամ սերվերներ ավելացնել load balancer-ի հետևում։ Cache եմ անում config-ը և route-ները, օգտագործում եմ OPcache և դիտարկում եմ Octane-ը, երբ կոդն անվտանգ է երկար ապրող process-ների համար․ այն վերացնում է boot-ի արժեքը, բայց պատժում է արտահոսող state-ը։ Դանդաղ աշխատանքը տեղափոխում եմ queue-ներ և worker-ները մասշտաբավորում եմ web node-երից առանձին։ Database-ի կողմից նախ ավելացնում եմ ճիշտ index-ները, հետո read replica-ներ՝ Laravel-ի read և write connection config-ով և sticky տարբերակով, որպեսզի user-ը կարդա իր իսկ գրածը։ Թանկ կարդալները cache եմ անում բացահայտ invalidation-ով։ Static asset-ները դնում եմ CDN-ի հետևում։ Trade-off-ը օպերացիոն բարդությունն է․ յուրաքանչյուր քայլ ավելացնում է շարժվող մասեր, ուստի հաջորդն ավելացնելուց առաջ չափում եմ, թե որտեղ է գնում ժամանակը։
S2Ի՞նչ է փոխվում, երբ Laravel app-ը տեղափոխում եք Octane։
App-ը boot է լինում մեկ անգամ յուրաքանչյուր worker-ի համար և շատ request-ներ սպասարկում նույն հիշողությունից, ուստի throughput-ը բարձրանում է, իսկ latency-ն նվազում, քանի որ չկա յուրաքանչյուր request-ի bootstrap։ Գինն այն է, որ state-ն այժմ արտահոսում է։ Singleton-ները, որոնք պահում են request-ը, config-ը կամ authenticated user-ին, պահպանում են առաջին request-ի արժեքները, static property-ներն ու array-ները անվերջ աճում են, իսկ package-ները, որոնք ենթադրում են թարմ process, կարող են կոտրվել։ Ես audit եմ անում service provider-ները և request-ից կախված singleton-ները փոխարինում եմ scoped binding-ներով կամ resolve եմ անում ծույլ, խուսափում եմ static cache-երից և գործարկում եմ load test-եր՝ հետևելով յուրաքանչյուր worker-ի հիշողությանը։ Octane-ը կարող է restart անել worker-ները որոշակի քանակի request-ներից հետո՝ որպես ապահովության ցանց, բայց դա թաքցնում է արտահոսքերը, ոչ թե ուղղում։ Դանդաղ database ունեցող CRUD app-երի համար Octane-ը քիչ բան է տալիս, իսկ CPU-ի համար թեթև, բարձր ծավալով API-ների համար կարող է էական լինել։
S3Ինչպե՞ս եք կանխում overselling-ը կամ double-spending-ը միաժամանակյա request-ների դեպքում։
Երբեք մի կարդացեք արժեքը, ստուգեք PHP-ում և հետ գրեք։ Մեկ counter-ի համար, ինչպիսիք են stock-ը կամ balance-ը, օգտագործում եմ atomic պայմանական update՝ նվազեցնել այնտեղ, որտեղ stock-ը առնվազն քանակին հավասար է, և զրո ազդված տողը համարում եմ ձախողում․ այն երկար lock-եր չի պահում և լավ է մասշտաբավորվում։ Երբ բիզնես գործողությունը կարդում և գրում է մի քանի տող, օգտագործում եմ կարճ transaction lockForUpdate-ով ներգրավված տողերի վրա, lock անելով հետևողական հերթականությամբ, և թողնում եմ, որ DB::transaction-ը կրկնի deadlock-ի դեպքում։ Pessimistic lock-երը պարզ են, բայց նվազեցնում են throughput-ը տաք տողերի վրա, իսկ version սյունով optimistic locking-ը հարմար է քիչ կոնֆլիկտ ունեցող խմբագրումների համար, ինչպես admin-ի ձևերը։ Արտաքին side effect-ների համար, ինչպես վճարումները, idempotency key-ը unique index-ի հետ երաշխավորում է մեկ գանձում, նույնիսկ եթե client-ը կրկնի։
S4Ինչպե՞ս եք մեծ Laravel codebase-ը մոդուլային պահում։
Laravel-ը մոդուլներ չի պարտադրում, ուստի դուք եք ընտրում մակարդակը։ Ամենաթեթևը domain ենթաթղթապանակներն են app-ի ներսում, օրինակ՝ Orders, Catalog և Loyalty, յուրաքանչյուրն իր action-ներով, model-ներով և event-ներով, գումարած կանոն, որ մոդուլները շփվում են action-ների կամ event-ների միջոցով, ոչ թե միմյանց model-ներով։ Հաջորդ մակարդակը լոկալ Composer package-ներն են իրենց service provider-ներով, որոնք սահմանները դարձնում են բացահայտ և վերօգտագործելի նախագծերի միջև՝ ավելի շատ կապակցման գնով։ Ինչ էլ ընտրեք, ամրապնդեք այն գործիքներով՝ Deptrac կամ Pest-ի architecture test-եր, որոնք ձախողում են CI-ը, երբ մոդուլը import է անում մեկ այլ մոդուլի ներքին մասերը։ Ես խուսափում եմ service-ների բաժանելուց, մինչև domain-ը մասշտաբավորման կամ release-ի այլ կարիք չունենա, քանի որ լավ սահմանազատված monolith-ը շատ ավելի էժան է շահագործել և refactor անել։
Բառարան
- Service container
- Laravel-ի dependency injection համակարգը։ Կառուցում է class-ները, resolve է անում constructor-ի dependency-ները և որոշում՝ instance-ները վերօգտագործե՞լ, թե ոչ։
- Service provider
- Class, որը boot-ի ժամանակ application-ում գրանցում է binding-ներ, route-ներ, event-ներ կամ config։
- Facade
- Static տեսք ունեցող shortcut, օրինակ՝ Cache::get(), որը կանչերը փոխանցում է container-ից resolve արված իրական օբյեկտին։
- Scoped binding
- Container binding, որն ապրում է մեկ request-ի կամ մեկ queue job-ի ընթացքում և հետո reset է լինում։
- Octane
- Laravel package, որը FrankenPHP-ի, Swoole-ի կամ RoadRunner-ի միջոցով application-ը request-ների միջև բեռնված պահում է հիշողության մեջ։
- Route model binding
- Laravel-ը URL-ի id-ն ավտոմատ վերածում է բեռնված model-ի և վերադարձնում 404, եթե այն գոյություն չունի։
- Cast
- Model-ի կանոն, որը կարդալիս և գրելիս փոխակերպում է սյունը, օրինակ՝ enum-ի, ամսաթվի, JSON-ի կամ encrypted արժեքի։
- Policy
- Class, որը խմբավորում է մեկ model-ի authorization կանոնները, օրինակ՝ կարո՞ղ է user-ը թարմացնել կամ ջնջել պատվերը։
- Cursor pagination
- Էջավորում վերջին տեսած key-ով՝ offset-ի փոխարեն․ արագ է հսկա աղյուսակների վրա, բայց 500-րդ էջ ցատկել հնարավոր չէ։
Շարունակեք սովորել
Պրոդուկտի գաղափար ունե՞ք։Եկեք ստեղծենք այն։
Պատմեք ձեր գաղափարի, պրոդուկտի կամ թիմի մասին։ Սովորաբար պատասխանում եմ մեկ աշխատանքային օրվա ընթացքում։