MongoDB大集合字段加时间戳及Eloquent集合保存技术问询
I’ve worked with Laravel + MongoDB setups quite a bit, so let’s break down how to solve your timestamp problem while optimizing that messy nested loop you’ve got going. Here are practical, actionable approaches:
1. Clean Up & Optimize Your Nested Loop
First, let’s refactor your current loop to be more readable and efficient. The goal is to avoid unnecessary array copies and make the logic easier to maintain:
$automations = Automation::all(); $updatedDocs = []; foreach ($automations as $automation) { // Get the workflow data (use reference to modify in-place) $workflows = &$automation->workflow; foreach ($workflows as &$item) { // Add timestamps to each subscriber's fields if (isset($item['subscribers'])) { foreach ($item['subscribers'] as &$subscriber) { foreach ($subscriber as $key => $value) { $subscriber["{$key}_updated_at"] = now(); // Add created_at only if it doesn't exist if (!isset($subscriber["{$key}_created_at"])) { $subscriber["{$key}_created_at"] = now(); } } } } // Add timestamps to non-subscriber fields matching your delay condition foreach ($item as $fieldName => &$fieldValue) { if ($fieldName !== 'subscribers' && is_array($fieldValue)) { if ($fieldValue['delay'] === "0" && $fieldValue['delay_time'] === 'now') { $fieldValue['updated_at'] = now(); $fieldValue['created_at'] = $fieldValue['created_at'] ?? now(); } } } } $updatedDocs[] = $automation->toArray(); } // Use bulk upsert to minimize database round trips Automation::upsert($updatedDocs, ['id'], ['workflow']);
- Using references (
&$variable) avoids copying large arrays, saving memory and processing time. - Collecting all updates first then doing a single upsert cuts down on database calls (critical for large collections).
2. Use Eloquent Mutators for Automatic Timestamps
If you’re using the jenssegers/mongodb package (the standard for Laravel-MongoDB integration), you can define mutators on your model to automatically add timestamps whenever nested fields are modified. This eliminates manual loops entirely:
use Jenssegers\Mongodb\Eloquent\Model; class Automation extends Model { protected $collection = 'automations'; // Your large MongoDB collection name // Automatically process workflow data when setting it public function setWorkflowAttribute($value) { $this->addTimestampsToNestedData($value); $this->attributes['workflow'] = $value; } // Recursive helper to add timestamps to all nested fields private function addTimestampsToNestedData(&$data) { if (!is_array($data)) return; foreach ($data as $key => &$value) { if ($key === 'subscribers') { foreach ($value as &$subscriber) { $this->addTimestampsToNestedData($subscriber); } } else { if (is_array($value)) { // Apply your delay condition check here if (isset($value['delay'], $value['delay_time']) && $value['delay'] === "0" && $value['delay_time'] === 'now') { $value['updated_at'] = now(); $value['created_at'] = $value['created_at'] ?? now(); } // Recurse into deeper nested fields $this->addTimestampsToNestedData($value); } } } } }
Usage:
Now whenever you fetch and save an Automation model, the timestamps are added automatically:
$automations = Automation::all(); foreach ($automations as $automation) { // Any change to $automation->workflow triggers the mutator $automation->save(); }
3. Bulk Update Directly in MongoDB (For Large Datasets)
For collections with thousands of documents, skipping Eloquent entirely and using raw MongoDB queries is way faster. Use MongoDB’s array filters to target nested fields directly:
use Jenssegers\Mongodb\Facades\DB; use MongoDB\BSON\UTCDateTime; // Update non-subscriber fields matching your delay condition DB::collection('automations')->updateMany( [], [ '$set' => [ 'workflow.$[elem].updated_at' => new UTCDateTime(now()), 'workflow.$[elem].created_at' => new UTCDateTime(now()) ] ], [ 'arrayFilters' => [['elem.delay' => "0", 'elem.delay_time' => 'now']] ] ); // Update all subscriber fields DB::collection('automations')->updateMany( [], [ '$set' => [ 'workflow.$[].subscribers.$[sub].updated_at' => new UTCDateTime(now()), 'workflow.$[].subscribers.$[sub].created_at' => new UTCDateTime(now()) ] ], [ 'arrayFilters' => [['sub' => ['$type' => 'object']]] ] );
This approach runs directly on MongoDB, avoiding Eloquent overhead and processing all documents in bulk.
4. Key Optimization Tips
- Batch Processing: If even bulk updates are too heavy, process documents in chunks to avoid memory exhaustion:
Automation::chunk(100, function ($batch) { foreach ($batch as $automation) { $automation->save(); } }); - Lazy Loading: Fetch only the fields you need with
Automation::select('id', 'workflow')->get()to reduce memory usage. - Avoid Overwriting Original Data: Always work with copies or references instead of modifying the original Eloquent collection directly.
内容的提问来源于stack exchange,提问作者version_control

