120 lines
2.9 KiB
Markdown
120 lines
2.9 KiB
Markdown
|
|
# CLI Commands — `commands/`
|
||
|
|
|
||
|
|
Scripts run via `php console <CommandName>`. No HTTP context — output goes to stdout.
|
||
|
|
|
||
|
|
## File Naming
|
||
|
|
|
||
|
|
`PascalCaseCommand.php` — e.g. `SyncDataCommand.php`
|
||
|
|
|
||
|
|
## Running Commands
|
||
|
|
|
||
|
|
```bash
|
||
|
|
php console SyncDataCommand
|
||
|
|
php console SyncDataCommand --date="2024-10-28" --force
|
||
|
|
php console CleanupDatabaseCommand --days=30 --dry-run
|
||
|
|
php console ProcessEmailQueueCommand --limit=50
|
||
|
|
```
|
||
|
|
|
||
|
|
## Command Structure
|
||
|
|
|
||
|
|
```php
|
||
|
|
<?php
|
||
|
|
// commands/SyncDataCommand.php
|
||
|
|
|
||
|
|
echo "=== Data Sync ===\n";
|
||
|
|
|
||
|
|
$options = getopt("", ["date:", "force"]);
|
||
|
|
$date = $options['date'] ?? 'today';
|
||
|
|
$force = isset($options['force']);
|
||
|
|
|
||
|
|
echo "Syncing for: $date\n";
|
||
|
|
|
||
|
|
try {
|
||
|
|
$records = Db::select("SELECT * FROM sp_sync_queue WHERE synced_at IS NULL");
|
||
|
|
|
||
|
|
foreach ($records as $record) {
|
||
|
|
if (!$force) {
|
||
|
|
$exists = Db::getRow("SELECT 1 FROM sp_data WHERE external_id = :id LIMIT 1",
|
||
|
|
[':id' => $record['external_id']]
|
||
|
|
);
|
||
|
|
if ($exists) { echo "Skipping {$record['external_id']}\n"; continue; }
|
||
|
|
}
|
||
|
|
|
||
|
|
Db::insert('sp_data', [
|
||
|
|
'external_id' => $record['external_id'],
|
||
|
|
'payload' => $record['payload'],
|
||
|
|
'synced_at' => date('Y-m-d H:i:s')
|
||
|
|
]);
|
||
|
|
echo "Inserted {$record['external_id']}\n";
|
||
|
|
}
|
||
|
|
|
||
|
|
echo "Done.\n";
|
||
|
|
|
||
|
|
} catch (Exception $e) {
|
||
|
|
echo "ERROR: " . $e->getMessage() . "\n";
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Error Handling Pattern
|
||
|
|
|
||
|
|
Log individual failures and keep processing — exit with code 1 only on fatal errors:
|
||
|
|
|
||
|
|
```php
|
||
|
|
$errors = [];
|
||
|
|
$processed = 0;
|
||
|
|
|
||
|
|
foreach ($items as $item) {
|
||
|
|
try {
|
||
|
|
processItem($item);
|
||
|
|
$processed++;
|
||
|
|
echo ".";
|
||
|
|
} catch (Exception $e) {
|
||
|
|
$errors[] = ['id' => $item['id'], 'error' => $e->getMessage()];
|
||
|
|
echo "E";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
echo "\n\nProcessed: $processed | Errors: " . count($errors) . "\n";
|
||
|
|
|
||
|
|
if (!empty($errors)) {
|
||
|
|
foreach ($errors as $err) {
|
||
|
|
echo "Item #{$err['id']}: {$err['error']}\n";
|
||
|
|
}
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Dry-Run Pattern
|
||
|
|
|
||
|
|
```php
|
||
|
|
$dryRun = isset($options['dry-run']);
|
||
|
|
if ($dryRun) { echo "DRY RUN — no changes will be made\n"; }
|
||
|
|
|
||
|
|
$count = Db::getRow("SELECT COUNT(*) as n FROM sp_logs WHERE created_at < :date",
|
||
|
|
[':date' => $cutoffDate]
|
||
|
|
);
|
||
|
|
echo "Rows to delete: {$count['n']}\n";
|
||
|
|
|
||
|
|
if (!$dryRun) {
|
||
|
|
Db::delete('sp_logs', 'created_at < :date', [':date' => $cutoffDate]);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Cron Setup
|
||
|
|
|
||
|
|
```bash
|
||
|
|
crontab -e
|
||
|
|
```
|
||
|
|
|
||
|
|
```cron
|
||
|
|
# Every 5 minutes
|
||
|
|
*/5 * * * * cd /www/wwwroot/appSeedProject && php console ProcessEmailQueueCommand >> /var/log/email-queue.log 2>&1
|
||
|
|
|
||
|
|
# Every hour
|
||
|
|
0 * * * * cd /www/wwwroot/appSeedProject && php console SyncDataCommand >> /var/log/sync.log 2>&1
|
||
|
|
|
||
|
|
# Nightly cleanup at 2 AM
|
||
|
|
0 2 * * * cd /www/wwwroot/appSeedProject && php console CleanupDatabaseCommand --days=30 >> /var/log/cleanup.log 2>&1
|
||
|
|
```
|