/** * @param string $identity * @param Document $document * @return void */ public function upsert(string $identity, Document $document) { $converted = $this->converter->objectToArray($document); if ($this->collection->count([$this->identityField => $identity]) > 0) { $this->collection->replaceOne([$this->identityField => $identity], $converted); } else { $this->collection->insertOne($converted); } }
/** * {@inheritdoc} */ public function storeData($key, $data, $expiration) { if ($this->collection instanceof \MongoDB\Collection) { $id = self::mapKey($key); try { $this->collection->replaceOne(['_id' => $id], ['_id' => $id, 'data' => serialize($data), 'expiration' => $expiration], ['upsert' => true]); } catch (\MongoDB\Driver\Exception\BulkWriteException $ignored) { // As of right now, BulkWriteException can be thrown by replaceOne in high-throughput environments where race conditions can occur } } else { try { $this->collection->save(['_id' => self::mapKey($key), 'data' => serialize($data), 'expiration' => $expiration]); } catch (\MongoDuplicateKeyException $ignored) { // Because it's Mongo, we might have had this problem because of a cache stampede } } return true; }
/** * {@inheritdoc} */ public function set($key, $value) { KeyUtil::validate($key); $serialized = $this->serialize->__invoke($value); try { $this->collection->replaceOne(array('_id' => $key), array('_id' => $key, 'value' => $serialized), array('upsert' => true)); } catch (UnexpectedValueException $e) { throw UnsupportedValueException::forType('binary', $this, 0, $e); } catch (Exception $e) { throw WriteException::forException($e); } }
/** * Saves an object to this collection * * @link http://www.php.net/manual/en/mongocollection.save.php * @param array|object $a Array to save. If an object is used, it may not have protected or private properties. * @param array $options Options for the save. * @throws MongoException if the inserted document is empty or if it contains zero-length keys. Attempting to insert an object with protected and private properties will cause a zero-length key error. * @throws MongoCursorException if the "w" option is set and the write fails. * @throws MongoCursorTimeoutException if the "w" option is set to a value greater than one and the operation takes longer than MongoCursor::$timeout milliseconds to complete. This does not kill the operation on the server, it is a client-side timeout. The operation in MongoCollection::$wtimeout is milliseconds. * @return array|boolean If w was set, returns an array containing the status of the save. * Otherwise, returns a boolean representing if the array was not empty (an empty array will not be inserted). */ public function save(&$a, array $options = []) { $id = $this->ensureDocumentHasMongoId($a); $document = (array) $a; $options['upsert'] = true; try { /** @var \MongoDB\UpdateResult $result */ $result = $this->collection->replaceOne(TypeConverter::fromLegacy(['_id' => $id]), TypeConverter::fromLegacy($document), $this->convertWriteConcernOptions($options)); } catch (\MongoDB\Driver\Exception\Exception $e) { ExceptionConverter::toLegacy($e); } if (!$result->isAcknowledged()) { return true; } return ['ok' => 1.0, 'nModified' => $result->getModifiedCount(), 'n' => $result->getMatchedCount(), 'err' => null, 'errmsg' => null, 'updatedExisting' => $result->getUpsertedCount() == 0]; }
/** * Add to process registry. Adds based on $maxGlobalProcesses and $maxHostProcesses after a process registry cleaning. * * @param \MongoDB\Collection $collection the collection * @param string $id a unique id * @param int $minsBeforeExpire number of minutes before a process is considered expired. * @param int $maxGlobalProcesses max processes of an id allowed to run across all hosts. * @param int $maxHostProcesses max processes of an id allowed to run across a single host. * * @return boolean true if the process was added, false if not or there is too much concurrency at the moment. * * @throws \InvalidArgumentException if $id was not a string * @throws \InvalidArgumentException if $minsBeforeExpire was not an int * @throws \InvalidArgumentException if $maxGlobalProcesses was not an int * @throws \InvalidArgumentException if $maxHostProcesses was not an int */ public static function add(\MongoDB\Collection $collection, $id, $minsBeforeExpire = PHP_INT_MAX, $maxGlobalProcesses = 1, $maxHostProcesses = 1) { if (!is_string($id)) { throw new \InvalidArgumentException('$id was not a string'); } if (!is_int($minsBeforeExpire)) { throw new \InvalidArgumentException('$minsBeforeExpire was not an int'); } if (!is_int($maxGlobalProcesses)) { throw new \InvalidArgumentException('$maxGlobalProcesses was not an int'); } if (!is_int($maxHostProcesses)) { throw new \InvalidArgumentException('$maxHostProcesses was not an int'); } $thisHostName = self::_getEncodedHostname(); $thisPid = getmypid(); //loop in case the update fails its optimistic concurrency check for ($i = 0; $i < 5; ++$i) { $collection->findOneAndUpdate(['_id' => $id], ['$setOnInsert' => ['hosts' => [], 'version' => new \MongoDB\BSON\ObjectID()]], ['upsert' => true]); $existing = $collection->findOne(['_id' => $id], ['typeMap' => ['root' => 'array', 'document' => 'array', 'array' => 'array']]); $replacement = $existing; $replacement['version'] = new \MongoDB\BSON\ObjectID(); //clean $replacement based on their pids and expire times foreach ($existing['hosts'] as $hostname => $pids) { foreach ($pids as $pid => $expires) { //our machine and not running //the task expired //our machine and pid is recycled (should rarely happen) if ($hostname === $thisHostName && !file_exists("/proc/{$pid}") || time() >= $expires->toDateTime()->getTimestamp() || $hostname === $thisHostName && $pid === $thisPid) { unset($replacement['hosts'][$hostname][$pid]); } } if (empty($replacement['hosts'][$hostname])) { unset($replacement['hosts'][$hostname]); } } $totalPidCount = 0; foreach ($replacement['hosts'] as $hostname => $pids) { $totalPidCount += count($pids); } $thisHostPids = array_key_exists($thisHostName, $replacement['hosts']) ? $replacement['hosts'][$thisHostName] : []; if ($totalPidCount >= $maxGlobalProcesses || count($thisHostPids) >= $maxHostProcesses) { return false; } // add our process $expireSecs = time() + $minsBeforeExpire * 60; if (!is_int($expireSecs)) { if ($minsBeforeExpire > 0) { $expireSecs = self::MONGO_INT32_MAX; } else { $expireSecs = 0; } } $thisHostPids[$thisPid] = new \MongoDB\BSON\UTCDateTime($expireSecs * 1000); $replacement['hosts'][$thisHostName] = $thisHostPids; $status = $collection->replaceOne(['_id' => $existing['_id'], 'version' => $existing['version']], $replacement, ['writeConcern' => new \MongoDB\Driver\WriteConcern(1, 100, true)]); if ($status->getMatchedCount() === 1) { return true; } //@codeCoverageIgnoreStart //hard to test the optimistic concurrency check } //too much concurrency at the moment, return false to signify not added. return false; //@codeCoverageIgnoreEnd }
/** * {@inheritdoc} */ public function replaceOne($filter, $replacement, array $options = []) { $event = $this->startQueryLogging(__FUNCTION__, $filter, $replacement, $options); $result = parent::replaceOne($filter, $replacement, $options); $this->logger->logQuery($event); return $result; }
/** * @see DataModelInterface::update */ public function update($book) { $this->verifyBook($book); $result = $this->db->replaceOne(array('_id' => new ObjectId($book['id'])), $this->arrayToBook($book)); return $result->getModifiedCount(); }