diff --git a/README.md b/README.md index 4063c04..a277a86 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Add the following to your `composer.json` file: ``` "require": { - "matryoshka-model/mongo-wrapper": "~0.7.0" + "matryoshka-model/mongo-wrapper": "~0.8.0" } ``` @@ -97,6 +97,12 @@ It's important to always use the `HydratingResultSet` class included in this pac - `Matryoshka\Model\Wrapper\Mongo\Service` contains abstract service factories generally aimed at instantiation of `\MongoCollection` and `\MongoDb` objects. Use `mongocollection` and `mongodb` configuration nodes to respectively setup them (see [above](#configuration)). +## Continuous integration + +**CI** provided through [TravisCI](http://travis-ci.org/matryoshka-model/mongo-wrapper). + +This wrapper is tested against the following MongoDB PHP clients: **1.4.5**, **1.5.0**, **1.5.1**, **1.5.2**, **1.5.3**, **1.5.3**, **1.5.5**, **1.5.6**, **1.5.7**, **1.5.8**, **1.6.0**, **1.6.1**, **1.6.2**, **1.6.3**, **1.6.4**, **1.6.5**, **1.6.6**, **1.6.7**, **1.6.8**, **1.6.9**. + --- [![Analytics](https://ga-beacon.appspot.com/UA-49657176-2/mongo-wrapper?flat)](https://github.com/igrigorik/ga-beacon) diff --git a/composer.json b/composer.json index 9d7ccf6..926cb08 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,7 @@ "require": { "php": ">=5.5.0", "ext-mongo": "*", - "matryoshka-model/matryoshka": "0.8.*@dev", + "matryoshka-model/matryoshka": "~0.8.0", "zendframework/zend-stdlib": "2.*", "zendframework/zend-paginator": "2.*", "zendframework/zend-servicemanager": "2.*" @@ -40,10 +40,17 @@ ], "autoload": { "psr-4": { - "Matryoshka\\Model\\Wrapper\\Mongo\\": "library/", - "MatryoshkaModelWrapperMongoTest\\": "tests/" + "Matryoshka\\Model\\Wrapper\\Mongo\\": "library/" } }, + "autoload-dev": { + "psr-4": { + "MatryoshkaModelWrapperMongoTest\\": "tests/" + }, + "files": [ + "tests/TestAsset/MongoCollectionMockProxy.php" + ] + }, "keywords": [ "model", "matryoshka", @@ -63,6 +70,7 @@ "suggest": { "matryoshka-model/matryoshka": "A lightweight framework that provides a standard and easy way to implement a model service layer", "matryoshka-model/zf2-matryoshka-module": "ZF2 module for matryoshka library", - "matryoshka-model/rest-wrapper": "Matryoshka wrapper aimed at creating restful API clients" + "matryoshka-model/rest-wrapper": "Matryoshka wrapper aimed at creating restful API clients", + "matryoshka-model/mongo-transactional": "Perform transactional operations with MongoDB" } } diff --git a/library/Criteria/ActiveRecord/ActiveRecordCriteria.php b/library/Criteria/ActiveRecord/ActiveRecordCriteria.php index 4eb5162..7cb7a7a 100644 --- a/library/Criteria/ActiveRecord/ActiveRecordCriteria.php +++ b/library/Criteria/ActiveRecord/ActiveRecordCriteria.php @@ -12,7 +12,6 @@ use Matryoshka\Model\Exception; use Matryoshka\Model\ModelStubInterface; use Matryoshka\Model\Wrapper\Mongo\Criteria\HandleResultTrait; -use Zend\Stdlib\Hydrator\AbstractHydrator; /** * Class ActiveRecordCriteria @@ -35,23 +34,27 @@ class ActiveRecordCriteria extends AbstractCriteria /** * @var array */ - protected $saveOptions = []; + protected $mongoOptions = []; /** + * Get options for Mongo save and remove operations + * * @return array */ - public function getSaveOptions() + public function getMongoOptions() { - return $this->saveOptions; + return $this->mongoOptions; } /** + * Set options for Mongo save and remove operations + * * @param array $options * @return $this */ - public function setSaveOptions(array $options) + public function setMongoOptions(array $options) { - $this->saveOptions = $options; + $this->mongoOptions = $options; return $this; } @@ -76,14 +79,16 @@ public function applyWrite(ModelStubInterface $model, array &$data) /** @var $dataGateway \MongoCollection */ $dataGateway = $model->getDataGateway(); - unset($data['_id']); - - if ($this->id) { + if ($this->hasId()) { $data['_id'] = $this->extractId($model); } + if (array_key_exists('_id', $data) && null === $data['_id']) { + unset($data['_id']); + } + $tmp = $data; // passing a referenced variable to save will fail in update the content - $result = $dataGateway->save($tmp, $this->getSaveOptions()); + $result = $dataGateway->save($tmp, $this->getMongoOptions()); $data = $tmp; return $this->handleResult($result); } @@ -93,7 +98,10 @@ public function applyWrite(ModelStubInterface $model, array &$data) */ public function applyDelete(ModelStubInterface $model) { - $result = $model->getDataGateway()->remove(['_id' => $this->extractId($model)]); + $result = $model->getDataGateway()->remove( + ['_id' => $this->extractId($model)], + ['justOne' => true] + $this->getMongoOptions() + ); return $this->handleResult($result, true); } @@ -103,11 +111,13 @@ public function applyDelete(ModelStubInterface $model) */ protected function extractId(ModelStubInterface $model) { - if (!$model->getHydrator() instanceof AbstractHydrator) { + $hydrator = $model->getHydrator(); + if (!method_exists($hydrator, 'extractValue')) { throw new Exception\RuntimeException( - 'Hydrator must be an instance of \Zend\Stdlib\Hydrator\AbstractHydrator' - ); + 'Hydrator must have extractValue() method ' . + 'in order to extract a single value' + ); } - return $model->getHydrator()->extractValue('_id', $this->getId()); + return $hydrator->extractValue('_id', $this->getId()); } } diff --git a/library/Criteria/FindAllCriteria.php b/library/Criteria/FindAllCriteria.php index e97a7ed..9f1cc7a 100644 --- a/library/Criteria/FindAllCriteria.php +++ b/library/Criteria/FindAllCriteria.php @@ -14,7 +14,6 @@ use Matryoshka\Model\Exception\InvalidArgumentException; use Matryoshka\Model\ModelStubInterface; use Matryoshka\Model\Wrapper\Mongo\Paginator\MongoPaginatorAdapter; -use Zend\Stdlib\Hydrator\AbstractHydrator; /** * Class FindAllCriteria @@ -111,17 +110,15 @@ public function setOrderBy(array $orders = []) */ protected function extractValue(ModelStubInterface $model, $name, $value, $object = null) { - if (!$model->getHydrator() instanceof AbstractHydrator) { + $hydrator = $model->getHydrator(); + if (!method_exists($hydrator, 'extractValue')) { throw new Exception\RuntimeException( - sprintf( - 'Hydrator must be an instance of "%s"; detected "%s"', - '\Zend\Stdlib\Hydrator\AbstractHydrator', - get_class($model->getHydrator()) - ) - ); + 'Hydrator must have extractValue() method ' . + 'in order to extract a single value' + ); } - return $model->getHydrator()->extractValue($name, $value, $object); + return $hydrator->extractValue($name, $value, $object); } /** diff --git a/library/Criteria/Isolated/ActiveRecordCriteria.php b/library/Criteria/Isolated/ActiveRecordCriteria.php index e5ffc43..2fca47f 100644 --- a/library/Criteria/Isolated/ActiveRecordCriteria.php +++ b/library/Criteria/Isolated/ActiveRecordCriteria.php @@ -39,16 +39,18 @@ public function apply(ModelStubInterface $model) */ public function applyWrite(ModelStubInterface $model, array &$data) { - unset($data['_id']); - - if ($this->id) { + if ($this->hasId()) { $data['_id'] = $this->extractId($model); } + if (array_key_exists('_id', $data) && null === $data['_id']) { + unset($data['_id']); + } + return $this->getDocumentStore()->isolatedUpsert( $model->getDataGateway(), $data, - $this->getSaveOptions() + $this->getMongoOptions() ); } @@ -60,7 +62,8 @@ public function applyDelete(ModelStubInterface $model) { return $this->getDocumentStore()->isolatedRemove( $model->getDataGateway(), - $this->extractId($model) + $this->extractId($model), + $this->getMongoOptions() ); } } diff --git a/library/Criteria/Isolated/DocumentStore.php b/library/Criteria/Isolated/DocumentStore.php index 0291f74..6bcf414 100644 --- a/library/Criteria/Isolated/DocumentStore.php +++ b/library/Criteria/Isolated/DocumentStore.php @@ -230,7 +230,7 @@ public function isolatedUpsert(MongoCollection $dataGateway, array &$data, array $oldDocumentData, $data, // modifiers and non-modifiers cannot be mixed, // the _id presence ensure at least one non-modifiers - array_merge($options, ['multi' => false, 'upsert' => false]) + ['multi' => false, 'upsert' => false] + $options ); $result = $this->handleResult($result); @@ -258,7 +258,7 @@ public function isolatedRemove(MongoCollection $dataGateway, $id, array $options throw new RuntimeException(sprintf('No local copy found for the document "%s"', $id)); } - $result = $dataGateway->remove($this->get($dataGateway, $id), $options); + $result = $dataGateway->remove($this->get($dataGateway, $id), ['justOne' => true] + $options); $result = $this->handleResult($result, true); if ($result != 1) { diff --git a/library/Hydrator/Strategy/MongoBinDataStrategy.php b/library/Hydrator/Strategy/MongoBinDataStrategy.php index 90f6086..f35594c 100644 --- a/library/Hydrator/Strategy/MongoBinDataStrategy.php +++ b/library/Hydrator/Strategy/MongoBinDataStrategy.php @@ -8,13 +8,19 @@ */ namespace Matryoshka\Model\Wrapper\Mongo\Hydrator\Strategy; +use MongoBinData; use Zend\Stdlib\Hydrator\Strategy\StrategyInterface; +use Matryoshka\Model\Hydrator\Strategy\NullableStrategyTrait; +use Matryoshka\Model\Hydrator\Strategy\NullableStrategyInterface; +use Matryoshka\Model\Exception; /** * Class MongoBinDataStrategy */ -class MongoBinDataStrategy implements StrategyInterface +class MongoBinDataStrategy implements StrategyInterface, NullableStrategyInterface { + use NullableStrategyTrait; + /** * @var int */ @@ -45,24 +51,41 @@ public function setType($type) $this->type = (int) $type; return $this; } - + /** - * Ensure the value extracted is typed as \MongoBinData or null - * + * Convert a MongoBinData to binary string + * * @param mixed $value The original value. - * @return null|\MongoBinData Returns the value that should be extracted. + * @return null|string Returns the value that should be hydrated. */ - public function extract($value) + public function hydrate($value) { - return null === $value ? null : new \MongoBinData($value, $this->type); + if ($this->nullable && $value === null) { + return null; + } + + if ($value instanceof MongoBinData) { + return $value->bin; + } + + throw new Exception\InvalidArgumentException(sprintf( + 'Invalid value: must be an instance of MongoBinData, "%s" given', + is_object($value) ? get_class($value) : gettype($value) + )); } /** + * Ensure the value extracted is typed as MongoBinData or null + * * @param mixed $value The original value. - * @return null|string Returns the value that should be hydrated. + * @return null|\MongoBinData Returns the value that should be extracted. */ - public function hydrate($value) + public function extract($value) { - return $value instanceof \MongoBinData ? $value->bin : null; + if ($this->nullable && $value === null) { + return null; + } + + return new MongoBinData($value, $this->type); } } diff --git a/library/Hydrator/Strategy/MongoDateStrategy.php b/library/Hydrator/Strategy/MongoDateStrategy.php index 6a44b0c..729301b 100644 --- a/library/Hydrator/Strategy/MongoDateStrategy.php +++ b/library/Hydrator/Strategy/MongoDateStrategy.php @@ -11,12 +11,17 @@ use DateTime; use MongoDate; use Zend\Stdlib\Hydrator\Strategy\StrategyInterface; +use Matryoshka\Model\Hydrator\Strategy\NullableStrategyInterface; +use Matryoshka\Model\Hydrator\Strategy\NullableStrategyTrait; +use Matryoshka\Model\Exception; /** * Class MongoDateStrategy */ -class MongoDateStrategy implements StrategyInterface +class MongoDateStrategy implements StrategyInterface, NullableStrategyInterface { + use NullableStrategyTrait; + /** * @var string */ @@ -34,34 +39,53 @@ public function __construct($format = null) } } + /** + * Convert a MongoDate to a DateTime + * * @param mixed $value - * @return DateTime|mixed + * @return mixed|MongoDate */ - public function extract($value) + public function hydrate($value) { - if ($value instanceof DateTime) { - $value = new MongoDate($value->format('U')); - } else { - $value = null; + if ($value instanceof MongoDate) { + return new DateTime(date($this->getFormat(), $value->sec)); + } + + if ($this->nullable && $value === null) { + return null; } - return $value; + + throw new Exception\InvalidArgumentException(sprintf( + 'Invalid value: must be an instance of MongoDate, "%s" given.', + is_object($value) ? get_class($value) : gettype($value) + )); } - + + /** + * Convert a DateTime to a MongoDate + * * @param mixed $value - * @return mixed|MongoDate + * @return DateTime|mixed */ - public function hydrate($value) + public function extract($value) { - if ($value instanceof MongoDate) { - $value = new DateTime(date($this->getFormat(), $value->sec)); - } else { - $value = null; + if ($value instanceof DateTime) { + return new MongoDate($value->format('U')); + } + + if ($this->nullable && $value === null) { + return null; } - return $value; + + throw new Exception\InvalidArgumentException(sprintf( + 'Invalid value: must be an instance of DateTime, "%s" given.', + is_object($value) ? get_class($value) : gettype($value) + )); } + /** * @param string $format * @return $this diff --git a/library/Hydrator/Strategy/MongoIdStrategy.php b/library/Hydrator/Strategy/MongoIdStrategy.php index b6bd254..c050292 100644 --- a/library/Hydrator/Strategy/MongoIdStrategy.php +++ b/library/Hydrator/Strategy/MongoIdStrategy.php @@ -8,32 +8,61 @@ */ namespace Matryoshka\Model\Wrapper\Mongo\Hydrator\Strategy; +use MongoId; use Zend\Stdlib\Hydrator\Strategy\StrategyInterface; +use Matryoshka\Model\Hydrator\Strategy\NullableStrategyTrait; +use Matryoshka\Model\Hydrator\Strategy\NullableStrategyInterface; +use Matryoshka\Model\Exception; /** * Class MongoIdStrategy */ -class MongoIdStrategy implements StrategyInterface +class MongoIdStrategy implements StrategyInterface, NullableStrategyInterface { + use NullableStrategyTrait; + /** - * Ensure the value extracted is typed as \MongoId or null + * Ensure the value extracted is typed as string or null * * @param mixed $value The original value. - * @return null|\MongoId Returns the value that should be extracted. + * @return null|string Returns the value that should be hydrated. */ - public function extract($value) + public function hydrate($value) { - return null === $value ? null : new \MongoId($value); + if ($value instanceof MongoId) { + return (string)$value; + } + + if ($this->nullable && $value === null) { + return null; + } + + throw new Exception\InvalidArgumentException(sprintf( + 'Invalid value: must be an instance of MongoId, "%s" given.', + is_object($value) ? get_class($value) : gettype($value) + )); } - + + /** - * Ensure the value extracted is typed as string or null - * + * Ensure the value extracted is typed as MongoId or null + * * @param mixed $value The original value. - * @return null|string Returns the value that should be hydrated. + * @return null|MongoId Returns the value that should be extracted. */ - public function hydrate($value) + public function extract($value) { - return $value === null ? null : (string)$value; + if (is_string($value)) { + return new MongoId($value); + } + + if ($this->nullable && $value === null) { + return null; + } + + throw new Exception\InvalidArgumentException(sprintf( + 'Invalid value: must be a string containing a valid mongo ID, "%s" given.', + is_object($value) ? get_class($value) : gettype($value) + )); } } diff --git a/tests/Criteria/ActiveRecord/ActiveRecordCriteriaTest.php b/tests/Criteria/ActiveRecord/ActiveRecordCriteriaTest.php index b0f677a..b761a0a 100644 --- a/tests/Criteria/ActiveRecord/ActiveRecordCriteriaTest.php +++ b/tests/Criteria/ActiveRecord/ActiveRecordCriteriaTest.php @@ -20,19 +20,6 @@ */ class ActiveRecordCriteriaTest extends \PHPUnit_Framework_TestCase { - protected static $oldErrorLevel; - - protected static function disableStrictErrors() - { - self::$oldErrorLevel = error_reporting(); - error_reporting(self::$oldErrorLevel & ~E_STRICT); - } - - protected static function restoreErrorReportingLevel() - { - error_reporting(self::$oldErrorLevel); - } - /** * @var Model */ @@ -50,10 +37,8 @@ public function setUp() $this->mongoCollectionMock = $mongoCollectionMock; - self::disableStrictErrors(); - $mockProxy = new MongoCollectionMockProxy(); - self::restoreErrorReportingLevel(); - $mockProxy->__MongoCollectionMockProxy__setMock($mongoCollectionMock); + $mockProxy = new MongoCollectionMockProxy; + $mockProxy->__MongoCollectionMockProxy__setMock($this->mongoCollectionMock); $rs = new ArrayObjectResultSet(); $model = new Model($mockProxy, $rs); @@ -90,13 +75,15 @@ public function testApply() public function testApplyWrite() { + $options = ['foo' => 'baz']; $ar = new ActiveRecordCriteria(); + $ar->setMongoOptions($options); $testId = 1; $testData = ['_id' => $testId]; $this->mongoCollectionMock->expects($this->at(0)) ->method('save') - ->with($this->equalTo($testData), $this->equalTo($ar->getSaveOptions())); + ->with($this->equalTo($testData), $this->equalTo($options)); $ar->setId($testId); @@ -129,30 +116,33 @@ public function testApplyWriteWithoutId() $this->mongoCollectionMock->expects($this->at(0)) ->method('save') - ->with($this->equalTo($testUnsetData), $this->equalTo($ar->getSaveOptions())); + ->with($this->equalTo($testUnsetData), $this->equalTo($ar->getMongoOptions())); $ar->applyWrite($this->model, $testData); $this->assertInstanceOf('\MongoId', $testData['_id']); } - public function testSaveOptions() + public function testGetSetMongoOptions() { - $saveOptions = ['foo', 'bar']; $ar = new ActiveRecordCriteria(); - $ar->setSaveOptions($saveOptions); + $this->assertEmpty($ar->getMongoOptions()); - $this->assertEquals($saveOptions, $ar->getSaveOptions()); + $saveOptions = ['foo', 'bar']; + $ar->setMongoOptions($saveOptions); + $this->assertEquals($saveOptions, $ar->getMongoOptions()); } public function testApplyDelete() { + $options = ['foo' => 'baz']; $ar = new ActiveRecordCriteria(); + $ar->setMongoOptions($options); $testId = 1; $testData = ['_id' => $testId]; $this->mongoCollectionMock->expects($this->at(0)) ->method('remove') - ->with($this->equalTo($testData)); + ->with($this->equalTo($testData), $this->equalTo(['justOne' => true] + $options)); $ar->setId($testId); diff --git a/tests/Criteria/Isolated/ActiveRecordCriteriaTest.php b/tests/Criteria/Isolated/ActiveRecordCriteriaTest.php index 7eecd19..d984837 100644 --- a/tests/Criteria/Isolated/ActiveRecordCriteriaTest.php +++ b/tests/Criteria/Isolated/ActiveRecordCriteriaTest.php @@ -20,26 +20,12 @@ */ class ActiveRecordCriteriaTest extends \PHPUnit_Framework_TestCase { - protected static $oldErrorLevel; - - protected static function disableStrictErrors() - { - self::$oldErrorLevel = error_reporting(); - error_reporting(self::$oldErrorLevel & ~E_STRICT); - } - - protected static function restoreErrorReportingLevel() - { - error_reporting(self::$oldErrorLevel); - } protected static $sharedDataGateway; public static function setUpBeforeClass() { - self::disableStrictErrors(); self::$sharedDataGateway = new MongoCollectionMockProxy(); - self::restoreErrorReportingLevel(); } /** @var \PHPUnit_Framework_MockObject_MockObject $mongoCollectionMock */ @@ -165,7 +151,7 @@ public function testApplyWrite() $this->mongoCollectionMock->expects($this->atLeastOnce()) ->method('insert') - ->with($this->equalTo($expectedResult), $this->equalTo($criteria->getSaveOptions())) + ->with($this->equalTo($expectedResult), $this->equalTo($criteria->getMongoOptions())) ->will($this->returnValue(['ok' => true, 'n' => 0])); // MongoDB returns 0 on insert operation $criteria->setId($testId); @@ -181,7 +167,7 @@ public function testApplyWrite() ->with( $this->equalTo($currentDataState), $this->equalTo($expectedResult), - $this->equalTo(array_merge($criteria->getSaveOptions(), ['multi' => false, 'upsert' => false])) + $this->equalTo(['multi' => false, 'upsert' => false] + $criteria->getMongoOptions()) ) ->will($this->returnValue(['ok' => true, 'n' => 1, 'updatedExisting' => true])); @@ -190,9 +176,9 @@ public function testApplyWrite() $this->assertHasDocumentCache($testId, $expectedResult); } - /** - * @depends testApplyWrite - */ +// /** +// * @depends testApplyWrite +// */ public function testApplyWriteWithoutId() { $model = $this->model; @@ -202,7 +188,7 @@ public function testApplyWriteWithoutId() //Test insert $this->mongoCollectionMock->expects($this->atLeastOnce()) ->method('insert') - ->with($this->equalTo($testData), $this->equalTo($criteria->getSaveOptions())) + ->with($this->equalTo($testData), $this->equalTo($criteria->getMongoOptions())) ->will($this->returnValue(['ok' => true, 'n' => 0])); // MongoDB returns 0 on insert operation @@ -212,6 +198,26 @@ public function testApplyWriteWithoutId() $this->assertHasDocumentCache($testId, $testData); } + + public function testApplyWriteWithNullId() + { + $model = $this->model; + $criteria = $this->criteria; + $dataWithoutId = ['test' => 'test']; + $testData = array_merge($dataWithoutId, ['_id' => null]); + + //Test insert + $this->mongoCollectionMock->expects($this->atLeastOnce()) + ->method('insert') + ->with($this->equalTo($dataWithoutId), $this->equalTo($criteria->getMongoOptions())) + ->will($this->returnValue(['ok' => true, 'n' => 0])); // MongoDB returns 0 on insert operation + $this->assertEquals(1, $criteria->applyWrite($model, $testData)); + + $this->assertInstanceOf('\MongoId', $testData['_id']); + $testId = (string) $testData['_id']; + + $this->assertHasDocumentCache($testId, $testData); + } /** * @depends testApplyWriteWithoutId diff --git a/tests/Criteria/Isolated/DocumentStoreTest.php b/tests/Criteria/Isolated/DocumentStoreTest.php index 51a1a0a..814c192 100644 --- a/tests/Criteria/Isolated/DocumentStoreTest.php +++ b/tests/Criteria/Isolated/DocumentStoreTest.php @@ -16,19 +16,6 @@ */ class DocumentStoreTest extends \PHPUnit_Framework_TestCase { - protected static $oldErrorLevel; - - protected static function disableStrictErrors() - { - self::$oldErrorLevel = error_reporting(); - error_reporting(self::$oldErrorLevel & ~E_STRICT); - } - - protected static function restoreErrorReportingLevel() - { - error_reporting(self::$oldErrorLevel); - } - protected static $sharedDataGateway; /** @@ -39,9 +26,7 @@ protected static function restoreErrorReportingLevel() public static function setUpBeforeClass() { - self::disableStrictErrors(); self::$sharedDataGateway = new MongoCollectionMockProxy(); - self::restoreErrorReportingLevel(); } /** @var \PHPUnit_Framework_MockObject_MockObject $mongoCollectionMock */ @@ -295,7 +280,7 @@ public function testIsolatedRemove() $this->mongoCollectionMock->expects($this->atLeastOnce()) ->method('remove') - ->with($this->equalTo($testData)) + ->with($this->equalTo($testData), $this->equalTo(['justOne' => true] + $options)) ->will($this->returnValue(['ok' => true, 'n' => 1])); diff --git a/tests/Hydrator/Strategy/MongoBinDataTest.php b/tests/Hydrator/Strategy/MongoBinDataTest.php index 6ee27de..361e3bd 100644 --- a/tests/Hydrator/Strategy/MongoBinDataTest.php +++ b/tests/Hydrator/Strategy/MongoBinDataTest.php @@ -9,6 +9,7 @@ namespace MatryoshkaModelWrapperMongoTest\Hydrator\Strategy; use Matryoshka\Model\Wrapper\Mongo\Hydrator\Strategy\MongoBinDataStrategy; +use Matryoshka\Model\Exception\InvalidArgumentException; /** * Class MongoBinDataTest @@ -48,4 +49,13 @@ public function testHydrate() $result = $strategy->hydrate(null); $this->assertNull($result); } + + public function testHydrateShouldThrowExceptionWhenInvalidType() + { + $strategy = new MongoBinDataStrategy(\MongoBinData::CUSTOM); + $mongoBinData = new \MongoBinData('foo', \MongoBinData::CUSTOM); + + $this->setExpectedException(InvalidArgumentException::class); + $strategy->hydrate('invalid type'); + } } diff --git a/tests/Hydrator/Strategy/MongoDateStrategyTest.php b/tests/Hydrator/Strategy/MongoDateStrategyTest.php index b7c929a..fac2bbc 100644 --- a/tests/Hydrator/Strategy/MongoDateStrategyTest.php +++ b/tests/Hydrator/Strategy/MongoDateStrategyTest.php @@ -9,6 +9,7 @@ namespace MatryoshkaModelWrapperMongoTest\Hydrator\Strategy; use Matryoshka\Model\Wrapper\Mongo\Hydrator\Strategy\MongoDateStrategy; +use Matryoshka\Model\Exception\InvalidArgumentException; /** * Class MongoDateStrategyTest @@ -32,9 +33,11 @@ public function testExtract() $result = $strategy->extract(\DateTime::createFromFormat('U', $now)); $this->assertEquals(new \MongoDate($now), $result); + + $this->assertNull($strategy->extract(null)); + $this->setExpectedException(InvalidArgumentException::class); $result = $strategy->extract('test invalid value'); - $this->assertNull($result); } public function testHydrate() @@ -45,9 +48,11 @@ public function testHydrate() $result = $strategy->hydrate(new \MongoDate($now)); $this->assertEquals(new \DateTime(date($format, $now)), $result); + + $this->assertNull($strategy->hydrate(null)); + $this->setExpectedException(InvalidArgumentException::class); $result = $strategy->hydrate('test invalid value'); - $this->assertNull($result); } public function testGetSetFormat() diff --git a/tests/Hydrator/Strategy/MongoIdStrategyTest.php b/tests/Hydrator/Strategy/MongoIdStrategyTest.php index 85b37a4..ddaaecb 100644 --- a/tests/Hydrator/Strategy/MongoIdStrategyTest.php +++ b/tests/Hydrator/Strategy/MongoIdStrategyTest.php @@ -9,6 +9,7 @@ namespace MatryoshkaModelWrapperMongoTest\Hydrator\Strategy; use Matryoshka\Model\Wrapper\Mongo\Hydrator\Strategy\MongoIdStrategy; +use Matryoshka\Model\Exception\InvalidArgumentException; /** * Class MongoIdStrategyTest @@ -26,6 +27,9 @@ public function testExtract() $result = $strategy->extract(null); $this->assertNull($result); + + $this->setExpectedException(InvalidArgumentException::class); + $strategy->extract(['not-a-string' => 'invalid-type']); } public function testHydrate() @@ -38,5 +42,9 @@ public function testHydrate() $result = $strategy->hydrate(null); $this->assertNull($result); + + $this->setExpectedException(InvalidArgumentException::class); + $strategy->hydrate('invalid-type'); } + } diff --git a/tests/TestAsset/MongoCollectionMockProxy.php b/tests/TestAsset/MongoCollectionMockProxy.php index 1e417bf..79bfdf4 100644 --- a/tests/TestAsset/MongoCollectionMockProxy.php +++ b/tests/TestAsset/MongoCollectionMockProxy.php @@ -1,359 +1,8 @@ mock = $mock; - } - - public function __set($name, $value) - { - return $this->mock->{$name} = $value; - } - - public function __get($name) - { - return $this->mock->{$name}; - } - - public function __call($method, array $params) - { - if (count($params) > 0) { - return call_user_func_array([$this->mock, $method], $params); - } - return call_user_func([$this->mock, $method]); - } - - /** - * Saves an object to this collection - * - * @link http://www.php.net/manual/en/mongocollection.save.php - * @param mixed $a - * Array to save. - * @param array $options - * Options for the save. - * @throws \MongoCursorException - * @return mixed - */ - public function save(array &$a, array $options = []) - { - $return = $this->mock->save($a, $options); - if (!isset($a['_id']) || $a['_id'] === null) { - $a['_id'] = new \MongoId(); - } - return $return; - } - - /** - * Inserts a document into the collection - * - * @link http://php.net/manual/en/mongocollection.insert.php - * @param mixed $a - * Array to save. - * @param array $options - * Options for the save. - * @throws \MongoCursorException - * @return mixed - */ - public function insert(array &$a, array $options = []) - { - $return = $this->mock->insert($a, $options); - if (!isset($a['_id']) || $a['_id'] === null) { - $a['_id'] = new \MongoId(); - } - return $return; - } - - /** - * String representation of this collection - * - * @return string - */ - public function __toString() - { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * Returns this collections name - * - * @return string - */ - public function getName() - { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * Drops this collection - * - * @return array - */ - public function drop() - { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * Validates this collection - * - * @param boolean $scan_data - * Only validate indices, not the base collection. - * - * @return array - */ - public function validate($scan_data = false) - { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * Inserts multiple documents into this collection - * - * @param array $a - * An array of arrays. - * @param array $options - * Options for the inserts. - * - * @return mixed If "safe" is set, returns an associative array with the - * status of the inserts ("ok") - * and any error that may have occured ("err"). Otherwise, returns - * TRUE if the batch insert - * was successfully sent, FALSE otherwise. - */ - public function batchInsert($a, $options = array()) - { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * Update records based on a given criteria - * - * @param array $criteria - * Description of the objects to update. - * @param array $newobj - * The object with which to update the matching records. - * @param array $options - * Options for update. - * - * @return boolean - */ - public function update($criteria, $newobj, $options = array()) - { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * Remove records from this collection - * - * @param array $criteria - * Description of records to remove. - * @param array $options - * Options for remove. - * - * @return mixed - */ - public function remove($criteria = array(), $options = array()) - { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * Querys this collection - * - * @param array $query - * The fields for which to search - * @param array $fields - * Fields of the results to return. - * The array is in the format array('fieldname' => true, - * 'fieldname2' => true). - * The _id field is always returned. - * - * @return MongoCursor - */ - public function find($query = array(), $fields = array()) - { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * Querys this collection, returning a single element - * - * As opposed to MongoCollection::find(), this method will return only the - * first - * result from the result set, and not a MongoCursor that can be iterated - * over. - * - * @param array $query - * The fields for which to search - * @param array $fields - * Fields of the results to return. - * The array is in the format array('fieldname' => true, - * 'fieldname2' => true). - * The _id field is always returned. - * - * @return array - */ - public function findOne($query = array(), $fields = array(), $options = array()) - { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * Creates an index on the given field(s), or does nothing if the index - * already exists - * - * @param array $keys - * An array of fields by which to sort the index on. - * Each element in the array has as key the field name, - * and as value either 1 for ascending sort, or -1 for descending - * sort. - * @param array $options - * Options for the ensureIndex. - * - * @return boolean - */ - public function ensureIndex($keys, $options = array()) - { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * Deletes an index from this collection - * - * @param string|array $keys - * Field or fields from which to delete the index. - * - * @return array - */ - public function deleteIndex($keys) - { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * Delete all indices for this collection - * - * @return array - */ - public function deleteIndexes() - { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * Returns an array of index names for this collection - * - * @return array - */ - public function getIndexInfo() - { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * Counts the number of documents in this collection - * - * @param array $query - * Associative array or object with fields to match. - * @param integer $limit - * Specifies an upper limit to the number returned. - * @param integer $skip - * Specifies a number of results to skip before starting the - * count. - * - * @return integer - */ - public function count($query = array(), $limit = null, $skip = null) - { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * Creates a database reference - * - * @param array $a - * Object to which to create a reference. - * - * @return array - */ - public function createDBRef($a) - { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * Fetches the document pointed to by a database reference - * - * @param array $ref - * A database reference. - * - * @return array - */ - public function getDBRef($ref) - { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * Performs an operation similar to SQL's GROUP BY command - * - * @param mixed $keys - * Fields to group by. If an array or non-code object - * is passed, it will be the key used to group results. - * @param array $initial - * Initial value of the aggregation counter object. - * @param \MongoCode $reduce - * A function that takes two arguments (the current - * document and the aggregation to this point) and does the - * aggregation. - * @param array $options - * Optional parameters to the group command - * - * @return array - */ - public function group($keys, $initial, $reduce, $options = array()) - { - return $this->__call(__FUNCTION__, func_get_args()); - } -} +$oldErrorLevel = error_reporting(); +error_reporting($oldErrorLevel & ~E_STRICT); +include '_MongoCollectionMockProxy.php'; +error_reporting($oldErrorLevel); \ No newline at end of file diff --git a/tests/TestAsset/_MongoCollectionMockProxy.php b/tests/TestAsset/_MongoCollectionMockProxy.php new file mode 100644 index 0000000..6d0fad4 --- /dev/null +++ b/tests/TestAsset/_MongoCollectionMockProxy.php @@ -0,0 +1,373 @@ +mock = $mock; + } + + public function __set($name, $value) + { + return $this->mock->{$name} = $value; + } + + public function __get($name) + { + return $this->mock->{$name}; + } + + public function __call($method, array $params) + { + if (count($params) > 0) { + return call_user_func_array([$this->mock, $method], $params); + } + return call_user_func([$this->mock, $method]); + } + + /** + * Saves an object to this collection + * + * @link http://www.php.net/manual/en/mongocollection.save.php + * @param mixed $a + * Array to save. + * @param array $options + * Options for the save. + * @throws \MongoCursorException + * @return mixed + */ + public function save(array &$a, array $options = []) + { + $return = $this->mock->save($a, $options); + if (!isset($a['_id']) || $a['_id'] === null) { + $a['_id'] = new \MongoId(); + } + return $return; + } + + /** + * Inserts a document into the collection + * + * @link http://php.net/manual/en/mongocollection.insert.php + * @param mixed $a + * Array to save. + * @param array $options + * Options for the save. + * @throws \MongoCursorException + * @return mixed + */ + public function insert(array &$a, array $options = []) + { + $return = $this->mock->insert($a, $options); + if (!isset($a['_id']) || $a['_id'] === null) { + $a['_id'] = new \MongoId(); + } + return $return; + } + + /** + * String representation of this collection + * + * @return string + */ + public function __toString() + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * Returns this collections name + * + * @return string + */ + public function getName() + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * Drops this collection + * + * @return array + */ + public function drop() + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * Validates this collection + * + * @param boolean $scan_data + * Only validate indices, not the base collection. + * + * @return array + */ + public function validate($scan_data = false) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * Inserts multiple documents into this collection + * + * @param array $a + * An array of arrays. + * @param array $options + * Options for the inserts. + * + * @return mixed If "safe" is set, returns an associative array with the + * status of the inserts ("ok") + * and any error that may have occured ("err"). Otherwise, returns + * TRUE if the batch insert + * was successfully sent, FALSE otherwise. + */ + public function batchInsert($a, $options = array()) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * Update records based on a given criteria + * + * @param array $criteria + * Description of the objects to update. + * @param array $newobj + * The object with which to update the matching records. + * @param array $options + * Options for update. + * + * @return boolean + */ + public function update($criteria, $newobj, $options = array()) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * Remove records from this collection + * + * @param array $criteria + * Description of records to remove. + * @param array $options + * Options for remove. + * + * @return mixed + */ + public function remove($criteria = array(), $options = array()) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * Querys this collection + * + * @param array $query + * The fields for which to search + * @param array $fields + * Fields of the results to return. + * The array is in the format array('fieldname' => true, + * 'fieldname2' => true). + * The _id field is always returned. + * + * @return MongoCursor + */ + public function find($query = array(), $fields = array()) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * Querys this collection, returning a single element + * + * As opposed to MongoCollection::find(), this method will return only the + * first + * result from the result set, and not a MongoCursor that can be iterated + * over. + * + * @param array $query + * The fields for which to search + * @param array $fields + * Fields of the results to return. + * The array is in the format array('fieldname' => true, + * 'fieldname2' => true). + * The _id field is always returned. + * + * @return array + */ + public function findOne($query = array(), $fields = array(), $options = array()) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * Creates an index on the given field(s), or does nothing if the index + * already exists + * + * @param array $keys + * An array of fields by which to sort the index on. + * Each element in the array has as key the field name, + * and as value either 1 for ascending sort, or -1 for descending + * sort. + * @param array $options + * Options for the ensureIndex. + * + * @return boolean + */ + public function ensureIndex($keys, $options = array()) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * Deletes an index from this collection + * + * @param string|array $keys + * Field or fields from which to delete the index. + * + * @return array + */ + public function deleteIndex($keys) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * Delete all indices for this collection + * + * @return array + */ + public function deleteIndexes() + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * Returns an array of index names for this collection + * + * @return array + */ + public function getIndexInfo() + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * Counts the number of documents in this collection + * + * @param array $query + * Associative array or object with fields to match. + * @param integer $limit + * Specifies an upper limit to the number returned. + * @param integer $skip + * Specifies a number of results to skip before starting the + * count. + * + * @return integer + */ + public function count($query = array(), $limit = null, $skip = null) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * Creates a database reference + * + * @param array $a + * Object to which to create a reference. + * + * @return array + */ + public function createDBRef($a) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * Fetches the document pointed to by a database reference + * + * @param array $ref + * A database reference. + * + * @return array + */ + public function getDBRef($ref) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * Performs an operation similar to SQL's GROUP BY command + * + * @param mixed $keys + * Fields to group by. If an array or non-code object + * is passed, it will be the key used to group results. + * @param array $initial + * Initial value of the aggregation counter object. + * @param \MongoCode $reduce + * A function that takes two arguments (the current + * document and the aggregation to this point) and does the + * aggregation. + * @param array $options + * Optional parameters to the group command + * + * @return array + */ + public function group($keys, $initial, $reduce, $options = array()) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @param array $query + * The query criteria to search for. + * @param array $update + * The update criteria. + * @param array $fields + * Optionally only return these fields. + * @param array $options + * An array of options to apply, such as remove the match document from the DB and return it. + * + * @return array Returns the original document, or the modified document when new is set + */ + public function findAndModify($query, $update = array(), $fields = array(), $options = array()) + { + return $this->__call(__FUNCTION__, func_get_args()); + } +}