تحسين الأداء في Laravel: دليل شامل للـ Performance Optimization
دليل تخصصي من علاء عامر – مطور ومصمم مواقع وتطبيقات محترف
تحسين أداء Laravel أساسي لبناء تطبيقات سريعة وقابلة للتطوير. تعلم أحدث استراتيجيات الأداء والتحسين.
1️⃣ Database Optimization استراتيجيات متقدمة
| التحسين | التأثير | صعوبة التطبيق | أولوية التنفيذ |
|---|---|---|---|
| Database Indexing | عالي جداً | متوسط | 🔥 عاجل |
| Query Optimization | عالي | متوسط | 🔥 عاجل |
| Eager Loading | عالي | سهل | 🔥 عاجل |
| Database Connection Pool | متوسط | صعب | ⭐ مهم |
Database Indexes المحسنة:
<?php
// database/migrations/add_performance_indexes.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddPerformanceIndexes extends Migration
{
public function up()
{
Schema::table('posts', function (Blueprint $table) {
// فهرسة مفردة للأعمدة المستخدمة بكثرة
$table->index('status');
$table->index('published_at');
$table->index('created_at');
$table->index('user_id');
$table->index('category_id');
// فهرسة مركبة للاستعلامات المعقدة
$table->index(['status', 'published_at'], 'posts_status_published_idx');
$table->index(['user_id', 'status'], 'posts_user_status_idx');
$table->index(['category_id', 'status', 'published_at'], 'posts_category_status_published_idx');
// فهرسة نصية للبحث
$table->fulltext(['title', 'content'], 'posts_search_idx');
});
Schema::table('users', function (Blueprint $table) {
$table->index('email'); // للتسجيل السريع
$table->index('status');
$table->index('created_at');
$table->index(['status', 'email_verified_at'], 'users_active_verified_idx');
});
Schema::table('comments', function (Blueprint $table) {
$table->index('post_id');
$table->index('user_id');
$table->index('parent_id');
$table->index(['post_id', 'status'], 'comments_post_status_idx');
});
// جداول المحورة
Schema::table('post_tag', function (Blueprint $table) {
$table->index('post_id');
$table->index('tag_id');
});
}
public function down()
{
Schema::table('posts', function (Blueprint $table) {
$table->dropIndex('posts_status_published_idx');
$table->dropIndex('posts_user_status_idx');
$table->dropIndex('posts_category_status_published_idx');
$table->dropIndex('posts_search_idx');
$table->dropIndex(['status']);
$table->dropIndex(['published_at']);
$table->dropIndex(['created_at']);
$table->dropIndex(['user_id']);
$table->dropIndex(['category_id']);
});
// باقي dropIndex commands...
}
}
Query Optimization المتقدم:
<?php
// app/Repositories/PostRepository.php
namespace App\Repositories;
use App\Models\Post;
use Illuminate\Database\Query\Builder;
use Illuminate\Pagination\LengthAwarePaginator;
class PostRepository
{
/**
* استعلام محسن للمقالات المنشورة
*/
public function getPublishedPosts(array $filters = [], int $perPage = 15): LengthAwarePaginator
{
$query = Post::query()
->select([
'posts.id',
'posts.title',
'posts.slug',
'posts.excerpt',
'posts.featured_image',
'posts.published_at',
'posts.views_count',
'posts.user_id',
'posts.category_id'
])
->with([
'author:id,name,avatar',
'category:id,name,slug'
])
->withCount(['comments', 'likes'])
->where('posts.status', 'published')
->where('posts.published_at', '<=', now());
// تطبيق الفلاتر
$this->applyFilters($query, $filters);
return $query->orderByDesc('posts.published_at')
->paginate($perPage);
}
/**
* استعلام محسن لمقال واحد مع العلاقات
*/
public function findWithRelations(string $slug): ?Post
{
return Post::query()
->select([
'posts.*',
// حساب إحصائيات مباشرة في الاستعلام
\DB::raw('(SELECT COUNT(*) FROM comments WHERE post_id = posts.id) as comments_count'),
\DB::raw('(SELECT COUNT(*) FROM post_likes WHERE post_id = posts.id) as likes_count')
])
->with([
'author' => function ($query) {
$query->select('id', 'name', 'avatar', 'bio', 'created_at');
},
'category:id,name,slug,description',
'tags:id,name,slug',
'comments' => function ($query) {
$query->select('id', 'content', 'user_id', 'post_id', 'created_at', 'parent_id')
->with('user:id,name,avatar')
->whereNull('parent_id')
->latest()
->limit(10);
}
])
->where('slug', $slug)
->where('status', 'published')
->first();
}
/**
* المقالات ذات الصلة (محسن)
*/
public function getRelatedPosts(Post $post, int $limit = 5): \Illuminate\Database\Eloquent\Collection
{
return Post::query()
->select('id', 'title', 'slug', 'excerpt', 'featured_image', 'published_at')
->where('category_id', $post->category_id)
->where('id', '!=', $post->id)
->where('status', 'published')
->where('published_at', '<=', now())
->inRandomOrder()
->limit($limit)
->get();
}
/**
* البحث المحسن باستخدام Full-Text Search
*/
public function search(string $term, array $filters = [], int $perPage = 15): LengthAwarePaginator
{
$query = Post::query()
->select([
'posts.*',
\DB::raw('MATCH(title, content) AGAINST(? IN BOOLEAN MODE) as relevance_score')
])
->whereRaw('MATCH(title, content) AGAINST(? IN BOOLEAN MODE)', ["+{$term}*"])
->with(['author:id,name', 'category:id,name,slug'])
->where('status', 'published')
->where('published_at', '<=', now());
$this->applyFilters($query, $filters);
return $query->orderByDesc('relevance_score')
->orderByDesc('published_at')
->paginate($perPage);
}
/**
* إحصائيات محسنة
*/
public function getStatsOptimized(): array
{
return \DB::table('posts')
->selectRaw('
COUNT(*) as total_posts,
SUM(CASE WHEN status = "published" THEN 1 ELSE 0 END) as published_posts,
SUM(CASE WHEN status = "draft" THEN 1 ELSE 0 END) as draft_posts,
SUM(views_count) as total_views,
AVG(views_count) as avg_views_per_post
')
->first();
}
/**
* Bulk operations محسنة
*/
public function bulkUpdateViews(array $postIds): void
{
\DB::table('posts')
->whereIn('id', $postIds)
->increment('views_count');
}
/**
* تطبيق الفلاتر
*/
private function applyFilters(Builder $query, array $filters): void
{
if (!empty($filters['category'])) {
$query->whereHas('category', function ($q) use ($filters) {
$q->where('slug', $filters['category']);
});
}
if (!empty($filters['author'])) {
$query->where('user_id', $filters['author']);
}
if (!empty($filters['tag'])) {
$query->whereHas('tags', function ($q) use ($filters) {
$q->where('slug', $filters['tag']);
});
}
if (!empty($filters['date_from'])) {
$query->where('published_at', '>=', $filters['date_from']);
}
if (!empty($filters['date_to'])) {
$query->where('published_at', '<=', $filters['date_to']);
}
}
}
// app/Models/Post.php - إضافة Scopes محسنة
class Post extends Model
{
// استعلام محسن للمقالات الشائعة
public function scopePopular($query, int $days = 7)
{
return $query->select('posts.*')
->selectRaw('(views_count + (SELECT COUNT(*) FROM post_likes WHERE post_id = posts.id) * 5 + (SELECT COUNT(*) FROM comments WHERE post_id = posts.id) * 3) as popularity_score')
->where('published_at', '>=', now()->subDays($days))
->orderByDesc('popularity_score');
}
// استعلام محسن للمقالات الجديدة
public function scopeRecent($query, int $limit = 10)
{
return $query->select('id', 'title', 'slug', 'excerpt', 'featured_image', 'published_at')
->where('status', 'published')
->where('published_at', '<=', now())
->latest('published_at')
->limit($limit);
}
}
2️⃣ Caching Strategy الشامل
Multi-Level Caching:
<?php
// app/Services/CacheService.php
namespace App\Services;
use Illuminate\Support\Facades\{Cache, Redis};
use Illuminate\Database\Eloquent\Model;
class CacheService
{
const CACHE_PREFIXES = [
'posts' => 'posts:',
'users' => 'users:',
'categories' => 'categories:',
'stats' => 'stats:',
'search' => 'search:'
];
const CACHE_DURATIONS = [
'short' => 300, // 5 دقائق
'medium' => 3600, // ساعة
'long' => 86400, // يوم
'permanent' => 604800 // أسبوع
];
/**
* تخزين مؤقت للمقالات مع استراتيجية متقدمة
*/
public function cachePost(int $postId, callable $callback, string $duration = 'medium'): mixed
{
$key = self::CACHE_PREFIXES['posts'] . $postId;
return Cache::tags(['posts', "post.{$postId}"])
->remember($key, self::CACHE_DURATIONS[$duration], $callback);
}
/**
* تخزين مؤقت للاستعلامات المعقدة
*/
public function cacheQuery(string $key, callable $callback, array $tags = [], string $duration = 'medium'): mixed
{
return Cache::tags($tags)
->remember($key, self::CACHE_DURATIONS[$duration], $callback);
}
/**
* تخزين مؤقت للإحصائيات
*/
public function cacheStats(string $type, callable $callback): mixed
{
$key = self::CACHE_PREFIXES['stats'] . $type;
return Cache::tags(['stats'])
->remember($key, self::CACHE_DURATIONS['long'], $callback);
}
/**
* تخزين مؤقت للبحث
*/
public function cacheSearch(string $term, array $filters, callable $callback): mixed
{
$key = self::CACHE_PREFIXES['search'] . md5($term . serialize($filters));
return Cache::tags(['search'])
->remember($key, self::CACHE_DURATIONS['short'], $callback);
}
/**
* محو cache متقدم
*/
public function invalidatePost(int $postId): void
{
// محو cache المقال المحدد
Cache::tags("post.{$postId}")->flush();
// محو cache المقالات المرتبطة
Cache::tags(['posts', 'homepage', 'categories'])->flush();
// محو cache البحث
Cache::tags(['search'])->flush();
}
/**
* محو cache المستخدم
*/
public function invalidateUser(int $userId): void
{
Cache::tags("user.{$userId}")->flush();
Cache::forget(self::CACHE_PREFIXES['users'] . $userId);
}
/**
* Warm up cache للبيانات المهمة
*/
public function warmUpCache(): void
{
// تحميل المقالات الشائعة
$this->cacheQuery('popular_posts', function () {
return \App\Models\Post::popular(7)->limit(10)->get();
}, ['posts', 'popular'], 'long');
// تحميل التصنيفات
$this->cacheQuery('all_categories', function () {
return \App\Models\Category::with('posts_count')->get();
}, ['categories'], 'permanent');
// تحميل إحصائيات الموقع
$this->cacheStats('site_stats', function () {
return [
'total_posts' => \App\Models\Post::published()->count(),
'total_users' => \App\Models\User::active()->count(),
'total_views' => \App\Models\Post::sum('views_count'),
];
});
}
/**
* Redis cache للبيانات السريعة
*/
public function setRedisCache(string $key, mixed $value, int $seconds = 3600): void
{
Redis::setex($key, $seconds, serialize($value));
}
public function getRedisCache(string $key): mixed
{
$value = Redis::get($key);
return $value ? unserialize($value) : null;
}
/**
* Rate limiting cache
*/
public function checkRateLimit(string $identifier, int $maxAttempts = 60, int $decayMinutes = 1): bool
{
$key = 'rate_limit:' . $identifier;
$attempts = (int) Redis::get($key);
if ($attempts >= $maxAttempts) {
return false;
}
Redis::multi();
Redis::incr($key);
Redis::expire($key, $decayMinutes * 60);
Redis::exec();
return true;
}
}
// app/Http/Middleware/CacheHeaders.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class CacheHeaders
{
public function handle(Request $request, Closure $next, ...$options)
{
$response = $next($request);
if ($request->method() === 'GET' && $response->getStatusCode() === 200) {
$cacheType = $options[0] ?? 'default';
switch ($cacheType) {
case 'static':
$response->header('Cache-Control', 'public, max-age=31536000'); // سنة
break;
case 'dynamic':
$response->header('Cache-Control', 'public, max-age=3600'); // ساعة
break;
case 'private':
$response->header('Cache-Control', 'private, max-age=300'); // 5 دقائق
break;
default:
$response->header('Cache-Control', 'public, max-age=1800'); // 30 دقيقة
}
$response->header('ETag', md5($response->getContent()));
}
return $response;
}
}
View Caching المتقدم:
<?php
// app/Services/ViewCacheService.php
namespace App\Services;
use Illuminate\Support\Facades\{View, Cache};
class ViewCacheService
{
/**
* تخزين مؤقت للـ Views
*/
public function cacheView(string $view, array $data = [], int $duration = 3600): string
{
$key = 'view:' . $view . ':' . md5(serialize($data));
return Cache::remember($key, $duration, function () use ($view, $data) {
return View::make($view, $data)->render();
});
}
/**
* Fragment caching للأجزاء المعقدة
*/
public function cacheFragment(string $name, callable $callback, int $duration = 3600): string
{
$key = 'fragment:' . $name;
return Cache::remember($key, $duration, $callback);
}
}
// استخدام في Blade Templates
// resources/views/components/cached-sidebar.blade.php
@php
$cacheService = app(\App\Services\ViewCacheService::class);
@endphp
{!! $cacheService->cacheFragment('sidebar', function() {
return view('partials.sidebar', [
'categories' => \App\Models\Category::withCount('posts')->get(),
'popular_posts' => \App\Models\Post::popular()->limit(5)->get(),
'recent_posts' => \App\Models\Post::recent()->limit(5)->get()
])->render();
}, 3600) !!}
3️⃣ Queue System التحسين
Advanced Queue Configuration:
<?php
// config/queue.php - إعدادات محسنة
return [
'default' => env('QUEUE_CONNECTION', 'redis'),
'connections' => [
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => 90,
'block_for' => null,
'after_commit' => false,
'serializer' => 'json',
],
'high_priority' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => 'high_priority',
'retry_after' => 60,
],
'low_priority' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => 'low_priority',
'retry_after' => 300,
],
],
'failed' => [
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
'database' => env('DB_CONNECTION', 'mysql'),
'table' => 'failed_jobs',
],
];
// app/Jobs/ProcessImageOptimization.php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\{InteractsWithQueue, SerializesModels};
use App\Models\Post;
class ProcessImageOptimization implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 3;
public $timeout = 300;
public $maxExceptions = 3;
public function __construct(
private Post $post,
private string $imagePath
) {
// تحديد الـ queue حسب الأولوية
$this->onQueue('high_priority');
// تأخير المعالجة إذا لزم الأمر
$this->delay(now()->addSeconds(10));
}
public function handle()
{
try {
// تحسين الصورة
$optimizedPath = $this->optimizeImage($this->imagePath);
// تحديث المقال
$this->post->update(['featured_image' => $optimizedPath]);
// إنشاء أحجام مختلفة
$this->createThumbnails($optimizedPath);
// محو الـ cache
app(\App\Services\CacheService::class)->invalidatePost($this->post->id);
} catch (\Exception $e) {
// تسجيل الخطأ
\Log::error('Image optimization failed', [
'post_id' => $this->post->id,
'image_path' => $this->imagePath,
'error' => $e->getMessage()
]);
throw $e; // لإعادة المحاولة
}
}
public function failed(\Throwable $exception)
{
// إرسال إشعار بالفشل
\Notification::route('slack', config('slack.webhook'))
->notify(new \App\Notifications\JobFailedNotification(
'Image Optimization Failed',
$this->post->id,
$exception->getMessage()
));
}
private function optimizeImage(string $path): string
{
// منطق تحسين الصورة
return $path;
}
private function createThumbnails(string $path): void
{
// إنشاء thumbnails
}
}
// app/Jobs/BatchEmailJob.php - معالجة batch
namespace App\Jobs;
use Illuminate\Bus\{Batchable, Queueable};
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\{InteractsWithQueue, SerializesModels};
class BatchEmailJob implements ShouldQueue
{
use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
private array $recipients,
private string $template,
private array $data
) {
$this->onQueue('low_priority');
}
public function handle()
{
if ($this->batch()->cancelled()) {
return;
}
foreach ($this->recipients as $recipient) {
\Mail::to($recipient)->send(new \App\Mail\NewsletterMail($this->template, $this->data));
// تحديث progress
$this->batch()->increment();
}
}
}
Queue Monitoring & Management:
<?php
// app/Console/Commands/QueueMonitor.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\{Redis, Queue};
class QueueMonitor extends Command
{
protected $signature = 'queue:monitor {--threshold=100}';
protected $description = 'Monitor queue performance and send alerts';
public function handle()
{
$threshold = $this->option('threshold');
$queues = ['default', 'high_priority', 'low_priority'];
foreach ($queues as $queueName) {
$size = Queue::size($queueName);
$this->info("Queue '{$queueName}': {$size} jobs");
if ($size > $threshold) {
$this->warn("⚠️ Queue '{$queueName}' has {$size} jobs (threshold: {$threshold})");
// إرسال إنذار
$this->sendAlert($queueName, $size);
}
}
// مراقبة الـ failed jobs
$failedJobs = \DB::table('failed_jobs')->count();
if ($failedJobs > 10) {
$this->error("❌ {$failedJobs} failed jobs detected");
$this->sendFailedJobsAlert($failedJobs);
}
// مراقبة memory usage
$memoryUsage = memory_get_usage(true) / 1024 / 1024;
$this->info("Memory usage: {$memoryUsage} MB");
}
private function sendAlert(string $queue, int $size): void
{
// إرسال إشعار Slack أو email
}
private function sendFailedJobsAlert(int $count): void
{
// إرسال إشعار بالمهام الفاشلة
}
}
// app/Console/Commands/QueueOptimizer.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class QueueOptimizer extends Command
{
protected $signature = 'queue:optimize';
protected $description = 'Optimize queue performance';
public function handle()
{
// تنظيف الـ completed jobs القديمة
$this->cleanOldJobs();
// إعادة تشغيل الـ failed jobs القابلة للإصلاح
$this->retryRecoverableJobs();
// تحسين Redis memory
$this->optimizeRedisMemory();
$this->info('Queue optimization completed');
}
private function cleanOldJobs(): void
{
$this->call('queue:prune-batches', ['--hours' => 48]);
$this->call('queue:flush');
}
private function retryRecoverableJobs(): void
{
// منطق إعادة المحاولة للمهام القابلة للإصلاح
}
private function optimizeRedisMemory(): void
{
\Redis::command('MEMORY', ['PURGE']);
}
}
4️⃣ Front-end Optimization
Asset Optimization:
<?php
// webpack.mix.js - تحسين الأصول
const mix = require('laravel-mix');
mix.js('resources/js/app.js', 'public/js')
.sass('resources/sass/app.scss', 'public/css')
.options({
processCssUrls: false,
postCss: [
require('autoprefixer'),
require('cssnano')({
preset: ['default', { discardComments: { removeAll: true } }]
})
]
})
.version()
.sourceMaps(false, 'source-map');
// تحسين الصور
mix.copy('resources/images', 'public/images')
.then(() => {
// ضغط الصور
const imagemin = require('imagemin');
const imageminPngquant = require('imagemin-pngquant');
const imageminMozjpeg = require('imagemin-mozjpeg');
imagemin(['public/images/*.{jpg,png}'], {
destination: 'public/images/optimized',
plugins: [
imageminMozjpeg({ quality: 80 }),
imageminPngquant({ quality: [0.6, 0.8] })
]
});
});
// Code splitting
mix.extract(['vue', 'axios', 'lodash']);
if (mix.inProduction()) {
mix.options({
terser: {
terserOptions: {
compress: {
drop_console: true,
}
}
}
});
}
CDN Integration:
<?php
// app/Services/CDNService.php
namespace App\Services;
class CDNService
{
private array $cdnDomains;
private bool $enabled;
public function __construct()
{
$this->cdnDomains = config('cdn.domains', []);
$this->enabled = config('cdn.enabled', false);
}
public function asset(string $path): string
{
if (!$this->enabled || empty($this->cdnDomains)) {
return asset($path);
}
// توزيع الأصول على domains متعددة
$domainIndex = crc32($path) % count($this->cdnDomains);
$domain = $this->cdnDomains[$domainIndex];
return rtrim($domain, '/') . '/' . ltrim($path, '/');
}
public function image(string $path, array $transformations = []): string
{
$url = $this->asset($path);
if (!empty($transformations)) {
$params = http_build_query($transformations);
$url .= '?' . $params;
}
return $url;
}
}
// Helper function
if (!function_exists('cdn_asset')) {
function cdn_asset(string $path): string {
return app(\App\Services\CDNService::class)->asset($path);
}
}
// config/cdn.php
return [
'enabled' => env('CDN_ENABLED', false),
'domains' => [
'https://cdn1.yoursite.com',
'https://cdn2.yoursite.com',
'https://cdn3.yoursite.com',
],
];
💡 Performance Monitoring
Advanced Performance Tracking:
<?php
// app/Middleware/PerformanceMonitor.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\{DB, Log, Redis};
class PerformanceMonitor
{
public function handle(Request $request, Closure $next)
{
$startTime = microtime(true);
$startMemory = memory_get_usage(true);
// تسجيل بداية الطلب
$requestId = uniqid();
$request->attributes->set('request_id', $requestId);
// تشغيل query logging
DB::enableQueryLog();
$response = $next($request);
$endTime = microtime(true);
$endMemory = memory_get_usage(true);
$metrics = [
'request_id' => $requestId,
'url' => $request->fullUrl(),
'method' => $request->method(),
'execution_time' => round(($endTime - $startTime) * 1000, 2), // ms
'memory_usage' => round(($endMemory - $startMemory) / 1024 / 1024, 2), // MB
'peak_memory' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
'queries_count' => count(DB::getQueryLog()),
'response_size' => strlen($response->getContent()),
'status_code' => $response->getStatusCode(),
'user_id' => auth()->id(),
'ip' => $request->ip(),
'timestamp' => now()->toISOString(),
];
// تحليل الاستعلامات البطيئة
$slowQueries = collect(DB::getQueryLog())
->where('time', '>', 100) // أبطأ من 100ms
->count();
if ($slowQueries > 0) {
$metrics['slow_queries_count'] = $slowQueries;
}
// إرسال البيانات للتحليل
$this->sendMetrics($metrics);
// إضافة headers للتشخيص
$response->headers->set('X-Execution-Time', $metrics['execution_time'] . 'ms');
$response->headers->set('X-Memory-Usage', $metrics['memory_usage'] . 'MB');
$response->headers->set('X-Queries-Count', $metrics['queries_count']);
return $response;
}
private function sendMetrics(array $metrics): void
{
// إرسال للـ Redis للتحليل السريع
Redis::lpush('performance_metrics', json_encode($metrics));
Redis::ltrim('performance_metrics', 0, 1000); // الاحتفاظ بآخر 1000 طلب
// تسجيل الطلبات البطيئة
if ($metrics['execution_time'] > 1000) { // أبطأ من ثانية
Log::warning('Slow request detected', $metrics);
}
// إرسال للـ monitoring service (مثل New Relic, DataDog)
if (config('monitoring.enabled')) {
$this->sendToMonitoringService($metrics);
}
}
private function sendToMonitoringService(array $metrics): void
{
// إرسال البيانات لخدمة المراقبة الخارجية
}
}
الخطوة التالية
تعلم Deployment و DevOps في Laravel لنشر تطبيقاتك بكفاءة عالية.
