Appearance
Cron Job Framework
Flow ID: SY-01 Module(s): job Complexity: Medium Last Updated: 2026-05-27
Business Context
The job framework is a queue-based scheduling system that runs background tasks. Jobs implement the JobCommand interface and are configured in application/config/jobs.php with cron expressions. The system uses a database queue (job_schedule table), supports retries, timeouts, job locking, and 10 named queues for organizational separation.
API Reference
REST Endpoints (Modern Layer)
The cron job framework exposes no REST endpoints. All job execution is triggered by system cron calling php cli.php job_manager; there is no HTTP surface for job control.
Legacy Admin Routes
The cron job framework has no legacy admin routes. Queue management and job status monitoring are performed via CLI and direct inspection of the job_schedule table; no web-facing admin controller exists for this module.
Code Flow
Job Interface (ecommercen/core/JobCommand.php):
php
interface JobCommand {
public function executeCommand(array $options): void;
public static function getOptions(): array;
}Execution path: php cli.php job/{JobName} (legacy) or php cli.php job_manager (queue-based).
Queue-based flow:
AdvCreateJobSchedulereads cron expressions → generatesjob_schedulerows for todayAdvJobManagerspawns Symfony Process per enabled queueAdvJobQueueManagerpicks next ready job → executes via subprocess → updates statusAdvClearJobSchedulepurges old records perkeepLogPeriod
Data Model
job_schedule
database/initial/initial.sql:661-677 (base schema) + database/migrations/20250324124000_add_retry_count_to_job_schedule.php (retry_count column and KEY retry_count index)
sql
CREATE TABLE `job_schedule` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`queue` varchar(255) NOT NULL,
`job` varchar(100) NOT NULL,
`start_date` datetime DEFAULT NULL,
`end_date` datetime DEFAULT NULL,
`run_at` datetime NOT NULL,
`job_status` int(11) NOT NULL,
`pid` int(11) DEFAULT NULL,
`max_retries` int(11) DEFAULT NULL,
`grace_time` int(11) DEFAULT NULL,
`job_arguments` text DEFAULT NULL,
`retry_count` int(11) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `job` (`job`),
KEY `job_status` (`job_status`),
KEY `queue` (`queue`),
KEY `retry_count` (`retry_count`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;retry_count column and index added by database/migrations/20250324124000_add_retry_count_to_job_schedule.php:13.
| Column | Type | Purpose |
|---|---|---|
id | int(11) AUTO_INCREMENT | Primary key |
queue | varchar(255) | Queue name (e.g., core, tracking) |
job | varchar(100) | Fully-qualified job class name |
start_date | datetime | Timestamp set when the subprocess begins |
end_date | datetime | Timestamp set when the subprocess completes |
run_at | datetime | Scheduled execution time; rows ordered ASC by this column (ecommercen/job/models/AdvJobsModel.php, getNextJob()) |
job_status | int(11) | State machine: NOT_STARTED → RUNNING → JOB_FINISHED / JOB_FAILED (ecommercen/job/controllers/AdvJobQueueManager.php, executeJob()) |
pid | int(11) | PID of the executing Symfony Process subprocess |
max_retries | int(11) | Configured maximum retry count (copied from retryTimes config at schedule creation) (ecommercen/job/libraries/AdvCreateJobSchedule.php:84) |
grace_time | int(11) | Per-row subprocess kill timeout in seconds |
job_arguments | text | Options passed to executeCommand() |
retry_count | int(11) NOT NULL DEFAULT 0 | Mutable counter of retry attempts made; incremented by AdvJobQueueManager::trackRetryJob() (database/migrations/20250324124000_add_retry_count_to_job_schedule.php:13) |
Configuration
File: application/config/jobs.php
10 queues: core, personalization, tracking, import, giftCards, erp, public_marketplace, shopflix_marketplace, reporting, fileManager.
Each queue has: enabled, keepLogPeriod (DateInterval), hasScheduler, lockJobs, commands[].
Each command: command (class name), schedule (cron expr), graceTime (timeout seconds), retryTimes, options (passed to executeCommand()).
Job implementations: 70+ JobCommand classes in ecommercen/job/libraries/.
Queue status (10 queues configured):
| Queue | Status | Purpose |
|---|---|---|
core | Enabled | Primary job queue -- order processing, emails, cache, backups |
tracking | Enabled | Price tracking, delivery status polling |
giftCards | Enabled | Gift card order processing and delivery |
reporting | Enabled | Analytics export (Databox) |
fileManager | Enabled | Bulk imports (products, images) |
import | Disabled | Empty — reserved for CSV product import |
personalization | Disabled | Customer audience/tagging segmentation |
erp | Disabled | ERP sync (Farmacon, Europharmacy) |
public_marketplace | Disabled | Public marketplace order sync |
shopflix_marketplace | Disabled | Shopflix marketplace handling |
Client Extension Points
Individual job libraries can be overridden at the client application layer by providing a same-named file under application/modules/job/libraries/. The framework will load the client file in preference to the core ecommercen/job/libraries/ counterpart.
Known override path:
application/modules/job/libraries/ClearExpiredSessions.php— client-level override forecommercen/job/libraries/AdvClearExpiredSessions.php. Placing a class here allows the client to substitute custom session GC logic without modifying the core job library.
Adding new jobs (Business Rule #6): New job classes implementing JobCommand can be registered entirely through config — add the class under the appropriate queue's commands[] array in application/config/jobs.php. No framework code changes are required. Custom queues can also be declared in jobs.php for client-specific processing isolation.
Domain Layer
Modern Domain
No modern domain layer exists for the cron framework. The scheduler and all job classes live entirely in the legacy layer.
REST Layer
No REST layer exists for the cron framework. Job execution is triggered by system cron via CLI (php cli.php job_manager); there is no HTTP surface for job control.
Legacy Layer
Order & Fulfillment:
AdvCancelIncompleteOrders— Cancels pending orders exceeding payment timeoutAdvGetOrdersTransferStatus— Polls courier APIs for delivery tracking (15 carriers); modern port available asAdvisable\Domains\Transporter\Jobs\GetOrdersTransferStatus(toggle-activate, #274)AdvSendEmailBasedOnSentStatus— Email on invoiced statusAdvSendEmailAndSMSBasedOnSentStatus— Email + SMS when shippedAdvSendEmailAndSMSBasedOnFromStoreStatus— Notify store-based deliveriesAdvSendEmailForDeliveryDelay— Alert delayed deliveriesAdvSendOrderReminderMail— Order reminder emailsAdvEmailPaypalCanceledAndIsPaidToAdmin— Suspicious PayPal state reports
Customer & Marketing:
AdvAddCustomersToAudience— Auto-segment customers into audiencesAdvAddCustomersToSpecificAudience— Specific audience targetingAdvAddRegisteredTagToCustomers— Tag registered customersAdvAddTagsToCustomers— Personalization tagsAdvSanitizeGuestCustomers— Auto-delete guest data per policyAdvSendMailToCustomersForReview— Review request emailsAdvSentMailToCustomerBirthday— Birthday greeting emailsAdvSentMailToCustomerRemainingPoints— Points reminder emailsAdvSendEmailForWaitingList— Stock availability notificationsAdvClearOldEmails— Purge old message history
Loyalty:
AdvAddPointsToCustomerDeliver— Award points for delivered ordersAdvAddPointsToCustomerFromStore— Award points for store purchases
Product & Catalog:
Advisable\Domains\Product\PriceTracking\Jobs\TrackPrices— Record price historyAdvInsertProductsFromFile— Bulk product importAdvUpdateProductsFromFileByBarcode— Update by barcode CSVAdvUpdateProductsFromFileByProductCode— Update by SKU CSVAdvUploadImagesFromZipFile— Bulk image importAdvUpdateTempOrderBasketTable— Maintain cart analytics tableAdvSolrIndex— Index products to Solr
Payment:
AdvPayByBankGetStatus— Check PayByBank payment statusAdvCancelPendingGiftCards(ecommercen/gift_cards/jobs/) — Cancel expired gift card ordersAdvSendGiftCardsToEmails(ecommercen/gift_cards/jobs/) — Email gift card codesAdvSendGiftCardsToPhones(ecommercen/gift_cards/jobs/) — SMS gift card codes
ERP & Marketplace:
AdvFarmakonPostOrders/AdvFarmakonSyncProducts— Farmacon ERP syncAdvPostOrdersEuropharmacy/AdvSyncProductsEuropharmacy/AdvSyncOrdersStatusEuropharmacy— Europharmacy ERPAdvGetPublicMarketplaceAwaitingOrders/AdvSetPublicOrdersSent/AdvSetTrackingPublicMarketplace/AdvUpdatePublicOrdersStatus/AdvUpdatePublic2PSupplier— Public marketplaceAdvHandleShopflixOrders— Shopflix marketplaceAdvRemoveOldSkroutzOrders— Cleanup expired Skroutz ordersAdvReportingToDatabox— Analytics export
Infrastructure:
AdvBackUpDataBase/AdvCleanBackUps— Database backup lifecycleAdvClearSiteCache/AdvClearCacheOnLimit— Cache managementAdvGenerateSitemaps— XML sitemap generation (chunked, 500 URLs/file)AdvCheckCDNCertificate— CDN SSL cert monitoringAdvCheckVideoStatusStream/AdvDeleteVideoToStream/AdvUploadVideoToStream— Video streamingAdvGetBulkerSmsStatus— SMS delivery statusAdvClearUsersAudit— Purge audit logsAdvClearExpiredSessions— Deterministic session garbage collection viafind -delete. Runs daily at 03:00 (0 3 * * *). Configured withgraceTime: 300(5 minutes) andretryTimes: 0(application/config/jobs.php:34). The legacy CLI shimexecute()delegates toexecuteCommand([])(ecommercen/job/libraries/AdvClearExpiredSessions.php:79-82).ClearExpiredSessions::getOptions()returns[], registered ascommandOptions(application/config/jobs.php:209). File:ecommercen/job/libraries/AdvClearExpiredSessions.php, client override:application/modules/job/libraries/ClearExpiredSessions.php.Execution flow (
ecommercen/job/libraries/AdvClearExpiredSessions.php:15-72):- Windows guard (
:17-20): logs'Session GC skipped: not supported on Windows host'and returns.find -deleteis not available on Windows; production runs Linux/Docker only. - Driver short-circuit (
:22-26): ifsess_driveris not'files'(e.g.redis,database), logs'Session GC skipped: driver handles its own expiration'and returns. Non-file drivers manage their own TTL. - Config reads (
:28-30): readssess_save_path,sess_cookie_name, andsess_expirationviaconfig_item(). - Path canonicalization (
:36-41):realpath($savePath)collapses the literal..in the default config value (FCPATH . '../sessions'), resolves symlinks, and returnsfalsefor non-existent paths. Logs an error and aborts if resolution fails or the result is not a directory. - Remaining guards (
:42-49): emptysess_cookie_name→ error + abort;sess_expiration <= 0→ error + abort. find -delete(:55-64): invokesfind <savePath> -maxdepth 1 -type f -name '<cookie_name>*' -mmin +<minutes> -deleteviaexec(). BothsavePathand the glob pattern are wrapped inescapeshellarg().sess_expiration(seconds) is converted to minutes withceil()and a 1-minute floor. Stderr is captured via2>&1; a non-zero exit code triggers an error log with the captured output.- Success log (
:71):"Session GC completed: path=… prefix=… maxlifetime=…s".
Why
find -deleteinstead ofsession_gc(): PHP's nativefilessave_handler (used bysession_gc()) scans for thesess_*prefix, but CI3 writes session files as<cookie_name><sid>— e.g.ci_session1a2b.... Usingsession_gc()from CLI targeted the wrong files and silently deleted nothing in production.find -deletewith the configuredsess_cookie_nameas the glob prefix correctly targets the actual CI3 session files (#210).- Windows guard (
AdvAddProductsToAdvisableAi— AI feed syncAdvCreateJobSchedule/AdvClearJobSchedule— Job framework self-managementGenerateOpenApiJson— OpenAPI spec generationRemoveExpiredRefreshTokens— Auth token cleanup
Business Rules
- Job locking --
lockJobs: trueprevents parallel execution per queue. A running job blocks the queue until it finishes or its maximum runtime is exceeded. (application/config/jobs.php, per-queuelockJobssetting) - Grace time = timeout -- Subprocess killed after
graceTimeseconds (default 60). Maximum runtime calculated asgraceTime * (retryTimes + 1). (ecommercen/job/controllers/AdvJobQueueManager.php,executeJob()) - Retry on failure --
retryTimesattempts before marking JOB_FAILED. Between retries, 3-second sleep backoff. (ecommercen/job/controllers/AdvJobQueueManager.php,executeJob()) - Cron expressions -- Standard 5-field format, parsed by
Cron\CronExpressionlibrary. (ecommercen/job/libraries/AdvCreateJobSchedule.php) - Queue isolation -- Each queue runs as separate subprocess via Symfony Process. (
ecommercen/job/controllers/AdvJobManager.php,index()) - Client customization -- Add jobs to
jobs.phpconfig; no code changes needed for scheduling. Custom queues can be added for client-specific processing. - DB reconnect on long runs --
AdvJobsModel::update()calls$this->db->close(); $this->db->initialize()before updates to handle MySQL "gone away" errors. (ecommercen/job/models/AdvJobsModel.php) - Schedule regeneration -- If no
NOT_STARTEDjobs exist for a queue on invocation, the scheduler regenerates the schedule from config. (ecommercen/job/controllers/AdvJobQueueManager.php,initializeJobs()) - Run-at ordering -- Jobs are picked in
run_at ASCorder; earliest scheduled job runs first. (ecommercen/job/models/AdvJobsModel.php,getNextJob())
Known Issues & Security Gaps
— Resolved: the job no longer callssession_gc()return value not checked for failuresession_gc(). The rewrite (#210) replaced the PHP session GC path withfind -delete, which is prefix-aware and targets CI3's actual session files. Theexec()return is checked via exit code; non-zero logs an error with captured stderr.— Resolved: column renamed toretriescolumn name is misleading — stores a limit, not a countermax_retriesin migration20260416093953_rename_retries_to_max_retries.php(#109).
Tests
No dedicated unit or integration test files exist for the cron job framework classes (AdvJobManager, AdvJobQueueManager, AdvCreateJobSchedule, AdvClearJobSchedule). Testing the queue manager requires a Symfony Process integration harness that is not currently present in the test suite.
AdvClearExpiredSessions is an exception: tests/Legacy/Job/AdvClearExpiredSessionsTest.php (7 cases) covers the Windows skip, driver short-circuit, all four config validation paths (empty/missing path, empty cookie_name, non-positive expiration), and the prefix-aware happy path (asserts expired <cookie_name>* files are deleted while fresh files and prefix-mismatched files are retained). POSIX-only tests skip on Windows.
Platform-wide PHPUnit coverage (670 tests across tests/):
| Suite | Count |
|---|---|
| Unit | 440 |
| Integration | 221 |
| Legacy | 9 |
| Total | 670 |
All 18 DDD domains have test coverage; the job scheduler and individual job libraries do not.
Related Flows
System Flows (Jobs)
- SY-02 Order Status Emails -- email/SMS jobs on order status transitions
- SY-03 Incomplete Order Cancellation --
CancelIncompleteOrdersin core queue - SY-04 Price Tracking --
TrackPricesin tracking queue - SY-05 Sitemap Generation --
GenerateSitemapsin core queue - SY-06 Solr Indexing --
SolrIndexin core queue - SY-07 Cache Management --
ClearSiteCache/ClearCacheOnLimitin core queue - SY-08 Database Backup --
BackUpDataBase/CleanBackUpsin core queue - SY-09 Customer Data Jobs -- customer audience/tagging in personalization queue
- SY-10 Loyalty Points Jobs -- points awarded on delivery
- SY-11 Review Reminders -- review request emails
- SY-12 Birthday Points Emails -- birthday greeting emails
- SY-13 Waiting List Notifications -- stock availability email notifications
- SY-14 Order Reminders -- order reminder emails
- SY-15 Delivery Delay -- delivery delay alert emails
- SY-16 PayByBank Polling -- payment status checking
- SY-17 Bulk Product Import -- CSV/Excel product import
- SY-18 Image Upload ZIP -- bulk image import from ZIP
System Infrastructure
- SY-22 Job Manager -- detailed coverage of the queue processing system
- SY-24 Email Dispatch -- email infrastructure used by email jobs
- SY-27 Deferred Tasks -- deferred task execution as an alternative to cron jobs
- SY-29 Deployment Architecture -- version-gated cron execution in K8s via
init_cron.php
Integration Flows
- IN-08 ERP Integrations -- ERP sync jobs in erp queue
- IN-04 Public Marketplace -- marketplace order sync in public_marketplace queue
- IN-03 Shopflix Marketplace -- Shopflix order handling in shopflix_marketplace queue
- IN-12 Analytics -- reporting jobs in reporting queue
Wiki Guide: Job Manager Guide -- operational guide for the job scheduling system