MongoDBCache.php 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. <?php
  2. /*
  3. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  4. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  5. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  6. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  7. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  8. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  9. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  10. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  11. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  12. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  13. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  14. *
  15. * This software consists of voluntary contributions made by many individuals
  16. * and is licensed under the MIT license. For more information, see
  17. * <http://www.doctrine-project.org>.
  18. */
  19. namespace Doctrine\Common\Cache;
  20. use MongoBinData;
  21. use MongoCollection;
  22. use MongoCursorException;
  23. use MongoDate;
  24. /**
  25. * MongoDB cache provider.
  26. *
  27. * @since 1.1
  28. * @author Jeremy Mikola <jmikola@gmail.com>
  29. */
  30. class MongoDBCache extends CacheProvider
  31. {
  32. /**
  33. * The data field will store the serialized PHP value.
  34. */
  35. const DATA_FIELD = 'd';
  36. /**
  37. * The expiration field will store a MongoDate value indicating when the
  38. * cache entry should expire.
  39. *
  40. * With MongoDB 2.2+, entries can be automatically deleted by MongoDB by
  41. * indexing this field with the "expireAfterSeconds" option equal to zero.
  42. * This will direct MongoDB to regularly query for and delete any entries
  43. * whose date is older than the current time. Entries without a date value
  44. * in this field will be ignored.
  45. *
  46. * The cache provider will also check dates on its own, in case expired
  47. * entries are fetched before MongoDB's TTLMonitor pass can expire them.
  48. *
  49. * @see http://docs.mongodb.org/manual/tutorial/expire-data/
  50. */
  51. const EXPIRATION_FIELD = 'e';
  52. /**
  53. * @var MongoCollection
  54. */
  55. private $collection;
  56. /**
  57. * Constructor.
  58. *
  59. * This provider will default to the write concern and read preference
  60. * options set on the MongoCollection instance (or inherited from MongoDB or
  61. * MongoClient). Using an unacknowledged write concern (< 1) may make the
  62. * return values of delete() and save() unreliable. Reading from secondaries
  63. * may make contain() and fetch() unreliable.
  64. *
  65. * @see http://www.php.net/manual/en/mongo.readpreferences.php
  66. * @see http://www.php.net/manual/en/mongo.writeconcerns.php
  67. * @param MongoCollection $collection
  68. */
  69. public function __construct(MongoCollection $collection)
  70. {
  71. $this->collection = $collection;
  72. }
  73. /**
  74. * {@inheritdoc}
  75. */
  76. protected function doFetch($id)
  77. {
  78. $document = $this->collection->findOne(array('_id' => $id), array(self::DATA_FIELD, self::EXPIRATION_FIELD));
  79. if ($document === null) {
  80. return false;
  81. }
  82. if ($this->isExpired($document)) {
  83. $this->doDelete($id);
  84. return false;
  85. }
  86. return unserialize($document[self::DATA_FIELD]->bin);
  87. }
  88. /**
  89. * {@inheritdoc}
  90. */
  91. protected function doContains($id)
  92. {
  93. $document = $this->collection->findOne(array('_id' => $id), array(self::EXPIRATION_FIELD));
  94. if ($document === null) {
  95. return false;
  96. }
  97. if ($this->isExpired($document)) {
  98. $this->doDelete($id);
  99. return false;
  100. }
  101. return true;
  102. }
  103. /**
  104. * {@inheritdoc}
  105. */
  106. protected function doSave($id, $data, $lifeTime = 0)
  107. {
  108. try {
  109. $result = $this->collection->update(
  110. array('_id' => $id),
  111. array('$set' => array(
  112. self::EXPIRATION_FIELD => ($lifeTime > 0 ? new MongoDate(time() + $lifeTime) : null),
  113. self::DATA_FIELD => new MongoBinData(serialize($data), MongoBinData::BYTE_ARRAY),
  114. )),
  115. array('upsert' => true, 'multiple' => false)
  116. );
  117. } catch (MongoCursorException $e) {
  118. return false;
  119. }
  120. return isset($result['ok']) ? $result['ok'] == 1 : true;
  121. }
  122. /**
  123. * {@inheritdoc}
  124. */
  125. protected function doDelete($id)
  126. {
  127. $result = $this->collection->remove(array('_id' => $id));
  128. return isset($result['ok']) ? $result['ok'] == 1 : true;
  129. }
  130. /**
  131. * {@inheritdoc}
  132. */
  133. protected function doFlush()
  134. {
  135. // Use remove() in lieu of drop() to maintain any collection indexes
  136. $result = $this->collection->remove();
  137. return isset($result['ok']) ? $result['ok'] == 1 : true;
  138. }
  139. /**
  140. * {@inheritdoc}
  141. */
  142. protected function doGetStats()
  143. {
  144. $serverStatus = $this->collection->db->command(array(
  145. 'serverStatus' => 1,
  146. 'locks' => 0,
  147. 'metrics' => 0,
  148. 'recordStats' => 0,
  149. 'repl' => 0,
  150. ));
  151. $collStats = $this->collection->db->command(array('collStats' => 1));
  152. return array(
  153. Cache::STATS_HITS => null,
  154. Cache::STATS_MISSES => null,
  155. Cache::STATS_UPTIME => (isset($serverStatus['uptime']) ? (int) $serverStatus['uptime'] : null),
  156. Cache::STATS_MEMORY_USAGE => (isset($collStats['size']) ? (int) $collStats['size'] : null),
  157. Cache::STATS_MEMORY_AVAILABLE => null,
  158. );
  159. }
  160. /**
  161. * Check if the document is expired.
  162. *
  163. * @param array $document
  164. *
  165. * @return bool
  166. */
  167. private function isExpired(array $document)
  168. {
  169. return isset($document[self::EXPIRATION_FIELD]) &&
  170. $document[self::EXPIRATION_FIELD] instanceof MongoDate &&
  171. $document[self::EXPIRATION_FIELD]->sec < time();
  172. }
  173. }