Cron & CLIالمهام والأوامر.
المهام المجدولة اللي بتشغّل نص Magento في الخلفية، والأوامر اللي بتستخدمها كل يوم. إزاي يشتغلوا، إزاي تعمل بتاعك، وإزاي تشخّص لما يقفوا.
الـ cron في Magento 2 بيشتغل على طبقتين: cron النظام بينده bin/magento cron:run كل دقيقة، وجدولة Magento بتقرر أي مهمة حان وقتها حسب crontab.xml وتسجّلها في جدول cron_schedule بحالات pending و running و success و missed و error. المهام متجمّعة في مجموعات زي default و index و consumers. والـ CLI أوامر Symfony Console بتسجّلها في di.xml.
إزاي الـ Cron بيشتغل
فيه طبقتين بيتخلطوا، والفرق بينهم أساس التشخيص.
# الطريقة الرسمية — Magento بيكتبه بنفسه bin/magento cron:install # شوف اللي اتكتب crontab -l # الشكل المتوقّع تقريباً: # * * * * * /usr/bin/php /var/www/bin/magento cron:run
المجموعات والإعدادات
المهام متجمّعة، وكل مجموعة بإعداداتها.
من Stores → Configuration → Advanced → System → Cron.
جدول الجدولة
فهم الجدول ده = فهم التشخيص.
كل مهمة مجدولة بتتسجّل فيه كصف بحالة وأوقات.
-- توزيع الحالات في آخر ساعة SELECT status, COUNT(*) FROM cron_schedule WHERE scheduled_at > NOW() - INTERVAL 1 HOUR GROUP BY status; -- آخر المهام الناجحة — هل الـ cron حي؟ SELECT job_code, executed_at FROM cron_schedule WHERE status = 'success' ORDER BY executed_at DESC LIMIT 10; -- الأخطاء بأسبابها SELECT job_code, messages, scheduled_at FROM cron_schedule WHERE status = 'error' ORDER BY scheduled_at DESC LIMIT 20; -- مهام عالقة في running SELECT job_code, executed_at FROM cron_schedule WHERE status = 'running' AND executed_at < NOW() - INTERVAL 1 HOUR;
عمل مهمة مخصّصة
من التعريف للتشغيل.
<?xml version="1.0"?> <config> <group id="default"> <!-- جدولة ثابتة --> <job name="vendor_cleanup_old_data" instance="Vendor\Module\Cron\CleanupOldData" method="execute"> <!-- كل يوم 3 صباحاً --> <schedule>0 3 * * *</schedule> </job> <!-- جدولة من الإعدادات — الأفضل --> <job name="vendor_sync_erp" instance="Vendor\Module\Cron\SyncErp" method="execute"> <config_path>vendor/sync/schedule</config_path> </job> </group> </config>
schedule
التوقيت ثابت في الكود. تغييره يحتاج نشر.
config_path
التوقيت من الإعدادات. الفريق يغيّره من الأدمن.
*/15 * * * * # كل 15 دقيقة 0 * * * * # كل ساعة في الدقيقة صفر 0 3 * * * # كل يوم 3 صباحاً 0 3 * * 1 # كل إثنين 3 صباحاً 0 0 1 * * # أول كل شهر */5 9-17 * * 1-5 # كل 5 دقايق، أيام العمل بس
namespace Vendor\Module\Cron; use Psr\Log\LoggerInterface; use Magento\Framework\App\Config\ScopeConfigInterface; class SyncErp { private const ENABLED_PATH = 'vendor/sync/enabled'; public function __construct( private readonly ScopeConfigInterface $config, private readonly SyncService $service, private readonly LoggerInterface $logger ) {} public function execute(): void { // 1. مفتاح تعطيل — مهم جداً if (!$this->config->isSetFlag(self::ENABLED_PATH)) { return; } $start = microtime(true); try { $count = $this->service->sync(); // 2. سجّل النجاح بأرقام مفيدة $this->logger->info('ERP sync done', [ 'records' => $count, 'duration' => round(microtime(true) - $start, 2), ]); } catch (\Exception $e) { // 3. سجّل وارمي — عشان تتعلّم error $this->logger->error('ERP sync failed', [ 'error' => $e->getMessage(), ]); throw $e; } } }
- مفتاح التعطيل — لو المهمة سبّبت مشكلة في production، فريق التشغيل يقدر يوقفها من الأدمن فوراً من غير نشر.
- تسجيل بأرقام — عدد السجلات والمدة. من غيرهم مش هتعرف المهمة بتتقل ولا لأ إلا لما تقع.
- الرمي بعد التسجيل — عشان الحالة تتعلّم error وتبان في الجدول.
تشخيص الـ Cron
لما حاجة تقف والسبب مش واضح.
- الـ crontab موجود؟ — crontab -l بمستخدم الـ web server.
- الـ cron بيشتغل فعلاً؟ — آخر success في الجدول.
- المهمة بتتجدول؟ — دوّر على job_code بتاعها.
- بتقع بخطأ؟ — شوف عمود messages.
- عالقة في running؟ — عملية ماتت من غير تحديث.
- الجدول متضخّم؟ — بيبطّئ كل حاجة.
# شغّل يدوياً وشوف المخرجات bin/magento cron:run # مجموعة محددة bin/magento cron:run --group=index # هل فيه عمليات cron شغّالة؟ ps aux | grep cron:run # أخطاء الـ cron في السجلات grep -i cron var/log/system.log | tail -30
-- شوف الأول قبل ما تعدّل SELECT * FROM cron_schedule WHERE status = 'running' AND executed_at < NOW() - INTERVAL 2 HOUR; -- بعد التأكد إن مفيش عملية شغّالة فعلاً UPDATE cron_schedule SET status = 'error', messages = 'Manually cleared - stuck' WHERE status = 'running' AND executed_at < NOW() - INTERVAL 2 HOUR;
لو التنظيف مش شغّال، cron_schedule بيوصل لملايين الصفوف. النتيجة: كل نداء للـ cron بيبطّئ، ومع الوقت المهام بتبدأ تتعلّم missed من غير سبب حقيقي.
SELECT COUNT(*) AS total, MIN(scheduled_at) AS oldest FROM cron_schedule; -- لو الأقدم من شهور، التنظيف مش شغّال
الأوامر الأساسية
اللي بتستخدمها كل يوم.
bin/magento cache:status bin/magento cache:clean config layout block_html bin/magento cache:flush bin/magento indexer:status bin/magento indexer:reindex bin/magento indexer:set-mode schedule
bin/magento module:status bin/magento module:enable Vendor_Module bin/magento module:disable Vendor_Module bin/magento setup:upgrade bin/magento setup:di:compile bin/magento setup:static-content:deploy -f ar_SA en_US
# اقرا إعداد bin/magento config:show web/secure/base_url # غيّر إعداد bin/magento config:set web/secure/use_in_frontend 1 # إعداد حسّاس — بيتشفّر bin/magento config:set:sensitive payment/gateway/key VALUE
bin/magento deploy:mode:show bin/magento deploy:mode:set production bin/magento maintenance:enable bin/magento maintenance:enable --ip=1.2.3.4 bin/magento maintenance:disable
bin/magento queue:consumers:list bin/magento queue:consumers:start NAME --max-messages=100 bin/magento admin:user:create bin/magento admin:user:unlock USERNAME bin/magento info:adminuri
عمل أمر مخصّص
أبسط شكل، مشروح سطر سطر.
<type name="Magento\Framework\Console\CommandList"> <arguments> <argument name="commands" xsi:type="array"> <item name="vendorSyncProducts" xsi:type="object"> Vendor\Module\Console\Command\SyncProducts </item> </argument> </arguments> </type>
namespace Vendor\Module\Console\Command; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; class SyncProducts extends Command { public function __construct( private readonly SyncService $service, $name = null ) { parent::__construct($name); } // اسم الأمر ووصفه protected function configure(): void { $this->setName('vendor:sync:products'); $this->setDescription('Sync products from ERP'); parent::configure(); } // المنطق protected function execute( InputInterface $input, OutputInterface $output ): int { try { $count = $this->service->sync(); $output->writeln( "<info>Synced {$count} products</info>" ); // 0 = نجاح return Command::SUCCESS; } catch (\Exception $e) { $output->writeln( "<error>{$e->getMessage()}</error>" ); // غير صفر = فشل return Command::FAILURE; } } }
- اسم الأمر — بصيغة vendor:group:action عشان ينتظم مع الباقي.
- القيمة الراجعة — 0 نجاح، 1 فشل. مهمة جداً في السكريبتات والأتمتة.
- وسوم التلوين — <info> أخضر، <error> أحمر، <comment> أصفر.
bin/magento setup:upgrade bin/magento cache:clean # شوفه في القائمة bin/magento list | grep vendor # شغّله bin/magento vendor:sync:products
أمر متقدّم
مدخلات، خيارات، وشريط تقدّم.
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; protected function configure(): void { $this->setName('vendor:sync:products'); // مُدخل إجباري $this->addArgument( 'source', InputArgument::REQUIRED, 'Source system code' ); // خيار بقيمة $this->addOption( 'limit', 'l', InputOption::VALUE_OPTIONAL, 'Max records', 100 // القيمة الافتراضية ); // خيار بدون قيمة — علم $this->addOption( 'dry-run', null, InputOption::VALUE_NONE, 'Preview without saving' ); parent::configure(); }
protected function execute( InputInterface $input, OutputInterface $output ): int { $source = $input->getArgument('source'); $limit = (int) $input->getOption('limit'); $dryRun = (bool) $input->getOption('dry-run'); if ($dryRun) { $output->writeln( '<comment>Dry run — nothing saved</comment>' ); } $records = $this->service->fetch($source, $limit); // شريط تقدّم — مفيد للعمليات الطويلة $progress = new ProgressBar($output, count($records)); $progress->start(); foreach ($records as $record) { if (!$dryRun) { $this->service->save($record); } $progress->advance(); } $progress->finish(); $output->writeln(''); return Command::SUCCESS; }
bin/magento vendor:sync:products erp bin/magento vendor:sync:products erp --limit=500 bin/magento vendor:sync:products erp --dry-run
الفخاخ الشائعة
اقرا دي حتى لو مش هتقرا حاجة تانية.
- مفيش crontab أصلاً — كل حاجة في الخلفية واقفة بصمت. الفخ الأول.
- crontab بمستخدم root — مشاكل صلاحيات غامضة في الملفات.
- مهام عالقة في running — بتمنع نفسها من الاشتغال للأبد.
- جدول cron_schedule متضخّم — بيبطّئ كل نداء للـ cron.
- بلع الاستثناء في مهمة cron — الفشل مابيبانش في الجدول.
- مفيش مفتاح تعطيل — مهمة سبّبت مشكلة ومحتاج نشر عشان توقفها.
- جدولة ثابتة في الكود — تغيير التوقيت محتاج نشر.
- أمر بيرجّع 0 دايماً — سكريبتات النشر بتفتكر إنه نجح.
- تحميل كل البيانات في أمر — نفاد الذاكرة.
- تعديل حالة running من غير فحص — نسختين بيشتغلوا مع بعض.
أسئلة الإنترفيو
الأسئلة المتكررة.
Cron & CLI ✓
دلوقتي فاهم الطبقتين في الـ cron، جدول الجدولة وحالاته، إزاي تعمل مهمة وأمر مخصّصين، وإزاي تشخّص لما حاجة تقف.
الخلاصة: الـ cron بيقع بصمت فاعمله فحص دوري، ارمي الاستثناء في المهام عشان الفشل يبان، وأي أمر لازم يرجّع كود خروج صحيح.
خلّصت الدرس؟علّمه عشان تتابع تقدّمك في الكورس.
أسئلة شائعة
إزاي الـ cron بيشتغل في Magento 2؟
طبقتين. cron النظام في Linux بينده bin/magento cron:run كل دقيقة، وجدولة Magento جوّه الأمر ده بتقرر أي مهمة حان وقتها حسب ملفات crontab.xml وتسجّلها في جدول cron_schedule وتشغّلها. لو الـ crontab مش موجود مفيش أي حاجة بتشتغل في الخلفية مهما كانت الجدولة مظبوطة.
الـ cron في Magento مش بيشتغل، أشخّص إزاي؟
بالترتيب: اتأكد إن الـ crontab موجود بمستخدم الـ web server بـ crontab -l، وبعدين شوف آخر صف بحالة success في cron_schedule عشان تعرف الـ cron حي ولا لأ، وبعدين دوّر على الـ job_code بتاع المهمة وشوف حالتها وعمود messages. لو لقيتها running من ساعات، فده غالبًا process ماتت من غير ما تحدّث الحالة.
إيه حالات جدول cron_schedule في Magento 2؟
خمس حالات: pending مجدولة ولسه، running شغّالة دلوقتي، success خلصت، missed فات وقتها ومااشتغلتش في المدة المسموحة، و error وقعت وسبب الخطأ في عمود messages. الـ missed بكثرة معناه الـ cron بطيء أو واقف، وصف running من ساعات معناه مهمة عالقة بتمنع نفسها من الاشتغال تاني.
إزاي أعمل مهمة cron مخصّصة في Magento 2؟
عرّفها في etc/crontab.xml جوّه <group id="default"> بـ <job name instance method>، والتوقيت إما <schedule> ثابت بصيغة cron أو <config_path> عشان يتغيّر من الأدمن. الكلاس عادي فيه method execute. ضيف مفتاح تعطيل من الإعدادات، وسجّل النتيجة بالأرقام، وارمي الـ exception عشان الفشل يتعلّم error في الجدول.
إزاي أعمل أمر CLI مخصّص في Magento 2؟
كلاس بيورّث من Symfony\Component\Console\Command\Command، بتحدد الاسم والوصف في configure() والمنطق في execute()، وبتسجّله في etc/di.xml كـ item جوّه argument commands بتاع Magento\Framework\Console\CommandListInterface. بعد setup:upgrade و cache:clean بيظهر في bin/magento list. ارجّع 0 للنجاح وغير صفر للفشل عشان سكريبتات النشر تتصرّف صح.
إزاي أصلّح مهمة cron عالقة في حالة running؟
الأول اتأكد بـ ps aux | grep cron:run إن مفيش process شغّالة فعلًا. بعدها حدّث الصفوف القديمة في cron_schedule اللي حالتها running من أكتر من ساعتين لحالة error برسالة توضّح إنك مسحتها يدويًا. لو غيّرت الحالة والمهمة لسه شغّالة، Magento هيشغّل نسخة تانية وهيبقى عندك اتنين بيكتبوا في نفس الوقت.
إيه الفرق بين schedule و config_path في crontab.xml؟
<schedule> بيثبّت التوقيت في الكود، فأي تغيير محتاج نشر. <config_path> بيقرا التوقيت من مسار إعداد، فتقدر تعرضه في system.xml وفريق التشغيل يغيّره من الأدمن. استخدم config_path لأي مهمة تشغيلية زي المزامنة والتقارير والتنظيف، لأن توقيتها هيتغيّر في وقت ما.