MongoDBCache.php 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  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 MongoDate;
  23. /**
  24. * MongoDB cache provider.
  25. *
  26. * @since 1.1
  27. * @author Jeremy Mikola <jmikola@gmail.com>
  28. */
  29. class MongoDBCache extends CacheProvider
  30. {
  31. /**
  32. * The data field will store the serialized PHP value.
  33. */
  34. const DATA_FIELD = 'd';
  35. /**
  36. * The expiration field will store a MongoDate value indicating when the
  37. * cache entry should expire.
  38. *
  39. * With MongoDB 2.2+, entries can be automatically deleted by MongoDB by
  40. * indexing this field with the "expireAfterSeconds" option equal to zero.
  41. * This will direct MongoDB to regularly query for and delete any entries
  42. * whose date is older than the current time. Entries without a date value
  43. * in this field will be ignored.
  44. *
  45. * The cache provider will also check dates on its own, in case expired
  46. * entries are fetched before MongoDB's TTLMonitor pass can expire them.
  47. *
  48. * @see http://docs.mongodb.org/manual/tutorial/expire-data/
  49. */
  50. const EXPIRATION_FIELD = 'e';
  51. /**
  52. * @var MongoCollection
  53. */
  54. private $collection;
  55. /**
  56. * Constructor.
  57. *
  58. * This provider will default to the write concern and read preference
  59. * options set on the MongoCollection instance (or inherited from MongoDB or
  60. * MongoClient). Using an unacknowledged write concern (< 1) may make the
  61. * return values of delete() and save() unreliable. Reading from secondaries
  62. * may make contain() and fetch() unreliable.
  63. *
  64. * @see http://www.php.net/manual/en/mongo.readpreferences.php
  65. * @see http://www.php.net/manual/en/mongo.writeconcerns.php
  66. * @param MongoCollection $collection
  67. */
  68. public function __construct(MongoCollection $collection)
  69. {
  70. $this->collection = $collection;
  71. }
  72. /**
  73. * {@inheritdoc}
  74. */
  75. protected function doFetch($id)
  76. {
  77. $document = $this->collection->findOne(array('_id' => $id), array(self::DATA_FIELD, self::EXPIRATION_FIELD));
  78. if ($document === null) {
  79. return false;
  80. }
  81. if ($this->isExpired($document)) {
  82. $this->doDelete($id);
  83. return false;
  84. }
  85. return unserialize($document[self::DATA_FIELD]->bin);
  86. }
  87. /**
  88. * {@inheritdoc}
  89. */
  90. protected function doContains($id)
  91. {
  92. $document = $this->collection->findOne(array('_id' => $id), array(self::EXPIRATION_FIELD));
  93. if ($document === null) {
  94. return false;
  95. }
  96. if ($this->isExpired($document)) {
  97. $this->doDelete($id);
  98. return false;
  99. }
  100. return true;
  101. }
  102. /**
  103. * {@inheritdoc}
  104. */
  105. protected function doSave($id, $data, $lifeTime = 0)
  106. {
  107. $result = $this->collection->update(
  108. array('_id' => $id),
  109. array('$set' => array(
  110. self::EXPIRATION_FIELD => ($lifeTime > 0 ? new MongoDate(time() + $lifeTime) : null),
  111. self::DATA_FIELD => new MongoBinData(serialize($data), MongoBinData::BYTE_ARRAY),
  112. )),
  113. array('upsert' => true, 'multiple' => false)
  114. );
  115. return isset($result['ok']) ? $result['ok'] == 1 : true;
  116. }
  117. /**
  118. * {@inheritdoc}
  119. */
  120. protected function doDelete($id)
  121. {
  122. $result = $this->collection->remove(array('_id' => $id));
  123. return isset($result['ok']) ? $result['ok'] == 1 : true;
  124. }
  125. /**
  126. * {@inheritdoc}
  127. */
  128. protected function doFlush()
  129. {
  130. // Use remove() in lieu of drop() to maintain any collection indexes
  131. $result = $this->collection->remove();
  132. return isset($result['ok']) ? $result['ok'] == 1 : true;
  133. }
  134. /**
  135. * {@inheritdoc}
  136. */
  137. protected function doGetStats()
  138. {
  139. $serverStatus = $this->collection->db->command(array(
  140. 'serverStatus' => 1,
  141. 'locks' => 0,
  142. 'metrics' => 0,
  143. 'recordStats' => 0,
  144. 'repl' => 0,
  145. ));
  146. $collStats = $this->collection->db->command(array('collStats' => 1));
  147. return array(
  148. Cache::STATS_HITS => null,
  149. Cache::STATS_MISSES => null,
  150. Cache::STATS_UPTIME => (isset($serverStatus['uptime']) ? (int) $serverStatus['uptime'] : null),
  151. Cache::STATS_MEMORY_USAGE => (isset($collStats['size']) ? (int) $collStats['size'] : null),
  152. Cache::STATS_MEMORY_AVAILABLE => null,
  153. );
  154. }
  155. /**
  156. * Check if the document is expired.
  157. *
  158. * @param array $document
  159. * @return boolean
  160. */
  161. private function isExpired(array $document)
  162. {
  163. return isset($document[self::EXPIRATION_FIELD]) &&
  164. $document[self::EXPIRATION_FIELD] instanceof MongoDate &&
  165. $document[self::EXPIRATION_FIELD]->sec < time();
  166. }
  167. }