Collection.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\mongodb\file;
  8. use MongoDB\BSON\ObjectID;
  9. use yii\mongodb\Exception;
  10. use Yii;
  11. use yii\web\UploadedFile;
  12. /**
  13. * Collection represents the Mongo GridFS collection information.
  14. *
  15. * A file collection object is usually created by calling [[Database::getFileCollection()]] or [[Connection::getFileCollection()]].
  16. *
  17. * File collection inherits all interface from regular [[\yii\mongo\Collection]], adding methods to store files.
  18. *
  19. * @property \yii\mongodb\Collection $chunkCollection Mongo collection instance. This property is read-only.
  20. * @property \yii\mongodb\Collection $fileCollection Mongo collection instance. This property is read-only.
  21. * @property string $prefix Prefix of this file collection.
  22. *
  23. * @author Paul Klimov <klimov.paul@gmail.com>
  24. * @since 2.0
  25. */
  26. class Collection extends \yii\mongodb\Collection
  27. {
  28. /**
  29. * @var \yii\mongodb\Database MongoDB database instance.
  30. */
  31. public $database;
  32. /**
  33. * @var string prefix of this file collection.
  34. */
  35. private $_prefix;
  36. /**
  37. * @var \yii\mongodb\Collection file chunks MongoDB collection.
  38. */
  39. private $_chunkCollection;
  40. /**
  41. * @var \yii\mongodb\Collection files MongoDB collection.
  42. */
  43. private $_fileCollection;
  44. /**
  45. * @var bool whether file related fields indexes are ensured for this collection.
  46. */
  47. private $indexesEnsured = false;
  48. /**
  49. * @return string prefix of this file collection.
  50. */
  51. public function getPrefix()
  52. {
  53. return $this->_prefix;
  54. }
  55. /**
  56. * @param string $prefix prefix of this file collection.
  57. */
  58. public function setPrefix($prefix)
  59. {
  60. $this->_prefix = $prefix;
  61. $this->name = $prefix . '.files';
  62. }
  63. /**
  64. * Creates upload command.
  65. * @param array $options upload options.
  66. * @return Upload file upload instance.
  67. * @since 2.1
  68. */
  69. public function createUpload($options = [])
  70. {
  71. $config = $options;
  72. $config['collection'] = $this;
  73. return new Upload($config);
  74. }
  75. /**
  76. * Creates download command.
  77. * @param array|ObjectID $document file document ot be downloaded.
  78. * @return Download file download instance.
  79. * @since 2.1
  80. */
  81. public function createDownload($document)
  82. {
  83. return new Download([
  84. 'collection' => $this,
  85. 'document' => $document,
  86. ]);
  87. }
  88. /**
  89. * Returns the MongoDB collection for the file chunks.
  90. * @param bool $refresh whether to reload the collection instance even if it is found in the cache.
  91. * @return \yii\mongodb\Collection mongo collection instance.
  92. */
  93. public function getChunkCollection($refresh = false)
  94. {
  95. if ($refresh || !is_object($this->_chunkCollection)) {
  96. $this->_chunkCollection = Yii::createObject([
  97. 'class' => 'yii\mongodb\Collection',
  98. 'database' => $this->database,
  99. 'name' => $this->getPrefix() . '.chunks'
  100. ]);
  101. }
  102. return $this->_chunkCollection;
  103. }
  104. /**
  105. * Returns the MongoDB collection for the files.
  106. * @param bool $refresh whether to reload the collection instance even if it is found in the cache.
  107. * @return \yii\mongodb\Collection mongo collection instance.
  108. * @since 2.1
  109. */
  110. public function getFileCollection($refresh = false)
  111. {
  112. if ($refresh || !is_object($this->_fileCollection)) {
  113. $this->_fileCollection = Yii::createObject([
  114. 'class' => 'yii\mongodb\Collection',
  115. 'database' => $this->database,
  116. 'name' => $this->name
  117. ]);
  118. }
  119. return $this->_fileCollection;
  120. }
  121. /**
  122. * {@inheritdoc}
  123. */
  124. public function drop()
  125. {
  126. return parent::drop() && $this->database->dropCollection($this->getChunkCollection()->name);
  127. }
  128. /**
  129. * {@inheritdoc}
  130. * @return Cursor cursor for the search results
  131. */
  132. public function find($condition = [], $fields = [], $options = [])
  133. {
  134. return new Cursor($this, parent::find($condition, $fields, $options));
  135. }
  136. /**
  137. * {@inheritdoc}
  138. */
  139. public function remove($condition = [], $options = [])
  140. {
  141. $fileCollection = $this->getFileCollection();
  142. $chunkCollection = $this->getChunkCollection();
  143. if (empty($condition) && empty($options['limit'])) {
  144. // truncate :
  145. $deleteCount = $fileCollection->remove([], $options);
  146. $chunkCollection->remove([], $options);
  147. return $deleteCount;
  148. }
  149. $batchSize = 200;
  150. $options['batchSize'] = $batchSize;
  151. $cursor = $fileCollection->find($condition, ['_id'], $options);
  152. unset($options['limit']);
  153. $deleteCount = 0;
  154. $deleteCallback = function ($ids) use ($fileCollection, $chunkCollection, $options) {
  155. $chunkCollection->remove(['files_id' => ['$in' => $ids]], $options);
  156. return $fileCollection->remove(['_id' => ['$in' => $ids]], $options);
  157. };
  158. $ids = [];
  159. $idsCount = 0;
  160. foreach ($cursor as $row) {
  161. $ids[] = $row['_id'];
  162. $idsCount++;
  163. if ($idsCount >= $batchSize) {
  164. $deleteCount += $deleteCallback($ids);
  165. $ids = [];
  166. $idsCount = 0;
  167. }
  168. }
  169. if (!empty($ids)) {
  170. $deleteCount += $deleteCallback($ids);
  171. }
  172. return $deleteCount;
  173. }
  174. /**
  175. * Creates new file in GridFS collection from given local filesystem file.
  176. * Additional attributes can be added file document using $metadata.
  177. * @param string $filename name of the file to store.
  178. * @param array $metadata other metadata fields to include in the file document.
  179. * @param array $options list of options in format: optionName => optionValue
  180. * @return mixed the "_id" of the saved file document. This will be a generated [[\MongoId]]
  181. * unless an "_id" was explicitly specified in the metadata.
  182. * @throws Exception on failure.
  183. */
  184. public function insertFile($filename, $metadata = [], $options = [])
  185. {
  186. $options['document'] = $metadata;
  187. $document = $this->createUpload($options)->addFile($filename)->complete();
  188. return $document['_id'];
  189. }
  190. /**
  191. * Creates new file in GridFS collection with specified content.
  192. * Additional attributes can be added file document using $metadata.
  193. * @param string $bytes string of bytes to store.
  194. * @param array $metadata other metadata fields to include in the file document.
  195. * @param array $options list of options in format: optionName => optionValue
  196. * @return mixed the "_id" of the saved file document. This will be a generated [[\MongoId]]
  197. * unless an "_id" was explicitly specified in the metadata.
  198. * @throws Exception on failure.
  199. */
  200. public function insertFileContent($bytes, $metadata = [], $options = [])
  201. {
  202. $options['document'] = $metadata;
  203. $document = $this->createUpload($options)->addContent($bytes)->complete();
  204. return $document['_id'];
  205. }
  206. /**
  207. * Creates new file in GridFS collection from uploaded file.
  208. * Additional attributes can be added file document using $metadata.
  209. * @param string $name name of the uploaded file to store. This should correspond to
  210. * the file field's name attribute in the HTML form.
  211. * @param array $metadata other metadata fields to include in the file document.
  212. * @param array $options list of options in format: optionName => optionValue
  213. * @return mixed the "_id" of the saved file document. This will be a generated [[\MongoId]]
  214. * unless an "_id" was explicitly specified in the metadata.
  215. * @throws Exception on failure.
  216. */
  217. public function insertUploads($name, $metadata = [], $options = [])
  218. {
  219. $uploadedFile = UploadedFile::getInstanceByName($name);
  220. if ($uploadedFile === null) {
  221. throw new Exception("Uploaded file '{$name}' does not exist.");
  222. }
  223. $options['filename'] = $uploadedFile->name;
  224. $options['document'] = $metadata;
  225. $document = $this->createUpload($options)->addFile($uploadedFile->tempName)->complete();
  226. return $document['_id'];
  227. }
  228. /**
  229. * Retrieves the file with given _id.
  230. * @param mixed $id _id of the file to find.
  231. * @return Download|null found file, or null if file does not exist
  232. * @throws Exception on failure.
  233. */
  234. public function get($id)
  235. {
  236. $document = $this->getFileCollection()->findOne(['_id' => $id]);
  237. return empty($document) ? null : $this->createDownload($document);
  238. }
  239. /**
  240. * Deletes the file with given _id.
  241. * @param mixed $id _id of the file to find.
  242. * @return bool whether the operation was successful.
  243. * @throws Exception on failure.
  244. */
  245. public function delete($id)
  246. {
  247. $this->remove(['_id' => $id], ['limit' => 1]);
  248. return true;
  249. }
  250. /**
  251. * Makes sure that indexes, which are crucial for the file processing,
  252. * exist at this collection and [[chunkCollection]].
  253. * The check result is cached per collection instance.
  254. * @param bool $force whether to ignore internal collection instance cache.
  255. * @return $this self reference.
  256. */
  257. public function ensureIndexes($force = false)
  258. {
  259. if (!$force && $this->indexesEnsured) {
  260. return $this;
  261. }
  262. $this->ensureFileIndexes();
  263. $this->ensureChunkIndexes();
  264. $this->indexesEnsured = true;
  265. return $this;
  266. }
  267. /**
  268. * Ensures indexes at file collection.
  269. */
  270. private function ensureFileIndexes()
  271. {
  272. $indexKey = ['filename' => 1, 'uploadDate' => 1];
  273. foreach ($this->listIndexes() as $index) {
  274. if ($index['key'] === $indexKey) {
  275. return;
  276. }
  277. }
  278. $this->createIndex($indexKey);
  279. }
  280. /**
  281. * Ensures indexes at chunk collection.
  282. */
  283. private function ensureChunkIndexes()
  284. {
  285. $chunkCollection = $this->getChunkCollection();
  286. $indexKey = ['files_id' => 1, 'n' => 1];
  287. foreach ($chunkCollection->listIndexes() as $index) {
  288. if (!empty($index['unique']) && $index['key'] === $indexKey) {
  289. return;
  290. }
  291. }
  292. $chunkCollection->createIndex($indexKey, ['unique' => true]);
  293. }
  294. }