Commit ca608a81 by Klimov Paul

Mongo file Active Record updated.

parent 85a32bea
...@@ -250,7 +250,7 @@ abstract class ActiveRecord extends BaseActiveRecord ...@@ -250,7 +250,7 @@ abstract class ActiveRecord extends BaseActiveRecord
} }
/** /**
* @see CActiveRecord::update() * @see ActiveRecord::update()
* @throws StaleObjectException * @throws StaleObjectException
*/ */
protected function updateInternal($attributes = null) protected function updateInternal($attributes = null)
...@@ -309,24 +309,34 @@ abstract class ActiveRecord extends BaseActiveRecord ...@@ -309,24 +309,34 @@ abstract class ActiveRecord extends BaseActiveRecord
{ {
$result = false; $result = false;
if ($this->beforeDelete()) { if ($this->beforeDelete()) {
// we do not check the return value of deleteAll() because it's possible $result = $this->deleteInternal();
// the record is already deleted in the database and thus the method will return 0
$condition = $this->getOldPrimaryKey(true);
$lock = $this->optimisticLock();
if ($lock !== null) {
$condition[$lock] = $this->$lock;
}
$result = static::getCollection()->remove($condition);
if ($lock !== null && !$result) {
throw new StaleObjectException('The object being deleted is outdated.');
}
$this->setOldAttributes(null);
$this->afterDelete(); $this->afterDelete();
} }
return $result; return $result;
} }
/** /**
* @see ActiveRecord::delete()
* @throws StaleObjectException
*/
protected function deleteInternal()
{
// we do not check the return value of deleteAll() because it's possible
// the record is already deleted in the database and thus the method will return 0
$condition = $this->getOldPrimaryKey(true);
$lock = $this->optimisticLock();
if ($lock !== null) {
$condition[$lock] = $this->$lock;
}
$result = static::getCollection()->remove($condition);
if ($lock !== null && !$result) {
throw new StaleObjectException('The object being deleted is outdated.');
}
$this->setOldAttributes(null);
return $result;
}
/**
* Returns a value indicating whether the given active record is the same as the current one. * Returns a value indicating whether the given active record is the same as the current one.
* The comparison is made by comparing the table names and the primary key values of the two active records. * The comparison is made by comparing the table names and the primary key values of the two active records.
* If one of the records [[isNewRecord|is new]] they are also considered not equal. * If one of the records [[isNewRecord|is new]] they are also considered not equal.
......
...@@ -7,6 +7,10 @@ ...@@ -7,6 +7,10 @@
namespace yii\mongo\file; namespace yii\mongo\file;
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. * ActiveRecord is the base class for classes representing Mongo GridFS files in terms of objects.
* *
...@@ -16,11 +20,6 @@ namespace yii\mongo\file; ...@@ -16,11 +20,6 @@ namespace yii\mongo\file;
class ActiveRecord extends \yii\mongo\ActiveRecord class ActiveRecord extends \yii\mongo\ActiveRecord
{ {
/** /**
* @var \MongoGridFSFile|string
*/
public $file;
/**
* Creates an [[ActiveQuery]] instance. * Creates an [[ActiveQuery]] instance.
* This method is called by [[find()]] to start a "find" command. * This method is called by [[find()]] to start a "find" command.
* You may override this method to return a customized query (e.g. `CustomerQuery` specified * You may override this method to return a customized query (e.g. `CustomerQuery` specified
...@@ -54,29 +53,6 @@ class ActiveRecord extends \yii\mongo\ActiveRecord ...@@ -54,29 +53,6 @@ class ActiveRecord extends \yii\mongo\ActiveRecord
} }
/** /**
* Creates an active record object using a row of data.
* This method is called by [[ActiveQuery]] to populate the query results
* into Active Records. It is not meant to be used to create new records.
* @param \MongoGridFSFile $row attribute values (name => value)
* @return ActiveRecord the newly created active record.
*/
public static function create($row)
{
$record = static::instantiate($row);
$columns = array_flip($record->attributes());
foreach ($row->file as $name => $value) {
if (isset($columns[$name])) {
$record->setAttribute($name, $value);
} else {
$record->$name = $value;
}
}
$record->setOldAttributes($record->getAttributes());
$record->afterFind();
return $record;
}
/**
* Returns the list of all attribute names of the model. * Returns the list of all attribute names of the model.
* This method could be overridden by child classes to define available attributes. * This method could be overridden by child classes to define available attributes.
* Note: primary key attribute "_id" should be always present in returned array. * Note: primary key attribute "_id" should be always present in returned array.
...@@ -84,7 +60,16 @@ class ActiveRecord extends \yii\mongo\ActiveRecord ...@@ -84,7 +60,16 @@ class ActiveRecord extends \yii\mongo\ActiveRecord
*/ */
public function attributes() public function attributes()
{ {
return ['id', 'filename']; return [
'_id',
'filename',
'uploadDate',
'length',
'chunkSize',
'md5',
'file',
'newFileContent'
];
} }
/** /**
...@@ -103,7 +88,30 @@ class ActiveRecord extends \yii\mongo\ActiveRecord ...@@ -103,7 +88,30 @@ class ActiveRecord extends \yii\mongo\ActiveRecord
} }
} }
$collection = static::getCollection(); $collection = static::getCollection();
$newId = $collection->insert($values); if (array_key_exists('newFileContent', $values)) {
$fileContent = $values['newFileContent'];
unset($values['newFileContent']);
unset($values['file']);
$newId = $collection->storeBytes($fileContent, $values);
} elseif (array_key_exists('file', $values)) {
$file = $values['file'];
if ($file instanceof UploadedFile) {
$fileName = $file->tempName;
} elseif (is_string($file)) {
if (file_exists($file)) {
$fileName = $file;
} else {
throw new InvalidParamException("File '{$file}' does not exist.");
}
} else {
throw new InvalidParamException('Unsupported type of "file" attribute.');
}
unset($values['newFileContent']);
unset($values['file']);
$newId = $collection->storeFile($fileName, $values);
} else {
$newId = $collection->insert($values);
}
$this->setAttribute('_id', $newId); $this->setAttribute('_id', $newId);
foreach ($values as $name => $value) { foreach ($values as $name => $value) {
$this->setOldAttribute($name, $value); $this->setOldAttribute($name, $value);
...@@ -113,7 +121,7 @@ class ActiveRecord extends \yii\mongo\ActiveRecord ...@@ -113,7 +121,7 @@ class ActiveRecord extends \yii\mongo\ActiveRecord
} }
/** /**
* @see CActiveRecord::update() * @see ActiveRecord::update()
* @throws StaleObjectException * @throws StaleObjectException
*/ */
protected function updateInternal($attributes = null) protected function updateInternal($attributes = null)
...@@ -126,20 +134,50 @@ class ActiveRecord extends \yii\mongo\ActiveRecord ...@@ -126,20 +134,50 @@ class ActiveRecord extends \yii\mongo\ActiveRecord
$this->afterSave(false); $this->afterSave(false);
return 0; return 0;
} }
$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 = static::getCollection()->update($condition, $values);
if ($lock !== null && !$rows) { $collection = static::getCollection();
throw new StaleObjectException('The object being updated is outdated.'); if (array_key_exists('newFileContent', $values)) {
$fileContent = $values['newFileContent'];
unset($values['newFileContent']);
unset($values['file']);
$values['_id'] = $this->getAttribute('_id');
$this->deleteInternal();
$collection->storeBytes($fileContent, $values);
$rows = 1;
} elseif (array_key_exists('file', $values)) {
$file = $values['file'];
if ($file instanceof UploadedFile) {
$fileName = $file->tempName;
} elseif (is_string($file)) {
if (file_exists($file)) {
$fileName = $file;
} else {
throw new InvalidParamException("File '{$file}' does not exist.");
}
} else {
throw new InvalidParamException('Unsupported type of "file" attribute.');
}
unset($values['newFileContent']);
unset($values['file']);
$values['_id'] = $this->getAttribute('_id');
$this->deleteInternal();
$collection->storeFile($fileName, $values);
$rows = 1;
} 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.');
}
} }
foreach ($values as $name => $value) { foreach ($values as $name => $value) {
...@@ -149,26 +187,26 @@ class ActiveRecord extends \yii\mongo\ActiveRecord ...@@ -149,26 +187,26 @@ class ActiveRecord extends \yii\mongo\ActiveRecord
return $rows; return $rows;
} }
public function getContent() /**
* Returns the associated file content.
* @return null|string file content.
* @throws \yii\base\InvalidParamException on invalid file value.
*/
public function getFileContent()
{ {
$file = $this->getAttribute('file'); $file = $this->getAttribute('file');
if (empty($file)) { if (empty($file)) {
return null; return null;
} } elseif ($file instanceof \MongoGridFSFile) {
if ($file instanceof \MongoGridFSFile) {
return $file->getBytes(); return $file->getBytes();
} elseif ($file instanceof UploadedFile) {
return file_get_contents($file->tempName);
} elseif (is_string($file)) {
if (file_exists($file)) {
return file_get_contents($file);
}
} else {
throw new InvalidParamException('Unsupported type of "file" attribute.');
} }
} }
public function getFileName()
{
$file = $this->getAttribute('file');
if (empty($file)) {
return null;
}
if ($file instanceof \MongoGridFSFile) {
return $file->getFilename();
}
}
} }
\ No newline at end of file
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
namespace yii\mongo\file; namespace yii\mongo\file;
use Yii; use Yii;
use yii\helpers\Json;
use yii\mongo\Exception;
/** /**
* Class Query * Class Query
...@@ -29,4 +31,52 @@ class Query extends \yii\mongo\Query ...@@ -29,4 +31,52 @@ class Query extends \yii\mongo\Query
} }
return $db->getFileCollection($this->from); return $db->getFileCollection($this->from);
} }
/**
* Fetches rows from the given Mongo cursor.
* @param \MongoCursor $cursor Mongo cursor instance to fetch data from.
* @param boolean $all whether to fetch all rows or only first one.
* @param string|callable $indexBy the column name or PHP callback,
* by which the query results should be indexed by.
* @throws Exception on failure.
* @return array|boolean result.
*/
protected function fetchRows(\MongoCursor $cursor, $all = true, $indexBy = null)
{
$token = 'Querying: ' . Json::encode($cursor->info());
Yii::info($token, __METHOD__);
try {
Yii::beginProfile($token, __METHOD__);
$result = [];
if ($all) {
foreach ($cursor as $file) {
$row = $file->file;
$row['file'] = $file;
if ($indexBy !== null) {
if (is_string($indexBy)) {
$key = $row[$indexBy];
} else {
$key = call_user_func($indexBy, $row);
}
$result[$key] = $row;
} else {
$result[] = $row;
}
}
} else {
if ($cursor->hasNext()) {
$file = $cursor->getNext();
$result = $file->file;
$result['file'] = $file;
} else {
$result = false;
}
}
Yii::endProfile($token, __METHOD__);
return $result;
} catch (\Exception $e) {
Yii::endProfile($token, __METHOD__);
throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
}
}
} }
\ No newline at end of file
<?php
namespace yiiunit\data\ar\mongo\file;
/**
* Test Mongo ActiveRecord
*/
class ActiveRecord extends \yii\mongo\file\ActiveRecord
{
public static $db;
public static function getDb()
{
return self::$db;
}
}
\ No newline at end of file
<?php
namespace yiiunit\data\ar\mongo\file;
class CustomerFile extends ActiveRecord
{
public static function collectionName()
{
return 'customer_fs';
}
public function attributes()
{
return array_merge(
parent::attributes(),
[
'tag',
'status',
]
);
}
public static function activeOnly($query)
{
$query->andWhere(['status' => 2]);
}
}
\ No newline at end of file
<?php
namespace yiiunit\extensions\mongo\file;
use yiiunit\extensions\mongo\MongoTestCase;
use yii\mongo\file\ActiveQuery;
use yiiunit\data\ar\mongo\file\ActiveRecord;
use yiiunit\data\ar\mongo\file\CustomerFile;
/**
* @group mongo
*/
class ActiveRecordTest extends MongoTestCase
{
/**
* @var array[] list of test rows.
*/
protected $testRows = [];
protected function setUp()
{
parent::setUp();
ActiveRecord::$db = $this->getConnection();
$this->setUpTestRows();
}
protected function tearDown()
{
$this->dropFileCollection(CustomerFile::collectionName());
parent::tearDown();
}
/**
* Sets up test rows.
*/
protected function setUpTestRows()
{
$collection = $this->getConnection()->getFileCollection(CustomerFile::collectionName());
$rows = [];
for ($i = 1; $i <= 10; $i++) {
$record = [
'tag' => 'tag' . $i,
'status' => $i,
];
$content = 'content' . $i;
$record['_id'] = $collection->storeBytes($content, $record);
$record['content'] = $content;
$rows[] = $record;
}
$this->testRows = $rows;
}
// Tests :
public function testFind()
{
// find one
$result = CustomerFile::find();
$this->assertTrue($result instanceof ActiveQuery);
$customer = $result->one();
$this->assertTrue($customer instanceof CustomerFile);
// find all
$customers = CustomerFile::find()->all();
$this->assertEquals(10, count($customers));
$this->assertTrue($customers[0] instanceof CustomerFile);
$this->assertTrue($customers[1] instanceof CustomerFile);
// find by _id
$testId = $this->testRows[0]['_id'];
$customer = CustomerFile::find($testId);
$this->assertTrue($customer instanceof CustomerFile);
$this->assertEquals($testId, $customer->_id);
// find by column values
$customer = CustomerFile::find(['tag' => 'tag5']);
$this->assertTrue($customer instanceof CustomerFile);
$this->assertEquals($this->testRows[4]['_id'], $customer->_id);
$this->assertEquals('tag5', $customer->tag);
$customer = CustomerFile::find(['tag' => 'unexisting tag']);
$this->assertNull($customer);
// find by attributes
$customer = CustomerFile::find()->where(['status' => 4])->one();
$this->assertTrue($customer instanceof CustomerFile);
$this->assertEquals(4, $customer->status);
// find count, sum, average, min, max, distinct
$this->assertEquals(10, CustomerFile::find()->count());
$this->assertEquals(1, CustomerFile::find()->where(['status' => 2])->count());
$this->assertEquals((1+10)/2*10, CustomerFile::find()->sum('status'));
$this->assertEquals((1+10)/2, CustomerFile::find()->average('status'));
$this->assertEquals(1, CustomerFile::find()->min('status'));
$this->assertEquals(10, CustomerFile::find()->max('status'));
$this->assertEquals(range(1, 10), CustomerFile::find()->distinct('status'));
// scope
$this->assertEquals(1, CustomerFile::find()->activeOnly()->count());
// asArray
$testRow = $this->testRows[2];
$customer = CustomerFile::find()->where(['_id' => $testRow['_id']])->asArray()->one();
$this->assertEquals($testRow['_id'], $customer['_id']);
$this->assertEquals($testRow['tag'], $customer['tag']);
$this->assertEquals($testRow['status'], $customer['status']);
// indexBy
$customers = CustomerFile::find()->indexBy('tag')->all();
$this->assertTrue($customers['tag1'] instanceof CustomerFile);
$this->assertTrue($customers['tag2'] instanceof CustomerFile);
// indexBy callable
$customers = CustomerFile::find()->indexBy(function ($customer) {
return $customer->status . '-' . $customer->status;
})->all();
$this->assertTrue($customers['1-1'] instanceof CustomerFile);
$this->assertTrue($customers['2-2'] instanceof CustomerFile);
}
}
\ No newline at end of file
...@@ -51,7 +51,8 @@ class QueryTest extends MongoTestCase ...@@ -51,7 +51,8 @@ class QueryTest extends MongoTestCase
$connection = $this->getConnection(); $connection = $this->getConnection();
$query = new Query; $query = new Query;
$row = $query->from('fs')->one($connection); $row = $query->from('fs')->one($connection);
$this->assertTrue($row instanceof \MongoGridFSFile); $this->assertTrue(is_array($row));
$this->assertTrue($row['file'] instanceof \MongoGridFSFile);
} }
public function testDirectMatch() public function testDirectMatch()
...@@ -64,6 +65,6 @@ class QueryTest extends MongoTestCase ...@@ -64,6 +65,6 @@ class QueryTest extends MongoTestCase
$this->assertEquals(1, count($rows)); $this->assertEquals(1, count($rows));
/** @var $file \MongoGridFSFile */ /** @var $file \MongoGridFSFile */
$file = $rows[0]; $file = $rows[0];
$this->assertEquals('name5', $file->file['filename']); $this->assertEquals('name5', $file['filename']);
} }
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment