Example #1
0
 /**
  * @param string $sagaType
  * @param string $identity
  * @param array $associationValues
  * @param string $data
  * @return void
  */
 public function update(string $sagaType, string $identity, array $associationValues, string $data)
 {
     if ($this->collection->count(['identity' => $identity]) == 0) {
         $this->insert($sagaType, $identity, $associationValues, $data);
     } else {
         $this->collection->findOneAndUpdate(['identity' => $identity, 'type' => $sagaType, 'associations' => $associationValues], ['$set' => ['serialized' => $data]]);
     }
 }
 /**
  * Update a document and return it
  *
  * @link http://www.php.net/manual/ru/mongocollection.findandmodify.php
  * @param array $query The query criteria to search for.
  * @param array $update The update criteria.
  * @param array $fields Optionally only return these fields.
  * @param array $options An array of options to apply, such as remove the match document from the DB and return it.
  * @return array Returns the original document, or the modified document when new is set.
  */
 public function findAndModify(array $query, array $update = null, array $fields = null, array $options = [])
 {
     $query = TypeConverter::fromLegacy($query);
     try {
         if (isset($options['remove'])) {
             unset($options['remove']);
             $document = $this->collection->findOneAndDelete($query, $options);
         } else {
             $update = is_array($update) ? TypeConverter::fromLegacy($update) : [];
             if (isset($options['new'])) {
                 $options['returnDocument'] = \MongoDB\Operation\FindOneAndUpdate::RETURN_DOCUMENT_AFTER;
                 unset($options['new']);
             }
             $options['projection'] = is_array($fields) ? TypeConverter::fromLegacy($fields) : [];
             $document = $this->collection->findOneAndUpdate($query, $update, $options);
         }
     } catch (\MongoDB\Driver\Exception\ConnectionException $e) {
         throw new MongoResultException($e->getMessage(), $e->getCode(), $e);
     } catch (\MongoDB\Driver\Exception\Exception $e) {
         ExceptionConverter::toLegacy($e, 'MongoResultException');
     }
     if ($document) {
         $document = TypeConverter::toLegacy($document);
     }
     return $document;
 }
Example #3
0
 /**
  * Updates a document and returns it.
  * @param array $condition query condition
  * @param array $update update criteria
  * @param array $fields fields to be returned
  * @param array $options list of options in format: optionName => optionValue.
  * @return array|null the original document, or the modified document when $options['new'] is set.
  * @throws Exception on failure.
  * @see http://www.php.net/manual/en/mongocollection.findandmodify.php
  */
 public function findAndModify($condition, $update, $options = [])
 {
     $token = "mongoyii\\" . $this->collection->getCollectionName() . ".findAndModify({\$condition: " . json_encode($condition) . ", \$update: " . json_encode($update) . ", \$options: " . json_encode($options) . " })";
     Yii::trace($token, "mongoyii\\Collection");
     if ($this->client->enableProfiling) {
         Yii::beginProfile($token, 'mongoyii\\Collection.findAndModify');
     }
     try {
         if (isset($options['remove']) && $options['remove']) {
             $result = $this->collection->findOneAndDelete($condition, $options);
         } elseif (isset($options['update']) && $options['update']) {
             if (isset($options['upsert']) && $options['upsert']) {
                 $result = $this->collection->findOneAndReplace($condition, $update, $options);
             } else {
                 $result = $this->collection->findOneAndUpdate($condition, $update, $options);
             }
         } else {
             throw new Exception('Must enter a operation type for findAndModify');
         }
         if ($this->client->enableProfiling) {
             Yii::endProfile($token, 'mongoyii\\Collection.findAndModify');
         }
         return $result;
     } catch (\Exception $e) {
         if ($this->client->enableProfiling) {
             Yii::endProfile($token, 'mongoyii\\Collection.findAndModify');
         }
         throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
     }
 }
 /**
  * Get a non running message from the queue.
  *
  * @param array $query in same format as \MongoDB\Collection::find() where top level fields do not contain operators.
  *                     Lower level fields can however. eg: valid {a: {$gt: 1}, "b.c": 3},
  *                     invalid {$and: [{...}, {...}]}
  * @param int $runningResetDuration second duration the message can stay unacked before it resets and can be
  *                                  retreived again.
  * @param int $waitDurationInMillis millisecond duration to wait for a message.
  * @param int $pollDurationInMillis millisecond duration to wait between polls.
  *
  * @return array|null the message or null if one is not found
  *
  * @throws \InvalidArgumentException $runningResetDuration, $waitDurationInMillis or $pollDurationInMillis was not
  *                                   an int
  * @throws \InvalidArgumentException key in $query was not a string
  */
 public function get(array $query, $runningResetDuration, $waitDurationInMillis = 3000, $pollDurationInMillis = 200)
 {
     if (!is_int($runningResetDuration)) {
         throw new \InvalidArgumentException('$runningResetDuration was not an int');
     }
     if (!is_int($waitDurationInMillis)) {
         throw new \InvalidArgumentException('$waitDurationInMillis was not an int');
     }
     if (!is_int($pollDurationInMillis)) {
         throw new \InvalidArgumentException('$pollDurationInMillis was not an int');
     }
     if ($pollDurationInMillis < 0) {
         $pollDurationInMillis = 0;
     }
     //reset stuck messages
     $this->collection->updateMany(['running' => true, 'resetTimestamp' => ['$lte' => new \MongoDB\BSON\UTCDateTime((int) (microtime(true) * 1000))]], ['$set' => ['running' => false]]);
     $completeQuery = ['running' => false];
     foreach ($query as $key => $value) {
         if (!is_string($key)) {
             throw new \InvalidArgumentException('key in $query was not a string');
         }
         $completeQuery["payload.{$key}"] = $value;
     }
     $completeQuery['earliestGet'] = ['$lte' => new \MongoDB\BSON\UTCDateTime((int) (microtime(true) * 1000))];
     $resetTimestamp = time() + $runningResetDuration;
     //ints overflow to floats
     if (!is_int($resetTimestamp)) {
         $resetTimestamp = $runningResetDuration > 0 ? self::MONGO_INT32_MAX : 0;
     }
     $resetTimestamp = min(max(0, $resetTimestamp * 1000), self::MONGO_INT32_MAX);
     $update = ['$set' => ['resetTimestamp' => new \MongoDB\BSON\UTCDateTime($resetTimestamp), 'running' => true]];
     $options = ['sort' => ['priority' => 1, 'created' => 1]];
     //ints overflow to floats, should be fine
     $end = microtime(true) + $waitDurationInMillis / 1000.0;
     $sleepTime = $pollDurationInMillis * 1000;
     //ints overflow to floats and already checked $pollDurationInMillis was positive
     if (!is_int($sleepTime)) {
         //ignore since testing a giant sleep takes too long
         //@codeCoverageIgnoreStart
         $sleepTime = PHP_INT_MAX;
     }
     //@codeCoverageIgnoreEnd
     while (true) {
         $message = $this->collection->findOneAndUpdate($completeQuery, $update, $options);
         //checking if _id exist because findAndModify doesnt seem to return null when it can't match the query on
         //older mongo extension
         if ($message !== null && array_key_exists('_id', $message)) {
             // findOneAndUpdate does not correctly return result according to typeMap options so just refetch.
             $message = $this->collection->findOne(['_id' => $message->_id]);
             //id on left of union operator so a possible id in payload doesnt wipe it out the generated one
             return ['id' => $message['_id']] + (array) $message['payload'];
         }
         if (microtime(true) >= $end) {
             return null;
         }
         usleep($sleepTime);
     }
     //ignore since always return from the function from the while loop
     //@codeCoverageIgnoreStart
 }
Example #5
0
 /**
  * {@inheritdoc}
  */
 public function findOneAndUpdate($filter, $update, array $options = [])
 {
     $event = $this->startQueryLogging(__FUNCTION__, $filter, $update, $options);
     $result = parent::findOneAndUpdate($filter, $update, $options);
     $this->logger->logQuery($event);
     return $result;
 }
 /**
  * Update a document and return it
  *
  * @link http://www.php.net/manual/ru/mongocollection.findandmodify.php
  * @param array $query The query criteria to search for.
  * @param array $update The update criteria.
  * @param array $fields Optionally only return these fields.
  * @param array $options An array of options to apply, such as remove the match document from the DB and return it.
  * @return array Returns the original document, or the modified document when new is set.
  */
 public function findAndModify(array $query, array $update = null, array $fields = null, array $options = [])
 {
     $query = TypeConverter::fromLegacy($query);
     if (isset($options['remove'])) {
         unset($options['remove']);
         $document = $this->collection->findOneAndDelete($query, $options);
     } else {
         $update = is_array($update) ? TypeConverter::fromLegacy($update) : [];
         if (isset($options['new'])) {
             $options['returnDocument'] = \MongoDB\Operation\FindOneAndUpdate::RETURN_DOCUMENT_AFTER;
             unset($options['new']);
         }
         $options['projection'] = is_array($fields) ? TypeConverter::fromLegacy($fields) : [];
         $document = $this->collection->findOneAndUpdate($query, $update, $options);
     }
     if ($document) {
         $document = TypeConverter::toLegacy($document);
     }
     return $document;
 }
 /**
  * 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
 }