This commit is contained in:
2020-10-06 14:27:47 +07:00
commit 586be80cf6
16613 changed files with 3274099 additions and 0 deletions

View File

@@ -0,0 +1,183 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\mongodb\file;
use yii\db\ActiveQueryInterface;
use yii\db\ActiveQueryTrait;
use yii\db\ActiveRelationTrait;
/**
* ActiveQuery represents a Mongo query associated with an file Active Record class.
*
* ActiveQuery instances are usually created by [[ActiveRecord::find()]].
*
* Because ActiveQuery extends from [[Query]], one can use query methods, such as [[where()]],
* [[orderBy()]] to customize the query options.
*
* ActiveQuery also provides the following additional query options:
*
* - [[with()]]: list of relations that this query should be performed with.
* - [[asArray()]]: whether to return each record as an array.
*
* These options can be configured using methods of the same name. For example:
*
* ```php
* $images = ImageFile::find()->with('tags')->asArray()->all();
* ```
*
* @property Collection $collection Collection instance. This property is read-only.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class ActiveQuery extends Query implements ActiveQueryInterface
{
use ActiveQueryTrait;
use ActiveRelationTrait;
/**
* @event Event an event that is triggered when the query is initialized via [[init()]].
*/
const EVENT_INIT = 'init';
/**
* Constructor.
* @param array $modelClass the model class associated with this query
* @param array $config configurations to be applied to the newly created query object
*/
public function __construct($modelClass, $config = [])
{
$this->modelClass = $modelClass;
parent::__construct($config);
}
/**
* Initializes the object.
* This method is called at the end of the constructor. The default implementation will trigger
* an [[EVENT_INIT]] event. If you override this method, make sure you call the parent implementation at the end
* to ensure triggering of the event.
*/
public function init()
{
parent::init();
$this->trigger(self::EVENT_INIT);
}
/**
* {@inheritdoc}
*/
public function buildCursor($db = null)
{
if ($this->primaryModel !== null) {
// lazy loading
if ($this->via instanceof self) {
// via pivot collection
$viaModels = $this->via->findJunctionRows([$this->primaryModel]);
$this->filterByModels($viaModels);
} elseif (is_array($this->via)) {
// via relation
/* @var $viaQuery ActiveQuery */
list($viaName, $viaQuery) = $this->via;
if ($viaQuery->multiple) {
$viaModels = $viaQuery->all();
$this->primaryModel->populateRelation($viaName, $viaModels);
} else {
$model = $viaQuery->one();
$this->primaryModel->populateRelation($viaName, $model);
$viaModels = $model === null ? [] : [$model];
}
$this->filterByModels($viaModels);
} else {
$this->filterByModels([$this->primaryModel]);
}
}
return parent::buildCursor($db);
}
/**
* Executes query and returns all results as an array.
* @param \yii\mongodb\Connection $db the Mongo connection used to execute the query.
* If null, the Mongo connection returned by [[modelClass]] will be used.
* @return array|ActiveRecord the query results. If the query results in nothing, an empty array will be returned.
*/
public function all($db = null)
{
return parent::all($db);
}
/**
* Executes query and returns a single row of result.
* @param \yii\mongodb\Connection $db the Mongo connection used to execute the query.
* If null, the Mongo connection returned by [[modelClass]] will be used.
* @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]],
* the query result may be either an array or an ActiveRecord object. Null will be returned
* if the query results in nothing.
*/
public function one($db = null)
{
$row = parent::one($db);
if ($row !== false) {
$models = $this->populate([$row]);
return reset($models) ?: null;
}
return null;
}
/**
* Returns the Mongo collection for this query.
* @param \yii\mongodb\Connection $db Mongo connection.
* @return Collection collection instance.
*/
public function getCollection($db = null)
{
/* @var $modelClass ActiveRecord */
$modelClass = $this->modelClass;
if ($db === null) {
$db = $modelClass::getDb();
}
if ($this->from === null) {
$this->from = $modelClass::collectionName();
}
return $db->getFileCollection($this->from);
}
/**
* Converts the raw query results into the format as specified by this query.
* This method is internally used to convert the data fetched from MongoDB
* into the format as required by this query.
* @param array $rows the raw query result from MongoDB
* @return array the converted query result
*/
public function populate($rows)
{
if (empty($rows)) {
return [];
}
$indexBy = $this->indexBy;
$this->indexBy = null;
$rows = parent::populate($rows);
$this->indexBy = $indexBy;
$models = $this->createModels($rows);
if (!empty($this->with)) {
$this->findWith($this->with, $models);
}
if (!$this->asArray) {
foreach ($models as $model) {
$model->afterFind();
}
}
return parent::populate($models);
}
}

View File

@@ -0,0 +1,335 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\mongodb\file;
use Yii;
use yii\base\InvalidParamException;
use yii\db\StaleObjectException;
use yii\web\UploadedFile;
/**
* ActiveRecord is the base class for classes representing Mongo GridFS files in terms of objects.
*
* To specify source file use the [[file]] attribute. It can be specified in one of the following ways:
* - string - full name of the file, which content should be stored in GridFS
* - \yii\web\UploadedFile - uploaded file instance, which content should be stored in GridFS
*
* For example:
*
* ```php
* $record = new ImageFile();
* $record->file = '/path/to/some/file.jpg';
* $record->save();
* ```
*
* You can also specify file content via [[newFileContent]] attribute:
*
* ```php
* $record = new ImageFile();
* $record->newFileContent = 'New file content';
* $record->save();
* ```
*
* Note: [[newFileContent]] always takes precedence over [[file]].
*
* @property null|string $fileContent File content. This property is read-only.
* @property resource $fileResource File stream resource. This property is read-only.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
abstract class ActiveRecord extends \yii\mongodb\ActiveRecord
{
/**
* {@inheritdoc}
* @return ActiveQuery the newly created [[ActiveQuery]] instance.
*/
public static function find()
{
return Yii::createObject(ActiveQuery::className(), [get_called_class()]);
}
/**
* Return the Mongo GridFS collection instance for this AR class.
* @return Collection collection instance.
*/
public static function getCollection()
{
return static::getDb()->getFileCollection(static::collectionName());
}
/**
* Returns the list of all attribute names of the model.
* This method could be overridden by child classes to define available attributes.
* Note: all attributes defined in base Active Record class should be always present
* in returned array.
* For example:
*
* ```php
* public function attributes()
* {
* return array_merge(
* parent::attributes(),
* ['tags', 'status']
* );
* }
* ```
*
* @return array list of attribute names.
*/
public function attributes()
{
return [
'_id',
'filename',
'uploadDate',
'length',
'chunkSize',
'md5',
'file',
'newFileContent'
];
}
/**
* @see ActiveRecord::insert()
*/
protected function insertInternal($attributes = null)
{
if (!$this->beforeSave(true)) {
return false;
}
$values = $this->getDirtyAttributes($attributes);
if (empty($values)) {
$currentAttributes = $this->getAttributes();
foreach ($this->primaryKey() as $key) {
$values[$key] = isset($currentAttributes[$key]) ? $currentAttributes[$key] : null;
}
}
$collection = static::getCollection();
if (isset($values['newFileContent'])) {
$newFileContent = $values['newFileContent'];
unset($values['newFileContent']);
}
if (isset($values['file'])) {
$newFile = $values['file'];
unset($values['file']);
}
if (isset($newFileContent)) {
$newId = $collection->insertFileContent($newFileContent, $values);
} elseif (isset($newFile)) {
$fileName = $this->extractFileName($newFile);
$newId = $collection->insertFile($fileName, $values);
} else {
$newId = $collection->insert($values);
}
if ($newId !== null) {
$this->setAttribute('_id', $newId);
$values['_id'] = $newId;
}
$changedAttributes = array_fill_keys(array_keys($values), null);
$this->setOldAttributes($values);
$this->afterSave(true, $changedAttributes);
return true;
}
/**
* @see ActiveRecord::update()
* @throws StaleObjectException
*/
protected function updateInternal($attributes = null)
{
if (!$this->beforeSave(false)) {
return false;
}
$values = $this->getDirtyAttributes($attributes);
if (empty($values)) {
$this->afterSave(false, $values);
return 0;
}
$collection = static::getCollection();
if (isset($values['newFileContent'])) {
$newFileContent = $values['newFileContent'];
unset($values['newFileContent']);
}
if (isset($values['file'])) {
$newFile = $values['file'];
unset($values['file']);
}
if (isset($newFileContent) || isset($newFile)) {
$fileAssociatedAttributeNames = [
'filename',
'uploadDate',
'length',
'chunkSize',
'md5',
'file',
'newFileContent'
];
$values = array_merge($this->getAttributes(null, $fileAssociatedAttributeNames), $values);
$rows = $this->deleteInternal();
$insertValues = $values;
$insertValues['_id'] = $this->getAttribute('_id');
if (isset($newFileContent)) {
$collection->insertFileContent($newFileContent, $insertValues);
} else {
$fileName = $this->extractFileName($newFile);
$collection->insertFile($fileName, $insertValues);
}
$this->setAttribute('newFileContent', null);
$this->setAttribute('file', null);
} else {
$condition = $this->getOldPrimaryKey(true);
$lock = $this->optimisticLock();
if ($lock !== null) {
if (!isset($values[$lock])) {
$values[$lock] = $this->$lock + 1;
}
$condition[$lock] = $this->$lock;
}
// We do not check the return value of update() because it's possible
// that it doesn't change anything and thus returns 0.
$rows = $collection->update($condition, $values);
if ($lock !== null && !$rows) {
throw new StaleObjectException('The object being updated is outdated.');
}
}
$changedAttributes = [];
foreach ($values as $name => $value) {
$changedAttributes[$name] = $this->getOldAttribute($name);
$this->setOldAttribute($name, $value);
}
$this->afterSave(false, $changedAttributes);
return $rows;
}
/**
* Extracts filename from given raw file value.
* @param mixed $file raw file value.
* @return string file name.
* @throws \yii\base\InvalidParamException on invalid file value.
*/
protected function extractFileName($file)
{
if ($file instanceof UploadedFile) {
return $file->tempName;
} elseif (is_string($file)) {
if (file_exists($file)) {
return $file;
}
throw new InvalidParamException("File '{$file}' does not exist.");
}
throw new InvalidParamException('Unsupported type of "file" attribute.');
}
/**
* Refreshes the [[file]] attribute from file collection, using current primary key.
* @return \MongoGridFSFile|null refreshed file value.
*/
public function refreshFile()
{
$mongoFile = $this->getCollection()->get($this->getPrimaryKey());
$this->setAttribute('file', $mongoFile);
return $mongoFile;
}
/**
* Returns the associated file content.
* @return null|string file content.
* @throws \yii\base\InvalidParamException on invalid file attribute value.
*/
public function getFileContent()
{
$file = $this->getAttribute('file');
if (empty($file) && !$this->getIsNewRecord()) {
$file = $this->refreshFile();
}
if (empty($file)) {
return null;
} elseif ($file instanceof Download) {
$fileSize = $file->getSize();
return empty($fileSize) ? null : $file->toString();
} elseif ($file instanceof UploadedFile) {
return file_get_contents($file->tempName);
} elseif (is_string($file)) {
if (file_exists($file)) {
return file_get_contents($file);
}
throw new InvalidParamException("File '{$file}' does not exist.");
}
throw new InvalidParamException('Unsupported type of "file" attribute.');
}
/**
* Writes the the internal file content into the given filename.
* @param string $filename full filename to be written.
* @return bool whether the operation was successful.
* @throws \yii\base\InvalidParamException on invalid file attribute value.
*/
public function writeFile($filename)
{
$file = $this->getAttribute('file');
if (empty($file) && !$this->getIsNewRecord()) {
$file = $this->refreshFile();
}
if (empty($file)) {
throw new InvalidParamException('There is no file associated with this object.');
} elseif ($file instanceof Download) {
return ($file->toFile($filename) == $file->getSize());
} elseif ($file instanceof UploadedFile) {
return copy($file->tempName, $filename);
} elseif (is_string($file)) {
if (file_exists($file)) {
return copy($file, $filename);
}
throw new InvalidParamException("File '{$file}' does not exist.");
}
throw new InvalidParamException('Unsupported type of "file" attribute.');
}
/**
* This method returns a stream resource that can be used with all file functions in PHP,
* which deal with reading files. The contents of the file are pulled out of MongoDB on the fly,
* so that the whole file does not have to be loaded into memory first.
* @return resource file stream resource.
* @throws \yii\base\InvalidParamException on invalid file attribute value.
*/
public function getFileResource()
{
$file = $this->getAttribute('file');
if (empty($file) && !$this->getIsNewRecord()) {
$file = $this->refreshFile();
}
if (empty($file)) {
throw new InvalidParamException('There is no file associated with this object.');
} elseif ($file instanceof Download) {
return $file->getResource();
} elseif ($file instanceof UploadedFile) {
return fopen($file->tempName, 'r');
} elseif (is_string($file)) {
if (file_exists($file)) {
return fopen($file, 'r');
}
throw new InvalidParamException("File '{$file}' does not exist.");
}
throw new InvalidParamException('Unsupported type of "file" attribute.');
}
}

View File

@@ -0,0 +1,327 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\mongodb\file;
use MongoDB\BSON\ObjectID;
use yii\mongodb\Exception;
use Yii;
use yii\web\UploadedFile;
/**
* Collection represents the Mongo GridFS collection information.
*
* A file collection object is usually created by calling [[Database::getFileCollection()]] or [[Connection::getFileCollection()]].
*
* File collection inherits all interface from regular [[\yii\mongo\Collection]], adding methods to store files.
*
* @property \yii\mongodb\Collection $chunkCollection Mongo collection instance. This property is read-only.
* @property \yii\mongodb\Collection $fileCollection Mongo collection instance. This property is read-only.
* @property string $prefix Prefix of this file collection.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class Collection extends \yii\mongodb\Collection
{
/**
* @var \yii\mongodb\Database MongoDB database instance.
*/
public $database;
/**
* @var string prefix of this file collection.
*/
private $_prefix;
/**
* @var \yii\mongodb\Collection file chunks MongoDB collection.
*/
private $_chunkCollection;
/**
* @var \yii\mongodb\Collection files MongoDB collection.
*/
private $_fileCollection;
/**
* @var bool whether file related fields indexes are ensured for this collection.
*/
private $indexesEnsured = false;
/**
* @return string prefix of this file collection.
*/
public function getPrefix()
{
return $this->_prefix;
}
/**
* @param string $prefix prefix of this file collection.
*/
public function setPrefix($prefix)
{
$this->_prefix = $prefix;
$this->name = $prefix . '.files';
}
/**
* Creates upload command.
* @param array $options upload options.
* @return Upload file upload instance.
* @since 2.1
*/
public function createUpload($options = [])
{
$config = $options;
$config['collection'] = $this;
return new Upload($config);
}
/**
* Creates download command.
* @param array|ObjectID $document file document ot be downloaded.
* @return Download file download instance.
* @since 2.1
*/
public function createDownload($document)
{
return new Download([
'collection' => $this,
'document' => $document,
]);
}
/**
* Returns the MongoDB collection for the file chunks.
* @param bool $refresh whether to reload the collection instance even if it is found in the cache.
* @return \yii\mongodb\Collection mongo collection instance.
*/
public function getChunkCollection($refresh = false)
{
if ($refresh || !is_object($this->_chunkCollection)) {
$this->_chunkCollection = Yii::createObject([
'class' => 'yii\mongodb\Collection',
'database' => $this->database,
'name' => $this->getPrefix() . '.chunks'
]);
}
return $this->_chunkCollection;
}
/**
* Returns the MongoDB collection for the files.
* @param bool $refresh whether to reload the collection instance even if it is found in the cache.
* @return \yii\mongodb\Collection mongo collection instance.
* @since 2.1
*/
public function getFileCollection($refresh = false)
{
if ($refresh || !is_object($this->_fileCollection)) {
$this->_fileCollection = Yii::createObject([
'class' => 'yii\mongodb\Collection',
'database' => $this->database,
'name' => $this->name
]);
}
return $this->_fileCollection;
}
/**
* {@inheritdoc}
*/
public function drop()
{
return parent::drop() && $this->database->dropCollection($this->getChunkCollection()->name);
}
/**
* {@inheritdoc}
* @return Cursor cursor for the search results
*/
public function find($condition = [], $fields = [], $options = [])
{
return new Cursor($this, parent::find($condition, $fields, $options));
}
/**
* {@inheritdoc}
*/
public function remove($condition = [], $options = [])
{
$fileCollection = $this->getFileCollection();
$chunkCollection = $this->getChunkCollection();
if (empty($condition) && empty($options['limit'])) {
// truncate :
$deleteCount = $fileCollection->remove([], $options);
$chunkCollection->remove([], $options);
return $deleteCount;
}
$batchSize = 200;
$options['batchSize'] = $batchSize;
$cursor = $fileCollection->find($condition, ['_id'], $options);
unset($options['limit']);
$deleteCount = 0;
$deleteCallback = function ($ids) use ($fileCollection, $chunkCollection, $options) {
$chunkCollection->remove(['files_id' => ['$in' => $ids]], $options);
return $fileCollection->remove(['_id' => ['$in' => $ids]], $options);
};
$ids = [];
$idsCount = 0;
foreach ($cursor as $row) {
$ids[] = $row['_id'];
$idsCount++;
if ($idsCount >= $batchSize) {
$deleteCount += $deleteCallback($ids);
$ids = [];
$idsCount = 0;
}
}
if (!empty($ids)) {
$deleteCount += $deleteCallback($ids);
}
return $deleteCount;
}
/**
* Creates new file in GridFS collection from given local filesystem file.
* Additional attributes can be added file document using $metadata.
* @param string $filename name of the file to store.
* @param array $metadata other metadata fields to include in the file document.
* @param array $options list of options in format: optionName => optionValue
* @return mixed the "_id" of the saved file document. This will be a generated [[\MongoId]]
* unless an "_id" was explicitly specified in the metadata.
* @throws Exception on failure.
*/
public function insertFile($filename, $metadata = [], $options = [])
{
$options['document'] = $metadata;
$document = $this->createUpload($options)->addFile($filename)->complete();
return $document['_id'];
}
/**
* Creates new file in GridFS collection with specified content.
* Additional attributes can be added file document using $metadata.
* @param string $bytes string of bytes to store.
* @param array $metadata other metadata fields to include in the file document.
* @param array $options list of options in format: optionName => optionValue
* @return mixed the "_id" of the saved file document. This will be a generated [[\MongoId]]
* unless an "_id" was explicitly specified in the metadata.
* @throws Exception on failure.
*/
public function insertFileContent($bytes, $metadata = [], $options = [])
{
$options['document'] = $metadata;
$document = $this->createUpload($options)->addContent($bytes)->complete();
return $document['_id'];
}
/**
* Creates new file in GridFS collection from uploaded file.
* Additional attributes can be added file document using $metadata.
* @param string $name name of the uploaded file to store. This should correspond to
* the file field's name attribute in the HTML form.
* @param array $metadata other metadata fields to include in the file document.
* @param array $options list of options in format: optionName => optionValue
* @return mixed the "_id" of the saved file document. This will be a generated [[\MongoId]]
* unless an "_id" was explicitly specified in the metadata.
* @throws Exception on failure.
*/
public function insertUploads($name, $metadata = [], $options = [])
{
$uploadedFile = UploadedFile::getInstanceByName($name);
if ($uploadedFile === null) {
throw new Exception("Uploaded file '{$name}' does not exist.");
}
$options['filename'] = $uploadedFile->name;
$options['document'] = $metadata;
$document = $this->createUpload($options)->addFile($uploadedFile->tempName)->complete();
return $document['_id'];
}
/**
* Retrieves the file with given _id.
* @param mixed $id _id of the file to find.
* @return Download|null found file, or null if file does not exist
* @throws Exception on failure.
*/
public function get($id)
{
$document = $this->getFileCollection()->findOne(['_id' => $id]);
return empty($document) ? null : $this->createDownload($document);
}
/**
* Deletes the file with given _id.
* @param mixed $id _id of the file to find.
* @return bool whether the operation was successful.
* @throws Exception on failure.
*/
public function delete($id)
{
$this->remove(['_id' => $id], ['limit' => 1]);
return true;
}
/**
* Makes sure that indexes, which are crucial for the file processing,
* exist at this collection and [[chunkCollection]].
* The check result is cached per collection instance.
* @param bool $force whether to ignore internal collection instance cache.
* @return $this self reference.
*/
public function ensureIndexes($force = false)
{
if (!$force && $this->indexesEnsured) {
return $this;
}
$this->ensureFileIndexes();
$this->ensureChunkIndexes();
$this->indexesEnsured = true;
return $this;
}
/**
* Ensures indexes at file collection.
*/
private function ensureFileIndexes()
{
$indexKey = ['filename' => 1, 'uploadDate' => 1];
foreach ($this->listIndexes() as $index) {
if ($index['key'] === $indexKey) {
return;
}
}
$this->createIndex($indexKey);
}
/**
* Ensures indexes at chunk collection.
*/
private function ensureChunkIndexes()
{
$chunkCollection = $this->getChunkCollection();
$indexKey = ['files_id' => 1, 'n' => 1];
foreach ($chunkCollection->listIndexes() as $index) {
if (!empty($index['unique']) && $index['key'] === $indexKey) {
return;
}
}
$chunkCollection->createIndex($indexKey, ['unique' => true]);
}
}

View File

@@ -0,0 +1,149 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\mongodb\file;
/**
* Cursor is a wrapper around [[\MongoDB\Driver\Cursor]], which allows returning of the
* record with [[Download]] instance attached.
*
* @method \MongoDB\Driver\Cursor getInnerIterator()
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.1
*/
class Cursor extends \IteratorIterator implements \Countable
{
/**
* @var Collection related GridFS collection instance.
*/
public $collection;
/**
* Constructor.
* @param Collection $collection
* @param \MongoDB\Driver\Cursor $cursor
*/
public function __construct($collection, $cursor)
{
$this->collection = $collection;
parent::__construct($cursor);
}
/**
* Return the current element
* This method is required by the interface [[\Iterator]].
* @return mixed current row
*/
public function current()
{
$value = parent::current();
if (!isset($value['file'])) {
$value['file'] = $this->collection->createDownload(array_intersect_key($value, ['_id' => true, 'filename' => true, 'length' => true, 'chunkSize' => true]));
}
return $value;
}
/**
* Count elements of this cursor.
* This method is required by the interface [[\Countable]].
* @return int elements count.
*/
public function count()
{
return count($this->cursor);
}
// Mock up original cursor interface :
/**
* Returns an array containing all results for this cursor
* @return array containing all results for this cursor.
*/
public function toArray()
{
$result = [];
foreach ($this as $key => $value) {
$result[$key] = $value;
}
return $result;
}
/**
* Returns the ID for this cursor.
* @return \MongoDB\Driver\CursorId cursor ID.
*/
public function getId()
{
return $this->getInnerIterator()->getId();
}
/**
* Sets a type map to use for BSON unserialization.
* @param array $typemap type map.
*/
public function setTypeMap($typemap)
{
$this->getInnerIterator()->setTypeMap($typemap);
}
/**
* PHP magic method, which is invoked on attempt of invocation not existing method.
* It redirects method call to inner iterator.
* @param string $name method name.
* @param array $arguments method arguments
* @return mixed method result.
*/
public function __call($name, $arguments)
{
return call_user_func_array([$this->getInnerIterator(), $name], $arguments);
}
/**
* PHP magic method, which is invoked on attempt of setting not existing property.
* It passes value to the inner iterator.
* @param string $name field name.
* @param mixed $value field value.
*/
public function __set($name, $value)
{
$this->getInnerIterator()->{$name} = $value;
}
/**
* PHP magic method, which is invoked on attempt of getting not existing property.
* It returns value from the inner iterator.
* @param string $name field name.
* @return mixed field value.
*/
public function __get($name)
{
return $this->getInnerIterator()->{$name};
}
/**
* PHP magic method, which is invoked on attempt of checking if a property is set.
* @param string $name field name.
* @return bool whether field exists or not.
*/
public function __isset($name)
{
$cursor = $this->getInnerIterator();
return isset($cursor->$name);
}
/**
* PHP magic method, which is invoked on attempt of unsetting of property.
* @param string $name field name.
*/
public function __unset($name)
{
$cursor = $this->getInnerIterator();
unset($cursor->$name);
}
}

View File

@@ -0,0 +1,319 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\mongodb\file;
use MongoDB\BSON\ObjectID;
use Yii;
use yii\base\InvalidConfigException;
use yii\base\BaseObject;
use yii\helpers\FileHelper;
use yii\helpers\StringHelper;
/**
* Download represents the GridFS download operation.
*
* A `Download` object is usually created by calling [[Collection::get()]] or [[Collection::createDownload()]].
*
* Usage example:
*
* ```php
* Yii::$app->mongodb->getFileCollection()->createDownload($document['_id'])->toFile('/path/to/file.dat');
* ```
*
* You can use `Download::substr()` to read a specific part of the file:
*
* ```php
* $filePart = Yii::$app->mongodb->getFileCollection()->createDownload($document['_id'])->substr(256, 1024);
* ```
*
* @property string $bytes File content. This property is read-only.
* @property \MongoDB\Driver\Cursor $chunkCursor Chuck list cursor. This property is read-only.
* @property \Iterator $chunkIterator Chuck cursor iterator. This property is read-only.
* @property array $document Document to be downloaded. Note that the type of this property differs in getter
* and setter. See [[getDocument()]] and [[setDocument()]] for details.
* @property string|null $filename File name. This property is read-only.
* @property resource $resource File stream resource. This property is read-only.
* @property int $size File size. This property is read-only.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.1
*/
class Download extends BaseObject
{
/**
* @var Collection file collection to be used.
*/
public $collection;
/**
* @var array|ObjectID document to be downloaded.
*/
private $_document;
/**
* @var \MongoDB\Driver\Cursor cursor for the file chunks.
*/
private $_chunkCursor;
/**
* @var \Iterator iterator for [[chunkCursor]].
*/
private $_chunkIterator;
/**
* @var resource|null
*/
private $_resource;
/**
* @return array document to be downloaded.
* @throws InvalidConfigException on invalid document configuration.
*/
public function getDocument()
{
if (!is_array($this->_document)) {
if (is_scalar($this->_document) || $this->_document instanceof ObjectID) {
$document = $this->collection->findOne(['_id' => $this->_document]);
if (empty($document)) {
throw new InvalidConfigException('Document id=' . $this->_document . ' does not exist at collection "' . $this->collection->getFullName() . '"');
}
$this->_document = $document;
} else {
$this->_document = (array)$this->_document;
}
}
return $this->_document;
}
/**
* Sets data of the document to be downloaded.
* Document can be specified by its ID, in this case its data will be fetched automatically
* via extra query.
* @param array|ObjectID $document document raw data or document ID.
*/
public function setDocument($document)
{
$this->_document = $document;
}
/**
* Returns the size of the associated file.
* @return int file size.
*/
public function getSize()
{
$document = $this->getDocument();
return isset($document['length']) ? $document['length'] : 0;
}
/**
* Returns associated file's filename.
* @return string|null file name.
*/
public function getFilename()
{
$document = $this->getDocument();
return isset($document['filename']) ? $document['filename'] : null;
}
/**
* Returns file chunks read cursor.
* @param bool $refresh whether to recreate cursor, if it is already exist.
* @return \MongoDB\Driver\Cursor chuck list cursor.
* @throws InvalidConfigException
*/
public function getChunkCursor($refresh = false)
{
if ($refresh || $this->_chunkCursor === null) {
$file = $this->getDocument();
$this->_chunkCursor = $this->collection->getChunkCollection()->find(
['files_id' => $file['_id']],
[],
['sort' => ['n' => 1]]
);
}
return $this->_chunkCursor;
}
/**
* Returns iterator for the file chunks cursor.
* @param bool $refresh whether to recreate iterator, if it is already exist.
* @return \Iterator chuck cursor iterator.
*/
public function getChunkIterator($refresh = false)
{
if ($refresh || $this->_chunkIterator === null) {
$this->_chunkIterator = new \IteratorIterator($this->getChunkCursor($refresh));
$this->_chunkIterator->rewind();
}
return $this->_chunkIterator;
}
/**
* Saves file into the given stream.
* @param resource $stream stream, which file should be saved to.
* @return int number of written bytes.
*/
public function toStream($stream)
{
$bytesWritten = 0;
foreach ($this->getChunkCursor() as $chunk) {
$bytesWritten += fwrite($stream, $chunk['data']->getData());
}
return $bytesWritten;
}
/**
* Saves download to the physical file.
* @param string $filename name of the physical file.
* @return int number of written bytes.
*/
public function toFile($filename)
{
$filename = Yii::getAlias($filename);
FileHelper::createDirectory(dirname($filename));
return $this->toStream(fopen($filename, 'w+'));
}
/**
* Returns a string of the bytes in the associated file.
* @return string file content.
*/
public function toString()
{
$result = '';
foreach ($this->getChunkCursor() as $chunk) {
$result .= $chunk['data']->getData();
}
return $result;
}
/**
* Returns an opened stream resource, which can be used to read file.
* Note: each invocation of this method will create new file resource.
* @return resource stream resource.
*/
public function toResource()
{
$protocol = $this->collection->database->connection->registerFileStreamWrapper();
$context = stream_context_create([
$protocol => [
'download' => $this,
]
]);
$document = $this->getDocument();
$url = "{$protocol}://{$this->collection->database->name}.{$this->collection->prefix}?_id={$document['_id']}";
return fopen($url, 'r', false, $context);
}
/**
* Return part of a file.
* @param int $start reading start position.
* If non-negative, the returned string will start at the start'th position in file, counting from zero.
* If negative, the returned string will start at the start'th character from the end of file.
* @param int $length number of bytes to read.
* If given and is positive, the string returned will contain at most length characters beginning from start (depending on the length of file).
* If given and is negative, then that many characters will be omitted from the end of file (after the start position has been calculated when a start is negative).
* @return string|false the extracted part of file or `false` on failure
*/
public function substr($start, $length)
{
$document = $this->getDocument();
if ($start < 0) {
$start = max($document['length'] + $start, 0);
}
if ($start > $document['length']) {
return false;
}
if ($length < 0) {
$length = $document['length'] - $start + $length;
if ($length < 0) {
return false;
}
}
$chunkSize = $document['chunkSize'];
$startChunkNumber = floor($start / $chunkSize);
$chunkIterator = $this->getChunkIterator();
if (!$chunkIterator->valid()) {
// invalid iterator state - recreate iterator
// unable to use `rewind` due to error "Cursors cannot rewind after starting iteration"
$chunkIterator = $this->getChunkIterator(true);
}
if ($chunkIterator->key() > $startChunkNumber) {
// unable to go back by iterator
// unable to use `rewind` due to error "Cursors cannot rewind after starting iteration"
$chunkIterator = $this->getChunkIterator(true);
}
$result = '';
$chunkDataOffset = $start - $startChunkNumber * $chunkSize;
while ($chunkIterator->valid()) {
if ($chunkIterator->key() >= $startChunkNumber) {
$chunk = $chunkIterator->current();
$data = $chunk['data']->getData();
$readLength = min($chunkSize - $chunkDataOffset, $length);
$result .= StringHelper::byteSubstr($data, $chunkDataOffset, $readLength);
$length -= $readLength;
if ($length <= 0) {
break;
}
$chunkDataOffset = 0;
}
$chunkIterator->next();
}
return $result;
}
// Compatibility with `MongoGridFSFile` :
/**
* Alias of [[toString()]] method.
* @return string file content.
*/
public function getBytes()
{
return $this->toString();
}
/**
* Alias of [[toFile()]] method.
* @param string $filename name of the physical file.
* @return int number of written bytes.
*/
public function write($filename)
{
return $this->toFile($filename);
}
/**
* Returns persistent stream resource, which can be used to read file.
* @return resource file stream resource.
*/
public function getResource()
{
if ($this->_resource === null) {
$this->_resource = $this->toResource();
}
return $this->_resource;
}
}

View File

@@ -0,0 +1,39 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\mongodb\file;
use Yii;
/**
* Query represents Mongo "find" operation for GridFS collection.
*
* Query behaves exactly as regular [[\yii\mongodb\Query]].
* Found files will be represented as arrays of file document attributes with
* additional 'file' key, which stores [[\MongoGridFSFile]] instance.
*
* @property Collection $collection Collection instance. This property is read-only.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class Query extends \yii\mongodb\Query
{
/**
* Returns the Mongo collection for this query.
* @param \yii\mongodb\Connection $db Mongo connection.
* @return Collection collection instance.
*/
public function getCollection($db = null)
{
if ($db === null) {
$db = Yii::$app->get('mongodb');
}
return $db->getFileCollection($this->from);
}
}

View File

@@ -0,0 +1,415 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\mongodb\file;
use yii\base\InvalidConfigException;
use yii\base\BaseObject;
use yii\di\Instance;
use yii\helpers\StringHelper;
use yii\mongodb\Connection;
/**
* StreamWrapper provides stream wrapper for MongoDB GridFS, allowing file operations via
* regular PHP stream resources.
*
* Before feature can be used this wrapper should be registered via [[register()]] method.
* It is usually performed via [[yii\mongodb\Connection::registerFileStreamWrapper()]].
*
* Note: do not use this class directly - its instance will be created and maintained by PHP internally
* once corresponding stream resource is created.
*
* Resource path should be specified in following format:
*
* ```
* 'protocol://databaseName.fileCollectionPrefix?file_attribute=value'
* ```
*
* Write example:
*
* ```php
* $resource = fopen('gridfs://mydatabase.fs?filename=new_file.txt', 'w');
* fwrite($resource, 'some content');
* // ...
* fclose($resource);
* ```
*
* Read example:
*
* ```php
* $resource = fopen('gridfs://mydatabase.fs?filename=my_file.txt', 'r');
* $fileContent = stream_get_contents($resource);
* ```
*
* @see http://php.net/manual/en/function.stream-wrapper-register.php
*
* @property array $contextOptions Context options. This property is read-only.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.1
*/
class StreamWrapper extends BaseObject
{
/**
* @var resource associated stream resource context.
* This property is set automatically by PHP once wrapper is instantiated.
*/
public $context;
/**
* @var array context options associated with [[context]].
*/
private $_contextOptions;
/**
* @var string protocol associated with stream
*/
private $_protocol;
/**
* @var string namespace in format 'databaseName.collectionName' associated with stream.
*/
private $_namespace;
/**
* @var array query parameters passed for the stream.
*/
private $_queryParams = [];
/**
* @var Upload file upload instance
*/
private $_upload;
/**
* @var Download file upload instance
*/
private $_download;
/**
* @var int file pointer offset.
*/
private $_pointerOffset = 0;
/**
* Registers this steam wrapper.
* @param string $protocol name of the protocol to be used.
* @param bool $force whether to register wrapper, even if protocol is already taken.
*/
public static function register($protocol = 'gridfs', $force = false)
{
if (in_array($protocol, stream_get_wrappers())) {
if (!$force) {
return;
}
stream_wrapper_unregister($protocol);
}
stream_wrapper_register($protocol, get_called_class(), STREAM_IS_URL);
}
/**
* Returns options associated with [[context]].
* @return array context options.
*/
public function getContextOptions()
{
if ($this->_contextOptions === null) {
$this->_contextOptions = stream_context_get_options($this->context);
}
return $this->_contextOptions;
}
/**
* Parses stream open path, initializes internal parameters.
* @param string $path stream open path.
*/
private function parsePath($path)
{
$pathInfo = parse_url($path);
$this->_protocol = $pathInfo['scheme'];
$this->_namespace = $pathInfo['host'];
parse_str($pathInfo['query'], $this->_queryParams);
}
/**
* Prepares [[Download]] instance for the read operations.
* @return bool success.
* @throws InvalidConfigException on invalid context configuration.
*/
private function prepareDownload()
{
$contextOptions = $this->getContextOptions();
if (isset($contextOptions[$this->_protocol]['download'])) {
$download = $contextOptions[$this->_protocol]['download'];
if (!$download instanceof Download) {
throw new InvalidConfigException('"download" context option should be an instance of "' . Download::className() . '"');
}
$this->_download = $download;
return true;
}
$collection = $this->fetchCollection();
if (empty($this->_queryParams)) {
return false;
}
$file = $collection->findOne($this->_queryParams);
if (empty($file)) {
throw new InvalidConfigException('Requested file does not exits.');
}
$this->_download = $file['file'];
return true;
}
/**
* Prepares [[Upload]] instance for the write operations.
* @return bool success.
* @throws InvalidConfigException on invalid context configuration.
*/
private function prepareUpload()
{
$contextOptions = $this->getContextOptions();
if (isset($contextOptions[$this->_protocol]['upload'])) {
$upload = $contextOptions[$this->_protocol]['upload'];
if (!$upload instanceof Upload) {
throw new InvalidConfigException('"upload" context option should be an instance of "' . Upload::className() . '"');
}
$this->_upload = $upload;
return true;
}
$collection = $this->fetchCollection();
$this->_upload = $collection->createUpload(['document' => $this->_queryParams]);
return true;
}
/**
* Fetches associated file collection from stream options.
* @return Collection file collection instance.
* @throws InvalidConfigException on invalid stream options.
*/
private function fetchCollection()
{
$contextOptions = $this->getContextOptions();
if (isset($contextOptions[$this->_protocol]['collection'])) {
$collection = $contextOptions[$this->_protocol]['collection'];
if ($collection instanceof Collection) {
throw new InvalidConfigException('"collection" context option should be an instance of "' . Collection::className() . '"');
}
return $collection;
}
$connection = isset($contextOptions[$this->_protocol]['db'])
? $contextOptions[$this->_protocol]['db']
: 'mongodb';
/* @var $connection Connection */
$connection = Instance::ensure($connection, Connection::className());
list($databaseName, $collectionPrefix) = explode('.', $this->_namespace, 2);
return $connection->getDatabase($databaseName)->getFileCollection($collectionPrefix);
}
/**
* Default template for file statistic data set.
* @see stat()
* @return array statistic information.
*/
private function fileStatisticsTemplate()
{
return [
0 => 0, 'dev' => 0,
1 => 0, 'ino' => 0,
2 => 0, 'mode' => 0,
3 => 0, 'nlink' => 0,
4 => 0, 'uid' => 0,
5 => 0, 'gid' => 0,
6 => -1, 'rdev' => -1,
7 => 0, 'size' => 0,
8 => 0, 'atime' => 0,
9 => 0, 'mtime' => 0,
10 => 0, 'ctime' => 0,
11 => -1, 'blksize' => -1,
12 => -1, 'blocks' => -1,
];
}
// Stream Interface :
/**
* Closes a resource.
* This method is called in response to `fclose()`.
* @see fclose()
*/
public function stream_close()
{
if ($this->_upload !== null) {
$this->_upload->complete();
$this->_upload = null;
}
if ($this->_download !== null) {
$this->_download = null;
}
}
/**
* Tests for end-of-file on a file pointer.
* This method is called in response to `feof()`.
* @see feof()
* @return bool `true` if the read/write position is at the end of the stream and
* if no more data is available to be read, or `false` otherwise.
*/
public function stream_eof()
{
return $this->_download !== null
? ($this->_pointerOffset >= $this->_download->getSize())
: true;
}
/**
* Opens file.
* This method is called immediately after the wrapper is initialized (f.e. by `fopen()` and `file_get_contents()`).
* @see fopen()
* @param string $path specifies the URL that was passed to the original function.
* @param string $mode mode used to open the file, as detailed for `fopen()`.
* @param int $options additional flags set by the streams API.
* @param string $openedPath real opened path.
* @return bool whether operation is successful.
*/
public function stream_open($path, $mode, $options, &$openedPath)
{
if ($options & STREAM_USE_PATH) {
$openedPath = $path;
}
$this->parsePath($path);
switch ($mode) {
case 'r':
return $this->prepareDownload();
case 'w':
return $this->prepareUpload();
}
return false;
}
/**
* Reads from stream.
* This method is called in response to `fread()` and `fgets()`.
* @see fread()
* @param int $count count of bytes of data from the current position should be returned.
* @return string|false if there are less than count bytes available, return as many as are available.
* If no more data is available, return `false`.
*/
public function stream_read($count)
{
if ($this->_download === null) {
return false;
}
$result = $this->_download->substr($this->_pointerOffset, $count);
$this->_pointerOffset += $count;
return $result;
}
/**
* Writes to stream.
* This method is called in response to `fwrite()`.
* @see fwrite()
* @param string $data string to be stored into the underlying stream.
* @return int the number of bytes that were successfully stored.
*/
public function stream_write($data)
{
if ($this->_upload === null) {
return false;
}
$this->_upload->addContent($data);
$result = StringHelper::byteLength($data);
$this->_pointerOffset += $result;
return $result;
}
/**
* This method is called in response to `fflush()` and when the stream is being closed
* while any unflushed data has been written to it before.
* @see fflush()
* @return bool whether cached data was successfully stored.
*/
public function stream_flush()
{
return true;
}
/**
* Retrieve information about a file resource.
* This method is called in response to `stat()`.
* @see stat()
* @return array file statistic information.
*/
public function stream_stat()
{
$statistics = $this->fileStatisticsTemplate();
if ($this->_download !== null) {
$statistics[7] = $statistics['size'] = $this->_download->getSize();
}
if ($this->_upload !== null) {
$statistics[7] = $statistics['size'] = $this->_pointerOffset;
}
return $statistics;
}
/**
* Seeks to specific location in a stream.
* This method is called in response to `fseek()`.
* @see fseek()
* @param int $offset The stream offset to seek to.
* @param int $whence
* Possible values:
*
* - SEEK_SET - Set position equal to offset bytes.
* - SEEK_CUR - Set position to current location plus offset.
* - SEEK_END - Set position to end-of-file plus offset.
*
* @return bool Return true if the position was updated, false otherwise.
*/
public function stream_seek($offset, $whence = SEEK_SET)
{
switch ($whence) {
case SEEK_SET:
if ($offset < $this->_download->getSize() && $offset >= 0) {
$this->_pointerOffset = $offset;
return true;
}
return false;
case SEEK_CUR:
if ($offset >= 0) {
$this->_pointerOffset += $offset;
return true;
}
return false;
case SEEK_END:
if ($this->_download->getSize() + $offset >= 0) {
$this->_pointerOffset = $this->_download->getSize() + $offset;
return true;
}
return false;
}
return false;
}
/**
* Retrieve the current position of a stream.
* This method is called in response to `fseek()` to determine the current position.
* @see fseek()
* @return int Should return the current position of the stream.
*/
public function stream_tell()
{
return $this->_pointerOffset;
}
}

View File

@@ -0,0 +1,280 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\mongodb\file;
use MongoDB\BSON\Binary;
use MongoDB\BSON\ObjectID;
use MongoDB\BSON\UTCDatetime;
use MongoDB\Driver\Exception\InvalidArgumentException;
use yii\base\InvalidParamException;
use yii\base\BaseObject;
use yii\helpers\StringHelper;
/**
* Upload represents the GridFS upload operation.
*
* An `Upload` object is usually created by calling [[Collection::createUpload()]].
*
* Note: instance of this class is 'single use' only. Do not attempt to use same `Upload` instance for
* multiple file upload.
*
* Usage example:
*
* ```php
* $document = Yii::$app->mongodb->getFileCollection()->createUpload()
* ->addContent('Part 1')
* ->addContent('Part 2')
* // ...
* ->complete();
* ```
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.1
*/
class Upload extends BaseObject
{
/**
* @var Collection file collection to be used.
*/
public $collection;
/**
* @var string filename to be used for file storage.
*/
public $filename;
/**
* @var array additional file document contents.
* Common GridFS columns:
*
* - metadata: array, additional data associated with the file.
* - aliases: array, an array of aliases.
* - contentType: string, content type to be stored with the file.
*/
public $document = [];
/**
* @var int chunk size in bytes.
*/
public $chunkSize = 261120;
/**
* @var int total upload length in bytes.
*/
public $length = 0;
/**
* @var int file chunk counts.
*/
public $chunkCount = 0;
/**
* @var ObjectID file document ID.
*/
private $_documentId;
/**
* @var resource has context for collecting md5 hash
*/
private $_hashContext;
/**
* @var string internal data buffer
*/
private $_buffer;
/**
* @var bool indicates whether upload is complete or not.
*/
private $_isComplete = false;
/**
* Destructor.
* Makes sure abandoned upload is cancelled.
*/
public function __destruct()
{
if (!$this->_isComplete) {
$this->cancel();
}
}
/**
* {@inheritdoc}
*/
public function init()
{
$this->_hashContext = hash_init('md5');
if (isset($this->document['_id'])) {
if ($this->document['_id'] instanceof ObjectID) {
$this->_documentId = $this->document['_id'];
} else {
try {
$this->_documentId = new ObjectID($this->document['_id']);
} catch (InvalidArgumentException $e) {
// invalid id format
$this->_documentId = $this->document['_id'];
}
}
} else {
$this->_documentId = new ObjectID();
}
$this->collection->ensureIndexes();
}
/**
* Adds string content to the upload.
* This method can invoked several times before [[complete()]] is called.
* @param string $content binary content.
* @return $this self reference.
*/
public function addContent($content)
{
$freeBufferLength = $this->chunkSize - StringHelper::byteLength($this->_buffer);
$contentLength = StringHelper::byteLength($content);
if ($contentLength > $freeBufferLength) {
$this->_buffer .= StringHelper::byteSubstr($content, 0, $freeBufferLength);
$this->flushBuffer(true);
return $this->addContent(StringHelper::byteSubstr($content, $freeBufferLength));
} else {
$this->_buffer .= $content;
$this->flushBuffer();
}
return $this;
}
/**
* Adds stream content to the upload.
* This method can invoked several times before [[complete()]] is called.
* @param resource $stream data source stream.
* @return $this self reference.
*/
public function addStream($stream)
{
while (!feof($stream)) {
$freeBufferLength = $this->chunkSize - StringHelper::byteLength($this->_buffer);
$streamChunk = fread($stream, $freeBufferLength);
if ($streamChunk === false) {
break;
}
$this->_buffer .= $streamChunk;
$this->flushBuffer();
}
return $this;
}
/**
* Adds a file content to the upload.
* This method can invoked several times before [[complete()]] is called.
* @param string $filename source file name.
* @return $this self reference.
*/
public function addFile($filename)
{
if ($this->filename === null) {
$this->filename = basename($filename);
}
$stream = fopen($filename, 'r+');
if ($stream === false) {
throw new InvalidParamException("Unable to read file '{$filename}'");
}
return $this->addStream($stream);
}
/**
* Completes upload.
* @return array saved document.
*/
public function complete()
{
$this->flushBuffer(true);
$document = $this->insertFile();
$this->_isComplete = true;
return $document;
}
/**
* Cancels the upload.
*/
public function cancel()
{
$this->_buffer = null;
$this->collection->getChunkCollection()->remove(['files_id' => $this->_documentId], ['limit' => 0]);
$this->collection->remove(['_id' => $this->_documentId], ['limit' => 1]);
$this->_isComplete = true;
}
/**
* Flushes [[buffer]] to the chunk if it is full.
* @param bool $force whether to enforce flushing.
*/
private function flushBuffer($force = false)
{
if ($this->_buffer === null) {
return;
}
if ($force || StringHelper::byteLength($this->_buffer) == $this->chunkSize) {
$this->insertChunk($this->_buffer);
$this->_buffer = null;
}
}
/**
* Inserts file chunk.
* @param string $data chunk binary content.
*/
private function insertChunk($data)
{
$chunkDocument = [
'files_id' => $this->_documentId,
'n' => $this->chunkCount,
'data' => new Binary($data, Binary::TYPE_GENERIC),
];
hash_update($this->_hashContext, $data);
$this->collection->getChunkCollection()->insert($chunkDocument);
$this->length += StringHelper::byteLength($data);
$this->chunkCount++;
}
/**
* Inserts [[document]] into file collection.
* @return array inserted file document data.
*/
private function insertFile()
{
$fileDocument = [
'_id' => $this->_documentId,
'uploadDate' => new UTCDateTime(round(microtime(true) * 1000)),
];
if ($this->filename === null) {
$fileDocument['filename'] = $this->_documentId . '.dat';
} else {
$fileDocument['filename'] = $this->filename;
}
$fileDocument = array_merge(
$fileDocument,
$this->document,
[
'chunkSize' => $this->chunkSize,
'length' => $this->length,
'md5' => hash_final($this->_hashContext),
]
);
$this->collection->insert($fileDocument);
return $fileDocument;
}
}