Browse Source

Mongo file Active Record updated.

tags/2.0.0-beta
Klimov Paul 11 years ago
parent
commit
ca608a81a4
  1. 16
      extensions/mongo/ActiveRecord.php
  2. 130
      extensions/mongo/file/ActiveRecord.php
  3. 50
      extensions/mongo/file/Query.php
  4. 16
      tests/unit/data/ar/mongo/file/ActiveRecord.php
  5. 27
      tests/unit/data/ar/mongo/file/CustomerFile.php
  6. 119
      tests/unit/extensions/mongo/file/ActiveRecordTest.php
  7. 5
      tests/unit/extensions/mongo/file/QueryTest.php

16
extensions/mongo/ActiveRecord.php

@ -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,6 +309,18 @@ abstract class ActiveRecord extends BaseActiveRecord
{ {
$result = false; $result = false;
if ($this->beforeDelete()) { if ($this->beforeDelete()) {
$result = $this->deleteInternal();
$this->afterDelete();
}
return $result;
}
/**
* @see ActiveRecord::delete()
* @throws StaleObjectException
*/
protected function deleteInternal()
{
// we do not check the return value of deleteAll() because it's possible // 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 // the record is already deleted in the database and thus the method will return 0
$condition = $this->getOldPrimaryKey(true); $condition = $this->getOldPrimaryKey(true);
@ -321,8 +333,6 @@ abstract class ActiveRecord extends BaseActiveRecord
throw new StaleObjectException('The object being deleted is outdated.'); throw new StaleObjectException('The object being deleted is outdated.');
} }
$this->setOldAttributes(null); $this->setOldAttributes(null);
$this->afterDelete();
}
return $result; return $result;
} }

130
extensions/mongo/file/ActiveRecord.php

@ -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;
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
} }
/** /**
* 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
*/ */
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
} }
} }
$collection = static::getCollection(); $collection = static::getCollection();
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); $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
} }
/** /**
* @see CActiveRecord::update() * @see ActiveRecord::update()
* @throws StaleObjectException * @throws StaleObjectException
*/ */
protected function updateInternal($attributes = null) protected function updateInternal($attributes = null)
@ -126,6 +134,36 @@ class ActiveRecord extends \yii\mongo\ActiveRecord
$this->afterSave(false); $this->afterSave(false);
return 0; return 0;
} }
$collection = static::getCollection();
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); $condition = $this->getOldPrimaryKey(true);
$lock = $this->optimisticLock(); $lock = $this->optimisticLock();
if ($lock !== null) { if ($lock !== null) {
@ -136,11 +174,11 @@ class ActiveRecord extends \yii\mongo\ActiveRecord
} }
// We do not check the return value of update() because it's possible // We do not check the return value of update() because it's possible
// that it doesn't change anything and thus returns 0. // that it doesn't change anything and thus returns 0.
$rows = static::getCollection()->update($condition, $values); $rows = $collection->update($condition, $values);
if ($lock !== null && !$rows) { if ($lock !== null && !$rows) {
throw new StaleObjectException('The object being updated is outdated.'); throw new StaleObjectException('The object being updated is outdated.');
} }
}
foreach ($values as $name => $value) { foreach ($values as $name => $value) {
$this->setOldAttribute($name, $this->getAttribute($name)); $this->setOldAttribute($name, $this->getAttribute($name));
@ -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();
} }
} }
} }

50
extensions/mongo/file/Query.php

@ -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
} }
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);
}
}
} }

16
tests/unit/data/ar/mongo/file/ActiveRecord.php

@ -0,0 +1,16 @@
<?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;
}
}

27
tests/unit/data/ar/mongo/file/CustomerFile.php

@ -0,0 +1,27 @@
<?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]);
}
}

119
tests/unit/extensions/mongo/file/ActiveRecordTest.php

@ -0,0 +1,119 @@
<?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);
}
}

5
tests/unit/extensions/mongo/file/QueryTest.php

@ -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
$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']);
} }
} }
Loading…
Cancel
Save