From ab8986dd1769ade440d51e882e075269c397d8d2 Mon Sep 17 00:00:00 2001 From: Maksim Kotlyar Date: Tue, 24 Oct 2017 18:21:10 +0300 Subject: [PATCH 1/2] add support of mongodb push operator. --- src/Converter.php | 37 ++++++++++++++++++++++++++++++++++--- src/Storage.php | 12 ++++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/Converter.php b/src/Converter.php index 40ad7ec..719adfa 100644 --- a/src/Converter.php +++ b/src/Converter.php @@ -18,9 +18,15 @@ public static function convertJsonPatchToMongoUpdate(array $diff) switch ($op['op']) { case 'add': - if (is_array($op['value'])) { + if (static::isPathArray($op['path'])) { + if (false == isset($update['$push'][self::pathToDotWithoutLastPart($op['path'])]['$each'])) { + $update['$push'][self::pathToDotWithoutLastPart($op['path'])]['$each'] = []; + } + + $update['$push'][self::pathToDotWithoutLastPart($op['path'])]['$each'][] = $op['value']; + } else if (is_array($op['value'])) { foreach ($op['value'] as $key => $value) { - $update['$set'][self::pathToDot($op['path']).'.'.$key] = $value; + $update['$set'][self::pathToDot($op['path']) . '.' . $key] = $value; } } else { $update['$set'][self::pathToDot($op['path'])] = $op['value']; @@ -42,6 +48,10 @@ public static function convertJsonPatchToMongoUpdate(array $diff) } + if (empty($update['$push'])) { + unset($update['$push']); + } + if (empty($update['$set'])) { unset($update['$set']); } @@ -57,10 +67,31 @@ public static function convertJsonPatchToMongoUpdate(array $diff) * * @return string */ - private static function pathToDot($path) + private static function pathToDot(string $path): string { $path = ltrim($path, '/'); return str_replace('/', '.', $path); } + + /** + * @param string $path + * + * @return string + */ + private static function pathToDotWithoutLastPart(string $path): string + { + $parts = explode('/', ltrim($path)); + + array_pop($parts); + + return static::pathToDot(implode('/', $parts)); + } + + private static function isPathArray(string $path): bool + { + $parts = explode('/', ltrim($path)); + + return is_numeric(array_pop($parts)); + } } \ No newline at end of file diff --git a/src/Storage.php b/src/Storage.php index 75a9986..61bde90 100644 --- a/src/Storage.php +++ b/src/Storage.php @@ -119,8 +119,20 @@ public function update($model, $filter = null, array $options = []) return; } + // mongodb's update cannot do a change of existing element and push a new one to a collection. + $pushUpdate = []; + if (array_key_exists('$push', $update)) { + $pushUpdate['$push'] = $update['$push']; + + unset($update['$push']); + } + $result = $this->collection->updateOne($filter, $update, $options); + if ($pushUpdate) { + $this->collection->updateOne($filter, $pushUpdate, $options); + } + if ($result->getUpsertedCount()) { set_object_id($model, new ObjectID((string) $result->getUpsertedId())); } From fbe58aa060412054e56dbe582a1d22e5b7c599cc Mon Sep 17 00:00:00 2001 From: Maksim Kotlyar Date: Wed, 25 Oct 2017 15:22:11 +0300 Subject: [PATCH 2/2] allow any string id not only mongo's valid object id. --- src/Converter.php | 1 + src/PessimisticLock.php | 21 ++++++++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/Converter.php b/src/Converter.php index 719adfa..918d15d 100644 --- a/src/Converter.php +++ b/src/Converter.php @@ -11,6 +11,7 @@ class Converter public static function convertJsonPatchToMongoUpdate(array $diff) { $update = ['$set' => [], '$unset' => []]; + foreach ($diff as $op) { if (isset($op['path']) && '/_id' == $op['path']) { continue; diff --git a/src/PessimisticLock.php b/src/PessimisticLock.php index 38693f1..9423a68 100644 --- a/src/PessimisticLock.php +++ b/src/PessimisticLock.php @@ -1,7 +1,6 @@ collection = $collection; $this->sessionId = $sessionId ?: getmypid().'-'.(microtime(true)*10000); + $this->limit = $limit; register_shutdown_function(function () { $this->unlockAll(); }); } @@ -38,14 +38,16 @@ public function __construct(Collection $collection, $sessionId = null) * @param string $id * @param int $limit */ - public function lock($id, $limit = 300) + public function lock(string $id, int $limit = 300): void { + $this->createIndexes(); + $timeout = time() + $limit; // I think it must be a bit greater then mongos index ttl so there is a way to process data. while (time() < $timeout) { try { $result = $this->collection->insertOne([ - '_id' => new ObjectID((string) $id), + 'id' => $id, 'timestamp' => new UTCDatetime(time() * 1000), 'sessionId' => $this->sessionId, ]); @@ -73,10 +75,10 @@ public function lock($id, $limit = 300) /** * @param string $id */ - public function unlock($id) + public function unlock(string $id): void { $result = $this->collection->deleteOne([ - '_id' => new ObjectID((string) $id), + 'id' => $id, 'sessionId' => $this->sessionId, ]); @@ -103,6 +105,7 @@ public function createIndexes() } catch (RuntimeException $e) { } + $this->collection->createIndex(['id' => 1], ['unique' => true]); $this->collection->createIndex(['timestamp' => 1], ['expireAfterSeconds' => 302]); $this->collection->createIndex(['sessionId' => 1], ['unique' => false]); }