From de412ebe8c47e874c05d06e800f9b22b83221718 Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Tue, 16 Oct 2018 11:59:16 -0300 Subject: [PATCH 001/116] Use LMBR Coding Standard --- .gitignore | 1 + .styleci.yml | 1 - .travis.yml | 1 + Makefile | 2 +- coding_standard.xml | 7 --- composer.json | 4 +- phpcs.xml | 30 ++++++++++++ src/Mongolid/ActiveRecord.php | 9 ++-- src/Mongolid/Connection/Connection.php | 1 - src/Mongolid/Connection/Pool.php | 1 - src/Mongolid/Container/Ioc.php | 1 - src/Mongolid/Cursor/CacheableCursor.php | 5 -- src/Mongolid/Cursor/Cursor.php | 11 ++--- src/Mongolid/Cursor/CursorFactory.php | 5 -- src/Mongolid/Cursor/CursorInterface.php | 3 +- src/Mongolid/Cursor/EmbeddedCursor.php | 3 -- src/Mongolid/DataMapper/BulkWrite.php | 9 ---- src/Mongolid/DataMapper/DataMapper.php | 15 ++---- src/Mongolid/DataMapper/EntityAssembler.php | 1 - src/Mongolid/DataMapper/SchemaMapper.php | 1 - src/Mongolid/Event/EventTriggerInterface.php | 1 - src/Mongolid/Event/EventTriggerService.php | 1 - .../Exception/ModelNotFoundException.php | 1 - .../Exception/NoCollectionNameException.php | 1 - src/Mongolid/Manager.php | 1 - src/Mongolid/Model/Attributes.php | 1 - .../Model/AttributesAccessInterface.php | 1 - src/Mongolid/Model/DocumentEmbedder.php | 1 - src/Mongolid/Model/PolymorphableInterface.php | 1 - src/Mongolid/Model/Relations.php | 1 - src/Mongolid/Schema/DynamicSchema.php | 1 - src/Mongolid/Schema/HasSchemaInterface.php | 3 -- src/Mongolid/Schema/Schema.php | 1 - src/Mongolid/Util/CacheComponent.php | 1 - src/Mongolid/Util/CacheComponentInterface.php | 1 - src/Mongolid/Util/LocalDateTime.php | 14 ------ src/Mongolid/Util/ObjectIdUtils.php | 1 - src/Mongolid/Util/SequenceService.php | 5 -- tests/Mongolid/ActiveRecordTest.php | 32 ++++++------- tests/Mongolid/Connection/ConnectionTest.php | 1 - tests/Mongolid/Connection/PoolTest.php | 1 - tests/Mongolid/Container/IocTest.php | 1 - tests/Mongolid/Cursor/CacheableCursorTest.php | 1 - tests/Mongolid/Cursor/CursorFactoryTest.php | 1 - tests/Mongolid/Cursor/CursorTest.php | 4 +- tests/Mongolid/Cursor/EmbeddedCursorTest.php | 1 - tests/Mongolid/DataMapper/BulkWriteTest.php | 1 - tests/Mongolid/DataMapper/DataMapperTest.php | 10 ++-- .../DataMapper/EntityAssemblerTest.php | 46 +++++++++---------- .../Mongolid/DataMapper/SchemaMapperTest.php | 1 - tests/Mongolid/DynamicSchemaTest.php | 1 - .../Event/EventTriggerServiceTest.php | 1 - tests/Mongolid/ManagerTest.php | 1 - tests/Mongolid/Model/AttributesTest.php | 1 - tests/Mongolid/Model/DocumentEmbedderTest.php | 1 - tests/Mongolid/Model/RelationsTest.php | 19 +++++++- tests/Mongolid/SchemaTest.php | 1 - tests/Mongolid/Util/CacheComponentTest.php | 1 - tests/Mongolid/Util/LocalDateTimeTest.php | 3 +- tests/Mongolid/Util/ObjectIdUtilsTest.php | 1 - tests/Mongolid/Util/SequenceServiceTest.php | 1 - tests/TestCase.php | 6 +-- 62 files changed, 114 insertions(+), 171 deletions(-) delete mode 100644 .styleci.yml delete mode 100644 coding_standard.xml create mode 100644 phpcs.xml diff --git a/.gitignore b/.gitignore index fd9ae985..fd177cf5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /build /site composer.lock +.phpcs-cache diff --git a/.styleci.yml b/.styleci.yml deleted file mode 100644 index 974f5fa5..00000000 --- a/.styleci.yml +++ /dev/null @@ -1 +0,0 @@ -preset: symfony diff --git a/.travis.yml b/.travis.yml index 7728915f..6947b2b7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,7 @@ before_script: script: - mkdir -p build/logs + - vendor/bin/phpcs - phpunit -c phpunit.xml.dist && make sniff after_script: diff --git a/Makefile b/Makefile index 9ece02fe..3fa54ddb 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ sniff: - vendor/bin/phpcs ./src --standard='./coding_standard.xml' -n + vendor/bin/phpcs phpunit: vendor/bin/phpunit diff --git a/coding_standard.xml b/coding_standard.xml deleted file mode 100644 index e0481693..00000000 --- a/coding_standard.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - Extends PSR-2 making sure that the docblocks are well written - - - - diff --git a/composer.json b/composer.json index 1ccda769..ea2a1938 100644 --- a/composer.json +++ b/composer.json @@ -22,8 +22,8 @@ "mockery/mockery": "^1.0", "satooshi/php-coveralls": "^1.0", "phpunit/phpunit": "^6.4", - "squizlabs/php_codesniffer": "^2.0", - "sami/sami": "^4.0" + "sami/sami": "^4.0", + "leroy-merlin-br/coding-standard": "^0.1.1" }, "autoload": { "psr-4": { diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 00000000..e93ea39a --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + bootstrap + src + tests + + + + + + tests/TestCase.php + + + tests/Mongolid/DataMapper/EntityAssemblerTest.php + + + + tests/Mongolid/DataMapper/EntityAssemblerTest.php + + diff --git a/src/Mongolid/ActiveRecord.php b/src/Mongolid/ActiveRecord.php index 50661b67..ea17b6d5 100644 --- a/src/Mongolid/ActiveRecord.php +++ b/src/Mongolid/ActiveRecord.php @@ -1,5 +1,4 @@ collection ? $this->collection : $this->getSchema()->collection; + return $this->collection ?: $this->getSchema()->collection; } /** @@ -343,7 +342,7 @@ protected function execute(string $action) /** * Returns the a valid instance from Ioc. * - * @throws NoCollectionNameException throws exception when has no collection filled + * @throws NoCollectionNameException Throws exception when has no collection filled * * @return mixed */ diff --git a/src/Mongolid/Connection/Connection.php b/src/Mongolid/Connection/Connection.php index 4e4184ea..ff129c31 100644 --- a/src/Mongolid/Connection/Connection.php +++ b/src/Mongolid/Connection/Connection.php @@ -1,5 +1,4 @@ setBulkWrite(new MongoBulkWrite(['ordered' => false])); @@ -63,8 +57,6 @@ public function getBulkWrite() * Set BulkWrite object that will receive all operations * and later be executed. * - * @param MongoBulkWrite $bulkWrite - * * @return $this */ public function setBulkWrite(MongoBulkWrite $bulkWrite) @@ -84,7 +76,6 @@ public function setBulkWrite(MongoBulkWrite $bulkWrite) * @param ObjectId|string $id * @param array $dataToSet * @param array $options - * @param string $operator */ public function updateOne( $id, diff --git a/src/Mongolid/DataMapper/DataMapper.php b/src/Mongolid/DataMapper/DataMapper.php index 68f22262..cd1ad192 100644 --- a/src/Mongolid/DataMapper/DataMapper.php +++ b/src/Mongolid/DataMapper/DataMapper.php @@ -1,5 +1,4 @@ schema->entityClass objects upon iteration. - * - * @return \Mongolid\Cursor\Cursor */ public function all(): Cursor { @@ -309,7 +304,7 @@ public function first( * @param array $projection fields to project in Mongo query * @param bool $cacheable retrieves the first through a CacheableCursor * - * @throws ModelNotFoundException if no document was found + * @throws ModelNotFoundException If no document was found * * @return mixed First document matching query as an $this->schema->entityClass object */ @@ -362,8 +357,6 @@ protected function getSchemaMapper() /** * Retrieves the Collection object. - * - * @return Collection */ protected function getCollection(): Collection { @@ -457,7 +450,7 @@ protected function fireEvent(string $event, $entity, bool $halt = false) { $event = "mongolid.{$event}: ".get_class($entity); - $this->eventService ? $this->eventService : $this->eventService = Ioc::make(EventTriggerService::class); + $this->eventService ?: $this->eventService = Ioc::make(EventTriggerService::class); return $this->eventService->fire($event, $entity, $halt); } @@ -481,7 +474,7 @@ protected function fireEvent(string $event, $entity, bool $halt = false) * * @param array $fields fields to project * - * @throws InvalidArgumentException if the given $fields are not a valid projection + * @throws InvalidArgumentException If the given $fields are not a valid projection * * @return array */ @@ -563,8 +556,6 @@ public function getSchema(): Schema /** * Set a Schema object that describes an Entity in MongoDB. - * - * @param Schema $schema */ public function setSchema(Schema $schema) { diff --git a/src/Mongolid/DataMapper/EntityAssembler.php b/src/Mongolid/DataMapper/EntityAssembler.php index 70412293..085bae6f 100644 --- a/src/Mongolid/DataMapper/EntityAssembler.php +++ b/src/Mongolid/DataMapper/EntityAssembler.php @@ -1,5 +1,4 @@ assertEquals($schema, $result->getSchema()); } - /** - * @expectedException \Mongolid\Exception\NoCollectionNameException - */ public function testShouldRaiseExceptionWhenHasNoCollectionAndTryToCallAllFunction() { $entity = new class() extends ActiveRecord { }; - $this->assertNull($entity->getCollectionName()); - + $this->expectException(NoCollectionNameException::class); $entity->all(); } - /** - * @expectedException \Mongolid\Exception\NoCollectionNameException - */ public function testShouldRaiseExceptionWhenHasNoCollectionAndTryToCallFirstFunction() { $entity = new class() extends ActiveRecord { }; - $this->assertNull($entity->getCollectionName()); - + $this->expectException(NoCollectionNameException::class); $entity->first(); } - /** - * @expectedException \Mongolid\Exception\NoCollectionNameException - */ public function testShouldRaiseExceptionWhenHasNoCollectionAndTryToCallWhereFunction() { $entity = new class() extends ActiveRecord { }; - $this->assertNull($entity->getCollectionName()); - + $this->expectException(NoCollectionNameException::class); $entity->where(); } public function testShouldGetCollectionName() { $entity = new class() extends ActiveRecord { + /** + * @var {inheritdoc} + */ protected $collection = 'collection_name'; }; @@ -423,6 +414,9 @@ public function testShouldGetCollectionName() public function testShouldAttachToAttribute() { $entity = new class() extends ActiveRecord { + /** + * @var {inheritdoc} + */ protected $collection = 'collection_name'; public function class() @@ -441,6 +435,9 @@ public function class() public function testShouldEmbedToAttribute() { $entity = new class() extends ActiveRecord { + /** + * @var {inheritdoc} + */ protected $collection = 'collection_name'; public function classes() @@ -458,6 +455,9 @@ public function classes() public function testShouldThrowBadMethodCallExceptionWhenCallingInvalidMethod() { $entity = new class() extends ActiveRecord { + /** + * @var {inheritdoc} + */ protected $collection = 'collection_name'; }; diff --git a/tests/Mongolid/Connection/ConnectionTest.php b/tests/Mongolid/Connection/ConnectionTest.php index fb96d8fa..7c8d8586 100644 --- a/tests/Mongolid/Connection/ConnectionTest.php +++ b/tests/Mongolid/Connection/ConnectionTest.php @@ -1,5 +1,4 @@ assertEquals($expectation, $result); } - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage Invalid projection: 'invalid-key' => 'invalid-value' - */ public function testPrepareProjectionShouldThrownAnException() { // Arrange @@ -801,6 +797,10 @@ public function testPrepareProjectionShouldThrownAnException() $mapper = new DataMapper($connPool); $data = ['valid' => true, 'invalid-key' => 'invalid-value']; + // Expectations + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("Invalid projection: 'invalid-key' => 'invalid-value'"); + // Act $this->callProtected($mapper, 'prepareProjection', [$data]); } diff --git a/tests/Mongolid/DataMapper/EntityAssemblerTest.php b/tests/Mongolid/DataMapper/EntityAssemblerTest.php index b41b9f62..9f70e1e7 100644 --- a/tests/Mongolid/DataMapper/EntityAssemblerTest.php +++ b/tests/Mongolid/DataMapper/EntityAssemblerTest.php @@ -1,5 +1,4 @@ assertEquals($expectedOutput, $result); } - public function EntityAssemblerFixture() + public function entityAssemblerFixture() { return [ //--------------------------- @@ -57,7 +57,7 @@ public function EntityAssemblerFixture() ], 'availableSchmas' => [ // Schemas that will exist in the test context 'studentSchema' => [ - 'entityClass' => _stubStudent::class, + 'entityClass' => StubStudent::class, 'fields' => [ '_id' => 'objectId', 'name' => 'string', @@ -68,7 +68,7 @@ public function EntityAssemblerFixture() ], ], 'inputSchema' => 'studentSchema', // Schema that will be used to assembly $inputValue - 'expectedOutput' => new _stubStudent([ // Expected output + 'expectedOutput' => new StubStudent([ // Expected output '_id' => new ObjectID('507f1f77bcf86cd799439011'), 'name' => 'John Doe', 'age' => 25, @@ -88,7 +88,7 @@ public function EntityAssemblerFixture() ], 'availableSchmas' => [ // Schemas that will exist in the test context 'studentSchema' => [ - 'entityClass' => _stubStudent::class, + 'entityClass' => StubStudent::class, 'fields' => [ '_id' => 'objectId', 'name' => 'string', @@ -98,7 +98,7 @@ public function EntityAssemblerFixture() ], ], 'TestSchema' => [ - 'entityClass' => _stubTestGrade::class, + 'entityClass' => StubTestGrade::class, 'fields' => [ '_id' => 'objectId', 'subject' => 'string', @@ -107,7 +107,7 @@ public function EntityAssemblerFixture() ], ], 'inputSchema' => 'studentSchema', // Schema that will be used to assembly $inputValue - 'expectedOutput' => new _stubStudent([ // Expected output + 'expectedOutput' => new StubStudent([ // Expected output '_id' => new ObjectID('507f1f77bcf86cd799439011'), 'name' => 'John Doe', 'age' => 25, @@ -132,7 +132,7 @@ public function EntityAssemblerFixture() ], 'availableSchmas' => [ // Schemas that will exist in the test context 'studentSchema' => [ - 'entityClass' => _stubStudent::class, + 'entityClass' => StubStudent::class, 'fields' => [ '_id' => 'objectId', 'name' => 'string', @@ -142,7 +142,7 @@ public function EntityAssemblerFixture() ], ], 'TestSchema' => [ - 'entityClass' => _stubTestGrade::class, + 'entityClass' => StubTestGrade::class, 'fields' => [ '_id' => 'objectId', 'subject' => 'string', @@ -151,12 +151,12 @@ public function EntityAssemblerFixture() ], ], 'inputSchema' => 'studentSchema', // Schema that will be used to assembly $inputValue - 'expectedOutput' => new _stubStudent([ // Expected output + 'expectedOutput' => new StubStudent([ // Expected output '_id' => new ObjectID('507f1f77bcf86cd799439011'), 'name' => 'John Doe', 'age' => 25, 'tests' => [ - new _stubTestGrade([ + new StubTestGrade([ '_id' => new ObjectID('507f1f77bcf86cd7994390ea'), 'subject' => 'math', 'grade' => 7.25, @@ -189,7 +189,7 @@ public function EntityAssemblerFixture() ], 'availableSchmas' => [ // Schemas that will exist in the test context 'studentSchema' => [ - 'entityClass' => _stubStudent::class, + 'entityClass' => StubStudent::class, 'fields' => [ '_id' => 'objectId', 'name' => 'string', @@ -199,7 +199,7 @@ public function EntityAssemblerFixture() ], ], 'TestSchema' => [ - 'entityClass' => _stubTestGrade::class, + 'entityClass' => StubTestGrade::class, 'fields' => [ '_id' => 'objectId', 'subject' => 'string', @@ -208,17 +208,17 @@ public function EntityAssemblerFixture() ], ], 'inputSchema' => 'studentSchema', // Schema that will be used to assembly $inputValue - 'expectedOutput' => new _stubStudent([ // Expected output + 'expectedOutput' => new StubStudent([ // Expected output '_id' => new ObjectID('507f1f77bcf86cd799439011'), 'name' => 'John Doe', 'age' => 25, 'tests' => [ - new _stubTestGrade([ + new StubTestGrade([ '_id' => new ObjectID('507f1f77bcf86cd7994390ea'), 'subject' => 'math', 'grade' => 7.25, ]), - new _stubTestGrade([ + new StubTestGrade([ '_id' => new ObjectID('507f1f77bcf86cd7994390eb'), 'subject' => 'english', 'grade' => 9.0, @@ -239,7 +239,7 @@ public function EntityAssemblerFixture() ], 'availableSchmas' => [ // Schemas that will exist in the test context 'studentSchema' => [ - 'entityClass' => _polymorphableStudent::class, + 'entityClass' => PolymorphableStudent::class, 'fields' => [ '_id' => 'objectId', 'name' => 'string', @@ -250,7 +250,7 @@ public function EntityAssemblerFixture() ], ], 'inputSchema' => 'studentSchema', // Schema that will be used to assembly $inputValue - 'expectedOutput' => new _stubStudent([ // Expected output + 'expectedOutput' => new StubStudent([ // Expected output '_id' => new ObjectID('507f1f77bcf86cd799439011'), 'name' => 'John Doe', 'age' => 25, @@ -261,7 +261,7 @@ public function EntityAssemblerFixture() } } -class _stubStudent extends \stdClass implements AttributesAccessInterface +class StubStudent extends stdClass implements AttributesAccessInterface { use Attributes; @@ -275,7 +275,7 @@ public function __construct($attr = []) } } -class _stubTestGrade extends \stdClass +class StubTestGrade extends stdClass { public function __construct($attr = []) { @@ -285,7 +285,7 @@ public function __construct($attr = []) } } -class _polymorphableStudent extends \stdClass implements PolymorphableInterface +class PolymorphableStudent extends stdClass implements PolymorphableInterface { public function __construct($attr = []) { @@ -296,6 +296,6 @@ public function __construct($attr = []) public function polymorph() { - return new _stubStudent((array) $this); + return new StubStudent((array) $this); } } diff --git a/tests/Mongolid/DataMapper/SchemaMapperTest.php b/tests/Mongolid/DataMapper/SchemaMapperTest.php index 5dd571af..8427fde3 100644 --- a/tests/Mongolid/DataMapper/SchemaMapperTest.php +++ b/tests/Mongolid/DataMapper/SchemaMapperTest.php @@ -1,5 +1,4 @@ [ 'entity' => new class() extends ActiveRecord { + /** + * @var {inheritdoc} + */ protected $collection = 'foobar'; }, 'field' => 'foo', @@ -211,6 +213,9 @@ public function referenceScenarios() // ------------------------- 'ActiveRecord referenced by objectId' => [ 'entity' => new class() extends ActiveRecord { + /** + * @var {inheritdoc} + */ protected $collection = 'foobar'; }, 'field' => 'foo', @@ -236,6 +241,9 @@ public function referenceScenarios() // ------------------------- 'ActiveRecord referenced with series of string objectIds' => [ 'entity' => new class() extends ActiveRecord { + /** + * @var {inheritdoc} + */ protected $collection = 'foobar'; }, 'field' => 'foo', @@ -261,6 +269,9 @@ public function referenceScenarios() // ------------------------- 'ActiveRecord referenced with null' => [ 'entity' => new class() extends ActiveRecord { + /** + * @var {inheritdoc} + */ protected $collection = 'foobar'; }, 'field' => 'foo', @@ -296,6 +307,9 @@ public function embedsScenarios() // ------------------------- 'Embedded document referent to an ActiveRecord entity' => [ 'entity' => new class() extends ActiveRecord { + /** + * @var {inheritdoc} + */ protected $collection = 'foobar'; }, 'field' => 'foo', @@ -305,6 +319,9 @@ public function embedsScenarios() // ------------------------- 'Embedded documents referent to an ActiveRecord entity' => [ 'entity' => new class() extends ActiveRecord { + /** + * @var {inheritdoc} + */ protected $collection = 'foobar'; }, 'field' => 'foo', diff --git a/tests/Mongolid/SchemaTest.php b/tests/Mongolid/SchemaTest.php index addbdfb0..79764477 100644 --- a/tests/Mongolid/SchemaTest.php +++ b/tests/Mongolid/SchemaTest.php @@ -1,5 +1,4 @@ Date: Thu, 18 Oct 2018 17:36:00 -0300 Subject: [PATCH 002/116] Move files and drop bootstrap file --- bootstrap/bootstrap.php | 12 --- composer.json | 13 ++- src/{Mongolid => }/ActiveRecord.php | 0 src/{Mongolid => }/Connection/Connection.php | 0 src/{Mongolid => }/Connection/Pool.php | 0 src/{Mongolid => }/Container/Ioc.php | 0 src/{Mongolid => }/Cursor/CacheableCursor.php | 0 src/{Mongolid => }/Cursor/Cursor.php | 0 src/{Mongolid => }/Cursor/CursorFactory.php | 0 src/{Mongolid => }/Cursor/CursorInterface.php | 0 src/{Mongolid => }/Cursor/EmbeddedCursor.php | 0 src/{Mongolid => }/DataMapper/BulkWrite.php | 0 src/{Mongolid => }/DataMapper/DataMapper.php | 0 .../DataMapper/EntityAssembler.php | 0 .../DataMapper/SchemaMapper.php | 0 .../Event/EventTriggerInterface.php | 0 .../Event/EventTriggerService.php | 0 .../Exception/ModelNotFoundException.php | 0 .../Exception/NoCollectionNameException.php | 0 src/{Mongolid => }/Manager.php | 0 src/{Mongolid => }/Model/Attributes.php | 0 .../Model/AttributesAccessInterface.php | 0 src/{Mongolid => }/Model/DocumentEmbedder.php | 0 .../Model/PolymorphableInterface.php | 0 src/{Mongolid => }/Model/Relations.php | 0 src/{Mongolid => }/Schema/DynamicSchema.php | 0 .../Schema/HasSchemaInterface.php | 0 src/{Mongolid => }/Schema/Schema.php | 0 src/{Mongolid => }/Util/CacheComponent.php | 0 .../Util/CacheComponentInterface.php | 0 src/{Mongolid => }/Util/LocalDateTime.php | 0 src/{Mongolid => }/Util/ObjectIdUtils.php | 0 src/{Mongolid => }/Util/SequenceService.php | 0 tests/{Mongolid => }/ActiveRecordTest.php | 9 +- .../Connection/ConnectionTest.php | 2 +- tests/{Mongolid => }/Connection/PoolTest.php | 8 +- tests/Container/IocTest.php | 44 ++++++++ .../Cursor/CacheableCursorTest.php | 8 +- .../Cursor/CursorFactoryTest.php | 2 +- tests/{Mongolid => }/Cursor/CursorTest.php | 8 +- .../Cursor/EmbeddedCursorTest.php | 8 +- .../DataMapper/BulkWriteTest.php | 8 +- .../DataMapper/DataMapperTest.php | 21 +--- .../DataMapper/EntityAssemblerTest.php | 8 +- .../DataMapper/SchemaMapperTest.php | 8 +- tests/{Mongolid => }/DynamicSchemaTest.php | 8 +- .../Event/EventTriggerServiceTest.php | 8 +- tests/{Mongolid => }/ManagerTest.php | 7 +- tests/{Mongolid => }/Model/AttributesTest.php | 8 +- .../Model/DocumentEmbedderTest.php | 8 +- tests/{Mongolid => }/Model/RelationsTest.php | 8 +- tests/Mongolid/Container/IocTest.php | 100 ------------------ tests/{Mongolid => }/SchemaTest.php | 8 +- tests/TestCase.php | 33 ++++-- .../Util/CacheComponentTest.php | 7 +- .../{Mongolid => }/Util/LocalDateTimeTest.php | 8 +- .../{Mongolid => }/Util/ObjectIdUtilsTest.php | 2 +- .../Util/SequenceServiceTest.php | 8 +- 58 files changed, 111 insertions(+), 261 deletions(-) delete mode 100644 bootstrap/bootstrap.php rename src/{Mongolid => }/ActiveRecord.php (100%) rename src/{Mongolid => }/Connection/Connection.php (100%) rename src/{Mongolid => }/Connection/Pool.php (100%) rename src/{Mongolid => }/Container/Ioc.php (100%) rename src/{Mongolid => }/Cursor/CacheableCursor.php (100%) rename src/{Mongolid => }/Cursor/Cursor.php (100%) rename src/{Mongolid => }/Cursor/CursorFactory.php (100%) rename src/{Mongolid => }/Cursor/CursorInterface.php (100%) rename src/{Mongolid => }/Cursor/EmbeddedCursor.php (100%) rename src/{Mongolid => }/DataMapper/BulkWrite.php (100%) rename src/{Mongolid => }/DataMapper/DataMapper.php (100%) rename src/{Mongolid => }/DataMapper/EntityAssembler.php (100%) rename src/{Mongolid => }/DataMapper/SchemaMapper.php (100%) rename src/{Mongolid => }/Event/EventTriggerInterface.php (100%) rename src/{Mongolid => }/Event/EventTriggerService.php (100%) rename src/{Mongolid => }/Exception/ModelNotFoundException.php (100%) rename src/{Mongolid => }/Exception/NoCollectionNameException.php (100%) rename src/{Mongolid => }/Manager.php (100%) rename src/{Mongolid => }/Model/Attributes.php (100%) rename src/{Mongolid => }/Model/AttributesAccessInterface.php (100%) rename src/{Mongolid => }/Model/DocumentEmbedder.php (100%) rename src/{Mongolid => }/Model/PolymorphableInterface.php (100%) rename src/{Mongolid => }/Model/Relations.php (100%) rename src/{Mongolid => }/Schema/DynamicSchema.php (100%) rename src/{Mongolid => }/Schema/HasSchemaInterface.php (100%) rename src/{Mongolid => }/Schema/Schema.php (100%) rename src/{Mongolid => }/Util/CacheComponent.php (100%) rename src/{Mongolid => }/Util/CacheComponentInterface.php (100%) rename src/{Mongolid => }/Util/LocalDateTime.php (100%) rename src/{Mongolid => }/Util/ObjectIdUtils.php (100%) rename src/{Mongolid => }/Util/SequenceService.php (100%) rename tests/{Mongolid => }/ActiveRecordTest.php (99%) rename tests/{Mongolid => }/Connection/ConnectionTest.php (99%) rename tests/{Mongolid => }/Connection/PoolTest.php (93%) create mode 100644 tests/Container/IocTest.php rename tests/{Mongolid => }/Cursor/CacheableCursorTest.php (98%) rename tests/{Mongolid => }/Cursor/CursorFactoryTest.php (99%) rename tests/{Mongolid => }/Cursor/CursorTest.php (99%) rename tests/{Mongolid => }/Cursor/EmbeddedCursorTest.php (98%) rename tests/{Mongolid => }/DataMapper/BulkWriteTest.php (97%) rename tests/{Mongolid => }/DataMapper/DataMapperTest.php (98%) rename tests/{Mongolid => }/DataMapper/EntityAssemblerTest.php (99%) rename tests/{Mongolid => }/DataMapper/SchemaMapperTest.php (98%) rename tests/{Mongolid => }/DynamicSchemaTest.php (84%) rename tests/{Mongolid => }/Event/EventTriggerServiceTest.php (91%) rename tests/{Mongolid => }/ManagerTest.php (98%) rename tests/{Mongolid => }/Model/AttributesTest.php (98%) rename tests/{Mongolid => }/Model/DocumentEmbedderTest.php (98%) rename tests/{Mongolid => }/Model/RelationsTest.php (99%) delete mode 100644 tests/Mongolid/Container/IocTest.php rename tests/{Mongolid => }/SchemaTest.php (97%) rename tests/{Mongolid => }/Util/CacheComponentTest.php (94%) rename tests/{Mongolid => }/Util/LocalDateTimeTest.php (95%) rename tests/{Mongolid => }/Util/ObjectIdUtilsTest.php (97%) rename tests/{Mongolid => }/Util/SequenceServiceTest.php (96%) diff --git a/bootstrap/bootstrap.php b/bootstrap/bootstrap.php deleted file mode 100644 index 50031e64..00000000 --- a/bootstrap/bootstrap.php +++ /dev/null @@ -1,12 +0,0 @@ -=7.0", + "ext-mongodb": "*", "mongodb/mongodb": "^1.3", "illuminate/container": "^5.4" }, @@ -27,11 +28,13 @@ }, "autoload": { "psr-4": { - "Mongolid\\": "src/Mongolid" - }, - "classmap": [ - "tests/TestCase.php" - ] + "Mongolid\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Mongolid\\": "tests" + } }, "extra": { "branch-alias": { diff --git a/src/Mongolid/ActiveRecord.php b/src/ActiveRecord.php similarity index 100% rename from src/Mongolid/ActiveRecord.php rename to src/ActiveRecord.php diff --git a/src/Mongolid/Connection/Connection.php b/src/Connection/Connection.php similarity index 100% rename from src/Mongolid/Connection/Connection.php rename to src/Connection/Connection.php diff --git a/src/Mongolid/Connection/Pool.php b/src/Connection/Pool.php similarity index 100% rename from src/Mongolid/Connection/Pool.php rename to src/Connection/Pool.php diff --git a/src/Mongolid/Container/Ioc.php b/src/Container/Ioc.php similarity index 100% rename from src/Mongolid/Container/Ioc.php rename to src/Container/Ioc.php diff --git a/src/Mongolid/Cursor/CacheableCursor.php b/src/Cursor/CacheableCursor.php similarity index 100% rename from src/Mongolid/Cursor/CacheableCursor.php rename to src/Cursor/CacheableCursor.php diff --git a/src/Mongolid/Cursor/Cursor.php b/src/Cursor/Cursor.php similarity index 100% rename from src/Mongolid/Cursor/Cursor.php rename to src/Cursor/Cursor.php diff --git a/src/Mongolid/Cursor/CursorFactory.php b/src/Cursor/CursorFactory.php similarity index 100% rename from src/Mongolid/Cursor/CursorFactory.php rename to src/Cursor/CursorFactory.php diff --git a/src/Mongolid/Cursor/CursorInterface.php b/src/Cursor/CursorInterface.php similarity index 100% rename from src/Mongolid/Cursor/CursorInterface.php rename to src/Cursor/CursorInterface.php diff --git a/src/Mongolid/Cursor/EmbeddedCursor.php b/src/Cursor/EmbeddedCursor.php similarity index 100% rename from src/Mongolid/Cursor/EmbeddedCursor.php rename to src/Cursor/EmbeddedCursor.php diff --git a/src/Mongolid/DataMapper/BulkWrite.php b/src/DataMapper/BulkWrite.php similarity index 100% rename from src/Mongolid/DataMapper/BulkWrite.php rename to src/DataMapper/BulkWrite.php diff --git a/src/Mongolid/DataMapper/DataMapper.php b/src/DataMapper/DataMapper.php similarity index 100% rename from src/Mongolid/DataMapper/DataMapper.php rename to src/DataMapper/DataMapper.php diff --git a/src/Mongolid/DataMapper/EntityAssembler.php b/src/DataMapper/EntityAssembler.php similarity index 100% rename from src/Mongolid/DataMapper/EntityAssembler.php rename to src/DataMapper/EntityAssembler.php diff --git a/src/Mongolid/DataMapper/SchemaMapper.php b/src/DataMapper/SchemaMapper.php similarity index 100% rename from src/Mongolid/DataMapper/SchemaMapper.php rename to src/DataMapper/SchemaMapper.php diff --git a/src/Mongolid/Event/EventTriggerInterface.php b/src/Event/EventTriggerInterface.php similarity index 100% rename from src/Mongolid/Event/EventTriggerInterface.php rename to src/Event/EventTriggerInterface.php diff --git a/src/Mongolid/Event/EventTriggerService.php b/src/Event/EventTriggerService.php similarity index 100% rename from src/Mongolid/Event/EventTriggerService.php rename to src/Event/EventTriggerService.php diff --git a/src/Mongolid/Exception/ModelNotFoundException.php b/src/Exception/ModelNotFoundException.php similarity index 100% rename from src/Mongolid/Exception/ModelNotFoundException.php rename to src/Exception/ModelNotFoundException.php diff --git a/src/Mongolid/Exception/NoCollectionNameException.php b/src/Exception/NoCollectionNameException.php similarity index 100% rename from src/Mongolid/Exception/NoCollectionNameException.php rename to src/Exception/NoCollectionNameException.php diff --git a/src/Mongolid/Manager.php b/src/Manager.php similarity index 100% rename from src/Mongolid/Manager.php rename to src/Manager.php diff --git a/src/Mongolid/Model/Attributes.php b/src/Model/Attributes.php similarity index 100% rename from src/Mongolid/Model/Attributes.php rename to src/Model/Attributes.php diff --git a/src/Mongolid/Model/AttributesAccessInterface.php b/src/Model/AttributesAccessInterface.php similarity index 100% rename from src/Mongolid/Model/AttributesAccessInterface.php rename to src/Model/AttributesAccessInterface.php diff --git a/src/Mongolid/Model/DocumentEmbedder.php b/src/Model/DocumentEmbedder.php similarity index 100% rename from src/Mongolid/Model/DocumentEmbedder.php rename to src/Model/DocumentEmbedder.php diff --git a/src/Mongolid/Model/PolymorphableInterface.php b/src/Model/PolymorphableInterface.php similarity index 100% rename from src/Mongolid/Model/PolymorphableInterface.php rename to src/Model/PolymorphableInterface.php diff --git a/src/Mongolid/Model/Relations.php b/src/Model/Relations.php similarity index 100% rename from src/Mongolid/Model/Relations.php rename to src/Model/Relations.php diff --git a/src/Mongolid/Schema/DynamicSchema.php b/src/Schema/DynamicSchema.php similarity index 100% rename from src/Mongolid/Schema/DynamicSchema.php rename to src/Schema/DynamicSchema.php diff --git a/src/Mongolid/Schema/HasSchemaInterface.php b/src/Schema/HasSchemaInterface.php similarity index 100% rename from src/Mongolid/Schema/HasSchemaInterface.php rename to src/Schema/HasSchemaInterface.php diff --git a/src/Mongolid/Schema/Schema.php b/src/Schema/Schema.php similarity index 100% rename from src/Mongolid/Schema/Schema.php rename to src/Schema/Schema.php diff --git a/src/Mongolid/Util/CacheComponent.php b/src/Util/CacheComponent.php similarity index 100% rename from src/Mongolid/Util/CacheComponent.php rename to src/Util/CacheComponent.php diff --git a/src/Mongolid/Util/CacheComponentInterface.php b/src/Util/CacheComponentInterface.php similarity index 100% rename from src/Mongolid/Util/CacheComponentInterface.php rename to src/Util/CacheComponentInterface.php diff --git a/src/Mongolid/Util/LocalDateTime.php b/src/Util/LocalDateTime.php similarity index 100% rename from src/Mongolid/Util/LocalDateTime.php rename to src/Util/LocalDateTime.php diff --git a/src/Mongolid/Util/ObjectIdUtils.php b/src/Util/ObjectIdUtils.php similarity index 100% rename from src/Mongolid/Util/ObjectIdUtils.php rename to src/Util/ObjectIdUtils.php diff --git a/src/Mongolid/Util/SequenceService.php b/src/Util/SequenceService.php similarity index 100% rename from src/Mongolid/Util/SequenceService.php rename to src/Util/SequenceService.php diff --git a/tests/Mongolid/ActiveRecordTest.php b/tests/ActiveRecordTest.php similarity index 99% rename from tests/Mongolid/ActiveRecordTest.php rename to tests/ActiveRecordTest.php index 02a40ee4..e526abf0 100644 --- a/tests/Mongolid/ActiveRecordTest.php +++ b/tests/ActiveRecordTest.php @@ -11,7 +11,7 @@ use Mongolid\Model\Relations; use Mongolid\Schema\Schema; use stdClass; -use TestCase; +use Mongolid\TestCase; class ActiveRecordTest extends TestCase { @@ -23,7 +23,7 @@ class ActiveRecordTest extends TestCase /** * {@inheritdoc} */ - public function setUp() + protected function setUp() { parent::setUp(); $this->entity = new class() extends ActiveRecord { @@ -33,11 +33,10 @@ public function setUp() /** * {@inheritdoc} */ - public function tearDown() + protected function tearDown() { - parent::tearDown(); - m::close(); unset($this->entity); + parent::tearDown(); } public function testShouldHaveCorrectPropertiesByDefault() diff --git a/tests/Mongolid/Connection/ConnectionTest.php b/tests/Connection/ConnectionTest.php similarity index 99% rename from tests/Mongolid/Connection/ConnectionTest.php rename to tests/Connection/ConnectionTest.php index fb96d8fa..4557929f 100644 --- a/tests/Mongolid/Connection/ConnectionTest.php +++ b/tests/Connection/ConnectionTest.php @@ -4,7 +4,7 @@ use MongoDB\Client; use MongoDB\Driver\Manager; -use TestCase; +use Mongolid\TestCase; class ConnectionTest extends TestCase { diff --git a/tests/Mongolid/Connection/PoolTest.php b/tests/Connection/PoolTest.php similarity index 93% rename from tests/Mongolid/Connection/PoolTest.php rename to tests/Connection/PoolTest.php index 2ffc83be..93b0d397 100644 --- a/tests/Mongolid/Connection/PoolTest.php +++ b/tests/Connection/PoolTest.php @@ -3,16 +3,10 @@ namespace Mongolid\Connection; use Mockery as m; -use TestCase; +use Mongolid\TestCase; class PoolTest extends TestCase { - public function tearDown() - { - parent::tearDown(); - m::close(); - } - public function testShouldGetAConnectionFromPoolIfContainsAny() { // Arrange diff --git a/tests/Container/IocTest.php b/tests/Container/IocTest.php new file mode 100644 index 00000000..5546b884 --- /dev/null +++ b/tests/Container/IocTest.php @@ -0,0 +1,44 @@ +flush(); + + parent::tearDown(); + } + + public function testShouldCallMethodsProperlyWithNoArguments() + { + $container = m::mock(Container::class); + + $container->expects() + ->method() + ->andReturn(true); + + Ioc::setContainer($container); + + Ioc::method(); + } + + public function testShouldCallMethodsProperlyWithArguments() + { + $container = m::mock(Container::class); + + $container->expects() + ->method(1, 2, 3) + ->andReturn(true); + + Ioc::setContainer($container); + + Ioc::method(1, 2, 3); + } +} diff --git a/tests/Mongolid/Cursor/CacheableCursorTest.php b/tests/Cursor/CacheableCursorTest.php similarity index 98% rename from tests/Mongolid/Cursor/CacheableCursorTest.php rename to tests/Cursor/CacheableCursorTest.php index cca8eaa8..ddd23236 100644 --- a/tests/Mongolid/Cursor/CacheableCursorTest.php +++ b/tests/Cursor/CacheableCursorTest.php @@ -10,16 +10,10 @@ use Mongolid\Container\Ioc; use Mongolid\Schema\Schema; use Mongolid\Util\CacheComponentInterface; -use TestCase; +use Mongolid\TestCase; class CacheableCursorTest extends TestCase { - public function tearDown() - { - parent::tearDown(); - m::close(); - } - public function testShouldGetCursorFromPreviousIteration() { // Arrange diff --git a/tests/Mongolid/Cursor/CursorFactoryTest.php b/tests/Cursor/CursorFactoryTest.php similarity index 99% rename from tests/Mongolid/Cursor/CursorFactoryTest.php rename to tests/Cursor/CursorFactoryTest.php index e902a45c..ffbd65cc 100644 --- a/tests/Mongolid/Cursor/CursorFactoryTest.php +++ b/tests/Cursor/CursorFactoryTest.php @@ -5,7 +5,7 @@ use Mockery as m; use MongoDB\Collection; use Mongolid\Schema\Schema; -use TestCase; +use Mongolid\TestCase; class CursorFactoryTest extends TestCase { diff --git a/tests/Mongolid/Cursor/CursorTest.php b/tests/Cursor/CursorTest.php similarity index 99% rename from tests/Mongolid/Cursor/CursorTest.php rename to tests/Cursor/CursorTest.php index 6ff26933..46adce94 100644 --- a/tests/Mongolid/Cursor/CursorTest.php +++ b/tests/Cursor/CursorTest.php @@ -16,17 +16,11 @@ use Mongolid\Schema\DynamicSchema; use Mongolid\Schema\Schema; use stdClass; -use TestCase; +use Mongolid\TestCase; use Traversable; class CursorTest extends TestCase { - public function tearDown() - { - parent::tearDown(); - m::close(); - } - public function testShouldLimitDocumentQuantity() { // Arrange diff --git a/tests/Mongolid/Cursor/EmbeddedCursorTest.php b/tests/Cursor/EmbeddedCursorTest.php similarity index 98% rename from tests/Mongolid/Cursor/EmbeddedCursorTest.php rename to tests/Cursor/EmbeddedCursorTest.php index 1488cb1f..34377cad 100644 --- a/tests/Mongolid/Cursor/EmbeddedCursorTest.php +++ b/tests/Cursor/EmbeddedCursorTest.php @@ -6,16 +6,10 @@ use Mongolid\ActiveRecord; use Mongolid\Model\PolymorphableInterface; use stdClass; -use TestCase; +use Mongolid\TestCase; class EmbeddedCursorTest extends TestCase { - public function tearDown() - { - parent::tearDown(); - m::close(); - } - public function testShouldLimitDocumentQuantity() { // Arrange diff --git a/tests/Mongolid/DataMapper/BulkWriteTest.php b/tests/DataMapper/BulkWriteTest.php similarity index 97% rename from tests/Mongolid/DataMapper/BulkWriteTest.php rename to tests/DataMapper/BulkWriteTest.php index fd6a2358..312cfae3 100644 --- a/tests/Mongolid/DataMapper/BulkWriteTest.php +++ b/tests/DataMapper/BulkWriteTest.php @@ -11,16 +11,10 @@ use Mongolid\Manager; use Mongolid\Schema\HasSchemaInterface; use Mongolid\Schema\Schema; -use TestCase; +use Mongolid\TestCase; class BulkWriteTest extends TestCase { - protected function tearDown() - { - m::close(); - parent::tearDown(); - } - public function testShouldConstructBulkWriteObject() { // Arrange diff --git a/tests/Mongolid/DataMapper/DataMapperTest.php b/tests/DataMapper/DataMapperTest.php similarity index 98% rename from tests/Mongolid/DataMapper/DataMapperTest.php rename to tests/DataMapper/DataMapperTest.php index e157e2c8..c0b55a0e 100644 --- a/tests/Mongolid/DataMapper/DataMapperTest.php +++ b/tests/DataMapper/DataMapperTest.php @@ -15,22 +15,10 @@ use Mongolid\Model\AttributesAccessInterface; use Mongolid\Schema\Schema; use stdClass; -use TestCase; +use Mongolid\TestCase; class DataMapperTest extends TestCase { - /** - * @var m\MockInterface|EventTriggerService - */ - protected $eventService; - - public function tearDown() - { - unset($this->eventService); - parent::tearDown(); - m::close(); - } - public function testShouldBeAbleToConstructWithSchema() { // Arrange @@ -807,12 +795,11 @@ public function testPrepareProjectionShouldThrownAnException() protected function getEventService() { - if (!($this->eventService ?? false)) { - $this->eventService = m::mock(EventTriggerService::class); - Ioc::instance(EventTriggerService::class, $this->eventService); + if (!Ioc::has(EventTriggerService::class)) { + Ioc::instance(EventTriggerService::class, m::mock(EventTriggerService::class)); } - return $this->eventService; + return Ioc::make(EventTriggerService::class); } protected function expectEventToBeFired($event, $entity, bool $halt, $return = true) diff --git a/tests/Mongolid/DataMapper/EntityAssemblerTest.php b/tests/DataMapper/EntityAssemblerTest.php similarity index 99% rename from tests/Mongolid/DataMapper/EntityAssemblerTest.php rename to tests/DataMapper/EntityAssemblerTest.php index b41b9f62..2913c276 100644 --- a/tests/Mongolid/DataMapper/EntityAssemblerTest.php +++ b/tests/DataMapper/EntityAssemblerTest.php @@ -9,16 +9,10 @@ use Mongolid\Model\AttributesAccessInterface; use Mongolid\Model\PolymorphableInterface; use Mongolid\Schema\Schema; -use TestCase; +use Mongolid\TestCase; class EntityAssemblerTest extends TestCase { - public function tearDown() - { - parent::tearDown(); - m::close(); - } - /** * @dataProvider EntityAssemblerFixture */ diff --git a/tests/Mongolid/DataMapper/SchemaMapperTest.php b/tests/DataMapper/SchemaMapperTest.php similarity index 98% rename from tests/Mongolid/DataMapper/SchemaMapperTest.php rename to tests/DataMapper/SchemaMapperTest.php index 5dd571af..bf206f35 100644 --- a/tests/Mongolid/DataMapper/SchemaMapperTest.php +++ b/tests/DataMapper/SchemaMapperTest.php @@ -5,16 +5,10 @@ use Mockery as m; use Mongolid\Container\Ioc; use Mongolid\Schema\Schema; -use TestCase; +use Mongolid\TestCase; class SchemaMapperTest extends TestCase { - public function tearDown() - { - parent::tearDown(); - m::close(); - } - public function testShouldMapToFieldsOfSchema() { // Arrange diff --git a/tests/Mongolid/DynamicSchemaTest.php b/tests/DynamicSchemaTest.php similarity index 84% rename from tests/Mongolid/DynamicSchemaTest.php rename to tests/DynamicSchemaTest.php index bd3cc9ac..210c17fc 100644 --- a/tests/Mongolid/DynamicSchemaTest.php +++ b/tests/DynamicSchemaTest.php @@ -5,16 +5,10 @@ use Mockery as m; use Mongolid\Schema\DynamicSchema; use Mongolid\Schema\Schema; -use TestCase; +use Mongolid\TestCase; class DynamicSchemaTest extends TestCase { - public function tearDown() - { - parent::tearDown(); - m::close(); - } - public function testShouldExtendSchema() { // Arrange diff --git a/tests/Mongolid/Event/EventTriggerServiceTest.php b/tests/Event/EventTriggerServiceTest.php similarity index 91% rename from tests/Mongolid/Event/EventTriggerServiceTest.php rename to tests/Event/EventTriggerServiceTest.php index 81ee37b9..c83b99f7 100644 --- a/tests/Mongolid/Event/EventTriggerServiceTest.php +++ b/tests/Event/EventTriggerServiceTest.php @@ -3,16 +3,10 @@ namespace Mongolid\Event; use Mockery as m; -use TestCase; +use Mongolid\TestCase; class EventTriggerServiceTest extends TestCase { - public function tearDown() - { - parent::tearDown(); - m::close(); - } - public function testShouldSendTheEventsToTheExternalDispatcher() { // Arrange diff --git a/tests/Mongolid/ManagerTest.php b/tests/ManagerTest.php similarity index 98% rename from tests/Mongolid/ManagerTest.php rename to tests/ManagerTest.php index 600ceda3..af2c1341 100644 --- a/tests/Mongolid/ManagerTest.php +++ b/tests/ManagerTest.php @@ -13,15 +13,14 @@ use Mongolid\Event\EventTriggerService; use Mongolid\Schema\Schema; use Mongolid\Util\CacheComponentInterface; -use TestCase; +use Mongolid\TestCase; class ManagerTest extends TestCase { - public function tearDown() + protected function tearDown() { - parent::tearDown(); - m::close(); $this->setProtected(Manager::class, 'singleton', null); + parent::tearDown(); } public function testShouldAddAndGetConnection() diff --git a/tests/Mongolid/Model/AttributesTest.php b/tests/Model/AttributesTest.php similarity index 98% rename from tests/Mongolid/Model/AttributesTest.php rename to tests/Model/AttributesTest.php index 2acc1998..b8125d4a 100644 --- a/tests/Mongolid/Model/AttributesTest.php +++ b/tests/Model/AttributesTest.php @@ -4,16 +4,10 @@ use Mockery as m; use stdClass; -use TestCase; +use Mongolid\TestCase; class AttributesTest extends TestCase { - public function tearDown() - { - parent::tearDown(); - m::close(); - } - public function testShouldHaveDynamicSetters() { // Arrange diff --git a/tests/Mongolid/Model/DocumentEmbedderTest.php b/tests/Model/DocumentEmbedderTest.php similarity index 98% rename from tests/Mongolid/Model/DocumentEmbedderTest.php rename to tests/Model/DocumentEmbedderTest.php index c7f1825c..3be09ec5 100644 --- a/tests/Mongolid/Model/DocumentEmbedderTest.php +++ b/tests/Model/DocumentEmbedderTest.php @@ -6,16 +6,10 @@ use Mockery\Matcher\Any; use MongoDB\BSON\ObjectID; use stdClass; -use TestCase; +use Mongolid\TestCase; class DocumentEmbedderTest extends TestCase { - public function tearDown() - { - parent::tearDown(); - m::close(); - } - /** * @dataProvider getEmbedOptions */ diff --git a/tests/Mongolid/Model/RelationsTest.php b/tests/Model/RelationsTest.php similarity index 99% rename from tests/Mongolid/Model/RelationsTest.php rename to tests/Model/RelationsTest.php index 4e81c798..16c0ecce 100644 --- a/tests/Mongolid/Model/RelationsTest.php +++ b/tests/Model/RelationsTest.php @@ -11,16 +11,10 @@ use Mongolid\Cursor\EmbeddedCursor; use Mongolid\DataMapper\DataMapper; use Mongolid\Schema\Schema; -use TestCase; +use Mongolid\TestCase; class RelationsTest extends TestCase { - public function tearDown() - { - parent::tearDown(); - m::close(); - } - /** * @dataProvider referenceScenarios */ diff --git a/tests/Mongolid/Container/IocTest.php b/tests/Mongolid/Container/IocTest.php deleted file mode 100644 index 1a5f8097..00000000 --- a/tests/Mongolid/Container/IocTest.php +++ /dev/null @@ -1,100 +0,0 @@ -shouldReceive('method') - ->once() - ->with() - ->andReturn(true); - - Ioc::setContainer($container); - - Ioc::method(); - } - - public function testShouldCallMethodsPropertlywithOneArgument() - { - $container = m::mock(Container::class); - - $container->shouldReceive('method') - ->once() - ->with(1) - ->andReturn(true); - - Ioc::setContainer($container); - - Ioc::method(1); - } - - public function testShouldCallMethodsPropertlywithTwoArgument() - { - $container = m::mock(Container::class); - - $container->shouldReceive('method') - ->once() - ->with(1, 2) - ->andReturn(true); - - Ioc::setContainer($container); - - Ioc::method(1, 2); - } - - public function testShouldCallMethodsPropertlywithThreeArgument() - { - $container = m::mock(Container::class); - - $container->shouldReceive('method') - ->once() - ->with(1, 2, 3) - ->andReturn(true); - - Ioc::setContainer($container); - - Ioc::method(1, 2, 3); - } - - public function testShouldCallMethodsPropertlywithFourArgument() - { - $container = m::mock(Container::class); - - $container->shouldReceive('method') - ->once() - ->with(1, 2, 3, 4) - ->andReturn(true); - - Ioc::setContainer($container); - - Ioc::method(1, 2, 3, 4); - } - - public function testShouldCallMethodsPropertlywithFiveOrMoreArgument() - { - $container = m::mock(Container::class); - - $container->shouldReceive('method') - ->once() - ->with(1, 2, 3, 4, 5) - ->andReturn(true); - - Ioc::setContainer($container); - - Ioc::method(1, 2, 3, 4, 5); - } -} diff --git a/tests/Mongolid/SchemaTest.php b/tests/SchemaTest.php similarity index 97% rename from tests/Mongolid/SchemaTest.php rename to tests/SchemaTest.php index addbdfb0..91728bb3 100644 --- a/tests/Mongolid/SchemaTest.php +++ b/tests/SchemaTest.php @@ -8,16 +8,10 @@ use Mongolid\Container\Ioc; use Mongolid\Schema\Schema; use Mongolid\Util\SequenceService; -use TestCase; +use Mongolid\TestCase; class SchemaTest extends TestCase { - public function tearDown() - { - parent::tearDown(); - m::close(); - } - public function testShouldNotBeDynamicByDefault() { // Arrange diff --git a/tests/TestCase.php b/tests/TestCase.php index 212bdf3c..cff29277 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -1,15 +1,26 @@ $value) { if (is_object($value)) { - $this->assertInstanceOf(get_class($value), $query[$key], 'Type of an object within the query is not equals'); + $this->assertInstanceOf( + get_class($value), + $query[$key], + 'Type of an object within the query is not equals' + ); if (method_exists($value, '__toString')) { - $this->assertEquals((string) $expectedQuery[$key], (string) $query[$key], 'Object within the query is not equals'); + $this->assertEquals( + (string) $expectedQuery[$key], + (string) $query[$key], + 'Object within the query is not equals' + ); } } diff --git a/tests/Mongolid/Util/CacheComponentTest.php b/tests/Util/CacheComponentTest.php similarity index 94% rename from tests/Mongolid/Util/CacheComponentTest.php rename to tests/Util/CacheComponentTest.php index 1c8ee6eb..63c8eb0d 100644 --- a/tests/Mongolid/Util/CacheComponentTest.php +++ b/tests/Util/CacheComponentTest.php @@ -3,7 +3,7 @@ namespace Mongolid\Util; use Mockery as m; -use TestCase; +use Mongolid\TestCase; class CacheComponentTest extends TestCase { @@ -14,11 +14,6 @@ class CacheComponentTest extends TestCase */ public $time = 1466710000; - public function tearDown() - { - parent::tearDown(); - m::close(); - } public function testShouldImplementCacheComponentInterface() { diff --git a/tests/Mongolid/Util/LocalDateTimeTest.php b/tests/Util/LocalDateTimeTest.php similarity index 95% rename from tests/Mongolid/Util/LocalDateTimeTest.php rename to tests/Util/LocalDateTimeTest.php index 50e3482b..12b3d4ee 100644 --- a/tests/Mongolid/Util/LocalDateTimeTest.php +++ b/tests/Util/LocalDateTimeTest.php @@ -5,7 +5,7 @@ use DateTime; use DateTimeZone; use MongoDB\BSON\UTCDateTime; -use PHPUnit\Framework\TestCase as TestCase; +use Mongolid\TestCase; class LocalDateTimeTest extends TestCase { @@ -22,7 +22,7 @@ class LocalDateTimeTest extends TestCase /** * {@inheritdoc} */ - public function setUp() + protected function setUp() { parent::setUp(); @@ -35,10 +35,10 @@ public function setUp() /** * {@inheritdoc} */ - public function tearDown() + protected function tearDown() { - parent::tearDown(); unset($this->date, $this->format); + parent::tearDown(); } public function testGetShouldRetrievesDateUsingTimezone() diff --git a/tests/Mongolid/Util/ObjectIdUtilsTest.php b/tests/Util/ObjectIdUtilsTest.php similarity index 97% rename from tests/Mongolid/Util/ObjectIdUtilsTest.php rename to tests/Util/ObjectIdUtilsTest.php index 19bd18fe..69567604 100644 --- a/tests/Mongolid/Util/ObjectIdUtilsTest.php +++ b/tests/Util/ObjectIdUtilsTest.php @@ -3,7 +3,7 @@ namespace Mongolid\Util; use MongoDB\BSON\ObjectID; -use TestCase; +use Mongolid\TestCase; class ObjectIdUtilsTest extends TestCase { diff --git a/tests/Mongolid/Util/SequenceServiceTest.php b/tests/Util/SequenceServiceTest.php similarity index 96% rename from tests/Mongolid/Util/SequenceServiceTest.php rename to tests/Util/SequenceServiceTest.php index 6723f694..5dcf479b 100644 --- a/tests/Mongolid/Util/SequenceServiceTest.php +++ b/tests/Util/SequenceServiceTest.php @@ -6,16 +6,10 @@ use MongoDB\Collection; use Mongolid\Connection\Connection; use Mongolid\Connection\Pool; -use TestCase; +use Mongolid\TestCase; class SequenceServiceTest extends TestCase { - public function tearDown() - { - parent::tearDown(); - m::close(); - } - /** * @dataProvider sequenceScenarios */ From b82b881dca25f6c780da52809ac1c4288258c034 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Thu, 18 Oct 2018 17:36:24 -0300 Subject: [PATCH 003/116] Ignore some files when exporting package --- .gitattributes | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..a6a483b2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,15 @@ +# Path-based git attributes +# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html + +# Ignore all test and documentation with "export-ignore". +/.gitattributes export-ignore +/.gitignore export-ignore +/.coveralls.yml export-ignore +/.travis.yml export-ignore +/.styleci.yml export-ignore +/phpunit.xml export-ignore +/phpunit.xml.dist export-ignore +/tests export-ignore +/docs export-ignore +/CODE_OF_CONDUCT.md export-ignore +/sami.php export-ignore From 3f93cfe8a4aa7a2fe9f70b9cc3d952ae9e4a759b Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Thu, 18 Oct 2018 18:10:36 -0300 Subject: [PATCH 004/116] Require PHP >= 7.1 --- .travis.yml | 1 - composer.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6947b2b7..8feab2a6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: php php: - - 7.0 - 7.1 - 7.2 diff --git a/composer.json b/composer.json index ea2a1938..8f5c4ead 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": ">=7.0", + "php": ">=7.1", "mongodb/mongodb": "^1.3", "illuminate/container": "^5.4" }, From 372de06b5cc0d8a2d203d8534d783376d3562efd Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Thu, 18 Oct 2018 18:33:02 -0300 Subject: [PATCH 005/116] Fix CS for new structure --- phpcs.xml | 8 ++------ tests/ActiveRecordTest.php | 1 - tests/Container/IocTest.php | 1 - tests/Cursor/CacheableCursorTest.php | 2 +- tests/Cursor/CursorTest.php | 2 +- tests/Cursor/EmbeddedCursorTest.php | 3 +-- tests/DataMapper/DataMapperTest.php | 2 +- tests/DynamicSchemaTest.php | 1 - tests/ManagerTest.php | 1 - tests/Model/AttributesTest.php | 3 +-- tests/Model/DocumentEmbedderTest.php | 2 +- tests/SchemaTest.php | 1 - tests/TestCase.php | 1 - 13 files changed, 8 insertions(+), 20 deletions(-) diff --git a/phpcs.xml b/phpcs.xml index e93ea39a..9965de2e 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -10,21 +10,17 @@ - bootstrap src tests - - tests/TestCase.php - - tests/Mongolid/DataMapper/EntityAssemblerTest.php + tests/DataMapper/EntityAssemblerTest.php - tests/Mongolid/DataMapper/EntityAssemblerTest.php + tests/DataMapper/EntityAssemblerTest.php diff --git a/tests/ActiveRecordTest.php b/tests/ActiveRecordTest.php index 7a92148f..b5f84f2e 100644 --- a/tests/ActiveRecordTest.php +++ b/tests/ActiveRecordTest.php @@ -11,7 +11,6 @@ use Mongolid\Model\Relations; use Mongolid\Schema\Schema; use stdClass; -use Mongolid\TestCase; class ActiveRecordTest extends TestCase { diff --git a/tests/Container/IocTest.php b/tests/Container/IocTest.php index 5546b884..2cde5ce8 100644 --- a/tests/Container/IocTest.php +++ b/tests/Container/IocTest.php @@ -1,5 +1,4 @@ Date: Thu, 18 Oct 2018 18:45:28 -0300 Subject: [PATCH 006/116] Update readme, license and composer.json files --- LICENSE | 2 +- README.md | 43 +++++-------------------------------------- composer.json | 8 ++++++-- 3 files changed, 12 insertions(+), 41 deletions(-) diff --git a/LICENSE b/LICENSE index 613682dd..bedd0a02 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2013-2017 Leroy Merlin Brasil +Copyright (c) Leroy Merlin Brazil Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index b7632949..3c91bc34 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,18 @@ # Mongolid ODM for MongoDB (PHP7) -> Easy, powerful and ultrafast ODM for PHP7 build on top of the [new mongodb driver](https://docs.mongodb.org/ecosystem/drivers/php/). +> Easy, powerful and ultrafast ODM for PHP 7.1+ build on top of the [new mongodb driver](https://docs.mongodb.org/ecosystem/drivers/php/). ![Mongolid](https://user-images.githubusercontent.com/1991286/28967747-fe5c258a-78f2-11e7-91c7-8850ffb32004.png) Mongolid supports both **ActiveRecord** and **DataMapper** patterns. **You choose! (:** [![Build Status](https://travis-ci.org/leroy-merlin-br/mongolid.svg?branch=master)](https://travis-ci.org/leroy-merlin-br/mongolid) -[![Codacy Badge](https://api.codacy.com/project/badge/Grade/cc45e93bb0d0413d9e0355c7377d4d33)](https://www.codacy.com/app/zizaco/mongolid?utm_source=github.com&utm_medium=referral&utm_content=leroy-merlin-br/mongolid&utm_campaign=Badge_Grade) -[![StyleCI](https://styleci.io/repos/9799450/shield?branch=master)](https://styleci.io/repos/9799450) [![Coverage Status](https://coveralls.io/repos/github/leroy-merlin-br/mongolid/badge.svg?branch=master)](https://coveralls.io/github/leroy-merlin-br/mongolid?branch=master) [![Latest Stable Version](https://poser.pugx.org/leroy-merlin-br/mongolid/v/stable)](https://packagist.org/packages/leroy-merlin-br/mongolid) [![Total Downloads](https://poser.pugx.org/leroy-merlin-br/mongolid/downloads)](https://packagist.org/packages/leroy-merlin-br/mongolid) [![Latest Unstable Version](https://poser.pugx.org/leroy-merlin-br/mongolid/v/unstable)](https://packagist.org/packages/leroy-merlin-br/mongolid) [![License](https://poser.pugx.org/leroy-merlin-br/mongolid/license)](https://packagist.org/packages/leroy-merlin-br/mongolid) -[![SensioLabsInsight](https://insight.sensiolabs.com/projects/25636a94-9a5d-4438-bd5e-9f9694104529/small.png)](https://insight.sensiolabs.com/projects/25636a94-9a5d-4438-bd5e-9f9694104529) - ## Introduction @@ -35,45 +31,16 @@ $ composer require leroy-merlin-br/mongolid ### Requirements -- PHP**7** +- PHP **7.1** - [MongoDB Driver](http://php.net/manual/en/set.mongodb.php) -> **Note:** If you are looking for the old PHP 5.x version, head to the [v0.8 branch](https://github.com/leroy-merlin-br/mongolid/tree/v0.8-dev). - ## [Read the Docs: leroy-merlin-br.github.com/mongolid](http://leroy-merlin-br.github.com/mongolid) -[![Mongolid Docs](https://dl.dropboxusercontent.com/u/12506137/libs_bundles/MongolidDocs.png)](http://leroy-merlin-br.github.com/mongolid) - - -## Troubleshooting - -**"PHP Fatal error: Class 'MongoDB\Client' not found in ..."** - -The `MongoDB\Client` class is contained in the [**new** MongoDB driver](http://pecl.php.net/package/mongodb) for PHP. [Here is an installation guide](http://www.php.net/manual/en/mongodb.installation.php). The driver is a PHP extension written in C and maintained by [MongoDB](https://mongodb.com). Mongolid and most other MongoDB PHP libraries utilize it in order to be fast and reliable. - -**"Class 'MongoDB\Client' not found in ..." in CLI persists even with MongoDB driver installed.** - -Make sure that the **php.ini** file used in the CLI environment includes the MongoDB extension. In some systems, the default PHP installation uses different **.ini** files for the web and CLI environments. - -Run `php -i | grep 'Configuration File'` in a terminal to check the **.ini** that is being used. - -To check if PHP in the CLI environment is importing the driver properly run `php -i | grep -i 'mongo'` in your terminal. You should get output similar to: - -``` -$ php -i | grep -i 'mongo' -MongoDB support => enabled -MongoDB extension version => 1.2.8 -MongoDB extension stability => stable -libmongoc bundled version => 1.5.5 -``` - -**"This package requires php >=7.0 but your PHP version (X.X.X) does not satisfy that requirement."** - -The new (and improved) version 2.0 of Mongolid requires php7. If you are looking for the old PHP 5.x version, head to the [v0.8 branch](https://github.com/leroy-merlin-br/mongolid/tree/v0.8-dev). +[![Mongolid Docs](https://user-images.githubusercontent.com/1991286/28967747-fe5c258a-78f2-11e7-91c7-8850ffb32004.png)](http://leroy-merlin-br.github.com/mongolid) ## License -Mongolid is free software distributed under the terms of the [MIT license](http://opensource.org/licenses/MIT) +Mongolid is free software distributed under the terms of the [MIT license](LICENSE). ## Additional information @@ -82,4 +49,4 @@ Mongolid was proudly built by the [Leroy Merlin Brazil](https://github.com/leroy Any questions, feel free to contact us. -Any issues, please [report here](https://github.com/Zizaco/mongolid) +Any issues, please [report here](https://github.com/leroy-merlin-br/mongolid). diff --git a/composer.json b/composer.json index bd4154ef..e7dd7066 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "leroy-merlin-br/mongolid", "description": "Easy, powerful and ultrafast ODM for PHP and MongoDB.", - "keywords": ["odm","mongodb","nosql"], + "keywords": ["odm", "mongodb", "mongo", "nosql", "active record", "data mapper", "laravel"], "license": "MIT", "authors": [ { @@ -11,6 +11,10 @@ { "name": "Guilherme Guitte", "email": "guilherme.guitte@gmail.com" + }, + { + "name": "Boitatá", + "email": "boitata@leroymerlin.com.br" } ], "require": { @@ -38,7 +42,7 @@ }, "extra": { "branch-alias": { - "dev-master": "v2.1.x-dev" + "dev-master": "v3.0.x-dev" } } } From 5a94fa17d95202978bf03b0aafe8b0d619a0fe01 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Thu, 18 Oct 2018 18:49:38 -0300 Subject: [PATCH 007/116] Fix Schema tests namespace --- tests/{ => Schema}/DynamicSchemaTest.php | 5 ++--- tests/{ => Schema}/SchemaTest.php | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) rename tests/{ => Schema}/DynamicSchemaTest.php (85%) rename tests/{ => Schema}/SchemaTest.php (98%) diff --git a/tests/DynamicSchemaTest.php b/tests/Schema/DynamicSchemaTest.php similarity index 85% rename from tests/DynamicSchemaTest.php rename to tests/Schema/DynamicSchemaTest.php index 1c84e8ee..02c5a758 100644 --- a/tests/DynamicSchemaTest.php +++ b/tests/Schema/DynamicSchemaTest.php @@ -1,9 +1,8 @@ Date: Thu, 18 Oct 2018 19:22:39 -0300 Subject: [PATCH 008/116] Use new mockery syntax --- tests/ActiveRecordTest.php | 107 +++++----- tests/Connection/PoolTest.php | 21 +- tests/Cursor/CacheableCursorTest.php | 68 +++--- tests/Cursor/CursorTest.php | 106 ++++++---- tests/DataMapper/BulkWriteTest.php | 62 +++--- tests/DataMapper/DataMapperTest.php | 269 +++++++++++------------- tests/DataMapper/SchemaMapperTest.php | 20 +- tests/Event/EventTriggerServiceTest.php | 8 +- tests/ManagerTest.php | 7 +- tests/Model/RelationsTest.php | 29 ++- tests/Schema/SchemaTest.php | 9 +- tests/Util/CacheComponentTest.php | 3 +- tests/Util/SequenceServiceTest.php | 16 +- 13 files changed, 367 insertions(+), 358 deletions(-) diff --git a/tests/ActiveRecordTest.php b/tests/ActiveRecordTest.php index b5f84f2e..3c3da97d 100644 --- a/tests/ActiveRecordTest.php +++ b/tests/ActiveRecordTest.php @@ -69,18 +69,19 @@ public function testShouldSave() $dataMapper = m::mock(); // Act - $entity->shouldReceive('getDataMapper') + $entity->expects() + ->getDataMapper() ->andReturn($dataMapper); - $entity->shouldReceive('getCollectionName') + $entity->expects() + ->getCollectionName() ->andReturn('mongolid'); - $entity->shouldReceive('syncOriginalAttributes') - ->once(); + $entity->expects() + ->syncOriginalAttributes(); - $dataMapper->shouldReceive('save') - ->once() - ->with($entity, ['writeConcern' => new WriteConcern(1)]) + $dataMapper->expects() + ->save($entity, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); // Assert @@ -94,18 +95,19 @@ public function testShouldInsert() $dataMapper = m::mock(); // Act - $entity->shouldReceive('getDataMapper') + $entity->expects() + ->getDataMapper() ->andReturn($dataMapper); - $entity->shouldReceive('getCollectionName') + $entity->expects() + ->getCollectionName() ->andReturn('mongolid'); - $entity->shouldReceive('syncOriginalAttributes') - ->once(); + $entity->expects() + ->syncOriginalAttributes(); - $dataMapper->shouldReceive('insert') - ->once() - ->with($entity, ['writeConcern' => new WriteConcern(1)]) + $dataMapper->expects() + ->insert($entity, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); // Assert @@ -119,18 +121,19 @@ public function testShouldUpdate() $dataMapper = m::mock(); // Act - $entity->shouldReceive('getDataMapper') + $entity->expects() + ->getDataMapper() ->andReturn($dataMapper); - $entity->shouldReceive('getCollectionName') + $entity->expects() + ->getCollectionName() ->andReturn('mongolid'); - $entity->shouldReceive('syncOriginalAttributes') - ->once(); + $entity->expects() + ->syncOriginalAttributes(); - $dataMapper->shouldReceive('update') - ->once() - ->with($entity, ['writeConcern' => new WriteConcern(1)]) + $dataMapper->expects() + ->update($entity, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); // Assert @@ -144,15 +147,16 @@ public function testShouldDelete() $dataMapper = m::mock(); // Act - $entity->shouldReceive('getDataMapper') + $entity->expects() + ->getDataMapper() ->andReturn($dataMapper); - $entity->shouldReceive('getCollectionName') + $entity->expects() + ->getCollectionName() ->andReturn('mongolid'); - $dataMapper->shouldReceive('delete') - ->once() - ->with($entity, ['writeConcern' => new WriteConcern(1)]) + $dataMapper->expects() + ->delete($entity, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); // Assert @@ -192,12 +196,12 @@ public function testShouldGetWithWhereQuery() // Act Ioc::instance(get_class($entity), $entity); - $entity->shouldReceive('getDataMapper') + $entity->expects() + ->getDataMapper() ->andReturn($dataMapper); - $dataMapper->shouldReceive('where') - ->once() - ->with($query, $projection, true) + $dataMapper->expects() + ->where($query, $projection, true) ->andReturn($cursor); // Assert @@ -215,11 +219,12 @@ public function testShouldGetAll() // Act Ioc::instance(get_class($entity), $entity); - $entity->shouldReceive('getDataMapper') + $entity->expects() + ->getDataMapper() ->andReturn($dataMapper); - $dataMapper->shouldReceive('all') - ->once() + $dataMapper->expects() + ->all() ->andReturn($cursor); // Assert @@ -238,12 +243,12 @@ public function testShouldGetFirstWithQuery() // Act Ioc::instance(get_class($entity), $entity); - $entity->shouldReceive('getDataMapper') + $entity->expects() + ->getDataMapper() ->andReturn($dataMapper); - $dataMapper->shouldReceive('first') - ->once() - ->with($query, $projection, true) + $dataMapper->expects() + ->first($query, $projection, true) ->andReturn($entity); // Assert @@ -262,12 +267,12 @@ public function testShouldGetFirstOrFail() // Act Ioc::instance(get_class($entity), $entity); - $entity->shouldReceive('getDataMapper') + $entity->expects() + ->getDataMapper() ->andReturn($dataMapper); - $dataMapper->shouldReceive('firstOrFail') - ->once() - ->with($query, $projection, true) + $dataMapper->expects() + ->firstOrFail($query, $projection, true) ->andReturn($entity); // Assert @@ -285,12 +290,12 @@ public function testShouldGetFirstOrNewAndReturnExistingModel() // Act Ioc::instance(get_class($entity), $entity); - $entity->shouldReceive('getDataMapper') + $entity->expects() + ->getDataMapper() ->andReturn($dataMapper); - $dataMapper->shouldReceive('first') - ->once() - ->with($id) + $dataMapper->expects() + ->first($id) ->andReturn($entity); // Assert @@ -308,12 +313,12 @@ public function testShouldGetFirstOrNewAndReturnNewModel() // Act Ioc::instance(get_class($entity), $entity); - $entity->shouldReceive('getDataMapper') + $entity->expects() + ->getDataMapper() ->andReturn($dataMapper); - $dataMapper->shouldReceive('first') - ->once() - ->with($id) + $dataMapper->expects() + ->first($id) ->andReturn(null); // Assert @@ -360,8 +365,8 @@ public function testShouldGetDataMapper() // Act $entity->shouldAllowMockingProtectedMethods(); - $entity->shouldReceive('getSchema') - ->once() + $entity->expects() + ->getSchema() ->andReturn($schema); // Assert diff --git a/tests/Connection/PoolTest.php b/tests/Connection/PoolTest.php index 781deeaf..9c589bed 100644 --- a/tests/Connection/PoolTest.php +++ b/tests/Connection/PoolTest.php @@ -15,13 +15,12 @@ public function testShouldGetAConnectionFromPoolIfContainsAny() $this->setProtected($pool, 'connections', $connQueue); // Act - $connQueue->shouldReceive('pop') - ->once() + $connQueue->expects() + ->pop() ->andReturn($connection); - $connQueue->shouldReceive('push') - ->once() - ->with($connection); + $connQueue->expects() + ->push($connection); // Assert $this->assertEquals($connection, $pool->getConnection()); @@ -35,11 +34,12 @@ public function testShouldGetNullConnectionFromPoolIfItsEmpty() $this->setProtected($pool, 'connections', $connQueue); // Act - $connQueue->shouldReceive('pop') - ->once() + $connQueue->expects() + ->pop() ->andReturn(null); - $connQueue->shouldReceive('push') + $connQueue->expects() + ->push() ->never(); // Assert @@ -55,9 +55,8 @@ public function testShouldAddConnectionToPool() $this->setProtected($pool, 'connections', $connQueue); // Act - $connQueue->shouldReceive('push') - ->once() - ->with($connection); + $connQueue->expects() + ->push($connection); // Assert $this->assertTrue($pool->addConnection($connection)); diff --git a/tests/Cursor/CacheableCursorTest.php b/tests/Cursor/CacheableCursorTest.php index 9c31b70b..3941a789 100644 --- a/tests/Cursor/CacheableCursorTest.php +++ b/tests/Cursor/CacheableCursorTest.php @@ -39,14 +39,14 @@ public function testShouldGetCursorFromCache() $cacheComponent = m::mock(CacheComponentInterface::class); // Act - $cursor->shouldReceive('generateCacheKey') + $cursor->expects() + ->generateCacheKey() ->andReturn('find:collection:123'); Ioc::instance(CacheComponentInterface::class, $cacheComponent); - $cacheComponent->shouldReceive('get') - ->once() - ->with('find:collection:123', null) + $cacheComponent->expects() + ->get('find:collection:123', null) ->andReturn($documentsFromCache); // Assert @@ -72,26 +72,26 @@ public function testShouldGetFromDatabaseWhenCacheFails() ); // Act - $cursor->shouldReceive('generateCacheKey') + $cursor->expects() + ->generateCacheKey() ->andReturn($cacheKey); Ioc::instance(CacheComponentInterface::class, $cacheComponent); - $cacheComponent->shouldReceive('get') - ->with($cacheKey, null) + $cacheComponent->expects() + ->get($cacheKey, null) ->andThrow( new ErrorException( sprintf('Unable to unserialize cache %s', $cacheKey) ) ); - $rawCollection->shouldReceive('find') - ->with([], ['limit' => 100]) + $rawCollection->expects() + ->find([], ['limit' => 100]) ->andReturn(new ArrayIterator($documentsFromDb)); - $cacheComponent->shouldReceive('put') - ->once() - ->with($cacheKey, $documentsFromDb, m::any()); + $cacheComponent->expects() + ->put($cacheKey, $documentsFromDb, m::any()); // Assert $this->assertEquals( @@ -115,22 +115,22 @@ public function testShouldGetCursorFromDatabaseAndCacheForLater() ); // Act - $cursor->shouldReceive('generateCacheKey') + $cursor->expects() + ->generateCacheKey() ->andReturn('find:collection:123'); Ioc::instance(CacheComponentInterface::class, $cacheComponent); - $cacheComponent->shouldReceive('get') - ->with('find:collection:123', null) + $cacheComponent->expects() + ->get('find:collection:123', null) ->andReturn(null); - $rawCollection->shouldReceive('find') - ->with([], ['limit' => 100]) + $rawCollection->expects() + ->find([], ['limit' => 100]) ->andReturn(new ArrayIterator($documentsFromDb)); - $cacheComponent->shouldReceive('put') - ->once() - ->with('find:collection:123', $documentsFromDb, m::any()); + $cacheComponent->expects() + ->put('find:collection:123', $documentsFromDb, m::any()); // Assert $this->assertEquals( @@ -160,20 +160,22 @@ public function testShouldGetOriginalCursorFromDatabaseAfterTheDocumentLimit() ); // Act - $cursor->shouldReceive('generateCacheKey') + $cursor->expects() + ->generateCacheKey() ->never(); Ioc::instance(CacheComponentInterface::class, $cacheComponent); - $cacheComponent->shouldReceive('get') - ->with('find:collection:123', null) + $cacheComponent->expects() + ->get('find:collection:123', null) ->never(); - $rawCollection->shouldReceive('find') - ->with([], ['skip' => CacheableCursor::DOCUMENT_LIMIT, 'limit' => 49]) + $rawCollection->expects() + ->find([], ['skip' => CacheableCursor::DOCUMENT_LIMIT, 'limit' => 49]) ->andReturn(new ArrayIterator($documentsFromDb)); - $cacheComponent->shouldReceive('put') + $cacheComponent->expects() + ->put() ->never(); // Assert @@ -189,7 +191,8 @@ public function testShouldGenerateUniqueCacheKey() $cursor = $this->getCachableCursor(null, null, 'find', [['color' => 'red']]); // Act - $cursor->shouldReceive('generateCacheKey') + $cursor->expects() + ->generateCacheKey() ->passthru(); $expectedCacheKey = sprintf( @@ -220,9 +223,13 @@ protected function getCachableCursor( if (!$collection) { $collection = m::mock(Collection::class); - $collection->shouldReceive('getNamespace') + + $collection->allows() + ->getNamespace() ->andReturn('my_db.my_collection'); - $collection->shouldReceive('getCollectionName') + + $collection->allows() + ->getCollectionName() ->andReturn('my_collection'); } @@ -233,7 +240,8 @@ protected function getCachableCursor( $mock->shouldAllowMockingProtectedMethods(); if ($driverCursor) { - $mock->shouldReceive('getCursor') + $mock->expects() + ->getCursor() ->andReturn($driverCursor); } diff --git a/tests/Cursor/CursorTest.php b/tests/Cursor/CursorTest.php index b053a1af..ea2ab363 100644 --- a/tests/Cursor/CursorTest.php +++ b/tests/Cursor/CursorTest.php @@ -97,9 +97,8 @@ public function testShouldCountDocuments() $cursor = $this->getCursor(null, $collection); // Act - $collection->shouldReceive('count') - ->once() - ->with([]) + $collection->expects() + ->count([]) ->andReturn(5); // Assert @@ -113,9 +112,8 @@ public function testShouldCountDocumentsWithCountFunction() $cursor = $this->getCursor(null, $collection); // Act - $collection->shouldReceive('count') - ->once() - ->with([]) + $collection->expects() + ->count([]) ->andReturn(5); // Assert @@ -132,8 +130,8 @@ public function testShouldRewind() $this->setProtected($cursor, 'position', 10); // Act - $driverCursor->shouldReceive('rewind') - ->once(); + $driverCursor->expects() + ->rewind(); // Assert $cursor->rewind(); @@ -150,7 +148,8 @@ public function testShouldRewindACursorThatHasAlreadyBeenInitialized() $this->setProtected($cursor, 'position', 10); // Act - $driverCursor->shouldReceive('rewind') + $driverCursor->expects() + ->rewind() ->twice() ->andReturnUsing(function () use ($cursor) { if ($this->getProtected($cursor, 'cursor')) { @@ -171,8 +170,8 @@ public function testShouldGetCurrent() $cursor = $this->getCursor(null, $collection, 'find', [[]], $driverCursor); // Act - $driverCursor->shouldReceive('current') - ->once() + $driverCursor->expects() + ->current() ->andReturn(['name' => 'John Doe']); // Assert @@ -204,11 +203,11 @@ public function testShouldGetFirst() $cursor = $this->getCursor(null, $collection, 'find', [[]], $driverCursor); // Act - $driverCursor->shouldReceive('rewind') - ->once(); + $driverCursor->expects() + ->rewind(); - $driverCursor->shouldReceive('current') - ->once() + $driverCursor->expects() + ->current() ->andReturn(['name' => 'John Doe']); // Assert @@ -225,11 +224,11 @@ public function testShouldGetFirstWhenEmpty() $cursor = $this->getCursor(null, $collection, 'find', [[]], $driverCursor); // Act - $driverCursor->shouldReceive('rewind') - ->once(); + $driverCursor->expects() + ->rewind(); - $driverCursor->shouldReceive('current') - ->once() + $driverCursor->expects() + ->current() ->andReturn(null); // Assert @@ -270,8 +269,8 @@ public function testShouldImplementNextMethodFromIterator() $this->setProtected($cursor, 'position', 7); // Act - $driverCursor->shouldReceive('next') - ->once(); + $driverCursor->expects() + ->next(); // Assert $cursor->next(); @@ -286,14 +285,15 @@ public function testShouldImplementValidMethodFromIterator() $cursor = $this->getCursor(null, $collection, 'find', [[]], $driverCursor); // Act - $driverCursor->shouldReceive('valid') + $driverCursor->expects() + ->valid() ->andReturn(true); // Assert $this->assertTrue($cursor->valid()); } - public function testShouldWrapMongoDriverCursorWithIteratoriterator() + public function testShouldWrapMongoDriverCursorWithIterator() { // Arrange $collection = m::mock(Collection::class); @@ -302,18 +302,30 @@ public function testShouldWrapMongoDriverCursorWithIteratoriterator() $driverIterator = m::mock(Iterator::class); // Act - $collection->shouldReceive('find') - ->once() - ->with(['bacon' => true]) + $collection->expects() + ->find(['bacon' => true]) ->andReturn($driverCursor); - $driverCursor->shouldReceive('getIterator') + $driverCursor->expects() + ->getIterator() ->andReturn($driverIterator); // Because when creating an IteratorIterator with the driverCursor // this methods will be called once to initialize the iterable object. - $driverIterator->shouldReceive('rewind', 'valid', 'current', 'key') - ->once() + $driverIterator->expects() + ->rewind() + ->andReturn(true); + + $driverIterator->expects() + ->valid() + ->andReturn(true); + + $driverIterator->expects() + ->current() + ->andReturn(true); + + $driverIterator->expects() + ->key() ->andReturn(true); // Assert @@ -329,13 +341,20 @@ public function testShouldReturnAllResults() $cursor = $this->getCursor(null, $collection, 'find', [[]], $driverCursor); // Act - $driverCursor->shouldReceive('rewind', 'valid', 'key') + $driverCursor->expects() + ->rewind(); + + $driverCursor->expects() + ->valid() + ->times(3) ->andReturn(true, true, false); - $driverCursor->shouldReceive('next') + $driverCursor->expects('next') + ->twice() ->andReturn(true, false); - $driverCursor->shouldReceive('current') + $driverCursor->expects() + ->current() ->twice() ->andReturn( ['name' => 'bob', 'occupation' => 'coder'], @@ -362,13 +381,21 @@ public function testShouldReturnResultsToArray() $cursor = $this->getCursor(null, $collection, 'find', [[]], $driverCursor); // Act - $driverCursor->shouldReceive('rewind', 'valid', 'key') + $driverCursor->expects() + ->rewind(); + + $driverCursor->expects() + ->valid() + ->times(3) ->andReturn(true, true, false); - $driverCursor->shouldReceive('next') + $driverCursor->expects() + ->next() + ->twice() ->andReturn(true, false); - $driverCursor->shouldReceive('current') + $driverCursor->expects() + ->current() ->twice() ->andReturn( ['name' => 'bob', 'occupation' => 'coder'], @@ -401,10 +428,12 @@ public function testShouldSerializeAnActiveCursor() // Act Ioc::instance(Pool::class, $pool); - $pool->shouldReceive('getConnection') + $pool->expects() + ->getConnection() ->andReturn($conn); - $conn->shouldReceive('getRawConnection') + $conn->expects() + ->getRawConnection() ->andReturn($conn); $conn->defaultDatabase = 'db'; @@ -441,7 +470,8 @@ protected function getCursor( ); $mock->shouldAllowMockingProtectedMethods(); - $mock->shouldReceive('getCursor') + $mock->allows() + ->getCursor() ->andReturn($driverCursor); $this->setProtected($mock, 'cursor', $driverCursor); diff --git a/tests/DataMapper/BulkWriteTest.php b/tests/DataMapper/BulkWriteTest.php index 03571ad7..2f6fcc4f 100644 --- a/tests/DataMapper/BulkWriteTest.php +++ b/tests/DataMapper/BulkWriteTest.php @@ -20,8 +20,8 @@ public function testShouldConstructBulkWriteObject() $entity = m::mock(HasSchemaInterface::class); // Expect - $entity->shouldReceive('getSchema') - ->once(); + $entity->expects() + ->getSchema(); // Act $bulkWrite = new BulkWrite($entity); @@ -37,8 +37,8 @@ public function testShouldSetAndGetMongoBulkWrite() $mongoBulkWrite = new MongoBulkWrite(); // Expect - $entity->shouldReceive('getSchema') - ->once(); + $entity->expects() + ->getSchema(); // Act $bulkWrite = new BulkWrite($entity); @@ -58,18 +58,16 @@ public function testShouldAddUpdateOneOperationToBulkWrite() $data = ['name' => 'John']; // Expect - $entity->shouldReceive('getSchema') - ->once(); + $entity->expects() + ->getSchema(); - $mongoBulkWrite->shouldReceive('update') - ->once() - ->with(['_id' => $id], ['$set' => $data], ['upsert' => true]); + $mongoBulkWrite->expects() + ->update(['_id' => $id], ['$set' => $data], ['upsert' => true]); $bulkWrite = m::mock(BulkWrite::class.'[getBulkWrite]', [$entity]); - $bulkWrite->shouldReceive('getBulkWrite') - ->once() - ->with() + $bulkWrite->expects() + ->getBulkWrite() ->andReturn($mongoBulkWrite); // Act @@ -86,19 +84,16 @@ public function testShouldUpdateOneWithUnsetOperationToBulkWrite() $data = ['name' => 'John']; // Expect - $entity->shouldReceive('getSchema') - ->withNoArgs() - ->once(); + $entity->expects() + ->getSchema(); - $mongoBulkWrite->shouldReceive('update') - ->with(['_id' => $id], ['$unset' => $data], ['upsert' => true]) - ->once(); + $mongoBulkWrite->expects() + ->update(['_id' => $id], ['$unset' => $data], ['upsert' => true]); $bulkWrite = m::mock(BulkWrite::class.'[getBulkWrite]', [$entity]); - $bulkWrite->shouldReceive('getBulkWrite') - ->with() - ->once() + $bulkWrite->expects() + ->getBulkWrite() ->andReturn($mongoBulkWrite); // Act @@ -122,31 +117,26 @@ public function testShouldExecuteBulkWrite() Ioc::instance(Pool::class, $pool); // Expect - $entity->shouldReceive('getSchema') - ->once() - ->with() + $entity->expects() + ->getSchema() ->andReturn($schema); - $pool->shouldReceive('getConnection') - ->once() - ->with() + $pool->expects() + ->getConnection() ->andReturn($connection); - $connection->shouldReceive('getRawManager') - ->once() - ->with() + $connection->expects() + ->getRawManager() ->andReturn($manager); - $manager->shouldReceive('executeBulkWrite') - ->once() - ->with($namespace, $mongoBulkWrite, ['writeConcern' => new WriteConcern(1)]) + $manager->expects() + ->executeBulkWrite($namespace, $mongoBulkWrite, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); $bulkWrite = m::mock(BulkWrite::class.'[getBulkWrite]', [$entity]); - $bulkWrite->shouldReceive('getBulkWrite') - ->once() - ->with() + $bulkWrite->expects() + ->getBulkWrite() ->andReturn($mongoBulkWrite); // Act diff --git a/tests/DataMapper/DataMapperTest.php b/tests/DataMapper/DataMapperTest.php index 41885278..45e8bb3b 100644 --- a/tests/DataMapper/DataMapperTest.php +++ b/tests/DataMapper/DataMapperTest.php @@ -50,34 +50,36 @@ public function testShouldSave($entity, $writeConcern, $shouldFireEventAfter, $e // Act $mapper->shouldAllowMockingProtectedMethods(); - $mapper->shouldReceive('parseToDocument') - ->once() - ->with($entity) + $mapper->expects() + ->parseToDocument($entity) ->andReturn($parsedObject); - $mapper->shouldReceive('getCollection') - ->once() + $mapper->expects() + ->getCollection() ->andReturn($collection); - $collection->shouldReceive('replaceOne') - ->once() - ->with( + $collection->expects() + ->replaceOne( ['_id' => 123], $parsedObject, ['upsert' => true, 'writeConcern' => new WriteConcern($writeConcern)] )->andReturn($operationResult); - $operationResult->shouldReceive('isAcknowledged') - ->once() + $operationResult->expects() + ->isAcknowledged() ->andReturn((bool) $writeConcern); - $operationResult->shouldReceive('getModifiedCount', 'getUpsertedCount') + $operationResult->allows() + ->getModifiedCount() + ->andReturn(1); + + $operationResult->allows() + ->getUpsertedCount() ->andReturn(1); if ($entity instanceof AttributesAccessInterface) { - $entity->shouldReceive('syncOriginalAttributes') - ->once() - ->with(); + $entity->expects() + ->syncOriginalAttributes(); } $this->expectEventToBeFired('saving', $entity, true); @@ -111,31 +113,29 @@ public function testShouldInsert($entity, $writeConcern, $shouldFireEventAfter, // Act $mapper->shouldAllowMockingProtectedMethods(); - $mapper->shouldReceive('parseToDocument') - ->once() - ->with($entity) + $mapper->expects() + ->parseToDocument($entity) ->andReturn($parsedObject); - $mapper->shouldReceive('getCollection') - ->once() + $mapper->expects() + ->getCollection() ->andReturn($collection); - $collection->shouldReceive('insertOne') - ->once() - ->with($parsedObject, ['writeConcern' => new WriteConcern($writeConcern)]) + $collection->expects() + ->insertOne($parsedObject, ['writeConcern' => new WriteConcern($writeConcern)]) ->andReturn($operationResult); - $operationResult->shouldReceive('isAcknowledged') - ->once() + $operationResult->expects() + ->isAcknowledged() ->andReturn((bool) $writeConcern); - $operationResult->shouldReceive('getInsertedCount') + $operationResult->allows() + ->getInsertedCount() ->andReturn(1); if ($entity instanceof AttributesAccessInterface) { - $entity->shouldReceive('syncOriginalAttributes') - ->once() - ->with(); + $entity->expects() + ->syncOriginalAttributes(); } $this->expectEventToBeFired('inserting', $entity, true); @@ -169,31 +169,29 @@ public function testShouldInsertWithoutFiringEvents($entity, $writeConcern, $sho // Act $mapper->shouldAllowMockingProtectedMethods(); - $mapper->shouldReceive('parseToDocument') - ->once() - ->with($entity) + $mapper->expects() + ->parseToDocument($entity) ->andReturn($parsedObject); - $mapper->shouldReceive('getCollection') - ->once() + $mapper->expects() + ->getCollection() ->andReturn($collection); - $collection->shouldReceive('insertOne') - ->once() - ->with($parsedObject, ['writeConcern' => new WriteConcern($writeConcern)]) + $collection->expects() + ->insertOne($parsedObject, ['writeConcern' => new WriteConcern($writeConcern)]) ->andReturn($operationResult); - $operationResult->shouldReceive('isAcknowledged') - ->once() + $operationResult->expects() + ->isAcknowledged() ->andReturn((bool) $writeConcern); - $operationResult->shouldReceive('getInsertedCount') + $operationResult->allows() + ->getInsertedCount() ->andReturn(1); if ($entity instanceof AttributesAccessInterface) { - $entity->shouldReceive('syncOriginalAttributes') - ->once() - ->with(); + $entity->expects() + ->syncOriginalAttributes(); } $this->expectEventNotToBeFired('inserting', $entity); @@ -222,34 +220,32 @@ public function testShouldUpdate($entity, $writeConcern, $shouldFireEventAfter, // Act $mapper->shouldAllowMockingProtectedMethods(); - $mapper->shouldReceive('parseToDocument') - ->once() - ->with($entity) + $mapper->expects() + ->parseToDocument($entity) ->andReturn($parsedObject); - $mapper->shouldReceive('getCollection') - ->once() + $mapper->expects() + ->getCollection() ->andReturn($collection); - $collection->shouldReceive('updateOne') - ->once() - ->with( + $collection->expects() + ->updateOne( ['_id' => 123], ['$set' => $parsedObject], ['writeConcern' => new WriteConcern($writeConcern)] )->andReturn($operationResult); - $operationResult->shouldReceive('isAcknowledged') - ->once() + $operationResult->expects() + ->isAcknowledged() ->andReturn((bool) $writeConcern); - $operationResult->shouldReceive('getModifiedCount') + $operationResult->allows() + ->getModifiedCount() ->andReturn(1); if ($entity instanceof AttributesAccessInterface) { - $entity->shouldReceive('syncOriginalAttributes') - ->once() - ->with(); + $entity->expects() + ->syncOriginalAttributes(); } $this->expectEventToBeFired('updating', $entity, true); @@ -287,33 +283,31 @@ public function testUpdateShouldCallInsertWhenObjectHasNoId( // Act $mapper->shouldAllowMockingProtectedMethods(); - $mapper->shouldReceive('parseToDocument') - ->once() - ->with($entity) + $mapper->expects() + ->parseToDocument($entity) ->andReturn($parsedObject); - $mapper->shouldReceive('getCollection') - ->once() + $mapper->expects() + ->getCollection() ->andReturn($collection); - $collection->shouldReceive('insertOne') - ->once() - ->with( + $collection->expects() + ->insertOne( $parsedObject, ['writeConcern' => new WriteConcern($writeConcern)] )->andReturn($operationResult); - $operationResult->shouldReceive('isAcknowledged') - ->once() + $operationResult->expects() + ->isAcknowledged() ->andReturn((bool) $writeConcern); - $operationResult->shouldReceive('getInsertedCount') + $operationResult->allows() + ->getInsertedCount() ->andReturn(1); if ($entity instanceof AttributesAccessInterface) { - $entity->shouldReceive('syncOriginalAttributes') - ->once() - ->with(); + $entity->expects() + ->syncOriginalAttributes(); } $this->expectEventToBeFired('updating', $entity, true); @@ -350,31 +344,29 @@ public function testShouldDelete($entity, $writeConcern, $shouldFireEventAfter, // Act $mapper->shouldAllowMockingProtectedMethods(); - $mapper->shouldReceive('parseToDocument') - ->once() - ->with($entity) + $mapper->expects() + ->parseToDocument($entity) ->andReturn($parsedObject); - $mapper->shouldReceive('getCollection') - ->once() + $mapper->expects() + ->getCollection() ->andReturn($collection); - $collection->shouldReceive('deleteOne') - ->once() - ->with(['_id' => 123], ['writeConcern' => new WriteConcern($writeConcern)]) + $collection->expects() + ->deleteOne(['_id' => 123], ['writeConcern' => new WriteConcern($writeConcern)]) ->andReturn($operationResult); - $operationResult->shouldReceive('isAcknowledged') - ->once() + $operationResult->expects() + ->isAcknowledged() ->andReturn((bool) $writeConcern); - $operationResult->shouldReceive('getDeletedCount') + $operationResult->allows() + ->getDeletedCount() ->andReturn(1); if ($entity instanceof AttributesAccessInterface) { - $entity->shouldReceive('syncOriginalAttributes') - ->once() - ->with(); + $entity->expects() + ->syncOriginalAttributes(); } $this->expectEventToBeFired('deleting', $entity, true); @@ -406,14 +398,15 @@ public function testDatabaseOperationsShouldBailOutIfTheEventHandlerReturnsFalse $mapper->shouldAllowMockingProtectedMethods(); // Expect - $mapper->shouldReceive('parseToDocument') - ->with($entity) + $mapper->expects() + ->parseToDocument($entity) ->never(); - $mapper->shouldReceive('getCollection') + $mapper->allows() + ->getCollection() ->andReturn($collection); - $collection->shouldReceive($dbOperation) + $collection->expects($dbOperation) ->never(); /* "Mocks" the fireEvent to return false and bail the operation */ @@ -444,11 +437,14 @@ public function testShouldGetWithWhereQuery() $mapper->shouldAllowMockingProtectedMethods(); // Expect - $mapper->shouldReceive('prepareValueQuery') - ->with($query) + $mapper->expects() + ->prepareValueQuery($query) + ->twice() ->andReturn($preparedQuery); - $mapper->shouldReceive('getCollection') + $mapper->expects() + ->getCollection() + ->twice() ->andReturn($collection); // Act @@ -485,9 +481,8 @@ public function testShouldGetAll() $mongolidCursor = m::mock(Cursor::class); // Expect - $mapper->shouldReceive('where') - ->once() - ->with([]) + $mapper->expects() + ->where([]) ->andReturn($mongolidCursor); // Act @@ -513,18 +508,16 @@ public function testShouldGetFirstWithQuery() $mapper->shouldAllowMockingProtectedMethods(); // Act - $mapper->shouldReceive('prepareValueQuery') - ->once() - ->with($query) + $mapper->expects() + ->prepareValueQuery($query) ->andReturn($preparedQuery); - $mapper->shouldReceive('getCollection') - ->once() + $mapper->expects() + ->getCollection() ->andReturn($collection); - $collection->shouldReceive('findOne') - ->once() - ->with($preparedQuery, ['projection' => []]) + $collection->expects() + ->findOne($preparedQuery, ['projection' => []]) ->andReturn(['name' => 'John Doe']); $result = $mapper->first($query); @@ -551,18 +544,16 @@ public function testShouldGetNullIfFirstCantFindAnything() $mapper->shouldAllowMockingProtectedMethods(); // Expect - $mapper->shouldReceive('prepareValueQuery') - ->once() - ->with($query) + $mapper->expects() + ->prepareValueQuery($query) ->andReturn($preparedQuery); - $mapper->shouldReceive('getCollection') - ->once() + $mapper->expects() + ->getCollection() ->andReturn($collection); - $collection->shouldReceive('findOne') - ->once() - ->with($preparedQuery, ['projection' => []]) + $collection->expects() + ->findOne($preparedQuery, ['projection' => []]) ->andReturn(null); // Act @@ -593,18 +584,16 @@ public function testShouldGetFirstProjectingFields() $mapper->shouldAllowMockingProtectedMethods(); // Expect - $mapper->shouldReceive('prepareValueQuery') - ->once() - ->with($query) + $mapper->expects() + ->prepareValueQuery($query) ->andReturn($preparedQuery); - $mapper->shouldReceive('getCollection') - ->once() + $mapper->expects() + ->getCollection() ->andReturn($collection); - $collection->shouldReceive('findOne') - ->once() - ->with($preparedQuery, ['projection' => $projection]) + $collection->expects() + ->findOne($preparedQuery, ['projection' => $projection]) ->andReturn(null); // Act @@ -624,13 +613,12 @@ public function testShouldGetFirstTroughACacheableCursor() $cursor = m::mock(CacheableCursor::class); // Expect - $mapper->shouldReceive('where') - ->once() - ->with($query, [], true) + $mapper->expects() + ->where($query, [], true) ->andReturn($cursor); - $cursor->shouldReceive('first') - ->once() + $cursor->expects() + ->first() ->andReturn($entity); // Act @@ -651,13 +639,12 @@ public function testShouldGetFirstTroughACacheableCursorProjectingFields() $projection = ['project' => true, '_id' => false]; // Expect - $mapper->shouldReceive('where') - ->once() - ->with($query, $projection, true) + $mapper->expects() + ->where($query, $projection, true) ->andReturn($cursor); - $cursor->shouldReceive('first') - ->once() + $cursor->expects() + ->first() ->andReturn($entity); // Act @@ -679,13 +666,12 @@ public function testShouldParseObjectToDocumentAndPutResultingIdIntoTheGivenObje $mapper->shouldAllowMockingProtectedMethods(); // Expect - $mapper->shouldReceive('getSchemaMapper') - ->once() + $mapper->expects() + ->getSchemaMapper() ->andReturn($schemaMapper); - $schemaMapper->shouldReceive('map') - ->once() - ->with($entity) + $schemaMapper->expects() + ->map($entity) ->andReturn($parsedDocument); // Act @@ -732,11 +718,12 @@ public function testShouldGetRawCollection() $connection->grimory = (object) ['foobar' => $collection]; // Expect - $connPool->shouldReceive('getConnection') - ->once() + $connPool->expects() + ->getConnection() ->andReturn($connection); - $connection->shouldReceive('getRawConnection') + $connection->expects() + ->getRawConnection() ->andReturn($connection); // Act @@ -806,10 +793,8 @@ protected function expectEventToBeFired($event, $entity, bool $halt, $return = t { $event = 'mongolid.'.$event.': '.get_class($entity); - $this->getEventService()->shouldReceive('fire') - ->with($event, $entity, $halt) - ->atLeast() - ->once() + $this->getEventService()->expects() + ->fire($event, $entity, $halt) ->andReturn($return); } @@ -817,8 +802,8 @@ protected function expectEventNotToBeFired($event, $entity) { $event = 'mongolid.'.$event.': '.get_class($entity); - $this->getEventService()->shouldReceive('fire') - ->with($event, $entity, m::any()) + $this->getEventService()->expects() + ->fire($event, $entity, m::any()) ->never(); } diff --git a/tests/DataMapper/SchemaMapperTest.php b/tests/DataMapper/SchemaMapperTest.php index 7025c9a2..c572c691 100644 --- a/tests/DataMapper/SchemaMapperTest.php +++ b/tests/DataMapper/SchemaMapperTest.php @@ -29,14 +29,12 @@ public function testShouldMapToFieldsOfSchema() ]; // Act - $schemaMapper->shouldReceive('clearDynamic') - ->once() - ->with($data); + $schemaMapper->expects() + ->clearDynamic($data); foreach ($schema->fields as $key => $value) { - $schemaMapper->shouldReceive('parseField') - ->once() - ->with($data[$key], $value) + $schemaMapper->expects() + ->parseField($data[$key], $value) ->andReturn($data[$key].'.PARSED'); } @@ -135,9 +133,8 @@ public function testShouldParseFieldIntoAnotherMappedSchemaIfTypeBeginsWithSchem $schemaMapper->shouldAllowMockingProtectedMethods(); // Act - $schemaMapper->shouldReceive('mapToSchema') - ->once() - ->with(['foo' => 'bar'], 'FooBarSchema') + $schemaMapper->expects() + ->mapToSchema(['foo' => 'bar'], 'FooBarSchema') ->andReturn(['foo' => 123]); // Assert @@ -188,9 +185,8 @@ public function testShouldMapAnArrayValueToAnotherSchema() $anotherSchemaMapper = m::mock(SchemaMapper::class, [$params['schema']]); // Set expectation to receive a map call - $anotherSchemaMapper->shouldReceive('map') - ->once() - ->with($value) + $anotherSchemaMapper->expects() + ->map($value) ->andReturn(['foo' => 'PARSED']); return $anotherSchemaMapper; diff --git a/tests/Event/EventTriggerServiceTest.php b/tests/Event/EventTriggerServiceTest.php index 4053a9dc..3eff6cd4 100644 --- a/tests/Event/EventTriggerServiceTest.php +++ b/tests/Event/EventTriggerServiceTest.php @@ -13,9 +13,8 @@ public function testShouldSendTheEventsToTheExternalDispatcher() $service = new EventTriggerService(); // Act - $dispatcher->shouldReceive('fire') - ->once() - ->with('foobar', ['answer' => 23], true) + $dispatcher->expects() + ->fire('foobar', ['answer' => 23], true) ->andReturn(true); // Assertion @@ -32,7 +31,8 @@ public function testShouldReturnTrueIfThereIsNoExternalDispatcher() $service = new EventTriggerService(); // Act - $dispatcher->shouldReceive('fire') + $dispatcher->expects() + ->fire() ->never(); // Assertion diff --git a/tests/ManagerTest.php b/tests/ManagerTest.php index b8eaa092..ba52bb64 100644 --- a/tests/ManagerTest.php +++ b/tests/ManagerTest.php @@ -29,7 +29,8 @@ public function testShouldAddAndGetConnection() $rawConnection = m::mock(Client::class); // Act - $connection->shouldReceive('getRawConnection') + $connection->expects() + ->getRawConnection() ->andReturn($rawConnection); // Assert @@ -48,8 +49,8 @@ public function testShouldSetEventTrigger() $this->setProtected($manager, 'container', $container); // Act - $container->shouldReceive('instance') - ->once() + $container->expects() + ->instance(EventTriggerService::class, m::type(EventTriggerService::class)) ->andReturnUsing(function ($class, $eventService) use ($test, $eventTrigger) { $test->assertEquals(EventTriggerService::class, $class); $test->assertAttributeEquals($eventTrigger, 'dispatcher', $eventService); diff --git a/tests/Model/RelationsTest.php b/tests/Model/RelationsTest.php index 8fd494a7..115bff7e 100644 --- a/tests/Model/RelationsTest.php +++ b/tests/Model/RelationsTest.php @@ -31,9 +31,8 @@ public function testShouldReferenceOne($entity, $field, $fieldValue, $useCache, Ioc::instance(DataMapper::class, $dataMapper); Ioc::instance('EntityClass', $entity); - $dataMapper->shouldReceive('first') - ->with(m::type('array'), [], $useCache) - ->once() + $dataMapper->expects() + ->first(m::type('array'), [], $useCache) ->andReturnUsing(function ($query) use ($result, $expectedQuery) { $this->assertMongoQueryEquals($expectedQuery, $query); @@ -64,9 +63,8 @@ public function testShouldReferenceMany($entity, $field, $fieldValue, $useCache, Ioc::instance(DataMapper::class, $dataMapper); Ioc::instance('EntityClass', $entity); - $dataMapper->shouldReceive('where') - ->with(m::type('array'), [], $useCache) - ->once() + $dataMapper->expects() + ->where(m::type('array'), [], $useCache) ->andReturnUsing(function ($query) use ($result, $expectedQuery) { $this->assertMongoQueryEquals($expectedQuery, $query); @@ -97,13 +95,12 @@ public function testShouldEmbedsOne($entity, $field, $fieldValue, $expectedItems // Act Ioc::instance(CursorFactory::class, $cursorFactory); - $cursorFactory->shouldReceive('createEmbeddedCursor') - ->once() - ->with($instantiableClass, $expectedItems) + $cursorFactory->expects() + ->createEmbeddedCursor($instantiableClass, $expectedItems) ->andReturn($cursor); - $cursor->shouldReceive('first') - ->once() + $cursor->expects() + ->first() ->andReturn(new $instantiableClass()); // Assert @@ -128,9 +125,8 @@ public function testShouldEmbedsMany($entity, $field, $fieldValue, $expectedItem // Act Ioc::instance(CursorFactory::class, $cursorFactory); - $cursorFactory->shouldReceive('createEmbeddedCursor') - ->once() - ->with($instantiableClass, $expectedItems) + $cursorFactory->expects() + ->createEmbeddedCursor($instantiableClass, $expectedItems) ->andReturn($cursor); // Assert @@ -153,9 +149,8 @@ public function testShouldEmbeddedUnembedAttachAndDetachDocuments($method) // Act Ioc::instance(DocumentEmbedder::class, $documentEmbedder); - $documentEmbedder->shouldReceive($method) - ->once() - ->with($model, 'foo', $document); + $documentEmbedder->expects() + ->$method($model, 'foo', $document); // Assert $model->$method('foo', $document); diff --git a/tests/Schema/SchemaTest.php b/tests/Schema/SchemaTest.php index 64f2811d..6588a470 100644 --- a/tests/Schema/SchemaTest.php +++ b/tests/Schema/SchemaTest.php @@ -84,9 +84,8 @@ public function testShouldCastNullIntoAutoIncrementSequence() // Act Ioc::instance(SequenceService::class, $sequenceService); - $sequenceService->shouldReceive('getNextValue') - ->with('resources') - ->once() + $sequenceService->expects() + ->getNextValue('resources') ->andReturn(7); // Assertion @@ -104,8 +103,8 @@ public function testShouldNotAutoIncrementSequenceIfValueIsNotNull() // Act Ioc::instance(SequenceService::class, $sequenceService); - $sequenceService->shouldReceive('getNextValue') - ->with('resources') + $sequenceService->expects() + ->getNextValue('resources') ->never() ->andReturn(7); // Should never be returned diff --git a/tests/Util/CacheComponentTest.php b/tests/Util/CacheComponentTest.php index 81d4ee6b..b3df3f30 100644 --- a/tests/Util/CacheComponentTest.php +++ b/tests/Util/CacheComponentTest.php @@ -54,7 +54,8 @@ protected function getCacheComponent() $test = $this; $cacheComponent = m::mock(CacheComponent::class.'[time]'); $cacheComponent->shouldAllowMockingProtectedMethods(); - $cacheComponent->shouldReceive('time') + $cacheComponent->allows() + ->time() ->andReturnUsing(function () use ($test) { return $test->time; }); diff --git a/tests/Util/SequenceServiceTest.php b/tests/Util/SequenceServiceTest.php index 8a79e5a5..b6e68df9 100644 --- a/tests/Util/SequenceServiceTest.php +++ b/tests/Util/SequenceServiceTest.php @@ -21,13 +21,12 @@ public function testShouldGetNextValue($sequenceName, $currentValue, $expectatio $rawCollection = m::mock(Collection::class); // Act - $sequenceService->shouldReceive('rawCollection') - ->once() + $sequenceService->expects() + ->rawCollection() ->andReturn($rawCollection); - $rawCollection->shouldReceive('findOneAndUpdate') - ->once() - ->with( + $rawCollection->expects() + ->findOneAndUpdate( ['_id' => $sequenceName], ['$inc' => ['seq' => 1]], ['upsert' => true] @@ -54,11 +53,12 @@ public function testShouldGetRawCollection() $connection->grimory = (object) ['foobar' => $collection]; // Act - $connPool->shouldReceive('getConnection') - ->once() + $connPool->expects() + ->getConnection() ->andReturn($connection); - $connection->shouldReceive('getRawConnection') + $connection->expects() + ->getRawConnection() ->andReturn($connection); // Assertion From c7396896d5e0a1bee998a93256b548fcb8b17009 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Fri, 19 Oct 2018 18:17:40 -0300 Subject: [PATCH 009/116] Commit lock file to speed up installation --- .gitignore | 2 +- composer.lock | 3305 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 3306 insertions(+), 1 deletion(-) create mode 100644 composer.lock diff --git a/.gitignore b/.gitignore index fd177cf5..097df324 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,5 @@ /.coverage /build /site -composer.lock .phpcs-cache +docker-compose.override.yml diff --git a/composer.lock b/composer.lock new file mode 100644 index 00000000..7629345e --- /dev/null +++ b/composer.lock @@ -0,0 +1,3305 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "1a331ad3eeb47f9f707a5d63d8a9688b", + "packages": [ + { + "name": "doctrine/inflector", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "5527a48b7313d15261292c149e55e26eae771b0a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/5527a48b7313d15261292c149e55e26eae771b0a", + "reference": "5527a48b7313d15261292c149e55e26eae771b0a", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Common String Manipulations with regard to casing and singular/plural rules.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "inflection", + "pluralize", + "singularize", + "string" + ], + "time": "2018-01-09T20:05:19+00:00" + }, + { + "name": "illuminate/container", + "version": "v5.7.9", + "source": { + "type": "git", + "url": "https://github.com/illuminate/container.git", + "reference": "73cde7bd4985eefb1d468a745e1d50d03e276121" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/container/zipball/73cde7bd4985eefb1d468a745e1d50d03e276121", + "reference": "73cde7bd4985eefb1d468a745e1d50d03e276121", + "shasum": "" + }, + "require": { + "illuminate/contracts": "5.7.*", + "illuminate/support": "5.7.*", + "php": "^7.1.3", + "psr/container": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.7-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Container\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Container package.", + "homepage": "https://laravel.com", + "time": "2018-10-07T15:52:17+00:00" + }, + { + "name": "illuminate/contracts", + "version": "v5.7.9", + "source": { + "type": "git", + "url": "https://github.com/illuminate/contracts.git", + "reference": "64df81d3382d876f1c1d3d5481d89c93b61b8279" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/64df81d3382d876f1c1d3d5481d89c93b61b8279", + "reference": "64df81d3382d876f1c1d3d5481d89c93b61b8279", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "psr/container": "^1.0", + "psr/simple-cache": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.7-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Contracts\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Contracts package.", + "homepage": "https://laravel.com", + "time": "2018-10-08T13:34:14+00:00" + }, + { + "name": "illuminate/support", + "version": "v5.7.9", + "source": { + "type": "git", + "url": "https://github.com/illuminate/support.git", + "reference": "ea95697233b06650382eb0f5798be22b4e520dea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/support/zipball/ea95697233b06650382eb0f5798be22b4e520dea", + "reference": "ea95697233b06650382eb0f5798be22b4e520dea", + "shasum": "" + }, + "require": { + "doctrine/inflector": "^1.1", + "ext-mbstring": "*", + "illuminate/contracts": "5.7.*", + "nesbot/carbon": "^1.26.3", + "php": "^7.1.3" + }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, + "suggest": { + "illuminate/filesystem": "Required to use the composer class (5.7.*).", + "moontoast/math": "Required to use ordered UUIDs (^1.1).", + "ramsey/uuid": "Required to use Str::uuid() (^3.7).", + "symfony/process": "Required to use the composer class (^4.1).", + "symfony/var-dumper": "Required to use the dd function (^4.1)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.7-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + }, + "files": [ + "helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Support package.", + "homepage": "https://laravel.com", + "time": "2018-10-07T15:51:39+00:00" + }, + { + "name": "mongodb/mongodb", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/mongodb/mongo-php-library.git", + "reference": "bd148eab0493e38354e45e2cd7db59b90fdcad79" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/bd148eab0493e38354e45e2cd7db59b90fdcad79", + "reference": "bd148eab0493e38354e45e2cd7db59b90fdcad79", + "shasum": "" + }, + "require": { + "ext-hash": "*", + "ext-json": "*", + "ext-mongodb": "^1.5.0", + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-4": { + "MongoDB\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Jeremy Mikola", + "email": "jmikola@gmail.com" + }, + { + "name": "Derick Rethans", + "email": "github@derickrethans.nl" + }, + { + "name": "Katherine Walker", + "email": "katherine.walker@mongodb.com" + } + ], + "description": "MongoDB driver library", + "homepage": "https://jira.mongodb.org/browse/PHPLIB", + "keywords": [ + "database", + "driver", + "mongodb", + "persistence" + ], + "time": "2018-07-18T14:33:41+00:00" + }, + { + "name": "nesbot/carbon", + "version": "1.34.0", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "1dbd3cb01c5645f3e7deda7aa46ef780d95fcc33" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/1dbd3cb01c5645f3e7deda7aa46ef780d95fcc33", + "reference": "1dbd3cb01c5645f3e7deda7aa46ef780d95fcc33", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/translation": "~2.6 || ~3.0 || ~4.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2", + "phpunit/phpunit": "^4.8.35 || ^5.7" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "http://nesbot.com" + } + ], + "description": "A simple API extension for DateTime.", + "homepage": "http://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "time": "2018-09-20T19:36:25+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "time": "2017-10-23T01:57:42+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.9.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d0cd638f4634c16d8df4508e847f14e9e43168b8", + "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2018-08-06T14:22:27+00:00" + }, + { + "name": "symfony/translation", + "version": "v4.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "9f0b61e339160a466ebcde167a6c5521c810e304" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/9f0b61e339160a466ebcde167a6c5521c810e304", + "reference": "9f0b61e339160a466ebcde167a6c5521c810e304", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/config": "<3.4", + "symfony/dependency-injection": "<3.4", + "symfony/yaml": "<3.4" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.4|~4.0", + "symfony/console": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/finder": "~2.8|~3.0|~4.0", + "symfony/intl": "~3.4|~4.0", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "psr/log-implementation": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Translation Component", + "homepage": "https://symfony.com", + "time": "2018-10-02T16:36:10+00:00" + } + ], + "packages-dev": [ + { + "name": "blackfire/php-sdk", + "version": "v1.17.1", + "source": { + "type": "git", + "url": "https://github.com/blackfireio/php-sdk.git", + "reference": "a85703a57df9da0d840f98c4e044ea70d3621444" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/blackfireio/php-sdk/zipball/a85703a57df9da0d840f98c4e044ea70d3621444", + "reference": "a85703a57df9da0d840f98c4e044ea70d3621444", + "shasum": "" + }, + "require": { + "composer/ca-bundle": "^1.0", + "php": ">=5.2.0" + }, + "suggest": { + "ext-blackfire": "The C version of the Blackfire probe", + "ext-zlib": "To push config to remote profiling targets" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5.x-dev" + } + }, + "autoload": { + "files": [ + "src/autostart.php" + ], + "psr-4": { + "Blackfire\\": "src/Blackfire" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Blackfire.io", + "email": "support@blackfire.io" + } + ], + "description": "Blackfire.io PHP SDK", + "keywords": [ + "performance", + "profiler", + "uprofiler", + "xhprof" + ], + "time": "2018-07-16T09:18:18+00:00" + }, + { + "name": "composer/ca-bundle", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/composer/ca-bundle.git", + "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/8afa52cd417f4ec417b4bfe86b68106538a87660", + "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^5.3.2 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5", + "psr/log": "^1.0", + "symfony/process": "^2.5 || ^3.0 || ^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\CaBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "time": "2018-10-18T06:09:13+00:00" + }, + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.4.4", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "2e41850d5f7797cbb1af7b030d245b3b24e63a08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/2e41850d5f7797cbb1af7b030d245b3b24e63a08", + "reference": "2e41850d5f7797cbb1af7b030d245b3b24e63a08", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0", + "php": "^5.3|^7", + "squizlabs/php_codesniffer": "*" + }, + "require-dev": { + "composer/composer": "*", + "wimg/php-compatibility": "^8.0" + }, + "suggest": { + "dealerdirect/qa-tools": "All the PHP QA tools you'll need" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "f.nijhof@dealerdirect.nl", + "homepage": "http://workingatdealerdirect.eu", + "role": "Developer" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://workingatdealerdirect.eu", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "time": "2017-12-06T16:27:17+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "^6.2.3", + "squizlabs/php_codesniffer": "^3.0.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2017-07-22T11:58:36+00:00" + }, + { + "name": "guzzle/guzzle", + "version": "v3.9.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle3.git", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.3", + "symfony/event-dispatcher": "~2.1" + }, + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + "require-dev": { + "doctrine/cache": "~1.3", + "monolog/monolog": "~1.0", + "phpunit/phpunit": "3.7.*", + "psr/log": "~1.0", + "symfony/class-loader": "~2.1", + "zendframework/zend-cache": "2.*,<2.3", + "zendframework/zend-log": "2.*,<2.3" + }, + "suggest": { + "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.9-dev" + } + }, + "autoload": { + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "abandoned": "guzzlehttp/guzzle", + "time": "2015-03-18T18:23:50+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.0.0", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/776503d3a8e85d4f9a1148614f95b7a608b046ad", + "reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad", + "shasum": "" + }, + "require": { + "php": "^5.3|^7.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "1.3.3", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "time": "2016-01-20T08:20:44+00:00" + }, + { + "name": "leroy-merlin-br/coding-standard", + "version": "v0.1.1", + "source": { + "type": "git", + "url": "https://github.com/leroy-merlin-br/coding-standard.git", + "reference": "f98cb91c03f5e591218ad6c22d159ce7cc3de86a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/leroy-merlin-br/coding-standard/zipball/f98cb91c03f5e591218ad6c22d159ce7cc3de86a", + "reference": "f98cb91c03f5e591218ad6c22d159ce7cc3de86a", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.4", + "php": "^7.1", + "slevomat/coding-standard": "^4.8.0", + "squizlabs/php_codesniffer": "^3.3.2" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Boitatá", + "email": "boitata@leroymerlin.com.br" + } + ], + "description": "The coding standard for PHP projects on LMBR", + "keywords": [ + "checks", + "code", + "coding", + "cs", + "leroy-merlin", + "php", + "rules", + "sniffer", + "sniffs", + "standard", + "style" + ], + "time": "2018-10-08T14:35:54+00:00" + }, + { + "name": "michelf/php-markdown", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/michelf/php-markdown.git", + "reference": "01ab082b355bf188d907b9929cd99b2923053495" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/michelf/php-markdown/zipball/01ab082b355bf188d907b9929cd99b2923053495", + "reference": "01ab082b355bf188d907b9929cd99b2923053495", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Michelf\\": "Michelf/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Michel Fortin", + "email": "michel.fortin@michelf.ca", + "homepage": "https://michelf.ca/", + "role": "Developer" + }, + { + "name": "John Gruber", + "homepage": "https://daringfireball.net/" + } + ], + "description": "PHP Markdown", + "homepage": "https://michelf.ca/projects/php-markdown/", + "keywords": [ + "markdown" + ], + "time": "2018-01-15T00:49:33+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "100633629bf76d57430b86b7098cd6beb996a35a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/100633629bf76d57430b86b7098cd6beb996a35a", + "reference": "100633629bf76d57430b86b7098cd6beb996a35a", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "~2.0", + "lib-pcre": ">=7.0", + "php": ">=5.6.0" + }, + "require-dev": { + "phpunit/phpunit": "~5.7.10|~6.5|~7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Mockery": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "http://blog.astrumfutura.com" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "http://davedevelopment.co.uk" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "time": "2018-10-02T21:52:37+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.8.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", + "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2018-06-11T23:09:50+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v3.1.5", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/bb87e28e7d7b8d9a7fda231d37457c9210faf6ce", + "reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "~4.0|~5.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "time": "2018-02-28T20:30:58+00:00" + }, + { + "name": "phar-io/manifest", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^1.0.1", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2017-03-05T18:14:27+00:00" + }, + { + "name": "phar-io/version", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2017-03-05T17:38:23+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "2.0.5", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "e6a969a640b00d8daa3c66518b0405fb41ae0c4b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/e6a969a640b00d8daa3c66518b0405fb41ae0c4b", + "reference": "e6a969a640b00d8daa3c66518b0405fb41ae0c4b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "dflydev/markdown": "~1.0", + "erusev/parsedown": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "phpDocumentor": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "time": "2016-01-25T08:17:30+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "sebastian/comparator": "^1.1|^2.0|^3.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2018-08-05T17:53:17+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "5.3.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "c89677919c5dd6d3b3852f230a663118762218ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c89677919c5dd6d3b3852f230a663118762218ac", + "reference": "c89677919c5dd6d3b3852f230a663118762218ac", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^7.0", + "phpunit/php-file-iterator": "^1.4.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^2.0.1", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^3.0", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-xdebug": "^2.5.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2018-04-06T15:36:58+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "791198a2c6254db10131eecfe8c06670700904db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", + "reference": "791198a2c6254db10131eecfe8c06670700904db", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2017-11-27T05:48:46+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "6.5.13", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "0973426fb012359b2f18d3bd1e90ef1172839693" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0973426fb012359b2f18d3bd1e90ef1172839693", + "reference": "0973426fb012359b2f18d3bd1e90ef1172839693", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "^1.6.1", + "phar-io/manifest": "^1.0.1", + "phar-io/version": "^1.0", + "php": "^7.0", + "phpspec/prophecy": "^1.7", + "phpunit/php-code-coverage": "^5.3", + "phpunit/php-file-iterator": "^1.4.3", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^1.0.9", + "phpunit/phpunit-mock-objects": "^5.0.9", + "sebastian/comparator": "^2.1", + "sebastian/diff": "^2.0", + "sebastian/environment": "^3.1", + "sebastian/exporter": "^3.1", + "sebastian/global-state": "^2.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^1.0", + "sebastian/version": "^2.0.1" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2", + "phpunit/dbunit": "<3.0" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "^1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.5.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2018-09-08T15:10:43+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "5.0.10", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/cd1cf05c553ecfec36b170070573e540b67d3f1f", + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.5", + "php": "^7.0", + "phpunit/php-text-template": "^1.2.1", + "sebastian/exporter": "^3.1" + }, + "conflict": { + "phpunit/phpunit": "<6.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.5.11" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2018-08-09T05:50:03+00:00" + }, + { + "name": "pimple/pimple", + "version": "v3.2.3", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/9e403941ef9d65d20cba7d54e29fe906db42cf32", + "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/container": "^1.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "autoload": { + "psr-0": { + "Pimple": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Pimple, a simple Dependency Injection Container", + "homepage": "http://pimple.sensiolabs.org", + "keywords": [ + "container", + "dependency injection" + ], + "time": "2018-01-21T07:42:36+00:00" + }, + { + "name": "psr/log", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2016-10-10T12:19:37+00:00" + }, + { + "name": "sami/sami", + "version": "v4.1.2", + "source": { + "type": "git", + "url": "https://github.com/FriendsOfPHP/Sami.git", + "reference": "19b8a82b858bd31544c468317c8307188ccb4022" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FriendsOfPHP/Sami/zipball/19b8a82b858bd31544c468317c8307188ccb4022", + "reference": "19b8a82b858bd31544c468317c8307188ccb4022", + "shasum": "" + }, + "require": { + "blackfire/php-sdk": "^1.5.18", + "michelf/php-markdown": "~1.3", + "nikic/php-parser": "~3.0", + "php": "^7.1.3", + "phpdocumentor/reflection-docblock": "~2.0", + "pimple/pimple": "~3.0", + "symfony/console": "~3.0|~4.0", + "symfony/filesystem": "~3.0|~4.0", + "symfony/finder": "~3.0|~4.0", + "symfony/process": "~3.0|~4.0", + "symfony/yaml": "~3.0|~4.0", + "twig/twig": "~2.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "~4.0" + }, + "bin": [ + "sami.php" + ], + "type": "application", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Sami\\": "Sami/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Sami, an API documentation generator", + "homepage": "http://sami.sensiolabs.org", + "keywords": [ + "phpdoc" + ], + "abandoned": true, + "time": "2018-07-02T13:20:39+00:00" + }, + { + "name": "satooshi/php-coveralls", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-coveralls/php-coveralls.git", + "reference": "37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-coveralls/php-coveralls/zipball/37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad", + "reference": "37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-simplexml": "*", + "guzzle/guzzle": "^2.8 || ^3.0", + "php": "^5.3.3 || ^7.0", + "psr/log": "^1.0", + "symfony/config": "^2.1 || ^3.0 || ^4.0", + "symfony/console": "^2.1 || ^3.0 || ^4.0", + "symfony/stopwatch": "^2.0 || ^3.0 || ^4.0", + "symfony/yaml": "^2.0 || ^3.0 || ^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0" + }, + "suggest": { + "symfony/http-kernel": "Allows Symfony integration" + }, + "bin": [ + "bin/coveralls" + ], + "type": "library", + "autoload": { + "psr-4": { + "Satooshi\\": "src/Satooshi/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kitamura Satoshi", + "email": "with.no.parachute@gmail.com", + "homepage": "https://www.facebook.com/satooshi.jp" + } + ], + "description": "PHP client library for Coveralls API", + "homepage": "https://github.com/php-coveralls/php-coveralls", + "keywords": [ + "ci", + "coverage", + "github", + "test" + ], + "abandoned": "php-coveralls/php-coveralls", + "time": "2017-12-06T23:17:56+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "2.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/diff": "^2.0 || ^3.0", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2018-02-01T13:46:46+00:00" + }, + { + "name": "sebastian/diff", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-08-03T08:09:46+00:00" + }, + { + "name": "sebastian/environment", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2017-07-01T08:51:00+00:00" + }, + { + "name": "sebastian/exporter", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2017-04-03T13:19:02+00:00" + }, + { + "name": "sebastian/global-state", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2017-04-27T15:39:26+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-08-03T12:35:26+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "773f97c67f28de00d397be301821b06708fca0be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "time": "2017-03-29T09:07:27+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2017-03-03T06:23:57+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28T20:34:47+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "4.8.5", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "057f3f154cf4888b60eb4cdffadc509a3ae9dccd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/057f3f154cf4888b60eb4cdffadc509a3ae9dccd", + "reference": "057f3f154cf4888b60eb4cdffadc509a3ae9dccd", + "shasum": "" + }, + "require": { + "php": "^7.1", + "squizlabs/php_codesniffer": "^3.3.0" + }, + "require-dev": { + "jakub-onderka/php-parallel-lint": "1.0.0", + "phing/phing": "2.16.1", + "phpstan/phpstan": "0.9.2", + "phpstan/phpstan-phpunit": "0.9.4", + "phpstan/phpstan-strict-rules": "0.9", + "phpunit/phpunit": "7.3.5" + }, + "type": "phpcodesniffer-standard", + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "time": "2018-10-05T12:10:21+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "6ad28354c04b364c3c71a34e4a18b629cc3b231e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/6ad28354c04b364c3c71a34e4a18b629cc3b231e", + "reference": "6ad28354c04b364c3c71a34e4a18b629cc3b231e", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2018-09-23T23:08:17+00:00" + }, + { + "name": "symfony/config", + "version": "v4.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "b3d4d7b567d7a49e6dfafb6d4760abc921177c96" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/b3d4d7b567d7a49e6dfafb6d4760abc921177c96", + "reference": "b3d4d7b567d7a49e6dfafb6d4760abc921177c96", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/filesystem": "~3.4|~4.0", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/finder": "<3.4" + }, + "require-dev": { + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/finder": "~3.4|~4.0", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "time": "2018-09-08T13:24:10+00:00" + }, + { + "name": "symfony/console", + "version": "v4.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "dc7122fe5f6113cfaba3b3de575d31112c9aa60b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/dc7122fe5f6113cfaba3b3de575d31112c9aa60b", + "reference": "dc7122fe5f6113cfaba3b3de575d31112c9aa60b", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/process": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0" + }, + "suggest": { + "psr/log-implementation": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2018-10-03T08:15:46+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v2.8.46", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "84ae343f39947aa084426ed1138bb96bf94d1f12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/84ae343f39947aa084426ed1138bb96bf94d1f12", + "reference": "84ae343f39947aa084426ed1138bb96bf94d1f12", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.6|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2018-07-26T09:03:18+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v4.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "596d12b40624055c300c8b619755b748ca5cf0b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/596d12b40624055c300c8b619755b748ca5cf0b5", + "reference": "596d12b40624055c300c8b619755b748ca5cf0b5", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "time": "2018-10-02T12:40:59+00:00" + }, + { + "name": "symfony/finder", + "version": "v4.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "1f17195b44543017a9c9b2d437c670627e96ad06" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/1f17195b44543017a9c9b2d437c670627e96ad06", + "reference": "1f17195b44543017a9c9b2d437c670627e96ad06", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "time": "2018-10-03T08:47:56+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.9.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", + "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2018-08-06T14:22:27+00:00" + }, + { + "name": "symfony/process", + "version": "v4.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "ee33c0322a8fee0855afcc11fff81e6b1011b529" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/ee33c0322a8fee0855afcc11fff81e6b1011b529", + "reference": "ee33c0322a8fee0855afcc11fff81e6b1011b529", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2018-10-02T12:40:59+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v4.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "5bfc064125b73ff81229e19381ce1c34d3416f4b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5bfc064125b73ff81229e19381ce1c34d3416f4b", + "reference": "5bfc064125b73ff81229e19381ce1c34d3416f4b", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Stopwatch Component", + "homepage": "https://symfony.com", + "time": "2018-10-02T12:40:59+00:00" + }, + { + "name": "symfony/yaml", + "version": "v4.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "367e689b2fdc19965be435337b50bc8adf2746c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/367e689b2fdc19965be435337b50bc8adf2746c9", + "reference": "367e689b2fdc19965be435337b50bc8adf2746c9", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "~3.4|~4.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2018-10-02T16:36:10+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "time": "2017-04-07T12:08:54+00:00" + }, + { + "name": "twig/twig", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "6a5f676b77a90823c2d4eaf76137b771adf31323" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/6a5f676b77a90823c2d4eaf76137b771adf31323", + "reference": "6a5f676b77a90823c2d4eaf76137b771adf31323", + "shasum": "" + }, + "require": { + "php": "^7.0", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "psr/container": "^1.0", + "symfony/debug": "^2.7", + "symfony/phpunit-bridge": "^3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "autoload": { + "psr-0": { + "Twig_": "lib/" + }, + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + }, + { + "name": "Twig Team", + "homepage": "https://twig.symfony.com/contributors", + "role": "Contributors" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "time": "2018-07-13T07:18:09+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.1", + "ext-mongodb": "*" + }, + "platform-dev": [] +} From d25d9954071c8034ab53432358aed68cd6cfb751 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Fri, 19 Oct 2018 18:21:55 -0300 Subject: [PATCH 010/116] Move unit tests --- composer.json | 2 +- tests/{ => Unit}/ActiveRecordTest.php | 0 tests/{ => Unit}/Connection/ConnectionTest.php | 0 tests/{ => Unit}/Connection/PoolTest.php | 0 tests/{ => Unit}/Container/IocTest.php | 0 tests/{ => Unit}/Cursor/CacheableCursorTest.php | 0 tests/{ => Unit}/Cursor/CursorFactoryTest.php | 0 tests/{ => Unit}/Cursor/CursorTest.php | 0 tests/{ => Unit}/Cursor/EmbeddedCursorTest.php | 0 tests/{ => Unit}/DataMapper/BulkWriteTest.php | 0 tests/{ => Unit}/DataMapper/DataMapperTest.php | 0 tests/{ => Unit}/DataMapper/EntityAssemblerTest.php | 0 tests/{ => Unit}/DataMapper/SchemaMapperTest.php | 0 tests/{ => Unit}/Event/EventTriggerServiceTest.php | 0 tests/{ => Unit}/ManagerTest.php | 0 tests/{ => Unit}/Model/AttributesTest.php | 0 tests/{ => Unit}/Model/DocumentEmbedderTest.php | 0 tests/{ => Unit}/Model/RelationsTest.php | 0 tests/{ => Unit}/Schema/DynamicSchemaTest.php | 0 tests/{ => Unit}/Schema/SchemaTest.php | 0 tests/{ => Unit}/TestCase.php | 0 tests/{ => Unit}/Util/CacheComponentTest.php | 0 tests/{ => Unit}/Util/LocalDateTimeTest.php | 0 tests/{ => Unit}/Util/ObjectIdUtilsTest.php | 0 tests/{ => Unit}/Util/SequenceServiceTest.php | 0 25 files changed, 1 insertion(+), 1 deletion(-) rename tests/{ => Unit}/ActiveRecordTest.php (100%) rename tests/{ => Unit}/Connection/ConnectionTest.php (100%) rename tests/{ => Unit}/Connection/PoolTest.php (100%) rename tests/{ => Unit}/Container/IocTest.php (100%) rename tests/{ => Unit}/Cursor/CacheableCursorTest.php (100%) rename tests/{ => Unit}/Cursor/CursorFactoryTest.php (100%) rename tests/{ => Unit}/Cursor/CursorTest.php (100%) rename tests/{ => Unit}/Cursor/EmbeddedCursorTest.php (100%) rename tests/{ => Unit}/DataMapper/BulkWriteTest.php (100%) rename tests/{ => Unit}/DataMapper/DataMapperTest.php (100%) rename tests/{ => Unit}/DataMapper/EntityAssemblerTest.php (100%) rename tests/{ => Unit}/DataMapper/SchemaMapperTest.php (100%) rename tests/{ => Unit}/Event/EventTriggerServiceTest.php (100%) rename tests/{ => Unit}/ManagerTest.php (100%) rename tests/{ => Unit}/Model/AttributesTest.php (100%) rename tests/{ => Unit}/Model/DocumentEmbedderTest.php (100%) rename tests/{ => Unit}/Model/RelationsTest.php (100%) rename tests/{ => Unit}/Schema/DynamicSchemaTest.php (100%) rename tests/{ => Unit}/Schema/SchemaTest.php (100%) rename tests/{ => Unit}/TestCase.php (100%) rename tests/{ => Unit}/Util/CacheComponentTest.php (100%) rename tests/{ => Unit}/Util/LocalDateTimeTest.php (100%) rename tests/{ => Unit}/Util/ObjectIdUtilsTest.php (100%) rename tests/{ => Unit}/Util/SequenceServiceTest.php (100%) diff --git a/composer.json b/composer.json index e7dd7066..de50a65d 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,7 @@ }, "autoload-dev": { "psr-4": { - "Mongolid\\": "tests" + "Mongolid\\": "tests/Unit" } }, "extra": { diff --git a/tests/ActiveRecordTest.php b/tests/Unit/ActiveRecordTest.php similarity index 100% rename from tests/ActiveRecordTest.php rename to tests/Unit/ActiveRecordTest.php diff --git a/tests/Connection/ConnectionTest.php b/tests/Unit/Connection/ConnectionTest.php similarity index 100% rename from tests/Connection/ConnectionTest.php rename to tests/Unit/Connection/ConnectionTest.php diff --git a/tests/Connection/PoolTest.php b/tests/Unit/Connection/PoolTest.php similarity index 100% rename from tests/Connection/PoolTest.php rename to tests/Unit/Connection/PoolTest.php diff --git a/tests/Container/IocTest.php b/tests/Unit/Container/IocTest.php similarity index 100% rename from tests/Container/IocTest.php rename to tests/Unit/Container/IocTest.php diff --git a/tests/Cursor/CacheableCursorTest.php b/tests/Unit/Cursor/CacheableCursorTest.php similarity index 100% rename from tests/Cursor/CacheableCursorTest.php rename to tests/Unit/Cursor/CacheableCursorTest.php diff --git a/tests/Cursor/CursorFactoryTest.php b/tests/Unit/Cursor/CursorFactoryTest.php similarity index 100% rename from tests/Cursor/CursorFactoryTest.php rename to tests/Unit/Cursor/CursorFactoryTest.php diff --git a/tests/Cursor/CursorTest.php b/tests/Unit/Cursor/CursorTest.php similarity index 100% rename from tests/Cursor/CursorTest.php rename to tests/Unit/Cursor/CursorTest.php diff --git a/tests/Cursor/EmbeddedCursorTest.php b/tests/Unit/Cursor/EmbeddedCursorTest.php similarity index 100% rename from tests/Cursor/EmbeddedCursorTest.php rename to tests/Unit/Cursor/EmbeddedCursorTest.php diff --git a/tests/DataMapper/BulkWriteTest.php b/tests/Unit/DataMapper/BulkWriteTest.php similarity index 100% rename from tests/DataMapper/BulkWriteTest.php rename to tests/Unit/DataMapper/BulkWriteTest.php diff --git a/tests/DataMapper/DataMapperTest.php b/tests/Unit/DataMapper/DataMapperTest.php similarity index 100% rename from tests/DataMapper/DataMapperTest.php rename to tests/Unit/DataMapper/DataMapperTest.php diff --git a/tests/DataMapper/EntityAssemblerTest.php b/tests/Unit/DataMapper/EntityAssemblerTest.php similarity index 100% rename from tests/DataMapper/EntityAssemblerTest.php rename to tests/Unit/DataMapper/EntityAssemblerTest.php diff --git a/tests/DataMapper/SchemaMapperTest.php b/tests/Unit/DataMapper/SchemaMapperTest.php similarity index 100% rename from tests/DataMapper/SchemaMapperTest.php rename to tests/Unit/DataMapper/SchemaMapperTest.php diff --git a/tests/Event/EventTriggerServiceTest.php b/tests/Unit/Event/EventTriggerServiceTest.php similarity index 100% rename from tests/Event/EventTriggerServiceTest.php rename to tests/Unit/Event/EventTriggerServiceTest.php diff --git a/tests/ManagerTest.php b/tests/Unit/ManagerTest.php similarity index 100% rename from tests/ManagerTest.php rename to tests/Unit/ManagerTest.php diff --git a/tests/Model/AttributesTest.php b/tests/Unit/Model/AttributesTest.php similarity index 100% rename from tests/Model/AttributesTest.php rename to tests/Unit/Model/AttributesTest.php diff --git a/tests/Model/DocumentEmbedderTest.php b/tests/Unit/Model/DocumentEmbedderTest.php similarity index 100% rename from tests/Model/DocumentEmbedderTest.php rename to tests/Unit/Model/DocumentEmbedderTest.php diff --git a/tests/Model/RelationsTest.php b/tests/Unit/Model/RelationsTest.php similarity index 100% rename from tests/Model/RelationsTest.php rename to tests/Unit/Model/RelationsTest.php diff --git a/tests/Schema/DynamicSchemaTest.php b/tests/Unit/Schema/DynamicSchemaTest.php similarity index 100% rename from tests/Schema/DynamicSchemaTest.php rename to tests/Unit/Schema/DynamicSchemaTest.php diff --git a/tests/Schema/SchemaTest.php b/tests/Unit/Schema/SchemaTest.php similarity index 100% rename from tests/Schema/SchemaTest.php rename to tests/Unit/Schema/SchemaTest.php diff --git a/tests/TestCase.php b/tests/Unit/TestCase.php similarity index 100% rename from tests/TestCase.php rename to tests/Unit/TestCase.php diff --git a/tests/Util/CacheComponentTest.php b/tests/Unit/Util/CacheComponentTest.php similarity index 100% rename from tests/Util/CacheComponentTest.php rename to tests/Unit/Util/CacheComponentTest.php diff --git a/tests/Util/LocalDateTimeTest.php b/tests/Unit/Util/LocalDateTimeTest.php similarity index 100% rename from tests/Util/LocalDateTimeTest.php rename to tests/Unit/Util/LocalDateTimeTest.php diff --git a/tests/Util/ObjectIdUtilsTest.php b/tests/Unit/Util/ObjectIdUtilsTest.php similarity index 100% rename from tests/Util/ObjectIdUtilsTest.php rename to tests/Unit/Util/ObjectIdUtilsTest.php diff --git a/tests/Util/SequenceServiceTest.php b/tests/Unit/Util/SequenceServiceTest.php similarity index 100% rename from tests/Util/SequenceServiceTest.php rename to tests/Unit/Util/SequenceServiceTest.php From 5c84b67e256d97b28b2f70d2b3743559b40323ad Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Fri, 19 Oct 2018 18:36:21 -0300 Subject: [PATCH 011/116] Create integration tests folder and testsuite --- .travis.yml | 4 ++-- phpunit.xml | 7 +++++-- phpunit.xml.dist | 7 +++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8feab2a6..c6940883 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,9 +12,9 @@ before_script: - composer install --no-interaction script: - - mkdir -p build/logs - vendor/bin/phpcs - - phpunit -c phpunit.xml.dist && make sniff + - phpunit --testsuite unit -c phpunit.xml.dist + - phpunit --testsuite integration after_script: - php vendor/bin/coveralls -v diff --git a/phpunit.xml b/phpunit.xml index 7c9faa18..2541f4c2 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -12,8 +12,11 @@ syntaxCheck="false" > - - ./tests/ + + tests/Unit + + + tests/Integration diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d386bae0..8f79b652 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -12,8 +12,11 @@ syntaxCheck="false" > - - ./tests/ + + tests/Unit + + + tests/Integration From e57aba6211e09619e73c63cd4862b386d9561ecd Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Fri, 19 Oct 2018 23:49:13 -0300 Subject: [PATCH 012/116] Fix CS rules --- phpcs.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpcs.xml b/phpcs.xml index 9965de2e..e6c77911 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -17,10 +17,10 @@ - tests/DataMapper/EntityAssemblerTest.php + tests/Unit/DataMapper/EntityAssemblerTest.php - tests/DataMapper/EntityAssemblerTest.php + tests/Unit/DataMapper/EntityAssemblerTest.php From 3d1ba7589e841d0a183135929fc5d54393b99527 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Fri, 19 Oct 2018 23:51:23 -0300 Subject: [PATCH 013/116] Add pre-commit hook --- .github/pre-commit | 52 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100755 .github/pre-commit diff --git a/.github/pre-commit b/.github/pre-commit new file mode 100755 index 00000000..a6cfe6af --- /dev/null +++ b/.github/pre-commit @@ -0,0 +1,52 @@ +#!/bin/bash + +STAGED_FILES_CMD=`git diff --cached --name-only --diff-filter=ACMR | grep \.php` + +# Determine if a file list is passed +if [ "$#" -eq 1 ] +then + oIFS=$IFS + IFS=' + ' + SFILES="$1" + IFS=$oIFS +fi +SFILES=${SFILES:-$STAGED_FILES_CMD} + +# Fix path for docker +for FILE in $SFILES +do + FILES="$FILES $FILE" +done + +if [ "$FILES" != "" ] +then + echo -e "\033[1;33m"Running Code Sniffer..."\033[0m" + docker-compose run --rm --no-deps -T php vendor/bin/phpcs $FILES + + if [ $? != 0 ] + then + # Allows us to read user input below, assigns stdin to keyboard + exec < /dev/tty + + read -p "There are some Coding Standards violations. Do you want to fix the auto-fixable ones? (Yes) " choice + [ "$choice" = "" ] && choice='Y' + + case ${choice:0:1} in + y|Y ) + echo -e "\033[1;33m"Running Code Beautifier..."\033[0m" + docker-compose run --rm --no-deps -T php vendor/bin/phpcbf $FILES + echo -e "\033[0;32m"Done. Please add the fixes before commit."\033[0m" + + exit 1 + ;; + * ) + echo -e "\033[41m"Please, fix the Coding Standards violations before commit."\033[0m" + + exit 1 + ;; + esac + fi +fi + +exit $? From 6d800c527f5a26ca9b364ceaf74d08e4ba4cff16 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Sat, 20 Oct 2018 12:03:53 -0300 Subject: [PATCH 014/116] Create integration test to ensure no regressions later --- composer.json | 4 +- docker-compose.yml | 2 + tests/Integration/IntegrationTestCase.php | 28 +++++ tests/Integration/PersistedDataTest.php | 133 ++++++++++++++++++++++ tests/Integration/Stubs/User.php | 30 +++++ tests/Util/DropDatabaseTrait.php | 17 +++ tests/Util/SetupPoolTrait.php | 25 ++++ 7 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 tests/Integration/IntegrationTestCase.php create mode 100644 tests/Integration/PersistedDataTest.php create mode 100644 tests/Integration/Stubs/User.php create mode 100644 tests/Util/DropDatabaseTrait.php create mode 100644 tests/Util/SetupPoolTrait.php diff --git a/composer.json b/composer.json index de50a65d..7ae76de6 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,9 @@ }, "autoload-dev": { "psr-4": { - "Mongolid\\": "tests/Unit" + "Mongolid\\": "tests/Unit", + "Mongolid\\Tests\\Integration\\": "tests/Integration", + "Mongolid\\Tests\\Util\\": "tests/Util" } }, "extra": { diff --git a/docker-compose.yml b/docker-compose.yml index c633d274..4d7c6db0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,6 +7,8 @@ services: - db volumes: - .:/var/www/html + environment: + - DB_HOST=db db: image: mongo:4.0 diff --git a/tests/Integration/IntegrationTestCase.php b/tests/Integration/IntegrationTestCase.php new file mode 100644 index 00000000..af0f389d --- /dev/null +++ b/tests/Integration/IntegrationTestCase.php @@ -0,0 +1,28 @@ +setupPool($host, $database); + $this->dropDatabase(); + } + + protected function tearDown() + { + $this->dropDatabase(); + parent::tearDown(); + } +} diff --git a/tests/Integration/PersistedDataTest.php b/tests/Integration/PersistedDataTest.php new file mode 100644 index 00000000..634c75aa --- /dev/null +++ b/tests/Integration/PersistedDataTest.php @@ -0,0 +1,133 @@ +_id = new ObjectId('5bcb310783a7fcdf1bf1a672'); + } + + public function testSaveInsertingData() + { + // Set + $user = $this->getUser(); + + $expected = [ + '_id' => (string) $this->_id, + 'name' => 'John Doe', + 'age' => 25, + 'height' => 1.80, + 'preferences' => [ + 'email' => 'never', + ], + 'friends' => [], + 'address' => null, + ]; + + // Actions + $saveResult = $user->save(); + $result = (array) $user->collection()->findOne(['_id' => $this->_id]); + $result['_id'] = (string) ($result['_id'] ?? ''); + + // Assertions + $this->assertTrue($saveResult); + $this->assertSame($expected, $result); + } + + public function testSaveUpdatingData() + { + // Set + $user = $this->getUser(true); + + $user->name = 'Jane Doe'; + unset($user->age); + $user->height = null; + $user->email = 'jane@doe.com'; + $user->preferences = []; + $user->friends = ['Mary']; + $user->address = '123 Blue Street'; + + $expected = [ + '_id' => (string) $user->_id, + 'name' => 'Jane Doe', + 'height' => null, + 'preferences' => [], + 'friends' => ['Mary'], + 'address' => '123 Blue Street', + 'email' => 'jane@doe.com', + ]; + + // Actions + $updateResult = $user->save(); + $result = (array) $user->collection()->findOne(['_id' => $user->_id]); + $result['_id'] = (string) ($result['_id'] ?? ''); + + // Assertions + $this->assertTrue($updateResult); + $this->assertSame($expected, $result); + } + + public function testUpdateData() + { + // Set + $user = $this->getUser(true); + + $user->name = 'Jane Doe'; + $user->age = null; // TODO unset($user->age); not working right now - bug! + $user->height = null; + $user->email = 'jane@doe.com'; + $user->preferences = []; + $user->friends = ['Mary']; + $user->address = '123 Blue Street'; + + $expected = [ + '_id' => (string) $user->_id, + 'name' => 'Jane Doe', + 'age' => null, + 'height' => null, + 'preferences' => [], + 'friends' => ['Mary'], + 'address' => '123 Blue Street', + 'email' => 'jane@doe.com', + ]; + + // Actions + $updateResult = $user->update(); + $result = (array) $user->collection()->findOne(['_id' => $user->_id]); + $result['_id'] = (string) ($result['_id'] ?? ''); + + // Assertions + $this->assertTrue($updateResult); + $this->assertSame($expected, $result); + } + + private function getUser(bool $save = false): User + { + $user = new User(); + $user->_id = $this->_id; + $user->name = 'John Doe'; + $user->age = 25; + $user->height = 1.80; + $user->preferences = [ + 'email' => 'never', + ]; + $user->friends = []; + $user->address = null; + + if ($save) { + $this->assertTrue($user->save(), 'Failed to save user!'); + } + + return $user; + } +} diff --git a/tests/Integration/Stubs/User.php b/tests/Integration/Stubs/User.php new file mode 100644 index 00000000..439b6279 --- /dev/null +++ b/tests/Integration/Stubs/User.php @@ -0,0 +1,30 @@ + 'objectId', + ]; + + public function collection(): Collection + { + $connection = Ioc::make(Pool::class)->getConnection(); + $client = $connection->getRawConnection(); + + return $client->{$connection->defaultDatabase}->{$this->collection}; + } +} diff --git a/tests/Util/DropDatabaseTrait.php b/tests/Util/DropDatabaseTrait.php new file mode 100644 index 00000000..6f726c82 --- /dev/null +++ b/tests/Util/DropDatabaseTrait.php @@ -0,0 +1,17 @@ +getConnection(); + + $connection->getRawConnection() + ->dropDatabase($connection->defaultDatabase); + } +} diff --git a/tests/Util/SetupPoolTrait.php b/tests/Util/SetupPoolTrait.php new file mode 100644 index 00000000..a864385c --- /dev/null +++ b/tests/Util/SetupPoolTrait.php @@ -0,0 +1,25 @@ +defaultDatabase = $database; + + $pool = new Pool(); + $pool->addConnection($connection); + + return $pool; + } + ); + } +} From 06e1e9ab694e9bc22409168e6c43f1b00462bcd9 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Sat, 20 Oct 2018 12:13:38 -0300 Subject: [PATCH 015/116] Fix travis --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index c6940883..fb4e5d02 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,8 @@ php: - 7.1 - 7.2 +services: mongodb + before_install: - if [[ $TRAVIS_PHP_VERSION != 7.2 ]]; then pecl install mongodb; fi - echo "extension = mongodb.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini From 6920ac74f8ddc2624ed87c5b6ac643e5b861fefd Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Sat, 20 Oct 2018 12:29:09 -0300 Subject: [PATCH 016/116] Update .gitattributes --- .gitattributes | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/.gitattributes b/.gitattributes index a6a483b2..cb199a2a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,14 +2,22 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.gitattributes export-ignore -/.gitignore export-ignore -/.coveralls.yml export-ignore -/.travis.yml export-ignore -/.styleci.yml export-ignore -/phpunit.xml export-ignore -/phpunit.xml.dist export-ignore -/tests export-ignore -/docs export-ignore -/CODE_OF_CONDUCT.md export-ignore -/sami.php export-ignore +# Folders +/.github export-ignore +/docs export-ignore +/tests export-ignore +# Files +/.coveralls.yml export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.travis.yml export-ignore +/CODE_OF_CONDUCT.md export-ignore +/composer.lock export-ignore +/docker-compose.override.yml.example export-ignore +/docker-compose.yml export-ignore +/Makefile export-ignore +/mkdocs.yml export-ignore +/phpcs.xml export-ignore +/phpunit.xml export-ignore +/phpunit.xml.dist export-ignore +/sami.php export-ignore From cb97919e48f94bc732628c222f6226b299b1fceb Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Sat, 20 Oct 2018 13:18:59 -0300 Subject: [PATCH 017/116] Drop connection pool The PHP driver uses libmongoc's single-threaded client mode, which does not implement connection pools. There is also no way to implement your own connection pooling using this driver. Each single-threaded libmongoc client object maintains at most one socket connection per mongod or mongos server. The relationship between MongoDB\Driver\Manager objects and libmongoc client objects is discussed in Connection Handling in the driver documentation. Connection pools should not be needed for any single-threaded application, unless you're using an async PHP framework (e.g. Amp, ReactPHP). In those cases, you'd probably need to implement your own driver as we provide no way to integrate libmongoc's IO with a PHP event loop. Multi-threaded PHP applications (e.g. pthreads, forking) are not supported by the driver. https://github.com/mongodb/mongo-php-driver/issues/688#issuecomment-349193875 --- docs/basics.md | 122 +++++++++--------- src/Connection/Pool.php | 56 -------- src/Cursor/Cursor.php | 8 +- src/DataMapper/BulkWrite.php | 6 +- src/DataMapper/DataMapper.php | 21 ++- src/Manager.php | 36 +++--- src/Util/SequenceService.php | 23 ++-- tests/Integration/IntegrationTestCase.php | 6 +- tests/Integration/Stubs/User.php | 4 +- tests/Unit/Connection/PoolTest.php | 64 --------- tests/Unit/Cursor/CursorTest.php | 20 +-- tests/Unit/DataMapper/BulkWriteTest.php | 12 +- tests/Unit/DataMapper/DataMapperTest.php | 90 ++++++------- tests/Unit/ManagerTest.php | 8 +- tests/Unit/Util/SequenceServiceTest.php | 14 +- tests/Util/DropDatabaseTrait.php | 5 +- ...PoolTrait.php => SetupConnectionTrait.php} | 12 +- 17 files changed, 177 insertions(+), 330 deletions(-) delete mode 100644 src/Connection/Pool.php delete mode 100644 tests/Unit/Connection/PoolTest.php rename tests/Util/{SetupPoolTrait.php => SetupConnectionTrait.php} (58%) diff --git a/docs/basics.md b/docs/basics.md index 30732b51..ab731ff2 100644 --- a/docs/basics.md +++ b/docs/basics.md @@ -2,20 +2,18 @@ You can install library through Composer: -``` -$ composer require leroy-merlin-br/mongolid +```shell +composer require leroy-merlin-br/mongolid ``` ### Requirements -- PHP**7** +- PHP **7.1** - [MongoDB Driver](http://php.net/manual/en/set.mongodb.php) -> **Note:** If you are looking for the old PHP 5.x version, head to the [v0.8 branch](https://github.com/leroy-merlin-br/mongolid/tree/v0.8-dev). - ## Setup -If you are not using Laravel, you should initialize the Mongolid connection pool and container manually. +If you are not using Laravel, you should initialize the Mongolid connection and container manually. The minimalistic way of doing it is to use `Mongolid\Manager`: ```php @@ -23,9 +21,10 @@ The minimalistic way of doing it is to use `Mongolid\Manager`: require 'vendor/autoload.php'; use Mongolid\Manager; -use Mongolid\Connection; +use Mongolid\Connection\Connection; -$manager = new Manager(new Connection('mongodb://localhost:27017')); +$manager = new Manager(); +$manager->setConnection(new Connection('mongodb://localhost:27017')); ``` Now you are ready to create your own models :smile: @@ -35,7 +34,9 @@ Now you are ready to create your own models :smile: > **Note:** Mongolid does support [**DataMapper** pattern](./datamapper.md), but in order to understand it let's begin with the **ActiveRecord** pattern: ```php -class Post extends Mongolid\ActiveRecord {} +class Post extends Mongolid\ActiveRecord +{ +} ``` Note that we did not tell Mongolid which collection to use for our `Post` model. So, in this case, Mongolid **will not save the model into the database**. This can be used for models that represents objects that will be embedded within another object and will not have their own collection. @@ -59,56 +60,55 @@ Once a model is defined, you are ready to start retrieving and creating document **Retrieving All Models** ```php - $posts = Post::all(); +$posts = Post::all(); ``` **Retrieving A Document By Primary Key** ```php - $post = Post::first('4af9f23d8ead0e1d32000000'); +$post = Post::first('4af9f23d8ead0e1d32000000'); - // or +// or - $post = Post::first(new MongoDB\BSON\ObjectID('4af9f23d8ead0e1d32000000')); +$post = Post::first(new MongoDB\BSON\ObjectID('4af9f23d8ead0e1d32000000')); ``` **Retrieving One Document By attribute** ```php - $user = Post::first(['title'=>'How Monglid saved the day']); +$user = Post::first(['title' => 'How Mongolid saved the day']); ``` **Retrieving Many Documents By attribute** ```php - $posts = Post::where(['category'=>'coding']); +$posts = Post::where(['category' => 'coding']); ``` **Querying Using Mongolid Models** ```php - $posts = Post::where(['votes'=>['$gt'=>100]])->limit(10); // Mongolid\Cursor\Cursor +$posts = Post::where(['votes' => ['$gt' => 100]])->limit(10); // Mongolid\Cursor\Cursor - foreach ($posts as $post) - { - var_dump($post->title); - } +foreach ($posts as $post) { + var_dump($post->title); +} ``` **Mongolid Count** ```php - $count = Post::where(['votes'=>['$gt'=>100]])->count(); // integer +$count = Post::where(['votes' => ['$gt' => 100]])->count(); // int ``` Pretty easy right? -## Monglid Cursor +## Mongolid Cursor In MongoDB, a cursor is used to iterate through the results of a database query. For example, to query the database and see all results: ```php - $cursor = User::where(['kind'=>'visitor']); +$cursor = User::where(['kind' => 'visitor']); ``` In the above example, the $cursor variable will be a `Mongolid\Cursor\Cursor`. @@ -120,36 +120,36 @@ The Mongolid's `Cursor` wraps the original `MongoDB\Driver\Cursor` object of the The `Mongolid\Cursor\Cursor` object has alot of methods that helps you to iterate, refine and get information. For example: ```php - $cursor = User::where(['kind'=>'visitor']); +$cursor = User::where(['kind'=>'visitor']); - // Sorts the results by given fields. In the example bellow, it sorts by username DESC - $cursor->sort(['username'=>-1]); +// Sorts the results by given fields. In the example bellow, it sorts by username DESC +$cursor->sort(['username'=>-1]); - // Limits the number of results returned. - $cursor->limit(10); +// Limits the number of results returned. +$cursor->limit(10); - // Skips a number of results. Good for pagination - $cursor->skip(20); +// Skips a number of results. Good for pagination +$cursor->skip(20); - // Checks if the cursor is reading a valid result. - $cursor->valid(); +// Checks if the cursor is reading a valid result. +$cursor->valid(); - // Returns the first result - $cursor->first(); +// Returns the first result +$cursor->first(); ``` You can also chain some methods: ```php - $page = 2; +$page = 2; - // In order to display 10 results per page - $cursor = User::all()->sort(['_id'=>1])->skip(10 * $page)->limit(10); +// In order to display 10 results per page +$cursor = User::all()->sort(['_id'=>1])->skip(10 * $page)->limit(10); - // Then iterate through it - foreach($cursor as $user) { - // do something - } +// Then iterate through it +foreach($cursor as $user) { + // do something +} ``` ## Insert, Update, Delete @@ -159,11 +159,11 @@ To create a new document in the database from a model, simply create a new model **Saving A New Model** ```php - $post = new Post; +$post = new Post(); - $post->title = 'Foo bar john doe'; +$post->title = 'Foo bar john doe'; - $post->save(); +$post->save(); ``` > **Note:** Typically, your Mongolid models will have auto-generated `_id` keys. However, if you wish to specify your own keys, set the `_id` attribute. @@ -173,11 +173,11 @@ To update a model, you may retrieve it, change an attribute, and use the `save` **Updating A Retrieved Model** ```php - $post = Post::first('4af9f23d8ead0e1d32000000'); +$post = Post::first('4af9f23d8ead0e1d32000000'); - $post->subject = 'technology'; +$post->subject = 'technology'; - $post->save(); +$post->save(); ``` To delete a model, simply call the `delete` method on the instance: @@ -185,9 +185,9 @@ To delete a model, simply call the `delete` method on the instance: **Deleting An Existing Model** ```php - $post = Post::first('4af9f23d8ead0e1d32000000'); +$post = Post::first('4af9f23d8ead0e1d32000000'); - $post->delete(); +$post->delete(); ``` ## Mass Assignment @@ -201,11 +201,11 @@ The `fillable` property specifies which attributes should be mass-assignable. Th **Defining Fillable Attributes On A Model** ```php - class Post extends ActiveRecord { +class Post extends ActiveRecord { - protected $fillable = ['title', 'category', 'body']; + protected $fillable = ['title', 'category', 'body']; - } +} ``` In this example, only the three listed attributes will be mass-assignable. @@ -215,11 +215,11 @@ The inverse of `fillable` is `guarded`, and serves as a "black-list" instead of **Defining Guarded Attributes On A Model** ```php - class Post extends ActiveRecord { +class Post extends ActiveRecord { - protected $guarded = ['_id', 'votes']; + protected $guarded = ['_id', 'votes']; - } +} ``` In the example above, the `id` and `votes` attributes may **not** be mass assigned. All other attributes will be mass assignable. @@ -227,8 +227,8 @@ In the example above, the `id` and `votes` attributes may **not** be mass assign You can mass assign attributes using the `fill` method: ```php - $post = new Post; - $post->fill(['title' => 'Bacon']); +$post = new Post; +$post->fill(['title' => 'Bacon']); ``` ## Converting To Arrays / JSON @@ -238,15 +238,15 @@ When building JSON APIs, you may often need to convert your models to arrays or **Converting A Model To An Array** ```php - $user = User::with('roles')->first(); +$user = User::first(); - return $user->toArray(); +return $user->toArray(); ``` Note that [cursors](#cursor) can be converted to array too: ```php - return User::all()->toArray(); +return User::all()->toArray(); ``` To convert a model to JSON, you may use the `toJson` method: @@ -254,5 +254,5 @@ To convert a model to JSON, you may use the `toJson` method: **Converting A Model To JSON** ```php - return User::find(1)->toJson(); +return User::find(1)->toJson(); ``` diff --git a/src/Connection/Pool.php b/src/Connection/Pool.php deleted file mode 100644 index dc44d35d..00000000 --- a/src/Connection/Pool.php +++ /dev/null @@ -1,56 +0,0 @@ -connections = Ioc::make('SplQueue'); - } - - /** - * Gets a connection from the pool. It will cycle through the existent - * connections. - * - * @return Connection - */ - public function getConnection() - { - if ($chosenConn = $this->connections->pop()) { - $this->connections->push($chosenConn); - - return $chosenConn; - } - } - - /** - * Adds a new connection to the pool. - * - * @param Connection $conn the actual connection that will be added to the pool - * - * @return bool Success - */ - public function addConnection(Connection $conn) - { - $this->connections->push($conn); - - return true; - } -} diff --git a/src/Cursor/Cursor.php b/src/Cursor/Cursor.php index f502f036..cc53ccad 100644 --- a/src/Cursor/Cursor.php +++ b/src/Cursor/Cursor.php @@ -7,7 +7,7 @@ use MongoDB\Driver\Exception\LogicException; use MongoDB\Driver\ReadPreference; use Mongolid\ActiveRecord; -use Mongolid\Connection\Pool; +use Mongolid\Connection\Connection; use Mongolid\Container\Ioc; use Mongolid\DataMapper\EntityAssembler; use Mongolid\Schema\Schema; @@ -348,9 +348,9 @@ public function unserialize($serialized) { $attributes = unserialize($serialized); - $conn = Ioc::make(Pool::class)->getConnection(); - $db = $conn->defaultDatabase; - $collectionObject = $conn->getRawConnection()->$db->{$attributes['collection']}; + $connection = Ioc::make(Connection::class); + $db = $connection->defaultDatabase; + $collectionObject = $connection->getRawConnection()->$db->{$attributes['collection']}; foreach ($attributes as $key => $value) { $this->$key = $value; diff --git a/src/DataMapper/BulkWrite.php b/src/DataMapper/BulkWrite.php index 89654b20..02b1f32f 100644 --- a/src/DataMapper/BulkWrite.php +++ b/src/DataMapper/BulkWrite.php @@ -4,7 +4,7 @@ use MongoDB\BSON\ObjectId; use MongoDB\Driver\BulkWrite as MongoBulkWrite; use MongoDB\Driver\WriteConcern; -use Mongolid\Connection\Pool; +use Mongolid\Connection\Connection; use Mongolid\Container\Ioc; use Mongolid\Schema\HasSchemaInterface; use Mongolid\Schema\Schema; @@ -91,7 +91,7 @@ public function updateOne( } /** - * Execute the BulkWrite, using a connection from the Pool. + * Execute the BulkWrite, using connection. * The collection is inferred from entity's collection name. * * @param int $writeConcern @@ -100,7 +100,7 @@ public function updateOne( */ public function execute($writeConcern = 1) { - $connection = Ioc::make(Pool::class)->getConnection(); + $connection = Ioc::make(Connection::class); $manager = $connection->getRawManager(); $namespace = $connection->defaultDatabase.'.'.$this->schema->collection; diff --git a/src/DataMapper/DataMapper.php b/src/DataMapper/DataMapper.php index cd1ad192..376e6e6b 100644 --- a/src/DataMapper/DataMapper.php +++ b/src/DataMapper/DataMapper.php @@ -4,7 +4,7 @@ use InvalidArgumentException; use MongoDB\BSON\ObjectId; use MongoDB\Collection; -use Mongolid\Connection\Pool; +use Mongolid\Connection\Connection; use Mongolid\Container\Ioc; use Mongolid\Cursor\CacheableCursor; use Mongolid\Cursor\Cursor; @@ -38,11 +38,11 @@ class DataMapper implements HasSchemaInterface protected $schema; /** - * Connections that are going to be used to interact with the database. + * Connection that is going to be used to interact with the database. * - * @var Pool + * @var Connection */ - protected $connPool; + protected $connection; /** * Have the responsibility of assembling the data coming from the database into actual entities. @@ -58,12 +58,9 @@ class DataMapper implements HasSchemaInterface */ protected $eventService; - /** - * @param Pool $connPool the connections that are going to be used to interact with the database - */ - public function __construct(Pool $connPool) + public function __construct(Connection $connection) { - $this->connPool = $connPool; + $this->connection = $connection; } /** @@ -360,11 +357,11 @@ protected function getSchemaMapper() */ protected function getCollection(): Collection { - $conn = $this->connPool->getConnection(); - $database = $conn->defaultDatabase; + $connection = $this->connection; + $database = $connection->defaultDatabase; $collection = $this->schema->collection; - return $conn->getRawConnection()->$database->$collection; + return $connection->getRawConnection()->$database->$collection; } /** diff --git a/src/Manager.php b/src/Manager.php index bd2e293d..15e4dcb4 100644 --- a/src/Manager.php +++ b/src/Manager.php @@ -3,7 +3,6 @@ use Illuminate\Container\Container; use Mongolid\Connection\Connection; -use Mongolid\Connection\Pool; use Mongolid\Container\Ioc; use Mongolid\DataMapper\DataMapper; use Mongolid\Event\EventTriggerInterface; @@ -17,10 +16,10 @@ * it easy to use without any framework. * * With the Mongolid\Manager, you can start using Mongolid with pure PHP by - * simply calling the addConnection method. + * simply calling the setConnection method. * * @example - * (new Mongolid\Manager)->addConnection(new Connection); + * (new Mongolid\Manager)->setConnection(new Connection); * // And then start persisting and querying your models. */ class Manager @@ -40,18 +39,18 @@ class Manager public $container; /** - * Mongolid connection pool being object. + * Mongolid cache component object. * - * @var Pool + * @var CacheComponent */ - public $connectionPool; + public $cacheComponent; /** - * Mongolid cache component object. + * Mongolid connection object. * - * @var CacheComponent + * @var Connection */ - public $cacheComponent; + protected $connection; /** * Stores the schemas that have been registered for later use. This may be @@ -70,10 +69,12 @@ class Manager * * @return bool Success */ - public function addConnection(Connection $connection): bool + public function setConnection(Connection $connection): bool { $this->init(); - $this->connectionPool->addConnection($connection); + $this->container->instance(Connection::class, $this->connection); + + $this->connection = $connection; return true; } @@ -87,7 +88,7 @@ public function getConnection() { $this->init(); - return $this->connectionPool->getConnection()->getRawConnection(); + return $this->connection->getRawConnection(); } /** @@ -143,13 +144,14 @@ protected function init() } $this->container = new Container(); - $this->connectionPool = new Pool(); - $this->cacheComponent = new CacheComponent(); - - $this->container->instance(Pool::class, $this->connectionPool); - $this->container->instance(CacheComponentInterface::class, $this->cacheComponent); Ioc::setContainer($this->container); + $this->cacheComponent = new CacheComponent(); + $this->container->instance( + CacheComponentInterface::class, + $this->cacheComponent + ); + static::$singleton = $this; } } diff --git a/src/Util/SequenceService.php b/src/Util/SequenceService.php index eb071e7e..be9cec0e 100644 --- a/src/Util/SequenceService.php +++ b/src/Util/SequenceService.php @@ -2,7 +2,7 @@ namespace Mongolid\Util; use MongoDB\Collection; -use Mongolid\Connection\Pool; +use Mongolid\Connection\Connection; /** * Sequence service will manage and provide auto-increment sequences to be used @@ -19,19 +19,15 @@ class SequenceService protected $collection; /** - * Connections that are going to be used to interact with the database. + * Connection that is going to be used to interact with the database. * - * @var Pool + * @var Connection */ - protected $connPool; + protected $connection; - /** - * @param Pool $connPool the connections that are going to be used to interact with the database - * @param string $collection the collection where the sequences will be stored - */ - public function __construct(Pool $connPool, string $collection = 'mongolid_sequences') + public function __construct(Connection $connection, string $collection = 'mongolid_sequences') { - $this->connPool = $connPool; + $this->connection = $connection; $this->collection = $collection; } @@ -60,9 +56,10 @@ public function getNextValue(string $sequenceName): int */ protected function rawCollection(): Collection { - $conn = $this->connPool->getConnection(); - $database = $conn->defaultDatabase; + $database = $this->connection->defaultDatabase; - return $conn->getRawConnection()->$database->{$this->collection}; + return $this->connection->getRawConnection() + ->$database + ->{$this->collection}; } } diff --git a/tests/Integration/IntegrationTestCase.php b/tests/Integration/IntegrationTestCase.php index af0f389d..15227958 100644 --- a/tests/Integration/IntegrationTestCase.php +++ b/tests/Integration/IntegrationTestCase.php @@ -3,12 +3,12 @@ use Mongolid\TestCase; use Mongolid\Tests\Util\DropDatabaseTrait; -use Mongolid\Tests\Util\SetupPoolTrait; +use Mongolid\Tests\Util\SetupConnectionTrait; class IntegrationTestCase extends TestCase { use DropDatabaseTrait; - use SetupPoolTrait; + use SetupConnectionTrait; protected function setUp() { @@ -16,7 +16,7 @@ protected function setUp() $host = env('DB_HOST', 'localhost'); $database = env('DB_DATABASE', 'testing'); - $this->setupPool($host, $database); + $this->setupConnection($host, $database); $this->dropDatabase(); } diff --git a/tests/Integration/Stubs/User.php b/tests/Integration/Stubs/User.php index 439b6279..a007765f 100644 --- a/tests/Integration/Stubs/User.php +++ b/tests/Integration/Stubs/User.php @@ -3,7 +3,7 @@ use MongoDB\Collection; use Mongolid\ActiveRecord; -use Mongolid\Connection\Pool; +use Mongolid\Connection\Connection; use Mongolid\Container\Ioc; class User extends ActiveRecord @@ -22,7 +22,7 @@ class User extends ActiveRecord public function collection(): Collection { - $connection = Ioc::make(Pool::class)->getConnection(); + $connection = Ioc::make(Connection::class); $client = $connection->getRawConnection(); return $client->{$connection->defaultDatabase}->{$this->collection}; diff --git a/tests/Unit/Connection/PoolTest.php b/tests/Unit/Connection/PoolTest.php deleted file mode 100644 index 9c589bed..00000000 --- a/tests/Unit/Connection/PoolTest.php +++ /dev/null @@ -1,64 +0,0 @@ -setProtected($pool, 'connections', $connQueue); - - // Act - $connQueue->expects() - ->pop() - ->andReturn($connection); - - $connQueue->expects() - ->push($connection); - - // Assert - $this->assertEquals($connection, $pool->getConnection()); - } - - public function testShouldGetNullConnectionFromPoolIfItsEmpty() - { - // Arrange - $pool = new Pool(); - $connQueue = m::mock(); - $this->setProtected($pool, 'connections', $connQueue); - - // Act - $connQueue->expects() - ->pop() - ->andReturn(null); - - $connQueue->expects() - ->push() - ->never(); - - // Assert - $this->assertNull($pool->getConnection()); - } - - public function testShouldAddConnectionToPool() - { - // Arrange - $pool = new Pool(); - $connQueue = m::mock(); - $connection = m::mock(Connection::class); - $this->setProtected($pool, 'connections', $connQueue); - - // Act - $connQueue->expects() - ->push($connection); - - // Assert - $this->assertTrue($pool->addConnection($connection)); - } -} diff --git a/tests/Unit/Cursor/CursorTest.php b/tests/Unit/Cursor/CursorTest.php index ea2ab363..18c663fd 100644 --- a/tests/Unit/Cursor/CursorTest.php +++ b/tests/Unit/Cursor/CursorTest.php @@ -10,7 +10,6 @@ use MongoDB\Driver\ReadPreference; use Mongolid\ActiveRecord; use Mongolid\Connection\Connection; -use Mongolid\Connection\Pool; use Mongolid\Container\Ioc; use Mongolid\Schema\DynamicSchema; use Mongolid\Schema\Schema; @@ -417,8 +416,7 @@ public function testShouldReturnResultsToArray() public function testShouldSerializeAnActiveCursor() { // Arrange - $pool = m::mock(Pool::class); - $conn = m::mock(Connection::class); + $connection = m::mock(Connection::class); $schema = new DynamicSchema(); $cursor = $this->getCursor($schema, null, 'find', [[]]); $driverCollection = $this->getDriverCollection(); @@ -426,19 +424,15 @@ public function testShouldSerializeAnActiveCursor() $this->setProtected($cursor, 'collection', $driverCollection); // Act - Ioc::instance(Pool::class, $pool); + Ioc::instance(Connection::class, $connection); - $pool->expects() - ->getConnection() - ->andReturn($conn); - - $conn->expects() + $connection->expects() ->getRawConnection() - ->andReturn($conn); + ->andReturn($connection); - $conn->defaultDatabase = 'db'; - $conn->db = $conn; - $conn->my_collection = $driverCollection; // Return the same driver Collection + $connection->defaultDatabase = 'db'; + $connection->db = $connection; + $connection->my_collection = $driverCollection; // Return the same driver Collection // Assert $result = unserialize(serialize($cursor)); diff --git a/tests/Unit/DataMapper/BulkWriteTest.php b/tests/Unit/DataMapper/BulkWriteTest.php index 2f6fcc4f..777a0cb2 100644 --- a/tests/Unit/DataMapper/BulkWriteTest.php +++ b/tests/Unit/DataMapper/BulkWriteTest.php @@ -3,11 +3,10 @@ use Mockery as m; use MongoDB\Driver\BulkWrite as MongoBulkWrite; +use MongoDB\Driver\Manager; use MongoDB\Driver\WriteConcern; use Mongolid\Connection\Connection; -use Mongolid\Connection\Pool; use Mongolid\Container\Ioc; -use Mongolid\Manager; use Mongolid\Schema\HasSchemaInterface; use Mongolid\Schema\Schema; use Mongolid\TestCase; @@ -106,25 +105,20 @@ public function testShouldExecuteBulkWrite() $schema = m::mock(Schema::class); $entity->schema = $schema; $mongoBulkWrite = m::mock(new MongoBulkWrite()); - $pool = m::mock(Pool::class); $connection = m::mock(Connection::class); - $manager = m::mock(Manager::class); + $manager = m::mock(new Manager()); $connection->defaultDatabase = 'foo'; $schema->collection = 'bar'; $namespace = 'foo.bar'; - Ioc::instance(Pool::class, $pool); + Ioc::instance(Connection::class, $connection); // Expect $entity->expects() ->getSchema() ->andReturn($schema); - $pool->expects() - ->getConnection() - ->andReturn($connection); - $connection->expects() ->getRawManager() ->andReturn($manager); diff --git a/tests/Unit/DataMapper/DataMapperTest.php b/tests/Unit/DataMapper/DataMapperTest.php index 45e8bb3b..cace8716 100644 --- a/tests/Unit/DataMapper/DataMapperTest.php +++ b/tests/Unit/DataMapper/DataMapperTest.php @@ -7,7 +7,6 @@ use MongoDB\Collection; use MongoDB\Driver\WriteConcern; use Mongolid\Connection\Connection; -use Mongolid\Connection\Pool; use Mongolid\Container\Ioc; use Mongolid\Cursor\CacheableCursor; use Mongolid\Cursor\Cursor; @@ -22,13 +21,13 @@ class DataMapperTest extends TestCase public function testShouldBeAbleToConstructWithSchema() { // Arrange - $connPool = m::mock(Pool::class); + $connection = m::mock(Connection::class); // Act - $mapper = new DataMapper($connPool); + $mapper = new DataMapper($connection); // Assert - $this->assertAttributeEquals($connPool, 'connPool', $mapper); + $this->assertAttributeEquals($connection, 'connection', $mapper); } /** @@ -37,8 +36,8 @@ public function testShouldBeAbleToConstructWithSchema() public function testShouldSave($entity, $writeConcern, $shouldFireEventAfter, $expected) { // Arrange - $connPool = m::mock(Pool::class); - $mapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connPool]); + $connection = m::mock(Connection::class); + $mapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connection]); $options = ['writeConcern' => new WriteConcern($writeConcern)]; $collection = m::mock(Collection::class); @@ -100,8 +99,8 @@ public function testShouldSave($entity, $writeConcern, $shouldFireEventAfter, $e public function testShouldInsert($entity, $writeConcern, $shouldFireEventAfter, $expected) { // Arrange - $connPool = m::mock(Pool::class); - $mapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connPool]); + $connection = m::mock(Connection::class); + $mapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connection]); $options = ['writeConcern' => new WriteConcern($writeConcern)]; $collection = m::mock(Collection::class); @@ -156,8 +155,8 @@ public function testShouldInsert($entity, $writeConcern, $shouldFireEventAfter, public function testShouldInsertWithoutFiringEvents($entity, $writeConcern, $shouldFireEventAfter, $expected) { // Arrange - $connPool = m::mock(Pool::class); - $mapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connPool]); + $connection = m::mock(Connection::class); + $mapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connection]); $options = ['writeConcern' => new WriteConcern($writeConcern)]; $collection = m::mock(Collection::class); @@ -207,8 +206,8 @@ public function testShouldInsertWithoutFiringEvents($entity, $writeConcern, $sho public function testShouldUpdate($entity, $writeConcern, $shouldFireEventAfter, $expected) { // Arrange - $connPool = m::mock(Pool::class); - $mapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connPool]); + $connection = m::mock(Connection::class); + $mapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connection]); $collection = m::mock(Collection::class); $parsedObject = ['_id' => 123]; @@ -270,8 +269,8 @@ public function testUpdateShouldCallInsertWhenObjectHasNoId( $expected ) { // Arrange - $connPool = m::mock(Pool::class); - $mapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connPool]); + $connection = m::mock(Connection::class); + $mapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connection]); $collection = m::mock(Collection::class); $parsedObject = ['_id' => 123]; @@ -331,8 +330,8 @@ public function testUpdateShouldCallInsertWhenObjectHasNoId( public function testShouldDelete($entity, $writeConcern, $shouldFireEventAfter, $expected) { // Arrange - $connPool = m::mock(Pool::class); - $mapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connPool]); + $connection = m::mock(Connection::class); + $mapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connection]); $collection = m::mock(Collection::class); $parsedObject = ['_id' => 123]; @@ -390,8 +389,8 @@ public function testDatabaseOperationsShouldBailOutIfTheEventHandlerReturnsFalse $eventName ) { // Arrange - $connPool = m::mock(Pool::class); - $mapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connPool]); + $connection = m::mock(Connection::class); + $mapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connection]); $collection = m::mock(Collection::class); $entity = m::mock(); @@ -422,8 +421,8 @@ public function testDatabaseOperationsShouldBailOutIfTheEventHandlerReturnsFalse public function testShouldGetWithWhereQuery() { // Arrange - $connPool = m::mock(Pool::class); - $mapper = m::mock(DataMapper::class.'[prepareValueQuery,getCollection]', [$connPool]); + $connection = m::mock(Connection::class); + $mapper = m::mock(DataMapper::class.'[prepareValueQuery,getCollection]', [$connection]); $schema = m::mock(Schema::class); $collection = m::mock(Collection::class); @@ -476,8 +475,8 @@ public function testShouldGetWithWhereQuery() public function testShouldGetAll() { // Arrange - $connPool = m::mock(Pool::class); - $mapper = m::mock(DataMapper::class.'[where]', [$connPool]); + $connection = m::mock(Connection::class); + $mapper = m::mock(DataMapper::class.'[where]', [$connection]); $mongolidCursor = m::mock(Cursor::class); // Expect @@ -495,8 +494,8 @@ public function testShouldGetAll() public function testShouldGetFirstWithQuery() { // Arrange - $connPool = m::mock(Pool::class); - $mapper = m::mock(DataMapper::class.'[prepareValueQuery,getCollection]', [$connPool]); + $connection = m::mock(Connection::class); + $mapper = m::mock(DataMapper::class.'[prepareValueQuery,getCollection]', [$connection]); $schema = m::mock(Schema::class); $collection = m::mock(Collection::class); $query = 123; @@ -530,8 +529,8 @@ public function testShouldGetFirstWithQuery() public function testShouldGetNullIfFirstCantFindAnything() { // Arrange - $connPool = m::mock(Pool::class); - $mapper = m::mock(DataMapper::class.'[prepareValueQuery,getCollection]', [$connPool]); + $connection = m::mock(Connection::class); + $mapper = m::mock(DataMapper::class.'[prepareValueQuery,getCollection]', [$connection]); $schema = m::mock(Schema::class); $collection = m::mock(Collection::class); @@ -566,10 +565,10 @@ public function testShouldGetNullIfFirstCantFindAnything() public function testShouldGetFirstProjectingFields() { // Arrange - $connPool = m::mock(Pool::class); + $connection = m::mock(Connection::class); $mapper = m::mock( DataMapper::class.'[prepareValueQuery,getCollection]', - [$connPool] + [$connection] ); $schema = m::mock(Schema::class); @@ -606,8 +605,8 @@ public function testShouldGetFirstProjectingFields() public function testShouldGetFirstTroughACacheableCursor() { // Arrange - $connPool = m::mock(Pool::class); - $mapper = m::mock(DataMapper::class.'[where]', [$connPool]); + $connection = m::mock(Connection::class); + $mapper = m::mock(DataMapper::class.'[where]', [$connection]); $query = 123; $entity = new stdClass(); $cursor = m::mock(CacheableCursor::class); @@ -631,8 +630,8 @@ public function testShouldGetFirstTroughACacheableCursor() public function testShouldGetFirstTroughACacheableCursorProjectingFields() { // Arrange - $connPool = m::mock(Pool::class); - $mapper = m::mock(DataMapper::class.'[where]', [$connPool]); + $connection = m::mock(Connection::class); + $mapper = m::mock(DataMapper::class.'[where]', [$connection]); $query = 123; $entity = new stdClass(); $cursor = m::mock(CacheableCursor::class); @@ -657,8 +656,8 @@ public function testShouldGetFirstTroughACacheableCursorProjectingFields() public function testShouldParseObjectToDocumentAndPutResultingIdIntoTheGivenObject() { // Arrange - $connPool = m::mock(Pool::class); - $mapper = m::mock(DataMapper::class.'[getSchemaMapper]', [$connPool]); + $connection = m::mock(Connection::class); + $mapper = m::mock(DataMapper::class.'[getSchemaMapper]', [$connection]); $entity = m::mock(); $parsedDocument = ['a_field' => 123, '_id' => 'bacon']; $schemaMapper = m::mock(Schema::class.'[]'); @@ -688,8 +687,8 @@ public function testShouldParseObjectToDocumentAndPutResultingIdIntoTheGivenObje public function testShouldGetSchemaMapper() { // Arrange - $connPool = m::mock(Pool::class); - $mapper = new DataMapper($connPool); + $connection = m::mock(Connection::class); + $mapper = new DataMapper($connection); $mapper->schemaClass = 'MySchema'; $schema = m::mock(Schema::class); @@ -706,9 +705,8 @@ public function testShouldGetSchemaMapper() public function testShouldGetRawCollection() { // Arrange - $connPool = m::mock(Pool::class); - $mapper = new DataMapper($connPool); $connection = m::mock(Connection::class); + $mapper = new DataMapper($connection); $collection = m::mock(Collection::class); $schema = m::mock(Schema::class); $schema->collection = 'foobar'; @@ -718,10 +716,6 @@ public function testShouldGetRawCollection() $connection->grimory = (object) ['foobar' => $collection]; // Expect - $connPool->expects() - ->getConnection() - ->andReturn($connection); - $connection->expects() ->getRawConnection() ->andReturn($connection); @@ -739,8 +733,8 @@ public function testShouldGetRawCollection() public function testShouldPrepareQueryValue($value, $expectation) { // Arrange - $connPool = m::mock(Pool::class); - $mapper = new DataMapper($connPool); + $connection = m::mock(Connection::class); + $mapper = new DataMapper($connection); // Act $result = $this->callProtected($mapper, 'prepareValueQuery', [$value]); @@ -755,8 +749,8 @@ public function testShouldPrepareQueryValue($value, $expectation) public function testPrepareProjectionShouldConvertArray($data, $expectation) { // Arrange - $connPool = m::mock(Pool::class); - $mapper = new DataMapper($connPool); + $connection = m::mock(Connection::class); + $mapper = new DataMapper($connection); // Act $result = $this->callProtected($mapper, 'prepareProjection', [$data]); @@ -768,8 +762,8 @@ public function testPrepareProjectionShouldConvertArray($data, $expectation) public function testPrepareProjectionShouldThrownAnException() { // Arrange - $connPool = m::mock(Pool::class); - $mapper = new DataMapper($connPool); + $connection = m::mock(Connection::class); + $mapper = new DataMapper($connection); $data = ['valid' => true, 'invalid-key' => 'invalid-value']; // Expectations diff --git a/tests/Unit/ManagerTest.php b/tests/Unit/ManagerTest.php index ba52bb64..ca24c68a 100644 --- a/tests/Unit/ManagerTest.php +++ b/tests/Unit/ManagerTest.php @@ -5,7 +5,6 @@ use Mockery as m; use MongoDB\Client; use Mongolid\Connection\Connection; -use Mongolid\Connection\Pool; use Mongolid\Container\Ioc; use Mongolid\DataMapper\DataMapper; use Mongolid\Event\EventTriggerInterface; @@ -28,13 +27,15 @@ public function testShouldAddAndGetConnection() $connection = m::mock(Connection::class); $rawConnection = m::mock(Client::class); - // Act + // Expect $connection->expects() ->getRawConnection() ->andReturn($rawConnection); + // Act + $manager->setConnection($connection); + // Assert - $manager->addConnection($connection); $this->assertEquals($rawConnection, $manager->getConnection()); } @@ -115,7 +116,6 @@ public function testShouldInitializeOnce() // Assertion $this->assertAttributeEquals($manager, 'singleton', Manager::class); $this->assertAttributeInstanceOf(Container::class, 'container', $manager); - $this->assertAttributeInstanceOf(Pool::class, 'connectionPool', $manager); $this->assertAttributeInstanceOf(CacheComponentInterface::class, 'cacheComponent', $manager); $container = $manager->container; diff --git a/tests/Unit/Util/SequenceServiceTest.php b/tests/Unit/Util/SequenceServiceTest.php index b6e68df9..6d94c631 100644 --- a/tests/Unit/Util/SequenceServiceTest.php +++ b/tests/Unit/Util/SequenceServiceTest.php @@ -4,7 +4,6 @@ use Mockery as m; use MongoDB\Collection; use Mongolid\Connection\Connection; -use Mongolid\Connection\Pool; use Mongolid\TestCase; class SequenceServiceTest extends TestCase @@ -15,8 +14,8 @@ class SequenceServiceTest extends TestCase public function testShouldGetNextValue($sequenceName, $currentValue, $expectation) { // Arrange - $connPool = m::mock(Pool::class); - $sequenceService = m::mock(SequenceService::class.'[rawCollection]', [$connPool]); + $connection = m::mock(Connection::class); + $sequenceService = m::mock(SequenceService::class.'[rawCollection]', [$connection]); $sequenceService->shouldAllowMockingProtectedMethods(); $rawCollection = m::mock(Collection::class); @@ -44,22 +43,17 @@ public function testShouldGetNextValue($sequenceName, $currentValue, $expectatio public function testShouldGetRawCollection() { // Arrange - $connPool = m::mock(Pool::class); - $sequenceService = new SequenceService($connPool, 'foobar'); $connection = m::mock(Connection::class); + $sequenceService = new SequenceService($connection, 'foobar'); $collection = m::mock(Collection::class); $connection->defaultDatabase = 'grimory'; $connection->grimory = (object) ['foobar' => $collection]; // Act - $connPool->expects() - ->getConnection() - ->andReturn($connection); - $connection->expects() ->getRawConnection() - ->andReturn($connection); + ->andReturnSelf($connection); // Assertion $this->assertEquals( diff --git a/tests/Util/DropDatabaseTrait.php b/tests/Util/DropDatabaseTrait.php index 6f726c82..854f0f62 100644 --- a/tests/Util/DropDatabaseTrait.php +++ b/tests/Util/DropDatabaseTrait.php @@ -1,15 +1,14 @@ getConnection(); + $connection = Ioc::make(Connection::class); $connection->getRawConnection() ->dropDatabase($connection->defaultDatabase); diff --git a/tests/Util/SetupPoolTrait.php b/tests/Util/SetupConnectionTrait.php similarity index 58% rename from tests/Util/SetupPoolTrait.php rename to tests/Util/SetupConnectionTrait.php index a864385c..24711a64 100644 --- a/tests/Util/SetupPoolTrait.php +++ b/tests/Util/SetupConnectionTrait.php @@ -2,23 +2,19 @@ namespace Mongolid\Tests\Util; use Mongolid\Connection\Connection; -use Mongolid\Connection\Pool; use Mongolid\Container\Ioc; -trait SetupPoolTrait +trait SetupConnectionTrait { - public function setupPool(string $host, string $database) + public function setupConnection(string $host, string $database) { Ioc::singleton( - Pool::class, + Connection::class, function () use ($host, $database) { $connection = new Connection("mongodb://{$host}:27017/{$database}"); $connection->defaultDatabase = $database; - $pool = new Pool(); - $pool->addConnection($connection); - - return $pool; + return $connection; } ); } From 4e6111c92ede68e8f7c8704b8efb1246bac8ee90 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 22 Oct 2018 14:33:08 -0300 Subject: [PATCH 018/116] Use atomic updates for update() method of data mapper --- src/DataMapper/DataMapper.php | 78 ++++- src/DataMapper/SchemaMapper.php | 33 +- src/Model/Attributes.php | 23 +- src/Model/AttributesAccessInterface.php | 11 +- tests/Integration/PersistedDataTest.php | 34 +- tests/Unit/DataMapper/DataMapperTest.php | 324 +++++++++--------- tests/Unit/DataMapper/EntityAssemblerTest.php | 2 +- tests/Unit/DataMapper/SchemaMapperTest.php | 113 +++--- 8 files changed, 369 insertions(+), 249 deletions(-) diff --git a/src/DataMapper/DataMapper.php b/src/DataMapper/DataMapper.php index 376e6e6b..95335134 100644 --- a/src/DataMapper/DataMapper.php +++ b/src/DataMapper/DataMapper.php @@ -79,7 +79,7 @@ public function save($entity, array $options = []) // If the "saving" event returns false we'll bail out of the save and return // false, indicating that the save failed. This gives an opportunities to // listeners to cancel save operations if validations fail or whatever. - if (false === $this->fireEvent('saving', $entity, true)) { + if (!$this->fireEvent('saving', $entity, true)) { return false; } @@ -118,7 +118,7 @@ public function save($entity, array $options = []) */ public function insert($entity, array $options = [], bool $fireEvents = true): bool { - if ($fireEvents && false === $this->fireEvent('inserting', $entity, true)) { + if ($fireEvents && !$this->fireEvent('inserting', $entity, true)) { return false; } @@ -156,7 +156,7 @@ public function insert($entity, array $options = [], bool $fireEvents = true): b */ public function update($entity, array $options = []): bool { - if (false === $this->fireEvent('updating', $entity, true)) { + if (!$this->fireEvent('updating', $entity, true)) { return false; } @@ -172,9 +172,11 @@ public function update($entity, array $options = []): bool $data = $this->parseToDocument($entity); + $updateData = $this->getUpdateData($entity, $data); + $queryResult = $this->getCollection()->updateOne( ['_id' => $data['_id']], - ['$set' => $data], + $updateData, $this->mergeOptions($options) ); @@ -201,7 +203,7 @@ public function update($entity, array $options = []): bool */ public function delete($entity, array $options = []): bool { - if (false === $this->fireEvent('deleting', $entity, true)) { + if (!$this->fireEvent('deleting', $entity, true)) { return false; } @@ -317,6 +319,22 @@ public function firstOrFail( throw (new ModelNotFoundException())->setModel($this->schema->entityClass); } + /** + * {@inheritdoc} + */ + public function getSchema(): Schema + { + return $this->schema; + } + + /** + * Set a Schema object that describes an Entity in MongoDB. + */ + public function setSchema(Schema $schema) + { + $this->schema = $schema; + } + /** * Parses an object with SchemaMapper and the given Schema. * @@ -518,6 +536,36 @@ protected function prepareProjection(array $fields) return $projection; } + /** + * Based on the work of bjori/mongo-php-transistor. + * Calculate `$set` and `$unset` arrays for update operation and store them on $changes. + * + * @see https://github.com/bjori/mongo-php-transistor/blob/70f5af00795d67f4d5a8c397e831435814df9937/src/Transistor.php#L108 + */ + private function calculateChanges(array &$changes, array $newData, array $oldData, string $keyfix = '') + { + foreach ($newData as $k => $v) { + if (!isset($oldData[$k])) { // new field + $changes['$set']["{$keyfix}{$k}"] = $v; + } elseif ($oldData[$k] != $v) { // changed value + if (is_array($v) && $oldData[$k] && $v) { // check array recursively for changes + $this->calculateChanges($changes, $v, $oldData[$k], "{$keyfix}{$k}."); + } else { + // overwrite normal changes in keys + // this applies to previously empty arrays/documents too + $changes['$set']["{$keyfix}{$k}"] = $v; + } + } + } + + foreach ($oldData as $k => $v) { // data that used to exist, but now doesn't + if (!isset($newData[$k])) { // removed field + $changes['$unset']["{$keyfix}{$k}"] = ''; + continue; + } + } + } + /** * Merge all options. * @@ -543,19 +591,15 @@ private function afterSuccess($entity) } } - /** - * {@inheritdoc} - */ - public function getSchema(): Schema + private function getUpdateData($entity, array $data) { - return $this->schema; - } + if (!$entity instanceof AttributesAccessInterface) { + return ['$set' => $data]; + } - /** - * Set a Schema object that describes an Entity in MongoDB. - */ - public function setSchema(Schema $schema) - { - $this->schema = $schema; + $changes = []; + $this->calculateChanges($changes, $data, $entity->getOriginalAttributes()); + + return $changes; } } diff --git a/src/DataMapper/SchemaMapper.php b/src/DataMapper/SchemaMapper.php index b28165b9..0c43ba47 100644 --- a/src/DataMapper/SchemaMapper.php +++ b/src/DataMapper/SchemaMapper.php @@ -56,19 +56,12 @@ public function map($data) $data[$key] = $this->parseField($data[$key] ?? null, $fieldType); } - return $data; - } - - /** - * If the schema is not dynamic, remove all non specified fields. - * - * @param array $data Reference of the fields. The passed array will be modified. - */ - protected function clearDynamic(array &$data) - { - if (!$this->schema->dynamic) { - $data = array_intersect_key($data, $this->schema->fields); - } + return array_filter( + $data, + function ($value) { + return null !== $value; + } + ); } /** @@ -87,7 +80,7 @@ public function parseField($value, string $fieldType) } // Returns null or an empty array - if (null === $value || is_array($value) && empty($value)) { + if (null === $value || [] === $value) { return $value; } @@ -104,6 +97,18 @@ public function parseField($value, string $fieldType) return $value; } + /** + * If the schema is not dynamic, remove all non specified fields. + * + * @param array $data Reference of the fields. The passed array will be modified. + */ + protected function clearDynamic(array &$data) + { + if (!$this->schema->dynamic) { + $data = array_intersect_key($data, $this->schema->fields); + } + } + /** * Uses PHP's settype to cast a value to a type. * diff --git a/src/Model/Attributes.php b/src/Model/Attributes.php index 7d7288d7..bf154097 100644 --- a/src/Model/Attributes.php +++ b/src/Model/Attributes.php @@ -1,6 +1,8 @@ attributes; } @@ -124,13 +124,28 @@ public function setAttribute(string $key, $value) /** * Stores original attributes from actual data from attributes * to be used in future comparisons about changes. + * It tries to clone the attributes (using serialize/unserialize) + * so modifications to objects will be correctly identified + * as changes. * * Ideally should be called once right after retrieving data from * the database. */ public function syncOriginalAttributes() { - $this->original = $this->attributes; + try { + $this->original = unserialize(serialize($this->attributes)); + } catch (Exception $e) { + $this->original = $this->attributes; + } + } + + /** + * Retrieve original attributes. + */ + public function getOriginalAttributes(): array + { + return $this->original; } /** diff --git a/src/Model/AttributesAccessInterface.php b/src/Model/AttributesAccessInterface.php index d1e9bbe2..f7ebd5f7 100644 --- a/src/Model/AttributesAccessInterface.php +++ b/src/Model/AttributesAccessInterface.php @@ -22,10 +22,8 @@ public function getAttribute(string $key); /** * Get all attributes from the model. - * - * @return mixed */ - public function getAttributes(); + public function getAttributes(): array; /** * Set the model attributes using an array. @@ -36,7 +34,7 @@ public function getAttributes(); public function fill(array $input, bool $force = false); /** - * Set a given attribute on the model. + * Unset a given attribute on the model. * * @param string $key name of the attribute to be unset */ @@ -58,4 +56,9 @@ public function setAttribute(string $key, $value); * the database. */ public function syncOriginalAttributes(); + + /** + * Retrieve original attributes. + */ + public function getOriginalAttributes(): array; } diff --git a/tests/Integration/PersistedDataTest.php b/tests/Integration/PersistedDataTest.php index 634c75aa..aaf53413 100644 --- a/tests/Integration/PersistedDataTest.php +++ b/tests/Integration/PersistedDataTest.php @@ -31,7 +31,11 @@ public function testSaveInsertingData() 'email' => 'never', ], 'friends' => [], - 'address' => null, + 'skills' => [ + 'PHP' => ['percentage' => '100%', 'version' => '7.0'], + 'JavaScript' => ['percentage' => '80%', 'version' => 'ES6'], + 'CSS' => ['percentage' => '45%', 'version' => 'CSS3'], + ], ]; // Actions @@ -56,14 +60,21 @@ public function testSaveUpdatingData() $user->preferences = []; $user->friends = ['Mary']; $user->address = '123 Blue Street'; + $user->skills->HTML = ['percentage' => '89%', 'version' => 'HTML5']; + $user->skills->PHP['version'] = '7.1'; $expected = [ '_id' => (string) $user->_id, 'name' => 'Jane Doe', - 'height' => null, 'preferences' => [], 'friends' => ['Mary'], 'address' => '123 Blue Street', + 'skills' => [ + 'PHP' => ['percentage' => '100%', 'version' => '7.1'], + 'JavaScript' => ['percentage' => '80%', 'version' => 'ES6'], + 'CSS' => ['percentage' => '45%', 'version' => 'CSS3'], + 'HTML' => ['percentage' => '89%', 'version' => 'HTML5'], + ], 'email' => 'jane@doe.com', ]; @@ -80,23 +91,29 @@ public function testSaveUpdatingData() public function testUpdateData() { // Set - $user = $this->getUser(true); + $user = $this->getUser(true); $user->name = 'Jane Doe'; - $user->age = null; // TODO unset($user->age); not working right now - bug! + unset($user->age); $user->height = null; $user->email = 'jane@doe.com'; $user->preferences = []; $user->friends = ['Mary']; $user->address = '123 Blue Street'; + $user->skills->HTML = ['percentage' => '89%', 'version' => 'HTML5']; + $user->skills->PHP['version'] = '7.1'; $expected = [ '_id' => (string) $user->_id, 'name' => 'Jane Doe', - 'age' => null, - 'height' => null, 'preferences' => [], 'friends' => ['Mary'], + 'skills' => [ + 'PHP' => ['percentage' => '100%', 'version' => '7.1'], + 'JavaScript' => ['percentage' => '80%', 'version' => 'ES6'], + 'CSS' => ['percentage' => '45%', 'version' => 'CSS3'], + 'HTML' => ['percentage' => '89%', 'version' => 'HTML5'], + ], 'address' => '123 Blue Street', 'email' => 'jane@doe.com', ]; @@ -123,6 +140,11 @@ private function getUser(bool $save = false): User ]; $user->friends = []; $user->address = null; + $user->skills = (object) [ + 'PHP' => ['percentage' => '100%', 'version' => '7.0'], + 'JavaScript' => ['percentage' => '80%', 'version' => 'ES6'], + 'CSS' => ['percentage' => '45%', 'version' => 'CSS3'], + ]; if ($save) { $this->assertTrue($user->save(), 'Failed to save user!'); diff --git a/tests/Unit/DataMapper/DataMapperTest.php b/tests/Unit/DataMapper/DataMapperTest.php index cace8716..be7c6007 100644 --- a/tests/Unit/DataMapper/DataMapperTest.php +++ b/tests/Unit/DataMapper/DataMapperTest.php @@ -4,14 +4,16 @@ use InvalidArgumentException; use Mockery as m; use MongoDB\BSON\ObjectID; +use MongoDB\Client; use MongoDB\Collection; +use MongoDB\Database; use MongoDB\Driver\WriteConcern; +use Mongolid\ActiveRecord; use Mongolid\Connection\Connection; use Mongolid\Container\Ioc; use Mongolid\Cursor\CacheableCursor; use Mongolid\Cursor\Cursor; use Mongolid\Event\EventTriggerService; -use Mongolid\Model\AttributesAccessInterface; use Mongolid\Schema\Schema; use Mongolid\TestCase; use stdClass; @@ -24,10 +26,10 @@ public function testShouldBeAbleToConstructWithSchema() $connection = m::mock(Connection::class); // Act - $mapper = new DataMapper($connection); + $dataMapper = new DataMapper($connection); // Assert - $this->assertAttributeEquals($connection, 'connection', $mapper); + $this->assertAttributeEquals($connection, 'connection', $dataMapper); } /** @@ -37,7 +39,7 @@ public function testShouldSave($entity, $writeConcern, $shouldFireEventAfter, $e { // Arrange $connection = m::mock(Connection::class); - $mapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connection]); + $dataMapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connection]); $options = ['writeConcern' => new WriteConcern($writeConcern)]; $collection = m::mock(Collection::class); @@ -47,13 +49,13 @@ public function testShouldSave($entity, $writeConcern, $shouldFireEventAfter, $e $entity->_id = null; // Act - $mapper->shouldAllowMockingProtectedMethods(); + $dataMapper->shouldAllowMockingProtectedMethods(); - $mapper->expects() + $dataMapper->expects() ->parseToDocument($entity) ->andReturn($parsedObject); - $mapper->expects() + $dataMapper->expects() ->getCollection() ->andReturn($collection); @@ -76,11 +78,6 @@ public function testShouldSave($entity, $writeConcern, $shouldFireEventAfter, $e ->getUpsertedCount() ->andReturn(1); - if ($entity instanceof AttributesAccessInterface) { - $entity->expects() - ->syncOriginalAttributes(); - } - $this->expectEventToBeFired('saving', $entity, true); if ($shouldFireEventAfter) { @@ -90,7 +87,7 @@ public function testShouldSave($entity, $writeConcern, $shouldFireEventAfter, $e } // Assert - $this->assertEquals($expected, $mapper->save($entity, $options)); + $this->assertSame($expected, $dataMapper->save($entity, $options)); } /** @@ -100,7 +97,7 @@ public function testShouldInsert($entity, $writeConcern, $shouldFireEventAfter, { // Arrange $connection = m::mock(Connection::class); - $mapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connection]); + $dataMapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connection]); $options = ['writeConcern' => new WriteConcern($writeConcern)]; $collection = m::mock(Collection::class); @@ -110,13 +107,13 @@ public function testShouldInsert($entity, $writeConcern, $shouldFireEventAfter, $entity->_id = null; // Act - $mapper->shouldAllowMockingProtectedMethods(); + $dataMapper->shouldAllowMockingProtectedMethods(); - $mapper->expects() + $dataMapper->expects() ->parseToDocument($entity) ->andReturn($parsedObject); - $mapper->expects() + $dataMapper->expects() ->getCollection() ->andReturn($collection); @@ -132,11 +129,6 @@ public function testShouldInsert($entity, $writeConcern, $shouldFireEventAfter, ->getInsertedCount() ->andReturn(1); - if ($entity instanceof AttributesAccessInterface) { - $entity->expects() - ->syncOriginalAttributes(); - } - $this->expectEventToBeFired('inserting', $entity, true); if ($shouldFireEventAfter) { @@ -146,7 +138,7 @@ public function testShouldInsert($entity, $writeConcern, $shouldFireEventAfter, } // Assert - $this->assertEquals($expected, $mapper->insert($entity, $options)); + $this->assertSame($expected, $dataMapper->insert($entity, $options)); } /** @@ -156,7 +148,7 @@ public function testShouldInsertWithoutFiringEvents($entity, $writeConcern, $sho { // Arrange $connection = m::mock(Connection::class); - $mapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connection]); + $dataMapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connection]); $options = ['writeConcern' => new WriteConcern($writeConcern)]; $collection = m::mock(Collection::class); @@ -166,13 +158,13 @@ public function testShouldInsertWithoutFiringEvents($entity, $writeConcern, $sho $entity->_id = null; // Act - $mapper->shouldAllowMockingProtectedMethods(); + $dataMapper->shouldAllowMockingProtectedMethods(); - $mapper->expects() + $dataMapper->expects() ->parseToDocument($entity) ->andReturn($parsedObject); - $mapper->expects() + $dataMapper->expects() ->getCollection() ->andReturn($collection); @@ -188,16 +180,11 @@ public function testShouldInsertWithoutFiringEvents($entity, $writeConcern, $sho ->getInsertedCount() ->andReturn(1); - if ($entity instanceof AttributesAccessInterface) { - $entity->expects() - ->syncOriginalAttributes(); - } - $this->expectEventNotToBeFired('inserting', $entity); $this->expectEventNotToBeFired('inserted', $entity); // Assert - $this->assertEquals($expected, $mapper->insert($entity, $options, false)); + $this->assertSame($expected, $dataMapper->insert($entity, $options, false)); } /** @@ -207,24 +194,41 @@ public function testShouldUpdate($entity, $writeConcern, $shouldFireEventAfter, { // Arrange $connection = m::mock(Connection::class); - $mapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connection]); + $client = m::mock(Client::class); + $database = m::mock(Database::class); + $dataMapper = new DataMapper($connection); $collection = m::mock(Collection::class); $parsedObject = ['_id' => 123]; $operationResult = m::mock(); $options = ['writeConcern' => new WriteConcern($writeConcern)]; + Ioc::instance( + Schema::class, + new class() extends Schema + { + /** + * {@inheritdoc} + */ + public $fields = [ + '_id' => 'objectId', + ]; + } + ); + $entity->_id = 123; - // Act - $mapper->shouldAllowMockingProtectedMethods(); + // Expect + $connection->expects() + ->getRawConnection() + ->andReturn($client); - $mapper->expects() - ->parseToDocument($entity) - ->andReturn($parsedObject); + $client->expects() + ->selectDatabase('mongolid') + ->andReturn($database); - $mapper->expects() - ->getCollection() + $database->expects() + ->selectCollection('') ->andReturn($collection); $collection->expects() @@ -242,11 +246,6 @@ public function testShouldUpdate($entity, $writeConcern, $shouldFireEventAfter, ->getModifiedCount() ->andReturn(1); - if ($entity instanceof AttributesAccessInterface) { - $entity->expects() - ->syncOriginalAttributes(); - } - $this->expectEventToBeFired('updating', $entity, true); if ($shouldFireEventAfter) { @@ -255,8 +254,11 @@ public function testShouldUpdate($entity, $writeConcern, $shouldFireEventAfter, $this->expectEventNotToBeFired('updated', $entity); } + // Act + $result = $dataMapper->update($entity, $options); + // Assert - $this->assertEquals($expected, $mapper->update($entity, $options)); + $this->assertSame($expected, $result); } /** @@ -270,7 +272,7 @@ public function testUpdateShouldCallInsertWhenObjectHasNoId( ) { // Arrange $connection = m::mock(Connection::class); - $mapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connection]); + $dataMapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connection]); $collection = m::mock(Collection::class); $parsedObject = ['_id' => 123]; @@ -280,13 +282,13 @@ public function testUpdateShouldCallInsertWhenObjectHasNoId( $entity->_id = null; // Act - $mapper->shouldAllowMockingProtectedMethods(); + $dataMapper->shouldAllowMockingProtectedMethods(); - $mapper->expects() + $dataMapper->expects() ->parseToDocument($entity) ->andReturn($parsedObject); - $mapper->expects() + $dataMapper->expects() ->getCollection() ->andReturn($collection); @@ -304,11 +306,6 @@ public function testUpdateShouldCallInsertWhenObjectHasNoId( ->getInsertedCount() ->andReturn(1); - if ($entity instanceof AttributesAccessInterface) { - $entity->expects() - ->syncOriginalAttributes(); - } - $this->expectEventToBeFired('updating', $entity, true); if ($shouldFireEventAfter) { @@ -321,7 +318,7 @@ public function testUpdateShouldCallInsertWhenObjectHasNoId( $this->expectEventNotToBeFired('inserted', $entity); // Assert - $this->assertEquals($expected, $mapper->update($entity, $options)); + $this->assertSame($expected, $dataMapper->update($entity, $options)); } /** @@ -331,7 +328,7 @@ public function testShouldDelete($entity, $writeConcern, $shouldFireEventAfter, { // Arrange $connection = m::mock(Connection::class); - $mapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connection]); + $dataMapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connection]); $collection = m::mock(Collection::class); $parsedObject = ['_id' => 123]; @@ -341,13 +338,13 @@ public function testShouldDelete($entity, $writeConcern, $shouldFireEventAfter, $entity->_id = null; // Act - $mapper->shouldAllowMockingProtectedMethods(); + $dataMapper->shouldAllowMockingProtectedMethods(); - $mapper->expects() + $dataMapper->expects() ->parseToDocument($entity) ->andReturn($parsedObject); - $mapper->expects() + $dataMapper->expects() ->getCollection() ->andReturn($collection); @@ -363,11 +360,6 @@ public function testShouldDelete($entity, $writeConcern, $shouldFireEventAfter, ->getDeletedCount() ->andReturn(1); - if ($entity instanceof AttributesAccessInterface) { - $entity->expects() - ->syncOriginalAttributes(); - } - $this->expectEventToBeFired('deleting', $entity, true); if ($shouldFireEventAfter) { @@ -377,7 +369,7 @@ public function testShouldDelete($entity, $writeConcern, $shouldFireEventAfter, } // Assert - $this->assertEquals($expected, $mapper->delete($entity, $options)); + $this->assertSame($expected, $dataMapper->delete($entity, $options)); } /** @@ -390,18 +382,18 @@ public function testDatabaseOperationsShouldBailOutIfTheEventHandlerReturnsFalse ) { // Arrange $connection = m::mock(Connection::class); - $mapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connection]); + $dataMapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connection]); $collection = m::mock(Collection::class); $entity = m::mock(); - $mapper->shouldAllowMockingProtectedMethods(); + $dataMapper->shouldAllowMockingProtectedMethods(); // Expect - $mapper->expects() + $dataMapper->expects() ->parseToDocument($entity) ->never(); - $mapper->allows() + $dataMapper->allows() ->getCollection() ->andReturn($collection); @@ -412,7 +404,7 @@ public function testDatabaseOperationsShouldBailOutIfTheEventHandlerReturnsFalse $this->expectEventToBeFired($eventName, $entity, true, false); // Act - $result = $mapper->$operation($entity); + $result = $dataMapper->$operation($entity); // Assert $this->assertFalse($result); @@ -422,7 +414,7 @@ public function testShouldGetWithWhereQuery() { // Arrange $connection = m::mock(Connection::class); - $mapper = m::mock(DataMapper::class.'[prepareValueQuery,getCollection]', [$connection]); + $dataMapper = m::mock(DataMapper::class.'[prepareValueQuery,getCollection]', [$connection]); $schema = m::mock(Schema::class); $collection = m::mock(Collection::class); @@ -431,24 +423,24 @@ public function testShouldGetWithWhereQuery() $projection = ['project' => true, '_id' => false]; $schema->entityClass = 'stdClass'; - $mapper->setSchema($schema); + $dataMapper->setSchema($schema); - $mapper->shouldAllowMockingProtectedMethods(); + $dataMapper->shouldAllowMockingProtectedMethods(); // Expect - $mapper->expects() + $dataMapper->expects() ->prepareValueQuery($query) ->twice() ->andReturn($preparedQuery); - $mapper->expects() + $dataMapper->expects() ->getCollection() ->twice() ->andReturn($collection); // Act - $result = $mapper->where($query, $projection); - $cacheableResult = $mapper->where($query, [], true); + $result = $dataMapper->where($query, $projection); + $cacheableResult = $dataMapper->where($query, [], true); // Assert $this->assertInstanceOf(Cursor::class, $result); @@ -476,42 +468,42 @@ public function testShouldGetAll() { // Arrange $connection = m::mock(Connection::class); - $mapper = m::mock(DataMapper::class.'[where]', [$connection]); + $dataMapper = m::mock(DataMapper::class.'[where]', [$connection]); $mongolidCursor = m::mock(Cursor::class); // Expect - $mapper->expects() + $dataMapper->expects() ->where([]) ->andReturn($mongolidCursor); // Act - $result = $mapper->all(); + $result = $dataMapper->all(); // Assert - $this->assertEquals($mongolidCursor, $result); + $this->assertSame($mongolidCursor, $result); } public function testShouldGetFirstWithQuery() { // Arrange $connection = m::mock(Connection::class); - $mapper = m::mock(DataMapper::class.'[prepareValueQuery,getCollection]', [$connection]); + $dataMapper = m::mock(DataMapper::class.'[prepareValueQuery,getCollection]', [$connection]); $schema = m::mock(Schema::class); $collection = m::mock(Collection::class); $query = 123; $preparedQuery = ['_id' => 123]; $schema->entityClass = 'stdClass'; - $mapper->setSchema($schema); + $dataMapper->setSchema($schema); - $mapper->shouldAllowMockingProtectedMethods(); + $dataMapper->shouldAllowMockingProtectedMethods(); // Act - $mapper->expects() + $dataMapper->expects() ->prepareValueQuery($query) ->andReturn($preparedQuery); - $mapper->expects() + $dataMapper->expects() ->getCollection() ->andReturn($collection); @@ -519,7 +511,7 @@ public function testShouldGetFirstWithQuery() ->findOne($preparedQuery, ['projection' => []]) ->andReturn(['name' => 'John Doe']); - $result = $mapper->first($query); + $result = $dataMapper->first($query); // Assert $this->assertInstanceOf(stdClass::class, $result); @@ -530,7 +522,7 @@ public function testShouldGetNullIfFirstCantFindAnything() { // Arrange $connection = m::mock(Connection::class); - $mapper = m::mock(DataMapper::class.'[prepareValueQuery,getCollection]', [$connection]); + $dataMapper = m::mock(DataMapper::class.'[prepareValueQuery,getCollection]', [$connection]); $schema = m::mock(Schema::class); $collection = m::mock(Collection::class); @@ -538,16 +530,16 @@ public function testShouldGetNullIfFirstCantFindAnything() $preparedQuery = ['_id' => 123]; $schema->entityClass = 'stdClass'; - $mapper->setSchema($schema); + $dataMapper->setSchema($schema); - $mapper->shouldAllowMockingProtectedMethods(); + $dataMapper->shouldAllowMockingProtectedMethods(); // Expect - $mapper->expects() + $dataMapper->expects() ->prepareValueQuery($query) ->andReturn($preparedQuery); - $mapper->expects() + $dataMapper->expects() ->getCollection() ->andReturn($collection); @@ -556,7 +548,7 @@ public function testShouldGetNullIfFirstCantFindAnything() ->andReturn(null); // Act - $result = $mapper->first($query); + $result = $dataMapper->first($query); // Assert $this->assertNull($result); @@ -566,7 +558,7 @@ public function testShouldGetFirstProjectingFields() { // Arrange $connection = m::mock(Connection::class); - $mapper = m::mock( + $dataMapper = m::mock( DataMapper::class.'[prepareValueQuery,getCollection]', [$connection] ); @@ -578,16 +570,16 @@ public function testShouldGetFirstProjectingFields() $projection = ['project' => true, 'fields' => false]; $schema->entityClass = 'stdClass'; - $mapper->setSchema($schema); + $dataMapper->setSchema($schema); - $mapper->shouldAllowMockingProtectedMethods(); + $dataMapper->shouldAllowMockingProtectedMethods(); // Expect - $mapper->expects() + $dataMapper->expects() ->prepareValueQuery($query) ->andReturn($preparedQuery); - $mapper->expects() + $dataMapper->expects() ->getCollection() ->andReturn($collection); @@ -596,7 +588,7 @@ public function testShouldGetFirstProjectingFields() ->andReturn(null); // Act - $result = $mapper->first($query, $projection); + $result = $dataMapper->first($query, $projection); // Assert $this->assertNull($result); @@ -606,13 +598,13 @@ public function testShouldGetFirstTroughACacheableCursor() { // Arrange $connection = m::mock(Connection::class); - $mapper = m::mock(DataMapper::class.'[where]', [$connection]); + $dataMapper = m::mock(DataMapper::class.'[where]', [$connection]); $query = 123; $entity = new stdClass(); $cursor = m::mock(CacheableCursor::class); // Expect - $mapper->expects() + $dataMapper->expects() ->where($query, [], true) ->andReturn($cursor); @@ -621,24 +613,24 @@ public function testShouldGetFirstTroughACacheableCursor() ->andReturn($entity); // Act - $result = $mapper->first($query, [], true); + $result = $dataMapper->first($query, [], true); // Assert - $this->assertEquals($entity, $result); + $this->assertSame($entity, $result); } public function testShouldGetFirstTroughACacheableCursorProjectingFields() { // Arrange $connection = m::mock(Connection::class); - $mapper = m::mock(DataMapper::class.'[where]', [$connection]); + $dataMapper = m::mock(DataMapper::class.'[where]', [$connection]); $query = 123; $entity = new stdClass(); $cursor = m::mock(CacheableCursor::class); $projection = ['project' => true, '_id' => false]; // Expect - $mapper->expects() + $dataMapper->expects() ->where($query, $projection, true) ->andReturn($cursor); @@ -647,25 +639,25 @@ public function testShouldGetFirstTroughACacheableCursorProjectingFields() ->andReturn($entity); // Act - $result = $mapper->first($query, $projection, true); + $result = $dataMapper->first($query, $projection, true); // Assert - $this->assertEquals($entity, $result); + $this->assertSame($entity, $result); } public function testShouldParseObjectToDocumentAndPutResultingIdIntoTheGivenObject() { // Arrange $connection = m::mock(Connection::class); - $mapper = m::mock(DataMapper::class.'[getSchemaMapper]', [$connection]); + $dataMapper = m::mock(DataMapper::class.'[getSchemaMapper]', [$connection]); $entity = m::mock(); $parsedDocument = ['a_field' => 123, '_id' => 'bacon']; $schemaMapper = m::mock(Schema::class.'[]'); - $mapper->shouldAllowMockingProtectedMethods(); + $dataMapper->shouldAllowMockingProtectedMethods(); // Expect - $mapper->expects() + $dataMapper->expects() ->getSchemaMapper() ->andReturn($schemaMapper); @@ -674,11 +666,11 @@ public function testShouldParseObjectToDocumentAndPutResultingIdIntoTheGivenObje ->andReturn($parsedDocument); // Act - $result = $this->callProtected($mapper, 'parseToDocument', $entity); + $result = $this->callProtected($dataMapper, 'parseToDocument', $entity); // Assert - $this->assertEquals($parsedDocument, $result); - $this->assertEquals( + $this->assertSame($parsedDocument, $result); + $this->assertSame( 'bacon', // Since this was the parsedDocument _id $entity->_id ); @@ -688,30 +680,30 @@ public function testShouldGetSchemaMapper() { // Arrange $connection = m::mock(Connection::class); - $mapper = new DataMapper($connection); - $mapper->schemaClass = 'MySchema'; + $dataMapper = new DataMapper($connection); + $dataMapper->schemaClass = 'MySchema'; $schema = m::mock(Schema::class); Ioc::instance('MySchema', $schema); // Act - $result = $this->callProtected($mapper, 'getSchemaMapper'); + $result = $this->callProtected($dataMapper, 'getSchemaMapper'); // Assert $this->assertInstanceOf(SchemaMapper::class, $result); - $this->assertEquals($schema, $result->schema); + $this->assertSame($schema, $result->schema); } public function testShouldGetRawCollection() { // Arrange $connection = m::mock(Connection::class); - $mapper = new DataMapper($connection); + $dataMapper = new DataMapper($connection); $collection = m::mock(Collection::class); $schema = m::mock(Schema::class); $schema->collection = 'foobar'; - $mapper->setSchema($schema); + $dataMapper->setSchema($schema); $connection->defaultDatabase = 'grimory'; $connection->grimory = (object) ['foobar' => $collection]; @@ -721,10 +713,10 @@ public function testShouldGetRawCollection() ->andReturn($connection); // Act - $result = $this->callProtected($mapper, 'getCollection'); + $result = $this->callProtected($dataMapper, 'getCollection'); // Assert - $this->assertEquals($collection, $result); + $this->assertSame($collection, $result); } /** @@ -734,10 +726,10 @@ public function testShouldPrepareQueryValue($value, $expectation) { // Arrange $connection = m::mock(Connection::class); - $mapper = new DataMapper($connection); + $dataMapper = new DataMapper($connection); // Act - $result = $this->callProtected($mapper, 'prepareValueQuery', [$value]); + $result = $this->callProtected($dataMapper, 'prepareValueQuery', [$value]); // Assert $this->assertMongoQueryEquals($expectation, $result); @@ -750,20 +742,20 @@ public function testPrepareProjectionShouldConvertArray($data, $expectation) { // Arrange $connection = m::mock(Connection::class); - $mapper = new DataMapper($connection); + $dataMapper = new DataMapper($connection); // Act - $result = $this->callProtected($mapper, 'prepareProjection', [$data]); + $result = $this->callProtected($dataMapper, 'prepareProjection', [$data]); // Assert - $this->assertEquals($expectation, $result); + $this->assertSame($expectation, $result); } public function testPrepareProjectionShouldThrownAnException() { // Arrange $connection = m::mock(Connection::class); - $mapper = new DataMapper($connection); + $dataMapper = new DataMapper($connection); $data = ['valid' => true, 'invalid-key' => 'invalid-value']; // Expectations @@ -771,34 +763,7 @@ public function testPrepareProjectionShouldThrownAnException() $this->expectExceptionMessage("Invalid projection: 'invalid-key' => 'invalid-value'"); // Act - $this->callProtected($mapper, 'prepareProjection', [$data]); - } - - protected function getEventService() - { - if (!Ioc::has(EventTriggerService::class)) { - Ioc::instance(EventTriggerService::class, m::mock(EventTriggerService::class)); - } - - return Ioc::make(EventTriggerService::class); - } - - protected function expectEventToBeFired($event, $entity, bool $halt, $return = true) - { - $event = 'mongolid.'.$event.': '.get_class($entity); - - $this->getEventService()->expects() - ->fire($event, $entity, $halt) - ->andReturn($return); - } - - protected function expectEventNotToBeFired($event, $entity) - { - $event = 'mongolid.'.$event.': '.get_class($entity); - - $this->getEventService()->expects() - ->fire($event, $entity, m::any()) - ->never(); + $this->callProtected($dataMapper, 'prepareProjection', [$data]); } public function eventsToBailOperations() @@ -874,27 +839,35 @@ public function queryValueScenarios() public function getWriteConcernVariations() { + $model = new class extends ActiveRecord + { + }; + + $model2 = new class extends ActiveRecord + { + }; + return [ 'acknowledged write concern with plain object' => [ - 'object' => m::mock(), + 'object' => new stdClass(), 'writeConcern' => 1, 'shouldFireEventAfter' => true, 'expected' => true, ], - 'acknowledged write concern with attributesAccessIntesarface' => [ - 'object' => m::mock(AttributesAccessInterface::class), + 'acknowledged write concern with attributesAccessInterface' => [ + 'object' => $model, 'writeConcern' => 1, 'shouldFireEventAfter' => true, 'expected' => true, ], 'unacknowledged write concern with plain object' => [ - 'object' => m::mock(), + 'object' => new stdClass(), 'writeConcern' => 0, 'shouldFireEventAfter' => false, 'expected' => false, ], 'unacknowledged write concern with attributesAccessInterface' => [ - 'object' => m::mock(AttributesAccessInterface::class), + 'object' => $model2, 'writeConcern' => 0, 'shouldFireEventAfter' => false, 'expected' => false, @@ -926,4 +899,31 @@ public function getProjections() ], ]; } + + protected function getEventService() + { + if (!Ioc::has(EventTriggerService::class)) { + Ioc::instance(EventTriggerService::class, m::mock(EventTriggerService::class)); + } + + return Ioc::make(EventTriggerService::class); + } + + protected function expectEventToBeFired($event, $entity, bool $halt, $return = true) + { + $event = 'mongolid.'.$event.': '.get_class($entity); + + $this->getEventService()->expects() + ->fire($event, $entity, $halt) + ->andReturn($return); + } + + protected function expectEventNotToBeFired($event, $entity) + { + $event = 'mongolid.'.$event.': '.get_class($entity); + + $this->getEventService()->expects() + ->fire($event, $entity, m::any()) + ->never(); + } } diff --git a/tests/Unit/DataMapper/EntityAssemblerTest.php b/tests/Unit/DataMapper/EntityAssemblerTest.php index e369ac1b..fee5f41f 100644 --- a/tests/Unit/DataMapper/EntityAssemblerTest.php +++ b/tests/Unit/DataMapper/EntityAssemblerTest.php @@ -265,7 +265,7 @@ public function __construct($attr = []) $this->$key = $value; } - $this->original = $this->attributes; + $this->syncOriginalAttributes(); } } diff --git a/tests/Unit/DataMapper/SchemaMapperTest.php b/tests/Unit/DataMapper/SchemaMapperTest.php index c572c691..6d7cfbf2 100644 --- a/tests/Unit/DataMapper/SchemaMapperTest.php +++ b/tests/Unit/DataMapper/SchemaMapperTest.php @@ -5,48 +5,74 @@ use Mongolid\Container\Ioc; use Mongolid\Schema\Schema; use Mongolid\TestCase; +use stdClass; class SchemaMapperTest extends TestCase { public function testShouldMapToFieldsOfSchema() { // Arrange - $schema = m::mock(Schema::class); - $schema->fields = [ - 'name' => 'string', - 'age' => 'int', - 'stuff' => 'schema.My\Own\Schema', - ]; - $schemaMapper = m::mock( - SchemaMapper::class.'[clearDynamic,parseField]', - [$schema] - ); - $schemaMapper->shouldAllowMockingProtectedMethods(); + $myOwnSchema = new class() extends Schema + { + /** + * {@inheritdoc} + */ + public $dynamic = true; + + /** + * {@inheritdoc} + */ + public $fields = []; + }; + + Ioc::instance('My\Own\Schema', $myOwnSchema); + + $schema = new class() extends Schema + { + /** + * {@inheritdoc} + */ + public $dynamic = true; + + /** + * {@inheritdoc} + */ + public $fields = [ + 'name' => 'string', + 'surname' => 'string', + 'age' => 'int', + 'stuff' => 'schema.My\Own\Schema', + ]; + }; + + $schemaMapper = new SchemaMapper($schema); + $stuff = new stdClass(); + $stuff->address = '1, Blue Street'; + + $otherStuff = new stdClass(); + $otherStuff->address = '2, Green Street'; + $data = [ + 'name' => 'John', + 'surname' => null, + 'age' => '23', + 'stuff' => [$stuff, $otherStuff], + 'invalid' => null, + 'empty' => '', + ]; + + $expected = [ 'name' => 'John', 'age' => 23, - 'stuff' => 'fooBar', + 'stuff' => [['address' => '1, Blue Street'], ['address' => '2, Green Street']], + 'empty' => '', ]; // Act - $schemaMapper->expects() - ->clearDynamic($data); - - foreach ($schema->fields as $key => $value) { - $schemaMapper->expects() - ->parseField($data[$key], $value) - ->andReturn($data[$key].'.PARSED'); - } + $result = $schemaMapper->map($data); // Assert - $this->assertEquals( - [ - 'name' => 'John.PARSED', - 'age' => '23.PARSED', - 'stuff' => 'fooBar.PARSED', - ], - $schemaMapper->map($data) - ); + $this->assertSame($expected, $result); } public function testShouldClearDynamicFieldsIfSchemaIsNotDynamic() @@ -147,7 +173,8 @@ public function testShouldParseFieldIntoAnotherMappedSchemaIfTypeBeginsWithSchem public function testShouldParseFieldUsingAMethodInSchemaIfTypeIsAnUnknownString() { // Arrange - $schemaClass = new class() extends Schema { + $schemaClass = new class() extends Schema + { public function pumpkinPoint($value) { return $value * 2; @@ -177,20 +204,23 @@ public function testShouldMapAnArrayValueToAnotherSchema() Ioc::instance('Xd\MySchema', $mySchema); // When instantiating the SchemaMapper with the specified $param as dependency - Ioc::bind(SchemaMapper::class, function ($container, $params) use ($value, $mySchema, $test) { - // Check if mySchema has been injected correctly - $test->assertSame($mySchema, $params['schema']); + Ioc::bind( + SchemaMapper::class, + function ($container, $params) use ($value, $mySchema, $test) { + // Check if mySchema has been injected correctly + $test->assertSame($mySchema, $params['schema']); - // Instantiate a SchemaMapper with mySchema - $anotherSchemaMapper = m::mock(SchemaMapper::class, [$params['schema']]); + // Instantiate a SchemaMapper with mySchema + $anotherSchemaMapper = m::mock(SchemaMapper::class, [$params['schema']]); - // Set expectation to receive a map call - $anotherSchemaMapper->expects() - ->map($value) - ->andReturn(['foo' => 'PARSED']); + // Set expectation to receive a map call + $anotherSchemaMapper->expects() + ->map($value) + ->andReturn(['foo' => 'PARSED']); - return $anotherSchemaMapper; - }); + return $anotherSchemaMapper; + } + ); // Assert $this->assertEquals( @@ -234,7 +264,8 @@ public function testShouldGetAttributesWhenGetAttributesMethodIsAvailable() // Arrange $schema = m::mock(Schema::class); $schemaMapper = new SchemaMapper($schema); - $object = new class() { + $object = new class() + { public function getAttributes() { return ['foo' => 'bar']; From d6fb298979f6d09021a66df03a2e980e327f8d54 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 22 Oct 2018 15:11:54 -0300 Subject: [PATCH 019/116] Allow modification of array values directly It is now possible to use `$model->array['key'] = 'value';` --- src/Model/Attributes.php | 97 ++++++++++++++----------- tests/Integration/PersistedDataTest.php | 10 +++ 2 files changed, 64 insertions(+), 43 deletions(-) diff --git a/src/Model/Attributes.php b/src/Model/Attributes.php index bf154097..c00e7810 100644 --- a/src/Model/Attributes.php +++ b/src/Model/Attributes.php @@ -13,6 +13,15 @@ */ trait Attributes { + /** + * Check if model should mutate attributes checking + * the existence of a specific method on model + * class. Default is false. + * + * @var bool + */ + public $mutable = false; + /** * The model's attributes. * @@ -45,13 +54,11 @@ trait Attributes protected $guarded = []; /** - * Check if model should mutate attributes checking - * the existence of a specific method on model - * class. Default is false. + * Store mutable attribute values to work with `&__get()`. * - * @var bool + * @var array */ - public $mutable = false; + protected $mutableCache = []; /** * Get an attribute from the model. @@ -62,13 +69,7 @@ trait Attributes */ public function getAttribute(string $key) { - $inAttributes = array_key_exists($key, $this->attributes); - - if ($inAttributes) { - return $this->attributes[$key]; - } elseif ('attributes' == $key) { - return $this->attributes; - } + return $this->{$key}; } /** @@ -148,34 +149,6 @@ public function getOriginalAttributes(): array return $this->original; } - /** - * Verify if model has a mutator method defined. - * - * @param mixed $key attribute name - * @param mixed $prefix method prefix to be used - * - * @return bool - */ - protected function hasMutatorMethod($key, $prefix) - { - $method = $this->buildMutatorMethod($key, $prefix); - - return method_exists($this, $method); - } - - /** - * Create mutator method pattern. - * - * @param mixed $key attribute name - * @param mixed $prefix method prefix to be used - * - * @return string - */ - protected function buildMutatorMethod($key, $prefix) - { - return $prefix.ucfirst($key).'Attribute'; - } - /** * Returns the model instance as an Array. * @@ -193,13 +166,23 @@ public function toArray() * * @return mixed */ - public function __get($key) + public function &__get($key) { + if ('attributes' === $key) { + return $this->attributes; + } + if ($this->mutable && $this->hasMutatorMethod($key, 'get')) { - return $this->{$this->buildMutatorMethod($key, 'get')}(); + $this->mutableCache[$key] = $this->{$this->buildMutatorMethod($key, 'get')}(); + + return $this->mutableCache[$key]; } - return $this->getAttribute($key); + if (!array_key_exists($key, $this->attributes)) { + $this->attributes[$key] = null; + } + + return $this->attributes[$key]; } /** @@ -238,4 +221,32 @@ public function __unset($key) { unset($this->attributes[$key]); } + + /** + * Verify if model has a mutator method defined. + * + * @param mixed $key attribute name + * @param mixed $prefix method prefix to be used + * + * @return bool + */ + protected function hasMutatorMethod($key, $prefix) + { + $method = $this->buildMutatorMethod($key, $prefix); + + return method_exists($this, $method); + } + + /** + * Create mutator method pattern. + * + * @param mixed $key attribute name + * @param mixed $prefix method prefix to be used + * + * @return string + */ + protected function buildMutatorMethod($key, $prefix) + { + return $prefix.ucfirst($key).'Attribute'; + } } diff --git a/tests/Integration/PersistedDataTest.php b/tests/Integration/PersistedDataTest.php index aaf53413..bba1fd60 100644 --- a/tests/Integration/PersistedDataTest.php +++ b/tests/Integration/PersistedDataTest.php @@ -36,6 +36,7 @@ public function testSaveInsertingData() 'JavaScript' => ['percentage' => '80%', 'version' => 'ES6'], 'CSS' => ['percentage' => '45%', 'version' => 'CSS3'], ], + 'photos' => ['profile' => '/user-photo', 'icon' => '/user-icon'], ]; // Actions @@ -75,6 +76,7 @@ public function testSaveUpdatingData() 'CSS' => ['percentage' => '45%', 'version' => 'CSS3'], 'HTML' => ['percentage' => '89%', 'version' => 'HTML5'], ], + 'photos' => ['profile' => '/user-photo', 'icon' => '/user-icon'], 'email' => 'jane@doe.com', ]; @@ -114,6 +116,7 @@ public function testUpdateData() 'CSS' => ['percentage' => '45%', 'version' => 'CSS3'], 'HTML' => ['percentage' => '89%', 'version' => 'HTML5'], ], + 'photos' => ['profile' => '/user-photo', 'icon' => '/user-icon'], 'address' => '123 Blue Street', 'email' => 'jane@doe.com', ]; @@ -146,6 +149,13 @@ private function getUser(bool $save = false): User 'CSS' => ['percentage' => '45%', 'version' => 'CSS3'], ]; + // dinamically set array + $user->photos['profile'] = '/user-photo'; + $user->photos['icon'] = '/user-icon'; + + // access unknown field and don't find it saved later. + $user->unknown; + if ($save) { $this->assertTrue($user->save(), 'Failed to save user!'); } From 367c77dcc80ca7fba7b4f4af9d1aeb91f6ab5730 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 22 Oct 2018 15:14:05 -0300 Subject: [PATCH 020/116] Allow integration testes to be accounted on coverage results --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index fb4e5d02..79869de9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,8 +15,7 @@ before_script: script: - vendor/bin/phpcs - - phpunit --testsuite unit -c phpunit.xml.dist - - phpunit --testsuite integration + - vendor/bin/phpunit -c phpunit.xml.dist after_script: - php vendor/bin/coveralls -v From 83ea3b932fbd5c880260ee0e2789503138522319 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 22 Oct 2018 17:24:24 -0300 Subject: [PATCH 021/116] Move Manager to Connections Namespace --- docs/basics.md | 4 ++-- docs/datamapper.md | 2 +- src/{ => Connection}/Manager.php | 9 ++++----- tests/Unit/{ => Connection}/ManagerTest.php | 4 ++-- 4 files changed, 9 insertions(+), 10 deletions(-) rename src/{ => Connection}/Manager.php (93%) rename tests/Unit/{ => Connection}/ManagerTest.php (98%) diff --git a/docs/basics.md b/docs/basics.md index ab731ff2..8dc965ef 100644 --- a/docs/basics.md +++ b/docs/basics.md @@ -14,13 +14,13 @@ composer require leroy-merlin-br/mongolid ## Setup If you are not using Laravel, you should initialize the Mongolid connection and container manually. -The minimalistic way of doing it is to use `Mongolid\Manager`: +The minimalistic way of doing it is to use `Mongolid\Connection\Manager`: ```php registerSchema(new ArticleSchema); diff --git a/src/Manager.php b/src/Connection/Manager.php similarity index 93% rename from src/Manager.php rename to src/Connection/Manager.php index 15e4dcb4..a38b0f3f 100644 --- a/src/Manager.php +++ b/src/Connection/Manager.php @@ -1,8 +1,7 @@ setConnection(new Connection); + * (new Mongolid\Connection\Manager)->setConnection(new Connection()); * // And then start persisting and querying your models. */ class Manager @@ -61,7 +60,7 @@ class Manager protected $schemas = []; /** - * Main entry point to openning a connection and start using Mongolid in + * Main entry point to opening a connection and start using Mongolid in * pure PHP. After adding a connection into the Manager you are ready to * persist and query your models. * diff --git a/tests/Unit/ManagerTest.php b/tests/Unit/Connection/ManagerTest.php similarity index 98% rename from tests/Unit/ManagerTest.php rename to tests/Unit/Connection/ManagerTest.php index ca24c68a..83279282 100644 --- a/tests/Unit/ManagerTest.php +++ b/tests/Unit/Connection/ManagerTest.php @@ -1,15 +1,15 @@ Date: Mon, 22 Oct 2018 17:27:21 -0300 Subject: [PATCH 022/116] Move ActiveRecord to Model Namespace --- docs/basics.md | 6 +++--- src/Cursor/Cursor.php | 2 +- src/Cursor/EmbeddedCursor.php | 2 +- src/{ => Model}/ActiveRecord.php | 8 +++----- tests/Integration/Stubs/User.php | 2 +- tests/Unit/Cursor/CursorTest.php | 2 +- tests/Unit/Cursor/EmbeddedCursorTest.php | 2 +- tests/Unit/DataMapper/DataMapperTest.php | 2 +- tests/Unit/{ => Model}/ActiveRecordTest.php | 6 +++--- tests/Unit/Model/RelationsTest.php | 1 - 10 files changed, 15 insertions(+), 18 deletions(-) rename src/{ => Model}/ActiveRecord.php (97%) rename tests/Unit/{ => Model}/ActiveRecordTest.php (99%) diff --git a/docs/basics.md b/docs/basics.md index 8dc965ef..2b084969 100644 --- a/docs/basics.md +++ b/docs/basics.md @@ -34,7 +34,7 @@ Now you are ready to create your own models :smile: > **Note:** Mongolid does support [**DataMapper** pattern](./datamapper.md), but in order to understand it let's begin with the **ActiveRecord** pattern: ```php -class Post extends Mongolid\ActiveRecord +class Post extends Mongolid\Model\ActiveRecord { } ``` @@ -192,9 +192,9 @@ $post->delete(); ## Mass Assignment -If you are extending `Mongolid\ActiveRecord` you can set an array of attributes to the model using the `fill` method. These attributes are then assigned to the model via mass-assignment. This is convenient; however, can be a **serious** security concern when blindly passing user input into a model. If user input is blindly passed into a model, the user is free to modify **any** and **all** of the model's attributes. By default, all attributes are fillable. +If you are extending `Mongolid\Model\ActiveRecord` you can set an array of attributes to the model using the `fill` method. These attributes are then assigned to the model via mass-assignment. This is convenient; however, can be a **serious** security concern when blindly passing user input into a model. If user input is blindly passed into a model, the user is free to modify **any** and **all** of the model's attributes. By default, all attributes are fillable. -`Mongolid\ActiveRecord` (and `Mongolid\Model\Attributes` trait) will use the `fillable` or `guarded` properties on your model. +`Mongolid\Model\ActiveRecord` (and `Mongolid\Model\Attributes` trait) will use the `fillable` or `guarded` properties on your model. The `fillable` property specifies which attributes should be mass-assignable. This can be set at the class or instance level. diff --git a/src/Cursor/Cursor.php b/src/Cursor/Cursor.php index cc53ccad..1f134827 100644 --- a/src/Cursor/Cursor.php +++ b/src/Cursor/Cursor.php @@ -6,10 +6,10 @@ use MongoDB\Driver\Cursor as DriverCursor; use MongoDB\Driver\Exception\LogicException; use MongoDB\Driver\ReadPreference; -use Mongolid\ActiveRecord; use Mongolid\Connection\Connection; use Mongolid\Container\Ioc; use Mongolid\DataMapper\EntityAssembler; +use Mongolid\Model\ActiveRecord; use Mongolid\Schema\Schema; use Serializable; use Traversable; diff --git a/src/Cursor/EmbeddedCursor.php b/src/Cursor/EmbeddedCursor.php index 35c522ff..34a5975d 100644 --- a/src/Cursor/EmbeddedCursor.php +++ b/src/Cursor/EmbeddedCursor.php @@ -1,9 +1,9 @@ Date: Mon, 22 Oct 2018 17:32:08 -0300 Subject: [PATCH 023/116] Prevent query 'first(null)' to hit the database This simple change improve relationship checks, where for example a `referencesOne` with no value would hit the database previously. --- src/DataMapper/DataMapper.php | 6 +++++- tests/Unit/DataMapper/DataMapperTest.php | 13 +++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/DataMapper/DataMapper.php b/src/DataMapper/DataMapper.php index 95335134..7e86853b 100644 --- a/src/DataMapper/DataMapper.php +++ b/src/DataMapper/DataMapper.php @@ -277,6 +277,10 @@ public function first( array $projection = [], bool $cacheable = false ) { + if (null === $query) { + return null; + } + if ($cacheable) { return $this->where($query, $projection, true)->first(); } @@ -287,7 +291,7 @@ public function first( ); if (!$document) { - return; + return null; } $model = $this->getAssembler()->assemble($document, $this->schema); diff --git a/tests/Unit/DataMapper/DataMapperTest.php b/tests/Unit/DataMapper/DataMapperTest.php index 27a340a1..aca5200d 100644 --- a/tests/Unit/DataMapper/DataMapperTest.php +++ b/tests/Unit/DataMapper/DataMapperTest.php @@ -518,6 +518,19 @@ public function testShouldGetFirstWithQuery() $this->assertAttributeEquals('John Doe', 'name', $result); } + public function testFirstWithNullShouldNotHitTheDatabase() + { + // Arrange + $connection = m::mock(Connection::class); + $dataMapper = new DataMapper($connection); + + // Act + $result = $dataMapper->first(null); + + // Assert + $this->assertNull($result); + } + public function testShouldGetNullIfFirstCantFindAnything() { // Arrange From d942731e917494932a6cac629f1cd5fd1fed13e6 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 22 Oct 2018 18:22:57 -0300 Subject: [PATCH 024/116] Make mutable field protected --- src/Model/Attributes.php | 18 ++++++------ tests/Unit/Model/AttributesTest.php | 43 ++++++++++++++++------------- 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/src/Model/Attributes.php b/src/Model/Attributes.php index c00e7810..663bc09b 100644 --- a/src/Model/Attributes.php +++ b/src/Model/Attributes.php @@ -13,15 +13,6 @@ */ trait Attributes { - /** - * Check if model should mutate attributes checking - * the existence of a specific method on model - * class. Default is false. - * - * @var bool - */ - public $mutable = false; - /** * The model's attributes. * @@ -53,6 +44,15 @@ trait Attributes */ protected $guarded = []; + /** + * Check if model should mutate attributes checking + * the existence of a specific method on model + * class. Default is false. + * + * @var bool + */ + protected $mutable = false; + /** * Store mutable attribute values to work with `&__get()`. * diff --git a/tests/Unit/Model/AttributesTest.php b/tests/Unit/Model/AttributesTest.php index 687bb6b1..ca38a5fd 100644 --- a/tests/Unit/Model/AttributesTest.php +++ b/tests/Unit/Model/AttributesTest.php @@ -79,15 +79,17 @@ public function testShouldCheckIfMutatedAttributeIsSet() $model = new class() { use Attributes; + public function __construct() + { + $this->mutable = true; + } + public function getNameAttribute() { return 'John'; } }; - /* Enable mutator methods */ - $model->mutable = true; - // Assert $this->assertTrue(isset($model->name)); $this->assertFalse(isset($model->nonexistant)); @@ -98,20 +100,19 @@ public function testShouldUnsetAttributes() // Arrange $model = new class() { use Attributes; - }; - $this->setProtected( - $model, - 'attributes', - [ - 'name' => 'John', - 'age' => 25, - ] - ); + public function __construct() + { + $this->attributes = [ + 'name' => 'John', + 'age' => 25, + ]; + } + }; // Assert unset($model->age); - $this->assertAttributeEquals( + $this->assertAttributeSame( [ 'name' => 'John', ], @@ -126,14 +127,17 @@ public function testShouldGetAttributeFromMutator() $model = new class() { use Attributes; + public function __construct() + { + $this->mutable = true; + } + public function getSomeAttribute() { return 'something-else'; } }; - /* Enable mutator methods */ - $model->mutable = true; $model->some = 'some-value'; // Assert @@ -157,8 +161,6 @@ public function setSomeAttribute($value) } }; - /* Disable mutator methods */ - $model->mutable = false; $model->some = 'some-value'; // Assert @@ -171,14 +173,17 @@ public function testShouldSetAttributeFromMutator() $model = new class() { use Attributes; + public function __construct() + { + $this->mutable = true; + } + public function setSomeAttribute($value) { return strtoupper($value); } }; - /* Enable mutator methods */ - $model->mutable = true; $model->some = 'some-value'; // Assert From 965736c1fe96f4908775d330db8aee1cd813461a Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 22 Oct 2018 18:44:48 -0300 Subject: [PATCH 025/116] Small improvements --- src/Model/Attributes.php | 11 ++-- tests/Unit/DataMapper/SchemaMapperTest.php | 61 +++++++++++++++------- 2 files changed, 45 insertions(+), 27 deletions(-) diff --git a/src/Model/Attributes.php b/src/Model/Attributes.php index 663bc09b..2d7b7961 100644 --- a/src/Model/Attributes.php +++ b/src/Model/Attributes.php @@ -89,13 +89,8 @@ public function getAttributes(): array public function fill(array $input, bool $force = false) { foreach ($input as $key => $value) { - if ($force) { - $this->setAttribute($key, $value); - - continue; - } - - if ((empty($this->fillable) || in_array($key, $this->fillable)) && !in_array($key, $this->guarded)) { + if ($force + || ((!$this->fillable || in_array($key, $this->fillable)) && !in_array($key, $this->guarded))) { $this->setAttribute($key, $value); } } @@ -154,7 +149,7 @@ public function getOriginalAttributes(): array * * @return array */ - public function toArray() + public function toArray(): array { return $this->getAttributes(); } diff --git a/tests/Unit/DataMapper/SchemaMapperTest.php b/tests/Unit/DataMapper/SchemaMapperTest.php index 6d7cfbf2..cd0438ea 100644 --- a/tests/Unit/DataMapper/SchemaMapperTest.php +++ b/tests/Unit/DataMapper/SchemaMapperTest.php @@ -3,6 +3,7 @@ use Mockery as m; use Mongolid\Container\Ioc; +use Mongolid\Schema\DynamicSchema; use Mongolid\Schema\Schema; use Mongolid\TestCase; use stdClass; @@ -78,12 +79,16 @@ public function testShouldMapToFieldsOfSchema() public function testShouldClearDynamicFieldsIfSchemaIsNotDynamic() { // Arrange - $schema = m::mock(Schema::class); - $schema->dynamic = false; - $schema->fields = [ - 'name' => 'string', - 'age' => 'int', - ]; + $schema = new class extends Schema + { + /** + * {@inheritdoc} + */ + public $fields = [ + 'name' => 'string', + 'age' => 'int', + ]; + }; $schemaMapper = new SchemaMapper($schema); $data = [ 'name' => 'John', @@ -105,12 +110,16 @@ public function testShouldClearDynamicFieldsIfSchemaIsNotDynamic() public function testShouldNotClearDynamicFieldsIfSchemaIsDynamic() { // Arrange - $schema = m::mock(Schema::class); - $schema->dynamic = true; - $schema->fields = [ - 'name' => 'string', - 'age' => 'int', - ]; + $schema = new class extends DynamicSchema + { + /** + * {@inheritdoc} + */ + public $fields = [ + 'name' => 'string', + 'age' => 'int', + ]; + }; $schemaMapper = new SchemaMapper($schema); $data = [ 'name' => 'John', @@ -133,7 +142,9 @@ public function testShouldNotClearDynamicFieldsIfSchemaIsDynamic() public function testShouldParseFieldIntoCastableType() { // Arrange - $schema = m::mock(Schema::class); + $schema = new class extends Schema + { + }; $schemaMapper = new SchemaMapper($schema); // Assert @@ -151,7 +162,9 @@ public function testShouldParseFieldIntoCastableType() public function testShouldParseFieldIntoAnotherMappedSchemaIfTypeBeginsWithSchema() { // Arrange - $schema = m::mock(Schema::class); + $schema = new class extends Schema + { + }; $schemaMapper = m::mock( SchemaMapper::class.'[mapToSchema]', [$schema] @@ -194,8 +207,12 @@ public function pumpkinPoint($value) public function testShouldMapAnArrayValueToAnotherSchema() { // Arrange - $schema = m::mock(Schema::class); - $mySchema = m::mock(Schema::class); + $schema = new class extends Schema + { + }; + $mySchema = new class extends Schema + { + }; $schemaMapper = new SchemaMapper($schema); $value = ['foo' => 'bar']; $test = $this; @@ -234,7 +251,9 @@ function ($container, $params) use ($value, $mySchema, $test) { public function testShouldParseToArrayGettingObjectAttributes() { // Arrange - $schema = m::mock(Schema::class); + $schema = new class extends Schema + { + }; $schemaMapper = new SchemaMapper($schema); $object = (object) ['foo' => 'bar', 'name' => 'wilson']; @@ -248,7 +267,9 @@ public function testShouldParseToArrayGettingObjectAttributes() public function testShouldParseToArrayIfIsAnArray() { // Arrange - $schema = m::mock(Schema::class); + $schema = new class extends Schema + { + }; $schemaMapper = new SchemaMapper($schema); $object = ['age' => 25]; @@ -262,7 +283,9 @@ public function testShouldParseToArrayIfIsAnArray() public function testShouldGetAttributesWhenGetAttributesMethodIsAvailable() { // Arrange - $schema = m::mock(Schema::class); + $schema = new class extends Schema + { + }; $schemaMapper = new SchemaMapper($schema); $object = new class() { From 647b1e8a23c531dbfc44af07468665cbe59b47dc Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 22 Oct 2018 19:20:48 -0300 Subject: [PATCH 026/116] Rename model attributes and original attributes --- docs/relationships.md | 257 ++++++++++----------- src/DataMapper/DataMapper.php | 2 +- src/DataMapper/SchemaMapper.php | 5 +- src/Model/Attributes.php | 34 ++- src/Model/AttributesAccessInterface.php | 4 +- src/Model/PolymorphableInterface.php | 2 +- tests/Unit/Cursor/EmbeddedCursorTest.php | 2 +- tests/Unit/DataMapper/SchemaMapperTest.php | 31 ++- tests/Unit/Model/AttributesTest.php | 108 +++++---- 9 files changed, 234 insertions(+), 211 deletions(-) diff --git a/docs/relationships.md b/docs/relationships.md index a05789f9..d3c039d1 100644 --- a/docs/relationships.md +++ b/docs/relationships.md @@ -18,38 +18,34 @@ A Embeds One relationship is a very basic relation. For example, a `User` model **Defining A Embeds One Relation** ```php - // models/Person.php - class Person extends ActiveRecord { - - // This model is saved in the collection people - protected $collection = 'people'; - - // Method that will be used to access the phone - public function phone() - { - return $this->embedsOne('Phone', 'phone'); - } +// models/Person.php +class Person extends ActiveRecord { + // This model is saved in the collection people + protected $collection = 'people'; + // Method that will be used to access the phone + public function phone() + { + return $this->embedsOne(Phone::class, 'phone'); } +} - // models/Phone.php - class Phone extends ActiveRecord { - - // This model will be embedded only - protected $collection = null; - - public function getFullPhone() - { - return '+' . $this->regionCode . $this->number; - } +// models/Phone.php +class Phone extends ActiveRecord { + // This model will be embedded only + protected $collection = null; + public function getFullPhone() + { + return '+' . $this->regionCode . $this->number; } +} ``` The first argument passed to the `embedsOne` method is the name of the related model. The second argument is in what attribute that object will be embedded. Once the relationship is defined, we may retrieve it using: ```php - $phone = User::find('4af9f23d8ead0e1d32000000')->phone(); +$phone = User::find('4af9f23d8ead0e1d32000000')->phone(); ``` Which will translate to: @@ -61,48 +57,48 @@ Which will translate to: In order to embed a document to be used in a Embeds One relationship, simply do the following: ```php - // The object that will be embeded - $phoneObj = new Phone; - $phoneObj->regionCode = '55'; - $phoneObj->number = '1532323232'; +// The object that will be embeded +$phone = new Phone(); +$phone->regionCode = '55'; +$phone->number = '1532323232'; - // The object that will contain the phone - $user = User::first('4af9f23d8ead0e1d32000000'); +// The object that will contain the phone +$user = User::first('4af9f23d8ead0e1d32000000'); - // This method will embed the $phoneObj into the phone attribute of the user - $user->embed('phone', $phoneObj); +// This method will embed the $phone into the phone attribute of the user +$user->embed('phone', $phone); - // This is an alias to the method called above. - $user->embedToPhone($phoneObj); +// This is an alias to the method called above. +$user->embedToPhone($phone); - // Not recomended, but also works - $user->phone = $phoneObj->attributes; +// Not recomended, but also works +$user->phone = $phone->attributes(); - // Or (not recomended) - $user->phone = $phoneObj->toArray(); +// Or (not recomended) +$user->phone = $phone->toArray(); - // Or even (not recomended) - $user->phone = [ - 'regionCode' => $phoneObj->regionCode, - 'number' => $phoneObj->number - ]; +// Or even (not recomended) +$user->phone = [ + 'regionCode' => $phone->regionCode, + 'number' => $phone->number +]; - $user->save(); +$user->save(); - // Now we can retrieve the object by calling - $user->phone(); // Will return a Phone object similar to $phoneObj +// Now we can retrieve the object by calling +$user->phone(); // Will return a Phone object similar to $phone ``` > **Note:** When using Mongolid models you will need to call the `save()` method after embeding or attaching objects. The changes will only persists after you call the 'save()' method. -It's recomended that you don't embed your models by setting the attribute directly. The `embed` method will include an `_id` to identify your embeded document and allow the usage of `unembed` and `embed` to update models. +It's recommended that you don't embed your models by setting the attribute directly. The `embed` method will include an `_id` to identify your embedded document and allow the usage of `unembed` and `embed` to update models. ```php - $user->embed('phone', $phoneObj); // Now, $phoneObj have an _id +$user->embed('phone', $phone); // Now, $phone have an _id - $phoneObj->regionCode = 77; // Update the region code - - $user->embed($phoneObj); // Will update +$phone->regionCode = 77; // Update the region code + +$user->embed($phone); // Will update ``` ### Embeds many @@ -110,58 +106,55 @@ It's recomended that you don't embed your models by setting the attribute direct An example of a Embeds Many relation is a blog post that "has many" comments. We can model this relation like so: ```php - // models/Post.php - class Post extends ActiveRecord { - - protected $collection = 'posts'; - - public function comments() - { - return $this->embedsMany('Comment', 'comments'); - } +// models/Post.php +class Post extends ActiveRecord { + protected $collection = 'posts'; + public function comments() + { + return $this->embedsMany(Comment::class, 'comments'); } - // models/Comment.php - class Comment extends ActiveRecord { - - // This model will be embedded only - protected $collection = null; +} - } +// models/Comment.php +class Comment extends ActiveRecord { + // This model will be embedded only + protected $collection = null; +} ``` Now we can access the post's comments `EmbeddedCursor` through the comments method: ```php - $comments = Post::find('4af9f23d8ead0e1d32000000')->comments(); +$comments = Post::find('4af9f23d8ead0e1d32000000')->comments(); ``` Now you can iterate and perform cursor operations in the `EmbeddedCursor` that is retrieved ```php - foreach($comments->limit(10) as $comment) - { - // do something - } +foreach($comments->limit(10) as $comment) +{ + // do something +} ``` In order to embed a document to be used in a Embeds Many relationship, you should use the `embed` method or the alias `embedTo`: ```php - $commentA = new Comment; - $commentA->content = 'Cool feature bro!'; +$commentA = new Comment(); +$commentA->content = 'Cool feature bro!'; - $commentB = new Comment; - $commentB->content = 'Awesome!'; +$commentB = new Comment(); +$commentB->content = 'Awesome!'; - $post = Post::first('4af9f23d8ead0e1d32000000'); +$post = Post::first('4af9f23d8ead0e1d32000000'); - // Both ways work - $post->embedToComments($commentA); - $post->embed('Comments', $commentB); +// Both ways work +$post->embedToComments($commentA); +$post->embed('Comments', $commentB); - $post->save(); +$post->save(); ``` > **Note:** When using Mongolid models you will need to call the `save()` method after embeding or attaching objects. The changes will only persists after you call the 'save()' method. @@ -169,10 +162,10 @@ In order to embed a document to be used in a Embeds Many relationship, you shoul The `embed` method will include an `_id` to identify your embeded document and allow the usage of `embed` and `unembed` to update or delete embeded documents: ```php - $commentB->content = "Pretty awesome!"; +$commentB->content = "Pretty awesome!"; - $post->unembed($commentA); // Removes 'Cool feature bro!' - $post->embed($commentB); // Updates 'Awesome' to 'Pretty awesome' +$post->unembed($commentA); // Removes 'Cool feature bro!' +$post->embed($commentB); // Updates 'Awesome' to 'Pretty awesome' ``` ### References One @@ -188,30 +181,27 @@ In general, use references when embedding would result in duplication of data an **Defining A References One Relation** ```php - // models/Post.php - class Post extends ActiveRecord { - - protected $collection = 'posts'; - - public function author() - { - return $this->referencesOne('User', 'author'); - } +// models/Post.php +class Post extends ActiveRecord { + protected $collection = 'posts'; + public function author() + { + return $this->referencesOne(User::class, 'author'); } - // models/User.php - class User extends ActiveRecord { - - protected $collection = 'users'; +} - } +// models/User.php +class User extends ActiveRecord { + protected $collection = 'users'; +} ``` The first argument passed to the `referencesOne` method is the name of the related model, the second argument is the attribute where the referenced model `_id` will be stored. Once the relationship is defined, we may retrieve it using the following method: ```php - $user = Post::find('4af9f23d8ead0e1d32000000')->author(); +$user = Post::find('4af9f23d8ead0e1d32000000')->author(); ``` This statement will perform the following: @@ -223,26 +213,26 @@ This statement will perform the following: In order to set a reference to a document, simply set the attribute used in the relationship to the reference's `_id` or use the attach method or it's alias. For example: ```php - // The object that will be embeded - $userObj = new User; - $userObj->name = 'John'; - $userObj->save() // This will populates the $userObj->_id +// The object that will be embeded +$user = new User(); +$user->name = 'John'; +$user->save() // This will populates the $user->_id - // The object that will contain the user - $post = Post::first('4af9f23d8ead0e1d32000000'); +// The object that will contain the user +$post = Post::first('4af9f23d8ead0e1d32000000'); - // This method will attach the $phoneObj _id into the phone attribute of the user - $post->attach('author', $userObj); +// This method will attach the $phone _id into the phone attribute of the user +$post->attach('author', $user); - // This is an alias to the method called above. - $post->attachToAuthor($userObj); +// This is an alias to the method called above. +$post->attachToAuthor($user); - // This will will also work - $post->author = $userObj->_id; +// This will will also work +$post->author = $user->_id; - $post->save(); +$post->save(); - $post->author(); // Will return a User object +$post->author(); // Will return a User object ``` > **Note:** When using Mongolid models you will need to call the `save()` method after embedding or attaching objects. The changes will only persists after you call the 'save()' method. @@ -258,30 +248,27 @@ In general, use references when embedding would result in duplication of data an **Defining A References Many Relation** ```php - // models/User.php - class User extends ActiveRecord { - - protected $collection = 'users'; - - public function questions() - { - return $this->referencesMany('Question', 'questions'); - } +// models/User.php +class User extends ActiveRecord { + protected $collection = 'users'; + public function questions() + { + return $this->referencesMany(Question::class, 'questions'); } - // models/Question.php - class Question extends ActiveRecord { - - protected $collection = 'questions'; +} - } +// models/Question.php +class Question extends ActiveRecord { + protected $collection = 'questions'; +} ``` The first argument passed to the `referencesMany` method is the name of the related model, the second argument is the attribute where the `_id`s will be stored. Once the relationship is defined, we may retrieve it using the following method: ```php - $posts = User::find('4af9f23d8ead0e1d32000000')->posts(); +$posts = User::find('4af9f23d8ead0e1d32000000')->posts(); ``` This statement will perform the following: @@ -293,21 +280,21 @@ This statement will perform the following: In order to set a reference to a document use the attach method or it's alias. For example: ```php - $postA = new Post; - $postA->title = 'Nice post'; +$postA = new Post(); +$postA->title = 'Nice post'; - $postB = new Post; - $postB->title = 'Nicer post'; +$postB = new Post(); +$postB->title = 'Nicer post'; - $user = User::first('4af9f23d8ead0e1d32000000'); +$user = User::first('4af9f23d8ead0e1d32000000'); - // Both ways work - $user->attachToPosts($postA); - $user->attach('posts', $postB); +// Both ways work +$user->attachToPosts($postA); +$user->attach('posts', $postB); - $user->save(); +$user->save(); ``` > **Note:** When using Mongolid models you will need to call the `save()` method after embedding or attaching objects. The changes will only persists after you call the 'save()' method. -You can use `dettach` method with the referenced object or it's `_id` in order to remove a single reference. +You can use `detach` method with the referenced object or it's `_id` in order to remove a single reference. diff --git a/src/DataMapper/DataMapper.php b/src/DataMapper/DataMapper.php index 7e86853b..a8106e7e 100644 --- a/src/DataMapper/DataMapper.php +++ b/src/DataMapper/DataMapper.php @@ -602,7 +602,7 @@ private function getUpdateData($entity, array $data) } $changes = []; - $this->calculateChanges($changes, $data, $entity->getOriginalAttributes()); + $this->calculateChanges($changes, $data, $entity->originalAttributes()); return $changes; } diff --git a/src/DataMapper/SchemaMapper.php b/src/DataMapper/SchemaMapper.php index 0c43ba47..5d9b8893 100644 --- a/src/DataMapper/SchemaMapper.php +++ b/src/DataMapper/SchemaMapper.php @@ -2,6 +2,7 @@ namespace Mongolid\DataMapper; use Mongolid\Container\Ioc; +use Mongolid\Model\AttributesAccessInterface; use Mongolid\Schema\Schema; /** @@ -162,8 +163,8 @@ protected function mapToSchema($value, string $schemaClass) protected function parseToArray($object): array { if (!is_array($object)) { - $attributes = method_exists($object, 'getAttributes') - ? $object->getAttributes() + $attributes = $object instanceof AttributesAccessInterface + ? $object->attributes() : get_object_vars($object); return $attributes; diff --git a/src/Model/Attributes.php b/src/Model/Attributes.php index 2d7b7961..565d9900 100644 --- a/src/Model/Attributes.php +++ b/src/Model/Attributes.php @@ -18,14 +18,14 @@ trait Attributes * * @var array */ - protected $attributes = []; + private $_mongolid_attributes = []; /** * The model attribute's original state. * * @var array */ - protected $original = []; + private $_mongolid_original_attributes = []; /** * Once you put at least one string in this array, only @@ -75,9 +75,9 @@ public function getAttribute(string $key) /** * Get all attributes from the model. */ - public function getAttributes(): array + public function attributes(): array { - return $this->attributes; + return $this->_mongolid_attributes; } /** @@ -103,7 +103,7 @@ public function fill(array $input, bool $force = false) */ public function cleanAttribute(string $key) { - unset($this->attributes[$key]); + unset($this->_mongolid_attributes[$key]); } /** @@ -114,7 +114,7 @@ public function cleanAttribute(string $key) */ public function setAttribute(string $key, $value) { - $this->attributes[$key] = $value; + $this->_mongolid_attributes[$key] = $value; } /** @@ -130,18 +130,18 @@ public function setAttribute(string $key, $value) public function syncOriginalAttributes() { try { - $this->original = unserialize(serialize($this->attributes)); + $this->_mongolid_original_attributes = unserialize(serialize($this->attributes())); } catch (Exception $e) { - $this->original = $this->attributes; + $this->_mongolid_original_attributes = $this->attributes(); } } /** * Retrieve original attributes. */ - public function getOriginalAttributes(): array + public function originalAttributes(): array { - return $this->original; + return $this->_mongolid_original_attributes; } /** @@ -151,7 +151,7 @@ public function getOriginalAttributes(): array */ public function toArray(): array { - return $this->getAttributes(); + return $this->attributes(); } /** @@ -163,21 +163,17 @@ public function toArray(): array */ public function &__get($key) { - if ('attributes' === $key) { - return $this->attributes; - } - if ($this->mutable && $this->hasMutatorMethod($key, 'get')) { $this->mutableCache[$key] = $this->{$this->buildMutatorMethod($key, 'get')}(); return $this->mutableCache[$key]; } - if (!array_key_exists($key, $this->attributes)) { - $this->attributes[$key] = null; + if (!array_key_exists($key, $this->_mongolid_attributes)) { + $this->_mongolid_attributes[$key] = null; } - return $this->attributes[$key]; + return $this->_mongolid_attributes[$key]; } /** @@ -214,7 +210,7 @@ public function __isset($key) */ public function __unset($key) { - unset($this->attributes[$key]); + unset($this->_mongolid_attributes[$key]); } /** diff --git a/src/Model/AttributesAccessInterface.php b/src/Model/AttributesAccessInterface.php index f7ebd5f7..2362f37b 100644 --- a/src/Model/AttributesAccessInterface.php +++ b/src/Model/AttributesAccessInterface.php @@ -23,7 +23,7 @@ public function getAttribute(string $key); /** * Get all attributes from the model. */ - public function getAttributes(): array; + public function attributes(): array; /** * Set the model attributes using an array. @@ -60,5 +60,5 @@ public function syncOriginalAttributes(); /** * Retrieve original attributes. */ - public function getOriginalAttributes(): array; + public function originalAttributes(): array; } diff --git a/src/Model/PolymorphableInterface.php b/src/Model/PolymorphableInterface.php index 6303c782..def1cf1a 100644 --- a/src/Model/PolymorphableInterface.php +++ b/src/Model/PolymorphableInterface.php @@ -25,7 +25,7 @@ interface PolymorphableInterface * { * if ($this->video != null) { * $obj = new VideoContent; - * $obj->fill($this->attributes); + * $obj->fill($this->attributes(); * * return $obj; * } else { diff --git a/tests/Unit/Cursor/EmbeddedCursorTest.php b/tests/Unit/Cursor/EmbeddedCursorTest.php index 5c9a8bc8..d6c6e1f8 100644 --- a/tests/Unit/Cursor/EmbeddedCursorTest.php +++ b/tests/Unit/Cursor/EmbeddedCursorTest.php @@ -164,7 +164,7 @@ public function polymorph() }; $class = get_class($object); - $items = [$object->attributes]; + $items = [$object->attributes()]; $cursor = $this->getCursor($class, $items); $this->setProtected($cursor, 'position', 0); diff --git a/tests/Unit/DataMapper/SchemaMapperTest.php b/tests/Unit/DataMapper/SchemaMapperTest.php index cd0438ea..1bd84c07 100644 --- a/tests/Unit/DataMapper/SchemaMapperTest.php +++ b/tests/Unit/DataMapper/SchemaMapperTest.php @@ -3,6 +3,7 @@ use Mockery as m; use Mongolid\Container\Ioc; +use Mongolid\Model\AttributesAccessInterface; use Mongolid\Schema\DynamicSchema; use Mongolid\Schema\Schema; use Mongolid\TestCase; @@ -280,19 +281,43 @@ public function testShouldParseToArrayIfIsAnArray() ); } - public function testShouldGetAttributesWhenGetAttributesMethodIsAvailable() + public function testShouldGetAttributesWhenObjectImplementsAttributesAccessInterface() { // Arrange $schema = new class extends Schema { }; $schemaMapper = new SchemaMapper($schema); - $object = new class() + $object = new class implements AttributesAccessInterface { - public function getAttributes() + public function getAttribute(string $key) + { + } + + public function attributes(): array { return ['foo' => 'bar']; } + + public function fill(array $input, bool $force = false) + { + } + + public function cleanAttribute(string $key) + { + } + + public function setAttribute(string $key, $value) + { + } + + public function syncOriginalAttributes() + { + } + + public function originalAttributes(): array + { + } }; // Assert diff --git a/tests/Unit/Model/AttributesTest.php b/tests/Unit/Model/AttributesTest.php index ca38a5fd..d1a79bcd 100644 --- a/tests/Unit/Model/AttributesTest.php +++ b/tests/Unit/Model/AttributesTest.php @@ -9,7 +9,8 @@ class AttributesTest extends TestCase public function testShouldHaveDynamicSetters() { // Arrange - $model = new class() { + $model = new class() + { use Attributes; }; @@ -19,64 +20,67 @@ public function testShouldHaveDynamicSetters() $model->name = 'John'; $model->age = 25; $model->child = $childObj; - $this->assertAttributeEquals( + $this->assertSame( [ 'name' => 'John', 'age' => 25, 'child' => $childObj, ], - 'attributes', - $model + $model->attributes() ); } public function testShouldHaveDynamicGetters() { // Arrange - $model = new class() { + $child = new stdClass(); + $attributes = [ + 'name' => 'John', + 'age' => 25, + 'child' => $child, + ]; + + $model = new class($attributes) + { use Attributes; - }; - $childObj = new stdClass(); - $this->setProtected( - $model, - 'attributes', - [ - 'name' => 'John', - 'age' => 25, - 'child' => $childObj, - ] - ); + public function __construct(array $attributes) + { + $this->_mongolid_attributes = $attributes; + } + }; // Assert $this->assertEquals('John', $model->name); $this->assertEquals(25, $model->age); - $this->assertEquals($childObj, $model->child); + $this->assertEquals($child, $model->child); $this->assertEquals(null, $model->nonexistant); } public function testShouldCheckIfAttributeIsSet() { // Arrange - $model = new class() { + $model = new class(['name' => 'John', 'ignored' => null]) + { use Attributes; - }; - $this->setProtected( - $model, - 'attributes', - ['name' => 'John'] - ); + public function __construct(array $attributes) + { + $this->_mongolid_attributes = $attributes; + } + }; // Assert $this->assertTrue(isset($model->name)); $this->assertFalse(isset($model->nonexistant)); + $this->assertFalse(isset($model->ignored)); } public function testShouldCheckIfMutatedAttributeIsSet() { // Arrange - $model = new class() { + $model = new class() + { use Attributes; public function __construct() @@ -98,33 +102,32 @@ public function getNameAttribute() public function testShouldUnsetAttributes() { // Arrange - $model = new class() { + $model = new class() + { use Attributes; public function __construct() { - $this->attributes = [ + $this->_mongolid_attributes = [ 'name' => 'John', 'age' => 25, ]; } }; - // Assert + // Act unset($model->age); - $this->assertAttributeSame( - [ - 'name' => 'John', - ], - 'attributes', - $model - ); + $result = $model->attributes(); + + // Assert + $this->assertSame(['name' => 'John'], $result); } public function testShouldGetAttributeFromMutator() { // Arrange - $model = new class() { + $model = new class() + { use Attributes; public function __construct() @@ -147,7 +150,8 @@ public function getSomeAttribute() public function testShouldIgnoreMutators() { // Arrange - $model = new class() { + $model = new class() + { use Attributes; public function getSomeAttribute() @@ -170,7 +174,8 @@ public function setSomeAttribute($value) public function testShouldSetAttributeFromMutator() { // Arrange - $model = new class() { + $model = new class() + { use Attributes; public function __construct() @@ -200,22 +205,29 @@ public function testShouldFillOnlyPermittedAttributes( $expected ) { // Arrange - $model = new class() { + $model = new class($fillable, $guarded) + { use Attributes; + + public function __construct(array $fillable, array $guarded) + { + $this->fillable = $fillable; + $this->guarded = $guarded; + } }; - $this->setProtected($model, 'fillable', $fillable); - $this->setProtected($model, 'guarded', $guarded); + // Act + $model->fill($input); // Assert - $model->fill($input); - $this->assertAttributeEquals($expected, 'attributes', $model); + $this->assertSame($expected, $model->attributes()); } public function testShouldForceFillAttributes() { // Arrange - $model = new class() { + $model = new class() + { use Attributes; }; @@ -234,7 +246,8 @@ public function testShouldForceFillAttributes() public function testShouldBeCastableToArray() { // Arrange - $model = new class() { + $model = new class() + { use Attributes; }; @@ -251,7 +264,8 @@ public function testShouldBeCastableToArray() public function testShouldSetOriginalAttributes() { // Arrange - $model = new class() implements AttributesAccessInterface { + $model = new class() implements AttributesAccessInterface + { use Attributes; }; @@ -262,7 +276,7 @@ public function testShouldSetOriginalAttributes() $model->syncOriginalAttributes(); // Assert - $this->assertAttributeEquals($model->attributes, 'original', $model); + $this->assertSame($model->attributes(), $model->originalAttributes()); } public function getFillableOptions() From 1e0768fa51d32e0d1a513e2898e35b39c3d3c660 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 22 Oct 2018 20:08:11 -0300 Subject: [PATCH 027/116] Create integration tests for relationships by reference --- .../ReferencesManyRelationTest.php | 65 +++++++++++++++++++ .../Integration/ReferencesOneRelationTest.php | 51 +++++++++++++++ tests/Integration/Stubs/User.php | 10 +++ tests/Util/SetupConnectionTrait.php | 7 ++ 4 files changed, 133 insertions(+) create mode 100644 tests/Integration/ReferencesManyRelationTest.php create mode 100644 tests/Integration/ReferencesOneRelationTest.php diff --git a/tests/Integration/ReferencesManyRelationTest.php b/tests/Integration/ReferencesManyRelationTest.php new file mode 100644 index 00000000..336665ce --- /dev/null +++ b/tests/Integration/ReferencesManyRelationTest.php @@ -0,0 +1,65 @@ +createUser('Chuck'); + $john = $this->createUser('John'); + $john->attach('siblings', $chuck); + + $this->assertSiblings([$chuck], $john); + // hit cache + $this->assertSiblings([$chuck], $john); + + $mary = $this->createUser('Mary'); + $john->attach('siblings', $mary); + + $this->assertSiblings([$chuck, $mary], $john); + // hit cache + $this->assertSiblings([$chuck, $mary], $john); + + // remove one sibling + $john->unembed('siblings', $chuck); + $this->assertSiblings([$mary], $john); + // hit cache + $this->assertSiblings([$mary], $john); + + // replace siblings + $bob = $this->createUser('Bob'); + unset($john->siblings); + $john->attach('siblings', $bob); + + $this->assertSiblings([$bob], $john); + // hit cache + $this->assertSiblings([$bob], $john); + + // remove with unembed + $john->unembed('siblings', $bob); + + $this->assertEmpty($john->siblings()->all()); + } + + private function createUser(string $name): User + { + $user = new User(); + $user->_id = new ObjectId(); + $user->name = $name; + $this->assertTrue($user->save()); + + return $user; + } + + private function assertSiblings($expected, User $model) + { + $siblings = $model->siblings(); + $this->assertInstanceOf(CursorInterface::class, $siblings); + $this->assertEquals($expected, $siblings->all()); + } +} diff --git a/tests/Integration/ReferencesOneRelationTest.php b/tests/Integration/ReferencesOneRelationTest.php new file mode 100644 index 00000000..a5930d9b --- /dev/null +++ b/tests/Integration/ReferencesOneRelationTest.php @@ -0,0 +1,51 @@ +createUser('Chuck'); + $john = $this->createUser('John'); + $john->attach('parent', $chuck); + + $this->assertParent($chuck, $john); + // hit cache + $this->assertParent($chuck, $john); + + // replace parent + $bob = $this->createUser('Bob'); + unset($john->parent); + $john->attach('parent', $bob); + + $this->assertParent($bob, $john); + // hit cache + $this->assertParent($bob, $john); + + // remove with unembed + $john->unembed('parent', $bob); + + $this->assertNull($john->parent()); + } + + private function createUser(string $name): User + { + $user = new User(); + $user->_id = new ObjectId(); + $user->name = $name; + $this->assertTrue($user->save()); + + return $user; + } + + private function assertParent($expected, User $model) + { + $parent = $model->parent(); + $this->assertInstanceOf(User::class, $parent); + $this->assertEquals($expected, $parent); + } +} diff --git a/tests/Integration/Stubs/User.php b/tests/Integration/Stubs/User.php index 8930ba80..a89c26d4 100644 --- a/tests/Integration/Stubs/User.php +++ b/tests/Integration/Stubs/User.php @@ -27,4 +27,14 @@ public function collection(): Collection return $client->{$connection->defaultDatabase}->{$this->collection}; } + + public function parent() + { + return $this->referencesOne(User::class, 'parent'); + } + + public function siblings() + { + return $this->referencesMany(User::class, 'siblings'); + } } diff --git a/tests/Util/SetupConnectionTrait.php b/tests/Util/SetupConnectionTrait.php index 24711a64..68faded3 100644 --- a/tests/Util/SetupConnectionTrait.php +++ b/tests/Util/SetupConnectionTrait.php @@ -3,6 +3,8 @@ use Mongolid\Connection\Connection; use Mongolid\Container\Ioc; +use Mongolid\Util\CacheComponent; +use Mongolid\Util\CacheComponentInterface; trait SetupConnectionTrait { @@ -17,5 +19,10 @@ function () use ($host, $database) { return $connection; } ); + + Ioc::instance( + CacheComponentInterface::class, + new CacheComponent() + ); } } From 75261859bc081e44054403c89447dc0d22b6ee46 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 22 Oct 2018 20:52:06 -0300 Subject: [PATCH 028/116] Improve coverage a bit --- src/Model/Attributes.php | 2 +- tests/Unit/DataMapper/DataMapperTest.php | 135 +++++++++++++++++- .../Exception/ModelNotFoundExceptionTest.php | 22 +++ tests/Unit/Model/AttributesTest.php | 24 ++++ 4 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 tests/Unit/Exception/ModelNotFoundExceptionTest.php diff --git a/src/Model/Attributes.php b/src/Model/Attributes.php index 565d9900..3c345519 100644 --- a/src/Model/Attributes.php +++ b/src/Model/Attributes.php @@ -210,7 +210,7 @@ public function __isset($key) */ public function __unset($key) { - unset($this->_mongolid_attributes[$key]); + $this->cleanAttribute($key); } /** diff --git a/tests/Unit/DataMapper/DataMapperTest.php b/tests/Unit/DataMapper/DataMapperTest.php index aca5200d..b741eebd 100644 --- a/tests/Unit/DataMapper/DataMapperTest.php +++ b/tests/Unit/DataMapper/DataMapperTest.php @@ -13,6 +13,7 @@ use Mongolid\Cursor\CacheableCursor; use Mongolid\Cursor\Cursor; use Mongolid\Event\EventTriggerService; +use Mongolid\Exception\ModelNotFoundException; use Mongolid\Model\ActiveRecord; use Mongolid\Schema\Schema; use Mongolid\TestCase; @@ -235,7 +236,7 @@ public function testShouldUpdate($entity, $writeConcern, $shouldFireEventAfter, ->updateOne( ['_id' => 123], ['$set' => $parsedObject], - ['writeConcern' => new WriteConcern($writeConcern)] + $options )->andReturn($operationResult); $operationResult->expects() @@ -261,6 +262,81 @@ public function testShouldUpdate($entity, $writeConcern, $shouldFireEventAfter, $this->assertSame($expected, $result); } + public function testShouldUpdateUnsettingFields() + { + // Arrange + $connection = m::mock(Connection::class); + $client = m::mock(Client::class); + $database = m::mock(Database::class); + $dataMapper = new DataMapper($connection); + + $entity = new class extends ActiveRecord + { + }; + $collection = m::mock(Collection::class); + $operationResult = m::mock(); + $options = ['writeConcern' => new WriteConcern(1)]; + + Ioc::instance( + Schema::class, + new class() extends Schema + { + /** + * {@inheritdoc} + */ + public $fields = [ + '_id' => 'objectId', + 'unchanged' => 'string', + ]; + } + ); + + $entity->unchanged = 'unchanged'; + $entity->notOnSchema = 'to be deleted'; + $entity->name = 'John'; + $entity->syncOriginalAttributes(); + $entity->_id = 123; + unset($entity->name); + + // Expect + $connection->expects() + ->getRawConnection() + ->andReturn($client); + + $client->expects() + ->selectDatabase('mongolid') + ->andReturn($database); + + $database->expects() + ->selectCollection('') + ->andReturn($collection); + + $collection->expects() + ->updateOne( + ['_id' => 123], + ['$set' => ['_id' => 123], '$unset' => ['name' => '', 'notOnSchema' => '']], + $options + )->andReturn($operationResult); + + $operationResult->expects() + ->isAcknowledged() + ->andReturn(true); + + $operationResult->allows() + ->getModifiedCount() + ->andReturn(1); + + $this->expectEventToBeFired('updating', $entity, true); + + $this->expectEventToBeFired('updated', $entity, false); + + // Act + $result = $dataMapper->update($entity, $options); + + // Assert + $this->assertTrue($result); + } + /** * @dataProvider getWriteConcernVariations */ @@ -531,6 +607,63 @@ public function testFirstWithNullShouldNotHitTheDatabase() $this->assertNull($result); } + public function testFirstOrFailShouldGetFirst() + { + // Arrange + $connection = m::mock(Connection::class); + $dataMapper = m::mock(DataMapper::class.'[prepareValueQuery,getCollection]', [$connection]); + $schema = m::mock(Schema::class); + $collection = m::mock(Collection::class); + $query = 123; + $preparedQuery = ['_id' => 123]; + + $schema->entityClass = 'stdClass'; + $dataMapper->setSchema($schema); + + $dataMapper->shouldAllowMockingProtectedMethods(); + + // Act + $dataMapper->expects() + ->prepareValueQuery($query) + ->andReturn($preparedQuery); + + $dataMapper->expects() + ->getCollection() + ->andReturn($collection); + + $collection->expects() + ->findOne($preparedQuery, ['projection' => []]) + ->andReturn(['name' => 'John Doe']); + + $result = $dataMapper->firstOrFail($query); + + // Assert + $this->assertInstanceOf(stdClass::class, $result); + $this->assertAttributeEquals('John Doe', 'name', $result); + } + + public function testFirstOrFailWithNullShouldFail() + { + // Arrange + $connection = m::mock(Connection::class); + $dataMapper = new DataMapper($connection); + $dataMapper->setSchema( + new class extends Schema + { + /** + * {@inheritdoc} + */ + public $entityClass = 'User'; + } + ); + + $this->expectException(ModelNotFoundException::class); + $this->expectExceptionMessage('No query results for model [User].'); + + // Act + $dataMapper->firstOrFail(null); + } + public function testShouldGetNullIfFirstCantFindAnything() { // Arrange diff --git a/tests/Unit/Exception/ModelNotFoundExceptionTest.php b/tests/Unit/Exception/ModelNotFoundExceptionTest.php new file mode 100644 index 00000000..e3666555 --- /dev/null +++ b/tests/Unit/Exception/ModelNotFoundExceptionTest.php @@ -0,0 +1,22 @@ +setModel('User'); + + // Actions + $modelResult = $object->getModel(); + $messageResult = $object->getMessage(); + + // Assertions + $this->assertSame('User', $modelResult); + $this->assertSame('No query results for model [User].', $messageResult); + } +} diff --git a/tests/Unit/Model/AttributesTest.php b/tests/Unit/Model/AttributesTest.php index d1a79bcd..cc9f93a0 100644 --- a/tests/Unit/Model/AttributesTest.php +++ b/tests/Unit/Model/AttributesTest.php @@ -145,6 +145,7 @@ public function getSomeAttribute() // Assert $this->assertEquals('something-else', $model->some); + $this->assertEquals('something-else', $model->getAttribute('some')); } public function testShouldIgnoreMutators() @@ -169,6 +170,7 @@ public function setSomeAttribute($value) // Assert $this->assertEquals('some-value', $model->some); + $this->assertEquals('some-value', $model->getAttribute('some')); } public function testShouldSetAttributeFromMutator() @@ -279,6 +281,28 @@ public function testShouldSetOriginalAttributes() $this->assertSame($model->attributes(), $model->originalAttributes()); } + public function testShouldFallbackOriginalAttributesIfUnserializationFails() + { + // Arrange + $model = new class() implements AttributesAccessInterface + { + use Attributes; + + public function __construct() + { + $this->_mongolid_attributes = [function () { + }, + ]; + } + }; + + // Act + $model->syncOriginalAttributes(); + + // Assert + $this->assertSame($model->attributes(), $model->originalAttributes()); + } + public function getFillableOptions() { return [ From 7b9112347a141ae8403857c1b8318162fcd85aa7 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Thu, 1 Nov 2018 10:19:00 -0300 Subject: [PATCH 029/116] Update composer and coveralls dependency --- composer.json | 15 +- composer.lock | 458 ++++++++++++++++++++++++++++++-------------------- 2 files changed, 286 insertions(+), 187 deletions(-) diff --git a/composer.json b/composer.json index 7ae76de6..8f4a102b 100644 --- a/composer.json +++ b/composer.json @@ -20,15 +20,15 @@ "require": { "php": ">=7.1", "ext-mongodb": "*", - "mongodb/mongodb": "^1.3", + "mongodb/mongodb": "^1.4", "illuminate/container": "^5.4" }, "require-dev": { - "mockery/mockery": "^1.0", - "satooshi/php-coveralls": "^1.0", - "phpunit/phpunit": "^6.4", - "sami/sami": "^4.0", - "leroy-merlin-br/coding-standard": "^0.1.1" + "leroy-merlin-br/coding-standard": "^0.1", + "mockery/mockery": "^1.2", + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^6.5", + "sami/sami": "^4.1" }, "autoload": { "psr-4": { @@ -46,5 +46,8 @@ "branch-alias": { "dev-master": "v3.0.x-dev" } + }, + "config": { + "sort-packages": true } } diff --git a/composer.lock b/composer.lock index 7629345e..3b88b95e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1a331ad3eeb47f9f707a5d63d8a9688b", + "content-hash": "7061baa6f32a4285b8cf6eddf36c5150", "packages": [ { "name": "doctrine/inflector", @@ -440,16 +440,16 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.9.0", + "version": "v1.10.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8" + "reference": "c79c051f5b3a46be09205c73b80b346e4153e494" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d0cd638f4634c16d8df4508e847f14e9e43168b8", - "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494", + "reference": "c79c051f5b3a46be09205c73b80b346e4153e494", "shasum": "" }, "require": { @@ -495,7 +495,7 @@ "portable", "shim" ], - "time": "2018-08-06T14:22:27+00:00" + "time": "2018-09-21T13:07:52+00:00" }, { "name": "symfony/translation", @@ -802,70 +802,44 @@ "time": "2017-07-22T11:58:36+00:00" }, { - "name": "guzzle/guzzle", - "version": "v3.9.3", + "name": "guzzlehttp/guzzle", + "version": "6.3.3", "source": { "type": "git", - "url": "https://github.com/guzzle/guzzle3.git", - "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9" + "url": "https://github.com/guzzle/guzzle.git", + "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9", - "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba", "shasum": "" }, "require": { - "ext-curl": "*", - "php": ">=5.3.3", - "symfony/event-dispatcher": "~2.1" - }, - "replace": { - "guzzle/batch": "self.version", - "guzzle/cache": "self.version", - "guzzle/common": "self.version", - "guzzle/http": "self.version", - "guzzle/inflection": "self.version", - "guzzle/iterator": "self.version", - "guzzle/log": "self.version", - "guzzle/parser": "self.version", - "guzzle/plugin": "self.version", - "guzzle/plugin-async": "self.version", - "guzzle/plugin-backoff": "self.version", - "guzzle/plugin-cache": "self.version", - "guzzle/plugin-cookie": "self.version", - "guzzle/plugin-curlauth": "self.version", - "guzzle/plugin-error-response": "self.version", - "guzzle/plugin-history": "self.version", - "guzzle/plugin-log": "self.version", - "guzzle/plugin-md5": "self.version", - "guzzle/plugin-mock": "self.version", - "guzzle/plugin-oauth": "self.version", - "guzzle/service": "self.version", - "guzzle/stream": "self.version" + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.4", + "php": ">=5.5" }, "require-dev": { - "doctrine/cache": "~1.3", - "monolog/monolog": "~1.0", - "phpunit/phpunit": "3.7.*", - "psr/log": "~1.0", - "symfony/class-loader": "~2.1", - "zendframework/zend-cache": "2.*,<2.3", - "zendframework/zend-log": "2.*,<2.3" + "ext-curl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", + "psr/log": "^1.0" }, "suggest": { - "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." + "psr/log": "Required for using the Log middleware" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.9-dev" + "dev-master": "6.3-dev" } }, "autoload": { - "psr-0": { - "Guzzle": "src/", - "Guzzle\\Tests": "tests/" + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -877,13 +851,9 @@ "name": "Michael Dowling", "email": "mtdowling@gmail.com", "homepage": "https://github.com/mtdowling" - }, - { - "name": "Guzzle Community", - "homepage": "https://github.com/guzzle/guzzle/contributors" } ], - "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", + "description": "Guzzle is a PHP HTTP client library", "homepage": "http://guzzlephp.org/", "keywords": [ "client", @@ -894,8 +864,123 @@ "rest", "web service" ], - "abandoned": "guzzlehttp/guzzle", - "time": "2015-03-18T18:23:50+00:00" + "time": "2018-04-22T15:46:56+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2016-12-20T10:07:11+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "request", + "response", + "stream", + "uri", + "url" + ], + "time": "2017-03-20T17:10:46+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -1304,6 +1389,89 @@ "description": "Library for handling version information and constraints", "time": "2017-03-05T17:38:23+00:00" }, + { + "name": "php-coveralls/php-coveralls", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-coveralls/php-coveralls.git", + "reference": "3b00c229726f892bfdadeaf01ea430ffd04a939d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-coveralls/php-coveralls/zipball/3b00c229726f892bfdadeaf01ea430ffd04a939d", + "reference": "3b00c229726f892bfdadeaf01ea430ffd04a939d", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^6.0", + "php": "^5.5 || ^7.0", + "psr/log": "^1.0", + "symfony/config": "^2.1 || ^3.0 || ^4.0", + "symfony/console": "^2.1 || ^3.0 || ^4.0", + "symfony/stopwatch": "^2.0 || ^3.0 || ^4.0", + "symfony/yaml": "^2.0 || ^3.0 || ^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0" + }, + "suggest": { + "symfony/http-kernel": "Allows Symfony integration" + }, + "bin": [ + "bin/php-coveralls" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "psr-4": { + "PhpCoveralls\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kitamura Satoshi", + "email": "with.no.parachute@gmail.com", + "homepage": "https://www.facebook.com/satooshi.jp", + "role": "Original creator" + }, + { + "name": "Takashi Matsuo", + "email": "tmatsuo@google.com" + }, + { + "name": "Google Inc" + }, + { + "name": "Dariusz Ruminski", + "email": "dariusz.ruminski@gmail.com", + "homepage": "https://github.com/keradus" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-coveralls/php-coveralls/graphs/contributors" + } + ], + "description": "PHP client library for Coveralls API", + "homepage": "https://github.com/php-coveralls/php-coveralls", + "keywords": [ + "ci", + "coverage", + "github", + "test" + ], + "time": "2018-05-22T23:11:08+00:00" + }, { "name": "phpdocumentor/reflection-docblock", "version": "2.0.5", @@ -1858,6 +2026,56 @@ ], "time": "2018-01-21T07:42:36+00:00" }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06T14:39:51+00:00" + }, { "name": "psr/log", "version": "1.0.2", @@ -1968,68 +2186,6 @@ "abandoned": true, "time": "2018-07-02T13:20:39+00:00" }, - { - "name": "satooshi/php-coveralls", - "version": "v1.1.0", - "source": { - "type": "git", - "url": "https://github.com/php-coveralls/php-coveralls.git", - "reference": "37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-coveralls/php-coveralls/zipball/37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad", - "reference": "37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-simplexml": "*", - "guzzle/guzzle": "^2.8 || ^3.0", - "php": "^5.3.3 || ^7.0", - "psr/log": "^1.0", - "symfony/config": "^2.1 || ^3.0 || ^4.0", - "symfony/console": "^2.1 || ^3.0 || ^4.0", - "symfony/stopwatch": "^2.0 || ^3.0 || ^4.0", - "symfony/yaml": "^2.0 || ^3.0 || ^4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0" - }, - "suggest": { - "symfony/http-kernel": "Allows Symfony integration" - }, - "bin": [ - "bin/coveralls" - ], - "type": "library", - "autoload": { - "psr-4": { - "Satooshi\\": "src/Satooshi/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Kitamura Satoshi", - "email": "with.no.parachute@gmail.com", - "homepage": "https://www.facebook.com/satooshi.jp" - } - ], - "description": "PHP client library for Coveralls API", - "homepage": "https://github.com/php-coveralls/php-coveralls", - "keywords": [ - "ci", - "coverage", - "github", - "test" - ], - "abandoned": "php-coveralls/php-coveralls", - "time": "2017-12-06T23:17:56+00:00" - }, { "name": "sebastian/code-unit-reverse-lookup", "version": "1.0.1", @@ -2810,66 +2966,6 @@ "homepage": "https://symfony.com", "time": "2018-10-03T08:15:46+00:00" }, - { - "name": "symfony/event-dispatcher", - "version": "v2.8.46", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "84ae343f39947aa084426ed1138bb96bf94d1f12" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/84ae343f39947aa084426ed1138bb96bf94d1f12", - "reference": "84ae343f39947aa084426ed1138bb96bf94d1f12", - "shasum": "" - }, - "require": { - "php": ">=5.3.9" - }, - "require-dev": { - "psr/log": "~1.0", - "symfony/config": "^2.0.5|~3.0.0", - "symfony/dependency-injection": "~2.6|~3.0.0", - "symfony/expression-language": "~2.6|~3.0.0", - "symfony/stopwatch": "~2.3|~3.0.0" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.8-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony EventDispatcher Component", - "homepage": "https://symfony.com", - "time": "2018-07-26T09:03:18+00:00" - }, { "name": "symfony/filesystem", "version": "v4.1.6", @@ -2971,7 +3067,7 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.9.0", + "version": "v1.10.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", From 0d4811f458a6a85243041eed43a8b423d65ddaf6 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 5 Nov 2018 12:13:36 -0200 Subject: [PATCH 030/116] Suggest installing mongolid-laravel package --- composer.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/composer.json b/composer.json index 8f4a102b..30a0693c 100644 --- a/composer.json +++ b/composer.json @@ -49,5 +49,8 @@ }, "config": { "sort-packages": true + }, + "suggest": { + "leroy-merlin-br/mongolid-laravel": "Easy, powerful and ultrafast MongoDB ODM for Laravel." } } From 8c01f7629dc4e2218f9ddce39ab0e76de7c88d0f Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 5 Nov 2018 17:03:43 -0200 Subject: [PATCH 031/116] Update travis CI script and allow PHP 7.3 to run --- .travis.yml | 11 ++++++++++- phpunit.xml | 4 ++-- phpunit.xml.dist | 5 +++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 79869de9..3f9dd46b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,11 +3,16 @@ language: php php: - 7.1 - 7.2 + - 7.3 + +cache: + directories: + - $HOME/.composer/cache services: mongodb before_install: - - if [[ $TRAVIS_PHP_VERSION != 7.2 ]]; then pecl install mongodb; fi + - if [[ $(phpenv version-name) = "7.1" ]]; then pecl install mongodb; fi - echo "extension = mongodb.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini before_script: @@ -19,3 +24,7 @@ script: after_script: - php vendor/bin/coveralls -v + +matrix: + allow_failures: + - php: 7.3 diff --git a/phpunit.xml b/phpunit.xml index 2541f4c2..ae4a836b 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -13,10 +13,10 @@ > - tests/Unit + tests/Unit - tests/Integration + tests/Integration diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 8f79b652..d2c88298 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -13,10 +13,10 @@ > - tests/Unit + tests/Unit - tests/Integration + tests/Integration @@ -25,6 +25,7 @@ + From 336d3c09d7888ccf35ce607c68bff3a785615d00 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 7 Nov 2018 12:09:21 -0200 Subject: [PATCH 032/116] Remove get_class() and improve CursorInterface --- src/Cursor/Cursor.php | 14 +- src/Cursor/CursorFactory.php | 2 +- src/Cursor/CursorInterface.php | 12 +- src/Cursor/EmbeddedCursor.php | 17 +- src/DataMapper/DataMapper.php | 5 +- src/Model/ActiveRecord.php | 19 +- src/Model/Relations.php | 27 +-- tests/Unit/Cursor/EmbeddedCursorTest.php | 6 + tests/Unit/Model/ActiveRecordTest.php | 245 +++++++++-------------- tests/Unit/Model/RelationsTest.php | 15 +- tests/Unit/TestCase.php | 15 ++ 11 files changed, 165 insertions(+), 212 deletions(-) diff --git a/src/Cursor/Cursor.php b/src/Cursor/Cursor.php index 1f134827..23b50c33 100644 --- a/src/Cursor/Cursor.php +++ b/src/Cursor/Cursor.php @@ -93,9 +93,9 @@ public function __construct( * * @param int $amount the number of results to return * - * @return Cursor returns this cursor + * @return static */ - public function limit(int $amount) + public function limit(int $amount): CursorInterface { $this->params[1]['limit'] = $amount; @@ -109,9 +109,9 @@ public function limit(int $amount) * Each element in the array has as key the field name, * and as value either 1 for ascending sort, or -1 for descending sort. * - * @return Cursor returns this cursor + * @return static */ - public function sort(array $fields) + public function sort(array $fields): CursorInterface { $this->params[1]['sort'] = $fields; @@ -123,9 +123,9 @@ public function sort(array $fields) * * @param int $amount the number of results to skip * - * @return Cursor returns this cursor + * @return static */ - public function skip(int $amount) + public function skip(int $amount): CursorInterface { $this->params[1]['skip'] = $amount; @@ -138,7 +138,7 @@ public function skip(int $amount) * * @param bool $flag toggle timeout on or off * - * @return Cursor returns this cursor + * @return static */ public function disableTimeout(bool $flag = true) { diff --git a/src/Cursor/CursorFactory.php b/src/Cursor/CursorFactory.php index 4b30871c..665be628 100644 --- a/src/Cursor/CursorFactory.php +++ b/src/Cursor/CursorFactory.php @@ -24,7 +24,7 @@ public function createCursor( string $command, array $params, bool $cacheable = false - ): Cursor { + ): CursorInterface { $cursorClass = $cacheable ? CacheableCursor::class : Cursor::class; return new $cursorClass($entitySchema, $collection, $command, $params); diff --git a/src/Cursor/CursorInterface.php b/src/Cursor/CursorInterface.php index 14a64d7a..7ad34615 100644 --- a/src/Cursor/CursorInterface.php +++ b/src/Cursor/CursorInterface.php @@ -14,9 +14,9 @@ interface CursorInterface extends Countable, Iterator * * @param int $amount the number of results to return * - * @return CursorInterface returns this cursor + * @return static */ - public function limit(int $amount); + public function limit(int $amount): CursorInterface; /** * Sorts the results by given fields. @@ -25,18 +25,18 @@ public function limit(int $amount); * Each element in the array has as key the field name, * and as value either 1 for ascending sort, or -1 for descending sort. * - * @return CursorInterface returns this cursor + * @return static */ - public function sort(array $fields); + public function sort(array $fields): CursorInterface; /** * Skips a number of results. * * @param int $amount the number of results to skip * - * @return CursorInterface returns this cursor + * @return static */ - public function skip(int $amount); + public function skip(int $amount): CursorInterface; /** * Returns the first element of the cursor. diff --git a/src/Cursor/EmbeddedCursor.php b/src/Cursor/EmbeddedCursor.php index 34a5975d..3a72fd51 100644 --- a/src/Cursor/EmbeddedCursor.php +++ b/src/Cursor/EmbeddedCursor.php @@ -53,7 +53,7 @@ public function __construct(string $entityClass, array $items) * * @return EmbeddedCursor returns this cursor */ - public function limit(int $amount) + public function limit(int $amount): CursorInterface { $this->items = array_slice($this->items, 0, $amount); @@ -69,21 +69,16 @@ public function limit(int $amount) * * @return EmbeddedCursor returns this cursor */ - public function sort(array $fields) + public function sort(array $fields): CursorInterface { foreach (array_reverse($fields) as $key => $direction) { // Uses usort with a function that will access the $key and sort in - // the $direction. It mimics how the mongodb does sorting internally. + // the $direction. It mimics how MongoDB does sorting internally. usort( $this->items, function ($a, $b) use ($key, $direction) { - $a = is_object($a) - ? ($a->$key ?? null) - : ($a[$key] ?? null); - - $b = is_object($b) - ? ($b->$key ?? null) - : ($b[$key] ?? null); + $a = (is_object($a) ? $a->$key : $a[$key]) ?? null; + $b = (is_object($b) ? $b->$key : $b[$key]) ?? null; return ($a <=> $b) * $direction; } @@ -100,7 +95,7 @@ function ($a, $b) use ($key, $direction) { * * @return EmbeddedCursor returns this cursor */ - public function skip(int $amount) + public function skip(int $amount): CursorInterface { $this->items = array_slice($this->items, $amount); diff --git a/src/DataMapper/DataMapper.php b/src/DataMapper/DataMapper.php index a8106e7e..d1a97470 100644 --- a/src/DataMapper/DataMapper.php +++ b/src/DataMapper/DataMapper.php @@ -8,6 +8,7 @@ use Mongolid\Container\Ioc; use Mongolid\Cursor\CacheableCursor; use Mongolid\Cursor\Cursor; +use Mongolid\Cursor\CursorInterface; use Mongolid\Event\EventTriggerService; use Mongolid\Exception\ModelNotFoundException; use Mongolid\Model\AttributesAccessInterface; @@ -237,7 +238,7 @@ public function where( $query = [], array $projection = [], bool $cacheable = false - ): Cursor { + ): CursorInterface { $cursorClass = $cacheable ? CacheableCursor::class : Cursor::class; $cursor = new $cursorClass( @@ -257,7 +258,7 @@ public function where( * Retrieve a database cursor that will return all documents as * $this->schema->entityClass objects upon iteration. */ - public function all(): Cursor + public function all(): CursorInterface { return $this->where([]); } diff --git a/src/Model/ActiveRecord.php b/src/Model/ActiveRecord.php index 4c93044c..74708e22 100644 --- a/src/Model/ActiveRecord.php +++ b/src/Model/ActiveRecord.php @@ -4,6 +4,7 @@ use BadMethodCallException; use MongoDB\Driver\WriteConcern; use Mongolid\Container\Ioc; +use Mongolid\Cursor\CursorInterface; use Mongolid\DataMapper\DataMapper; use Mongolid\Exception\NoCollectionNameException; use Mongolid\ModelNotFoundException; @@ -108,14 +109,12 @@ public function delete() * @param array $query mongoDB selection criteria * @param array $projection fields to project in Mongo query * @param bool $useCache retrieves a CacheableCursor instead - * - * @return \Mongolid\Cursor\Cursor */ public static function where( array $query = [], array $projection = [], bool $useCache = false - ) { + ): CursorInterface { return self::getDataMapperInstance()->where( $query, $projection, @@ -125,10 +124,8 @@ public static function where( /** * Gets a cursor of this kind of entities from the database. - * - * @return \Mongolid\Cursor\Cursor */ - public static function all() + public static function all(): CursorInterface { return self::getDataMapperInstance()->all(); } @@ -230,7 +227,7 @@ public function __call($method, $parameters) throw new BadMethodCallException( sprintf( 'The following method can not be reached or does not exist: %s@%s', - get_class($this), + static::class, $method ) ); @@ -290,7 +287,7 @@ public function getSchema(): Schema } $schema = new DynamicSchema(); - $schema->entityClass = get_class($this); + $schema->entityClass = static::class; $schema->fields = $this->fields; $schema->dynamic = $this->dynamic; $schema->collection = $this->collection; @@ -341,12 +338,10 @@ protected function execute(string $action) * Returns the a valid instance from Ioc. * * @throws NoCollectionNameException Throws exception when has no collection filled - * - * @return mixed */ - private static function getDataMapperInstance() + private static function getDataMapperInstance(): DataMapper { - $instance = Ioc::make(get_called_class()); + $instance = new static(); if (!$instance->getCollectionName()) { throw new NoCollectionNameException(); diff --git a/src/Model/Relations.php b/src/Model/Relations.php index 8fa3b7df..8fb39423 100644 --- a/src/Model/Relations.php +++ b/src/Model/Relations.php @@ -4,6 +4,7 @@ use MongoDB\BSON\ObjectId; use Mongolid\Container\Ioc; use Mongolid\Cursor\CursorFactory; +use Mongolid\Cursor\CursorInterface; use Mongolid\Cursor\EmbeddedCursor; use Mongolid\DataMapper\DataMapper; use Mongolid\Schema\Schema; @@ -15,7 +16,7 @@ trait Relations { /** - * Returns the referenced documents as objects. + * Returns the referenced document as object. * * @param string $entity class of the entity or of the schema of the entity * @param string $field the field where the _id is stored @@ -44,15 +45,13 @@ protected function referencesOne(string $entity, string $field, bool $cacheable } /** - * Returns the cursor for the referenced documents as objects. + * Returns the cursor for the referenced document objects. * * @param string $entity class of the entity or of the schema of the entity * @param string $field the field where the _ids are stored * @param bool $cacheable retrieves a CacheableCursor instead - * - * @return array */ - protected function referencesMany(string $entity, string $field, bool $cacheable = true) + protected function referencesMany(string $entity, string $field, bool $cacheable = true): CursorInterface { $referencedIds = (array) $this->$field; @@ -77,30 +76,20 @@ protected function referencesMany(string $entity, string $field, bool $cacheable } /** - * Return a embedded documents as object. + * Return first embedded document as object. * * @param string $entity class of the entity or of the schema of the entity * @param string $field field where the embedded document is stored * - * @return Model|null + * @return mixed */ protected function embedsOne(string $entity, string $field) { - if (is_subclass_of($entity, Schema::class)) { - $entity = (new $entity())->entityClass; - } - - $items = (array) $this->$field; - if (false === empty($items) && false === array_key_exists(0, $items)) { - $items = [$items]; - } - - return Ioc::make(CursorFactory::class) - ->createEmbeddedCursor($entity, $items)->first(); + return $this->embedsMany($entity, $field)->first(); } /** - * Return array of embedded documents as objects. + * Return embedded documents cursor. * * @param string $entity class of the entity or of the schema of the entity * @param string $field field where the embedded documents are stored diff --git a/tests/Unit/Cursor/EmbeddedCursorTest.php b/tests/Unit/Cursor/EmbeddedCursorTest.php index d6c6e1f8..b302ac31 100644 --- a/tests/Unit/Cursor/EmbeddedCursorTest.php +++ b/tests/Unit/Cursor/EmbeddedCursorTest.php @@ -295,9 +295,11 @@ public function getDocumentsToSort() $age24, ['age' => 26, 'name' => 'Zizaco'], ['age' => 26, 'name' => 'John'], + [], ], 'parameters' => ['age' => 1], 'expected' => [ + [], $age24, ['age' => 25], ['age' => 26, 'name' => 'Abe'], @@ -312,6 +314,7 @@ public function getDocumentsToSort() $age24, ['age' => 26, 'name' => 'Zizaco'], ['age' => 26, 'name' => 'John'], + [], ], 'parameters' => ['age' => -1], 'expected' => [ @@ -320,6 +323,7 @@ public function getDocumentsToSort() ['age' => 26, 'name' => 'John'], ['age' => 25], $age24, + [], ], ], 'two sorting parameters' => [ @@ -329,9 +333,11 @@ public function getDocumentsToSort() $age24, ['age' => 26, 'name' => 'Zizaco'], ['age' => 26, 'name' => 'John'], + [], ], 'parameters' => ['age' => 1, 'name' => -1], 'expected' => [ + [], $age24, ['age' => 25], ['age' => 26, 'name' => 'Zizaco'], diff --git a/tests/Unit/Model/ActiveRecordTest.php b/tests/Unit/Model/ActiveRecordTest.php index a2a282c2..170e1f63 100644 --- a/tests/Unit/Model/ActiveRecordTest.php +++ b/tests/Unit/Model/ActiveRecordTest.php @@ -5,9 +5,10 @@ use Mockery as m; use MongoDB\BSON\ObjectID; use MongoDB\Driver\WriteConcern; -use Mongolid\Container\Ioc; -use Mongolid\DataMapper; +use Mongolid\Cursor\CursorInterface; +use Mongolid\DataMapper\DataMapper; use Mongolid\Exception\NoCollectionNameException; +use Mongolid\Schema\DynamicSchema; use Mongolid\Schema\Schema; use Mongolid\TestCase; use stdClass; @@ -25,7 +26,22 @@ class ActiveRecordTest extends TestCase protected function setUp() { parent::setUp(); - $this->entity = new class() extends ActiveRecord { + $this->entity = new class() extends ActiveRecord + { + /** + * {@inheritdoc} + */ + protected $collection = 'mongolid'; + + public function unsetCollection() + { + unset($this->collection); + } + + public function setFields($value) + { + $this->fields = $value; + } }; } @@ -56,7 +72,7 @@ public function testShouldHaveCorrectPropertiesByDefault() public function testShouldImplementModelTraits() { // Assert - $this->assertEquals( + $this->assertSame( [Attributes::class, Relations::class], array_keys(class_uses(ActiveRecord::class)) ); @@ -65,277 +81,217 @@ public function testShouldImplementModelTraits() public function testShouldSave() { // Arrage - $entity = m::mock(ActiveRecord::class.'[getDataMapper,getCollectionName,syncOriginalAttributes]'); - $dataMapper = m::mock(); + $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); // Act - $entity->expects() - ->getDataMapper() - ->andReturn($dataMapper); - - $entity->expects() - ->getCollectionName() - ->andReturn('mongolid'); - - $entity->expects() - ->syncOriginalAttributes(); + $dataMapper->expects() + ->setSchema(m::type(DynamicSchema::class)); $dataMapper->expects() - ->save($entity, ['writeConcern' => new WriteConcern(1)]) + ->save($this->entity, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); // Assert - $this->assertTrue($entity->save()); + $this->assertTrue($this->entity->save()); } public function testShouldInsert() { // Arrage - $entity = m::mock(ActiveRecord::class.'[getDataMapper,getCollectionName,syncOriginalAttributes]'); - $dataMapper = m::mock(); + $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); // Act - $entity->expects() - ->getDataMapper() - ->andReturn($dataMapper); - - $entity->expects() - ->getCollectionName() - ->andReturn('mongolid'); - - $entity->expects() - ->syncOriginalAttributes(); + $dataMapper->expects() + ->setSchema(m::type(DynamicSchema::class)); $dataMapper->expects() - ->insert($entity, ['writeConcern' => new WriteConcern(1)]) + ->insert($this->entity, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); // Assert - $this->assertTrue($entity->insert()); + $this->assertTrue($this->entity->insert()); } public function testShouldUpdate() { // Arrage - $entity = m::mock(ActiveRecord::class.'[getDataMapper,getCollectionName,syncOriginalAttributes]'); - $dataMapper = m::mock(); + $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); // Act - $entity->expects() - ->getDataMapper() - ->andReturn($dataMapper); - - $entity->expects() - ->getCollectionName() - ->andReturn('mongolid'); - - $entity->expects() - ->syncOriginalAttributes(); + $dataMapper->expects() + ->setSchema(m::type(DynamicSchema::class)); $dataMapper->expects() - ->update($entity, ['writeConcern' => new WriteConcern(1)]) + ->update($this->entity, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); // Assert - $this->assertTrue($entity->update()); + $this->assertTrue($this->entity->update()); } public function testShouldDelete() { // Arrage - $entity = m::mock(ActiveRecord::class.'[getDataMapper,getCollectionName]'); - $dataMapper = m::mock(); + $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); // Act - $entity->expects() - ->getDataMapper() - ->andReturn($dataMapper); - - $entity->expects() - ->getCollectionName() - ->andReturn('mongolid'); + $dataMapper->expects() + ->setSchema(m::type(DynamicSchema::class)); $dataMapper->expects() - ->delete($entity, ['writeConcern' => new WriteConcern(1)]) + ->delete($this->entity, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); // Assert - $this->assertTrue($entity->delete()); + $this->assertTrue($this->entity->delete()); } public function testSaveShouldReturnFalseIfCollectionIsNull() { + $this->entity->unsetCollection(); $this->assertFalse($this->entity->save()); } public function testUpdateShouldReturnFalseIfCollectionIsNull() { + $this->entity->unsetCollection(); $this->assertFalse($this->entity->update()); } public function testInsertShouldReturnFalseIfCollectionIsNull() { + $this->entity->unsetCollection(); $this->assertFalse($this->entity->insert()); } public function testDeleteShouldReturnFalseIfCollectionIsNull() { + $this->entity->unsetCollection(); $this->assertFalse($this->entity->delete()); } public function testShouldGetWithWhereQuery() { // Arrage - $entity = m::mock(ActiveRecord::class.'[getDataMapper]'); - $this->setProtected($entity, 'collection', 'mongolid'); $query = ['foo' => 'bar']; $projection = ['some', 'fields']; - $dataMapper = m::mock(); - $cursor = m::mock(); + $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); - // Act - Ioc::instance(get_class($entity), $entity); + $cursor = m::mock(CursorInterface::class); - $entity->expects() - ->getDataMapper() - ->andReturn($dataMapper); + // Act + $dataMapper->expects() + ->setSchema(m::type(DynamicSchema::class)); $dataMapper->expects() ->where($query, $projection, true) ->andReturn($cursor); // Assert - $this->assertEquals($cursor, $entity->where($query, $projection, true)); + $this->assertSame($cursor, $this->entity->where($query, $projection, true)); } public function testShouldGetAll() { // Arrage - $entity = m::mock(ActiveRecord::class.'[getDataMapper]'); - $this->setProtected($entity, 'collection', 'mongolid'); - $dataMapper = m::mock(); - $cursor = m::mock(); + $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); - // Act - Ioc::instance(get_class($entity), $entity); + $cursor = m::mock(CursorInterface::class); - $entity->expects() - ->getDataMapper() - ->andReturn($dataMapper); + // Act + $dataMapper->expects() + ->setSchema(m::type(DynamicSchema::class)); $dataMapper->expects() ->all() ->andReturn($cursor); // Assert - $this->assertEquals($cursor, $entity->all()); + $this->assertSame($cursor, $this->entity->all()); } public function testShouldGetFirstWithQuery() { // Arrage - $entity = m::mock(ActiveRecord::class.'[getDataMapper]'); - $this->setProtected($entity, 'collection', 'mongolid'); $query = ['foo' => 'bar']; $projection = ['some', 'fields']; - $dataMapper = m::mock(); + $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); // Act - Ioc::instance(get_class($entity), $entity); - - $entity->expects() - ->getDataMapper() - ->andReturn($dataMapper); + $dataMapper->expects() + ->setSchema(m::type(DynamicSchema::class)); $dataMapper->expects() ->first($query, $projection, true) - ->andReturn($entity); + ->andReturn($this->entity); // Assert - $this->assertEquals($entity, $entity->first($query, $projection, true)); + $this->assertSame($this->entity, $this->entity->first($query, $projection, true)); } public function testShouldGetFirstOrFail() { // Arrage - $entity = m::mock(ActiveRecord::class.'[getDataMapper]'); - $this->setProtected($entity, 'collection', 'mongolid'); + $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); $query = ['foo' => 'bar']; $projection = ['some', 'fields']; - $dataMapper = m::mock(); // Act - Ioc::instance(get_class($entity), $entity); - - $entity->expects() - ->getDataMapper() - ->andReturn($dataMapper); + $dataMapper->expects() + ->setSchema(m::type(DynamicSchema::class)); $dataMapper->expects() ->firstOrFail($query, $projection, true) - ->andReturn($entity); + ->andReturn($this->entity); // Assert - $this->assertEquals($entity, $entity->firstOrFail($query, $projection, true)); + $this->assertSame($this->entity, $this->entity->firstOrFail($query, $projection, true)); } public function testShouldGetFirstOrNewAndReturnExistingModel() { // Arrage - $entity = m::mock(ActiveRecord::class.'[getDataMapper]'); - $this->setProtected($entity, 'collection', 'mongolid'); + $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); $id = 123; - $dataMapper = m::mock(); // Act - Ioc::instance(get_class($entity), $entity); - - $entity->expects() - ->getDataMapper() - ->andReturn($dataMapper); + $dataMapper->expects() + ->setSchema(m::type(DynamicSchema::class)); $dataMapper->expects() ->first($id) - ->andReturn($entity); + ->andReturn($this->entity); // Assert - $this->assertEquals($entity, $entity->firstOrNew($id)); + $this->assertSame($this->entity, $this->entity->firstOrNew($id)); } public function testShouldGetFirstOrNewAndReturnNewModel() { // Arrage - $entity = m::mock(ActiveRecord::class.'[getDataMapper]'); - $this->setProtected($entity, 'collection', 'mongolid'); + $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); $id = 123; - $dataMapper = m::mock(); // Act - Ioc::instance(get_class($entity), $entity); - - $entity->expects() - ->getDataMapper() - ->andReturn($dataMapper); + $dataMapper->expects() + ->setSchema(m::type(DynamicSchema::class)); $dataMapper->expects() ->first($id) ->andReturn(null); // Assert - $this->assertNotEquals($entity, $entity->firstOrNew($id)); + $this->assertNotEquals($this->entity, $this->entity->firstOrNew($id)); } public function testShouldGetSchemaIfFieldsIsTheClassName() { // Arrage - $this->setProtected($this->entity, 'fields', 'MySchemaClass'); - $schema = m::mock(Schema::class); - - // Act - Ioc::instance('MySchemaClass', $schema); + $this->entity->setFields('MySchemaClass'); + $schema = $this->instance('MySchemaClass', m::mock(Schema::class)); // Assert - $this->assertEquals( + $this->assertSame( $schema, $this->entity->getSchema() ); @@ -345,15 +301,15 @@ public function testShouldGetSchemaIfFieldsDescribesSchemaFields() { // Arrage $fields = ['name' => 'string', 'age' => 'int']; - $this->setProtected($this->entity, 'fields', $fields); + $this->entity->setFields($fields); // Assert $result = $this->entity->getSchema(); $this->assertInstanceOf(Schema::class, $result); - $this->assertEquals($fields, $result->fields); - $this->assertEquals($this->entity->dynamic, $result->dynamic); - $this->assertEquals($this->entity->getCollectionName(), $result->collection); - $this->assertEquals(get_class($this->entity), $result->entityClass); + $this->assertSame($fields, $result->fields); + $this->assertSame($this->entity->dynamic, $result->dynamic); + $this->assertSame($this->entity->getCollectionName(), $result->collection); + $this->assertSame(get_class($this->entity), $result->entityClass); } public function testShouldGetDataMapper() @@ -371,8 +327,8 @@ public function testShouldGetDataMapper() // Assert $result = $this->callProtected($entity, 'getDataMapper'); - $this->assertInstanceOf(DataMapper\DataMapper::class, $result); - $this->assertEquals($schema, $result->getSchema()); + $this->assertInstanceOf(DataMapper::class, $result); + $this->assertSame($schema, $result->getSchema()); } public function testShouldRaiseExceptionWhenHasNoCollectionAndTryToCallAllFunction() @@ -404,19 +360,13 @@ public function testShouldRaiseExceptionWhenHasNoCollectionAndTryToCallWhereFunc public function testShouldGetCollectionName() { - $entity = new class() extends ActiveRecord { - /** - * @var {inheritdoc} - */ - protected $collection = 'collection_name'; - }; - - $this->assertEquals('collection_name', $entity->getCollectionName()); + $this->assertSame('mongolid', $this->entity->getCollectionName()); } public function testShouldAttachToAttribute() { - $entity = new class() extends ActiveRecord { + $entity = new class() extends ActiveRecord + { /** * @var {inheritdoc} */ @@ -432,12 +382,13 @@ public function class() $embedded->name = 'Course Class #1'; $entity->attachToCourseClass($embedded); - $this->assertEquals([$embedded->_id], $entity->courseClass); + $this->assertSame([$embedded->_id], $entity->courseClass); } public function testShouldEmbedToAttribute() { - $entity = new class() extends ActiveRecord { + $this->entity = new class() extends ActiveRecord + { /** * @var {inheritdoc} */ @@ -450,14 +401,15 @@ public function classes() }; $embedded = new stdClass(); $embedded->name = 'Course Class #1'; - $entity->embedToCourseClasses($embedded); + $this->entity->embedToCourseClasses($embedded); - $this->assertEquals('Course Class #1', $entity->classes()->first()->name); + $this->assertSame('Course Class #1', $this->entity->classes()->first()->name); } public function testShouldThrowBadMethodCallExceptionWhenCallingInvalidMethod() { - $entity = new class() extends ActiveRecord { + $this->entity = new class() extends ActiveRecord + { /** * @var {inheritdoc} */ @@ -466,14 +418,13 @@ public function testShouldThrowBadMethodCallExceptionWhenCallingInvalidMethod() $this->expectException(BadMethodCallException::class); - $entity->foobar(); + $this->entity->foobar(); } public function testShouldGetSetWriteConcernInActiveRecordClass() { - $this->assertEquals(1, $this->entity->getWriteConcern()); - $this->assertEquals(1, $this->entity->getWriteConcern()); + $this->assertSame(1, $this->entity->getWriteConcern()); $this->entity->setWriteConcern(0); - $this->assertEquals(0, $this->entity->getWriteConcern()); + $this->assertSame(0, $this->entity->getWriteConcern()); } } diff --git a/tests/Unit/Model/RelationsTest.php b/tests/Unit/Model/RelationsTest.php index fa8764f0..3267d4f2 100644 --- a/tests/Unit/Model/RelationsTest.php +++ b/tests/Unit/Model/RelationsTest.php @@ -4,8 +4,8 @@ use Mockery as m; use MongoDB\BSON\ObjectID; use Mongolid\Container\Ioc; -use Mongolid\Cursor\Cursor; use Mongolid\Cursor\CursorFactory; +use Mongolid\Cursor\CursorInterface; use Mongolid\Cursor\EmbeddedCursor; use Mongolid\DataMapper\DataMapper; use Mongolid\Schema\Schema; @@ -22,13 +22,14 @@ public function testShouldReferenceOne($entity, $field, $fieldValue, $useCache, $expectedQuery = $expectedQuery['referencesOne']; $model = m::mock(ActiveRecord::class.'[]'); $dataMapper = m::mock(DataMapper::class)->makePartial(); - $result = m::mock(); + $result = new class() extends Schema { + }; $model->$field = $fieldValue; // Act Ioc::instance(DataMapper::class, $dataMapper); - Ioc::instance('EntityClass', $entity); + Ioc::instance(get_class($entity), $entity); $dataMapper->expects() ->first(m::type('array'), [], $useCache) @@ -41,7 +42,7 @@ public function testShouldReferenceOne($entity, $field, $fieldValue, $useCache, // Assert $this->assertSame( $result, - $this->callProtected($model, 'referencesOne', ['EntityClass', $field, $useCache]) + $this->callProtected($model, 'referencesOne', [get_class($entity), $field, $useCache]) ); } @@ -54,13 +55,13 @@ public function testShouldReferenceMany($entity, $field, $fieldValue, $useCache, $expectedQuery = $expectedQuery['referencesMany']; $model = m::mock(ActiveRecord::class.'[]'); $dataMapper = m::mock(DataMapper::class)->makePartial(); - $result = m::mock(Cursor::class); + $result = m::mock(CursorInterface::class); $model->$field = $fieldValue; // Act Ioc::instance(DataMapper::class, $dataMapper); - Ioc::instance('EntityClass', $entity); + Ioc::instance(get_class($entity), $entity); $dataMapper->expects() ->where(m::type('array'), [], $useCache) @@ -73,7 +74,7 @@ public function testShouldReferenceMany($entity, $field, $fieldValue, $useCache, // Assert $this->assertSame( $result, - $this->callProtected($model, 'referencesMany', ['EntityClass', $field, $useCache]) + $this->callProtected($model, 'referencesMany', [get_class($entity), $field, $useCache]) ); } diff --git a/tests/Unit/TestCase.php b/tests/Unit/TestCase.php index 5a5a7506..f1202a9b 100644 --- a/tests/Unit/TestCase.php +++ b/tests/Unit/TestCase.php @@ -125,4 +125,19 @@ protected function getProtected($obj, $property) return $property->getValue($obj); } + + /** + * Replace instance on Ioc + */ + protected function instance($abstract, $instance) + { + Ioc::bind( + $abstract, + function () use ($instance) { + return $instance; + } + ); + + return $instance; + } } From f2e77e0a975e4bd72f1880fb2619523a5210d7f3 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 7 Nov 2018 12:27:07 -0200 Subject: [PATCH 033/116] Replace Ioc:instance() calls --- tests/Unit/Connection/ManagerTest.php | 7 +-- tests/Unit/Cursor/CacheableCursorTest.php | 17 ++----- tests/Unit/Cursor/CursorTest.php | 5 +- tests/Unit/DataMapper/BulkWriteTest.php | 5 +- tests/Unit/DataMapper/DataMapperTest.php | 8 ++- tests/Unit/DataMapper/EntityAssemblerTest.php | 8 +-- tests/Unit/DataMapper/SchemaMapperTest.php | 51 +++++++++---------- tests/Unit/Model/RelationsTest.php | 25 +++------ tests/Unit/Schema/SchemaTest.php | 9 +--- tests/Util/SetupConnectionTrait.php | 2 +- 10 files changed, 49 insertions(+), 88 deletions(-) diff --git a/tests/Unit/Connection/ManagerTest.php b/tests/Unit/Connection/ManagerTest.php index 83279282..611ebd32 100644 --- a/tests/Unit/Connection/ManagerTest.php +++ b/tests/Unit/Connection/ManagerTest.php @@ -4,7 +4,6 @@ use Illuminate\Container\Container; use Mockery as m; use MongoDB\Client; -use Mongolid\Container\Ioc; use Mongolid\DataMapper\DataMapper; use Mongolid\Event\EventTriggerInterface; use Mongolid\Event\EventTriggerService; @@ -82,17 +81,15 @@ public function testShouldGetDataMapperForEntitiesWithRegisteredSchemas() // Arrange $manager = new Manager(); $schema = m::mock(Schema::class); - $dataMapper = m::mock(DataMapper::class)->makePartial(); + $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)->makePartial()); $schema->entityClass = 'Bacon'; // Act - Ioc::instance(DataMapper::class, $dataMapper); - - // Assert $manager->registerSchema($schema); $result = $manager->getMapper('Bacon'); + // Assert $this->assertEquals($dataMapper, $result); $this->assertAttributeEquals($schema, 'schema', $result); } diff --git a/tests/Unit/Cursor/CacheableCursorTest.php b/tests/Unit/Cursor/CacheableCursorTest.php index 3941a789..22cdd596 100644 --- a/tests/Unit/Cursor/CacheableCursorTest.php +++ b/tests/Unit/Cursor/CacheableCursorTest.php @@ -6,7 +6,6 @@ use IteratorIterator; use Mockery as m; use MongoDB\Collection; -use Mongolid\Container\Ioc; use Mongolid\Schema\Schema; use Mongolid\TestCase; use Mongolid\Util\CacheComponentInterface; @@ -36,15 +35,13 @@ public function testShouldGetCursorFromCache() // Arrange $documentsFromCache = [['name' => 'joe'], ['name' => 'doe']]; $cursor = $this->getCachableCursor(); - $cacheComponent = m::mock(CacheComponentInterface::class); + $cacheComponent = $this->instance(CacheComponentInterface::class, m::mock(CacheComponentInterface::class)); // Act $cursor->expects() ->generateCacheKey() ->andReturn('find:collection:123'); - Ioc::instance(CacheComponentInterface::class, $cacheComponent); - $cacheComponent->expects() ->get('find:collection:123', null) ->andReturn($documentsFromCache); @@ -61,7 +58,7 @@ public function testShouldGetFromDatabaseWhenCacheFails() // Arrange $documentsFromDb = [['name' => 'joe'], ['name' => 'doe']]; $cursor = $this->getCachableCursor()->limit(150); - $cacheComponent = m::mock(CacheComponentInterface::class); + $cacheComponent = $this->instance(CacheComponentInterface::class, m::mock(CacheComponentInterface::class)); $rawCollection = m::mock(); $cacheKey = 'find:collection:123'; @@ -76,8 +73,6 @@ public function testShouldGetFromDatabaseWhenCacheFails() ->generateCacheKey() ->andReturn($cacheKey); - Ioc::instance(CacheComponentInterface::class, $cacheComponent); - $cacheComponent->expects() ->get($cacheKey, null) ->andThrow( @@ -105,7 +100,7 @@ public function testShouldGetCursorFromDatabaseAndCacheForLater() // Arrange $documentsFromDb = [['name' => 'joe'], ['name' => 'doe']]; $cursor = $this->getCachableCursor()->limit(150); - $cacheComponent = m::mock(CacheComponentInterface::class); + $cacheComponent = $this->instance(CacheComponentInterface::class, m::mock(CacheComponentInterface::class)); $rawCollection = m::mock(); $this->setProtected( @@ -119,8 +114,6 @@ public function testShouldGetCursorFromDatabaseAndCacheForLater() ->generateCacheKey() ->andReturn('find:collection:123'); - Ioc::instance(CacheComponentInterface::class, $cacheComponent); - $cacheComponent->expects() ->get('find:collection:123', null) ->andReturn(null); @@ -144,7 +137,7 @@ public function testShouldGetOriginalCursorFromDatabaseAfterTheDocumentLimit() // Arrange $documentsFromDb = [['name' => 'joe'], ['name' => 'doe']]; $cursor = $this->getCachableCursor()->limit(150); - $cacheComponent = m::mock(CacheComponentInterface::class); + $cacheComponent = $this->instance(CacheComponentInterface::class, m::mock(CacheComponentInterface::class)); $rawCollection = m::mock(); $this->setProtected( @@ -164,8 +157,6 @@ public function testShouldGetOriginalCursorFromDatabaseAfterTheDocumentLimit() ->generateCacheKey() ->never(); - Ioc::instance(CacheComponentInterface::class, $cacheComponent); - $cacheComponent->expects() ->get('find:collection:123', null) ->never(); diff --git a/tests/Unit/Cursor/CursorTest.php b/tests/Unit/Cursor/CursorTest.php index de587474..02553d1b 100644 --- a/tests/Unit/Cursor/CursorTest.php +++ b/tests/Unit/Cursor/CursorTest.php @@ -9,7 +9,6 @@ use MongoDB\Driver\Exception\LogicException; use MongoDB\Driver\ReadPreference; use Mongolid\Connection\Connection; -use Mongolid\Container\Ioc; use Mongolid\Model\ActiveRecord; use Mongolid\Schema\DynamicSchema; use Mongolid\Schema\Schema; @@ -416,7 +415,7 @@ public function testShouldReturnResultsToArray() public function testShouldSerializeAnActiveCursor() { // Arrange - $connection = m::mock(Connection::class); + $connection = $this->instance(Connection::class, m::mock(Connection::class)); $schema = new DynamicSchema(); $cursor = $this->getCursor($schema, null, 'find', [[]]); $driverCollection = $this->getDriverCollection(); @@ -424,8 +423,6 @@ public function testShouldSerializeAnActiveCursor() $this->setProtected($cursor, 'collection', $driverCollection); // Act - Ioc::instance(Connection::class, $connection); - $connection->expects() ->getRawConnection() ->andReturn($connection); diff --git a/tests/Unit/DataMapper/BulkWriteTest.php b/tests/Unit/DataMapper/BulkWriteTest.php index 777a0cb2..09d9789d 100644 --- a/tests/Unit/DataMapper/BulkWriteTest.php +++ b/tests/Unit/DataMapper/BulkWriteTest.php @@ -6,7 +6,6 @@ use MongoDB\Driver\Manager; use MongoDB\Driver\WriteConcern; use Mongolid\Connection\Connection; -use Mongolid\Container\Ioc; use Mongolid\Schema\HasSchemaInterface; use Mongolid\Schema\Schema; use Mongolid\TestCase; @@ -105,15 +104,13 @@ public function testShouldExecuteBulkWrite() $schema = m::mock(Schema::class); $entity->schema = $schema; $mongoBulkWrite = m::mock(new MongoBulkWrite()); - $connection = m::mock(Connection::class); + $connection = $this->instance(Connection::class, m::mock(Connection::class)); $manager = m::mock(new Manager()); $connection->defaultDatabase = 'foo'; $schema->collection = 'bar'; $namespace = 'foo.bar'; - Ioc::instance(Connection::class, $connection); - // Expect $entity->expects() ->getSchema() diff --git a/tests/Unit/DataMapper/DataMapperTest.php b/tests/Unit/DataMapper/DataMapperTest.php index b741eebd..ce87c12f 100644 --- a/tests/Unit/DataMapper/DataMapperTest.php +++ b/tests/Unit/DataMapper/DataMapperTest.php @@ -204,7 +204,7 @@ public function testShouldUpdate($entity, $writeConcern, $shouldFireEventAfter, $operationResult = m::mock(); $options = ['writeConcern' => new WriteConcern($writeConcern)]; - Ioc::instance( + $this->instance( Schema::class, new class() extends Schema { @@ -277,7 +277,7 @@ public function testShouldUpdateUnsettingFields() $operationResult = m::mock(); $options = ['writeConcern' => new WriteConcern(1)]; - Ioc::instance( + $this->instance( Schema::class, new class() extends Schema { @@ -828,9 +828,7 @@ public function testShouldGetSchemaMapper() $connection = m::mock(Connection::class); $dataMapper = new DataMapper($connection); $dataMapper->schemaClass = 'MySchema'; - $schema = m::mock(Schema::class); - - Ioc::instance('MySchema', $schema); + $schema = $this->instance('MySchema', m::mock(Schema::class)); // Act $result = $this->callProtected($dataMapper, 'getSchemaMapper'); diff --git a/tests/Unit/DataMapper/EntityAssemblerTest.php b/tests/Unit/DataMapper/EntityAssemblerTest.php index fee5f41f..168cd43d 100644 --- a/tests/Unit/DataMapper/EntityAssemblerTest.php +++ b/tests/Unit/DataMapper/EntityAssemblerTest.php @@ -3,7 +3,6 @@ use Mockery as m; use MongoDB\BSON\ObjectID; -use Mongolid\Container\Ioc; use Mongolid\Model\Attributes; use Mongolid\Model\AttributesAccessInterface; use Mongolid\Model\PolymorphableInterface; @@ -22,18 +21,15 @@ public function testShouldAssembleEntityForTheGivenSchema($inputValue, $availabl $entityAssembler = new EntityAssembler(); $schemas = []; foreach ($availableSchemas as $key => $value) { - $schemas[$key] = m::mock(Schema::class.'[]'); + $schemas[$key] = $this->instance($key, m::mock(Schema::class.'[]')); $schemas[$key]->entityClass = $value['entityClass']; $schemas[$key]->fields = $value['fields']; } // Act - foreach ($schemas as $className => $instance) { - Ioc::instance($className, $instance); - } + $result = $entityAssembler->assemble($inputValue, $schemas[$inputSchema]); // Assert - $result = $entityAssembler->assemble($inputValue, $schemas[$inputSchema]); $this->assertEquals($expectedOutput, $result); } diff --git a/tests/Unit/DataMapper/SchemaMapperTest.php b/tests/Unit/DataMapper/SchemaMapperTest.php index 1bd84c07..9dbc2cd7 100644 --- a/tests/Unit/DataMapper/SchemaMapperTest.php +++ b/tests/Unit/DataMapper/SchemaMapperTest.php @@ -14,20 +14,21 @@ class SchemaMapperTest extends TestCase public function testShouldMapToFieldsOfSchema() { // Arrange - $myOwnSchema = new class() extends Schema - { - /** - * {@inheritdoc} - */ - public $dynamic = true; - - /** - * {@inheritdoc} - */ - public $fields = []; - }; - - Ioc::instance('My\Own\Schema', $myOwnSchema); + $this->instance( + 'My\Own\Schema', + new class() extends Schema + { + /** + * {@inheritdoc} + */ + public $dynamic = true; + + /** + * {@inheritdoc} + */ + public $fields = []; + } + ); $schema = new class() extends Schema { @@ -211,16 +212,16 @@ public function testShouldMapAnArrayValueToAnotherSchema() $schema = new class extends Schema { }; - $mySchema = new class extends Schema - { - }; + $mySchema = $this->instance( + 'Xd\MySchema', + new class extends Schema + { + } + ); $schemaMapper = new SchemaMapper($schema); $value = ['foo' => 'bar']; $test = $this; - // Act - Ioc::instance('Xd\MySchema', $mySchema); - // When instantiating the SchemaMapper with the specified $param as dependency Ioc::bind( SchemaMapper::class, @@ -240,13 +241,11 @@ function ($container, $params) use ($value, $mySchema, $test) { } ); + // Act + $result = $this->callProtected($schemaMapper, 'mapToSchema', [$value, 'Xd\MySchema']); + // Assert - $this->assertEquals( - [ - ['foo' => 'PARSED'], - ], - $this->callProtected($schemaMapper, 'mapToSchema', [$value, 'Xd\MySchema']) - ); + $this->assertEquals([['foo' => 'PARSED']], $result); } public function testShouldParseToArrayGettingObjectAttributes() diff --git a/tests/Unit/Model/RelationsTest.php b/tests/Unit/Model/RelationsTest.php index 3267d4f2..67664081 100644 --- a/tests/Unit/Model/RelationsTest.php +++ b/tests/Unit/Model/RelationsTest.php @@ -3,7 +3,6 @@ use Mockery as m; use MongoDB\BSON\ObjectID; -use Mongolid\Container\Ioc; use Mongolid\Cursor\CursorFactory; use Mongolid\Cursor\CursorInterface; use Mongolid\Cursor\EmbeddedCursor; @@ -21,16 +20,15 @@ public function testShouldReferenceOne($entity, $field, $fieldValue, $useCache, // Set $expectedQuery = $expectedQuery['referencesOne']; $model = m::mock(ActiveRecord::class.'[]'); - $dataMapper = m::mock(DataMapper::class)->makePartial(); + $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)->makePartial()); $result = new class() extends Schema { }; $model->$field = $fieldValue; - // Act - Ioc::instance(DataMapper::class, $dataMapper); - Ioc::instance(get_class($entity), $entity); + $this->instance(get_class($entity), $entity); + // Act $dataMapper->expects() ->first(m::type('array'), [], $useCache) ->andReturnUsing(function ($query) use ($result, $expectedQuery) { @@ -54,14 +52,13 @@ public function testShouldReferenceMany($entity, $field, $fieldValue, $useCache, // Set $expectedQuery = $expectedQuery['referencesMany']; $model = m::mock(ActiveRecord::class.'[]'); - $dataMapper = m::mock(DataMapper::class)->makePartial(); + $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)->makePartial()); $result = m::mock(CursorInterface::class); $model->$field = $fieldValue; // Act - Ioc::instance(DataMapper::class, $dataMapper); - Ioc::instance(get_class($entity), $entity); + $this->instance(get_class($entity), $entity); $dataMapper->expects() ->where(m::type('array'), [], $useCache) @@ -85,7 +82,7 @@ public function testShouldEmbedsOne($entity, $field, $fieldValue, $expectedItems { // Set $model = m::mock(ActiveRecord::class.'[]'); - $cursorFactory = m::mock(CursorFactory::class); + $cursorFactory = $this->instance(CursorFactory::class, m::mock(CursorFactory::class)); $cursor = m::mock(EmbeddedCursor::class); $document = $fieldValue; $model->$field = $document; @@ -93,8 +90,6 @@ public function testShouldEmbedsOne($entity, $field, $fieldValue, $expectedItems $instantiableClass = $entity instanceof Schema ? 'stdClass' : get_class($entity); // Act - Ioc::instance(CursorFactory::class, $cursorFactory); - $cursorFactory->expects() ->createEmbeddedCursor($instantiableClass, $expectedItems) ->andReturn($cursor); @@ -115,7 +110,7 @@ public function testShouldEmbedsMany($entity, $field, $fieldValue, $expectedItem { // Set $model = m::mock(ActiveRecord::class.'[]'); - $cursorFactory = m::mock(CursorFactory::class); + $cursorFactory = $this->instance(CursorFactory::class, m::mock(CursorFactory::class)); $cursor = m::mock(EmbeddedCursor::class); $document = $fieldValue; $model->$field = $document; @@ -123,8 +118,6 @@ public function testShouldEmbedsMany($entity, $field, $fieldValue, $expectedItem $instantiableClass = $entity instanceof Schema ? 'stdClass' : get_class($entity); // Act - Ioc::instance(CursorFactory::class, $cursorFactory); - $cursorFactory->expects() ->createEmbeddedCursor($instantiableClass, $expectedItems) ->andReturn($cursor); @@ -144,11 +137,9 @@ public function testShouldEmbeddedUnembedAttachAndDetachDocuments($method) use Relations; }; $document = m::mock(); - $documentEmbedder = m::mock(DocumentEmbedder::class); + $documentEmbedder = $this->instance(DocumentEmbedder::class, m::mock(DocumentEmbedder::class)); // Act - Ioc::instance(DocumentEmbedder::class, $documentEmbedder); - $documentEmbedder->expects() ->$method($model, 'foo', $document); diff --git a/tests/Unit/Schema/SchemaTest.php b/tests/Unit/Schema/SchemaTest.php index 6588a470..5bcbfb60 100644 --- a/tests/Unit/Schema/SchemaTest.php +++ b/tests/Unit/Schema/SchemaTest.php @@ -4,7 +4,6 @@ use Mockery as m; use MongoDB\BSON\ObjectID; use MongoDB\BSON\UTCDateTime; -use Mongolid\Container\Ioc; use Mongolid\TestCase; use Mongolid\Util\SequenceService; @@ -76,14 +75,12 @@ public function testShouldCastNullIntoAutoIncrementSequence() { // Arrange $schema = m::mock(Schema::class.'[]'); - $sequenceService = m::mock(SequenceService::class); + $sequenceService = $this->instance(SequenceService::class, m::mock(SequenceService::class)); $value = null; $schema->collection = 'resources'; // Act - Ioc::instance(SequenceService::class, $sequenceService); - $sequenceService->expects() ->getNextValue('resources') ->andReturn(7); @@ -95,14 +92,12 @@ public function testShouldCastNullIntoAutoIncrementSequence() public function testShouldNotAutoIncrementSequenceIfValueIsNotNull() { $schema = m::mock(Schema::class.'[]'); - $sequenceService = m::mock(SequenceService::class); + $sequenceService = $this->instance(SequenceService::class, m::mock(SequenceService::class)); $value = 3; $schema->collection = 'resources'; // Act - Ioc::instance(SequenceService::class, $sequenceService); - $sequenceService->expects() ->getNextValue('resources') ->never() diff --git a/tests/Util/SetupConnectionTrait.php b/tests/Util/SetupConnectionTrait.php index 68faded3..ee8ec2cd 100644 --- a/tests/Util/SetupConnectionTrait.php +++ b/tests/Util/SetupConnectionTrait.php @@ -20,7 +20,7 @@ function () use ($host, $database) { } ); - Ioc::instance( + $this->instance( CacheComponentInterface::class, new CacheComponent() ); From adf459c0ccd38ab5c9a6355b3cf670eebbfa593d Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 7 Nov 2018 12:31:37 -0200 Subject: [PATCH 034/116] Simplify protected helpers --- tests/Unit/DataMapper/DataMapperTest.php | 2 +- tests/Unit/TestCase.php | 18 ------------------ 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/tests/Unit/DataMapper/DataMapperTest.php b/tests/Unit/DataMapper/DataMapperTest.php index ce87c12f..caf066b9 100644 --- a/tests/Unit/DataMapper/DataMapperTest.php +++ b/tests/Unit/DataMapper/DataMapperTest.php @@ -812,7 +812,7 @@ public function testShouldParseObjectToDocumentAndPutResultingIdIntoTheGivenObje ->andReturn($parsedDocument); // Act - $result = $this->callProtected($dataMapper, 'parseToDocument', $entity); + $result = $this->callProtected($dataMapper, 'parseToDocument', [$entity]); // Assert $this->assertSame($parsedDocument, $result); diff --git a/tests/Unit/TestCase.php b/tests/Unit/TestCase.php index f1202a9b..032e5626 100644 --- a/tests/Unit/TestCase.php +++ b/tests/Unit/TestCase.php @@ -74,12 +74,6 @@ protected function callProtected($obj, $method, $args = []) $methodObj = new ReflectionMethod(get_class($obj), $method); $methodObj->setAccessible(true); - if (is_object($args)) { - $args = [$args]; - } else { - $args = (array) $args; - } - return $methodObj->invokeArgs($obj, $args); } @@ -95,13 +89,6 @@ protected function setProtected($obj, $property, $value) $class = new ReflectionClass($obj); $property = $class->getProperty($property); $property->setAccessible(true); - - if (is_string($obj)) { // static - $property->setValue($value); - - return; - } - $property->setValue($obj, $value); } @@ -118,11 +105,6 @@ protected function getProtected($obj, $property) $class = new ReflectionClass($obj); $property = $class->getProperty($property); $property->setAccessible(true); - - if (is_string($obj)) { // static - return $property->getValue(); - } - return $property->getValue($obj); } From a5b9d603db93cd85dd1ee18bbe3e868b93b613ec Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 7 Nov 2018 12:50:06 -0200 Subject: [PATCH 035/116] Replace self::class with static::class and makeWith with make --- src/Cursor/EmbeddedCursor.php | 2 +- src/DataMapper/DataMapper.php | 2 +- src/DataMapper/EntityAssembler.php | 2 +- src/DataMapper/SchemaMapper.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Cursor/EmbeddedCursor.php b/src/Cursor/EmbeddedCursor.php index 3a72fd51..b83cefd5 100644 --- a/src/Cursor/EmbeddedCursor.php +++ b/src/Cursor/EmbeddedCursor.php @@ -139,7 +139,7 @@ public function current() } $schema = $this->getSchemaForEntity(); - $entityAssembler = Ioc::makeWith(EntityAssembler::class, compact('schema')); + $entityAssembler = Ioc::make(EntityAssembler::class, compact('schema')); return $entityAssembler->assemble($document, $schema); } diff --git a/src/DataMapper/DataMapper.php b/src/DataMapper/DataMapper.php index d1a97470..50928a7a 100644 --- a/src/DataMapper/DataMapper.php +++ b/src/DataMapper/DataMapper.php @@ -372,7 +372,7 @@ protected function getSchemaMapper() $this->schema = Ioc::make($this->schemaClass); } - return Ioc::makeWith(SchemaMapper::class, ['schema' => $this->schema]); + return Ioc::make(SchemaMapper::class, ['schema' => $this->schema]); } /** diff --git a/src/DataMapper/EntityAssembler.php b/src/DataMapper/EntityAssembler.php index 085bae6f..da9d9f75 100644 --- a/src/DataMapper/EntityAssembler.php +++ b/src/DataMapper/EntityAssembler.php @@ -99,7 +99,7 @@ protected function assembleDocumentsRecursively($value, string $schemaClass) } $schema = Ioc::make($schemaClass); - $assembler = Ioc::make(self::class); + $assembler = Ioc::make(static::class); if (!isset($value[0])) { $value = [$value]; diff --git a/src/DataMapper/SchemaMapper.php b/src/DataMapper/SchemaMapper.php index 5d9b8893..ad8294e7 100644 --- a/src/DataMapper/SchemaMapper.php +++ b/src/DataMapper/SchemaMapper.php @@ -140,7 +140,7 @@ protected function mapToSchema($value, string $schemaClass) { $value = (array) $value; $schema = Ioc::make($schemaClass); - $mapper = Ioc::makeWith(self::class, compact('schema')); + $mapper = Ioc::make(static::class, compact('schema')); if (!isset($value[0])) { $value = [$value]; From 1b9aa8276e7d36e75e6f33c0cab4f5be7c5d1d06 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 7 Nov 2018 15:44:15 -0200 Subject: [PATCH 036/116] Revert change that is breaking on CI --- src/Cursor/EmbeddedCursor.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Cursor/EmbeddedCursor.php b/src/Cursor/EmbeddedCursor.php index b83cefd5..9c7f5f73 100644 --- a/src/Cursor/EmbeddedCursor.php +++ b/src/Cursor/EmbeddedCursor.php @@ -77,8 +77,13 @@ public function sort(array $fields): CursorInterface usort( $this->items, function ($a, $b) use ($key, $direction) { - $a = (is_object($a) ? $a->$key : $a[$key]) ?? null; - $b = (is_object($b) ? $b->$key : $b[$key]) ?? null; + $a = is_object($a) + ? ($a->$key ?? null) + : ($a[$key] ?? null); + + $b = is_object($b) + ? ($b->$key ?? null) + : ($b[$key] ?? null); return ($a <=> $b) * $direction; } From 6129370129376e31541f89599c5f2bb4669b0322 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 7 Nov 2018 17:52:35 -0200 Subject: [PATCH 037/116] Change document attributes based on team feedback --- src/DataMapper/DataMapper.php | 4 +- src/DataMapper/EntityAssembler.php | 2 +- src/DataMapper/SchemaMapper.php | 2 +- src/Model/ActiveRecord.php | 2 +- src/Model/Attributes.php | 46 +++++++++---------- src/Model/AttributesAccessInterface.php | 12 ++--- src/Model/DocumentEmbedder.php | 2 +- tests/Unit/Cursor/EmbeddedCursorTest.php | 2 +- tests/Unit/DataMapper/DataMapperTest.php | 2 +- tests/Unit/DataMapper/EntityAssemblerTest.php | 2 +- tests/Unit/DataMapper/SchemaMapperTest.php | 12 ++--- tests/Unit/Model/AttributesTest.php | 36 +++++++-------- tests/Unit/Model/DocumentEmbedderTest.php | 4 +- 13 files changed, 64 insertions(+), 64 deletions(-) diff --git a/src/DataMapper/DataMapper.php b/src/DataMapper/DataMapper.php index 50928a7a..c1d37a3f 100644 --- a/src/DataMapper/DataMapper.php +++ b/src/DataMapper/DataMapper.php @@ -592,7 +592,7 @@ private function mergeOptions(array $defaultOptions = [], array $toMergeOptions private function afterSuccess($entity) { if ($entity instanceof AttributesAccessInterface) { - $entity->syncOriginalAttributes(); + $entity->syncOriginalDocumentAttributes(); } } @@ -603,7 +603,7 @@ private function getUpdateData($entity, array $data) } $changes = []; - $this->calculateChanges($changes, $data, $entity->originalAttributes()); + $this->calculateChanges($changes, $data, $entity->getOriginalDocumentAttributes()); return $changes; } diff --git a/src/DataMapper/EntityAssembler.php b/src/DataMapper/EntityAssembler.php index da9d9f75..bc29989e 100644 --- a/src/DataMapper/EntityAssembler.php +++ b/src/DataMapper/EntityAssembler.php @@ -76,7 +76,7 @@ protected function morphingTime($entity) protected function prepareOriginalAttributes($entity) { if ($entity instanceof AttributesAccessInterface) { - $entity->syncOriginalAttributes(); + $entity->syncOriginalDocumentAttributes(); } return $entity; diff --git a/src/DataMapper/SchemaMapper.php b/src/DataMapper/SchemaMapper.php index ad8294e7..15a0d097 100644 --- a/src/DataMapper/SchemaMapper.php +++ b/src/DataMapper/SchemaMapper.php @@ -164,7 +164,7 @@ protected function parseToArray($object): array { if (!is_array($object)) { $attributes = $object instanceof AttributesAccessInterface - ? $object->attributes() + ? $object->getDocumentAttributes() : get_object_vars($object); return $attributes; diff --git a/src/Model/ActiveRecord.php b/src/Model/ActiveRecord.php index 74708e22..f2224df9 100644 --- a/src/Model/ActiveRecord.php +++ b/src/Model/ActiveRecord.php @@ -328,7 +328,7 @@ protected function execute(string $action) ]; if ($result = $this->getDataMapper()->$action($this, $options)) { - $this->syncOriginalAttributes(); + $this->syncOriginalDocumentAttributes(); } return $result; diff --git a/src/Model/Attributes.php b/src/Model/Attributes.php index 3c345519..4e94c1e9 100644 --- a/src/Model/Attributes.php +++ b/src/Model/Attributes.php @@ -18,19 +18,19 @@ trait Attributes * * @var array */ - private $_mongolid_attributes = []; + private $attributes = []; /** * The model attribute's original state. * * @var array */ - private $_mongolid_original_attributes = []; + private $originalAttributes = []; /** * Once you put at least one string in this array, only * the attributes specified here will be changed - * with the setAttributes method. + * with the setDocumentAttributes method. * * @var array */ @@ -67,7 +67,7 @@ trait Attributes * * @return mixed */ - public function getAttribute(string $key) + public function getDocumentAttribute(string $key) { return $this->{$key}; } @@ -75,9 +75,9 @@ public function getAttribute(string $key) /** * Get all attributes from the model. */ - public function attributes(): array + public function getDocumentAttributes(): array { - return $this->_mongolid_attributes; + return $this->attributes; } /** @@ -91,7 +91,7 @@ public function fill(array $input, bool $force = false) foreach ($input as $key => $value) { if ($force || ((!$this->fillable || in_array($key, $this->fillable)) && !in_array($key, $this->guarded))) { - $this->setAttribute($key, $value); + $this->setDocumentAttribute($key, $value); } } } @@ -101,9 +101,9 @@ public function fill(array $input, bool $force = false) * * @param string $key name of the attribute to be unset */ - public function cleanAttribute(string $key) + public function cleanDocumentAttribute(string $key) { - unset($this->_mongolid_attributes[$key]); + unset($this->attributes[$key]); } /** @@ -112,9 +112,9 @@ public function cleanAttribute(string $key) * @param string $key name of the attribute to be set * @param mixed $value value to be set */ - public function setAttribute(string $key, $value) + public function setDocumentAttribute(string $key, $value) { - $this->_mongolid_attributes[$key] = $value; + $this->attributes[$key] = $value; } /** @@ -127,21 +127,21 @@ public function setAttribute(string $key, $value) * Ideally should be called once right after retrieving data from * the database. */ - public function syncOriginalAttributes() + public function syncOriginalDocumentAttributes() { try { - $this->_mongolid_original_attributes = unserialize(serialize($this->attributes())); + $this->originalAttributes = unserialize(serialize($this->getDocumentAttributes())); } catch (Exception $e) { - $this->_mongolid_original_attributes = $this->attributes(); + $this->originalAttributes = $this->getDocumentAttributes(); } } /** * Retrieve original attributes. */ - public function originalAttributes(): array + public function getOriginalDocumentAttributes(): array { - return $this->_mongolid_original_attributes; + return $this->originalAttributes; } /** @@ -151,7 +151,7 @@ public function originalAttributes(): array */ public function toArray(): array { - return $this->attributes(); + return $this->getDocumentAttributes(); } /** @@ -169,11 +169,11 @@ public function &__get($key) return $this->mutableCache[$key]; } - if (!array_key_exists($key, $this->_mongolid_attributes)) { - $this->_mongolid_attributes[$key] = null; + if (!array_key_exists($key, $this->attributes)) { + $this->attributes[$key] = null; } - return $this->_mongolid_attributes[$key]; + return $this->attributes[$key]; } /** @@ -188,7 +188,7 @@ public function __set($key, $value) $value = $this->{$this->buildMutatorMethod($key, 'set')}($value); } - $this->setAttribute($key, $value); + $this->setDocumentAttribute($key, $value); } /** @@ -210,7 +210,7 @@ public function __isset($key) */ public function __unset($key) { - $this->cleanAttribute($key); + $this->cleanDocumentAttribute($key); } /** @@ -238,6 +238,6 @@ protected function hasMutatorMethod($key, $prefix) */ protected function buildMutatorMethod($key, $prefix) { - return $prefix.ucfirst($key).'Attribute'; + return $prefix.ucfirst($key).'DocumentAttribute'; } } diff --git a/src/Model/AttributesAccessInterface.php b/src/Model/AttributesAccessInterface.php index 2362f37b..876cd14a 100644 --- a/src/Model/AttributesAccessInterface.php +++ b/src/Model/AttributesAccessInterface.php @@ -18,12 +18,12 @@ interface AttributesAccessInterface * * @return mixed */ - public function getAttribute(string $key); + public function getDocumentAttribute(string $key); /** * Get all attributes from the model. */ - public function attributes(): array; + public function getDocumentAttributes(): array; /** * Set the model attributes using an array. @@ -38,7 +38,7 @@ public function fill(array $input, bool $force = false); * * @param string $key name of the attribute to be unset */ - public function cleanAttribute(string $key); + public function cleanDocumentAttribute(string $key); /** * Set a given attribute on the model. @@ -46,7 +46,7 @@ public function cleanAttribute(string $key); * @param string $key name of the attribute to be set * @param mixed $value value to be set */ - public function setAttribute(string $key, $value); + public function setDocumentAttribute(string $key, $value); /** * Stores original attributes from actual data from attributes @@ -55,10 +55,10 @@ public function setAttribute(string $key, $value); * Ideally should be called once right after retrieving data from * the database. */ - public function syncOriginalAttributes(); + public function syncOriginalDocumentAttributes(); /** * Retrieve original attributes. */ - public function originalAttributes(): array; + public function getOriginalDocumentAttributes(): array; } diff --git a/src/Model/DocumentEmbedder.php b/src/Model/DocumentEmbedder.php index 79863ae5..930036e0 100644 --- a/src/Model/DocumentEmbedder.php +++ b/src/Model/DocumentEmbedder.php @@ -4,7 +4,7 @@ use MongoDB\BSON\ObjectId; /** - * Document embeder is a service that will embed documents within each other. + * Document embedder is a service that will embed documents within each other. */ class DocumentEmbedder { diff --git a/tests/Unit/Cursor/EmbeddedCursorTest.php b/tests/Unit/Cursor/EmbeddedCursorTest.php index b302ac31..e35fb795 100644 --- a/tests/Unit/Cursor/EmbeddedCursorTest.php +++ b/tests/Unit/Cursor/EmbeddedCursorTest.php @@ -164,7 +164,7 @@ public function polymorph() }; $class = get_class($object); - $items = [$object->attributes()]; + $items = [$object->getDocumentAttributes()]; $cursor = $this->getCursor($class, $items); $this->setProtected($cursor, 'position', 0); diff --git a/tests/Unit/DataMapper/DataMapperTest.php b/tests/Unit/DataMapper/DataMapperTest.php index caf066b9..81b6b5a4 100644 --- a/tests/Unit/DataMapper/DataMapperTest.php +++ b/tests/Unit/DataMapper/DataMapperTest.php @@ -294,7 +294,7 @@ public function testShouldUpdateUnsettingFields() $entity->unchanged = 'unchanged'; $entity->notOnSchema = 'to be deleted'; $entity->name = 'John'; - $entity->syncOriginalAttributes(); + $entity->syncOriginalDocumentAttributes(); $entity->_id = 123; unset($entity->name); diff --git a/tests/Unit/DataMapper/EntityAssemblerTest.php b/tests/Unit/DataMapper/EntityAssemblerTest.php index 168cd43d..309cdcef 100644 --- a/tests/Unit/DataMapper/EntityAssemblerTest.php +++ b/tests/Unit/DataMapper/EntityAssemblerTest.php @@ -261,7 +261,7 @@ public function __construct($attr = []) $this->$key = $value; } - $this->syncOriginalAttributes(); + $this->syncOriginalDocumentAttributes(); } } diff --git a/tests/Unit/DataMapper/SchemaMapperTest.php b/tests/Unit/DataMapper/SchemaMapperTest.php index 9dbc2cd7..1b58c87c 100644 --- a/tests/Unit/DataMapper/SchemaMapperTest.php +++ b/tests/Unit/DataMapper/SchemaMapperTest.php @@ -289,11 +289,11 @@ public function testShouldGetAttributesWhenObjectImplementsAttributesAccessInter $schemaMapper = new SchemaMapper($schema); $object = new class implements AttributesAccessInterface { - public function getAttribute(string $key) + public function getDocumentAttribute(string $key) { } - public function attributes(): array + public function getDocumentAttributes(): array { return ['foo' => 'bar']; } @@ -302,19 +302,19 @@ public function fill(array $input, bool $force = false) { } - public function cleanAttribute(string $key) + public function cleanDocumentAttribute(string $key) { } - public function setAttribute(string $key, $value) + public function setDocumentAttribute(string $key, $value) { } - public function syncOriginalAttributes() + public function syncOriginalDocumentAttributes() { } - public function originalAttributes(): array + public function getOriginalDocumentAttributes(): array { } }; diff --git a/tests/Unit/Model/AttributesTest.php b/tests/Unit/Model/AttributesTest.php index cc9f93a0..27ac952b 100644 --- a/tests/Unit/Model/AttributesTest.php +++ b/tests/Unit/Model/AttributesTest.php @@ -26,7 +26,7 @@ public function testShouldHaveDynamicSetters() 'age' => 25, 'child' => $childObj, ], - $model->attributes() + $model->getDocumentAttributes() ); } @@ -46,7 +46,7 @@ public function testShouldHaveDynamicGetters() public function __construct(array $attributes) { - $this->_mongolid_attributes = $attributes; + $this->attributes = $attributes; } }; @@ -66,7 +66,7 @@ public function testShouldCheckIfAttributeIsSet() public function __construct(array $attributes) { - $this->_mongolid_attributes = $attributes; + $this->attributes = $attributes; } }; @@ -88,7 +88,7 @@ public function __construct() $this->mutable = true; } - public function getNameAttribute() + public function getNameDocumentAttribute() { return 'John'; } @@ -108,7 +108,7 @@ public function testShouldUnsetAttributes() public function __construct() { - $this->_mongolid_attributes = [ + $this->attributes = [ 'name' => 'John', 'age' => 25, ]; @@ -117,7 +117,7 @@ public function __construct() // Act unset($model->age); - $result = $model->attributes(); + $result = $model->getDocumentAttributes(); // Assert $this->assertSame(['name' => 'John'], $result); @@ -135,7 +135,7 @@ public function __construct() $this->mutable = true; } - public function getSomeAttribute() + public function getSomeDocumentAttribute() { return 'something-else'; } @@ -145,7 +145,7 @@ public function getSomeAttribute() // Assert $this->assertEquals('something-else', $model->some); - $this->assertEquals('something-else', $model->getAttribute('some')); + $this->assertEquals('something-else', $model->getDocumentAttribute('some')); } public function testShouldIgnoreMutators() @@ -155,12 +155,12 @@ public function testShouldIgnoreMutators() { use Attributes; - public function getSomeAttribute() + public function getSomeDocumentAttribute() { return 'something-else'; } - public function setSomeAttribute($value) + public function setSomeDocumentAttribute($value) { return strtoupper($value); } @@ -170,7 +170,7 @@ public function setSomeAttribute($value) // Assert $this->assertEquals('some-value', $model->some); - $this->assertEquals('some-value', $model->getAttribute('some')); + $this->assertEquals('some-value', $model->getDocumentAttribute('some')); } public function testShouldSetAttributeFromMutator() @@ -185,7 +185,7 @@ public function __construct() $this->mutable = true; } - public function setSomeAttribute($value) + public function setSomeDocumentAttribute($value) { return strtoupper($value); } @@ -222,7 +222,7 @@ public function __construct(array $fillable, array $guarded) $model->fill($input); // Assert - $this->assertSame($expected, $model->attributes()); + $this->assertSame($expected, $model->getDocumentAttributes()); } public function testShouldForceFillAttributes() @@ -275,10 +275,10 @@ public function testShouldSetOriginalAttributes() $model->age = 25; // Act - $model->syncOriginalAttributes(); + $model->syncOriginalDocumentAttributes(); // Assert - $this->assertSame($model->attributes(), $model->originalAttributes()); + $this->assertSame($model->getDocumentAttributes(), $model->getOriginalDocumentAttributes()); } public function testShouldFallbackOriginalAttributesIfUnserializationFails() @@ -290,17 +290,17 @@ public function testShouldFallbackOriginalAttributesIfUnserializationFails() public function __construct() { - $this->_mongolid_attributes = [function () { + $this->attributes = [function () { }, ]; } }; // Act - $model->syncOriginalAttributes(); + $model->syncOriginalDocumentAttributes(); // Assert - $this->assertSame($model->attributes(), $model->originalAttributes()); + $this->assertSame($model->getDocumentAttributes(), $model->getOriginalDocumentAttributes()); } public function getFillableOptions() diff --git a/tests/Unit/Model/DocumentEmbedderTest.php b/tests/Unit/Model/DocumentEmbedderTest.php index adabc803..1f52c243 100644 --- a/tests/Unit/Model/DocumentEmbedderTest.php +++ b/tests/Unit/Model/DocumentEmbedderTest.php @@ -17,10 +17,10 @@ public function testShouldEmbed($originalField, $entity, $method, $expectation) // Arrange $parent = new stdClass(); $parent->foo = $originalField; - $embeder = new DocumentEmbedder(); + $embedder = new DocumentEmbedder(); // Assert - $embeder->$method($parent, 'foo', $entity); + $embedder->$method($parent, 'foo', $entity); $result = $parent->foo; foreach ($expectation as $index => $expectedDoc) { From 41b6e4771188b046b40c0a4e4777271ef4ef345b Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 7 Nov 2018 18:02:20 -0200 Subject: [PATCH 038/116] Fix mutator names for snake_case attributes --- src/Model/Attributes.php | 3 ++- tests/Unit/Model/AttributesTest.php | 32 ++++++++++++++--------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/Model/Attributes.php b/src/Model/Attributes.php index 4e94c1e9..39519724 100644 --- a/src/Model/Attributes.php +++ b/src/Model/Attributes.php @@ -2,6 +2,7 @@ namespace Mongolid\Model; use Exception; +use Illuminate\Support\Str; /** * This trait adds attribute getter, setters and also a useful @@ -238,6 +239,6 @@ protected function hasMutatorMethod($key, $prefix) */ protected function buildMutatorMethod($key, $prefix) { - return $prefix.ucfirst($key).'DocumentAttribute'; + return $prefix.Str::camel($key).'DocumentAttribute'; } } diff --git a/tests/Unit/Model/AttributesTest.php b/tests/Unit/Model/AttributesTest.php index 27ac952b..e0c56e1e 100644 --- a/tests/Unit/Model/AttributesTest.php +++ b/tests/Unit/Model/AttributesTest.php @@ -135,17 +135,17 @@ public function __construct() $this->mutable = true; } - public function getSomeDocumentAttribute() + public function getShortNameDocumentAttribute() { - return 'something-else'; + return 'Other name'; } }; - $model->some = 'some-value'; + $model->short_name = 'My awesome name'; // Assert - $this->assertEquals('something-else', $model->some); - $this->assertEquals('something-else', $model->getDocumentAttribute('some')); + $this->assertEquals('Other name', $model->short_name); + $this->assertEquals('Other name', $model->getDocumentAttribute('short_name')); } public function testShouldIgnoreMutators() @@ -155,22 +155,22 @@ public function testShouldIgnoreMutators() { use Attributes; - public function getSomeDocumentAttribute() + public function getShortNameDocumentAttribute() { - return 'something-else'; + return 'Other name'; } - public function setSomeDocumentAttribute($value) + public function setShortNameDocumentAttribute($value) { return strtoupper($value); } }; - $model->some = 'some-value'; + $model->short_name = 'My awesome name'; // Assert - $this->assertEquals('some-value', $model->some); - $this->assertEquals('some-value', $model->getDocumentAttribute('some')); + $this->assertEquals('My awesome name', $model->short_name); + $this->assertEquals('My awesome name', $model->getDocumentAttribute('short_name')); } public function testShouldSetAttributeFromMutator() @@ -185,16 +185,16 @@ public function __construct() $this->mutable = true; } - public function setSomeDocumentAttribute($value) + public function setShortNameDocumentAttribute($value) { return strtoupper($value); } }; - $model->some = 'some-value'; + $model->short_name = 'My awesome name'; // Assert - $this->assertEquals('SOME-VALUE', $model->some); + $this->assertSame('MY AWESOME NAME', $model->short_name); } /** @@ -235,14 +235,14 @@ public function testShouldForceFillAttributes() $input = [ 'name' => 'Josh', - 'notAllowedAttribute' => true, + 'not_allowed_attribute' => true, ]; // Act $model->fill($input, true); // Assert - $this->assertTrue($model->notAllowedAttribute); + $this->assertTrue($model->not_allowed_attribute); } public function testShouldBeCastableToArray() From de572cd20f86c181c82ffd0421ae34de807a98f9 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Thu, 8 Nov 2018 17:48:50 -0200 Subject: [PATCH 039/116] Rename traits, interfaces and abstract classes to follow code standard --- src/Connection/Manager.php | 6 +- src/Cursor/Cursor.php | 16 +- src/Cursor/CursorFactory.php | 14 +- src/Cursor/EmbeddedCursor.php | 10 +- src/DataMapper/BulkWrite.php | 4 +- src/DataMapper/DataMapper.php | 16 +- src/DataMapper/EntityAssembler.php | 12 +- src/DataMapper/SchemaMapper.php | 12 +- ...iveRecord.php => AbstractActiveRecord.php} | 155 +++++++++--------- ...terface.php => HasAttributesInterface.php} | 4 +- ...{Attributes.php => HasAttributesTrait.php} | 2 +- .../{Relations.php => HasRelationsTrait.php} | 10 +- src/Schema/{Schema.php => AbstractSchema.php} | 2 +- src/Schema/DynamicSchema.php | 2 +- src/Schema/HasSchemaInterface.php | 2 +- tests/Integration/Stubs/User.php | 4 +- tests/Unit/Connection/ManagerTest.php | 6 +- tests/Unit/Cursor/CacheableCursorTest.php | 4 +- tests/Unit/Cursor/CursorFactoryTest.php | 6 +- tests/Unit/Cursor/CursorTest.php | 10 +- tests/Unit/Cursor/EmbeddedCursorTest.php | 4 +- ...rTest.php => AbstractSchemaMapperTest.php} | 30 ++-- tests/Unit/DataMapper/BulkWriteTest.php | 4 +- tests/Unit/DataMapper/DataMapperTest.php | 36 ++-- tests/Unit/DataMapper/EntityAssemblerTest.php | 12 +- ...dTest.php => AbstractActiveRecordTest.php} | 35 ++-- ...tesTest.php => HasAttributesTraitTest.php} | 32 ++-- ...ionsTest.php => HasRelationsTraitTest.php} | 44 ++--- ...{SchemaTest.php => AbstractSchemaTest.php} | 22 +-- tests/Unit/Schema/DynamicSchemaTest.php | 2 +- 30 files changed, 261 insertions(+), 257 deletions(-) rename src/Model/{ActiveRecord.php => AbstractActiveRecord.php} (94%) rename src/Model/{AttributesAccessInterface.php => HasAttributesInterface.php} (96%) rename src/Model/{Attributes.php => HasAttributesTrait.php} (99%) rename src/Model/{Relations.php => HasRelationsTrait.php} (95%) rename src/Schema/{Schema.php => AbstractSchema.php} (99%) rename tests/Unit/DataMapper/{SchemaMapperTest.php => AbstractSchemaMapperTest.php} (91%) rename tests/Unit/Model/{ActiveRecordTest.php => AbstractActiveRecordTest.php} (91%) rename tests/Unit/Model/{AttributesTest.php => HasAttributesTraitTest.php} (92%) rename tests/Unit/Model/{RelationsTest.php => HasRelationsTraitTest.php} (88%) rename tests/Unit/Schema/{SchemaTest.php => AbstractSchemaTest.php} (87%) diff --git a/src/Connection/Manager.php b/src/Connection/Manager.php index a38b0f3f..78eea166 100644 --- a/src/Connection/Manager.php +++ b/src/Connection/Manager.php @@ -6,7 +6,7 @@ use Mongolid\DataMapper\DataMapper; use Mongolid\Event\EventTriggerInterface; use Mongolid\Event\EventTriggerService; -use Mongolid\Schema\Schema; +use Mongolid\Schema\AbstractSchema; use Mongolid\Util\CacheComponent; use Mongolid\Util\CacheComponentInterface; @@ -107,9 +107,9 @@ public function setEventTrigger(EventTriggerInterface $eventTrigger) /** * Allow document Schemas to be registered for later use. * - * @param Schema $schema schema being registered + * @param AbstractSchema $schema schema being registered */ - public function registerSchema(Schema $schema) + public function registerSchema(AbstractSchema $schema) { $this->schemas[$schema->entityClass] = $schema; } diff --git a/src/Cursor/Cursor.php b/src/Cursor/Cursor.php index 23b50c33..43750bf2 100644 --- a/src/Cursor/Cursor.php +++ b/src/Cursor/Cursor.php @@ -9,8 +9,8 @@ use Mongolid\Connection\Connection; use Mongolid\Container\Ioc; use Mongolid\DataMapper\EntityAssembler; -use Mongolid\Model\ActiveRecord; -use Mongolid\Schema\Schema; +use Mongolid\Model\AbstractActiveRecord; +use Mongolid\Schema\AbstractSchema; use Serializable; use Traversable; @@ -70,13 +70,13 @@ class Cursor implements CursorInterface, Serializable protected $assembler; /** - * @param Schema $entitySchema schema that describes the entity that will be retrieved from the database - * @param Collection $collection the raw collection object that will be used to retrieve the documents - * @param string $command the command that is being called in the $collection - * @param array $params the parameters of the $command + * @param AbstractSchema $entitySchema schema that describes the entity that will be retrieved from the database + * @param Collection $collection the raw collection object that will be used to retrieve the documents + * @param string $command the command that is being called in the $collection + * @param array $params the parameters of the $command */ public function __construct( - Schema $entitySchema, + AbstractSchema $entitySchema, Collection $collection, string $command, array $params @@ -200,7 +200,7 @@ public function current() { $document = $this->getCursor()->current(); - if ($document instanceof ActiveRecord) { + if ($document instanceof AbstractActiveRecord) { $documentToArray = $document->toArray(); $this->entitySchema = $document->getSchema(); } else { diff --git a/src/Cursor/CursorFactory.php b/src/Cursor/CursorFactory.php index 665be628..89704a0f 100644 --- a/src/Cursor/CursorFactory.php +++ b/src/Cursor/CursorFactory.php @@ -2,7 +2,7 @@ namespace Mongolid\Cursor; use MongoDB\Collection; -use Mongolid\Schema\Schema; +use Mongolid\Schema\AbstractSchema; /** * Factory of new EmbeddedCursor instances. @@ -12,14 +12,14 @@ class CursorFactory /** * Creates a new instance of a non embedded Cursor. * - * @param Schema $entitySchema schema that describes the entity that will be retrieved from the database - * @param Collection $collection the raw collection object that will be used to retrieve the documents - * @param string $command the command that is being called in the $collection - * @param array $params the parameters of the $command - * @param bool $cacheable retrieves a CacheableCursor instead + * @param AbstractSchema $entitySchema schema that describes the entity that will be retrieved from the database + * @param Collection $collection the raw collection object that will be used to retrieve the documents + * @param string $command the command that is being called in the $collection + * @param array $params the parameters of the $command + * @param bool $cacheable retrieves a CacheableCursor instead */ public function createCursor( - Schema $entitySchema, + AbstractSchema $entitySchema, Collection $collection, string $command, array $params, diff --git a/src/Cursor/EmbeddedCursor.php b/src/Cursor/EmbeddedCursor.php index 9c7f5f73..68b37235 100644 --- a/src/Cursor/EmbeddedCursor.php +++ b/src/Cursor/EmbeddedCursor.php @@ -3,9 +3,9 @@ use Mongolid\Container\Ioc; use Mongolid\DataMapper\EntityAssembler; -use Mongolid\Model\ActiveRecord; +use Mongolid\Model\AbstractActiveRecord; +use Mongolid\Schema\AbstractSchema; use Mongolid\Schema\DynamicSchema; -use Mongolid\Schema\Schema; /** * This class wraps the query execution and the actual creation of the driver cursor. @@ -152,15 +152,15 @@ public function current() /** * Retrieve a schema based on Entity Class. */ - protected function getSchemaForEntity(): Schema + protected function getSchemaForEntity(): AbstractSchema { - if ($this->entityClass instanceof Schema) { + if ($this->entityClass instanceof AbstractSchema) { return $this->entityClass; } $model = new $this->entityClass(); - if ($model instanceof ActiveRecord) { + if ($model instanceof AbstractActiveRecord) { return $model->getSchema(); } diff --git a/src/DataMapper/BulkWrite.php b/src/DataMapper/BulkWrite.php index 02b1f32f..33e8efce 100644 --- a/src/DataMapper/BulkWrite.php +++ b/src/DataMapper/BulkWrite.php @@ -6,8 +6,8 @@ use MongoDB\Driver\WriteConcern; use Mongolid\Connection\Connection; use Mongolid\Container\Ioc; +use Mongolid\Schema\AbstractSchema; use Mongolid\Schema\HasSchemaInterface; -use Mongolid\Schema\Schema; /** * This class is meant to provide a better API for handling @@ -32,7 +32,7 @@ class BulkWrite protected $bulkWrite; /** - * @var Schema + * @var AbstractSchema */ protected $schema; diff --git a/src/DataMapper/DataMapper.php b/src/DataMapper/DataMapper.php index c1d37a3f..bebbe63f 100644 --- a/src/DataMapper/DataMapper.php +++ b/src/DataMapper/DataMapper.php @@ -11,9 +11,9 @@ use Mongolid\Cursor\CursorInterface; use Mongolid\Event\EventTriggerService; use Mongolid\Exception\ModelNotFoundException; -use Mongolid\Model\AttributesAccessInterface; +use Mongolid\Model\HasAttributesInterface; +use Mongolid\Schema\AbstractSchema; use Mongolid\Schema\HasSchemaInterface; -use Mongolid\Schema\Schema; use Mongolid\Util\ObjectIdUtils; /** @@ -29,12 +29,12 @@ class DataMapper implements HasSchemaInterface * * @var string */ - public $schemaClass = Schema::class; + public $schemaClass = AbstractSchema::class; /** * Schema object. Will be set after the $schemaClass. * - * @var Schema + * @var AbstractSchema */ protected $schema; @@ -327,7 +327,7 @@ public function firstOrFail( /** * {@inheritdoc} */ - public function getSchema(): Schema + public function getSchema(): AbstractSchema { return $this->schema; } @@ -335,7 +335,7 @@ public function getSchema(): Schema /** * Set a Schema object that describes an Entity in MongoDB. */ - public function setSchema(Schema $schema) + public function setSchema(AbstractSchema $schema) { $this->schema = $schema; } @@ -591,14 +591,14 @@ private function mergeOptions(array $defaultOptions = [], array $toMergeOptions */ private function afterSuccess($entity) { - if ($entity instanceof AttributesAccessInterface) { + if ($entity instanceof HasAttributesInterface) { $entity->syncOriginalDocumentAttributes(); } } private function getUpdateData($entity, array $data) { - if (!$entity instanceof AttributesAccessInterface) { + if (!$entity instanceof HasAttributesInterface) { return ['$set' => $data]; } diff --git a/src/DataMapper/EntityAssembler.php b/src/DataMapper/EntityAssembler.php index bc29989e..37b38169 100644 --- a/src/DataMapper/EntityAssembler.php +++ b/src/DataMapper/EntityAssembler.php @@ -2,9 +2,9 @@ namespace Mongolid\DataMapper; use Mongolid\Container\Ioc; -use Mongolid\Model\AttributesAccessInterface; +use Mongolid\Model\HasAttributesInterface; use Mongolid\Model\PolymorphableInterface; -use Mongolid\Schema\Schema; +use Mongolid\Schema\AbstractSchema; /** * EntityAssembler have the responsibility of assembling the data coming from @@ -22,12 +22,12 @@ class EntityAssembler /** * Builds an object from the provided data. * - * @param array|object $document the attributes that will be used to compose the entity - * @param Schema $schema schema that will be used to map each field + * @param array|object $document the attributes that will be used to compose the entity + * @param AbstractSchema $schema schema that will be used to map each field * * @return mixed */ - public function assemble($document, Schema $schema) + public function assemble($document, AbstractSchema $schema) { $entityClass = $schema->entityClass; $model = Ioc::make($entityClass); @@ -75,7 +75,7 @@ protected function morphingTime($entity) */ protected function prepareOriginalAttributes($entity) { - if ($entity instanceof AttributesAccessInterface) { + if ($entity instanceof HasAttributesInterface) { $entity->syncOriginalDocumentAttributes(); } diff --git a/src/DataMapper/SchemaMapper.php b/src/DataMapper/SchemaMapper.php index 15a0d097..f143a661 100644 --- a/src/DataMapper/SchemaMapper.php +++ b/src/DataMapper/SchemaMapper.php @@ -2,8 +2,8 @@ namespace Mongolid\DataMapper; use Mongolid\Container\Ioc; -use Mongolid\Model\AttributesAccessInterface; -use Mongolid\Schema\Schema; +use Mongolid\Model\HasAttributesInterface; +use Mongolid\Schema\AbstractSchema; /** * The SchemaMapper will map an object or an array of data to a Schema object. @@ -18,7 +18,7 @@ class SchemaMapper /** * The actual schema to maps the data. * - * @var Schema + * @var AbstractSchema */ public $schema; @@ -32,9 +32,9 @@ class SchemaMapper protected $castableTypes = ['int', 'integer', 'bool', 'boolean', 'float', 'double', 'real', 'string']; /** - * @param Schema $schema schema that will be used to map each field + * @param AbstractSchema $schema schema that will be used to map each field */ - public function __construct(Schema $schema) + public function __construct(AbstractSchema $schema) { $this->schema = $schema; } @@ -163,7 +163,7 @@ protected function mapToSchema($value, string $schemaClass) protected function parseToArray($object): array { if (!is_array($object)) { - $attributes = $object instanceof AttributesAccessInterface + $attributes = $object instanceof HasAttributesInterface ? $object->getDocumentAttributes() : get_object_vars($object); diff --git a/src/Model/ActiveRecord.php b/src/Model/AbstractActiveRecord.php similarity index 94% rename from src/Model/ActiveRecord.php rename to src/Model/AbstractActiveRecord.php index f2224df9..35a3e467 100644 --- a/src/Model/ActiveRecord.php +++ b/src/Model/AbstractActiveRecord.php @@ -6,11 +6,11 @@ use Mongolid\Container\Ioc; use Mongolid\Cursor\CursorInterface; use Mongolid\DataMapper\DataMapper; +use Mongolid\Exception\ModelNotFoundException; use Mongolid\Exception\NoCollectionNameException; -use Mongolid\ModelNotFoundException; +use Mongolid\Schema\AbstractSchema; use Mongolid\Schema\DynamicSchema; use Mongolid\Schema\HasSchemaInterface; -use Mongolid\Schema\Schema; /** * The Mongolid\Model\ActiveRecord base class will ensure to enable your entity to @@ -19,9 +19,20 @@ * The Mongolid\Schema\Schema that describes the entity will be generated on the go * based on the $fields. */ -abstract class ActiveRecord implements AttributesAccessInterface, HasSchemaInterface +abstract class AbstractActiveRecord implements HasAttributesInterface, HasSchemaInterface { - use Attributes, Relations; + use HasAttributesTrait; + use HasRelationsTrait; + + /** + * The $dynamic property tells if the object will accept additional fields + * that are not specified in the $fields property. This is useful if you + * does not have a strict document format or if you want to take full + * advantage of the "schemaless" nature of MongoDB. + * + * @var bool + */ + public $dynamic = true; /** * Name of the collection where this kind of Entity is going to be saved or @@ -42,7 +53,7 @@ abstract class ActiveRecord implements AttributesAccessInterface, HasSchemaInter * Describes the Schema fields of the model. Optionally you can set it to * the name of a Schema class to be used. * - * @see \Mongolid\Schema\Schema::$fields + * @see \Mongolid\Schema\AbstractSchema::$fields * * @var string|string[] */ @@ -52,56 +63,6 @@ abstract class ActiveRecord implements AttributesAccessInterface, HasSchemaInter 'updated_at' => 'updatedAtTimestamp', ]; - /** - * The $dynamic property tells if the object will accept additional fields - * that are not specified in the $fields property. This is useful if you - * does not have a strict document format or if you want to take full - * advantage of the "schemaless" nature of MongoDB. - * - * @var bool - */ - public $dynamic = true; - - /** - * Saves this object into database. - * - * @return bool Success - */ - public function save() - { - return $this->execute('save'); - } - - /** - * Insert this object into database. - * - * @return bool Success - */ - public function insert() - { - return $this->execute('insert'); - } - - /** - * Updates this object in database. - * - * @return bool Success - */ - public function update() - { - return $this->execute('update'); - } - - /** - * Deletes this object in database. - * - * @return bool Success - */ - public function delete() - { - return $this->execute('delete'); - } - /** * Gets a cursor of this kind of entities that matches the query from the * database. @@ -137,7 +98,7 @@ public static function all(): CursorInterface * @param array $projection fields to project in Mongo query * @param bool $useCache retrieves the entity through a CacheableCursor * - * @return ActiveRecord + * @return AbstractActiveRecord */ public static function first( $query = [], @@ -161,7 +122,7 @@ public static function first( * * @throws ModelNotFoundException If no document was found * - * @return ActiveRecord + * @return AbstractActiveRecord */ public static function firstOrFail( $query = [], @@ -182,7 +143,7 @@ public static function firstOrFail( * * @param mixed $id document id * - * @return ActiveRecord + * @return AbstractActiveRecord */ public static function firstOrNew($id) { @@ -196,6 +157,62 @@ public static function firstOrNew($id) return $entity; } + /** + * Returns the a valid instance from Ioc. + * + * @throws NoCollectionNameException Throws exception when has no collection filled + */ + private static function getDataMapperInstance(): DataMapper + { + $instance = new static(); + + if (!$instance->getCollectionName()) { + throw new NoCollectionNameException(); + } + + return $instance->getDataMapper(); + } + + /** + * Saves this object into database. + * + * @return bool Success + */ + public function save() + { + return $this->execute('save'); + } + + /** + * Insert this object into database. + * + * @return bool Success + */ + public function insert() + { + return $this->execute('insert'); + } + + /** + * Updates this object in database. + * + * @return bool Success + */ + public function update() + { + return $this->execute('update'); + } + + /** + * Deletes this object in database. + * + * @return bool Success + */ + public function delete() + { + return $this->execute('delete'); + } + /** * Handle dynamic method calls into the model. * @@ -280,7 +297,7 @@ public function setWriteConcern($writeConcern) /** * {@inheritdoc} */ - public function getSchema(): Schema + public function getSchema(): AbstractSchema { if ($schema = $this->instantiateSchemaInFields()) { return $schema; @@ -299,12 +316,12 @@ public function getSchema(): Schema * Will check if the current value of $fields property is the name of a * Schema class and instantiate it if possible. * - * @return Schema|null + * @return AbstractSchema|null */ protected function instantiateSchemaInFields() { if (is_string($this->fields)) { - if (is_subclass_of($instance = Ioc::make($this->fields), Schema::class)) { + if (is_subclass_of($instance = Ioc::make($this->fields), AbstractSchema::class)) { return $instance; } } @@ -333,20 +350,4 @@ protected function execute(string $action) return $result; } - - /** - * Returns the a valid instance from Ioc. - * - * @throws NoCollectionNameException Throws exception when has no collection filled - */ - private static function getDataMapperInstance(): DataMapper - { - $instance = new static(); - - if (!$instance->getCollectionName()) { - throw new NoCollectionNameException(); - } - - return $instance->getDataMapper(); - } } diff --git a/src/Model/AttributesAccessInterface.php b/src/Model/HasAttributesInterface.php similarity index 96% rename from src/Model/AttributesAccessInterface.php rename to src/Model/HasAttributesInterface.php index 876cd14a..10e928e2 100644 --- a/src/Model/AttributesAccessInterface.php +++ b/src/Model/HasAttributesInterface.php @@ -7,9 +7,9 @@ * * It is supposed to be used in conjunction with Attributes trait * - * @see Attributes + * @see HasAttributesTrait */ -interface AttributesAccessInterface +interface HasAttributesInterface { /** * Get an attribute from the model. diff --git a/src/Model/Attributes.php b/src/Model/HasAttributesTrait.php similarity index 99% rename from src/Model/Attributes.php rename to src/Model/HasAttributesTrait.php index 39519724..1f19edf7 100644 --- a/src/Model/Attributes.php +++ b/src/Model/HasAttributesTrait.php @@ -12,7 +12,7 @@ * * It is supposed to be used in model classes in general */ -trait Attributes +trait HasAttributesTrait { /** * The model's attributes. diff --git a/src/Model/Relations.php b/src/Model/HasRelationsTrait.php similarity index 95% rename from src/Model/Relations.php rename to src/Model/HasRelationsTrait.php index 8fb39423..d13ae180 100644 --- a/src/Model/Relations.php +++ b/src/Model/HasRelationsTrait.php @@ -7,13 +7,13 @@ use Mongolid\Cursor\CursorInterface; use Mongolid\Cursor\EmbeddedCursor; use Mongolid\DataMapper\DataMapper; -use Mongolid\Schema\Schema; +use Mongolid\Schema\AbstractSchema; use Mongolid\Util\ObjectIdUtils; /** * It is supposed to be used in model classes in general. */ -trait Relations +trait HasRelationsTrait { /** * Returns the referenced document as object. @@ -34,7 +34,7 @@ protected function referencesOne(string $entity, string $field, bool $cacheable $entityInstance = Ioc::make($entity); - if ($entityInstance instanceof Schema) { + if ($entityInstance instanceof AbstractSchema) { $dataMapper = Ioc::make(DataMapper::class); $dataMapper->setSchema($entityInstance); @@ -65,7 +65,7 @@ protected function referencesMany(string $entity, string $field, bool $cacheable $entityInstance = Ioc::make($entity); - if ($entityInstance instanceof Schema) { + if ($entityInstance instanceof AbstractSchema) { $dataMapper = Ioc::make(DataMapper::class); $dataMapper->setSchema($entityInstance); @@ -98,7 +98,7 @@ protected function embedsOne(string $entity, string $field) */ protected function embedsMany(string $entity, string $field) { - if (is_subclass_of($entity, Schema::class)) { + if (is_subclass_of($entity, AbstractSchema::class)) { $entity = (new $entity())->entityClass; } diff --git a/src/Schema/Schema.php b/src/Schema/AbstractSchema.php similarity index 99% rename from src/Schema/Schema.php rename to src/Schema/AbstractSchema.php index e49160dd..85eca9fd 100644 --- a/src/Schema/Schema.php +++ b/src/Schema/AbstractSchema.php @@ -11,7 +11,7 @@ * A schema maps to a MongoDB collection and defines the shape of the documents * within that collection. */ -abstract class Schema +abstract class AbstractSchema { /** * The $dynamic property tells if the schema will accept additional fields diff --git a/src/Schema/DynamicSchema.php b/src/Schema/DynamicSchema.php index cb72aac4..bdd43275 100644 --- a/src/Schema/DynamicSchema.php +++ b/src/Schema/DynamicSchema.php @@ -6,7 +6,7 @@ * the $schema property. This is useful if you does not have a clear idea * of how the document will look like. */ -class DynamicSchema extends Schema +class DynamicSchema extends AbstractSchema { /** * {@inheritdoc} diff --git a/src/Schema/HasSchemaInterface.php b/src/Schema/HasSchemaInterface.php index c01e79db..65d9da34 100644 --- a/src/Schema/HasSchemaInterface.php +++ b/src/Schema/HasSchemaInterface.php @@ -6,5 +6,5 @@ interface HasSchemaInterface /** * Returns a Schema object that describes an Entity in MongoDB. */ - public function getSchema(): Schema; + public function getSchema(): AbstractSchema; } diff --git a/tests/Integration/Stubs/User.php b/tests/Integration/Stubs/User.php index a89c26d4..f40e2d1f 100644 --- a/tests/Integration/Stubs/User.php +++ b/tests/Integration/Stubs/User.php @@ -4,9 +4,9 @@ use MongoDB\Collection; use Mongolid\Connection\Connection; use Mongolid\Container\Ioc; -use Mongolid\Model\ActiveRecord; +use Mongolid\Model\AbstractActiveRecord; -class User extends ActiveRecord +class User extends AbstractActiveRecord { /** * @var string diff --git a/tests/Unit/Connection/ManagerTest.php b/tests/Unit/Connection/ManagerTest.php index 611ebd32..15e4b15e 100644 --- a/tests/Unit/Connection/ManagerTest.php +++ b/tests/Unit/Connection/ManagerTest.php @@ -7,7 +7,7 @@ use Mongolid\DataMapper\DataMapper; use Mongolid\Event\EventTriggerInterface; use Mongolid\Event\EventTriggerService; -use Mongolid\Schema\Schema; +use Mongolid\Schema\AbstractSchema; use Mongolid\TestCase; use Mongolid\Util\CacheComponentInterface; @@ -64,7 +64,7 @@ public function testShouldRegisterSchema() { // Arrange $manager = new Manager(); - $schema = m::mock(Schema::class); + $schema = m::mock(AbstractSchema::class); $schema->entityClass = 'Bacon'; // Assert @@ -80,7 +80,7 @@ public function testShouldGetDataMapperForEntitiesWithRegisteredSchemas() { // Arrange $manager = new Manager(); - $schema = m::mock(Schema::class); + $schema = m::mock(AbstractSchema::class); $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)->makePartial()); $schema->entityClass = 'Bacon'; diff --git a/tests/Unit/Cursor/CacheableCursorTest.php b/tests/Unit/Cursor/CacheableCursorTest.php index 22cdd596..dc663488 100644 --- a/tests/Unit/Cursor/CacheableCursorTest.php +++ b/tests/Unit/Cursor/CacheableCursorTest.php @@ -6,7 +6,7 @@ use IteratorIterator; use Mockery as m; use MongoDB\Collection; -use Mongolid\Schema\Schema; +use Mongolid\Schema\AbstractSchema; use Mongolid\TestCase; use Mongolid\Util\CacheComponentInterface; @@ -209,7 +209,7 @@ protected function getCachableCursor( $driverCursor = null ) { if (!$entitySchema) { - $entitySchema = m::mock(Schema::class.'[]'); + $entitySchema = m::mock(AbstractSchema::class.'[]'); } if (!$collection) { diff --git a/tests/Unit/Cursor/CursorFactoryTest.php b/tests/Unit/Cursor/CursorFactoryTest.php index f539272b..75e85689 100644 --- a/tests/Unit/Cursor/CursorFactoryTest.php +++ b/tests/Unit/Cursor/CursorFactoryTest.php @@ -3,7 +3,7 @@ use Mockery as m; use MongoDB\Collection; -use Mongolid\Schema\Schema; +use Mongolid\Schema\AbstractSchema; use Mongolid\TestCase; class CursorFactoryTest extends TestCase @@ -12,7 +12,7 @@ public function testShouldCreateACursor() { // Set $factory = new CursorFactory(); - $schema = m::mock(Schema::class); + $schema = m::mock(AbstractSchema::class); $collection = m::mock(Collection::class); // Assert @@ -36,7 +36,7 @@ public function testShouldCreateACacheableCursor() { // Set $factory = new CursorFactory(); - $schema = m::mock(Schema::class); + $schema = m::mock(AbstractSchema::class); $collection = m::mock(Collection::class); // Assert diff --git a/tests/Unit/Cursor/CursorTest.php b/tests/Unit/Cursor/CursorTest.php index 02553d1b..b6184255 100644 --- a/tests/Unit/Cursor/CursorTest.php +++ b/tests/Unit/Cursor/CursorTest.php @@ -9,9 +9,9 @@ use MongoDB\Driver\Exception\LogicException; use MongoDB\Driver\ReadPreference; use Mongolid\Connection\Connection; -use Mongolid\Model\ActiveRecord; +use Mongolid\Model\AbstractActiveRecord; +use Mongolid\Schema\AbstractSchema; use Mongolid\Schema\DynamicSchema; -use Mongolid\Schema\Schema; use Mongolid\TestCase; use Serializable; use stdClass; @@ -182,14 +182,14 @@ public function testShouldGetCurrentUsingActiveRecordClasses() { // Arrange $collection = m::mock(Collection::class); - $entity = m::mock(ActiveRecord::class.'[]'); + $entity = m::mock(AbstractActiveRecord::class.'[]'); $entity->name = 'John Doe'; $driverCursor = new ArrayIterator([$entity]); $cursor = $this->getCursor(null, $collection, 'find', [[]], $driverCursor); // Assert $entity = $cursor->current(); - $this->assertInstanceOf(ActiveRecord::class, $entity); + $this->assertInstanceOf(AbstractActiveRecord::class, $entity); $this->assertEquals('John Doe', $entity->name); } @@ -444,7 +444,7 @@ protected function getCursor( $driverCursor = null ) { if (!$entitySchema) { - $entitySchema = m::mock(Schema::class.'[]'); + $entitySchema = m::mock(AbstractSchema::class.'[]'); } if (!$collection) { diff --git a/tests/Unit/Cursor/EmbeddedCursorTest.php b/tests/Unit/Cursor/EmbeddedCursorTest.php index e35fb795..346191bf 100644 --- a/tests/Unit/Cursor/EmbeddedCursorTest.php +++ b/tests/Unit/Cursor/EmbeddedCursorTest.php @@ -1,7 +1,7 @@ instance( 'My\Own\Schema', - new class() extends Schema + new class() extends AbstractSchema { /** * {@inheritdoc} @@ -30,7 +30,7 @@ public function testShouldMapToFieldsOfSchema() } ); - $schema = new class() extends Schema + $schema = new class() extends AbstractSchema { /** * {@inheritdoc} @@ -81,7 +81,7 @@ public function testShouldMapToFieldsOfSchema() public function testShouldClearDynamicFieldsIfSchemaIsNotDynamic() { // Arrange - $schema = new class extends Schema + $schema = new class extends AbstractSchema { /** * {@inheritdoc} @@ -144,7 +144,7 @@ public function testShouldNotClearDynamicFieldsIfSchemaIsDynamic() public function testShouldParseFieldIntoCastableType() { // Arrange - $schema = new class extends Schema + $schema = new class extends AbstractSchema { }; $schemaMapper = new SchemaMapper($schema); @@ -164,7 +164,7 @@ public function testShouldParseFieldIntoCastableType() public function testShouldParseFieldIntoAnotherMappedSchemaIfTypeBeginsWithSchema() { // Arrange - $schema = new class extends Schema + $schema = new class extends AbstractSchema { }; $schemaMapper = m::mock( @@ -188,7 +188,7 @@ public function testShouldParseFieldIntoAnotherMappedSchemaIfTypeBeginsWithSchem public function testShouldParseFieldUsingAMethodInSchemaIfTypeIsAnUnknownString() { // Arrange - $schemaClass = new class() extends Schema + $schemaClass = new class() extends AbstractSchema { public function pumpkinPoint($value) { @@ -209,12 +209,12 @@ public function pumpkinPoint($value) public function testShouldMapAnArrayValueToAnotherSchema() { // Arrange - $schema = new class extends Schema + $schema = new class extends AbstractSchema { }; $mySchema = $this->instance( 'Xd\MySchema', - new class extends Schema + new class extends AbstractSchema { } ); @@ -251,7 +251,7 @@ function ($container, $params) use ($value, $mySchema, $test) { public function testShouldParseToArrayGettingObjectAttributes() { // Arrange - $schema = new class extends Schema + $schema = new class extends AbstractSchema { }; $schemaMapper = new SchemaMapper($schema); @@ -267,7 +267,7 @@ public function testShouldParseToArrayGettingObjectAttributes() public function testShouldParseToArrayIfIsAnArray() { // Arrange - $schema = new class extends Schema + $schema = new class extends AbstractSchema { }; $schemaMapper = new SchemaMapper($schema); @@ -283,11 +283,11 @@ public function testShouldParseToArrayIfIsAnArray() public function testShouldGetAttributesWhenObjectImplementsAttributesAccessInterface() { // Arrange - $schema = new class extends Schema + $schema = new class extends AbstractSchema { }; $schemaMapper = new SchemaMapper($schema); - $object = new class implements AttributesAccessInterface + $object = new class implements HasAttributesInterface { public function getDocumentAttribute(string $key) { diff --git a/tests/Unit/DataMapper/BulkWriteTest.php b/tests/Unit/DataMapper/BulkWriteTest.php index 09d9789d..69aef4ca 100644 --- a/tests/Unit/DataMapper/BulkWriteTest.php +++ b/tests/Unit/DataMapper/BulkWriteTest.php @@ -6,8 +6,8 @@ use MongoDB\Driver\Manager; use MongoDB\Driver\WriteConcern; use Mongolid\Connection\Connection; +use Mongolid\Schema\AbstractSchema; use Mongolid\Schema\HasSchemaInterface; -use Mongolid\Schema\Schema; use Mongolid\TestCase; class BulkWriteTest extends TestCase @@ -101,7 +101,7 @@ public function testShouldUpdateOneWithUnsetOperationToBulkWrite() public function testShouldExecuteBulkWrite() { $entity = m::mock(HasSchemaInterface::class); - $schema = m::mock(Schema::class); + $schema = m::mock(AbstractSchema::class); $entity->schema = $schema; $mongoBulkWrite = m::mock(new MongoBulkWrite()); $connection = $this->instance(Connection::class, m::mock(Connection::class)); diff --git a/tests/Unit/DataMapper/DataMapperTest.php b/tests/Unit/DataMapper/DataMapperTest.php index 81b6b5a4..a722256c 100644 --- a/tests/Unit/DataMapper/DataMapperTest.php +++ b/tests/Unit/DataMapper/DataMapperTest.php @@ -14,8 +14,8 @@ use Mongolid\Cursor\Cursor; use Mongolid\Event\EventTriggerService; use Mongolid\Exception\ModelNotFoundException; -use Mongolid\Model\ActiveRecord; -use Mongolid\Schema\Schema; +use Mongolid\Model\AbstractActiveRecord; +use Mongolid\Schema\AbstractSchema; use Mongolid\TestCase; use stdClass; @@ -205,8 +205,8 @@ public function testShouldUpdate($entity, $writeConcern, $shouldFireEventAfter, $options = ['writeConcern' => new WriteConcern($writeConcern)]; $this->instance( - Schema::class, - new class() extends Schema + AbstractSchema::class, + new class() extends AbstractSchema { /** * {@inheritdoc} @@ -270,7 +270,7 @@ public function testShouldUpdateUnsettingFields() $database = m::mock(Database::class); $dataMapper = new DataMapper($connection); - $entity = new class extends ActiveRecord + $entity = new class extends AbstractActiveRecord { }; $collection = m::mock(Collection::class); @@ -278,8 +278,8 @@ public function testShouldUpdateUnsettingFields() $options = ['writeConcern' => new WriteConcern(1)]; $this->instance( - Schema::class, - new class() extends Schema + AbstractSchema::class, + new class() extends AbstractSchema { /** * {@inheritdoc} @@ -491,7 +491,7 @@ public function testShouldGetWithWhereQuery() // Arrange $connection = m::mock(Connection::class); $dataMapper = m::mock(DataMapper::class.'[prepareValueQuery,getCollection]', [$connection]); - $schema = m::mock(Schema::class); + $schema = m::mock(AbstractSchema::class); $collection = m::mock(Collection::class); $query = 123; @@ -564,7 +564,7 @@ public function testShouldGetFirstWithQuery() // Arrange $connection = m::mock(Connection::class); $dataMapper = m::mock(DataMapper::class.'[prepareValueQuery,getCollection]', [$connection]); - $schema = m::mock(Schema::class); + $schema = m::mock(AbstractSchema::class); $collection = m::mock(Collection::class); $query = 123; $preparedQuery = ['_id' => 123]; @@ -612,7 +612,7 @@ public function testFirstOrFailShouldGetFirst() // Arrange $connection = m::mock(Connection::class); $dataMapper = m::mock(DataMapper::class.'[prepareValueQuery,getCollection]', [$connection]); - $schema = m::mock(Schema::class); + $schema = m::mock(AbstractSchema::class); $collection = m::mock(Collection::class); $query = 123; $preparedQuery = ['_id' => 123]; @@ -648,7 +648,7 @@ public function testFirstOrFailWithNullShouldFail() $connection = m::mock(Connection::class); $dataMapper = new DataMapper($connection); $dataMapper->setSchema( - new class extends Schema + new class extends AbstractSchema { /** * {@inheritdoc} @@ -669,7 +669,7 @@ public function testShouldGetNullIfFirstCantFindAnything() // Arrange $connection = m::mock(Connection::class); $dataMapper = m::mock(DataMapper::class.'[prepareValueQuery,getCollection]', [$connection]); - $schema = m::mock(Schema::class); + $schema = m::mock(AbstractSchema::class); $collection = m::mock(Collection::class); $query = 123; @@ -708,7 +708,7 @@ public function testShouldGetFirstProjectingFields() DataMapper::class.'[prepareValueQuery,getCollection]', [$connection] ); - $schema = m::mock(Schema::class); + $schema = m::mock(AbstractSchema::class); $collection = m::mock(Collection::class); $query = 123; @@ -798,7 +798,7 @@ public function testShouldParseObjectToDocumentAndPutResultingIdIntoTheGivenObje $dataMapper = m::mock(DataMapper::class.'[getSchemaMapper]', [$connection]); $entity = m::mock(); $parsedDocument = ['a_field' => 123, '_id' => 'bacon']; - $schemaMapper = m::mock(Schema::class.'[]'); + $schemaMapper = m::mock(AbstractSchema::class.'[]'); $dataMapper->shouldAllowMockingProtectedMethods(); @@ -828,7 +828,7 @@ public function testShouldGetSchemaMapper() $connection = m::mock(Connection::class); $dataMapper = new DataMapper($connection); $dataMapper->schemaClass = 'MySchema'; - $schema = $this->instance('MySchema', m::mock(Schema::class)); + $schema = $this->instance('MySchema', m::mock(AbstractSchema::class)); // Act $result = $this->callProtected($dataMapper, 'getSchemaMapper'); @@ -844,7 +844,7 @@ public function testShouldGetRawCollection() $connection = m::mock(Connection::class); $dataMapper = new DataMapper($connection); $collection = m::mock(Collection::class); - $schema = m::mock(Schema::class); + $schema = m::mock(AbstractSchema::class); $schema->collection = 'foobar'; $dataMapper->setSchema($schema); @@ -983,11 +983,11 @@ public function queryValueScenarios() public function getWriteConcernVariations() { - $model = new class extends ActiveRecord + $model = new class extends AbstractActiveRecord { }; - $model2 = new class extends ActiveRecord + $model2 = new class extends AbstractActiveRecord { }; diff --git a/tests/Unit/DataMapper/EntityAssemblerTest.php b/tests/Unit/DataMapper/EntityAssemblerTest.php index 309cdcef..328e3667 100644 --- a/tests/Unit/DataMapper/EntityAssemblerTest.php +++ b/tests/Unit/DataMapper/EntityAssemblerTest.php @@ -3,10 +3,10 @@ use Mockery as m; use MongoDB\BSON\ObjectID; -use Mongolid\Model\Attributes; -use Mongolid\Model\AttributesAccessInterface; +use Mongolid\Model\HasAttributesInterface; +use Mongolid\Model\HasAttributesTrait; use Mongolid\Model\PolymorphableInterface; -use Mongolid\Schema\Schema; +use Mongolid\Schema\AbstractSchema; use Mongolid\TestCase; use stdClass; @@ -21,7 +21,7 @@ public function testShouldAssembleEntityForTheGivenSchema($inputValue, $availabl $entityAssembler = new EntityAssembler(); $schemas = []; foreach ($availableSchemas as $key => $value) { - $schemas[$key] = $this->instance($key, m::mock(Schema::class.'[]')); + $schemas[$key] = $this->instance($key, m::mock(AbstractSchema::class.'[]')); $schemas[$key]->entityClass = $value['entityClass']; $schemas[$key]->fields = $value['fields']; } @@ -251,9 +251,9 @@ public function entityAssemblerFixture() } } -class StubStudent extends stdClass implements AttributesAccessInterface +class StubStudent extends stdClass implements HasAttributesInterface { - use Attributes; + use HasAttributesTrait; public function __construct($attr = []) { diff --git a/tests/Unit/Model/ActiveRecordTest.php b/tests/Unit/Model/AbstractActiveRecordTest.php similarity index 91% rename from tests/Unit/Model/ActiveRecordTest.php rename to tests/Unit/Model/AbstractActiveRecordTest.php index 170e1f63..efd7c9e3 100644 --- a/tests/Unit/Model/ActiveRecordTest.php +++ b/tests/Unit/Model/AbstractActiveRecordTest.php @@ -8,15 +8,15 @@ use Mongolid\Cursor\CursorInterface; use Mongolid\DataMapper\DataMapper; use Mongolid\Exception\NoCollectionNameException; +use Mongolid\Schema\AbstractSchema; use Mongolid\Schema\DynamicSchema; -use Mongolid\Schema\Schema; use Mongolid\TestCase; use stdClass; -class ActiveRecordTest extends TestCase +class AbstractActiveRecordTest extends TestCase { /** - * @var ActiveRecord + * @var AbstractActiveRecord */ protected $entity; @@ -26,7 +26,7 @@ class ActiveRecordTest extends TestCase protected function setUp() { parent::setUp(); - $this->entity = new class() extends ActiveRecord + $this->entity = new class() extends AbstractActiveRecord { /** * {@inheritdoc} @@ -73,8 +73,8 @@ public function testShouldImplementModelTraits() { // Assert $this->assertSame( - [Attributes::class, Relations::class], - array_keys(class_uses(ActiveRecord::class)) + [HasAttributesTrait::class, HasRelationsTrait::class], + array_keys(class_uses(AbstractActiveRecord::class)) ); } @@ -288,7 +288,7 @@ public function testShouldGetSchemaIfFieldsIsTheClassName() { // Arrage $this->entity->setFields('MySchemaClass'); - $schema = $this->instance('MySchemaClass', m::mock(Schema::class)); + $schema = $this->instance('MySchemaClass', m::mock(AbstractSchema::class)); // Assert $this->assertSame( @@ -305,7 +305,7 @@ public function testShouldGetSchemaIfFieldsDescribesSchemaFields() // Assert $result = $this->entity->getSchema(); - $this->assertInstanceOf(Schema::class, $result); + $this->assertInstanceOf(AbstractSchema::class, $result); $this->assertSame($fields, $result->fields); $this->assertSame($this->entity->dynamic, $result->dynamic); $this->assertSame($this->entity->getCollectionName(), $result->collection); @@ -315,8 +315,8 @@ public function testShouldGetSchemaIfFieldsDescribesSchemaFields() public function testShouldGetDataMapper() { // Arrage - $entity = m::mock(ActiveRecord::class.'[getSchema]'); - $schema = m::mock(Schema::class.'[]'); + $entity = m::mock(AbstractActiveRecord::class.'[getSchema]'); + $schema = m::mock(AbstractSchema::class.'[]'); // Act $entity->shouldAllowMockingProtectedMethods(); @@ -333,7 +333,8 @@ public function testShouldGetDataMapper() public function testShouldRaiseExceptionWhenHasNoCollectionAndTryToCallAllFunction() { - $entity = new class() extends ActiveRecord { + $entity = new class() extends AbstractActiveRecord + { }; $this->expectException(NoCollectionNameException::class); @@ -342,7 +343,8 @@ public function testShouldRaiseExceptionWhenHasNoCollectionAndTryToCallAllFuncti public function testShouldRaiseExceptionWhenHasNoCollectionAndTryToCallFirstFunction() { - $entity = new class() extends ActiveRecord { + $entity = new class() extends AbstractActiveRecord + { }; $this->expectException(NoCollectionNameException::class); @@ -351,7 +353,8 @@ public function testShouldRaiseExceptionWhenHasNoCollectionAndTryToCallFirstFunc public function testShouldRaiseExceptionWhenHasNoCollectionAndTryToCallWhereFunction() { - $entity = new class() extends ActiveRecord { + $entity = new class() extends AbstractActiveRecord + { }; $this->expectException(NoCollectionNameException::class); @@ -365,7 +368,7 @@ public function testShouldGetCollectionName() public function testShouldAttachToAttribute() { - $entity = new class() extends ActiveRecord + $entity = new class() extends AbstractActiveRecord { /** * @var {inheritdoc} @@ -387,7 +390,7 @@ public function class() public function testShouldEmbedToAttribute() { - $this->entity = new class() extends ActiveRecord + $this->entity = new class() extends AbstractActiveRecord { /** * @var {inheritdoc} @@ -408,7 +411,7 @@ public function classes() public function testShouldThrowBadMethodCallExceptionWhenCallingInvalidMethod() { - $this->entity = new class() extends ActiveRecord + $this->entity = new class() extends AbstractActiveRecord { /** * @var {inheritdoc} diff --git a/tests/Unit/Model/AttributesTest.php b/tests/Unit/Model/HasAttributesTraitTest.php similarity index 92% rename from tests/Unit/Model/AttributesTest.php rename to tests/Unit/Model/HasAttributesTraitTest.php index e0c56e1e..3e16942e 100644 --- a/tests/Unit/Model/AttributesTest.php +++ b/tests/Unit/Model/HasAttributesTraitTest.php @@ -4,14 +4,14 @@ use Mongolid\TestCase; use stdClass; -class AttributesTest extends TestCase +class HasAttributesTraitTest extends TestCase { public function testShouldHaveDynamicSetters() { // Arrange $model = new class() { - use Attributes; + use HasAttributesTrait; }; $childObj = new stdClass(); @@ -42,7 +42,7 @@ public function testShouldHaveDynamicGetters() $model = new class($attributes) { - use Attributes; + use HasAttributesTrait; public function __construct(array $attributes) { @@ -62,7 +62,7 @@ public function testShouldCheckIfAttributeIsSet() // Arrange $model = new class(['name' => 'John', 'ignored' => null]) { - use Attributes; + use HasAttributesTrait; public function __construct(array $attributes) { @@ -81,7 +81,7 @@ public function testShouldCheckIfMutatedAttributeIsSet() // Arrange $model = new class() { - use Attributes; + use HasAttributesTrait; public function __construct() { @@ -104,7 +104,7 @@ public function testShouldUnsetAttributes() // Arrange $model = new class() { - use Attributes; + use HasAttributesTrait; public function __construct() { @@ -128,7 +128,7 @@ public function testShouldGetAttributeFromMutator() // Arrange $model = new class() { - use Attributes; + use HasAttributesTrait; public function __construct() { @@ -153,7 +153,7 @@ public function testShouldIgnoreMutators() // Arrange $model = new class() { - use Attributes; + use HasAttributesTrait; public function getShortNameDocumentAttribute() { @@ -178,7 +178,7 @@ public function testShouldSetAttributeFromMutator() // Arrange $model = new class() { - use Attributes; + use HasAttributesTrait; public function __construct() { @@ -209,7 +209,7 @@ public function testShouldFillOnlyPermittedAttributes( // Arrange $model = new class($fillable, $guarded) { - use Attributes; + use HasAttributesTrait; public function __construct(array $fillable, array $guarded) { @@ -230,7 +230,7 @@ public function testShouldForceFillAttributes() // Arrange $model = new class() { - use Attributes; + use HasAttributesTrait; }; $input = [ @@ -250,7 +250,7 @@ public function testShouldBeCastableToArray() // Arrange $model = new class() { - use Attributes; + use HasAttributesTrait; }; $model->name = 'John'; @@ -266,9 +266,9 @@ public function testShouldBeCastableToArray() public function testShouldSetOriginalAttributes() { // Arrange - $model = new class() implements AttributesAccessInterface + $model = new class() implements HasAttributesInterface { - use Attributes; + use HasAttributesTrait; }; $model->name = 'John'; @@ -284,9 +284,9 @@ public function testShouldSetOriginalAttributes() public function testShouldFallbackOriginalAttributesIfUnserializationFails() { // Arrange - $model = new class() implements AttributesAccessInterface + $model = new class() implements HasAttributesInterface { - use Attributes; + use HasAttributesTrait; public function __construct() { diff --git a/tests/Unit/Model/RelationsTest.php b/tests/Unit/Model/HasRelationsTraitTest.php similarity index 88% rename from tests/Unit/Model/RelationsTest.php rename to tests/Unit/Model/HasRelationsTraitTest.php index 67664081..2e61b7c4 100644 --- a/tests/Unit/Model/RelationsTest.php +++ b/tests/Unit/Model/HasRelationsTraitTest.php @@ -7,10 +7,10 @@ use Mongolid\Cursor\CursorInterface; use Mongolid\Cursor\EmbeddedCursor; use Mongolid\DataMapper\DataMapper; -use Mongolid\Schema\Schema; +use Mongolid\Schema\AbstractSchema; use Mongolid\TestCase; -class RelationsTest extends TestCase +class HasRelationsTraitTest extends TestCase { /** * @dataProvider referenceScenarios @@ -19,9 +19,9 @@ public function testShouldReferenceOne($entity, $field, $fieldValue, $useCache, { // Set $expectedQuery = $expectedQuery['referencesOne']; - $model = m::mock(ActiveRecord::class.'[]'); + $model = m::mock(AbstractActiveRecord::class.'[]'); $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)->makePartial()); - $result = new class() extends Schema { + $result = new class() extends AbstractSchema { }; $model->$field = $fieldValue; @@ -51,7 +51,7 @@ public function testShouldReferenceMany($entity, $field, $fieldValue, $useCache, { // Set $expectedQuery = $expectedQuery['referencesMany']; - $model = m::mock(ActiveRecord::class.'[]'); + $model = m::mock(AbstractActiveRecord::class.'[]'); $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)->makePartial()); $result = m::mock(CursorInterface::class); @@ -81,13 +81,13 @@ public function testShouldReferenceMany($entity, $field, $fieldValue, $useCache, public function testShouldEmbedsOne($entity, $field, $fieldValue, $expectedItems) { // Set - $model = m::mock(ActiveRecord::class.'[]'); + $model = m::mock(AbstractActiveRecord::class.'[]'); $cursorFactory = $this->instance(CursorFactory::class, m::mock(CursorFactory::class)); $cursor = m::mock(EmbeddedCursor::class); $document = $fieldValue; $model->$field = $document; - $instantiableClass = $entity instanceof Schema ? 'stdClass' : get_class($entity); + $instantiableClass = $entity instanceof AbstractSchema ? 'stdClass' : get_class($entity); // Act $cursorFactory->expects() @@ -109,13 +109,13 @@ public function testShouldEmbedsOne($entity, $field, $fieldValue, $expectedItems public function testShouldEmbedsMany($entity, $field, $fieldValue, $expectedItems) { // Set - $model = m::mock(ActiveRecord::class.'[]'); + $model = m::mock(AbstractActiveRecord::class.'[]'); $cursorFactory = $this->instance(CursorFactory::class, m::mock(CursorFactory::class)); $cursor = m::mock(EmbeddedCursor::class); $document = $fieldValue; $model->$field = $document; - $instantiableClass = $entity instanceof Schema ? 'stdClass' : get_class($entity); + $instantiableClass = $entity instanceof AbstractSchema ? 'stdClass' : get_class($entity); // Act $cursorFactory->expects() @@ -134,7 +134,7 @@ public function testShouldEmbeddedUnembedAttachAndDetachDocuments($method) { // Set $model = new class() { - use Relations; + use HasRelationsTrait; }; $document = m::mock(); $documentEmbedder = $this->instance(DocumentEmbedder::class, m::mock(DocumentEmbedder::class)); @@ -152,7 +152,7 @@ public function referenceScenarios() return [ // ------------------------- 'Schema referenced by numeric id' => [ - 'entity' => new class() extends Schema { + 'entity' => new class() extends AbstractSchema { }, 'field' => 'foo', 'fieldValue' => 12345, @@ -164,7 +164,7 @@ public function referenceScenarios() ], // ------------------------- 'ActiveRecord referenced by string id' => [ - 'entity' => new class() extends ActiveRecord { + 'entity' => new class() extends AbstractActiveRecord { /** * @var {inheritdoc} */ @@ -180,7 +180,7 @@ public function referenceScenarios() ], // ------------------------- 'Schema referenced by string objectId' => [ - 'entity' => new class() extends Schema { + 'entity' => new class() extends AbstractSchema { }, 'field' => 'foo', 'fieldValue' => ['553e3c80293fce6572ff2a40', '5571df31cf3fce544481a085'], @@ -192,7 +192,7 @@ public function referenceScenarios() ], // ------------------------- 'ActiveRecord referenced by objectId' => [ - 'entity' => new class() extends ActiveRecord { + 'entity' => new class() extends AbstractActiveRecord { /** * @var {inheritdoc} */ @@ -208,7 +208,7 @@ public function referenceScenarios() ], // ------------------------- 'Schema referenced with series of numeric ids' => [ - 'entity' => new class() extends Schema { + 'entity' => new class() extends AbstractSchema { }, 'field' => 'foo', 'fieldValue' => [1, 2, 3, 4, 5], @@ -220,7 +220,7 @@ public function referenceScenarios() ], // ------------------------- 'ActiveRecord referenced with series of string objectIds' => [ - 'entity' => new class() extends ActiveRecord { + 'entity' => new class() extends AbstractActiveRecord { /** * @var {inheritdoc} */ @@ -236,7 +236,7 @@ public function referenceScenarios() ], // ------------------------- 'Schema referenced with series of real objectIds' => [ - 'entity' => new class() extends Schema { + 'entity' => new class() extends AbstractSchema { }, 'field' => 'foo', 'fieldValue' => [new ObjectID('577afb0b4d3cec136058fa82'), new ObjectID('577afb7e4d3cec136258fa83')], @@ -248,7 +248,7 @@ public function referenceScenarios() ], // ------------------------- 'ActiveRecord referenced with null' => [ - 'entity' => new class() extends ActiveRecord { + 'entity' => new class() extends AbstractActiveRecord { /** * @var {inheritdoc} */ @@ -270,7 +270,7 @@ public function embedsScenarios() return [ // ------------------------- 'Embedded document referent to an Schema' => [ - 'entity' => new class() extends Schema { + 'entity' => new class() extends AbstractSchema { }, 'field' => 'foo', 'fieldValue' => ['_id' => 12345, 'name' => 'batata'], @@ -278,7 +278,7 @@ public function embedsScenarios() ], // ------------------------- 'Embedded documents referent to an Schema' => [ - 'entity' => new class() extends Schema { + 'entity' => new class() extends AbstractSchema { }, 'field' => 'foo', 'fieldValue' => [['_id' => 12345, 'name' => 'batata'], ['_id' => 67890, 'name' => 'bar']], @@ -286,7 +286,7 @@ public function embedsScenarios() ], // ------------------------- 'Embedded document referent to an ActiveRecord entity' => [ - 'entity' => new class() extends ActiveRecord { + 'entity' => new class() extends AbstractActiveRecord { /** * @var {inheritdoc} */ @@ -298,7 +298,7 @@ public function embedsScenarios() ], // ------------------------- 'Embedded documents referent to an ActiveRecord entity' => [ - 'entity' => new class() extends ActiveRecord { + 'entity' => new class() extends AbstractActiveRecord { /** * @var {inheritdoc} */ diff --git a/tests/Unit/Schema/SchemaTest.php b/tests/Unit/Schema/AbstractSchemaTest.php similarity index 87% rename from tests/Unit/Schema/SchemaTest.php rename to tests/Unit/Schema/AbstractSchemaTest.php index 5bcbfb60..a72e6bb8 100644 --- a/tests/Unit/Schema/SchemaTest.php +++ b/tests/Unit/Schema/AbstractSchemaTest.php @@ -7,12 +7,12 @@ use Mongolid\TestCase; use Mongolid\Util\SequenceService; -class SchemaTest extends TestCase +class AbstractSchemaTest extends TestCase { public function testShouldNotBeDynamicByDefault() { // Arrange - $schema = m::mock(Schema::class.'[]'); + $schema = m::mock(AbstractSchema::class.'[]'); // Assert $this->assertAttributeEquals(false, 'dynamic', $schema); @@ -21,7 +21,7 @@ public function testShouldNotBeDynamicByDefault() public function testMustHaveAnEntityClass() { // Arrange - $schema = m::mock(Schema::class.'[]'); + $schema = m::mock(AbstractSchema::class.'[]'); // Assert $this->assertAttributeEquals('stdClass', 'entityClass', $schema); @@ -30,7 +30,7 @@ public function testMustHaveAnEntityClass() public function testShouldCastNullIntoObjectId() { // Arrange - $schema = m::mock(Schema::class.'[]'); + $schema = m::mock(AbstractSchema::class.'[]'); $value = null; // Assert @@ -43,7 +43,7 @@ public function testShouldCastNullIntoObjectId() public function testShouldNotCastRandomStringIntoObjectId() { // Arrange - $schema = m::mock(Schema::class.'[]'); + $schema = m::mock(AbstractSchema::class.'[]'); $value = 'A random string'; // Assert @@ -56,7 +56,7 @@ public function testShouldNotCastRandomStringIntoObjectId() public function testShouldCastObjectIdStringIntoObjectId() { // Arrange - $schema = m::mock(Schema::class.'[]'); + $schema = m::mock(AbstractSchema::class.'[]'); $value = '507f1f77bcf86cd799439011'; // Assert @@ -74,7 +74,7 @@ public function testShouldCastObjectIdStringIntoObjectId() public function testShouldCastNullIntoAutoIncrementSequence() { // Arrange - $schema = m::mock(Schema::class.'[]'); + $schema = m::mock(AbstractSchema::class.'[]'); $sequenceService = $this->instance(SequenceService::class, m::mock(SequenceService::class)); $value = null; @@ -91,7 +91,7 @@ public function testShouldCastNullIntoAutoIncrementSequence() public function testShouldNotAutoIncrementSequenceIfValueIsNotNull() { - $schema = m::mock(Schema::class.'[]'); + $schema = m::mock(AbstractSchema::class.'[]'); $sequenceService = $this->instance(SequenceService::class, m::mock(SequenceService::class)); $value = 3; @@ -110,7 +110,7 @@ public function testShouldNotAutoIncrementSequenceIfValueIsNotNull() public function testShouldCastDocumentTimestamps() { // Arrange - $schema = m::mock(Schema::class.'[]'); + $schema = m::mock(AbstractSchema::class.'[]'); $value = null; // Assertion @@ -123,7 +123,7 @@ public function testShouldCastDocumentTimestamps() public function testShouldRefreshUpdatedAtTimestamps() { // Arrange - $schema = m::mock(Schema::class.'[]'); + $schema = m::mock(AbstractSchema::class.'[]'); $value = (new UTCDateTime(25)); // Assertion @@ -141,7 +141,7 @@ public function testShouldNotRefreshCreatedAtTimestamps( $compareTimestamp = true ) { // Arrange - $schema = m::mock(Schema::class.'[]'); + $schema = m::mock(AbstractSchema::class.'[]'); // Assertion $result = $schema->createdAtTimestamp($value); diff --git a/tests/Unit/Schema/DynamicSchemaTest.php b/tests/Unit/Schema/DynamicSchemaTest.php index 02c5a758..3a290775 100644 --- a/tests/Unit/Schema/DynamicSchemaTest.php +++ b/tests/Unit/Schema/DynamicSchemaTest.php @@ -12,7 +12,7 @@ public function testShouldExtendSchema() $schema = m::mock(DynamicSchema::class.'[]'); // Assert - $this->assertInstanceOf(Schema::class, $schema); + $this->assertInstanceOf(AbstractSchema::class, $schema); } public function testShouldBeDynamic() From b23f6a94db2134c7b4c24e5e67fd12ae83b41b75 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Thu, 8 Nov 2018 18:40:23 -0200 Subject: [PATCH 040/116] Improve a bit the relation between AbstractActiveRecord and HasAttributesTrait --- docs/datamapper.md | 4 +- docs/relationships.md | 6 +- src/DataMapper/EntityAssembler.php | 2 +- src/Model/AbstractActiveRecord.php | 43 +++ src/Model/HasAttributesInterface.php | 7 + src/Model/HasAttributesTrait.php | 140 +++------- src/Model/HasRelationsTrait.php | 2 +- .../DataMapper/AbstractSchemaMapperTest.php | 4 + tests/Unit/DataMapper/EntityAssemblerTest.php | 8 +- tests/Unit/Model/AbstractActiveRecordTest.php | 248 +++++++++++++++--- tests/Unit/Model/HasAttributesTraitTest.php | 183 ++++--------- 11 files changed, 364 insertions(+), 283 deletions(-) diff --git a/docs/datamapper.md b/docs/datamapper.md index f27c9f88..9185af6d 100644 --- a/docs/datamapper.md +++ b/docs/datamapper.md @@ -127,9 +127,9 @@ class MySchema extends Mongolid\Schema\Schema } ``` -### Schema definition for embeded documents +### Schema definition for embedded documents -By using the `"schema."` syntax you can create schemas and map Entities for embeded documents. For example, with the definition below: +By using the `"schema."` syntax you can create schemas and map Entities for embedded documents. For example, with the definition below: ```php class PostSchema extends Mongolid\Schema\Schema diff --git a/docs/relationships.md b/docs/relationships.md index d3c039d1..ab07d326 100644 --- a/docs/relationships.md +++ b/docs/relationships.md @@ -57,7 +57,7 @@ Which will translate to: In order to embed a document to be used in a Embeds One relationship, simply do the following: ```php -// The object that will be embeded +// The object that will be embedded $phone = new Phone(); $phone->regionCode = '55'; $phone->number = '1532323232'; @@ -159,7 +159,7 @@ $post->save(); > **Note:** When using Mongolid models you will need to call the `save()` method after embeding or attaching objects. The changes will only persists after you call the 'save()' method. -The `embed` method will include an `_id` to identify your embeded document and allow the usage of `embed` and `unembed` to update or delete embeded documents: +The `embed` method will include an `_id` to identify your embedded document and allow the usage of `embed` and `unembed` to update or delete embedded documents: ```php $commentB->content = "Pretty awesome!"; @@ -213,7 +213,7 @@ This statement will perform the following: In order to set a reference to a document, simply set the attribute used in the relationship to the reference's `_id` or use the attach method or it's alias. For example: ```php -// The object that will be embeded +// The object that will be embedded $user = new User(); $user->name = 'John'; $user->save() // This will populates the $user->_id diff --git a/src/DataMapper/EntityAssembler.php b/src/DataMapper/EntityAssembler.php index 37b38169..0a3cb326 100644 --- a/src/DataMapper/EntityAssembler.php +++ b/src/DataMapper/EntityAssembler.php @@ -85,7 +85,7 @@ protected function prepareOriginalAttributes($entity) /** * Assembly multiple documents for the given $schemaClass recursively. * - * @param mixed $value a value of an embeded field containing entity data to be assembled + * @param mixed $value a value of an embedded field containing entity data to be assembled * @param string $schemaClass the schemaClass to be used when assembling the entities within $value * * @return mixed diff --git a/src/Model/AbstractActiveRecord.php b/src/Model/AbstractActiveRecord.php index 35a3e467..fbd7e2d7 100644 --- a/src/Model/AbstractActiveRecord.php +++ b/src/Model/AbstractActiveRecord.php @@ -213,6 +213,49 @@ public function delete() return $this->execute('delete'); } + /** + * Dynamically retrieve attributes on the model. + * + * @param string $key name of the attribute + * + * @return mixed + */ + public function &__get(string $key) + { + return $this->getDocumentAttribute($key); + } + + /** + * Dynamically set attributes on the model. + * + * @param string $key attribute name + * @param mixed $value value to be set + */ + public function __set(string $key, $value) + { + $this->setDocumentAttribute($key, $value); + } + + /** + * Determine if an attribute exists on the model. + * + * @param string $key attribute name + */ + public function __isset(string $key): bool + { + return $this->hasDocumentAttribute($key); + } + + /** + * Unset an attribute on the model. + * + * @param string $key attribute name + */ + public function __unset(string $key) + { + $this->cleanDocumentAttribute($key); + } + /** * Handle dynamic method calls into the model. * diff --git a/src/Model/HasAttributesInterface.php b/src/Model/HasAttributesInterface.php index 10e928e2..e5579996 100644 --- a/src/Model/HasAttributesInterface.php +++ b/src/Model/HasAttributesInterface.php @@ -11,6 +11,13 @@ */ interface HasAttributesInterface { + /** + * Check if an attribute is set on the model. + * + * @param string $key the attribute to be checked + */ + public function hasDocumentAttribute(string $key): bool; + /** * Get an attribute from the model. * diff --git a/src/Model/HasAttributesTrait.php b/src/Model/HasAttributesTrait.php index 1f19edf7..4427d52a 100644 --- a/src/Model/HasAttributesTrait.php +++ b/src/Model/HasAttributesTrait.php @@ -62,19 +62,33 @@ trait HasAttributesTrait protected $mutableCache = []; /** - * Get an attribute from the model. - * - * @param string $key the attribute to be accessed - * - * @return mixed + * {@inheritdoc} */ - public function getDocumentAttribute(string $key) + public function hasDocumentAttribute(string $key): bool { - return $this->{$key}; + return !is_null($this->getDocumentAttribute($key)); + } + + /** + * {@inheritdoc} + */ + public function &getDocumentAttribute(string $key) + { + if ($this->mutable && $this->hasMutatorMethod($key, 'get')) { + $this->mutableCache[$key] = $this->{$this->buildMutatorMethod($key, 'get')}(); + + return $this->mutableCache[$key]; + } + + if (!array_key_exists($key, $this->attributes)) { + $this->attributes[$key] = null; + } + + return $this->attributes[$key]; } /** - * Get all attributes from the model. + * {@inheritdoc} */ public function getDocumentAttributes(): array { @@ -82,10 +96,7 @@ public function getDocumentAttributes(): array } /** - * Set the model attributes using an array. - * - * @param array $input the data that will be used to fill the attributes - * @param bool $force force fill + * {@inheritdoc} */ public function fill(array $input, bool $force = false) { @@ -98,9 +109,7 @@ public function fill(array $input, bool $force = false) } /** - * Set a given attribute on the model. - * - * @param string $key name of the attribute to be unset + * {@inheritdoc} */ public function cleanDocumentAttribute(string $key) { @@ -108,25 +117,19 @@ public function cleanDocumentAttribute(string $key) } /** - * Set a given attribute on the model. - * - * @param string $key name of the attribute to be set - * @param mixed $value value to be set + * {@inheritdoc} */ public function setDocumentAttribute(string $key, $value) { + if ($this->mutable && $this->hasMutatorMethod($key, 'set')) { + $value = $this->{$this->buildMutatorMethod($key, 'set')}($value); + } + $this->attributes[$key] = $value; } /** - * Stores original attributes from actual data from attributes - * to be used in future comparisons about changes. - * It tries to clone the attributes (using serialize/unserialize) - * so modifications to objects will be correctly identified - * as changes. - * - * Ideally should be called once right after retrieving data from - * the database. + * {@inheritdoc} */ public function syncOriginalDocumentAttributes() { @@ -138,7 +141,7 @@ public function syncOriginalDocumentAttributes() } /** - * Retrieve original attributes. + * {@inheritdoc} */ public function getOriginalDocumentAttributes(): array { @@ -146,83 +149,20 @@ public function getOriginalDocumentAttributes(): array } /** - * Returns the model instance as an Array. - * - * @return array + * {@inheritdoc} */ public function toArray(): array { return $this->getDocumentAttributes(); } - /** - * Dynamically retrieve attributes on the model. - * - * @param mixed $key name of the attribute - * - * @return mixed - */ - public function &__get($key) - { - if ($this->mutable && $this->hasMutatorMethod($key, 'get')) { - $this->mutableCache[$key] = $this->{$this->buildMutatorMethod($key, 'get')}(); - - return $this->mutableCache[$key]; - } - - if (!array_key_exists($key, $this->attributes)) { - $this->attributes[$key] = null; - } - - return $this->attributes[$key]; - } - - /** - * Dynamically set attributes on the model. - * - * @param mixed $key attribute name - * @param mixed $value value to be set - */ - public function __set($key, $value) - { - if ($this->mutable && $this->hasMutatorMethod($key, 'set')) { - $value = $this->{$this->buildMutatorMethod($key, 'set')}($value); - } - - $this->setDocumentAttribute($key, $value); - } - - /** - * Determine if an attribute exists on the model. - * - * @param mixed $key attribute name - * - * @return bool - */ - public function __isset($key) - { - return !is_null($this->{$key}); - } - - /** - * Unset an attribute on the model. - * - * @param mixed $key attribute name - */ - public function __unset($key) - { - $this->cleanDocumentAttribute($key); - } - /** * Verify if model has a mutator method defined. * - * @param mixed $key attribute name - * @param mixed $prefix method prefix to be used - * - * @return bool + * @param string $key attribute name + * @param string $prefix method prefix to be used (get, set) */ - protected function hasMutatorMethod($key, $prefix) + protected function hasMutatorMethod(string $key, $prefix): bool { $method = $this->buildMutatorMethod($key, $prefix); @@ -232,13 +172,11 @@ protected function hasMutatorMethod($key, $prefix) /** * Create mutator method pattern. * - * @param mixed $key attribute name - * @param mixed $prefix method prefix to be used - * - * @return string + * @param string $key attribute name + * @param string $prefix method prefix to be used (get, set) */ - protected function buildMutatorMethod($key, $prefix) + protected function buildMutatorMethod(string $key, string $prefix): string { - return $prefix.Str::camel($key).'DocumentAttribute'; + return $prefix.Str::studly($key).'DocumentAttribute'; } } diff --git a/src/Model/HasRelationsTrait.php b/src/Model/HasRelationsTrait.php index d13ae180..ccf4bd9a 100644 --- a/src/Model/HasRelationsTrait.php +++ b/src/Model/HasRelationsTrait.php @@ -128,7 +128,7 @@ public function embed(string $field, &$obj) * Removes an embedded document from the given field. It does that by using * the _id of the given $obj. * - * @param string $field name of the field where the $obj is embeded + * @param string $field name of the field where the $obj is embedded * @param mixed $obj document, model instance or _id */ public function unembed(string $field, &$obj) diff --git a/tests/Unit/DataMapper/AbstractSchemaMapperTest.php b/tests/Unit/DataMapper/AbstractSchemaMapperTest.php index 58b6f165..203841b6 100644 --- a/tests/Unit/DataMapper/AbstractSchemaMapperTest.php +++ b/tests/Unit/DataMapper/AbstractSchemaMapperTest.php @@ -317,6 +317,10 @@ public function syncOriginalDocumentAttributes() public function getOriginalDocumentAttributes(): array { } + + public function hasDocumentAttribute(string $key): bool + { + } }; // Assert diff --git a/tests/Unit/DataMapper/EntityAssemblerTest.php b/tests/Unit/DataMapper/EntityAssemblerTest.php index 328e3667..76cb26ae 100644 --- a/tests/Unit/DataMapper/EntityAssemblerTest.php +++ b/tests/Unit/DataMapper/EntityAssemblerTest.php @@ -68,7 +68,7 @@ public function entityAssemblerFixture() //--------------------------- - 'A schema containing an embeded schema but with null field' => [ + 'A schema containing an embedded schema but with null field' => [ 'inputValue' => [ // Data that will be used to assembly the entity '_id' => new ObjectID('507f1f77bcf86cd799439011'), 'name' => 'John Doe', @@ -76,7 +76,7 @@ public function entityAssemblerFixture() 'tests' => null, 'finalGrade' => 7.25, ], - 'availableSchmas' => [ // Schemas that will exist in the test context + 'availableSchemas' => [ // Schemas that will exist in the test context 'studentSchema' => [ 'entityClass' => StubStudent::class, 'fields' => [ @@ -108,7 +108,7 @@ public function entityAssemblerFixture() //--------------------------- - 'A stdClass with a schema containing an embeded schema with a document directly into the field' => [ + 'A stdClass with a schema containing an embedded schema with a document directly into the field' => [ 'inputValue' => (object) [ // Data that will be used to assembly the entity '_id' => new ObjectID('507f1f77bcf86cd799439011'), 'name' => 'John Doe', @@ -158,7 +158,7 @@ public function entityAssemblerFixture() //--------------------------- - 'A schema containing an embeded schema with multiple documents in the field' => [ + 'A schema containing an embedded schema with multiple documents in the field' => [ 'inputValue' => [ // Data that will be used to assembly the entity '_id' => new ObjectID('507f1f77bcf86cd799439011'), 'name' => 'John Doe', diff --git a/tests/Unit/Model/AbstractActiveRecordTest.php b/tests/Unit/Model/AbstractActiveRecordTest.php index efd7c9e3..87208422 100644 --- a/tests/Unit/Model/AbstractActiveRecordTest.php +++ b/tests/Unit/Model/AbstractActiveRecordTest.php @@ -56,7 +56,7 @@ protected function tearDown() public function testShouldHaveCorrectPropertiesByDefault() { - // Assert + // Assertions $this->assertAttributeEquals( [ '_id' => 'objectId', @@ -71,7 +71,7 @@ public function testShouldHaveCorrectPropertiesByDefault() public function testShouldImplementModelTraits() { - // Assert + // Assertions $this->assertSame( [HasAttributesTrait::class, HasRelationsTrait::class], array_keys(class_uses(AbstractActiveRecord::class)) @@ -80,10 +80,10 @@ public function testShouldImplementModelTraits() public function testShouldSave() { - // Arrage + // Set $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); - // Act + // Actions $dataMapper->expects() ->setSchema(m::type(DynamicSchema::class)); @@ -91,16 +91,16 @@ public function testShouldSave() ->save($this->entity, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); - // Assert + // Assertions $this->assertTrue($this->entity->save()); } public function testShouldInsert() { - // Arrage + // Set $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); - // Act + // Actions $dataMapper->expects() ->setSchema(m::type(DynamicSchema::class)); @@ -108,16 +108,16 @@ public function testShouldInsert() ->insert($this->entity, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); - // Assert + // Assertions $this->assertTrue($this->entity->insert()); } public function testShouldUpdate() { - // Arrage + // Set $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); - // Act + // Actions $dataMapper->expects() ->setSchema(m::type(DynamicSchema::class)); @@ -125,16 +125,16 @@ public function testShouldUpdate() ->update($this->entity, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); - // Assert + // Assertions $this->assertTrue($this->entity->update()); } public function testShouldDelete() { - // Arrage + // Set $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); - // Act + // Actions $dataMapper->expects() ->setSchema(m::type(DynamicSchema::class)); @@ -142,7 +142,7 @@ public function testShouldDelete() ->delete($this->entity, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); - // Assert + // Assertions $this->assertTrue($this->entity->delete()); } @@ -172,14 +172,14 @@ public function testDeleteShouldReturnFalseIfCollectionIsNull() public function testShouldGetWithWhereQuery() { - // Arrage + // Set $query = ['foo' => 'bar']; $projection = ['some', 'fields']; $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); $cursor = m::mock(CursorInterface::class); - // Act + // Actions $dataMapper->expects() ->setSchema(m::type(DynamicSchema::class)); @@ -187,18 +187,18 @@ public function testShouldGetWithWhereQuery() ->where($query, $projection, true) ->andReturn($cursor); - // Assert + // Assertions $this->assertSame($cursor, $this->entity->where($query, $projection, true)); } public function testShouldGetAll() { - // Arrage + // Set $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); $cursor = m::mock(CursorInterface::class); - // Act + // Actions $dataMapper->expects() ->setSchema(m::type(DynamicSchema::class)); @@ -206,18 +206,18 @@ public function testShouldGetAll() ->all() ->andReturn($cursor); - // Assert + // Assertions $this->assertSame($cursor, $this->entity->all()); } public function testShouldGetFirstWithQuery() { - // Arrage + // Set $query = ['foo' => 'bar']; $projection = ['some', 'fields']; $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); - // Act + // Actions $dataMapper->expects() ->setSchema(m::type(DynamicSchema::class)); @@ -225,18 +225,18 @@ public function testShouldGetFirstWithQuery() ->first($query, $projection, true) ->andReturn($this->entity); - // Assert + // Assertions $this->assertSame($this->entity, $this->entity->first($query, $projection, true)); } public function testShouldGetFirstOrFail() { - // Arrage + // Set $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); $query = ['foo' => 'bar']; $projection = ['some', 'fields']; - // Act + // Actions $dataMapper->expects() ->setSchema(m::type(DynamicSchema::class)); @@ -244,17 +244,17 @@ public function testShouldGetFirstOrFail() ->firstOrFail($query, $projection, true) ->andReturn($this->entity); - // Assert + // Assertions $this->assertSame($this->entity, $this->entity->firstOrFail($query, $projection, true)); } public function testShouldGetFirstOrNewAndReturnExistingModel() { - // Arrage + // Set $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); $id = 123; - // Act + // Actions $dataMapper->expects() ->setSchema(m::type(DynamicSchema::class)); @@ -262,17 +262,17 @@ public function testShouldGetFirstOrNewAndReturnExistingModel() ->first($id) ->andReturn($this->entity); - // Assert + // Assertions $this->assertSame($this->entity, $this->entity->firstOrNew($id)); } public function testShouldGetFirstOrNewAndReturnNewModel() { - // Arrage + // Set $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); $id = 123; - // Act + // Actions $dataMapper->expects() ->setSchema(m::type(DynamicSchema::class)); @@ -280,17 +280,17 @@ public function testShouldGetFirstOrNewAndReturnNewModel() ->first($id) ->andReturn(null); - // Assert + // Assertions $this->assertNotEquals($this->entity, $this->entity->firstOrNew($id)); } public function testShouldGetSchemaIfFieldsIsTheClassName() { - // Arrage + // Set $this->entity->setFields('MySchemaClass'); $schema = $this->instance('MySchemaClass', m::mock(AbstractSchema::class)); - // Assert + // Assertions $this->assertSame( $schema, $this->entity->getSchema() @@ -299,11 +299,11 @@ public function testShouldGetSchemaIfFieldsIsTheClassName() public function testShouldGetSchemaIfFieldsDescribesSchemaFields() { - // Arrage + // Set $fields = ['name' => 'string', 'age' => 'int']; $this->entity->setFields($fields); - // Assert + // Assertions $result = $this->entity->getSchema(); $this->assertInstanceOf(AbstractSchema::class, $result); $this->assertSame($fields, $result->fields); @@ -314,18 +314,18 @@ public function testShouldGetSchemaIfFieldsDescribesSchemaFields() public function testShouldGetDataMapper() { - // Arrage + // Set $entity = m::mock(AbstractActiveRecord::class.'[getSchema]'); $schema = m::mock(AbstractSchema::class.'[]'); - // Act + // Actions $entity->shouldAllowMockingProtectedMethods(); $entity->expects() ->getSchema() ->andReturn($schema); - // Assert + // Assertions $result = $this->callProtected($entity, 'getDataMapper'); $this->assertInstanceOf(DataMapper::class, $result); $this->assertSame($schema, $result->getSchema()); @@ -430,4 +430,174 @@ public function testShouldGetSetWriteConcernInActiveRecordClass() $this->entity->setWriteConcern(0); $this->assertSame(0, $this->entity->getWriteConcern()); } + + public function testShouldHaveDynamicSetters() + { + // Set + $model = new class() extends AbstractActiveRecord + { + }; + + $childObj = new stdClass(); + + // Assertions + $model->name = 'John'; + $model->age = 25; + $model->child = $childObj; + $this->assertSame( + [ + 'name' => 'John', + 'age' => 25, + 'child' => $childObj, + ], + $model->getDocumentAttributes() + ); + } + + public function testShouldHaveDynamicGetters() + { + // Set + $child = new stdClass(); + $model = new class() extends AbstractActiveRecord + { + }; + $model->fill( + [ + 'name' => 'John', + 'age' => 25, + 'child' => $child, + ] + ); + + // Assertions + $this->assertSame('John', $model->name); + $this->assertSame(25, $model->age); + $this->assertSame($child, $model->child); + $this->assertSame(null, $model->nonexistant); + } + + public function testShouldCheckIfAttributeIsSet() + { + // Set + $model = new class() extends AbstractActiveRecord + { + }; + $model->fill(['name' => 'John', 'ignored' => null]); + + // Assertions + $this->assertTrue(isset($model->name)); + $this->assertFalse(isset($model->nonexistant)); + $this->assertFalse(isset($model->ignored)); + } + + public function testShouldCheckIfMutatedAttributeIsSet() + { + // Set + $model = new class() extends AbstractActiveRecord + { + /** + * {@inheritdoc} + */ + public $mutable = true; + + public function getNameDocumentAttribute() + { + return 'John'; + } + }; + + // Assertions + $this->assertTrue(isset($model->name)); + $this->assertFalse(isset($model->nonexistant)); + } + + public function testShouldUnsetAttributes() + { + // Set + $model = new class() extends AbstractActiveRecord + { + }; + $model->fill( + [ + 'name' => 'John', + 'age' => 25, + ] + ); + + // Actions + unset($model->age); + $result = $model->getDocumentAttributes(); + + // Assertions + $this->assertSame(['name' => 'John'], $result); + } + + public function testShouldGetAttributeFromMutator() + { + // Set + $model = new class() extends AbstractActiveRecord + { + /** + * {@inheritdoc} + */ + public $mutable = true; + + public function getShortNameDocumentAttribute() + { + return 'Other name'; + } + }; + + // Actions + $model->short_name = 'My awesome name'; + $result = $model->short_name; + + // Assertions + $this->assertSame('Other name', $result); + } + + public function testShouldIgnoreMutators() + { + // Set + $model = new class() extends AbstractActiveRecord + { + public function getShortNameDocumentAttribute() + { + return 'Other name'; + } + + public function setShortNameDocumentAttribute($value) + { + return strtoupper($value); + } + }; + + $model->short_name = 'My awesome name'; + + // Assertions + $this->assertSame('My awesome name', $model->short_name); + } + + public function testShouldSetAttributeFromMutator() + { + // Arrange + $model = new class() extends AbstractActiveRecord + { + /** + * {@inheritdoc} + */ + protected $mutable = true; + + public function setShortNameDocumentAttribute($value) + { + return strtoupper($value); + } + }; + + $model->short_name = 'My awesome name'; + $result = $model->short_name; + + // Assert + $this->assertSame('MY AWESOME NAME', $result); + } } diff --git a/tests/Unit/Model/HasAttributesTraitTest.php b/tests/Unit/Model/HasAttributesTraitTest.php index 3e16942e..d19eb1ac 100644 --- a/tests/Unit/Model/HasAttributesTraitTest.php +++ b/tests/Unit/Model/HasAttributesTraitTest.php @@ -2,130 +2,12 @@ namespace Mongolid\Model; use Mongolid\TestCase; -use stdClass; class HasAttributesTraitTest extends TestCase { - public function testShouldHaveDynamicSetters() - { - // Arrange - $model = new class() - { - use HasAttributesTrait; - }; - - $childObj = new stdClass(); - - // Assert - $model->name = 'John'; - $model->age = 25; - $model->child = $childObj; - $this->assertSame( - [ - 'name' => 'John', - 'age' => 25, - 'child' => $childObj, - ], - $model->getDocumentAttributes() - ); - } - - public function testShouldHaveDynamicGetters() - { - // Arrange - $child = new stdClass(); - $attributes = [ - 'name' => 'John', - 'age' => 25, - 'child' => $child, - ]; - - $model = new class($attributes) - { - use HasAttributesTrait; - - public function __construct(array $attributes) - { - $this->attributes = $attributes; - } - }; - - // Assert - $this->assertEquals('John', $model->name); - $this->assertEquals(25, $model->age); - $this->assertEquals($child, $model->child); - $this->assertEquals(null, $model->nonexistant); - } - - public function testShouldCheckIfAttributeIsSet() - { - // Arrange - $model = new class(['name' => 'John', 'ignored' => null]) - { - use HasAttributesTrait; - - public function __construct(array $attributes) - { - $this->attributes = $attributes; - } - }; - - // Assert - $this->assertTrue(isset($model->name)); - $this->assertFalse(isset($model->nonexistant)); - $this->assertFalse(isset($model->ignored)); - } - - public function testShouldCheckIfMutatedAttributeIsSet() - { - // Arrange - $model = new class() - { - use HasAttributesTrait; - - public function __construct() - { - $this->mutable = true; - } - - public function getNameDocumentAttribute() - { - return 'John'; - } - }; - - // Assert - $this->assertTrue(isset($model->name)); - $this->assertFalse(isset($model->nonexistant)); - } - - public function testShouldUnsetAttributes() - { - // Arrange - $model = new class() - { - use HasAttributesTrait; - - public function __construct() - { - $this->attributes = [ - 'name' => 'John', - 'age' => 25, - ]; - } - }; - - // Act - unset($model->age); - $result = $model->getDocumentAttributes(); - - // Assert - $this->assertSame(['name' => 'John'], $result); - } - public function testShouldGetAttributeFromMutator() { - // Arrange + // Set $model = new class() { use HasAttributesTrait; @@ -141,11 +23,11 @@ public function getShortNameDocumentAttribute() } }; - $model->short_name = 'My awesome name'; + $model->setDocumentAttribute('short_name', 'My awesome name'); + $result = $model->getDocumentAttribute('short_name'); - // Assert - $this->assertEquals('Other name', $model->short_name); - $this->assertEquals('Other name', $model->getDocumentAttribute('short_name')); + // Assertions + $this->assertSame('Other name', $result); } public function testShouldIgnoreMutators() @@ -166,11 +48,11 @@ public function setShortNameDocumentAttribute($value) } }; - $model->short_name = 'My awesome name'; + $model->setDocumentAttribute('short_name', 'My awesome name'); + $result = $model->getDocumentAttribute('short_name'); // Assert - $this->assertEquals('My awesome name', $model->short_name); - $this->assertEquals('My awesome name', $model->getDocumentAttribute('short_name')); + $this->assertSame('My awesome name', $result); } public function testShouldSetAttributeFromMutator() @@ -191,10 +73,11 @@ public function setShortNameDocumentAttribute($value) } }; - $model->short_name = 'My awesome name'; + $model->setDocumentAttribute('short_name', 'My awesome name'); + $result = $model->getDocumentAttribute('short_name'); // Assert - $this->assertSame('MY AWESOME NAME', $model->short_name); + $this->assertSame('MY AWESOME NAME', $result); } /** @@ -242,7 +125,7 @@ public function testShouldForceFillAttributes() $model->fill($input, true); // Assert - $this->assertTrue($model->not_allowed_attribute); + $this->assertTrue($model->getDocumentAttribute('not_allowed_attribute')); } public function testShouldBeCastableToArray() @@ -253,11 +136,11 @@ public function testShouldBeCastableToArray() use HasAttributesTrait; }; - $model->name = 'John'; - $model->age = 25; + $model->setDocumentAttribute('name', 'John'); + $model->setDocumentAttribute('age', 25); // Assert - $this->assertEquals( + $this->assertSame( ['name' => 'John', 'age' => 25], $model->toArray() ); @@ -366,4 +249,40 @@ public function getFillableOptions() ], ]; } + + + public function testShouldCheckIfAttributeIsSet() + { + // Set + $model = new class() extends AbstractActiveRecord + { + }; + $model->fill(['name' => 'John', 'ignored' => null]); + + // Assertions + $this->assertTrue(isset($model->name)); + $this->assertFalse(isset($model->nonexistant)); + $this->assertFalse(isset($model->ignored)); + } + + public function testShouldCheckIfMutatedAttributeIsSet() + { + // Set + $model = new class() extends AbstractActiveRecord + { + /** + * {@inheritdoc} + */ + public $mutable = true; + + public function getNameDocumentAttribute() + { + return 'John'; + } + }; + + // Assertions + $this->assertTrue(isset($model->name)); + $this->assertFalse(isset($model->nonexistant)); + } } From b76436ed95435210b90af0192038647c234401c2 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Fri, 9 Nov 2018 15:52:05 -0200 Subject: [PATCH 041/116] New draft for relations --- src/Model/AbstractActiveRecord.php | 40 +----- src/Model/HasAttributesTrait.php | 21 ++- src/Model/HasRelationsTrait.php | 134 +++++------------- src/Model/Relations/AbstractRelation.php | 65 +++++++++ src/Model/Relations/EmbedsMany.php | 53 +++++++ src/Model/Relations/EmbedsOne.php | 27 ++++ src/Model/Relations/ReferencesMany.php | 76 ++++++++++ src/Model/Relations/ReferencesOne.php | 32 +++++ src/Util/ObjectIdUtils.php | 6 + .../ReferencesManyRelationTest.php | 20 +-- .../Integration/ReferencesOneRelationTest.php | 15 +- tests/Integration/Stubs/User.php | 4 +- 12 files changed, 339 insertions(+), 154 deletions(-) create mode 100644 src/Model/Relations/AbstractRelation.php create mode 100644 src/Model/Relations/EmbedsMany.php create mode 100644 src/Model/Relations/EmbedsOne.php create mode 100644 src/Model/Relations/ReferencesMany.php create mode 100644 src/Model/Relations/ReferencesOne.php diff --git a/src/Model/AbstractActiveRecord.php b/src/Model/AbstractActiveRecord.php index fbd7e2d7..851f848b 100644 --- a/src/Model/AbstractActiveRecord.php +++ b/src/Model/AbstractActiveRecord.php @@ -1,7 +1,6 @@ cleanDocumentAttribute($key); } - /** - * Handle dynamic method calls into the model. - * - * @param mixed $method name of the method that is being called - * @param mixed $parameters parameters of $method - * - * @throws BadMethodCallException In case of invalid methods be called - * - * @return mixed - */ - public function __call($method, $parameters) - { - $value = $parameters[0] ?? null; - - // Alias to attach - if ('attachTo' == substr($method, 0, 8)) { - $field = lcfirst(substr($method, 8)); - - return $this->attach($field, $value); - } - - // Alias to embed - if ('embedTo' == substr($method, 0, 7)) { - $field = lcfirst(substr($method, 7)); - - return $this->embed($field, $value); - } - - throw new BadMethodCallException( - sprintf( - 'The following method can not be reached or does not exist: %s@%s', - static::class, - $method - ) - ); - } - /** * Returns a DataMapper configured with the Schema and collection described * in this entity. @@ -373,7 +335,7 @@ protected function instantiateSchemaInFields() /** * Performs the given action into database. * - * @param string $action datamapper function to execute + * @param string $action DataMapper function to execute * * @return bool */ diff --git a/src/Model/HasAttributesTrait.php b/src/Model/HasAttributesTrait.php index 4427d52a..548b7fd1 100644 --- a/src/Model/HasAttributesTrait.php +++ b/src/Model/HasAttributesTrait.php @@ -80,13 +80,30 @@ public function &getDocumentAttribute(string $key) return $this->mutableCache[$key]; } - if (!array_key_exists($key, $this->attributes)) { - $this->attributes[$key] = null; + if (array_key_exists($key, $this->attributes)) { + return $this->attributes[$key]; } + if (!method_exists(self::class, $key) && method_exists($this, $key)) { + return $this->getRelationValue($key); + } + + $this->attributes[$key] = null; + return $this->attributes[$key]; } + private function &getRelationValue(string $method) + { + if (!$this->relationLoaded($method)) { + $relation = $this->$method(); + + $this->setRelation($method, $relation->getResults()); + } + + return $this->getRelation($method); + } + /** * {@inheritdoc} */ diff --git a/src/Model/HasRelationsTrait.php b/src/Model/HasRelationsTrait.php index ccf4bd9a..32daefba 100644 --- a/src/Model/HasRelationsTrait.php +++ b/src/Model/HasRelationsTrait.php @@ -1,47 +1,33 @@ $field; - - if (is_array($referenced_id) && isset($referenced_id[0])) { - $referenced_id = $referenced_id[0]; - } - - $entityInstance = Ioc::make($entity); - - if ($entityInstance instanceof AbstractSchema) { - $dataMapper = Ioc::make(DataMapper::class); - $dataMapper->setSchema($entityInstance); - - return $dataMapper->first(['_id' => $referenced_id], [], $cacheable); - } - - return $entityInstance::first(['_id' => $referenced_id], [], $cacheable); + return new ReferencesOne($this, $entity, $field, $cacheable); } /** @@ -51,28 +37,9 @@ protected function referencesOne(string $entity, string $field, bool $cacheable * @param string $field the field where the _ids are stored * @param bool $cacheable retrieves a CacheableCursor instead */ - protected function referencesMany(string $entity, string $field, bool $cacheable = true): CursorInterface + protected function referencesMany(string $entity, string $field, bool $cacheable = true): ReferencesMany { - $referencedIds = (array) $this->$field; - - if (ObjectIdUtils::isObjectId($referencedIds[0] ?? '')) { - foreach ($referencedIds as $key => $value) { - $referencedIds[$key] = new ObjectId($value); - } - } - - $query = ['_id' => ['$in' => array_values($referencedIds)]]; - - $entityInstance = Ioc::make($entity); - - if ($entityInstance instanceof AbstractSchema) { - $dataMapper = Ioc::make(DataMapper::class); - $dataMapper->setSchema($entityInstance); - - return $dataMapper->where($query, [], $cacheable); - } - - return $entityInstance::where($query, [], $cacheable); + return new ReferencesMany($this, $entity, $field, $cacheable); } /** @@ -80,12 +47,10 @@ protected function referencesMany(string $entity, string $field, bool $cacheable * * @param string $entity class of the entity or of the schema of the entity * @param string $field field where the embedded document is stored - * - * @return mixed */ - protected function embedsOne(string $entity, string $field) + protected function embedsOne(string $entity, string $field): EmbedsOne { - return $this->embedsMany($entity, $field)->first(); + return new EmbedsOne($this, $entity, $field); } /** @@ -93,73 +58,52 @@ protected function embedsOne(string $entity, string $field) * * @param string $entity class of the entity or of the schema of the entity * @param string $field field where the embedded documents are stored - * - * @return EmbeddedCursor Array with the embedded documents */ - protected function embedsMany(string $entity, string $field) + protected function embedsMany(string $entity, string $field): EmbedsMany { - if (is_subclass_of($entity, AbstractSchema::class)) { - $entity = (new $entity())->entityClass; - } - - $items = (array) $this->$field; - if (false === empty($items) && false === array_key_exists(0, $items)) { - $items = [$items]; - } - - return Ioc::make(CursorFactory::class) - ->createEmbeddedCursor($entity, $items); + return new EmbedsMany($this, $entity, $field); } /** - * Embed a new document to an attribute. It will also generate an - * _id for the document if it's not present. + * Get a specified relationship. * - * @param string $field field to where the $obj will be embedded - * @param mixed $obj document or model instance + * @return mixed */ - public function embed(string $field, &$obj) + public function getRelation(string $relation) { - $embedder = Ioc::make(DocumentEmbedder::class); - $embedder->embed($this, $field, $obj); + return $this->relations[$relation]; } /** - * Removes an embedded document from the given field. It does that by using - * the _id of the given $obj. - * - * @param string $field name of the field where the $obj is embedded - * @param mixed $obj document, model instance or _id + * Determine if the given relation is loaded. */ - public function unembed(string $field, &$obj) + public function relationLoaded(string $key): bool { - $embedder = Ioc::make(DocumentEmbedder::class); - $embedder->unembed($this, $field, $obj); + return array_key_exists($key, $this->relations); } /** - * Attach document _id reference to an attribute. It will also generate an - * _id for the document if it's not present. + * Set the given relationship on the model. * - * @param string $field name of the field where the reference will be stored - * @param mixed $obj document, model instance or _id to be referenced + * @param mixed $value */ - public function attach(string $field, &$obj) + public function setRelation(string $relation, $value) { - $embedder = Ioc::make(DocumentEmbedder::class); - $embedder->attach($this, $field, $obj); + $this->relations[$relation] = $value; } /** - * Removes a document _id reference from an attribute. It will remove the - * _id of the given $obj from inside the given $field. + * Unset a loaded relationship. * - * @param string $field field where the reference is stored - * @param mixed $obj document, model instance or _id that have been referenced by $field + * @param string $relation */ - public function detach(string $field, &$obj) + public function unsetRelation($relation) + { + unset($this->relations[$relation]); + } + + public function getRelations(): array { - $embedder = Ioc::make(DocumentEmbedder::class); - $embedder->detach($this, $field, $obj); + return $this->relations; } } diff --git a/src/Model/Relations/AbstractRelation.php b/src/Model/Relations/AbstractRelation.php new file mode 100644 index 00000000..95fb37d1 --- /dev/null +++ b/src/Model/Relations/AbstractRelation.php @@ -0,0 +1,65 @@ +relationName = $this->guessRelationName(); + $this->parent = $parent; + $this->entity = $entity; + $this->field = $field; + + $this->documentEmbedder = Ioc::make(DocumentEmbedder::class); + } + + abstract public function getResults(); + + /** + * @return mixed + */ + private function guessRelationName() + { + $functionName = __FUNCTION__; + + return collect(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5)) + ->pluck('function') + ->first( + function ($value) use ($functionName) { + return !in_array( + $value, + [$functionName, '__construct', 'referencesOne', 'referencesMany', 'embedsOne', 'embedsMany'] + ); + } + ); + } +} diff --git a/src/Model/Relations/EmbedsMany.php b/src/Model/Relations/EmbedsMany.php new file mode 100644 index 00000000..182b9993 --- /dev/null +++ b/src/Model/Relations/EmbedsMany.php @@ -0,0 +1,53 @@ +documentEmbedder->embed($this->parent, $this->field, $entity); + $this->parent->unsetRelation($this->relationName); + } + + /** + * Removes an embedded document from the given field. It does that by using + * the _id of given $entity. + * + * @param mixed $entity model or _id + */ + public function remove($entity) + { + $this->documentEmbedder->unembed($this->parent, $this->field, $entity); + $this->parent->unsetRelation($this->relationName); + } + + public function removeAll() + { + unset($this->parent->{$this->field}); + $this->parent->unsetRelation($this->relationName); + } + + + public function getResults() + { + $items = (array) $this->parent->{$this->field}; + + if (!empty($items) && !array_key_exists(0, $items)) { + $items = [$items]; + } + + $cursor = Ioc::make(CursorFactory::class) + ->createEmbeddedCursor($this->entity, $items); + + return $cursor->all(); + } +} diff --git a/src/Model/Relations/EmbedsOne.php b/src/Model/Relations/EmbedsOne.php new file mode 100644 index 00000000..beb525a6 --- /dev/null +++ b/src/Model/Relations/EmbedsOne.php @@ -0,0 +1,27 @@ +removeAll(); + } + + public function getResults() + { + $items = (array) $this->parent->{$this->field}; + + if (!empty($items) && !array_key_exists(0, $items)) { + $items = [$items]; + } + + $cursor = Ioc::make(CursorFactory::class) + ->createEmbeddedCursor($this->entity, $items); + + return $cursor->all(); + } +} diff --git a/src/Model/Relations/ReferencesMany.php b/src/Model/Relations/ReferencesMany.php new file mode 100644 index 00000000..19894e40 --- /dev/null +++ b/src/Model/Relations/ReferencesMany.php @@ -0,0 +1,76 @@ +cacheable = $cacheable; + $this->entityInstance = Ioc::make($this->entity); + } + /** + * Attach document _id reference to an attribute. It will also generate an + * _id for the document if it's not present. + * + * @param mixed $entity model instance or _id to be referenced + */ + public function attach($entity) + { + $this->documentEmbedder->attach($this->parent, $this->field, $entity); + $this->parent->unsetRelation($this->relationName); + } + + /** + * Removes a document _id reference from an attribute. It will remove the + * _id of the given $entity from inside the given $field. + * + * @param mixed $entity document, model instance or _id that have been referenced by $field + */ + public function detach($entity) + { + $this->documentEmbedder->detach($this->parent, $this->field, $entity); + $this->parent->unsetRelation($this->relationName); + } + + /** + * Removes all document references from relation. + */ + public function detachAll() + { + unset($this->parent->{$this->field}); + $this->parent->unsetRelation($this->relationName); + } + + public function getResults() + { + $referencedIds = (array) $this->parent->{$this->field}; + + if (ObjectIdUtils::isObjectId($referencedIds[0] ?? '')) { + foreach ($referencedIds as $key => $value) { + $referencedIds[$key] = new ObjectId((string) $value); + } + } + + return $this->entityInstance->where( + ['_id' => ['$in' => array_values($referencedIds)]], + [], + $this->cacheable + ); + } +} diff --git a/src/Model/Relations/ReferencesOne.php b/src/Model/Relations/ReferencesOne.php new file mode 100644 index 00000000..e8d02ef2 --- /dev/null +++ b/src/Model/Relations/ReferencesOne.php @@ -0,0 +1,32 @@ +detachAll(); + } + + public function getResults() + { + $referencedId = $this->parent->{$this->field}; + + if (is_array($referencedId) && isset($referencedId[0])) { + $referencedId = $referencedId[0]; + } + + if (ObjectIdUtils::isObjectId($referencedId)) { + $referencedId = new ObjectId((string) $referencedId); + } + + return $this->entityInstance->first( + ['_id' => $referencedId], + [], + $this->cacheable + ); + } +} diff --git a/src/Util/ObjectIdUtils.php b/src/Util/ObjectIdUtils.php index 0a8b01ab..184b0d3e 100644 --- a/src/Util/ObjectIdUtils.php +++ b/src/Util/ObjectIdUtils.php @@ -1,6 +1,8 @@ createUser('Chuck'); $john = $this->createUser('John'); - $john->attach('siblings', $chuck); + $john->siblings()->attach($chuck); $this->assertSiblings([$chuck], $john); // hit cache $this->assertSiblings([$chuck], $john); $mary = $this->createUser('Mary'); - $john->attach('siblings', $mary); + $john->siblings()->attach($mary); $this->assertSiblings([$chuck, $mary], $john); // hit cache $this->assertSiblings([$chuck, $mary], $john); // remove one sibling - $john->unembed('siblings', $chuck); + $john->siblings()->detach($chuck); $this->assertSiblings([$mary], $john); // hit cache $this->assertSiblings([$mary], $john); // replace siblings $bob = $this->createUser('Bob'); - unset($john->siblings); - $john->attach('siblings', $bob); + // unset($john->siblings_ids); // TODO make this work! + $john->siblings()->detachAll(); + $this->assertEmpty($john->siblings->all()); + $john->siblings()->attach($bob); $this->assertSiblings([$bob], $john); // hit cache $this->assertSiblings([$bob], $john); // remove with unembed - $john->unembed('siblings', $bob); + $john->siblings()->detach($bob); - $this->assertEmpty($john->siblings()->all()); + $this->assertEmpty($john->siblings->all()); } private function createUser(string $name): User @@ -58,7 +60,7 @@ private function createUser(string $name): User private function assertSiblings($expected, User $model) { - $siblings = $model->siblings(); + $siblings = $model->siblings; $this->assertInstanceOf(CursorInterface::class, $siblings); $this->assertEquals($expected, $siblings->all()); } diff --git a/tests/Integration/ReferencesOneRelationTest.php b/tests/Integration/ReferencesOneRelationTest.php index a5930d9b..4c4311d5 100644 --- a/tests/Integration/ReferencesOneRelationTest.php +++ b/tests/Integration/ReferencesOneRelationTest.php @@ -11,7 +11,7 @@ public function testShouldRetrieveParentOfUser() // create parent $chuck = $this->createUser('Chuck'); $john = $this->createUser('John'); - $john->attach('parent', $chuck); + $john->parent()->attach($chuck); $this->assertParent($chuck, $john); // hit cache @@ -19,17 +19,18 @@ public function testShouldRetrieveParentOfUser() // replace parent $bob = $this->createUser('Bob'); - unset($john->parent); - $john->attach('parent', $bob); + $john->parent()->detach(); //todo remove this line and ensure only one parent is attached + $john->parent()->attach($bob); $this->assertParent($bob, $john); // hit cache $this->assertParent($bob, $john); - // remove with unembed - $john->unembed('parent', $bob); + // remove + //unset($john->parent_id);// TODO make this work! + $john->parent()->detach(); - $this->assertNull($john->parent()); + $this->assertNull($john->parent); } private function createUser(string $name): User @@ -44,7 +45,7 @@ private function createUser(string $name): User private function assertParent($expected, User $model) { - $parent = $model->parent(); + $parent = $model->parent; $this->assertInstanceOf(User::class, $parent); $this->assertEquals($expected, $parent); } diff --git a/tests/Integration/Stubs/User.php b/tests/Integration/Stubs/User.php index f40e2d1f..e49a91f7 100644 --- a/tests/Integration/Stubs/User.php +++ b/tests/Integration/Stubs/User.php @@ -30,11 +30,11 @@ public function collection(): Collection public function parent() { - return $this->referencesOne(User::class, 'parent'); + return $this->referencesOne(User::class, 'parent_id'); } public function siblings() { - return $this->referencesMany(User::class, 'siblings'); + return $this->referencesMany(User::class, 'siblings_ids'); } } From 92863d0474935e6c382cf18ed120298d619b8c82 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 12 Nov 2018 11:31:08 -0200 Subject: [PATCH 042/116] Fix HasRelationsTraitTest tests --- phpcs.xml | 2 + src/Model/HasRelationsTrait.php | 2 +- src/Model/Relations/AbstractRelation.php | 2 +- src/Model/Relations/EmbedsMany.php | 4 +- src/Model/Relations/EmbedsOne.php | 4 +- src/Util/ObjectIdUtils.php | 2 +- tests/Unit/DataMapper/DataMapperTest.php | 12 +- tests/Unit/DataMapper/EntityAssemblerTest.php | 34 +- tests/Unit/Model/AbstractActiveRecordTest.php | 60 ---- tests/Unit/Model/DocumentEmbedderTest.php | 62 ++-- tests/Unit/Model/HasRelationsTraitTest.php | 313 ++++++------------ tests/Unit/Schema/AbstractSchemaTest.php | 6 +- tests/Unit/TestCase.php | 2 +- tests/Unit/Util/ObjectIdUtilsTest.php | 4 +- 14 files changed, 166 insertions(+), 343 deletions(-) diff --git a/phpcs.xml b/phpcs.xml index e6c77911..948bf4b5 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -18,9 +18,11 @@ tests/Unit/DataMapper/EntityAssemblerTest.php + tests/Unit/Model/HasRelationsTraitTest.php tests/Unit/DataMapper/EntityAssemblerTest.php + tests/Unit/Model/HasRelationsTraitTest.php diff --git a/src/Model/HasRelationsTrait.php b/src/Model/HasRelationsTrait.php index 32daefba..26096749 100644 --- a/src/Model/HasRelationsTrait.php +++ b/src/Model/HasRelationsTrait.php @@ -69,7 +69,7 @@ protected function embedsMany(string $entity, string $field): EmbedsMany * * @return mixed */ - public function getRelation(string $relation) + public function &getRelation(string $relation) { return $this->relations[$relation]; } diff --git a/src/Model/Relations/AbstractRelation.php b/src/Model/Relations/AbstractRelation.php index 95fb37d1..0431934f 100644 --- a/src/Model/Relations/AbstractRelation.php +++ b/src/Model/Relations/AbstractRelation.php @@ -45,7 +45,7 @@ public function __construct(HasAttributesInterface $parent, string $entity, stri abstract public function getResults(); /** - * @return mixed + * @return string|null */ private function guessRelationName() { diff --git a/src/Model/Relations/EmbedsMany.php b/src/Model/Relations/EmbedsMany.php index 182b9993..c372778b 100644 --- a/src/Model/Relations/EmbedsMany.php +++ b/src/Model/Relations/EmbedsMany.php @@ -45,9 +45,7 @@ public function getResults() $items = [$items]; } - $cursor = Ioc::make(CursorFactory::class) + return Ioc::make(CursorFactory::class) ->createEmbeddedCursor($this->entity, $items); - - return $cursor->all(); } } diff --git a/src/Model/Relations/EmbedsOne.php b/src/Model/Relations/EmbedsOne.php index beb525a6..ec042bdf 100644 --- a/src/Model/Relations/EmbedsOne.php +++ b/src/Model/Relations/EmbedsOne.php @@ -19,9 +19,7 @@ public function getResults() $items = [$items]; } - $cursor = Ioc::make(CursorFactory::class) + return Ioc::make(CursorFactory::class) ->createEmbeddedCursor($this->entity, $items); - - return $cursor->all(); } } diff --git a/src/Util/ObjectIdUtils.php b/src/Util/ObjectIdUtils.php index 184b0d3e..f40ea1d0 100644 --- a/src/Util/ObjectIdUtils.php +++ b/src/Util/ObjectIdUtils.php @@ -12,7 +12,7 @@ class ObjectIdUtils /** * Checks if the given value can be a valid ObjectId. * - * @param mixed $value string to be evaluated if it can be used as a valid ObjectID + * @param mixed $value string to be evaluated if it can be used as a valid ObjectId * * @return bool true if is valid */ diff --git a/tests/Unit/DataMapper/DataMapperTest.php b/tests/Unit/DataMapper/DataMapperTest.php index a722256c..3ec2b541 100644 --- a/tests/Unit/DataMapper/DataMapperTest.php +++ b/tests/Unit/DataMapper/DataMapperTest.php @@ -3,7 +3,7 @@ use InvalidArgumentException; use Mockery as m; -use MongoDB\BSON\ObjectID; +use MongoDB\BSON\ObjectId; use MongoDB\Client; use MongoDB\Collection; use MongoDB\Database; @@ -949,12 +949,12 @@ public function queryValueScenarios() // ------------------------ 'An ObjectId string' => [ 'value' => '507f1f77bcf86cd799439011', - 'expectation' => ['_id' => new ObjectID('507f1f77bcf86cd799439011')], + 'expectation' => ['_id' => new ObjectId('507f1f77bcf86cd799439011')], ], // ------------------------ 'An ObjectId string within a query' => [ 'value' => ['_id' => '507f1f77bcf86cd799439011'], - 'expectation' => ['_id' => new ObjectID('507f1f77bcf86cd799439011')], + 'expectation' => ['_id' => new ObjectId('507f1f77bcf86cd799439011')], ], // ------------------------ 'Other type of _id, sequence for example' => [ @@ -967,8 +967,8 @@ public function queryValueScenarios() 'expectation' => [ '_id' => [ '$in' => [ - new ObjectID('507f1f77bcf86cd799439011'), - new ObjectID('507f1f77bcf86cd799439012'), + new ObjectId('507f1f77bcf86cd799439011'), + new ObjectId('507f1f77bcf86cd799439012'), ], ], ], @@ -976,7 +976,7 @@ public function queryValueScenarios() // ------------------------ 'Series of string _ids as the $in parameter' => [ 'value' => ['_id' => ['$nin' => ['507f1f77bcf86cd799439011']]], - 'expectation' => ['_id' => ['$nin' => [new ObjectID('507f1f77bcf86cd799439011')]]], + 'expectation' => ['_id' => ['$nin' => [new ObjectId('507f1f77bcf86cd799439011')]]], ], ]; } diff --git a/tests/Unit/DataMapper/EntityAssemblerTest.php b/tests/Unit/DataMapper/EntityAssemblerTest.php index 76cb26ae..9ea0606f 100644 --- a/tests/Unit/DataMapper/EntityAssemblerTest.php +++ b/tests/Unit/DataMapper/EntityAssemblerTest.php @@ -2,7 +2,7 @@ namespace Mongolid\DataMapper; use Mockery as m; -use MongoDB\BSON\ObjectID; +use MongoDB\BSON\ObjectId; use Mongolid\Model\HasAttributesInterface; use Mongolid\Model\HasAttributesTrait; use Mongolid\Model\PolymorphableInterface; @@ -40,7 +40,7 @@ public function entityAssemblerFixture() 'A simple schema to a entity' => [ 'inputValue' => [ // Data that will be used to assembly the entity - '_id' => new ObjectID('507f1f77bcf86cd799439011'), + '_id' => new ObjectId('507f1f77bcf86cd799439011'), 'name' => 'John Doe', 'age' => 25, 'grade' => 7.25, @@ -59,7 +59,7 @@ public function entityAssemblerFixture() ], 'inputSchema' => 'studentSchema', // Schema that will be used to assembly $inputValue 'expectedOutput' => new StubStudent([ // Expected output - '_id' => new ObjectID('507f1f77bcf86cd799439011'), + '_id' => new ObjectId('507f1f77bcf86cd799439011'), 'name' => 'John Doe', 'age' => 25, 'grade' => 7.25, @@ -70,7 +70,7 @@ public function entityAssemblerFixture() 'A schema containing an embedded schema but with null field' => [ 'inputValue' => [ // Data that will be used to assembly the entity - '_id' => new ObjectID('507f1f77bcf86cd799439011'), + '_id' => new ObjectId('507f1f77bcf86cd799439011'), 'name' => 'John Doe', 'age' => 25, 'tests' => null, @@ -98,7 +98,7 @@ public function entityAssemblerFixture() ], 'inputSchema' => 'studentSchema', // Schema that will be used to assembly $inputValue 'expectedOutput' => new StubStudent([ // Expected output - '_id' => new ObjectID('507f1f77bcf86cd799439011'), + '_id' => new ObjectId('507f1f77bcf86cd799439011'), 'name' => 'John Doe', 'age' => 25, 'tests' => null, @@ -110,11 +110,11 @@ public function entityAssemblerFixture() 'A stdClass with a schema containing an embedded schema with a document directly into the field' => [ 'inputValue' => (object) [ // Data that will be used to assembly the entity - '_id' => new ObjectID('507f1f77bcf86cd799439011'), + '_id' => new ObjectId('507f1f77bcf86cd799439011'), 'name' => 'John Doe', 'age' => 25, 'tests' => [ - '_id' => new ObjectID('507f1f77bcf86cd7994390ea'), + '_id' => new ObjectId('507f1f77bcf86cd7994390ea'), 'subject' => 'math', 'grade' => 7.25, ], @@ -142,12 +142,12 @@ public function entityAssemblerFixture() ], 'inputSchema' => 'studentSchema', // Schema that will be used to assembly $inputValue 'expectedOutput' => new StubStudent([ // Expected output - '_id' => new ObjectID('507f1f77bcf86cd799439011'), + '_id' => new ObjectId('507f1f77bcf86cd799439011'), 'name' => 'John Doe', 'age' => 25, 'tests' => [ new StubTestGrade([ - '_id' => new ObjectID('507f1f77bcf86cd7994390ea'), + '_id' => new ObjectId('507f1f77bcf86cd7994390ea'), 'subject' => 'math', 'grade' => 7.25, ]), @@ -160,17 +160,17 @@ public function entityAssemblerFixture() 'A schema containing an embedded schema with multiple documents in the field' => [ 'inputValue' => [ // Data that will be used to assembly the entity - '_id' => new ObjectID('507f1f77bcf86cd799439011'), + '_id' => new ObjectId('507f1f77bcf86cd799439011'), 'name' => 'John Doe', 'age' => 25, 'tests' => [ [ - '_id' => new ObjectID('507f1f77bcf86cd7994390ea'), + '_id' => new ObjectId('507f1f77bcf86cd7994390ea'), 'subject' => 'math', 'grade' => 7.25, ], [ - '_id' => new ObjectID('507f1f77bcf86cd7994390eb'), + '_id' => new ObjectId('507f1f77bcf86cd7994390eb'), 'subject' => 'english', 'grade' => 9.0, ], @@ -199,17 +199,17 @@ public function entityAssemblerFixture() ], 'inputSchema' => 'studentSchema', // Schema that will be used to assembly $inputValue 'expectedOutput' => new StubStudent([ // Expected output - '_id' => new ObjectID('507f1f77bcf86cd799439011'), + '_id' => new ObjectId('507f1f77bcf86cd799439011'), 'name' => 'John Doe', 'age' => 25, 'tests' => [ new StubTestGrade([ - '_id' => new ObjectID('507f1f77bcf86cd7994390ea'), + '_id' => new ObjectId('507f1f77bcf86cd7994390ea'), 'subject' => 'math', 'grade' => 7.25, ]), new StubTestGrade([ - '_id' => new ObjectID('507f1f77bcf86cd7994390eb'), + '_id' => new ObjectId('507f1f77bcf86cd7994390eb'), 'subject' => 'english', 'grade' => 9.0, ]), @@ -222,7 +222,7 @@ public function entityAssemblerFixture() 'A simple schema with a polymorphable interface' => [ 'inputValue' => [ // Data that will be used to assembly the entity - '_id' => new ObjectID('507f1f77bcf86cd799439011'), + '_id' => new ObjectId('507f1f77bcf86cd799439011'), 'name' => 'John Doe', 'age' => 25, 'grade' => 7.25, @@ -241,7 +241,7 @@ public function entityAssemblerFixture() ], 'inputSchema' => 'studentSchema', // Schema that will be used to assembly $inputValue 'expectedOutput' => new StubStudent([ // Expected output - '_id' => new ObjectID('507f1f77bcf86cd799439011'), + '_id' => new ObjectId('507f1f77bcf86cd799439011'), 'name' => 'John Doe', 'age' => 25, 'grade' => 7.25, diff --git a/tests/Unit/Model/AbstractActiveRecordTest.php b/tests/Unit/Model/AbstractActiveRecordTest.php index 87208422..66dffcd7 100644 --- a/tests/Unit/Model/AbstractActiveRecordTest.php +++ b/tests/Unit/Model/AbstractActiveRecordTest.php @@ -1,9 +1,7 @@ assertSame('mongolid', $this->entity->getCollectionName()); } - public function testShouldAttachToAttribute() - { - $entity = new class() extends AbstractActiveRecord - { - /** - * @var {inheritdoc} - */ - protected $collection = 'collection_name'; - - public function class() - { - return $this->referencesOne(stdClass::class, 'courseClass'); - } - }; - $embedded = new stdClass(); - $embedded->_id = new ObjectID(); - $embedded->name = 'Course Class #1'; - $entity->attachToCourseClass($embedded); - - $this->assertSame([$embedded->_id], $entity->courseClass); - } - - public function testShouldEmbedToAttribute() - { - $this->entity = new class() extends AbstractActiveRecord - { - /** - * @var {inheritdoc} - */ - protected $collection = 'collection_name'; - - public function classes() - { - return $this->embedsMany(stdClass::class, 'courseClasses'); - } - }; - $embedded = new stdClass(); - $embedded->name = 'Course Class #1'; - $this->entity->embedToCourseClasses($embedded); - - $this->assertSame('Course Class #1', $this->entity->classes()->first()->name); - } - - public function testShouldThrowBadMethodCallExceptionWhenCallingInvalidMethod() - { - $this->entity = new class() extends AbstractActiveRecord - { - /** - * @var {inheritdoc} - */ - protected $collection = 'collection_name'; - }; - - $this->expectException(BadMethodCallException::class); - - $this->entity->foobar(); - } - public function testShouldGetSetWriteConcernInActiveRecordClass() { $this->assertSame(1, $this->entity->getWriteConcern()); diff --git a/tests/Unit/Model/DocumentEmbedderTest.php b/tests/Unit/Model/DocumentEmbedderTest.php index 1f52c243..ec7eaaa2 100644 --- a/tests/Unit/Model/DocumentEmbedderTest.php +++ b/tests/Unit/Model/DocumentEmbedderTest.php @@ -3,7 +3,7 @@ use Mockery as m; use Mockery\Matcher\Any; -use MongoDB\BSON\ObjectID; +use MongoDB\BSON\ObjectId; use Mongolid\TestCase; use stdClass; @@ -24,7 +24,7 @@ public function testShouldEmbed($originalField, $entity, $method, $expectation) $result = $parent->foo; foreach ($expectation as $index => $expectedDoc) { - if ($expectedDoc instanceof ObjectID) { + if ($expectedDoc instanceof ObjectId) { $this->assertEquals($expectedDoc, $result[$index]); continue; @@ -61,12 +61,12 @@ public function getEmbedOptions() 'embedding array with _id' => [ 'originalField' => [], 'entity' => [ - '_id' => (new ObjectID('507f191e810c19729de860ea')), + '_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'John Doe', ], 'method' => 'embed', 'expectation' => [ - ['_id' => (new ObjectID('507f191e810c19729de860ea')), 'name' => 'John Doe'], + ['_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'John Doe'], ], ], @@ -86,12 +86,12 @@ public function getEmbedOptions() 'embedding object with _id' => [ 'originalField' => null, 'entity' => (object) [ - '_id' => (new ObjectID('507f191e810c19729de860ea')), + '_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'John Doe', ], 'method' => 'embed', 'expectation' => [ - (object) ['_id' => (new ObjectID('507f191e810c19729de860ea')), 'name' => 'John Doe'], + (object) ['_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'John Doe'], ], ], @@ -99,17 +99,17 @@ public function getEmbedOptions() 'updating embedded object with _id' => [ 'originalField' => [ [ - '_id' => (new ObjectID('507f191e810c19729de860ea')), + '_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'Bob', ], ], 'entity' => (object) [ - '_id' => (new ObjectID('507f191e810c19729de860ea')), + '_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'John Doe', ], 'method' => 'embed', 'expectation' => [ - (object) ['_id' => (new ObjectID('507f191e810c19729de860ea')), 'name' => 'John Doe'], + (object) ['_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'John Doe'], ], ], @@ -117,16 +117,16 @@ public function getEmbedOptions() 'updating embedded array with _id' => [ 'originalField' => [ [ - '_id' => (new ObjectID()), + '_id' => (new ObjectId()), 'name' => 'Louis', ], [ - '_id' => (new ObjectID('507f191e810c19729de860ea')), + '_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'Bob', ], ], 'entity' => [ - '_id' => (new ObjectID('507f191e810c19729de860ea')), + '_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'John Doe', ], 'method' => 'embed', @@ -136,7 +136,7 @@ public function getEmbedOptions() 'name' => 'Louis', ], [ - '_id' => (new ObjectID('507f191e810c19729de860ea')), + '_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'John Doe', ], ], @@ -146,16 +146,16 @@ public function getEmbedOptions() 'unembeding array with _id' => [ 'originalField' => [ [ - '_id' => (new ObjectID('507f191e810c19729de860ea')), + '_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'John Doe', ], [ - '_id' => (new ObjectID()), + '_id' => (new ObjectId()), 'name' => 'Louis', ], ], 'entity' => [ - '_id' => (new ObjectID('507f191e810c19729de860ea')), + '_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'John Doe', ], 'method' => 'unembed', @@ -171,12 +171,12 @@ public function getEmbedOptions() 'attaching array with _id' => [ 'originalField' => null, 'entity' => [ - '_id' => (new ObjectID('507f191e810c19729de860ea')), + '_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'John Doe', ], 'method' => 'attach', 'expectation' => [ - (new ObjectID('507f191e810c19729de860ea')), + (new ObjectId('507f191e810c19729de860ea')), ], ], @@ -184,29 +184,29 @@ public function getEmbedOptions() 'attaching object with _id' => [ 'originalField' => null, 'entity' => (object) [ - '_id' => (new ObjectID('507f191e810c19729de860ea')), + '_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'John Doe', ], 'method' => 'attach', 'expectation' => [ - (new ObjectID('507f191e810c19729de860ea')), + (new ObjectId('507f191e810c19729de860ea')), ], ], // ------------------------------ 'attaching object with _id that is already attached' => [ 'originalField' => [ - (new ObjectID('507f191e810c19729de860ea')), - (new ObjectID('507f191e810c19729de86011')), + (new ObjectId('507f191e810c19729de860ea')), + (new ObjectId('507f191e810c19729de86011')), ], 'entity' => (object) [ - '_id' => (new ObjectID('507f191e810c19729de860ea')), + '_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'John Doe', ], 'method' => 'attach', 'expectation' => [ - (new ObjectID('507f191e810c19729de860ea')), - (new ObjectID('507f191e810c19729de86011')), + (new ObjectId('507f191e810c19729de860ea')), + (new ObjectId('507f191e810c19729de86011')), ], ], @@ -223,26 +223,26 @@ public function getEmbedOptions() // ------------------------------ 'detaching an object by its _id' => [ 'originalField' => [ - (new ObjectID('507f191e810c19729de860ea')), - (new ObjectID('507f191e810c19729de86011')), + (new ObjectId('507f191e810c19729de860ea')), + (new ObjectId('507f191e810c19729de86011')), ], 'entity' => (object) [ - '_id' => (new ObjectID('507f191e810c19729de860ea')), + '_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'John Doe', ], 'method' => 'detach', 'expectation' => [ - (new ObjectID('507f191e810c19729de86011')), + (new ObjectId('507f191e810c19729de86011')), ], ], // ------------------------------ 'attaching an _id' => [ 'originalField' => null, - 'entity' => new ObjectID('507f191e810c19729de860ea'), + 'entity' => new ObjectId('507f191e810c19729de860ea'), 'method' => 'attach', 'expectation' => [ - (new ObjectID('507f191e810c19729de860ea')), + (new ObjectId('507f191e810c19729de860ea')), ], ], diff --git a/tests/Unit/Model/HasRelationsTraitTest.php b/tests/Unit/Model/HasRelationsTraitTest.php index 2e61b7c4..368778a9 100644 --- a/tests/Unit/Model/HasRelationsTraitTest.php +++ b/tests/Unit/Model/HasRelationsTraitTest.php @@ -2,12 +2,9 @@ namespace Mongolid\Model; use Mockery as m; -use MongoDB\BSON\ObjectID; -use Mongolid\Cursor\CursorFactory; -use Mongolid\Cursor\CursorInterface; +use MongoDB\BSON\ObjectId; use Mongolid\Cursor\EmbeddedCursor; use Mongolid\DataMapper\DataMapper; -use Mongolid\Schema\AbstractSchema; use Mongolid\TestCase; class HasRelationsTraitTest extends TestCase @@ -15,248 +12,135 @@ class HasRelationsTraitTest extends TestCase /** * @dataProvider referenceScenarios */ - public function testShouldReferenceOne($entity, $field, $fieldValue, $useCache, $expectedQuery) + public function testShouldReferenceOne($fieldValue, $expectedQuery) { // Set - $expectedQuery = $expectedQuery['referencesOne']; - $model = m::mock(AbstractActiveRecord::class.'[]'); - $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)->makePartial()); - $result = new class() extends AbstractSchema { - }; + $model = new UserStub(); + $model->refOne = $fieldValue; - $model->$field = $fieldValue; - - $this->instance(get_class($entity), $entity); + $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)->makePartial()); + $expectedQuery = $expectedQuery['referencesOne']; + $expected = new RelatedStub(); - // Act + // Expectations $dataMapper->expects() - ->first(m::type('array'), [], $useCache) - ->andReturnUsing(function ($query) use ($result, $expectedQuery) { - $this->assertMongoQueryEquals($expectedQuery, $query); + ->first($expectedQuery, [], true) + ->andReturn($expected); - return $result; - }); + // Actions + $result = $model->relationReferencesOne; - // Assert - $this->assertSame( - $result, - $this->callProtected($model, 'referencesOne', [get_class($entity), $field, $useCache]) - ); + // Assertions + $this->assertSame($expected, $result); } /** * @dataProvider referenceScenarios */ - public function testShouldReferenceMany($entity, $field, $fieldValue, $useCache, $expectedQuery) + public function testShouldReferenceMany($fieldValue, $expectedQuery) { // Set - $expectedQuery = $expectedQuery['referencesMany']; - $model = m::mock(AbstractActiveRecord::class.'[]'); - $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)->makePartial()); - $result = m::mock(CursorInterface::class); + $model = new UserStub(); + $model->refMany = $fieldValue; - $model->$field = $fieldValue; - - // Act - $this->instance(get_class($entity), $entity); + $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)->makePartial()); + $expectedQuery = $expectedQuery['referencesMany']; + $expected = new EmbeddedCursor(RelatedStub::class, []); + // Expectations $dataMapper->expects() - ->where(m::type('array'), [], $useCache) - ->andReturnUsing(function ($query) use ($result, $expectedQuery) { - $this->assertMongoQueryEquals($expectedQuery, $query); + ->where($expectedQuery, [], true) + ->andReturn($expected); - return $result; - }); + // Actions + $result = $model->relationReferencesMany; - // Assert - $this->assertSame( - $result, - $this->callProtected($model, 'referencesMany', [get_class($entity), $field, $useCache]) - ); + // Assertions + $this->assertSame($expected, $result); } /** * @dataProvider embedsScenarios */ - public function testShouldEmbedsOne($entity, $field, $fieldValue, $expectedItems) + public function testShouldEmbedsOne($fieldValue, $expectedItems) { // Set - $model = m::mock(AbstractActiveRecord::class.'[]'); - $cursorFactory = $this->instance(CursorFactory::class, m::mock(CursorFactory::class)); - $cursor = m::mock(EmbeddedCursor::class); - $document = $fieldValue; - $model->$field = $document; - - $instantiableClass = $entity instanceof AbstractSchema ? 'stdClass' : get_class($entity); + $model = new UserStub(); + $model->embOne = $fieldValue; // Act - $cursorFactory->expects() - ->createEmbeddedCursor($instantiableClass, $expectedItems) - ->andReturn($cursor); - - $cursor->expects() - ->first() - ->andReturn(new $instantiableClass()); + $result = $model->relationEmbedsOne; + $values = array_map( + function ($item) { + return $item->getDocumentAttributes(); + }, + $result->all() + ); // Assert - $result = $this->callProtected($model, 'embedsOne', [get_class($entity), $field]); - $this->assertInstanceOf($instantiableClass, $result); + $this->assertInstanceOf(EmbeddedCursor::class, $result); + $this->assertContainsOnlyInstancesOf(RelatedStub::class, $result->all()); + $this->assertEquals($expectedItems, $values); } /** * @dataProvider embedsScenarios */ - public function testShouldEmbedsMany($entity, $field, $fieldValue, $expectedItems) - { - // Set - $model = m::mock(AbstractActiveRecord::class.'[]'); - $cursorFactory = $this->instance(CursorFactory::class, m::mock(CursorFactory::class)); - $cursor = m::mock(EmbeddedCursor::class); - $document = $fieldValue; - $model->$field = $document; - - $instantiableClass = $entity instanceof AbstractSchema ? 'stdClass' : get_class($entity); - - // Act - $cursorFactory->expects() - ->createEmbeddedCursor($instantiableClass, $expectedItems) - ->andReturn($cursor); - - // Assert - $result = $this->callProtected($model, 'embedsMany', [get_class($entity), $field]); - $this->assertEquals($cursor, $result); - } - - /** - * @dataProvider manipulativeMethods - */ - public function testShouldEmbeddedUnembedAttachAndDetachDocuments($method) + public function testShouldEmbedsMany($fieldValue, $expectedItems) { // Set - $model = new class() { - use HasRelationsTrait; - }; - $document = m::mock(); - $documentEmbedder = $this->instance(DocumentEmbedder::class, m::mock(DocumentEmbedder::class)); + $model = new UserStub(); + $model->embMany = $fieldValue; // Act - $documentEmbedder->expects() - ->$method($model, 'foo', $document); + $result = $model->relationEmbedsMany; + $values = array_map( + function ($item) { + return $item->getDocumentAttributes(); + }, + $result->all() + ); // Assert - $model->$method('foo', $document); + $this->assertInstanceOf(EmbeddedCursor::class, $result); + $this->assertContainsOnlyInstancesOf(RelatedStub::class, $result->all()); + $this->assertEquals($expectedItems, $values); } public function referenceScenarios() { return [ - // ------------------------- - 'Schema referenced by numeric id' => [ - 'entity' => new class() extends AbstractSchema { - }, - 'field' => 'foo', - 'fieldValue' => 12345, - 'useCache' => true, - 'expectedQuery' => [ - 'referencesOne' => ['_id' => 12345], - 'referencesMany' => ['_id' => ['$in' => [12345]]], - ], - ], - // ------------------------- 'ActiveRecord referenced by string id' => [ - 'entity' => new class() extends AbstractActiveRecord { - /** - * @var {inheritdoc} - */ - protected $collection = 'foobar'; - }, - 'field' => 'foo', 'fieldValue' => 'abc123', - 'useCache' => true, 'expectedQuery' => [ 'referencesOne' => ['_id' => 'abc123'], 'referencesMany' => ['_id' => ['$in' => ['abc123']]], ], ], - // ------------------------- - 'Schema referenced by string objectId' => [ - 'entity' => new class() extends AbstractSchema { - }, - 'field' => 'foo', - 'fieldValue' => ['553e3c80293fce6572ff2a40', '5571df31cf3fce544481a085'], - 'useCache' => false, - 'expectedQuery' => [ - 'referencesOne' => ['_id' => '553e3c80293fce6572ff2a40'], - 'referencesMany' => ['_id' => ['$in' => [new ObjectID('553e3c80293fce6572ff2a40'), new ObjectID('5571df31cf3fce544481a085')]]], - ], - ], - // ------------------------- 'ActiveRecord referenced by objectId' => [ - 'entity' => new class() extends AbstractActiveRecord { - /** - * @var {inheritdoc} - */ - protected $collection = 'foobar'; - }, - 'field' => 'foo', 'fieldValue' => '577afb0b4d3cec136058fa82', - 'useCache' => true, - 'expectedQuery' => [ - 'referencesOne' => ['_id' => '577afb0b4d3cec136058fa82'], - 'referencesMany' => ['_id' => ['$in' => ['577afb0b4d3cec136058fa82']]], - ], - ], - // ------------------------- - 'Schema referenced with series of numeric ids' => [ - 'entity' => new class() extends AbstractSchema { - }, - 'field' => 'foo', - 'fieldValue' => [1, 2, 3, 4, 5], - 'useCache' => false, 'expectedQuery' => [ - 'referencesOne' => ['_id' => 1], - 'referencesMany' => ['_id' => ['$in' => [1, 2, 3, 4, 5]]], + 'referencesOne' => ['_id' => new ObjectId('577afb0b4d3cec136058fa82')], + 'referencesMany' => ['_id' => ['$in' => [new ObjectId('577afb0b4d3cec136058fa82')]]], ], ], - // ------------------------- 'ActiveRecord referenced with series of string objectIds' => [ - 'entity' => new class() extends AbstractActiveRecord { - /** - * @var {inheritdoc} - */ - protected $collection = 'foobar'; - }, - 'field' => 'foo', - 'fieldValue' => ['577afb0b4d3cec136058fa82', '577afb7e4d3cec136258fa83'], - 'useCache' => false, + 'fieldValue' => [new ObjectId('577afb0b4d3cec136058fa82'), new ObjectId('577afb7e4d3cec136258fa83')], 'expectedQuery' => [ - 'referencesOne' => ['_id' => '577afb0b4d3cec136058fa82'], - 'referencesMany' => ['_id' => ['$in' => [new ObjectID('577afb0b4d3cec136058fa82'), new ObjectID('577afb7e4d3cec136258fa83')]]], + 'referencesOne' => ['_id' => new ObjectId('577afb0b4d3cec136058fa82')], + 'referencesMany' => [ + '_id' => [ + '$in' => [ + new ObjectId('577afb0b4d3cec136058fa82'), + new ObjectId('577afb7e4d3cec136258fa83'), + ], + ], + ], ], ], - // ------------------------- - 'Schema referenced with series of real objectIds' => [ - 'entity' => new class() extends AbstractSchema { - }, - 'field' => 'foo', - 'fieldValue' => [new ObjectID('577afb0b4d3cec136058fa82'), new ObjectID('577afb7e4d3cec136258fa83')], - 'useCache' => true, - 'expectedQuery' => [ - 'referencesOne' => ['_id' => new ObjectID('577afb0b4d3cec136058fa82')], - 'referencesMany' => ['_id' => ['$in' => [new ObjectID('577afb0b4d3cec136058fa82'), new ObjectID('577afb7e4d3cec136258fa83')]]], - ], - ], - // ------------------------- + // TODO should not hit database? 'ActiveRecord referenced with null' => [ - 'entity' => new class() extends AbstractActiveRecord { - /** - * @var {inheritdoc} - */ - protected $collection = 'foobar'; - }, - 'field' => 'foo', 'fieldValue' => null, - 'useCache' => false, 'expectedQuery' => [ 'referencesOne' => ['_id' => null], 'referencesMany' => ['_id' => ['$in' => []]], @@ -268,57 +152,58 @@ public function referenceScenarios() public function embedsScenarios() { return [ - // ------------------------- 'Embedded document referent to an Schema' => [ - 'entity' => new class() extends AbstractSchema { - }, - 'field' => 'foo', 'fieldValue' => ['_id' => 12345, 'name' => 'batata'], 'expectedItems' => [['_id' => 12345, 'name' => 'batata']], ], - // ------------------------- 'Embedded documents referent to an Schema' => [ - 'entity' => new class() extends AbstractSchema { - }, - 'field' => 'foo', 'fieldValue' => [['_id' => 12345, 'name' => 'batata'], ['_id' => 67890, 'name' => 'bar']], 'expectedItems' => [['_id' => 12345, 'name' => 'batata'], ['_id' => 67890, 'name' => 'bar']], ], - // ------------------------- 'Embedded document referent to an ActiveRecord entity' => [ - 'entity' => new class() extends AbstractActiveRecord { - /** - * @var {inheritdoc} - */ - protected $collection = 'foobar'; - }, - 'field' => 'foo', 'fieldValue' => ['_id' => 12345, 'name' => 'batata'], 'expectedItems' => [['_id' => 12345, 'name' => 'batata']], ], - // ------------------------- 'Embedded documents referent to an ActiveRecord entity' => [ - 'entity' => new class() extends AbstractActiveRecord { - /** - * @var {inheritdoc} - */ - protected $collection = 'foobar'; - }, - 'field' => 'foo', 'fieldValue' => [['_id' => 12345, 'name' => 'batata'], ['_id' => 67890, 'name' => 'bar']], 'expectedItems' => [['_id' => 12345, 'name' => 'batata'], ['_id' => 67890, 'name' => 'bar']], ], - // ------------------------- ]; } +} + +class UserStub extends AbstractActiveRecord +{ + /** + * {@inheritdoc} + */ + protected $collection = 'users'; + + public function relationReferencesOne() + { + return $this->referencesOne(RelatedStub::class, 'refOne'); + } - public function manipulativeMethods() + public function relationReferencesMany() { - return [ - ['embed'], - ['unembed'], - ['attach'], - ['detach'], - ]; + return $this->referencesMany(RelatedStub::class, 'refMany'); + } + + public function relationEmbedsOne() + { + return $this->embedsOne(RelatedStub::class, 'embOne'); } + + public function relationEmbedsMany() + { + return $this->embedsMany(RelatedStub::class, 'embMany'); + } +} + +class RelatedStub extends AbstractActiveRecord +{ + /** + * {@inheritdoc} + */ + protected $collection = 'related'; } diff --git a/tests/Unit/Schema/AbstractSchemaTest.php b/tests/Unit/Schema/AbstractSchemaTest.php index a72e6bb8..160ec552 100644 --- a/tests/Unit/Schema/AbstractSchemaTest.php +++ b/tests/Unit/Schema/AbstractSchemaTest.php @@ -2,7 +2,7 @@ namespace Mongolid\Schema; use Mockery as m; -use MongoDB\BSON\ObjectID; +use MongoDB\BSON\ObjectId; use MongoDB\BSON\UTCDateTime; use Mongolid\TestCase; use Mongolid\Util\SequenceService; @@ -35,7 +35,7 @@ public function testShouldCastNullIntoObjectId() // Assert $this->assertInstanceOf( - ObjectID::class, + ObjectId::class, $schema->objectId($value) ); } @@ -61,7 +61,7 @@ public function testShouldCastObjectIdStringIntoObjectId() // Assert $this->assertInstanceOf( - ObjectID::class, + ObjectId::class, $schema->objectId($value) ); diff --git a/tests/Unit/TestCase.php b/tests/Unit/TestCase.php index 032e5626..7572354b 100644 --- a/tests/Unit/TestCase.php +++ b/tests/Unit/TestCase.php @@ -23,7 +23,7 @@ protected function tearDown() } /** - * Assert if two queries are equals. It will compare ObjectIDs within any + * Assert if two queries are equals. It will compare ObjectIds within any * level of the query and make sure that they are the same. * * @param mixed $expectedQuery correct query diff --git a/tests/Unit/Util/ObjectIdUtilsTest.php b/tests/Unit/Util/ObjectIdUtilsTest.php index fcbf4856..301d0a86 100644 --- a/tests/Unit/Util/ObjectIdUtilsTest.php +++ b/tests/Unit/Util/ObjectIdUtilsTest.php @@ -1,7 +1,7 @@ Date: Mon, 12 Nov 2018 13:27:25 -0200 Subject: [PATCH 043/116] Improve how embedded relations are returned --- src/Model/Relations/EmbedsOne.php | 2 +- tests/Unit/Model/HasRelationsTraitTest.php | 38 ++++++++++++++-------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/Model/Relations/EmbedsOne.php b/src/Model/Relations/EmbedsOne.php index ec042bdf..cd15bc28 100644 --- a/src/Model/Relations/EmbedsOne.php +++ b/src/Model/Relations/EmbedsOne.php @@ -20,6 +20,6 @@ public function getResults() } return Ioc::make(CursorFactory::class) - ->createEmbeddedCursor($this->entity, $items); + ->createEmbeddedCursor($this->entity, $items)->first(); } } diff --git a/tests/Unit/Model/HasRelationsTraitTest.php b/tests/Unit/Model/HasRelationsTraitTest.php index 368778a9..2fa378e7 100644 --- a/tests/Unit/Model/HasRelationsTraitTest.php +++ b/tests/Unit/Model/HasRelationsTraitTest.php @@ -68,19 +68,14 @@ public function testShouldEmbedsOne($fieldValue, $expectedItems) $model = new UserStub(); $model->embOne = $fieldValue; + $expectedItems = $expectedItems['embedsOne']; + // Act $result = $model->relationEmbedsOne; - $values = array_map( - function ($item) { - return $item->getDocumentAttributes(); - }, - $result->all() - ); // Assert - $this->assertInstanceOf(EmbeddedCursor::class, $result); - $this->assertContainsOnlyInstancesOf(RelatedStub::class, $result->all()); - $this->assertEquals($expectedItems, $values); + $this->assertInstanceOf(RelatedStub::class, $result); + $this->assertEquals($expectedItems, $result->getDocumentAttributes()); } /** @@ -92,6 +87,8 @@ public function testShouldEmbedsMany($fieldValue, $expectedItems) $model = new UserStub(); $model->embMany = $fieldValue; + $expectedItems = $expectedItems['embedsMany']; + // Act $result = $model->relationEmbedsMany; $values = array_map( @@ -154,19 +151,34 @@ public function embedsScenarios() return [ 'Embedded document referent to an Schema' => [ 'fieldValue' => ['_id' => 12345, 'name' => 'batata'], - 'expectedItems' => [['_id' => 12345, 'name' => 'batata']], + 'expectedItems' => [ + 'embedsOne' => ['_id' => 12345, 'name' => 'batata'], + 'embedsMany' => [['_id' => 12345, 'name' => 'batata']], + ], ], 'Embedded documents referent to an Schema' => [ 'fieldValue' => [['_id' => 12345, 'name' => 'batata'], ['_id' => 67890, 'name' => 'bar']], - 'expectedItems' => [['_id' => 12345, 'name' => 'batata'], ['_id' => 67890, 'name' => 'bar']], + 'expectedItems' => [ + 'embedsOne' => ['_id' => 12345, 'name' => 'batata'], + 'embedsMany' => [ + ['_id' => 12345, 'name' => 'batata'], + ['_id' => 67890, 'name' => 'bar'], + ], + ], ], 'Embedded document referent to an ActiveRecord entity' => [ 'fieldValue' => ['_id' => 12345, 'name' => 'batata'], - 'expectedItems' => [['_id' => 12345, 'name' => 'batata']], + 'expectedItems' => [ + 'embedsOne' => ['_id' => 12345, 'name' => 'batata'], + 'embedsMany' => [['_id' => 12345, 'name' => 'batata']], + ], ], 'Embedded documents referent to an ActiveRecord entity' => [ 'fieldValue' => [['_id' => 12345, 'name' => 'batata'], ['_id' => 67890, 'name' => 'bar']], - 'expectedItems' => [['_id' => 12345, 'name' => 'batata'], ['_id' => 67890, 'name' => 'bar']], + 'expectedItems' => [ + 'embedsOne' => ['_id' => 12345, 'name' => 'batata'], + 'embedsMany' => [['_id' => 12345, 'name' => 'batata'], ['_id' => 67890, 'name' => 'bar']], + ], ], ]; } From 7c32eec914b8c986451fe086788df9b11779459d Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 12 Nov 2018 13:30:28 -0200 Subject: [PATCH 044/116] Improve tests scenarios --- tests/Unit/Model/HasRelationsTraitTest.php | 48 ++++++++++++---------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/tests/Unit/Model/HasRelationsTraitTest.php b/tests/Unit/Model/HasRelationsTraitTest.php index 2fa378e7..2ba3684b 100644 --- a/tests/Unit/Model/HasRelationsTraitTest.php +++ b/tests/Unit/Model/HasRelationsTraitTest.php @@ -107,21 +107,28 @@ function ($item) { public function referenceScenarios() { return [ - 'ActiveRecord referenced by string id' => [ + 'referenced by string id' => [ 'fieldValue' => 'abc123', 'expectedQuery' => [ 'referencesOne' => ['_id' => 'abc123'], 'referencesMany' => ['_id' => ['$in' => ['abc123']]], ], ], - 'ActiveRecord referenced by objectId' => [ + 'referenced by objectId represented as string' => [ 'fieldValue' => '577afb0b4d3cec136058fa82', 'expectedQuery' => [ 'referencesOne' => ['_id' => new ObjectId('577afb0b4d3cec136058fa82')], 'referencesMany' => ['_id' => ['$in' => [new ObjectId('577afb0b4d3cec136058fa82')]]], ], ], - 'ActiveRecord referenced with series of string objectIds' => [ + 'referenced by an objectId itself' => [ + 'fieldValue' => new ObjectId('577afb0b4d3cec136058fa82'), + 'expectedQuery' => [ + 'referencesOne' => ['_id' => new ObjectId('577afb0b4d3cec136058fa82')], + 'referencesMany' => ['_id' => ['$in' => [new ObjectId('577afb0b4d3cec136058fa82')]]], + ], + ], + 'series of objectIds' => [ 'fieldValue' => [new ObjectId('577afb0b4d3cec136058fa82'), new ObjectId('577afb7e4d3cec136258fa83')], 'expectedQuery' => [ 'referencesOne' => ['_id' => new ObjectId('577afb0b4d3cec136058fa82')], @@ -135,6 +142,20 @@ public function referenceScenarios() ], ], ], + 'series of objectIds as strings' => [ + 'fieldValue' => ['577afb0b4d3cec136058fa82', '577afb7e4d3cec136258fa83'], + 'expectedQuery' => [ + 'referencesOne' => ['_id' => new ObjectId('577afb0b4d3cec136058fa82')], + 'referencesMany' => [ + '_id' => [ + '$in' => [ + new ObjectId('577afb0b4d3cec136058fa82'), + new ObjectId('577afb7e4d3cec136258fa83'), + ], + ], + ], + ], + ], // TODO should not hit database? 'ActiveRecord referenced with null' => [ 'fieldValue' => null, @@ -149,31 +170,14 @@ public function referenceScenarios() public function embedsScenarios() { return [ - 'Embedded document referent to an Schema' => [ - 'fieldValue' => ['_id' => 12345, 'name' => 'batata'], - 'expectedItems' => [ - 'embedsOne' => ['_id' => 12345, 'name' => 'batata'], - 'embedsMany' => [['_id' => 12345, 'name' => 'batata']], - ], - ], - 'Embedded documents referent to an Schema' => [ - 'fieldValue' => [['_id' => 12345, 'name' => 'batata'], ['_id' => 67890, 'name' => 'bar']], - 'expectedItems' => [ - 'embedsOne' => ['_id' => 12345, 'name' => 'batata'], - 'embedsMany' => [ - ['_id' => 12345, 'name' => 'batata'], - ['_id' => 67890, 'name' => 'bar'], - ], - ], - ], - 'Embedded document referent to an ActiveRecord entity' => [ + 'A single embedded document' => [ 'fieldValue' => ['_id' => 12345, 'name' => 'batata'], 'expectedItems' => [ 'embedsOne' => ['_id' => 12345, 'name' => 'batata'], 'embedsMany' => [['_id' => 12345, 'name' => 'batata']], ], ], - 'Embedded documents referent to an ActiveRecord entity' => [ + 'Many embedded documents' => [ 'fieldValue' => [['_id' => 12345, 'name' => 'batata'], ['_id' => 67890, 'name' => 'bar']], 'expectedItems' => [ 'embedsOne' => ['_id' => 12345, 'name' => 'batata'], From 664964c452c68621448f1115ff6857520d5463a5 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 12 Nov 2018 13:32:34 -0200 Subject: [PATCH 045/116] Drop unused method --- src/Model/HasRelationsTrait.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Model/HasRelationsTrait.php b/src/Model/HasRelationsTrait.php index 26096749..e9cc4c90 100644 --- a/src/Model/HasRelationsTrait.php +++ b/src/Model/HasRelationsTrait.php @@ -101,9 +101,4 @@ public function unsetRelation($relation) { unset($this->relations[$relation]); } - - public function getRelations(): array - { - return $this->relations; - } } From 8da2eb6ad326af2ee79280945bf3c038e0383822 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 12 Nov 2018 13:40:00 -0200 Subject: [PATCH 046/116] Improve polymorph docs a bit --- src/Model/PolymorphableInterface.php | 28 +++++++++++-------- tests/Unit/DataMapper/EntityAssemblerTest.php | 7 ----- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/Model/PolymorphableInterface.php b/src/Model/PolymorphableInterface.php index def1cf1a..507ae037 100644 --- a/src/Model/PolymorphableInterface.php +++ b/src/Model/PolymorphableInterface.php @@ -8,7 +8,7 @@ * * See the docblock of the `polymorph` method for more details. * - * @see Mongolid\DataMapper\EntityAssembler + * @see \Mongolid\DataMapper\EntityAssembler */ interface PolymorphableInterface { @@ -16,21 +16,27 @@ interface PolymorphableInterface * The polymorphic method is something that may be implemented in order to * make a model polymorphic. For example: You may have three models within * the same collection: `Content`, `ArticleContent` and `VideoContent`. - * By implementing the polymorph method it is possible to retrieve an - * `ArticleContent` or a `VideoContent` object object by simply querying - * within the `Content` model using first, find, where or all. + * By implementing the polymorph() method it is possible to retrieve an + * `ArticleContent` or a `VideoContent` object by simply querying + * within the `Content` model using `first()`, `where()` or `all()`, etc. * - * Example: + * @example * public function polymorph() * { - * if ($this->video != null) { - * $obj = new VideoContent; - * $obj->fill($this->attributes(); + * if ($this->type === 'video') { + * $video = new VideoContent(); + * $video->fill($this->getDocumentAttributes()); * - * return $obj; - * } else { - * return $this; + * return $video; * } + * elseif ($this->type === 'article') { + * $article = new ArticleContent(); + * $article->fill($this->getDocumentAttributes()); + * + * return $article; + * } + * + * return $this; * } * * In the example above, if you call Content::first() and the content diff --git a/tests/Unit/DataMapper/EntityAssemblerTest.php b/tests/Unit/DataMapper/EntityAssemblerTest.php index 9ea0606f..c3848e7c 100644 --- a/tests/Unit/DataMapper/EntityAssemblerTest.php +++ b/tests/Unit/DataMapper/EntityAssemblerTest.php @@ -277,13 +277,6 @@ public function __construct($attr = []) class PolymorphableStudent extends stdClass implements PolymorphableInterface { - public function __construct($attr = []) - { - foreach ($attr as $key => $value) { - $this->$key = $value; - } - } - public function polymorph() { return new StubStudent((array) $this); From 0872145631d0aa7530bdde99604d2b1b9804f8e0 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 12 Nov 2018 15:12:54 -0200 Subject: [PATCH 047/116] Allow relations by other reference field --- src/Model/DocumentEmbedder.php | 85 ++++++++++--------- src/Model/HasRelationsTrait.php | 11 +-- src/Model/Relations/ReferencesMany.php | 19 +++-- src/Model/Relations/ReferencesOne.php | 12 +-- .../ReferencesManyRelationTest.php | 59 ++++++++++++- .../Integration/ReferencesOneRelationTest.php | 42 ++++++++- tests/Integration/Stubs/User.php | 10 +++ 7 files changed, 181 insertions(+), 57 deletions(-) diff --git a/src/Model/DocumentEmbedder.php b/src/Model/DocumentEmbedder.php index 930036e0..7d73b0cf 100644 --- a/src/Model/DocumentEmbedder.php +++ b/src/Model/DocumentEmbedder.php @@ -8,9 +8,24 @@ */ class DocumentEmbedder { + /** + * @var string + */ + private $key; + + public function __construct(string $key = '_id') + { + $this->setKey($key); + } + + public function setKey(string $key): void + { + $this->key = $key; + } + /** * Embeds the given $entity into $field of $parent. This method will also - * consider the _id of the $entity in order to update it if it is already + * consider the key of the $entity in order to update it if it is already * present in $field. * * @param mixed $parent the object where the $entity will be embedded @@ -24,16 +39,14 @@ public function embed($parent, string $field, &$entity): bool // In order to update the document if it exists inside the $parent $this->unembed($parent, $field, $entity); - $fieldValue = $parent->$field; - $fieldValue[] = $entity; - $parent->$field = array_values($fieldValue); + $parent->$field[] = $entity; return true; } /** * Removes the given $entity from $field of $parent. This method will - * consider the _id of the $entity in order to remove it. + * consider the key of the $entity in order to remove it. * * @param mixed $parent the object where the $entity will be removed * @param string $field name of the field of the object where the document is @@ -43,95 +56,91 @@ public function embed($parent, string $field, &$entity): bool */ public function unembed($parent, string $field, &$entity): bool { - $fieldValue = (array) $parent->$field; - $id = $this->getId($entity); + $embeddedKey = $this->getKey($entity); - foreach ($fieldValue as $key => $document) { - if ($id == $this->getId($document)) { - unset($fieldValue[$key]); + foreach ((array) $parent->$field as $arrayKey => $document) { + if ($embeddedKey == $this->getKey($document)) { + unset($parent->$field[$arrayKey]); } } - $parent->$field = array_values($fieldValue); + $parent->$field = array_values((array) $parent->$field); return true; } /** - * Attach a new _id reference into $field of $parent. + * Attach a new key reference into $field of $parent. * * @param mixed $parent the object where $entity will be referenced - * @param string $field the field where the _id reference of $entity will be stored + * @param string $field the field where the key reference of $entity will be stored * @param object|array $entity the object that is being attached * * @return bool Success */ public function attach($parent, string $field, &$entity): bool { - $fieldValue = (array) $parent->$field; - $newId = $this->getId($entity); + $referencedKey = $this->getKey($entity); - foreach ($fieldValue as $id) { - if ($id == $newId) { + foreach ((array) $parent->$field as $key) { + if ($key == $referencedKey) { return true; } } - $fieldValue[] = $newId; - $parent->$field = $fieldValue; + $parent->$field[] = $referencedKey; return true; } /** - * Removes an _id reference from $field of $parent. + * Removes an key reference from $field of $parent. * * @param mixed $parent the object where $entity reference will be removed - * @param string $field the field where the _id reference of $entity is stored - * @param mixed $entity the object being detached or its _id + * @param string $field the field where the key reference of $entity is stored + * @param mixed $entity the object being detached or its key * * @return bool Success */ public function detach($parent, string $field, &$entity): bool { - $fieldValue = (array) $parent->$field; - $newId = $this->getId($entity); + $referencedKey = $this->getKey($entity); - foreach ($fieldValue as $key => $id) { - if ($id == $newId) { - unset($fieldValue[$key]); + foreach ((array) $parent->$field as $arrayKey => $documentKey) { + if ($documentKey == $referencedKey) { + unset($parent->$field[$arrayKey]); } } - $parent->$field = array_values($fieldValue); + $parent->$field = array_values((array) $parent->$field); return true; } /** - * Gets the _id of the given object or array. If there is no _id in it a new - * _id will be generated and set on the object (while still returning it). + * Gets the key of the given object or array. If there is no key in it a new + * key will be generated and set on the object (while still returning it). * - * @param mixed $object the object|array that the _id will be retrieved from + * @param mixed $object the object|array that the key will be retrieved from * * @return ObjectId|mixed */ - protected function getId(&$object) + protected function getKey(&$object) { if (is_array($object)) { - if (isset($object['_id']) && $object['_id']) { - return $object['_id']; + if (isset($object[$this->key]) && $object[$this->key]) { + return $object[$this->key]; } - return $object['_id'] = new ObjectId(); + return $object[$this->key] = new ObjectId(); } if (is_object($object) && !$object instanceof ObjectId) { - if (isset($object->_id) && $object->_id) { - return $object->_id; + if (isset($object->{$this->key}) && $object->{$this->key}) { + return $object->{$this->key}; } - return $object->_id = new ObjectId(); + return $object->{$this->key} = new ObjectId(); } return $object; diff --git a/src/Model/HasRelationsTrait.php b/src/Model/HasRelationsTrait.php index e9cc4c90..1d5bf906 100644 --- a/src/Model/HasRelationsTrait.php +++ b/src/Model/HasRelationsTrait.php @@ -22,12 +22,13 @@ trait HasRelationsTrait * Returns the referenced document as object. * * @param string $entity class of the entity or of the schema of the entity - * @param string $field the field where the _id is stored + * @param string $field the field where the $key is stored + * @param string $key the field that the document will be referenced by (usually _id) * @param bool $cacheable retrieves a CacheableCursor instead */ - protected function referencesOne(string $entity, string $field, bool $cacheable = true): ReferencesOne + protected function referencesOne(string $entity, string $field, string $key = '_id', bool $cacheable = true): ReferencesOne { - return new ReferencesOne($this, $entity, $field, $cacheable); + return new ReferencesOne($this, $entity, $field, $key, $cacheable); } /** @@ -37,9 +38,9 @@ protected function referencesOne(string $entity, string $field, bool $cacheable * @param string $field the field where the _ids are stored * @param bool $cacheable retrieves a CacheableCursor instead */ - protected function referencesMany(string $entity, string $field, bool $cacheable = true): ReferencesMany + protected function referencesMany(string $entity, string $field, string $key = '_id', bool $cacheable = true): ReferencesMany { - return new ReferencesMany($this, $entity, $field, $cacheable); + return new ReferencesMany($this, $entity, $field, $key, $cacheable); } /** diff --git a/src/Model/Relations/ReferencesMany.php b/src/Model/Relations/ReferencesMany.php index 19894e40..5b5335ad 100644 --- a/src/Model/Relations/ReferencesMany.php +++ b/src/Model/Relations/ReferencesMany.php @@ -18,9 +18,16 @@ class ReferencesMany extends AbstractRelation */ protected $entityInstance; - public function __construct(HasAttributesInterface $parent, string $entity, string $field, bool $cacheable = true) + /** + * @var string + */ + protected $key; + + public function __construct(HasAttributesInterface $parent, string $entity, string $field, string $key, bool $cacheable = true) { parent::__construct($parent, $entity, $field); + $this->key = $key; + $this->documentEmbedder->setKey($key); $this->cacheable = $cacheable; $this->entityInstance = Ioc::make($this->entity); } @@ -59,16 +66,16 @@ public function detachAll() public function getResults() { - $referencedIds = (array) $this->parent->{$this->field}; + $referencedKeys = (array) $this->parent->{$this->field}; - if (ObjectIdUtils::isObjectId($referencedIds[0] ?? '')) { - foreach ($referencedIds as $key => $value) { - $referencedIds[$key] = new ObjectId((string) $value); + if (ObjectIdUtils::isObjectId($referencedKeys[0] ?? '')) { + foreach ($referencedKeys as $key => $value) { + $referencedKeys[$key] = new ObjectId((string) $value); } } return $this->entityInstance->where( - ['_id' => ['$in' => array_values($referencedIds)]], + [$this->key => ['$in' => array_values($referencedKeys)]], [], $this->cacheable ); diff --git a/src/Model/Relations/ReferencesOne.php b/src/Model/Relations/ReferencesOne.php index e8d02ef2..168d263e 100644 --- a/src/Model/Relations/ReferencesOne.php +++ b/src/Model/Relations/ReferencesOne.php @@ -13,18 +13,18 @@ public function detach($entity = null) public function getResults() { - $referencedId = $this->parent->{$this->field}; + $referencedKey = $this->parent->{$this->field}; - if (is_array($referencedId) && isset($referencedId[0])) { - $referencedId = $referencedId[0]; + if (is_array($referencedKey) && isset($referencedKey[0])) { + $referencedKey = $referencedKey[0]; } - if (ObjectIdUtils::isObjectId($referencedId)) { - $referencedId = new ObjectId((string) $referencedId); + if (ObjectIdUtils::isObjectId($referencedKey)) { + $referencedKey = new ObjectId((string) $referencedKey); } return $this->entityInstance->first( - ['_id' => $referencedId], + [$this->key => $referencedKey], [], $this->cacheable ); diff --git a/tests/Integration/ReferencesManyRelationTest.php b/tests/Integration/ReferencesManyRelationTest.php index d3148b77..f42986e4 100644 --- a/tests/Integration/ReferencesManyRelationTest.php +++ b/tests/Integration/ReferencesManyRelationTest.php @@ -48,11 +48,61 @@ public function testShouldRetrieveSiblingsOfUser() $this->assertEmpty($john->siblings->all()); } - private function createUser(string $name): User + public function testShouldRetrieveGrandsonsOfUser() + { + // create sibling + $chuck = $this->createUser('Chuck', '010'); + $john = $this->createUser('John', '369'); + $john->grandsons()->attach($chuck); + + $this->assertSame(['010'], $john->grandsons_ids); + $this->assertGrandsons([$chuck], $john); + // hit cache + $this->assertGrandsons([$chuck], $john); + + $mary = $this->createUser('Mary', '222'); + $john->grandsons()->attach($mary); + + $this->assertSame(['010', '222'], $john->grandsons_ids); + $this->assertGrandsons([$chuck, $mary], $john); + // hit cache + $this->assertGrandsons([$chuck, $mary], $john); + + // remove one sibling + $john->grandsons()->detach($chuck); + + $this->assertSame(['222'], $john->grandsons_ids); + $this->assertGrandsons([$mary], $john); + // hit cache + $this->assertGrandsons([$mary], $john); + + // replace grandsons + $bob = $this->createUser('Bob', '987'); + // unset($john->grandsons_ids); // TODO make this work! + $john->grandsons()->detachAll(); + $this->assertEmpty($john->grandsons->all()); + $john->grandsons()->attach($bob); + + $this->assertSame(['987'], $john->grandsons_ids); + $this->assertGrandsons([$bob], $john); + // hit cache + $this->assertGrandsons([$bob], $john); + + // remove with unembed + $john->grandsons()->detach($bob); + + $this->assertEmpty($john->grandsons_ids); + $this->assertEmpty($john->grandsons->all()); + } + + private function createUser(string $name, string $code = null): User { $user = new User(); $user->_id = new ObjectId(); $user->name = $name; + if ($code) { + $user->code = $code; + } $this->assertTrue($user->save()); return $user; @@ -64,4 +114,11 @@ private function assertSiblings($expected, User $model) $this->assertInstanceOf(CursorInterface::class, $siblings); $this->assertEquals($expected, $siblings->all()); } + + private function assertGrandsons($expected, User $model) + { + $grandsons = $model->grandsons; + $this->assertInstanceOf(CursorInterface::class, $grandsons); + $this->assertEquals($expected, $grandsons->all()); + } } diff --git a/tests/Integration/ReferencesOneRelationTest.php b/tests/Integration/ReferencesOneRelationTest.php index 4c4311d5..9bd93e72 100644 --- a/tests/Integration/ReferencesOneRelationTest.php +++ b/tests/Integration/ReferencesOneRelationTest.php @@ -33,11 +33,44 @@ public function testShouldRetrieveParentOfUser() $this->assertNull($john->parent); } - private function createUser(string $name): User + public function testShouldRetrieveSonOfUser() + { + // create parent + $chuck = $this->createUser('Chuck', '010'); + $john = $this->createUser('John', '369'); + $john->son()->attach($chuck); + + $this->assertSame(['010'], $john->son_id); // TODO store as single code (not array) + $this->assertSon($chuck, $john); + // hit cache + $this->assertSon($chuck, $john); + + // replace son + $bob = $this->createUser('Bob', '987'); + $john->son()->detach(); //todo remove this line and ensure only one son is attached + $john->son()->attach($bob); + + $this->assertSame(['987'], $john->son_id); + $this->assertSon($bob, $john); + // hit cache + $this->assertSon($bob, $john); + + // remove + //unset($john->son_id);// TODO make this work! + $john->son()->detach(); + + $this->assertNull($john->son_id); + $this->assertNull($john->son); + } + + private function createUser(string $name, string $code = null): User { $user = new User(); $user->_id = new ObjectId(); $user->name = $name; + if ($code) { + $user->code = $code; + } $this->assertTrue($user->save()); return $user; @@ -49,4 +82,11 @@ private function assertParent($expected, User $model) $this->assertInstanceOf(User::class, $parent); $this->assertEquals($expected, $parent); } + + private function assertSon($expected, User $model) + { + $son = $model->son; + $this->assertInstanceOf(User::class, $son); + $this->assertEquals($expected, $son); + } } diff --git a/tests/Integration/Stubs/User.php b/tests/Integration/Stubs/User.php index e49a91f7..52e9baca 100644 --- a/tests/Integration/Stubs/User.php +++ b/tests/Integration/Stubs/User.php @@ -37,4 +37,14 @@ public function siblings() { return $this->referencesMany(User::class, 'siblings_ids'); } + + public function son() + { + return $this->referencesOne(User::class, 'son_id', 'code'); + } + + public function grandsons() + { + return $this->referencesMany(User::class, 'grandsons_ids', 'code'); + } } From 1636e902719842ae5d3ec3769699a74ebcf6b138 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 12 Nov 2018 19:12:30 -0200 Subject: [PATCH 048/116] Infer relation names --- src/Model/HasAttributesTrait.php | 2 + src/Model/HasRelationsTrait.php | 155 +++++++++++++----- src/Model/Relations/AbstractRelation.php | 23 +-- src/Model/Relations/ReferencesMany.php | 4 +- tests/Integration/EmbedsManyRelationTest.php | 123 ++++++++++++++ tests/Integration/EmbedsOneRelationTest.php | 90 ++++++++++ tests/Integration/PersistedDataTest.php | 6 +- .../ReferencesManyRelationTest.php | 35 ++-- .../Integration/ReferencesOneRelationTest.php | 23 +-- .../Stubs/{User.php => EmbeddedUser.php} | 10 +- tests/Integration/Stubs/ReferencedUser.php | 50 ++++++ 11 files changed, 429 insertions(+), 92 deletions(-) create mode 100644 tests/Integration/EmbedsManyRelationTest.php create mode 100644 tests/Integration/EmbedsOneRelationTest.php rename tests/Integration/Stubs/{User.php => EmbeddedUser.php} (70%) create mode 100644 tests/Integration/Stubs/ReferencedUser.php diff --git a/src/Model/HasAttributesTrait.php b/src/Model/HasAttributesTrait.php index 548b7fd1..1eef4f98 100644 --- a/src/Model/HasAttributesTrait.php +++ b/src/Model/HasAttributesTrait.php @@ -98,6 +98,8 @@ private function &getRelationValue(string $method) if (!$this->relationLoaded($method)) { $relation = $this->$method(); + // TODO make sure that it's a relation + $this->setRelation($method, $relation->getResults()); } diff --git a/src/Model/HasRelationsTrait.php b/src/Model/HasRelationsTrait.php index 1d5bf906..1439855f 100644 --- a/src/Model/HasRelationsTrait.php +++ b/src/Model/HasRelationsTrait.php @@ -1,6 +1,7 @@ relations[$relation]; } /** - * Returns the cursor for the referenced document objects. - * - * @param string $entity class of the entity or of the schema of the entity - * @param string $field the field where the _ids are stored - * @param bool $cacheable retrieves a CacheableCursor instead + * Determine if the given relation is loaded. */ - protected function referencesMany(string $entity, string $field, string $key = '_id', bool $cacheable = true): ReferencesMany + public function relationLoaded(string $key): bool { - return new ReferencesMany($this, $entity, $field, $key, $cacheable); + return array_key_exists($key, $this->relations); } /** - * Return first embedded document as object. + * Set the given relationship on the model. * - * @param string $entity class of the entity or of the schema of the entity - * @param string $field field where the embedded document is stored + * @param mixed $value */ - protected function embedsOne(string $entity, string $field): EmbedsOne + public function setRelation(string $relation, $value): void { - return new EmbedsOne($this, $entity, $field); + $this->relations[$relation] = $value; + } + + /** + * Unset a loaded relationship. + */ + public function unsetRelation(string $relation): void + { + unset($this->relations[$relation]); + } + + /** + * Create a ReferencesOne Relation. + * + * @param string $entity class of the entity or of the schema of the entity + * @param string|null $field the field where the $key is stored + * @param string $key the field that the document will be referenced by (usually _id) + * @param bool $cacheable retrieves a CacheableCursor instead + */ + protected function referencesOne( + string $entity, + string $field = null, + string $key = '_id', + bool $cacheable = true + ): ReferencesOne { + $relationName = $this->guessRelationName(); + $field = $field ?: $this->inferFieldForReference($relationName, $key, false); + + return new ReferencesOne($this, $entity, $field, $key, $relationName, $cacheable); } /** - * Return embedded documents cursor. + * Create a ReferencesMany Relation. * - * @param string $entity class of the entity or of the schema of the entity - * @param string $field field where the embedded documents are stored + * @param string $entity class of the entity or of the schema of the entity + * @param string|null $field the field where the _ids are stored + * @param bool $cacheable retrieves a CacheableCursor instead */ - protected function embedsMany(string $entity, string $field): EmbedsMany + protected function referencesMany( + string $entity, + string $field = null, + string $key = '_id', + bool $cacheable = true + ): ReferencesMany { + $relationName = $this->guessRelationName(); + $field = $field ?: $this->inferFieldForReference($relationName, $key, true); + + return new ReferencesMany($this, $entity, $field, $key, $relationName, $cacheable); + } + + /** + * Create a EmbedsOne Relation. + * + * @param string|null $entity class of the entity or of the schema of the entity + * @param string $field field where the embedded document is stored + */ + protected function embedsOne(string $entity, string $field = null): EmbedsOne { - return new EmbedsMany($this, $entity, $field); + $relationName = $this->guessRelationName(); + $field = $field ?: $this->inferFieldForEmbed($relationName); + + return new EmbedsOne($this, $entity, $field, $relationName); } /** - * Get a specified relationship. + * Create a EmbedsMany Relation. * - * @return mixed + * @param string|null $entity class of the entity or of the schema of the entity + * @param string $field field where the embedded documents are stored */ - public function &getRelation(string $relation) + protected function embedsMany(string $entity, string $field = null): EmbedsMany { - return $this->relations[$relation]; + $relationName = $this->guessRelationName(); + $field = $field ?: $this->inferFieldForEmbed($relationName); + + return new EmbedsMany($this, $entity, $field, $relationName); } /** - * Determine if the given relation is loaded. + * Retrieve relation name. For example, if we have a code like this: + * + * ``` + * class User extends AbstractActiveRecord + * { + * public function brother() + * { + * return $this->referencesOne(User::class); + * } + * } + * ``` + * we will retrieve `brother` as the relation name. + * This is useful for storing the "Brother Reference" + * on a field called `brother_id`. */ - public function relationLoaded(string $key): bool + private function guessRelationName(): string { - return array_key_exists($key, $this->relations); + [$method, $relationType, $relation] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3); + + // TODO validate that the relation has different name from field? + return $relation['function']; } /** - * Set the given relationship on the model. + * Infer field name for reference relations. + * This is useful for storing the relation on + * a field based on both the relation name and the + * referenced key used. * - * @param mixed $value + * @example a `parent` relation on a `code` field + * would be infered as `parent_code`. + * @example a `addresses` relation on `_id` field + * would be infered as `addresses_ids`. */ - public function setRelation(string $relation, $value) + private function inferFieldForReference(string $relationName, string $key, bool $plural): string { - $this->relations[$relation] = $value; + $relationName = Str::snake($relationName); + $key = $plural ? Str::plural($key) : $key; + + return $relationName.'_'.ltrim($key, '_'); } /** - * Unset a loaded relationship. + * Infer field name for embed relations. + * This is useful for storing the relation on + * a field based on the relation name. * - * @param string $relation + * @example a `comments` relation on would be infered as `embedded_comments`. + * @example a `tag` relation on would be infered as `embedded_tag`. */ - public function unsetRelation($relation) + private function inferFieldForEmbed(string $relationName): string { - unset($this->relations[$relation]); + $relationName = Str::snake($relationName); + + return 'embedded_'.$relationName; } } diff --git a/src/Model/Relations/AbstractRelation.php b/src/Model/Relations/AbstractRelation.php index 0431934f..8f9cf9ff 100644 --- a/src/Model/Relations/AbstractRelation.php +++ b/src/Model/Relations/AbstractRelation.php @@ -32,9 +32,9 @@ abstract class AbstractRelation */ protected $relationName; - public function __construct(HasAttributesInterface $parent, string $entity, string $field) + public function __construct(HasAttributesInterface $parent, string $entity, string $field, string $relationName) { - $this->relationName = $this->guessRelationName(); + $this->relationName = $relationName; $this->parent = $parent; $this->entity = $entity; $this->field = $field; @@ -43,23 +43,4 @@ public function __construct(HasAttributesInterface $parent, string $entity, stri } abstract public function getResults(); - - /** - * @return string|null - */ - private function guessRelationName() - { - $functionName = __FUNCTION__; - - return collect(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5)) - ->pluck('function') - ->first( - function ($value) use ($functionName) { - return !in_array( - $value, - [$functionName, '__construct', 'referencesOne', 'referencesMany', 'embedsOne', 'embedsMany'] - ); - } - ); - } } diff --git a/src/Model/Relations/ReferencesMany.php b/src/Model/Relations/ReferencesMany.php index 5b5335ad..e4e2eb4e 100644 --- a/src/Model/Relations/ReferencesMany.php +++ b/src/Model/Relations/ReferencesMany.php @@ -23,9 +23,9 @@ class ReferencesMany extends AbstractRelation */ protected $key; - public function __construct(HasAttributesInterface $parent, string $entity, string $field, string $key, bool $cacheable = true) + public function __construct(HasAttributesInterface $parent, string $entity, string $field, string $key, string $relationName, bool $cacheable = true) { - parent::__construct($parent, $entity, $field); + parent::__construct($parent, $entity, $field, $relationName); $this->key = $key; $this->documentEmbedder->setKey($key); $this->cacheable = $cacheable; diff --git a/tests/Integration/EmbedsManyRelationTest.php b/tests/Integration/EmbedsManyRelationTest.php new file mode 100644 index 00000000..38fb7c0e --- /dev/null +++ b/tests/Integration/EmbedsManyRelationTest.php @@ -0,0 +1,123 @@ +createUser('Chuck'); + $john = $this->createUser('John'); + $john->siblings()->add($chuck); + + $this->assertSiblings([$chuck], $john); + // hit cache + $this->assertSiblings([$chuck], $john); + + $mary = $this->createUser('Mary'); + $john->siblings()->add($mary); + + $this->assertSiblings([$chuck, $mary], $john); + // hit cache + $this->assertSiblings([$chuck, $mary], $john); + + // remove one sibling + $john->siblings()->remove($chuck); + $this->assertSiblings([$mary], $john); + // hit cache + $this->assertSiblings([$mary], $john); + + // replace siblings + $bob = $this->createUser('Bob'); + // unset($john->embedded_siblings); // TODO make this work! + $john->siblings()->removeAll(); + $this->assertEmpty($john->siblings->all()); + $john->siblings()->add($bob); + + $this->assertSiblings([$bob], $john); + // hit cache + $this->assertSiblings([$bob], $john); + + // remove with unembed + $john->siblings()->remove($bob); + + $this->assertEmpty($john->embedded_siblings); + $this->assertEmpty($john->siblings->all()); + } + + public function testShouldRetrieveGrandsonsOfUser() + { + // create sibling + $chuck = $this->createUser('Chuck'); + $john = $this->createUser('John'); + $john->grandsons()->add($chuck); + + $this->assertSame([$chuck], $john->other_arbitrary_field); + $this->assertGrandsons([$chuck], $john); + // hit cache + $this->assertGrandsons([$chuck], $john); + + $mary = $this->createUser('Mary'); + $john->grandsons()->add($mary); + + $this->assertSame([$chuck, $mary], $john->other_arbitrary_field); + $this->assertGrandsons([$chuck, $mary], $john); + // hit cache + $this->assertGrandsons([$chuck, $mary], $john); + + // remove one sibling + $john->grandsons()->remove($chuck); + + $this->assertSame([$mary], $john->other_arbitrary_field); + $this->assertGrandsons([$mary], $john); + // hit cache + $this->assertGrandsons([$mary], $john); + + // replace grandsons + $bob = $this->createUser('Bob'); + // unset($john->other_arbitrary_field); // TODO make this work! + $john->grandsons()->removeAll(); + $this->assertEmpty($john->grandsons->all()); + $john->grandsons()->add($bob); + + $this->assertSame([$bob], $john->other_arbitrary_field); + $this->assertGrandsons([$bob], $john); + // hit cache + $this->assertGrandsons([$bob], $john); + + // remove with unembed + $john->grandsons()->remove($bob); + + $this->assertEmpty($john->other_arbitrary_field); + $this->assertEmpty($john->grandsons->all()); + } + + private function createUser(string $name): EmbeddedUser + { + $user = new EmbeddedUser(); + $user->_id = new ObjectId(); + $user->name = $name; + $this->assertTrue($user->save()); + + return $user; + } + + private function assertSiblings($expected, EmbeddedUser $model) + { + $siblings = $model->siblings; + $this->assertInstanceOf(CursorInterface::class, $siblings); + $this->assertEquals($expected, $siblings->all()); + $this->assertSame($expected, $model->embedded_siblings); + } + + private function assertGrandsons($expected, EmbeddedUser $model) + { + $grandsons = $model->grandsons; + $this->assertInstanceOf(CursorInterface::class, $grandsons); + $this->assertEquals($expected, $grandsons->all()); + } +} diff --git a/tests/Integration/EmbedsOneRelationTest.php b/tests/Integration/EmbedsOneRelationTest.php new file mode 100644 index 00000000..9d4b7fb4 --- /dev/null +++ b/tests/Integration/EmbedsOneRelationTest.php @@ -0,0 +1,90 @@ +createUser('Chuck'); + $john = $this->createUser('John'); + $john->parent()->add($chuck); + + $this->assertParent($chuck, $john); + // hit cache + $this->assertParent($chuck, $john); + + // replace parent + $bob = $this->createUser('Bob'); + $john->parent()->remove(); //todo remove this line and ensure only one parent is added + $john->parent()->add($bob); + + $this->assertParent($bob, $john); + // hit cache + $this->assertParent($bob, $john); + + // remove + //unset($john->embedded_parent);// TODO make this work! + $john->parent()->removeAll(); + + $this->assertNull($john->embedded_parent); + $this->assertNull($john->parent); + } + + public function testShouldRetrieveSonOfUser() + { + // create parent + $chuck = $this->createUser('Chuck'); + $john = $this->createUser('John'); + $john->son()->add($chuck); + + $this->assertSon($chuck, $john); + // hit cache + $this->assertSon($chuck, $john); + + // replace son + $bob = $this->createUser('Bob'); + $john->son()->remove(); //todo remove this line and ensure only one son is added + $john->son()->add($bob); + + $this->assertSon($bob, $john); + // hit cache + $this->assertSon($bob, $john); + + // remove + //unset($john->arbitrary_field);// TODO make this work! + $john->son()->removeAll(); + + $this->assertNull($john->arbitrary_field); + $this->assertNull($john->son); + } + + private function createUser(string $name): EmbeddedUser + { + $user = new EmbeddedUser(); + $user->_id = new ObjectId(); + $user->name = $name; + $this->assertTrue($user->save()); + + return $user; + } + + private function assertParent($expected, EmbeddedUser $model) + { + $parent = $model->parent; + $this->assertInstanceOf(EmbeddedUser::class, $parent); + $this->assertEquals($expected, $parent); + $this->assertSame([$expected], $model->embedded_parent); // TODO store as single array + } + + private function assertSon($expected, EmbeddedUser $model) + { + $son = $model->son; + $this->assertInstanceOf(EmbeddedUser::class, $son); + $this->assertEquals($expected, $son); + $this->assertSame([$expected], $model->arbitrary_field); // TODO store as single array + } +} diff --git a/tests/Integration/PersistedDataTest.php b/tests/Integration/PersistedDataTest.php index bba1fd60..d477b6ce 100644 --- a/tests/Integration/PersistedDataTest.php +++ b/tests/Integration/PersistedDataTest.php @@ -2,7 +2,7 @@ namespace Mongolid\Tests\Integration; use MongoDB\BSON\ObjectId; -use Mongolid\Tests\Integration\Stubs\User; +use Mongolid\Tests\Integration\Stubs\ReferencedUser; class PersistedDataTest extends IntegrationTestCase { @@ -131,9 +131,9 @@ public function testUpdateData() $this->assertSame($expected, $result); } - private function getUser(bool $save = false): User + private function getUser(bool $save = false): ReferencedUser { - $user = new User(); + $user = new ReferencedUser(); $user->_id = $this->_id; $user->name = 'John Doe'; $user->age = 25; diff --git a/tests/Integration/ReferencesManyRelationTest.php b/tests/Integration/ReferencesManyRelationTest.php index f42986e4..05b7a456 100644 --- a/tests/Integration/ReferencesManyRelationTest.php +++ b/tests/Integration/ReferencesManyRelationTest.php @@ -3,7 +3,7 @@ use MongoDB\BSON\ObjectId; use Mongolid\Cursor\CursorInterface; -use Mongolid\Tests\Integration\Stubs\User; +use Mongolid\Tests\Integration\Stubs\ReferencedUser; class ReferencesManyRelationTest extends IntegrationTestCase { @@ -45,6 +45,7 @@ public function testShouldRetrieveSiblingsOfUser() // remove with unembed $john->siblings()->detach($bob); + $this->assertEmpty($john->siblings_ids); $this->assertEmpty($john->siblings->all()); } @@ -55,7 +56,7 @@ public function testShouldRetrieveGrandsonsOfUser() $john = $this->createUser('John', '369'); $john->grandsons()->attach($chuck); - $this->assertSame(['010'], $john->grandsons_ids); + $this->assertSame(['010'], $john->grandsons_codes); $this->assertGrandsons([$chuck], $john); // hit cache $this->assertGrandsons([$chuck], $john); @@ -63,7 +64,7 @@ public function testShouldRetrieveGrandsonsOfUser() $mary = $this->createUser('Mary', '222'); $john->grandsons()->attach($mary); - $this->assertSame(['010', '222'], $john->grandsons_ids); + $this->assertSame(['010', '222'], $john->grandsons_codes); $this->assertGrandsons([$chuck, $mary], $john); // hit cache $this->assertGrandsons([$chuck, $mary], $john); @@ -71,19 +72,19 @@ public function testShouldRetrieveGrandsonsOfUser() // remove one sibling $john->grandsons()->detach($chuck); - $this->assertSame(['222'], $john->grandsons_ids); + $this->assertSame(['222'], $john->grandsons_codes); $this->assertGrandsons([$mary], $john); // hit cache $this->assertGrandsons([$mary], $john); // replace grandsons $bob = $this->createUser('Bob', '987'); - // unset($john->grandsons_ids); // TODO make this work! + // unset($john->grandsons_codes); // TODO make this work! $john->grandsons()->detachAll(); $this->assertEmpty($john->grandsons->all()); $john->grandsons()->attach($bob); - $this->assertSame(['987'], $john->grandsons_ids); + $this->assertSame(['987'], $john->grandsons_codes); $this->assertGrandsons([$bob], $john); // hit cache $this->assertGrandsons([$bob], $john); @@ -91,13 +92,13 @@ public function testShouldRetrieveGrandsonsOfUser() // remove with unembed $john->grandsons()->detach($bob); - $this->assertEmpty($john->grandsons_ids); + $this->assertEmpty($john->grandsons_codes); $this->assertEmpty($john->grandsons->all()); } - private function createUser(string $name, string $code = null): User + private function createUser(string $name, string $code = null): ReferencedUser { - $user = new User(); + $user = new ReferencedUser(); $user->_id = new ObjectId(); $user->name = $name; if ($code) { @@ -108,17 +109,29 @@ private function createUser(string $name, string $code = null): User return $user; } - private function assertSiblings($expected, User $model) + private function assertSiblings($expected, ReferencedUser $model) { $siblings = $model->siblings; $this->assertInstanceOf(CursorInterface::class, $siblings); $this->assertEquals($expected, $siblings->all()); + + foreach ($expected as $expectedModel) { + $ids[] = $expectedModel->_id; + } + + $this->assertSame($ids, $model->siblings_ids); } - private function assertGrandsons($expected, User $model) + private function assertGrandsons($expected, ReferencedUser $model) { $grandsons = $model->grandsons; $this->assertInstanceOf(CursorInterface::class, $grandsons); $this->assertEquals($expected, $grandsons->all()); + + foreach ($expected as $expectedModel) { + $codes[] = $expectedModel->code; + } + + $this->assertSame($codes, $model->grandsons_codes); } } diff --git a/tests/Integration/ReferencesOneRelationTest.php b/tests/Integration/ReferencesOneRelationTest.php index 9bd93e72..ea00afb9 100644 --- a/tests/Integration/ReferencesOneRelationTest.php +++ b/tests/Integration/ReferencesOneRelationTest.php @@ -2,7 +2,7 @@ namespace Mongolid\Tests\Integration; use MongoDB\BSON\ObjectId; -use Mongolid\Tests\Integration\Stubs\User; +use Mongolid\Tests\Integration\Stubs\ReferencedUser; class ReferencesOneRelationTest extends IntegrationTestCase { @@ -30,6 +30,7 @@ public function testShouldRetrieveParentOfUser() //unset($john->parent_id);// TODO make this work! $john->parent()->detach(); + $this->assertNull($john->parent_id); $this->assertNull($john->parent); } @@ -40,7 +41,6 @@ public function testShouldRetrieveSonOfUser() $john = $this->createUser('John', '369'); $john->son()->attach($chuck); - $this->assertSame(['010'], $john->son_id); // TODO store as single code (not array) $this->assertSon($chuck, $john); // hit cache $this->assertSon($chuck, $john); @@ -50,22 +50,21 @@ public function testShouldRetrieveSonOfUser() $john->son()->detach(); //todo remove this line and ensure only one son is attached $john->son()->attach($bob); - $this->assertSame(['987'], $john->son_id); $this->assertSon($bob, $john); // hit cache $this->assertSon($bob, $john); // remove - //unset($john->son_id);// TODO make this work! + //unset($john->arbitrary_field);// TODO make this work! $john->son()->detach(); - $this->assertNull($john->son_id); + $this->assertNull($john->arbitrary_field); $this->assertNull($john->son); } - private function createUser(string $name, string $code = null): User + private function createUser(string $name, string $code = null): ReferencedUser { - $user = new User(); + $user = new ReferencedUser(); $user->_id = new ObjectId(); $user->name = $name; if ($code) { @@ -76,17 +75,19 @@ private function createUser(string $name, string $code = null): User return $user; } - private function assertParent($expected, User $model) + private function assertParent($expected, ReferencedUser $model) { $parent = $model->parent; - $this->assertInstanceOf(User::class, $parent); + $this->assertInstanceOf(ReferencedUser::class, $parent); $this->assertEquals($expected, $parent); + $this->assertEquals([$expected->_id], $model->parent_id); // TODO store as single code (not array) } - private function assertSon($expected, User $model) + private function assertSon($expected, ReferencedUser $model) { $son = $model->son; - $this->assertInstanceOf(User::class, $son); + $this->assertInstanceOf(ReferencedUser::class, $son); $this->assertEquals($expected, $son); + $this->assertSame([$expected->code], $model->arbitrary_field); // TODO store as single code (not array) } } diff --git a/tests/Integration/Stubs/User.php b/tests/Integration/Stubs/EmbeddedUser.php similarity index 70% rename from tests/Integration/Stubs/User.php rename to tests/Integration/Stubs/EmbeddedUser.php index 52e9baca..435e6447 100644 --- a/tests/Integration/Stubs/User.php +++ b/tests/Integration/Stubs/EmbeddedUser.php @@ -6,7 +6,7 @@ use Mongolid\Container\Ioc; use Mongolid\Model\AbstractActiveRecord; -class User extends AbstractActiveRecord +class EmbeddedUser extends AbstractActiveRecord { /** * @var string @@ -30,21 +30,21 @@ public function collection(): Collection public function parent() { - return $this->referencesOne(User::class, 'parent_id'); + return $this->embedsOne(EmbeddedUser::class); } public function siblings() { - return $this->referencesMany(User::class, 'siblings_ids'); + return $this->embedsMany(EmbeddedUser::class); } public function son() { - return $this->referencesOne(User::class, 'son_id', 'code'); + return $this->embedsOne(EmbeddedUser::class, 'arbitrary_field'); } public function grandsons() { - return $this->referencesMany(User::class, 'grandsons_ids', 'code'); + return $this->embedsMany(EmbeddedUser::class, 'other_arbitrary_field'); } } diff --git a/tests/Integration/Stubs/ReferencedUser.php b/tests/Integration/Stubs/ReferencedUser.php new file mode 100644 index 00000000..cc5e2a3c --- /dev/null +++ b/tests/Integration/Stubs/ReferencedUser.php @@ -0,0 +1,50 @@ + 'objectId', + ]; + + public function collection(): Collection + { + $connection = Ioc::make(Connection::class); + $client = $connection->getRawConnection(); + + return $client->{$connection->defaultDatabase}->{$this->collection}; + } + + public function parent() + { + return $this->referencesOne(ReferencedUser::class); + } + + public function siblings() + { + return $this->referencesMany(ReferencedUser::class); + } + + public function son() + { + return $this->referencesOne(ReferencedUser::class, 'arbitrary_field', 'code'); + } + + public function grandsons() + { + return $this->referencesMany(ReferencedUser::class, null, 'code'); + } +} From 89ca2fe63b8d7e3cb4ebef0cf5bdccda731bff9a Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 12 Nov 2018 19:38:49 -0200 Subject: [PATCH 049/116] Create RelationInterface and validate attribute access of methods --- src/Model/HasAttributesTrait.php | 6 +++++- src/Model/Relations/AbstractRelation.php | 2 +- src/Model/Relations/EmbedsMany.php | 3 +-- src/Model/Relations/InvalidRelationException.php | 8 ++++++++ src/Model/Relations/RelationInterface.php | 12 ++++++++++++ tests/Integration/ReferencesOneRelationTest.php | 14 ++++++++++++++ tests/Integration/Stubs/ReferencedUser.php | 5 +++++ 7 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 src/Model/Relations/InvalidRelationException.php create mode 100644 src/Model/Relations/RelationInterface.php diff --git a/src/Model/HasAttributesTrait.php b/src/Model/HasAttributesTrait.php index 1eef4f98..308d0dc9 100644 --- a/src/Model/HasAttributesTrait.php +++ b/src/Model/HasAttributesTrait.php @@ -3,6 +3,8 @@ use Exception; use Illuminate\Support\Str; +use Mongolid\Model\Relations\InvalidRelationException; +use Mongolid\Model\Relations\RelationInterface; /** * This trait adds attribute getter, setters and also a useful @@ -98,7 +100,9 @@ private function &getRelationValue(string $method) if (!$this->relationLoaded($method)) { $relation = $this->$method(); - // TODO make sure that it's a relation + if (!$relation instanceof RelationInterface) { + throw new InvalidRelationException("Called method \"{$method}\" is not a Relation!"); + } $this->setRelation($method, $relation->getResults()); } diff --git a/src/Model/Relations/AbstractRelation.php b/src/Model/Relations/AbstractRelation.php index 8f9cf9ff..7a472780 100644 --- a/src/Model/Relations/AbstractRelation.php +++ b/src/Model/Relations/AbstractRelation.php @@ -5,7 +5,7 @@ use Mongolid\Model\DocumentEmbedder; use Mongolid\Model\HasAttributesInterface; -abstract class AbstractRelation +abstract class AbstractRelation implements RelationInterface { /** * @var HasAttributesInterface diff --git a/src/Model/Relations/EmbedsMany.php b/src/Model/Relations/EmbedsMany.php index c372778b..242a884a 100644 --- a/src/Model/Relations/EmbedsMany.php +++ b/src/Model/Relations/EmbedsMany.php @@ -27,7 +27,7 @@ public function add($entity) public function remove($entity) { $this->documentEmbedder->unembed($this->parent, $this->field, $entity); - $this->parent->unsetRelation($this->relationName); + $this->parent->unsetRelation($this->relationName); // TODO better implementation of cache invalidation } public function removeAll() @@ -36,7 +36,6 @@ public function removeAll() $this->parent->unsetRelation($this->relationName); } - public function getResults() { $items = (array) $this->parent->{$this->field}; diff --git a/src/Model/Relations/InvalidRelationException.php b/src/Model/Relations/InvalidRelationException.php new file mode 100644 index 00000000..13511304 --- /dev/null +++ b/src/Model/Relations/InvalidRelationException.php @@ -0,0 +1,8 @@ +assertNull($john->son); } + public function testShouldCatchInvalidRelations() + { + // Set + $user = new ReferencedUser(); + + // Expectations + $this->expectException(InvalidRelationException::class); + $this->expectExceptionMessage('Called method "invalid" is not a Relation!'); + + // Actions + $user->invalid; + } + private function createUser(string $name, string $code = null): ReferencedUser { $user = new ReferencedUser(); diff --git a/tests/Integration/Stubs/ReferencedUser.php b/tests/Integration/Stubs/ReferencedUser.php index cc5e2a3c..d7d2e137 100644 --- a/tests/Integration/Stubs/ReferencedUser.php +++ b/tests/Integration/Stubs/ReferencedUser.php @@ -47,4 +47,9 @@ public function grandsons() { return $this->referencesMany(ReferencedUser::class, null, 'code'); } + + public function invalid() + { + return 'I am not a relation!'; + } } From a5397513de81b36e4d20d82eccef85a569a31557 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 12 Nov 2018 19:52:06 -0200 Subject: [PATCH 050/116] Flip attributes to follow the same order as parent --- src/Model/HasRelationsTrait.php | 4 ++-- src/Model/Relations/ReferencesMany.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Model/HasRelationsTrait.php b/src/Model/HasRelationsTrait.php index 1439855f..38388fc6 100644 --- a/src/Model/HasRelationsTrait.php +++ b/src/Model/HasRelationsTrait.php @@ -72,7 +72,7 @@ protected function referencesOne( $relationName = $this->guessRelationName(); $field = $field ?: $this->inferFieldForReference($relationName, $key, false); - return new ReferencesOne($this, $entity, $field, $key, $relationName, $cacheable); + return new ReferencesOne($this, $entity, $field, $relationName, $key, $cacheable); } /** @@ -91,7 +91,7 @@ protected function referencesMany( $relationName = $this->guessRelationName(); $field = $field ?: $this->inferFieldForReference($relationName, $key, true); - return new ReferencesMany($this, $entity, $field, $key, $relationName, $cacheable); + return new ReferencesMany($this, $entity, $field, $relationName, $key, $cacheable); } /** diff --git a/src/Model/Relations/ReferencesMany.php b/src/Model/Relations/ReferencesMany.php index e4e2eb4e..2dab034f 100644 --- a/src/Model/Relations/ReferencesMany.php +++ b/src/Model/Relations/ReferencesMany.php @@ -23,7 +23,7 @@ class ReferencesMany extends AbstractRelation */ protected $key; - public function __construct(HasAttributesInterface $parent, string $entity, string $field, string $key, string $relationName, bool $cacheable = true) + public function __construct(HasAttributesInterface $parent, string $entity, string $field, string $relationName, string $key, bool $cacheable = true) { parent::__construct($parent, $entity, $field, $relationName); $this->key = $key; From 336795ece22e684152dbd8f7f5fd98b34f408c9a Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 12 Nov 2018 19:53:07 -0200 Subject: [PATCH 051/116] Rename exception to be clearer --- src/Model/HasAttributesTrait.php | 4 ++-- src/Model/Relations/InvalidRelationException.php | 8 -------- src/Model/Relations/NotARelationException.php | 8 ++++++++ tests/Integration/ReferencesOneRelationTest.php | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) delete mode 100644 src/Model/Relations/InvalidRelationException.php create mode 100644 src/Model/Relations/NotARelationException.php diff --git a/src/Model/HasAttributesTrait.php b/src/Model/HasAttributesTrait.php index 308d0dc9..65b53cb6 100644 --- a/src/Model/HasAttributesTrait.php +++ b/src/Model/HasAttributesTrait.php @@ -3,7 +3,7 @@ use Exception; use Illuminate\Support\Str; -use Mongolid\Model\Relations\InvalidRelationException; +use Mongolid\Model\Relations\NotARelationException; use Mongolid\Model\Relations\RelationInterface; /** @@ -101,7 +101,7 @@ private function &getRelationValue(string $method) $relation = $this->$method(); if (!$relation instanceof RelationInterface) { - throw new InvalidRelationException("Called method \"{$method}\" is not a Relation!"); + throw new NotARelationException("Called method \"{$method}\" is not a Relation!"); } $this->setRelation($method, $relation->getResults()); diff --git a/src/Model/Relations/InvalidRelationException.php b/src/Model/Relations/InvalidRelationException.php deleted file mode 100644 index 13511304..00000000 --- a/src/Model/Relations/InvalidRelationException.php +++ /dev/null @@ -1,8 +0,0 @@ -expectException(InvalidRelationException::class); + $this->expectException(NotARelationException::class); $this->expectExceptionMessage('Called method "invalid" is not a Relation!'); // Actions From 7ca5c81ac6d6c4e8146b2b80cf8cc52c764cb3a1 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 12 Nov 2018 19:59:53 -0200 Subject: [PATCH 052/116] Prevent field name from being the same as the relation name --- src/Model/Relations/AbstractRelation.php | 6 ++++++ src/Model/Relations/InvalidFieldNameException.php | 8 ++++++++ tests/Integration/EmbedsOneRelationTest.php | 14 ++++++++++++++ tests/Integration/Stubs/EmbeddedUser.php | 5 +++++ 4 files changed, 33 insertions(+) create mode 100644 src/Model/Relations/InvalidFieldNameException.php diff --git a/src/Model/Relations/AbstractRelation.php b/src/Model/Relations/AbstractRelation.php index 7a472780..03ebf7b6 100644 --- a/src/Model/Relations/AbstractRelation.php +++ b/src/Model/Relations/AbstractRelation.php @@ -34,6 +34,12 @@ abstract class AbstractRelation implements RelationInterface public function __construct(HasAttributesInterface $parent, string $entity, string $field, string $relationName) { + if ($relationName === $field) { + throw new InvalidFieldNameException( + "The field for relation \"{$relationName}\" cannot have the same name as the relation" + ); + } + $this->relationName = $relationName; $this->parent = $parent; $this->entity = $entity; diff --git a/src/Model/Relations/InvalidFieldNameException.php b/src/Model/Relations/InvalidFieldNameException.php new file mode 100644 index 00000000..2994f9b1 --- /dev/null +++ b/src/Model/Relations/InvalidFieldNameException.php @@ -0,0 +1,8 @@ +assertEquals($expected, $son); $this->assertSame([$expected], $model->arbitrary_field); // TODO store as single array } + + public function testShouldCatchInvalidFieldNameOnRelations() + { + // Set + $user = new EmbeddedUser(); + + // Expectations + $this->expectException(InvalidFieldNameException::class); + $this->expectExceptionMessage('The field for relation "sameName" cannot have the same name as the relation'); + + // Actions + $user->sameName; + } } diff --git a/tests/Integration/Stubs/EmbeddedUser.php b/tests/Integration/Stubs/EmbeddedUser.php index 435e6447..346d4fea 100644 --- a/tests/Integration/Stubs/EmbeddedUser.php +++ b/tests/Integration/Stubs/EmbeddedUser.php @@ -47,4 +47,9 @@ public function grandsons() { return $this->embedsMany(EmbeddedUser::class, 'other_arbitrary_field'); } + + public function sameName() + { + $this->embedsOne(EmbeddedUser::class, 'sameName'); + } } From 851f723940fe9ff4396750b62e41170447f5b45b Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Tue, 13 Nov 2018 10:07:42 -0200 Subject: [PATCH 053/116] Fixes based on review --- src/Connection/Manager.php | 2 -- src/DataMapper/DataMapper.php | 14 +++----- src/Model/AbstractActiveRecord.php | 34 +++++-------------- src/Model/DocumentEmbedder.php | 10 +----- src/Model/HasAttributesTrait.php | 2 +- src/Model/HasRelationsTrait.php | 2 +- src/Util/CacheComponent.php | 2 -- src/Util/CacheComponentInterface.php | 2 -- src/Util/ObjectIdUtils.php | 4 +-- tests/Integration/EmbedsManyRelationTest.php | 2 +- tests/Integration/EmbedsOneRelationTest.php | 2 +- .../ReferencesManyRelationTest.php | 2 +- .../Integration/ReferencesOneRelationTest.php | 2 +- 13 files changed, 22 insertions(+), 58 deletions(-) diff --git a/src/Connection/Manager.php b/src/Connection/Manager.php index 78eea166..fdd4490d 100644 --- a/src/Connection/Manager.php +++ b/src/Connection/Manager.php @@ -65,8 +65,6 @@ class Manager * persist and query your models. * * @param Connection $connection connection instance to be used in database interactions - * - * @return bool Success */ public function setConnection(Connection $connection): bool { diff --git a/src/DataMapper/DataMapper.php b/src/DataMapper/DataMapper.php index bebbe63f..48ae89be 100644 --- a/src/DataMapper/DataMapper.php +++ b/src/DataMapper/DataMapper.php @@ -69,13 +69,12 @@ public function __construct(Connection $connection) * is acknowledged. * * Notice: Saves with Unacknowledged WriteConcern will not fire `saved` event. + * Return is always false if write concern is Unacknowledged. * * @param mixed $entity the entity used in the operation * @param array $options possible options to send to mongo driver - * - * @return bool Success (but always false if write concern is Unacknowledged) */ - public function save($entity, array $options = []) + public function save($entity, array $options = []): bool { // If the "saving" event returns false we'll bail out of the save and return // false, indicating that the save failed. This gives an opportunities to @@ -110,12 +109,11 @@ public function save($entity, array $options = []) * exists. * * Notice: Inserts with Unacknowledged WriteConcern will not fire `inserted` event. + * Return is always false if write concern is Unacknowledged. * * @param mixed $entity the entity used in the operation * @param array $options possible options to send to mongo driver * @param bool $fireEvents whether events should be fired - * - * @return bool Success (but always false if write concern is Unacknowledged) */ public function insert($entity, array $options = [], bool $fireEvents = true): bool { @@ -149,11 +147,10 @@ public function insert($entity, array $options = [], bool $fireEvents = true): b * the given _id did not exists. * * Notice: Updates with Unacknowledged WriteConcern will not fire `updated` event. + * Return is always false if write concern is Unacknowledged. * * @param mixed $entity the entity used in the operation * @param array $options possible options to send to mongo driver - * - * @return bool Success (but always false if write concern is Unacknowledged) */ public function update($entity, array $options = []): bool { @@ -196,11 +193,10 @@ public function update($entity, array $options = []): bool * Removes the given document from the collection. * * Notice: Deletes with Unacknowledged WriteConcern will not fire `deleted` event. + * Return is always false if write concern is Unacknowledged. * * @param mixed $entity the entity used in the operation * @param array $options possible options to send to mongo driver - * - * @return bool Success (but always false if write concern is Unacknowledged) */ public function delete($entity, array $options = []): bool { diff --git a/src/Model/AbstractActiveRecord.php b/src/Model/AbstractActiveRecord.php index 851f848b..9f15869f 100644 --- a/src/Model/AbstractActiveRecord.php +++ b/src/Model/AbstractActiveRecord.php @@ -157,7 +157,7 @@ public static function firstOrNew($id) } /** - * Returns the a valid instance from Ioc. + * Returns a valid instance from Ioc. * * @throws NoCollectionNameException Throws exception when has no collection filled */ @@ -174,8 +174,6 @@ private static function getDataMapperInstance(): DataMapper /** * Saves this object into database. - * - * @return bool Success */ public function save() { @@ -184,8 +182,6 @@ public function save() /** * Insert this object into database. - * - * @return bool Success */ public function insert() { @@ -194,8 +190,6 @@ public function insert() /** * Updates this object in database. - * - * @return bool Success */ public function update() { @@ -204,8 +198,6 @@ public function update() /** * Deletes this object in database. - * - * @return bool Success */ public function delete() { @@ -258,10 +250,8 @@ public function __unset(string $key) /** * Returns a DataMapper configured with the Schema and collection described * in this entity. - * - * @return DataMapper */ - public function getDataMapper() + public function getDataMapper(): DataMapper { $dataMapper = Ioc::make(DataMapper::class); $dataMapper->setSchema($this->getSchema()); @@ -271,30 +261,26 @@ public function getDataMapper() /** * Getter for the $collection attribute. - * - * @return string */ - public function getCollectionName() + public function getCollectionName(): ?string { return $this->collection ?: $this->getSchema()->collection; } /** - * Getter for $writeConcern variable. - * - * @return mixed + * Getter for $writeConcern attribute. */ - public function getWriteConcern() + public function getWriteConcern(): int { return $this->writeConcern; } /** - * Setter for $writeConcern variable. + * Setter for $writeConcern attribute. * - * @param mixed $writeConcern level of write concern to the transation + * @param int $writeConcern level of write concern for the transation */ - public function setWriteConcern($writeConcern) + public function setWriteConcern(int $writeConcern) { $this->writeConcern = $writeConcern; } @@ -336,10 +322,8 @@ protected function instantiateSchemaInFields() * Performs the given action into database. * * @param string $action DataMapper function to execute - * - * @return bool */ - protected function execute(string $action) + protected function execute(string $action): bool { if (!$this->getCollectionName()) { return false; diff --git a/src/Model/DocumentEmbedder.php b/src/Model/DocumentEmbedder.php index 7d73b0cf..d0a69454 100644 --- a/src/Model/DocumentEmbedder.php +++ b/src/Model/DocumentEmbedder.php @@ -31,8 +31,6 @@ public function setKey(string $key): void * @param mixed $parent the object where the $entity will be embedded * @param string $field name of the field of the object where the document will be embedded * @param mixed $entity entity that will be embedded within $parent - * - * @return bool Success */ public function embed($parent, string $field, &$entity): bool { @@ -51,8 +49,6 @@ public function embed($parent, string $field, &$entity): bool * @param mixed $parent the object where the $entity will be removed * @param string $field name of the field of the object where the document is * @param mixed $entity entity that will be removed from $parent - * - * @return bool Success */ public function unembed($parent, string $field, &$entity): bool { @@ -75,8 +71,6 @@ public function unembed($parent, string $field, &$entity): bool * @param mixed $parent the object where $entity will be referenced * @param string $field the field where the key reference of $entity will be stored * @param object|array $entity the object that is being attached - * - * @return bool Success */ public function attach($parent, string $field, &$entity): bool { @@ -94,13 +88,11 @@ public function attach($parent, string $field, &$entity): bool } /** - * Removes an key reference from $field of $parent. + * Removes a key reference from $field of $parent. * * @param mixed $parent the object where $entity reference will be removed * @param string $field the field where the key reference of $entity is stored * @param mixed $entity the object being detached or its key - * - * @return bool Success */ public function detach($parent, string $field, &$entity): bool { diff --git a/src/Model/HasAttributesTrait.php b/src/Model/HasAttributesTrait.php index 65b53cb6..74e8c6af 100644 --- a/src/Model/HasAttributesTrait.php +++ b/src/Model/HasAttributesTrait.php @@ -12,7 +12,7 @@ * properties to make sure that only the correct attributes * will be set. * - * It is supposed to be used in model classes in general + * It is supposed to be used on model classes in general */ trait HasAttributesTrait { diff --git a/src/Model/HasRelationsTrait.php b/src/Model/HasRelationsTrait.php index 38388fc6..679662d7 100644 --- a/src/Model/HasRelationsTrait.php +++ b/src/Model/HasRelationsTrait.php @@ -8,7 +8,7 @@ use Mongolid\Model\Relations\ReferencesOne; /** - * It is supposed to be used in model classes in general. + * It is supposed to be used on model classes in general. */ trait HasRelationsTrait { diff --git a/src/Util/CacheComponent.php b/src/Util/CacheComponent.php index 25b92a12..1f1bd46e 100644 --- a/src/Util/CacheComponent.php +++ b/src/Util/CacheComponent.php @@ -54,8 +54,6 @@ public function put(string $key, $value, float $minutes) * memory if so. * * @param string $key cache key of the item - * - * @return bool has cache key */ public function has(string $key): bool { diff --git a/src/Util/CacheComponentInterface.php b/src/Util/CacheComponentInterface.php index 96574387..5684f717 100644 --- a/src/Util/CacheComponentInterface.php +++ b/src/Util/CacheComponentInterface.php @@ -31,8 +31,6 @@ public function put(string $key, $value, float $minutes); * memory if so. * * @param string $key cache key of the item - * - * @return bool has cache key */ public function has(string $key): bool; } diff --git a/src/Util/ObjectIdUtils.php b/src/Util/ObjectIdUtils.php index f40ea1d0..72c57e97 100644 --- a/src/Util/ObjectIdUtils.php +++ b/src/Util/ObjectIdUtils.php @@ -13,10 +13,8 @@ class ObjectIdUtils * Checks if the given value can be a valid ObjectId. * * @param mixed $value string to be evaluated if it can be used as a valid ObjectId - * - * @return bool true if is valid */ - public static function isObjectId($value) + public static function isObjectId($value): bool { if ($value instanceof ObjectId) { return true; diff --git a/tests/Integration/EmbedsManyRelationTest.php b/tests/Integration/EmbedsManyRelationTest.php index 38fb7c0e..5b6ff5c9 100644 --- a/tests/Integration/EmbedsManyRelationTest.php +++ b/tests/Integration/EmbedsManyRelationTest.php @@ -49,7 +49,7 @@ public function testShouldRetrieveSiblingsOfUser() $this->assertEmpty($john->siblings->all()); } - public function testShouldRetrieveGrandsonsOfUser() + public function testShouldRetrieveGrandsonsOfUserUsingCustomKey() { // create sibling $chuck = $this->createUser('Chuck'); diff --git a/tests/Integration/EmbedsOneRelationTest.php b/tests/Integration/EmbedsOneRelationTest.php index aca13804..a7c01ec7 100644 --- a/tests/Integration/EmbedsOneRelationTest.php +++ b/tests/Integration/EmbedsOneRelationTest.php @@ -35,7 +35,7 @@ public function testShouldRetrieveParentOfUser() $this->assertNull($john->parent); } - public function testShouldRetrieveSonOfUser() + public function testShouldRetrieveSonOfUserUsingCustomKey() { // create parent $chuck = $this->createUser('Chuck'); diff --git a/tests/Integration/ReferencesManyRelationTest.php b/tests/Integration/ReferencesManyRelationTest.php index 05b7a456..b9c35301 100644 --- a/tests/Integration/ReferencesManyRelationTest.php +++ b/tests/Integration/ReferencesManyRelationTest.php @@ -49,7 +49,7 @@ public function testShouldRetrieveSiblingsOfUser() $this->assertEmpty($john->siblings->all()); } - public function testShouldRetrieveGrandsonsOfUser() + public function testShouldRetrieveGrandsonsOfUserUsingCustomKey() { // create sibling $chuck = $this->createUser('Chuck', '010'); diff --git a/tests/Integration/ReferencesOneRelationTest.php b/tests/Integration/ReferencesOneRelationTest.php index 0066208b..3ce620a4 100644 --- a/tests/Integration/ReferencesOneRelationTest.php +++ b/tests/Integration/ReferencesOneRelationTest.php @@ -35,7 +35,7 @@ public function testShouldRetrieveParentOfUser() $this->assertNull($john->parent); } - public function testShouldRetrieveSonOfUser() + public function testShouldRetrieveSonOfUserUsingCustomKey() { // create parent $chuck = $this->createUser('Chuck', '010'); From c4f2dc27c2db2c9573db4937c478986ca8d93a49 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Tue, 13 Nov 2018 10:14:08 -0200 Subject: [PATCH 054/116] Add a test for old reserved words --- tests/Integration/AttributesKeyTest.php | 33 +++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/Integration/AttributesKeyTest.php diff --git a/tests/Integration/AttributesKeyTest.php b/tests/Integration/AttributesKeyTest.php new file mode 100644 index 00000000..8709c145 --- /dev/null +++ b/tests/Integration/AttributesKeyTest.php @@ -0,0 +1,33 @@ +name = 'John'; + $user->email = 'john@doe.com'; + + // attributes that used to be "reserved" + $user->attributes = ['my', 'attributes']; + $user->originalAttributes = ['my', 'original', 'attributes']; + + $this->assertSame('John', $user->name); + $this->assertSame('john@doe.com', $user->email); + $this->assertSame(['my', 'attributes'], $user->attributes); + $this->assertSame(['my', 'original', 'attributes'], $user->originalAttributes); + $this->assertSame( + [ + 'name' => 'John', + 'email' => 'john@doe.com', + 'attributes' => ['my', 'attributes'], + 'originalAttributes' => ['my', 'original', 'attributes'], + ], + $user->getDocumentAttributes() + ); + $this->assertSame([], $user->getOriginalDocumentAttributes()); + } +} From e68105818ddc8dea0b51a2255ffa1066c8b188ee Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Tue, 13 Nov 2018 10:16:59 -0200 Subject: [PATCH 055/116] Improve attributes test --- tests/Integration/AttributesKeyTest.php | 29 +++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/Integration/AttributesKeyTest.php b/tests/Integration/AttributesKeyTest.php index 8709c145..791de58f 100644 --- a/tests/Integration/AttributesKeyTest.php +++ b/tests/Integration/AttributesKeyTest.php @@ -29,5 +29,34 @@ public function testAttributesKeysShouldNotBeReserved() $user->getDocumentAttributes() ); $this->assertSame([], $user->getOriginalDocumentAttributes()); + + // Save and refetch from database + $this->assertTrue($user->save()); + $user = $user->first(); + + $this->assertSame('John', $user->name); + $this->assertSame('john@doe.com', $user->email); + $this->assertSame(['my', 'attributes'], $user->attributes); + $this->assertSame(['my', 'original', 'attributes'], $user->originalAttributes); + $this->assertSame( + [ + '_id' => $user->_id, + 'name' => 'John', + 'email' => 'john@doe.com', + 'attributes' => ['my', 'attributes'], + 'originalAttributes' => ['my', 'original', 'attributes'], + ], + $user->getDocumentAttributes() + ); + $this->assertEquals( + [ + '_id' => $user->_id, + 'name' => 'John', + 'email' => 'john@doe.com', + 'attributes' => ['my', 'attributes'], + 'originalAttributes' => ['my', 'original', 'attributes'], + ], + $user->getOriginalDocumentAttributes() + ); } } From a715c5ba9e58fe17b295ebca013a5a53801c71c9 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Tue, 13 Nov 2018 11:59:17 -0200 Subject: [PATCH 056/116] New cache strategy for relations --- src/Model/HasAttributesTrait.php | 47 ++++-------- src/Model/HasRelationsTrait.php | 86 +++++++++++++++++----- src/Model/Relations/AbstractRelation.php | 20 +++-- src/Model/Relations/EmbedsMany.php | 39 +++++++--- src/Model/Relations/EmbedsOne.php | 28 ++++--- src/Model/Relations/ReferencesMany.php | 49 +++++++----- src/Model/Relations/ReferencesOne.php | 38 ++++++---- tests/Unit/Model/HasRelationsTraitTest.php | 1 - 8 files changed, 193 insertions(+), 115 deletions(-) diff --git a/src/Model/HasAttributesTrait.php b/src/Model/HasAttributesTrait.php index 74e8c6af..6f97aa50 100644 --- a/src/Model/HasAttributesTrait.php +++ b/src/Model/HasAttributesTrait.php @@ -3,8 +3,6 @@ use Exception; use Illuminate\Support\Str; -use Mongolid\Model\Relations\NotARelationException; -use Mongolid\Model\Relations\RelationInterface; /** * This trait adds attribute getter, setters and also a useful @@ -16,20 +14,6 @@ */ trait HasAttributesTrait { - /** - * The model's attributes. - * - * @var array - */ - private $attributes = []; - - /** - * The model attribute's original state. - * - * @var array - */ - private $originalAttributes = []; - /** * Once you put at least one string in this array, only * the attributes specified here will be changed @@ -63,6 +47,20 @@ trait HasAttributesTrait */ protected $mutableCache = []; + /** + * The model's attributes. + * + * @var array + */ + private $attributes = []; + + /** + * The model attribute's original state. + * + * @var array + */ + private $originalAttributes = []; + /** * {@inheritdoc} */ @@ -87,7 +85,7 @@ public function &getDocumentAttribute(string $key) } if (!method_exists(self::class, $key) && method_exists($this, $key)) { - return $this->getRelationValue($key); + return $this->getRelationResults($key); } $this->attributes[$key] = null; @@ -95,21 +93,6 @@ public function &getDocumentAttribute(string $key) return $this->attributes[$key]; } - private function &getRelationValue(string $method) - { - if (!$this->relationLoaded($method)) { - $relation = $this->$method(); - - if (!$relation instanceof RelationInterface) { - throw new NotARelationException("Called method \"{$method}\" is not a Relation!"); - } - - $this->setRelation($method, $relation->getResults()); - } - - return $this->getRelation($method); - } - /** * {@inheritdoc} */ diff --git a/src/Model/HasRelationsTrait.php b/src/Model/HasRelationsTrait.php index 679662d7..8db7bf6f 100644 --- a/src/Model/HasRelationsTrait.php +++ b/src/Model/HasRelationsTrait.php @@ -4,8 +4,11 @@ use Illuminate\Support\Str; use Mongolid\Model\Relations\EmbedsMany; use Mongolid\Model\Relations\EmbedsOne; +use Mongolid\Model\Relations\InvalidFieldNameException; +use Mongolid\Model\Relations\NotARelationException; use Mongolid\Model\Relations\ReferencesMany; use Mongolid\Model\Relations\ReferencesOne; +use Mongolid\Model\Relations\RelationInterface; /** * It is supposed to be used on model classes in general. @@ -15,16 +18,14 @@ trait HasRelationsTrait /** * The loaded relationships for the model. * - * @var array + * @var RelationInterface[] */ private $relations = []; /** * Get a specified relationship. - * - * @return mixed */ - public function &getRelation(string $relation) + public function &getRelation(string $relation): RelationInterface { return $this->relations[$relation]; } @@ -39,11 +40,11 @@ public function relationLoaded(string $key): bool /** * Set the given relationship on the model. - * - * @param mixed $value + * Field is only used for validation. */ - public function setRelation(string $relation, $value): void + public function setRelation(string $relation, RelationInterface $value, string $field): void { + $this->validateField($relation, $field); $this->relations[$relation] = $value; } @@ -55,6 +56,15 @@ public function unsetRelation(string $relation): void unset($this->relations[$relation]); } + public function &getRelationResults(string $relation) + { + if (!$this->relationLoaded($relation) && !$this->$relation() instanceof RelationInterface) { + throw new NotARelationException("Called method \"{$relation}\" is not a Relation!"); + } + + return $this->getRelation($relation)->getResults(); + } + /** * Create a ReferencesOne Relation. * @@ -70,9 +80,15 @@ protected function referencesOne( bool $cacheable = true ): ReferencesOne { $relationName = $this->guessRelationName(); - $field = $field ?: $this->inferFieldForReference($relationName, $key, false); - return new ReferencesOne($this, $entity, $field, $relationName, $key, $cacheable); + if (!$this->relationLoaded($relationName)) { + $field = $field ?: $this->inferFieldForReference($relationName, $key, false); + + $relation = new ReferencesOne($this, $entity, $field, $key, $cacheable); + $this->setRelation($relationName, $relation, $field); + } + + return $this->getRelation($relationName); } /** @@ -89,9 +105,15 @@ protected function referencesMany( bool $cacheable = true ): ReferencesMany { $relationName = $this->guessRelationName(); - $field = $field ?: $this->inferFieldForReference($relationName, $key, true); - return new ReferencesMany($this, $entity, $field, $relationName, $key, $cacheable); + if (!$this->relationLoaded($relationName)) { + $field = $field ?: $this->inferFieldForReference($relationName, $key, true); + + $relation = new ReferencesMany($this, $entity, $field, $key, $cacheable); + $this->setRelation($relationName, $relation, $field); + } + + return $this->getRelation($relationName); } /** @@ -103,9 +125,15 @@ protected function referencesMany( protected function embedsOne(string $entity, string $field = null): EmbedsOne { $relationName = $this->guessRelationName(); - $field = $field ?: $this->inferFieldForEmbed($relationName); - return new EmbedsOne($this, $entity, $field, $relationName); + if (!$this->relationLoaded($relationName)) { + $field = $field ?: $this->inferFieldForEmbed($relationName); + + $relation = new EmbedsOne($this, $entity, $field); + $this->setRelation($relationName, $relation, $field); + } + + return $this->getRelation($relationName); } /** @@ -117,9 +145,15 @@ protected function embedsOne(string $entity, string $field = null): EmbedsOne protected function embedsMany(string $entity, string $field = null): EmbedsMany { $relationName = $this->guessRelationName(); - $field = $field ?: $this->inferFieldForEmbed($relationName); - return new EmbedsMany($this, $entity, $field, $relationName); + if (!$this->relationLoaded($relationName)) { + $field = $field ?: $this->inferFieldForEmbed($relationName); + + $relation = new EmbedsMany($this, $entity, $field); + $this->setRelation($relationName, $relation, $field); + } + + return $this->getRelation($relationName); } /** @@ -142,7 +176,6 @@ private function guessRelationName(): string { [$method, $relationType, $relation] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3); - // TODO validate that the relation has different name from field? return $relation['function']; } @@ -153,9 +186,9 @@ private function guessRelationName(): string * referenced key used. * * @example a `parent` relation on a `code` field - * would be infered as `parent_code`. + * would be inferred as `parent_code`. * @example a `addresses` relation on `_id` field - * would be infered as `addresses_ids`. + * would be inferred as `addresses_ids`. */ private function inferFieldForReference(string $relationName, string $key, bool $plural): string { @@ -170,8 +203,8 @@ private function inferFieldForReference(string $relationName, string $key, bool * This is useful for storing the relation on * a field based on the relation name. * - * @example a `comments` relation on would be infered as `embedded_comments`. - * @example a `tag` relation on would be infered as `embedded_tag`. + * @example a `comments` relation on would be inferred as `embedded_comments`. + * @example a `tag` relation on would be inferred as `embedded_tag`. */ private function inferFieldForEmbed(string $relationName): string { @@ -179,4 +212,17 @@ private function inferFieldForEmbed(string $relationName): string return 'embedded_'.$relationName; } + + /** + * Ensure that fieldName is not the same as the relationName. + * Otherwise, we would ran into trouble using magic accessors for relations. + */ + private function validateField(string $relationName, string $fieldName): void + { + if ($relationName === $fieldName) { + throw new InvalidFieldNameException( + "The field for relation \"{$relationName}\" cannot have the same name as the relation" + ); + } + } } diff --git a/src/Model/Relations/AbstractRelation.php b/src/Model/Relations/AbstractRelation.php index 03ebf7b6..f7e9104d 100644 --- a/src/Model/Relations/AbstractRelation.php +++ b/src/Model/Relations/AbstractRelation.php @@ -28,19 +28,12 @@ abstract class AbstractRelation implements RelationInterface protected $documentEmbedder; /** - * @var string + * @var bool */ - protected $relationName; + protected $pristine = false; - public function __construct(HasAttributesInterface $parent, string $entity, string $field, string $relationName) + public function __construct(HasAttributesInterface $parent, string $entity, string $field) { - if ($relationName === $field) { - throw new InvalidFieldNameException( - "The field for relation \"{$relationName}\" cannot have the same name as the relation" - ); - } - - $this->relationName = $relationName; $this->parent = $parent; $this->entity = $entity; $this->field = $field; @@ -48,5 +41,10 @@ public function __construct(HasAttributesInterface $parent, string $entity, stri $this->documentEmbedder = Ioc::make(DocumentEmbedder::class); } - abstract public function getResults(); + abstract public function &getResults(); + + protected function pristine(): bool + { + return $this->pristine; + } } diff --git a/src/Model/Relations/EmbedsMany.php b/src/Model/Relations/EmbedsMany.php index 242a884a..86dbedd2 100644 --- a/src/Model/Relations/EmbedsMany.php +++ b/src/Model/Relations/EmbedsMany.php @@ -3,19 +3,28 @@ use Mongolid\Container\Ioc; use Mongolid\Cursor\CursorFactory; +use Mongolid\Cursor\CursorInterface; +use Mongolid\Cursor\EmbeddedCursor; class EmbedsMany extends AbstractRelation { + /** + * Cached results. + * + * @var EmbeddedCursor + */ + private $cursor; + /** * Embed a new document to an attribute. It will also generate an * _id for the document if it's not present. * * @param mixed $entity model */ - public function add($entity) + public function add($entity): void { $this->documentEmbedder->embed($this->parent, $this->field, $entity); - $this->parent->unsetRelation($this->relationName); + $this->pristine = false; } /** @@ -24,26 +33,36 @@ public function add($entity) * * @param mixed $entity model or _id */ - public function remove($entity) + public function remove($entity): void { $this->documentEmbedder->unembed($this->parent, $this->field, $entity); - $this->parent->unsetRelation($this->relationName); // TODO better implementation of cache invalidation + $this->pristine = false; } - public function removeAll() + public function removeAll(): void { unset($this->parent->{$this->field}); - $this->parent->unsetRelation($this->relationName); + $this->pristine = false; } - public function getResults() + public function &getResults() { - $items = (array) $this->parent->{$this->field}; + if (!$this->pristine()) { + $items = (array) $this->parent->{$this->field}; - if (!empty($items) && !array_key_exists(0, $items)) { - $items = [$items]; + if (!empty($items) && !array_key_exists(0, $items)) { + $items = [$items]; + } + + $this->cursor = $this->createCursor($items); + $this->pristine = true; } + return $this->cursor; + } + + protected function createCursor($items): CursorInterface + { return Ioc::make(CursorFactory::class) ->createEmbeddedCursor($this->entity, $items); } diff --git a/src/Model/Relations/EmbedsOne.php b/src/Model/Relations/EmbedsOne.php index cd15bc28..30a34386 100644 --- a/src/Model/Relations/EmbedsOne.php +++ b/src/Model/Relations/EmbedsOne.php @@ -1,25 +1,33 @@ removeAll(); } - public function getResults() + public function &getResults() { - $items = (array) $this->parent->{$this->field}; + if (!$this->pristine()) { + $items = (array) $this->parent->{$this->field}; + + if (!empty($items) && !array_key_exists(0, $items)) { + $items = [$items]; + } - if (!empty($items) && !array_key_exists(0, $items)) { - $items = [$items]; + $this->document = $this->createCursor($items)->first(); + $this->pristine = true; } - return Ioc::make(CursorFactory::class) - ->createEmbeddedCursor($this->entity, $items)->first(); + return $this->document; } } diff --git a/src/Model/Relations/ReferencesMany.php b/src/Model/Relations/ReferencesMany.php index 2dab034f..0f9187b2 100644 --- a/src/Model/Relations/ReferencesMany.php +++ b/src/Model/Relations/ReferencesMany.php @@ -3,6 +3,7 @@ use MongoDB\BSON\ObjectId; use Mongolid\Container\Ioc; +use Mongolid\Cursor\CursorInterface; use Mongolid\Model\HasAttributesInterface; use Mongolid\Util\ObjectIdUtils; @@ -23,9 +24,16 @@ class ReferencesMany extends AbstractRelation */ protected $key; - public function __construct(HasAttributesInterface $parent, string $entity, string $field, string $relationName, string $key, bool $cacheable = true) + /** + * Cached results + * + * @var CursorInterface + */ + private $cursor; + + public function __construct(HasAttributesInterface $parent, string $entity, string $field, string $key, bool $cacheable = true) { - parent::__construct($parent, $entity, $field, $relationName); + parent::__construct($parent, $entity, $field); $this->key = $key; $this->documentEmbedder->setKey($key); $this->cacheable = $cacheable; @@ -37,10 +45,10 @@ public function __construct(HasAttributesInterface $parent, string $entity, stri * * @param mixed $entity model instance or _id to be referenced */ - public function attach($entity) + public function attach($entity): void { $this->documentEmbedder->attach($this->parent, $this->field, $entity); - $this->parent->unsetRelation($this->relationName); + $this->pristine = false; } /** @@ -49,35 +57,40 @@ public function attach($entity) * * @param mixed $entity document, model instance or _id that have been referenced by $field */ - public function detach($entity) + public function detach($entity): void { $this->documentEmbedder->detach($this->parent, $this->field, $entity); - $this->parent->unsetRelation($this->relationName); + $this->pristine = false; } /** * Removes all document references from relation. */ - public function detachAll() + public function detachAll(): void { unset($this->parent->{$this->field}); - $this->parent->unsetRelation($this->relationName); + $this->pristine = false; } - public function getResults() + public function &getResults() { - $referencedKeys = (array) $this->parent->{$this->field}; + if (!$this->pristine()) { + $referencedKeys = (array) $this->parent->{$this->field}; - if (ObjectIdUtils::isObjectId($referencedKeys[0] ?? '')) { - foreach ($referencedKeys as $key => $value) { - $referencedKeys[$key] = new ObjectId((string) $value); + if (ObjectIdUtils::isObjectId($referencedKeys[0] ?? '')) { + foreach ($referencedKeys as $key => $value) { + $referencedKeys[$key] = new ObjectId((string) $value); + } } + + $this->cursor = $this->entityInstance->where( + [$this->key => ['$in' => array_values($referencedKeys)]], + [], + $this->cacheable + ); + $this->pristine = true; } - return $this->entityInstance->where( - [$this->key => ['$in' => array_values($referencedKeys)]], - [], - $this->cacheable - ); + return $this->cursor; } } diff --git a/src/Model/Relations/ReferencesOne.php b/src/Model/Relations/ReferencesOne.php index 168d263e..5b85a67c 100644 --- a/src/Model/Relations/ReferencesOne.php +++ b/src/Model/Relations/ReferencesOne.php @@ -6,27 +6,39 @@ class ReferencesOne extends ReferencesMany { - public function detach($entity = null) + /** + * Cached result + * + * @var mixed + */ + private $document; + + public function detach($entity = null): void { $this->detachAll(); } - public function getResults() + public function &getResults() { - $referencedKey = $this->parent->{$this->field}; + if (!$this->pristine()) { + $referencedKey = $this->parent->{$this->field}; - if (is_array($referencedKey) && isset($referencedKey[0])) { - $referencedKey = $referencedKey[0]; - } + if (is_array($referencedKey) && isset($referencedKey[0])) { + $referencedKey = $referencedKey[0]; + } + + if (ObjectIdUtils::isObjectId($referencedKey)) { + $referencedKey = new ObjectId((string) $referencedKey); + } - if (ObjectIdUtils::isObjectId($referencedKey)) { - $referencedKey = new ObjectId((string) $referencedKey); + $this->document = $this->entityInstance->first( + [$this->key => $referencedKey], + [], + $this->cacheable + ); + $this->pristine = true; } - return $this->entityInstance->first( - [$this->key => $referencedKey], - [], - $this->cacheable - ); + return $this->document; } } diff --git a/tests/Unit/Model/HasRelationsTraitTest.php b/tests/Unit/Model/HasRelationsTraitTest.php index 2ba3684b..9b9d6940 100644 --- a/tests/Unit/Model/HasRelationsTraitTest.php +++ b/tests/Unit/Model/HasRelationsTraitTest.php @@ -156,7 +156,6 @@ public function referenceScenarios() ], ], ], - // TODO should not hit database? 'ActiveRecord referenced with null' => [ 'fieldValue' => null, 'expectedQuery' => [ From 534b3a1bc7a76c40e4cda3b4b81991ddef91e54c Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Tue, 13 Nov 2018 12:24:23 -0200 Subject: [PATCH 057/116] Invalidate relation cache when unsetting field --- src/Model/HasAttributesTrait.php | 4 + src/Model/HasRelationsTrait.php | 21 ++++- tests/Integration/EmbedsManyRelationTest.php | 72 ++++++++++------- tests/Integration/EmbedsOneRelationTest.php | 80 +++++++++++++------ .../ReferencesManyRelationTest.php | 40 ++++++---- .../Integration/ReferencesOneRelationTest.php | 60 ++++++++++---- 6 files changed, 192 insertions(+), 85 deletions(-) diff --git a/src/Model/HasAttributesTrait.php b/src/Model/HasAttributesTrait.php index 6f97aa50..b11426dd 100644 --- a/src/Model/HasAttributesTrait.php +++ b/src/Model/HasAttributesTrait.php @@ -120,6 +120,10 @@ public function fill(array $input, bool $force = false) public function cleanDocumentAttribute(string $key) { unset($this->attributes[$key]); + + if ($this->hasFieldRelation($key)) { + $this->unsetRelation($this->getFieldRelation($key)); + } } /** diff --git a/src/Model/HasRelationsTrait.php b/src/Model/HasRelationsTrait.php index 8db7bf6f..674440ef 100644 --- a/src/Model/HasRelationsTrait.php +++ b/src/Model/HasRelationsTrait.php @@ -16,12 +16,19 @@ trait HasRelationsTrait { /** - * The loaded relationships for the model. + * Relation cache. * * @var RelationInterface[] */ private $relations = []; + /** + * The bound between relations and fields. + * + * @var array + */ + private $fieldRelations = []; + /** * Get a specified relationship. */ @@ -40,12 +47,12 @@ public function relationLoaded(string $key): bool /** * Set the given relationship on the model. - * Field is only used for validation. */ public function setRelation(string $relation, RelationInterface $value, string $field): void { $this->validateField($relation, $field); $this->relations[$relation] = $value; + $this->fieldRelations[$field] = $relation; } /** @@ -65,6 +72,16 @@ public function &getRelationResults(string $relation) return $this->getRelation($relation)->getResults(); } + public function hasFieldRelation(string $field): bool + { + return isset($this->fieldRelations[$field]); + } + + public function getFieldRelation(string $field): string + { + return $this->fieldRelations[$field]; + } + /** * Create a ReferencesOne Relation. * diff --git a/tests/Integration/EmbedsManyRelationTest.php b/tests/Integration/EmbedsManyRelationTest.php index 5b6ff5c9..26793fee 100644 --- a/tests/Integration/EmbedsManyRelationTest.php +++ b/tests/Integration/EmbedsManyRelationTest.php @@ -14,84 +14,87 @@ public function testShouldRetrieveSiblingsOfUser() $john = $this->createUser('John'); $john->siblings()->add($chuck); - $this->assertSiblings([$chuck], $john); - // hit cache $this->assertSiblings([$chuck], $john); $mary = $this->createUser('Mary'); $john->siblings()->add($mary); - $this->assertSiblings([$chuck, $mary], $john); - // hit cache $this->assertSiblings([$chuck, $mary], $john); // remove one sibling $john->siblings()->remove($chuck); $this->assertSiblings([$mary], $john); - // hit cache - $this->assertSiblings([$mary], $john); // replace siblings + $john->siblings()->remove($mary); $bob = $this->createUser('Bob'); - // unset($john->embedded_siblings); // TODO make this work! - $john->siblings()->removeAll(); - $this->assertEmpty($john->siblings->all()); - $john->siblings()->add($bob); + // unset + $john->siblings()->add($bob); $this->assertSiblings([$bob], $john); - // hit cache + unset($john->embedded_siblings); + $this->assertEmpty($john->siblings->all()); + $this->assertEmpty($john->embedded_siblings); + + // remove all + $john->siblings()->add($bob); $this->assertSiblings([$bob], $john); + $john->siblings()->removeAll(); + $this->assertEmpty($john->siblings->all()); + $this->assertEmpty($john->embedded_siblings); - // remove with unembed + // remove + $john->siblings()->add($bob); + $this->assertSiblings([$bob], $john); $john->siblings()->remove($bob); - $this->assertEmpty($john->embedded_siblings); $this->assertEmpty($john->siblings->all()); } public function testShouldRetrieveGrandsonsOfUserUsingCustomKey() { - // create sibling + // create grandson $chuck = $this->createUser('Chuck'); $john = $this->createUser('John'); $john->grandsons()->add($chuck); $this->assertSame([$chuck], $john->other_arbitrary_field); $this->assertGrandsons([$chuck], $john); - // hit cache - $this->assertGrandsons([$chuck], $john); $mary = $this->createUser('Mary'); $john->grandsons()->add($mary); $this->assertSame([$chuck, $mary], $john->other_arbitrary_field); $this->assertGrandsons([$chuck, $mary], $john); - // hit cache - $this->assertGrandsons([$chuck, $mary], $john); - // remove one sibling + // remove one grandson $john->grandsons()->remove($chuck); $this->assertSame([$mary], $john->other_arbitrary_field); $this->assertGrandsons([$mary], $john); - // hit cache - $this->assertGrandsons([$mary], $john); // replace grandsons + $john->grandsons()->remove($mary); $bob = $this->createUser('Bob'); - // unset($john->other_arbitrary_field); // TODO make this work! - $john->grandsons()->removeAll(); - $this->assertEmpty($john->grandsons->all()); - $john->grandsons()->add($bob); - $this->assertSame([$bob], $john->other_arbitrary_field); + // unset + $john->grandsons()->add($bob); $this->assertGrandsons([$bob], $john); - // hit cache + unset($john->other_arbitrary_field); + $this->assertEmpty($john->other_arbitrary_field); + $this->assertEmpty($john->grandsons->all()); + + // removeAll + $john->grandsons()->add($bob); $this->assertGrandsons([$bob], $john); + $john->grandsons()->removeAll(); + $this->assertEmpty($john->other_arbitrary_field); + $this->assertEmpty($john->grandsons->all()); - // remove with unembed + // remove + $john->grandsons()->add($bob); + $this->assertGrandsons([$bob], $john); $john->grandsons()->remove($bob); - $this->assertEmpty($john->other_arbitrary_field); $this->assertEmpty($john->grandsons->all()); } @@ -112,6 +115,12 @@ private function assertSiblings($expected, EmbeddedUser $model) $this->assertInstanceOf(CursorInterface::class, $siblings); $this->assertEquals($expected, $siblings->all()); $this->assertSame($expected, $model->embedded_siblings); + + // hit cache + $siblings = $model->siblings; + $this->assertInstanceOf(CursorInterface::class, $siblings); + $this->assertEquals($expected, $siblings->all()); + $this->assertSame($expected, $model->embedded_siblings); } private function assertGrandsons($expected, EmbeddedUser $model) @@ -119,5 +128,10 @@ private function assertGrandsons($expected, EmbeddedUser $model) $grandsons = $model->grandsons; $this->assertInstanceOf(CursorInterface::class, $grandsons); $this->assertEquals($expected, $grandsons->all()); + + // hit cache + $grandsons = $model->grandsons; + $this->assertInstanceOf(CursorInterface::class, $grandsons); + $this->assertEquals($expected, $grandsons->all()); } } diff --git a/tests/Integration/EmbedsOneRelationTest.php b/tests/Integration/EmbedsOneRelationTest.php index a7c01ec7..6d7cc708 100644 --- a/tests/Integration/EmbedsOneRelationTest.php +++ b/tests/Integration/EmbedsOneRelationTest.php @@ -14,22 +14,32 @@ public function testShouldRetrieveParentOfUser() $john = $this->createUser('John'); $john->parent()->add($chuck); - $this->assertParent($chuck, $john); - // hit cache $this->assertParent($chuck, $john); // replace parent $bob = $this->createUser('Bob'); $john->parent()->remove(); //todo remove this line and ensure only one parent is added - $john->parent()->add($bob); + // unset + $john->parent()->add($bob); $this->assertParent($bob, $john); - // hit cache + unset($john->embedded_parent); + + $this->assertNull($john->embedded_parent); + $this->assertNull($john->parent); + + // remove all + $john->parent()->add($bob); $this->assertParent($bob, $john); + $john->parent()->removeAll(); + + $this->assertNull($john->embedded_parent); + $this->assertNull($john->parent); // remove - //unset($john->embedded_parent);// TODO make this work! - $john->parent()->removeAll(); + $john->parent()->add($bob); + $this->assertParent($bob, $john); + $john->parent()->remove($bob); $this->assertNull($john->embedded_parent); $this->assertNull($john->parent); @@ -42,27 +52,50 @@ public function testShouldRetrieveSonOfUserUsingCustomKey() $john = $this->createUser('John'); $john->son()->add($chuck); - $this->assertSon($chuck, $john); - // hit cache $this->assertSon($chuck, $john); // replace son $bob = $this->createUser('Bob'); $john->son()->remove(); //todo remove this line and ensure only one son is added - $john->son()->add($bob); + // unset + $john->son()->add($bob); $this->assertSon($bob, $john); - // hit cache + unset($john->arbitrary_field); + + $this->assertNull($john->arbitrary_field); + $this->assertNull($john->son); + + // remove all + $john->son()->add($bob); $this->assertSon($bob, $john); + $john->son()->removeAll(); + + $this->assertNull($john->arbitrary_field); + $this->assertNull($john->son); // remove - //unset($john->arbitrary_field);// TODO make this work! - $john->son()->removeAll(); + $john->son()->add($bob); + $this->assertSon($bob, $john); + $john->son()->remove($bob); $this->assertNull($john->arbitrary_field); $this->assertNull($john->son); } + public function testShouldCatchInvalidFieldNameOnRelations() + { + // Set + $user = new EmbeddedUser(); + + // Expectations + $this->expectException(InvalidFieldNameException::class); + $this->expectExceptionMessage('The field for relation "sameName" cannot have the same name as the relation'); + + // Actions + $user->sameName; + } + private function createUser(string $name): EmbeddedUser { $user = new EmbeddedUser(); @@ -79,6 +112,12 @@ private function assertParent($expected, EmbeddedUser $model) $this->assertInstanceOf(EmbeddedUser::class, $parent); $this->assertEquals($expected, $parent); $this->assertSame([$expected], $model->embedded_parent); // TODO store as single array + + // hit cache + $parent = $model->parent; + $this->assertInstanceOf(EmbeddedUser::class, $parent); + $this->assertEquals($expected, $parent); + $this->assertSame([$expected], $model->embedded_parent); } private function assertSon($expected, EmbeddedUser $model) @@ -87,18 +126,11 @@ private function assertSon($expected, EmbeddedUser $model) $this->assertInstanceOf(EmbeddedUser::class, $son); $this->assertEquals($expected, $son); $this->assertSame([$expected], $model->arbitrary_field); // TODO store as single array - } - public function testShouldCatchInvalidFieldNameOnRelations() - { - // Set - $user = new EmbeddedUser(); - - // Expectations - $this->expectException(InvalidFieldNameException::class); - $this->expectExceptionMessage('The field for relation "sameName" cannot have the same name as the relation'); - - // Actions - $user->sameName; + // hit cache + $son = $model->son; + $this->assertInstanceOf(EmbeddedUser::class, $son); + $this->assertEquals($expected, $son); + $this->assertSame([$expected], $model->arbitrary_field); } } diff --git a/tests/Integration/ReferencesManyRelationTest.php b/tests/Integration/ReferencesManyRelationTest.php index b9c35301..37e20597 100644 --- a/tests/Integration/ReferencesManyRelationTest.php +++ b/tests/Integration/ReferencesManyRelationTest.php @@ -14,22 +14,16 @@ public function testShouldRetrieveSiblingsOfUser() $john = $this->createUser('John'); $john->siblings()->attach($chuck); - $this->assertSiblings([$chuck], $john); - // hit cache $this->assertSiblings([$chuck], $john); $mary = $this->createUser('Mary'); $john->siblings()->attach($mary); - $this->assertSiblings([$chuck, $mary], $john); - // hit cache $this->assertSiblings([$chuck, $mary], $john); // remove one sibling $john->siblings()->detach($chuck); $this->assertSiblings([$mary], $john); - // hit cache - $this->assertSiblings([$mary], $john); // replace siblings $bob = $this->createUser('Bob'); @@ -38,8 +32,6 @@ public function testShouldRetrieveSiblingsOfUser() $this->assertEmpty($john->siblings->all()); $john->siblings()->attach($bob); - $this->assertSiblings([$bob], $john); - // hit cache $this->assertSiblings([$bob], $john); // remove with unembed @@ -58,24 +50,18 @@ public function testShouldRetrieveGrandsonsOfUserUsingCustomKey() $this->assertSame(['010'], $john->grandsons_codes); $this->assertGrandsons([$chuck], $john); - // hit cache - $this->assertGrandsons([$chuck], $john); $mary = $this->createUser('Mary', '222'); $john->grandsons()->attach($mary); $this->assertSame(['010', '222'], $john->grandsons_codes); $this->assertGrandsons([$chuck, $mary], $john); - // hit cache - $this->assertGrandsons([$chuck, $mary], $john); // remove one sibling $john->grandsons()->detach($chuck); $this->assertSame(['222'], $john->grandsons_codes); $this->assertGrandsons([$mary], $john); - // hit cache - $this->assertGrandsons([$mary], $john); // replace grandsons $bob = $this->createUser('Bob', '987'); @@ -86,8 +72,6 @@ public function testShouldRetrieveGrandsonsOfUserUsingCustomKey() $this->assertSame(['987'], $john->grandsons_codes); $this->assertGrandsons([$bob], $john); - // hit cache - $this->assertGrandsons([$bob], $john); // remove with unembed $john->grandsons()->detach($bob); @@ -120,6 +104,18 @@ private function assertSiblings($expected, ReferencedUser $model) } $this->assertSame($ids, $model->siblings_ids); + + // hit cache + $siblings = $model->siblings; + $this->assertInstanceOf(CursorInterface::class, $siblings); + $this->assertEquals($expected, $siblings->all()); + + $ids = []; + foreach ($expected as $expectedModel) { + $ids[] = $expectedModel->_id; + } + + $this->assertSame($ids, $model->siblings_ids); } private function assertGrandsons($expected, ReferencedUser $model) @@ -133,5 +129,17 @@ private function assertGrandsons($expected, ReferencedUser $model) } $this->assertSame($codes, $model->grandsons_codes); + + // hit cache + $grandsons = $model->grandsons; + $this->assertInstanceOf(CursorInterface::class, $grandsons); + $this->assertEquals($expected, $grandsons->all()); + + $codes = []; + foreach ($expected as $expectedModel) { + $codes[] = $expectedModel->code; + } + + $this->assertSame($codes, $model->grandsons_codes); } } diff --git a/tests/Integration/ReferencesOneRelationTest.php b/tests/Integration/ReferencesOneRelationTest.php index 3ce620a4..ff00bbe0 100644 --- a/tests/Integration/ReferencesOneRelationTest.php +++ b/tests/Integration/ReferencesOneRelationTest.php @@ -14,22 +14,32 @@ public function testShouldRetrieveParentOfUser() $john = $this->createUser('John'); $john->parent()->attach($chuck); - $this->assertParent($chuck, $john); - // hit cache $this->assertParent($chuck, $john); // replace parent $bob = $this->createUser('Bob'); $john->parent()->detach(); //todo remove this line and ensure only one parent is attached - $john->parent()->attach($bob); + // unset + $john->parent()->attach($bob); $this->assertParent($bob, $john); - // hit cache + unset($john->parent_id); + + $this->assertNull($john->parent_id); + $this->assertNull($john->parent); + + // detach all + $john->parent()->attach($bob); $this->assertParent($bob, $john); + $john->parent()->detachAll(); - // remove - //unset($john->parent_id);// TODO make this work! - $john->parent()->detach(); + $this->assertNull($john->parent_id); + $this->assertNull($john->parent); + + // detach + $john->parent()->attach($bob); + $this->assertParent($bob, $john); + $john->parent()->detach($bob); $this->assertNull($john->parent_id); $this->assertNull($john->parent); @@ -42,22 +52,32 @@ public function testShouldRetrieveSonOfUserUsingCustomKey() $john = $this->createUser('John', '369'); $john->son()->attach($chuck); - $this->assertSon($chuck, $john); - // hit cache $this->assertSon($chuck, $john); // replace son $bob = $this->createUser('Bob', '987'); $john->son()->detach(); //todo remove this line and ensure only one son is attached - $john->son()->attach($bob); + // unset + $john->son()->attach($bob); $this->assertSon($bob, $john); - // hit cache + unset($john->arbitrary_field); + + $this->assertNull($john->arbitrary_field); + $this->assertNull($john->son); + + // detachAll + $john->son()->attach($bob); $this->assertSon($bob, $john); + $john->son()->detachAll(); - // remove - //unset($john->arbitrary_field);// TODO make this work! - $john->son()->detach(); + $this->assertNull($john->arbitrary_field); + $this->assertNull($john->son); + + // detach + $john->son()->attach($bob); + $this->assertSon($bob, $john); + $john->son()->detach($bob); $this->assertNull($john->arbitrary_field); $this->assertNull($john->son); @@ -95,6 +115,12 @@ private function assertParent($expected, ReferencedUser $model) $this->assertInstanceOf(ReferencedUser::class, $parent); $this->assertEquals($expected, $parent); $this->assertEquals([$expected->_id], $model->parent_id); // TODO store as single code (not array) + + // hit cache + $parent = $model->parent; + $this->assertInstanceOf(ReferencedUser::class, $parent); + $this->assertEquals($expected, $parent); + $this->assertEquals([$expected->_id], $model->parent_id); } private function assertSon($expected, ReferencedUser $model) @@ -103,5 +129,11 @@ private function assertSon($expected, ReferencedUser $model) $this->assertInstanceOf(ReferencedUser::class, $son); $this->assertEquals($expected, $son); $this->assertSame([$expected->code], $model->arbitrary_field); // TODO store as single code (not array) + + // hit cache + $son = $model->son; + $this->assertInstanceOf(ReferencedUser::class, $son); + $this->assertEquals($expected, $son); + $this->assertSame([$expected->code], $model->arbitrary_field); } } From d81e1eef98dcde657361f16cfbc6b67ed0ea2156 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Tue, 13 Nov 2018 13:53:22 -0200 Subject: [PATCH 058/116] Invalidate relation cache when setting field directly --- src/Model/HasAttributesTrait.php | 4 ++ tests/Integration/EmbedsManyRelationTest.php | 28 ++++++++ tests/Integration/EmbedsOneRelationTest.php | 33 +++++++-- .../ReferencesManyRelationTest.php | 69 ++++++++++++++++--- .../Integration/ReferencesOneRelationTest.php | 34 +++++++-- tests/Unit/Model/HasAttributesTraitTest.php | 8 +++ 6 files changed, 154 insertions(+), 22 deletions(-) diff --git a/src/Model/HasAttributesTrait.php b/src/Model/HasAttributesTrait.php index b11426dd..d266e20d 100644 --- a/src/Model/HasAttributesTrait.php +++ b/src/Model/HasAttributesTrait.php @@ -136,6 +136,10 @@ public function setDocumentAttribute(string $key, $value) } $this->attributes[$key] = $value; + + if ($this->hasFieldRelation($key)) { + $this->unsetRelation($this->getFieldRelation($key)); + } } /** diff --git a/tests/Integration/EmbedsManyRelationTest.php b/tests/Integration/EmbedsManyRelationTest.php index 26793fee..1007fa75 100644 --- a/tests/Integration/EmbedsManyRelationTest.php +++ b/tests/Integration/EmbedsManyRelationTest.php @@ -49,6 +49,20 @@ public function testShouldRetrieveSiblingsOfUser() $john->siblings()->remove($bob); $this->assertEmpty($john->embedded_siblings); $this->assertEmpty($john->siblings->all()); + + // changing the field directly + $john->siblings()->add($bob); + $this->assertSiblings([$bob], $john); + $john->embedded_siblings = [$chuck]; + $this->assertSiblings([$chuck], $john); + + $john->siblings()->removeAll(); + + // changing the field with fillable + $john->siblings()->add($bob); + $this->assertSiblings([$bob], $john); + $john->fill(['embedded_siblings' => [$chuck]], true); + $this->assertSiblings([$chuck], $john); } public function testShouldRetrieveGrandsonsOfUserUsingCustomKey() @@ -97,6 +111,20 @@ public function testShouldRetrieveGrandsonsOfUserUsingCustomKey() $john->grandsons()->remove($bob); $this->assertEmpty($john->other_arbitrary_field); $this->assertEmpty($john->grandsons->all()); + + // changing the field directly + $john->grandsons()->add($bob); + $this->assertGrandsons([$bob], $john); + $john->other_arbitrary_field = [$chuck]; + $this->assertGrandsons([$chuck], $john); + + $john->grandsons()->removeAll(); + + // changing the field with fillable + $john->grandsons()->add($bob); + $this->assertGrandsons([$bob], $john); + $john->fill(['other_arbitrary_field' => [$chuck]], true); + $this->assertGrandsons([$chuck], $john); } private function createUser(string $name): EmbeddedUser diff --git a/tests/Integration/EmbedsOneRelationTest.php b/tests/Integration/EmbedsOneRelationTest.php index 6d7cc708..4c9003f7 100644 --- a/tests/Integration/EmbedsOneRelationTest.php +++ b/tests/Integration/EmbedsOneRelationTest.php @@ -32,7 +32,6 @@ public function testShouldRetrieveParentOfUser() $john->parent()->add($bob); $this->assertParent($bob, $john); $john->parent()->removeAll(); - $this->assertNull($john->embedded_parent); $this->assertNull($john->parent); @@ -40,9 +39,22 @@ public function testShouldRetrieveParentOfUser() $john->parent()->add($bob); $this->assertParent($bob, $john); $john->parent()->remove($bob); - $this->assertNull($john->embedded_parent); $this->assertNull($john->parent); + + // changing the field directly + $john->parent()->add($bob); + $this->assertParent($bob, $john); + $john->embedded_parent = [$chuck]; + $this->assertParent($chuck, $john); + + $john->parent()->removeAll(); + + // changing the field with fillable + $john->parent()->add($bob); + $this->assertParent($bob, $john); + $john->fill(['embedded_parent' => [$chuck]], true); + $this->assertParent($chuck, $john); } public function testShouldRetrieveSonOfUserUsingCustomKey() @@ -62,7 +74,6 @@ public function testShouldRetrieveSonOfUserUsingCustomKey() $john->son()->add($bob); $this->assertSon($bob, $john); unset($john->arbitrary_field); - $this->assertNull($john->arbitrary_field); $this->assertNull($john->son); @@ -70,7 +81,6 @@ public function testShouldRetrieveSonOfUserUsingCustomKey() $john->son()->add($bob); $this->assertSon($bob, $john); $john->son()->removeAll(); - $this->assertNull($john->arbitrary_field); $this->assertNull($john->son); @@ -78,9 +88,22 @@ public function testShouldRetrieveSonOfUserUsingCustomKey() $john->son()->add($bob); $this->assertSon($bob, $john); $john->son()->remove($bob); - $this->assertNull($john->arbitrary_field); $this->assertNull($john->son); + + // changing the field directly + $john->son()->add($bob); + $this->assertSon($bob, $john); + $john->arbitrary_field = [$chuck]; + $this->assertSon($chuck, $john); + + $john->son()->removeAll(); + + // changing the field with fillable + $john->son()->add($bob); + $this->assertSon($bob, $john); + $john->fill(['arbitrary_field' => [$chuck]], true); + $this->assertSon($chuck, $john); } public function testShouldCatchInvalidFieldNameOnRelations() diff --git a/tests/Integration/ReferencesManyRelationTest.php b/tests/Integration/ReferencesManyRelationTest.php index 37e20597..257f8a9b 100644 --- a/tests/Integration/ReferencesManyRelationTest.php +++ b/tests/Integration/ReferencesManyRelationTest.php @@ -26,19 +26,43 @@ public function testShouldRetrieveSiblingsOfUser() $this->assertSiblings([$mary], $john); // replace siblings + $john->siblings()->detach($mary); $bob = $this->createUser('Bob'); - // unset($john->siblings_ids); // TODO make this work! - $john->siblings()->detachAll(); - $this->assertEmpty($john->siblings->all()); + + // unset $john->siblings()->attach($bob); + $this->assertSiblings([$bob], $john); + unset($john->siblings_ids); + $this->assertEmpty($john->siblings_ids); + $this->assertEmpty($john->siblings->all()); + // detachAll + $john->siblings()->attach($bob); $this->assertSiblings([$bob], $john); + $john->siblings()->detachAll(); + $this->assertEmpty($john->siblings_ids); + $this->assertEmpty($john->siblings->all()); - // remove with unembed + // detach + $john->siblings()->attach($bob); + $this->assertSiblings([$bob], $john); $john->siblings()->detach($bob); - $this->assertEmpty($john->siblings_ids); $this->assertEmpty($john->siblings->all()); + + // changing the field directly + $john->siblings()->attach($bob); + $this->assertSiblings([$bob], $john); + $john->siblings_ids = [$chuck->_id]; + $this->assertSiblings([$chuck], $john); + + $john->siblings()->detachAll(); + + // changing the field with fillable + $john->siblings()->attach($bob); + $this->assertSiblings([$bob], $john); + $john->fill(['siblings_ids' => [$chuck->_id]], true); + $this->assertSiblings([$chuck], $john); } public function testShouldRetrieveGrandsonsOfUserUsingCustomKey() @@ -64,20 +88,43 @@ public function testShouldRetrieveGrandsonsOfUserUsingCustomKey() $this->assertGrandsons([$mary], $john); // replace grandsons + $john->grandsons()->detach($mary); $bob = $this->createUser('Bob', '987'); - // unset($john->grandsons_codes); // TODO make this work! - $john->grandsons()->detachAll(); - $this->assertEmpty($john->grandsons->all()); + + // unset $john->grandsons()->attach($bob); + $this->assertGrandsons([$bob], $john); + unset($john->grandsons_codes); + $this->assertEmpty($john->grandsons_codes); + $this->assertEmpty($john->grandsons->all()); - $this->assertSame(['987'], $john->grandsons_codes); + // detachAll + $john->grandsons()->attach($bob); $this->assertGrandsons([$bob], $john); + $john->grandsons()->detachAll(); + $this->assertEmpty($john->grandsons_codes); + $this->assertEmpty($john->grandsons->all()); - // remove with unembed + // detach + $john->grandsons()->attach($bob); + $this->assertGrandsons([$bob], $john); $john->grandsons()->detach($bob); - $this->assertEmpty($john->grandsons_codes); $this->assertEmpty($john->grandsons->all()); + + // changing the field directly + $john->grandsons()->attach($bob); + $this->assertGrandsons([$bob], $john); + $john->grandsons_codes = [$chuck->code]; + $this->assertGrandsons([$chuck], $john); + + $john->grandsons()->detachAll(); + + // changing the field with fillable + $john->grandsons()->attach($bob); + $this->assertGrandsons([$bob], $john); + $john->fill(['grandsons_codes' => [$chuck->code]], true); + $this->assertGrandsons([$chuck], $john); } private function createUser(string $name, string $code = null): ReferencedUser diff --git a/tests/Integration/ReferencesOneRelationTest.php b/tests/Integration/ReferencesOneRelationTest.php index ff00bbe0..3518a647 100644 --- a/tests/Integration/ReferencesOneRelationTest.php +++ b/tests/Integration/ReferencesOneRelationTest.php @@ -24,7 +24,6 @@ public function testShouldRetrieveParentOfUser() $john->parent()->attach($bob); $this->assertParent($bob, $john); unset($john->parent_id); - $this->assertNull($john->parent_id); $this->assertNull($john->parent); @@ -32,7 +31,6 @@ public function testShouldRetrieveParentOfUser() $john->parent()->attach($bob); $this->assertParent($bob, $john); $john->parent()->detachAll(); - $this->assertNull($john->parent_id); $this->assertNull($john->parent); @@ -40,9 +38,22 @@ public function testShouldRetrieveParentOfUser() $john->parent()->attach($bob); $this->assertParent($bob, $john); $john->parent()->detach($bob); - $this->assertNull($john->parent_id); $this->assertNull($john->parent); + + // changing the field directly + $john->parent()->attach($bob); + $this->assertParent($bob, $john); + $john->parent_id = [$chuck->_id]; + $this->assertParent($chuck, $john); + + $john->parent()->detachAll(); + + // changing the field with fillable + $john->parent()->attach($bob); + $this->assertParent($bob, $john); + $john->fill(['parent_id' => [$chuck->_id]], true); + $this->assertParent($chuck, $john); } public function testShouldRetrieveSonOfUserUsingCustomKey() @@ -62,7 +73,6 @@ public function testShouldRetrieveSonOfUserUsingCustomKey() $john->son()->attach($bob); $this->assertSon($bob, $john); unset($john->arbitrary_field); - $this->assertNull($john->arbitrary_field); $this->assertNull($john->son); @@ -70,7 +80,6 @@ public function testShouldRetrieveSonOfUserUsingCustomKey() $john->son()->attach($bob); $this->assertSon($bob, $john); $john->son()->detachAll(); - $this->assertNull($john->arbitrary_field); $this->assertNull($john->son); @@ -78,9 +87,22 @@ public function testShouldRetrieveSonOfUserUsingCustomKey() $john->son()->attach($bob); $this->assertSon($bob, $john); $john->son()->detach($bob); - $this->assertNull($john->arbitrary_field); $this->assertNull($john->son); + + // changing the field directly + $john->son()->attach($bob); + $this->assertSon($bob, $john); + $john->arbitrary_field = [$chuck->code]; + $this->assertSon($chuck, $john); + + $john->son()->detachAll(); + + // changing the field with fillable + $john->son()->attach($bob); + $this->assertSon($bob, $john); + $john->fill(['arbitrary_field' => [$chuck->code]], true); + $this->assertSon($chuck, $john); } public function testShouldCatchInvalidRelations() diff --git a/tests/Unit/Model/HasAttributesTraitTest.php b/tests/Unit/Model/HasAttributesTraitTest.php index d19eb1ac..d03bd5b6 100644 --- a/tests/Unit/Model/HasAttributesTraitTest.php +++ b/tests/Unit/Model/HasAttributesTraitTest.php @@ -11,6 +11,7 @@ public function testShouldGetAttributeFromMutator() $model = new class() { use HasAttributesTrait; + use HasRelationsTrait; public function __construct() { @@ -36,6 +37,7 @@ public function testShouldIgnoreMutators() $model = new class() { use HasAttributesTrait; + use HasRelationsTrait; public function getShortNameDocumentAttribute() { @@ -61,6 +63,7 @@ public function testShouldSetAttributeFromMutator() $model = new class() { use HasAttributesTrait; + use HasRelationsTrait; public function __construct() { @@ -93,6 +96,7 @@ public function testShouldFillOnlyPermittedAttributes( $model = new class($fillable, $guarded) { use HasAttributesTrait; + use HasRelationsTrait; public function __construct(array $fillable, array $guarded) { @@ -114,6 +118,7 @@ public function testShouldForceFillAttributes() $model = new class() { use HasAttributesTrait; + use HasRelationsTrait; }; $input = [ @@ -134,6 +139,7 @@ public function testShouldBeCastableToArray() $model = new class() { use HasAttributesTrait; + use HasRelationsTrait; }; $model->setDocumentAttribute('name', 'John'); @@ -152,6 +158,7 @@ public function testShouldSetOriginalAttributes() $model = new class() implements HasAttributesInterface { use HasAttributesTrait; + use HasRelationsTrait; }; $model->name = 'John'; @@ -170,6 +177,7 @@ public function testShouldFallbackOriginalAttributesIfUnserializationFails() $model = new class() implements HasAttributesInterface { use HasAttributesTrait; + use HasRelationsTrait; public function __construct() { From 0970c550e37ce8def74509a7139433c0a286e20a Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Tue, 13 Nov 2018 14:01:09 -0200 Subject: [PATCH 059/116] Simplify logic relations' cached results --- src/Model/Relations/AbstractRelation.php | 29 +++++++++++++++- src/Model/Relations/EmbedsMany.php | 25 ++++---------- src/Model/Relations/EmbedsOne.php | 25 +++++--------- src/Model/Relations/ReferencesMany.php | 43 ++++++++++-------------- src/Model/Relations/ReferencesOne.php | 36 +++++++------------- 5 files changed, 72 insertions(+), 86 deletions(-) diff --git a/src/Model/Relations/AbstractRelation.php b/src/Model/Relations/AbstractRelation.php index f7e9104d..6713d586 100644 --- a/src/Model/Relations/AbstractRelation.php +++ b/src/Model/Relations/AbstractRelation.php @@ -32,6 +32,13 @@ abstract class AbstractRelation implements RelationInterface */ protected $pristine = false; + /** + * Cached results. + * + * @var mixed + */ + protected $results; + public function __construct(HasAttributesInterface $parent, string $entity, string $field) { $this->parent = $parent; @@ -41,7 +48,27 @@ public function __construct(HasAttributesInterface $parent, string $entity, stri $this->documentEmbedder = Ioc::make(DocumentEmbedder::class); } - abstract public function &getResults(); + /** + * Retrieve Relation Results. + * + * @return mixed + */ + abstract public function get(); + + /** + * Retrieve cached Relation Results. + * + * @return mixed + */ + public function &getResults() + { + if (!$this->pristine()) { + $this->results = $this->get(); + $this->pristine = true; + } + + return $this->results; + } protected function pristine(): bool { diff --git a/src/Model/Relations/EmbedsMany.php b/src/Model/Relations/EmbedsMany.php index 86dbedd2..3dc8317f 100644 --- a/src/Model/Relations/EmbedsMany.php +++ b/src/Model/Relations/EmbedsMany.php @@ -3,18 +3,10 @@ use Mongolid\Container\Ioc; use Mongolid\Cursor\CursorFactory; -use Mongolid\Cursor\CursorInterface; use Mongolid\Cursor\EmbeddedCursor; class EmbedsMany extends AbstractRelation { - /** - * Cached results. - * - * @var EmbeddedCursor - */ - private $cursor; - /** * Embed a new document to an attribute. It will also generate an * _id for the document if it's not present. @@ -45,23 +37,18 @@ public function removeAll(): void $this->pristine = false; } - public function &getResults() + public function get() { - if (!$this->pristine()) { - $items = (array) $this->parent->{$this->field}; - - if (!empty($items) && !array_key_exists(0, $items)) { - $items = [$items]; - } + $items = (array) $this->parent->{$this->field}; - $this->cursor = $this->createCursor($items); - $this->pristine = true; + if (!empty($items) && !array_key_exists(0, $items)) { + $items = [$items]; } - return $this->cursor; + return $this->createCursor($items); } - protected function createCursor($items): CursorInterface + protected function createCursor($items): EmbeddedCursor { return Ioc::make(CursorFactory::class) ->createEmbeddedCursor($this->entity, $items); diff --git a/src/Model/Relations/EmbedsOne.php b/src/Model/Relations/EmbedsOne.php index 30a34386..7416c0d9 100644 --- a/src/Model/Relations/EmbedsOne.php +++ b/src/Model/Relations/EmbedsOne.php @@ -3,31 +3,22 @@ class EmbedsOne extends EmbedsMany { - /** - * Cached result. - * - * @var mixed - */ - private $document; - public function remove($entity = null): void { $this->removeAll(); } - public function &getResults() + /** + * @return mixed + */ + public function get() { - if (!$this->pristine()) { - $items = (array) $this->parent->{$this->field}; - - if (!empty($items) && !array_key_exists(0, $items)) { - $items = [$items]; - } + $items = (array) $this->parent->{$this->field}; - $this->document = $this->createCursor($items)->first(); - $this->pristine = true; + if (!empty($items) && !array_key_exists(0, $items)) { + $items = [$items]; } - return $this->document; + return $this->createCursor($items)->first(); } } diff --git a/src/Model/Relations/ReferencesMany.php b/src/Model/Relations/ReferencesMany.php index 0f9187b2..cdb902ec 100644 --- a/src/Model/Relations/ReferencesMany.php +++ b/src/Model/Relations/ReferencesMany.php @@ -3,7 +3,6 @@ use MongoDB\BSON\ObjectId; use Mongolid\Container\Ioc; -use Mongolid\Cursor\CursorInterface; use Mongolid\Model\HasAttributesInterface; use Mongolid\Util\ObjectIdUtils; @@ -24,21 +23,20 @@ class ReferencesMany extends AbstractRelation */ protected $key; - /** - * Cached results - * - * @var CursorInterface - */ - private $cursor; - - public function __construct(HasAttributesInterface $parent, string $entity, string $field, string $key, bool $cacheable = true) - { + public function __construct( + HasAttributesInterface $parent, + string $entity, + string $field, + string $key, + bool $cacheable = true + ) { parent::__construct($parent, $entity, $field); $this->key = $key; $this->documentEmbedder->setKey($key); $this->cacheable = $cacheable; $this->entityInstance = Ioc::make($this->entity); } + /** * Attach document _id reference to an attribute. It will also generate an * _id for the document if it's not present. @@ -72,25 +70,20 @@ public function detachAll(): void $this->pristine = false; } - public function &getResults() + public function get() { - if (!$this->pristine()) { - $referencedKeys = (array) $this->parent->{$this->field}; + $referencedKeys = (array) $this->parent->{$this->field}; - if (ObjectIdUtils::isObjectId($referencedKeys[0] ?? '')) { - foreach ($referencedKeys as $key => $value) { - $referencedKeys[$key] = new ObjectId((string) $value); - } + if (ObjectIdUtils::isObjectId($referencedKeys[0] ?? '')) { + foreach ($referencedKeys as $key => $value) { + $referencedKeys[$key] = new ObjectId((string) $value); } - - $this->cursor = $this->entityInstance->where( - [$this->key => ['$in' => array_values($referencedKeys)]], - [], - $this->cacheable - ); - $this->pristine = true; } - return $this->cursor; + return $this->entityInstance->where( + [$this->key => ['$in' => array_values($referencedKeys)]], + [], + $this->cacheable + ); } } diff --git a/src/Model/Relations/ReferencesOne.php b/src/Model/Relations/ReferencesOne.php index 5b85a67c..baff3ff3 100644 --- a/src/Model/Relations/ReferencesOne.php +++ b/src/Model/Relations/ReferencesOne.php @@ -6,39 +6,27 @@ class ReferencesOne extends ReferencesMany { - /** - * Cached result - * - * @var mixed - */ - private $document; - public function detach($entity = null): void { $this->detachAll(); } - public function &getResults() + public function get() { - if (!$this->pristine()) { - $referencedKey = $this->parent->{$this->field}; - - if (is_array($referencedKey) && isset($referencedKey[0])) { - $referencedKey = $referencedKey[0]; - } + $referencedKey = $this->parent->{$this->field}; - if (ObjectIdUtils::isObjectId($referencedKey)) { - $referencedKey = new ObjectId((string) $referencedKey); - } + if (is_array($referencedKey) && isset($referencedKey[0])) { + $referencedKey = $referencedKey[0]; + } - $this->document = $this->entityInstance->first( - [$this->key => $referencedKey], - [], - $this->cacheable - ); - $this->pristine = true; + if (ObjectIdUtils::isObjectId($referencedKey)) { + $referencedKey = new ObjectId((string) $referencedKey); } - return $this->document; + return $this->entityInstance->first( + [$this->key => $referencedKey], + [], + $this->cacheable + ); } } From 9b00a844bf972f0771eff966102a55778f13e945 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Tue, 13 Nov 2018 14:03:19 -0200 Subject: [PATCH 060/116] Prevent EmbedsOne and ReferencesOne to have more than one relation --- src/Model/Relations/EmbedsOne.php | 6 ++++++ src/Model/Relations/ReferencesOne.php | 6 ++++++ tests/Integration/EmbedsOneRelationTest.php | 2 -- tests/Integration/ReferencesOneRelationTest.php | 2 -- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Model/Relations/EmbedsOne.php b/src/Model/Relations/EmbedsOne.php index 7416c0d9..3a84af4b 100644 --- a/src/Model/Relations/EmbedsOne.php +++ b/src/Model/Relations/EmbedsOne.php @@ -3,6 +3,12 @@ class EmbedsOne extends EmbedsMany { + public function add($entity): void + { + $this->removeAll(); + parent::add($entity); + } + public function remove($entity = null): void { $this->removeAll(); diff --git a/src/Model/Relations/ReferencesOne.php b/src/Model/Relations/ReferencesOne.php index baff3ff3..a6c993c0 100644 --- a/src/Model/Relations/ReferencesOne.php +++ b/src/Model/Relations/ReferencesOne.php @@ -6,6 +6,12 @@ class ReferencesOne extends ReferencesMany { + public function attach($entity): void + { + $this->detachAll(); + parent::attach($entity); + } + public function detach($entity = null): void { $this->detachAll(); diff --git a/tests/Integration/EmbedsOneRelationTest.php b/tests/Integration/EmbedsOneRelationTest.php index 4c9003f7..8071b312 100644 --- a/tests/Integration/EmbedsOneRelationTest.php +++ b/tests/Integration/EmbedsOneRelationTest.php @@ -18,7 +18,6 @@ public function testShouldRetrieveParentOfUser() // replace parent $bob = $this->createUser('Bob'); - $john->parent()->remove(); //todo remove this line and ensure only one parent is added // unset $john->parent()->add($bob); @@ -68,7 +67,6 @@ public function testShouldRetrieveSonOfUserUsingCustomKey() // replace son $bob = $this->createUser('Bob'); - $john->son()->remove(); //todo remove this line and ensure only one son is added // unset $john->son()->add($bob); diff --git a/tests/Integration/ReferencesOneRelationTest.php b/tests/Integration/ReferencesOneRelationTest.php index 3518a647..8c8175e6 100644 --- a/tests/Integration/ReferencesOneRelationTest.php +++ b/tests/Integration/ReferencesOneRelationTest.php @@ -18,7 +18,6 @@ public function testShouldRetrieveParentOfUser() // replace parent $bob = $this->createUser('Bob'); - $john->parent()->detach(); //todo remove this line and ensure only one parent is attached // unset $john->parent()->attach($bob); @@ -67,7 +66,6 @@ public function testShouldRetrieveSonOfUserUsingCustomKey() // replace son $bob = $this->createUser('Bob', '987'); - $john->son()->detach(); //todo remove this line and ensure only one son is attached // unset $john->son()->attach($bob); From d30e5f970106059452439290033f81a7dff996ca Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Tue, 13 Nov 2018 17:34:05 -0200 Subject: [PATCH 061/116] Fix regression on DataMapper Events --- src/DataMapper/DataMapper.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/DataMapper/DataMapper.php b/src/DataMapper/DataMapper.php index 48ae89be..3326f1a4 100644 --- a/src/DataMapper/DataMapper.php +++ b/src/DataMapper/DataMapper.php @@ -79,7 +79,7 @@ public function save($entity, array $options = []): bool // If the "saving" event returns false we'll bail out of the save and return // false, indicating that the save failed. This gives an opportunities to // listeners to cancel save operations if validations fail or whatever. - if (!$this->fireEvent('saving', $entity, true)) { + if (false === $this->fireEvent('saving', $entity, true)) { return false; } @@ -117,7 +117,7 @@ public function save($entity, array $options = []): bool */ public function insert($entity, array $options = [], bool $fireEvents = true): bool { - if ($fireEvents && !$this->fireEvent('inserting', $entity, true)) { + if ($fireEvents && false === $this->fireEvent('inserting', $entity, true)) { return false; } @@ -154,7 +154,7 @@ public function insert($entity, array $options = [], bool $fireEvents = true): b */ public function update($entity, array $options = []): bool { - if (!$this->fireEvent('updating', $entity, true)) { + if (false === $this->fireEvent('updating', $entity, true)) { return false; } @@ -200,7 +200,7 @@ public function update($entity, array $options = []): bool */ public function delete($entity, array $options = []): bool { - if (!$this->fireEvent('deleting', $entity, true)) { + if (false === $this->fireEvent('deleting', $entity, true)) { return false; } From 91d427267d1b0048da9ae8b3009d5a04238e93c3 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Tue, 13 Nov 2018 18:24:50 -0200 Subject: [PATCH 062/116] Fix regression where DocumentEmbedder would not trigger setDocumentAttribute --- src/Model/DocumentEmbedder.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Model/DocumentEmbedder.php b/src/Model/DocumentEmbedder.php index d0a69454..571d16ba 100644 --- a/src/Model/DocumentEmbedder.php +++ b/src/Model/DocumentEmbedder.php @@ -37,7 +37,9 @@ public function embed($parent, string $field, &$entity): bool // In order to update the document if it exists inside the $parent $this->unembed($parent, $field, $entity); - $parent->$field[] = $entity; + $values = $parent->$field; + $values[] = $entity; + $parent->$field = $values; return true; } @@ -82,7 +84,9 @@ public function attach($parent, string $field, &$entity): bool } } - $parent->$field[] = $referencedKey; + $values = $parent->$field; + $values[] = $referencedKey; + $parent->$field = $values; return true; } From 71320a2a5e73e7a40110ce2105eef39c36c22ba1 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Tue, 13 Nov 2018 20:01:10 -0200 Subject: [PATCH 063/116] Only create relations with objects, embed only attributes and create new helper methods --- src/Model/DocumentEmbedder.php | 12 +- src/Model/HasAttributesTrait.php | 7 +- src/Model/Relations/EmbedsMany.php | 25 +++- src/Model/Relations/ReferencesMany.php | 23 ++++ tests/Integration/EmbedsManyRelationTest.php | 36 +++-- tests/Integration/EmbedsOneRelationTest.php | 16 +-- tests/Unit/Model/DocumentEmbedderTest.php | 137 ++++--------------- tests/Unit/Model/HasAttributesTraitTest.php | 19 +++ 8 files changed, 136 insertions(+), 139 deletions(-) diff --git a/src/Model/DocumentEmbedder.php b/src/Model/DocumentEmbedder.php index 571d16ba..67db181a 100644 --- a/src/Model/DocumentEmbedder.php +++ b/src/Model/DocumentEmbedder.php @@ -37,9 +37,9 @@ public function embed($parent, string $field, &$entity): bool // In order to update the document if it exists inside the $parent $this->unembed($parent, $field, $entity); - $values = $parent->$field; - $values[] = $entity; - $parent->$field = $values; + $fieldValue = $parent->$field; + $fieldValue[] = $entity->getDocumentAttributes(); + $parent->$field = array_values($fieldValue); return true; } @@ -84,9 +84,9 @@ public function attach($parent, string $field, &$entity): bool } } - $values = $parent->$field; - $values[] = $referencedKey; - $parent->$field = $values; + $fieldValue = $parent->$field; + $fieldValue[] = $referencedKey; + $parent->$field = array_values($fieldValue); return true; } diff --git a/src/Model/HasAttributesTrait.php b/src/Model/HasAttributesTrait.php index d266e20d..71837901 100644 --- a/src/Model/HasAttributesTrait.php +++ b/src/Model/HasAttributesTrait.php @@ -98,7 +98,12 @@ public function &getDocumentAttribute(string $key) */ public function getDocumentAttributes(): array { - return $this->attributes; + return array_filter( + $this->attributes ?? [], + function ($value) { + return !is_null($value); + } + ); } /** diff --git a/src/Model/Relations/EmbedsMany.php b/src/Model/Relations/EmbedsMany.php index 3dc8317f..e70af55f 100644 --- a/src/Model/Relations/EmbedsMany.php +++ b/src/Model/Relations/EmbedsMany.php @@ -8,7 +8,7 @@ class EmbedsMany extends AbstractRelation { /** - * Embed a new document to an attribute. It will also generate an + * Embed a new document. It will also generate an * _id for the document if it's not present. * * @param mixed $entity model @@ -19,6 +19,29 @@ public function add($entity): void $this->pristine = false; } + /** + * Embed many documents at once. + * + * @param array $entities model + */ + public function addMany(array $entities): void + { + foreach ($entities as $entity) { + $this->add($entity); + } + } + + /** + * Replace embedded documents. + * + * @param array $entities + */ + public function replace(array $entities): void + { + $this->removeAll(); + $this->addMany($entities); + } + /** * Removes an embedded document from the given field. It does that by using * the _id of given $entity. diff --git a/src/Model/Relations/ReferencesMany.php b/src/Model/Relations/ReferencesMany.php index cdb902ec..b3f06a91 100644 --- a/src/Model/Relations/ReferencesMany.php +++ b/src/Model/Relations/ReferencesMany.php @@ -49,6 +49,29 @@ public function attach($entity): void $this->pristine = false; } + /** + * Attach many documents at once. + * + * @param array $entities model + */ + public function attachMany(array $entities): void + { + foreach ($entities as $entity) { + $this->attach($entity); + } + } + + /** + * Replace attached documents. + * + * @param array $entities + */ + public function replace(array $entities): void + { + $this->detachAll(); + $this->attachMany($entities); + } + /** * Removes a document _id reference from an attribute. It will remove the * _id of the given $entity from inside the given $field. diff --git a/tests/Integration/EmbedsManyRelationTest.php b/tests/Integration/EmbedsManyRelationTest.php index 1007fa75..3bfe3fd9 100644 --- a/tests/Integration/EmbedsManyRelationTest.php +++ b/tests/Integration/EmbedsManyRelationTest.php @@ -53,7 +53,7 @@ public function testShouldRetrieveSiblingsOfUser() // changing the field directly $john->siblings()->add($bob); $this->assertSiblings([$bob], $john); - $john->embedded_siblings = [$chuck]; + $john->embedded_siblings = [$chuck->getDocumentAttributes()]; $this->assertSiblings([$chuck], $john); $john->siblings()->removeAll(); @@ -61,7 +61,7 @@ public function testShouldRetrieveSiblingsOfUser() // changing the field with fillable $john->siblings()->add($bob); $this->assertSiblings([$bob], $john); - $john->fill(['embedded_siblings' => [$chuck]], true); + $john->fill(['embedded_siblings' => [$chuck->getDocumentAttributes()]], true); $this->assertSiblings([$chuck], $john); } @@ -72,19 +72,15 @@ public function testShouldRetrieveGrandsonsOfUserUsingCustomKey() $john = $this->createUser('John'); $john->grandsons()->add($chuck); - $this->assertSame([$chuck], $john->other_arbitrary_field); $this->assertGrandsons([$chuck], $john); $mary = $this->createUser('Mary'); $john->grandsons()->add($mary); - $this->assertSame([$chuck, $mary], $john->other_arbitrary_field); $this->assertGrandsons([$chuck, $mary], $john); // remove one grandson $john->grandsons()->remove($chuck); - - $this->assertSame([$mary], $john->other_arbitrary_field); $this->assertGrandsons([$mary], $john); // replace grandsons @@ -115,7 +111,7 @@ public function testShouldRetrieveGrandsonsOfUserUsingCustomKey() // changing the field directly $john->grandsons()->add($bob); $this->assertGrandsons([$bob], $john); - $john->other_arbitrary_field = [$chuck]; + $john->other_arbitrary_field = [$chuck->getDocumentAttributes()]; $this->assertGrandsons([$chuck], $john); $john->grandsons()->removeAll(); @@ -123,7 +119,7 @@ public function testShouldRetrieveGrandsonsOfUserUsingCustomKey() // changing the field with fillable $john->grandsons()->add($bob); $this->assertGrandsons([$bob], $john); - $john->fill(['other_arbitrary_field' => [$chuck]], true); + $john->fill(['other_arbitrary_field' => [$chuck->getDocumentAttributes()]], true); $this->assertGrandsons([$chuck], $john); } @@ -137,29 +133,41 @@ private function createUser(string $name): EmbeddedUser return $user; } - private function assertSiblings($expected, EmbeddedUser $model) + private function assertSiblings($expectedSiblings, EmbeddedUser $model) { + $expected = []; + foreach ($expectedSiblings as $sibling) { + $expected[] = $sibling->getDocumentAttributes(); + } + $siblings = $model->siblings; $this->assertInstanceOf(CursorInterface::class, $siblings); - $this->assertEquals($expected, $siblings->all()); + $this->assertEquals($expectedSiblings, $siblings->all()); $this->assertSame($expected, $model->embedded_siblings); // hit cache $siblings = $model->siblings; $this->assertInstanceOf(CursorInterface::class, $siblings); - $this->assertEquals($expected, $siblings->all()); + $this->assertEquals($expectedSiblings, $siblings->all()); $this->assertSame($expected, $model->embedded_siblings); } - private function assertGrandsons($expected, EmbeddedUser $model) + private function assertGrandsons($expectedGrandsons, EmbeddedUser $model) { + $expected = []; + foreach ($expectedGrandsons as $grandson) { + $expected[] = $grandson->getDocumentAttributes(); + } + $grandsons = $model->grandsons; $this->assertInstanceOf(CursorInterface::class, $grandsons); - $this->assertEquals($expected, $grandsons->all()); + $this->assertEquals($expectedGrandsons, $grandsons->all()); + $this->assertSame($expected, $model->other_arbitrary_field); // hit cache $grandsons = $model->grandsons; $this->assertInstanceOf(CursorInterface::class, $grandsons); - $this->assertEquals($expected, $grandsons->all()); + $this->assertEquals($expectedGrandsons, $grandsons->all()); + $this->assertSame($expected, $model->other_arbitrary_field); } } diff --git a/tests/Integration/EmbedsOneRelationTest.php b/tests/Integration/EmbedsOneRelationTest.php index 8071b312..eb7b8b96 100644 --- a/tests/Integration/EmbedsOneRelationTest.php +++ b/tests/Integration/EmbedsOneRelationTest.php @@ -44,7 +44,7 @@ public function testShouldRetrieveParentOfUser() // changing the field directly $john->parent()->add($bob); $this->assertParent($bob, $john); - $john->embedded_parent = [$chuck]; + $john->embedded_parent = [$chuck->getDocumentAttributes()]; $this->assertParent($chuck, $john); $john->parent()->removeAll(); @@ -52,7 +52,7 @@ public function testShouldRetrieveParentOfUser() // changing the field with fillable $john->parent()->add($bob); $this->assertParent($bob, $john); - $john->fill(['embedded_parent' => [$chuck]], true); + $john->fill(['embedded_parent' => [$chuck->getDocumentAttributes()]], true); $this->assertParent($chuck, $john); } @@ -92,7 +92,7 @@ public function testShouldRetrieveSonOfUserUsingCustomKey() // changing the field directly $john->son()->add($bob); $this->assertSon($bob, $john); - $john->arbitrary_field = [$chuck]; + $john->arbitrary_field = [$chuck->getDocumentAttributes()]; $this->assertSon($chuck, $john); $john->son()->removeAll(); @@ -100,7 +100,7 @@ public function testShouldRetrieveSonOfUserUsingCustomKey() // changing the field with fillable $john->son()->add($bob); $this->assertSon($bob, $john); - $john->fill(['arbitrary_field' => [$chuck]], true); + $john->fill(['arbitrary_field' => [$chuck->getDocumentAttributes()]], true); $this->assertSon($chuck, $john); } @@ -132,13 +132,13 @@ private function assertParent($expected, EmbeddedUser $model) $parent = $model->parent; $this->assertInstanceOf(EmbeddedUser::class, $parent); $this->assertEquals($expected, $parent); - $this->assertSame([$expected], $model->embedded_parent); // TODO store as single array + $this->assertSame([$expected->getDocumentAttributes()], $model->embedded_parent); // TODO store as single array // hit cache $parent = $model->parent; $this->assertInstanceOf(EmbeddedUser::class, $parent); $this->assertEquals($expected, $parent); - $this->assertSame([$expected], $model->embedded_parent); + $this->assertSame([$expected->getDocumentAttributes()], $model->embedded_parent); } private function assertSon($expected, EmbeddedUser $model) @@ -146,12 +146,12 @@ private function assertSon($expected, EmbeddedUser $model) $son = $model->son; $this->assertInstanceOf(EmbeddedUser::class, $son); $this->assertEquals($expected, $son); - $this->assertSame([$expected], $model->arbitrary_field); // TODO store as single array + $this->assertSame([$expected->getDocumentAttributes()], $model->arbitrary_field); // TODO store as single array // hit cache $son = $model->son; $this->assertInstanceOf(EmbeddedUser::class, $son); $this->assertEquals($expected, $son); - $this->assertSame([$expected], $model->arbitrary_field); + $this->assertSame([$expected->getDocumentAttributes()], $model->arbitrary_field); } } diff --git a/tests/Unit/Model/DocumentEmbedderTest.php b/tests/Unit/Model/DocumentEmbedderTest.php index ec7eaaa2..205b12e8 100644 --- a/tests/Unit/Model/DocumentEmbedderTest.php +++ b/tests/Unit/Model/DocumentEmbedderTest.php @@ -4,19 +4,30 @@ use Mockery as m; use Mockery\Matcher\Any; use MongoDB\BSON\ObjectId; +use Mongolid\Connection\Connection; +use Mongolid\DataMapper\DataMapper; use Mongolid\TestCase; -use stdClass; class DocumentEmbedderTest extends TestCase { /** * @dataProvider getEmbedOptions */ - public function testShouldEmbed($originalField, $entity, $method, $expectation) + public function testShouldEmbed($originalField, $entityFields, $method, $expectation) { // Arrange - $parent = new stdClass(); + $connection = new Connection(); + $parent = new DataMapper($connection); $parent->foo = $originalField; + + if (is_array($entityFields)) { + $entity = new class extends AbstractActiveRecord + { + }; + $entity->fill($entityFields); + } else { + $entity = $entityFields; + } $embedder = new DocumentEmbedder(); // Assert @@ -45,35 +56,10 @@ public function testShouldEmbed($originalField, $entity, $method, $expectation) public function getEmbedOptions() { return [ - // ------------------------------ - 'embedding array without _id' => [ - 'originalField' => null, - 'entity' => [ - 'name' => 'John Doe', - ], - 'method' => 'embed', - 'expectation' => [ - ['_id' => m::any(), 'name' => 'John Doe'], - ], - ], - - // ------------------------------ - 'embedding array with _id' => [ - 'originalField' => [], - 'entity' => [ - '_id' => (new ObjectId('507f191e810c19729de860ea')), - 'name' => 'John Doe', - ], - 'method' => 'embed', - 'expectation' => [ - ['_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'John Doe'], - ], - ], - // ------------------------------ 'embedding object without _id' => [ 'originalField' => null, - 'entity' => (object) [ + 'entity' => [ 'name' => 'John Doe', ], 'method' => 'embed', @@ -85,7 +71,7 @@ public function getEmbedOptions() // ------------------------------ 'embedding object with _id' => [ 'originalField' => null, - 'entity' => (object) [ + 'entity' => [ '_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'John Doe', ], @@ -103,117 +89,50 @@ public function getEmbedOptions() 'name' => 'Bob', ], ], - 'entity' => (object) [ - '_id' => (new ObjectId('507f191e810c19729de860ea')), - 'name' => 'John Doe', - ], - 'method' => 'embed', - 'expectation' => [ - (object) ['_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'John Doe'], - ], - ], - - // ------------------------------ - 'updating embedded array with _id' => [ - 'originalField' => [ - [ - '_id' => (new ObjectId()), - 'name' => 'Louis', - ], - [ - '_id' => (new ObjectId('507f191e810c19729de860ea')), - 'name' => 'Bob', - ], - ], 'entity' => [ '_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'John Doe', ], 'method' => 'embed', 'expectation' => [ - [ - '_id' => m::any(), - 'name' => 'Louis', - ], - [ - '_id' => (new ObjectId('507f191e810c19729de860ea')), - 'name' => 'John Doe', - ], - ], - ], - - // ------------------------------ - 'unembeding array with _id' => [ - 'originalField' => [ - [ - '_id' => (new ObjectId('507f191e810c19729de860ea')), - 'name' => 'John Doe', - ], - [ - '_id' => (new ObjectId()), - 'name' => 'Louis', - ], - ], - 'entity' => [ - '_id' => (new ObjectId('507f191e810c19729de860ea')), - 'name' => 'John Doe', - ], - 'method' => 'unembed', - 'expectation' => [ - [ - '_id' => m::any(), - 'name' => 'Louis', - ], - ], - ], - - // ------------------------------ - 'attaching array with _id' => [ - 'originalField' => null, - 'entity' => [ - '_id' => (new ObjectId('507f191e810c19729de860ea')), - 'name' => 'John Doe', - ], - 'method' => 'attach', - 'expectation' => [ - (new ObjectId('507f191e810c19729de860ea')), + (object) ['_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'John Doe'], ], ], // ------------------------------ 'attaching object with _id' => [ 'originalField' => null, - 'entity' => (object) [ - '_id' => (new ObjectId('507f191e810c19729de860ea')), + 'entity' => [ + '_id' => new ObjectId('507f191e810c19729de860ea'), 'name' => 'John Doe', ], 'method' => 'attach', 'expectation' => [ - (new ObjectId('507f191e810c19729de860ea')), + new ObjectId('507f191e810c19729de860ea'), ], ], // ------------------------------ 'attaching object with _id that is already attached' => [ 'originalField' => [ - (new ObjectId('507f191e810c19729de860ea')), - (new ObjectId('507f191e810c19729de86011')), + new ObjectId('507f191e810c19729de860ea'), + new ObjectId('507f191e810c19729de86011'), ], - 'entity' => (object) [ - '_id' => (new ObjectId('507f191e810c19729de860ea')), + 'entity' => [ + '_id' => new ObjectId('507f191e810c19729de860ea'), 'name' => 'John Doe', ], 'method' => 'attach', 'expectation' => [ - (new ObjectId('507f191e810c19729de860ea')), - (new ObjectId('507f191e810c19729de86011')), + new ObjectId('507f191e810c19729de860ea'), + new ObjectId('507f191e810c19729de86011'), ], ], // ------------------------------ 'attaching object without _id' => [ 'originalField' => null, - 'entity' => (object) [ + 'entity' => [ 'name' => 'John Doe', ], 'method' => 'attach', @@ -226,7 +145,7 @@ public function getEmbedOptions() (new ObjectId('507f191e810c19729de860ea')), (new ObjectId('507f191e810c19729de86011')), ], - 'entity' => (object) [ + 'entity' => [ '_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'John Doe', ], diff --git a/tests/Unit/Model/HasAttributesTraitTest.php b/tests/Unit/Model/HasAttributesTraitTest.php index d03bd5b6..10e5f599 100644 --- a/tests/Unit/Model/HasAttributesTraitTest.php +++ b/tests/Unit/Model/HasAttributesTraitTest.php @@ -255,6 +255,25 @@ public function getFillableOptions() 'name' => 'John', ], ], + + // ----------------------------- + 'ignore nulls but not falsy ones' => [ + 'fillable' => ['name', 'surname', 'sex', 'age', 'has_sex'], + 'guarded' => [], + 'input' => [ + 'name' => 'John', + 'surname' => '', + 'sex' => null, + 'age' => 0, + 'has_sex' => false, + ], + 'expected' => [ + 'name' => 'John', + 'surname' => '', + 'age' => 0, + 'has_sex' => false, + ], + ], ]; } From ef44a0f250603fb4b434930812198f7b900265be Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 14 Nov 2018 10:56:27 -0200 Subject: [PATCH 064/116] Parse document to consider fields when embedding models --- src/DataMapper/DataMapper.php | 4 +--- src/Model/AbstractActiveRecord.php | 10 ++++++++++ src/Model/DocumentEmbedder.php | 6 ++++-- src/Schema/HasSchemaInterface.php | 7 +++++++ tests/Integration/EmbedsManyRelationTest.php | 15 ++++++++------ tests/Integration/EmbedsOneRelationTest.php | 21 ++++++++++++-------- tests/Integration/Stubs/EmbeddedUser.php | 1 + 7 files changed, 45 insertions(+), 19 deletions(-) diff --git a/src/DataMapper/DataMapper.php b/src/DataMapper/DataMapper.php index 3326f1a4..cfc56ffa 100644 --- a/src/DataMapper/DataMapper.php +++ b/src/DataMapper/DataMapper.php @@ -340,10 +340,8 @@ public function setSchema(AbstractSchema $schema) * Parses an object with SchemaMapper and the given Schema. * * @param mixed $entity the object to be parsed - * - * @return array Document */ - protected function parseToDocument($entity) + public function parseToDocument($entity): array { $schemaMapper = $this->getSchemaMapper(); $parsedDocument = $schemaMapper->map($entity); diff --git a/src/Model/AbstractActiveRecord.php b/src/Model/AbstractActiveRecord.php index 9f15869f..6d041b61 100644 --- a/src/Model/AbstractActiveRecord.php +++ b/src/Model/AbstractActiveRecord.php @@ -172,6 +172,16 @@ private static function getDataMapperInstance(): DataMapper return $instance->getDataMapper(); } + /** + * Parses an object with SchemaMapper. + * + * @param mixed $entity the object to be parsed + */ + public function parseToDocument($entity): array + { + return $this->getDataMapper()->parseToDocument($entity); + } + /** * Saves this object into database. */ diff --git a/src/Model/DocumentEmbedder.php b/src/Model/DocumentEmbedder.php index 67db181a..3d1b828c 100644 --- a/src/Model/DocumentEmbedder.php +++ b/src/Model/DocumentEmbedder.php @@ -2,6 +2,7 @@ namespace Mongolid\Model; use MongoDB\BSON\ObjectId; +use Mongolid\Schema\HasSchemaInterface; /** * Document embedder is a service that will embed documents within each other. @@ -32,13 +33,14 @@ public function setKey(string $key): void * @param string $field name of the field of the object where the document will be embedded * @param mixed $entity entity that will be embedded within $parent */ - public function embed($parent, string $field, &$entity): bool + public function embed($parent, string $field, HasSchemaInterface &$entity): bool { // In order to update the document if it exists inside the $parent $this->unembed($parent, $field, $entity); + $data = $entity->parseToDocument($entity); $fieldValue = $parent->$field; - $fieldValue[] = $entity->getDocumentAttributes(); + $fieldValue[] = $data; $parent->$field = array_values($fieldValue); return true; diff --git a/src/Schema/HasSchemaInterface.php b/src/Schema/HasSchemaInterface.php index 65d9da34..1b291dd7 100644 --- a/src/Schema/HasSchemaInterface.php +++ b/src/Schema/HasSchemaInterface.php @@ -7,4 +7,11 @@ interface HasSchemaInterface * Returns a Schema object that describes an Entity in MongoDB. */ public function getSchema(): AbstractSchema; + + /** + * Parses an object to a document. + * + * @param mixed $entity the object to be parsed + */ + public function parseToDocument($entity): array; } diff --git a/tests/Integration/EmbedsManyRelationTest.php b/tests/Integration/EmbedsManyRelationTest.php index 3bfe3fd9..5d8e9575 100644 --- a/tests/Integration/EmbedsManyRelationTest.php +++ b/tests/Integration/EmbedsManyRelationTest.php @@ -2,6 +2,7 @@ namespace Mongolid\Tests\Integration; use MongoDB\BSON\ObjectId; +use MongoDB\BSON\UTCDateTime; use Mongolid\Cursor\CursorInterface; use Mongolid\Tests\Integration\Stubs\EmbeddedUser; @@ -53,7 +54,7 @@ public function testShouldRetrieveSiblingsOfUser() // changing the field directly $john->siblings()->add($bob); $this->assertSiblings([$bob], $john); - $john->embedded_siblings = [$chuck->getDocumentAttributes()]; + $john->embedded_siblings = [$chuck->toArray()]; $this->assertSiblings([$chuck], $john); $john->siblings()->removeAll(); @@ -61,7 +62,7 @@ public function testShouldRetrieveSiblingsOfUser() // changing the field with fillable $john->siblings()->add($bob); $this->assertSiblings([$bob], $john); - $john->fill(['embedded_siblings' => [$chuck->getDocumentAttributes()]], true); + $john->fill(['embedded_siblings' => [$chuck->toArray()]], true); $this->assertSiblings([$chuck], $john); } @@ -111,7 +112,7 @@ public function testShouldRetrieveGrandsonsOfUserUsingCustomKey() // changing the field directly $john->grandsons()->add($bob); $this->assertGrandsons([$bob], $john); - $john->other_arbitrary_field = [$chuck->getDocumentAttributes()]; + $john->other_arbitrary_field = [$chuck->toArray()]; $this->assertGrandsons([$chuck], $john); $john->grandsons()->removeAll(); @@ -119,7 +120,7 @@ public function testShouldRetrieveGrandsonsOfUserUsingCustomKey() // changing the field with fillable $john->grandsons()->add($bob); $this->assertGrandsons([$bob], $john); - $john->fill(['other_arbitrary_field' => [$chuck->getDocumentAttributes()]], true); + $john->fill(['other_arbitrary_field' => [$chuck->toArray()]], true); $this->assertGrandsons([$chuck], $john); } @@ -137,7 +138,8 @@ private function assertSiblings($expectedSiblings, EmbeddedUser $model) { $expected = []; foreach ($expectedSiblings as $sibling) { - $expected[] = $sibling->getDocumentAttributes(); + $expected[] = $sibling->toArray(); + $this->assertInstanceOf(UTCDateTime::class, $sibling->created_at); } $siblings = $model->siblings; @@ -156,7 +158,8 @@ private function assertGrandsons($expectedGrandsons, EmbeddedUser $model) { $expected = []; foreach ($expectedGrandsons as $grandson) { - $expected[] = $grandson->getDocumentAttributes(); + $expected[] = $grandson->toArray(); + $this->assertInstanceOf(UTCDateTime::class, $grandson->created_at); } $grandsons = $model->grandsons; diff --git a/tests/Integration/EmbedsOneRelationTest.php b/tests/Integration/EmbedsOneRelationTest.php index eb7b8b96..52992ec0 100644 --- a/tests/Integration/EmbedsOneRelationTest.php +++ b/tests/Integration/EmbedsOneRelationTest.php @@ -2,6 +2,7 @@ namespace Mongolid\Tests\Integration; use MongoDB\BSON\ObjectId; +use MongoDB\BSON\UTCDateTime; use Mongolid\Model\Relations\InvalidFieldNameException; use Mongolid\Tests\Integration\Stubs\EmbeddedUser; @@ -44,7 +45,7 @@ public function testShouldRetrieveParentOfUser() // changing the field directly $john->parent()->add($bob); $this->assertParent($bob, $john); - $john->embedded_parent = [$chuck->getDocumentAttributes()]; + $john->embedded_parent = [$chuck->toArray()]; $this->assertParent($chuck, $john); $john->parent()->removeAll(); @@ -52,7 +53,7 @@ public function testShouldRetrieveParentOfUser() // changing the field with fillable $john->parent()->add($bob); $this->assertParent($bob, $john); - $john->fill(['embedded_parent' => [$chuck->getDocumentAttributes()]], true); + $john->fill(['embedded_parent' => [$chuck->toArray()]], true); $this->assertParent($chuck, $john); } @@ -92,7 +93,7 @@ public function testShouldRetrieveSonOfUserUsingCustomKey() // changing the field directly $john->son()->add($bob); $this->assertSon($bob, $john); - $john->arbitrary_field = [$chuck->getDocumentAttributes()]; + $john->arbitrary_field = [$chuck->toArray()]; $this->assertSon($chuck, $john); $john->son()->removeAll(); @@ -100,7 +101,7 @@ public function testShouldRetrieveSonOfUserUsingCustomKey() // changing the field with fillable $john->son()->add($bob); $this->assertSon($bob, $john); - $john->fill(['arbitrary_field' => [$chuck->getDocumentAttributes()]], true); + $john->fill(['arbitrary_field' => [$chuck->toArray()]], true); $this->assertSon($chuck, $john); } @@ -131,27 +132,31 @@ private function assertParent($expected, EmbeddedUser $model) { $parent = $model->parent; $this->assertInstanceOf(EmbeddedUser::class, $parent); + $this->assertInstanceOf(UTCDateTime::class, $parent->created_at); $this->assertEquals($expected, $parent); - $this->assertSame([$expected->getDocumentAttributes()], $model->embedded_parent); // TODO store as single array + $this->assertSame([$expected->toArray()], $model->embedded_parent); // TODO store as single array // hit cache $parent = $model->parent; $this->assertInstanceOf(EmbeddedUser::class, $parent); + $this->assertInstanceOf(UTCDateTime::class, $parent->created_at); $this->assertEquals($expected, $parent); - $this->assertSame([$expected->getDocumentAttributes()], $model->embedded_parent); + $this->assertSame([$expected->toArray()], $model->embedded_parent); } private function assertSon($expected, EmbeddedUser $model) { $son = $model->son; $this->assertInstanceOf(EmbeddedUser::class, $son); + $this->assertInstanceOf(UTCDateTime::class, $son->created_at); $this->assertEquals($expected, $son); - $this->assertSame([$expected->getDocumentAttributes()], $model->arbitrary_field); // TODO store as single array + $this->assertSame([$expected->toArray()], $model->arbitrary_field); // TODO store as single array // hit cache $son = $model->son; $this->assertInstanceOf(EmbeddedUser::class, $son); + $this->assertInstanceOf(UTCDateTime::class, $son->created_at); $this->assertEquals($expected, $son); - $this->assertSame([$expected->getDocumentAttributes()], $model->arbitrary_field); + $this->assertSame([$expected->toArray()], $model->arbitrary_field); } } diff --git a/tests/Integration/Stubs/EmbeddedUser.php b/tests/Integration/Stubs/EmbeddedUser.php index 346d4fea..258ef0e5 100644 --- a/tests/Integration/Stubs/EmbeddedUser.php +++ b/tests/Integration/Stubs/EmbeddedUser.php @@ -18,6 +18,7 @@ class EmbeddedUser extends AbstractActiveRecord */ protected $fields = [ '_id' => 'objectId', + 'created_at' => 'createdAtTimestamp', ]; public function collection(): Collection From f961c8814d8ca66bccbc8b848d684d27d99d8dbb Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 21 Nov 2018 11:14:39 -0200 Subject: [PATCH 065/116] Add new test to make sure that date queries works --- src/DataMapper/DataMapper.php | 29 ++++----- tests/Integration/DateQueriesTest.php | 90 +++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 16 deletions(-) create mode 100644 tests/Integration/DateQueriesTest.php diff --git a/src/DataMapper/DataMapper.php b/src/DataMapper/DataMapper.php index cfc56ffa..aeefe5b7 100644 --- a/src/DataMapper/DataMapper.php +++ b/src/DataMapper/DataMapper.php @@ -6,8 +6,7 @@ use MongoDB\Collection; use Mongolid\Connection\Connection; use Mongolid\Container\Ioc; -use Mongolid\Cursor\CacheableCursor; -use Mongolid\Cursor\Cursor; +use Mongolid\Cursor\CursorFactory; use Mongolid\Cursor\CursorInterface; use Mongolid\Event\EventTriggerService; use Mongolid\Exception\ModelNotFoundException; @@ -92,7 +91,7 @@ public function save($entity, array $options = []): bool ); $result = $queryResult->isAcknowledged() && - ($queryResult->getModifiedCount() || $queryResult->getUpsertedCount()); + ($queryResult->getModifiedCount() || $queryResult->getUpsertedCount()); if ($result) { $this->afterSuccess($entity); @@ -235,19 +234,17 @@ public function where( array $projection = [], bool $cacheable = false ): CursorInterface { - $cursorClass = $cacheable ? CacheableCursor::class : Cursor::class; - - $cursor = new $cursorClass( - $this->schema, - $this->getCollection(), - 'find', - [ - $this->prepareValueQuery($query), - ['projection' => $this->prepareProjection($projection)], - ] - ); - - return $cursor; + return Ioc::make(CursorFactory::class) + ->createCursor( + $this->schema, + $this->getCollection(), + 'find', + [ + $this->prepareValueQuery($query), + ['projection' => $this->prepareProjection($projection)], + ], + $cacheable + ); } /** diff --git a/tests/Integration/DateQueriesTest.php b/tests/Integration/DateQueriesTest.php new file mode 100644 index 00000000..32fdcfd5 --- /dev/null +++ b/tests/Integration/DateQueriesTest.php @@ -0,0 +1,90 @@ +some_date = new UTCDateTime(new DateTime('2018-10-10 00:00:00')); + + $this->assertTrue($user->save()); + + $greaterEqualResult = ReferencedUser::where( + [ + 'some_date' => [ + '$gte' => new UTCDateTime(new DateTime('2018-10-10 00:00:00')), + ], + ] + ); + + $this->assertCount(1, $greaterEqualResult); + $this->assertEquals($user, $greaterEqualResult->first()); + + $greaterResult = ReferencedUser::where( + [ + 'some_date' => [ + '$gt' => new UTCDateTime(new DateTime('2018-10-10 00:00:00')), + ], + ] + ); + + $this->assertCount(0, $greaterResult); + + $emptyResult = ReferencedUser::where( + [ + 'some_date' => [ + '$gte' => new UTCDateTime(new DateTime('2018-10-10 00:00:01')), + ], + ] + ); + + $this->assertCount(0, $emptyResult); + } + + public function testShouldRetrieveDocumentsUsingDateFiltersWithRelativeDates() + { + // Set + $user = new ReferencedUser(); + $user->some_date = new UTCDateTime(new DateTime('+10 days')); + + $this->assertTrue($user->save()); + + $greaterEqualResult = ReferencedUser::where( + [ + 'some_date' => [ + '$gte' => new UTCDateTime(), + ], + ] + ); + + $this->assertCount(1, $greaterEqualResult); + $this->assertEquals($user, $greaterEqualResult->first()); + + $greaterResult = ReferencedUser::where( + [ + 'some_date' => [ + '$gt' => new UTCDateTime(), + ], + ] + ); + + $this->assertCount(1, $greaterResult); + $this->assertEquals($user, $greaterResult->first()); + + $emptyResult = ReferencedUser::where( + [ + 'some_date' => [ + '$gte' => new UTCDateTime(new DateTime('+10 days +1 second')), + ], + ] + ); + + $this->assertCount(0, $emptyResult); + } +} From d883c3f9d8484c450f446368ea33b788ff87b784 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 21 Nov 2018 11:29:40 -0200 Subject: [PATCH 066/116] Fix LogicException bug (probably mongodb bug?) and add test to it --- src/Cursor/Cursor.php | 65 +++++++++++----------- tests/Integration/RewindableCursorTest.php | 51 +++++++++++++++++ 2 files changed, 84 insertions(+), 32 deletions(-) create mode 100644 tests/Integration/RewindableCursorTest.php diff --git a/src/Cursor/Cursor.php b/src/Cursor/Cursor.php index 43750bf2..fcf88eed 100644 --- a/src/Cursor/Cursor.php +++ b/src/Cursor/Cursor.php @@ -2,6 +2,7 @@ namespace Mongolid\Cursor; use IteratorIterator; +use LogicException as BaseLogicException; use MongoDB\Collection; use MongoDB\Driver\Cursor as DriverCursor; use MongoDB\Driver\Exception\LogicException; @@ -182,9 +183,9 @@ public function rewind() { try { $this->getCursor()->rewind(); - } catch (LogicException $e) { + } catch (LogicException | BaseLogicException $e) { $this->fresh(); - $this->getCursor()->rewind(); + $this->getCursor(); } $this->position = 0; @@ -295,36 +296,6 @@ public function toArray(): array return $result ?? []; } - /** - * Actually returns a Traversable object with the DriverCursor within. - * If it does not exists yet, create it using the $collection, $command and - * $params given. - */ - protected function getCursor(): Traversable - { - if (!$this->cursor) { - $driverCursor = $this->collection->{$this->command}(...$this->params); - $this->cursor = new IteratorIterator($driverCursor); - $this->cursor->rewind(); - } - - return $this->cursor; - } - - /** - * Retrieves an EntityAssembler instance. - * - * @return EntityAssembler - */ - protected function getAssembler() - { - if (!$this->assembler) { - $this->assembler = Ioc::make(EntityAssembler::class); - } - - return $this->assembler; - } - /** * Serializes this object storing the collection name instead of the actual * MongoDb\Collection (which is unserializable). @@ -358,4 +329,34 @@ public function unserialize($serialized) $this->collection = $collectionObject; } + + /** + * Actually returns a Traversable object with the DriverCursor within. + * If it does not exists yet, create it using the $collection, $command and + * $params given. + */ + protected function getCursor(): Traversable + { + if (!$this->cursor) { + $driverCursor = $this->collection->{$this->command}(...$this->params); + $this->cursor = new IteratorIterator($driverCursor); + $this->cursor->rewind(); + } + + return $this->cursor; + } + + /** + * Retrieves an EntityAssembler instance. + * + * @return EntityAssembler + */ + protected function getAssembler() + { + if (!$this->assembler) { + $this->assembler = Ioc::make(EntityAssembler::class); + } + + return $this->assembler; + } } diff --git a/tests/Integration/RewindableCursorTest.php b/tests/Integration/RewindableCursorTest.php new file mode 100644 index 00000000..516a1bcf --- /dev/null +++ b/tests/Integration/RewindableCursorTest.php @@ -0,0 +1,51 @@ +createUser('Bob'); + $this->createUser('Mary'); + $this->createUser('John'); + $this->createUser('Jane'); + + $cursor = ReferencedUser::all(); + + // exhaust cursor + foreach ($cursor as $user) { + $this->assertInstanceOf(ReferencedUser::class, $user); + } + + // try again + foreach ($cursor as $user) { + $this->assertInstanceOf(ReferencedUser::class, $user); + } + + // rewind and try again + $cursor->rewind(); + foreach ($cursor as $user) { + $this->assertInstanceOf(ReferencedUser::class, $user); + } + + // serializing + $newCursor = unserialize(serialize($cursor)); + foreach ($newCursor as $user) { + $this->assertInstanceOf(ReferencedUser::class, $user); + } + } + + + private function createUser(string $name): ReferencedUser + { + $user = new ReferencedUser(); + $user->_id = new ObjectId(); + $user->name = $name; + $this->assertTrue($user->save()); + + return $user; + } +} From db7c3d29f8906d55f3d39db38925c5c7fc3dde42 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 21 Nov 2018 11:40:19 -0200 Subject: [PATCH 067/116] Use CachingIterator to wrap cursor --- src/Cursor/Cursor.php | 5 +- tests/Integration/RewindableCursorTest.php | 1 - tests/Unit/Cursor/CacheableCursorTest.php | 4 +- tests/Unit/Cursor/CursorTest.php | 61 +++++++++------------- 4 files changed, 30 insertions(+), 41 deletions(-) diff --git a/src/Cursor/Cursor.php b/src/Cursor/Cursor.php index fcf88eed..00088bdf 100644 --- a/src/Cursor/Cursor.php +++ b/src/Cursor/Cursor.php @@ -1,12 +1,12 @@ collection->getCollectionName(); + unset($properties['cursor']); return serialize($properties); } @@ -339,7 +340,7 @@ protected function getCursor(): Traversable { if (!$this->cursor) { $driverCursor = $this->collection->{$this->command}(...$this->params); - $this->cursor = new IteratorIterator($driverCursor); + $this->cursor = new CachingIterator($driverCursor); $this->cursor->rewind(); } diff --git a/tests/Integration/RewindableCursorTest.php b/tests/Integration/RewindableCursorTest.php index 516a1bcf..aa1dff84 100644 --- a/tests/Integration/RewindableCursorTest.php +++ b/tests/Integration/RewindableCursorTest.php @@ -38,7 +38,6 @@ public function testCursorShouldBeRewindableAndSerializable() } } - private function createUser(string $name): ReferencedUser { $user = new ReferencedUser(); diff --git a/tests/Unit/Cursor/CacheableCursorTest.php b/tests/Unit/Cursor/CacheableCursorTest.php index dc663488..44040ca4 100644 --- a/tests/Unit/Cursor/CacheableCursorTest.php +++ b/tests/Unit/Cursor/CacheableCursorTest.php @@ -3,9 +3,9 @@ use ArrayIterator; use ErrorException; -use IteratorIterator; use Mockery as m; use MongoDB\Collection; +use MongoDB\Model\CachingIterator; use Mongolid\Schema\AbstractSchema; use Mongolid\TestCase; use Mongolid\Util\CacheComponentInterface; @@ -171,7 +171,7 @@ public function testShouldGetOriginalCursorFromDatabaseAfterTheDocumentLimit() // Assert $this->assertEquals( - new IteratorIterator(new ArrayIterator($documentsFromDb)), + new CachingIterator(new ArrayIterator($documentsFromDb)), $this->callProtected($cursor, 'getCursor') ); } diff --git a/tests/Unit/Cursor/CursorTest.php b/tests/Unit/Cursor/CursorTest.php index b6184255..a37bc469 100644 --- a/tests/Unit/Cursor/CursorTest.php +++ b/tests/Unit/Cursor/CursorTest.php @@ -2,12 +2,13 @@ namespace Mongolid\Cursor; use ArrayIterator; +use ArrayObject; use Iterator; -use IteratorIterator; use Mockery as m; use MongoDB\Collection; use MongoDB\Driver\Exception\LogicException; use MongoDB\Driver\ReadPreference; +use MongoDB\Model\CachingIterator; use Mongolid\Connection\Connection; use Mongolid\Model\AbstractActiveRecord; use Mongolid\Schema\AbstractSchema; @@ -122,7 +123,7 @@ public function testShouldRewind() { // Arrange $collection = m::mock(Collection::class); - $driverCursor = m::mock(IteratorIterator::class); + $driverCursor = m::mock(CachingIterator::class); $cursor = $this->getCursor(null, $collection, 'find', [[]], $driverCursor); $this->setProtected($cursor, 'position', 10); @@ -140,7 +141,7 @@ public function testShouldRewindACursorThatHasAlreadyBeenInitialized() { // Arrange $collection = m::mock(Collection::class); - $driverCursor = m::mock(IteratorIterator::class); + $driverCursor = m::mock(CachingIterator::class); $cursor = $this->getCursor(null, $collection, 'find', [[]], $driverCursor); $this->setProtected($cursor, 'position', 10); @@ -148,12 +149,13 @@ public function testShouldRewindACursorThatHasAlreadyBeenInitialized() // Act $driverCursor->expects() ->rewind() - ->twice() - ->andReturnUsing(function () use ($cursor) { - if ($this->getProtected($cursor, 'cursor')) { - throw new LogicException('Cursor already initialized', 1); + ->andReturnUsing( + function () use ($cursor) { + if ($this->getProtected($cursor, 'cursor')) { + throw new LogicException('Cursor already initialized', 1); + } } - }); + ); // Assert $cursor->rewind(); @@ -164,16 +166,13 @@ public function testShouldGetCurrent() { // Arrange $collection = m::mock(Collection::class); - $driverCursor = m::mock(IteratorIterator::class); + $driverCursor = new CachingIterator(new ArrayObject([['name' => 'John Doe']])); $cursor = $this->getCursor(null, $collection, 'find', [[]], $driverCursor); // Act - $driverCursor->expects() - ->current() - ->andReturn(['name' => 'John Doe']); + $entity = $cursor->current(); // Assert - $entity = $cursor->current(); $this->assertInstanceOf(stdClass::class, $entity); $this->assertAttributeEquals('John Doe', 'name', $entity); } @@ -197,19 +196,13 @@ public function testShouldGetFirst() { // Arrange $collection = m::mock(Collection::class); - $driverCursor = m::mock(IteratorIterator::class); + $driverCursor = new CachingIterator(new ArrayObject([['name' => 'John Doe']])); $cursor = $this->getCursor(null, $collection, 'find', [[]], $driverCursor); // Act - $driverCursor->expects() - ->rewind(); - - $driverCursor->expects() - ->current() - ->andReturn(['name' => 'John Doe']); + $entity = $cursor->first(); // Assert - $entity = $cursor->first(); $this->assertInstanceOf(stdClass::class, $entity); $this->assertAttributeEquals('John Doe', 'name', $entity); } @@ -218,26 +211,21 @@ public function testShouldGetFirstWhenEmpty() { // Arrange $collection = m::mock(Collection::class); - $driverCursor = m::mock(IteratorIterator::class); + $driverCursor = new CachingIterator(new ArrayObject()); + $cursor = $this->getCursor(null, $collection, 'find', [[]], $driverCursor); // Act - $driverCursor->expects() - ->rewind(); - - $driverCursor->expects() - ->current() - ->andReturn(null); + $result = $cursor->first(); // Assert - $result = $cursor->first(); $this->assertNull($result); } public function testShouldRefreshTheCursor() { // Arrange - $driverCursor = m::mock(IteratorIterator::class); + $driverCursor = new CachingIterator(new ArrayObject()); $cursor = $this->getCursor(); $this->setProtected($cursor, 'cursor', $driverCursor); @@ -261,7 +249,7 @@ public function testShouldImplementNextMethodFromIterator() { // Arrange $collection = m::mock(Collection::class); - $driverCursor = m::mock(IteratorIterator::class); + $driverCursor = m::mock(CachingIterator::class); $cursor = $this->getCursor(null, $collection, 'find', [[]], $driverCursor); $this->setProtected($cursor, 'position', 7); @@ -279,7 +267,7 @@ public function testShouldImplementValidMethodFromIterator() { // Arrange $collection = m::mock(Collection::class); - $driverCursor = m::mock(IteratorIterator::class); + $driverCursor = m::mock(CachingIterator::class); $cursor = $this->getCursor(null, $collection, 'find', [[]], $driverCursor); // Act @@ -328,14 +316,14 @@ public function testShouldWrapMongoDriverCursorWithIterator() // Assert $result = $this->callProtected($cursor, 'getCursor'); - $this->assertInstanceOf(IteratorIterator::class, $result); + $this->assertInstanceOf(CachingIterator::class, $result); } public function testShouldReturnAllResults() { // Arrange $collection = m::mock(Collection::class); - $driverCursor = m::mock(IteratorIterator::class); + $driverCursor = m::mock(CachingIterator::class); $cursor = $this->getCursor(null, $collection, 'find', [[]], $driverCursor); // Act @@ -375,7 +363,7 @@ public function testShouldReturnResultsToArray() { // Arrange $collection = m::mock(Collection::class); - $driverCursor = m::mock(IteratorIterator::class); + $driverCursor = m::mock(CachingIterator::class); $cursor = $this->getCursor(null, $collection, 'find', [[]], $driverCursor); // Act @@ -479,7 +467,8 @@ protected function getDriverCollection() /* * Emulates a MongoDB\Collection non serializable behavior. */ - return new class() implements Serializable { + return new class() implements Serializable + { public function serialize() { throw new Exception('Unable to serialize', 1); From 3949f51c5236c3768b75a190d7756e3c8240e180 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 21 Nov 2018 12:17:59 -0200 Subject: [PATCH 068/116] Some improvements on Cursors --- src/Cursor/CacheableCursor.php | 10 ++-------- src/Cursor/Cursor.php | 12 ++++-------- src/Cursor/EmbeddedCursor.php | 2 +- src/DataMapper/DataMapper.php | 4 ++-- src/Model/AbstractActiveRecord.php | 6 +++--- 5 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/Cursor/CacheableCursor.php b/src/Cursor/CacheableCursor.php index 2d0490d2..f1446c18 100644 --- a/src/Cursor/CacheableCursor.php +++ b/src/Cursor/CacheableCursor.php @@ -90,7 +90,7 @@ protected function getCursor(): Traversable $cacheComponent->put($cacheKey, $this->documents, 0.6); // Drops the unserializable DriverCursor. - $this->cursor = null; + $this->fresh(); // Return the documents iterator return $this->documents = new ArrayIterator($this->documents); @@ -158,15 +158,9 @@ protected function getOriginalCursor(): Traversable return $this->getOriginalCursor(); } - /** - * Serializes this object. Drops the unserializable DriverCursor. In order - * to make the CacheableCursor object serializable. - * - * @return string serialized object - */ public function serialize() { - $this->documents = $this->cursor = null; + $this->documents = null; return parent::serialize(); } diff --git a/src/Cursor/Cursor.php b/src/Cursor/Cursor.php index 00088bdf..79ba0465 100644 --- a/src/Cursor/Cursor.php +++ b/src/Cursor/Cursor.php @@ -199,7 +199,9 @@ public function rewind() */ public function current() { - $document = $this->getCursor()->current(); + if (!$document = $this->getCursor()->current()) { + return null; + } if ($document instanceof AbstractActiveRecord) { $documentToArray = $document->toArray(); @@ -222,13 +224,8 @@ public function current() public function first() { $this->rewind(); - $document = $this->getCursor()->current(); - - if (!$document) { - return; - } - return $this->getAssembler()->assemble($document, $this->entitySchema); + return $this->current(); } /** @@ -341,7 +338,6 @@ protected function getCursor(): Traversable if (!$this->cursor) { $driverCursor = $this->collection->{$this->command}(...$this->params); $this->cursor = new CachingIterator($driverCursor); - $this->cursor->rewind(); } return $this->cursor; diff --git a/src/Cursor/EmbeddedCursor.php b/src/Cursor/EmbeddedCursor.php index 68b37235..79e94d8a 100644 --- a/src/Cursor/EmbeddedCursor.php +++ b/src/Cursor/EmbeddedCursor.php @@ -134,7 +134,7 @@ public function rewind() public function current() { if (!$this->valid()) { - return; + return null; } $document = $this->items[$this->position]; diff --git a/src/DataMapper/DataMapper.php b/src/DataMapper/DataMapper.php index aeefe5b7..fb8ac33b 100644 --- a/src/DataMapper/DataMapper.php +++ b/src/DataMapper/DataMapper.php @@ -264,7 +264,7 @@ public function all(): CursorInterface * @param array $projection fields to project in Mongo query * @param bool $cacheable retrieves the first through a CacheableCursor * - * @return mixed First document matching query as an $this->schema->entityClass object + * @return static|null First document matching query as an $this->schema->entityClass object */ public function first( $query = [], @@ -303,7 +303,7 @@ public function first( * * @throws ModelNotFoundException If no document was found * - * @return mixed First document matching query as an $this->schema->entityClass object + * @return static|null First document matching query as an $this->schema->entityClass object */ public function firstOrFail( $query = [], diff --git a/src/Model/AbstractActiveRecord.php b/src/Model/AbstractActiveRecord.php index 6d041b61..3db3e4d6 100644 --- a/src/Model/AbstractActiveRecord.php +++ b/src/Model/AbstractActiveRecord.php @@ -97,7 +97,7 @@ public static function all(): CursorInterface * @param array $projection fields to project in Mongo query * @param bool $useCache retrieves the entity through a CacheableCursor * - * @return AbstractActiveRecord + * @return static|null */ public static function first( $query = [], @@ -121,7 +121,7 @@ public static function first( * * @throws ModelNotFoundException If no document was found * - * @return AbstractActiveRecord + * @return static|null */ public static function firstOrFail( $query = [], @@ -142,7 +142,7 @@ public static function firstOrFail( * * @param mixed $id document id * - * @return AbstractActiveRecord + * @return static|null */ public static function firstOrNew($id) { From bef0a6aab6dd01c58726b40744e8ac26b0efd34c Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 26 Nov 2018 08:48:10 -0200 Subject: [PATCH 069/116] Remove cacheable cursor implementation --- src/Connection/Manager.php | 15 -- src/Cursor/CacheableCursor.php | 167 ------------ src/Cursor/CursorFactory.php | 43 ---- src/DataMapper/DataMapper.php | 33 +-- src/Model/AbstractActiveRecord.php | 50 +--- src/Model/HasRelationsTrait.php | 18 +- src/Model/Relations/ReferencesMany.php | 15 +- src/Model/Relations/ReferencesOne.php | 6 +- src/Util/CacheComponent.php | 83 ------ src/Util/CacheComponentInterface.php | 36 --- tests/Unit/Connection/ManagerTest.php | 2 - tests/Unit/Cursor/CacheableCursorTest.php | 241 ------------------ tests/Unit/Cursor/CursorFactoryTest.php | 75 ------ tests/Unit/DataMapper/DataMapperTest.php | 65 ----- tests/Unit/Model/AbstractActiveRecordTest.php | 12 +- tests/Unit/Model/HasRelationsTraitTest.php | 4 +- tests/Unit/Util/CacheComponentTest.php | 73 ------ tests/Util/SetupConnectionTrait.php | 7 - 18 files changed, 38 insertions(+), 907 deletions(-) delete mode 100644 src/Cursor/CacheableCursor.php delete mode 100644 src/Cursor/CursorFactory.php delete mode 100644 src/Util/CacheComponent.php delete mode 100644 src/Util/CacheComponentInterface.php delete mode 100644 tests/Unit/Cursor/CacheableCursorTest.php delete mode 100644 tests/Unit/Cursor/CursorFactoryTest.php delete mode 100644 tests/Unit/Util/CacheComponentTest.php diff --git a/src/Connection/Manager.php b/src/Connection/Manager.php index fdd4490d..36f5372a 100644 --- a/src/Connection/Manager.php +++ b/src/Connection/Manager.php @@ -7,8 +7,6 @@ use Mongolid\Event\EventTriggerInterface; use Mongolid\Event\EventTriggerService; use Mongolid\Schema\AbstractSchema; -use Mongolid\Util\CacheComponent; -use Mongolid\Util\CacheComponentInterface; /** * Wraps the Mongolid initialization. The main purpose of the Manager is to make @@ -37,13 +35,6 @@ class Manager */ public $container; - /** - * Mongolid cache component object. - * - * @var CacheComponent - */ - public $cacheComponent; - /** * Mongolid connection object. * @@ -143,12 +134,6 @@ protected function init() $this->container = new Container(); Ioc::setContainer($this->container); - $this->cacheComponent = new CacheComponent(); - $this->container->instance( - CacheComponentInterface::class, - $this->cacheComponent - ); - static::$singleton = $this; } } diff --git a/src/Cursor/CacheableCursor.php b/src/Cursor/CacheableCursor.php deleted file mode 100644 index f1446c18..00000000 --- a/src/Cursor/CacheableCursor.php +++ /dev/null @@ -1,167 +0,0 @@ -ignoreCache || $this->position >= self::DOCUMENT_LIMIT) { - return $this->getOriginalCursor(); - } - - // Returns cached set of documents - if ($this->documents) { - return $this->documents; - } - - // Check if there is a cached set of documents - $cacheComponent = Ioc::make(CacheComponentInterface::class); - $cacheKey = $this->generateCacheKey(); - - try { - $cachedDocuments = $cacheComponent->get($cacheKey, null); - } catch (ErrorException $error) { - $cachedDocuments = []; - } - - if ($cachedDocuments) { - return $this->documents = new ArrayIterator($cachedDocuments); - } - - // Stores the original "limit" clause of the query - $this->storeOriginalLimit(); - - // Stores the documents within the object and cache then for later use - $this->documents = []; - foreach (parent::getCursor() as $document) { - $this->documents[] = $document; - } - - $cacheComponent->put($cacheKey, $this->documents, 0.6); - - // Drops the unserializable DriverCursor. - $this->fresh(); - - // Return the documents iterator - return $this->documents = new ArrayIterator($this->documents); - } - - /** - * Generates an unique cache key for the cursor in it's current state. - * - * @return string cache key to identify the query of the current cursor - */ - protected function generateCacheKey(): string - { - return sprintf( - '%s:%s:%s', - $this->command, - $this->collection->getNamespace(), - md5(serialize($this->params)) - ); - } - - /** - * Stores the original "limit" clause of the query. - */ - protected function storeOriginalLimit() - { - if (isset($this->params[1]['limit'])) { - $this->originalLimit = $this->params[1]['limit']; - } - - if ($this->originalLimit > self::DOCUMENT_LIMIT) { - $this->limit(self::DOCUMENT_LIMIT); - } - } - - /** - * Gets the limit clause of the query if any. - * - * @return mixed Int or null - */ - protected function getLimit() - { - return $this->originalLimit ?: ($this->params[1]['limit'] ?? null); - } - - /** - * Returns the DriverCursor considering the documents that have already - * been retrieved from cache. - */ - protected function getOriginalCursor(): Traversable - { - if ($this->ignoreCache) { - return parent::getCursor(); - } - - if ($this->getLimit()) { - $this->params[1]['limit'] = $this->getLimit() - $this->position; - } - - $skipped = $this->params[1]['skip'] ?? 0; - - $this->skip($skipped + $this->position - 1); - - $this->ignoreCache = true; - - return $this->getOriginalCursor(); - } - - public function serialize() - { - $this->documents = null; - - return parent::serialize(); - } -} diff --git a/src/Cursor/CursorFactory.php b/src/Cursor/CursorFactory.php deleted file mode 100644 index 89704a0f..00000000 --- a/src/Cursor/CursorFactory.php +++ /dev/null @@ -1,43 +0,0 @@ -createCursor( $this->schema, @@ -242,8 +238,7 @@ public function where( [ $this->prepareValueQuery($query), ['projection' => $this->prepareProjection($projection)], - ], - $cacheable + ] ); } @@ -262,23 +257,15 @@ public function all(): CursorInterface * * @param mixed $query mongoDB query to retrieve the document * @param array $projection fields to project in Mongo query - * @param bool $cacheable retrieves the first through a CacheableCursor * * @return static|null First document matching query as an $this->schema->entityClass object */ - public function first( - $query = [], - array $projection = [], - bool $cacheable = false - ) { + public function first($query = [], array $projection = []) + { if (null === $query) { return null; } - if ($cacheable) { - return $this->where($query, $projection, true)->first(); - } - $document = $this->getCollection()->findOne( $this->prepareValueQuery($query), ['projection' => $this->prepareProjection($projection)] @@ -299,18 +286,14 @@ public function first( * * @param mixed $query mongoDB query to retrieve the document * @param array $projection fields to project in Mongo query - * @param bool $cacheable retrieves the first through a CacheableCursor * * @throws ModelNotFoundException If no document was found * * @return static|null First document matching query as an $this->schema->entityClass object */ - public function firstOrFail( - $query = [], - array $projection = [], - bool $cacheable = false - ) { - if ($result = $this->first($query, $projection, $cacheable)) { + public function firstOrFail($query = [], array $projection = []) + { + if ($result = $this->first($query, $projection)) { return $result; } diff --git a/src/Model/AbstractActiveRecord.php b/src/Model/AbstractActiveRecord.php index 3db3e4d6..1b7a1796 100644 --- a/src/Model/AbstractActiveRecord.php +++ b/src/Model/AbstractActiveRecord.php @@ -68,18 +68,10 @@ abstract class AbstractActiveRecord implements HasAttributesInterface, HasSchema * * @param array $query mongoDB selection criteria * @param array $projection fields to project in Mongo query - * @param bool $useCache retrieves a CacheableCursor instead */ - public static function where( - array $query = [], - array $projection = [], - bool $useCache = false - ): CursorInterface { - return self::getDataMapperInstance()->where( - $query, - $projection, - $useCache - ); + public static function where(array $query = [], array $projection = []): CursorInterface + { + return self::getDataMapperInstance()->where($query, $projection); } /** @@ -95,20 +87,12 @@ public static function all(): CursorInterface * * @param mixed $query mongoDB selection criteria * @param array $projection fields to project in Mongo query - * @param bool $useCache retrieves the entity through a CacheableCursor * * @return static|null */ - public static function first( - $query = [], - array $projection = [], - bool $useCache = false - ) { - return self::getDataMapperInstance()->first( - $query, - $projection, - $useCache - ); + public static function first($query = [], array $projection = []) + { + return self::getDataMapperInstance()->first($query, $projection); } /** @@ -117,22 +101,14 @@ public static function first( * * @param mixed $query mongoDB selection criteria * @param array $projection fields to project in Mongo query - * @param bool $useCache retrieves the entity through a CacheableCursor * * @throws ModelNotFoundException If no document was found * * @return static|null */ - public static function firstOrFail( - $query = [], - array $projection = [], - bool $useCache = false - ) { - return self::getDataMapperInstance()->firstOrFail( - $query, - $projection, - $useCache - ); + public static function firstOrFail($query = [], array $projection = []) + { + return self::getDataMapperInstance()->firstOrFail($query, $projection); } /** @@ -146,13 +122,11 @@ public static function firstOrFail( */ public static function firstOrNew($id) { - if ($entity = self::getDataMapperInstance()->first($id)) { - return $entity; + if (!$entity = self::getDataMapperInstance()->first($id)) { + $entity = new static(); + $entity->_id = $id; } - $entity = new static(); - $entity->_id = $id; - return $entity; } diff --git a/src/Model/HasRelationsTrait.php b/src/Model/HasRelationsTrait.php index 674440ef..a293806e 100644 --- a/src/Model/HasRelationsTrait.php +++ b/src/Model/HasRelationsTrait.php @@ -85,23 +85,21 @@ public function getFieldRelation(string $field): string /** * Create a ReferencesOne Relation. * - * @param string $entity class of the entity or of the schema of the entity - * @param string|null $field the field where the $key is stored - * @param string $key the field that the document will be referenced by (usually _id) - * @param bool $cacheable retrieves a CacheableCursor instead + * @param string $entity class of the entity or of the schema of the entity + * @param string|null $field the field where the $key is stored + * @param string $key the field that the document will be referenced by (usually _id) */ protected function referencesOne( string $entity, string $field = null, - string $key = '_id', - bool $cacheable = true + string $key = '_id' ): ReferencesOne { $relationName = $this->guessRelationName(); if (!$this->relationLoaded($relationName)) { $field = $field ?: $this->inferFieldForReference($relationName, $key, false); - $relation = new ReferencesOne($this, $entity, $field, $key, $cacheable); + $relation = new ReferencesOne($this, $entity, $field, $key); $this->setRelation($relationName, $relation, $field); } @@ -113,20 +111,18 @@ protected function referencesOne( * * @param string $entity class of the entity or of the schema of the entity * @param string|null $field the field where the _ids are stored - * @param bool $cacheable retrieves a CacheableCursor instead */ protected function referencesMany( string $entity, string $field = null, - string $key = '_id', - bool $cacheable = true + string $key = '_id' ): ReferencesMany { $relationName = $this->guessRelationName(); if (!$this->relationLoaded($relationName)) { $field = $field ?: $this->inferFieldForReference($relationName, $key, true); - $relation = new ReferencesMany($this, $entity, $field, $key, $cacheable); + $relation = new ReferencesMany($this, $entity, $field, $key); $this->setRelation($relationName, $relation, $field); } diff --git a/src/Model/Relations/ReferencesMany.php b/src/Model/Relations/ReferencesMany.php index b3f06a91..455226d8 100644 --- a/src/Model/Relations/ReferencesMany.php +++ b/src/Model/Relations/ReferencesMany.php @@ -8,11 +8,6 @@ class ReferencesMany extends AbstractRelation { - /** - * @var bool - */ - protected $cacheable; - /** * @var HasAttributesInterface */ @@ -27,13 +22,11 @@ public function __construct( HasAttributesInterface $parent, string $entity, string $field, - string $key, - bool $cacheable = true + string $key ) { parent::__construct($parent, $entity, $field); $this->key = $key; $this->documentEmbedder->setKey($key); - $this->cacheable = $cacheable; $this->entityInstance = Ioc::make($this->entity); } @@ -103,10 +96,6 @@ public function get() } } - return $this->entityInstance->where( - [$this->key => ['$in' => array_values($referencedKeys)]], - [], - $this->cacheable - ); + return $this->entityInstance->where([$this->key => ['$in' => array_values($referencedKeys)]]); } } diff --git a/src/Model/Relations/ReferencesOne.php b/src/Model/Relations/ReferencesOne.php index a6c993c0..65bfee0d 100644 --- a/src/Model/Relations/ReferencesOne.php +++ b/src/Model/Relations/ReferencesOne.php @@ -29,10 +29,6 @@ public function get() $referencedKey = new ObjectId((string) $referencedKey); } - return $this->entityInstance->first( - [$this->key => $referencedKey], - [], - $this->cacheable - ); + return $this->entityInstance->first([$this->key => $referencedKey]); } } diff --git a/src/Util/CacheComponent.php b/src/Util/CacheComponent.php deleted file mode 100644 index 1f1bd46e..00000000 --- a/src/Util/CacheComponent.php +++ /dev/null @@ -1,83 +0,0 @@ -has($key)) { - return $this->storage[$key]; - } - } - - /** - * Store an item in the cache for a given number of minutes. - * - * @param string $key cache key of the item - * @param mixed $value value being stored in cache - * @param float $minutes cache ttl - */ - public function put(string $key, $value, float $minutes) - { - $this->storage[$key] = $value; - $this->ttl[$key] = $this->time() + 60 * $minutes; - } - - /** - * Determine if an item exists in the cache. This method will also check - * if the ttl of the given cache key has been expired and will free the - * memory if so. - * - * @param string $key cache key of the item - */ - public function has(string $key): bool - { - if (array_key_exists($key, $this->ttl) && - $this->time() - $this->ttl[$key] > 0 - ) { - unset($this->ttl[$key]); - unset($this->storage[$key]); - - return false; - } - - return array_key_exists($key, $this->storage); - } - - /** - * Return the current time in order to check ttl. - * - * @codeCoverageIgnore - * - * @return int return current Unix timestamp - */ - protected function time() - { - return time(); - } -} diff --git a/src/Util/CacheComponentInterface.php b/src/Util/CacheComponentInterface.php deleted file mode 100644 index 5684f717..00000000 --- a/src/Util/CacheComponentInterface.php +++ /dev/null @@ -1,36 +0,0 @@ -assertAttributeEquals($manager, 'singleton', Manager::class); $this->assertAttributeInstanceOf(Container::class, 'container', $manager); - $this->assertAttributeInstanceOf(CacheComponentInterface::class, 'cacheComponent', $manager); $container = $manager->container; $this->callProtected($manager, 'init'); diff --git a/tests/Unit/Cursor/CacheableCursorTest.php b/tests/Unit/Cursor/CacheableCursorTest.php deleted file mode 100644 index 44040ca4..00000000 --- a/tests/Unit/Cursor/CacheableCursorTest.php +++ /dev/null @@ -1,241 +0,0 @@ - 'joe'], ['name' => 'doe']]); - $cursor = $this->getCachableCursor(); - $this->setProtected( - $cursor, - 'documents', - $documentsFromDb - ); - - // Assert - $this->assertEquals( - new ArrayIterator($documentsFromDb), - $this->callProtected($cursor, 'getCursor') - ); - } - - public function testShouldGetCursorFromCache() - { - // Arrange - $documentsFromCache = [['name' => 'joe'], ['name' => 'doe']]; - $cursor = $this->getCachableCursor(); - $cacheComponent = $this->instance(CacheComponentInterface::class, m::mock(CacheComponentInterface::class)); - - // Act - $cursor->expects() - ->generateCacheKey() - ->andReturn('find:collection:123'); - - $cacheComponent->expects() - ->get('find:collection:123', null) - ->andReturn($documentsFromCache); - - // Assert - $this->assertEquals( - new ArrayIterator($documentsFromCache), - $this->callProtected($cursor, 'getCursor') - ); - } - - public function testShouldGetFromDatabaseWhenCacheFails() - { - // Arrange - $documentsFromDb = [['name' => 'joe'], ['name' => 'doe']]; - $cursor = $this->getCachableCursor()->limit(150); - $cacheComponent = $this->instance(CacheComponentInterface::class, m::mock(CacheComponentInterface::class)); - $rawCollection = m::mock(); - $cacheKey = 'find:collection:123'; - - $this->setProtected( - $cursor, - 'collection', - $rawCollection - ); - - // Act - $cursor->expects() - ->generateCacheKey() - ->andReturn($cacheKey); - - $cacheComponent->expects() - ->get($cacheKey, null) - ->andThrow( - new ErrorException( - sprintf('Unable to unserialize cache %s', $cacheKey) - ) - ); - - $rawCollection->expects() - ->find([], ['limit' => 100]) - ->andReturn(new ArrayIterator($documentsFromDb)); - - $cacheComponent->expects() - ->put($cacheKey, $documentsFromDb, m::any()); - - // Assert - $this->assertEquals( - new ArrayIterator($documentsFromDb), - $this->callProtected($cursor, 'getCursor') - ); - } - - public function testShouldGetCursorFromDatabaseAndCacheForLater() - { - // Arrange - $documentsFromDb = [['name' => 'joe'], ['name' => 'doe']]; - $cursor = $this->getCachableCursor()->limit(150); - $cacheComponent = $this->instance(CacheComponentInterface::class, m::mock(CacheComponentInterface::class)); - $rawCollection = m::mock(); - - $this->setProtected( - $cursor, - 'collection', - $rawCollection - ); - - // Act - $cursor->expects() - ->generateCacheKey() - ->andReturn('find:collection:123'); - - $cacheComponent->expects() - ->get('find:collection:123', null) - ->andReturn(null); - - $rawCollection->expects() - ->find([], ['limit' => 100]) - ->andReturn(new ArrayIterator($documentsFromDb)); - - $cacheComponent->expects() - ->put('find:collection:123', $documentsFromDb, m::any()); - - // Assert - $this->assertEquals( - new ArrayIterator($documentsFromDb), - $this->callProtected($cursor, 'getCursor') - ); - } - - public function testShouldGetOriginalCursorFromDatabaseAfterTheDocumentLimit() - { - // Arrange - $documentsFromDb = [['name' => 'joe'], ['name' => 'doe']]; - $cursor = $this->getCachableCursor()->limit(150); - $cacheComponent = $this->instance(CacheComponentInterface::class, m::mock(CacheComponentInterface::class)); - $rawCollection = m::mock(); - - $this->setProtected( - $cursor, - 'position', - CacheableCursor::DOCUMENT_LIMIT + 1 - ); - - $this->setProtected( - $cursor, - 'collection', - $rawCollection - ); - - // Act - $cursor->expects() - ->generateCacheKey() - ->never(); - - $cacheComponent->expects() - ->get('find:collection:123', null) - ->never(); - - $rawCollection->expects() - ->find([], ['skip' => CacheableCursor::DOCUMENT_LIMIT, 'limit' => 49]) - ->andReturn(new ArrayIterator($documentsFromDb)); - - $cacheComponent->expects() - ->put() - ->never(); - - // Assert - $this->assertEquals( - new CachingIterator(new ArrayIterator($documentsFromDb)), - $this->callProtected($cursor, 'getCursor') - ); - } - - public function testShouldGenerateUniqueCacheKey() - { - // Arrange - $cursor = $this->getCachableCursor(null, null, 'find', [['color' => 'red']]); - - // Act - $cursor->expects() - ->generateCacheKey() - ->passthru(); - - $expectedCacheKey = sprintf( - '%s:%s:%s', - 'find', - 'my_db.my_collection', - md5(serialize([['color' => 'red']])) - ); - - // Assert - - $this->assertEquals( - $expectedCacheKey, - $cursor->generateCacheKey() - ); - } - - protected function getCachableCursor( - $entitySchema = null, - $collection = null, - $command = 'find', - $params = [[]], - $driverCursor = null - ) { - if (!$entitySchema) { - $entitySchema = m::mock(AbstractSchema::class.'[]'); - } - - if (!$collection) { - $collection = m::mock(Collection::class); - - $collection->allows() - ->getNamespace() - ->andReturn('my_db.my_collection'); - - $collection->allows() - ->getCollectionName() - ->andReturn('my_collection'); - } - - $mock = m::mock( - CacheableCursor::class.'[generateCacheKey]', - [$entitySchema, $collection, $command, $params] - ); - $mock->shouldAllowMockingProtectedMethods(); - - if ($driverCursor) { - $mock->expects() - ->getCursor() - ->andReturn($driverCursor); - } - - return $mock; - } -} diff --git a/tests/Unit/Cursor/CursorFactoryTest.php b/tests/Unit/Cursor/CursorFactoryTest.php deleted file mode 100644 index 75e85689..00000000 --- a/tests/Unit/Cursor/CursorFactoryTest.php +++ /dev/null @@ -1,75 +0,0 @@ -createCursor( - $schema, - $collection, - 'find', - $params = ['age' => ['$gr' => 25]] - ); - - $this->assertInstanceOf(Cursor::class, $result); - $this->assertNotInstanceOf(CacheableCursor::class, $result); - $this->assertNotInstanceOf(EmbeddedCursor::class, $result); - $this->assertAttributeEquals($schema, 'entitySchema', $result); - $this->assertAttributeEquals($collection, 'collection', $result); - $this->assertAttributeEquals('find', 'command', $result); - $this->assertAttributeEquals($params, 'params', $result); - } - - public function testShouldCreateACacheableCursor() - { - // Set - $factory = new CursorFactory(); - $schema = m::mock(AbstractSchema::class); - $collection = m::mock(Collection::class); - - // Assert - $result = $factory->createCursor( - $schema, - $collection, - 'find', - $params = ['age' => ['$gr' => 25]], - true // $cacheable - ); - - $this->assertInstanceOf(Cursor::class, $result); - $this->assertInstanceOf(CacheableCursor::class, $result); - $this->assertNotInstanceOf(EmbeddedCursor::class, $result); - $this->assertAttributeEquals($schema, 'entitySchema', $result); - $this->assertAttributeEquals($collection, 'collection', $result); - $this->assertAttributeEquals('find', 'command', $result); - $this->assertAttributeEquals($params, 'params', $result); - } - - public function testShouldCreateAEmbeddedCursor() - { - // Set - $factory = new CursorFactory(); - $entityClass = 'MyModelClass'; - - // Assert - $result = $factory->createEmbeddedCursor($entityClass, [['foo' => 'bar']]); - - $this->assertInstanceOf(EmbeddedCursor::class, $result); - $this->assertNotInstanceOf(Cursor::class, $result); - $this->assertNotInstanceOf(CacheableCursor::class, $result); - $this->assertAttributeEquals($entityClass, 'entityClass', $result); - $this->assertAttributeEquals([['foo' => 'bar']], 'items', $result); - } -} diff --git a/tests/Unit/DataMapper/DataMapperTest.php b/tests/Unit/DataMapper/DataMapperTest.php index 3ec2b541..38c18bb6 100644 --- a/tests/Unit/DataMapper/DataMapperTest.php +++ b/tests/Unit/DataMapper/DataMapperTest.php @@ -10,7 +10,6 @@ use MongoDB\Driver\WriteConcern; use Mongolid\Connection\Connection; use Mongolid\Container\Ioc; -use Mongolid\Cursor\CacheableCursor; use Mongolid\Cursor\Cursor; use Mongolid\Event\EventTriggerService; use Mongolid\Exception\ModelNotFoundException; @@ -506,21 +505,17 @@ public function testShouldGetWithWhereQuery() // Expect $dataMapper->expects() ->prepareValueQuery($query) - ->twice() ->andReturn($preparedQuery); $dataMapper->expects() ->getCollection() - ->twice() ->andReturn($collection); // Act $result = $dataMapper->where($query, $projection); - $cacheableResult = $dataMapper->where($query, [], true); // Assert $this->assertInstanceOf(Cursor::class, $result); - $this->assertNotInstanceOf(CacheableCursor::class, $result); $this->assertAttributeEquals($schema, 'entitySchema', $result); $this->assertAttributeEquals($collection, 'collection', $result); $this->assertAttributeEquals('find', 'command', $result); @@ -529,15 +524,6 @@ public function testShouldGetWithWhereQuery() 'params', $result ); - - $this->assertInstanceOf(CacheableCursor::class, $cacheableResult); - $this->assertAttributeEquals($schema, 'entitySchema', $cacheableResult); - $this->assertAttributeEquals($collection, 'collection', $cacheableResult); - $this->assertAttributeEquals( - [$preparedQuery, ['projection' => []]], - 'params', - $cacheableResult - ); } public function testShouldGetAll() @@ -740,57 +726,6 @@ public function testShouldGetFirstProjectingFields() $this->assertNull($result); } - public function testShouldGetFirstTroughACacheableCursor() - { - // Arrange - $connection = m::mock(Connection::class); - $dataMapper = m::mock(DataMapper::class.'[where]', [$connection]); - $query = 123; - $entity = new stdClass(); - $cursor = m::mock(CacheableCursor::class); - - // Expect - $dataMapper->expects() - ->where($query, [], true) - ->andReturn($cursor); - - $cursor->expects() - ->first() - ->andReturn($entity); - - // Act - $result = $dataMapper->first($query, [], true); - - // Assert - $this->assertSame($entity, $result); - } - - public function testShouldGetFirstTroughACacheableCursorProjectingFields() - { - // Arrange - $connection = m::mock(Connection::class); - $dataMapper = m::mock(DataMapper::class.'[where]', [$connection]); - $query = 123; - $entity = new stdClass(); - $cursor = m::mock(CacheableCursor::class); - $projection = ['project' => true, '_id' => false]; - - // Expect - $dataMapper->expects() - ->where($query, $projection, true) - ->andReturn($cursor); - - $cursor->expects() - ->first() - ->andReturn($entity); - - // Act - $result = $dataMapper->first($query, $projection, true); - - // Assert - $this->assertSame($entity, $result); - } - public function testShouldParseObjectToDocumentAndPutResultingIdIntoTheGivenObject() { // Arrange diff --git a/tests/Unit/Model/AbstractActiveRecordTest.php b/tests/Unit/Model/AbstractActiveRecordTest.php index 66dffcd7..a5aa48e6 100644 --- a/tests/Unit/Model/AbstractActiveRecordTest.php +++ b/tests/Unit/Model/AbstractActiveRecordTest.php @@ -182,11 +182,11 @@ public function testShouldGetWithWhereQuery() ->setSchema(m::type(DynamicSchema::class)); $dataMapper->expects() - ->where($query, $projection, true) + ->where($query, $projection) ->andReturn($cursor); // Assertions - $this->assertSame($cursor, $this->entity->where($query, $projection, true)); + $this->assertSame($cursor, $this->entity->where($query, $projection)); } public function testShouldGetAll() @@ -220,11 +220,11 @@ public function testShouldGetFirstWithQuery() ->setSchema(m::type(DynamicSchema::class)); $dataMapper->expects() - ->first($query, $projection, true) + ->first($query, $projection) ->andReturn($this->entity); // Assertions - $this->assertSame($this->entity, $this->entity->first($query, $projection, true)); + $this->assertSame($this->entity, $this->entity->first($query, $projection)); } public function testShouldGetFirstOrFail() @@ -239,11 +239,11 @@ public function testShouldGetFirstOrFail() ->setSchema(m::type(DynamicSchema::class)); $dataMapper->expects() - ->firstOrFail($query, $projection, true) + ->firstOrFail($query, $projection) ->andReturn($this->entity); // Assertions - $this->assertSame($this->entity, $this->entity->firstOrFail($query, $projection, true)); + $this->assertSame($this->entity, $this->entity->firstOrFail($query, $projection)); } public function testShouldGetFirstOrNewAndReturnExistingModel() diff --git a/tests/Unit/Model/HasRelationsTraitTest.php b/tests/Unit/Model/HasRelationsTraitTest.php index 9b9d6940..2740bda2 100644 --- a/tests/Unit/Model/HasRelationsTraitTest.php +++ b/tests/Unit/Model/HasRelationsTraitTest.php @@ -24,7 +24,7 @@ public function testShouldReferenceOne($fieldValue, $expectedQuery) // Expectations $dataMapper->expects() - ->first($expectedQuery, [], true) + ->first($expectedQuery, []) ->andReturn($expected); // Actions @@ -49,7 +49,7 @@ public function testShouldReferenceMany($fieldValue, $expectedQuery) // Expectations $dataMapper->expects() - ->where($expectedQuery, [], true) + ->where($expectedQuery, []) ->andReturn($expected); // Actions diff --git a/tests/Unit/Util/CacheComponentTest.php b/tests/Unit/Util/CacheComponentTest.php deleted file mode 100644 index b3df3f30..00000000 --- a/tests/Unit/Util/CacheComponentTest.php +++ /dev/null @@ -1,73 +0,0 @@ -assertInstanceOf(CacheComponentInterface::class, $cacheComponent); - } - - public function testShouldPutAndRetrieveValues() - { - // Arrange - $cacheComponent = $this->getCacheComponent(); - - // Assertion - $cacheComponent->put('saveThe', 'bacon', 1); // 1 minute of ttl - $this->tick(30); // After 30 seconds - $this->assertTrue($cacheComponent->has('saveThe')); - $this->assertEquals('bacon', $cacheComponent->get('saveThe')); - } - - public function testShouldExpireValues() - { - // Arrange - $cacheComponent = $this->getCacheComponent(); - $cacheComponent->put('saveThe', 'bacon', 1); // 1 minute of ttl - - // Act - $this->tick(61); // After 61 seconds - - // Assertion - $this->assertFalse($cacheComponent->has('saveThe')); - $this->assertNull($cacheComponent->get('saveThe')); - } - - protected function getCacheComponent() - { - $test = $this; - $cacheComponent = m::mock(CacheComponent::class.'[time]'); - $cacheComponent->shouldAllowMockingProtectedMethods(); - $cacheComponent->allows() - ->time() - ->andReturnUsing(function () use ($test) { - return $test->time; - }); - - return $cacheComponent; - } - - /** - * Skips $seconds of time. - */ - protected function tick($seconds) - { - $this->time += $seconds; - } -} diff --git a/tests/Util/SetupConnectionTrait.php b/tests/Util/SetupConnectionTrait.php index ee8ec2cd..24711a64 100644 --- a/tests/Util/SetupConnectionTrait.php +++ b/tests/Util/SetupConnectionTrait.php @@ -3,8 +3,6 @@ use Mongolid\Connection\Connection; use Mongolid\Container\Ioc; -use Mongolid\Util\CacheComponent; -use Mongolid\Util\CacheComponentInterface; trait SetupConnectionTrait { @@ -19,10 +17,5 @@ function () use ($host, $database) { return $connection; } ); - - $this->instance( - CacheComponentInterface::class, - new CacheComponent() - ); } } From 3f79f96e716340718449c84ca4a809e5f6a4434e Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 26 Nov 2018 08:48:39 -0200 Subject: [PATCH 070/116] Drop cursor factory --- src/DataMapper/DataMapper.php | 21 ++++++++++----------- src/Model/HasRelationsTrait.php | 4 ++-- src/Model/Relations/EmbedsMany.php | 5 +---- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/DataMapper/DataMapper.php b/src/DataMapper/DataMapper.php index b936b3e7..f8792b14 100644 --- a/src/DataMapper/DataMapper.php +++ b/src/DataMapper/DataMapper.php @@ -6,7 +6,7 @@ use MongoDB\Collection; use Mongolid\Connection\Connection; use Mongolid\Container\Ioc; -use Mongolid\Cursor\CursorFactory; +use Mongolid\Cursor\Cursor; use Mongolid\Cursor\CursorInterface; use Mongolid\Event\EventTriggerService; use Mongolid\Exception\ModelNotFoundException; @@ -230,16 +230,15 @@ public function delete($entity, array $options = []): bool */ public function where($query = [], array $projection = []): CursorInterface { - return Ioc::make(CursorFactory::class) - ->createCursor( - $this->schema, - $this->getCollection(), - 'find', - [ - $this->prepareValueQuery($query), - ['projection' => $this->prepareProjection($projection)], - ] - ); + return new Cursor( + $this->schema, + $this->getCollection(), + 'find', + [ + $this->prepareValueQuery($query), + ['projection' => $this->prepareProjection($projection)], + ] + ); } /** diff --git a/src/Model/HasRelationsTrait.php b/src/Model/HasRelationsTrait.php index a293806e..67ca5071 100644 --- a/src/Model/HasRelationsTrait.php +++ b/src/Model/HasRelationsTrait.php @@ -109,8 +109,8 @@ protected function referencesOne( /** * Create a ReferencesMany Relation. * - * @param string $entity class of the entity or of the schema of the entity - * @param string|null $field the field where the _ids are stored + * @param string $entity class of the entity or of the schema of the entity + * @param string|null $field the field where the _ids are stored */ protected function referencesMany( string $entity, diff --git a/src/Model/Relations/EmbedsMany.php b/src/Model/Relations/EmbedsMany.php index e70af55f..007dfb50 100644 --- a/src/Model/Relations/EmbedsMany.php +++ b/src/Model/Relations/EmbedsMany.php @@ -1,8 +1,6 @@ createEmbeddedCursor($this->entity, $items); + return new EmbeddedCursor($this->entity, $items); } } From fc9057a84b6e144c83e37f7ad571fffb3b92c67f Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 26 Nov 2018 10:49:04 -0200 Subject: [PATCH 071/116] "Drop" DataMapper pattern We don't actually implement the pattern neither use it, so there is no need to have it. --- phpcs.xml | 4 +- src/Connection/Manager.php | 29 +- src/Cursor/Cursor.php | 34 +- src/Cursor/EmbeddedCursor.php | 36 +- src/Event/EventTriggerService.php | 12 +- ...ractActiveRecord.php => AbstractModel.php} | 99 +++-- src/Model/DocumentEmbedder.php | 53 ++- .../InvalidFieldNameException.php | 2 +- .../Exception/ModelNotFoundException.php | 2 +- .../Exception/NoCollectionNameException.php | 4 +- .../NotARelationException.php | 2 +- src/Model/HasRelationsTrait.php | 50 ++- src/Model/ModelInterface.php | 19 + src/Model/PolymorphableInterface.php | 6 +- src/Model/Relations/AbstractRelation.php | 6 +- src/Model/Relations/EmbedsMany.php | 24 +- src/Model/Relations/EmbedsOne.php | 10 +- src/Model/Relations/ReferencesMany.php | 42 +- src/Model/Relations/ReferencesOne.php | 9 +- .../DataMapper.php => Query/Builder.php} | 153 ++++---- src/{DataMapper => Query}/BulkWrite.php | 14 +- .../ModelAssembler.php} | 54 ++- src/{DataMapper => Query}/SchemaMapper.php | 4 +- src/Schema/AbstractSchema.php | 5 +- src/Schema/HasSchemaInterface.php | 17 - tests/Integration/EmbedsOneRelationTest.php | 2 +- .../Integration/ReferencesOneRelationTest.php | 2 +- tests/Integration/Stubs/EmbeddedUser.php | 4 +- tests/Integration/Stubs/ReferencedUser.php | 4 +- tests/Unit/Connection/ManagerTest.php | 16 +- tests/Unit/Cursor/CursorTest.php | 104 ++--- tests/Unit/Cursor/EmbeddedCursorTest.php | 91 +++-- tests/Unit/Event/EventTriggerServiceTest.php | 16 +- ...veRecordTest.php => AbstractModelTest.php} | 192 +++++----- tests/Unit/Model/DocumentEmbedderTest.php | 37 +- .../Exception/ModelNotFoundExceptionTest.php | 2 +- tests/Unit/Model/HasAttributesTraitTest.php | 4 +- tests/Unit/Model/HasRelationsTraitTest.php | 16 +- .../AbstractSchemaMapperTest.php | 2 +- .../BuilderTest.php} | 358 +++++++++--------- .../{DataMapper => Query}/BulkWriteTest.php | 36 +- .../ModelAssemblerTest.php} | 68 ++-- tests/Unit/Schema/AbstractSchemaTest.php | 4 +- 43 files changed, 813 insertions(+), 835 deletions(-) rename src/Model/{AbstractActiveRecord.php => AbstractModel.php} (70%) rename src/Model/{Relations => Exception}/InvalidFieldNameException.php (70%) rename src/{ => Model}/Exception/ModelNotFoundException.php (95%) rename src/{ => Model}/Exception/NoCollectionNameException.php (61%) rename src/Model/{Relations => Exception}/NotARelationException.php (69%) create mode 100644 src/Model/ModelInterface.php rename src/{DataMapper/DataMapper.php => Query/Builder.php} (76%) rename src/{DataMapper => Query}/BulkWrite.php (86%) rename src/{DataMapper/EntityAssembler.php => Query/ModelAssembler.php} (60%) rename src/{DataMapper => Query}/SchemaMapper.php (98%) delete mode 100644 src/Schema/HasSchemaInterface.php rename tests/Unit/Model/{AbstractActiveRecordTest.php => AbstractModelTest.php} (64%) rename tests/Unit/{ => Model}/Exception/ModelNotFoundExceptionTest.php (93%) rename tests/Unit/{DataMapper => Query}/AbstractSchemaMapperTest.php (99%) rename tests/Unit/{DataMapper/DataMapperTest.php => Query/BuilderTest.php} (68%) rename tests/Unit/{DataMapper => Query}/BulkWriteTest.php (82%) rename tests/Unit/{DataMapper/EntityAssemblerTest.php => Query/ModelAssemblerTest.php} (83%) diff --git a/phpcs.xml b/phpcs.xml index 948bf4b5..7d1cb137 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -17,12 +17,12 @@ - tests/Unit/DataMapper/EntityAssemblerTest.php + tests/Unit/Query/ModelAssemblerTest.php tests/Unit/Model/HasRelationsTraitTest.php - tests/Unit/DataMapper/EntityAssemblerTest.php + tests/Unit/Query/ModelAssemblerTest.php tests/Unit/Model/HasRelationsTraitTest.php diff --git a/src/Connection/Manager.php b/src/Connection/Manager.php index 36f5372a..1182f842 100644 --- a/src/Connection/Manager.php +++ b/src/Connection/Manager.php @@ -3,9 +3,9 @@ use Illuminate\Container\Container; use Mongolid\Container\Ioc; -use Mongolid\DataMapper\DataMapper; use Mongolid\Event\EventTriggerInterface; use Mongolid\Event\EventTriggerService; +use Mongolid\Query\Builder; use Mongolid\Schema\AbstractSchema; /** @@ -43,8 +43,7 @@ class Manager protected $connection; /** - * Stores the schemas that have been registered for later use. This may be - * useful when using Mongolid DataMapper pattern. + * Stores the schemas that have been registered for later use. * * @var array */ @@ -88,7 +87,7 @@ public function setEventTrigger(EventTriggerInterface $eventTrigger) { $this->init(); $eventService = new EventTriggerService(); - $eventService->registerEventDispatcher($eventTrigger); + $eventService->registerEventBuilder($eventTrigger); $this->container->instance(EventTriggerService::class, $eventService); } @@ -100,26 +99,26 @@ public function setEventTrigger(EventTriggerInterface $eventTrigger) */ public function registerSchema(AbstractSchema $schema) { - $this->schemas[$schema->entityClass] = $schema; + $this->schemas[$schema->modelClass] = $schema; } /** - * Retrieves a DataMapper for the given $entityClass. This can only be done - * if the Schema for that entity has been previously registered with + * Retrieves a Builder for the given $modelClass. This can only be done + * if the Schema for that model has been previously registered with * registerSchema() method. * - * @param string $entityClass class of the entity that needs to be mapped - * - * @return DataMapper|null dataMapper configured for the $entityClass + * @param string $modelClass class of the model that needs to be mapped */ - public function getMapper(string $entityClass) + public function getBuilder(string $modelClass): ?Builder { - if (isset($this->schemas[$entityClass])) { - $dataMapper = Ioc::make(DataMapper::class); - $dataMapper->setSchema($this->schemas[$entityClass] ?? null); + if (isset($this->schemas[$modelClass])) { + $builder = Ioc::make(Builder::class); + $builder->setSchema($this->schemas[$modelClass] ?? null); - return $dataMapper; + return $builder; } + + return null; } /** diff --git a/src/Cursor/Cursor.php b/src/Cursor/Cursor.php index 79ba0465..d81af845 100644 --- a/src/Cursor/Cursor.php +++ b/src/Cursor/Cursor.php @@ -9,8 +9,8 @@ use MongoDB\Model\CachingIterator; use Mongolid\Connection\Connection; use Mongolid\Container\Ioc; -use Mongolid\DataMapper\EntityAssembler; -use Mongolid\Model\AbstractActiveRecord; +use Mongolid\Model\AbstractModel; +use Mongolid\Query\ModelAssembler; use Mongolid\Schema\AbstractSchema; use Serializable; use Traversable; @@ -24,11 +24,11 @@ class Cursor implements CursorInterface, Serializable { /** - * Schema that describes the entity that will be retrieved when iterating through the cursor. + * Schema that describes the model that will be retrieved when iterating through the cursor. * * @var string */ - public $entitySchema; + public $modelSchema; /** * @var Collection @@ -66,24 +66,24 @@ class Cursor implements CursorInterface, Serializable /** * Have the responsibility of assembling the data coming from the database into actual entities. * - * @var EntityAssembler + * @var ModelAssembler */ protected $assembler; /** - * @param AbstractSchema $entitySchema schema that describes the entity that will be retrieved from the database - * @param Collection $collection the raw collection object that will be used to retrieve the documents - * @param string $command the command that is being called in the $collection - * @param array $params the parameters of the $command + * @param AbstractSchema $modelSchema schema that describes the model that will be retrieved from the database + * @param Collection $collection the raw collection object that will be used to retrieve the documents + * @param string $command the command that is being called in the $collection + * @param array $params the parameters of the $command */ public function __construct( - AbstractSchema $entitySchema, + AbstractSchema $modelSchema, Collection $collection, string $command, array $params ) { $this->cursor = null; - $this->entitySchema = $entitySchema; + $this->modelSchema = $modelSchema; $this->collection = $collection; $this->command = $command; $this->params = $params; @@ -203,16 +203,16 @@ public function current() return null; } - if ($document instanceof AbstractActiveRecord) { + if ($document instanceof AbstractModel) { $documentToArray = $document->toArray(); - $this->entitySchema = $document->getSchema(); + $this->modelSchema = $document->getSchema(); } else { $documentToArray = (array) $document; } return $this->getAssembler()->assemble( $documentToArray, - $this->entitySchema + $this->modelSchema ); } @@ -344,14 +344,14 @@ protected function getCursor(): Traversable } /** - * Retrieves an EntityAssembler instance. + * Retrieves an ModelAssembler instance. * - * @return EntityAssembler + * @return ModelAssembler */ protected function getAssembler() { if (!$this->assembler) { - $this->assembler = Ioc::make(EntityAssembler::class); + $this->assembler = Ioc::make(ModelAssembler::class); } return $this->assembler; diff --git a/src/Cursor/EmbeddedCursor.php b/src/Cursor/EmbeddedCursor.php index 79e94d8a..a2caf021 100644 --- a/src/Cursor/EmbeddedCursor.php +++ b/src/Cursor/EmbeddedCursor.php @@ -2,8 +2,8 @@ namespace Mongolid\Cursor; use Mongolid\Container\Ioc; -use Mongolid\DataMapper\EntityAssembler; -use Mongolid\Model\AbstractActiveRecord; +use Mongolid\Model\AbstractModel; +use Mongolid\Query\ModelAssembler; use Mongolid\Schema\AbstractSchema; use Mongolid\Schema\DynamicSchema; @@ -16,11 +16,11 @@ class EmbeddedCursor implements CursorInterface { /** - * Entity class that will be returned while iterating. + * Model class that will be returned while iterating. * * @var string */ - public $entityClass; + public $modelClass; /** * The actual array of embedded documents. @@ -37,13 +37,13 @@ class EmbeddedCursor implements CursorInterface private $position = 0; /** - * @param string $entityClass class of the objects that will be retrieved by the cursor - * @param array $items the items array + * @param string $modelClass class of the objects that will be retrieved by the cursor + * @param array $items the items array */ - public function __construct(string $entityClass, array $items) + public function __construct(string $modelClass, array $items) { $this->items = $items; - $this->entityClass = $entityClass; + $this->modelClass = $modelClass; } /** @@ -139,28 +139,28 @@ public function current() $document = $this->items[$this->position]; - if ($document instanceof $this->entityClass) { + if ($document instanceof $this->modelClass) { return $document; } - $schema = $this->getSchemaForEntity(); - $entityAssembler = Ioc::make(EntityAssembler::class, compact('schema')); + $schema = $this->getSchemaForModel(); + $modelAssembler = Ioc::make(ModelAssembler::class, compact('schema')); - return $entityAssembler->assemble($document, $schema); + return $modelAssembler->assemble($document, $schema); } /** - * Retrieve a schema based on Entity Class. + * Retrieve a schema based on Model Class. */ - protected function getSchemaForEntity(): AbstractSchema + protected function getSchemaForModel(): AbstractSchema { - if ($this->entityClass instanceof AbstractSchema) { - return $this->entityClass; + if ($this->modelClass instanceof AbstractSchema) { + return $this->modelClass; } - $model = new $this->entityClass(); + $model = new $this->modelClass(); - if ($model instanceof AbstractActiveRecord) { + if ($model instanceof AbstractModel) { return $model->getSchema(); } diff --git a/src/Event/EventTriggerService.php b/src/Event/EventTriggerService.php index bc42b533..d5e64d62 100644 --- a/src/Event/EventTriggerService.php +++ b/src/Event/EventTriggerService.php @@ -11,17 +11,17 @@ class EventTriggerService * * @var EventTriggerInterface */ - protected $dispatcher; + protected $builder; /** * Registers a object that will have the responsibility of firing events to * the rest of the application. * - * @param EventTriggerInterface $dispatcher event trigger object + * @param EventTriggerInterface $builder event trigger object */ - public function registerEventDispatcher(EventTriggerInterface $dispatcher) + public function registerEventBuilder(EventTriggerInterface $builder) { - $this->dispatcher = $dispatcher; + $this->builder = $builder; } /** @@ -38,8 +38,8 @@ public function registerEventDispatcher(EventTriggerInterface $dispatcher) */ public function fire(string $event, $payload, bool $halt = false) { - if ($this->dispatcher) { - return $this->dispatcher->fire($event, $payload, $halt); + if ($this->builder) { + return $this->builder->fire($event, $payload, $halt); } return true; diff --git a/src/Model/AbstractActiveRecord.php b/src/Model/AbstractModel.php similarity index 70% rename from src/Model/AbstractActiveRecord.php rename to src/Model/AbstractModel.php index 1b7a1796..d8b51fdf 100644 --- a/src/Model/AbstractActiveRecord.php +++ b/src/Model/AbstractModel.php @@ -4,21 +4,20 @@ use MongoDB\Driver\WriteConcern; use Mongolid\Container\Ioc; use Mongolid\Cursor\CursorInterface; -use Mongolid\DataMapper\DataMapper; -use Mongolid\Exception\ModelNotFoundException; -use Mongolid\Exception\NoCollectionNameException; +use Mongolid\Model\Exception\ModelNotFoundException; +use Mongolid\Model\Exception\NoCollectionNameException; +use Mongolid\Query\Builder; use Mongolid\Schema\AbstractSchema; use Mongolid\Schema\DynamicSchema; -use Mongolid\Schema\HasSchemaInterface; /** - * The Mongolid\Model\ActiveRecord base class will ensure to enable your entity to + * The Mongolid\Model\Model base class will ensure to enable your model to * have methods to interact with the database. It means that 'save', 'insert', * 'update', 'where', 'first' and 'all' are available within every instance. - * The Mongolid\Schema\Schema that describes the entity will be generated on the go + * The Mongolid\Schema\Schema that describes the model will be generated on the go * based on the $fields. */ -abstract class AbstractActiveRecord implements HasAttributesInterface, HasSchemaInterface +abstract class AbstractModel implements ModelInterface { use HasAttributesTrait; use HasRelationsTrait; @@ -34,7 +33,7 @@ abstract class AbstractActiveRecord implements HasAttributesInterface, HasSchema public $dynamic = true; /** - * Name of the collection where this kind of Entity is going to be saved or + * Name of the collection where this kind of Model is going to be saved or * retrieved from. * * @var string @@ -71,7 +70,7 @@ abstract class AbstractActiveRecord implements HasAttributesInterface, HasSchema */ public static function where(array $query = [], array $projection = []): CursorInterface { - return self::getDataMapperInstance()->where($query, $projection); + return self::getBuilderInstance()->where($query, $projection); } /** @@ -79,55 +78,49 @@ public static function where(array $query = [], array $projection = []): CursorI */ public static function all(): CursorInterface { - return self::getDataMapperInstance()->all(); + return self::getBuilderInstance()->all(); } /** - * Gets the first entity of this kind that matches the query. + * Gets the first model of this kind that matches the query. * * @param mixed $query mongoDB selection criteria * @param array $projection fields to project in Mongo query - * - * @return static|null */ - public static function first($query = [], array $projection = []) + public static function first($query = [], array $projection = []): ?ModelInterface { - return self::getDataMapperInstance()->first($query, $projection); + return self::getBuilderInstance()->first($query, $projection); } /** - * Gets the first entity of this kind that matches the query. If no + * Gets the first model of this kind that matches the query. If no * document was found, throws ModelNotFoundException. * * @param mixed $query mongoDB selection criteria * @param array $projection fields to project in Mongo query * * @throws ModelNotFoundException If no document was found - * - * @return static|null */ - public static function firstOrFail($query = [], array $projection = []) + public static function firstOrFail($query = [], array $projection = []): ?ModelInterface { - return self::getDataMapperInstance()->firstOrFail($query, $projection); + return self::getBuilderInstance()->firstOrFail($query, $projection); } /** - * Gets the first entity of this kind that matches the query. If no - * document was found, a new entity will be returned with the + * Gets the first model of this kind that matches the query. If no + * document was found, a new model will be returned with the * _if field filled. * * @param mixed $id document id - * - * @return static|null */ - public static function firstOrNew($id) + public static function firstOrNew($id): ?ModelInterface { - if (!$entity = self::getDataMapperInstance()->first($id)) { - $entity = new static(); - $entity->_id = $id; + if (!$model = self::getBuilderInstance()->first($id)) { + $model = new static(); + $model->_id = $id; } - return $entity; + return $model; } /** @@ -135,7 +128,7 @@ public static function firstOrNew($id) * * @throws NoCollectionNameException Throws exception when has no collection filled */ - private static function getDataMapperInstance(): DataMapper + private static function getBuilderInstance(): Builder { $instance = new static(); @@ -143,23 +136,23 @@ private static function getDataMapperInstance(): DataMapper throw new NoCollectionNameException(); } - return $instance->getDataMapper(); + return $instance->getBuilder(); } /** * Parses an object with SchemaMapper. * - * @param mixed $entity the object to be parsed + * @param mixed $model the object to be parsed */ - public function parseToDocument($entity): array + public function parseToDocument($model): array { - return $this->getDataMapper()->parseToDocument($entity); + return $this->getBuilder()->parseToDocument($model); } /** * Saves this object into database. */ - public function save() + public function save(): bool { return $this->execute('save'); } @@ -167,7 +160,7 @@ public function save() /** * Insert this object into database. */ - public function insert() + public function insert(): bool { return $this->execute('insert'); } @@ -175,7 +168,7 @@ public function insert() /** * Updates this object in database. */ - public function update() + public function update(): bool { return $this->execute('update'); } @@ -183,7 +176,7 @@ public function update() /** * Deletes this object in database. */ - public function delete() + public function delete(): bool { return $this->execute('delete'); } @@ -206,7 +199,7 @@ public function &__get(string $key) * @param string $key attribute name * @param mixed $value value to be set */ - public function __set(string $key, $value) + public function __set(string $key, $value): void { $this->setDocumentAttribute($key, $value); } @@ -226,21 +219,21 @@ public function __isset(string $key): bool * * @param string $key attribute name */ - public function __unset(string $key) + public function __unset(string $key): void { $this->cleanDocumentAttribute($key); } /** - * Returns a DataMapper configured with the Schema and collection described - * in this entity. + * Returns a Builder configured with the Schema and collection described + * in this model. */ - public function getDataMapper(): DataMapper + public function getBuilder(): Builder { - $dataMapper = Ioc::make(DataMapper::class); - $dataMapper->setSchema($this->getSchema()); + $builder = Ioc::make(Builder::class); + $builder->setSchema($this->getSchema()); - return $dataMapper; + return $builder; } /** @@ -264,7 +257,7 @@ public function getWriteConcern(): int * * @param int $writeConcern level of write concern for the transation */ - public function setWriteConcern(int $writeConcern) + public function setWriteConcern(int $writeConcern): void { $this->writeConcern = $writeConcern; } @@ -279,7 +272,7 @@ public function getSchema(): AbstractSchema } $schema = new DynamicSchema(); - $schema->entityClass = static::class; + $schema->modelClass = static::class; $schema->fields = $this->fields; $schema->dynamic = $this->dynamic; $schema->collection = $this->collection; @@ -290,22 +283,22 @@ public function getSchema(): AbstractSchema /** * Will check if the current value of $fields property is the name of a * Schema class and instantiate it if possible. - * - * @return AbstractSchema|null */ - protected function instantiateSchemaInFields() + protected function instantiateSchemaInFields(): ?AbstractSchema { if (is_string($this->fields)) { if (is_subclass_of($instance = Ioc::make($this->fields), AbstractSchema::class)) { return $instance; } } + + return null; } /** * Performs the given action into database. * - * @param string $action DataMapper function to execute + * @param string $action Builder function to execute */ protected function execute(string $action): bool { @@ -317,7 +310,7 @@ protected function execute(string $action): bool 'writeConcern' => new WriteConcern($this->getWriteConcern()), ]; - if ($result = $this->getDataMapper()->$action($this, $options)) { + if ($result = $this->getBuilder()->$action($this, $options)) { $this->syncOriginalDocumentAttributes(); } diff --git a/src/Model/DocumentEmbedder.php b/src/Model/DocumentEmbedder.php index 3d1b828c..6a6b8ea7 100644 --- a/src/Model/DocumentEmbedder.php +++ b/src/Model/DocumentEmbedder.php @@ -2,7 +2,6 @@ namespace Mongolid\Model; use MongoDB\BSON\ObjectId; -use Mongolid\Schema\HasSchemaInterface; /** * Document embedder is a service that will embed documents within each other. @@ -25,20 +24,20 @@ public function setKey(string $key): void } /** - * Embeds the given $entity into $field of $parent. This method will also - * consider the key of the $entity in order to update it if it is already + * Embeds the given $model into $field of $parent. This method will also + * consider the key of the $model in order to update it if it is already * present in $field. * - * @param mixed $parent the object where the $entity will be embedded - * @param string $field name of the field of the object where the document will be embedded - * @param mixed $entity entity that will be embedded within $parent + * @param ModelInterface $parent the object where the $model will be embedded + * @param string $field name of the field of the object where the document will be embedded + * @param ModelInterface|array $model model that will be embedded within $parent */ - public function embed($parent, string $field, HasSchemaInterface &$entity): bool + public function embed(ModelInterface $parent, string $field, &$model): bool { // In order to update the document if it exists inside the $parent - $this->unembed($parent, $field, $entity); + $this->unembed($parent, $field, $model); - $data = $entity->parseToDocument($entity); + $data = $model->parseToDocument($model); $fieldValue = $parent->$field; $fieldValue[] = $data; $parent->$field = array_values($fieldValue); @@ -47,16 +46,16 @@ public function embed($parent, string $field, HasSchemaInterface &$entity): bool } /** - * Removes the given $entity from $field of $parent. This method will - * consider the key of the $entity in order to remove it. + * Removes the given $model from $field of $parent. This method will + * consider the key of the $model in order to remove it. * - * @param mixed $parent the object where the $entity will be removed - * @param string $field name of the field of the object where the document is - * @param mixed $entity entity that will be removed from $parent + * @param ModelInterface $parent the object where the $model will be removed + * @param string $field name of the field of the object where the document is + * @param ModelInterface|array $model model that will be removed from $parent */ - public function unembed($parent, string $field, &$entity): bool + public function unembed(ModelInterface $parent, string $field, &$model): bool { - $embeddedKey = $this->getKey($entity); + $embeddedKey = $this->getKey($model); foreach ((array) $parent->$field as $arrayKey => $document) { if ($embeddedKey == $this->getKey($document)) { @@ -72,13 +71,13 @@ public function unembed($parent, string $field, &$entity): bool /** * Attach a new key reference into $field of $parent. * - * @param mixed $parent the object where $entity will be referenced - * @param string $field the field where the key reference of $entity will be stored - * @param object|array $entity the object that is being attached + * @param ModelInterface $parent the object where $model will be referenced + * @param string $field the field where the key reference of $model will be stored + * @param ModelInterface|array $model the object that is being attached */ - public function attach($parent, string $field, &$entity): bool + public function attach(ModelInterface $parent, string $field, &$model): bool { - $referencedKey = $this->getKey($entity); + $referencedKey = $this->getKey($model); foreach ((array) $parent->$field as $key) { if ($key == $referencedKey) { @@ -96,13 +95,13 @@ public function attach($parent, string $field, &$entity): bool /** * Removes a key reference from $field of $parent. * - * @param mixed $parent the object where $entity reference will be removed - * @param string $field the field where the key reference of $entity is stored - * @param mixed $entity the object being detached or its key + * @param ModelInterface $parent the object where $model reference will be removed + * @param string $field the field where the key reference of $model is stored + * @param ModelInterface|array $model the object being detached or its key */ - public function detach($parent, string $field, &$entity): bool + public function detach(ModelInterface $parent, string $field, &$model): bool { - $referencedKey = $this->getKey($entity); + $referencedKey = $this->getKey($model); foreach ((array) $parent->$field as $arrayKey => $documentKey) { if ($documentKey == $referencedKey) { @@ -119,7 +118,7 @@ public function detach($parent, string $field, &$entity): bool * Gets the key of the given object or array. If there is no key in it a new * key will be generated and set on the object (while still returning it). * - * @param mixed $object the object|array that the key will be retrieved from + * @param ModelInterface|array $object the object|array that the key will be retrieved from * * @return ObjectId|mixed */ diff --git a/src/Model/Relations/InvalidFieldNameException.php b/src/Model/Exception/InvalidFieldNameException.php similarity index 70% rename from src/Model/Relations/InvalidFieldNameException.php rename to src/Model/Exception/InvalidFieldNameException.php index 2994f9b1..b459a8e7 100644 --- a/src/Model/Relations/InvalidFieldNameException.php +++ b/src/Model/Exception/InvalidFieldNameException.php @@ -1,5 +1,5 @@ guessRelationName(); if (!$this->relationLoaded($relationName)) { $field = $field ?: $this->inferFieldForReference($relationName, $key, false); - $relation = new ReferencesOne($this, $entity, $field, $key); + $relation = new ReferencesOne($this, $model, $field, $key); $this->setRelation($relationName, $relation, $field); } @@ -109,20 +106,17 @@ protected function referencesOne( /** * Create a ReferencesMany Relation. * - * @param string $entity class of the entity or of the schema of the entity - * @param string|null $field the field where the _ids are stored + * @param string $model class of the model or of the schema of the model + * @param string|null $field the field where the _ids are stored */ - protected function referencesMany( - string $entity, - string $field = null, - string $key = '_id' - ): ReferencesMany { + protected function referencesMany(string $model, string $field = null, string $key = '_id'): ReferencesMany + { $relationName = $this->guessRelationName(); if (!$this->relationLoaded($relationName)) { $field = $field ?: $this->inferFieldForReference($relationName, $key, true); - $relation = new ReferencesMany($this, $entity, $field, $key); + $relation = new ReferencesMany($this, $model, $field, $key); $this->setRelation($relationName, $relation, $field); } @@ -132,17 +126,17 @@ protected function referencesMany( /** * Create a EmbedsOne Relation. * - * @param string|null $entity class of the entity or of the schema of the entity - * @param string $field field where the embedded document is stored + * @param string|null $model class of the model or of the schema of the model + * @param string $field field where the embedded document is stored */ - protected function embedsOne(string $entity, string $field = null): EmbedsOne + protected function embedsOne(string $model, string $field = null): EmbedsOne { $relationName = $this->guessRelationName(); if (!$this->relationLoaded($relationName)) { $field = $field ?: $this->inferFieldForEmbed($relationName); - $relation = new EmbedsOne($this, $entity, $field); + $relation = new EmbedsOne($this, $model, $field); $this->setRelation($relationName, $relation, $field); } @@ -152,17 +146,17 @@ protected function embedsOne(string $entity, string $field = null): EmbedsOne /** * Create a EmbedsMany Relation. * - * @param string|null $entity class of the entity or of the schema of the entity - * @param string $field field where the embedded documents are stored + * @param string|null $model class of the model or of the schema of the model + * @param string $field field where the embedded documents are stored */ - protected function embedsMany(string $entity, string $field = null): EmbedsMany + protected function embedsMany(string $model, string $field = null): EmbedsMany { $relationName = $this->guessRelationName(); if (!$this->relationLoaded($relationName)) { $field = $field ?: $this->inferFieldForEmbed($relationName); - $relation = new EmbedsMany($this, $entity, $field); + $relation = new EmbedsMany($this, $model, $field); $this->setRelation($relationName, $relation, $field); } @@ -173,7 +167,7 @@ protected function embedsMany(string $entity, string $field = null): EmbedsMany * Retrieve relation name. For example, if we have a code like this: * * ``` - * class User extends AbstractActiveRecord + * class User extends AbstractModel * { * public function brother() * { diff --git a/src/Model/ModelInterface.php b/src/Model/ModelInterface.php new file mode 100644 index 00000000..837ab83e --- /dev/null +++ b/src/Model/ModelInterface.php @@ -0,0 +1,19 @@ +parent = $parent; - $this->entity = $entity; + $this->model = $model; $this->field = $field; $this->documentEmbedder = Ioc::make(DocumentEmbedder::class); diff --git a/src/Model/Relations/EmbedsMany.php b/src/Model/Relations/EmbedsMany.php index 007dfb50..c87e5124 100644 --- a/src/Model/Relations/EmbedsMany.php +++ b/src/Model/Relations/EmbedsMany.php @@ -2,18 +2,17 @@ namespace Mongolid\Model\Relations; use Mongolid\Cursor\EmbeddedCursor; +use Mongolid\Model\ModelInterface; class EmbedsMany extends AbstractRelation { /** * Embed a new document. It will also generate an * _id for the document if it's not present. - * - * @param mixed $entity model */ - public function add($entity): void + public function add(ModelInterface $model): void { - $this->documentEmbedder->embed($this->parent, $this->field, $entity); + $this->documentEmbedder->embed($this->parent, $this->field, $model); $this->pristine = false; } @@ -24,8 +23,8 @@ public function add($entity): void */ public function addMany(array $entities): void { - foreach ($entities as $entity) { - $this->add($entity); + foreach ($entities as $model) { + $this->add($model); } } @@ -42,13 +41,13 @@ public function replace(array $entities): void /** * Removes an embedded document from the given field. It does that by using - * the _id of given $entity. + * the _id of given $model. * - * @param mixed $entity model or _id + * @param mixed $model model or _id */ - public function remove($entity): void + public function remove(ModelInterface $model): void { - $this->documentEmbedder->unembed($this->parent, $this->field, $entity); + $this->documentEmbedder->unembed($this->parent, $this->field, $model); $this->pristine = false; } @@ -58,6 +57,9 @@ public function removeAll(): void $this->pristine = false; } + /** + * @return EmbeddedCursor + */ public function get() { $items = (array) $this->parent->{$this->field}; @@ -71,6 +73,6 @@ public function get() protected function createCursor($items): EmbeddedCursor { - return new EmbeddedCursor($this->entity, $items); + return new EmbeddedCursor($this->model, $items); } } diff --git a/src/Model/Relations/EmbedsOne.php b/src/Model/Relations/EmbedsOne.php index 3a84af4b..80b27ab6 100644 --- a/src/Model/Relations/EmbedsOne.php +++ b/src/Model/Relations/EmbedsOne.php @@ -1,21 +1,23 @@ removeAll(); - parent::add($entity); + parent::add($model); } - public function remove($entity = null): void + public function remove(ModelInterface $model = null): void { $this->removeAll(); } /** - * @return mixed + * @return ModelInterface|null */ public function get() { diff --git a/src/Model/Relations/ReferencesMany.php b/src/Model/Relations/ReferencesMany.php index 455226d8..0fa9551e 100644 --- a/src/Model/Relations/ReferencesMany.php +++ b/src/Model/Relations/ReferencesMany.php @@ -4,6 +4,7 @@ use MongoDB\BSON\ObjectId; use Mongolid\Container\Ioc; use Mongolid\Model\HasAttributesInterface; +use Mongolid\Model\ModelInterface; use Mongolid\Util\ObjectIdUtils; class ReferencesMany extends AbstractRelation @@ -11,7 +12,7 @@ class ReferencesMany extends AbstractRelation /** * @var HasAttributesInterface */ - protected $entityInstance; + protected $modelInstance; /** * @var string @@ -20,44 +21,42 @@ class ReferencesMany extends AbstractRelation public function __construct( HasAttributesInterface $parent, - string $entity, + string $model, string $field, string $key ) { - parent::__construct($parent, $entity, $field); + parent::__construct($parent, $model, $field); $this->key = $key; $this->documentEmbedder->setKey($key); - $this->entityInstance = Ioc::make($this->entity); + $this->modelInstance = Ioc::make($this->model); } /** - * Attach document _id reference to an attribute. It will also generate an - * _id for the document if it's not present. - * - * @param mixed $entity model instance or _id to be referenced + * Attach model reference. It will also generate an + * _id for the model if it's not present. */ - public function attach($entity): void + public function attach(ModelInterface $model): void { - $this->documentEmbedder->attach($this->parent, $this->field, $entity); + $this->documentEmbedder->attach($this->parent, $this->field, $model); $this->pristine = false; } /** - * Attach many documents at once. + * Attach many models at once. * - * @param array $entities model + * @param ModelInterface[] $entities */ public function attachMany(array $entities): void { - foreach ($entities as $entity) { - $this->attach($entity); + foreach ($entities as $model) { + $this->attach($model); } } /** * Replace attached documents. * - * @param array $entities + * @param ModelInterface[] $entities */ public function replace(array $entities): void { @@ -66,19 +65,16 @@ public function replace(array $entities): void } /** - * Removes a document _id reference from an attribute. It will remove the - * _id of the given $entity from inside the given $field. - * - * @param mixed $entity document, model instance or _id that have been referenced by $field + * Removes model reference from an attribute. */ - public function detach($entity): void + public function detach(ModelInterface $model): void { - $this->documentEmbedder->detach($this->parent, $this->field, $entity); + $this->documentEmbedder->detach($this->parent, $this->field, $model); $this->pristine = false; } /** - * Removes all document references from relation. + * Removes all model references from relation. */ public function detachAll(): void { @@ -96,6 +92,6 @@ public function get() } } - return $this->entityInstance->where([$this->key => ['$in' => array_values($referencedKeys)]]); + return $this->modelInstance->where([$this->key => ['$in' => array_values($referencedKeys)]]); } } diff --git a/src/Model/Relations/ReferencesOne.php b/src/Model/Relations/ReferencesOne.php index 65bfee0d..f697ffbb 100644 --- a/src/Model/Relations/ReferencesOne.php +++ b/src/Model/Relations/ReferencesOne.php @@ -2,17 +2,18 @@ namespace Mongolid\Model\Relations; use MongoDB\BSON\ObjectId; +use Mongolid\Model\ModelInterface; use Mongolid\Util\ObjectIdUtils; class ReferencesOne extends ReferencesMany { - public function attach($entity): void + public function attach(ModelInterface $model): void { $this->detachAll(); - parent::attach($entity); + parent::attach($model); } - public function detach($entity = null): void + public function detach(ModelInterface $model = null): void { $this->detachAll(); } @@ -29,6 +30,6 @@ public function get() $referencedKey = new ObjectId((string) $referencedKey); } - return $this->entityInstance->first([$this->key => $referencedKey]); + return $this->modelInstance->first([$this->key => $referencedKey]); } } diff --git a/src/DataMapper/DataMapper.php b/src/Query/Builder.php similarity index 76% rename from src/DataMapper/DataMapper.php rename to src/Query/Builder.php index f8792b14..a7665ebe 100644 --- a/src/DataMapper/DataMapper.php +++ b/src/Query/Builder.php @@ -1,5 +1,5 @@ fireEvent('saving', $entity, true)) { + if (false === $this->fireEvent('saving', $model, true)) { return false; } - $data = $this->parseToDocument($entity); + $data = $this->parseToDocument($model); $queryResult = $this->getCollection()->replaceOne( ['_id' => $data['_id']], @@ -94,9 +93,9 @@ public function save($entity, array $options = []): bool ($queryResult->getModifiedCount() || $queryResult->getUpsertedCount()); if ($result) { - $this->afterSuccess($entity); + $this->afterSuccess($model); - $this->fireEvent('saved', $entity); + $this->fireEvent('saved', $model); } return $result; @@ -110,17 +109,17 @@ public function save($entity, array $options = []): bool * Notice: Inserts with Unacknowledged WriteConcern will not fire `inserted` event. * Return is always false if write concern is Unacknowledged. * - * @param mixed $entity the entity used in the operation - * @param array $options possible options to send to mongo driver - * @param bool $fireEvents whether events should be fired + * @param ModelInterface $model the model used in the operation + * @param array $options possible options to send to mongo driver + * @param bool $fireEvents whether events should be fired */ - public function insert($entity, array $options = [], bool $fireEvents = true): bool + public function insert(ModelInterface $model, array $options = [], bool $fireEvents = true): bool { - if ($fireEvents && false === $this->fireEvent('inserting', $entity, true)) { + if ($fireEvents && false === $this->fireEvent('inserting', $model, true)) { return false; } - $data = $this->parseToDocument($entity); + $data = $this->parseToDocument($model); $queryResult = $this->getCollection()->insertOne( $data, @@ -130,10 +129,10 @@ public function insert($entity, array $options = [], bool $fireEvents = true): b $result = $queryResult->isAcknowledged() && $queryResult->getInsertedCount(); if ($result) { - $this->afterSuccess($entity); + $this->afterSuccess($model); if ($fireEvents) { - $this->fireEvent('inserted', $entity); + $this->fireEvent('inserted', $model); } } @@ -148,28 +147,28 @@ public function insert($entity, array $options = [], bool $fireEvents = true): b * Notice: Updates with Unacknowledged WriteConcern will not fire `updated` event. * Return is always false if write concern is Unacknowledged. * - * @param mixed $entity the entity used in the operation - * @param array $options possible options to send to mongo driver + * @param ModelInterface $model the model used in the operation + * @param array $options possible options to send to mongo driver */ - public function update($entity, array $options = []): bool + public function update(ModelInterface $model, array $options = []): bool { - if (false === $this->fireEvent('updating', $entity, true)) { + if (false === $this->fireEvent('updating', $model, true)) { return false; } - if (!$entity->_id) { - if ($result = $this->insert($entity, $options, false)) { - $this->afterSuccess($entity); + if (!$model->_id) { + if ($result = $this->insert($model, $options, false)) { + $this->afterSuccess($model); - $this->fireEvent('updated', $entity); + $this->fireEvent('updated', $model); } return $result; } - $data = $this->parseToDocument($entity); + $data = $this->parseToDocument($model); - $updateData = $this->getUpdateData($entity, $data); + $updateData = $this->getUpdateData($model, $data); $queryResult = $this->getCollection()->updateOne( ['_id' => $data['_id']], @@ -180,9 +179,9 @@ public function update($entity, array $options = []): bool $result = $queryResult->isAcknowledged() && $queryResult->getModifiedCount(); if ($result) { - $this->afterSuccess($entity); + $this->afterSuccess($model); - $this->fireEvent('updated', $entity); + $this->fireEvent('updated', $model); } return $result; @@ -194,16 +193,16 @@ public function update($entity, array $options = []): bool * Notice: Deletes with Unacknowledged WriteConcern will not fire `deleted` event. * Return is always false if write concern is Unacknowledged. * - * @param mixed $entity the entity used in the operation - * @param array $options possible options to send to mongo driver + * @param ModelInterface $model the model used in the operation + * @param array $options possible options to send to mongo driver */ - public function delete($entity, array $options = []): bool + public function delete(ModelInterface $model, array $options = []): bool { - if (false === $this->fireEvent('deleting', $entity, true)) { + if (false === $this->fireEvent('deleting', $model, true)) { return false; } - $data = $this->parseToDocument($entity); + $data = $this->parseToDocument($model); $queryResult = $this->getCollection()->deleteOne( ['_id' => $data['_id']], @@ -213,7 +212,7 @@ public function delete($entity, array $options = []): bool if ($queryResult->isAcknowledged() && $queryResult->getDeletedCount() ) { - $this->fireEvent('deleted', $entity); + $this->fireEvent('deleted', $model); return true; } @@ -222,7 +221,7 @@ public function delete($entity, array $options = []): bool } /** - * Retrieve a database cursor that will return $this->schema->entityClass + * Retrieve a database cursor that will return $this->schema->modelClass * objects that upon iteration. * * @param mixed $query mongoDB query to retrieve documents @@ -243,7 +242,7 @@ public function where($query = [], array $projection = []): CursorInterface /** * Retrieve a database cursor that will return all documents as - * $this->schema->entityClass objects upon iteration. + * $this->schema->modelClass objects upon iteration. */ public function all(): CursorInterface { @@ -251,13 +250,13 @@ public function all(): CursorInterface } /** - * Retrieve one $this->schema->entityClass objects that matches the given + * Retrieve one $this->schema->modelClass objects that matches the given * query. * * @param mixed $query mongoDB query to retrieve the document * @param array $projection fields to project in Mongo query * - * @return static|null First document matching query as an $this->schema->entityClass object + * @return static|null First document matching query as an $this->schema->modelClass object */ public function first($query = [], array $projection = []) { @@ -280,7 +279,7 @@ public function first($query = [], array $projection = []) } /** - * Retrieve one $this->schema->entityClass objects that matches the given + * Retrieve one $this->schema->modelClass objects that matches the given * query. If no document was found, throws ModelNotFoundException. * * @param mixed $query mongoDB query to retrieve the document @@ -288,7 +287,7 @@ public function first($query = [], array $projection = []) * * @throws ModelNotFoundException If no document was found * - * @return static|null First document matching query as an $this->schema->entityClass object + * @return static|null First document matching query as an $this->schema->modelClass object */ public function firstOrFail($query = [], array $projection = []) { @@ -296,7 +295,7 @@ public function firstOrFail($query = [], array $projection = []) return $result; } - throw (new ModelNotFoundException())->setModel($this->schema->entityClass); + throw (new ModelNotFoundException())->setModel($this->schema->modelClass); } /** @@ -308,9 +307,9 @@ public function getSchema(): AbstractSchema } /** - * Set a Schema object that describes an Entity in MongoDB. + * Set a Schema object that describes an Model in MongoDB. */ - public function setSchema(AbstractSchema $schema) + public function setSchema(AbstractSchema $schema): void { $this->schema = $schema; } @@ -318,16 +317,16 @@ public function setSchema(AbstractSchema $schema) /** * Parses an object with SchemaMapper and the given Schema. * - * @param mixed $entity the object to be parsed + * @param ModelInterface $model the object to be parsed */ - public function parseToDocument($entity): array + public function parseToDocument(ModelInterface $model): array { $schemaMapper = $this->getSchemaMapper(); - $parsedDocument = $schemaMapper->map($entity); + $parsedDocument = $schemaMapper->map($model); - if (is_object($entity)) { + if (is_object($model)) { foreach ($parsedDocument as $field => $value) { - $entity->$field = $value; + $model->$field = $value; } } @@ -336,10 +335,8 @@ public function parseToDocument($entity): array /** * Returns a SchemaMapper with the $schema or $schemaClass instance. - * - * @return SchemaMapper */ - protected function getSchemaMapper() + protected function getSchemaMapper(): SchemaMapper { if (!$this->schema) { $this->schema = Ioc::make($this->schemaClass); @@ -417,14 +414,12 @@ protected function prepareArrayFieldOfQuery(array $value): array } /** - * Retrieves an EntityAssembler instance. - * - * @return EntityAssembler + * Retrieves an ModelAssembler instance. */ - protected function getAssembler() + protected function getAssembler(): ModelAssembler { if (!$this->assembler) { - $this->assembler = Ioc::make(EntityAssembler::class); + $this->assembler = Ioc::make(ModelAssembler::class); } return $this->assembler; @@ -433,19 +428,19 @@ protected function getAssembler() /** * Triggers an event. May return if that event had success. * - * @param string $event identification of the event - * @param mixed $entity event payload - * @param bool $halt true if the return of the event handler will be used in a conditional + * @param string $event identification of the event + * @param mixed $model event payload + * @param bool $halt true if the return of the event handler will be used in a conditional * * @return mixed event handler return */ - protected function fireEvent(string $event, $entity, bool $halt = false) + protected function fireEvent(string $event, ModelInterface $model, bool $halt = false) { - $event = "mongolid.{$event}: ".get_class($entity); + $event = "mongolid.{$event}: ".get_class($model); $this->eventService ?: $this->eventService = Ioc::make(EventTriggerService::class); - return $this->eventService->fire($event, $entity, $halt); + return $this->eventService->fire($event, $model, $halt); } /** @@ -520,7 +515,7 @@ protected function prepareProjection(array $fields) * * @see https://github.com/bjori/mongo-php-transistor/blob/70f5af00795d67f4d5a8c397e831435814df9937/src/Transistor.php#L108 */ - private function calculateChanges(array &$changes, array $newData, array $oldData, string $keyfix = '') + private function calculateChanges(array &$changes, array $newData, array $oldData, string $keyfix = ''): void { foreach ($newData as $k => $v) { if (!isset($oldData[$k])) { // new field @@ -552,31 +547,23 @@ private function calculateChanges(array &$changes, array $newData, array $oldDat * * @return array */ - private function mergeOptions(array $defaultOptions = [], array $toMergeOptions = []) + private function mergeOptions(array $defaultOptions = [], array $toMergeOptions = []): array { return array_merge($defaultOptions, $toMergeOptions); } /** * Perform actions on object before firing the after event. - * - * @param mixed $entity */ - private function afterSuccess($entity) + private function afterSuccess(ModelInterface $model): void { - if ($entity instanceof HasAttributesInterface) { - $entity->syncOriginalDocumentAttributes(); - } + $model->syncOriginalDocumentAttributes(); } - private function getUpdateData($entity, array $data) + private function getUpdateData($model, array $data): array { - if (!$entity instanceof HasAttributesInterface) { - return ['$set' => $data]; - } - $changes = []; - $this->calculateChanges($changes, $data, $entity->getOriginalDocumentAttributes()); + $this->calculateChanges($changes, $data, $model->getOriginalDocumentAttributes()); return $changes; } diff --git a/src/DataMapper/BulkWrite.php b/src/Query/BulkWrite.php similarity index 86% rename from src/DataMapper/BulkWrite.php rename to src/Query/BulkWrite.php index 33e8efce..8ac7a0e6 100644 --- a/src/DataMapper/BulkWrite.php +++ b/src/Query/BulkWrite.php @@ -1,13 +1,13 @@ setBulkWrite(new MongoBulkWrite(['ordered' => false])); - $this->schema = $entity->getSchema(); + $this->schema = $model->getSchema(); } /** @@ -92,7 +90,7 @@ public function updateOne( /** * Execute the BulkWrite, using connection. - * The collection is inferred from entity's collection name. + * The collection is inferred from model's collection name. * * @param int $writeConcern * diff --git a/src/DataMapper/EntityAssembler.php b/src/Query/ModelAssembler.php similarity index 60% rename from src/DataMapper/EntityAssembler.php rename to src/Query/ModelAssembler.php index 0a3cb326..890be429 100644 --- a/src/DataMapper/EntityAssembler.php +++ b/src/Query/ModelAssembler.php @@ -1,36 +1,34 @@ entityClass; - $model = Ioc::make($entityClass); + $modelClass = $schema->modelClass; + $model = Ioc::make($modelClass); foreach ($document as $field => $value) { $fieldType = $schema->fields[$field] ?? null; @@ -42,50 +40,48 @@ public function assemble($document, AbstractSchema $schema) $model->$field = $value; } - $entity = $this->morphingTime($model); + $model = $this->morphingTime($model); - return $this->prepareOriginalAttributes($entity); + return $this->prepareOriginalAttributes($model); } /** - * Returns the return of polymorph method of the given entity if available. + * Returns the return of polymorph method of the given model if available. * * @see \Mongolid\Model\PolymorphableInterface::polymorph * @see https://i.ytimg.com/vi/TFGN9kAjdis/maxresdefault.jpg * - * @param mixed $entity the entity that may or may not have a polymorph method + * @param mixed $model the model that may or may not have a polymorph method * - * @return mixed the result of $entity->polymorph or the $entity itself + * @return mixed the result of $model->polymorph or the $model itself */ - protected function morphingTime($entity) + protected function morphingTime(ModelInterface $model): ModelInterface { - if ($entity instanceof PolymorphableInterface) { - return $entity->polymorph(); + if ($model instanceof PolymorphableInterface) { + return $model->polymorph(); } - return $entity; + return $model; } /** - * Stores original attributes from Entity if needed. + * Stores original attributes from Model if needed. * - * @param mixed $entity the entity that may have the attributes stored + * @param mixed $model the model that may have the attributes stored * - * @return mixed the entity with original attributes + * @return mixed the model with original attributes */ - protected function prepareOriginalAttributes($entity) + protected function prepareOriginalAttributes(ModelInterface $model) { - if ($entity instanceof HasAttributesInterface) { - $entity->syncOriginalDocumentAttributes(); - } + $model->syncOriginalDocumentAttributes(); - return $entity; + return $model; } /** * Assembly multiple documents for the given $schemaClass recursively. * - * @param mixed $value a value of an embedded field containing entity data to be assembled + * @param mixed $value a value of an embedded field containing model data to be assembled * @param string $schemaClass the schemaClass to be used when assembling the entities within $value * * @return mixed diff --git a/src/DataMapper/SchemaMapper.php b/src/Query/SchemaMapper.php similarity index 98% rename from src/DataMapper/SchemaMapper.php rename to src/Query/SchemaMapper.php index f143a661..d63aba55 100644 --- a/src/DataMapper/SchemaMapper.php +++ b/src/Query/SchemaMapper.php @@ -1,5 +1,5 @@ getNextValue($this->collection ?: $this->entityClass); + ->getNextValue($this->collection ?: $this->modelClass); } /** diff --git a/src/Schema/HasSchemaInterface.php b/src/Schema/HasSchemaInterface.php deleted file mode 100644 index 1b291dd7..00000000 --- a/src/Schema/HasSchemaInterface.php +++ /dev/null @@ -1,17 +0,0 @@ -instance(EventTriggerService::class, m::type(EventTriggerService::class)) ->andReturnUsing(function ($class, $eventService) use ($test, $eventTrigger) { $test->assertEquals(EventTriggerService::class, $class); - $test->assertAttributeEquals($eventTrigger, 'dispatcher', $eventService); + $test->assertAttributeEquals($eventTrigger, 'builder', $eventService); }); // Assert @@ -64,7 +64,7 @@ public function testShouldRegisterSchema() // Arrange $manager = new Manager(); $schema = m::mock(AbstractSchema::class); - $schema->entityClass = 'Bacon'; + $schema->modelClass = 'Bacon'; // Assert $manager->registerSchema($schema); @@ -80,16 +80,16 @@ public function testShouldGetDataMapperForEntitiesWithRegisteredSchemas() // Arrange $manager = new Manager(); $schema = m::mock(AbstractSchema::class); - $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)->makePartial()); + $builder = $this->instance(Builder::class, m::mock(Builder::class)->makePartial()); - $schema->entityClass = 'Bacon'; + $schema->modelClass = 'Bacon'; // Act $manager->registerSchema($schema); - $result = $manager->getMapper('Bacon'); + $result = $manager->getBuilder('Bacon'); // Assert - $this->assertEquals($dataMapper, $result); + $this->assertEquals($builder, $result); $this->assertAttributeEquals($schema, 'schema', $result); } @@ -99,7 +99,7 @@ public function testShouldNotGetDataMapperForUnknownEntities() $manager = new Manager(); // Assert - $result = $manager->getMapper('Unknow'); + $result = $manager->getBuilder('Unknown'); $this->assertNull($result); } diff --git a/tests/Unit/Cursor/CursorTest.php b/tests/Unit/Cursor/CursorTest.php index a37bc469..3bef0def 100644 --- a/tests/Unit/Cursor/CursorTest.php +++ b/tests/Unit/Cursor/CursorTest.php @@ -10,12 +10,11 @@ use MongoDB\Driver\ReadPreference; use MongoDB\Model\CachingIterator; use Mongolid\Connection\Connection; -use Mongolid\Model\AbstractActiveRecord; +use Mongolid\Model\AbstractModel; use Mongolid\Schema\AbstractSchema; use Mongolid\Schema\DynamicSchema; use Mongolid\TestCase; use Serializable; -use stdClass; use Traversable; class CursorTest extends TestCase @@ -166,45 +165,53 @@ public function testShouldGetCurrent() { // Arrange $collection = m::mock(Collection::class); + $object = new class extends AbstractModel + { + }; $driverCursor = new CachingIterator(new ArrayObject([['name' => 'John Doe']])); - $cursor = $this->getCursor(null, $collection, 'find', [[]], $driverCursor); + $cursor = $this->getCursor($object->getSchema(), $collection, 'find', [[]], $driverCursor); // Act - $entity = $cursor->current(); + $model = $cursor->current(); // Assert - $this->assertInstanceOf(stdClass::class, $entity); - $this->assertAttributeEquals('John Doe', 'name', $entity); + $this->assertInstanceOf(get_class($object), $model); + $this->assertSame('John Doe', $model->name); } - public function testShouldGetCurrentUsingActiveRecordClasses() + public function testShouldGetCurrentUsingModelClasses() { // Arrange $collection = m::mock(Collection::class); - $entity = m::mock(AbstractActiveRecord::class.'[]'); - $entity->name = 'John Doe'; - $driverCursor = new ArrayIterator([$entity]); + $object = new class extends AbstractModel + { + }; + $object->name = 'John Doe'; + $driverCursor = new ArrayIterator([$object]); $cursor = $this->getCursor(null, $collection, 'find', [[]], $driverCursor); // Assert - $entity = $cursor->current(); - $this->assertInstanceOf(AbstractActiveRecord::class, $entity); - $this->assertEquals('John Doe', $entity->name); + $model = $cursor->current(); + $this->assertInstanceOf(AbstractModel::class, $model); + $this->assertEquals('John Doe', $model->name); } public function testShouldGetFirst() { // Arrange $collection = m::mock(Collection::class); + $object = new class extends AbstractModel + { + }; $driverCursor = new CachingIterator(new ArrayObject([['name' => 'John Doe']])); - $cursor = $this->getCursor(null, $collection, 'find', [[]], $driverCursor); + $cursor = $this->getCursor($object->getSchema(), $collection, 'find', [[]], $driverCursor); // Act - $entity = $cursor->first(); + $model = $cursor->first(); // Assert - $this->assertInstanceOf(stdClass::class, $entity); - $this->assertAttributeEquals('John Doe', 'name', $entity); + $this->assertInstanceOf(get_class($object), $model); + $this->assertSame('John Doe', $model->name); } public function testShouldGetFirstWhenEmpty() @@ -323,40 +330,33 @@ public function testShouldReturnAllResults() { // Arrange $collection = m::mock(Collection::class); - $driverCursor = m::mock(CachingIterator::class); - $cursor = $this->getCursor(null, $collection, 'find', [[]], $driverCursor); - - // Act - $driverCursor->expects() - ->rewind(); - - $driverCursor->expects() - ->valid() - ->times(3) - ->andReturn(true, true, false); + $driverCursor = new CachingIterator( + new ArrayObject( + [ + ['name' => 'bob', 'occupation' => 'coder'], + ['name' => 'jef', 'occupation' => 'tester'], + ] + ) + ); + $object = new class extends AbstractModel + { + }; + $cursor = $this->getCursor($object->getSchema(), $collection, 'find', [[]], $driverCursor); - $driverCursor->expects('next') - ->twice() - ->andReturn(true, false); + // Actions + $result = $cursor->all(); - $driverCursor->expects() - ->current() - ->twice() - ->andReturn( - ['name' => 'bob', 'occupation' => 'coder'], - ['name' => 'jef', 'occupation' => 'tester'] - ); + // Assertions + $this->assertCount(2, $result); + $this->assertContainsOnlyInstancesOf(get_class($object), $result); - $result = $cursor->all(); + $firstModel = $result[0]; + $this->assertSame('bob', $firstModel->name); + $this->assertSame('coder', $firstModel->occupation); - // Assert - $this->assertEquals( - [ - (object) ['name' => 'bob', 'occupation' => 'coder'], - (object) ['name' => 'jef', 'occupation' => 'tester'], - ], - $result - ); + $nextModel = $result[1]; + $this->assertSame('jef', $nextModel->name); + $this->assertSame('tester', $nextModel->occupation); } public function testShouldReturnResultsToArray() @@ -425,14 +425,14 @@ public function testShouldSerializeAnActiveCursor() } protected function getCursor( - $entitySchema = null, + $modelSchema = null, $collection = null, $command = 'find', $params = [[]], $driverCursor = null ) { - if (!$entitySchema) { - $entitySchema = m::mock(AbstractSchema::class.'[]'); + if (!$modelSchema) { + $modelSchema = m::mock(AbstractSchema::class.'[]'); } if (!$collection) { @@ -440,12 +440,12 @@ protected function getCursor( } if (!$driverCursor) { - return new Cursor($entitySchema, $collection, $command, $params); + return new Cursor($modelSchema, $collection, $command, $params); } $mock = m::mock( Cursor::class.'[getCursor]', - [$entitySchema, $collection, $command, $params] + [$modelSchema, $collection, $command, $params] ); $mock->shouldAllowMockingProtectedMethods(); diff --git a/tests/Unit/Cursor/EmbeddedCursorTest.php b/tests/Unit/Cursor/EmbeddedCursorTest.php index 346191bf..207df334 100644 --- a/tests/Unit/Cursor/EmbeddedCursorTest.php +++ b/tests/Unit/Cursor/EmbeddedCursorTest.php @@ -1,7 +1,7 @@ 'A'], ['name' => 'B'], ['name' => 'C'], ]; - $cursor = $this->getCursor(stdClass::class, $items); + $cursor = $this->getCursor($class, $items); $this->setProtected($cursor, 'position', 1); // Assert - $entity = $cursor->current(); - $this->assertInstanceOf(stdClass::class, $entity); - $this->assertAttributeEquals('B', 'name', $entity); + $model = $cursor->current(); + $this->assertInstanceOf($class, $model); + $this->assertSame('B', $model->name); } public function testShouldNotGetCurrentWhenCursorIsInvalid() @@ -133,11 +137,11 @@ public function testShouldNotGetCurrentWhenCursorIsInvalid() $this->setProtected($cursor, 'position', 1); // Assert - $entity = $cursor->current(); - $this->assertNull($entity); + $model = $cursor->current(); + $this->assertNull($model); } - public function testShouldGetCurrentUsingEntityClass() + public function testShouldGetCurrentUsingModelClass() { // Arrange $object = new stdClass(); @@ -148,30 +152,34 @@ public function testShouldGetCurrentUsingEntityClass() $this->setProtected($cursor, 'position', 0); // Assert - $entity = $cursor->current(); - $this->assertInstanceOf(stdClass::class, $entity); - $this->assertAttributeEquals('A', 'name', $entity); + $model = $cursor->current(); + $this->assertInstanceOf(stdClass::class, $model); + $this->assertAttributeEquals('A', 'name', $model); } - public function testShouldGetCurrentUsingEntityClassAndMorphinIt() + public function testShouldGetCurrentUsingModelClassAndMorphingIt() { // Arrange - $object = new class() extends AbstractActiveRecord implements PolymorphableInterface { + $object = new class() extends AbstractModel implements PolymorphableInterface + { public function polymorph() { - return 'Bacon'; + return $this; } }; + $object->name = 'John'; + $object->syncOriginalDocumentAttributes(); $class = get_class($object); $items = [$object->getDocumentAttributes()]; $cursor = $this->getCursor($class, $items); - $this->setProtected($cursor, 'position', 0); + // Actions + $model = $cursor->current(); - // Assert - $entity = $cursor->current(); - $this->assertEquals('Bacon', $entity); + // Assertions + $this->assertEquals($object, $model); + $this->assertSame('John', $model->name); } public function testShouldGetFirst() @@ -182,14 +190,18 @@ public function testShouldGetFirst() ['name' => 'B'], ['name' => 'C'], ]; - $cursor = $this->getCursor(stdClass::class, $items); + $object = new class extends AbstractModel + { + }; + $class = get_class($object); + $cursor = $this->getCursor($class, $items); $this->setProtected($cursor, 'position', 1); // Assert - $entity = $cursor->first(); - $this->assertInstanceOf(stdClass::class, $entity); - $this->assertAttributeEquals('A', 'name', $entity); + $model = $cursor->first(); + $this->assertInstanceOf($class, $model); + $this->assertSame('A', $model->name); } public function testShouldGetAllItems() @@ -199,19 +211,22 @@ public function testShouldGetAllItems() ['name' => 'A'], ['name' => 'B'], ]; - $cursor = $this->getCursor(stdClass::class, $items); - - $this->setProtected($cursor, 'position', 1); - $entityA = new stdClass(); - $entityA->name = 'A'; + $modelA = new class extends AbstractModel + { + }; + $modelA->name = 'A'; + $modelA->syncOriginalDocumentAttributes(); + $modelB = clone $modelA; + $modelB->name = 'B'; + $modelB->syncOriginalDocumentAttributes(); - $entityB = new stdClass(); - $entityB->name = 'B'; + $cursor = $this->getCursor(get_class($modelA), $items); + $this->setProtected($cursor, 'position', 1); $expected = [ - $entityA, - $entityB, + $modelA, + $modelB, ]; // Assert @@ -276,13 +291,6 @@ public function testShouldImplementValidMethodFromIterator() $this->assertFalse($cursor->valid()); } - protected function getCursor( - $entityClass = stdClass::class, - $items = [] - ) { - return new EmbeddedCursor($entityClass, $items); - } - public function getDocumentsToSort() { $age24 = (object) ['age' => 24]; @@ -366,4 +374,11 @@ public function getDocumentsToSort() ], ]; } + + protected function getCursor( + $modelClass = stdClass::class, + $items = [] + ) { + return new EmbeddedCursor($modelClass, $items); + } } diff --git a/tests/Unit/Event/EventTriggerServiceTest.php b/tests/Unit/Event/EventTriggerServiceTest.php index 3eff6cd4..85cc9cc3 100644 --- a/tests/Unit/Event/EventTriggerServiceTest.php +++ b/tests/Unit/Event/EventTriggerServiceTest.php @@ -6,37 +6,37 @@ class EventTriggerServiceTest extends TestCase { - public function testShouldSendTheEventsToTheExternalDispatcher() + public function testShouldSendTheEventsToTheExternalBuilder() { // Arrange - $dispatcher = m::mock(EventTriggerInterface::class); + $builder = m::mock(EventTriggerInterface::class); $service = new EventTriggerService(); // Act - $dispatcher->expects() + $builder->expects() ->fire('foobar', ['answer' => 23], true) ->andReturn(true); // Assertion - $service->registerEventDispatcher($dispatcher); + $service->registerEventBuilder($builder); $this->assertTrue( $service->fire('foobar', ['answer' => 23], true) ); } - public function testShouldReturnTrueIfThereIsNoExternalDispatcher() + public function testShouldReturnTrueIfThereIsNoExternalBuilder() { // Arrange - $dispatcher = m::mock(EventTriggerInterface::class); + $builder = m::mock(EventTriggerInterface::class); $service = new EventTriggerService(); // Act - $dispatcher->expects() + $builder->expects() ->fire() ->never(); // Assertion - /* without calling registerEventDispatcher */ + /* without calling registerEventBuilder */ $this->assertTrue( $service->fire('foobar', ['answer' => 23], true) ); diff --git a/tests/Unit/Model/AbstractActiveRecordTest.php b/tests/Unit/Model/AbstractModelTest.php similarity index 64% rename from tests/Unit/Model/AbstractActiveRecordTest.php rename to tests/Unit/Model/AbstractModelTest.php index a5aa48e6..3d772c5d 100644 --- a/tests/Unit/Model/AbstractActiveRecordTest.php +++ b/tests/Unit/Model/AbstractModelTest.php @@ -4,19 +4,19 @@ use Mockery as m; use MongoDB\Driver\WriteConcern; use Mongolid\Cursor\CursorInterface; -use Mongolid\DataMapper\DataMapper; -use Mongolid\Exception\NoCollectionNameException; +use Mongolid\Model\Exception\NoCollectionNameException; +use Mongolid\Query\Builder; use Mongolid\Schema\AbstractSchema; use Mongolid\Schema\DynamicSchema; use Mongolid\TestCase; use stdClass; -class AbstractActiveRecordTest extends TestCase +class AbstractModelTest extends TestCase { /** - * @var AbstractActiveRecord + * @var AbstractModel */ - protected $entity; + protected $model; /** * {@inheritdoc} @@ -24,7 +24,7 @@ class AbstractActiveRecordTest extends TestCase protected function setUp() { parent::setUp(); - $this->entity = new class() extends AbstractActiveRecord + $this->model = new class() extends AbstractModel { /** * {@inheritdoc} @@ -48,7 +48,7 @@ public function setFields($value) */ protected function tearDown() { - unset($this->entity); + unset($this->model); parent::tearDown(); } @@ -62,9 +62,9 @@ public function testShouldHaveCorrectPropertiesByDefault() 'updated_at' => 'updatedAtTimestamp', ], 'fields', - $this->entity + $this->model ); - $this->assertTrue($this->entity->dynamic); + $this->assertTrue($this->model->dynamic); } public function testShouldImplementModelTraits() @@ -72,100 +72,100 @@ public function testShouldImplementModelTraits() // Assertions $this->assertSame( [HasAttributesTrait::class, HasRelationsTrait::class], - array_keys(class_uses(AbstractActiveRecord::class)) + array_keys(class_uses(AbstractModel::class)) ); } public function testShouldSave() { // Set - $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); + $builder = $this->instance(Builder::class, m::mock(Builder::class)); // Actions - $dataMapper->expects() + $builder->expects() ->setSchema(m::type(DynamicSchema::class)); - $dataMapper->expects() - ->save($this->entity, ['writeConcern' => new WriteConcern(1)]) + $builder->expects() + ->save($this->model, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); // Assertions - $this->assertTrue($this->entity->save()); + $this->assertTrue($this->model->save()); } public function testShouldInsert() { // Set - $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); + $builder = $this->instance(Builder::class, m::mock(Builder::class)); // Actions - $dataMapper->expects() + $builder->expects() ->setSchema(m::type(DynamicSchema::class)); - $dataMapper->expects() - ->insert($this->entity, ['writeConcern' => new WriteConcern(1)]) + $builder->expects() + ->insert($this->model, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); // Assertions - $this->assertTrue($this->entity->insert()); + $this->assertTrue($this->model->insert()); } public function testShouldUpdate() { // Set - $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); + $builder = $this->instance(Builder::class, m::mock(Builder::class)); // Actions - $dataMapper->expects() + $builder->expects() ->setSchema(m::type(DynamicSchema::class)); - $dataMapper->expects() - ->update($this->entity, ['writeConcern' => new WriteConcern(1)]) + $builder->expects() + ->update($this->model, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); // Assertions - $this->assertTrue($this->entity->update()); + $this->assertTrue($this->model->update()); } public function testShouldDelete() { // Set - $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); + $builder = $this->instance(Builder::class, m::mock(Builder::class)); // Actions - $dataMapper->expects() + $builder->expects() ->setSchema(m::type(DynamicSchema::class)); - $dataMapper->expects() - ->delete($this->entity, ['writeConcern' => new WriteConcern(1)]) + $builder->expects() + ->delete($this->model, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); // Assertions - $this->assertTrue($this->entity->delete()); + $this->assertTrue($this->model->delete()); } public function testSaveShouldReturnFalseIfCollectionIsNull() { - $this->entity->unsetCollection(); - $this->assertFalse($this->entity->save()); + $this->model->unsetCollection(); + $this->assertFalse($this->model->save()); } public function testUpdateShouldReturnFalseIfCollectionIsNull() { - $this->entity->unsetCollection(); - $this->assertFalse($this->entity->update()); + $this->model->unsetCollection(); + $this->assertFalse($this->model->update()); } public function testInsertShouldReturnFalseIfCollectionIsNull() { - $this->entity->unsetCollection(); - $this->assertFalse($this->entity->insert()); + $this->model->unsetCollection(); + $this->assertFalse($this->model->insert()); } public function testDeleteShouldReturnFalseIfCollectionIsNull() { - $this->entity->unsetCollection(); - $this->assertFalse($this->entity->delete()); + $this->model->unsetCollection(); + $this->assertFalse($this->model->delete()); } public function testShouldGetWithWhereQuery() @@ -173,39 +173,39 @@ public function testShouldGetWithWhereQuery() // Set $query = ['foo' => 'bar']; $projection = ['some', 'fields']; - $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); + $builder = $this->instance(Builder::class, m::mock(Builder::class)); $cursor = m::mock(CursorInterface::class); // Actions - $dataMapper->expects() + $builder->expects() ->setSchema(m::type(DynamicSchema::class)); - $dataMapper->expects() + $builder->expects() ->where($query, $projection) ->andReturn($cursor); // Assertions - $this->assertSame($cursor, $this->entity->where($query, $projection)); + $this->assertSame($cursor, $this->model->where($query, $projection)); } public function testShouldGetAll() { // Set - $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); + $builder = $this->instance(Builder::class, m::mock(Builder::class)); $cursor = m::mock(CursorInterface::class); // Actions - $dataMapper->expects() + $builder->expects() ->setSchema(m::type(DynamicSchema::class)); - $dataMapper->expects() + $builder->expects() ->all() ->andReturn($cursor); // Assertions - $this->assertSame($cursor, $this->entity->all()); + $this->assertSame($cursor, $this->model->all()); } public function testShouldGetFirstWithQuery() @@ -213,85 +213,85 @@ public function testShouldGetFirstWithQuery() // Set $query = ['foo' => 'bar']; $projection = ['some', 'fields']; - $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); + $builder = $this->instance(Builder::class, m::mock(Builder::class)); // Actions - $dataMapper->expects() + $builder->expects() ->setSchema(m::type(DynamicSchema::class)); - $dataMapper->expects() + $builder->expects() ->first($query, $projection) - ->andReturn($this->entity); + ->andReturn($this->model); // Assertions - $this->assertSame($this->entity, $this->entity->first($query, $projection)); + $this->assertSame($this->model, $this->model->first($query, $projection)); } public function testShouldGetFirstOrFail() { // Set - $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); + $builder = $this->instance(Builder::class, m::mock(Builder::class)); $query = ['foo' => 'bar']; $projection = ['some', 'fields']; // Actions - $dataMapper->expects() + $builder->expects() ->setSchema(m::type(DynamicSchema::class)); - $dataMapper->expects() + $builder->expects() ->firstOrFail($query, $projection) - ->andReturn($this->entity); + ->andReturn($this->model); // Assertions - $this->assertSame($this->entity, $this->entity->firstOrFail($query, $projection)); + $this->assertSame($this->model, $this->model->firstOrFail($query, $projection)); } public function testShouldGetFirstOrNewAndReturnExistingModel() { // Set - $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); + $builder = $this->instance(Builder::class, m::mock(Builder::class)); $id = 123; // Actions - $dataMapper->expects() + $builder->expects() ->setSchema(m::type(DynamicSchema::class)); - $dataMapper->expects() + $builder->expects() ->first($id) - ->andReturn($this->entity); + ->andReturn($this->model); // Assertions - $this->assertSame($this->entity, $this->entity->firstOrNew($id)); + $this->assertSame($this->model, $this->model->firstOrNew($id)); } public function testShouldGetFirstOrNewAndReturnNewModel() { // Set - $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)); + $builder = $this->instance(Builder::class, m::mock(Builder::class)); $id = 123; // Actions - $dataMapper->expects() + $builder->expects() ->setSchema(m::type(DynamicSchema::class)); - $dataMapper->expects() + $builder->expects() ->first($id) ->andReturn(null); // Assertions - $this->assertNotEquals($this->entity, $this->entity->firstOrNew($id)); + $this->assertNotEquals($this->model, $this->model->firstOrNew($id)); } public function testShouldGetSchemaIfFieldsIsTheClassName() { // Set - $this->entity->setFields('MySchemaClass'); + $this->model->setFields('MySchemaClass'); $schema = $this->instance('MySchemaClass', m::mock(AbstractSchema::class)); // Assertions $this->assertSame( $schema, - $this->entity->getSchema() + $this->model->getSchema() ); } @@ -299,82 +299,82 @@ public function testShouldGetSchemaIfFieldsDescribesSchemaFields() { // Set $fields = ['name' => 'string', 'age' => 'int']; - $this->entity->setFields($fields); + $this->model->setFields($fields); // Assertions - $result = $this->entity->getSchema(); + $result = $this->model->getSchema(); $this->assertInstanceOf(AbstractSchema::class, $result); $this->assertSame($fields, $result->fields); - $this->assertSame($this->entity->dynamic, $result->dynamic); - $this->assertSame($this->entity->getCollectionName(), $result->collection); - $this->assertSame(get_class($this->entity), $result->entityClass); + $this->assertSame($this->model->dynamic, $result->dynamic); + $this->assertSame($this->model->getCollectionName(), $result->collection); + $this->assertSame(get_class($this->model), $result->modelClass); } public function testShouldGetDataMapper() { // Set - $entity = m::mock(AbstractActiveRecord::class.'[getSchema]'); + $model = m::mock(AbstractModel::class.'[getSchema]'); $schema = m::mock(AbstractSchema::class.'[]'); // Actions - $entity->shouldAllowMockingProtectedMethods(); + $model->shouldAllowMockingProtectedMethods(); - $entity->expects() + $model->expects() ->getSchema() ->andReturn($schema); // Assertions - $result = $this->callProtected($entity, 'getDataMapper'); - $this->assertInstanceOf(DataMapper::class, $result); + $result = $this->callProtected($model, 'getBuilder'); + $this->assertInstanceOf(Builder::class, $result); $this->assertSame($schema, $result->getSchema()); } public function testShouldRaiseExceptionWhenHasNoCollectionAndTryToCallAllFunction() { - $entity = new class() extends AbstractActiveRecord + $model = new class() extends AbstractModel { }; $this->expectException(NoCollectionNameException::class); - $entity->all(); + $model->all(); } public function testShouldRaiseExceptionWhenHasNoCollectionAndTryToCallFirstFunction() { - $entity = new class() extends AbstractActiveRecord + $model = new class() extends AbstractModel { }; $this->expectException(NoCollectionNameException::class); - $entity->first(); + $model->first(); } public function testShouldRaiseExceptionWhenHasNoCollectionAndTryToCallWhereFunction() { - $entity = new class() extends AbstractActiveRecord + $model = new class() extends AbstractModel { }; $this->expectException(NoCollectionNameException::class); - $entity->where(); + $model->where(); } public function testShouldGetCollectionName() { - $this->assertSame('mongolid', $this->entity->getCollectionName()); + $this->assertSame('mongolid', $this->model->getCollectionName()); } - public function testShouldGetSetWriteConcernInActiveRecordClass() + public function testShouldGetSetWriteConcernInModelClass() { - $this->assertSame(1, $this->entity->getWriteConcern()); - $this->entity->setWriteConcern(0); - $this->assertSame(0, $this->entity->getWriteConcern()); + $this->assertSame(1, $this->model->getWriteConcern()); + $this->model->setWriteConcern(0); + $this->assertSame(0, $this->model->getWriteConcern()); } public function testShouldHaveDynamicSetters() { // Set - $model = new class() extends AbstractActiveRecord + $model = new class() extends AbstractModel { }; @@ -398,7 +398,7 @@ public function testShouldHaveDynamicGetters() { // Set $child = new stdClass(); - $model = new class() extends AbstractActiveRecord + $model = new class() extends AbstractModel { }; $model->fill( @@ -419,7 +419,7 @@ public function testShouldHaveDynamicGetters() public function testShouldCheckIfAttributeIsSet() { // Set - $model = new class() extends AbstractActiveRecord + $model = new class() extends AbstractModel { }; $model->fill(['name' => 'John', 'ignored' => null]); @@ -433,7 +433,7 @@ public function testShouldCheckIfAttributeIsSet() public function testShouldCheckIfMutatedAttributeIsSet() { // Set - $model = new class() extends AbstractActiveRecord + $model = new class() extends AbstractModel { /** * {@inheritdoc} @@ -454,7 +454,7 @@ public function getNameDocumentAttribute() public function testShouldUnsetAttributes() { // Set - $model = new class() extends AbstractActiveRecord + $model = new class() extends AbstractModel { }; $model->fill( @@ -475,7 +475,7 @@ public function testShouldUnsetAttributes() public function testShouldGetAttributeFromMutator() { // Set - $model = new class() extends AbstractActiveRecord + $model = new class() extends AbstractModel { /** * {@inheritdoc} @@ -499,7 +499,7 @@ public function getShortNameDocumentAttribute() public function testShouldIgnoreMutators() { // Set - $model = new class() extends AbstractActiveRecord + $model = new class() extends AbstractModel { public function getShortNameDocumentAttribute() { @@ -521,7 +521,7 @@ public function setShortNameDocumentAttribute($value) public function testShouldSetAttributeFromMutator() { // Arrange - $model = new class() extends AbstractActiveRecord + $model = new class() extends AbstractModel { /** * {@inheritdoc} diff --git a/tests/Unit/Model/DocumentEmbedderTest.php b/tests/Unit/Model/DocumentEmbedderTest.php index 205b12e8..2ecb60e4 100644 --- a/tests/Unit/Model/DocumentEmbedderTest.php +++ b/tests/Unit/Model/DocumentEmbedderTest.php @@ -4,8 +4,6 @@ use Mockery as m; use Mockery\Matcher\Any; use MongoDB\BSON\ObjectId; -use Mongolid\Connection\Connection; -use Mongolid\DataMapper\DataMapper; use Mongolid\TestCase; class DocumentEmbedderTest extends TestCase @@ -13,25 +11,26 @@ class DocumentEmbedderTest extends TestCase /** * @dataProvider getEmbedOptions */ - public function testShouldEmbed($originalField, $entityFields, $method, $expectation) + public function testShouldEmbed($originalField, $modelFields, $method, $expectation) { // Arrange - $connection = new Connection(); - $parent = new DataMapper($connection); + $parent = new class extends AbstractModel + { + }; $parent->foo = $originalField; - if (is_array($entityFields)) { - $entity = new class extends AbstractActiveRecord + if (is_array($modelFields)) { + $model = new class extends AbstractModel { }; - $entity->fill($entityFields); + $model->fill($modelFields); } else { - $entity = $entityFields; + $model = $modelFields; } $embedder = new DocumentEmbedder(); // Assert - $embedder->$method($parent, 'foo', $entity); + $embedder->$method($parent, 'foo', $model); $result = $parent->foo; foreach ($expectation as $index => $expectedDoc) { @@ -59,7 +58,7 @@ public function getEmbedOptions() // ------------------------------ 'embedding object without _id' => [ 'originalField' => null, - 'entity' => [ + 'model' => [ 'name' => 'John Doe', ], 'method' => 'embed', @@ -71,7 +70,7 @@ public function getEmbedOptions() // ------------------------------ 'embedding object with _id' => [ 'originalField' => null, - 'entity' => [ + 'model' => [ '_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'John Doe', ], @@ -89,7 +88,7 @@ public function getEmbedOptions() 'name' => 'Bob', ], ], - 'entity' => [ + 'model' => [ '_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'John Doe', ], @@ -102,7 +101,7 @@ public function getEmbedOptions() // ------------------------------ 'attaching object with _id' => [ 'originalField' => null, - 'entity' => [ + 'model' => [ '_id' => new ObjectId('507f191e810c19729de860ea'), 'name' => 'John Doe', ], @@ -118,7 +117,7 @@ public function getEmbedOptions() new ObjectId('507f191e810c19729de860ea'), new ObjectId('507f191e810c19729de86011'), ], - 'entity' => [ + 'model' => [ '_id' => new ObjectId('507f191e810c19729de860ea'), 'name' => 'John Doe', ], @@ -132,7 +131,7 @@ public function getEmbedOptions() // ------------------------------ 'attaching object without _id' => [ 'originalField' => null, - 'entity' => [ + 'model' => [ 'name' => 'John Doe', ], 'method' => 'attach', @@ -145,7 +144,7 @@ public function getEmbedOptions() (new ObjectId('507f191e810c19729de860ea')), (new ObjectId('507f191e810c19729de86011')), ], - 'entity' => [ + 'model' => [ '_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'John Doe', ], @@ -158,7 +157,7 @@ public function getEmbedOptions() // ------------------------------ 'attaching an _id' => [ 'originalField' => null, - 'entity' => new ObjectId('507f191e810c19729de860ea'), + 'model' => new ObjectId('507f191e810c19729de860ea'), 'method' => 'attach', 'expectation' => [ (new ObjectId('507f191e810c19729de860ea')), @@ -171,7 +170,7 @@ public function getEmbedOptions() 6, 7, ], - 'entity' => 6, + 'model' => 6, 'method' => 'detach', 'expectation' => [ 7, diff --git a/tests/Unit/Exception/ModelNotFoundExceptionTest.php b/tests/Unit/Model/Exception/ModelNotFoundExceptionTest.php similarity index 93% rename from tests/Unit/Exception/ModelNotFoundExceptionTest.php rename to tests/Unit/Model/Exception/ModelNotFoundExceptionTest.php index e3666555..e4b0963c 100644 --- a/tests/Unit/Exception/ModelNotFoundExceptionTest.php +++ b/tests/Unit/Model/Exception/ModelNotFoundExceptionTest.php @@ -1,5 +1,5 @@ fill(['name' => 'John', 'ignored' => null]); @@ -295,7 +295,7 @@ public function testShouldCheckIfAttributeIsSet() public function testShouldCheckIfMutatedAttributeIsSet() { // Set - $model = new class() extends AbstractActiveRecord + $model = new class() extends AbstractModel { /** * {@inheritdoc} diff --git a/tests/Unit/Model/HasRelationsTraitTest.php b/tests/Unit/Model/HasRelationsTraitTest.php index 2740bda2..7670470e 100644 --- a/tests/Unit/Model/HasRelationsTraitTest.php +++ b/tests/Unit/Model/HasRelationsTraitTest.php @@ -4,7 +4,7 @@ use Mockery as m; use MongoDB\BSON\ObjectId; use Mongolid\Cursor\EmbeddedCursor; -use Mongolid\DataMapper\DataMapper; +use Mongolid\Query\Builder; use Mongolid\TestCase; class HasRelationsTraitTest extends TestCase @@ -18,12 +18,12 @@ public function testShouldReferenceOne($fieldValue, $expectedQuery) $model = new UserStub(); $model->refOne = $fieldValue; - $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)->makePartial()); + $builder = $this->instance(Builder::class, m::mock(Builder::class)->makePartial()); $expectedQuery = $expectedQuery['referencesOne']; $expected = new RelatedStub(); // Expectations - $dataMapper->expects() + $builder->expects() ->first($expectedQuery, []) ->andReturn($expected); @@ -43,12 +43,12 @@ public function testShouldReferenceMany($fieldValue, $expectedQuery) $model = new UserStub(); $model->refMany = $fieldValue; - $dataMapper = $this->instance(DataMapper::class, m::mock(DataMapper::class)->makePartial()); + $builder = $this->instance(Builder::class, m::mock(Builder::class)->makePartial()); $expectedQuery = $expectedQuery['referencesMany']; $expected = new EmbeddedCursor(RelatedStub::class, []); // Expectations - $dataMapper->expects() + $builder->expects() ->where($expectedQuery, []) ->andReturn($expected); @@ -156,7 +156,7 @@ public function referenceScenarios() ], ], ], - 'ActiveRecord referenced with null' => [ + 'Model referenced with null' => [ 'fieldValue' => null, 'expectedQuery' => [ 'referencesOne' => ['_id' => null], @@ -187,7 +187,7 @@ public function embedsScenarios() } } -class UserStub extends AbstractActiveRecord +class UserStub extends AbstractModel { /** * {@inheritdoc} @@ -215,7 +215,7 @@ public function relationEmbedsMany() } } -class RelatedStub extends AbstractActiveRecord +class RelatedStub extends AbstractModel { /** * {@inheritdoc} diff --git a/tests/Unit/DataMapper/AbstractSchemaMapperTest.php b/tests/Unit/Query/AbstractSchemaMapperTest.php similarity index 99% rename from tests/Unit/DataMapper/AbstractSchemaMapperTest.php rename to tests/Unit/Query/AbstractSchemaMapperTest.php index 203841b6..e43b89a9 100644 --- a/tests/Unit/DataMapper/AbstractSchemaMapperTest.php +++ b/tests/Unit/Query/AbstractSchemaMapperTest.php @@ -1,5 +1,5 @@ assertAttributeEquals($connection, 'connection', $dataMapper); + $this->assertAttributeEquals($connection, 'connection', $builder); } /** * @dataProvider getWriteConcernVariations */ - public function testShouldSave($entity, $writeConcern, $shouldFireEventAfter, $expected) + public function testShouldSave($model, $writeConcern, $shouldFireEventAfter, $expected) { // Arrange $connection = m::mock(Connection::class); - $dataMapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connection]); + $builder = m::mock(Builder::class.'[parseToDocument,getCollection]', [$connection]); $options = ['writeConcern' => new WriteConcern($writeConcern)]; $collection = m::mock(Collection::class); $parsedObject = ['_id' => 123]; $operationResult = m::mock(); - $entity->_id = null; + $model->_id = null; // Act - $dataMapper->shouldAllowMockingProtectedMethods(); + $builder->shouldAllowMockingProtectedMethods(); - $dataMapper->expects() - ->parseToDocument($entity) + $builder->expects() + ->parseToDocument($model) ->andReturn($parsedObject); - $dataMapper->expects() + $builder->expects() ->getCollection() ->andReturn($collection); @@ -78,42 +78,42 @@ public function testShouldSave($entity, $writeConcern, $shouldFireEventAfter, $e ->getUpsertedCount() ->andReturn(1); - $this->expectEventToBeFired('saving', $entity, true); + $this->expectEventToBeFired('saving', $model, true); if ($shouldFireEventAfter) { - $this->expectEventToBeFired('saved', $entity, false); + $this->expectEventToBeFired('saved', $model, false); } else { - $this->expectEventNotToBeFired('saved', $entity); + $this->expectEventNotToBeFired('saved', $model); } // Assert - $this->assertSame($expected, $dataMapper->save($entity, $options)); + $this->assertSame($expected, $builder->save($model, $options)); } /** * @dataProvider getWriteConcernVariations */ - public function testShouldInsert($entity, $writeConcern, $shouldFireEventAfter, $expected) + public function testShouldInsert($model, $writeConcern, $shouldFireEventAfter, $expected) { // Arrange $connection = m::mock(Connection::class); - $dataMapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connection]); + $builder = m::mock(Builder::class.'[parseToDocument,getCollection]', [$connection]); $options = ['writeConcern' => new WriteConcern($writeConcern)]; $collection = m::mock(Collection::class); $parsedObject = ['_id' => 123]; $operationResult = m::mock(); - $entity->_id = null; + $model->_id = null; // Act - $dataMapper->shouldAllowMockingProtectedMethods(); + $builder->shouldAllowMockingProtectedMethods(); - $dataMapper->expects() - ->parseToDocument($entity) + $builder->expects() + ->parseToDocument($model) ->andReturn($parsedObject); - $dataMapper->expects() + $builder->expects() ->getCollection() ->andReturn($collection); @@ -129,42 +129,42 @@ public function testShouldInsert($entity, $writeConcern, $shouldFireEventAfter, ->getInsertedCount() ->andReturn(1); - $this->expectEventToBeFired('inserting', $entity, true); + $this->expectEventToBeFired('inserting', $model, true); if ($shouldFireEventAfter) { - $this->expectEventToBeFired('inserted', $entity, false); + $this->expectEventToBeFired('inserted', $model, false); } else { - $this->expectEventNotToBeFired('inserted', $entity); + $this->expectEventNotToBeFired('inserted', $model); } // Assert - $this->assertSame($expected, $dataMapper->insert($entity, $options)); + $this->assertSame($expected, $builder->insert($model, $options)); } /** * @dataProvider getWriteConcernVariations */ - public function testShouldInsertWithoutFiringEvents($entity, $writeConcern, $shouldFireEventAfter, $expected) + public function testShouldInsertWithoutFiringEvents($model, $writeConcern, $shouldFireEventAfter, $expected) { // Arrange $connection = m::mock(Connection::class); - $dataMapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connection]); + $builder = m::mock(Builder::class.'[parseToDocument,getCollection]', [$connection]); $options = ['writeConcern' => new WriteConcern($writeConcern)]; $collection = m::mock(Collection::class); $parsedObject = ['_id' => 123]; $operationResult = m::mock(); - $entity->_id = null; + $model->_id = null; // Act - $dataMapper->shouldAllowMockingProtectedMethods(); + $builder->shouldAllowMockingProtectedMethods(); - $dataMapper->expects() - ->parseToDocument($entity) + $builder->expects() + ->parseToDocument($model) ->andReturn($parsedObject); - $dataMapper->expects() + $builder->expects() ->getCollection() ->andReturn($collection); @@ -180,23 +180,23 @@ public function testShouldInsertWithoutFiringEvents($entity, $writeConcern, $sho ->getInsertedCount() ->andReturn(1); - $this->expectEventNotToBeFired('inserting', $entity); - $this->expectEventNotToBeFired('inserted', $entity); + $this->expectEventNotToBeFired('inserting', $model); + $this->expectEventNotToBeFired('inserted', $model); // Assert - $this->assertSame($expected, $dataMapper->insert($entity, $options, false)); + $this->assertSame($expected, $builder->insert($model, $options, false)); } /** * @dataProvider getWriteConcernVariations */ - public function testShouldUpdate($entity, $writeConcern, $shouldFireEventAfter, $expected) + public function testShouldUpdate($model, $writeConcern, $shouldFireEventAfter, $expected) { // Arrange $connection = m::mock(Connection::class); $client = m::mock(Client::class); $database = m::mock(Database::class); - $dataMapper = new DataMapper($connection); + $builder = new Builder($connection); $collection = m::mock(Collection::class); $parsedObject = ['_id' => 123]; @@ -216,7 +216,7 @@ public function testShouldUpdate($entity, $writeConcern, $shouldFireEventAfter, } ); - $entity->_id = 123; + $model->_id = 123; // Expect $connection->expects() @@ -246,16 +246,16 @@ public function testShouldUpdate($entity, $writeConcern, $shouldFireEventAfter, ->getModifiedCount() ->andReturn(1); - $this->expectEventToBeFired('updating', $entity, true); + $this->expectEventToBeFired('updating', $model, true); if ($shouldFireEventAfter) { - $this->expectEventToBeFired('updated', $entity, false); + $this->expectEventToBeFired('updated', $model, false); } else { - $this->expectEventNotToBeFired('updated', $entity); + $this->expectEventNotToBeFired('updated', $model); } // Act - $result = $dataMapper->update($entity, $options); + $result = $builder->update($model, $options); // Assert $this->assertSame($expected, $result); @@ -267,9 +267,9 @@ public function testShouldUpdateUnsettingFields() $connection = m::mock(Connection::class); $client = m::mock(Client::class); $database = m::mock(Database::class); - $dataMapper = new DataMapper($connection); + $builder = new Builder($connection); - $entity = new class extends AbstractActiveRecord + $model = new class extends AbstractModel { }; $collection = m::mock(Collection::class); @@ -290,12 +290,12 @@ public function testShouldUpdateUnsettingFields() } ); - $entity->unchanged = 'unchanged'; - $entity->notOnSchema = 'to be deleted'; - $entity->name = 'John'; - $entity->syncOriginalDocumentAttributes(); - $entity->_id = 123; - unset($entity->name); + $model->unchanged = 'unchanged'; + $model->notOnSchema = 'to be deleted'; + $model->name = 'John'; + $model->syncOriginalDocumentAttributes(); + $model->_id = 123; + unset($model->name); // Expect $connection->expects() @@ -325,12 +325,12 @@ public function testShouldUpdateUnsettingFields() ->getModifiedCount() ->andReturn(1); - $this->expectEventToBeFired('updating', $entity, true); + $this->expectEventToBeFired('updating', $model, true); - $this->expectEventToBeFired('updated', $entity, false); + $this->expectEventToBeFired('updated', $model, false); // Act - $result = $dataMapper->update($entity, $options); + $result = $builder->update($model, $options); // Assert $this->assertTrue($result); @@ -340,30 +340,30 @@ public function testShouldUpdateUnsettingFields() * @dataProvider getWriteConcernVariations */ public function testUpdateShouldCallInsertWhenObjectHasNoId( - $entity, + $model, $writeConcern, $shouldFireEventAfter, $expected ) { // Arrange $connection = m::mock(Connection::class); - $dataMapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connection]); + $builder = m::mock(Builder::class.'[parseToDocument,getCollection]', [$connection]); $collection = m::mock(Collection::class); $parsedObject = ['_id' => 123]; $operationResult = m::mock(); $options = ['writeConcern' => new WriteConcern($writeConcern)]; - $entity->_id = null; + $model->_id = null; // Act - $dataMapper->shouldAllowMockingProtectedMethods(); + $builder->shouldAllowMockingProtectedMethods(); - $dataMapper->expects() - ->parseToDocument($entity) + $builder->expects() + ->parseToDocument($model) ->andReturn($parsedObject); - $dataMapper->expects() + $builder->expects() ->getCollection() ->andReturn($collection); @@ -381,45 +381,45 @@ public function testUpdateShouldCallInsertWhenObjectHasNoId( ->getInsertedCount() ->andReturn(1); - $this->expectEventToBeFired('updating', $entity, true); + $this->expectEventToBeFired('updating', $model, true); if ($shouldFireEventAfter) { - $this->expectEventToBeFired('updated', $entity, false); + $this->expectEventToBeFired('updated', $model, false); } else { - $this->expectEventNotToBeFired('updated', $entity); + $this->expectEventNotToBeFired('updated', $model); } - $this->expectEventNotToBeFired('inserting', $entity); - $this->expectEventNotToBeFired('inserted', $entity); + $this->expectEventNotToBeFired('inserting', $model); + $this->expectEventNotToBeFired('inserted', $model); // Assert - $this->assertSame($expected, $dataMapper->update($entity, $options)); + $this->assertSame($expected, $builder->update($model, $options)); } /** * @dataProvider getWriteConcernVariations */ - public function testShouldDelete($entity, $writeConcern, $shouldFireEventAfter, $expected) + public function testShouldDelete($model, $writeConcern, $shouldFireEventAfter, $expected) { // Arrange $connection = m::mock(Connection::class); - $dataMapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connection]); + $builder = m::mock(Builder::class.'[parseToDocument,getCollection]', [$connection]); $collection = m::mock(Collection::class); $parsedObject = ['_id' => 123]; $operationResult = m::mock(); $options = ['writeConcern' => new WriteConcern($writeConcern)]; - $entity->_id = null; + $model->_id = null; // Act - $dataMapper->shouldAllowMockingProtectedMethods(); + $builder->shouldAllowMockingProtectedMethods(); - $dataMapper->expects() - ->parseToDocument($entity) + $builder->expects() + ->parseToDocument($model) ->andReturn($parsedObject); - $dataMapper->expects() + $builder->expects() ->getCollection() ->andReturn($collection); @@ -435,16 +435,16 @@ public function testShouldDelete($entity, $writeConcern, $shouldFireEventAfter, ->getDeletedCount() ->andReturn(1); - $this->expectEventToBeFired('deleting', $entity, true); + $this->expectEventToBeFired('deleting', $model, true); if ($shouldFireEventAfter) { - $this->expectEventToBeFired('deleted', $entity, false); + $this->expectEventToBeFired('deleted', $model, false); } else { - $this->expectEventNotToBeFired('deleted', $entity); + $this->expectEventNotToBeFired('deleted', $model); } // Assert - $this->assertSame($expected, $dataMapper->delete($entity, $options)); + $this->assertSame($expected, $builder->delete($model, $options)); } /** @@ -457,18 +457,18 @@ public function testDatabaseOperationsShouldBailOutIfTheEventHandlerReturnsFalse ) { // Arrange $connection = m::mock(Connection::class); - $dataMapper = m::mock(DataMapper::class.'[parseToDocument,getCollection]', [$connection]); + $builder = m::mock(Builder::class.'[parseToDocument,getCollection]', [$connection]); $collection = m::mock(Collection::class); - $entity = m::mock(); + $model = m::mock(ModelInterface::class); - $dataMapper->shouldAllowMockingProtectedMethods(); + $builder->shouldAllowMockingProtectedMethods(); // Expect - $dataMapper->expects() - ->parseToDocument($entity) + $builder->expects() + ->parseToDocument($model) ->never(); - $dataMapper->allows() + $builder->allows() ->getCollection() ->andReturn($collection); @@ -476,10 +476,10 @@ public function testDatabaseOperationsShouldBailOutIfTheEventHandlerReturnsFalse ->never(); /* "Mocks" the fireEvent to return false and bail the operation */ - $this->expectEventToBeFired($eventName, $entity, true, false); + $this->expectEventToBeFired($eventName, $model, true, false); // Act - $result = $dataMapper->$operation($entity); + $result = $builder->$operation($model); // Assert $this->assertFalse($result); @@ -489,7 +489,7 @@ public function testShouldGetWithWhereQuery() { // Arrange $connection = m::mock(Connection::class); - $dataMapper = m::mock(DataMapper::class.'[prepareValueQuery,getCollection]', [$connection]); + $builder = m::mock(Builder::class.'[prepareValueQuery,getCollection]', [$connection]); $schema = m::mock(AbstractSchema::class); $collection = m::mock(Collection::class); @@ -497,26 +497,26 @@ public function testShouldGetWithWhereQuery() $preparedQuery = ['_id' => 123]; $projection = ['project' => true, '_id' => false]; - $schema->entityClass = 'stdClass'; - $dataMapper->setSchema($schema); + $schema->modelClass = 'stdClass'; + $builder->setSchema($schema); - $dataMapper->shouldAllowMockingProtectedMethods(); + $builder->shouldAllowMockingProtectedMethods(); // Expect - $dataMapper->expects() + $builder->expects() ->prepareValueQuery($query) ->andReturn($preparedQuery); - $dataMapper->expects() + $builder->expects() ->getCollection() ->andReturn($collection); // Act - $result = $dataMapper->where($query, $projection); + $result = $builder->where($query, $projection); // Assert $this->assertInstanceOf(Cursor::class, $result); - $this->assertAttributeEquals($schema, 'entitySchema', $result); + $this->assertAttributeEquals($schema, 'modelSchema', $result); $this->assertAttributeEquals($collection, 'collection', $result); $this->assertAttributeEquals('find', 'command', $result); $this->assertAttributeEquals( @@ -530,16 +530,16 @@ public function testShouldGetAll() { // Arrange $connection = m::mock(Connection::class); - $dataMapper = m::mock(DataMapper::class.'[where]', [$connection]); + $builder = m::mock(Builder::class.'[where]', [$connection]); $mongolidCursor = m::mock(Cursor::class); // Expect - $dataMapper->expects() + $builder->expects() ->where([]) ->andReturn($mongolidCursor); // Act - $result = $dataMapper->all(); + $result = $builder->all(); // Assert $this->assertSame($mongolidCursor, $result); @@ -549,23 +549,27 @@ public function testShouldGetFirstWithQuery() { // Arrange $connection = m::mock(Connection::class); - $dataMapper = m::mock(DataMapper::class.'[prepareValueQuery,getCollection]', [$connection]); + $builder = m::mock(Builder::class.'[prepareValueQuery,getCollection]', [$connection]); $schema = m::mock(AbstractSchema::class); $collection = m::mock(Collection::class); $query = 123; $preparedQuery = ['_id' => 123]; + $object = new class extends AbstractModel + { + }; - $schema->entityClass = 'stdClass'; - $dataMapper->setSchema($schema); + $class = get_class($object); + $schema->modelClass = $class; + $builder->setSchema($schema); - $dataMapper->shouldAllowMockingProtectedMethods(); + $builder->shouldAllowMockingProtectedMethods(); // Act - $dataMapper->expects() + $builder->expects() ->prepareValueQuery($query) ->andReturn($preparedQuery); - $dataMapper->expects() + $builder->expects() ->getCollection() ->andReturn($collection); @@ -573,21 +577,21 @@ public function testShouldGetFirstWithQuery() ->findOne($preparedQuery, ['projection' => []]) ->andReturn(['name' => 'John Doe']); - $result = $dataMapper->first($query); + $result = $builder->first($query); // Assert - $this->assertInstanceOf(stdClass::class, $result); - $this->assertAttributeEquals('John Doe', 'name', $result); + $this->assertInstanceOf($class, $result); + $this->assertSame('John Doe', $result->name); } public function testFirstWithNullShouldNotHitTheDatabase() { // Arrange $connection = m::mock(Connection::class); - $dataMapper = new DataMapper($connection); + $builder = new Builder($connection); // Act - $result = $dataMapper->first(null); + $result = $builder->first(null); // Assert $this->assertNull($result); @@ -597,23 +601,27 @@ public function testFirstOrFailShouldGetFirst() { // Arrange $connection = m::mock(Connection::class); - $dataMapper = m::mock(DataMapper::class.'[prepareValueQuery,getCollection]', [$connection]); + $builder = m::mock(Builder::class.'[prepareValueQuery,getCollection]', [$connection]); $schema = m::mock(AbstractSchema::class); $collection = m::mock(Collection::class); $query = 123; $preparedQuery = ['_id' => 123]; + $object = new class extends AbstractModel + { + }; - $schema->entityClass = 'stdClass'; - $dataMapper->setSchema($schema); + $class = get_class($object); + $schema->modelClass = $class; + $builder->setSchema($schema); - $dataMapper->shouldAllowMockingProtectedMethods(); + $builder->shouldAllowMockingProtectedMethods(); // Act - $dataMapper->expects() + $builder->expects() ->prepareValueQuery($query) ->andReturn($preparedQuery); - $dataMapper->expects() + $builder->expects() ->getCollection() ->andReturn($collection); @@ -621,25 +629,25 @@ public function testFirstOrFailShouldGetFirst() ->findOne($preparedQuery, ['projection' => []]) ->andReturn(['name' => 'John Doe']); - $result = $dataMapper->firstOrFail($query); + $result = $builder->firstOrFail($query); // Assert - $this->assertInstanceOf(stdClass::class, $result); - $this->assertAttributeEquals('John Doe', 'name', $result); + $this->assertInstanceOf($class, $result); + $this->assertSame('John Doe', $result->name); } public function testFirstOrFailWithNullShouldFail() { // Arrange $connection = m::mock(Connection::class); - $dataMapper = new DataMapper($connection); - $dataMapper->setSchema( + $builder = new Builder($connection); + $builder->setSchema( new class extends AbstractSchema { /** * {@inheritdoc} */ - public $entityClass = 'User'; + public $modelClass = 'User'; } ); @@ -647,31 +655,31 @@ public function testFirstOrFailWithNullShouldFail() $this->expectExceptionMessage('No query results for model [User].'); // Act - $dataMapper->firstOrFail(null); + $builder->firstOrFail(null); } public function testShouldGetNullIfFirstCantFindAnything() { // Arrange $connection = m::mock(Connection::class); - $dataMapper = m::mock(DataMapper::class.'[prepareValueQuery,getCollection]', [$connection]); + $builder = m::mock(Builder::class.'[prepareValueQuery,getCollection]', [$connection]); $schema = m::mock(AbstractSchema::class); $collection = m::mock(Collection::class); $query = 123; $preparedQuery = ['_id' => 123]; - $schema->entityClass = 'stdClass'; - $dataMapper->setSchema($schema); + $schema->modelClass = 'stdClass'; + $builder->setSchema($schema); - $dataMapper->shouldAllowMockingProtectedMethods(); + $builder->shouldAllowMockingProtectedMethods(); // Expect - $dataMapper->expects() + $builder->expects() ->prepareValueQuery($query) ->andReturn($preparedQuery); - $dataMapper->expects() + $builder->expects() ->getCollection() ->andReturn($collection); @@ -680,7 +688,7 @@ public function testShouldGetNullIfFirstCantFindAnything() ->andReturn(null); // Act - $result = $dataMapper->first($query); + $result = $builder->first($query); // Assert $this->assertNull($result); @@ -690,8 +698,8 @@ public function testShouldGetFirstProjectingFields() { // Arrange $connection = m::mock(Connection::class); - $dataMapper = m::mock( - DataMapper::class.'[prepareValueQuery,getCollection]', + $builder = m::mock( + Builder::class.'[prepareValueQuery,getCollection]', [$connection] ); $schema = m::mock(AbstractSchema::class); @@ -701,17 +709,17 @@ public function testShouldGetFirstProjectingFields() $preparedQuery = ['_id' => 123]; $projection = ['project' => true, 'fields' => false]; - $schema->entityClass = 'stdClass'; - $dataMapper->setSchema($schema); + $schema->modelClass = 'stdClass'; + $builder->setSchema($schema); - $dataMapper->shouldAllowMockingProtectedMethods(); + $builder->shouldAllowMockingProtectedMethods(); // Expect - $dataMapper->expects() + $builder->expects() ->prepareValueQuery($query) ->andReturn($preparedQuery); - $dataMapper->expects() + $builder->expects() ->getCollection() ->andReturn($collection); @@ -720,7 +728,7 @@ public function testShouldGetFirstProjectingFields() ->andReturn(null); // Act - $result = $dataMapper->first($query, $projection); + $result = $builder->first($query, $projection); // Assert $this->assertNull($result); @@ -730,30 +738,30 @@ public function testShouldParseObjectToDocumentAndPutResultingIdIntoTheGivenObje { // Arrange $connection = m::mock(Connection::class); - $dataMapper = m::mock(DataMapper::class.'[getSchemaMapper]', [$connection]); - $entity = m::mock(); + $builder = m::mock(Builder::class.'[getSchemaMapper]', [$connection]); + $model = m::mock(ModelInterface::class); $parsedDocument = ['a_field' => 123, '_id' => 'bacon']; - $schemaMapper = m::mock(AbstractSchema::class.'[]'); + $schemaMapper = m::mock(SchemaMapper::class, [m::mock(AbstractSchema::class)]); - $dataMapper->shouldAllowMockingProtectedMethods(); + $builder->shouldAllowMockingProtectedMethods(); // Expect - $dataMapper->expects() + $builder->expects() ->getSchemaMapper() ->andReturn($schemaMapper); $schemaMapper->expects() - ->map($entity) + ->map($model) ->andReturn($parsedDocument); // Act - $result = $this->callProtected($dataMapper, 'parseToDocument', [$entity]); + $result = $this->callProtected($builder, 'parseToDocument', [$model]); // Assert $this->assertSame($parsedDocument, $result); $this->assertSame( 'bacon', // Since this was the parsedDocument _id - $entity->_id + $model->_id ); } @@ -761,12 +769,12 @@ public function testShouldGetSchemaMapper() { // Arrange $connection = m::mock(Connection::class); - $dataMapper = new DataMapper($connection); - $dataMapper->schemaClass = 'MySchema'; + $builder = new Builder($connection); + $builder->schemaClass = 'MySchema'; $schema = $this->instance('MySchema', m::mock(AbstractSchema::class)); // Act - $result = $this->callProtected($dataMapper, 'getSchemaMapper'); + $result = $this->callProtected($builder, 'getSchemaMapper'); // Assert $this->assertInstanceOf(SchemaMapper::class, $result); @@ -777,12 +785,12 @@ public function testShouldGetRawCollection() { // Arrange $connection = m::mock(Connection::class); - $dataMapper = new DataMapper($connection); + $builder = new Builder($connection); $collection = m::mock(Collection::class); $schema = m::mock(AbstractSchema::class); $schema->collection = 'foobar'; - $dataMapper->setSchema($schema); + $builder->setSchema($schema); $connection->defaultDatabase = 'grimory'; $connection->grimory = (object) ['foobar' => $collection]; @@ -792,7 +800,7 @@ public function testShouldGetRawCollection() ->andReturn($connection); // Act - $result = $this->callProtected($dataMapper, 'getCollection'); + $result = $this->callProtected($builder, 'getCollection'); // Assert $this->assertSame($collection, $result); @@ -805,10 +813,10 @@ public function testShouldPrepareQueryValue($value, $expectation) { // Arrange $connection = m::mock(Connection::class); - $dataMapper = new DataMapper($connection); + $builder = new Builder($connection); // Act - $result = $this->callProtected($dataMapper, 'prepareValueQuery', [$value]); + $result = $this->callProtected($builder, 'prepareValueQuery', [$value]); // Assert $this->assertMongoQueryEquals($expectation, $result); @@ -821,10 +829,10 @@ public function testPrepareProjectionShouldConvertArray($data, $expectation) { // Arrange $connection = m::mock(Connection::class); - $dataMapper = new DataMapper($connection); + $builder = new Builder($connection); // Act - $result = $this->callProtected($dataMapper, 'prepareProjection', [$data]); + $result = $this->callProtected($builder, 'prepareProjection', [$data]); // Assert $this->assertSame($expectation, $result); @@ -834,7 +842,7 @@ public function testPrepareProjectionShouldThrownAnException() { // Arrange $connection = m::mock(Connection::class); - $dataMapper = new DataMapper($connection); + $builder = new Builder($connection); $data = ['valid' => true, 'invalid-key' => 'invalid-value']; // Expectations @@ -842,7 +850,7 @@ public function testPrepareProjectionShouldThrownAnException() $this->expectExceptionMessage("Invalid projection: 'invalid-key' => 'invalid-value'"); // Act - $this->callProtected($dataMapper, 'prepareProjection', [$data]); + $this->callProtected($builder, 'prepareProjection', [$data]); } public function eventsToBailOperations() @@ -918,34 +926,22 @@ public function queryValueScenarios() public function getWriteConcernVariations() { - $model = new class extends AbstractActiveRecord + $model = new class extends AbstractModel { }; - $model2 = new class extends AbstractActiveRecord + $model2 = new class extends AbstractModel { }; return [ - 'acknowledged write concern with plain object' => [ - 'object' => new stdClass(), - 'writeConcern' => 1, - 'shouldFireEventAfter' => true, - 'expected' => true, - ], - 'acknowledged write concern with attributesAccessInterface' => [ + 'acknowledged write concern ' => [ 'object' => $model, 'writeConcern' => 1, 'shouldFireEventAfter' => true, 'expected' => true, ], - 'unacknowledged write concern with plain object' => [ - 'object' => new stdClass(), - 'writeConcern' => 0, - 'shouldFireEventAfter' => false, - 'expected' => false, - ], - 'unacknowledged write concern with attributesAccessInterface' => [ + 'unacknowledged write concern' => [ 'object' => $model2, 'writeConcern' => 0, 'shouldFireEventAfter' => false, @@ -988,21 +984,21 @@ protected function getEventService() return Ioc::make(EventTriggerService::class); } - protected function expectEventToBeFired($event, $entity, bool $halt, $return = true) + protected function expectEventToBeFired($event, $model, bool $halt, $return = true) { - $event = 'mongolid.'.$event.': '.get_class($entity); + $event = 'mongolid.'.$event.': '.get_class($model); $this->getEventService()->expects() - ->fire($event, $entity, $halt) + ->fire($event, $model, $halt) ->andReturn($return); } - protected function expectEventNotToBeFired($event, $entity) + protected function expectEventNotToBeFired($event, $model) { - $event = 'mongolid.'.$event.': '.get_class($entity); + $event = 'mongolid.'.$event.': '.get_class($model); $this->getEventService()->expects() - ->fire($event, $entity, m::any()) + ->fire($event, $model, m::any()) ->never(); } } diff --git a/tests/Unit/DataMapper/BulkWriteTest.php b/tests/Unit/Query/BulkWriteTest.php similarity index 82% rename from tests/Unit/DataMapper/BulkWriteTest.php rename to tests/Unit/Query/BulkWriteTest.php index 69aef4ca..6ae855a0 100644 --- a/tests/Unit/DataMapper/BulkWriteTest.php +++ b/tests/Unit/Query/BulkWriteTest.php @@ -1,13 +1,13 @@ expects() + $model->expects() ->getSchema(); // Act - $bulkWrite = new BulkWrite($entity); + $bulkWrite = new BulkWrite($model); // Assert $this->assertInstanceOf(BulkWrite::class, $bulkWrite); @@ -31,15 +31,15 @@ public function testShouldConstructBulkWriteObject() public function testShouldSetAndGetMongoBulkWrite() { // Arrange - $entity = m::mock(HasSchemaInterface::class); + $model = m::mock(ModelInterface::class); $mongoBulkWrite = new MongoBulkWrite(); // Expect - $entity->expects() + $model->expects() ->getSchema(); // Act - $bulkWrite = new BulkWrite($entity); + $bulkWrite = new BulkWrite($model); $bulkWrite->setBulkWrite($mongoBulkWrite); // Assert @@ -49,20 +49,20 @@ public function testShouldSetAndGetMongoBulkWrite() public function testShouldAddUpdateOneOperationToBulkWrite() { // Arrange - $entity = m::mock(HasSchemaInterface::class); + $model = m::mock(ModelInterface::class); $mongoBulkWrite = m::mock(new MongoBulkWrite()); $id = '123'; $data = ['name' => 'John']; // Expect - $entity->expects() + $model->expects() ->getSchema(); $mongoBulkWrite->expects() ->update(['_id' => $id], ['$set' => $data], ['upsert' => true]); - $bulkWrite = m::mock(BulkWrite::class.'[getBulkWrite]', [$entity]); + $bulkWrite = m::mock(BulkWrite::class.'[getBulkWrite]', [$model]); $bulkWrite->expects() ->getBulkWrite() @@ -75,20 +75,20 @@ public function testShouldAddUpdateOneOperationToBulkWrite() public function testShouldUpdateOneWithUnsetOperationToBulkWrite() { // Arrange - $entity = m::mock(HasSchemaInterface::class); + $model = m::mock(ModelInterface::class); $mongoBulkWrite = m::mock(new MongoBulkWrite()); $id = '123'; $data = ['name' => 'John']; // Expect - $entity->expects() + $model->expects() ->getSchema(); $mongoBulkWrite->expects() ->update(['_id' => $id], ['$unset' => $data], ['upsert' => true]); - $bulkWrite = m::mock(BulkWrite::class.'[getBulkWrite]', [$entity]); + $bulkWrite = m::mock(BulkWrite::class.'[getBulkWrite]', [$model]); $bulkWrite->expects() ->getBulkWrite() @@ -100,9 +100,9 @@ public function testShouldUpdateOneWithUnsetOperationToBulkWrite() public function testShouldExecuteBulkWrite() { - $entity = m::mock(HasSchemaInterface::class); + $model = m::mock(ModelInterface::class); $schema = m::mock(AbstractSchema::class); - $entity->schema = $schema; + $model->schema = $schema; $mongoBulkWrite = m::mock(new MongoBulkWrite()); $connection = $this->instance(Connection::class, m::mock(Connection::class)); $manager = m::mock(new Manager()); @@ -112,7 +112,7 @@ public function testShouldExecuteBulkWrite() $namespace = 'foo.bar'; // Expect - $entity->expects() + $model->expects() ->getSchema() ->andReturn($schema); @@ -124,7 +124,7 @@ public function testShouldExecuteBulkWrite() ->executeBulkWrite($namespace, $mongoBulkWrite, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); - $bulkWrite = m::mock(BulkWrite::class.'[getBulkWrite]', [$entity]); + $bulkWrite = m::mock(BulkWrite::class.'[getBulkWrite]', [$model]); $bulkWrite->expects() ->getBulkWrite() diff --git a/tests/Unit/DataMapper/EntityAssemblerTest.php b/tests/Unit/Query/ModelAssemblerTest.php similarity index 83% rename from tests/Unit/DataMapper/EntityAssemblerTest.php rename to tests/Unit/Query/ModelAssemblerTest.php index c3848e7c..93dbd368 100644 --- a/tests/Unit/DataMapper/EntityAssemblerTest.php +++ b/tests/Unit/Query/ModelAssemblerTest.php @@ -1,53 +1,51 @@ $value) { $schemas[$key] = $this->instance($key, m::mock(AbstractSchema::class.'[]')); - $schemas[$key]->entityClass = $value['entityClass']; + $schemas[$key]->modelClass = $value['modelClass']; $schemas[$key]->fields = $value['fields']; } // Act - $result = $entityAssembler->assemble($inputValue, $schemas[$inputSchema]); + $result = $modelAssembler->assemble($inputValue, $schemas[$inputSchema]); // Assert $this->assertEquals($expectedOutput, $result); } - public function entityAssemblerFixture() + public function modelAssemblerFixture() { return [ //--------------------------- - 'A simple schema to a entity' => [ - 'inputValue' => [ // Data that will be used to assembly the entity + 'A simple schema to a model' => [ + 'inputValue' => [ // Data that will be used to assembly the model '_id' => new ObjectId('507f1f77bcf86cd799439011'), 'name' => 'John Doe', 'age' => 25, 'grade' => 7.25, ], - 'availableSchmas' => [ // Schemas that will exist in the test context + 'availableSchemas' => [ // Schemas that will exist in the test context 'studentSchema' => [ - 'entityClass' => StubStudent::class, + 'modelClass' => StubStudent::class, 'fields' => [ '_id' => 'objectId', 'name' => 'string', @@ -69,7 +67,7 @@ public function entityAssemblerFixture() //--------------------------- 'A schema containing an embedded schema but with null field' => [ - 'inputValue' => [ // Data that will be used to assembly the entity + 'inputValue' => [ // Data that will be used to assembly the model '_id' => new ObjectId('507f1f77bcf86cd799439011'), 'name' => 'John Doe', 'age' => 25, @@ -78,7 +76,7 @@ public function entityAssemblerFixture() ], 'availableSchemas' => [ // Schemas that will exist in the test context 'studentSchema' => [ - 'entityClass' => StubStudent::class, + 'modelClass' => StubStudent::class, 'fields' => [ '_id' => 'objectId', 'name' => 'string', @@ -88,7 +86,7 @@ public function entityAssemblerFixture() ], ], 'TestSchema' => [ - 'entityClass' => StubTestGrade::class, + 'modelClass' => StubTestGrade::class, 'fields' => [ '_id' => 'objectId', 'subject' => 'string', @@ -109,7 +107,7 @@ public function entityAssemblerFixture() //--------------------------- 'A stdClass with a schema containing an embedded schema with a document directly into the field' => [ - 'inputValue' => (object) [ // Data that will be used to assembly the entity + 'inputValue' => (object) [ // Data that will be used to assembly the model '_id' => new ObjectId('507f1f77bcf86cd799439011'), 'name' => 'John Doe', 'age' => 25, @@ -120,9 +118,9 @@ public function entityAssemblerFixture() ], 'finalGrade' => 7.25, ], - 'availableSchmas' => [ // Schemas that will exist in the test context + 'availableSchemas' => [ // Schemas that will exist in the test context 'studentSchema' => [ - 'entityClass' => StubStudent::class, + 'modelClass' => StubStudent::class, 'fields' => [ '_id' => 'objectId', 'name' => 'string', @@ -132,7 +130,7 @@ public function entityAssemblerFixture() ], ], 'TestSchema' => [ - 'entityClass' => StubTestGrade::class, + 'modelClass' => StubTestGrade::class, 'fields' => [ '_id' => 'objectId', 'subject' => 'string', @@ -159,7 +157,7 @@ public function entityAssemblerFixture() //--------------------------- 'A schema containing an embedded schema with multiple documents in the field' => [ - 'inputValue' => [ // Data that will be used to assembly the entity + 'inputValue' => [ // Data that will be used to assembly the model '_id' => new ObjectId('507f1f77bcf86cd799439011'), 'name' => 'John Doe', 'age' => 25, @@ -177,9 +175,9 @@ public function entityAssemblerFixture() ], 'finalGrade' => 7.25, ], - 'availableSchmas' => [ // Schemas that will exist in the test context + 'availableSchemas' => [ // Schemas that will exist in the test context 'studentSchema' => [ - 'entityClass' => StubStudent::class, + 'modelClass' => StubStudent::class, 'fields' => [ '_id' => 'objectId', 'name' => 'string', @@ -189,7 +187,7 @@ public function entityAssemblerFixture() ], ], 'TestSchema' => [ - 'entityClass' => StubTestGrade::class, + 'modelClass' => StubTestGrade::class, 'fields' => [ '_id' => 'objectId', 'subject' => 'string', @@ -221,15 +219,15 @@ public function entityAssemblerFixture() //--------------------------- 'A simple schema with a polymorphable interface' => [ - 'inputValue' => [ // Data that will be used to assembly the entity + 'inputValue' => [ // Data that will be used to assembly the model '_id' => new ObjectId('507f1f77bcf86cd799439011'), 'name' => 'John Doe', 'age' => 25, 'grade' => 7.25, ], - 'availableSchmas' => [ // Schemas that will exist in the test context + 'availableSchemas' => [ // Schemas that will exist in the test context 'studentSchema' => [ - 'entityClass' => PolymorphableStudent::class, + 'modelClass' => PolymorphableStudent::class, 'fields' => [ '_id' => 'objectId', 'name' => 'string', @@ -251,10 +249,8 @@ public function entityAssemblerFixture() } } -class StubStudent extends stdClass implements HasAttributesInterface +class StubStudent extends AbstractModel { - use HasAttributesTrait; - public function __construct($attr = []) { foreach ($attr as $key => $value) { @@ -265,20 +261,22 @@ public function __construct($attr = []) } } -class StubTestGrade extends stdClass +class StubTestGrade extends AbstractModel { public function __construct($attr = []) { foreach ($attr as $key => $value) { $this->$key = $value; } + + $this->syncOriginalDocumentAttributes(); } } -class PolymorphableStudent extends stdClass implements PolymorphableInterface +class PolymorphableStudent extends AbstractModel implements PolymorphableInterface { public function polymorph() { - return new StubStudent((array) $this); + return new StubStudent($this->getDocumentAttributes()); } } diff --git a/tests/Unit/Schema/AbstractSchemaTest.php b/tests/Unit/Schema/AbstractSchemaTest.php index 160ec552..93be2c80 100644 --- a/tests/Unit/Schema/AbstractSchemaTest.php +++ b/tests/Unit/Schema/AbstractSchemaTest.php @@ -18,13 +18,13 @@ public function testShouldNotBeDynamicByDefault() $this->assertAttributeEquals(false, 'dynamic', $schema); } - public function testMustHaveAnEntityClass() + public function testMustHaveAnModelClass() { // Arrange $schema = m::mock(AbstractSchema::class.'[]'); // Assert - $this->assertAttributeEquals('stdClass', 'entityClass', $schema); + $this->assertAttributeEquals('stdClass', 'modelClass', $schema); } public function testShouldCastNullIntoObjectId() From d73397dd1aa6d705b121705e05b8cd7e72d7740f Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 26 Nov 2018 16:13:21 -0200 Subject: [PATCH 072/116] Implement Persistable interface and simplify logic for parsing documents --- src/Cursor/Cursor.php | 8 +- src/Model/AbstractModel.php | 32 ++-- src/Model/DocumentEmbedder.php | 3 +- src/Model/ModelInterface.php | 10 +- src/Model/PolymorphableInterface.php | 2 - src/Query/Builder.php | 74 ++------- src/Query/SchemaMapper.php | 2 - tests/Integration/EmbedsManyRelationTest.php | 12 +- tests/Integration/EmbedsOneRelationTest.php | 16 +- tests/Integration/PersistedDataTest.php | 21 +-- tests/Unit/Connection/ConnectionTest.php | 6 +- tests/Unit/Cursor/EmbeddedCursorTest.php | 32 +++- tests/Unit/Model/DocumentEmbedderTest.php | 13 +- tests/Unit/Query/BuilderTest.php | 158 +++++-------------- 14 files changed, 149 insertions(+), 240 deletions(-) diff --git a/src/Cursor/Cursor.php b/src/Cursor/Cursor.php index d81af845..074a0fec 100644 --- a/src/Cursor/Cursor.php +++ b/src/Cursor/Cursor.php @@ -1,6 +1,7 @@ cursor) { $driverCursor = $this->collection->{$this->command}(...$this->params); @@ -345,10 +345,8 @@ protected function getCursor(): Traversable /** * Retrieves an ModelAssembler instance. - * - * @return ModelAssembler */ - protected function getAssembler() + protected function getAssembler(): ModelAssembler { if (!$this->assembler) { $this->assembler = Ioc::make(ModelAssembler::class); diff --git a/src/Model/AbstractModel.php b/src/Model/AbstractModel.php index d8b51fdf..c3ee8dc9 100644 --- a/src/Model/AbstractModel.php +++ b/src/Model/AbstractModel.php @@ -7,6 +7,7 @@ use Mongolid\Model\Exception\ModelNotFoundException; use Mongolid\Model\Exception\NoCollectionNameException; use Mongolid\Query\Builder; +use Mongolid\Query\SchemaMapper; use Mongolid\Schema\AbstractSchema; use Mongolid\Schema\DynamicSchema; @@ -139,16 +140,6 @@ private static function getBuilderInstance(): Builder return $instance->getBuilder(); } - /** - * Parses an object with SchemaMapper. - * - * @param mixed $model the object to be parsed - */ - public function parseToDocument($model): array - { - return $this->getBuilder()->parseToDocument($model); - } - /** * Saves this object into database. */ @@ -280,6 +271,27 @@ public function getSchema(): AbstractSchema return $schema; } + public function bsonSerialize() + { + $schemaMapper = Ioc::make(SchemaMapper::class, ['schema' => $this->getSchema()]); + + $parsedDocument = $schemaMapper->map($this); + + foreach ($parsedDocument as $field => $value) { + $this->setDocumentAttribute($field, $value); + } + + return $parsedDocument; + } + + public function bsonUnserialize(array $data) + { + unset($data['__pclass']); + $this->fill($data); + + $this->syncOriginalDocumentAttributes(); + } + /** * Will check if the current value of $fields property is the name of a * Schema class and instantiate it if possible. diff --git a/src/Model/DocumentEmbedder.php b/src/Model/DocumentEmbedder.php index 6a6b8ea7..0b09b102 100644 --- a/src/Model/DocumentEmbedder.php +++ b/src/Model/DocumentEmbedder.php @@ -37,9 +37,8 @@ public function embed(ModelInterface $parent, string $field, &$model): bool // In order to update the document if it exists inside the $parent $this->unembed($parent, $field, $model); - $data = $model->parseToDocument($model); $fieldValue = $parent->$field; - $fieldValue[] = $data; + $fieldValue[] = $model; $parent->$field = array_values($fieldValue); return true; diff --git a/src/Model/ModelInterface.php b/src/Model/ModelInterface.php index 837ab83e..b56f6731 100644 --- a/src/Model/ModelInterface.php +++ b/src/Model/ModelInterface.php @@ -1,19 +1,13 @@ parseToDocument($model); + // TODO rework this + $model->bsonSerialize(); $queryResult = $this->getCollection()->replaceOne( - ['_id' => $data['_id']], - $data, + ['_id' => $model->_id], + $model, $this->mergeOptions($options, ['upsert' => true]) ); @@ -119,10 +113,8 @@ public function insert(ModelInterface $model, array $options = [], bool $fireEve return false; } - $data = $this->parseToDocument($model); - $queryResult = $this->getCollection()->insertOne( - $data, + $model, $this->mergeOptions($options) ); @@ -166,12 +158,11 @@ public function update(ModelInterface $model, array $options = []): bool return $result; } - $data = $this->parseToDocument($model); - - $updateData = $this->getUpdateData($model, $data); + // TODO review this + $updateData = $this->getUpdateData($model, $model->bsonSerialize()); $queryResult = $this->getCollection()->updateOne( - ['_id' => $data['_id']], + ['_id' => $model->_id], $updateData, $this->mergeOptions($options) ); @@ -202,10 +193,8 @@ public function delete(ModelInterface $model, array $options = []): bool return false; } - $data = $this->parseToDocument($model); - $queryResult = $this->getCollection()->deleteOne( - ['_id' => $data['_id']], + ['_id' => $model->_id], $this->mergeOptions($options) ); @@ -264,18 +253,10 @@ public function first($query = [], array $projection = []) return null; } - $document = $this->getCollection()->findOne( + return $this->getCollection()->findOne( $this->prepareValueQuery($query), ['projection' => $this->prepareProjection($projection)] ); - - if (!$document) { - return null; - } - - $model = $this->getAssembler()->assemble($document, $this->schema); - - return $model; } /** @@ -301,7 +282,7 @@ public function firstOrFail($query = [], array $projection = []) /** * {@inheritdoc} */ - public function getSchema(): AbstractSchema + public function getSchema(): ?AbstractSchema { return $this->schema; } @@ -314,25 +295,6 @@ public function setSchema(AbstractSchema $schema): void $this->schema = $schema; } - /** - * Parses an object with SchemaMapper and the given Schema. - * - * @param ModelInterface $model the object to be parsed - */ - public function parseToDocument(ModelInterface $model): array - { - $schemaMapper = $this->getSchemaMapper(); - $parsedDocument = $schemaMapper->map($model); - - if (is_object($model)) { - foreach ($parsedDocument as $field => $value) { - $model->$field = $value; - } - } - - return $parsedDocument; - } - /** * Returns a SchemaMapper with the $schema or $schemaClass instance. */ @@ -352,7 +314,7 @@ protected function getCollection(): Collection { $connection = $this->connection; $database = $connection->defaultDatabase; - $collection = $this->schema->collection; + $collection = $this->getSchema()->collection; return $connection->getRawConnection()->$database->$collection; } @@ -413,18 +375,6 @@ protected function prepareArrayFieldOfQuery(array $value): array return $value; } - /** - * Retrieves an ModelAssembler instance. - */ - protected function getAssembler(): ModelAssembler - { - if (!$this->assembler) { - $this->assembler = Ioc::make(ModelAssembler::class); - } - - return $this->assembler; - } - /** * Triggers an event. May return if that event had success. * diff --git a/src/Query/SchemaMapper.php b/src/Query/SchemaMapper.php index d63aba55..8cf15ed7 100644 --- a/src/Query/SchemaMapper.php +++ b/src/Query/SchemaMapper.php @@ -10,8 +10,6 @@ * When instantiating a SchemaMapper you should provide a Schema. When calling * 'map' the Schema provided will be used to format the data to the correct * format. - * - * This class is meant to do the opposite of the ModelAssembler */ class SchemaMapper { diff --git a/tests/Integration/EmbedsManyRelationTest.php b/tests/Integration/EmbedsManyRelationTest.php index 5d8e9575..77a938d3 100644 --- a/tests/Integration/EmbedsManyRelationTest.php +++ b/tests/Integration/EmbedsManyRelationTest.php @@ -54,7 +54,7 @@ public function testShouldRetrieveSiblingsOfUser() // changing the field directly $john->siblings()->add($bob); $this->assertSiblings([$bob], $john); - $john->embedded_siblings = [$chuck->toArray()]; + $john->embedded_siblings = [$chuck]; $this->assertSiblings([$chuck], $john); $john->siblings()->removeAll(); @@ -62,7 +62,7 @@ public function testShouldRetrieveSiblingsOfUser() // changing the field with fillable $john->siblings()->add($bob); $this->assertSiblings([$bob], $john); - $john->fill(['embedded_siblings' => [$chuck->toArray()]], true); + $john->fill(['embedded_siblings' => [$chuck]], true); $this->assertSiblings([$chuck], $john); } @@ -112,7 +112,7 @@ public function testShouldRetrieveGrandsonsOfUserUsingCustomKey() // changing the field directly $john->grandsons()->add($bob); $this->assertGrandsons([$bob], $john); - $john->other_arbitrary_field = [$chuck->toArray()]; + $john->other_arbitrary_field = [$chuck]; $this->assertGrandsons([$chuck], $john); $john->grandsons()->removeAll(); @@ -120,7 +120,7 @@ public function testShouldRetrieveGrandsonsOfUserUsingCustomKey() // changing the field with fillable $john->grandsons()->add($bob); $this->assertGrandsons([$bob], $john); - $john->fill(['other_arbitrary_field' => [$chuck->toArray()]], true); + $john->fill(['other_arbitrary_field' => [$chuck]], true); $this->assertGrandsons([$chuck], $john); } @@ -138,7 +138,7 @@ private function assertSiblings($expectedSiblings, EmbeddedUser $model) { $expected = []; foreach ($expectedSiblings as $sibling) { - $expected[] = $sibling->toArray(); + $expected[] = $sibling; $this->assertInstanceOf(UTCDateTime::class, $sibling->created_at); } @@ -158,7 +158,7 @@ private function assertGrandsons($expectedGrandsons, EmbeddedUser $model) { $expected = []; foreach ($expectedGrandsons as $grandson) { - $expected[] = $grandson->toArray(); + $expected[] = $grandson; $this->assertInstanceOf(UTCDateTime::class, $grandson->created_at); } diff --git a/tests/Integration/EmbedsOneRelationTest.php b/tests/Integration/EmbedsOneRelationTest.php index 9045949f..9ecb7019 100644 --- a/tests/Integration/EmbedsOneRelationTest.php +++ b/tests/Integration/EmbedsOneRelationTest.php @@ -45,7 +45,7 @@ public function testShouldRetrieveParentOfUser() // changing the field directly $john->parent()->add($bob); $this->assertParent($bob, $john); - $john->embedded_parent = [$chuck->toArray()]; + $john->embedded_parent = [$chuck]; $this->assertParent($chuck, $john); $john->parent()->removeAll(); @@ -53,7 +53,7 @@ public function testShouldRetrieveParentOfUser() // changing the field with fillable $john->parent()->add($bob); $this->assertParent($bob, $john); - $john->fill(['embedded_parent' => [$chuck->toArray()]], true); + $john->fill(['embedded_parent' => [$chuck]], true); $this->assertParent($chuck, $john); } @@ -93,7 +93,7 @@ public function testShouldRetrieveSonOfUserUsingCustomKey() // changing the field directly $john->son()->add($bob); $this->assertSon($bob, $john); - $john->arbitrary_field = [$chuck->toArray()]; + $john->arbitrary_field = [$chuck]; $this->assertSon($chuck, $john); $john->son()->removeAll(); @@ -101,7 +101,7 @@ public function testShouldRetrieveSonOfUserUsingCustomKey() // changing the field with fillable $john->son()->add($bob); $this->assertSon($bob, $john); - $john->fill(['arbitrary_field' => [$chuck->toArray()]], true); + $john->fill(['arbitrary_field' => [$chuck]], true); $this->assertSon($chuck, $john); } @@ -134,14 +134,14 @@ private function assertParent($expected, EmbeddedUser $model) $this->assertInstanceOf(EmbeddedUser::class, $parent); $this->assertInstanceOf(UTCDateTime::class, $parent->created_at); $this->assertEquals($expected, $parent); - $this->assertSame([$expected->toArray()], $model->embedded_parent); // TODO store as single array + $this->assertSame([$expected], $model->embedded_parent); // TODO store as single array // hit cache $parent = $model->parent; $this->assertInstanceOf(EmbeddedUser::class, $parent); $this->assertInstanceOf(UTCDateTime::class, $parent->created_at); $this->assertEquals($expected, $parent); - $this->assertSame([$expected->toArray()], $model->embedded_parent); + $this->assertSame([$expected], $model->embedded_parent); } private function assertSon($expected, EmbeddedUser $model) @@ -150,13 +150,13 @@ private function assertSon($expected, EmbeddedUser $model) $this->assertInstanceOf(EmbeddedUser::class, $son); $this->assertInstanceOf(UTCDateTime::class, $son->created_at); $this->assertEquals($expected, $son); - $this->assertSame([$expected->toArray()], $model->arbitrary_field); // TODO store as single array + $this->assertSame([$expected], $model->arbitrary_field); // TODO store as single array // hit cache $son = $model->son; $this->assertInstanceOf(EmbeddedUser::class, $son); $this->assertInstanceOf(UTCDateTime::class, $son->created_at); $this->assertEquals($expected, $son); - $this->assertSame([$expected->toArray()], $model->arbitrary_field); + $this->assertSame([$expected], $model->arbitrary_field); } } diff --git a/tests/Integration/PersistedDataTest.php b/tests/Integration/PersistedDataTest.php index d477b6ce..ff8c28e1 100644 --- a/tests/Integration/PersistedDataTest.php +++ b/tests/Integration/PersistedDataTest.php @@ -41,12 +41,13 @@ public function testSaveInsertingData() // Actions $saveResult = $user->save(); - $result = (array) $user->collection()->findOne(['_id' => $this->_id]); - $result['_id'] = (string) ($result['_id'] ?? ''); + $result = $user->collection()->findOne(['_id' => $this->_id]); + $result->_id = (string) $result->_id; // Assertions $this->assertTrue($saveResult); - $this->assertSame($expected, $result); + $this->assertInstanceOf(ReferencedUser::class, $result); + $this->assertSame($expected, $result->toArray()); } public function testSaveUpdatingData() @@ -82,12 +83,13 @@ public function testSaveUpdatingData() // Actions $updateResult = $user->save(); - $result = (array) $user->collection()->findOne(['_id' => $user->_id]); - $result['_id'] = (string) ($result['_id'] ?? ''); + $result = $user->collection()->findOne(['_id' => $user->_id]); + $result->_id = (string) $result->_id; // Assertions $this->assertTrue($updateResult); - $this->assertSame($expected, $result); + $this->assertInstanceOf(ReferencedUser::class, $result); + $this->assertSame($expected, $result->toArray()); } public function testUpdateData() @@ -123,12 +125,13 @@ public function testUpdateData() // Actions $updateResult = $user->update(); - $result = (array) $user->collection()->findOne(['_id' => $user->_id]); - $result['_id'] = (string) ($result['_id'] ?? ''); + $result = $user->collection()->findOne(['_id' => $user->_id]); + $result->_id = (string) $result->_id; // Assertions $this->assertTrue($updateResult); - $this->assertSame($expected, $result); + $this->assertInstanceOf(ReferencedUser::class, $result); + $this->assertSame($expected, $result->toArray()); } private function getUser(bool $save = false): ReferencedUser diff --git a/tests/Unit/Connection/ConnectionTest.php b/tests/Unit/Connection/ConnectionTest.php index 635cd893..fde7cc81 100644 --- a/tests/Unit/Connection/ConnectionTest.php +++ b/tests/Unit/Connection/ConnectionTest.php @@ -39,7 +39,7 @@ public function testShouldDetermineDatabaseFromACluster() public function testShouldGetRawConnection() { - // Arrange + // Set $server = 'mongodb://my-server/my_db'; $options = ['some', 'uri', 'options']; $driverOptions = ['some', 'driver', 'options']; @@ -51,11 +51,11 @@ public function testShouldGetRawConnection() ], ]; - // Act + // Actions $connection = new Connection($server, $options, $driverOptions); $rawConnection = $connection->getRawConnection(); - // Assert + // Assertions $this->assertAttributeEquals($expectedParameters['uri'], 'uri', $rawConnection); $this->assertAttributeEquals($expectedParameters['typeMap'], 'typeMap', $rawConnection); } diff --git a/tests/Unit/Cursor/EmbeddedCursorTest.php b/tests/Unit/Cursor/EmbeddedCursorTest.php index 207df334..995e167b 100644 --- a/tests/Unit/Cursor/EmbeddedCursorTest.php +++ b/tests/Unit/Cursor/EmbeddedCursorTest.php @@ -206,7 +206,37 @@ public function testShouldGetFirst() public function testShouldGetAllItems() { - // Arrange + // Set + $modelA = new class extends AbstractModel + { + }; + $modelA->name = 'A'; + $modelA->syncOriginalDocumentAttributes(); + $modelB = clone $modelA; + $modelB->name = 'B'; + $modelB->syncOriginalDocumentAttributes(); + + $items = [ + $modelA, + $modelB, + ]; + $cursor = $this->getCursor(get_class($modelA), $items); + $this->setProtected($cursor, 'position', 1); + + $expected = [ + $modelA, + $modelB, + ]; + + // Assert + $result = $cursor->all(); + + $this->assertEquals($expected, $result); + } + + public function testShouldGetAllItemsWithBackwardsCompatibility() + { + // Set $items = [ ['name' => 'A'], ['name' => 'B'], diff --git a/tests/Unit/Model/DocumentEmbedderTest.php b/tests/Unit/Model/DocumentEmbedderTest.php index 2ecb60e4..ae1df19d 100644 --- a/tests/Unit/Model/DocumentEmbedderTest.php +++ b/tests/Unit/Model/DocumentEmbedderTest.php @@ -41,7 +41,7 @@ public function testShouldEmbed($originalField, $modelFields, $method, $expectat } $expectedDocArray = (array) $expectedDoc; - $resultDocArray = (array) $result[$index]; + $resultDocArray = is_int($result[$index]) ? [$result[$index]] : $result[$index]->toArray(); foreach ($expectedDocArray as $field => $value) { if ($value instanceof Any) { $this->assertTrue(isset($resultDocArray[$field])); @@ -111,6 +111,17 @@ public function getEmbedOptions() ], ], + // ------------------------------ + 'attaching object with integer _id' => [ + 'originalField' => [6], + 'model' => [ + '_id' => 7, + 'name' => 'John Doe', + ], + 'method' => 'attach', + 'expectation' => [6, 7], + ], + // ------------------------------ 'attaching object with _id that is already attached' => [ 'originalField' => [ diff --git a/tests/Unit/Query/BuilderTest.php b/tests/Unit/Query/BuilderTest.php index f3c126eb..0058810e 100644 --- a/tests/Unit/Query/BuilderTest.php +++ b/tests/Unit/Query/BuilderTest.php @@ -39,22 +39,15 @@ public function testShouldSave($model, $writeConcern, $shouldFireEventAfter, $ex { // Arrange $connection = m::mock(Connection::class); - $builder = m::mock(Builder::class.'[parseToDocument,getCollection]', [$connection]); + $builder = m::mock(Builder::class.'[getCollection]', [$connection]); $options = ['writeConcern' => new WriteConcern($writeConcern)]; $collection = m::mock(Collection::class); - $parsedObject = ['_id' => 123]; $operationResult = m::mock(); - $model->_id = null; - // Act $builder->shouldAllowMockingProtectedMethods(); - $builder->expects() - ->parseToDocument($model) - ->andReturn($parsedObject); - $builder->expects() ->getCollection() ->andReturn($collection); @@ -62,7 +55,7 @@ public function testShouldSave($model, $writeConcern, $shouldFireEventAfter, $ex $collection->expects() ->replaceOne( ['_id' => 123], - $parsedObject, + $model, ['upsert' => true, 'writeConcern' => new WriteConcern($writeConcern)] )->andReturn($operationResult); @@ -97,11 +90,10 @@ public function testShouldInsert($model, $writeConcern, $shouldFireEventAfter, $ { // Arrange $connection = m::mock(Connection::class); - $builder = m::mock(Builder::class.'[parseToDocument,getCollection]', [$connection]); + $builder = m::mock(Builder::class.'[getCollection]', [$connection]); $options = ['writeConcern' => new WriteConcern($writeConcern)]; $collection = m::mock(Collection::class); - $parsedObject = ['_id' => 123]; $operationResult = m::mock(); $model->_id = null; @@ -109,16 +101,12 @@ public function testShouldInsert($model, $writeConcern, $shouldFireEventAfter, $ // Act $builder->shouldAllowMockingProtectedMethods(); - $builder->expects() - ->parseToDocument($model) - ->andReturn($parsedObject); - $builder->expects() ->getCollection() ->andReturn($collection); $collection->expects() - ->insertOne($parsedObject, ['writeConcern' => new WriteConcern($writeConcern)]) + ->insertOne($model, ['writeConcern' => new WriteConcern($writeConcern)]) ->andReturn($operationResult); $operationResult->expects() @@ -148,11 +136,10 @@ public function testShouldInsertWithoutFiringEvents($model, $writeConcern, $shou { // Arrange $connection = m::mock(Connection::class); - $builder = m::mock(Builder::class.'[parseToDocument,getCollection]', [$connection]); + $builder = m::mock(Builder::class.'[getCollection]', [$connection]); $options = ['writeConcern' => new WriteConcern($writeConcern)]; $collection = m::mock(Collection::class); - $parsedObject = ['_id' => 123]; $operationResult = m::mock(); $model->_id = null; @@ -160,16 +147,12 @@ public function testShouldInsertWithoutFiringEvents($model, $writeConcern, $shou // Act $builder->shouldAllowMockingProtectedMethods(); - $builder->expects() - ->parseToDocument($model) - ->andReturn($parsedObject); - $builder->expects() ->getCollection() ->andReturn($collection); $collection->expects() - ->insertOne($parsedObject, ['writeConcern' => new WriteConcern($writeConcern)]) + ->insertOne($model, ['writeConcern' => new WriteConcern($writeConcern)]) ->andReturn($operationResult); $operationResult->expects() @@ -203,21 +186,6 @@ public function testShouldUpdate($model, $writeConcern, $shouldFireEventAfter, $ $operationResult = m::mock(); $options = ['writeConcern' => new WriteConcern($writeConcern)]; - $this->instance( - AbstractSchema::class, - new class() extends AbstractSchema - { - /** - * {@inheritdoc} - */ - public $fields = [ - '_id' => 'objectId', - ]; - } - ); - - $model->_id = 123; - // Expect $connection->expects() ->getRawConnection() @@ -271,25 +239,18 @@ public function testShouldUpdateUnsettingFields() $model = new class extends AbstractModel { + /** + * {@inheritdoc} + */ + public $fields = [ + '_id' => 'objectId', + 'unchanged' => 'string', + ]; }; $collection = m::mock(Collection::class); $operationResult = m::mock(); $options = ['writeConcern' => new WriteConcern(1)]; - $this->instance( - AbstractSchema::class, - new class() extends AbstractSchema - { - /** - * {@inheritdoc} - */ - public $fields = [ - '_id' => 'objectId', - 'unchanged' => 'string', - ]; - } - ); - $model->unchanged = 'unchanged'; $model->notOnSchema = 'to be deleted'; $model->name = 'John'; @@ -313,7 +274,7 @@ public function testShouldUpdateUnsettingFields() $collection->expects() ->updateOne( ['_id' => 123], - ['$set' => ['_id' => 123], '$unset' => ['name' => '', 'notOnSchema' => '']], + ['$set' => ['_id' => 123], '$unset' => ['name' => '']], $options )->andReturn($operationResult); @@ -347,10 +308,9 @@ public function testUpdateShouldCallInsertWhenObjectHasNoId( ) { // Arrange $connection = m::mock(Connection::class); - $builder = m::mock(Builder::class.'[parseToDocument,getCollection]', [$connection]); + $builder = m::mock(Builder::class.'[getCollection]', [$connection]); $collection = m::mock(Collection::class); - $parsedObject = ['_id' => 123]; $operationResult = m::mock(); $options = ['writeConcern' => new WriteConcern($writeConcern)]; @@ -359,17 +319,13 @@ public function testUpdateShouldCallInsertWhenObjectHasNoId( // Act $builder->shouldAllowMockingProtectedMethods(); - $builder->expects() - ->parseToDocument($model) - ->andReturn($parsedObject); - $builder->expects() ->getCollection() ->andReturn($collection); $collection->expects() ->insertOne( - $parsedObject, + $model, ['writeConcern' => new WriteConcern($writeConcern)] )->andReturn($operationResult); @@ -403,22 +359,15 @@ public function testShouldDelete($model, $writeConcern, $shouldFireEventAfter, $ { // Arrange $connection = m::mock(Connection::class); - $builder = m::mock(Builder::class.'[parseToDocument,getCollection]', [$connection]); + $builder = m::mock(Builder::class.'[getCollection]', [$connection]); $collection = m::mock(Collection::class); - $parsedObject = ['_id' => 123]; $operationResult = m::mock(); $options = ['writeConcern' => new WriteConcern($writeConcern)]; - $model->_id = null; - // Act $builder->shouldAllowMockingProtectedMethods(); - $builder->expects() - ->parseToDocument($model) - ->andReturn($parsedObject); - $builder->expects() ->getCollection() ->andReturn($collection); @@ -457,17 +406,13 @@ public function testDatabaseOperationsShouldBailOutIfTheEventHandlerReturnsFalse ) { // Arrange $connection = m::mock(Connection::class); - $builder = m::mock(Builder::class.'[parseToDocument,getCollection]', [$connection]); + $builder = m::mock(Builder::class.'[getCollection]', [$connection]); $collection = m::mock(Collection::class); $model = m::mock(ModelInterface::class); $builder->shouldAllowMockingProtectedMethods(); // Expect - $builder->expects() - ->parseToDocument($model) - ->never(); - $builder->allows() ->getCollection() ->andReturn($collection); @@ -550,18 +495,12 @@ public function testShouldGetFirstWithQuery() // Arrange $connection = m::mock(Connection::class); $builder = m::mock(Builder::class.'[prepareValueQuery,getCollection]', [$connection]); - $schema = m::mock(AbstractSchema::class); $collection = m::mock(Collection::class); $query = 123; $preparedQuery = ['_id' => 123]; $object = new class extends AbstractModel { }; - - $class = get_class($object); - $schema->modelClass = $class; - $builder->setSchema($schema); - $builder->shouldAllowMockingProtectedMethods(); // Act @@ -575,13 +514,12 @@ public function testShouldGetFirstWithQuery() $collection->expects() ->findOne($preparedQuery, ['projection' => []]) - ->andReturn(['name' => 'John Doe']); + ->andReturn($object); $result = $builder->first($query); // Assert - $this->assertInstanceOf($class, $result); - $this->assertSame('John Doe', $result->name); + $this->assertSame($object, $result); } public function testFirstWithNullShouldNotHitTheDatabase() @@ -602,7 +540,6 @@ public function testFirstOrFailShouldGetFirst() // Arrange $connection = m::mock(Connection::class); $builder = m::mock(Builder::class.'[prepareValueQuery,getCollection]', [$connection]); - $schema = m::mock(AbstractSchema::class); $collection = m::mock(Collection::class); $query = 123; $preparedQuery = ['_id' => 123]; @@ -610,10 +547,6 @@ public function testFirstOrFailShouldGetFirst() { }; - $class = get_class($object); - $schema->modelClass = $class; - $builder->setSchema($schema); - $builder->shouldAllowMockingProtectedMethods(); // Act @@ -627,13 +560,12 @@ public function testFirstOrFailShouldGetFirst() $collection->expects() ->findOne($preparedQuery, ['projection' => []]) - ->andReturn(['name' => 'John Doe']); + ->andReturn($object); $result = $builder->firstOrFail($query); // Assert - $this->assertInstanceOf($class, $result); - $this->assertSame('John Doe', $result->name); + $this->assertSame($object, $result); } public function testFirstOrFailWithNullShouldFail() @@ -734,37 +666,6 @@ public function testShouldGetFirstProjectingFields() $this->assertNull($result); } - public function testShouldParseObjectToDocumentAndPutResultingIdIntoTheGivenObject() - { - // Arrange - $connection = m::mock(Connection::class); - $builder = m::mock(Builder::class.'[getSchemaMapper]', [$connection]); - $model = m::mock(ModelInterface::class); - $parsedDocument = ['a_field' => 123, '_id' => 'bacon']; - $schemaMapper = m::mock(SchemaMapper::class, [m::mock(AbstractSchema::class)]); - - $builder->shouldAllowMockingProtectedMethods(); - - // Expect - $builder->expects() - ->getSchemaMapper() - ->andReturn($schemaMapper); - - $schemaMapper->expects() - ->map($model) - ->andReturn($parsedDocument); - - // Act - $result = $this->callProtected($builder, 'parseToDocument', [$model]); - - // Assert - $this->assertSame($parsedDocument, $result); - $this->assertSame( - 'bacon', // Since this was the parsedDocument _id - $model->_id - ); - } - public function testShouldGetSchemaMapper() { // Arrange @@ -928,12 +829,27 @@ public function getWriteConcernVariations() { $model = new class extends AbstractModel { + /** + * {@inheritdoc} + */ + public $fields = [ + '_id' => 'objectId', + ]; }; $model2 = new class extends AbstractModel { + /** + * {@inheritdoc} + */ + public $fields = [ + '_id' => 'objectId', + ]; }; + $model->_id = 123; + $model2->_id = 123; + return [ 'acknowledged write concern ' => [ 'object' => $model, From cad488aef5f1f8522b7cf51f5082547d1d19875e Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 26 Nov 2018 16:50:22 -0200 Subject: [PATCH 073/116] Improve docker configuration --- docker-compose.yml | 2 +- docker/php/Dockerfile | 10 ++++++++++ docker/php/custom.ini | 4 ++++ 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 docker/php/Dockerfile create mode 100644 docker/php/custom.ini diff --git a/docker-compose.yml b/docker-compose.yml index 4d7c6db0..6d190663 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: '3' services: php: - image: leroymerlinbr/php + build: docker/php depends_on: - db volumes: diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile new file mode 100644 index 00000000..464546fd --- /dev/null +++ b/docker/php/Dockerfile @@ -0,0 +1,10 @@ +FROM leroymerlinbr/php:7.2 + +USER root + +RUN pecl install xdebug \ + && docker-php-ext-enable xdebug + +COPY custom.ini /usr/local/etc/php/conf.d/custom.ini + +USER www-data:www-data diff --git a/docker/php/custom.ini b/docker/php/custom.ini new file mode 100644 index 00000000..70a5c913 --- /dev/null +++ b/docker/php/custom.ini @@ -0,0 +1,4 @@ +error_reporting=E_ALL +log_errors=On +memory_limit=-1 +xdebug.default_enable=1 From b0b272f0c91955543d95731dafb1e802336fecd2 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 26 Nov 2018 16:54:05 -0200 Subject: [PATCH 074/116] Set schema for broken tests --- tests/Unit/Query/BuilderTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Unit/Query/BuilderTest.php b/tests/Unit/Query/BuilderTest.php index 0058810e..f15b8b3f 100644 --- a/tests/Unit/Query/BuilderTest.php +++ b/tests/Unit/Query/BuilderTest.php @@ -180,6 +180,7 @@ public function testShouldUpdate($model, $writeConcern, $shouldFireEventAfter, $ $client = m::mock(Client::class); $database = m::mock(Database::class); $builder = new Builder($connection); + $builder->setSchema($model->getSchema()); $collection = m::mock(Collection::class); $parsedObject = ['_id' => 123]; @@ -247,6 +248,7 @@ public function testShouldUpdateUnsettingFields() 'unchanged' => 'string', ]; }; + $builder->setSchema($model->getSchema()); $collection = m::mock(Collection::class); $operationResult = m::mock(); $options = ['writeConcern' => new WriteConcern(1)]; @@ -851,7 +853,7 @@ public function getWriteConcernVariations() $model2->_id = 123; return [ - 'acknowledged write concern ' => [ + 'acknowledged write concern' => [ 'object' => $model, 'writeConcern' => 1, 'shouldFireEventAfter' => true, From e368033b13d64baf69871f61e520952e91bb8ea4 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 26 Nov 2018 18:19:17 -0200 Subject: [PATCH 075/116] Simplify schema use, drop model assembler and polymorphable interface There is no more the need to assemble models, or to use polymorph. Persistable interface takes care of that, with the same funcionality and less performance hit. --- phpcs.xml | 2 - src/Connection/Manager.php | 6 +- src/Cursor/Cursor.php | 49 +-- src/Cursor/EmbeddedCursor.php | 52 +--- src/Model/AbstractModel.php | 9 +- src/Model/ModelInterface.php | 4 +- src/Model/PolymorphableInterface.php | 47 --- src/Model/Relations/EmbedsMany.php | 8 +- src/Model/Relations/EmbedsOne.php | 6 +- src/Query/Builder.php | 10 +- src/Query/BulkWrite.php | 4 +- src/Query/ModelAssembler.php | 110 ------- src/Query/SchemaMapper.php | 8 +- src/Schema/AbstractSchema.php | 124 -------- src/Schema/DynamicSchema.php | 119 +++++++- tests/Unit/Connection/ManagerTest.php | 6 +- tests/Unit/Cursor/CursorTest.php | 44 +-- tests/Unit/Cursor/EmbeddedCursorTest.php | 125 ++++---- tests/Unit/Model/AbstractModelTest.php | 7 +- tests/Unit/Model/HasRelationsTraitTest.php | 34 ++- tests/Unit/Query/BuilderTest.php | 14 +- tests/Unit/Query/BulkWriteTest.php | 4 +- tests/Unit/Query/ModelAssemblerTest.php | 282 ------------------ ...emaMapperTest.php => SchemaMapperTest.php} | 41 ++- tests/Unit/Schema/AbstractSchemaTest.php | 173 ----------- tests/Unit/Schema/DynamicSchemaTest.php | 159 +++++++++- 26 files changed, 422 insertions(+), 1025 deletions(-) delete mode 100644 src/Model/PolymorphableInterface.php delete mode 100644 src/Query/ModelAssembler.php delete mode 100644 src/Schema/AbstractSchema.php delete mode 100644 tests/Unit/Query/ModelAssemblerTest.php rename tests/Unit/Query/{AbstractSchemaMapperTest.php => SchemaMapperTest.php} (90%) delete mode 100644 tests/Unit/Schema/AbstractSchemaTest.php diff --git a/phpcs.xml b/phpcs.xml index 7d1cb137..4c952b8d 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -17,12 +17,10 @@ - tests/Unit/Query/ModelAssemblerTest.php tests/Unit/Model/HasRelationsTraitTest.php - tests/Unit/Query/ModelAssemblerTest.php tests/Unit/Model/HasRelationsTraitTest.php diff --git a/src/Connection/Manager.php b/src/Connection/Manager.php index 1182f842..3649822f 100644 --- a/src/Connection/Manager.php +++ b/src/Connection/Manager.php @@ -6,7 +6,7 @@ use Mongolid\Event\EventTriggerInterface; use Mongolid\Event\EventTriggerService; use Mongolid\Query\Builder; -use Mongolid\Schema\AbstractSchema; +use Mongolid\Schema\DynamicSchema; /** * Wraps the Mongolid initialization. The main purpose of the Manager is to make @@ -95,9 +95,9 @@ public function setEventTrigger(EventTriggerInterface $eventTrigger) /** * Allow document Schemas to be registered for later use. * - * @param AbstractSchema $schema schema being registered + * @param DynamicSchema $schema schema being registered */ - public function registerSchema(AbstractSchema $schema) + public function registerSchema(DynamicSchema $schema) { $this->schemas[$schema->modelClass] = $schema; } diff --git a/src/Cursor/Cursor.php b/src/Cursor/Cursor.php index 074a0fec..7cea9662 100644 --- a/src/Cursor/Cursor.php +++ b/src/Cursor/Cursor.php @@ -10,9 +10,7 @@ use MongoDB\Model\CachingIterator; use Mongolid\Connection\Connection; use Mongolid\Container\Ioc; -use Mongolid\Model\AbstractModel; -use Mongolid\Query\ModelAssembler; -use Mongolid\Schema\AbstractSchema; +use Mongolid\Schema\DynamicSchema; use Serializable; /** @@ -64,20 +62,13 @@ class Cursor implements CursorInterface, Serializable protected $position = 0; /** - * Have the responsibility of assembling the data coming from the database into actual entities. - * - * @var ModelAssembler - */ - protected $assembler; - - /** - * @param AbstractSchema $modelSchema schema that describes the model that will be retrieved from the database - * @param Collection $collection the raw collection object that will be used to retrieve the documents - * @param string $command the command that is being called in the $collection - * @param array $params the parameters of the $command + * @param DynamicSchema $modelSchema schema that describes the model that will be retrieved from the database + * @param Collection $collection the raw collection object that will be used to retrieve the documents + * @param string $command the command that is being called in the $collection + * @param array $params the parameters of the $command */ public function __construct( - AbstractSchema $modelSchema, + DynamicSchema $modelSchema, Collection $collection, string $command, array $params @@ -199,21 +190,9 @@ public function rewind() */ public function current() { - if (!$document = $this->getCursor()->current()) { - return null; - } - - if ($document instanceof AbstractModel) { - $documentToArray = $document->toArray(); - $this->modelSchema = $document->getSchema(); - } else { - $documentToArray = (array) $document; - } + $cursor = $this->getCursor(); - return $this->getAssembler()->assemble( - $documentToArray, - $this->modelSchema - ); + return $cursor->valid() ? $cursor->current() : null; } /** @@ -342,16 +321,4 @@ protected function getCursor(): Iterator return $this->cursor; } - - /** - * Retrieves an ModelAssembler instance. - */ - protected function getAssembler(): ModelAssembler - { - if (!$this->assembler) { - $this->assembler = Ioc::make(ModelAssembler::class); - } - - return $this->assembler; - } } diff --git a/src/Cursor/EmbeddedCursor.php b/src/Cursor/EmbeddedCursor.php index a2caf021..7bd9e1ad 100644 --- a/src/Cursor/EmbeddedCursor.php +++ b/src/Cursor/EmbeddedCursor.php @@ -1,12 +1,6 @@ items = $items; - $this->modelClass = $modelClass; } /** @@ -133,38 +118,7 @@ public function rewind() */ public function current() { - if (!$this->valid()) { - return null; - } - - $document = $this->items[$this->position]; - - if ($document instanceof $this->modelClass) { - return $document; - } - - $schema = $this->getSchemaForModel(); - $modelAssembler = Ioc::make(ModelAssembler::class, compact('schema')); - - return $modelAssembler->assemble($document, $schema); - } - - /** - * Retrieve a schema based on Model Class. - */ - protected function getSchemaForModel(): AbstractSchema - { - if ($this->modelClass instanceof AbstractSchema) { - return $this->modelClass; - } - - $model = new $this->modelClass(); - - if ($model instanceof AbstractModel) { - return $model->getSchema(); - } - - return new DynamicSchema(); + return $this->items[$this->position] ?? null; } /** diff --git a/src/Model/AbstractModel.php b/src/Model/AbstractModel.php index c3ee8dc9..284fd31f 100644 --- a/src/Model/AbstractModel.php +++ b/src/Model/AbstractModel.php @@ -8,7 +8,6 @@ use Mongolid\Model\Exception\NoCollectionNameException; use Mongolid\Query\Builder; use Mongolid\Query\SchemaMapper; -use Mongolid\Schema\AbstractSchema; use Mongolid\Schema\DynamicSchema; /** @@ -52,7 +51,7 @@ abstract class AbstractModel implements ModelInterface * Describes the Schema fields of the model. Optionally you can set it to * the name of a Schema class to be used. * - * @see \Mongolid\Schema\AbstractSchema::$fields + * @see \Mongolid\Schema\DynamicSchema::$fields * * @var string|string[] */ @@ -256,7 +255,7 @@ public function setWriteConcern(int $writeConcern): void /** * {@inheritdoc} */ - public function getSchema(): AbstractSchema + public function getSchema(): DynamicSchema { if ($schema = $this->instantiateSchemaInFields()) { return $schema; @@ -296,10 +295,10 @@ public function bsonUnserialize(array $data) * Will check if the current value of $fields property is the name of a * Schema class and instantiate it if possible. */ - protected function instantiateSchemaInFields(): ?AbstractSchema + protected function instantiateSchemaInFields(): ?DynamicSchema { if (is_string($this->fields)) { - if (is_subclass_of($instance = Ioc::make($this->fields), AbstractSchema::class)) { + if (is_subclass_of($instance = Ioc::make($this->fields), DynamicSchema::class)) { return $instance; } } diff --git a/src/Model/ModelInterface.php b/src/Model/ModelInterface.php index b56f6731..dfb60eac 100644 --- a/src/Model/ModelInterface.php +++ b/src/Model/ModelInterface.php @@ -2,12 +2,12 @@ namespace Mongolid\Model; use MongoDB\BSON\Persistable; -use Mongolid\Schema\AbstractSchema; +use Mongolid\Schema\DynamicSchema; interface ModelInterface extends HasAttributesInterface, Persistable { /** * Returns a Schema object that describes an Model in MongoDB. */ - public function getSchema(): AbstractSchema; + public function getSchema(): DynamicSchema; } diff --git a/src/Model/PolymorphableInterface.php b/src/Model/PolymorphableInterface.php deleted file mode 100644 index cf99af79..00000000 --- a/src/Model/PolymorphableInterface.php +++ /dev/null @@ -1,47 +0,0 @@ -type === 'video') { - * $video = new VideoContent(); - * $video->fill($this->getDocumentAttributes()); - * - * return $video; - * } - * elseif ($this->type === 'article') { - * $article = new ArticleContent(); - * $article->fill($this->getDocumentAttributes()); - * - * return $article; - * } - * - * return $this; - * } - * - * In the example above, if you call Content::first() and the content - * returned have the key video set, then the object returned will be - * a VideoContent instead of a Content. - * - * @return PolymorphableInterface - */ - public function polymorph(); -} diff --git a/src/Model/Relations/EmbedsMany.php b/src/Model/Relations/EmbedsMany.php index c87e5124..293df8b2 100644 --- a/src/Model/Relations/EmbedsMany.php +++ b/src/Model/Relations/EmbedsMany.php @@ -62,17 +62,17 @@ public function removeAll(): void */ public function get() { - $items = (array) $this->parent->{$this->field}; + $items = $this->parent->{$this->field} ?? []; - if (!empty($items) && !array_key_exists(0, $items)) { + if (is_object($items)) { $items = [$items]; } return $this->createCursor($items); } - protected function createCursor($items): EmbeddedCursor + protected function createCursor(array $items): EmbeddedCursor { - return new EmbeddedCursor($this->model, $items); + return new EmbeddedCursor($items); } } diff --git a/src/Model/Relations/EmbedsOne.php b/src/Model/Relations/EmbedsOne.php index 80b27ab6..72ab5ae1 100644 --- a/src/Model/Relations/EmbedsOne.php +++ b/src/Model/Relations/EmbedsOne.php @@ -21,10 +21,10 @@ public function remove(ModelInterface $model = null): void */ public function get() { - $items = (array) $this->parent->{$this->field}; + $items = $this->parent->{$this->field} ?? []; - if (!empty($items) && !array_key_exists(0, $items)) { - $items = [$items]; + if (is_object($items)) { + return $items; } return $this->createCursor($items)->first(); diff --git a/src/Query/Builder.php b/src/Query/Builder.php index 5237fa72..6a4605be 100644 --- a/src/Query/Builder.php +++ b/src/Query/Builder.php @@ -11,7 +11,7 @@ use Mongolid\Event\EventTriggerService; use Mongolid\Model\Exception\ModelNotFoundException; use Mongolid\Model\ModelInterface; -use Mongolid\Schema\AbstractSchema; +use Mongolid\Schema\DynamicSchema; use Mongolid\Util\ObjectIdUtils; /** @@ -27,12 +27,12 @@ class Builder * * @var string */ - public $schemaClass = AbstractSchema::class; + public $schemaClass = DynamicSchema::class; /** * Schema object. Will be set after the $schemaClass. * - * @var AbstractSchema + * @var DynamicSchema */ protected $schema; @@ -282,7 +282,7 @@ public function firstOrFail($query = [], array $projection = []) /** * {@inheritdoc} */ - public function getSchema(): ?AbstractSchema + public function getSchema(): ?DynamicSchema { return $this->schema; } @@ -290,7 +290,7 @@ public function getSchema(): ?AbstractSchema /** * Set a Schema object that describes an Model in MongoDB. */ - public function setSchema(AbstractSchema $schema): void + public function setSchema(DynamicSchema $schema): void { $this->schema = $schema; } diff --git a/src/Query/BulkWrite.php b/src/Query/BulkWrite.php index eeb0312f..85a47963 100644 --- a/src/Query/BulkWrite.php +++ b/src/Query/BulkWrite.php @@ -7,7 +7,7 @@ use Mongolid\Connection\Connection; use Mongolid\Container\Ioc; use Mongolid\Model\ModelInterface; -use Mongolid\Schema\AbstractSchema; +use Mongolid\Schema\DynamicSchema; /** * This class is meant to provide a better API for handling @@ -30,7 +30,7 @@ class BulkWrite protected $bulkWrite; /** - * @var AbstractSchema + * @var DynamicSchema */ protected $schema; diff --git a/src/Query/ModelAssembler.php b/src/Query/ModelAssembler.php deleted file mode 100644 index 890be429..00000000 --- a/src/Query/ModelAssembler.php +++ /dev/null @@ -1,110 +0,0 @@ -modelClass; - $model = Ioc::make($modelClass); - - foreach ($document as $field => $value) { - $fieldType = $schema->fields[$field] ?? null; - - if ($fieldType && 'schema.' == substr($fieldType, 0, 7)) { - $value = $this->assembleDocumentsRecursively($value, substr($fieldType, 7)); - } - - $model->$field = $value; - } - - $model = $this->morphingTime($model); - - return $this->prepareOriginalAttributes($model); - } - - /** - * Returns the return of polymorph method of the given model if available. - * - * @see \Mongolid\Model\PolymorphableInterface::polymorph - * @see https://i.ytimg.com/vi/TFGN9kAjdis/maxresdefault.jpg - * - * @param mixed $model the model that may or may not have a polymorph method - * - * @return mixed the result of $model->polymorph or the $model itself - */ - protected function morphingTime(ModelInterface $model): ModelInterface - { - if ($model instanceof PolymorphableInterface) { - return $model->polymorph(); - } - - return $model; - } - - /** - * Stores original attributes from Model if needed. - * - * @param mixed $model the model that may have the attributes stored - * - * @return mixed the model with original attributes - */ - protected function prepareOriginalAttributes(ModelInterface $model) - { - $model->syncOriginalDocumentAttributes(); - - return $model; - } - - /** - * Assembly multiple documents for the given $schemaClass recursively. - * - * @param mixed $value a value of an embedded field containing model data to be assembled - * @param string $schemaClass the schemaClass to be used when assembling the entities within $value - * - * @return mixed - */ - protected function assembleDocumentsRecursively($value, string $schemaClass) - { - $value = (array) $value; - - if (empty($value)) { - return; - } - - $schema = Ioc::make($schemaClass); - $assembler = Ioc::make(static::class); - - if (!isset($value[0])) { - $value = [$value]; - } - - foreach ($value as $key => $subValue) { - $value[$key] = $assembler->assemble($subValue, $schema); - } - - return $value; - } -} diff --git a/src/Query/SchemaMapper.php b/src/Query/SchemaMapper.php index 8cf15ed7..5820c1aa 100644 --- a/src/Query/SchemaMapper.php +++ b/src/Query/SchemaMapper.php @@ -3,7 +3,7 @@ use Mongolid\Container\Ioc; use Mongolid\Model\HasAttributesInterface; -use Mongolid\Schema\AbstractSchema; +use Mongolid\Schema\DynamicSchema; /** * The SchemaMapper will map an object or an array of data to a Schema object. @@ -16,7 +16,7 @@ class SchemaMapper /** * The actual schema to maps the data. * - * @var AbstractSchema + * @var DynamicSchema */ public $schema; @@ -30,9 +30,9 @@ class SchemaMapper protected $castableTypes = ['int', 'integer', 'bool', 'boolean', 'float', 'double', 'real', 'string']; /** - * @param AbstractSchema $schema schema that will be used to map each field + * @param DynamicSchema $schema schema that will be used to map each field */ - public function __construct(AbstractSchema $schema) + public function __construct(DynamicSchema $schema) { $this->schema = $schema; } diff --git a/src/Schema/AbstractSchema.php b/src/Schema/AbstractSchema.php deleted file mode 100644 index 9aa772c5..00000000 --- a/src/Schema/AbstractSchema.php +++ /dev/null @@ -1,124 +0,0 @@ -' This represents an embedded document (or - * sub-document). - * - * @var string[] - */ - public $fields = [ - '_id' => 'objectId', // Means that the _id will pass trough the `objectId` method - 'created_at' => 'createdAtTimestamp', // Generates an automatic timestamp - 'updated_at' => 'updatedAtTimestamp', - ]; - - /** - * Name of the class that will be used to represent a document of this - * Schema when retrieve from the database. - * - * @var string - */ - public $modelClass = stdClass::class; - - /** - * Filters any field in the $fields that has it's value specified as a - * 'objectId'. It will wraps the $value, if any, into a ObjectId object. - * - * @param mixed $value value that may be converted to ObjectId - * - * @return ObjectId|mixed - */ - public function objectId($value = null) - { - if (null === $value) { - return new ObjectId(); - } - - if (is_string($value) && ObjectIdUtils::isObjectId($value)) { - $value = new ObjectId($value); - } - - return $value; - } - - /** - * Prepares the field to have a sequence. If $value is zero or not defined - * a new auto-increment number will be "generated" for the collection of - * the schema. The sequence generation is done by the SequenceService. - * - * @param int|null $value value that will be evaluated - * - * @return int - */ - public function sequence(int $value = null) - { - if ($value) { - return $value; - } - - return Ioc::make(SequenceService::class) - ->getNextValue($this->collection ?: $this->modelClass); - } - - /** - * Prepares the field to be the datetime that the document has been created. - * - * @param mixed|null $value value that will be evaluated - * - * @return UTCDateTime - */ - public function createdAtTimestamp($value) - { - if ($value instanceof UTCDateTime) { - return $value; - } - - return $this->updatedAtTimestamp(); - } - - /** - * Prepares the field to be now. - * - * @return UTCDateTime - */ - public function updatedAtTimestamp() - { - return new UTCDateTime(); - } -} diff --git a/src/Schema/DynamicSchema.php b/src/Schema/DynamicSchema.php index bdd43275..ae94ff28 100644 --- a/src/Schema/DynamicSchema.php +++ b/src/Schema/DynamicSchema.php @@ -1,15 +1,124 @@ ' This represents an embedded document (or + * sub-document). + * + * @var string[] + */ + public $fields = [ + '_id' => 'objectId', // Means that the _id will pass trough the `objectId` method + 'created_at' => 'createdAtTimestamp', // Generates an automatic timestamp + 'updated_at' => 'updatedAtTimestamp', + ]; + + /** + * Name of the class that will be used to represent a document of this + * Schema when retrieve from the database. + * + * @var string + */ + public $modelClass = stdClass::class; + + /** + * Filters any field in the $fields that has it's value specified as a + * 'objectId'. It will wraps the $value, if any, into a ObjectId object. + * + * @param mixed $value value that may be converted to ObjectId + * + * @return ObjectId|mixed + */ + public function objectId($value = null) + { + if (null === $value) { + return new ObjectId(); + } + + if (is_string($value) && ObjectIdUtils::isObjectId($value)) { + $value = new ObjectId($value); + } + + return $value; + } + + /** + * Prepares the field to have a sequence. If $value is zero or not defined + * a new auto-increment number will be "generated" for the collection of + * the schema. The sequence generation is done by the SequenceService. + * + * @param int|null $value value that will be evaluated + * + * @return int + */ + public function sequence(int $value = null) + { + if ($value) { + return $value; + } + + return Ioc::make(SequenceService::class) + ->getNextValue($this->collection ?: $this->modelClass); + } + + /** + * Prepares the field to be the datetime that the document has been created. + * + * @param mixed|null $value value that will be evaluated + * + * @return UTCDateTime + */ + public function createdAtTimestamp($value) + { + if ($value instanceof UTCDateTime) { + return $value; + } + + return $this->updatedAtTimestamp(); + } + + /** + * Prepares the field to be now. + * + * @return UTCDateTime + */ + public function updatedAtTimestamp() + { + return new UTCDateTime(); + } } diff --git a/tests/Unit/Connection/ManagerTest.php b/tests/Unit/Connection/ManagerTest.php index b08b433f..75f4498d 100644 --- a/tests/Unit/Connection/ManagerTest.php +++ b/tests/Unit/Connection/ManagerTest.php @@ -7,7 +7,7 @@ use Mongolid\Event\EventTriggerInterface; use Mongolid\Event\EventTriggerService; use Mongolid\Query\Builder; -use Mongolid\Schema\AbstractSchema; +use Mongolid\Schema\DynamicSchema; use Mongolid\TestCase; class ManagerTest extends TestCase @@ -63,7 +63,7 @@ public function testShouldRegisterSchema() { // Arrange $manager = new Manager(); - $schema = m::mock(AbstractSchema::class); + $schema = m::mock(DynamicSchema::class); $schema->modelClass = 'Bacon'; // Assert @@ -79,7 +79,7 @@ public function testShouldGetDataMapperForEntitiesWithRegisteredSchemas() { // Arrange $manager = new Manager(); - $schema = m::mock(AbstractSchema::class); + $schema = m::mock(DynamicSchema::class); $builder = $this->instance(Builder::class, m::mock(Builder::class)->makePartial()); $schema->modelClass = 'Bacon'; diff --git a/tests/Unit/Cursor/CursorTest.php b/tests/Unit/Cursor/CursorTest.php index 3bef0def..51ce1ce3 100644 --- a/tests/Unit/Cursor/CursorTest.php +++ b/tests/Unit/Cursor/CursorTest.php @@ -11,7 +11,6 @@ use MongoDB\Model\CachingIterator; use Mongolid\Connection\Connection; use Mongolid\Model\AbstractModel; -use Mongolid\Schema\AbstractSchema; use Mongolid\Schema\DynamicSchema; use Mongolid\TestCase; use Serializable; @@ -162,24 +161,6 @@ function () use ($cursor) { } public function testShouldGetCurrent() - { - // Arrange - $collection = m::mock(Collection::class); - $object = new class extends AbstractModel - { - }; - $driverCursor = new CachingIterator(new ArrayObject([['name' => 'John Doe']])); - $cursor = $this->getCursor($object->getSchema(), $collection, 'find', [[]], $driverCursor); - - // Act - $model = $cursor->current(); - - // Assert - $this->assertInstanceOf(get_class($object), $model); - $this->assertSame('John Doe', $model->name); - } - - public function testShouldGetCurrentUsingModelClasses() { // Arrange $collection = m::mock(Collection::class); @@ -203,7 +184,8 @@ public function testShouldGetFirst() $object = new class extends AbstractModel { }; - $driverCursor = new CachingIterator(new ArrayObject([['name' => 'John Doe']])); + $object->name = 'John Doe'; + $driverCursor = new CachingIterator(new ArrayObject([$object])); $cursor = $this->getCursor($object->getSchema(), $collection, 'find', [[]], $driverCursor); // Act @@ -330,17 +312,19 @@ public function testShouldReturnAllResults() { // Arrange $collection = m::mock(Collection::class); - $driverCursor = new CachingIterator( - new ArrayObject( - [ - ['name' => 'bob', 'occupation' => 'coder'], - ['name' => 'jef', 'occupation' => 'tester'], - ] - ) - ); $object = new class extends AbstractModel { }; + $class = get_class($object); + $bob = new $class(); + $bob->name = 'bob'; + $bob->occupation = 'coder'; + + $jef = new $class(); + $jef->name = 'jef'; + $jef->occupation = 'tester'; + + $driverCursor = new CachingIterator(new ArrayObject([$bob, $jef])); $cursor = $this->getCursor($object->getSchema(), $collection, 'find', [[]], $driverCursor); // Actions @@ -348,7 +332,7 @@ public function testShouldReturnAllResults() // Assertions $this->assertCount(2, $result); - $this->assertContainsOnlyInstancesOf(get_class($object), $result); + $this->assertContainsOnlyInstancesOf($class, $result); $firstModel = $result[0]; $this->assertSame('bob', $firstModel->name); @@ -432,7 +416,7 @@ protected function getCursor( $driverCursor = null ) { if (!$modelSchema) { - $modelSchema = m::mock(AbstractSchema::class.'[]'); + $modelSchema = m::mock(DynamicSchema::class.'[]'); } if (!$collection) { diff --git a/tests/Unit/Cursor/EmbeddedCursorTest.php b/tests/Unit/Cursor/EmbeddedCursorTest.php index 995e167b..8279a4fe 100644 --- a/tests/Unit/Cursor/EmbeddedCursorTest.php +++ b/tests/Unit/Cursor/EmbeddedCursorTest.php @@ -2,7 +2,6 @@ namespace Mongolid\Cursor; use Mongolid\Model\AbstractModel; -use Mongolid\Model\PolymorphableInterface; use Mongolid\TestCase; use stdClass; @@ -16,7 +15,7 @@ public function testShouldLimitDocumentQuantity() ['name' => 'B'], ['name' => 'C'], ]; - $cursor = $this->getCursor(stdClass::class, $items); + $cursor = new EmbeddedCursor($items); // Assert $cursor->limit(2); @@ -36,7 +35,7 @@ public function testShouldLimitDocumentQuantity() public function testShouldSortDocuments($items, $parameters, $expected) { // Arrange - $cursor = $this->getCursor(stdClass::class, $items); + $cursor = new EmbeddedCursor($items); // Assert $cursor->sort($parameters); @@ -55,7 +54,7 @@ public function testShouldSkipDocuments() ['name' => 'B'], ['name' => 'C'], ]; - $cursor = $this->getCursor(stdClass::class, $items); + $cursor = new EmbeddedCursor($items); // Assert $cursor->skip(2); @@ -76,7 +75,7 @@ public function testShouldCountDocuments() ['name' => 'B'], ['name' => 'C'], ]; - $cursor = $this->getCursor(stdClass::class, $items); + $cursor = new EmbeddedCursor($items); // Assert $this->assertEquals(3, $cursor->count()); @@ -90,7 +89,7 @@ public function testShouldCountDocumentsWithCountFunction() ['name' => 'B'], ['name' => 'C'], ]; - $cursor = $this->getCursor(stdClass::class, $items); + $cursor = new EmbeddedCursor($items); // Assert $this->assertEquals(3, count($cursor)); @@ -99,7 +98,7 @@ public function testShouldCountDocumentsWithCountFunction() public function testShouldRewind() { // Arrange - $cursor = $this->getCursor(); + $cursor = new EmbeddedCursor([]); // Assert $cursor->rewind(); @@ -113,12 +112,21 @@ public function testShouldGetCurrent() { }; $class = get_class($object); + $itemA = new $class(); + $itemA->name = 'A'; + + $itemB = new $class(); + $itemB->name = 'B'; + + $itemC = new $class(); + $itemC->name = 'C'; + $items = [ - ['name' => 'A'], - ['name' => 'B'], - ['name' => 'C'], + $itemA, + $itemB, + $itemC, ]; - $cursor = $this->getCursor($class, $items); + $cursor = new EmbeddedCursor($items); $this->setProtected($cursor, 'position', 1); @@ -132,7 +140,7 @@ public function testShouldNotGetCurrentWhenCursorIsInvalid() { // Arrange $items = []; - $cursor = $this->getCursor(stdClass::class, $items); + $cursor = new EmbeddedCursor($items); $this->setProtected($cursor, 'position', 1); @@ -147,7 +155,7 @@ public function testShouldGetCurrentUsingModelClass() $object = new stdClass(); $object->name = 'A'; $items = [$object]; - $cursor = $this->getCursor(stdClass::class, $items); + $cursor = new EmbeddedCursor($items); $this->setProtected($cursor, 'position', 0); @@ -157,59 +165,40 @@ public function testShouldGetCurrentUsingModelClass() $this->assertAttributeEquals('A', 'name', $model); } - public function testShouldGetCurrentUsingModelClassAndMorphingIt() + public function testShouldGetCurrentUsingModelClassMorphingIt() { // Arrange - $object = new class() extends AbstractModel implements PolymorphableInterface + $object = new class() extends AbstractModel + { + }; + + // Other class (mimics polymorph behavior) + $model = new class() extends AbstractModel { - public function polymorph() - { - return $this; - } }; - $object->name = 'John'; - $object->syncOriginalDocumentAttributes(); + $model->name = 'John'; + $model->syncOriginalDocumentAttributes(); $class = get_class($object); - $items = [$object->getDocumentAttributes()]; - $cursor = $this->getCursor($class, $items); + $items = [$model]; + $cursor = new EmbeddedCursor($items); // Actions - $model = $cursor->current(); + $result = $cursor->current(); // Assertions - $this->assertEquals($object, $model); - $this->assertSame('John', $model->name); + $this->assertEquals($model, $result); + $this->assertSame('John', $result->name); } public function testShouldGetFirst() { // Arrange - $items = [ - ['name' => 'A'], - ['name' => 'B'], - ['name' => 'C'], - ]; $object = new class extends AbstractModel { }; $class = get_class($object); - $cursor = $this->getCursor($class, $items); - - $this->setProtected($cursor, 'position', 1); - - // Assert - $model = $cursor->first(); - $this->assertInstanceOf($class, $model); - $this->assertSame('A', $model->name); - } - - public function testShouldGetAllItems() - { - // Set - $modelA = new class extends AbstractModel - { - }; + $modelA = new $class(); $modelA->name = 'A'; $modelA->syncOriginalDocumentAttributes(); $modelB = clone $modelA; @@ -220,28 +209,19 @@ public function testShouldGetAllItems() $modelA, $modelB, ]; - $cursor = $this->getCursor(get_class($modelA), $items); - $this->setProtected($cursor, 'position', 1); + $cursor = new EmbeddedCursor($items); - $expected = [ - $modelA, - $modelB, - ]; + $this->setProtected($cursor, 'position', 1); // Assert - $result = $cursor->all(); - - $this->assertEquals($expected, $result); + $model = $cursor->first(); + $this->assertInstanceOf($class, $model); + $this->assertSame('A', $model->name); } - public function testShouldGetAllItemsWithBackwardsCompatibility() + public function testShouldGetAllItems() { // Set - $items = [ - ['name' => 'A'], - ['name' => 'B'], - ]; - $modelA = new class extends AbstractModel { }; @@ -251,7 +231,11 @@ public function testShouldGetAllItemsWithBackwardsCompatibility() $modelB->name = 'B'; $modelB->syncOriginalDocumentAttributes(); - $cursor = $this->getCursor(get_class($modelA), $items); + $items = [ + $modelA, + $modelB, + ]; + $cursor = new EmbeddedCursor($items); $this->setProtected($cursor, 'position', 1); $expected = [ @@ -273,7 +257,7 @@ public function testShouldGetAllInArrayFormat() ['name' => 'B'], ['name' => 'C'], ]; - $cursor = $this->getCursor(stdClass::class, $items); + $cursor = new EmbeddedCursor($items); $this->setProtected($cursor, 'position', 1); @@ -285,7 +269,7 @@ public function testShouldGetAllInArrayFormat() public function testShouldImplementKeyMethodFromIterator() { // Arrange - $cursor = $this->getCursor(); + $cursor = new EmbeddedCursor([]); $this->setProtected($cursor, 'position', 7); @@ -296,7 +280,7 @@ public function testShouldImplementKeyMethodFromIterator() public function testShouldImplementNextMethodFromIterator() { // Arrange - $cursor = $this->getCursor(); + $cursor = new EmbeddedCursor([]); $this->setProtected($cursor, 'position', 7); @@ -313,7 +297,7 @@ public function testShouldImplementValidMethodFromIterator() ['name' => 'B'], ['name' => 'C'], ]; - $cursor = $this->getCursor(stdClass::class, $items); + $cursor = new EmbeddedCursor($items); // Assert $this->assertTrue($cursor->valid()); @@ -404,11 +388,4 @@ public function getDocumentsToSort() ], ]; } - - protected function getCursor( - $modelClass = stdClass::class, - $items = [] - ) { - return new EmbeddedCursor($modelClass, $items); - } } diff --git a/tests/Unit/Model/AbstractModelTest.php b/tests/Unit/Model/AbstractModelTest.php index 3d772c5d..b1b193d8 100644 --- a/tests/Unit/Model/AbstractModelTest.php +++ b/tests/Unit/Model/AbstractModelTest.php @@ -6,7 +6,6 @@ use Mongolid\Cursor\CursorInterface; use Mongolid\Model\Exception\NoCollectionNameException; use Mongolid\Query\Builder; -use Mongolid\Schema\AbstractSchema; use Mongolid\Schema\DynamicSchema; use Mongolid\TestCase; use stdClass; @@ -286,7 +285,7 @@ public function testShouldGetSchemaIfFieldsIsTheClassName() { // Set $this->model->setFields('MySchemaClass'); - $schema = $this->instance('MySchemaClass', m::mock(AbstractSchema::class)); + $schema = $this->instance('MySchemaClass', m::mock(DynamicSchema::class)); // Assertions $this->assertSame( @@ -303,7 +302,7 @@ public function testShouldGetSchemaIfFieldsDescribesSchemaFields() // Assertions $result = $this->model->getSchema(); - $this->assertInstanceOf(AbstractSchema::class, $result); + $this->assertInstanceOf(DynamicSchema::class, $result); $this->assertSame($fields, $result->fields); $this->assertSame($this->model->dynamic, $result->dynamic); $this->assertSame($this->model->getCollectionName(), $result->collection); @@ -314,7 +313,7 @@ public function testShouldGetDataMapper() { // Set $model = m::mock(AbstractModel::class.'[getSchema]'); - $schema = m::mock(AbstractSchema::class.'[]'); + $schema = m::mock(DynamicSchema::class.'[]'); // Actions $model->shouldAllowMockingProtectedMethods(); diff --git a/tests/Unit/Model/HasRelationsTraitTest.php b/tests/Unit/Model/HasRelationsTraitTest.php index 7670470e..14e9cd42 100644 --- a/tests/Unit/Model/HasRelationsTraitTest.php +++ b/tests/Unit/Model/HasRelationsTraitTest.php @@ -45,7 +45,7 @@ public function testShouldReferenceMany($fieldValue, $expectedQuery) $builder = $this->instance(Builder::class, m::mock(Builder::class)->makePartial()); $expectedQuery = $expectedQuery['referencesMany']; - $expected = new EmbeddedCursor(RelatedStub::class, []); + $expected = new EmbeddedCursor([]); // Expectations $builder->expects() @@ -75,7 +75,7 @@ public function testShouldEmbedsOne($fieldValue, $expectedItems) // Assert $this->assertInstanceOf(RelatedStub::class, $result); - $this->assertEquals($expectedItems, $result->getDocumentAttributes()); + $this->assertEquals($expectedItems, $result); } /** @@ -91,17 +91,11 @@ public function testShouldEmbedsMany($fieldValue, $expectedItems) // Act $result = $model->relationEmbedsMany; - $values = array_map( - function ($item) { - return $item->getDocumentAttributes(); - }, - $result->all() - ); // Assert $this->assertInstanceOf(EmbeddedCursor::class, $result); $this->assertContainsOnlyInstancesOf(RelatedStub::class, $result->all()); - $this->assertEquals($expectedItems, $values); + $this->assertEquals($expectedItems, $result->all()); } public function referenceScenarios() @@ -168,19 +162,29 @@ public function referenceScenarios() public function embedsScenarios() { + $model1 = new RelatedStub(); + $model1->_id = 12345; + $model1->name = 'John'; + $model1->syncOriginalDocumentAttributes(); + + $model2 = new RelatedStub(); + $model2->_id = 67890; + $model2->name = 'Bob'; + $model2->syncOriginalDocumentAttributes(); + return [ 'A single embedded document' => [ - 'fieldValue' => ['_id' => 12345, 'name' => 'batata'], + 'fieldValue' => $model1, 'expectedItems' => [ - 'embedsOne' => ['_id' => 12345, 'name' => 'batata'], - 'embedsMany' => [['_id' => 12345, 'name' => 'batata']], + 'embedsOne' => $model1, + 'embedsMany' => [$model1], ], ], 'Many embedded documents' => [ - 'fieldValue' => [['_id' => 12345, 'name' => 'batata'], ['_id' => 67890, 'name' => 'bar']], + 'fieldValue' => [$model1, $model2], 'expectedItems' => [ - 'embedsOne' => ['_id' => 12345, 'name' => 'batata'], - 'embedsMany' => [['_id' => 12345, 'name' => 'batata'], ['_id' => 67890, 'name' => 'bar']], + 'embedsOne' => $model1, + 'embedsMany' => [$model1, $model2], ], ], ]; diff --git a/tests/Unit/Query/BuilderTest.php b/tests/Unit/Query/BuilderTest.php index f15b8b3f..48a29886 100644 --- a/tests/Unit/Query/BuilderTest.php +++ b/tests/Unit/Query/BuilderTest.php @@ -15,7 +15,7 @@ use Mongolid\Model\AbstractModel; use Mongolid\Model\Exception\ModelNotFoundException; use Mongolid\Model\ModelInterface; -use Mongolid\Schema\AbstractSchema; +use Mongolid\Schema\DynamicSchema; use Mongolid\TestCase; class BuilderTest extends TestCase @@ -437,7 +437,7 @@ public function testShouldGetWithWhereQuery() // Arrange $connection = m::mock(Connection::class); $builder = m::mock(Builder::class.'[prepareValueQuery,getCollection]', [$connection]); - $schema = m::mock(AbstractSchema::class); + $schema = m::mock(DynamicSchema::class); $collection = m::mock(Collection::class); $query = 123; @@ -576,7 +576,7 @@ public function testFirstOrFailWithNullShouldFail() $connection = m::mock(Connection::class); $builder = new Builder($connection); $builder->setSchema( - new class extends AbstractSchema + new class extends DynamicSchema { /** * {@inheritdoc} @@ -597,7 +597,7 @@ public function testShouldGetNullIfFirstCantFindAnything() // Arrange $connection = m::mock(Connection::class); $builder = m::mock(Builder::class.'[prepareValueQuery,getCollection]', [$connection]); - $schema = m::mock(AbstractSchema::class); + $schema = m::mock(DynamicSchema::class); $collection = m::mock(Collection::class); $query = 123; @@ -636,7 +636,7 @@ public function testShouldGetFirstProjectingFields() Builder::class.'[prepareValueQuery,getCollection]', [$connection] ); - $schema = m::mock(AbstractSchema::class); + $schema = m::mock(DynamicSchema::class); $collection = m::mock(Collection::class); $query = 123; @@ -674,7 +674,7 @@ public function testShouldGetSchemaMapper() $connection = m::mock(Connection::class); $builder = new Builder($connection); $builder->schemaClass = 'MySchema'; - $schema = $this->instance('MySchema', m::mock(AbstractSchema::class)); + $schema = $this->instance('MySchema', m::mock(DynamicSchema::class)); // Act $result = $this->callProtected($builder, 'getSchemaMapper'); @@ -690,7 +690,7 @@ public function testShouldGetRawCollection() $connection = m::mock(Connection::class); $builder = new Builder($connection); $collection = m::mock(Collection::class); - $schema = m::mock(AbstractSchema::class); + $schema = m::mock(DynamicSchema::class); $schema->collection = 'foobar'; $builder->setSchema($schema); diff --git a/tests/Unit/Query/BulkWriteTest.php b/tests/Unit/Query/BulkWriteTest.php index dce57a62..0024727a 100644 --- a/tests/Unit/Query/BulkWriteTest.php +++ b/tests/Unit/Query/BulkWriteTest.php @@ -7,7 +7,7 @@ use MongoDB\Driver\WriteConcern; use Mongolid\Connection\Connection; use Mongolid\Model\ModelInterface; -use Mongolid\Schema\AbstractSchema; +use Mongolid\Schema\DynamicSchema; use Mongolid\TestCase; class BulkWriteTest extends TestCase @@ -147,7 +147,7 @@ function ($actual) use ($query) { public function testShouldExecuteBulkWrite() { $model = m::mock(ModelInterface::class); - $schema = m::mock(AbstractSchema::class); + $schema = m::mock(DynamicSchema::class); $model->schema = $schema; $mongoBulkWrite = m::mock(new MongoBulkWrite()); $connection = $this->instance(Connection::class, m::mock(Connection::class)); diff --git a/tests/Unit/Query/ModelAssemblerTest.php b/tests/Unit/Query/ModelAssemblerTest.php deleted file mode 100644 index 93dbd368..00000000 --- a/tests/Unit/Query/ModelAssemblerTest.php +++ /dev/null @@ -1,282 +0,0 @@ - $value) { - $schemas[$key] = $this->instance($key, m::mock(AbstractSchema::class.'[]')); - $schemas[$key]->modelClass = $value['modelClass']; - $schemas[$key]->fields = $value['fields']; - } - - // Act - $result = $modelAssembler->assemble($inputValue, $schemas[$inputSchema]); - - // Assert - $this->assertEquals($expectedOutput, $result); - } - - public function modelAssemblerFixture() - { - return [ - //--------------------------- - - 'A simple schema to a model' => [ - 'inputValue' => [ // Data that will be used to assembly the model - '_id' => new ObjectId('507f1f77bcf86cd799439011'), - 'name' => 'John Doe', - 'age' => 25, - 'grade' => 7.25, - ], - 'availableSchemas' => [ // Schemas that will exist in the test context - 'studentSchema' => [ - 'modelClass' => StubStudent::class, - 'fields' => [ - '_id' => 'objectId', - 'name' => 'string', - 'age' => 'integer', - 'grade' => 'float', - 'finalGrade' => 'float', - ], - ], - ], - 'inputSchema' => 'studentSchema', // Schema that will be used to assembly $inputValue - 'expectedOutput' => new StubStudent([ // Expected output - '_id' => new ObjectId('507f1f77bcf86cd799439011'), - 'name' => 'John Doe', - 'age' => 25, - 'grade' => 7.25, - ]), - ], - - //--------------------------- - - 'A schema containing an embedded schema but with null field' => [ - 'inputValue' => [ // Data that will be used to assembly the model - '_id' => new ObjectId('507f1f77bcf86cd799439011'), - 'name' => 'John Doe', - 'age' => 25, - 'tests' => null, - 'finalGrade' => 7.25, - ], - 'availableSchemas' => [ // Schemas that will exist in the test context - 'studentSchema' => [ - 'modelClass' => StubStudent::class, - 'fields' => [ - '_id' => 'objectId', - 'name' => 'string', - 'age' => 'integer', - 'tests' => 'schema.TestSchema', - 'finalGrade' => 'float', - ], - ], - 'TestSchema' => [ - 'modelClass' => StubTestGrade::class, - 'fields' => [ - '_id' => 'objectId', - 'subject' => 'string', - 'grade' => 'float', - ], - ], - ], - 'inputSchema' => 'studentSchema', // Schema that will be used to assembly $inputValue - 'expectedOutput' => new StubStudent([ // Expected output - '_id' => new ObjectId('507f1f77bcf86cd799439011'), - 'name' => 'John Doe', - 'age' => 25, - 'tests' => null, - 'finalGrade' => 7.25, - ]), - ], - - //--------------------------- - - 'A stdClass with a schema containing an embedded schema with a document directly into the field' => [ - 'inputValue' => (object) [ // Data that will be used to assembly the model - '_id' => new ObjectId('507f1f77bcf86cd799439011'), - 'name' => 'John Doe', - 'age' => 25, - 'tests' => [ - '_id' => new ObjectId('507f1f77bcf86cd7994390ea'), - 'subject' => 'math', - 'grade' => 7.25, - ], - 'finalGrade' => 7.25, - ], - 'availableSchemas' => [ // Schemas that will exist in the test context - 'studentSchema' => [ - 'modelClass' => StubStudent::class, - 'fields' => [ - '_id' => 'objectId', - 'name' => 'string', - 'age' => 'integer', - 'tests' => 'schema.TestSchema', - 'finalGrade' => 'float', - ], - ], - 'TestSchema' => [ - 'modelClass' => StubTestGrade::class, - 'fields' => [ - '_id' => 'objectId', - 'subject' => 'string', - 'grade' => 'float', - ], - ], - ], - 'inputSchema' => 'studentSchema', // Schema that will be used to assembly $inputValue - 'expectedOutput' => new StubStudent([ // Expected output - '_id' => new ObjectId('507f1f77bcf86cd799439011'), - 'name' => 'John Doe', - 'age' => 25, - 'tests' => [ - new StubTestGrade([ - '_id' => new ObjectId('507f1f77bcf86cd7994390ea'), - 'subject' => 'math', - 'grade' => 7.25, - ]), - ], - 'finalGrade' => 7.25, - ]), - ], - - //--------------------------- - - 'A schema containing an embedded schema with multiple documents in the field' => [ - 'inputValue' => [ // Data that will be used to assembly the model - '_id' => new ObjectId('507f1f77bcf86cd799439011'), - 'name' => 'John Doe', - 'age' => 25, - 'tests' => [ - [ - '_id' => new ObjectId('507f1f77bcf86cd7994390ea'), - 'subject' => 'math', - 'grade' => 7.25, - ], - [ - '_id' => new ObjectId('507f1f77bcf86cd7994390eb'), - 'subject' => 'english', - 'grade' => 9.0, - ], - ], - 'finalGrade' => 7.25, - ], - 'availableSchemas' => [ // Schemas that will exist in the test context - 'studentSchema' => [ - 'modelClass' => StubStudent::class, - 'fields' => [ - '_id' => 'objectId', - 'name' => 'string', - 'age' => 'integer', - 'tests' => 'schema.TestSchema', - 'finalGrade' => 'float', - ], - ], - 'TestSchema' => [ - 'modelClass' => StubTestGrade::class, - 'fields' => [ - '_id' => 'objectId', - 'subject' => 'string', - 'grade' => 'float', - ], - ], - ], - 'inputSchema' => 'studentSchema', // Schema that will be used to assembly $inputValue - 'expectedOutput' => new StubStudent([ // Expected output - '_id' => new ObjectId('507f1f77bcf86cd799439011'), - 'name' => 'John Doe', - 'age' => 25, - 'tests' => [ - new StubTestGrade([ - '_id' => new ObjectId('507f1f77bcf86cd7994390ea'), - 'subject' => 'math', - 'grade' => 7.25, - ]), - new StubTestGrade([ - '_id' => new ObjectId('507f1f77bcf86cd7994390eb'), - 'subject' => 'english', - 'grade' => 9.0, - ]), - ], - 'finalGrade' => 7.25, - ]), - ], - - //--------------------------- - - 'A simple schema with a polymorphable interface' => [ - 'inputValue' => [ // Data that will be used to assembly the model - '_id' => new ObjectId('507f1f77bcf86cd799439011'), - 'name' => 'John Doe', - 'age' => 25, - 'grade' => 7.25, - ], - 'availableSchemas' => [ // Schemas that will exist in the test context - 'studentSchema' => [ - 'modelClass' => PolymorphableStudent::class, - 'fields' => [ - '_id' => 'objectId', - 'name' => 'string', - 'age' => 'integer', - 'grade' => 'float', - 'finalGrade' => 'float', - ], - ], - ], - 'inputSchema' => 'studentSchema', // Schema that will be used to assembly $inputValue - 'expectedOutput' => new StubStudent([ // Expected output - '_id' => new ObjectId('507f1f77bcf86cd799439011'), - 'name' => 'John Doe', - 'age' => 25, - 'grade' => 7.25, - ]), - ], - ]; - } -} - -class StubStudent extends AbstractModel -{ - public function __construct($attr = []) - { - foreach ($attr as $key => $value) { - $this->$key = $value; - } - - $this->syncOriginalDocumentAttributes(); - } -} - -class StubTestGrade extends AbstractModel -{ - public function __construct($attr = []) - { - foreach ($attr as $key => $value) { - $this->$key = $value; - } - - $this->syncOriginalDocumentAttributes(); - } -} - -class PolymorphableStudent extends AbstractModel implements PolymorphableInterface -{ - public function polymorph() - { - return new StubStudent($this->getDocumentAttributes()); - } -} diff --git a/tests/Unit/Query/AbstractSchemaMapperTest.php b/tests/Unit/Query/SchemaMapperTest.php similarity index 90% rename from tests/Unit/Query/AbstractSchemaMapperTest.php rename to tests/Unit/Query/SchemaMapperTest.php index e43b89a9..be52c68c 100644 --- a/tests/Unit/Query/AbstractSchemaMapperTest.php +++ b/tests/Unit/Query/SchemaMapperTest.php @@ -4,25 +4,19 @@ use Mockery as m; use Mongolid\Container\Ioc; use Mongolid\Model\HasAttributesInterface; -use Mongolid\Schema\AbstractSchema; use Mongolid\Schema\DynamicSchema; use Mongolid\TestCase; use stdClass; -class AbstractSchemaMapperTest extends TestCase +class SchemaMapperTest extends TestCase { public function testShouldMapToFieldsOfSchema() { // Arrange $this->instance( 'My\Own\Schema', - new class() extends AbstractSchema + new class() extends DynamicSchema { - /** - * {@inheritdoc} - */ - public $dynamic = true; - /** * {@inheritdoc} */ @@ -30,13 +24,8 @@ public function testShouldMapToFieldsOfSchema() } ); - $schema = new class() extends AbstractSchema + $schema = new class() extends DynamicSchema { - /** - * {@inheritdoc} - */ - public $dynamic = true; - /** * {@inheritdoc} */ @@ -81,7 +70,7 @@ public function testShouldMapToFieldsOfSchema() public function testShouldClearDynamicFieldsIfSchemaIsNotDynamic() { // Arrange - $schema = new class extends AbstractSchema + $schema = new class extends DynamicSchema { /** * {@inheritdoc} @@ -90,7 +79,13 @@ public function testShouldClearDynamicFieldsIfSchemaIsNotDynamic() 'name' => 'string', 'age' => 'int', ]; + + /** + * {@inheritdoc} + */ + public $dynamic = false; }; + $schemaMapper = new SchemaMapper($schema); $data = [ 'name' => 'John', @@ -144,7 +139,7 @@ public function testShouldNotClearDynamicFieldsIfSchemaIsDynamic() public function testShouldParseFieldIntoCastableType() { // Arrange - $schema = new class extends AbstractSchema + $schema = new class extends DynamicSchema { }; $schemaMapper = new SchemaMapper($schema); @@ -164,7 +159,7 @@ public function testShouldParseFieldIntoCastableType() public function testShouldParseFieldIntoAnotherMappedSchemaIfTypeBeginsWithSchema() { // Arrange - $schema = new class extends AbstractSchema + $schema = new class extends DynamicSchema { }; $schemaMapper = m::mock( @@ -188,7 +183,7 @@ public function testShouldParseFieldIntoAnotherMappedSchemaIfTypeBeginsWithSchem public function testShouldParseFieldUsingAMethodInSchemaIfTypeIsAnUnknownString() { // Arrange - $schemaClass = new class() extends AbstractSchema + $schemaClass = new class() extends DynamicSchema { public function pumpkinPoint($value) { @@ -209,12 +204,12 @@ public function pumpkinPoint($value) public function testShouldMapAnArrayValueToAnotherSchema() { // Arrange - $schema = new class extends AbstractSchema + $schema = new class extends DynamicSchema { }; $mySchema = $this->instance( 'Xd\MySchema', - new class extends AbstractSchema + new class extends DynamicSchema { } ); @@ -251,7 +246,7 @@ function ($container, $params) use ($value, $mySchema, $test) { public function testShouldParseToArrayGettingObjectAttributes() { // Arrange - $schema = new class extends AbstractSchema + $schema = new class extends DynamicSchema { }; $schemaMapper = new SchemaMapper($schema); @@ -267,7 +262,7 @@ public function testShouldParseToArrayGettingObjectAttributes() public function testShouldParseToArrayIfIsAnArray() { // Arrange - $schema = new class extends AbstractSchema + $schema = new class extends DynamicSchema { }; $schemaMapper = new SchemaMapper($schema); @@ -283,7 +278,7 @@ public function testShouldParseToArrayIfIsAnArray() public function testShouldGetAttributesWhenObjectImplementsAttributesAccessInterface() { // Arrange - $schema = new class extends AbstractSchema + $schema = new class extends DynamicSchema { }; $schemaMapper = new SchemaMapper($schema); diff --git a/tests/Unit/Schema/AbstractSchemaTest.php b/tests/Unit/Schema/AbstractSchemaTest.php deleted file mode 100644 index 93be2c80..00000000 --- a/tests/Unit/Schema/AbstractSchemaTest.php +++ /dev/null @@ -1,173 +0,0 @@ -assertAttributeEquals(false, 'dynamic', $schema); - } - - public function testMustHaveAnModelClass() - { - // Arrange - $schema = m::mock(AbstractSchema::class.'[]'); - - // Assert - $this->assertAttributeEquals('stdClass', 'modelClass', $schema); - } - - public function testShouldCastNullIntoObjectId() - { - // Arrange - $schema = m::mock(AbstractSchema::class.'[]'); - $value = null; - - // Assert - $this->assertInstanceOf( - ObjectId::class, - $schema->objectId($value) - ); - } - - public function testShouldNotCastRandomStringIntoObjectId() - { - // Arrange - $schema = m::mock(AbstractSchema::class.'[]'); - $value = 'A random string'; - - // Assert - $this->assertEquals( - $value, - $schema->objectId($value) - ); - } - - public function testShouldCastObjectIdStringIntoObjectId() - { - // Arrange - $schema = m::mock(AbstractSchema::class.'[]'); - $value = '507f1f77bcf86cd799439011'; - - // Assert - $this->assertInstanceOf( - ObjectId::class, - $schema->objectId($value) - ); - - $this->assertEquals( - $value, - (string) $schema->objectId($value) - ); - } - - public function testShouldCastNullIntoAutoIncrementSequence() - { - // Arrange - $schema = m::mock(AbstractSchema::class.'[]'); - $sequenceService = $this->instance(SequenceService::class, m::mock(SequenceService::class)); - $value = null; - - $schema->collection = 'resources'; - - // Act - $sequenceService->expects() - ->getNextValue('resources') - ->andReturn(7); - - // Assertion - $this->assertEquals(7, $schema->sequence($value)); - } - - public function testShouldNotAutoIncrementSequenceIfValueIsNotNull() - { - $schema = m::mock(AbstractSchema::class.'[]'); - $sequenceService = $this->instance(SequenceService::class, m::mock(SequenceService::class)); - $value = 3; - - $schema->collection = 'resources'; - - // Act - $sequenceService->expects() - ->getNextValue('resources') - ->never() - ->andReturn(7); // Should never be returned - - // Assertion - $this->assertEquals(3, $schema->sequence($value)); - } - - public function testShouldCastDocumentTimestamps() - { - // Arrange - $schema = m::mock(AbstractSchema::class.'[]'); - $value = null; - - // Assertion - $this->assertInstanceOf( - UTCDateTime::class, - $schema->createdAtTimestamp($value) - ); - } - - public function testShouldRefreshUpdatedAtTimestamps() - { - // Arrange - $schema = m::mock(AbstractSchema::class.'[]'); - $value = (new UTCDateTime(25)); - - // Assertion - $result = $schema->updatedAtTimestamp($value); - $this->assertInstanceOf(UTCDateTime::class, $result); - $this->assertNotEquals(25000, (string) $result); - } - - /** - * @dataProvider createdAtTimestampsFixture - */ - public function testShouldNotRefreshCreatedAtTimestamps( - $value, - $expectation, - $compareTimestamp = true - ) { - // Arrange - $schema = m::mock(AbstractSchema::class.'[]'); - - // Assertion - $result = $schema->createdAtTimestamp($value); - $this->assertInstanceOf(get_class($expectation), $result); - if ($compareTimestamp) { - $this->assertEquals((string) $expectation, (string) $result); - } - } - - public function createdAtTimestampsFixture() - { - return [ - 'MongoDB driver UTCDateTime' => [ - 'value' => new UTCDateTime(25), - 'expectation' => new UTCDateTime(25), - ], - 'Empty field' => [ - 'value' => null, - 'expectation' => new UTCDateTime(), - 'compareTimestamp' => false, - ], - 'An string' => [ - 'value' => 'foobar', - 'expectation' => new UTCDateTime(), - 'compareTimestamp' => false, - ], - ]; - } -} diff --git a/tests/Unit/Schema/DynamicSchemaTest.php b/tests/Unit/Schema/DynamicSchemaTest.php index 3a290775..2e75b1fb 100644 --- a/tests/Unit/Schema/DynamicSchemaTest.php +++ b/tests/Unit/Schema/DynamicSchemaTest.php @@ -2,25 +2,172 @@ namespace Mongolid\Schema; use Mockery as m; +use MongoDB\BSON\ObjectId; +use MongoDB\BSON\UTCDateTime; use Mongolid\TestCase; +use Mongolid\Util\SequenceService; class DynamicSchemaTest extends TestCase { - public function testShouldExtendSchema() + public function testShouldBeDynamicByDefault() { // Arrange - $schema = m::mock(DynamicSchema::class.'[]'); + $schema = new DynamicSchema(); // Assert - $this->assertInstanceOf(AbstractSchema::class, $schema); + $this->assertAttributeEquals(true, 'dynamic', $schema); } - public function testShouldBeDynamic() + public function testMustHaveAnModelClass() { // Arrange - $schema = m::mock(DynamicSchema::class.'[]'); + $schema = new DynamicSchema(); // Assert - $this->assertAttributeEquals(true, 'dynamic', $schema); + $this->assertAttributeEquals('stdClass', 'modelClass', $schema); + } + + public function testShouldCastNullIntoObjectId() + { + // Arrange + $schema = new DynamicSchema(); + $value = null; + + // Assert + $this->assertInstanceOf( + ObjectId::class, + $schema->objectId($value) + ); + } + + public function testShouldNotCastRandomStringIntoObjectId() + { + // Arrange + $schema = new DynamicSchema(); + $value = 'A random string'; + + // Assert + $this->assertEquals( + $value, + $schema->objectId($value) + ); + } + + public function testShouldCastObjectIdStringIntoObjectId() + { + // Arrange + $schema = new DynamicSchema(); + $value = '507f1f77bcf86cd799439011'; + + // Assert + $this->assertInstanceOf( + ObjectId::class, + $schema->objectId($value) + ); + + $this->assertEquals( + $value, + (string) $schema->objectId($value) + ); + } + + public function testShouldCastNullIntoAutoIncrementSequence() + { + // Arrange + $schema = new DynamicSchema(); + $sequenceService = $this->instance(SequenceService::class, m::mock(SequenceService::class)); + $value = null; + + $schema->collection = 'resources'; + + // Act + $sequenceService->expects() + ->getNextValue('resources') + ->andReturn(7); + + // Assertion + $this->assertEquals(7, $schema->sequence($value)); + } + + public function testShouldNotAutoIncrementSequenceIfValueIsNotNull() + { + $schema = new DynamicSchema(); + $sequenceService = $this->instance(SequenceService::class, m::mock(SequenceService::class)); + $value = 3; + + $schema->collection = 'resources'; + + // Act + $sequenceService->expects() + ->getNextValue('resources') + ->never() + ->andReturn(7); // Should never be returned + + // Assertion + $this->assertEquals(3, $schema->sequence($value)); + } + + public function testShouldCastDocumentTimestamps() + { + // Arrange + $schema = new DynamicSchema(); + $value = null; + + // Assertion + $this->assertInstanceOf( + UTCDateTime::class, + $schema->createdAtTimestamp($value) + ); + } + + public function testShouldRefreshUpdatedAtTimestamps() + { + // Arrange + $schema = new DynamicSchema(); + $value = (new UTCDateTime(25)); + + // Assertion + $result = $schema->updatedAtTimestamp($value); + $this->assertInstanceOf(UTCDateTime::class, $result); + $this->assertNotEquals(25000, (string) $result); + } + + /** + * @dataProvider createdAtTimestampsFixture + */ + public function testShouldNotRefreshCreatedAtTimestamps( + $value, + $expectation, + $compareTimestamp = true + ) { + // Arrange + $schema = new DynamicSchema(); + + // Assertion + $result = $schema->createdAtTimestamp($value); + $this->assertInstanceOf(get_class($expectation), $result); + if ($compareTimestamp) { + $this->assertEquals((string) $expectation, (string) $result); + } + } + + public function createdAtTimestampsFixture() + { + return [ + 'MongoDB driver UTCDateTime' => [ + 'value' => new UTCDateTime(25), + 'expectation' => new UTCDateTime(25), + ], + 'Empty field' => [ + 'value' => null, + 'expectation' => new UTCDateTime(), + 'compareTimestamp' => false, + ], + 'An string' => [ + 'value' => 'foobar', + 'expectation' => new UTCDateTime(), + 'compareTimestamp' => false, + ], + ]; } } From 4d8605d59339259fb0f7ece0c82e22def3110242 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Tue, 27 Nov 2018 12:07:00 -0200 Subject: [PATCH 076/116] Drop schemas in favor of bson serialization --- src/Connection/Manager.php | 38 --- src/Cursor/Cursor.php | 17 +- src/Model/AbstractModel.php | 142 +++------ src/Model/HasAttributesTrait.php | 2 +- src/Model/HasRelationsTrait.php | 35 +-- src/Model/ModelInterface.php | 97 +++++- src/Query/Builder.php | 118 +++----- src/Query/BulkWrite.php | 15 +- src/Query/ModelMapper.php | 84 ++++++ src/Query/SchemaMapper.php | 173 ----------- src/Schema/DynamicSchema.php | 124 -------- tests/Integration/Stubs/EmbeddedUser.php | 7 +- tests/Integration/Stubs/ReferencedUser.php | 6 +- tests/Unit/Connection/ManagerTest.php | 46 --- tests/Unit/Cursor/CursorTest.php | 37 +-- tests/Unit/Model/AbstractModelTest.php | 150 +++------- tests/Unit/Model/HasRelationsTraitTest.php | 4 +- tests/Unit/Query/BuilderTest.php | 156 +++++----- tests/Unit/Query/BulkWriteTest.php | 41 +-- tests/Unit/Query/ModelMapperTest.php | 185 ++++++++++++ tests/Unit/Query/SchemaMapperTest.php | 327 --------------------- tests/Unit/Schema/DynamicSchemaTest.php | 173 ----------- 22 files changed, 625 insertions(+), 1352 deletions(-) create mode 100644 src/Query/ModelMapper.php delete mode 100644 src/Query/SchemaMapper.php delete mode 100644 src/Schema/DynamicSchema.php create mode 100644 tests/Unit/Query/ModelMapperTest.php delete mode 100644 tests/Unit/Query/SchemaMapperTest.php delete mode 100644 tests/Unit/Schema/DynamicSchemaTest.php diff --git a/src/Connection/Manager.php b/src/Connection/Manager.php index 3649822f..d79b4743 100644 --- a/src/Connection/Manager.php +++ b/src/Connection/Manager.php @@ -5,8 +5,6 @@ use Mongolid\Container\Ioc; use Mongolid\Event\EventTriggerInterface; use Mongolid\Event\EventTriggerService; -use Mongolid\Query\Builder; -use Mongolid\Schema\DynamicSchema; /** * Wraps the Mongolid initialization. The main purpose of the Manager is to make @@ -42,13 +40,6 @@ class Manager */ protected $connection; - /** - * Stores the schemas that have been registered for later use. - * - * @var array - */ - protected $schemas = []; - /** * Main entry point to opening a connection and start using Mongolid in * pure PHP. After adding a connection into the Manager you are ready to @@ -92,35 +83,6 @@ public function setEventTrigger(EventTriggerInterface $eventTrigger) $this->container->instance(EventTriggerService::class, $eventService); } - /** - * Allow document Schemas to be registered for later use. - * - * @param DynamicSchema $schema schema being registered - */ - public function registerSchema(DynamicSchema $schema) - { - $this->schemas[$schema->modelClass] = $schema; - } - - /** - * Retrieves a Builder for the given $modelClass. This can only be done - * if the Schema for that model has been previously registered with - * registerSchema() method. - * - * @param string $modelClass class of the model that needs to be mapped - */ - public function getBuilder(string $modelClass): ?Builder - { - if (isset($this->schemas[$modelClass])) { - $builder = Ioc::make(Builder::class); - $builder->setSchema($this->schemas[$modelClass] ?? null); - - return $builder; - } - - return null; - } - /** * Initializes the Mongolid manager. */ diff --git a/src/Cursor/Cursor.php b/src/Cursor/Cursor.php index 7cea9662..b9df33be 100644 --- a/src/Cursor/Cursor.php +++ b/src/Cursor/Cursor.php @@ -10,7 +10,6 @@ use MongoDB\Model\CachingIterator; use Mongolid\Connection\Connection; use Mongolid\Container\Ioc; -use Mongolid\Schema\DynamicSchema; use Serializable; /** @@ -21,13 +20,6 @@ */ class Cursor implements CursorInterface, Serializable { - /** - * Schema that describes the model that will be retrieved when iterating through the cursor. - * - * @var string - */ - public $modelSchema; - /** * @var Collection */ @@ -62,19 +54,16 @@ class Cursor implements CursorInterface, Serializable protected $position = 0; /** - * @param DynamicSchema $modelSchema schema that describes the model that will be retrieved from the database - * @param Collection $collection the raw collection object that will be used to retrieve the documents - * @param string $command the command that is being called in the $collection - * @param array $params the parameters of the $command + * @param Collection $collection the raw collection object that will be used to retrieve the documents + * @param string $command the command that is being called in the $collection + * @param array $params the parameters of the $command */ public function __construct( - DynamicSchema $modelSchema, Collection $collection, string $command, array $params ) { $this->cursor = null; - $this->modelSchema = $modelSchema; $this->collection = $collection; $this->command = $command; $this->params = $params; diff --git a/src/Model/AbstractModel.php b/src/Model/AbstractModel.php index 284fd31f..ef5967f8 100644 --- a/src/Model/AbstractModel.php +++ b/src/Model/AbstractModel.php @@ -7,15 +7,12 @@ use Mongolid\Model\Exception\ModelNotFoundException; use Mongolid\Model\Exception\NoCollectionNameException; use Mongolid\Query\Builder; -use Mongolid\Query\SchemaMapper; -use Mongolid\Schema\DynamicSchema; +use Mongolid\Query\ModelMapper; /** * The Mongolid\Model\Model base class will ensure to enable your model to * have methods to interact with the database. It means that 'save', 'insert', * 'update', 'where', 'first' and 'all' are available within every instance. - * The Mongolid\Schema\Schema that describes the model will be generated on the go - * based on the $fields. */ abstract class AbstractModel implements ModelInterface { @@ -24,13 +21,21 @@ abstract class AbstractModel implements ModelInterface /** * The $dynamic property tells if the object will accept additional fields - * that are not specified in the $fields property. This is useful if you - * does not have a strict document format or if you want to take full - * advantage of the "schemaless" nature of MongoDB. + * that are not specified in the $fillable or $guarded properties. + * This is useful if you does not have a strict document format or + * if you want to take full advantage of the "schemaless" nature of MongoDB. * * @var bool */ - public $dynamic = true; + protected $dynamic = true; + + /** + * Whether the model should manage the `created_at` and `updated_at` + * timestamps automatically. + * + * @var bool + */ + protected $timestamps = true; /** * Name of the collection where this kind of Model is going to be saved or @@ -47,20 +52,6 @@ abstract class AbstractModel implements ModelInterface */ protected $writeConcern = 1; - /** - * Describes the Schema fields of the model. Optionally you can set it to - * the name of a Schema class to be used. - * - * @see \Mongolid\Schema\DynamicSchema::$fields - * - * @var string|string[] - */ - protected $fields = [ - '_id' => 'objectId', - 'created_at' => 'createdAtTimestamp', - 'updated_at' => 'updatedAtTimestamp', - ]; - /** * Gets a cursor of this kind of entities that matches the query from the * database. @@ -70,7 +61,7 @@ abstract class AbstractModel implements ModelInterface */ public static function where(array $query = [], array $projection = []): CursorInterface { - return self::getBuilderInstance()->where($query, $projection); + return self::getBuilderInstance()->where(new static(), $query, $projection); } /** @@ -78,7 +69,7 @@ public static function where(array $query = [], array $projection = []): CursorI */ public static function all(): CursorInterface { - return self::getBuilderInstance()->all(); + return self::getBuilderInstance()->all(new static()); } /** @@ -86,10 +77,12 @@ public static function all(): CursorInterface * * @param mixed $query mongoDB selection criteria * @param array $projection fields to project in Mongo query + * + * @return ModelInterface|null */ - public static function first($query = [], array $projection = []): ?ModelInterface + public static function first($query = [], array $projection = []) { - return self::getBuilderInstance()->first($query, $projection); + return self::getBuilderInstance()->first(new static(), $query, $projection); } /** @@ -100,10 +93,12 @@ public static function first($query = [], array $projection = []): ?ModelInterfa * @param array $projection fields to project in Mongo query * * @throws ModelNotFoundException If no document was found + * + * @return ModelInterface|null */ - public static function firstOrFail($query = [], array $projection = []): ?ModelInterface + public static function firstOrFail($query = [], array $projection = []) { - return self::getBuilderInstance()->firstOrFail($query, $projection); + return self::getBuilderInstance()->firstOrFail(new static(), $query, $projection); } /** @@ -112,10 +107,12 @@ public static function firstOrFail($query = [], array $projection = []): ?ModelI * _if field filled. * * @param mixed $id document id + * + * @return ModelInterface|null */ - public static function firstOrNew($id): ?ModelInterface + public static function firstOrNew($id) { - if (!$model = self::getBuilderInstance()->first($id)) { + if (!$model = self::first($id)) { $model = new static(); $model->_id = $id; } @@ -125,17 +122,11 @@ public static function firstOrNew($id): ?ModelInterface /** * Returns a valid instance from Ioc. - * - * @throws NoCollectionNameException Throws exception when has no collection filled */ private static function getBuilderInstance(): Builder { $instance = new static(); - if (!$instance->getCollectionName()) { - throw new NoCollectionNameException(); - } - return $instance->getBuilder(); } @@ -215,23 +206,15 @@ public function __unset(string $key): void } /** - * Returns a Builder configured with the Schema and collection described - * in this model. + * {@inheritdoc} */ - public function getBuilder(): Builder + public function getCollectionName(): string { - $builder = Ioc::make(Builder::class); - $builder->setSchema($this->getSchema()); - - return $builder; - } + if (!$this->collection) { + throw new NoCollectionNameException(); + } - /** - * Getter for the $collection attribute. - */ - public function getCollectionName(): ?string - { - return $this->collection ?: $this->getSchema()->collection; + return $this->collection; } /** @@ -245,42 +228,17 @@ public function getWriteConcern(): int /** * Setter for $writeConcern attribute. * - * @param int $writeConcern level of write concern for the transation + * @param int $writeConcern level of write concern for the transaction */ public function setWriteConcern(int $writeConcern): void { $this->writeConcern = $writeConcern; } - /** - * {@inheritdoc} - */ - public function getSchema(): DynamicSchema - { - if ($schema = $this->instantiateSchemaInFields()) { - return $schema; - } - - $schema = new DynamicSchema(); - $schema->modelClass = static::class; - $schema->fields = $this->fields; - $schema->dynamic = $this->dynamic; - $schema->collection = $this->collection; - - return $schema; - } - public function bsonSerialize() { - $schemaMapper = Ioc::make(SchemaMapper::class, ['schema' => $this->getSchema()]); - - $parsedDocument = $schemaMapper->map($this); - - foreach ($parsedDocument as $field => $value) { - $this->setDocumentAttribute($field, $value); - } - - return $parsedDocument; + return Ioc::make(ModelMapper::class) + ->map($this, array_merge($this->fillable, $this->guarded), $this->dynamic, $this->timestamps); } public function bsonUnserialize(array $data) @@ -291,21 +249,6 @@ public function bsonUnserialize(array $data) $this->syncOriginalDocumentAttributes(); } - /** - * Will check if the current value of $fields property is the name of a - * Schema class and instantiate it if possible. - */ - protected function instantiateSchemaInFields(): ?DynamicSchema - { - if (is_string($this->fields)) { - if (is_subclass_of($instance = Ioc::make($this->fields), DynamicSchema::class)) { - return $instance; - } - } - - return null; - } - /** * Performs the given action into database. * @@ -313,10 +256,6 @@ protected function instantiateSchemaInFields(): ?DynamicSchema */ protected function execute(string $action): bool { - if (!$this->getCollectionName()) { - return false; - } - $options = [ 'writeConcern' => new WriteConcern($this->getWriteConcern()), ]; @@ -327,4 +266,13 @@ protected function execute(string $action): bool return $result; } + + /** + * Returns a Builder configured with the collection described + * in this model. + */ + private function getBuilder(): Builder + { + return Ioc::make(Builder::class); + } } diff --git a/src/Model/HasAttributesTrait.php b/src/Model/HasAttributesTrait.php index 71837901..a785290b 100644 --- a/src/Model/HasAttributesTrait.php +++ b/src/Model/HasAttributesTrait.php @@ -17,7 +17,7 @@ trait HasAttributesTrait /** * Once you put at least one string in this array, only * the attributes specified here will be changed - * with the setDocumentAttributes method. + * with the setDocumentAttribute method. * * @var array */ diff --git a/src/Model/HasRelationsTrait.php b/src/Model/HasRelationsTrait.php index b515a3aa..e9484c4f 100644 --- a/src/Model/HasRelationsTrait.php +++ b/src/Model/HasRelationsTrait.php @@ -85,18 +85,18 @@ public function getFieldRelation(string $field): string /** * Create a ReferencesOne Relation. * - * @param string $model class of the model or of the schema of the model - * @param string|null $field the field where the $key is stored - * @param string $key the field that the document will be referenced by (usually _id) + * @param string $modelClass class of the referenced model + * @param string|null $field the field where the $key is stored + * @param string $key the field that the document will be referenced by (usually _id) */ - protected function referencesOne(string $model, string $field = null, string $key = '_id'): ReferencesOne + protected function referencesOne(string $modelClass, string $field = null, string $key = '_id'): ReferencesOne { $relationName = $this->guessRelationName(); if (!$this->relationLoaded($relationName)) { $field = $field ?: $this->inferFieldForReference($relationName, $key, false); - $relation = new ReferencesOne($this, $model, $field, $key); + $relation = new ReferencesOne($this, $modelClass, $field, $key); $this->setRelation($relationName, $relation, $field); } @@ -106,17 +106,18 @@ protected function referencesOne(string $model, string $field = null, string $ke /** * Create a ReferencesMany Relation. * - * @param string $model class of the model or of the schema of the model - * @param string|null $field the field where the _ids are stored + * @param string $modelClass class of the referenced model + * @param string|null $field the field where the _ids are stored + * @param string $key the field that the document will be referenced by (usually _id) */ - protected function referencesMany(string $model, string $field = null, string $key = '_id'): ReferencesMany + protected function referencesMany(string $modelClass, string $field = null, string $key = '_id'): ReferencesMany { $relationName = $this->guessRelationName(); if (!$this->relationLoaded($relationName)) { $field = $field ?: $this->inferFieldForReference($relationName, $key, true); - $relation = new ReferencesMany($this, $model, $field, $key); + $relation = new ReferencesMany($this, $modelClass, $field, $key); $this->setRelation($relationName, $relation, $field); } @@ -126,17 +127,17 @@ protected function referencesMany(string $model, string $field = null, string $k /** * Create a EmbedsOne Relation. * - * @param string|null $model class of the model or of the schema of the model - * @param string $field field where the embedded document is stored + * @param string $modelClass class of the embedded model + * @param string|null $field field where the embedded document is stored */ - protected function embedsOne(string $model, string $field = null): EmbedsOne + protected function embedsOne(string $modelClass, string $field = null): EmbedsOne { $relationName = $this->guessRelationName(); if (!$this->relationLoaded($relationName)) { $field = $field ?: $this->inferFieldForEmbed($relationName); - $relation = new EmbedsOne($this, $model, $field); + $relation = new EmbedsOne($this, $modelClass, $field); $this->setRelation($relationName, $relation, $field); } @@ -146,17 +147,17 @@ protected function embedsOne(string $model, string $field = null): EmbedsOne /** * Create a EmbedsMany Relation. * - * @param string|null $model class of the model or of the schema of the model - * @param string $field field where the embedded documents are stored + * @param string $modelClass class of the embedded model + * @param string|null $field field where the embedded documents are stored */ - protected function embedsMany(string $model, string $field = null): EmbedsMany + protected function embedsMany(string $modelClass, string $field = null): EmbedsMany { $relationName = $this->guessRelationName(); if (!$this->relationLoaded($relationName)) { $field = $field ?: $this->inferFieldForEmbed($relationName); - $relation = new EmbedsMany($this, $model, $field); + $relation = new EmbedsMany($this, $modelClass, $field); $this->setRelation($relationName, $relation, $field); } diff --git a/src/Model/ModelInterface.php b/src/Model/ModelInterface.php index dfb60eac..69b879b9 100644 --- a/src/Model/ModelInterface.php +++ b/src/Model/ModelInterface.php @@ -2,12 +2,103 @@ namespace Mongolid\Model; use MongoDB\BSON\Persistable; -use Mongolid\Schema\DynamicSchema; +use Mongolid\Cursor\CursorInterface; +/** + * @property array $fillable + * @property array $guarded + * @property bool $dynamic + * @property bool $timestamps + * @property \MongoDB\BSON\ObjectId $_id + * @property \MongoDB\BSON\UTCDateTime $created_at + * @property \MongoDB\BSON\UTCDateTime $updated_at + */ interface ModelInterface extends HasAttributesInterface, Persistable { /** - * Returns a Schema object that describes an Model in MongoDB. + * Gets a cursor of this kind of entities that matches the query from the + * database. + * + * @param array $query mongoDB selection criteria + * @param array $projection fields to project in Mongo query */ - public function getSchema(): DynamicSchema; + public static function where(array $query = [], array $projection = []): CursorInterface; + + /** + * Gets a cursor of this kind of entities from the database. + */ + public static function all(): CursorInterface; + + /** + * Gets the first model of this kind that matches the query. + * + * @param mixed $query mongoDB selection criteria + * @param array $projection fields to project in Mongo query + * + * @return ModelInterface|null + */ + public static function first($query = [], array $projection = []); + + /** + * Gets the first model of this kind that matches the query. If no + * document was found, throws ModelNotFoundException. + * + * @param mixed $query mongoDB selection criteria + * @param array $projection fields to project in Mongo query + * + * @throws \Mongolid\Model\Exception\ModelNotFoundException If no document was found + * + * @return ModelInterface|null + */ + public static function firstOrFail($query = [], array $projection = []); + + /** + * Gets the first model of this kind that matches the query. If no + * document was found, a new model will be returned with the + * _if field filled. + * + * @param mixed $id document id + * + * @return ModelInterface|null + */ + public static function firstOrNew($id); + + /** + * Retrieve MongoDB's collection name. + * + * @throws \Mongolid\Model\Exception\NoCollectionNameException + */ + public function getCollectionName(): string; + + /** + * Getter for $writeConcern attribute. + */ + public function getWriteConcern(): int; + + /** + * Setter for $writeConcern attribute. + * + * @param int $writeConcern level of write concern for the transaction + */ + public function setWriteConcern(int $writeConcern): void; + + /** + * Saves this object into database. + */ + public function save(): bool; + + /** + * Insert this object into database. + */ + public function insert(): bool; + + /** + * Updates this object in database. + */ + public function update(): bool; + + /** + * Deletes this object in database. + */ + public function delete(): bool; } diff --git a/src/Query/Builder.php b/src/Query/Builder.php index 6a4605be..9f129f81 100644 --- a/src/Query/Builder.php +++ b/src/Query/Builder.php @@ -11,31 +11,14 @@ use Mongolid\Event\EventTriggerService; use Mongolid\Model\Exception\ModelNotFoundException; use Mongolid\Model\ModelInterface; -use Mongolid\Schema\DynamicSchema; use Mongolid\Util\ObjectIdUtils; /** * This class will abstract how a Model is persisted and retrieved * from the database. - * The Builder will always use a Schema trough the SchemaMapper to parse the - * document in and out of the database. */ class Builder { - /** - * Name of the schema class to be used. - * - * @var string - */ - public $schemaClass = DynamicSchema::class; - - /** - * Schema object. Will be set after the $schemaClass. - * - * @var DynamicSchema - */ - protected $schema; - /** * Connection that is going to be used to interact with the database. * @@ -74,10 +57,9 @@ public function save(ModelInterface $model, array $options = []): bool return false; } - // TODO rework this $model->bsonSerialize(); - $queryResult = $this->getCollection()->replaceOne( + $queryResult = $this->getCollection($model)->replaceOne( ['_id' => $model->_id], $model, $this->mergeOptions($options, ['upsert' => true]) @@ -113,7 +95,7 @@ public function insert(ModelInterface $model, array $options = [], bool $fireEve return false; } - $queryResult = $this->getCollection()->insertOne( + $queryResult = $this->getCollection($model)->insertOne( $model, $this->mergeOptions($options) ); @@ -133,7 +115,7 @@ public function insert(ModelInterface $model, array $options = [], bool $fireEve /** * Updates the given object into database. Returns success if write concern - * is acknowledged. Since it's an update, it will fail if the document with + * is acknowledged. Since it's an update, it will fail if the model with * the given _id did not exists. * * Notice: Updates with Unacknowledged WriteConcern will not fire `updated` event. @@ -158,10 +140,9 @@ public function update(ModelInterface $model, array $options = []): bool return $result; } - // TODO review this $updateData = $this->getUpdateData($model, $model->bsonSerialize()); - $queryResult = $this->getCollection()->updateOne( + $queryResult = $this->getCollection($model)->updateOne( ['_id' => $model->_id], $updateData, $this->mergeOptions($options) @@ -193,7 +174,7 @@ public function delete(ModelInterface $model, array $options = []): bool return false; } - $queryResult = $this->getCollection()->deleteOne( + $queryResult = $this->getCollection($model)->deleteOne( ['_id' => $model->_id], $this->mergeOptions($options) ); @@ -210,17 +191,16 @@ public function delete(ModelInterface $model, array $options = []): bool } /** - * Retrieve a database cursor that will return $this->schema->modelClass - * objects that upon iteration. + * Retrieve a database cursor that will return models that upon iteration. * - * @param mixed $query mongoDB query to retrieve documents - * @param array $projection fields to project in Mongo query + * @param ModelInterface $model Model to query from collection + * @param mixed $query MongoDB query to retrieve documents + * @param array $projection fields to project in MongoDB query */ - public function where($query = [], array $projection = []): CursorInterface + public function where(ModelInterface $model, $query = [], array $projection = []): CursorInterface { return new Cursor( - $this->schema, - $this->getCollection(), + $this->getCollection($model), 'find', [ $this->prepareValueQuery($query), @@ -230,91 +210,65 @@ public function where($query = [], array $projection = []): CursorInterface } /** - * Retrieve a database cursor that will return all documents as - * $this->schema->modelClass objects upon iteration. + * Retrieve a database cursor that will return all models upon iteration. + * + * @param ModelInterface $model Model to query from collection */ - public function all(): CursorInterface + public function all(ModelInterface $model): CursorInterface { - return $this->where([]); + return $this->where($model, []); } /** - * Retrieve one $this->schema->modelClass objects that matches the given - * query. + * Retrieve first model that matches given query. * - * @param mixed $query mongoDB query to retrieve the document - * @param array $projection fields to project in Mongo query + * @param ModelInterface $model Model to query from collection + * @param mixed $query MongoDB query to retrieve the model + * @param array $projection fields to project in MongoDB query * - * @return static|null First document matching query as an $this->schema->modelClass object + * @return ModelInterface|array|null */ - public function first($query = [], array $projection = []) + public function first(ModelInterface $model, $query = [], array $projection = []) { if (null === $query) { return null; } - return $this->getCollection()->findOne( + return $this->getCollection($model)->findOne( $this->prepareValueQuery($query), ['projection' => $this->prepareProjection($projection)] ); } /** - * Retrieve one $this->schema->modelClass objects that matches the given - * query. If no document was found, throws ModelNotFoundException. + * Retrieve one model that matches given query. + * If no model was found, throws an exception. * - * @param mixed $query mongoDB query to retrieve the document - * @param array $projection fields to project in Mongo query + * @param ModelInterface $model Model to query from collection + * @param mixed $query MongoDB query to retrieve the model + * @param array $projection fields to project in MongoDB query * - * @throws ModelNotFoundException If no document was found + * @throws ModelNotFoundException If no model was found * - * @return static|null First document matching query as an $this->schema->modelClass object + * @return ModelInterface|null */ - public function firstOrFail($query = [], array $projection = []) + public function firstOrFail(ModelInterface $model, $query = [], array $projection = []) { - if ($result = $this->first($query, $projection)) { + if ($result = $this->first($model, $query, $projection)) { return $result; } - throw (new ModelNotFoundException())->setModel($this->schema->modelClass); - } - - /** - * {@inheritdoc} - */ - public function getSchema(): ?DynamicSchema - { - return $this->schema; - } - - /** - * Set a Schema object that describes an Model in MongoDB. - */ - public function setSchema(DynamicSchema $schema): void - { - $this->schema = $schema; - } - - /** - * Returns a SchemaMapper with the $schema or $schemaClass instance. - */ - protected function getSchemaMapper(): SchemaMapper - { - if (!$this->schema) { - $this->schema = Ioc::make($this->schemaClass); - } - - return Ioc::make(SchemaMapper::class, ['schema' => $this->schema]); + throw (new ModelNotFoundException())->setModel(get_class($model)); } /** * Retrieves the Collection object. */ - protected function getCollection(): Collection + protected function getCollection(ModelInterface $model): Collection { $connection = $this->connection; $database = $connection->defaultDatabase; - $collection = $this->getSchema()->collection; + $collection = $model->getCollectionName(); return $connection->getRawConnection()->$database->$collection; } @@ -324,7 +278,7 @@ protected function getCollection(): Collection * This method will take care of converting a single value into a query for * an _id, including when a objectId is passed as a string. * - * @param mixed $value the _id of the document + * @param mixed $value the _id of the model * * @return array Query for the given _id */ diff --git a/src/Query/BulkWrite.php b/src/Query/BulkWrite.php index 85a47963..50042849 100644 --- a/src/Query/BulkWrite.php +++ b/src/Query/BulkWrite.php @@ -7,7 +7,6 @@ use Mongolid\Connection\Connection; use Mongolid\Container\Ioc; use Mongolid\Model\ModelInterface; -use Mongolid\Schema\DynamicSchema; /** * This class is meant to provide a better API for handling @@ -30,14 +29,14 @@ class BulkWrite protected $bulkWrite; /** - * @var DynamicSchema + * @var ModelInterface */ - protected $schema; + private $model; public function __construct(ModelInterface $model) { $this->setBulkWrite(new MongoBulkWrite(['ordered' => false])); - $this->schema = $model->getSchema(); + $this->model = $model; } /** @@ -71,7 +70,7 @@ public function setBulkWrite(MongoBulkWrite $bulkWrite) * * @see https://docs.mongodb.com/manual/reference/operator/update/set/#set-top-level-fields * - * @param ObjectId|string|array $id + * @param ObjectId|string|array $filter * @param array $dataToSet * @param array $options */ @@ -94,16 +93,16 @@ public function updateOne( * Execute the BulkWrite, using connection. * The collection is inferred from model's collection name. * - * @param int $writeConcern + * @throws \Mongolid\Model\Exception\NoCollectionNameException * * @return \MongoDB\Driver\WriteResult */ - public function execute($writeConcern = 1) + public function execute(int $writeConcern = 1) { $connection = Ioc::make(Connection::class); $manager = $connection->getRawManager(); - $namespace = $connection->defaultDatabase.'.'.$this->schema->collection; + $namespace = $connection->defaultDatabase.'.'.$this->model->getCollectionName(); return $manager->executeBulkWrite( $namespace, diff --git a/src/Query/ModelMapper.php b/src/Query/ModelMapper.php new file mode 100644 index 00000000..27bb3085 --- /dev/null +++ b/src/Query/ModelMapper.php @@ -0,0 +1,84 @@ +clearNullFields($model); + $this->clearDynamicFields($model, $allowedFields, $dynamic, $timestamps); + $this->manageTimestamps($model, $timestamps); + $this->manageId($model); + + return $model->getDocumentAttributes(); + } + + /** + * If the model is not dynamic, remove all non specified fields. + */ + protected function clearDynamicFields(ModelInterface $model, array $allowedFields, bool $dynamic, bool $timestamps): void + { + if ($dynamic) { + return; + } + + $merge = ['_id']; + + if ($timestamps) { + $merge[] = 'created_at'; + $merge[] = 'updated_at'; + } + + $allowedFields = array_unique(array_merge($allowedFields, $merge)); + + foreach ($model->getDocumentAttributes() as $field => $value) { + if (!in_array($field, $allowedFields)) { + unset($model->{$field}); + } + } + } + + private function clearNullFields(ModelInterface $model): void + { + foreach ($model->getDocumentAttributes() as $field => $value) { + if (null === $value) { + unset($model->{$field}); + } + } + } + + private function manageTimestamps(ModelInterface $model, bool $timestamps): void + { + if (!$timestamps) { + return; + } + $model->updated_at = new UTCDateTime(); + + if (!$model->created_at instanceof UTCDateTime) { + $model->created_at = $model->updated_at; + } + } + + private function manageId(ModelInterface $model) + { + $value = $model->_id; + + if (is_null($value) || (is_string($value) && ObjectIdUtils::isObjectId($value))) { + $value = new ObjectId($value); + } + + $model->_id = $value; + } +} diff --git a/src/Query/SchemaMapper.php b/src/Query/SchemaMapper.php deleted file mode 100644 index 5820c1aa..00000000 --- a/src/Query/SchemaMapper.php +++ /dev/null @@ -1,173 +0,0 @@ -schema = $schema; - } - - /** - * Maps the input $data to the schema specified in the $schema property. - * - * @param array|object $data array or object with the fields that should - * be mapped to $this->schema specifications - * - * @return array - */ - public function map($data) - { - $data = $this->parseToArray($data); - $this->clearDynamic($data); - - // Parse each specified field - foreach ($this->schema->fields as $key => $fieldType) { - $data[$key] = $this->parseField($data[$key] ?? null, $fieldType); - } - - return array_filter( - $data, - function ($value) { - return null !== $value; - } - ); - } - - /** - * Parse a value based on a field yype of the schema. - * - * @param mixed $value value to be parsed - * @param string $fieldType description of how the field should be treated - * - * @return mixed $value Value parsed to match $type - */ - public function parseField($value, string $fieldType) - { - // Uses $fieldType method of the schema to parse the value - if (method_exists($this->schema, $fieldType)) { - return $this->schema->$fieldType($value); - } - - // Returns null or an empty array - if (null === $value || [] === $value) { - return $value; - } - - // If fieldType is castable (Ex: 'int') - if (in_array($fieldType, $this->castableTypes)) { - return $this->cast($value, $fieldType); - } - - // If the field type points to another schema. - if ('schema.' == substr($fieldType, 0, 7)) { - return $this->mapToSchema($value, substr($fieldType, 7)); - } - - return $value; - } - - /** - * If the schema is not dynamic, remove all non specified fields. - * - * @param array $data Reference of the fields. The passed array will be modified. - */ - protected function clearDynamic(array &$data) - { - if (!$this->schema->dynamic) { - $data = array_intersect_key($data, $this->schema->fields); - } - } - - /** - * Uses PHP's settype to cast a value to a type. - * - * @see http://php.net/manual/pt_BR/function.settype.php - * - * @param mixed $value value to be casted - * @param string $type type to which the $value should be casted to - * - * @return mixed - */ - protected function cast($value, string $type) - { - settype($value, $type); - - return $value; - } - - /** - * Instantiate another SchemaMapper with the given $schemaClass and maps - * the given $value. - * - * @param mixed $value value that will be mapped - * @param string $schemaClass class that will be passed to the new SchemaMapper constructor - * - * @return mixed - */ - protected function mapToSchema($value, string $schemaClass) - { - $value = (array) $value; - $schema = Ioc::make($schemaClass); - $mapper = Ioc::make(static::class, compact('schema')); - - if (!isset($value[0])) { - $value = [$value]; - } - - foreach ($value as $key => $subValue) { - $value[$key] = $mapper->map($subValue); - } - - return $value; - } - - /** - * Parses an object to an array before sending it to the SchemaMapper. - * - * @param mixed $object the object that will be transformed into an array - * - * @return array - */ - protected function parseToArray($object): array - { - if (!is_array($object)) { - $attributes = $object instanceof HasAttributesInterface - ? $object->getDocumentAttributes() - : get_object_vars($object); - - return $attributes; - } - - return $object; - } -} diff --git a/src/Schema/DynamicSchema.php b/src/Schema/DynamicSchema.php deleted file mode 100644 index ae94ff28..00000000 --- a/src/Schema/DynamicSchema.php +++ /dev/null @@ -1,124 +0,0 @@ -' This represents an embedded document (or - * sub-document). - * - * @var string[] - */ - public $fields = [ - '_id' => 'objectId', // Means that the _id will pass trough the `objectId` method - 'created_at' => 'createdAtTimestamp', // Generates an automatic timestamp - 'updated_at' => 'updatedAtTimestamp', - ]; - - /** - * Name of the class that will be used to represent a document of this - * Schema when retrieve from the database. - * - * @var string - */ - public $modelClass = stdClass::class; - - /** - * Filters any field in the $fields that has it's value specified as a - * 'objectId'. It will wraps the $value, if any, into a ObjectId object. - * - * @param mixed $value value that may be converted to ObjectId - * - * @return ObjectId|mixed - */ - public function objectId($value = null) - { - if (null === $value) { - return new ObjectId(); - } - - if (is_string($value) && ObjectIdUtils::isObjectId($value)) { - $value = new ObjectId($value); - } - - return $value; - } - - /** - * Prepares the field to have a sequence. If $value is zero or not defined - * a new auto-increment number will be "generated" for the collection of - * the schema. The sequence generation is done by the SequenceService. - * - * @param int|null $value value that will be evaluated - * - * @return int - */ - public function sequence(int $value = null) - { - if ($value) { - return $value; - } - - return Ioc::make(SequenceService::class) - ->getNextValue($this->collection ?: $this->modelClass); - } - - /** - * Prepares the field to be the datetime that the document has been created. - * - * @param mixed|null $value value that will be evaluated - * - * @return UTCDateTime - */ - public function createdAtTimestamp($value) - { - if ($value instanceof UTCDateTime) { - return $value; - } - - return $this->updatedAtTimestamp(); - } - - /** - * Prepares the field to be now. - * - * @return UTCDateTime - */ - public function updatedAtTimestamp() - { - return new UTCDateTime(); - } -} diff --git a/tests/Integration/Stubs/EmbeddedUser.php b/tests/Integration/Stubs/EmbeddedUser.php index d8334f10..be3859b4 100644 --- a/tests/Integration/Stubs/EmbeddedUser.php +++ b/tests/Integration/Stubs/EmbeddedUser.php @@ -14,12 +14,9 @@ class EmbeddedUser extends AbstractModel protected $collection = 'users'; /** - * @var array + * @var bool */ - protected $fields = [ - '_id' => 'objectId', - 'created_at' => 'createdAtTimestamp', - ]; + protected $timestamps = true; public function collection(): Collection { diff --git a/tests/Integration/Stubs/ReferencedUser.php b/tests/Integration/Stubs/ReferencedUser.php index 21fae041..446307bd 100644 --- a/tests/Integration/Stubs/ReferencedUser.php +++ b/tests/Integration/Stubs/ReferencedUser.php @@ -14,11 +14,9 @@ class ReferencedUser extends AbstractModel protected $collection = 'users'; /** - * @var array + * @var bool */ - protected $fields = [ - '_id' => 'objectId', - ]; + protected $timestamps = false; public function collection(): Collection { diff --git a/tests/Unit/Connection/ManagerTest.php b/tests/Unit/Connection/ManagerTest.php index 75f4498d..6e7ee639 100644 --- a/tests/Unit/Connection/ManagerTest.php +++ b/tests/Unit/Connection/ManagerTest.php @@ -6,8 +6,6 @@ use MongoDB\Client; use Mongolid\Event\EventTriggerInterface; use Mongolid\Event\EventTriggerService; -use Mongolid\Query\Builder; -use Mongolid\Schema\DynamicSchema; use Mongolid\TestCase; class ManagerTest extends TestCase @@ -59,50 +57,6 @@ public function testShouldSetEventTrigger() $manager->setEventTrigger($eventTrigger); } - public function testShouldRegisterSchema() - { - // Arrange - $manager = new Manager(); - $schema = m::mock(DynamicSchema::class); - $schema->modelClass = 'Bacon'; - - // Assert - $manager->registerSchema($schema); - $this->assertAttributeEquals( - ['Bacon' => $schema], - 'schemas', - $manager - ); - } - - public function testShouldGetDataMapperForEntitiesWithRegisteredSchemas() - { - // Arrange - $manager = new Manager(); - $schema = m::mock(DynamicSchema::class); - $builder = $this->instance(Builder::class, m::mock(Builder::class)->makePartial()); - - $schema->modelClass = 'Bacon'; - - // Act - $manager->registerSchema($schema); - $result = $manager->getBuilder('Bacon'); - - // Assert - $this->assertEquals($builder, $result); - $this->assertAttributeEquals($schema, 'schema', $result); - } - - public function testShouldNotGetDataMapperForUnknownEntities() - { - // Arrange - $manager = new Manager(); - - // Assert - $result = $manager->getBuilder('Unknown'); - $this->assertNull($result); - } - public function testShouldInitializeOnce() { // Arrange diff --git a/tests/Unit/Cursor/CursorTest.php b/tests/Unit/Cursor/CursorTest.php index 51ce1ce3..9aa814bb 100644 --- a/tests/Unit/Cursor/CursorTest.php +++ b/tests/Unit/Cursor/CursorTest.php @@ -11,7 +11,6 @@ use MongoDB\Model\CachingIterator; use Mongolid\Connection\Connection; use Mongolid\Model\AbstractModel; -use Mongolid\Schema\DynamicSchema; use Mongolid\TestCase; use Serializable; use Traversable; @@ -91,7 +90,7 @@ public function testShouldCountDocuments() { // Arrange $collection = m::mock(Collection::class); - $cursor = $this->getCursor(null, $collection); + $cursor = $this->getCursor($collection); // Act $collection->expects() @@ -106,7 +105,7 @@ public function testShouldCountDocumentsWithCountFunction() { // Arrange $collection = m::mock(Collection::class); - $cursor = $this->getCursor(null, $collection); + $cursor = $this->getCursor($collection); // Act $collection->expects() @@ -122,7 +121,7 @@ public function testShouldRewind() // Arrange $collection = m::mock(Collection::class); $driverCursor = m::mock(CachingIterator::class); - $cursor = $this->getCursor(null, $collection, 'find', [[]], $driverCursor); + $cursor = $this->getCursor($collection, 'find', [[]], $driverCursor); $this->setProtected($cursor, 'position', 10); @@ -140,7 +139,7 @@ public function testShouldRewindACursorThatHasAlreadyBeenInitialized() // Arrange $collection = m::mock(Collection::class); $driverCursor = m::mock(CachingIterator::class); - $cursor = $this->getCursor(null, $collection, 'find', [[]], $driverCursor); + $cursor = $this->getCursor($collection, 'find', [[]], $driverCursor); $this->setProtected($cursor, 'position', 10); @@ -169,7 +168,7 @@ public function testShouldGetCurrent() }; $object->name = 'John Doe'; $driverCursor = new ArrayIterator([$object]); - $cursor = $this->getCursor(null, $collection, 'find', [[]], $driverCursor); + $cursor = $this->getCursor($collection, 'find', [[]], $driverCursor); // Assert $model = $cursor->current(); @@ -186,7 +185,7 @@ public function testShouldGetFirst() }; $object->name = 'John Doe'; $driverCursor = new CachingIterator(new ArrayObject([$object])); - $cursor = $this->getCursor($object->getSchema(), $collection, 'find', [[]], $driverCursor); + $cursor = $this->getCursor($collection, 'find', [[]], $driverCursor); // Act $model = $cursor->first(); @@ -202,7 +201,7 @@ public function testShouldGetFirstWhenEmpty() $collection = m::mock(Collection::class); $driverCursor = new CachingIterator(new ArrayObject()); - $cursor = $this->getCursor(null, $collection, 'find', [[]], $driverCursor); + $cursor = $this->getCursor($collection, 'find', [[]], $driverCursor); // Act $result = $cursor->first(); @@ -239,7 +238,7 @@ public function testShouldImplementNextMethodFromIterator() // Arrange $collection = m::mock(Collection::class); $driverCursor = m::mock(CachingIterator::class); - $cursor = $this->getCursor(null, $collection, 'find', [[]], $driverCursor); + $cursor = $this->getCursor($collection, 'find', [[]], $driverCursor); $this->setProtected($cursor, 'position', 7); @@ -257,7 +256,7 @@ public function testShouldImplementValidMethodFromIterator() // Arrange $collection = m::mock(Collection::class); $driverCursor = m::mock(CachingIterator::class); - $cursor = $this->getCursor(null, $collection, 'find', [[]], $driverCursor); + $cursor = $this->getCursor($collection, 'find', [[]], $driverCursor); // Act $driverCursor->expects() @@ -272,7 +271,7 @@ public function testShouldWrapMongoDriverCursorWithIterator() { // Arrange $collection = m::mock(Collection::class); - $cursor = $this->getCursor(null, $collection, 'find', [['bacon' => true]]); + $cursor = $this->getCursor($collection, 'find', [['bacon' => true]]); $driverCursor = m::mock(Traversable::class); $driverIterator = m::mock(Iterator::class); @@ -325,7 +324,7 @@ public function testShouldReturnAllResults() $jef->occupation = 'tester'; $driverCursor = new CachingIterator(new ArrayObject([$bob, $jef])); - $cursor = $this->getCursor($object->getSchema(), $collection, 'find', [[]], $driverCursor); + $cursor = $this->getCursor($collection, 'find', [[]], $driverCursor); // Actions $result = $cursor->all(); @@ -348,7 +347,7 @@ public function testShouldReturnResultsToArray() // Arrange $collection = m::mock(Collection::class); $driverCursor = m::mock(CachingIterator::class); - $cursor = $this->getCursor(null, $collection, 'find', [[]], $driverCursor); + $cursor = $this->getCursor($collection, 'find', [[]], $driverCursor); // Act $driverCursor->expects() @@ -388,8 +387,7 @@ public function testShouldSerializeAnActiveCursor() { // Arrange $connection = $this->instance(Connection::class, m::mock(Connection::class)); - $schema = new DynamicSchema(); - $cursor = $this->getCursor($schema, null, 'find', [[]]); + $cursor = $this->getCursor(null, 'find', [[]]); $driverCollection = $this->getDriverCollection(); $this->setProtected($cursor, 'collection', $driverCollection); @@ -409,27 +407,22 @@ public function testShouldSerializeAnActiveCursor() } protected function getCursor( - $modelSchema = null, $collection = null, $command = 'find', $params = [[]], $driverCursor = null ) { - if (!$modelSchema) { - $modelSchema = m::mock(DynamicSchema::class.'[]'); - } - if (!$collection) { $collection = m::mock(Collection::class); } if (!$driverCursor) { - return new Cursor($modelSchema, $collection, $command, $params); + return new Cursor($collection, $command, $params); } $mock = m::mock( Cursor::class.'[getCursor]', - [$modelSchema, $collection, $command, $params] + [$collection, $command, $params] ); $mock->shouldAllowMockingProtectedMethods(); diff --git a/tests/Unit/Model/AbstractModelTest.php b/tests/Unit/Model/AbstractModelTest.php index b1b193d8..b9fe3fa5 100644 --- a/tests/Unit/Model/AbstractModelTest.php +++ b/tests/Unit/Model/AbstractModelTest.php @@ -6,7 +6,6 @@ use Mongolid\Cursor\CursorInterface; use Mongolid\Model\Exception\NoCollectionNameException; use Mongolid\Query\Builder; -use Mongolid\Schema\DynamicSchema; use Mongolid\TestCase; use stdClass; @@ -34,11 +33,6 @@ public function unsetCollection() { unset($this->collection); } - - public function setFields($value) - { - $this->fields = $value; - } }; } @@ -51,21 +45,6 @@ protected function tearDown() parent::tearDown(); } - public function testShouldHaveCorrectPropertiesByDefault() - { - // Assertions - $this->assertAttributeEquals( - [ - '_id' => 'objectId', - 'created_at' => 'createdAtTimestamp', - 'updated_at' => 'updatedAtTimestamp', - ], - 'fields', - $this->model - ); - $this->assertTrue($this->model->dynamic); - } - public function testShouldImplementModelTraits() { // Assertions @@ -81,9 +60,6 @@ public function testShouldSave() $builder = $this->instance(Builder::class, m::mock(Builder::class)); // Actions - $builder->expects() - ->setSchema(m::type(DynamicSchema::class)); - $builder->expects() ->save($this->model, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); @@ -98,9 +74,6 @@ public function testShouldInsert() $builder = $this->instance(Builder::class, m::mock(Builder::class)); // Actions - $builder->expects() - ->setSchema(m::type(DynamicSchema::class)); - $builder->expects() ->insert($this->model, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); @@ -115,9 +88,6 @@ public function testShouldUpdate() $builder = $this->instance(Builder::class, m::mock(Builder::class)); // Actions - $builder->expects() - ->setSchema(m::type(DynamicSchema::class)); - $builder->expects() ->update($this->model, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); @@ -132,9 +102,6 @@ public function testShouldDelete() $builder = $this->instance(Builder::class, m::mock(Builder::class)); // Actions - $builder->expects() - ->setSchema(m::type(DynamicSchema::class)); - $builder->expects() ->delete($this->model, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); @@ -143,28 +110,56 @@ public function testShouldDelete() $this->assertTrue($this->model->delete()); } - public function testSaveShouldReturnFalseIfCollectionIsNull() + public function testSaveShouldThrowExceptionIfCollectionIsNull() { + // Set $this->model->unsetCollection(); - $this->assertFalse($this->model->save()); + + // Expectations + $this->expectException(NoCollectionNameException::class); + $this->expectExceptionMessage('Collection name not specified into Model instance'); + + // Actions + $this->model->save(); } - public function testUpdateShouldReturnFalseIfCollectionIsNull() + public function testUpdateShouldThrowExceptionIfCollectionIsNull() { + // Set $this->model->unsetCollection(); - $this->assertFalse($this->model->update()); + + // Expectations + $this->expectException(NoCollectionNameException::class); + $this->expectExceptionMessage('Collection name not specified into Model instance'); + + // Actions + $this->model->update(); } - public function testInsertShouldReturnFalseIfCollectionIsNull() + public function testInsertShouldThrowExceptionIfCollectionIsNull() { + // Set $this->model->unsetCollection(); - $this->assertFalse($this->model->insert()); + + // Expectations + $this->expectException(NoCollectionNameException::class); + $this->expectExceptionMessage('Collection name not specified into Model instance'); + + // Actions + $this->model->insert(); } - public function testDeleteShouldReturnFalseIfCollectionIsNull() + public function testDeleteShouldThrowExceptionIfCollectionIsNull() { + // Set $this->model->unsetCollection(); - $this->assertFalse($this->model->delete()); + + // Expectations + $this->expectException(NoCollectionNameException::class); + $this->expectExceptionMessage('Collection name not specified into Model instance'); + + // Actions + $this->model->delete(); } public function testShouldGetWithWhereQuery() @@ -178,10 +173,7 @@ public function testShouldGetWithWhereQuery() // Actions $builder->expects() - ->setSchema(m::type(DynamicSchema::class)); - - $builder->expects() - ->where($query, $projection) + ->where(m::type(get_class($this->model)), $query, $projection) ->andReturn($cursor); // Assertions @@ -197,10 +189,7 @@ public function testShouldGetAll() // Actions $builder->expects() - ->setSchema(m::type(DynamicSchema::class)); - - $builder->expects() - ->all() + ->all(m::type(get_class($this->model))) ->andReturn($cursor); // Assertions @@ -216,10 +205,7 @@ public function testShouldGetFirstWithQuery() // Actions $builder->expects() - ->setSchema(m::type(DynamicSchema::class)); - - $builder->expects() - ->first($query, $projection) + ->first(m::type(get_class($this->model)), $query, $projection) ->andReturn($this->model); // Assertions @@ -235,10 +221,7 @@ public function testShouldGetFirstOrFail() // Actions $builder->expects() - ->setSchema(m::type(DynamicSchema::class)); - - $builder->expects() - ->firstOrFail($query, $projection) + ->firstOrFail(m::type(get_class($this->model)), $query, $projection) ->andReturn($this->model); // Assertions @@ -253,10 +236,7 @@ public function testShouldGetFirstOrNewAndReturnExistingModel() // Actions $builder->expects() - ->setSchema(m::type(DynamicSchema::class)); - - $builder->expects() - ->first($id) + ->first(m::type(get_class($this->model)), $id, []) ->andReturn($this->model); // Assertions @@ -271,61 +251,25 @@ public function testShouldGetFirstOrNewAndReturnNewModel() // Actions $builder->expects() - ->setSchema(m::type(DynamicSchema::class)); - - $builder->expects() - ->first($id) + ->first(m::type(get_class($this->model)), $id, []) ->andReturn(null); // Assertions $this->assertNotEquals($this->model, $this->model->firstOrNew($id)); } - public function testShouldGetSchemaIfFieldsIsTheClassName() - { - // Set - $this->model->setFields('MySchemaClass'); - $schema = $this->instance('MySchemaClass', m::mock(DynamicSchema::class)); - - // Assertions - $this->assertSame( - $schema, - $this->model->getSchema() - ); - } - - public function testShouldGetSchemaIfFieldsDescribesSchemaFields() + public function testShouldGetBuilder() { // Set - $fields = ['name' => 'string', 'age' => 'int']; - $this->model->setFields($fields); - - // Assertions - $result = $this->model->getSchema(); - $this->assertInstanceOf(DynamicSchema::class, $result); - $this->assertSame($fields, $result->fields); - $this->assertSame($this->model->dynamic, $result->dynamic); - $this->assertSame($this->model->getCollectionName(), $result->collection); - $this->assertSame(get_class($this->model), $result->modelClass); - } - - public function testShouldGetDataMapper() - { - // Set - $model = m::mock(AbstractModel::class.'[getSchema]'); - $schema = m::mock(DynamicSchema::class.'[]'); + $model = new class extends AbstractModel + { + }; // Actions - $model->shouldAllowMockingProtectedMethods(); - - $model->expects() - ->getSchema() - ->andReturn($schema); + $result = $this->callProtected($model, 'getBuilder'); // Assertions - $result = $this->callProtected($model, 'getBuilder'); $this->assertInstanceOf(Builder::class, $result); - $this->assertSame($schema, $result->getSchema()); } public function testShouldRaiseExceptionWhenHasNoCollectionAndTryToCallAllFunction() diff --git a/tests/Unit/Model/HasRelationsTraitTest.php b/tests/Unit/Model/HasRelationsTraitTest.php index 14e9cd42..899cfe4e 100644 --- a/tests/Unit/Model/HasRelationsTraitTest.php +++ b/tests/Unit/Model/HasRelationsTraitTest.php @@ -24,7 +24,7 @@ public function testShouldReferenceOne($fieldValue, $expectedQuery) // Expectations $builder->expects() - ->first($expectedQuery, []) + ->first(m::type(RelatedStub::class), $expectedQuery, []) ->andReturn($expected); // Actions @@ -49,7 +49,7 @@ public function testShouldReferenceMany($fieldValue, $expectedQuery) // Expectations $builder->expects() - ->where($expectedQuery, []) + ->where(m::type(RelatedStub::class), $expectedQuery, []) ->andReturn($expected); // Actions diff --git a/tests/Unit/Query/BuilderTest.php b/tests/Unit/Query/BuilderTest.php index 48a29886..28a6af50 100644 --- a/tests/Unit/Query/BuilderTest.php +++ b/tests/Unit/Query/BuilderTest.php @@ -15,12 +15,11 @@ use Mongolid\Model\AbstractModel; use Mongolid\Model\Exception\ModelNotFoundException; use Mongolid\Model\ModelInterface; -use Mongolid\Schema\DynamicSchema; use Mongolid\TestCase; class BuilderTest extends TestCase { - public function testShouldBeAbleToConstructWithSchema() + public function testShouldBeAbleToConstruct() { // Arrange $connection = m::mock(Connection::class); @@ -49,7 +48,7 @@ public function testShouldSave($model, $writeConcern, $shouldFireEventAfter, $ex $builder->shouldAllowMockingProtectedMethods(); $builder->expects() - ->getCollection() + ->getCollection($model) ->andReturn($collection); $collection->expects() @@ -102,7 +101,7 @@ public function testShouldInsert($model, $writeConcern, $shouldFireEventAfter, $ $builder->shouldAllowMockingProtectedMethods(); $builder->expects() - ->getCollection() + ->getCollection($model) ->andReturn($collection); $collection->expects() @@ -148,7 +147,7 @@ public function testShouldInsertWithoutFiringEvents($model, $writeConcern, $shou $builder->shouldAllowMockingProtectedMethods(); $builder->expects() - ->getCollection() + ->getCollection($model) ->andReturn($collection); $collection->expects() @@ -180,7 +179,6 @@ public function testShouldUpdate($model, $writeConcern, $shouldFireEventAfter, $ $client = m::mock(Client::class); $database = m::mock(Database::class); $builder = new Builder($connection); - $builder->setSchema($model->getSchema()); $collection = m::mock(Collection::class); $parsedObject = ['_id' => 123]; @@ -197,7 +195,7 @@ public function testShouldUpdate($model, $writeConcern, $shouldFireEventAfter, $ ->andReturn($database); $database->expects() - ->selectCollection('') + ->selectCollection('models') ->andReturn($collection); $collection->expects() @@ -243,18 +241,32 @@ public function testShouldUpdateUnsettingFields() /** * {@inheritdoc} */ - public $fields = [ - '_id' => 'objectId', - 'unchanged' => 'string', + protected $collection = 'models'; + + /** + * {@inheritdoc} + */ + public $fillable = [ + 'name', + 'unchanged', ]; + + /** + * {@inheritdoc} + */ + protected $dynamic = false; + + /** + * {@inheritdoc} + */ + protected $timestamps = false; }; - $builder->setSchema($model->getSchema()); $collection = m::mock(Collection::class); $operationResult = m::mock(); $options = ['writeConcern' => new WriteConcern(1)]; $model->unchanged = 'unchanged'; - $model->notOnSchema = 'to be deleted'; + $model->notOnFillable = 'to be deleted'; $model->name = 'John'; $model->syncOriginalDocumentAttributes(); $model->_id = 123; @@ -270,13 +282,13 @@ public function testShouldUpdateUnsettingFields() ->andReturn($database); $database->expects() - ->selectCollection('') + ->selectCollection('models') ->andReturn($collection); $collection->expects() ->updateOne( ['_id' => 123], - ['$set' => ['_id' => 123], '$unset' => ['name' => '']], + ['$set' => ['_id' => 123], '$unset' => ['name' => '', 'notOnFillable' => '']], $options )->andReturn($operationResult); @@ -322,7 +334,7 @@ public function testUpdateShouldCallInsertWhenObjectHasNoId( $builder->shouldAllowMockingProtectedMethods(); $builder->expects() - ->getCollection() + ->getCollection($model) ->andReturn($collection); $collection->expects() @@ -371,7 +383,7 @@ public function testShouldDelete($model, $writeConcern, $shouldFireEventAfter, $ $builder->shouldAllowMockingProtectedMethods(); $builder->expects() - ->getCollection() + ->getCollection($model) ->andReturn($collection); $collection->expects() @@ -416,7 +428,7 @@ public function testDatabaseOperationsShouldBailOutIfTheEventHandlerReturnsFalse // Expect $builder->allows() - ->getCollection() + ->getCollection($model) ->andReturn($collection); $collection->expects($dbOperation) @@ -437,16 +449,13 @@ public function testShouldGetWithWhereQuery() // Arrange $connection = m::mock(Connection::class); $builder = m::mock(Builder::class.'[prepareValueQuery,getCollection]', [$connection]); - $schema = m::mock(DynamicSchema::class); + $model = m::mock(ModelInterface::class); $collection = m::mock(Collection::class); $query = 123; $preparedQuery = ['_id' => 123]; $projection = ['project' => true, '_id' => false]; - $schema->modelClass = 'stdClass'; - $builder->setSchema($schema); - $builder->shouldAllowMockingProtectedMethods(); // Expect @@ -455,15 +464,14 @@ public function testShouldGetWithWhereQuery() ->andReturn($preparedQuery); $builder->expects() - ->getCollection() + ->getCollection($model) ->andReturn($collection); // Act - $result = $builder->where($query, $projection); + $result = $builder->where($model, $query, $projection); // Assert $this->assertInstanceOf(Cursor::class, $result); - $this->assertAttributeEquals($schema, 'modelSchema', $result); $this->assertAttributeEquals($collection, 'collection', $result); $this->assertAttributeEquals('find', 'command', $result); $this->assertAttributeEquals( @@ -479,14 +487,15 @@ public function testShouldGetAll() $connection = m::mock(Connection::class); $builder = m::mock(Builder::class.'[where]', [$connection]); $mongolidCursor = m::mock(Cursor::class); + $model = m::mock(ModelInterface::class); // Expect $builder->expects() - ->where([]) + ->where($model, []) ->andReturn($mongolidCursor); // Act - $result = $builder->all(); + $result = $builder->all($model); // Assert $this->assertSame($mongolidCursor, $result); @@ -502,6 +511,10 @@ public function testShouldGetFirstWithQuery() $preparedQuery = ['_id' => 123]; $object = new class extends AbstractModel { + /** + * {@inheritdoc} + */ + protected $collection = 'models'; }; $builder->shouldAllowMockingProtectedMethods(); @@ -511,14 +524,14 @@ public function testShouldGetFirstWithQuery() ->andReturn($preparedQuery); $builder->expects() - ->getCollection() + ->getCollection($object) ->andReturn($collection); $collection->expects() ->findOne($preparedQuery, ['projection' => []]) ->andReturn($object); - $result = $builder->first($query); + $result = $builder->first($object, $query); // Assert $this->assertSame($object, $result); @@ -531,7 +544,7 @@ public function testFirstWithNullShouldNotHitTheDatabase() $builder = new Builder($connection); // Act - $result = $builder->first(null); + $result = $builder->first(m::mock(ModelInterface::class), null); // Assert $this->assertNull($result); @@ -557,14 +570,14 @@ public function testFirstOrFailShouldGetFirst() ->andReturn($preparedQuery); $builder->expects() - ->getCollection() + ->getCollection($object) ->andReturn($collection); $collection->expects() ->findOne($preparedQuery, ['projection' => []]) ->andReturn($object); - $result = $builder->firstOrFail($query); + $result = $builder->firstOrFail($object, $query); // Assert $this->assertSame($object, $result); @@ -575,21 +588,15 @@ public function testFirstOrFailWithNullShouldFail() // Arrange $connection = m::mock(Connection::class); $builder = new Builder($connection); - $builder->setSchema( - new class extends DynamicSchema - { - /** - * {@inheritdoc} - */ - public $modelClass = 'User'; - } - ); + $model = new class extends AbstractModel + { + }; $this->expectException(ModelNotFoundException::class); - $this->expectExceptionMessage('No query results for model [User].'); + $this->expectExceptionMessage('No query results for model ['.get_class($model).'].'); // Act - $builder->firstOrFail(null); + $builder->firstOrFail($model, null); } public function testShouldGetNullIfFirstCantFindAnything() @@ -597,15 +604,11 @@ public function testShouldGetNullIfFirstCantFindAnything() // Arrange $connection = m::mock(Connection::class); $builder = m::mock(Builder::class.'[prepareValueQuery,getCollection]', [$connection]); - $schema = m::mock(DynamicSchema::class); - + $model = m::mock(ModelInterface::class); $collection = m::mock(Collection::class); $query = 123; $preparedQuery = ['_id' => 123]; - $schema->modelClass = 'stdClass'; - $builder->setSchema($schema); - $builder->shouldAllowMockingProtectedMethods(); // Expect @@ -614,7 +617,7 @@ public function testShouldGetNullIfFirstCantFindAnything() ->andReturn($preparedQuery); $builder->expects() - ->getCollection() + ->getCollection($model) ->andReturn($collection); $collection->expects() @@ -622,7 +625,7 @@ public function testShouldGetNullIfFirstCantFindAnything() ->andReturn(null); // Act - $result = $builder->first($query); + $result = $builder->first($model, $query); // Assert $this->assertNull($result); @@ -636,16 +639,13 @@ public function testShouldGetFirstProjectingFields() Builder::class.'[prepareValueQuery,getCollection]', [$connection] ); - $schema = m::mock(DynamicSchema::class); + $model = m::mock(ModelInterface::class); $collection = m::mock(Collection::class); $query = 123; $preparedQuery = ['_id' => 123]; $projection = ['project' => true, 'fields' => false]; - $schema->modelClass = 'stdClass'; - $builder->setSchema($schema); - $builder->shouldAllowMockingProtectedMethods(); // Expect @@ -654,7 +654,7 @@ public function testShouldGetFirstProjectingFields() ->andReturn($preparedQuery); $builder->expects() - ->getCollection() + ->getCollection($model) ->andReturn($collection); $collection->expects() @@ -662,48 +662,34 @@ public function testShouldGetFirstProjectingFields() ->andReturn(null); // Act - $result = $builder->first($query, $projection); + $result = $builder->first($model, $query, $projection); // Assert $this->assertNull($result); } - public function testShouldGetSchemaMapper() - { - // Arrange - $connection = m::mock(Connection::class); - $builder = new Builder($connection); - $builder->schemaClass = 'MySchema'; - $schema = $this->instance('MySchema', m::mock(DynamicSchema::class)); - - // Act - $result = $this->callProtected($builder, 'getSchemaMapper'); - - // Assert - $this->assertInstanceOf(SchemaMapper::class, $result); - $this->assertSame($schema, $result->schema); - } - public function testShouldGetRawCollection() { // Arrange $connection = m::mock(Connection::class); $builder = new Builder($connection); $collection = m::mock(Collection::class); - $schema = m::mock(DynamicSchema::class); - $schema->collection = 'foobar'; + $model = m::mock(ModelInterface::class); - $builder->setSchema($schema); $connection->defaultDatabase = 'grimory'; - $connection->grimory = (object) ['foobar' => $collection]; + $connection->grimory = (object) ['models' => $collection]; // Expect $connection->expects() ->getRawConnection() ->andReturn($connection); + $model->expects() + ->getCollectionName() + ->andReturn('models'); + // Act - $result = $this->callProtected($builder, 'getCollection'); + $result = $this->callProtected($builder, 'getCollection', [$model]); // Assert $this->assertSame($collection, $result); @@ -834,9 +820,12 @@ public function getWriteConcernVariations() /** * {@inheritdoc} */ - public $fields = [ - '_id' => 'objectId', - ]; + protected $timestamps = false; + + /** + * {@inheritdoc} + */ + protected $collection = 'models'; }; $model2 = new class extends AbstractModel @@ -844,9 +833,12 @@ public function getWriteConcernVariations() /** * {@inheritdoc} */ - public $fields = [ - '_id' => 'objectId', - ]; + protected $timestamps = false; + + /** + * {@inheritdoc} + */ + protected $collection = 'models'; }; $model->_id = 123; diff --git a/tests/Unit/Query/BulkWriteTest.php b/tests/Unit/Query/BulkWriteTest.php index 0024727a..8efea1a5 100644 --- a/tests/Unit/Query/BulkWriteTest.php +++ b/tests/Unit/Query/BulkWriteTest.php @@ -7,7 +7,6 @@ use MongoDB\Driver\WriteConcern; use Mongolid\Connection\Connection; use Mongolid\Model\ModelInterface; -use Mongolid\Schema\DynamicSchema; use Mongolid\TestCase; class BulkWriteTest extends TestCase @@ -17,10 +16,6 @@ public function testShouldConstructBulkWriteObject() // Arrange $model = m::mock(ModelInterface::class); - // Expect - $model->expects() - ->getSchema(); - // Act $bulkWrite = new BulkWrite($model); @@ -34,10 +29,6 @@ public function testShouldSetAndGetMongoBulkWrite() $model = m::mock(ModelInterface::class); $mongoBulkWrite = new MongoBulkWrite(); - // Expect - $model->expects() - ->getSchema(); - // Act $bulkWrite = new BulkWrite($model); $bulkWrite->setBulkWrite($mongoBulkWrite); @@ -48,17 +39,14 @@ public function testShouldSetAndGetMongoBulkWrite() public function testShouldAddUpdateOneOperationToBulkWrite() { - // Arrange + // Set $model = m::mock(ModelInterface::class); $mongoBulkWrite = m::mock(new MongoBulkWrite()); $id = '123'; $data = ['name' => 'John']; - // Expect - $model->expects() - ->getSchema(); - + // Expectations $mongoBulkWrite->expects() ->update(['_id' => $id], ['$set' => $data], ['upsert' => true]); @@ -68,23 +56,20 @@ public function testShouldAddUpdateOneOperationToBulkWrite() ->getBulkWrite() ->andReturn($mongoBulkWrite); - // Act + // Actions $bulkWrite->updateOne($id, $data); } public function testShouldUpdateOneWithUnsetOperationToBulkWrite() { - // Arrange + // Set $model = m::mock(ModelInterface::class); $mongoBulkWrite = m::mock(new MongoBulkWrite()); $id = '123'; $data = ['name' => 'John']; - // Expect - $model->expects() - ->getSchema(); - + // Expectations $mongoBulkWrite->expects() ->update( m::on( @@ -104,7 +89,7 @@ function ($actual) { ->getBulkWrite() ->andReturn($mongoBulkWrite); - // Act + // Actions $bulkWrite->updateOne($id, $data, ['upsert' => true], '$unset'); } @@ -118,9 +103,6 @@ public function testShouldUpdateOneWithQueryOnFilterToBulkWrite() $data = ['grades.std' => 6]; // Expect - $model->expects() - ->getSchema(); - $mongoBulkWrite->expects() ->update( m::on( @@ -147,25 +129,22 @@ function ($actual) use ($query) { public function testShouldExecuteBulkWrite() { $model = m::mock(ModelInterface::class); - $schema = m::mock(DynamicSchema::class); - $model->schema = $schema; $mongoBulkWrite = m::mock(new MongoBulkWrite()); $connection = $this->instance(Connection::class, m::mock(Connection::class)); $manager = m::mock(new Manager()); $connection->defaultDatabase = 'foo'; - $schema->collection = 'bar'; $namespace = 'foo.bar'; // Expect - $model->expects() - ->getSchema() - ->andReturn($schema); - $connection->expects() ->getRawManager() ->andReturn($manager); + $model->expects() + ->getCollectionName() + ->andReturn('bar'); + $manager->expects() ->executeBulkWrite($namespace, $mongoBulkWrite, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); diff --git a/tests/Unit/Query/ModelMapperTest.php b/tests/Unit/Query/ModelMapperTest.php new file mode 100644 index 00000000..a6519a86 --- /dev/null +++ b/tests/Unit/Query/ModelMapperTest.php @@ -0,0 +1,185 @@ +_id = 1; + $model->name = 'John'; + $model->age = 23; + $model->location = 'Brazil'; + $model->created_at = new UTCDateTime(); // `$model->timestamps` is false! + + // Actions + $result = $modelMapper->map($model, ['name', 'age'], false, false); + + // Assertions + $this->assertSame( + [ + '_id' => 1, + 'name' => 'John', + 'age' => 23, + ], + $result + ); + } + + public function testShouldNotClearDynamicFieldsIfModelIsDynamic() + { + // Set + $model = new class extends AbstractModel + { + }; + + $modelMapper = new ModelMapper(); + $model->_id = 1; + $model->name = 'John'; + $model->age = 23; + $model->location = 'Brazil'; + + // Actions + $result = $modelMapper->map($model, ['name', 'age'], true, false); + + // Assertions + $this->assertSame( + [ + '_id' => 1, + 'name' => 'John', + 'age' => 23, + 'location' => 'Brazil', + ], + $result + ); + } + + public function testShouldClearNullFields() + { + // Set + $model = new class extends AbstractModel + { + }; + + $modelMapper = new ModelMapper(); + $model->_id = 1; + $model->name = 'John'; + $model->age = null; + $model->location = null; + + // Actions + $result = $modelMapper->map($model, ['name', 'age'], true, false); + + // Assertions + $this->assertSame( + [ + '_id' => 1, + 'name' => 'John', + ], + $result + ); + } + + public function testShouldGenerateAnIdIfModelDoesNotHaveOne() + { + // Set + $model = new class extends AbstractModel + { + }; + + $modelMapper = new ModelMapper(); + $model->name = 'John'; + + // Actions + $result = $modelMapper->map($model, [], true, true); + + // Assertions + $this->assertSame('John', $result['name']); + $this->assertInstanceOf(ObjectId::class, $result['_id']); + $this->assertInstanceOf(ObjectId::class, $model->_id); + } + + public function testShouldCastObjectId() + { + // Set + $model = new class extends AbstractModel + { + }; + + $modelMapper = new ModelMapper(); + $id = '5bfd396038b5fa0001462681'; + $model->_id = $id; + + // Actions + $result = $modelMapper->map($model, [], true, true); + + // Assertions + $this->assertInstanceOf(ObjectId::class, $result['_id']); + $this->assertSame($model->_id, $result['_id']); + $this->assertEquals(new ObjectId($id), $model->_id); + } + + public function testShouldHandleTimestampsCreatingCreatedAtField() + { + // Set + $model = new class extends AbstractModel + { + }; + + $modelMapper = new ModelMapper(); + $model->_id = 1; + $model->name = 'John'; + + // Actions + $result = $modelMapper->map($model, ['name', 'age'], true, true); + + // Assertions + $this->assertSame('John', $result['name']); + $this->assertSame(1, $result['_id']); + $this->assertInstanceOf(UTCDateTime::class, $result['created_at']); + $this->assertInstanceOf(UTCDateTime::class, $result['updated_at']); + $this->assertSame($model->created_at, $result['created_at']); + $this->assertSame($model->updated_at, $result['updated_at']); + $this->assertSame($model->updated_at, $model->created_at); + } + + public function testShouldHandleTimestampsOnlyUpdatingUpdatedAtField() + { + // Set + $model = new class extends AbstractModel + { + }; + + $modelMapper = new ModelMapper(); + $model->_id = 1; + $model->name = 'John'; + $createdAt = new UTCDateTime(new DateTime('-2 hour')); + $updatedAt = new UTCDateTime(new DateTime('-1 hour')); + $model->created_at = $createdAt; + $model->updated_at = $updatedAt; + + // Actions + $result = $modelMapper->map($model, ['name', 'age'], true, true); + + // Assertions + $this->assertSame('John', $result['name']); + $this->assertSame(1, $result['_id']); + $this->assertInstanceOf(UTCDateTime::class, $result['created_at']); + $this->assertInstanceOf(UTCDateTime::class, $result['updated_at']); + $this->assertSame($model->created_at, $result['created_at']); + $this->assertSame($model->updated_at, $result['updated_at']); + $this->assertSame($createdAt, $model->created_at); + $this->assertNotSame($updatedAt, $model->updated_at); + $this->assertGreaterThan($updatedAt, $model->updated_at); + } +} diff --git a/tests/Unit/Query/SchemaMapperTest.php b/tests/Unit/Query/SchemaMapperTest.php deleted file mode 100644 index be52c68c..00000000 --- a/tests/Unit/Query/SchemaMapperTest.php +++ /dev/null @@ -1,327 +0,0 @@ -instance( - 'My\Own\Schema', - new class() extends DynamicSchema - { - /** - * {@inheritdoc} - */ - public $fields = []; - } - ); - - $schema = new class() extends DynamicSchema - { - /** - * {@inheritdoc} - */ - public $fields = [ - 'name' => 'string', - 'surname' => 'string', - 'age' => 'int', - 'stuff' => 'schema.My\Own\Schema', - ]; - }; - - $schemaMapper = new SchemaMapper($schema); - $stuff = new stdClass(); - $stuff->address = '1, Blue Street'; - - $otherStuff = new stdClass(); - $otherStuff->address = '2, Green Street'; - - $data = [ - 'name' => 'John', - 'surname' => null, - 'age' => '23', - 'stuff' => [$stuff, $otherStuff], - 'invalid' => null, - 'empty' => '', - ]; - - $expected = [ - 'name' => 'John', - 'age' => 23, - 'stuff' => [['address' => '1, Blue Street'], ['address' => '2, Green Street']], - 'empty' => '', - ]; - - // Act - $result = $schemaMapper->map($data); - - // Assert - $this->assertSame($expected, $result); - } - - public function testShouldClearDynamicFieldsIfSchemaIsNotDynamic() - { - // Arrange - $schema = new class extends DynamicSchema - { - /** - * {@inheritdoc} - */ - public $fields = [ - 'name' => 'string', - 'age' => 'int', - ]; - - /** - * {@inheritdoc} - */ - public $dynamic = false; - }; - - $schemaMapper = new SchemaMapper($schema); - $data = [ - 'name' => 'John', - 'age' => 23, - 'location' => 'Brazil', - ]; - - // Assert - $this->callProtected($schemaMapper, 'clearDynamic', [&$data]); - $this->assertEquals( - [ - 'name' => 'John', - 'age' => 23, - ], - $data - ); - } - - public function testShouldNotClearDynamicFieldsIfSchemaIsDynamic() - { - // Arrange - $schema = new class extends DynamicSchema - { - /** - * {@inheritdoc} - */ - public $fields = [ - 'name' => 'string', - 'age' => 'int', - ]; - }; - $schemaMapper = new SchemaMapper($schema); - $data = [ - 'name' => 'John', - 'age' => 23, - 'location' => 'Brazil', - ]; - - // Assert - $this->callProtected($schemaMapper, 'clearDynamic', [&$data]); - $this->assertEquals( - [ - 'name' => 'John', - 'age' => 23, - 'location' => 'Brazil', - ], - $data - ); - } - - public function testShouldParseFieldIntoCastableType() - { - // Arrange - $schema = new class extends DynamicSchema - { - }; - $schemaMapper = new SchemaMapper($schema); - - // Assert - $this->assertSame( - 23, - $schemaMapper->parseField('23', 'int') - ); - - $this->assertSame( - '1234', - $schemaMapper->parseField(1234, 'string') - ); - } - - public function testShouldParseFieldIntoAnotherMappedSchemaIfTypeBeginsWithSchema() - { - // Arrange - $schema = new class extends DynamicSchema - { - }; - $schemaMapper = m::mock( - SchemaMapper::class.'[mapToSchema]', - [$schema] - ); - $schemaMapper->shouldAllowMockingProtectedMethods(); - - // Act - $schemaMapper->expects() - ->mapToSchema(['foo' => 'bar'], 'FooBarSchema') - ->andReturn(['foo' => 123]); - - // Assert - $this->assertSame( - ['foo' => 123], - $schemaMapper->parseField(['foo' => 'bar'], 'schema.FooBarSchema') - ); - } - - public function testShouldParseFieldUsingAMethodInSchemaIfTypeIsAnUnknownString() - { - // Arrange - $schemaClass = new class() extends DynamicSchema - { - public function pumpkinPoint($value) - { - return $value * 2; - } - }; - - $schema = new $schemaClass(); - $schemaMapper = new SchemaMapper($schema); - - // Assert - $this->assertSame( - 6, - $schemaMapper->parseField(3, 'pumpkinPoint') - ); - } - - public function testShouldMapAnArrayValueToAnotherSchema() - { - // Arrange - $schema = new class extends DynamicSchema - { - }; - $mySchema = $this->instance( - 'Xd\MySchema', - new class extends DynamicSchema - { - } - ); - $schemaMapper = new SchemaMapper($schema); - $value = ['foo' => 'bar']; - $test = $this; - - // When instantiating the SchemaMapper with the specified $param as dependency - Ioc::bind( - SchemaMapper::class, - function ($container, $params) use ($value, $mySchema, $test) { - // Check if mySchema has been injected correctly - $test->assertSame($mySchema, $params['schema']); - - // Instantiate a SchemaMapper with mySchema - $anotherSchemaMapper = m::mock(SchemaMapper::class, [$params['schema']]); - - // Set expectation to receive a map call - $anotherSchemaMapper->expects() - ->map($value) - ->andReturn(['foo' => 'PARSED']); - - return $anotherSchemaMapper; - } - ); - - // Act - $result = $this->callProtected($schemaMapper, 'mapToSchema', [$value, 'Xd\MySchema']); - - // Assert - $this->assertEquals([['foo' => 'PARSED']], $result); - } - - public function testShouldParseToArrayGettingObjectAttributes() - { - // Arrange - $schema = new class extends DynamicSchema - { - }; - $schemaMapper = new SchemaMapper($schema); - $object = (object) ['foo' => 'bar', 'name' => 'wilson']; - - // Assert - $this->assertEquals( - ['foo' => 'bar', 'name' => 'wilson'], - $this->callProtected($schemaMapper, 'parseToArray', [$object]) - ); - } - - public function testShouldParseToArrayIfIsAnArray() - { - // Arrange - $schema = new class extends DynamicSchema - { - }; - $schemaMapper = new SchemaMapper($schema); - $object = ['age' => 25]; - - // Assert - $this->assertEquals( - $object, - $this->callProtected($schemaMapper, 'parseToArray', [$object]) - ); - } - - public function testShouldGetAttributesWhenObjectImplementsAttributesAccessInterface() - { - // Arrange - $schema = new class extends DynamicSchema - { - }; - $schemaMapper = new SchemaMapper($schema); - $object = new class implements HasAttributesInterface - { - public function getDocumentAttribute(string $key) - { - } - - public function getDocumentAttributes(): array - { - return ['foo' => 'bar']; - } - - public function fill(array $input, bool $force = false) - { - } - - public function cleanDocumentAttribute(string $key) - { - } - - public function setDocumentAttribute(string $key, $value) - { - } - - public function syncOriginalDocumentAttributes() - { - } - - public function getOriginalDocumentAttributes(): array - { - } - - public function hasDocumentAttribute(string $key): bool - { - } - }; - - // Assert - $this->assertEquals( - ['foo' => 'bar'], - $this->callProtected($schemaMapper, 'parseToArray', [$object]) - ); - } -} diff --git a/tests/Unit/Schema/DynamicSchemaTest.php b/tests/Unit/Schema/DynamicSchemaTest.php deleted file mode 100644 index 2e75b1fb..00000000 --- a/tests/Unit/Schema/DynamicSchemaTest.php +++ /dev/null @@ -1,173 +0,0 @@ -assertAttributeEquals(true, 'dynamic', $schema); - } - - public function testMustHaveAnModelClass() - { - // Arrange - $schema = new DynamicSchema(); - - // Assert - $this->assertAttributeEquals('stdClass', 'modelClass', $schema); - } - - public function testShouldCastNullIntoObjectId() - { - // Arrange - $schema = new DynamicSchema(); - $value = null; - - // Assert - $this->assertInstanceOf( - ObjectId::class, - $schema->objectId($value) - ); - } - - public function testShouldNotCastRandomStringIntoObjectId() - { - // Arrange - $schema = new DynamicSchema(); - $value = 'A random string'; - - // Assert - $this->assertEquals( - $value, - $schema->objectId($value) - ); - } - - public function testShouldCastObjectIdStringIntoObjectId() - { - // Arrange - $schema = new DynamicSchema(); - $value = '507f1f77bcf86cd799439011'; - - // Assert - $this->assertInstanceOf( - ObjectId::class, - $schema->objectId($value) - ); - - $this->assertEquals( - $value, - (string) $schema->objectId($value) - ); - } - - public function testShouldCastNullIntoAutoIncrementSequence() - { - // Arrange - $schema = new DynamicSchema(); - $sequenceService = $this->instance(SequenceService::class, m::mock(SequenceService::class)); - $value = null; - - $schema->collection = 'resources'; - - // Act - $sequenceService->expects() - ->getNextValue('resources') - ->andReturn(7); - - // Assertion - $this->assertEquals(7, $schema->sequence($value)); - } - - public function testShouldNotAutoIncrementSequenceIfValueIsNotNull() - { - $schema = new DynamicSchema(); - $sequenceService = $this->instance(SequenceService::class, m::mock(SequenceService::class)); - $value = 3; - - $schema->collection = 'resources'; - - // Act - $sequenceService->expects() - ->getNextValue('resources') - ->never() - ->andReturn(7); // Should never be returned - - // Assertion - $this->assertEquals(3, $schema->sequence($value)); - } - - public function testShouldCastDocumentTimestamps() - { - // Arrange - $schema = new DynamicSchema(); - $value = null; - - // Assertion - $this->assertInstanceOf( - UTCDateTime::class, - $schema->createdAtTimestamp($value) - ); - } - - public function testShouldRefreshUpdatedAtTimestamps() - { - // Arrange - $schema = new DynamicSchema(); - $value = (new UTCDateTime(25)); - - // Assertion - $result = $schema->updatedAtTimestamp($value); - $this->assertInstanceOf(UTCDateTime::class, $result); - $this->assertNotEquals(25000, (string) $result); - } - - /** - * @dataProvider createdAtTimestampsFixture - */ - public function testShouldNotRefreshCreatedAtTimestamps( - $value, - $expectation, - $compareTimestamp = true - ) { - // Arrange - $schema = new DynamicSchema(); - - // Assertion - $result = $schema->createdAtTimestamp($value); - $this->assertInstanceOf(get_class($expectation), $result); - if ($compareTimestamp) { - $this->assertEquals((string) $expectation, (string) $result); - } - } - - public function createdAtTimestampsFixture() - { - return [ - 'MongoDB driver UTCDateTime' => [ - 'value' => new UTCDateTime(25), - 'expectation' => new UTCDateTime(25), - ], - 'Empty field' => [ - 'value' => null, - 'expectation' => new UTCDateTime(), - 'compareTimestamp' => false, - ], - 'An string' => [ - 'value' => 'foobar', - 'expectation' => new UTCDateTime(), - 'compareTimestamp' => false, - ], - ]; - } -} From 6881038fc0a4136af75edb459ce4ae0852561869 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Tue, 27 Nov 2018 12:34:28 -0200 Subject: [PATCH 077/116] Update readme --- README.md | 46 ++++----- composer.json | 2 +- docs/basics.md | 14 ++- docs/datamapper.md | 228 ------------------------------------------ docs/index.md | 2 +- docs/relationships.md | 24 ++--- mkdocs.yml | 1 - 7 files changed, 37 insertions(+), 280 deletions(-) delete mode 100644 docs/datamapper.md diff --git a/README.md b/README.md index 3c91bc34..cdfba99f 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,48 @@ -# Mongolid ODM for MongoDB (PHP7) +# Mongolid ODM for MongoDB -> Easy, powerful and ultrafast ODM for PHP 7.1+ build on top of the [new mongodb driver](https://docs.mongodb.org/ecosystem/drivers/php/). +

Mongolid

-![Mongolid](https://user-images.githubusercontent.com/1991286/28967747-fe5c258a-78f2-11e7-91c7-8850ffb32004.png) +

+Build Status +Coverage Status +Latest Stable Version +Total Downloads +License +

-Mongolid supports both **ActiveRecord** and **DataMapper** patterns. **You choose! (:** +## About Mongolid -[![Build Status](https://travis-ci.org/leroy-merlin-br/mongolid.svg?branch=master)](https://travis-ci.org/leroy-merlin-br/mongolid) -[![Coverage Status](https://coveralls.io/repos/github/leroy-merlin-br/mongolid/badge.svg?branch=master)](https://coveralls.io/github/leroy-merlin-br/mongolid?branch=master) -[![Latest Stable Version](https://poser.pugx.org/leroy-merlin-br/mongolid/v/stable)](https://packagist.org/packages/leroy-merlin-br/mongolid) -[![Total Downloads](https://poser.pugx.org/leroy-merlin-br/mongolid/downloads)](https://packagist.org/packages/leroy-merlin-br/mongolid) -[![Latest Unstable Version](https://poser.pugx.org/leroy-merlin-br/mongolid/v/unstable)](https://packagist.org/packages/leroy-merlin-br/mongolid) -[![License](https://poser.pugx.org/leroy-merlin-br/mongolid/license)](https://packagist.org/packages/leroy-merlin-br/mongolid) +Easy, powerful and ultrafast ODM for PHP 7.1+ build on top of the [new mongodb driver](https://docs.mongodb.org/ecosystem/drivers/php/). + +Mongolid supports **ActiveRecord** pattern. - ## Introduction Mongolid ODM (Object Document Mapper) provides a beautiful, simple implementation for working with MongoDB. Each database collection can have a corresponding "Model" which is used to interact with that collection. -> **Note:** If you are working with Laravel, take a look at [mongolid-laravel repository](https://github.com/leroy-merlin-br/mongolid-laravel). +**Note:** If you are working with Laravel, take a look at [mongolid-laravel repository](https://github.com/leroy-merlin-br/mongolid-laravel). - ## Installation -You can install library through Composer: +You can install the library through Composer: ``` $ composer require leroy-merlin-br/mongolid ``` -### Requirements - +## Requirements - PHP **7.1** - [MongoDB Driver](http://php.net/manual/en/set.mongodb.php) -## [Read the Docs: leroy-merlin-br.github.com/mongolid](http://leroy-merlin-br.github.com/mongolid) -[![Mongolid Docs](https://user-images.githubusercontent.com/1991286/28967747-fe5c258a-78f2-11e7-91c7-8850ffb32004.png)](http://leroy-merlin-br.github.com/mongolid) +## Documentation +You can access the full documentation [here](http://leroy-merlin-br.github.com/mongolid). - ## License - Mongolid is free software distributed under the terms of the [MIT license](LICENSE). - ## Additional information +Made with ❤ by [Leroy Merlin Brazil](https://github.com/leroy-merlin-br) and [all contributors](https://github.com/leroy-merlin-br/mongolid/graphs/contributors). -Mongolid was proudly built by the [Leroy Merlin Brazil](https://github.com/leroy-merlin-br) team. [See all the contributors](https://github.com/leroy-merlin-br/mongolid/graphs/contributors). - -Any questions, feel free to contact us. +If you have any questions, feel free to contact us. -Any issues, please [report here](https://github.com/leroy-merlin-br/mongolid). +If you any issues, please [report here](https://github.com/leroy-merlin-br/mongolid/issues). diff --git a/composer.json b/composer.json index 30a0693c..b7cc05d5 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "leroy-merlin-br/mongolid", "description": "Easy, powerful and ultrafast ODM for PHP and MongoDB.", - "keywords": ["odm", "mongodb", "mongo", "nosql", "active record", "data mapper", "laravel"], + "keywords": ["odm", "mongodb", "mongo", "nosql", "active record", "laravel"], "license": "MIT", "authors": [ { diff --git a/docs/basics.md b/docs/basics.md index 2b084969..3648a1a5 100644 --- a/docs/basics.md +++ b/docs/basics.md @@ -31,10 +31,8 @@ Now you are ready to create your own models :smile: ## Basic Usage -> **Note:** Mongolid does support [**DataMapper** pattern](./datamapper.md), but in order to understand it let's begin with the **ActiveRecord** pattern: - ```php -class Post extends Mongolid\Model\ActiveRecord +class Post extends \Mongolid\Model\AbstractModel { } ``` @@ -44,7 +42,7 @@ Note that we did not tell Mongolid which collection to use for our `Post` model. You may specify a collection by defining a `collection` property on your model: ```php -class Post extends ActiveRecord { +class Post extends \Mongolid\Model\AbstractModel { protected $collection = 'posts'; @@ -192,16 +190,16 @@ $post->delete(); ## Mass Assignment -If you are extending `Mongolid\Model\ActiveRecord` you can set an array of attributes to the model using the `fill` method. These attributes are then assigned to the model via mass-assignment. This is convenient; however, can be a **serious** security concern when blindly passing user input into a model. If user input is blindly passed into a model, the user is free to modify **any** and **all** of the model's attributes. By default, all attributes are fillable. +If you are extending `Mongolid\Model\AbstractModel` you can set an array of attributes to the model using the `fill` method. These attributes are then assigned to the model via mass-assignment. This is convenient; however, can be a **serious** security concern when blindly passing user input into a model. If user input is blindly passed into a model, the user is free to modify **any** and **all** of the model's attributes. By default, all attributes are fillable. -`Mongolid\Model\ActiveRecord` (and `Mongolid\Model\Attributes` trait) will use the `fillable` or `guarded` properties on your model. +`Mongolid\Model\AbstractModel` (and `Mongolid\Model\Attributes` trait) will use the `fillable` or `guarded` properties on your model. The `fillable` property specifies which attributes should be mass-assignable. This can be set at the class or instance level. **Defining Fillable Attributes On A Model** ```php -class Post extends ActiveRecord { +class Post extends \Mongolid\Model\AbstractModel { protected $fillable = ['title', 'category', 'body']; @@ -215,7 +213,7 @@ The inverse of `fillable` is `guarded`, and serves as a "black-list" instead of **Defining Guarded Attributes On A Model** ```php -class Post extends ActiveRecord { +class Post extends \Mongolid\Model\AbstractModel { protected $guarded = ['_id', 'votes']; diff --git a/docs/datamapper.md b/docs/datamapper.md deleted file mode 100644 index 9185af6d..00000000 --- a/docs/datamapper.md +++ /dev/null @@ -1,228 +0,0 @@ -## Introduction - -In the [Basics](./basics.md#basic-usage) section you learned how to use Mongolid with the ActiveRecord pattern. The following section you explain what changes are necessary in order to use the DataMapper pattern of Mongolid. - -> **Note:** To use Mongolid in the DataMapper pattern is optional. If you are looking for a more [Domain Driven Design](https://en.wikipedia.org/wiki/Domain-driven_design) approach in your project it may be interesting to your. But if you are satisfied with what you've learned in the other sections, feel free to skip this one. - -## Basics - -First of all, you have to define a _Schema_ for your model. This is the way to map objects into the database. But don't worry, your schema can be dynamic, which means that you can define other fields than the specified ones. - -> **Note:** The _Mongolid Schema_ is equivalent to [mapping your objects using annotation or xml](http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/xml-mapping.html) in other ORM or ODM libraries. - -In order to define a schema you should extend `Mongolid\Schema\Schema` or `Mongolid\Schema\DynamicSchema`: - -```php - 'objectId', - 'title' => 'string', - 'body' => 'string', - 'views' => 'int', - 'created_at' => 'createdAtTimestamp', - 'updated_at' => 'updatedAtTimestamp' - ]; -} -``` - -Then you should register an instance of the schema into the `Mongolid\Connection\Manager`: - -```php -$manager->registerSchema(new ArticleSchema); -``` - -Now you just have to create your own Domain Entity (: - -```php -class Article -{ - public $_id - public $title - public $body - public $views - public $created_at - public $updated_at - - public function render() { - return "# {$this->title}\n\n{$this->body}"; - } -} -``` - -## Interacting with the database - -Interact with the database using the `DataMapper` retrieved through the Mongolid Manager: - -```php -$article = $manager->getMapper(Article::class) - ->where(['title' => 'Foobar'])->first(); - -get_class($article) // Article - -$article->render() // # Foobar\n\nBody -$article->title = 'How Mongolid saved the day'; - -$manager->getMapper(Article::class)->save($article); // true -``` - -## Schema definition - -When defining your schema you can eighter extend the `Mongolid\Schema\Schema` or `Mongolid\Schema\DynamicSchema`. The main difference is the `$dynamic` property value. - -**Making a schema dynamic** - -The `$dynamic` property is a `boolean` that tells if the schema will accept additional fields that are not specified in the $fields property. This is usefull if you doesn't have a strict document format or if you want to take full advantage of the "schemaless" nature of MongoDB. - -**Defining the collection and the Domain Entity to be mapped** - -`$collection` property should be the name of the collection where this kind of document is going to be saved or retrieved from. And the `$entityClass` should be the name of the class that will be used to represent a document of the Schema when retrieve from the database. - -**Defining the fields** - -The `$fields` property is an array that tells how a document should look like. For each field of the document you can specify a type or how it will be "formated". - -If an scalar type is used, it will perform a cast operation in the value. Othewise the schema will use the type as the name of the method to be called. - -See `Mongolid\Schema\Schema::objectId` method for example. It means that if a field type (in `$fields`) is defined as `"objectId"`, it will pass trought the `Mongolid\Schema\Schema::objectId` before being saved in the database. - -Because of this you can create your own custom field types easily. Just create a new public method in your schema and you are ready to use it's name as a type definition in `$fields`. - -The last option is to define a field as another schema by using the syntax _'schema.<Class>'_ This represents one or more embedded documents that will be formated using another _Schema_ class. - -### Default Schema $field types - -By default the `Mongolid\Schema\Schema` contains the following types: - -| type | description | -|--------------------|--------------------------------------------------------------------------------------------------------------------| -| <scalar type> | Casts field to `int`, `integer`, `bool`, `boolean`, `float`, `double`, `real` or `string`. | -| objectId | If the field is not defined or if it's a string compatible with ObjectId notation it will be saved as an ObjectId. | -| sequence | If value is zero or not defined a new auto-increment integer will be "generated" for that collection. | -| createdAtTimestamp | Prepares the field to be the datetime that the document has been created. (MongoDB\BSON\UTCDateTime) | -| updatedAtTimestamp | Prepares the field to be now whenever the document is saved. (MongoDB\BSON\UTCDateTime) | -| schema.<Class> | Delegates the objects or arrays within the field to be mapped by another schema. | - -But you can easily create your own types. For example: - -```php -class MySchema extends Mongolid\Schema\Schema -{ - ... - - $fields = [ - '_id' => 'sequence', - 'name' => 'uppercaseString' // Will be processed by the method below - ]; - - public function uppercaseString(string $value) { - return strtoupper($value); - } -} -``` - -### Schema definition for embedded documents - -By using the `"schema."` syntax you can create schemas and map Entities for embedded documents. For example, with the definition below: - -```php -class PostSchema extends Mongolid\Schema\Schema -{ - public $entityClass = 'Post'; - public $collection = 'posts'; - - $fields = [ - '_id' => 'objectId', - 'title' => 'string', - 'body' => 'string', - 'comments' => 'schema.CommentSchema' // Embeds comments - ]; -} - -class CommentSchema extends Mongolid\Schema\Schema -{ - public $entityClass = 'Comment'; - public $collection = null; // Optional since all comments will be embedded - - $fields = [ - '_id' => 'objectId', - 'body' => 'string', - 'author' => 'string' - ]; -} -``` - -The MongoDB document - -```javascript -{ - _id: ObjectId("5099803df3f4948bd2f98391"), - title: "Foo bar", - body: "Lorem ipsum", - comments: [ - { - _id: ObjectId("507f1f77bcf86cd799439011"), - body: "Awesome!", - author: "John Doe", - }, - { - _id: ObjectId("507f191e810c19729de860ea"), - body: "Cool!", - author: "Alan Turing", - } - ] -} -``` - -...Will be mapped to the actual domain Entities: - -```php -$post = $manager->getMapper(Post::class)->first('5099803df3f4948bd2f98391'); - -get_class($post) // Post - -get_class($post->comments[1]) // Comment; - -$post->comments[1]->author // Alan Turing - -// And if you are using Mongolid\Model\Relations trait in your entity ;) -$post->comments()->sort(['author' => 1])->first()->name // Alan Turing -``` - -## Entity helpers - -Mongolid provides some helpers for Domain Entities in the form of traits. - -### Attribute trait - -The `Mongolid\Model\Attributes` trait adds attribute getters, setters and the `fill` method that can be used with `$fillable` and `$guarded` properties to make sure that only the correct attributes will be set. - -By including this trait all the entity attributes will be isolated in the `$attributes` property of your entity and [mass assignment capabilities](./basics.md#mass-assignment) will be available. - -See `Mongolid\Model\Attributes` for more information. - -### Relations trait - -The `Mongolid\Model\Relations` trait adds functionality for handling relations between entities. It will enable `embedsOne`, `embedsMany`, `referencesOne`,`referencesMany` methods and all the [relationship capabilities](./relationships.md) in the entity. - -**Using relationship methods in DataMapper pattern** - -When using relationship methods (`embedsMany` for example) in DataMapper pattern, instead of referencing the _Entity_ class you should reference the _Schema_ class, for example: - -```php - ... - public function comments() - { - return $this->embedsMany('CommentSchema', 'comments'); - } -``` - -In the example above Mongolid will use the `CommentSchema` to determine which Entity object should be used to map data of the `comments` field. - -See `Mongolid\Model\Relations` for more information. diff --git a/docs/index.md b/docs/index.md index 04bc74c3..ca360835 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,7 +2,7 @@ > Easy, powerful and ultrafast ODM for PHP7 build on top of the [new mongodb driver](https://docs.mongodb.org/ecosystem/drivers/php/). -Mongolid supports both **ActiveRecord** and **DataMapper** patterns. **You choose! (:** +Mongolid supports **ActiveRecord** pattern. [![Build Status](https://travis-ci.org/leroy-merlin-br/mongolid.svg?branch=master)](https://travis-ci.org/leroy-merlin-br/mongolid) [![Coverage Status](https://coveralls.io/repos/github/leroy-merlin-br/mongolid/badge.svg?branch=master)](https://coveralls.io/github/leroy-merlin-br/mongolid?branch=master) diff --git a/docs/relationships.md b/docs/relationships.md index ab07d326..a4798bef 100644 --- a/docs/relationships.md +++ b/docs/relationships.md @@ -18,8 +18,7 @@ A Embeds One relationship is a very basic relation. For example, a `User` model **Defining A Embeds One Relation** ```php -// models/Person.php -class Person extends ActiveRecord { +class Person extends \Mongolid\Model\AbstractModel { // This model is saved in the collection people protected $collection = 'people'; @@ -30,8 +29,7 @@ class Person extends ActiveRecord { } } -// models/Phone.php -class Phone extends ActiveRecord { +class Phone extends \Mongolid\Model\AbstractModel { // This model will be embedded only protected $collection = null; @@ -106,8 +104,7 @@ $user->embed($phone); // Will update An example of a Embeds Many relation is a blog post that "has many" comments. We can model this relation like so: ```php -// models/Post.php -class Post extends ActiveRecord { +class Post extends \Mongolid\Model\AbstractModel { protected $collection = 'posts'; public function comments() @@ -117,8 +114,7 @@ class Post extends ActiveRecord { } -// models/Comment.php -class Comment extends ActiveRecord { +class Comment extends \Mongolid\Model\AbstractModel { // This model will be embedded only protected $collection = null; } @@ -181,8 +177,7 @@ In general, use references when embedding would result in duplication of data an **Defining A References One Relation** ```php -// models/Post.php -class Post extends ActiveRecord { +class Post extends \Mongolid\Model\AbstractModel { protected $collection = 'posts'; public function author() @@ -192,8 +187,7 @@ class Post extends ActiveRecord { } -// models/User.php -class User extends ActiveRecord { +class User extends \Mongolid\Model\AbstractModel { protected $collection = 'users'; } ``` @@ -248,8 +242,7 @@ In general, use references when embedding would result in duplication of data an **Defining A References Many Relation** ```php -// models/User.php -class User extends ActiveRecord { +class User extends \Mongolid\Model\AbstractModel { protected $collection = 'users'; public function questions() @@ -259,8 +252,7 @@ class User extends ActiveRecord { } -// models/Question.php -class Question extends ActiveRecord { +class Question extends \Mongolid\Model\AbstractModel { protected $collection = 'questions'; } ``` diff --git a/mkdocs.yml b/mkdocs.yml index 8bf0d0aa..ff4dd84e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -8,7 +8,6 @@ nav: - 'Home': 'index.md' - 'Basics': 'basics.md' - 'Relationships': 'relationships.md' - - 'DataMapper Pattern': 'datamapper.md' - 'API Documentation': 'api-docs.md' - 'Troubleshooting': 'troubleshooting.md' - 'Additional information': 'info.md' From a10fee37be5be5ca2fb631487f119197f4148841 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Tue, 27 Nov 2018 17:24:21 -0200 Subject: [PATCH 078/116] Improve test steps --- tests/Unit/Connection/ConnectionTest.php | 26 +-- tests/Unit/Connection/ManagerTest.php | 33 +-- tests/Unit/Container/IocTest.php | 12 +- tests/Unit/Cursor/CursorTest.php | 184 +++++++++------- tests/Unit/Cursor/EmbeddedCursorTest.php | 136 +++++++----- tests/Unit/Event/EventTriggerServiceTest.php | 28 +-- tests/Unit/Model/AbstractModelTest.php | 154 ++++++++++--- tests/Unit/Model/DocumentEmbedderTest.php | 30 +-- tests/Unit/Model/HasAttributesTraitTest.php | 143 ++++++------ tests/Unit/Model/HasRelationsTraitTest.php | 16 +- tests/Unit/Query/BuilderTest.php | 219 ++++++++++--------- tests/Unit/Query/BulkWriteTest.php | 34 +-- tests/Unit/TestCase.php | 6 +- tests/Unit/Util/LocalDateTimeTest.php | 60 ++--- tests/Unit/Util/ObjectIdUtilsTest.php | 44 ++-- tests/Unit/Util/SequenceServiceTest.php | 37 ++-- 16 files changed, 664 insertions(+), 498 deletions(-) diff --git a/tests/Unit/Connection/ConnectionTest.php b/tests/Unit/Connection/ConnectionTest.php index fde7cc81..d9bd77e0 100644 --- a/tests/Unit/Connection/ConnectionTest.php +++ b/tests/Unit/Connection/ConnectionTest.php @@ -9,32 +9,32 @@ class ConnectionTest extends TestCase { public function testShouldConstructANewConnection() { - // Arrange + // Set $server = 'mongodb://my-server/my_db'; $options = ['some', 'uri', 'options']; $driverOptions = ['some', 'driver', 'options']; - // Act + // Actions $connection = new Connection($server, $options, $driverOptions); - // Assert + // Assertions $this->assertAttributeInstanceOf(Client::class, 'rawConnection', $connection); - $this->assertAttributeEquals('my_db', 'defaultDatabase', $connection); + $this->assertAttributeSame('my_db', 'defaultDatabase', $connection); } public function testShouldDetermineDatabaseFromACluster() { - // Arrange + // Set $server = 'mongodb://my-server,other-server/my_db?replicaSet=someReplica'; $options = ['some', 'uri', 'options']; $driverOptions = ['some', 'driver', 'options']; - // Act + // Actions $connection = new Connection($server, $options, $driverOptions); - // Assert + // Assertions $this->assertAttributeInstanceOf(Client::class, 'rawConnection', $connection); - $this->assertAttributeEquals('my_db', 'defaultDatabase', $connection); + $this->assertAttributeSame('my_db', 'defaultDatabase', $connection); } public function testShouldGetRawConnection() @@ -56,22 +56,22 @@ public function testShouldGetRawConnection() $rawConnection = $connection->getRawConnection(); // Assertions - $this->assertAttributeEquals($expectedParameters['uri'], 'uri', $rawConnection); - $this->assertAttributeEquals($expectedParameters['typeMap'], 'typeMap', $rawConnection); + $this->assertAttributeSame($expectedParameters['uri'], 'uri', $rawConnection); + $this->assertAttributeSame($expectedParameters['typeMap'], 'typeMap', $rawConnection); } public function testShouldGetRawManager() { - // Arrange + // Set $server = 'mongodb://my-server/my_db'; $options = ['some', 'uri', 'options']; $driverOptions = ['some', 'driver', 'options']; - // Act + // Actions $connection = new Connection($server, $options, $driverOptions); $rawManager = $connection->getRawManager(); - // Assert + // Assertions $this->assertInstanceOf(Manager::class, $rawManager); } } diff --git a/tests/Unit/Connection/ManagerTest.php b/tests/Unit/Connection/ManagerTest.php index 6e7ee639..4651280f 100644 --- a/tests/Unit/Connection/ManagerTest.php +++ b/tests/Unit/Connection/ManagerTest.php @@ -18,26 +18,26 @@ protected function tearDown() public function testShouldAddAndGetConnection() { - // Arrange + // Set $manager = new Manager(); $connection = m::mock(Connection::class); $rawConnection = m::mock(Client::class); - // Expect + // Expectations $connection->expects() ->getRawConnection() ->andReturn($rawConnection); - // Act + // Actions $manager->setConnection($connection); - // Assert - $this->assertEquals($rawConnection, $manager->getConnection()); + // Assertions + $this->assertSame($rawConnection, $manager->getConnection()); } public function testShouldSetEventTrigger() { - // Arrange + // Set $test = $this; $manager = new Manager(); $container = m::mock(Container::class); @@ -45,31 +45,36 @@ public function testShouldSetEventTrigger() $this->setProtected($manager, 'container', $container); - // Act + // Expectations $container->expects() ->instance(EventTriggerService::class, m::type(EventTriggerService::class)) ->andReturnUsing(function ($class, $eventService) use ($test, $eventTrigger) { - $test->assertEquals(EventTriggerService::class, $class); - $test->assertAttributeEquals($eventTrigger, 'builder', $eventService); + $test->assertSame(EventTriggerService::class, $class); + $test->assertAttributeSame($eventTrigger, 'builder', $eventService); }); - // Assert + // Actions $manager->setEventTrigger($eventTrigger); } public function testShouldInitializeOnce() { - // Arrange + // Set $manager = new Manager(); + + // Actions $this->callProtected($manager, 'init'); - // Assertion - $this->assertAttributeEquals($manager, 'singleton', Manager::class); + // Assertions + $this->assertAttributeSame($manager, 'singleton', Manager::class); $this->assertAttributeInstanceOf(Container::class, 'container', $manager); + // Actions $container = $manager->container; $this->callProtected($manager, 'init'); + + // Assertions // Initializes again to make sure that it will not instantiate a new container - $this->assertAttributeEquals($container, 'container', $manager); + $this->assertAttributeSame($container, 'container', $manager); } } diff --git a/tests/Unit/Container/IocTest.php b/tests/Unit/Container/IocTest.php index 2cde5ce8..a1b6a7bf 100644 --- a/tests/Unit/Container/IocTest.php +++ b/tests/Unit/Container/IocTest.php @@ -17,27 +17,31 @@ protected function tearDown() public function testShouldCallMethodsProperlyWithNoArguments() { + // Set $container = m::mock(Container::class); + Ioc::setContainer($container); + // Expectations $container->expects() ->method() ->andReturn(true); - Ioc::setContainer($container); - + // Actions Ioc::method(); } public function testShouldCallMethodsProperlyWithArguments() { + // Set $container = m::mock(Container::class); + Ioc::setContainer($container); + // Expectations $container->expects() ->method(1, 2, 3) ->andReturn(true); - Ioc::setContainer($container); - + // Actions Ioc::method(1, 2, 3); } } diff --git a/tests/Unit/Cursor/CursorTest.php b/tests/Unit/Cursor/CursorTest.php index 9aa814bb..60418b47 100644 --- a/tests/Unit/Cursor/CursorTest.php +++ b/tests/Unit/Cursor/CursorTest.php @@ -3,6 +3,7 @@ use ArrayIterator; use ArrayObject; +use Exception; use Iterator; use Mockery as m; use MongoDB\Collection; @@ -19,12 +20,14 @@ class CursorTest extends TestCase { public function testShouldLimitDocumentQuantity() { - // Arrange + // Set $cursor = $this->getCursor(); - // Assert + // Actions $cursor->limit(10); - $this->assertAttributeEquals( + + // Assertions + $this->assertAttributeSame( [[], ['limit' => 10]], 'params', $cursor @@ -33,12 +36,14 @@ public function testShouldLimitDocumentQuantity() public function testShouldSortDocumentsOfCursor() { - // Arrange + // Set $cursor = $this->getCursor(); - // Assert + // Actions $cursor->sort(['name' => 1]); - $this->assertAttributeEquals( + + // Assertions + $this->assertAttributeSame( [[], ['sort' => ['name' => 1]]], 'params', $cursor @@ -47,12 +52,14 @@ public function testShouldSortDocumentsOfCursor() public function testShouldSkipDocuments() { - // Arrange + // Set $cursor = $this->getCursor(); - // Assert + // Actions $cursor->skip(5); - $this->assertAttributeEquals( + + // Assertions + $this->assertAttributeSame( [[], ['skip' => 5]], 'params', $cursor @@ -61,12 +68,14 @@ public function testShouldSkipDocuments() public function testShouldSetNoCursorTimeoutToTrue() { - // Arrange + // Set $cursor = $this->getCursor(); - // Assert + // Actions $cursor->disableTimeout(); - $this->assertAttributeEquals( + + // Assertions + $this->assertAttributeSame( [[], ['noCursorTimeout' => true]], 'params', $cursor @@ -75,75 +84,86 @@ public function testShouldSetNoCursorTimeoutToTrue() public function testShouldSetReadPreferenceParameterAccordingly() { - // Arrange + // Set $cursor = $this->getCursor(); $mode = ReadPreference::RP_SECONDARY; + + // Actions $cursor->setReadPreference($mode); $readPreferenceParameter = $this->getProtected($cursor, 'params')[1]['readPreference']; + $result = $readPreferenceParameter->getMode(); - // Assert + // Assertions $this->assertInstanceOf(ReadPreference::class, $readPreferenceParameter); - $this->assertSame($readPreferenceParameter->getMode(), $mode); + $this->assertSame($mode, $result); } public function testShouldCountDocuments() { - // Arrange + // Set $collection = m::mock(Collection::class); $cursor = $this->getCursor($collection); - // Act + // Expectations $collection->expects() ->count([]) ->andReturn(5); - // Assert - $this->assertEquals(5, $cursor->count()); + // Actions + $result = $cursor->count(); + + // Assertions + $this->assertSame(5, $result); } public function testShouldCountDocumentsWithCountFunction() { - // Arrange + // Set $collection = m::mock(Collection::class); $cursor = $this->getCursor($collection); - // Act + // Expectations $collection->expects() ->count([]) ->andReturn(5); - // Assert - $this->assertEquals(5, count($cursor)); + // Actions + $result = count($cursor); + + // Assertions + $this->assertSame(5, $result); } public function testShouldRewind() { - // Arrange + // Set $collection = m::mock(Collection::class); $driverCursor = m::mock(CachingIterator::class); $cursor = $this->getCursor($collection, 'find', [[]], $driverCursor); $this->setProtected($cursor, 'position', 10); - // Act + // Expectations $driverCursor->expects() ->rewind(); - // Assert + // Actions $cursor->rewind(); - $this->assertAttributeEquals(0, 'position', $cursor); + + // Assertions + $this->assertAttributeSame(0, 'position', $cursor); } public function testShouldRewindACursorThatHasAlreadyBeenInitialized() { - // Arrange + // Set $collection = m::mock(Collection::class); $driverCursor = m::mock(CachingIterator::class); $cursor = $this->getCursor($collection, 'find', [[]], $driverCursor); $this->setProtected($cursor, 'position', 10); - // Act + // Expectations $driverCursor->expects() ->rewind() ->andReturnUsing( @@ -154,14 +174,16 @@ function () use ($cursor) { } ); - // Assert + // Actions $cursor->rewind(); - $this->assertAttributeEquals(0, 'position', $cursor); + + // Assertions + $this->assertAttributeSame(0, 'position', $cursor); } public function testShouldGetCurrent() { - // Arrange + // Set $collection = m::mock(Collection::class); $object = new class extends AbstractModel { @@ -170,15 +192,17 @@ public function testShouldGetCurrent() $driverCursor = new ArrayIterator([$object]); $cursor = $this->getCursor($collection, 'find', [[]], $driverCursor); - // Assert + // Actions $model = $cursor->current(); + + // Assertions $this->assertInstanceOf(AbstractModel::class, $model); - $this->assertEquals('John Doe', $model->name); + $this->assertSame('John Doe', $model->name); } public function testShouldGetFirst() { - // Arrange + // Set $collection = m::mock(Collection::class); $object = new class extends AbstractModel { @@ -187,95 +211,104 @@ public function testShouldGetFirst() $driverCursor = new CachingIterator(new ArrayObject([$object])); $cursor = $this->getCursor($collection, 'find', [[]], $driverCursor); - // Act + // Actions $model = $cursor->first(); - // Assert + // Assertions $this->assertInstanceOf(get_class($object), $model); $this->assertSame('John Doe', $model->name); } public function testShouldGetFirstWhenEmpty() { - // Arrange + // Set $collection = m::mock(Collection::class); $driverCursor = new CachingIterator(new ArrayObject()); $cursor = $this->getCursor($collection, 'find', [[]], $driverCursor); - // Act + // Actions $result = $cursor->first(); - // Assert + // Assertions $this->assertNull($result); } public function testShouldRefreshTheCursor() { - // Arrange + // Set $driverCursor = new CachingIterator(new ArrayObject()); $cursor = $this->getCursor(); $this->setProtected($cursor, 'cursor', $driverCursor); - // Assert + // Actions $cursor->fresh(); - $this->assertAttributeEquals(null, 'cursor', $cursor); + + // Assertions + $this->assertAttributeSame(null, 'cursor', $cursor); } public function testShouldImplementKeyMethodFromIterator() { - // Arrange + // Set $cursor = $this->getCursor(); - $this->setProtected($cursor, 'position', 7); - // Assertion - $this->assertEquals(7, $cursor->key()); + // Actions + $result = $cursor->key(); + + // Assertions + $this->assertSame(7, $result); } public function testShouldImplementNextMethodFromIterator() { - // Arrange + // Set $collection = m::mock(Collection::class); $driverCursor = m::mock(CachingIterator::class); $cursor = $this->getCursor($collection, 'find', [[]], $driverCursor); $this->setProtected($cursor, 'position', 7); - // Act + // Expectations $driverCursor->expects() ->next(); - // Assert + // Actions $cursor->next(); - $this->assertEquals(8, $cursor->key()); + + // Assertions + $this->assertSame(8, $cursor->key()); } public function testShouldImplementValidMethodFromIterator() { - // Arrange + // Set $collection = m::mock(Collection::class); $driverCursor = m::mock(CachingIterator::class); $cursor = $this->getCursor($collection, 'find', [[]], $driverCursor); - // Act + // Expectations $driverCursor->expects() ->valid() ->andReturn(true); - // Assert - $this->assertTrue($cursor->valid()); + // Actions + $result = $cursor->valid(); + + // Assertions + $this->assertTrue($result); } public function testShouldWrapMongoDriverCursorWithIterator() { - // Arrange + // Set $collection = m::mock(Collection::class); $cursor = $this->getCursor($collection, 'find', [['bacon' => true]]); $driverCursor = m::mock(Traversable::class); $driverIterator = m::mock(Iterator::class); - // Act + // Expectations $collection->expects() ->find(['bacon' => true]) ->andReturn($driverCursor); @@ -302,14 +335,16 @@ public function testShouldWrapMongoDriverCursorWithIterator() ->key() ->andReturn(true); - // Assert + // Actions $result = $this->callProtected($cursor, 'getCursor'); + + // Assertions $this->assertInstanceOf(CachingIterator::class, $result); } public function testShouldReturnAllResults() { - // Arrange + // Set $collection = m::mock(Collection::class); $object = new class extends AbstractModel { @@ -344,12 +379,12 @@ public function testShouldReturnAllResults() public function testShouldReturnResultsToArray() { - // Arrange + // Set $collection = m::mock(Collection::class); $driverCursor = m::mock(CachingIterator::class); $cursor = $this->getCursor($collection, 'find', [[]], $driverCursor); - // Act + // Expectations $driverCursor->expects() ->rewind(); @@ -371,10 +406,11 @@ public function testShouldReturnResultsToArray() ['name' => 'jef', 'occupation' => 'tester'] ); + // Actions $result = $cursor->toArray(); - // Assert - $this->assertEquals( + // Assertions + $this->assertSame( [ ['name' => 'bob', 'occupation' => 'coder'], ['name' => 'jef', 'occupation' => 'tester'], @@ -385,24 +421,26 @@ public function testShouldReturnResultsToArray() public function testShouldSerializeAnActiveCursor() { - // Arrange + // Set $connection = $this->instance(Connection::class, m::mock(Connection::class)); $cursor = $this->getCursor(null, 'find', [[]]); $driverCollection = $this->getDriverCollection(); $this->setProtected($cursor, 'collection', $driverCollection); - // Act - $connection->expects() - ->getRawConnection() - ->andReturn($connection); - $connection->defaultDatabase = 'db'; $connection->db = $connection; $connection->my_collection = $driverCollection; // Return the same driver Collection - // Assert + // Expectations + $connection->expects() + ->getRawConnection() + ->andReturn($connection); + + // Actions $result = unserialize(serialize($cursor)); + + // Assertions $this->assertEquals($cursor, $result); } @@ -411,7 +449,7 @@ protected function getCursor( $command = 'find', $params = [[]], $driverCursor = null - ) { + ): Cursor { if (!$collection) { $collection = m::mock(Collection::class); } @@ -439,7 +477,7 @@ protected function getCursor( * Since the MongoDB\Collection is not serializable. This method will * emulate an unserializable collection from mongoDb driver. */ - protected function getDriverCollection() + protected function getDriverCollection(): Serializable { /* * Emulates a MongoDB\Collection non serializable behavior. @@ -455,7 +493,7 @@ public function unserialize($serialized) { } - public function getCollectionName() + public function getCollectionName(): string { return 'my_collection'; } diff --git a/tests/Unit/Cursor/EmbeddedCursorTest.php b/tests/Unit/Cursor/EmbeddedCursorTest.php index 8279a4fe..45468863 100644 --- a/tests/Unit/Cursor/EmbeddedCursorTest.php +++ b/tests/Unit/Cursor/EmbeddedCursorTest.php @@ -9,7 +9,7 @@ class EmbeddedCursorTest extends TestCase { public function testShouldLimitDocumentQuantity() { - // Arrange + // Set $items = [ ['name' => 'A'], ['name' => 'B'], @@ -17,9 +17,11 @@ public function testShouldLimitDocumentQuantity() ]; $cursor = new EmbeddedCursor($items); - // Assert + // Actions $cursor->limit(2); - $this->assertAttributeEquals( + + // Assertions + $this->assertAttributeSame( [ ['name' => 'A'], ['name' => 'B'], @@ -34,21 +36,19 @@ public function testShouldLimitDocumentQuantity() */ public function testShouldSortDocuments($items, $parameters, $expected) { - // Arrange + // Set $cursor = new EmbeddedCursor($items); - // Assert + // Actions $cursor->sort($parameters); - $this->assertAttributeSame( - $expected, - 'items', - $cursor - ); + + // Assertions + $this->assertAttributeSame($expected, 'items', $cursor); } public function testShouldSkipDocuments() { - // Arrange + // Set $items = [ ['name' => 'A'], ['name' => 'B'], @@ -56,9 +56,11 @@ public function testShouldSkipDocuments() ]; $cursor = new EmbeddedCursor($items); - // Assert + // Actions $cursor->skip(2); - $this->assertAttributeEquals( + + // Assertions + $this->assertAttributeSame( [ ['name' => 'C'], ], @@ -69,7 +71,7 @@ public function testShouldSkipDocuments() public function testShouldCountDocuments() { - // Arrange + // Set $items = [ ['name' => 'A'], ['name' => 'B'], @@ -77,13 +79,16 @@ public function testShouldCountDocuments() ]; $cursor = new EmbeddedCursor($items); - // Assert - $this->assertEquals(3, $cursor->count()); + // Actions + $result = $cursor->count(); + + // Assertions + $this->assertSame(3, $result); } public function testShouldCountDocumentsWithCountFunction() { - // Arrange + // Set $items = [ ['name' => 'A'], ['name' => 'B'], @@ -91,23 +96,28 @@ public function testShouldCountDocumentsWithCountFunction() ]; $cursor = new EmbeddedCursor($items); - // Assert - $this->assertEquals(3, count($cursor)); + // Actions + $result = count($cursor); + + // Assertions + $this->assertSame(3, $result); } public function testShouldRewind() { - // Arrange + // Set $cursor = new EmbeddedCursor([]); - // Assert + // Actions $cursor->rewind(); - $this->assertAttributeEquals(0, 'position', $cursor); + + // Assertions + $this->assertAttributeSame(0, 'position', $cursor); } public function testShouldGetCurrent() { - // Arrange + // Set $object = new class extends AbstractModel { }; @@ -130,28 +140,32 @@ public function testShouldGetCurrent() $this->setProtected($cursor, 'position', 1); - // Assert + // Actions $model = $cursor->current(); + + // Assertions $this->assertInstanceOf($class, $model); $this->assertSame('B', $model->name); } public function testShouldNotGetCurrentWhenCursorIsInvalid() { - // Arrange + // Set $items = []; $cursor = new EmbeddedCursor($items); $this->setProtected($cursor, 'position', 1); - // Assert + // Actions $model = $cursor->current(); + + // Assertions $this->assertNull($model); } public function testShouldGetCurrentUsingModelClass() { - // Arrange + // Set $object = new stdClass(); $object->name = 'A'; $items = [$object]; @@ -159,15 +173,17 @@ public function testShouldGetCurrentUsingModelClass() $this->setProtected($cursor, 'position', 0); - // Assert + // Actions $model = $cursor->current(); + + // Assertions $this->assertInstanceOf(stdClass::class, $model); - $this->assertAttributeEquals('A', 'name', $model); + $this->assertAttributeSame('A', 'name', $model); } public function testShouldGetCurrentUsingModelClassMorphingIt() { - // Arrange + // Set $object = new class() extends AbstractModel { }; @@ -179,7 +195,6 @@ public function testShouldGetCurrentUsingModelClassMorphingIt() $model->name = 'John'; $model->syncOriginalDocumentAttributes(); - $class = get_class($object); $items = [$model]; $cursor = new EmbeddedCursor($items); @@ -187,13 +202,13 @@ public function testShouldGetCurrentUsingModelClassMorphingIt() $result = $cursor->current(); // Assertions - $this->assertEquals($model, $result); + $this->assertSame($model, $result); $this->assertSame('John', $result->name); } public function testShouldGetFirst() { - // Arrange + // Set $object = new class extends AbstractModel { }; @@ -213,8 +228,10 @@ public function testShouldGetFirst() $this->setProtected($cursor, 'position', 1); - // Assert + // Actions $model = $cursor->first(); + + // Assertions $this->assertInstanceOf($class, $model); $this->assertSame('A', $model->name); } @@ -243,55 +260,60 @@ public function testShouldGetAllItems() $modelB, ]; - // Assert + // Actions $result = $cursor->all(); - $this->assertEquals($expected, $result); + // Assertions + $this->assertSame($expected, $result); } public function testShouldGetAllInArrayFormat() { - // Arrange + // Set $items = [ ['name' => 'A'], ['name' => 'B'], ['name' => 'C'], ]; $cursor = new EmbeddedCursor($items); - $this->setProtected($cursor, 'position', 1); - // Assert + // Actions $result = $cursor->toArray(); - $this->assertEquals($items, $result); + + // Assertions + $this->assertSame($items, $result); } public function testShouldImplementKeyMethodFromIterator() { - // Arrange + // Set $cursor = new EmbeddedCursor([]); - $this->setProtected($cursor, 'position', 7); - // Assertion - $this->assertEquals(7, $cursor->key()); + // Actions + $result = $cursor->key(); + + // Assertions + $this->assertSame(7, $result); } public function testShouldImplementNextMethodFromIterator() { - // Arrange + // Set $cursor = new EmbeddedCursor([]); - $this->setProtected($cursor, 'position', 7); - // Assertion + // Actions $cursor->next(); - $this->assertAttributeEquals(8, 'position', $cursor); + + // Assertions + $this->assertAttributeSame(8, 'position', $cursor); } public function testShouldImplementValidMethodFromIterator() { - // Arrange + // Set $items = [ ['name' => 'A'], ['name' => 'B'], @@ -299,13 +321,21 @@ public function testShouldImplementValidMethodFromIterator() ]; $cursor = new EmbeddedCursor($items); - // Assert - $this->assertTrue($cursor->valid()); + // Actions + $result = $cursor->valid(); + + // Assertions + $this->assertTrue($result); + + // Actions $this->setProtected($cursor, 'position', 8); - $this->assertFalse($cursor->valid()); + $result = $cursor->valid(); + + // Assertions + $this->assertFalse($result); } - public function getDocumentsToSort() + public function getDocumentsToSort(): array { $age24 = (object) ['age' => 24]; diff --git a/tests/Unit/Event/EventTriggerServiceTest.php b/tests/Unit/Event/EventTriggerServiceTest.php index 85cc9cc3..44c281db 100644 --- a/tests/Unit/Event/EventTriggerServiceTest.php +++ b/tests/Unit/Event/EventTriggerServiceTest.php @@ -8,37 +8,39 @@ class EventTriggerServiceTest extends TestCase { public function testShouldSendTheEventsToTheExternalBuilder() { - // Arrange + // Set $builder = m::mock(EventTriggerInterface::class); $service = new EventTriggerService(); + $service->registerEventBuilder($builder); - // Act + // Expectations $builder->expects() ->fire('foobar', ['answer' => 23], true) ->andReturn(true); - // Assertion - $service->registerEventBuilder($builder); - $this->assertTrue( - $service->fire('foobar', ['answer' => 23], true) - ); + // Actions + $result = $service->fire('foobar', ['answer' => 23], true); + + // Assertions + $this->assertTrue($result); } public function testShouldReturnTrueIfThereIsNoExternalBuilder() { - // Arrange + // Set $builder = m::mock(EventTriggerInterface::class); $service = new EventTriggerService(); - // Act + // Expectations $builder->expects() ->fire() ->never(); - // Assertion + // Actions + $result = $service->fire('foobar', ['answer' => 23], true); + + // Assertions /* without calling registerEventBuilder */ - $this->assertTrue( - $service->fire('foobar', ['answer' => 23], true) - ); + $this->assertTrue($result); } } diff --git a/tests/Unit/Model/AbstractModelTest.php b/tests/Unit/Model/AbstractModelTest.php index b9fe3fa5..0c0c7310 100644 --- a/tests/Unit/Model/AbstractModelTest.php +++ b/tests/Unit/Model/AbstractModelTest.php @@ -2,6 +2,10 @@ namespace Mongolid\Model; use Mockery as m; +use MongoDB\BSON\Persistable; +use MongoDB\BSON\Serializable; +use MongoDB\BSON\Type; +use MongoDB\BSON\Unserializable; use MongoDB\Driver\WriteConcern; use Mongolid\Cursor\CursorInterface; use Mongolid\Model\Exception\NoCollectionNameException; @@ -47,10 +51,32 @@ protected function tearDown() public function testShouldImplementModelTraits() { + // Actions + $result = array_keys(class_uses(AbstractModel::class)); + // Assertions $this->assertSame( [HasAttributesTrait::class, HasRelationsTrait::class], - array_keys(class_uses(AbstractModel::class)) + $result + ); + } + + public function testShouldImplementModelInterface() + { + // Actions + $result = array_keys(class_implements(AbstractModel::class)); + + // Assertions + $this->assertSame( + [ + ModelInterface::class, + Unserializable::class, + Serializable::class, + Type::class, + Persistable::class, + HasAttributesInterface::class, + ], + $result ); } @@ -59,13 +85,16 @@ public function testShouldSave() // Set $builder = $this->instance(Builder::class, m::mock(Builder::class)); - // Actions + // Expectations $builder->expects() ->save($this->model, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); + // Actions + $result = $this->model->save(); + // Assertions - $this->assertTrue($this->model->save()); + $this->assertTrue($result); } public function testShouldInsert() @@ -73,13 +102,16 @@ public function testShouldInsert() // Set $builder = $this->instance(Builder::class, m::mock(Builder::class)); - // Actions + // Expectations $builder->expects() ->insert($this->model, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); + // Actions + $result = $this->model->insert(); + // Assertions - $this->assertTrue($this->model->insert()); + $this->assertTrue($result); } public function testShouldUpdate() @@ -87,13 +119,16 @@ public function testShouldUpdate() // Set $builder = $this->instance(Builder::class, m::mock(Builder::class)); - // Actions + // Expectations $builder->expects() ->update($this->model, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); + // Actions + $result = $this->model->update(); + // Assertions - $this->assertTrue($this->model->update()); + $this->assertTrue($result); } public function testShouldDelete() @@ -101,13 +136,16 @@ public function testShouldDelete() // Set $builder = $this->instance(Builder::class, m::mock(Builder::class)); - // Actions + // Expectations $builder->expects() ->delete($this->model, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); + // Actions + $result = $this->model->delete(); + // Assertions - $this->assertTrue($this->model->delete()); + $this->assertTrue($result); } public function testSaveShouldThrowExceptionIfCollectionIsNull() @@ -171,29 +209,34 @@ public function testShouldGetWithWhereQuery() $cursor = m::mock(CursorInterface::class); - // Actions + // Expectations $builder->expects() ->where(m::type(get_class($this->model)), $query, $projection) ->andReturn($cursor); + // Actions + $result = $this->model->where($query, $projection); + // Assertions - $this->assertSame($cursor, $this->model->where($query, $projection)); + $this->assertSame($cursor, $result); } public function testShouldGetAll() { // Set $builder = $this->instance(Builder::class, m::mock(Builder::class)); - $cursor = m::mock(CursorInterface::class); - // Actions + // Expectations $builder->expects() ->all(m::type(get_class($this->model))) ->andReturn($cursor); + // Actions + $result = $this->model->all(); + // Assertions - $this->assertSame($cursor, $this->model->all()); + $this->assertSame($cursor, $result); } public function testShouldGetFirstWithQuery() @@ -203,13 +246,16 @@ public function testShouldGetFirstWithQuery() $projection = ['some', 'fields']; $builder = $this->instance(Builder::class, m::mock(Builder::class)); - // Actions + // Expectations $builder->expects() ->first(m::type(get_class($this->model)), $query, $projection) ->andReturn($this->model); + // Actions + $result = $this->model->first($query, $projection); + // Assertions - $this->assertSame($this->model, $this->model->first($query, $projection)); + $this->assertSame($this->model, $result); } public function testShouldGetFirstOrFail() @@ -219,13 +265,16 @@ public function testShouldGetFirstOrFail() $query = ['foo' => 'bar']; $projection = ['some', 'fields']; - // Actions + // Expectations $builder->expects() ->firstOrFail(m::type(get_class($this->model)), $query, $projection) ->andReturn($this->model); + // Actions + $result = $this->model->firstOrFail($query, $projection); + // Assertions - $this->assertSame($this->model, $this->model->firstOrFail($query, $projection)); + $this->assertSame($this->model, $result); } public function testShouldGetFirstOrNewAndReturnExistingModel() @@ -234,13 +283,16 @@ public function testShouldGetFirstOrNewAndReturnExistingModel() $builder = $this->instance(Builder::class, m::mock(Builder::class)); $id = 123; - // Actions + // Expectations $builder->expects() ->first(m::type(get_class($this->model)), $id, []) ->andReturn($this->model); + // Actions + $result = $this->model->firstOrNew($id); + // Assertions - $this->assertSame($this->model, $this->model->firstOrNew($id)); + $this->assertSame($this->model, $result); } public function testShouldGetFirstOrNewAndReturnNewModel() @@ -249,13 +301,16 @@ public function testShouldGetFirstOrNewAndReturnNewModel() $builder = $this->instance(Builder::class, m::mock(Builder::class)); $id = 123; - // Actions + // Expectations $builder->expects() ->first(m::type(get_class($this->model)), $id, []) ->andReturn(null); + // Actions + $result = $this->model->firstOrNew($id); + // Assertions - $this->assertNotEquals($this->model, $this->model->firstOrNew($id)); + $this->assertNotEquals($this->model, $result); } public function testShouldGetBuilder() @@ -274,44 +329,72 @@ public function testShouldGetBuilder() public function testShouldRaiseExceptionWhenHasNoCollectionAndTryToCallAllFunction() { + // Set $model = new class() extends AbstractModel { }; + // Expectations $this->expectException(NoCollectionNameException::class); + + // Actions $model->all(); } public function testShouldRaiseExceptionWhenHasNoCollectionAndTryToCallFirstFunction() { + // Set $model = new class() extends AbstractModel { }; + // Expectations $this->expectException(NoCollectionNameException::class); + + // Actions $model->first(); } public function testShouldRaiseExceptionWhenHasNoCollectionAndTryToCallWhereFunction() { + // Set $model = new class() extends AbstractModel { }; + // Expectations $this->expectException(NoCollectionNameException::class); + + // Actions $model->where(); } public function testShouldGetCollectionName() { - $this->assertSame('mongolid', $this->model->getCollectionName()); + // Actions + $result = $this->model->getCollectionName(); + + // Assertions + $this->assertSame('mongolid', $result); + } + + public function testShouldHaveDefaultWriteConcern() + { + // Actions + $result = $this->model->getWriteConcern(); + + // Assertions + $this->assertSame(1, $result); } - public function testShouldGetSetWriteConcernInModelClass() + public function testShouldSetWriteConcern() { - $this->assertSame(1, $this->model->getWriteConcern()); + // Actions $this->model->setWriteConcern(0); - $this->assertSame(0, $this->model->getWriteConcern()); + $result = $this->model->getWriteConcern(); + + // Assertions + $this->assertSame(0, $result); } public function testShouldHaveDynamicSetters() @@ -322,18 +405,21 @@ public function testShouldHaveDynamicSetters() }; $childObj = new stdClass(); - - // Assertions $model->name = 'John'; $model->age = 25; $model->child = $childObj; + + // Actions + $result = $model->getDocumentAttributes(); + + // Assertions $this->assertSame( [ 'name' => 'John', 'age' => 25, 'child' => $childObj, ], - $model->getDocumentAttributes() + $result ); } @@ -344,6 +430,8 @@ public function testShouldHaveDynamicGetters() $model = new class() extends AbstractModel { }; + + // Actions $model->fill( [ 'name' => 'John', @@ -365,6 +453,8 @@ public function testShouldCheckIfAttributeIsSet() $model = new class() extends AbstractModel { }; + + // Actions $model->fill(['name' => 'John', 'ignored' => null]); // Assertions @@ -455,6 +545,7 @@ public function setShortNameDocumentAttribute($value) } }; + // Actions $model->short_name = 'My awesome name'; // Assertions @@ -463,7 +554,7 @@ public function setShortNameDocumentAttribute($value) public function testShouldSetAttributeFromMutator() { - // Arrange + // Set $model = new class() extends AbstractModel { /** @@ -477,10 +568,11 @@ public function setShortNameDocumentAttribute($value) } }; + // Actions $model->short_name = 'My awesome name'; $result = $model->short_name; - // Assert + // Assertions $this->assertSame('MY AWESOME NAME', $result); } } diff --git a/tests/Unit/Model/DocumentEmbedderTest.php b/tests/Unit/Model/DocumentEmbedderTest.php index ae1df19d..1b997011 100644 --- a/tests/Unit/Model/DocumentEmbedderTest.php +++ b/tests/Unit/Model/DocumentEmbedderTest.php @@ -13,7 +13,7 @@ class DocumentEmbedderTest extends TestCase */ public function testShouldEmbed($originalField, $modelFields, $method, $expectation) { - // Arrange + // Set $parent = new class extends AbstractModel { }; @@ -29,10 +29,11 @@ public function testShouldEmbed($originalField, $modelFields, $method, $expectat } $embedder = new DocumentEmbedder(); - // Assert + // Actions $embedder->$method($parent, 'foo', $model); - $result = $parent->foo; + + // Assertions foreach ($expectation as $index => $expectedDoc) { if ($expectedDoc instanceof ObjectId) { $this->assertEquals($expectedDoc, $result[$index]); @@ -52,10 +53,9 @@ public function testShouldEmbed($originalField, $modelFields, $method, $expectat } } - public function getEmbedOptions() + public function getEmbedOptions(): array { return [ - // ------------------------------ 'embedding object without _id' => [ 'originalField' => null, 'model' => [ @@ -66,8 +66,6 @@ public function getEmbedOptions() (object) ['_id' => m::any(), 'name' => 'John Doe'], ], ], - - // ------------------------------ 'embedding object with _id' => [ 'originalField' => null, 'model' => [ @@ -79,8 +77,6 @@ public function getEmbedOptions() (object) ['_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'John Doe'], ], ], - - // ------------------------------ 'updating embedded object with _id' => [ 'originalField' => [ [ @@ -97,8 +93,6 @@ public function getEmbedOptions() (object) ['_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'John Doe'], ], ], - - // ------------------------------ 'attaching object with _id' => [ 'originalField' => null, 'model' => [ @@ -110,8 +104,6 @@ public function getEmbedOptions() new ObjectId('507f191e810c19729de860ea'), ], ], - - // ------------------------------ 'attaching object with integer _id' => [ 'originalField' => [6], 'model' => [ @@ -121,8 +113,6 @@ public function getEmbedOptions() 'method' => 'attach', 'expectation' => [6, 7], ], - - // ------------------------------ 'attaching object with _id that is already attached' => [ 'originalField' => [ new ObjectId('507f191e810c19729de860ea'), @@ -138,8 +128,6 @@ public function getEmbedOptions() new ObjectId('507f191e810c19729de86011'), ], ], - - // ------------------------------ 'attaching object without _id' => [ 'originalField' => null, 'model' => [ @@ -148,8 +136,6 @@ public function getEmbedOptions() 'method' => 'attach', 'expectation' => [], ], - - // ------------------------------ 'detaching an object by its _id' => [ 'originalField' => [ (new ObjectId('507f191e810c19729de860ea')), @@ -164,8 +150,6 @@ public function getEmbedOptions() (new ObjectId('507f191e810c19729de86011')), ], ], - - // ------------------------------ 'attaching an _id' => [ 'originalField' => null, 'model' => new ObjectId('507f191e810c19729de860ea'), @@ -174,8 +158,6 @@ public function getEmbedOptions() (new ObjectId('507f191e810c19729de860ea')), ], ], - - // ------------------------------ 'detaching an object using only the _id when it is an integer' => [ 'originalField' => [ 6, @@ -187,8 +169,6 @@ public function getEmbedOptions() 7, ], ], - - // ------------------------------ ]; } } diff --git a/tests/Unit/Model/HasAttributesTraitTest.php b/tests/Unit/Model/HasAttributesTraitTest.php index 731e6948..57831899 100644 --- a/tests/Unit/Model/HasAttributesTraitTest.php +++ b/tests/Unit/Model/HasAttributesTraitTest.php @@ -24,6 +24,7 @@ public function getShortNameDocumentAttribute() } }; + // Actions $model->setDocumentAttribute('short_name', 'My awesome name'); $result = $model->getDocumentAttribute('short_name'); @@ -33,7 +34,7 @@ public function getShortNameDocumentAttribute() public function testShouldIgnoreMutators() { - // Arrange + // Set $model = new class() { use HasAttributesTrait; @@ -50,16 +51,17 @@ public function setShortNameDocumentAttribute($value) } }; + // Actions $model->setDocumentAttribute('short_name', 'My awesome name'); $result = $model->getDocumentAttribute('short_name'); - // Assert + // Assertions $this->assertSame('My awesome name', $result); } public function testShouldSetAttributeFromMutator() { - // Arrange + // Set $model = new class() { use HasAttributesTrait; @@ -76,10 +78,11 @@ public function setShortNameDocumentAttribute($value) } }; + // Actions $model->setDocumentAttribute('short_name', 'My awesome name'); $result = $model->getDocumentAttribute('short_name'); - // Assert + // Assertions $this->assertSame('MY AWESOME NAME', $result); } @@ -92,7 +95,7 @@ public function testShouldFillOnlyPermittedAttributes( $input, $expected ) { - // Arrange + // Set $model = new class($fillable, $guarded) { use HasAttributesTrait; @@ -105,16 +108,16 @@ public function __construct(array $fillable, array $guarded) } }; - // Act + // Actions $model->fill($input); - // Assert + // Assertions $this->assertSame($expected, $model->getDocumentAttributes()); } public function testShouldForceFillAttributes() { - // Arrange + // Set $model = new class() { use HasAttributesTrait; @@ -126,16 +129,17 @@ public function testShouldForceFillAttributes() 'not_allowed_attribute' => true, ]; - // Act + // Actions $model->fill($input, true); + $result = $model->getDocumentAttribute('not_allowed_attribute'); - // Assert - $this->assertTrue($model->getDocumentAttribute('not_allowed_attribute')); + // Assertions + $this->assertTrue($result); } public function testShouldBeCastableToArray() { - // Arrange + // Set $model = new class() { use HasAttributesTrait; @@ -145,16 +149,16 @@ public function testShouldBeCastableToArray() $model->setDocumentAttribute('name', 'John'); $model->setDocumentAttribute('age', 25); - // Assert - $this->assertSame( - ['name' => 'John', 'age' => 25], - $model->toArray() - ); + // Actions + $result = $model->toArray(); + + // Assertions + $this->assertSame(['name' => 'John', 'age' => 25], $result); } public function testShouldSetOriginalAttributes() { - // Arrange + // Set $model = new class() implements HasAttributesInterface { use HasAttributesTrait; @@ -164,16 +168,17 @@ public function testShouldSetOriginalAttributes() $model->name = 'John'; $model->age = 25; - // Act + // Actions $model->syncOriginalDocumentAttributes(); + $result = $model->getOriginalDocumentAttributes(); - // Assert - $this->assertSame($model->getDocumentAttributes(), $model->getOriginalDocumentAttributes()); + // Assertions + $this->assertSame($model->getDocumentAttributes(), $result); } public function testShouldFallbackOriginalAttributesIfUnserializationFails() { - // Arrange + // Set $model = new class() implements HasAttributesInterface { use HasAttributesTrait; @@ -187,17 +192,55 @@ public function __construct() } }; - // Act + // Actions $model->syncOriginalDocumentAttributes(); + $result = $model->getOriginalDocumentAttributes(); - // Assert - $this->assertSame($model->getDocumentAttributes(), $model->getOriginalDocumentAttributes()); + // Assertions + $this->assertSame($model->getDocumentAttributes(), $result); } - public function getFillableOptions() + public function testShouldCheckIfAttributeIsSet() + { + // Set + $model = new class() extends AbstractModel + { + }; + + // Actions + $model->fill(['name' => 'John', 'ignored' => null]); + + // Assertions + $this->assertTrue(isset($model->name)); + $this->assertFalse(isset($model->nonexistant)); + $this->assertFalse(isset($model->ignored)); + } + + + public function testShouldCheckIfMutatedAttributeIsSet() + { + // Set + $model = new class() extends AbstractModel + { + /** + * {@inheritdoc} + */ + public $mutable = true; + + public function getNameDocumentAttribute() + { + return 'John'; + } + }; + + // Assertions + $this->assertTrue(isset($model->name)); + $this->assertFalse(isset($model->nonexistant)); + } + + public function getFillableOptions(): array { return [ - // ----------------------------- '$fillable = []; $guarded = []' => [ 'fillable' => [], 'guarded' => [], @@ -212,8 +255,6 @@ public function getFillableOptions() 'sex' => 'male', ], ], - - // ----------------------------- '$fillable = ["name"]; $guarded = []' => [ 'fillable' => ['name'], 'guarded' => [], @@ -226,9 +267,7 @@ public function getFillableOptions() 'name' => 'John', ], ], - - // ----------------------------- - '$fillable = []; $guarded = []' => [ + '$fillable = []; $guarded = [sex]' => [ 'fillable' => [], 'guarded' => ['sex'], 'input' => [ @@ -241,8 +280,6 @@ public function getFillableOptions() 'age' => 25, ], ], - - // ----------------------------- '$fillable = ["name", "sex"]; $guarded = ["sex"]' => [ 'fillable' => ['name', 'sex'], 'guarded' => ['sex'], @@ -255,8 +292,6 @@ public function getFillableOptions() 'name' => 'John', ], ], - - // ----------------------------- 'ignore nulls but not falsy ones' => [ 'fillable' => ['name', 'surname', 'sex', 'age', 'has_sex'], 'guarded' => [], @@ -276,40 +311,4 @@ public function getFillableOptions() ], ]; } - - - public function testShouldCheckIfAttributeIsSet() - { - // Set - $model = new class() extends AbstractModel - { - }; - $model->fill(['name' => 'John', 'ignored' => null]); - - // Assertions - $this->assertTrue(isset($model->name)); - $this->assertFalse(isset($model->nonexistant)); - $this->assertFalse(isset($model->ignored)); - } - - public function testShouldCheckIfMutatedAttributeIsSet() - { - // Set - $model = new class() extends AbstractModel - { - /** - * {@inheritdoc} - */ - public $mutable = true; - - public function getNameDocumentAttribute() - { - return 'John'; - } - }; - - // Assertions - $this->assertTrue(isset($model->name)); - $this->assertFalse(isset($model->nonexistant)); - } } diff --git a/tests/Unit/Model/HasRelationsTraitTest.php b/tests/Unit/Model/HasRelationsTraitTest.php index 899cfe4e..48515058 100644 --- a/tests/Unit/Model/HasRelationsTraitTest.php +++ b/tests/Unit/Model/HasRelationsTraitTest.php @@ -70,12 +70,12 @@ public function testShouldEmbedsOne($fieldValue, $expectedItems) $expectedItems = $expectedItems['embedsOne']; - // Act + // Actions $result = $model->relationEmbedsOne; - // Assert + // Assertions $this->assertInstanceOf(RelatedStub::class, $result); - $this->assertEquals($expectedItems, $result); + $this->assertSame($expectedItems, $result); } /** @@ -89,16 +89,16 @@ public function testShouldEmbedsMany($fieldValue, $expectedItems) $expectedItems = $expectedItems['embedsMany']; - // Act + // Actions $result = $model->relationEmbedsMany; - // Assert + // Assertions $this->assertInstanceOf(EmbeddedCursor::class, $result); $this->assertContainsOnlyInstancesOf(RelatedStub::class, $result->all()); - $this->assertEquals($expectedItems, $result->all()); + $this->assertSame($expectedItems, $result->all()); } - public function referenceScenarios() + public function referenceScenarios(): array { return [ 'referenced by string id' => [ @@ -160,7 +160,7 @@ public function referenceScenarios() ]; } - public function embedsScenarios() + public function embedsScenarios(): array { $model1 = new RelatedStub(); $model1->_id = 12345; diff --git a/tests/Unit/Query/BuilderTest.php b/tests/Unit/Query/BuilderTest.php index 28a6af50..25d3c1fb 100644 --- a/tests/Unit/Query/BuilderTest.php +++ b/tests/Unit/Query/BuilderTest.php @@ -21,14 +21,14 @@ class BuilderTest extends TestCase { public function testShouldBeAbleToConstruct() { - // Arrange + // Set $connection = m::mock(Connection::class); - // Act + // Actions $builder = new Builder($connection); - // Assert - $this->assertAttributeEquals($connection, 'connection', $builder); + // Assertions + $this->assertAttributeSame($connection, 'connection', $builder); } /** @@ -36,17 +36,16 @@ public function testShouldBeAbleToConstruct() */ public function testShouldSave($model, $writeConcern, $shouldFireEventAfter, $expected) { - // Arrange + // Set $connection = m::mock(Connection::class); $builder = m::mock(Builder::class.'[getCollection]', [$connection]); + $builder->shouldAllowMockingProtectedMethods(); $options = ['writeConcern' => new WriteConcern($writeConcern)]; $collection = m::mock(Collection::class); $operationResult = m::mock(); - // Act - $builder->shouldAllowMockingProtectedMethods(); - + // Expectations $builder->expects() ->getCollection($model) ->andReturn($collection); @@ -78,8 +77,11 @@ public function testShouldSave($model, $writeConcern, $shouldFireEventAfter, $ex $this->expectEventNotToBeFired('saved', $model); } - // Assert - $this->assertSame($expected, $builder->save($model, $options)); + // Actions + $result = $builder->save($model, $options); + + // Assertions + $this->assertSame($expected, $result); } /** @@ -87,9 +89,10 @@ public function testShouldSave($model, $writeConcern, $shouldFireEventAfter, $ex */ public function testShouldInsert($model, $writeConcern, $shouldFireEventAfter, $expected) { - // Arrange + // Set $connection = m::mock(Connection::class); $builder = m::mock(Builder::class.'[getCollection]', [$connection]); + $builder->shouldAllowMockingProtectedMethods(); $options = ['writeConcern' => new WriteConcern($writeConcern)]; $collection = m::mock(Collection::class); @@ -97,9 +100,7 @@ public function testShouldInsert($model, $writeConcern, $shouldFireEventAfter, $ $model->_id = null; - // Act - $builder->shouldAllowMockingProtectedMethods(); - + // Expectations $builder->expects() ->getCollection($model) ->andReturn($collection); @@ -124,8 +125,11 @@ public function testShouldInsert($model, $writeConcern, $shouldFireEventAfter, $ $this->expectEventNotToBeFired('inserted', $model); } - // Assert - $this->assertSame($expected, $builder->insert($model, $options)); + // Actions + $result = $builder->insert($model, $options); + + // Assertions + $this->assertSame($expected, $result); } /** @@ -133,9 +137,10 @@ public function testShouldInsert($model, $writeConcern, $shouldFireEventAfter, $ */ public function testShouldInsertWithoutFiringEvents($model, $writeConcern, $shouldFireEventAfter, $expected) { - // Arrange + // Set $connection = m::mock(Connection::class); $builder = m::mock(Builder::class.'[getCollection]', [$connection]); + $builder->shouldAllowMockingProtectedMethods(); $options = ['writeConcern' => new WriteConcern($writeConcern)]; $collection = m::mock(Collection::class); @@ -143,9 +148,7 @@ public function testShouldInsertWithoutFiringEvents($model, $writeConcern, $shou $model->_id = null; - // Act - $builder->shouldAllowMockingProtectedMethods(); - + // Expectations $builder->expects() ->getCollection($model) ->andReturn($collection); @@ -165,8 +168,11 @@ public function testShouldInsertWithoutFiringEvents($model, $writeConcern, $shou $this->expectEventNotToBeFired('inserting', $model); $this->expectEventNotToBeFired('inserted', $model); - // Assert - $this->assertSame($expected, $builder->insert($model, $options, false)); + // Actions + $result = $builder->insert($model, $options, false); + + // Assertions + $this->assertSame($expected, $result); } /** @@ -174,7 +180,7 @@ public function testShouldInsertWithoutFiringEvents($model, $writeConcern, $shou */ public function testShouldUpdate($model, $writeConcern, $shouldFireEventAfter, $expected) { - // Arrange + // Set $connection = m::mock(Connection::class); $client = m::mock(Client::class); $database = m::mock(Database::class); @@ -185,7 +191,7 @@ public function testShouldUpdate($model, $writeConcern, $shouldFireEventAfter, $ $operationResult = m::mock(); $options = ['writeConcern' => new WriteConcern($writeConcern)]; - // Expect + // Expectations $connection->expects() ->getRawConnection() ->andReturn($client); @@ -221,16 +227,16 @@ public function testShouldUpdate($model, $writeConcern, $shouldFireEventAfter, $ $this->expectEventNotToBeFired('updated', $model); } - // Act + // Actions $result = $builder->update($model, $options); - // Assert + // Assertions $this->assertSame($expected, $result); } public function testShouldUpdateUnsettingFields() { - // Arrange + // Set $connection = m::mock(Connection::class); $client = m::mock(Client::class); $database = m::mock(Database::class); @@ -272,7 +278,7 @@ public function testShouldUpdateUnsettingFields() $model->_id = 123; unset($model->name); - // Expect + // Expectations $connection->expects() ->getRawConnection() ->andReturn($client); @@ -304,10 +310,10 @@ public function testShouldUpdateUnsettingFields() $this->expectEventToBeFired('updated', $model, false); - // Act + // Actions $result = $builder->update($model, $options); - // Assert + // Assertions $this->assertTrue($result); } @@ -320,7 +326,7 @@ public function testUpdateShouldCallInsertWhenObjectHasNoId( $shouldFireEventAfter, $expected ) { - // Arrange + // Set $connection = m::mock(Connection::class); $builder = m::mock(Builder::class.'[getCollection]', [$connection]); @@ -330,7 +336,7 @@ public function testUpdateShouldCallInsertWhenObjectHasNoId( $model->_id = null; - // Act + // Actions $builder->shouldAllowMockingProtectedMethods(); $builder->expects() @@ -362,8 +368,11 @@ public function testUpdateShouldCallInsertWhenObjectHasNoId( $this->expectEventNotToBeFired('inserting', $model); $this->expectEventNotToBeFired('inserted', $model); - // Assert - $this->assertSame($expected, $builder->update($model, $options)); + // Actions + $result = $builder->update($model, $options); + + // Assertions + $this->assertSame($expected, $result); } /** @@ -371,17 +380,16 @@ public function testUpdateShouldCallInsertWhenObjectHasNoId( */ public function testShouldDelete($model, $writeConcern, $shouldFireEventAfter, $expected) { - // Arrange + // Set $connection = m::mock(Connection::class); $builder = m::mock(Builder::class.'[getCollection]', [$connection]); + $builder->shouldAllowMockingProtectedMethods(); $collection = m::mock(Collection::class); $operationResult = m::mock(); $options = ['writeConcern' => new WriteConcern($writeConcern)]; - // Act - $builder->shouldAllowMockingProtectedMethods(); - + // Expectations $builder->expects() ->getCollection($model) ->andReturn($collection); @@ -406,8 +414,11 @@ public function testShouldDelete($model, $writeConcern, $shouldFireEventAfter, $ $this->expectEventNotToBeFired('deleted', $model); } - // Assert - $this->assertSame($expected, $builder->delete($model, $options)); + // Actions + $result = $builder->delete($model, $options); + + // Assertions + $this->assertSame($expected, $result); } /** @@ -418,7 +429,7 @@ public function testDatabaseOperationsShouldBailOutIfTheEventHandlerReturnsFalse $dbOperation, $eventName ) { - // Arrange + // Set $connection = m::mock(Connection::class); $builder = m::mock(Builder::class.'[getCollection]', [$connection]); $collection = m::mock(Collection::class); @@ -426,7 +437,7 @@ public function testDatabaseOperationsShouldBailOutIfTheEventHandlerReturnsFalse $builder->shouldAllowMockingProtectedMethods(); - // Expect + // Expectations $builder->allows() ->getCollection($model) ->andReturn($collection); @@ -437,16 +448,16 @@ public function testDatabaseOperationsShouldBailOutIfTheEventHandlerReturnsFalse /* "Mocks" the fireEvent to return false and bail the operation */ $this->expectEventToBeFired($eventName, $model, true, false); - // Act + // Actions $result = $builder->$operation($model); - // Assert + // Assertions $this->assertFalse($result); } public function testShouldGetWithWhereQuery() { - // Arrange + // Set $connection = m::mock(Connection::class); $builder = m::mock(Builder::class.'[prepareValueQuery,getCollection]', [$connection]); @@ -458,7 +469,7 @@ public function testShouldGetWithWhereQuery() $builder->shouldAllowMockingProtectedMethods(); - // Expect + // Expectations $builder->expects() ->prepareValueQuery($query) ->andReturn($preparedQuery); @@ -467,14 +478,14 @@ public function testShouldGetWithWhereQuery() ->getCollection($model) ->andReturn($collection); - // Act + // Actions $result = $builder->where($model, $query, $projection); - // Assert + // Assertions $this->assertInstanceOf(Cursor::class, $result); - $this->assertAttributeEquals($collection, 'collection', $result); - $this->assertAttributeEquals('find', 'command', $result); - $this->assertAttributeEquals( + $this->assertAttributeSame($collection, 'collection', $result); + $this->assertAttributeSame('find', 'command', $result); + $this->assertAttributeSame( [$preparedQuery, ['projection' => $projection]], 'params', $result @@ -483,27 +494,27 @@ public function testShouldGetWithWhereQuery() public function testShouldGetAll() { - // Arrange + // Set $connection = m::mock(Connection::class); $builder = m::mock(Builder::class.'[where]', [$connection]); $mongolidCursor = m::mock(Cursor::class); $model = m::mock(ModelInterface::class); - // Expect + // Expectations $builder->expects() ->where($model, []) ->andReturn($mongolidCursor); - // Act + // Actions $result = $builder->all($model); - // Assert + // Assertions $this->assertSame($mongolidCursor, $result); } public function testShouldGetFirstWithQuery() { - // Arrange + // Set $connection = m::mock(Connection::class); $builder = m::mock(Builder::class.'[prepareValueQuery,getCollection]', [$connection]); $collection = m::mock(Collection::class); @@ -518,7 +529,7 @@ public function testShouldGetFirstWithQuery() }; $builder->shouldAllowMockingProtectedMethods(); - // Act + // Expectations $builder->expects() ->prepareValueQuery($query) ->andReturn($preparedQuery); @@ -531,28 +542,29 @@ public function testShouldGetFirstWithQuery() ->findOne($preparedQuery, ['projection' => []]) ->andReturn($object); + // Actions $result = $builder->first($object, $query); - // Assert + // Assertions $this->assertSame($object, $result); } public function testFirstWithNullShouldNotHitTheDatabase() { - // Arrange + // Set $connection = m::mock(Connection::class); $builder = new Builder($connection); - // Act + // Actions $result = $builder->first(m::mock(ModelInterface::class), null); - // Assert + // Assertions $this->assertNull($result); } public function testFirstOrFailShouldGetFirst() { - // Arrange + // Set $connection = m::mock(Connection::class); $builder = m::mock(Builder::class.'[prepareValueQuery,getCollection]', [$connection]); $collection = m::mock(Collection::class); @@ -564,7 +576,7 @@ public function testFirstOrFailShouldGetFirst() $builder->shouldAllowMockingProtectedMethods(); - // Act + // Expectations $builder->expects() ->prepareValueQuery($query) ->andReturn($preparedQuery); @@ -577,31 +589,33 @@ public function testFirstOrFailShouldGetFirst() ->findOne($preparedQuery, ['projection' => []]) ->andReturn($object); + // Actions $result = $builder->firstOrFail($object, $query); - // Assert + // Assertions $this->assertSame($object, $result); } public function testFirstOrFailWithNullShouldFail() { - // Arrange + // Set $connection = m::mock(Connection::class); $builder = new Builder($connection); $model = new class extends AbstractModel { }; + // Expectations $this->expectException(ModelNotFoundException::class); $this->expectExceptionMessage('No query results for model ['.get_class($model).'].'); - // Act + // Actions $builder->firstOrFail($model, null); } public function testShouldGetNullIfFirstCantFindAnything() { - // Arrange + // Set $connection = m::mock(Connection::class); $builder = m::mock(Builder::class.'[prepareValueQuery,getCollection]', [$connection]); $model = m::mock(ModelInterface::class); @@ -611,7 +625,7 @@ public function testShouldGetNullIfFirstCantFindAnything() $builder->shouldAllowMockingProtectedMethods(); - // Expect + // Expectations $builder->expects() ->prepareValueQuery($query) ->andReturn($preparedQuery); @@ -624,21 +638,22 @@ public function testShouldGetNullIfFirstCantFindAnything() ->findOne($preparedQuery, ['projection' => []]) ->andReturn(null); - // Act + // Actions $result = $builder->first($model, $query); - // Assert + // Assertions $this->assertNull($result); } public function testShouldGetFirstProjectingFields() { - // Arrange + // Set $connection = m::mock(Connection::class); $builder = m::mock( Builder::class.'[prepareValueQuery,getCollection]', [$connection] ); + $builder->shouldAllowMockingProtectedMethods(); $model = m::mock(ModelInterface::class); $collection = m::mock(Collection::class); @@ -646,9 +661,7 @@ public function testShouldGetFirstProjectingFields() $preparedQuery = ['_id' => 123]; $projection = ['project' => true, 'fields' => false]; - $builder->shouldAllowMockingProtectedMethods(); - - // Expect + // Expectations $builder->expects() ->prepareValueQuery($query) ->andReturn($preparedQuery); @@ -661,16 +674,16 @@ public function testShouldGetFirstProjectingFields() ->findOne($preparedQuery, ['projection' => $projection]) ->andReturn(null); - // Act + // Actions $result = $builder->first($model, $query, $projection); - // Assert + // Assertions $this->assertNull($result); } public function testShouldGetRawCollection() { - // Arrange + // Set $connection = m::mock(Connection::class); $builder = new Builder($connection); $collection = m::mock(Collection::class); @@ -679,7 +692,7 @@ public function testShouldGetRawCollection() $connection->defaultDatabase = 'grimory'; $connection->grimory = (object) ['models' => $collection]; - // Expect + // Expectations $connection->expects() ->getRawConnection() ->andReturn($connection); @@ -688,10 +701,10 @@ public function testShouldGetRawCollection() ->getCollectionName() ->andReturn('models'); - // Act + // Actions $result = $this->callProtected($builder, 'getCollection', [$model]); - // Assert + // Assertions $this->assertSame($collection, $result); } @@ -700,14 +713,14 @@ public function testShouldGetRawCollection() */ public function testShouldPrepareQueryValue($value, $expectation) { - // Arrange + // Set $connection = m::mock(Connection::class); $builder = new Builder($connection); - // Act + // Actions $result = $this->callProtected($builder, 'prepareValueQuery', [$value]); - // Assert + // Assertions $this->assertMongoQueryEquals($expectation, $result); } @@ -716,20 +729,20 @@ public function testShouldPrepareQueryValue($value, $expectation) */ public function testPrepareProjectionShouldConvertArray($data, $expectation) { - // Arrange + // Set $connection = m::mock(Connection::class); $builder = new Builder($connection); - // Act + // Actions $result = $this->callProtected($builder, 'prepareProjection', [$data]); - // Assert + // Assertions $this->assertSame($expectation, $result); } public function testPrepareProjectionShouldThrownAnException() { - // Arrange + // Set $connection = m::mock(Connection::class); $builder = new Builder($connection); $data = ['valid' => true, 'invalid-key' => 'invalid-value']; @@ -738,11 +751,11 @@ public function testPrepareProjectionShouldThrownAnException() $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("Invalid projection: 'invalid-key' => 'invalid-value'"); - // Act + // Actions $this->callProtected($builder, 'prepareProjection', [$data]); } - public function eventsToBailOperations() + public function eventsToBailOperations(): array { return [ 'Saving event' => [ @@ -750,19 +763,16 @@ public function eventsToBailOperations() 'dbOperation' => 'replaceOne', 'eventName' => 'saving', ], - // ------------------------ 'Inserting event' => [ 'operation' => 'insert', 'dbOperation' => 'insertOne', 'eventName' => 'inserting', ], - // ------------------------ 'Updating event' => [ 'operation' => 'update', 'dbOperation' => 'updateOne', 'eventName' => 'updating', ], - // ------------------------ 'Deleting event' => [ 'operation' => 'delete', 'dbOperation' => 'deleteOne', @@ -771,29 +781,25 @@ public function eventsToBailOperations() ]; } - public function queryValueScenarios() + public function queryValueScenarios(): array { return [ 'An array' => [ 'value' => ['age' => ['$gt' => 25]], 'expectation' => ['age' => ['$gt' => 25]], ], - // ------------------------ 'An ObjectId string' => [ 'value' => '507f1f77bcf86cd799439011', 'expectation' => ['_id' => new ObjectId('507f1f77bcf86cd799439011')], ], - // ------------------------ 'An ObjectId string within a query' => [ 'value' => ['_id' => '507f1f77bcf86cd799439011'], 'expectation' => ['_id' => new ObjectId('507f1f77bcf86cd799439011')], ], - // ------------------------ 'Other type of _id, sequence for example' => [ 'value' => 7, 'expectation' => ['_id' => 7], ], - // ------------------------ 'Series of string _ids as the $in parameter' => [ 'value' => ['_id' => ['$in' => ['507f1f77bcf86cd799439011', '507f1f77bcf86cd799439012']]], 'expectation' => [ @@ -805,15 +811,14 @@ public function queryValueScenarios() ], ], ], - // ------------------------ - 'Series of string _ids as the $in parameter' => [ + 'Series of string _ids as the $nin parameter' => [ 'value' => ['_id' => ['$nin' => ['507f1f77bcf86cd799439011']]], 'expectation' => ['_id' => ['$nin' => [new ObjectId('507f1f77bcf86cd799439011')]]], ], ]; } - public function getWriteConcernVariations() + public function getWriteConcernVariations(): array { $model = new class extends AbstractModel { @@ -863,7 +868,7 @@ public function getWriteConcernVariations() /** * Retrieves projections that should be replaced by mapper. */ - public function getProjections() + public function getProjections(): array { return [ 'Should return self array' => [ @@ -885,7 +890,7 @@ public function getProjections() ]; } - protected function getEventService() + protected function getEventService(): EventTriggerService { if (!Ioc::has(EventTriggerService::class)) { Ioc::instance(EventTriggerService::class, m::mock(EventTriggerService::class)); @@ -894,20 +899,22 @@ protected function getEventService() return Ioc::make(EventTriggerService::class); } - protected function expectEventToBeFired($event, $model, bool $halt, $return = true) + protected function expectEventToBeFired(string $event, ModelInterface $model, bool $halt, bool $return = true): void { $event = 'mongolid.'.$event.': '.get_class($model); - $this->getEventService()->expects() + $this->getEventService() + ->expects() ->fire($event, $model, $halt) ->andReturn($return); } - protected function expectEventNotToBeFired($event, $model) + protected function expectEventNotToBeFired(string $event, ModelInterface $model): void { $event = 'mongolid.'.$event.': '.get_class($model); - $this->getEventService()->expects() + $this->getEventService() + ->expects() ->fire($event, $model, m::any()) ->never(); } diff --git a/tests/Unit/Query/BulkWriteTest.php b/tests/Unit/Query/BulkWriteTest.php index 8efea1a5..c3317c9b 100644 --- a/tests/Unit/Query/BulkWriteTest.php +++ b/tests/Unit/Query/BulkWriteTest.php @@ -13,27 +13,27 @@ class BulkWriteTest extends TestCase { public function testShouldConstructBulkWriteObject() { - // Arrange + // Set $model = m::mock(ModelInterface::class); - // Act + // Actions $bulkWrite = new BulkWrite($model); - // Assert + // Assertions $this->assertInstanceOf(BulkWrite::class, $bulkWrite); } public function testShouldSetAndGetMongoBulkWrite() { - // Arrange + // Set $model = m::mock(ModelInterface::class); $mongoBulkWrite = new MongoBulkWrite(); - // Act + // Actions $bulkWrite = new BulkWrite($model); $bulkWrite->setBulkWrite($mongoBulkWrite); - // Assert + // Assertions $this->assertSame($mongoBulkWrite, $bulkWrite->getBulkWrite()); } @@ -69,6 +69,8 @@ public function testShouldUpdateOneWithUnsetOperationToBulkWrite() $id = '123'; $data = ['name' => 'John']; + $bulkWrite = m::mock(BulkWrite::class.'[getBulkWrite]', [$model]); + // Expectations $mongoBulkWrite->expects() ->update( @@ -83,8 +85,6 @@ function ($actual) { ['upsert' => true] ); - $bulkWrite = m::mock(BulkWrite::class.'[getBulkWrite]', [$model]); - $bulkWrite->expects() ->getBulkWrite() ->andReturn($mongoBulkWrite); @@ -95,14 +95,16 @@ function ($actual) { public function testShouldUpdateOneWithQueryOnFilterToBulkWrite() { - // Arrange + // Set $model = m::mock(ModelInterface::class); $mongoBulkWrite = m::mock(new MongoBulkWrite()); $query = ['_id' => '123', 'grades.grade' => 85]; $data = ['grades.std' => 6]; - // Expect + $bulkWrite = m::mock(BulkWrite::class.'[getBulkWrite]', [$model]); + + // Expectations $mongoBulkWrite->expects() ->update( m::on( @@ -116,13 +118,11 @@ function ($actual) use ($query) { ['upsert' => true] ); - $bulkWrite = m::mock(BulkWrite::class.'[getBulkWrite]', [$model]); - $bulkWrite->expects() ->getBulkWrite() ->andReturn($mongoBulkWrite); - // Act + // Actions $bulkWrite->updateOne($query, $data, ['upsert' => true], '$unset'); } @@ -136,7 +136,9 @@ public function testShouldExecuteBulkWrite() $connection->defaultDatabase = 'foo'; $namespace = 'foo.bar'; - // Expect + $bulkWrite = m::mock(BulkWrite::class.'[getBulkWrite]', [$model]); + + // Expectations $connection->expects() ->getRawManager() ->andReturn($manager); @@ -149,13 +151,11 @@ public function testShouldExecuteBulkWrite() ->executeBulkWrite($namespace, $mongoBulkWrite, ['writeConcern' => new WriteConcern(1)]) ->andReturn(true); - $bulkWrite = m::mock(BulkWrite::class.'[getBulkWrite]', [$model]); - $bulkWrite->expects() ->getBulkWrite() ->andReturn($mongoBulkWrite); - // Act + // Actions $bulkWrite->execute(); } } diff --git a/tests/Unit/TestCase.php b/tests/Unit/TestCase.php index 7572354b..f3a3058e 100644 --- a/tests/Unit/TestCase.php +++ b/tests/Unit/TestCase.php @@ -29,7 +29,7 @@ protected function tearDown() * @param mixed $expectedQuery correct query * @param mixed $query query being evaluated */ - public function assertMongoQueryEquals($expectedQuery, $query) + protected function assertMongoQueryEquals($expectedQuery, $query) { $this->assertEquals($expectedQuery, $query, 'Queries are not equals'); @@ -46,7 +46,7 @@ public function assertMongoQueryEquals($expectedQuery, $query) ); if (method_exists($value, '__toString')) { - $this->assertEquals( + $this->assertSame( (string) $expectedQuery[$key], (string) $query[$key], 'Object within the query is not equals' @@ -84,7 +84,7 @@ protected function callProtected($obj, $method, $args = []) * @param string $property property name * @param mixed $value value to be set */ - protected function setProtected($obj, $property, $value) + protected function setProtected($obj, $property, $value): void { $class = new ReflectionClass($obj); $property = $class->getProperty($property); diff --git a/tests/Unit/Util/LocalDateTimeTest.php b/tests/Unit/Util/LocalDateTimeTest.php index b1a7cada..01382347 100644 --- a/tests/Unit/Util/LocalDateTimeTest.php +++ b/tests/Unit/Util/LocalDateTimeTest.php @@ -36,53 +36,61 @@ protected function setUp() */ protected function tearDown() { - unset($this->date, $this->format); + unset($this->date); parent::tearDown(); } public function testGetShouldRetrievesDateUsingTimezone() { - $this->assertEquals( - $this->date, - LocalDateTime::get(new UTCDateTime($this->date)) - ); + // Set + $date = new UTCDateTime($this->date); + + // Actions + $result = LocalDateTime::get($date); + + // Assertions + $this->assertEquals($this->date, $result); } public function testFormatShouldRetrievesDateWithDefaultFormat() { - $this->date->setTimezone( - new DateTimeZone(date_default_timezone_get()) - ); + // Set + $timezone = new DateTimeZone(date_default_timezone_get()); + $this->date->setTimezone($timezone); - $this->assertEquals( - $this->date->format($this->format), - LocalDateTime::format(new UTCDateTime($this->date)) - ); + // Actions + $result = LocalDateTime::format(new UTCDateTime($this->date)); + + // Assertions + $this->assertSame($this->date->format($this->format), $result); } public function testFormatShouldRetrieesDateUsingGivenFormat() { - $this->date->setTimezone( - new DateTimeZone(date_default_timezone_get()) - ); - + // Set + $timezone = new DateTimeZone(date_default_timezone_get()); + $this->date->setTimezone($timezone); $format = 'Y-m-d H:i:s'; - $this->assertEquals( - $this->date->format($format), - LocalDateTime::format(new UTCDateTime($this->date), $format) - ); + // Actions + $result = LocalDateTime::format(new UTCDateTime($this->date), $format); + + // Assertions + $this->assertSame($this->date->format($format), $result); } public function testTimestampShouldRetrievesTimestampUsingTimezone() { - $dateTimestamp = $this->date->getTimestamp(); - $mongoDateTimestamp = LocalDateTime::timestamp( - new UTCDateTime($this->date) - ); + // Set + $timestamp = $this->date->getTimestamp(); + $date = new UTCDateTime($this->date); + + // Actions + $mongoDateTimestamp = LocalDateTime::timestamp($date); - $this->assertEquals( - DateTime::createFromFormat($dateTimestamp, $this->format), + // Assertions + $this->assertSame( + DateTime::createFromFormat($timestamp, $this->format), DateTime::createFromFormat($mongoDateTimestamp, $this->format) ); } diff --git a/tests/Unit/Util/ObjectIdUtilsTest.php b/tests/Unit/Util/ObjectIdUtilsTest.php index 301d0a86..f0d362ac 100644 --- a/tests/Unit/Util/ObjectIdUtilsTest.php +++ b/tests/Unit/Util/ObjectIdUtilsTest.php @@ -9,29 +9,37 @@ class ObjectIdUtilsTest extends TestCase /** * @dataProvider objectIdStringScenarios */ - public function testShouldEvaluateIfValueIsAnObjectid($value, $expectation) + public function testShouldEvaluateIfValueIsAnObjectId($value, bool $expectation) { - $this->assertEquals($expectation, ObjectIdUtils::isObjectId($value)); + // Actions + $result = ObjectIdUtils::isObjectId($value); + + // Assertions + $this->assertSame($expectation, $result); } - public function objectIdStringScenarios() + public function objectIdStringScenarios(): array { return [ - // [Value, Expectation], - ['577a68c44d3cec1f6c7796a2', true], - ['577a68d24d3cec1f817796a5', true], - ['577a68d14d3cec1f6d7796a3', true], - ['507f1f77bcf86cd799439011', true], - ['507f191e810c19729de860ea', true], - [new ObjectId(), true], - ['1', false], - ['507f191e810c197', false], - ['123456', false], - ['abcdefgh1234567890123456', false], - ['+07f191e810c19729de860ea', false], - [1234567, false], - [0.5, false], - [['key' => 'value'], false], + ['value' => '577a68c44d3cec1f6c7796a2', 'expectation' => true], + ['value' => '577a68d24d3cec1f817796a5', 'expectation' => true], + ['value' => '577a68d14d3cec1f6d7796a3', 'expectation' => true], + ['value' => '507f1f77bcf86cd799439011', 'expectation' => true], + ['value' => '507f191e810c19729de860ea', 'expectation' => true], + ['value' => new ObjectId(), 'expectation' => true], + ['value' => new ObjectId('577a68c44d3cec1f6c7796a2'), 'expectation' => true], + ['value' => 1, 'expectation' => false], + ['value' => '507f191e810c197', 'expectation' => false], + ['value' => 123456, 'expectation' => false], + ['value' => 'abcdefgh1234567890123456', 'expectation' => false], + ['value' => '+07f191e810c19729de860ea', 'expectation' => false], + ['value' => 1234567, 'expectation' => false], + ['value' => 0.5, 'expectation' => false], + ['value' => null, 'expectation' => false], + ['value' => true, 'expectation' => false], + ['value' => false, 'expectation' => false], + ['value' => ['key' => 'value'], 'expectation' => false], + ['value' => ['577a68c44d3cec1f6c7796a2'], 'expectation' => false], ]; } } diff --git a/tests/Unit/Util/SequenceServiceTest.php b/tests/Unit/Util/SequenceServiceTest.php index 6d94c631..538bd1c3 100644 --- a/tests/Unit/Util/SequenceServiceTest.php +++ b/tests/Unit/Util/SequenceServiceTest.php @@ -13,13 +13,13 @@ class SequenceServiceTest extends TestCase */ public function testShouldGetNextValue($sequenceName, $currentValue, $expectation) { - // Arrange + // Set $connection = m::mock(Connection::class); $sequenceService = m::mock(SequenceService::class.'[rawCollection]', [$connection]); $sequenceService->shouldAllowMockingProtectedMethods(); $rawCollection = m::mock(Collection::class); - // Act + // Expectations $sequenceService->expects() ->rawCollection() ->andReturn($rawCollection); @@ -33,16 +33,16 @@ public function testShouldGetNextValue($sequenceName, $currentValue, $expectatio $currentValue ? (object) ['seq' => $currentValue] : null ); - // Assertion - $this->assertEquals( - $expectation, - $sequenceService->getNextValue($sequenceName) - ); + // Actions + $result = $sequenceService->getNextValue($sequenceName); + + // Assertions + $this->assertSame($expectation, $result); } public function testShouldGetRawCollection() { - // Arrange + // Set $connection = m::mock(Connection::class); $sequenceService = new SequenceService($connection, 'foobar'); $collection = m::mock(Collection::class); @@ -50,19 +50,19 @@ public function testShouldGetRawCollection() $connection->defaultDatabase = 'grimory'; $connection->grimory = (object) ['foobar' => $collection]; - // Act + // Expectations $connection->expects() ->getRawConnection() ->andReturnSelf($connection); - // Assertion - $this->assertEquals( - $collection, - $this->callProtected($sequenceService, 'rawCollection') - ); + // Actions + $result = $this->callProtected($sequenceService, 'rawCollection'); + + // Assertions + $this->assertSame($collection, $result); } - public function sequenceScenarios() + public function sequenceScenarios(): array { return [ 'New sequence in collection "products"' => [ @@ -70,18 +70,11 @@ public function sequenceScenarios() 'currentValue' => 0, 'expectation' => 1, ], - // ----------------------- 'Existing sequence in collection "unicorns"' => [ 'sequenceName' => 'unicorns', 'currentValue' => 7, 'expectation' => 8, ], - // ----------------------- - 'Existing sequence in collection "unicorns"' => [ - 'sequenceName' => 'unicorns', - 'currentValue' => 3, - 'expectation' => 4, - ], ]; } } From 7b5e3fa9ee6d855298ff210b869cf4d58ef49b02 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Tue, 27 Nov 2018 17:28:16 -0200 Subject: [PATCH 079/116] Only report coverage for version 7.2 and ensure it runs on 7.3 --- .travis.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3f9dd46b..330b22ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,8 +23,4 @@ script: - vendor/bin/phpunit -c phpunit.xml.dist after_script: - - php vendor/bin/coveralls -v - -matrix: - allow_failures: - - php: 7.3 + - if [[ $(phpenv version-name) = "7.2" ]]; then php vendor/bin/coveralls -v; fi From ba1aaa88300d3bef38481d9f4ac42be46ac649d1 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Tue, 27 Nov 2018 17:54:38 -0200 Subject: [PATCH 080/116] Fix wrong renaming --- src/Connection/Manager.php | 2 +- src/Event/EventTriggerService.php | 10 +++++----- tests/Unit/Connection/ManagerTest.php | 2 +- tests/Unit/Event/EventTriggerServiceTest.php | 16 ++++++++-------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Connection/Manager.php b/src/Connection/Manager.php index d79b4743..6c8fda94 100644 --- a/src/Connection/Manager.php +++ b/src/Connection/Manager.php @@ -78,7 +78,7 @@ public function setEventTrigger(EventTriggerInterface $eventTrigger) { $this->init(); $eventService = new EventTriggerService(); - $eventService->registerEventBuilder($eventTrigger); + $eventService->registerEventDispatcher($eventTrigger); $this->container->instance(EventTriggerService::class, $eventService); } diff --git a/src/Event/EventTriggerService.php b/src/Event/EventTriggerService.php index d5e64d62..b7616297 100644 --- a/src/Event/EventTriggerService.php +++ b/src/Event/EventTriggerService.php @@ -11,7 +11,7 @@ class EventTriggerService * * @var EventTriggerInterface */ - protected $builder; + protected $dispatcher; /** * Registers a object that will have the responsibility of firing events to @@ -19,9 +19,9 @@ class EventTriggerService * * @param EventTriggerInterface $builder event trigger object */ - public function registerEventBuilder(EventTriggerInterface $builder) + public function registerEventDispatcher(EventTriggerInterface $builder) { - $this->builder = $builder; + $this->dispatcher = $builder; } /** @@ -38,8 +38,8 @@ public function registerEventBuilder(EventTriggerInterface $builder) */ public function fire(string $event, $payload, bool $halt = false) { - if ($this->builder) { - return $this->builder->fire($event, $payload, $halt); + if ($this->dispatcher) { + return $this->dispatcher->fire($event, $payload, $halt); } return true; diff --git a/tests/Unit/Connection/ManagerTest.php b/tests/Unit/Connection/ManagerTest.php index 4651280f..7e4af992 100644 --- a/tests/Unit/Connection/ManagerTest.php +++ b/tests/Unit/Connection/ManagerTest.php @@ -50,7 +50,7 @@ public function testShouldSetEventTrigger() ->instance(EventTriggerService::class, m::type(EventTriggerService::class)) ->andReturnUsing(function ($class, $eventService) use ($test, $eventTrigger) { $test->assertSame(EventTriggerService::class, $class); - $test->assertAttributeSame($eventTrigger, 'builder', $eventService); + $test->assertAttributeSame($eventTrigger, 'dispatcher', $eventService); }); // Actions diff --git a/tests/Unit/Event/EventTriggerServiceTest.php b/tests/Unit/Event/EventTriggerServiceTest.php index 44c281db..5c6c11d0 100644 --- a/tests/Unit/Event/EventTriggerServiceTest.php +++ b/tests/Unit/Event/EventTriggerServiceTest.php @@ -6,15 +6,15 @@ class EventTriggerServiceTest extends TestCase { - public function testShouldSendTheEventsToTheExternalBuilder() + public function testShouldSendTheEventsToTheExternalDispatcher() { // Set - $builder = m::mock(EventTriggerInterface::class); + $dispatcher = m::mock(EventTriggerInterface::class); $service = new EventTriggerService(); - $service->registerEventBuilder($builder); + $service->registerEventDispatcher($dispatcher); // Expectations - $builder->expects() + $dispatcher->expects() ->fire('foobar', ['answer' => 23], true) ->andReturn(true); @@ -25,14 +25,14 @@ public function testShouldSendTheEventsToTheExternalBuilder() $this->assertTrue($result); } - public function testShouldReturnTrueIfThereIsNoExternalBuilder() + public function testShouldReturnTrueIfThereIsNoExternalDispatcher() { // Set - $builder = m::mock(EventTriggerInterface::class); + $dispatcher = m::mock(EventTriggerInterface::class); $service = new EventTriggerService(); // Expectations - $builder->expects() + $dispatcher->expects() ->fire() ->never(); @@ -40,7 +40,7 @@ public function testShouldReturnTrueIfThereIsNoExternalBuilder() $result = $service->fire('foobar', ['answer' => 23], true); // Assertions - /* without calling registerEventBuilder */ + /* without calling registerEventDispatcher */ $this->assertTrue($result); } } From 74292fc2d7ecea8d460094dbdab046caaa725c6e Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 28 Nov 2018 09:58:10 -0200 Subject: [PATCH 081/116] Make fill method static and change signature to allow object to be passed --- src/Model/AbstractModel.php | 2 +- src/Model/HasAttributesInterface.php | 19 ++++++----- src/Model/HasAttributesTrait.php | 32 +++++++++++-------- tests/Integration/EmbedsManyRelationTest.php | 4 +-- tests/Integration/EmbedsOneRelationTest.php | 4 +-- .../ReferencesManyRelationTest.php | 4 +-- .../Integration/ReferencesOneRelationTest.php | 4 +-- tests/Unit/Model/AbstractModelTest.php | 9 +++--- tests/Unit/Model/DocumentEmbedderTest.php | 2 +- tests/Unit/Model/HasAttributesTraitTest.php | 10 +++--- 10 files changed, 50 insertions(+), 40 deletions(-) diff --git a/src/Model/AbstractModel.php b/src/Model/AbstractModel.php index ef5967f8..501c9b59 100644 --- a/src/Model/AbstractModel.php +++ b/src/Model/AbstractModel.php @@ -244,7 +244,7 @@ public function bsonSerialize() public function bsonUnserialize(array $data) { unset($data['__pclass']); - $this->fill($data); + static::fill($data, $this); $this->syncOriginalDocumentAttributes(); } diff --git a/src/Model/HasAttributesInterface.php b/src/Model/HasAttributesInterface.php index e5579996..9f6c8626 100644 --- a/src/Model/HasAttributesInterface.php +++ b/src/Model/HasAttributesInterface.php @@ -11,6 +11,17 @@ */ interface HasAttributesInterface { + /** + * Set the model attributes using an array. + * + * @param array $input the data that will be used to fill the attributes + * @param HasAttributesInterface|null $object if provided, $input will be merged with object attributes + * @param bool $force force fill (ignore fillable fields) + * + * @return HasAttributesInterface + */ + public static function fill(array $input, HasAttributesInterface $object = null, bool $force = false): HasAttributesInterface; + /** * Check if an attribute is set on the model. * @@ -32,14 +43,6 @@ public function getDocumentAttribute(string $key); */ public function getDocumentAttributes(): array; - /** - * Set the model attributes using an array. - * - * @param array $input the data that will be used to fill the attributes - * @param bool $force force fill - */ - public function fill(array $input, bool $force = false); - /** * Unset a given attribute on the model. * diff --git a/src/Model/HasAttributesTrait.php b/src/Model/HasAttributesTrait.php index a785290b..c88491f3 100644 --- a/src/Model/HasAttributesTrait.php +++ b/src/Model/HasAttributesTrait.php @@ -61,6 +61,25 @@ trait HasAttributesTrait */ private $originalAttributes = []; + /** + * {@inheritdoc} + */ + public static function fill(array $input, HasAttributesInterface $object = null, bool $force = false): HasAttributesInterface + { + if (!$object) { + $object = new static(); + } + + foreach ($input as $key => $value) { + if ($force + || ((!$object->fillable || in_array($key, $object->fillable)) && !in_array($key, $object->guarded))) { + $object->setDocumentAttribute($key, $value); + } + } + + return $object; + } + /** * {@inheritdoc} */ @@ -106,19 +125,6 @@ function ($value) { ); } - /** - * {@inheritdoc} - */ - public function fill(array $input, bool $force = false) - { - foreach ($input as $key => $value) { - if ($force - || ((!$this->fillable || in_array($key, $this->fillable)) && !in_array($key, $this->guarded))) { - $this->setDocumentAttribute($key, $value); - } - } - } - /** * {@inheritdoc} */ diff --git a/tests/Integration/EmbedsManyRelationTest.php b/tests/Integration/EmbedsManyRelationTest.php index 77a938d3..4d81cd63 100644 --- a/tests/Integration/EmbedsManyRelationTest.php +++ b/tests/Integration/EmbedsManyRelationTest.php @@ -62,7 +62,7 @@ public function testShouldRetrieveSiblingsOfUser() // changing the field with fillable $john->siblings()->add($bob); $this->assertSiblings([$bob], $john); - $john->fill(['embedded_siblings' => [$chuck]], true); + $john = EmbeddedUser::fill(['embedded_siblings' => [$chuck]], $john, true); $this->assertSiblings([$chuck], $john); } @@ -120,7 +120,7 @@ public function testShouldRetrieveGrandsonsOfUserUsingCustomKey() // changing the field with fillable $john->grandsons()->add($bob); $this->assertGrandsons([$bob], $john); - $john->fill(['other_arbitrary_field' => [$chuck]], true); + $john = EmbeddedUser::fill(['other_arbitrary_field' => [$chuck]], $john, true); $this->assertGrandsons([$chuck], $john); } diff --git a/tests/Integration/EmbedsOneRelationTest.php b/tests/Integration/EmbedsOneRelationTest.php index 9ecb7019..ec7cf0dd 100644 --- a/tests/Integration/EmbedsOneRelationTest.php +++ b/tests/Integration/EmbedsOneRelationTest.php @@ -53,7 +53,7 @@ public function testShouldRetrieveParentOfUser() // changing the field with fillable $john->parent()->add($bob); $this->assertParent($bob, $john); - $john->fill(['embedded_parent' => [$chuck]], true); + $john = EmbeddedUser::fill(['embedded_parent' => [$chuck]], $john, true); $this->assertParent($chuck, $john); } @@ -101,7 +101,7 @@ public function testShouldRetrieveSonOfUserUsingCustomKey() // changing the field with fillable $john->son()->add($bob); $this->assertSon($bob, $john); - $john->fill(['arbitrary_field' => [$chuck]], true); + $john = EmbeddedUser::fill(['arbitrary_field' => [$chuck]], $john, true); $this->assertSon($chuck, $john); } diff --git a/tests/Integration/ReferencesManyRelationTest.php b/tests/Integration/ReferencesManyRelationTest.php index 257f8a9b..d6fc691c 100644 --- a/tests/Integration/ReferencesManyRelationTest.php +++ b/tests/Integration/ReferencesManyRelationTest.php @@ -61,7 +61,7 @@ public function testShouldRetrieveSiblingsOfUser() // changing the field with fillable $john->siblings()->attach($bob); $this->assertSiblings([$bob], $john); - $john->fill(['siblings_ids' => [$chuck->_id]], true); + $john = ReferencedUser::fill(['siblings_ids' => [$chuck->_id]], $john, true); $this->assertSiblings([$chuck], $john); } @@ -123,7 +123,7 @@ public function testShouldRetrieveGrandsonsOfUserUsingCustomKey() // changing the field with fillable $john->grandsons()->attach($bob); $this->assertGrandsons([$bob], $john); - $john->fill(['grandsons_codes' => [$chuck->code]], true); + $john = ReferencedUser::fill(['grandsons_codes' => [$chuck->code]], $john, true); $this->assertGrandsons([$chuck], $john); } diff --git a/tests/Integration/ReferencesOneRelationTest.php b/tests/Integration/ReferencesOneRelationTest.php index 87b572b9..ffa6c969 100644 --- a/tests/Integration/ReferencesOneRelationTest.php +++ b/tests/Integration/ReferencesOneRelationTest.php @@ -51,7 +51,7 @@ public function testShouldRetrieveParentOfUser() // changing the field with fillable $john->parent()->attach($bob); $this->assertParent($bob, $john); - $john->fill(['parent_id' => [$chuck->_id]], true); + $john = ReferencedUser::fill(['parent_id' => [$chuck->_id]], $john, true); $this->assertParent($chuck, $john); } @@ -99,7 +99,7 @@ public function testShouldRetrieveSonOfUserUsingCustomKey() // changing the field with fillable $john->son()->attach($bob); $this->assertSon($bob, $john); - $john->fill(['arbitrary_field' => [$chuck->code]], true); + $john = ReferencedUser::fill(['arbitrary_field' => [$chuck->code]], $john, true); $this->assertSon($chuck, $john); } diff --git a/tests/Unit/Model/AbstractModelTest.php b/tests/Unit/Model/AbstractModelTest.php index 0c0c7310..25aa5c44 100644 --- a/tests/Unit/Model/AbstractModelTest.php +++ b/tests/Unit/Model/AbstractModelTest.php @@ -432,12 +432,13 @@ public function testShouldHaveDynamicGetters() }; // Actions - $model->fill( + $model = $model::fill( [ 'name' => 'John', 'age' => 25, 'child' => $child, - ] + ], + $model ); // Assertions @@ -455,7 +456,7 @@ public function testShouldCheckIfAttributeIsSet() }; // Actions - $model->fill(['name' => 'John', 'ignored' => null]); + $model = $model::fill(['name' => 'John', 'ignored' => null]); // Assertions $this->assertTrue(isset($model->name)); @@ -490,7 +491,7 @@ public function testShouldUnsetAttributes() $model = new class() extends AbstractModel { }; - $model->fill( + $model = $model::fill( [ 'name' => 'John', 'age' => 25, diff --git a/tests/Unit/Model/DocumentEmbedderTest.php b/tests/Unit/Model/DocumentEmbedderTest.php index 1b997011..214e2547 100644 --- a/tests/Unit/Model/DocumentEmbedderTest.php +++ b/tests/Unit/Model/DocumentEmbedderTest.php @@ -23,7 +23,7 @@ public function testShouldEmbed($originalField, $modelFields, $method, $expectat $model = new class extends AbstractModel { }; - $model->fill($modelFields); + $model = $model::fill($modelFields); } else { $model = $modelFields; } diff --git a/tests/Unit/Model/HasAttributesTraitTest.php b/tests/Unit/Model/HasAttributesTraitTest.php index 57831899..04fbc7c2 100644 --- a/tests/Unit/Model/HasAttributesTraitTest.php +++ b/tests/Unit/Model/HasAttributesTraitTest.php @@ -96,7 +96,7 @@ public function testShouldFillOnlyPermittedAttributes( $expected ) { // Set - $model = new class($fillable, $guarded) + $model = new class($fillable, $guarded) implements HasAttributesInterface { use HasAttributesTrait; use HasRelationsTrait; @@ -109,7 +109,7 @@ public function __construct(array $fillable, array $guarded) }; // Actions - $model->fill($input); + $model = $model::fill($input, $model); // Assertions $this->assertSame($expected, $model->getDocumentAttributes()); @@ -118,7 +118,7 @@ public function __construct(array $fillable, array $guarded) public function testShouldForceFillAttributes() { // Set - $model = new class() + $model = new class() implements HasAttributesInterface { use HasAttributesTrait; use HasRelationsTrait; @@ -130,7 +130,7 @@ public function testShouldForceFillAttributes() ]; // Actions - $model->fill($input, true); + $model = $model::fill($input, $model, true); $result = $model->getDocumentAttribute('not_allowed_attribute'); // Assertions @@ -208,7 +208,7 @@ public function testShouldCheckIfAttributeIsSet() }; // Actions - $model->fill(['name' => 'John', 'ignored' => null]); + $model = $model::fill(['name' => 'John', 'ignored' => null]); // Assertions $this->assertTrue(isset($model->name)); From 00c7c13ae0d8befc7c161b6f28b20bed5d3bce9d Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 28 Nov 2018 10:40:07 -0200 Subject: [PATCH 082/116] Move stubs to common tests namespace --- composer.json | 3 +-- tests/Integration/AttributesKeyTest.php | 2 +- tests/Integration/DateQueriesTest.php | 2 +- tests/Integration/EmbedsManyRelationTest.php | 2 +- tests/Integration/EmbedsOneRelationTest.php | 2 +- tests/Integration/PersistedDataTest.php | 2 +- tests/Integration/ReferencesManyRelationTest.php | 2 +- tests/Integration/ReferencesOneRelationTest.php | 2 +- tests/Integration/RewindableCursorTest.php | 2 +- tests/{Integration => }/Stubs/EmbeddedUser.php | 2 +- tests/{Integration => }/Stubs/ReferencedUser.php | 2 +- 11 files changed, 11 insertions(+), 12 deletions(-) rename tests/{Integration => }/Stubs/EmbeddedUser.php (96%) rename tests/{Integration => }/Stubs/ReferencedUser.php (96%) diff --git a/composer.json b/composer.json index b7cc05d5..ffd4e9be 100644 --- a/composer.json +++ b/composer.json @@ -38,8 +38,7 @@ "autoload-dev": { "psr-4": { "Mongolid\\": "tests/Unit", - "Mongolid\\Tests\\Integration\\": "tests/Integration", - "Mongolid\\Tests\\Util\\": "tests/Util" + "Mongolid\\Tests\\": "tests" } }, "extra": { diff --git a/tests/Integration/AttributesKeyTest.php b/tests/Integration/AttributesKeyTest.php index 791de58f..720b1000 100644 --- a/tests/Integration/AttributesKeyTest.php +++ b/tests/Integration/AttributesKeyTest.php @@ -1,7 +1,7 @@ Date: Wed, 28 Nov 2018 10:45:46 -0200 Subject: [PATCH 083/116] Use stubs for Relations test --- phpcs.xml | 9 --- tests/Unit/Model/HasRelationsTraitTest.php | 76 ++++++---------------- 2 files changed, 21 insertions(+), 64 deletions(-) diff --git a/phpcs.xml b/phpcs.xml index 4c952b8d..2414ba82 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -14,13 +14,4 @@ tests - - - - tests/Unit/Model/HasRelationsTraitTest.php - - - - tests/Unit/Model/HasRelationsTraitTest.php - diff --git a/tests/Unit/Model/HasRelationsTraitTest.php b/tests/Unit/Model/HasRelationsTraitTest.php index 48515058..1c712ee3 100644 --- a/tests/Unit/Model/HasRelationsTraitTest.php +++ b/tests/Unit/Model/HasRelationsTraitTest.php @@ -6,6 +6,8 @@ use Mongolid\Cursor\EmbeddedCursor; use Mongolid\Query\Builder; use Mongolid\TestCase; +use Mongolid\Tests\Stubs\EmbeddedUser; +use Mongolid\Tests\Stubs\ReferencedUser; class HasRelationsTraitTest extends TestCase { @@ -15,20 +17,20 @@ class HasRelationsTraitTest extends TestCase public function testShouldReferenceOne($fieldValue, $expectedQuery) { // Set - $model = new UserStub(); - $model->refOne = $fieldValue; + $model = new ReferencedUser(); + $model->parent_id = $fieldValue; $builder = $this->instance(Builder::class, m::mock(Builder::class)->makePartial()); $expectedQuery = $expectedQuery['referencesOne']; - $expected = new RelatedStub(); + $expected = new ReferencedUser(); // Expectations $builder->expects() - ->first(m::type(RelatedStub::class), $expectedQuery, []) + ->first(m::type(ReferencedUser::class), $expectedQuery, []) ->andReturn($expected); // Actions - $result = $model->relationReferencesOne; + $result = $model->parent; // Assertions $this->assertSame($expected, $result); @@ -40,8 +42,8 @@ public function testShouldReferenceOne($fieldValue, $expectedQuery) public function testShouldReferenceMany($fieldValue, $expectedQuery) { // Set - $model = new UserStub(); - $model->refMany = $fieldValue; + $model = new ReferencedUser(); + $model->siblings_ids = $fieldValue; $builder = $this->instance(Builder::class, m::mock(Builder::class)->makePartial()); $expectedQuery = $expectedQuery['referencesMany']; @@ -49,11 +51,11 @@ public function testShouldReferenceMany($fieldValue, $expectedQuery) // Expectations $builder->expects() - ->where(m::type(RelatedStub::class), $expectedQuery, []) + ->where(m::type(ReferencedUser::class), $expectedQuery, []) ->andReturn($expected); // Actions - $result = $model->relationReferencesMany; + $result = $model->siblings; // Assertions $this->assertSame($expected, $result); @@ -65,16 +67,16 @@ public function testShouldReferenceMany($fieldValue, $expectedQuery) public function testShouldEmbedsOne($fieldValue, $expectedItems) { // Set - $model = new UserStub(); - $model->embOne = $fieldValue; + $model = new EmbeddedUser(); + $model->embedded_parent = $fieldValue; $expectedItems = $expectedItems['embedsOne']; // Actions - $result = $model->relationEmbedsOne; + $result = $model->parent; // Assertions - $this->assertInstanceOf(RelatedStub::class, $result); + $this->assertInstanceOf(EmbeddedUser::class, $result); $this->assertSame($expectedItems, $result); } @@ -84,17 +86,17 @@ public function testShouldEmbedsOne($fieldValue, $expectedItems) public function testShouldEmbedsMany($fieldValue, $expectedItems) { // Set - $model = new UserStub(); - $model->embMany = $fieldValue; + $model = new EmbeddedUser(); + $model->embedded_siblings = $fieldValue; $expectedItems = $expectedItems['embedsMany']; // Actions - $result = $model->relationEmbedsMany; + $result = $model->siblings; // Assertions $this->assertInstanceOf(EmbeddedCursor::class, $result); - $this->assertContainsOnlyInstancesOf(RelatedStub::class, $result->all()); + $this->assertContainsOnlyInstancesOf(EmbeddedUser::class, $result->all()); $this->assertSame($expectedItems, $result->all()); } @@ -162,12 +164,12 @@ public function referenceScenarios(): array public function embedsScenarios(): array { - $model1 = new RelatedStub(); + $model1 = new EmbeddedUser(); $model1->_id = 12345; $model1->name = 'John'; $model1->syncOriginalDocumentAttributes(); - $model2 = new RelatedStub(); + $model2 = new EmbeddedUser(); $model2->_id = 67890; $model2->name = 'Bob'; $model2->syncOriginalDocumentAttributes(); @@ -190,39 +192,3 @@ public function embedsScenarios(): array ]; } } - -class UserStub extends AbstractModel -{ - /** - * {@inheritdoc} - */ - protected $collection = 'users'; - - public function relationReferencesOne() - { - return $this->referencesOne(RelatedStub::class, 'refOne'); - } - - public function relationReferencesMany() - { - return $this->referencesMany(RelatedStub::class, 'refMany'); - } - - public function relationEmbedsOne() - { - return $this->embedsOne(RelatedStub::class, 'embOne'); - } - - public function relationEmbedsMany() - { - return $this->embedsMany(RelatedStub::class, 'embMany'); - } -} - -class RelatedStub extends AbstractModel -{ - /** - * {@inheritdoc} - */ - protected $collection = 'related'; -} From ee464575d16c5668cf0a6f0434f7b3cf5c7f57a5 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 28 Nov 2018 11:39:47 -0200 Subject: [PATCH 084/116] Create new polymorphable model interface --- src/Model/AbstractModel.php | 2 +- src/Model/HasAttributesInterface.php | 5 +- src/Model/HasAttributesTrait.php | 21 +++- src/Model/PolymorphableModelInterface.php | 45 ++++++++ tests/Stubs/PolymorphedReferencedUser.php | 13 +++ tests/Stubs/ReferencedUser.php | 24 ++++- tests/Unit/Model/HasAttributesTraitTest.php | 112 +++++++++++++++++++- 7 files changed, 214 insertions(+), 8 deletions(-) create mode 100644 src/Model/PolymorphableModelInterface.php create mode 100644 tests/Stubs/PolymorphedReferencedUser.php diff --git a/src/Model/AbstractModel.php b/src/Model/AbstractModel.php index 501c9b59..d934baa5 100644 --- a/src/Model/AbstractModel.php +++ b/src/Model/AbstractModel.php @@ -244,7 +244,7 @@ public function bsonSerialize() public function bsonUnserialize(array $data) { unset($data['__pclass']); - static::fill($data, $this); + static::fill($data, $this, true); $this->syncOriginalDocumentAttributes(); } diff --git a/src/Model/HasAttributesInterface.php b/src/Model/HasAttributesInterface.php index 9f6c8626..331dc01e 100644 --- a/src/Model/HasAttributesInterface.php +++ b/src/Model/HasAttributesInterface.php @@ -13,12 +13,15 @@ interface HasAttributesInterface { /** * Set the model attributes using an array. + * Notice: Even though the $object (if passed) hold the changes for simple cases, + * you should always prefer to relay on the returned object, as it might change + * when using polymorphable models. * * @param array $input the data that will be used to fill the attributes * @param HasAttributesInterface|null $object if provided, $input will be merged with object attributes * @param bool $force force fill (ignore fillable fields) * - * @return HasAttributesInterface + * @see PolymorphableModelInterface */ public static function fill(array $input, HasAttributesInterface $object = null, bool $force = false): HasAttributesInterface; diff --git a/src/Model/HasAttributesTrait.php b/src/Model/HasAttributesTrait.php index c88491f3..636d7c63 100644 --- a/src/Model/HasAttributesTrait.php +++ b/src/Model/HasAttributesTrait.php @@ -11,6 +11,8 @@ * will be set. * * It is supposed to be used on model classes in general + * + * @mixin HasAttributesInterface */ trait HasAttributesTrait { @@ -64,12 +66,27 @@ trait HasAttributesTrait /** * {@inheritdoc} */ - public static function fill(array $input, HasAttributesInterface $object = null, bool $force = false): HasAttributesInterface - { + public static function fill( + array $input, + HasAttributesInterface $object = null, + bool $force = false + ): HasAttributesInterface { if (!$object) { $object = new static(); } + if ($object instanceof PolymorphableModelInterface) { + $class = $object->polymorph($input); + if ($class !== get_class($object)) { + $originalAttributes = $object->getDocumentAttributes(); + $object = new $class(); + + foreach ($originalAttributes as $key => $value) { + $object->setDocumentAttribute($key, $value); + } + } + } + foreach ($input as $key => $value) { if ($force || ((!$object->fillable || in_array($key, $object->fillable)) && !in_array($key, $object->guarded))) { diff --git a/src/Model/PolymorphableModelInterface.php b/src/Model/PolymorphableModelInterface.php new file mode 100644 index 00000000..97289af2 --- /dev/null +++ b/src/Model/PolymorphableModelInterface.php @@ -0,0 +1,45 @@ +assertSame($expected, $model->getDocumentAttributes()); } + public function testFillShouldRetrievePolymorphedModel() + { + // Set + $input = [ + 'type' => 'polymorphed', + 'new_field' => 'hello', + ]; + // Actions + $result = ReferencedUser::fill($input); + + // Assertions + $this->assertInstanceOf(PolymorphedReferencedUser::class, $result); + $this->assertSame('polymorphed', $result->type); + $this->assertSame('hello', $result->new_field); + } + + public function testFillShouldRetrievePolymorphedModelEvenWithExistingModel() + { + // Set + $input = [ + 'type' => 'polymorphed', + 'new_field' => 'hello', + 'exclusive' => 'value', // should not be set + 'other_exclusive' => 'value from fill', // should not be set + ]; + $model = new ReferencedUser(); + $id = new ObjectId(); + $model->_id = $id; + $model->name = 'Albert'; + $model->other_exclusive = 'other value'; // should be inherited + // Actions + $result = ReferencedUser::fill($input, $model); + + // Assertions + $this->assertInstanceOf(PolymorphedReferencedUser::class, $result); + $this->assertSame('polymorphed', $result->type); + $this->assertSame('hello', $result->new_field); + $this->assertSame($id, $result->_id); + $this->assertSame('Albert', $result->name); + $this->assertNull($result->exclusive); + $this->assertSame('other value', $result->other_exclusive); + } + + public function testFillShouldHoldValuesOnModel() + { + // Set + $input = [ + 'type' => 'regular', + 'new_field' => 'hello', // should not be set + ]; + $model = new ReferencedUser(); + $id = new ObjectId(); + $model->_id = $id; + $model->name = 'Albert'; + // Actions + $result = ReferencedUser::fill($input, $model); + + // Assertions + $this->assertSame($model, $result); + $this->assertSame( + [ + '_id' => $id, + 'name' => 'Albert', + 'type' => 'regular', + ], + $model->getDocumentAttributes() + ); + } + + public function testFillShouldNotHoldValuesOnModelIfPolymorphed() + { + // Set + $input = [ + 'type' => 'polymorphed', + 'new_field' => 'hello', + ]; + $model = new ReferencedUser(); + $id = new ObjectId(); + $model->_id = $id; + $model->name = 'Albert'; + // Actions + $result = ReferencedUser::fill($input, $model); + + // Assertions + $this->assertNotSame($model, $result); + $this->assertSame( + [ + '_id' => $id, + 'name' => 'Albert', + 'type' => 'polymorphed', + 'new_field' => 'hello', + ], + $result->getDocumentAttributes() + ); + $this->assertSame( + [ + '_id' => $id, + 'name' => 'Albert', + ], + $model->getDocumentAttributes() + ); + } + public function testShouldForceFillAttributes() { // Set @@ -186,8 +292,9 @@ public function testShouldFallbackOriginalAttributesIfUnserializationFails() public function __construct() { - $this->attributes = [function () { - }, + $this->attributes = [ + function () { + }, ]; } }; @@ -216,7 +323,6 @@ public function testShouldCheckIfAttributeIsSet() $this->assertFalse(isset($model->ignored)); } - public function testShouldCheckIfMutatedAttributeIsSet() { // Set From 9c526bfa1ab6e5af052d5cf4bea668289a8aa6c9 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 28 Nov 2018 13:59:39 -0200 Subject: [PATCH 085/116] Add __pclass field to projection --- src/Query/Builder.php | 6 +++++- tests/Unit/Query/BuilderTest.php | 20 ++++++++++++++------ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/Query/Builder.php b/src/Query/Builder.php index 9f129f81..141f0154 100644 --- a/src/Query/Builder.php +++ b/src/Query/Builder.php @@ -370,7 +370,7 @@ protected function fireEvent(string $event, ModelInterface $model, bool $halt = * * @return array */ - protected function prepareProjection(array $fields) + protected function prepareProjection(array $fields): array { $projection = []; foreach ($fields as $key => $value) { @@ -410,6 +410,10 @@ protected function prepareProjection(array $fields) ); } + if ($projection) { + $projection['__pclass'] = true; + } + return $projection; } diff --git a/tests/Unit/Query/BuilderTest.php b/tests/Unit/Query/BuilderTest.php index 25d3c1fb..a8229faa 100644 --- a/tests/Unit/Query/BuilderTest.php +++ b/tests/Unit/Query/BuilderTest.php @@ -465,7 +465,7 @@ public function testShouldGetWithWhereQuery() $collection = m::mock(Collection::class); $query = 123; $preparedQuery = ['_id' => 123]; - $projection = ['project' => true, '_id' => false]; + $projection = ['project' => true, '_id' => false, '__pclass' => true]; $builder->shouldAllowMockingProtectedMethods(); @@ -659,7 +659,7 @@ public function testShouldGetFirstProjectingFields() $collection = m::mock(Collection::class); $query = 123; $preparedQuery = ['_id' => 123]; - $projection = ['project' => true, 'fields' => false]; + $projection = ['project' => true, 'fields' => false, '__pclass' => true]; // Expectations $builder->expects() @@ -873,19 +873,27 @@ public function getProjections(): array return [ 'Should return self array' => [ 'projection' => ['some' => true, 'fields' => false], - 'expected' => ['some' => true, 'fields' => false], + 'expected' => ['some' => true, 'fields' => false, '__pclass' => true], ], 'Should convert number' => [ 'projection' => ['some' => 1, 'fields' => -1], - 'expected' => ['some' => true, 'fields' => false], + 'expected' => ['some' => true, 'fields' => false, '__pclass' => true], ], 'Should add true in fields' => [ 'projection' => ['some', 'fields'], - 'expected' => ['some' => true, 'fields' => true], + 'expected' => ['some' => true, 'fields' => true, '__pclass' => true], ], 'Should add boolean values according to key value' => [ 'projection' => ['-some', 'fields'], - 'expected' => ['some' => false, 'fields' => true], + 'expected' => ['some' => false, 'fields' => true, '__pclass' => true], + ], + 'Should not exclude __pclass from projection' => [ + 'projection' => ['fields' => true, '__pclass' => false], + 'expected' => ['fields' => true, '__pclass' => true], + ], + 'Empty should not include __pclass' => [ + 'projection' => [], + 'expected' => [], ], ]; } From 16a64faf656b619aa540181b664034068ffa89d6 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 28 Nov 2018 15:36:49 -0200 Subject: [PATCH 086/116] Move document embedder to relations namespace --- src/Model/Relations/AbstractRelation.php | 1 - src/Model/{ => Relations}/DocumentEmbedder.php | 3 ++- tests/Unit/Model/{ => Relations}/DocumentEmbedderTest.php | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) rename src/Model/{ => Relations}/DocumentEmbedder.php (98%) rename tests/Unit/Model/{ => Relations}/DocumentEmbedderTest.php (98%) diff --git a/src/Model/Relations/AbstractRelation.php b/src/Model/Relations/AbstractRelation.php index 3a9ddeae..17ce2587 100644 --- a/src/Model/Relations/AbstractRelation.php +++ b/src/Model/Relations/AbstractRelation.php @@ -2,7 +2,6 @@ namespace Mongolid\Model\Relations; use Mongolid\Container\Ioc; -use Mongolid\Model\DocumentEmbedder; use Mongolid\Model\HasAttributesInterface; abstract class AbstractRelation implements RelationInterface diff --git a/src/Model/DocumentEmbedder.php b/src/Model/Relations/DocumentEmbedder.php similarity index 98% rename from src/Model/DocumentEmbedder.php rename to src/Model/Relations/DocumentEmbedder.php index 0b09b102..a6ee3124 100644 --- a/src/Model/DocumentEmbedder.php +++ b/src/Model/Relations/DocumentEmbedder.php @@ -1,7 +1,8 @@ Date: Wed, 28 Nov 2018 17:16:23 -0200 Subject: [PATCH 087/116] Remove DocumentEmbedder and simplify relations with only one model --- src/Model/Relations/AbstractRelation.php | 37 ++-- src/Model/Relations/DocumentEmbedder.php | 145 --------------- src/Model/Relations/EmbedsMany.php | 21 ++- src/Model/Relations/EmbedsOne.php | 19 +- src/Model/Relations/ReferencesMany.php | 41 ++-- src/Model/Relations/ReferencesOne.php | 34 ++-- tests/Integration/EmbedsOneRelationTest.php | 28 +-- .../Integration/ReferencesOneRelationTest.php | 30 +-- tests/Unit/Model/HasRelationsTraitTest.php | 147 +++++++++------ .../Model/Relations/DocumentEmbedderTest.php | 175 ------------------ 10 files changed, 215 insertions(+), 462 deletions(-) delete mode 100644 src/Model/Relations/DocumentEmbedder.php delete mode 100644 tests/Unit/Model/Relations/DocumentEmbedderTest.php diff --git a/src/Model/Relations/AbstractRelation.php b/src/Model/Relations/AbstractRelation.php index 17ce2587..a6379990 100644 --- a/src/Model/Relations/AbstractRelation.php +++ b/src/Model/Relations/AbstractRelation.php @@ -1,13 +1,13 @@ parent = $parent; $this->model = $model; $this->field = $field; - - $this->documentEmbedder = Ioc::make(DocumentEmbedder::class); } /** @@ -73,4 +71,21 @@ protected function pristine(): bool { return $this->pristine; } + + /** + * Gets the key of the given model. If there is no key, + * a new key will be generated and set on the model (while still returning it). + * + * @param ModelInterface $model the object that the key will be retrieved from + * + * @return ObjectId|mixed + */ + protected function getKey(ModelInterface $model) + { + if (!$model->{$this->key}) { + $model->{$this->key} = new ObjectId(); + } + + return $model->{$this->key}; + } } diff --git a/src/Model/Relations/DocumentEmbedder.php b/src/Model/Relations/DocumentEmbedder.php deleted file mode 100644 index a6ee3124..00000000 --- a/src/Model/Relations/DocumentEmbedder.php +++ /dev/null @@ -1,145 +0,0 @@ -setKey($key); - } - - public function setKey(string $key): void - { - $this->key = $key; - } - - /** - * Embeds the given $model into $field of $parent. This method will also - * consider the key of the $model in order to update it if it is already - * present in $field. - * - * @param ModelInterface $parent the object where the $model will be embedded - * @param string $field name of the field of the object where the document will be embedded - * @param ModelInterface|array $model model that will be embedded within $parent - */ - public function embed(ModelInterface $parent, string $field, &$model): bool - { - // In order to update the document if it exists inside the $parent - $this->unembed($parent, $field, $model); - - $fieldValue = $parent->$field; - $fieldValue[] = $model; - $parent->$field = array_values($fieldValue); - - return true; - } - - /** - * Removes the given $model from $field of $parent. This method will - * consider the key of the $model in order to remove it. - * - * @param ModelInterface $parent the object where the $model will be removed - * @param string $field name of the field of the object where the document is - * @param ModelInterface|array $model model that will be removed from $parent - */ - public function unembed(ModelInterface $parent, string $field, &$model): bool - { - $embeddedKey = $this->getKey($model); - - foreach ((array) $parent->$field as $arrayKey => $document) { - if ($embeddedKey == $this->getKey($document)) { - unset($parent->$field[$arrayKey]); - } - } - - $parent->$field = array_values((array) $parent->$field); - - return true; - } - - /** - * Attach a new key reference into $field of $parent. - * - * @param ModelInterface $parent the object where $model will be referenced - * @param string $field the field where the key reference of $model will be stored - * @param ModelInterface|array $model the object that is being attached - */ - public function attach(ModelInterface $parent, string $field, &$model): bool - { - $referencedKey = $this->getKey($model); - - foreach ((array) $parent->$field as $key) { - if ($key == $referencedKey) { - return true; - } - } - - $fieldValue = $parent->$field; - $fieldValue[] = $referencedKey; - $parent->$field = array_values($fieldValue); - - return true; - } - - /** - * Removes a key reference from $field of $parent. - * - * @param ModelInterface $parent the object where $model reference will be removed - * @param string $field the field where the key reference of $model is stored - * @param ModelInterface|array $model the object being detached or its key - */ - public function detach(ModelInterface $parent, string $field, &$model): bool - { - $referencedKey = $this->getKey($model); - - foreach ((array) $parent->$field as $arrayKey => $documentKey) { - if ($documentKey == $referencedKey) { - unset($parent->$field[$arrayKey]); - } - } - - $parent->$field = array_values((array) $parent->$field); - - return true; - } - - /** - * Gets the key of the given object or array. If there is no key in it a new - * key will be generated and set on the object (while still returning it). - * - * @param ModelInterface|array $object the object|array that the key will be retrieved from - * - * @return ObjectId|mixed - */ - protected function getKey(&$object) - { - if (is_array($object)) { - if (isset($object[$this->key]) && $object[$this->key]) { - return $object[$this->key]; - } - - return $object[$this->key] = new ObjectId(); - } - - if (is_object($object) && !$object instanceof ObjectId) { - if (isset($object->{$this->key}) && $object->{$this->key}) { - return $object->{$this->key}; - } - - return $object->{$this->key} = new ObjectId(); - } - - return $object; - } -} diff --git a/src/Model/Relations/EmbedsMany.php b/src/Model/Relations/EmbedsMany.php index 293df8b2..fdfe015a 100644 --- a/src/Model/Relations/EmbedsMany.php +++ b/src/Model/Relations/EmbedsMany.php @@ -12,7 +12,11 @@ class EmbedsMany extends AbstractRelation */ public function add(ModelInterface $model): void { - $this->documentEmbedder->embed($this->parent, $this->field, $model); + $this->remove($model); + + $fieldValue = $this->parent->{$this->field}; + $fieldValue[] = $model; + $this->parent->{$this->field} = array_values($fieldValue); $this->pristine = false; } @@ -47,7 +51,15 @@ public function replace(array $entities): void */ public function remove(ModelInterface $model): void { - $this->documentEmbedder->unembed($this->parent, $this->field, $model); + $embeddedKey = $this->getKey($model); + + foreach ((array) $this->parent->{$this->field} as $arrayKey => $document) { + if ($embeddedKey == $this->getKey($document)) { + unset($this->parent->{$this->field}[$arrayKey]); + } + } + + $this->parent->{$this->field} = array_values((array) $this->parent->{$this->field}); $this->pristine = false; } @@ -68,11 +80,6 @@ public function get() $items = [$items]; } - return $this->createCursor($items); - } - - protected function createCursor(array $items): EmbeddedCursor - { return new EmbeddedCursor($items); } } diff --git a/src/Model/Relations/EmbedsOne.php b/src/Model/Relations/EmbedsOne.php index 72ab5ae1..1a18d864 100644 --- a/src/Model/Relations/EmbedsOne.php +++ b/src/Model/Relations/EmbedsOne.php @@ -3,17 +3,18 @@ use Mongolid\Model\ModelInterface; -class EmbedsOne extends EmbedsMany +class EmbedsOne extends AbstractRelation { public function add(ModelInterface $model): void { - $this->removeAll(); - parent::add($model); + $this->parent->{$this->field} = $model; + $this->pristine = false; } - public function remove(ModelInterface $model = null): void + public function remove(): void { - $this->removeAll(); + unset($this->parent->{$this->field}); + $this->pristine = false; } /** @@ -21,12 +22,6 @@ public function remove(ModelInterface $model = null): void */ public function get() { - $items = $this->parent->{$this->field} ?? []; - - if (is_object($items)) { - return $items; - } - - return $this->createCursor($items)->first(); + return $this->parent->{$this->field}; } } diff --git a/src/Model/Relations/ReferencesMany.php b/src/Model/Relations/ReferencesMany.php index 0fa9551e..5a71d5a2 100644 --- a/src/Model/Relations/ReferencesMany.php +++ b/src/Model/Relations/ReferencesMany.php @@ -3,31 +3,20 @@ use MongoDB\BSON\ObjectId; use Mongolid\Container\Ioc; -use Mongolid\Model\HasAttributesInterface; use Mongolid\Model\ModelInterface; use Mongolid\Util\ObjectIdUtils; class ReferencesMany extends AbstractRelation { /** - * @var HasAttributesInterface + * @var ModelInterface */ protected $modelInstance; - /** - * @var string - */ - protected $key; - - public function __construct( - HasAttributesInterface $parent, - string $model, - string $field, - string $key - ) { + public function __construct(ModelInterface $parent, string $model, string $field, string $key) + { parent::__construct($parent, $model, $field); $this->key = $key; - $this->documentEmbedder->setKey($key); $this->modelInstance = Ioc::make($this->model); } @@ -37,7 +26,17 @@ public function __construct( */ public function attach(ModelInterface $model): void { - $this->documentEmbedder->attach($this->parent, $this->field, $model); + $referencedKey = $this->getKey($model); + $fieldValue = (array) $this->parent->{$this->field}; + + foreach ($fieldValue as $key) { + if ($key == $referencedKey) { + return; + } + } + + $fieldValue[] = $referencedKey; + $this->parent->{$this->field} = array_values($fieldValue); $this->pristine = false; } @@ -69,8 +68,16 @@ public function replace(array $entities): void */ public function detach(ModelInterface $model): void { - $this->documentEmbedder->detach($this->parent, $this->field, $model); - $this->pristine = false; + $referencedKey = $this->getKey($model); + + foreach ((array) $this->parent->{$this->field} as $arrayKey => $documentKey) { + if ($documentKey == $referencedKey) { + unset($this->parent->{$this->field}[$arrayKey]); + $this->parent->{$this->field} = array_values((array) $this->parent->{$this->field}); + $this->pristine = false; + return; + } + } } /** diff --git a/src/Model/Relations/ReferencesOne.php b/src/Model/Relations/ReferencesOne.php index f697ffbb..76e1b502 100644 --- a/src/Model/Relations/ReferencesOne.php +++ b/src/Model/Relations/ReferencesOne.php @@ -2,32 +2,44 @@ namespace Mongolid\Model\Relations; use MongoDB\BSON\ObjectId; +use Mongolid\Container\Ioc; use Mongolid\Model\ModelInterface; use Mongolid\Util\ObjectIdUtils; -class ReferencesOne extends ReferencesMany +class ReferencesOne extends AbstractRelation { + /** + * @var ModelInterface + */ + protected $modelInstance; + + public function __construct(ModelInterface $parent, string $model, string $field, string $key) + { + parent::__construct($parent, $model, $field); + $this->key = $key; + $this->modelInstance = Ioc::make($this->model); + } + public function attach(ModelInterface $model): void { - $this->detachAll(); - parent::attach($model); + $this->parent->{$this->field} = $this->getKey($model); + $this->pristine = false; } - public function detach(ModelInterface $model = null): void + public function detach(): void { - $this->detachAll(); + unset($this->parent->{$this->field}); + $this->pristine = false; } public function get() { - $referencedKey = $this->parent->{$this->field}; - - if (is_array($referencedKey) && isset($referencedKey[0])) { - $referencedKey = $referencedKey[0]; + if (!$referencedKey = $this->parent->{$this->field}) { + return null; } - if (ObjectIdUtils::isObjectId($referencedKey)) { - $referencedKey = new ObjectId((string) $referencedKey); + if (is_string($referencedKey) && ObjectIdUtils::isObjectId($referencedKey)) { + $referencedKey = new ObjectId($referencedKey); } return $this->modelInstance->first([$this->key => $referencedKey]); diff --git a/tests/Integration/EmbedsOneRelationTest.php b/tests/Integration/EmbedsOneRelationTest.php index 6815a2ba..fc2b150c 100644 --- a/tests/Integration/EmbedsOneRelationTest.php +++ b/tests/Integration/EmbedsOneRelationTest.php @@ -31,29 +31,29 @@ public function testShouldRetrieveParentOfUser() // remove all $john->parent()->add($bob); $this->assertParent($bob, $john); - $john->parent()->removeAll(); + $john->parent()->remove(); $this->assertNull($john->embedded_parent); $this->assertNull($john->parent); // remove $john->parent()->add($bob); $this->assertParent($bob, $john); - $john->parent()->remove($bob); + $john->parent()->remove(); $this->assertNull($john->embedded_parent); $this->assertNull($john->parent); // changing the field directly $john->parent()->add($bob); $this->assertParent($bob, $john); - $john->embedded_parent = [$chuck]; + $john->embedded_parent = $chuck; $this->assertParent($chuck, $john); - $john->parent()->removeAll(); + $john->parent()->remove(); // changing the field with fillable $john->parent()->add($bob); $this->assertParent($bob, $john); - $john = EmbeddedUser::fill(['embedded_parent' => [$chuck]], $john, true); + $john = EmbeddedUser::fill(['embedded_parent' => $chuck], $john, true); $this->assertParent($chuck, $john); } @@ -79,29 +79,29 @@ public function testShouldRetrieveSonOfUserUsingCustomKey() // remove all $john->son()->add($bob); $this->assertSon($bob, $john); - $john->son()->removeAll(); + $john->son()->remove(); $this->assertNull($john->arbitrary_field); $this->assertNull($john->son); // remove $john->son()->add($bob); $this->assertSon($bob, $john); - $john->son()->remove($bob); + $john->son()->remove(); $this->assertNull($john->arbitrary_field); $this->assertNull($john->son); // changing the field directly $john->son()->add($bob); $this->assertSon($bob, $john); - $john->arbitrary_field = [$chuck]; + $john->arbitrary_field = $chuck; $this->assertSon($chuck, $john); - $john->son()->removeAll(); + $john->son()->remove(); // changing the field with fillable $john->son()->add($bob); $this->assertSon($bob, $john); - $john = EmbeddedUser::fill(['arbitrary_field' => [$chuck]], $john, true); + $john = EmbeddedUser::fill(['arbitrary_field' => $chuck], $john, true); $this->assertSon($chuck, $john); } @@ -134,14 +134,14 @@ private function assertParent($expected, EmbeddedUser $model) $this->assertInstanceOf(EmbeddedUser::class, $parent); $this->assertInstanceOf(UTCDateTime::class, $parent->created_at); $this->assertEquals($expected, $parent); - $this->assertSame([$expected], $model->embedded_parent); // TODO store as single array + $this->assertSame($expected, $model->embedded_parent); // hit cache $parent = $model->parent; $this->assertInstanceOf(EmbeddedUser::class, $parent); $this->assertInstanceOf(UTCDateTime::class, $parent->created_at); $this->assertEquals($expected, $parent); - $this->assertSame([$expected], $model->embedded_parent); + $this->assertSame($expected, $model->embedded_parent); } private function assertSon($expected, EmbeddedUser $model) @@ -150,13 +150,13 @@ private function assertSon($expected, EmbeddedUser $model) $this->assertInstanceOf(EmbeddedUser::class, $son); $this->assertInstanceOf(UTCDateTime::class, $son->created_at); $this->assertEquals($expected, $son); - $this->assertSame([$expected], $model->arbitrary_field); // TODO store as single array + $this->assertSame($expected, $model->arbitrary_field); // hit cache $son = $model->son; $this->assertInstanceOf(EmbeddedUser::class, $son); $this->assertInstanceOf(UTCDateTime::class, $son->created_at); $this->assertEquals($expected, $son); - $this->assertSame([$expected], $model->arbitrary_field); + $this->assertSame($expected, $model->arbitrary_field); } } diff --git a/tests/Integration/ReferencesOneRelationTest.php b/tests/Integration/ReferencesOneRelationTest.php index ab0fbc8a..977a0817 100644 --- a/tests/Integration/ReferencesOneRelationTest.php +++ b/tests/Integration/ReferencesOneRelationTest.php @@ -29,29 +29,29 @@ public function testShouldRetrieveParentOfUser() // detach all $john->parent()->attach($bob); $this->assertParent($bob, $john); - $john->parent()->detachAll(); + $john->parent()->detach(); $this->assertNull($john->parent_id); $this->assertNull($john->parent); // detach $john->parent()->attach($bob); $this->assertParent($bob, $john); - $john->parent()->detach($bob); + $john->parent()->detach(); $this->assertNull($john->parent_id); $this->assertNull($john->parent); // changing the field directly $john->parent()->attach($bob); $this->assertParent($bob, $john); - $john->parent_id = [$chuck->_id]; + $john->parent_id = $chuck->_id; $this->assertParent($chuck, $john); - $john->parent()->detachAll(); + $john->parent()->detach(); // changing the field with fillable $john->parent()->attach($bob); $this->assertParent($bob, $john); - $john = ReferencedUser::fill(['parent_id' => [$chuck->_id]], $john, true); + $john = ReferencedUser::fill(['parent_id' => $chuck->_id], $john, true); $this->assertParent($chuck, $john); } @@ -74,32 +74,32 @@ public function testShouldRetrieveSonOfUserUsingCustomKey() $this->assertNull($john->arbitrary_field); $this->assertNull($john->son); - // detachAll + // detach $john->son()->attach($bob); $this->assertSon($bob, $john); - $john->son()->detachAll(); + $john->son()->detach(); $this->assertNull($john->arbitrary_field); $this->assertNull($john->son); // detach $john->son()->attach($bob); $this->assertSon($bob, $john); - $john->son()->detach($bob); + $john->son()->detach(); $this->assertNull($john->arbitrary_field); $this->assertNull($john->son); // changing the field directly $john->son()->attach($bob); $this->assertSon($bob, $john); - $john->arbitrary_field = [$chuck->code]; + $john->arbitrary_field = $chuck->code; $this->assertSon($chuck, $john); - $john->son()->detachAll(); + $john->son()->detach(); // changing the field with fillable $john->son()->attach($bob); $this->assertSon($bob, $john); - $john = ReferencedUser::fill(['arbitrary_field' => [$chuck->code]], $john, true); + $john = ReferencedUser::fill(['arbitrary_field' => $chuck->code], $john, true); $this->assertSon($chuck, $john); } @@ -134,13 +134,13 @@ private function assertParent($expected, ReferencedUser $model) $parent = $model->parent; $this->assertInstanceOf(ReferencedUser::class, $parent); $this->assertEquals($expected, $parent); - $this->assertEquals([$expected->_id], $model->parent_id); // TODO store as single code (not array) + $this->assertSame($expected->_id, $model->parent_id); // hit cache $parent = $model->parent; $this->assertInstanceOf(ReferencedUser::class, $parent); $this->assertEquals($expected, $parent); - $this->assertEquals([$expected->_id], $model->parent_id); + $this->assertSame($expected->_id, $model->parent_id); } private function assertSon($expected, ReferencedUser $model) @@ -148,12 +148,12 @@ private function assertSon($expected, ReferencedUser $model) $son = $model->son; $this->assertInstanceOf(ReferencedUser::class, $son); $this->assertEquals($expected, $son); - $this->assertSame([$expected->code], $model->arbitrary_field); // TODO store as single code (not array) + $this->assertSame($expected->code, $model->arbitrary_field); // hit cache $son = $model->son; $this->assertInstanceOf(ReferencedUser::class, $son); $this->assertEquals($expected, $son); - $this->assertSame([$expected->code], $model->arbitrary_field); + $this->assertSame($expected->code, $model->arbitrary_field); } } diff --git a/tests/Unit/Model/HasRelationsTraitTest.php b/tests/Unit/Model/HasRelationsTraitTest.php index 1c712ee3..1f718345 100644 --- a/tests/Unit/Model/HasRelationsTraitTest.php +++ b/tests/Unit/Model/HasRelationsTraitTest.php @@ -12,7 +12,7 @@ class HasRelationsTraitTest extends TestCase { /** - * @dataProvider referenceScenarios + * @dataProvider referencesOneScenarios */ public function testShouldReferenceOne($fieldValue, $expectedQuery) { @@ -21,7 +21,6 @@ public function testShouldReferenceOne($fieldValue, $expectedQuery) $model->parent_id = $fieldValue; $builder = $this->instance(Builder::class, m::mock(Builder::class)->makePartial()); - $expectedQuery = $expectedQuery['referencesOne']; $expected = new ReferencedUser(); // Expectations @@ -36,8 +35,28 @@ public function testShouldReferenceOne($fieldValue, $expectedQuery) $this->assertSame($expected, $result); } + public function testShouldNotPerformQueryForNullReference() + { + // Set + $model = new ReferencedUser(); + + $builder = $this->instance(Builder::class, m::mock(Builder::class)->makePartial()); + + // Expectations + $builder->expects() + ->first() + ->withAnyArgs() + ->never(); + + // Actions + $result = $model->parent; + + // Assertions + $this->assertNull($result); + } + /** - * @dataProvider referenceScenarios + * @dataProvider referencesManyScenarios */ public function testShouldReferenceMany($fieldValue, $expectedQuery) { @@ -46,7 +65,6 @@ public function testShouldReferenceMany($fieldValue, $expectedQuery) $model->siblings_ids = $fieldValue; $builder = $this->instance(Builder::class, m::mock(Builder::class)->makePartial()); - $expectedQuery = $expectedQuery['referencesMany']; $expected = new EmbeddedCursor([]); // Expectations @@ -61,36 +79,61 @@ public function testShouldReferenceMany($fieldValue, $expectedQuery) $this->assertSame($expected, $result); } - /** - * @dataProvider embedsScenarios - */ - public function testShouldEmbedsOne($fieldValue, $expectedItems) + public function testShouldEmbedOne() { // Set $model = new EmbeddedUser(); - $model->embedded_parent = $fieldValue; - $expectedItems = $expectedItems['embedsOne']; + $embeddedModel = new EmbeddedUser(); + $embeddedModel->_id = 12345; + $embeddedModel->name = 'John'; + $embeddedModel->syncOriginalDocumentAttributes(); + + $model->embedded_parent = $embeddedModel; // Actions $result = $model->parent; // Assertions $this->assertInstanceOf(EmbeddedUser::class, $result); - $this->assertSame($expectedItems, $result); + $this->assertSame($embeddedModel, $result); + } + + public function testEmbedOneShouldAllowOnlyOneEmbeddedModel() + { + // Set + $model = new EmbeddedUser(); + + $oldEmbeddedModel = new EmbeddedUser(); + $oldEmbeddedModel->_id = 12345; + $oldEmbeddedModel->name = 'John'; + $oldEmbeddedModel->syncOriginalDocumentAttributes(); + + $newEmbeddedModel = new EmbeddedUser(); + $newEmbeddedModel->_id = 54321; + $newEmbeddedModel->name = 'Bob'; + $newEmbeddedModel->syncOriginalDocumentAttributes(); + + $model->embedded_parent = $oldEmbeddedModel; + + // Actions + $model->parent()->add($newEmbeddedModel); + $result = $model->parent; + + // Assertions + $this->assertInstanceOf(EmbeddedUser::class, $result); + $this->assertSame($newEmbeddedModel, $result); } /** - * @dataProvider embedsScenarios + * @dataProvider embedsManyScenarios */ - public function testShouldEmbedsMany($fieldValue, $expectedItems) + public function testShouldEmbedMany($fieldValue, $expectedItems) { // Set $model = new EmbeddedUser(); $model->embedded_siblings = $fieldValue; - $expectedItems = $expectedItems['embedsMany']; - // Actions $result = $model->siblings; @@ -100,40 +143,46 @@ public function testShouldEmbedsMany($fieldValue, $expectedItems) $this->assertSame($expectedItems, $result->all()); } - public function referenceScenarios(): array + public function referencesOneScenarios(): array { return [ 'referenced by string id' => [ 'fieldValue' => 'abc123', - 'expectedQuery' => [ - 'referencesOne' => ['_id' => 'abc123'], - 'referencesMany' => ['_id' => ['$in' => ['abc123']]], - ], + 'expectedQuery' => ['_id' => 'abc123'], ], 'referenced by objectId represented as string' => [ 'fieldValue' => '577afb0b4d3cec136058fa82', - 'expectedQuery' => [ - 'referencesOne' => ['_id' => new ObjectId('577afb0b4d3cec136058fa82')], - 'referencesMany' => ['_id' => ['$in' => [new ObjectId('577afb0b4d3cec136058fa82')]]], - ], + 'expectedQuery' => ['_id' => new ObjectId('577afb0b4d3cec136058fa82')], ], 'referenced by an objectId itself' => [ 'fieldValue' => new ObjectId('577afb0b4d3cec136058fa82'), - 'expectedQuery' => [ - 'referencesOne' => ['_id' => new ObjectId('577afb0b4d3cec136058fa82')], - 'referencesMany' => ['_id' => ['$in' => [new ObjectId('577afb0b4d3cec136058fa82')]]], - ], + 'expectedQuery' => ['_id' => new ObjectId('577afb0b4d3cec136058fa82')], + ], + ]; + } + + public function referencesManyScenarios(): array + { + return [ + 'referenced by string id' => [ + 'fieldValue' => 'abc123', + 'expectedQuery' => ['_id' => ['$in' => ['abc123']]], + ], + 'referenced by objectId represented as string' => [ + 'fieldValue' => '577afb0b4d3cec136058fa82', + 'expectedQuery' => ['_id' => ['$in' => [new ObjectId('577afb0b4d3cec136058fa82')]]], + ], + 'referenced by an objectId itself' => [ + 'fieldValue' => new ObjectId('577afb0b4d3cec136058fa82'), + 'expectedQuery' => ['_id' => ['$in' => [new ObjectId('577afb0b4d3cec136058fa82')]]], ], 'series of objectIds' => [ 'fieldValue' => [new ObjectId('577afb0b4d3cec136058fa82'), new ObjectId('577afb7e4d3cec136258fa83')], 'expectedQuery' => [ - 'referencesOne' => ['_id' => new ObjectId('577afb0b4d3cec136058fa82')], - 'referencesMany' => [ - '_id' => [ - '$in' => [ - new ObjectId('577afb0b4d3cec136058fa82'), - new ObjectId('577afb7e4d3cec136258fa83'), - ], + '_id' => [ + '$in' => [ + new ObjectId('577afb0b4d3cec136058fa82'), + new ObjectId('577afb7e4d3cec136258fa83'), ], ], ], @@ -141,28 +190,22 @@ public function referenceScenarios(): array 'series of objectIds as strings' => [ 'fieldValue' => ['577afb0b4d3cec136058fa82', '577afb7e4d3cec136258fa83'], 'expectedQuery' => [ - 'referencesOne' => ['_id' => new ObjectId('577afb0b4d3cec136058fa82')], - 'referencesMany' => [ - '_id' => [ - '$in' => [ - new ObjectId('577afb0b4d3cec136058fa82'), - new ObjectId('577afb7e4d3cec136258fa83'), - ], + '_id' => [ + '$in' => [ + new ObjectId('577afb0b4d3cec136058fa82'), + new ObjectId('577afb7e4d3cec136258fa83'), ], ], ], ], 'Model referenced with null' => [ 'fieldValue' => null, - 'expectedQuery' => [ - 'referencesOne' => ['_id' => null], - 'referencesMany' => ['_id' => ['$in' => []]], - ], + 'expectedQuery' => ['_id' => ['$in' => []]], ], ]; } - public function embedsScenarios(): array + public function embedsManyScenarios(): array { $model1 = new EmbeddedUser(); $model1->_id = 12345; @@ -177,17 +220,11 @@ public function embedsScenarios(): array return [ 'A single embedded document' => [ 'fieldValue' => $model1, - 'expectedItems' => [ - 'embedsOne' => $model1, - 'embedsMany' => [$model1], - ], + 'expectedItems' => [$model1], ], 'Many embedded documents' => [ 'fieldValue' => [$model1, $model2], - 'expectedItems' => [ - 'embedsOne' => $model1, - 'embedsMany' => [$model1, $model2], - ], + 'expectedItems' => [$model1, $model2], ], ]; } diff --git a/tests/Unit/Model/Relations/DocumentEmbedderTest.php b/tests/Unit/Model/Relations/DocumentEmbedderTest.php deleted file mode 100644 index e661ade6..00000000 --- a/tests/Unit/Model/Relations/DocumentEmbedderTest.php +++ /dev/null @@ -1,175 +0,0 @@ -foo = $originalField; - - if (is_array($modelFields)) { - $model = new class extends AbstractModel - { - }; - $model = $model::fill($modelFields); - } else { - $model = $modelFields; - } - $embedder = new DocumentEmbedder(); - - // Actions - $embedder->$method($parent, 'foo', $model); - $result = $parent->foo; - - // Assertions - foreach ($expectation as $index => $expectedDoc) { - if ($expectedDoc instanceof ObjectId) { - $this->assertEquals($expectedDoc, $result[$index]); - - continue; - } - - $expectedDocArray = (array) $expectedDoc; - $resultDocArray = is_int($result[$index]) ? [$result[$index]] : $result[$index]->toArray(); - foreach ($expectedDocArray as $field => $value) { - if ($value instanceof Any) { - $this->assertTrue(isset($resultDocArray[$field])); - } else { - $this->assertEquals($value, $resultDocArray[$field]); - } - } - } - } - - public function getEmbedOptions(): array - { - return [ - 'embedding object without _id' => [ - 'originalField' => null, - 'model' => [ - 'name' => 'John Doe', - ], - 'method' => 'embed', - 'expectation' => [ - (object) ['_id' => m::any(), 'name' => 'John Doe'], - ], - ], - 'embedding object with _id' => [ - 'originalField' => null, - 'model' => [ - '_id' => (new ObjectId('507f191e810c19729de860ea')), - 'name' => 'John Doe', - ], - 'method' => 'embed', - 'expectation' => [ - (object) ['_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'John Doe'], - ], - ], - 'updating embedded object with _id' => [ - 'originalField' => [ - [ - '_id' => (new ObjectId('507f191e810c19729de860ea')), - 'name' => 'Bob', - ], - ], - 'model' => [ - '_id' => (new ObjectId('507f191e810c19729de860ea')), - 'name' => 'John Doe', - ], - 'method' => 'embed', - 'expectation' => [ - (object) ['_id' => (new ObjectId('507f191e810c19729de860ea')), 'name' => 'John Doe'], - ], - ], - 'attaching object with _id' => [ - 'originalField' => null, - 'model' => [ - '_id' => new ObjectId('507f191e810c19729de860ea'), - 'name' => 'John Doe', - ], - 'method' => 'attach', - 'expectation' => [ - new ObjectId('507f191e810c19729de860ea'), - ], - ], - 'attaching object with integer _id' => [ - 'originalField' => [6], - 'model' => [ - '_id' => 7, - 'name' => 'John Doe', - ], - 'method' => 'attach', - 'expectation' => [6, 7], - ], - 'attaching object with _id that is already attached' => [ - 'originalField' => [ - new ObjectId('507f191e810c19729de860ea'), - new ObjectId('507f191e810c19729de86011'), - ], - 'model' => [ - '_id' => new ObjectId('507f191e810c19729de860ea'), - 'name' => 'John Doe', - ], - 'method' => 'attach', - 'expectation' => [ - new ObjectId('507f191e810c19729de860ea'), - new ObjectId('507f191e810c19729de86011'), - ], - ], - 'attaching object without _id' => [ - 'originalField' => null, - 'model' => [ - 'name' => 'John Doe', - ], - 'method' => 'attach', - 'expectation' => [], - ], - 'detaching an object by its _id' => [ - 'originalField' => [ - (new ObjectId('507f191e810c19729de860ea')), - (new ObjectId('507f191e810c19729de86011')), - ], - 'model' => [ - '_id' => (new ObjectId('507f191e810c19729de860ea')), - 'name' => 'John Doe', - ], - 'method' => 'detach', - 'expectation' => [ - (new ObjectId('507f191e810c19729de86011')), - ], - ], - 'attaching an _id' => [ - 'originalField' => null, - 'model' => new ObjectId('507f191e810c19729de860ea'), - 'method' => 'attach', - 'expectation' => [ - (new ObjectId('507f191e810c19729de860ea')), - ], - ], - 'detaching an object using only the _id when it is an integer' => [ - 'originalField' => [ - 6, - 7, - ], - 'model' => 6, - 'method' => 'detach', - 'expectation' => [ - 7, - ], - ], - ]; - } -} From 4b4d0f89473759607eb8b6a588fb2899a0c46415 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 28 Nov 2018 17:35:14 -0200 Subject: [PATCH 088/116] Add Arrayable interface to Cursor interface --- src/Cursor/CursorInterface.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Cursor/CursorInterface.php b/src/Cursor/CursorInterface.php index 7ad34615..e0f43319 100644 --- a/src/Cursor/CursorInterface.php +++ b/src/Cursor/CursorInterface.php @@ -2,12 +2,13 @@ namespace Mongolid\Cursor; use Countable; +use Illuminate\Contracts\Support\Arrayable; use Iterator; /** * Common interface for all kinds of cursors. */ -interface CursorInterface extends Countable, Iterator +interface CursorInterface extends Countable, Iterator, Arrayable { /** * Limits the number of results returned. From 88127214629a269a2c0001f481f2a5b15dbf4ccb Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 28 Nov 2018 17:40:07 -0200 Subject: [PATCH 089/116] Simplify query builder test --- tests/Unit/Query/BuilderTest.php | 2 +- tests/Unit/TestCase.php | 38 -------------------------------- 2 files changed, 1 insertion(+), 39 deletions(-) diff --git a/tests/Unit/Query/BuilderTest.php b/tests/Unit/Query/BuilderTest.php index a8229faa..4dc4eb85 100644 --- a/tests/Unit/Query/BuilderTest.php +++ b/tests/Unit/Query/BuilderTest.php @@ -721,7 +721,7 @@ public function testShouldPrepareQueryValue($value, $expectation) $result = $this->callProtected($builder, 'prepareValueQuery', [$value]); // Assertions - $this->assertMongoQueryEquals($expectation, $result); + $this->assertEquals($expectation, $result, 'Queries are not equals'); } /** diff --git a/tests/Unit/TestCase.php b/tests/Unit/TestCase.php index f3a3058e..18bc6942 100644 --- a/tests/Unit/TestCase.php +++ b/tests/Unit/TestCase.php @@ -22,44 +22,6 @@ protected function tearDown() parent::tearDown(); } - /** - * Assert if two queries are equals. It will compare ObjectIds within any - * level of the query and make sure that they are the same. - * - * @param mixed $expectedQuery correct query - * @param mixed $query query being evaluated - */ - protected function assertMongoQueryEquals($expectedQuery, $query) - { - $this->assertEquals($expectedQuery, $query, 'Queries are not equals'); - - if (!is_array($expectedQuery)) { - return; - } - - foreach ($expectedQuery as $key => $value) { - if (is_object($value)) { - $this->assertInstanceOf( - get_class($value), - $query[$key], - 'Type of an object within the query is not equals' - ); - - if (method_exists($value, '__toString')) { - $this->assertSame( - (string) $expectedQuery[$key], - (string) $query[$key], - 'Object within the query is not equals' - ); - } - } - - if (is_array($value)) { - $this->assertMongoQueryEquals($value, $query[$key]); - } - } - } - /** * Actually runs a protected method of the given object. * From 6ff6c7eab60ebdceeef1a161b40bc5a79339a768 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 28 Nov 2018 17:45:34 -0200 Subject: [PATCH 090/116] Add test to ensure that cursor preferences are not overriding each other --- tests/Unit/Cursor/CursorTest.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/Unit/Cursor/CursorTest.php b/tests/Unit/Cursor/CursorTest.php index 60418b47..8efa0b68 100644 --- a/tests/Unit/Cursor/CursorTest.php +++ b/tests/Unit/Cursor/CursorTest.php @@ -98,6 +98,25 @@ public function testShouldSetReadPreferenceParameterAccordingly() $this->assertSame($mode, $result); } + public function testShouldBeAbleToSetReadPreferenceAndCursorTimeoutTogether() + { + // Set + $cursor = $this->getCursor(); + $mode = ReadPreference::RP_SECONDARY; + + // Actions + $cursor->setReadPreference($mode); + $cursor->disableTimeout(); + $readPreferenceParameter = $this->getProtected($cursor, 'params')[1]['readPreference']; + $result = $readPreferenceParameter->getMode(); + $timeoutResult = $this->getProtected($cursor, 'params')[1]['noCursorTimeout']; + + // Assertions + $this->assertInstanceOf(ReadPreference::class, $readPreferenceParameter); + $this->assertSame($mode, $result); + $this->assertTrue($timeoutResult); + } + public function testShouldCountDocuments() { // Set From 5b1e07b8e77cc259a0cf884cb945740bc22a658b Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 28 Nov 2018 17:49:58 -0200 Subject: [PATCH 091/116] Fix coverage reporting --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 330b22ba..ebf0e891 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,4 +23,4 @@ script: - vendor/bin/phpunit -c phpunit.xml.dist after_script: - - if [[ $(phpenv version-name) = "7.2" ]]; then php vendor/bin/coveralls -v; fi + - if [[ $(phpenv version-name) = "7.2" ]]; then php vendor/bin/php-coveralls -v; fi From b4bf01365e62117a7d96d9da06e586f74cf2d86f Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 28 Nov 2018 18:10:47 -0200 Subject: [PATCH 092/116] Renaming rawCollection to client --- src/Connection/Connection.php | 47 +++++++++++------------- src/Connection/Manager.php | 9 ++--- src/Cursor/Cursor.php | 2 +- src/Query/Builder.php | 2 +- src/Query/BulkWrite.php | 2 +- src/Util/SequenceService.php | 2 +- tests/Stubs/EmbeddedUser.php | 2 +- tests/Stubs/ReferencedUser.php | 2 +- tests/Unit/Connection/ConnectionTest.php | 18 ++++----- tests/Unit/Connection/ManagerTest.php | 8 ++-- tests/Unit/Cursor/CursorTest.php | 18 +++++++-- tests/Unit/Query/BuilderTest.php | 24 ++++++++---- tests/Unit/Query/BulkWriteTest.php | 4 +- tests/Unit/Util/SequenceServiceTest.php | 22 ++++++++--- tests/Util/DropDatabaseTrait.php | 2 +- 15 files changed, 96 insertions(+), 68 deletions(-) diff --git a/src/Connection/Connection.php b/src/Connection/Connection.php index ff129c31..f0b4bcd1 100644 --- a/src/Connection/Connection.php +++ b/src/Connection/Connection.php @@ -2,7 +2,6 @@ namespace Mongolid\Connection; use MongoDB\Client; -use MongoDB\Driver\Manager; /** * Represents a single connection with the database. @@ -10,18 +9,18 @@ class Connection { /** - * The raw MongoDB\Client object that represents this connection. + * The default database where mongolid will store the documents. * - * @var Client + * @var string */ - protected $rawConnection; + public $defaultDatabase = 'mongolid'; /** - * The default database where mongolid will store the documents. + * MongoDB Client object that represents this connection. * - * @var string + * @var Client */ - public $defaultDatabase = 'mongolid'; + protected $client; /** * Constructs a new Mongolid connection. It uses the same constructor @@ -43,40 +42,38 @@ public function __construct( $this->findDefaultDatabase($server); - $this->rawConnection = new Client($server, $options, $driverOptions); + $this->client = new Client($server, $options, $driverOptions); } /** - * Find and stores the default database in the connection string. - * - * @param string $connectionString mongoDB connection string + * Getter for Client instance. */ - protected function findDefaultDatabase(string $connectionString) + public function getClient(): Client { - preg_match('/\S+\/(\w*)/', $connectionString, $matches); - - if ($matches[1] ?? null) { - $this->defaultDatabase = $matches[1]; - } + return $this->client; } /** - * Getter for Client instance. + * Getter for Manager instance. * - * @return Client + * @return \MongoDB\Driver\Manager */ - public function getRawConnection() + public function getManager() { - return $this->rawConnection; + return $this->getClient()->getManager(); } /** - * Getter for Manager instance. + * Find and stores the default database in the connection string. * - * @return Manager + * @param string $connectionString mongoDB connection string */ - public function getRawManager() + protected function findDefaultDatabase(string $connectionString) { - return $this->getRawConnection()->getManager(); + preg_match('/\S+\/(\w*)/', $connectionString, $matches); + + if ($matches[1] ?? null) { + $this->defaultDatabase = $matches[1]; + } } } diff --git a/src/Connection/Manager.php b/src/Connection/Manager.php index 6c8fda94..dad8c342 100644 --- a/src/Connection/Manager.php +++ b/src/Connection/Manager.php @@ -2,6 +2,7 @@ namespace Mongolid\Connection; use Illuminate\Container\Container; +use MongoDB\Client; use Mongolid\Container\Ioc; use Mongolid\Event\EventTriggerInterface; use Mongolid\Event\EventTriggerService; @@ -58,15 +59,13 @@ public function setConnection(Connection $connection): bool } /** - * Get the raw MongoDB connection. - * - * @return \MongoDB\Client + * Get MongoDB client. */ - public function getConnection() + public function getClient(): Client { $this->init(); - return $this->connection->getRawConnection(); + return $this->connection->getClient(); } /** diff --git a/src/Cursor/Cursor.php b/src/Cursor/Cursor.php index b9df33be..b49826b1 100644 --- a/src/Cursor/Cursor.php +++ b/src/Cursor/Cursor.php @@ -287,7 +287,7 @@ public function unserialize($serialized) $connection = Ioc::make(Connection::class); $db = $connection->defaultDatabase; - $collectionObject = $connection->getRawConnection()->$db->{$attributes['collection']}; + $collectionObject = $connection->getClient()->$db->{$attributes['collection']}; foreach ($attributes as $key => $value) { $this->$key = $value; diff --git a/src/Query/Builder.php b/src/Query/Builder.php index 141f0154..166501bb 100644 --- a/src/Query/Builder.php +++ b/src/Query/Builder.php @@ -270,7 +270,7 @@ protected function getCollection(ModelInterface $model): Collection $database = $connection->defaultDatabase; $collection = $model->getCollectionName(); - return $connection->getRawConnection()->$database->$collection; + return $connection->getClient()->$database->$collection; } /** diff --git a/src/Query/BulkWrite.php b/src/Query/BulkWrite.php index 50042849..bf69a117 100644 --- a/src/Query/BulkWrite.php +++ b/src/Query/BulkWrite.php @@ -100,7 +100,7 @@ public function updateOne( public function execute(int $writeConcern = 1) { $connection = Ioc::make(Connection::class); - $manager = $connection->getRawManager(); + $manager = $connection->getManager(); $namespace = $connection->defaultDatabase.'.'.$this->model->getCollectionName(); diff --git a/src/Util/SequenceService.php b/src/Util/SequenceService.php index be9cec0e..1d733268 100644 --- a/src/Util/SequenceService.php +++ b/src/Util/SequenceService.php @@ -58,7 +58,7 @@ protected function rawCollection(): Collection { $database = $this->connection->defaultDatabase; - return $this->connection->getRawConnection() + return $this->connection->getClient() ->$database ->{$this->collection}; } diff --git a/tests/Stubs/EmbeddedUser.php b/tests/Stubs/EmbeddedUser.php index 0fc9a2d5..25c1686d 100644 --- a/tests/Stubs/EmbeddedUser.php +++ b/tests/Stubs/EmbeddedUser.php @@ -21,7 +21,7 @@ class EmbeddedUser extends AbstractModel public function collection(): Collection { $connection = Ioc::make(Connection::class); - $client = $connection->getRawConnection(); + $client = $connection->getClient(); return $client->{$connection->defaultDatabase}->{$this->collection}; } diff --git a/tests/Stubs/ReferencedUser.php b/tests/Stubs/ReferencedUser.php index 971afd15..79e5825a 100644 --- a/tests/Stubs/ReferencedUser.php +++ b/tests/Stubs/ReferencedUser.php @@ -31,7 +31,7 @@ class ReferencedUser extends AbstractModel implements PolymorphableModelInterfac public function collection(): Collection { $connection = Ioc::make(Connection::class); - $client = $connection->getRawConnection(); + $client = $connection->getClient(); return $client->{$connection->defaultDatabase}->{$this->collection}; } diff --git a/tests/Unit/Connection/ConnectionTest.php b/tests/Unit/Connection/ConnectionTest.php index d9bd77e0..4258ecdd 100644 --- a/tests/Unit/Connection/ConnectionTest.php +++ b/tests/Unit/Connection/ConnectionTest.php @@ -18,7 +18,7 @@ public function testShouldConstructANewConnection() $connection = new Connection($server, $options, $driverOptions); // Assertions - $this->assertAttributeInstanceOf(Client::class, 'rawConnection', $connection); + $this->assertAttributeInstanceOf(Client::class, 'client', $connection); $this->assertAttributeSame('my_db', 'defaultDatabase', $connection); } @@ -33,11 +33,11 @@ public function testShouldDetermineDatabaseFromACluster() $connection = new Connection($server, $options, $driverOptions); // Assertions - $this->assertAttributeInstanceOf(Client::class, 'rawConnection', $connection); + $this->assertAttributeInstanceOf(Client::class, 'client', $connection); $this->assertAttributeSame('my_db', 'defaultDatabase', $connection); } - public function testShouldGetRawConnection() + public function testShouldGetConnection() { // Set $server = 'mongodb://my-server/my_db'; @@ -53,14 +53,14 @@ public function testShouldGetRawConnection() // Actions $connection = new Connection($server, $options, $driverOptions); - $rawConnection = $connection->getRawConnection(); + $client = $connection->getClient(); // Assertions - $this->assertAttributeSame($expectedParameters['uri'], 'uri', $rawConnection); - $this->assertAttributeSame($expectedParameters['typeMap'], 'typeMap', $rawConnection); + $this->assertAttributeSame($expectedParameters['uri'], 'uri', $client); + $this->assertAttributeSame($expectedParameters['typeMap'], 'typeMap', $client); } - public function testShouldGetRawManager() + public function testShouldGetManager() { // Set $server = 'mongodb://my-server/my_db'; @@ -69,9 +69,9 @@ public function testShouldGetRawManager() // Actions $connection = new Connection($server, $options, $driverOptions); - $rawManager = $connection->getRawManager(); + $manager = $connection->getManager(); // Assertions - $this->assertInstanceOf(Manager::class, $rawManager); + $this->assertInstanceOf(Manager::class, $manager); } } diff --git a/tests/Unit/Connection/ManagerTest.php b/tests/Unit/Connection/ManagerTest.php index 7e4af992..8ed64b63 100644 --- a/tests/Unit/Connection/ManagerTest.php +++ b/tests/Unit/Connection/ManagerTest.php @@ -21,18 +21,18 @@ public function testShouldAddAndGetConnection() // Set $manager = new Manager(); $connection = m::mock(Connection::class); - $rawConnection = m::mock(Client::class); + $client = m::mock(Client::class); // Expectations $connection->expects() - ->getRawConnection() - ->andReturn($rawConnection); + ->getClient() + ->andReturn($client); // Actions $manager->setConnection($connection); // Assertions - $this->assertSame($rawConnection, $manager->getConnection()); + $this->assertSame($client, $manager->getClient()); } public function testShouldSetEventTrigger() diff --git a/tests/Unit/Cursor/CursorTest.php b/tests/Unit/Cursor/CursorTest.php index 8efa0b68..1ce36b13 100644 --- a/tests/Unit/Cursor/CursorTest.php +++ b/tests/Unit/Cursor/CursorTest.php @@ -6,7 +6,9 @@ use Exception; use Iterator; use Mockery as m; +use MongoDB\Client; use MongoDB\Collection; +use MongoDB\Database; use MongoDB\Driver\Exception\LogicException; use MongoDB\Driver\ReadPreference; use MongoDB\Model\CachingIterator; @@ -447,14 +449,22 @@ public function testShouldSerializeAnActiveCursor() $this->setProtected($cursor, 'collection', $driverCollection); + $client = m::mock(Client::class); + $database = m::mock(Database::class); $connection->defaultDatabase = 'db'; - $connection->db = $connection; - $connection->my_collection = $driverCollection; // Return the same driver Collection // Expectations $connection->expects() - ->getRawConnection() - ->andReturn($connection); + ->getClient() + ->andReturn($client); + + $client->expects() + ->selectDatabase('db') + ->andReturn($database); + + $database->expects() + ->selectCollection('my_collection') + ->andReturn($driverCollection); // Actions $result = unserialize(serialize($cursor)); diff --git a/tests/Unit/Query/BuilderTest.php b/tests/Unit/Query/BuilderTest.php index 4dc4eb85..194b1281 100644 --- a/tests/Unit/Query/BuilderTest.php +++ b/tests/Unit/Query/BuilderTest.php @@ -193,7 +193,7 @@ public function testShouldUpdate($model, $writeConcern, $shouldFireEventAfter, $ // Expectations $connection->expects() - ->getRawConnection() + ->getClient() ->andReturn($client); $client->expects() @@ -280,7 +280,7 @@ public function testShouldUpdateUnsettingFields() // Expectations $connection->expects() - ->getRawConnection() + ->getClient() ->andReturn($client); $client->expects() @@ -681,21 +681,31 @@ public function testShouldGetFirstProjectingFields() $this->assertNull($result); } - public function testShouldGetRawCollection() + public function testShouldGetClient() { // Set $connection = m::mock(Connection::class); + $connection->defaultDatabase = 'grimory'; + $builder = new Builder($connection); $collection = m::mock(Collection::class); $model = m::mock(ModelInterface::class); - $connection->defaultDatabase = 'grimory'; - $connection->grimory = (object) ['models' => $collection]; + $client = m::mock(Client::class); + $database = m::mock(Database::class); // Expectations $connection->expects() - ->getRawConnection() - ->andReturn($connection); + ->getClient() + ->andReturn($client); + + $client->expects() + ->selectDatabase('grimory') + ->andReturn($database); + + $database->expects() + ->selectCollection('models') + ->andReturn($collection); $model->expects() ->getCollectionName() diff --git a/tests/Unit/Query/BulkWriteTest.php b/tests/Unit/Query/BulkWriteTest.php index c3317c9b..c8eecd43 100644 --- a/tests/Unit/Query/BulkWriteTest.php +++ b/tests/Unit/Query/BulkWriteTest.php @@ -131,7 +131,7 @@ public function testShouldExecuteBulkWrite() $model = m::mock(ModelInterface::class); $mongoBulkWrite = m::mock(new MongoBulkWrite()); $connection = $this->instance(Connection::class, m::mock(Connection::class)); - $manager = m::mock(new Manager()); + $manager = m::mock(new Manager('mongodb://localhost')); $connection->defaultDatabase = 'foo'; $namespace = 'foo.bar'; @@ -140,7 +140,7 @@ public function testShouldExecuteBulkWrite() // Expectations $connection->expects() - ->getRawManager() + ->getManager() ->andReturn($manager); $model->expects() diff --git a/tests/Unit/Util/SequenceServiceTest.php b/tests/Unit/Util/SequenceServiceTest.php index 538bd1c3..59af8c84 100644 --- a/tests/Unit/Util/SequenceServiceTest.php +++ b/tests/Unit/Util/SequenceServiceTest.php @@ -2,7 +2,9 @@ namespace Mongolid\Util; use Mockery as m; +use MongoDB\Client; use MongoDB\Collection; +use MongoDB\Database; use Mongolid\Connection\Connection; use Mongolid\TestCase; @@ -40,20 +42,30 @@ public function testShouldGetNextValue($sequenceName, $currentValue, $expectatio $this->assertSame($expectation, $result); } - public function testShouldGetRawCollection() + public function testShouldGetClient() { // Set $connection = m::mock(Connection::class); + $connection->defaultDatabase = 'grimory'; + $sequenceService = new SequenceService($connection, 'foobar'); $collection = m::mock(Collection::class); - $connection->defaultDatabase = 'grimory'; - $connection->grimory = (object) ['foobar' => $collection]; + $client = m::mock(Client::class); + $database = m::mock(Database::class); // Expectations $connection->expects() - ->getRawConnection() - ->andReturnSelf($connection); + ->getClient() + ->andReturn($client); + + $client->expects() + ->selectDatabase('grimory') + ->andReturn($database); + + $database->expects() + ->selectCollection('foobar') + ->andReturn($collection); // Actions $result = $this->callProtected($sequenceService, 'rawCollection'); diff --git a/tests/Util/DropDatabaseTrait.php b/tests/Util/DropDatabaseTrait.php index 854f0f62..3520f0a4 100644 --- a/tests/Util/DropDatabaseTrait.php +++ b/tests/Util/DropDatabaseTrait.php @@ -10,7 +10,7 @@ public function dropDatabase() { $connection = Ioc::make(Connection::class); - $connection->getRawConnection() + $connection->getClient() ->dropDatabase($connection->defaultDatabase); } } From 0761aba46c0e51ff148318448d854be209e9fa25 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 28 Nov 2018 18:21:24 -0200 Subject: [PATCH 093/116] Add integration test for BulkWrite --- tests/Integration/BulkWriteTest.php | 89 +++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 tests/Integration/BulkWriteTest.php diff --git a/tests/Integration/BulkWriteTest.php b/tests/Integration/BulkWriteTest.php new file mode 100644 index 00000000..0b4dbb89 --- /dev/null +++ b/tests/Integration/BulkWriteTest.php @@ -0,0 +1,89 @@ +createUser('Bob'); + $john = $this->createUser('John'); + $mary = $this->createUser('Mary'); + + $bulkWrite = new BulkWrite(new ReferencedUser()); + + $bulkWrite->updateOne( + ['_id' => $bob->_id], + ['name' => 'Bulk Updated Bob!'] + ); + $bulkWrite->updateOne( + ['_id' => $john->_id], + ['name' => 'Bulk Updated John!'] + ); + $bulkWrite->updateOne( + ['_id' => $mary->_id], + ['name' => 'Bulk Updated Mary!'] + ); + $bulkWrite->updateOne( + ['_id' => $bob->_id], + ['delete_this' => ''], + [], + '$unset' + ); + $bulkWrite->updateOne( + ['_id' => $john->_id], + ['delete_this' => ''], + [], + '$unset' + ); + $bulkWrite->updateOne( + ['_id' => $mary->_id], + ['delete_this' => ''], + [], + '$unset' + ); + + // Before running + $this->assertSame('Bob', $bob->name); + $this->assertSame('John', $john->name); + $this->assertSame('Mary', $mary->name); + + $this->assertSame('xxxxx', $bob->delete_this); + $this->assertSame('xxxxx', $john->delete_this); + $this->assertSame('xxxxx', $mary->delete_this); + + // Runs it + $result = $bulkWrite->execute(); + + $this->assertInstanceOf(WriteResult::class, $result); + + // Refresh models + $bob = $bob->first($bob->_id); + $john = $john->first($john->_id); + $mary = $mary->first($mary->_id); + + // After running + $this->assertSame('Bulk Updated Bob!', $bob->name); + $this->assertSame('Bulk Updated John!', $john->name); + $this->assertSame('Bulk Updated Mary!', $mary->name); + + $this->assertNull($bob->delete_this); + $this->assertNull($john->delete_this); + $this->assertNull($mary->delete_this); + } + + private function createUser(string $name): ReferencedUser + { + $user = new ReferencedUser(); + $user->_id = new ObjectId(); + $user->name = $name; + $user->delete_this = 'xxxxx'; + $this->assertTrue($user->save()); + + return $user; + } +} From 2b3abf720a4c561a31198966f2c8471858b9d163 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 28 Nov 2018 18:36:04 -0200 Subject: [PATCH 094/116] Use bulkwrite method from collection to simplify things a bit --- src/Query/BulkWrite.php | 70 ++++-------- tests/Integration/BulkWriteTest.php | 6 +- tests/Unit/Query/BulkWriteTest.php | 161 ---------------------------- 3 files changed, 25 insertions(+), 212 deletions(-) delete mode 100644 tests/Unit/Query/BulkWriteTest.php diff --git a/src/Query/BulkWrite.php b/src/Query/BulkWrite.php index bf69a117..c23e2c55 100644 --- a/src/Query/BulkWrite.php +++ b/src/Query/BulkWrite.php @@ -2,7 +2,7 @@ namespace Mongolid\Query; use MongoDB\BSON\ObjectId; -use MongoDB\Driver\BulkWrite as MongoBulkWrite; +use MongoDB\BulkWriteResult; use MongoDB\Driver\WriteConcern; use Mongolid\Connection\Connection; use Mongolid\Container\Ioc; @@ -12,55 +12,30 @@ * This class is meant to provide a better API for handling * with bulk operations. * - * It's an incomplete and highly opinionated abstraction - * but yet flexible, since you are able to access the - * driver's API. + * It's an incomplete and highly opinionated abstraction. */ class BulkWrite { - /** - * @var array - */ - protected $options; - - /** - * @var MongoBulkWrite - */ - protected $bulkWrite; - /** * @var ModelInterface */ private $model; - public function __construct(ModelInterface $model) - { - $this->setBulkWrite(new MongoBulkWrite(['ordered' => false])); - $this->model = $model; - } - /** - * Get the BulkWrite object to perform other operations - * not covered by this class. + * Hold bulk write operations to run. * - * @return MongoBulkWrite + * @var array */ - public function getBulkWrite() + private $operations = []; + + public function __construct(ModelInterface $model) { - return $this->bulkWrite; + $this->model = $model; } - /** - * Set BulkWrite object that will receive all operations - * and later be executed. - * - * @return $this - */ - public function setBulkWrite(MongoBulkWrite $bulkWrite) + public function isEmpty() { - $this->bulkWrite = $bulkWrite; - - return $this; + return count($this->operations); } /** @@ -79,14 +54,12 @@ public function updateOne( array $dataToSet, array $options = ['upsert' => true], string $operator = '$set' - ) { + ): void { $filter = is_array($filter) ? $filter : ['_id' => $filter]; - return $this->getBulkWrite()->update( - $filter, - [$operator => $dataToSet], - $options - ); + $update = [$operator => $dataToSet]; + + $this->operations[] = ['updateOne' => [$filter, $update, $options]]; } /** @@ -94,19 +67,18 @@ public function updateOne( * The collection is inferred from model's collection name. * * @throws \Mongolid\Model\Exception\NoCollectionNameException - * - * @return \MongoDB\Driver\WriteResult */ - public function execute(int $writeConcern = 1) + public function execute(int $writeConcern = 1): BulkWriteResult { $connection = Ioc::make(Connection::class); - $manager = $connection->getManager(); - $namespace = $connection->defaultDatabase.'.'.$this->model->getCollectionName(); + $database = $connection->defaultDatabase; + $collectionName = $this->model->getCollectionName(); + + $collection = $connection->getClient()->$database->$collectionName; - return $manager->executeBulkWrite( - $namespace, - $this->getBulkWrite(), + return $collection->bulkWrite( + $this->operations, ['writeConcern' => new WriteConcern($writeConcern)] ); } diff --git a/tests/Integration/BulkWriteTest.php b/tests/Integration/BulkWriteTest.php index 0b4dbb89..56fc543f 100644 --- a/tests/Integration/BulkWriteTest.php +++ b/tests/Integration/BulkWriteTest.php @@ -2,7 +2,7 @@ namespace Mongolid\Tests\Integration; use MongoDB\BSON\ObjectId; -use MongoDB\Driver\WriteResult; +use MongoDB\BulkWriteResult; use Mongolid\Query\BulkWrite; use Mongolid\Tests\Stubs\ReferencedUser; @@ -59,7 +59,9 @@ public function testShouldRunMultipleUpdateOperations() // Runs it $result = $bulkWrite->execute(); - $this->assertInstanceOf(WriteResult::class, $result); + $this->assertInstanceOf(BulkWriteResult::class, $result); + $this->assertTrue($result->isAcknowledged()); + $this->assertSame(6, $result->getModifiedCount()); // Refresh models $bob = $bob->first($bob->_id); diff --git a/tests/Unit/Query/BulkWriteTest.php b/tests/Unit/Query/BulkWriteTest.php deleted file mode 100644 index c8eecd43..00000000 --- a/tests/Unit/Query/BulkWriteTest.php +++ /dev/null @@ -1,161 +0,0 @@ -assertInstanceOf(BulkWrite::class, $bulkWrite); - } - - public function testShouldSetAndGetMongoBulkWrite() - { - // Set - $model = m::mock(ModelInterface::class); - $mongoBulkWrite = new MongoBulkWrite(); - - // Actions - $bulkWrite = new BulkWrite($model); - $bulkWrite->setBulkWrite($mongoBulkWrite); - - // Assertions - $this->assertSame($mongoBulkWrite, $bulkWrite->getBulkWrite()); - } - - public function testShouldAddUpdateOneOperationToBulkWrite() - { - // Set - $model = m::mock(ModelInterface::class); - $mongoBulkWrite = m::mock(new MongoBulkWrite()); - - $id = '123'; - $data = ['name' => 'John']; - - // Expectations - $mongoBulkWrite->expects() - ->update(['_id' => $id], ['$set' => $data], ['upsert' => true]); - - $bulkWrite = m::mock(BulkWrite::class.'[getBulkWrite]', [$model]); - - $bulkWrite->expects() - ->getBulkWrite() - ->andReturn($mongoBulkWrite); - - // Actions - $bulkWrite->updateOne($id, $data); - } - - public function testShouldUpdateOneWithUnsetOperationToBulkWrite() - { - // Set - $model = m::mock(ModelInterface::class); - $mongoBulkWrite = m::mock(new MongoBulkWrite()); - - $id = '123'; - $data = ['name' => 'John']; - - $bulkWrite = m::mock(BulkWrite::class.'[getBulkWrite]', [$model]); - - // Expectations - $mongoBulkWrite->expects() - ->update( - m::on( - function ($actual) { - $this->assertSame(['_id' => '123'], $actual); - - return true; - } - ), - ['$unset' => $data], - ['upsert' => true] - ); - - $bulkWrite->expects() - ->getBulkWrite() - ->andReturn($mongoBulkWrite); - - // Actions - $bulkWrite->updateOne($id, $data, ['upsert' => true], '$unset'); - } - - public function testShouldUpdateOneWithQueryOnFilterToBulkWrite() - { - // Set - $model = m::mock(ModelInterface::class); - $mongoBulkWrite = m::mock(new MongoBulkWrite()); - - $query = ['_id' => '123', 'grades.grade' => 85]; - $data = ['grades.std' => 6]; - - $bulkWrite = m::mock(BulkWrite::class.'[getBulkWrite]', [$model]); - - // Expectations - $mongoBulkWrite->expects() - ->update( - m::on( - function ($actual) use ($query) { - $this->assertSame($query, $actual); - - return true; - } - ), - ['$unset' => $data], - ['upsert' => true] - ); - - $bulkWrite->expects() - ->getBulkWrite() - ->andReturn($mongoBulkWrite); - - // Actions - $bulkWrite->updateOne($query, $data, ['upsert' => true], '$unset'); - } - - public function testShouldExecuteBulkWrite() - { - $model = m::mock(ModelInterface::class); - $mongoBulkWrite = m::mock(new MongoBulkWrite()); - $connection = $this->instance(Connection::class, m::mock(Connection::class)); - $manager = m::mock(new Manager('mongodb://localhost')); - - $connection->defaultDatabase = 'foo'; - $namespace = 'foo.bar'; - - $bulkWrite = m::mock(BulkWrite::class.'[getBulkWrite]', [$model]); - - // Expectations - $connection->expects() - ->getManager() - ->andReturn($manager); - - $model->expects() - ->getCollectionName() - ->andReturn('bar'); - - $manager->expects() - ->executeBulkWrite($namespace, $mongoBulkWrite, ['writeConcern' => new WriteConcern(1)]) - ->andReturn(true); - - $bulkWrite->expects() - ->getBulkWrite() - ->andReturn($mongoBulkWrite); - - // Actions - $bulkWrite->execute(); - } -} From a17f5641a630f4b4a78a71c054a215fd555c7739 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 28 Nov 2018 18:37:11 -0200 Subject: [PATCH 095/116] Drop getManager from Connection as it is not used anymore --- src/Connection/Connection.php | 10 ---------- tests/Unit/Connection/ConnectionTest.php | 16 ---------------- 2 files changed, 26 deletions(-) diff --git a/src/Connection/Connection.php b/src/Connection/Connection.php index f0b4bcd1..e9f6c586 100644 --- a/src/Connection/Connection.php +++ b/src/Connection/Connection.php @@ -53,16 +53,6 @@ public function getClient(): Client return $this->client; } - /** - * Getter for Manager instance. - * - * @return \MongoDB\Driver\Manager - */ - public function getManager() - { - return $this->getClient()->getManager(); - } - /** * Find and stores the default database in the connection string. * diff --git a/tests/Unit/Connection/ConnectionTest.php b/tests/Unit/Connection/ConnectionTest.php index 4258ecdd..342bfc99 100644 --- a/tests/Unit/Connection/ConnectionTest.php +++ b/tests/Unit/Connection/ConnectionTest.php @@ -2,7 +2,6 @@ namespace Mongolid\Connection; use MongoDB\Client; -use MongoDB\Driver\Manager; use Mongolid\TestCase; class ConnectionTest extends TestCase @@ -59,19 +58,4 @@ public function testShouldGetConnection() $this->assertAttributeSame($expectedParameters['uri'], 'uri', $client); $this->assertAttributeSame($expectedParameters['typeMap'], 'typeMap', $client); } - - public function testShouldGetManager() - { - // Set - $server = 'mongodb://my-server/my_db'; - $options = ['some', 'uri', 'options']; - $driverOptions = ['some', 'driver', 'options']; - - // Actions - $connection = new Connection($server, $options, $driverOptions); - $manager = $connection->getManager(); - - // Assertions - $this->assertInstanceOf(Manager::class, $manager); - } } From 75835ace52d6e8e5b6b5e42576bba17a0f5d78c1 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 28 Nov 2018 18:59:48 -0200 Subject: [PATCH 096/116] Create getCollection on Model to simplify interacting with MongoDB Collection directly --- src/Model/AbstractModel.php | 15 ++ src/Model/ModelInterface.php | 8 + src/Query/Builder.php | 25 +-- src/Query/BulkWrite.php | 9 +- tests/Stubs/ReplaceCollectionModel.php | 33 ++++ tests/Unit/Query/BuilderTest.php | 245 +++++-------------------- 6 files changed, 113 insertions(+), 222 deletions(-) create mode 100644 tests/Stubs/ReplaceCollectionModel.php diff --git a/src/Model/AbstractModel.php b/src/Model/AbstractModel.php index d934baa5..c0d7946e 100644 --- a/src/Model/AbstractModel.php +++ b/src/Model/AbstractModel.php @@ -1,7 +1,9 @@ collection; } + /** + * {@inheritdoc} + */ + public function getCollection(): Collection + { + $connection = Ioc::make(Connection::class); + + $database = $connection->defaultDatabase; + $collectionName = $this->getCollectionName(); + + return $connection->getClient()->$database->$collectionName; + } + /** * Getter for $writeConcern attribute. */ diff --git a/src/Model/ModelInterface.php b/src/Model/ModelInterface.php index 69b879b9..7a850d79 100644 --- a/src/Model/ModelInterface.php +++ b/src/Model/ModelInterface.php @@ -2,6 +2,7 @@ namespace Mongolid\Model; use MongoDB\BSON\Persistable; +use MongoDB\Collection; use Mongolid\Cursor\CursorInterface; /** @@ -70,6 +71,13 @@ public static function firstOrNew($id); */ public function getCollectionName(): string; + /** + * Retrieve MongoDB's collection. + * + * @throws \Mongolid\Model\Exception\NoCollectionNameException + */ + public function getCollection(): Collection; + /** * Getter for $writeConcern attribute. */ diff --git a/src/Query/Builder.php b/src/Query/Builder.php index 166501bb..64c4334a 100644 --- a/src/Query/Builder.php +++ b/src/Query/Builder.php @@ -3,7 +3,6 @@ use InvalidArgumentException; use MongoDB\BSON\ObjectId; -use MongoDB\Collection; use Mongolid\Connection\Connection; use Mongolid\Container\Ioc; use Mongolid\Cursor\Cursor; @@ -59,7 +58,7 @@ public function save(ModelInterface $model, array $options = []): bool $model->bsonSerialize(); - $queryResult = $this->getCollection($model)->replaceOne( + $queryResult = $model->getCollection()->replaceOne( ['_id' => $model->_id], $model, $this->mergeOptions($options, ['upsert' => true]) @@ -95,7 +94,7 @@ public function insert(ModelInterface $model, array $options = [], bool $fireEve return false; } - $queryResult = $this->getCollection($model)->insertOne( + $queryResult = $model->getCollection()->insertOne( $model, $this->mergeOptions($options) ); @@ -142,7 +141,7 @@ public function update(ModelInterface $model, array $options = []): bool $updateData = $this->getUpdateData($model, $model->bsonSerialize()); - $queryResult = $this->getCollection($model)->updateOne( + $queryResult = $model->getCollection()->updateOne( ['_id' => $model->_id], $updateData, $this->mergeOptions($options) @@ -174,7 +173,7 @@ public function delete(ModelInterface $model, array $options = []): bool return false; } - $queryResult = $this->getCollection($model)->deleteOne( + $queryResult = $model->getCollection()->deleteOne( ['_id' => $model->_id], $this->mergeOptions($options) ); @@ -200,7 +199,7 @@ public function delete(ModelInterface $model, array $options = []): bool public function where(ModelInterface $model, $query = [], array $projection = []): CursorInterface { return new Cursor( - $this->getCollection($model), + $model->getCollection(), 'find', [ $this->prepareValueQuery($query), @@ -234,7 +233,7 @@ public function first(ModelInterface $model, $query = [], array $projection = [] return null; } - return $this->getCollection($model)->findOne( + return $model->getCollection()->findOne( $this->prepareValueQuery($query), ['projection' => $this->prepareProjection($projection)] ); @@ -261,18 +260,6 @@ public function firstOrFail(ModelInterface $model, $query = [], array $projectio throw (new ModelNotFoundException())->setModel(get_class($model)); } - /** - * Retrieves the Collection object. - */ - protected function getCollection(ModelInterface $model): Collection - { - $connection = $this->connection; - $database = $connection->defaultDatabase; - $collection = $model->getCollectionName(); - - return $connection->getClient()->$database->$collection; - } - /** * Transforms a value that is not an array into an MongoDB query (array). * This method will take care of converting a single value into a query for diff --git a/src/Query/BulkWrite.php b/src/Query/BulkWrite.php index c23e2c55..c52d40df 100644 --- a/src/Query/BulkWrite.php +++ b/src/Query/BulkWrite.php @@ -4,8 +4,6 @@ use MongoDB\BSON\ObjectId; use MongoDB\BulkWriteResult; use MongoDB\Driver\WriteConcern; -use Mongolid\Connection\Connection; -use Mongolid\Container\Ioc; use Mongolid\Model\ModelInterface; /** @@ -70,12 +68,7 @@ public function updateOne( */ public function execute(int $writeConcern = 1): BulkWriteResult { - $connection = Ioc::make(Connection::class); - - $database = $connection->defaultDatabase; - $collectionName = $this->model->getCollectionName(); - - $collection = $connection->getClient()->$database->$collectionName; + $collection = $this->model->getCollection(); return $collection->bulkWrite( $this->operations, diff --git a/tests/Stubs/ReplaceCollectionModel.php b/tests/Stubs/ReplaceCollectionModel.php new file mode 100644 index 00000000..c40ecf2a --- /dev/null +++ b/tests/Stubs/ReplaceCollectionModel.php @@ -0,0 +1,33 @@ +rawCollection = $collection; + } + + public function getCollection(): Collection + { + return $this->rawCollection; + } +} diff --git a/tests/Unit/Query/BuilderTest.php b/tests/Unit/Query/BuilderTest.php index 194b1281..3abee4c6 100644 --- a/tests/Unit/Query/BuilderTest.php +++ b/tests/Unit/Query/BuilderTest.php @@ -4,9 +4,7 @@ use InvalidArgumentException; use Mockery as m; use MongoDB\BSON\ObjectId; -use MongoDB\Client; use MongoDB\Collection; -use MongoDB\Database; use MongoDB\Driver\WriteConcern; use Mongolid\Connection\Connection; use Mongolid\Container\Ioc; @@ -16,6 +14,7 @@ use Mongolid\Model\Exception\ModelNotFoundException; use Mongolid\Model\ModelInterface; use Mongolid\TestCase; +use Mongolid\Tests\Stubs\ReplaceCollectionModel; class BuilderTest extends TestCase { @@ -34,22 +33,19 @@ public function testShouldBeAbleToConstruct() /** * @dataProvider getWriteConcernVariations */ - public function testShouldSave($model, $writeConcern, $shouldFireEventAfter, $expected) + public function testShouldSave(ReplaceCollectionModel $model, $writeConcern, $shouldFireEventAfter, $expected) { // Set $connection = m::mock(Connection::class); - $builder = m::mock(Builder::class.'[getCollection]', [$connection]); - $builder->shouldAllowMockingProtectedMethods(); + $builder = new Builder($connection); $options = ['writeConcern' => new WriteConcern($writeConcern)]; $collection = m::mock(Collection::class); $operationResult = m::mock(); - // Expectations - $builder->expects() - ->getCollection($model) - ->andReturn($collection); + $model->setCollection($collection); + // Expectations $collection->expects() ->replaceOne( ['_id' => 123], @@ -87,24 +83,20 @@ public function testShouldSave($model, $writeConcern, $shouldFireEventAfter, $ex /** * @dataProvider getWriteConcernVariations */ - public function testShouldInsert($model, $writeConcern, $shouldFireEventAfter, $expected) + public function testShouldInsert(ReplaceCollectionModel $model, $writeConcern, $shouldFireEventAfter, $expected) { // Set $connection = m::mock(Connection::class); - $builder = m::mock(Builder::class.'[getCollection]', [$connection]); - $builder->shouldAllowMockingProtectedMethods(); + $builder = new Builder($connection); $options = ['writeConcern' => new WriteConcern($writeConcern)]; $collection = m::mock(Collection::class); $operationResult = m::mock(); + $model->setCollection($collection); $model->_id = null; // Expectations - $builder->expects() - ->getCollection($model) - ->andReturn($collection); - $collection->expects() ->insertOne($model, ['writeConcern' => new WriteConcern($writeConcern)]) ->andReturn($operationResult); @@ -135,24 +127,20 @@ public function testShouldInsert($model, $writeConcern, $shouldFireEventAfter, $ /** * @dataProvider getWriteConcernVariations */ - public function testShouldInsertWithoutFiringEvents($model, $writeConcern, $shouldFireEventAfter, $expected) + public function testShouldInsertWithoutFiringEvents(ReplaceCollectionModel $model, $writeConcern, $shouldFireEventAfter, $expected) { // Set $connection = m::mock(Connection::class); - $builder = m::mock(Builder::class.'[getCollection]', [$connection]); - $builder->shouldAllowMockingProtectedMethods(); + $builder = new Builder($connection); $options = ['writeConcern' => new WriteConcern($writeConcern)]; $collection = m::mock(Collection::class); $operationResult = m::mock(); + $model->setCollection($collection); $model->_id = null; // Expectations - $builder->expects() - ->getCollection($model) - ->andReturn($collection); - $collection->expects() ->insertOne($model, ['writeConcern' => new WriteConcern($writeConcern)]) ->andReturn($operationResult); @@ -178,12 +166,10 @@ public function testShouldInsertWithoutFiringEvents($model, $writeConcern, $shou /** * @dataProvider getWriteConcernVariations */ - public function testShouldUpdate($model, $writeConcern, $shouldFireEventAfter, $expected) + public function testShouldUpdate(ReplaceCollectionModel $model, $writeConcern, $shouldFireEventAfter, $expected) { // Set $connection = m::mock(Connection::class); - $client = m::mock(Client::class); - $database = m::mock(Database::class); $builder = new Builder($connection); $collection = m::mock(Collection::class); @@ -191,19 +177,9 @@ public function testShouldUpdate($model, $writeConcern, $shouldFireEventAfter, $ $operationResult = m::mock(); $options = ['writeConcern' => new WriteConcern($writeConcern)]; - // Expectations - $connection->expects() - ->getClient() - ->andReturn($client); - - $client->expects() - ->selectDatabase('mongolid') - ->andReturn($database); - - $database->expects() - ->selectCollection('models') - ->andReturn($collection); + $model->setCollection($collection); + // Expectations $collection->expects() ->updateOne( ['_id' => 123], @@ -238,17 +214,10 @@ public function testShouldUpdateUnsettingFields() { // Set $connection = m::mock(Connection::class); - $client = m::mock(Client::class); - $database = m::mock(Database::class); $builder = new Builder($connection); - $model = new class extends AbstractModel + $model = new class() extends ReplaceCollectionModel { - /** - * {@inheritdoc} - */ - protected $collection = 'models'; - /** * {@inheritdoc} */ @@ -261,15 +230,11 @@ public function testShouldUpdateUnsettingFields() * {@inheritdoc} */ protected $dynamic = false; - - /** - * {@inheritdoc} - */ - protected $timestamps = false; }; $collection = m::mock(Collection::class); $operationResult = m::mock(); $options = ['writeConcern' => new WriteConcern(1)]; + $model->setCollection($collection); $model->unchanged = 'unchanged'; $model->notOnFillable = 'to be deleted'; @@ -279,18 +244,6 @@ public function testShouldUpdateUnsettingFields() unset($model->name); // Expectations - $connection->expects() - ->getClient() - ->andReturn($client); - - $client->expects() - ->selectDatabase('mongolid') - ->andReturn($database); - - $database->expects() - ->selectCollection('models') - ->andReturn($collection); - $collection->expects() ->updateOne( ['_id' => 123], @@ -321,28 +274,23 @@ public function testShouldUpdateUnsettingFields() * @dataProvider getWriteConcernVariations */ public function testUpdateShouldCallInsertWhenObjectHasNoId( - $model, + ReplaceCollectionModel $model, $writeConcern, $shouldFireEventAfter, $expected ) { // Set $connection = m::mock(Connection::class); - $builder = m::mock(Builder::class.'[getCollection]', [$connection]); + $builder = new Builder($connection); $collection = m::mock(Collection::class); $operationResult = m::mock(); $options = ['writeConcern' => new WriteConcern($writeConcern)]; + $model->setCollection($collection); $model->_id = null; // Actions - $builder->shouldAllowMockingProtectedMethods(); - - $builder->expects() - ->getCollection($model) - ->andReturn($collection); - $collection->expects() ->insertOne( $model, @@ -378,22 +326,19 @@ public function testUpdateShouldCallInsertWhenObjectHasNoId( /** * @dataProvider getWriteConcernVariations */ - public function testShouldDelete($model, $writeConcern, $shouldFireEventAfter, $expected) + public function testShouldDelete(ReplaceCollectionModel $model, $writeConcern, $shouldFireEventAfter, $expected) { // Set $connection = m::mock(Connection::class); - $builder = m::mock(Builder::class.'[getCollection]', [$connection]); - $builder->shouldAllowMockingProtectedMethods(); + $builder = new Builder($connection); $collection = m::mock(Collection::class); $operationResult = m::mock(); $options = ['writeConcern' => new WriteConcern($writeConcern)]; - // Expectations - $builder->expects() - ->getCollection($model) - ->andReturn($collection); + $model->setCollection($collection); + // Expectations $collection->expects() ->deleteOne(['_id' => 123], ['writeConcern' => new WriteConcern($writeConcern)]) ->andReturn($operationResult); @@ -459,25 +404,21 @@ public function testShouldGetWithWhereQuery() { // Set $connection = m::mock(Connection::class); - $builder = m::mock(Builder::class.'[prepareValueQuery,getCollection]', [$connection]); + $builder = m::mock(Builder::class.'[prepareValueQuery]', [$connection]); + $builder->shouldAllowMockingProtectedMethods(); - $model = m::mock(ModelInterface::class); $collection = m::mock(Collection::class); + $model = new ReplaceCollectionModel(); + $model->setCollection($collection); $query = 123; $preparedQuery = ['_id' => 123]; $projection = ['project' => true, '_id' => false, '__pclass' => true]; - $builder->shouldAllowMockingProtectedMethods(); - // Expectations $builder->expects() ->prepareValueQuery($query) ->andReturn($preparedQuery); - $builder->expects() - ->getCollection($model) - ->andReturn($collection); - // Actions $result = $builder->where($model, $query, $projection); @@ -516,37 +457,28 @@ public function testShouldGetFirstWithQuery() { // Set $connection = m::mock(Connection::class); - $builder = m::mock(Builder::class.'[prepareValueQuery,getCollection]', [$connection]); + $builder = m::mock(Builder::class.'[prepareValueQuery]', [$connection]); + $builder->shouldAllowMockingProtectedMethods(); $collection = m::mock(Collection::class); $query = 123; $preparedQuery = ['_id' => 123]; - $object = new class extends AbstractModel - { - /** - * {@inheritdoc} - */ - protected $collection = 'models'; - }; - $builder->shouldAllowMockingProtectedMethods(); + $model = new ReplaceCollectionModel(); + $model->setCollection($collection); // Expectations $builder->expects() ->prepareValueQuery($query) ->andReturn($preparedQuery); - $builder->expects() - ->getCollection($object) - ->andReturn($collection); - $collection->expects() ->findOne($preparedQuery, ['projection' => []]) - ->andReturn($object); + ->andReturn($model); // Actions - $result = $builder->first($object, $query); + $result = $builder->first($model, $query); // Assertions - $this->assertSame($object, $result); + $this->assertSame($model, $result); } public function testFirstWithNullShouldNotHitTheDatabase() @@ -566,34 +498,28 @@ public function testFirstOrFailShouldGetFirst() { // Set $connection = m::mock(Connection::class); - $builder = m::mock(Builder::class.'[prepareValueQuery,getCollection]', [$connection]); + $builder = m::mock(Builder::class.'[prepareValueQuery]', [$connection]); + $builder->shouldAllowMockingProtectedMethods(); $collection = m::mock(Collection::class); $query = 123; $preparedQuery = ['_id' => 123]; - $object = new class extends AbstractModel - { - }; - - $builder->shouldAllowMockingProtectedMethods(); + $model = new ReplaceCollectionModel(); + $model->setCollection($collection); // Expectations $builder->expects() ->prepareValueQuery($query) ->andReturn($preparedQuery); - $builder->expects() - ->getCollection($object) - ->andReturn($collection); - $collection->expects() ->findOne($preparedQuery, ['projection' => []]) - ->andReturn($object); + ->andReturn($model); // Actions - $result = $builder->firstOrFail($object, $query); + $result = $builder->firstOrFail($model, $query); // Assertions - $this->assertSame($object, $result); + $this->assertSame($model, $result); } public function testFirstOrFailWithNullShouldFail() @@ -617,23 +543,19 @@ public function testShouldGetNullIfFirstCantFindAnything() { // Set $connection = m::mock(Connection::class); - $builder = m::mock(Builder::class.'[prepareValueQuery,getCollection]', [$connection]); - $model = m::mock(ModelInterface::class); + $builder = m::mock(Builder::class.'[prepareValueQuery]', [$connection]); + $builder->shouldAllowMockingProtectedMethods(); $collection = m::mock(Collection::class); $query = 123; $preparedQuery = ['_id' => 123]; - - $builder->shouldAllowMockingProtectedMethods(); + $model = new ReplaceCollectionModel(); + $model->setCollection($collection); // Expectations $builder->expects() ->prepareValueQuery($query) ->andReturn($preparedQuery); - $builder->expects() - ->getCollection($model) - ->andReturn($collection); - $collection->expects() ->findOne($preparedQuery, ['projection' => []]) ->andReturn(null); @@ -649,27 +571,21 @@ public function testShouldGetFirstProjectingFields() { // Set $connection = m::mock(Connection::class); - $builder = m::mock( - Builder::class.'[prepareValueQuery,getCollection]', - [$connection] - ); + $builder = m::mock(Builder::class.'[prepareValueQuery]', [$connection]); $builder->shouldAllowMockingProtectedMethods(); - $model = m::mock(ModelInterface::class); $collection = m::mock(Collection::class); $query = 123; $preparedQuery = ['_id' => 123]; $projection = ['project' => true, 'fields' => false, '__pclass' => true]; + $model = new ReplaceCollectionModel(); + $model->setCollection($collection); // Expectations $builder->expects() ->prepareValueQuery($query) ->andReturn($preparedQuery); - $builder->expects() - ->getCollection($model) - ->andReturn($collection); - $collection->expects() ->findOne($preparedQuery, ['projection' => $projection]) ->andReturn(null); @@ -681,43 +597,6 @@ public function testShouldGetFirstProjectingFields() $this->assertNull($result); } - public function testShouldGetClient() - { - // Set - $connection = m::mock(Connection::class); - $connection->defaultDatabase = 'grimory'; - - $builder = new Builder($connection); - $collection = m::mock(Collection::class); - $model = m::mock(ModelInterface::class); - - $client = m::mock(Client::class); - $database = m::mock(Database::class); - - // Expectations - $connection->expects() - ->getClient() - ->andReturn($client); - - $client->expects() - ->selectDatabase('grimory') - ->andReturn($database); - - $database->expects() - ->selectCollection('models') - ->andReturn($collection); - - $model->expects() - ->getCollectionName() - ->andReturn('models'); - - // Actions - $result = $this->callProtected($builder, 'getCollection', [$model]); - - // Assertions - $this->assertSame($collection, $result); - } - /** * @dataProvider queryValueScenarios */ @@ -830,32 +709,8 @@ public function queryValueScenarios(): array public function getWriteConcernVariations(): array { - $model = new class extends AbstractModel - { - /** - * {@inheritdoc} - */ - protected $timestamps = false; - - /** - * {@inheritdoc} - */ - protected $collection = 'models'; - }; - - $model2 = new class extends AbstractModel - { - /** - * {@inheritdoc} - */ - protected $timestamps = false; - - /** - * {@inheritdoc} - */ - protected $collection = 'models'; - }; - + $model = new ReplaceCollectionModel(); + $model2 = new ReplaceCollectionModel(); $model->_id = 123; $model2->_id = 123; From 9020d1717c6805f3426ad744f298a62eb8cef111 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 28 Nov 2018 19:05:56 -0200 Subject: [PATCH 097/116] Rename Ioc to Container --- src/Connection/Manager.php | 14 ++++++------ src/Container/{Ioc.php => Container.php} | 2 +- src/Cursor/Cursor.php | 4 ++-- src/Model/AbstractModel.php | 8 +++---- src/Model/Relations/ReferencesMany.php | 4 ++-- src/Model/Relations/ReferencesOne.php | 4 ++-- src/Query/Builder.php | 4 ++-- tests/Stubs/EmbeddedUser.php | 4 ++-- tests/Stubs/ReferencedUser.php | 4 ++-- tests/Unit/Connection/ManagerTest.php | 8 +++---- .../{IocTest.php => ContainerTest.php} | 22 +++++++++---------- tests/Unit/Query/BuilderTest.php | 8 +++---- tests/Unit/TestCase.php | 10 ++++----- tests/Util/DropDatabaseTrait.php | 4 ++-- tests/Util/SetupConnectionTrait.php | 4 ++-- 15 files changed, 52 insertions(+), 52 deletions(-) rename src/Container/{Ioc.php => Container.php} (98%) rename tests/Unit/Container/{IocTest.php => ContainerTest.php} (52%) diff --git a/src/Connection/Manager.php b/src/Connection/Manager.php index dad8c342..4b508837 100644 --- a/src/Connection/Manager.php +++ b/src/Connection/Manager.php @@ -1,9 +1,9 @@ init(); - $this->container->instance(Connection::class, $this->connection); + $this->container->instance(IlluminateContainer::class, $this->connection); $this->connection = $connection; @@ -91,8 +91,8 @@ protected function init() return; } - $this->container = new Container(); - Ioc::setContainer($this->container); + $this->container = new IlluminateContainer(); + Container::setContainer($this->container); static::$singleton = $this; } diff --git a/src/Container/Ioc.php b/src/Container/Container.php similarity index 98% rename from src/Container/Ioc.php rename to src/Container/Container.php index 9092b6c0..0da2e95a 100644 --- a/src/Container/Ioc.php +++ b/src/Container/Container.php @@ -7,7 +7,7 @@ * This class is a simple Facade for a Illuminate\Container\Container * in order to use the Container as IOC at all classes. */ -class Ioc +class Container { /** * Illuminate instance. diff --git a/src/Cursor/Cursor.php b/src/Cursor/Cursor.php index b49826b1..8f850d0d 100644 --- a/src/Cursor/Cursor.php +++ b/src/Cursor/Cursor.php @@ -9,7 +9,7 @@ use MongoDB\Driver\ReadPreference; use MongoDB\Model\CachingIterator; use Mongolid\Connection\Connection; -use Mongolid\Container\Ioc; +use Mongolid\Container\Container; use Serializable; /** @@ -285,7 +285,7 @@ public function unserialize($serialized) { $attributes = unserialize($serialized); - $connection = Ioc::make(Connection::class); + $connection = Container::make(Connection::class); $db = $connection->defaultDatabase; $collectionObject = $connection->getClient()->$db->{$attributes['collection']}; diff --git a/src/Model/AbstractModel.php b/src/Model/AbstractModel.php index c0d7946e..b15c15b6 100644 --- a/src/Model/AbstractModel.php +++ b/src/Model/AbstractModel.php @@ -4,7 +4,7 @@ use MongoDB\Collection; use MongoDB\Driver\WriteConcern; use Mongolid\Connection\Connection; -use Mongolid\Container\Ioc; +use Mongolid\Container\Container; use Mongolid\Cursor\CursorInterface; use Mongolid\Model\Exception\ModelNotFoundException; use Mongolid\Model\Exception\NoCollectionNameException; @@ -224,7 +224,7 @@ public function getCollectionName(): string */ public function getCollection(): Collection { - $connection = Ioc::make(Connection::class); + $connection = Container::make(Connection::class); $database = $connection->defaultDatabase; $collectionName = $this->getCollectionName(); @@ -252,7 +252,7 @@ public function setWriteConcern(int $writeConcern): void public function bsonSerialize() { - return Ioc::make(ModelMapper::class) + return Container::make(ModelMapper::class) ->map($this, array_merge($this->fillable, $this->guarded), $this->dynamic, $this->timestamps); } @@ -288,6 +288,6 @@ protected function execute(string $action): bool */ private function getBuilder(): Builder { - return Ioc::make(Builder::class); + return Container::make(Builder::class); } } diff --git a/src/Model/Relations/ReferencesMany.php b/src/Model/Relations/ReferencesMany.php index 5a71d5a2..12247b02 100644 --- a/src/Model/Relations/ReferencesMany.php +++ b/src/Model/Relations/ReferencesMany.php @@ -2,7 +2,7 @@ namespace Mongolid\Model\Relations; use MongoDB\BSON\ObjectId; -use Mongolid\Container\Ioc; +use Mongolid\Container\Container; use Mongolid\Model\ModelInterface; use Mongolid\Util\ObjectIdUtils; @@ -17,7 +17,7 @@ public function __construct(ModelInterface $parent, string $model, string $field { parent::__construct($parent, $model, $field); $this->key = $key; - $this->modelInstance = Ioc::make($this->model); + $this->modelInstance = Container::make($this->model); } /** diff --git a/src/Model/Relations/ReferencesOne.php b/src/Model/Relations/ReferencesOne.php index 76e1b502..2fb017ee 100644 --- a/src/Model/Relations/ReferencesOne.php +++ b/src/Model/Relations/ReferencesOne.php @@ -2,7 +2,7 @@ namespace Mongolid\Model\Relations; use MongoDB\BSON\ObjectId; -use Mongolid\Container\Ioc; +use Mongolid\Container\Container; use Mongolid\Model\ModelInterface; use Mongolid\Util\ObjectIdUtils; @@ -17,7 +17,7 @@ public function __construct(ModelInterface $parent, string $model, string $field { parent::__construct($parent, $model, $field); $this->key = $key; - $this->modelInstance = Ioc::make($this->model); + $this->modelInstance = Container::make($this->model); } public function attach(ModelInterface $model): void diff --git a/src/Query/Builder.php b/src/Query/Builder.php index 64c4334a..4d48036d 100644 --- a/src/Query/Builder.php +++ b/src/Query/Builder.php @@ -4,7 +4,7 @@ use InvalidArgumentException; use MongoDB\BSON\ObjectId; use Mongolid\Connection\Connection; -use Mongolid\Container\Ioc; +use Mongolid\Container\Container; use Mongolid\Cursor\Cursor; use Mongolid\Cursor\CursorInterface; use Mongolid\Event\EventTriggerService; @@ -329,7 +329,7 @@ protected function fireEvent(string $event, ModelInterface $model, bool $halt = { $event = "mongolid.{$event}: ".get_class($model); - $this->eventService ?: $this->eventService = Ioc::make(EventTriggerService::class); + $this->eventService ?: $this->eventService = Container::make(EventTriggerService::class); return $this->eventService->fire($event, $model, $halt); } diff --git a/tests/Stubs/EmbeddedUser.php b/tests/Stubs/EmbeddedUser.php index 25c1686d..81fcde0c 100644 --- a/tests/Stubs/EmbeddedUser.php +++ b/tests/Stubs/EmbeddedUser.php @@ -3,7 +3,7 @@ use MongoDB\Collection; use Mongolid\Connection\Connection; -use Mongolid\Container\Ioc; +use Mongolid\Container\Container; use Mongolid\Model\AbstractModel; class EmbeddedUser extends AbstractModel @@ -20,7 +20,7 @@ class EmbeddedUser extends AbstractModel public function collection(): Collection { - $connection = Ioc::make(Connection::class); + $connection = Container::make(Connection::class); $client = $connection->getClient(); return $client->{$connection->defaultDatabase}->{$this->collection}; diff --git a/tests/Stubs/ReferencedUser.php b/tests/Stubs/ReferencedUser.php index 79e5825a..7fa8a9eb 100644 --- a/tests/Stubs/ReferencedUser.php +++ b/tests/Stubs/ReferencedUser.php @@ -3,7 +3,7 @@ use MongoDB\Collection; use Mongolid\Connection\Connection; -use Mongolid\Container\Ioc; +use Mongolid\Container\Container; use Mongolid\Model\AbstractModel; use Mongolid\Model\PolymorphableModelInterface; @@ -30,7 +30,7 @@ class ReferencedUser extends AbstractModel implements PolymorphableModelInterfac public function collection(): Collection { - $connection = Ioc::make(Connection::class); + $connection = Container::make(Connection::class); $client = $connection->getClient(); return $client->{$connection->defaultDatabase}->{$this->collection}; diff --git a/tests/Unit/Connection/ManagerTest.php b/tests/Unit/Connection/ManagerTest.php index 8ed64b63..7bcdb9ee 100644 --- a/tests/Unit/Connection/ManagerTest.php +++ b/tests/Unit/Connection/ManagerTest.php @@ -1,7 +1,7 @@ setProtected($manager, 'container', $container); @@ -67,7 +67,7 @@ public function testShouldInitializeOnce() // Assertions $this->assertAttributeSame($manager, 'singleton', Manager::class); - $this->assertAttributeInstanceOf(Container::class, 'container', $manager); + $this->assertAttributeInstanceOf(IlluminateContainer::class, 'container', $manager); // Actions $container = $manager->container; diff --git a/tests/Unit/Container/IocTest.php b/tests/Unit/Container/ContainerTest.php similarity index 52% rename from tests/Unit/Container/IocTest.php rename to tests/Unit/Container/ContainerTest.php index a1b6a7bf..829796f4 100644 --- a/tests/Unit/Container/IocTest.php +++ b/tests/Unit/Container/ContainerTest.php @@ -1,15 +1,15 @@ flush(); parent::tearDown(); @@ -18,30 +18,30 @@ protected function tearDown() public function testShouldCallMethodsProperlyWithNoArguments() { // Set - $container = m::mock(Container::class); - Ioc::setContainer($container); + $illuminateContainer = m::mock(IlluminateContainer::class); + Container::setContainer($illuminateContainer); // Expectations - $container->expects() + $illuminateContainer->expects() ->method() ->andReturn(true); // Actions - Ioc::method(); + Container::method(); } public function testShouldCallMethodsProperlyWithArguments() { // Set - $container = m::mock(Container::class); - Ioc::setContainer($container); + $illuminateContainer = m::mock(IlluminateContainer::class); + Container::setContainer($illuminateContainer); // Expectations - $container->expects() + $illuminateContainer->expects() ->method(1, 2, 3) ->andReturn(true); // Actions - Ioc::method(1, 2, 3); + Container::method(1, 2, 3); } } diff --git a/tests/Unit/Query/BuilderTest.php b/tests/Unit/Query/BuilderTest.php index 3abee4c6..1f45294a 100644 --- a/tests/Unit/Query/BuilderTest.php +++ b/tests/Unit/Query/BuilderTest.php @@ -7,7 +7,7 @@ use MongoDB\Collection; use MongoDB\Driver\WriteConcern; use Mongolid\Connection\Connection; -use Mongolid\Container\Ioc; +use Mongolid\Container\Container; use Mongolid\Cursor\Cursor; use Mongolid\Event\EventTriggerService; use Mongolid\Model\AbstractModel; @@ -765,11 +765,11 @@ public function getProjections(): array protected function getEventService(): EventTriggerService { - if (!Ioc::has(EventTriggerService::class)) { - Ioc::instance(EventTriggerService::class, m::mock(EventTriggerService::class)); + if (!Container::has(EventTriggerService::class)) { + Container::instance(EventTriggerService::class, m::mock(EventTriggerService::class)); } - return Ioc::make(EventTriggerService::class); + return Container::make(EventTriggerService::class); } protected function expectEventToBeFired(string $event, ModelInterface $model, bool $halt, bool $return = true): void diff --git a/tests/Unit/TestCase.php b/tests/Unit/TestCase.php index 18bc6942..906ea6cb 100644 --- a/tests/Unit/TestCase.php +++ b/tests/Unit/TestCase.php @@ -1,9 +1,9 @@ getClient() ->dropDatabase($connection->defaultDatabase); diff --git a/tests/Util/SetupConnectionTrait.php b/tests/Util/SetupConnectionTrait.php index 24711a64..19a2582f 100644 --- a/tests/Util/SetupConnectionTrait.php +++ b/tests/Util/SetupConnectionTrait.php @@ -2,13 +2,13 @@ namespace Mongolid\Tests\Util; use Mongolid\Connection\Connection; -use Mongolid\Container\Ioc; +use Mongolid\Container\Container; trait SetupConnectionTrait { public function setupConnection(string $host, string $database) { - Ioc::singleton( + Container::singleton( Connection::class, function () use ($host, $database) { $connection = new Connection("mongodb://{$host}:27017/{$database}"); From d2ae4ddd4458d72715477be6b5cc75bcb1a20b5d Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 28 Nov 2018 19:11:37 -0200 Subject: [PATCH 098/116] Fix method isEmpty of BulkWrite and ensure it is empty after running --- src/Query/BulkWrite.php | 10 +++++++--- tests/Integration/BulkWriteTest.php | 6 ++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Query/BulkWrite.php b/src/Query/BulkWrite.php index c52d40df..359b9d44 100644 --- a/src/Query/BulkWrite.php +++ b/src/Query/BulkWrite.php @@ -31,9 +31,9 @@ public function __construct(ModelInterface $model) $this->model = $model; } - public function isEmpty() + public function isEmpty(): bool { - return count($this->operations); + return !$this->operations; } /** @@ -70,9 +70,13 @@ public function execute(int $writeConcern = 1): BulkWriteResult { $collection = $this->model->getCollection(); - return $collection->bulkWrite( + $result = $collection->bulkWrite( $this->operations, ['writeConcern' => new WriteConcern($writeConcern)] ); + + $this->operations = []; + + return $result; } } diff --git a/tests/Integration/BulkWriteTest.php b/tests/Integration/BulkWriteTest.php index 56fc543f..86e24834 100644 --- a/tests/Integration/BulkWriteTest.php +++ b/tests/Integration/BulkWriteTest.php @@ -16,6 +16,8 @@ public function testShouldRunMultipleUpdateOperations() $bulkWrite = new BulkWrite(new ReferencedUser()); + $this->assertTrue($bulkWrite->isEmpty()); + $bulkWrite->updateOne( ['_id' => $bob->_id], ['name' => 'Bulk Updated Bob!'] @@ -47,6 +49,8 @@ public function testShouldRunMultipleUpdateOperations() '$unset' ); + $this->assertFalse($bulkWrite->isEmpty()); + // Before running $this->assertSame('Bob', $bob->name); $this->assertSame('John', $john->name); @@ -59,6 +63,8 @@ public function testShouldRunMultipleUpdateOperations() // Runs it $result = $bulkWrite->execute(); + $this->assertTrue($bulkWrite->isEmpty()); + $this->assertInstanceOf(BulkWriteResult::class, $result); $this->assertTrue($result->isAcknowledged()); $this->assertSame(6, $result->getModifiedCount()); From ebec1bdbc253d09e4504bcbebd45459d96aa5eef Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Thu, 29 Nov 2018 11:29:20 -0200 Subject: [PATCH 099/116] 100% coverage --- src/Query/Builder.php | 2 +- tests/Integration/EmbedsManyRelationTest.php | 5 +- .../ReferencesManyRelationTest.php | 9 ++- tests/Unit/Query/BuilderTest.php | 62 ++++++++++++++++++- tests/Unit/Query/ModelMapperTest.php | 30 +++++++++ tests/Unit/Util/ObjectIdUtilsTest.php | 8 +++ 6 files changed, 107 insertions(+), 9 deletions(-) diff --git a/src/Query/Builder.php b/src/Query/Builder.php index 4d48036d..cda62db3 100644 --- a/src/Query/Builder.php +++ b/src/Query/Builder.php @@ -416,7 +416,7 @@ private function calculateChanges(array &$changes, array $newData, array $oldDat if (!isset($oldData[$k])) { // new field $changes['$set']["{$keyfix}{$k}"] = $v; } elseif ($oldData[$k] != $v) { // changed value - if (is_array($v) && $oldData[$k] && $v) { // check array recursively for changes + if (is_array($v) && is_array($oldData[$k]) && $v) { // check array recursively for changes $this->calculateChanges($changes, $v, $oldData[$k], "{$keyfix}{$k}."); } else { // overwrite normal changes in keys diff --git a/tests/Integration/EmbedsManyRelationTest.php b/tests/Integration/EmbedsManyRelationTest.php index efdc803f..fa7d3b68 100644 --- a/tests/Integration/EmbedsManyRelationTest.php +++ b/tests/Integration/EmbedsManyRelationTest.php @@ -18,7 +18,7 @@ public function testShouldRetrieveSiblingsOfUser() $this->assertSiblings([$chuck], $john); $mary = $this->createUser('Mary'); - $john->siblings()->add($mary); + $john->siblings()->addMany([$mary]); $this->assertSiblings([$chuck, $mary], $john); @@ -27,11 +27,10 @@ public function testShouldRetrieveSiblingsOfUser() $this->assertSiblings([$mary], $john); // replace siblings - $john->siblings()->remove($mary); $bob = $this->createUser('Bob'); // unset - $john->siblings()->add($bob); + $john->siblings()->replace([$bob]); $this->assertSiblings([$bob], $john); unset($john->embedded_siblings); $this->assertEmpty($john->siblings->all()); diff --git a/tests/Integration/ReferencesManyRelationTest.php b/tests/Integration/ReferencesManyRelationTest.php index d78ea62b..0158e66e 100644 --- a/tests/Integration/ReferencesManyRelationTest.php +++ b/tests/Integration/ReferencesManyRelationTest.php @@ -17,7 +17,7 @@ public function testShouldRetrieveSiblingsOfUser() $this->assertSiblings([$chuck], $john); $mary = $this->createUser('Mary'); - $john->siblings()->attach($mary); + $john->siblings()->attachMany([$mary]); $this->assertSiblings([$chuck, $mary], $john); @@ -26,11 +26,10 @@ public function testShouldRetrieveSiblingsOfUser() $this->assertSiblings([$mary], $john); // replace siblings - $john->siblings()->detach($mary); $bob = $this->createUser('Bob'); // unset - $john->siblings()->attach($bob); + $john->siblings()->replace([$bob]); $this->assertSiblings([$bob], $john); unset($john->siblings_ids); $this->assertEmpty($john->siblings_ids); @@ -63,6 +62,10 @@ public function testShouldRetrieveSiblingsOfUser() $this->assertSiblings([$bob], $john); $john = ReferencedUser::fill(['siblings_ids' => [$chuck->_id]], $john, true); $this->assertSiblings([$chuck], $john); + + // detach not attached has no problems + $john->siblings()->detach(new ReferencedUser()); + $this->assertSiblings([$chuck], $john); } public function testShouldRetrieveGrandsonsOfUserUsingCustomKey() diff --git a/tests/Unit/Query/BuilderTest.php b/tests/Unit/Query/BuilderTest.php index 1f45294a..a0a78a50 100644 --- a/tests/Unit/Query/BuilderTest.php +++ b/tests/Unit/Query/BuilderTest.php @@ -127,8 +127,12 @@ public function testShouldInsert(ReplaceCollectionModel $model, $writeConcern, $ /** * @dataProvider getWriteConcernVariations */ - public function testShouldInsertWithoutFiringEvents(ReplaceCollectionModel $model, $writeConcern, $shouldFireEventAfter, $expected) - { + public function testShouldInsertWithoutFiringEvents( + ReplaceCollectionModel $model, + $writeConcern, + $shouldFireEventAfter, + $expected + ) { // Set $connection = m::mock(Connection::class); $builder = new Builder($connection); @@ -270,6 +274,60 @@ public function testShouldUpdateUnsettingFields() $this->assertTrue($result); } + public function testUpdateShouldCalculateChangesAccordingly() + { + // Set + $connection = m::mock(Connection::class); + $builder = new Builder($connection); + + $model = new class() extends ReplaceCollectionModel + { + }; + $collection = m::mock(Collection::class); + $operationResult = m::mock(); + $options = ['writeConcern' => new WriteConcern(1)]; + $model->setCollection($collection); + + $model->unchanged = 'unchanged'; + $model->name = 'John'; + $model->surname = 'Doe'; + $model->addresses = ['1 Blue Street']; + $model->syncOriginalDocumentAttributes(); + $model->_id = 123; + unset($model->name); + $model->surname = ['Doe', 'Jr']; + $model->addresses = ['1 Blue Street', '2 Green Street']; + + // Expectations + $collection->expects() + ->updateOne( + ['_id' => 123], + [ + '$set' => ['_id' => 123, 'surname' => ['Doe', 'Jr'], 'addresses.1' => '2 Green Street'], + '$unset' => ['name' => ''], + ], + $options + )->andReturn($operationResult); + + $operationResult->expects() + ->isAcknowledged() + ->andReturn(true); + + $operationResult->allows() + ->getModifiedCount() + ->andReturn(1); + + $this->expectEventToBeFired('updating', $model, true); + + $this->expectEventToBeFired('updated', $model, false); + + // Actions + $result = $builder->update($model, $options); + + // Assertions + $this->assertTrue($result); + } + /** * @dataProvider getWriteConcernVariations */ diff --git a/tests/Unit/Query/ModelMapperTest.php b/tests/Unit/Query/ModelMapperTest.php index a6519a86..135dba5d 100644 --- a/tests/Unit/Query/ModelMapperTest.php +++ b/tests/Unit/Query/ModelMapperTest.php @@ -36,6 +36,36 @@ public function testShouldClearDynamicFieldsIfModelIsNotDynamic() ); } + public function testShouldClearDynamicFieldsIfModelIsNotDynamicCheckingTimestamps() + { + // Set + $model = new class extends AbstractModel + { + }; + $modelMapper = new ModelMapper(); + $model->_id = 1; + $model->name = 'John'; + $model->age = 23; + $model->location = 'Brazil'; + $dateTime = new UTCDateTime(); + $model->created_at = $dateTime; // `$model->timestamps` is false! + + // Actions + $result = $modelMapper->map($model, ['name', 'age'], false, true); + + // Assertions + $this->assertSame( + [ + '_id' => 1, + 'name' => 'John', + 'age' => 23, + 'created_at' => $dateTime, + 'updated_at' => $model->updated_at, + ], + $result + ); + } + public function testShouldNotClearDynamicFieldsIfModelIsDynamic() { // Set diff --git a/tests/Unit/Util/ObjectIdUtilsTest.php b/tests/Unit/Util/ObjectIdUtilsTest.php index f0d362ac..580e8b76 100644 --- a/tests/Unit/Util/ObjectIdUtilsTest.php +++ b/tests/Unit/Util/ObjectIdUtilsTest.php @@ -20,12 +20,20 @@ public function testShouldEvaluateIfValueIsAnObjectId($value, bool $expectation) public function objectIdStringScenarios(): array { + $object = new class { + public function __toString() + { + return '577a68c44d3cec1f6c7796a2'; + } + }; + return [ ['value' => '577a68c44d3cec1f6c7796a2', 'expectation' => true], ['value' => '577a68d24d3cec1f817796a5', 'expectation' => true], ['value' => '577a68d14d3cec1f6d7796a3', 'expectation' => true], ['value' => '507f1f77bcf86cd799439011', 'expectation' => true], ['value' => '507f191e810c19729de860ea', 'expectation' => true], + ['value' => $object, 'expectation' => true], ['value' => new ObjectId(), 'expectation' => true], ['value' => new ObjectId('577a68c44d3cec1f6c7796a2'), 'expectation' => true], ['value' => 1, 'expectation' => false], From 62473516a59767582cbc09bf85d9c7e40af79b9f Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Thu, 29 Nov 2018 12:00:12 -0200 Subject: [PATCH 100/116] Use implemented getCollection for test stubs --- tests/Integration/PersistedDataTest.php | 6 +++--- tests/Stubs/EmbeddedUser.php | 11 ----------- tests/Stubs/ReferencedUser.php | 11 ----------- 3 files changed, 3 insertions(+), 25 deletions(-) diff --git a/tests/Integration/PersistedDataTest.php b/tests/Integration/PersistedDataTest.php index 6920ad80..3b444471 100644 --- a/tests/Integration/PersistedDataTest.php +++ b/tests/Integration/PersistedDataTest.php @@ -41,7 +41,7 @@ public function testSaveInsertingData() // Actions $saveResult = $user->save(); - $result = $user->collection()->findOne(['_id' => $this->_id]); + $result = $user->getCollection()->findOne(['_id' => $this->_id]); $result->_id = (string) $result->_id; // Assertions @@ -83,7 +83,7 @@ public function testSaveUpdatingData() // Actions $updateResult = $user->save(); - $result = $user->collection()->findOne(['_id' => $user->_id]); + $result = $user->getCollection()->findOne(['_id' => $user->_id]); $result->_id = (string) $result->_id; // Assertions @@ -125,7 +125,7 @@ public function testUpdateData() // Actions $updateResult = $user->update(); - $result = $user->collection()->findOne(['_id' => $user->_id]); + $result = $user->getCollection()->findOne(['_id' => $user->_id]); $result->_id = (string) $result->_id; // Assertions diff --git a/tests/Stubs/EmbeddedUser.php b/tests/Stubs/EmbeddedUser.php index 81fcde0c..0d82cef4 100644 --- a/tests/Stubs/EmbeddedUser.php +++ b/tests/Stubs/EmbeddedUser.php @@ -1,9 +1,6 @@ getClient(); - - return $client->{$connection->defaultDatabase}->{$this->collection}; - } - public function parent() { return $this->embedsOne(EmbeddedUser::class); diff --git a/tests/Stubs/ReferencedUser.php b/tests/Stubs/ReferencedUser.php index 7fa8a9eb..71cc5a04 100644 --- a/tests/Stubs/ReferencedUser.php +++ b/tests/Stubs/ReferencedUser.php @@ -1,9 +1,6 @@ getClient(); - - return $client->{$connection->defaultDatabase}->{$this->collection}; - } - public function parent() { return $this->referencesOne(ReferencedUser::class); From e384914bafc7ca90dfc7a97998d16a7eef019244 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Thu, 29 Nov 2018 12:12:20 -0200 Subject: [PATCH 101/116] Update travis script --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ebf0e891..6fb3df30 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,5 +22,5 @@ script: - vendor/bin/phpcs - vendor/bin/phpunit -c phpunit.xml.dist -after_script: +after_success: - if [[ $(phpenv version-name) = "7.2" ]]; then php vendor/bin/php-coveralls -v; fi From b52b7c22372b15bdc54947f8ae76cd562a5e9691 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Thu, 29 Nov 2018 15:40:38 -0200 Subject: [PATCH 102/116] Change requirements order on readme --- README.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index cdfba99f..6ad8a58a 100644 --- a/README.md +++ b/README.md @@ -11,29 +11,26 @@

## About Mongolid - Easy, powerful and ultrafast ODM for PHP 7.1+ build on top of the [new mongodb driver](https://docs.mongodb.org/ecosystem/drivers/php/). Mongolid supports **ActiveRecord** pattern. ## Introduction - Mongolid ODM (Object Document Mapper) provides a beautiful, simple implementation for working with MongoDB. Each database collection can have a corresponding "Model" which is used to interact with that collection. **Note:** If you are working with Laravel, take a look at [mongolid-laravel repository](https://github.com/leroy-merlin-br/mongolid-laravel). -## Installation +## Requirements +- PHP **7.1** or superior +- [MongoDB Driver](http://php.net/manual/en/set.mongodb.php) +## Installation You can install the library through Composer: ``` $ composer require leroy-merlin-br/mongolid ``` -## Requirements -- PHP **7.1** -- [MongoDB Driver](http://php.net/manual/en/set.mongodb.php) - ## Documentation You can access the full documentation [here](http://leroy-merlin-br.github.com/mongolid). From 45270fa341cd156f628fa0773a48a26985205aed Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Thu, 29 Nov 2018 15:40:53 -0200 Subject: [PATCH 103/116] Use container to create instances on fill method --- src/Model/HasAttributesTrait.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Model/HasAttributesTrait.php b/src/Model/HasAttributesTrait.php index 636d7c63..262c3d53 100644 --- a/src/Model/HasAttributesTrait.php +++ b/src/Model/HasAttributesTrait.php @@ -3,6 +3,7 @@ use Exception; use Illuminate\Support\Str; +use Mongolid\Container\Container; /** * This trait adds attribute getter, setters and also a useful @@ -72,7 +73,7 @@ public static function fill( bool $force = false ): HasAttributesInterface { if (!$object) { - $object = new static(); + $object = Container::make(static::class); } if ($object instanceof PolymorphableModelInterface) { From a70d79878580c2ee084b6fa5eb7f8c304fc96a46 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Thu, 29 Nov 2018 16:34:18 -0200 Subject: [PATCH 104/116] Consider model attributes when checking for polymorph class --- src/Model/HasAttributesTrait.php | 3 +- tests/Unit/Model/HasAttributesTraitTest.php | 37 +++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/Model/HasAttributesTrait.php b/src/Model/HasAttributesTrait.php index 262c3d53..0ecabfae 100644 --- a/src/Model/HasAttributesTrait.php +++ b/src/Model/HasAttributesTrait.php @@ -77,7 +77,8 @@ public static function fill( } if ($object instanceof PolymorphableModelInterface) { - $class = $object->polymorph($input); + $class = $object->polymorph(array_merge($object->getDocumentAttributes(), $input)); + if ($class !== get_class($object)) { $originalAttributes = $object->getDocumentAttributes(); $object = new $class(); diff --git a/tests/Unit/Model/HasAttributesTraitTest.php b/tests/Unit/Model/HasAttributesTraitTest.php index 0ff1d882..049030e5 100644 --- a/tests/Unit/Model/HasAttributesTraitTest.php +++ b/tests/Unit/Model/HasAttributesTraitTest.php @@ -134,6 +134,43 @@ public function testFillShouldRetrievePolymorphedModel() $this->assertSame('hello', $result->new_field); } + public function testFillShouldRetrievePolymorphedModelConsideringModelAttributes() + { + // Set + $input = [ + 'new_field' => 'hello', + ]; + $model = new ReferencedUser(); + $model->type = 'polymorphed'; + + // Actions + $result = ReferencedUser::fill($input, $model); + + // Assertions + $this->assertInstanceOf(PolymorphedReferencedUser::class, $result); + $this->assertSame('polymorphed', $result->type); + $this->assertSame('hello', $result->new_field); + } + + public function testFillShouldRetrievePolymorphedModelConsideringModelAttributesButPrioritizingInput() + { + // Set + $input = [ + 'type' => 'default', + 'new_field' => 'hello', + ]; + $model = new PolymorphedReferencedUser(); + $model->type = 'polymorphed'; + + // Actions + $result = ReferencedUser::fill($input, $model); + + // Assertions + $this->assertInstanceOf(ReferencedUser::class, $result); + $this->assertSame('default', $result->type); + $this->assertSame('hello', $result->new_field); + } + public function testFillShouldRetrievePolymorphedModelEvenWithExistingModel() { // Set From 61d282c52a7d847c7604d936b26feef908cc3987 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Thu, 29 Nov 2018 17:02:15 -0200 Subject: [PATCH 105/116] Unset field when value is null --- src/Model/HasAttributesTrait.php | 6 ++++++ tests/Integration/PersistedDataTest.php | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Model/HasAttributesTrait.php b/src/Model/HasAttributesTrait.php index 0ecabfae..a774133d 100644 --- a/src/Model/HasAttributesTrait.php +++ b/src/Model/HasAttributesTrait.php @@ -165,6 +165,12 @@ public function setDocumentAttribute(string $key, $value) $value = $this->{$this->buildMutatorMethod($key, 'set')}($value); } + if (null === $value) { + $this->cleanDocumentAttribute($key); + + return; + } + $this->attributes[$key] = $value; if ($this->hasFieldRelation($key)) { diff --git a/tests/Integration/PersistedDataTest.php b/tests/Integration/PersistedDataTest.php index 3b444471..96dd6f7e 100644 --- a/tests/Integration/PersistedDataTest.php +++ b/tests/Integration/PersistedDataTest.php @@ -70,7 +70,6 @@ public function testSaveUpdatingData() 'name' => 'Jane Doe', 'preferences' => [], 'friends' => ['Mary'], - 'address' => '123 Blue Street', 'skills' => [ 'PHP' => ['percentage' => '100%', 'version' => '7.1'], 'JavaScript' => ['percentage' => '80%', 'version' => 'ES6'], @@ -79,6 +78,7 @@ public function testSaveUpdatingData() ], 'photos' => ['profile' => '/user-photo', 'icon' => '/user-icon'], 'email' => 'jane@doe.com', + 'address' => '123 Blue Street', ]; // Actions From 8b4d66343fc692a7c91bc1889cdf405f4e8a723d Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Thu, 29 Nov 2018 17:34:48 -0200 Subject: [PATCH 106/116] Clean null attributes when retrieving them --- src/Model/HasAttributesTrait.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Model/HasAttributesTrait.php b/src/Model/HasAttributesTrait.php index a774133d..b097f898 100644 --- a/src/Model/HasAttributesTrait.php +++ b/src/Model/HasAttributesTrait.php @@ -136,12 +136,13 @@ public function &getDocumentAttribute(string $key) */ public function getDocumentAttributes(): array { - return array_filter( - $this->attributes ?? [], - function ($value) { - return !is_null($value); + foreach ($this->attributes as $field => $value) { + if (null === $value) { + $this->cleanDocumentAttribute($field); } - ); + } + + return $this->attributes ?? []; } /** From 72dc4fdc32d3b4c79869a50d3d16df252f02cdbb Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Thu, 29 Nov 2018 19:21:13 -0200 Subject: [PATCH 107/116] Fix embedded models being retrieved as arrays instead of objects As a drawback, it is not possible to retrieve stdClass' fields from database. They are casted to array (to keep array behavior consistent). --- src/Connection/Connection.php | 2 +- src/Model/HasAttributesTrait.php | 5 +++++ tests/Integration/EmbedsManyRelationTest.php | 10 ++++++++++ tests/Unit/Connection/ConnectionTest.php | 5 +---- tests/Unit/Model/AbstractModelTest.php | 4 +++- 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/Connection/Connection.php b/src/Connection/Connection.php index e9f6c586..59dccfa3 100644 --- a/src/Connection/Connection.php +++ b/src/Connection/Connection.php @@ -38,7 +38,7 @@ public function __construct( array $driverOptions = [] ) { // In order to work with PHP arrays instead of with objects - $driverOptions['typeMap'] = ['array' => 'array', 'document' => 'array']; + $driverOptions['typeMap'] = ['array' => 'array']; $this->findDefaultDatabase($server); diff --git a/src/Model/HasAttributesTrait.php b/src/Model/HasAttributesTrait.php index b097f898..f4c57a7e 100644 --- a/src/Model/HasAttributesTrait.php +++ b/src/Model/HasAttributesTrait.php @@ -4,6 +4,7 @@ use Exception; use Illuminate\Support\Str; use Mongolid\Container\Container; +use stdClass; /** * This trait adds attribute getter, setters and also a useful @@ -92,6 +93,10 @@ public static function fill( foreach ($input as $key => $value) { if ($force || ((!$object->fillable || in_array($key, $object->fillable)) && !in_array($key, $object->guarded))) { + if ($value instanceof stdClass) { + $value = json_decode(json_encode($value), true); // cast to array + } + $object->setDocumentAttribute($key, $value); } } diff --git a/tests/Integration/EmbedsManyRelationTest.php b/tests/Integration/EmbedsManyRelationTest.php index fa7d3b68..dfd5ee72 100644 --- a/tests/Integration/EmbedsManyRelationTest.php +++ b/tests/Integration/EmbedsManyRelationTest.php @@ -121,6 +121,16 @@ public function testShouldRetrieveGrandsonsOfUserUsingCustomKey() $this->assertGrandsons([$bob], $john); $john = EmbeddedUser::fill(['other_arbitrary_field' => [$chuck]], $john, true); $this->assertGrandsons([$chuck], $john); + + // save and retrieve object + $this->assertTrue($john->save()); + $john = $john->first($john->_id); + + $this->assertInstanceOf(EmbeddedUser::class, $john->grandsons->first()); + $this->assertEquals( + array_except($chuck->toArray(), 'updated_at'), + array_except($john->grandsons->first()->toArray(), 'updated_at') + ); } private function createUser(string $name): EmbeddedUser diff --git a/tests/Unit/Connection/ConnectionTest.php b/tests/Unit/Connection/ConnectionTest.php index 342bfc99..b8e82bb2 100644 --- a/tests/Unit/Connection/ConnectionTest.php +++ b/tests/Unit/Connection/ConnectionTest.php @@ -44,10 +44,7 @@ public function testShouldGetConnection() $driverOptions = ['some', 'driver', 'options']; $expectedParameters = [ 'uri' => $server, - 'typeMap' => [ - 'array' => 'array', - 'document' => 'array', - ], + 'typeMap' => ['array' => 'array'], ]; // Actions diff --git a/tests/Unit/Model/AbstractModelTest.php b/tests/Unit/Model/AbstractModelTest.php index 25aa5c44..3f68da88 100644 --- a/tests/Unit/Model/AbstractModelTest.php +++ b/tests/Unit/Model/AbstractModelTest.php @@ -426,7 +426,9 @@ public function testShouldHaveDynamicSetters() public function testShouldHaveDynamicGetters() { // Set - $child = new stdClass(); + $child = new class() extends AbstractModel + { + }; $model = new class() extends AbstractModel { }; From 5605f45590da7ac57aebff3b9a2eebfc22e0a1ba Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Thu, 29 Nov 2018 19:29:12 -0200 Subject: [PATCH 108/116] Fix test that was breaking on CI --- tests/Integration/PersistedDataTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Integration/PersistedDataTest.php b/tests/Integration/PersistedDataTest.php index 96dd6f7e..e86fe191 100644 --- a/tests/Integration/PersistedDataTest.php +++ b/tests/Integration/PersistedDataTest.php @@ -89,7 +89,7 @@ public function testSaveUpdatingData() // Assertions $this->assertTrue($updateResult); $this->assertInstanceOf(ReferencedUser::class, $result); - $this->assertSame($expected, $result->toArray()); + $this->assertEquals($expected, $result->toArray()); } public function testUpdateData() @@ -131,7 +131,7 @@ public function testUpdateData() // Assertions $this->assertTrue($updateResult); $this->assertInstanceOf(ReferencedUser::class, $result); - $this->assertSame($expected, $result->toArray()); + $this->assertEquals($expected, $result->toArray()); } private function getUser(bool $save = false): ReferencedUser From 18926ad818e5b71899a9d1487d42f8c31aca1b17 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Fri, 30 Nov 2018 09:23:27 -0200 Subject: [PATCH 109/116] Remove unused variable --- tests/Unit/Cursor/EmbeddedCursorTest.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/Unit/Cursor/EmbeddedCursorTest.php b/tests/Unit/Cursor/EmbeddedCursorTest.php index 45468863..c140d6f6 100644 --- a/tests/Unit/Cursor/EmbeddedCursorTest.php +++ b/tests/Unit/Cursor/EmbeddedCursorTest.php @@ -184,11 +184,6 @@ public function testShouldGetCurrentUsingModelClass() public function testShouldGetCurrentUsingModelClassMorphingIt() { // Set - $object = new class() extends AbstractModel - { - }; - - // Other class (mimics polymorph behavior) $model = new class() extends AbstractModel { }; From e255ec0b7f4d65b1d5713e7eef9f67cf1427e266 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Fri, 30 Nov 2018 11:16:30 -0200 Subject: [PATCH 110/116] Small tweaks --- src/Model/AbstractModel.php | 6 ++--- src/Query/Builder.php | 2 +- src/Util/LocalDateTime.php | 2 +- tests/Integration/PersistedDataTest.php | 2 +- .../ReferencesManyRelationTest.php | 3 +++ tests/Unit/Cursor/EmbeddedCursorTest.php | 22 ++++++++-------- tests/Unit/Model/HasAttributesTraitTest.php | 8 +++--- tests/Unit/Model/HasRelationsTraitTest.php | 6 ++--- tests/Unit/Query/BuilderTest.php | 26 +++++++++---------- tests/Unit/TestCase.php | 3 ++- tests/Unit/Util/LocalDateTimeTest.php | 2 +- tests/Unit/Util/SequenceServiceTest.php | 6 ++--- 12 files changed, 46 insertions(+), 42 deletions(-) diff --git a/src/Model/AbstractModel.php b/src/Model/AbstractModel.php index b15c15b6..b6772d1d 100644 --- a/src/Model/AbstractModel.php +++ b/src/Model/AbstractModel.php @@ -80,7 +80,7 @@ public static function all(): CursorInterface * @param mixed $query mongoDB selection criteria * @param array $projection fields to project in Mongo query * - * @return ModelInterface|null + * @return AbstractModel|null */ public static function first($query = [], array $projection = []) { @@ -96,7 +96,7 @@ public static function first($query = [], array $projection = []) * * @throws ModelNotFoundException If no document was found * - * @return ModelInterface|null + * @return AbstractModel|null */ public static function firstOrFail($query = [], array $projection = []) { @@ -110,7 +110,7 @@ public static function firstOrFail($query = [], array $projection = []) * * @param mixed $id document id * - * @return ModelInterface|null + * @return AbstractModel|null */ public static function firstOrNew($id) { diff --git a/src/Query/Builder.php b/src/Query/Builder.php index cda62db3..62f5cc5d 100644 --- a/src/Query/Builder.php +++ b/src/Query/Builder.php @@ -405,7 +405,7 @@ protected function prepareProjection(array $fields): array } /** - * Based on the work of bjori/mongo-php-transistor. + * Based on the work of "bjori/mongo-php-transistor". * Calculate `$set` and `$unset` arrays for update operation and store them on $changes. * * @see https://github.com/bjori/mongo-php-transistor/blob/70f5af00795d67f4d5a8c397e831435814df9937/src/Transistor.php#L108 diff --git a/src/Util/LocalDateTime.php b/src/Util/LocalDateTime.php index 3c3c6586..a525ec8f 100644 --- a/src/Util/LocalDateTime.php +++ b/src/Util/LocalDateTime.php @@ -28,7 +28,7 @@ public static function get(UTCDateTime $date): DateTime } /** - * Retrieves formated date time using timezone. + * Retrieves formatted date time using timezone. */ public static function format( UTCDateTime $date, diff --git a/tests/Integration/PersistedDataTest.php b/tests/Integration/PersistedDataTest.php index e86fe191..ef10218d 100644 --- a/tests/Integration/PersistedDataTest.php +++ b/tests/Integration/PersistedDataTest.php @@ -152,7 +152,7 @@ private function getUser(bool $save = false): ReferencedUser 'CSS' => ['percentage' => '45%', 'version' => 'CSS3'], ]; - // dinamically set array + // dynamically set array $user->photos['profile'] = '/user-photo'; $user->photos['icon'] = '/user-icon'; diff --git a/tests/Integration/ReferencesManyRelationTest.php b/tests/Integration/ReferencesManyRelationTest.php index 0158e66e..28878b2d 100644 --- a/tests/Integration/ReferencesManyRelationTest.php +++ b/tests/Integration/ReferencesManyRelationTest.php @@ -149,6 +149,8 @@ private function assertSiblings($expected, ReferencedUser $model) $this->assertInstanceOf(CursorInterface::class, $siblings); $this->assertEquals($expected, $siblings->all()); + $ids = []; + foreach ($expected as $expectedModel) { $ids[] = $expectedModel->_id; } @@ -174,6 +176,7 @@ private function assertGrandsons($expected, ReferencedUser $model) $this->assertInstanceOf(CursorInterface::class, $grandsons); $this->assertEquals($expected, $grandsons->all()); + $codes = []; foreach ($expected as $expectedModel) { $codes[] = $expectedModel->code; } diff --git a/tests/Unit/Cursor/EmbeddedCursorTest.php b/tests/Unit/Cursor/EmbeddedCursorTest.php index c140d6f6..8e5f22a4 100644 --- a/tests/Unit/Cursor/EmbeddedCursorTest.php +++ b/tests/Unit/Cursor/EmbeddedCursorTest.php @@ -34,7 +34,7 @@ public function testShouldLimitDocumentQuantity() /** * @dataProvider getDocumentsToSort */ - public function testShouldSortDocuments($items, $parameters, $expected) + public function testShouldSortDocuments(array $items, array $parameters, array $expected) { // Set $cursor = new EmbeddedCursor($items); @@ -340,7 +340,7 @@ public function getDocumentsToSort(): array ['age' => 26, 'name' => 'Abe'], ['age' => 25], $age24, - ['age' => 26, 'name' => 'Zizaco'], + ['age' => 26, 'name' => 'Wilson'], ['age' => 26, 'name' => 'John'], [], ], @@ -350,7 +350,7 @@ public function getDocumentsToSort(): array $age24, ['age' => 25], ['age' => 26, 'name' => 'Abe'], - ['age' => 26, 'name' => 'Zizaco'], + ['age' => 26, 'name' => 'Wilson'], ['age' => 26, 'name' => 'John'], ], ], @@ -359,14 +359,14 @@ public function getDocumentsToSort(): array ['age' => 26, 'name' => 'Abe'], ['age' => 25], $age24, - ['age' => 26, 'name' => 'Zizaco'], + ['age' => 26, 'name' => 'Wilson'], ['age' => 26, 'name' => 'John'], [], ], 'parameters' => ['age' => -1], 'expected' => [ ['age' => 26, 'name' => 'Abe'], - ['age' => 26, 'name' => 'Zizaco'], + ['age' => 26, 'name' => 'Wilson'], ['age' => 26, 'name' => 'John'], ['age' => 25], $age24, @@ -378,7 +378,7 @@ public function getDocumentsToSort(): array ['age' => 26, 'name' => 'Abe'], ['age' => 25], $age24, - ['age' => 26, 'name' => 'Zizaco'], + ['age' => 26, 'name' => 'Wilson'], ['age' => 26, 'name' => 'John'], [], ], @@ -387,7 +387,7 @@ public function getDocumentsToSort(): array [], $age24, ['age' => 25], - ['age' => 26, 'name' => 'Zizaco'], + ['age' => 26, 'name' => 'Wilson'], ['age' => 26, 'name' => 'John'], ['age' => 26, 'name' => 'Abe'], ], @@ -397,16 +397,16 @@ public function getDocumentsToSort(): array ['age' => 26, 'name' => 'Abe', 'color' => 'red'], ['age' => 25], $age24, - ['age' => 26, 'name' => 'Zizaco', 'color' => 'red'], - ['age' => 26, 'name' => 'Zizaco', 'color' => 'blue'], + ['age' => 26, 'name' => 'Wilson', 'color' => 'red'], + ['age' => 26, 'name' => 'Wilson', 'color' => 'blue'], ['age' => 26, 'name' => 'John'], ], 'parameters' => ['age' => 1, 'name' => -1, 'color' => 1], 'expected' => [ $age24, ['age' => 25], - ['age' => 26, 'name' => 'Zizaco', 'color' => 'blue'], - ['age' => 26, 'name' => 'Zizaco', 'color' => 'red'], + ['age' => 26, 'name' => 'Wilson', 'color' => 'blue'], + ['age' => 26, 'name' => 'Wilson', 'color' => 'red'], ['age' => 26, 'name' => 'John'], ['age' => 26, 'name' => 'Abe', 'color' => 'red'], ], diff --git a/tests/Unit/Model/HasAttributesTraitTest.php b/tests/Unit/Model/HasAttributesTraitTest.php index 049030e5..f409e02f 100644 --- a/tests/Unit/Model/HasAttributesTraitTest.php +++ b/tests/Unit/Model/HasAttributesTraitTest.php @@ -93,10 +93,10 @@ public function setShortNameDocumentAttribute($value) * @dataProvider getFillableOptions */ public function testShouldFillOnlyPermittedAttributes( - $fillable, - $guarded, - $input, - $expected + array $fillable, + array $guarded, + array $input, + array $expected ) { // Set $model = new class($fillable, $guarded) implements HasAttributesInterface diff --git a/tests/Unit/Model/HasRelationsTraitTest.php b/tests/Unit/Model/HasRelationsTraitTest.php index 1f718345..80485790 100644 --- a/tests/Unit/Model/HasRelationsTraitTest.php +++ b/tests/Unit/Model/HasRelationsTraitTest.php @@ -14,7 +14,7 @@ class HasRelationsTraitTest extends TestCase /** * @dataProvider referencesOneScenarios */ - public function testShouldReferenceOne($fieldValue, $expectedQuery) + public function testShouldReferenceOne($fieldValue, array $expectedQuery) { // Set $model = new ReferencedUser(); @@ -58,7 +58,7 @@ public function testShouldNotPerformQueryForNullReference() /** * @dataProvider referencesManyScenarios */ - public function testShouldReferenceMany($fieldValue, $expectedQuery) + public function testShouldReferenceMany($fieldValue, array $expectedQuery) { // Set $model = new ReferencedUser(); @@ -128,7 +128,7 @@ public function testEmbedOneShouldAllowOnlyOneEmbeddedModel() /** * @dataProvider embedsManyScenarios */ - public function testShouldEmbedMany($fieldValue, $expectedItems) + public function testShouldEmbedMany($fieldValue, array $expectedItems) { // Set $model = new EmbeddedUser(); diff --git a/tests/Unit/Query/BuilderTest.php b/tests/Unit/Query/BuilderTest.php index a0a78a50..c7d88109 100644 --- a/tests/Unit/Query/BuilderTest.php +++ b/tests/Unit/Query/BuilderTest.php @@ -33,7 +33,7 @@ public function testShouldBeAbleToConstruct() /** * @dataProvider getWriteConcernVariations */ - public function testShouldSave(ReplaceCollectionModel $model, $writeConcern, $shouldFireEventAfter, $expected) + public function testShouldSave(ReplaceCollectionModel $model, int $writeConcern, bool $shouldFireEventAfter, bool $expected) { // Set $connection = m::mock(Connection::class); @@ -83,7 +83,7 @@ public function testShouldSave(ReplaceCollectionModel $model, $writeConcern, $sh /** * @dataProvider getWriteConcernVariations */ - public function testShouldInsert(ReplaceCollectionModel $model, $writeConcern, $shouldFireEventAfter, $expected) + public function testShouldInsert(ReplaceCollectionModel $model, int $writeConcern, bool $shouldFireEventAfter, bool $expected) { // Set $connection = m::mock(Connection::class); @@ -129,9 +129,9 @@ public function testShouldInsert(ReplaceCollectionModel $model, $writeConcern, $ */ public function testShouldInsertWithoutFiringEvents( ReplaceCollectionModel $model, - $writeConcern, - $shouldFireEventAfter, - $expected + int $writeConcern, + bool $shouldFireEventAfter, + bool $expected ) { // Set $connection = m::mock(Connection::class); @@ -170,7 +170,7 @@ public function testShouldInsertWithoutFiringEvents( /** * @dataProvider getWriteConcernVariations */ - public function testShouldUpdate(ReplaceCollectionModel $model, $writeConcern, $shouldFireEventAfter, $expected) + public function testShouldUpdate(ReplaceCollectionModel $model, int $writeConcern, bool $shouldFireEventAfter, bool $expected) { // Set $connection = m::mock(Connection::class); @@ -333,9 +333,9 @@ public function testUpdateShouldCalculateChangesAccordingly() */ public function testUpdateShouldCallInsertWhenObjectHasNoId( ReplaceCollectionModel $model, - $writeConcern, - $shouldFireEventAfter, - $expected + int $writeConcern, + bool $shouldFireEventAfter, + bool $expected ) { // Set $connection = m::mock(Connection::class); @@ -384,7 +384,7 @@ public function testUpdateShouldCallInsertWhenObjectHasNoId( /** * @dataProvider getWriteConcernVariations */ - public function testShouldDelete(ReplaceCollectionModel $model, $writeConcern, $shouldFireEventAfter, $expected) + public function testShouldDelete(ReplaceCollectionModel $model, int $writeConcern, bool $shouldFireEventAfter, bool $expected) { // Set $connection = m::mock(Connection::class); @@ -428,9 +428,9 @@ public function testShouldDelete(ReplaceCollectionModel $model, $writeConcern, $ * @dataProvider eventsToBailOperations */ public function testDatabaseOperationsShouldBailOutIfTheEventHandlerReturnsFalse( - $operation, - $dbOperation, - $eventName + string $operation, + string $dbOperation, + string $eventName ) { // Set $connection = m::mock(Connection::class); diff --git a/tests/Unit/TestCase.php b/tests/Unit/TestCase.php index 906ea6cb..f42fb490 100644 --- a/tests/Unit/TestCase.php +++ b/tests/Unit/TestCase.php @@ -12,6 +12,7 @@ class TestCase extends PHPUnitTestCase { protected function setUp() { + parent::setUp(); Container::setContainer(new IlluminateContainer()); } @@ -73,7 +74,7 @@ protected function getProtected($obj, $property) /** * Replace instance on Ioc */ - protected function instance($abstract, $instance) + protected function instance(string $abstract, $instance) { Container::bind( $abstract, diff --git a/tests/Unit/Util/LocalDateTimeTest.php b/tests/Unit/Util/LocalDateTimeTest.php index 01382347..50430a38 100644 --- a/tests/Unit/Util/LocalDateTimeTest.php +++ b/tests/Unit/Util/LocalDateTimeTest.php @@ -65,7 +65,7 @@ public function testFormatShouldRetrievesDateWithDefaultFormat() $this->assertSame($this->date->format($this->format), $result); } - public function testFormatShouldRetrieesDateUsingGivenFormat() + public function testFormatShouldRetrieveDateUsingGivenFormat() { // Set $timezone = new DateTimeZone(date_default_timezone_get()); diff --git a/tests/Unit/Util/SequenceServiceTest.php b/tests/Unit/Util/SequenceServiceTest.php index 59af8c84..e0101e5c 100644 --- a/tests/Unit/Util/SequenceServiceTest.php +++ b/tests/Unit/Util/SequenceServiceTest.php @@ -13,7 +13,7 @@ class SequenceServiceTest extends TestCase /** * @dataProvider sequenceScenarios */ - public function testShouldGetNextValue($sequenceName, $currentValue, $expectation) + public function testShouldGetNextValue(string $sequenceName, int $currentValue, int $expectation) { // Set $connection = m::mock(Connection::class); @@ -46,7 +46,7 @@ public function testShouldGetClient() { // Set $connection = m::mock(Connection::class); - $connection->defaultDatabase = 'grimory'; + $connection->defaultDatabase = 'production'; $sequenceService = new SequenceService($connection, 'foobar'); $collection = m::mock(Collection::class); @@ -60,7 +60,7 @@ public function testShouldGetClient() ->andReturn($client); $client->expects() - ->selectDatabase('grimory') + ->selectDatabase('production') ->andReturn($database); $database->expects() From 7ac32b1ff33229b7de4722aa8f7cb881cd9a1c96 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Wed, 26 Dec 2018 12:05:36 -0200 Subject: [PATCH 111/116] Use container to manage timestamps and id on ModelMapper This improves testing timestamps and id generation on applications using mongolid. --- src/Container/Container.php | 2 ++ src/Query/ModelMapper.php | 13 +++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Container/Container.php b/src/Container/Container.php index 0da2e95a..abeb3f57 100644 --- a/src/Container/Container.php +++ b/src/Container/Container.php @@ -6,6 +6,8 @@ /** * This class is a simple Facade for a Illuminate\Container\Container * in order to use the Container as IOC at all classes. + * + * @mixin \Illuminate\Container\Container */ class Container { diff --git a/src/Query/ModelMapper.php b/src/Query/ModelMapper.php index 27bb3085..80b843da 100644 --- a/src/Query/ModelMapper.php +++ b/src/Query/ModelMapper.php @@ -3,6 +3,7 @@ use MongoDB\BSON\ObjectId; use MongoDB\BSON\UTCDateTime; +use Mongolid\Container\Container; use Mongolid\Model\ModelInterface; use Mongolid\Util\ObjectIdUtils; @@ -28,8 +29,12 @@ public function map(ModelInterface $model, array $allowedFields, bool $dynamic, /** * If the model is not dynamic, remove all non specified fields. */ - protected function clearDynamicFields(ModelInterface $model, array $allowedFields, bool $dynamic, bool $timestamps): void - { + protected function clearDynamicFields( + ModelInterface $model, + array $allowedFields, + bool $dynamic, + bool $timestamps + ): void { if ($dynamic) { return; } @@ -64,7 +69,7 @@ private function manageTimestamps(ModelInterface $model, bool $timestamps): void if (!$timestamps) { return; } - $model->updated_at = new UTCDateTime(); + $model->updated_at = Container::make(UTCDateTime::class, ['milliseconds' => null]); if (!$model->created_at instanceof UTCDateTime) { $model->created_at = $model->updated_at; @@ -76,7 +81,7 @@ private function manageId(ModelInterface $model) $value = $model->_id; if (is_null($value) || (is_string($value) && ObjectIdUtils::isObjectId($value))) { - $value = new ObjectId($value); + $value = Container::make(ObjectId::class, ['id' => $value]); } $model->_id = $value; From 5bdf0bcdf917f649fd3ff5b30145d6299eb562bb Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Mon, 4 Feb 2019 18:27:53 -0200 Subject: [PATCH 112/116] Add a test to ensure that embedded models are correctly updated --- tests/Integration/EmbedsManyRelationTest.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/Integration/EmbedsManyRelationTest.php b/tests/Integration/EmbedsManyRelationTest.php index dfd5ee72..de2dd539 100644 --- a/tests/Integration/EmbedsManyRelationTest.php +++ b/tests/Integration/EmbedsManyRelationTest.php @@ -131,6 +131,19 @@ public function testShouldRetrieveGrandsonsOfUserUsingCustomKey() array_except($chuck->toArray(), 'updated_at'), array_except($john->grandsons->first()->toArray(), 'updated_at') ); + + $chuck = $john->grandsons->first(); + $chuck->name = 'Chuck Norris'; + $john->other_arbitrary_field = [$chuck]; + + $this->assertTrue($john->update()); + $john = $john->first($john->_id); + + $this->assertInstanceOf(EmbeddedUser::class, $john->grandsons->first()); + $this->assertEquals( + array_except($chuck->toArray(), 'updated_at'), + array_except($john->grandsons->first()->toArray(), 'updated_at') + ); } private function createUser(string $name): EmbeddedUser From 499225ce9dddd5f83d830d3b1c80bcfb2cb3edc8 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Thu, 26 Sep 2019 18:21:48 -0300 Subject: [PATCH 113/116] Update phpunit and illuminate dependencies --- .gitignore | 1 + composer.json | 4 +- composer.lock | 1034 ++++++++++------- docker-compose.yml | 4 +- docker/php/Dockerfile | 5 +- phpunit.xml | 1 - phpunit.xml.dist | 1 - src/Connection/Manager.php | 9 - tests/Integration/AttributesKeyTest.php | 2 +- tests/Integration/BulkWriteTest.php | 2 +- tests/Integration/DateQueriesTest.php | 4 +- tests/Integration/EmbedsManyRelationTest.php | 13 +- tests/Integration/EmbedsOneRelationTest.php | 6 +- tests/Integration/IntegrationTestCase.php | 8 +- tests/Integration/PersistedDataTest.php | 8 +- .../ReferencesManyRelationTest.php | 4 +- .../Integration/ReferencesOneRelationTest.php | 6 +- tests/Integration/RewindableCursorTest.php | 2 +- tests/Unit/Connection/ConnectionTest.php | 20 +- tests/Unit/Connection/ManagerTest.php | 20 +- tests/Unit/Container/ContainerTest.php | 6 +- tests/Unit/Cursor/CursorTest.php | 80 +- tests/Unit/Cursor/EmbeddedCursorTest.php | 62 +- tests/Unit/Event/EventTriggerServiceTest.php | 4 +- tests/Unit/Model/AbstractModelTest.php | 66 +- .../Exception/ModelNotFoundExceptionTest.php | 2 +- tests/Unit/Model/HasAttributesTraitTest.php | 30 +- tests/Unit/Model/HasRelationsTraitTest.php | 12 +- tests/Unit/Query/BuilderTest.php | 76 +- tests/Unit/Query/ModelMapperTest.php | 16 +- tests/Unit/TestCase.php | 4 +- tests/Unit/Util/LocalDateTimeTest.php | 12 +- tests/Unit/Util/ObjectIdUtilsTest.php | 2 +- tests/Unit/Util/SequenceServiceTest.php | 4 +- 34 files changed, 885 insertions(+), 645 deletions(-) diff --git a/.gitignore b/.gitignore index 097df324..0bc86738 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ /build /site .phpcs-cache +.phpunit.result.cache docker-compose.override.yml diff --git a/composer.json b/composer.json index ffd4e9be..9f47c928 100644 --- a/composer.json +++ b/composer.json @@ -21,13 +21,13 @@ "php": ">=7.1", "ext-mongodb": "*", "mongodb/mongodb": "^1.4", - "illuminate/container": "^5.4" + "illuminate/container": "^5.4 || ^6.0" }, "require-dev": { "leroy-merlin-br/coding-standard": "^0.1", "mockery/mockery": "^1.2", "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^6.5", + "phpunit/phpunit": "^7.0 || ^8.0", "sami/sami": "^4.1" }, "autoload": { diff --git a/composer.lock b/composer.lock index 3b88b95e..b61629a0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7061baa6f32a4285b8cf6eddf36c5150", + "content-hash": "f80b48bedd4e433067fe8a6eade22c3c", "packages": [ { "name": "doctrine/inflector", @@ -75,28 +75,28 @@ }, { "name": "illuminate/container", - "version": "v5.7.9", + "version": "v6.0.4", "source": { "type": "git", "url": "https://github.com/illuminate/container.git", - "reference": "73cde7bd4985eefb1d468a745e1d50d03e276121" + "reference": "d6d53581d463072538a96445e135ba86b49eb403" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/container/zipball/73cde7bd4985eefb1d468a745e1d50d03e276121", - "reference": "73cde7bd4985eefb1d468a745e1d50d03e276121", + "url": "https://api.github.com/repos/illuminate/container/zipball/d6d53581d463072538a96445e135ba86b49eb403", + "reference": "d6d53581d463072538a96445e135ba86b49eb403", "shasum": "" }, "require": { - "illuminate/contracts": "5.7.*", - "illuminate/support": "5.7.*", - "php": "^7.1.3", + "illuminate/contracts": "^6.0", + "illuminate/support": "^6.0", + "php": "^7.2", "psr/container": "^1.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.7-dev" + "dev-master": "6.0-dev" } }, "autoload": { @@ -116,31 +116,31 @@ ], "description": "The Illuminate Container package.", "homepage": "https://laravel.com", - "time": "2018-10-07T15:52:17+00:00" + "time": "2019-09-11T20:47:02+00:00" }, { "name": "illuminate/contracts", - "version": "v5.7.9", + "version": "v6.0.4", "source": { "type": "git", "url": "https://github.com/illuminate/contracts.git", - "reference": "64df81d3382d876f1c1d3d5481d89c93b61b8279" + "reference": "403b24e356346c1cd13ad794d87ec7c57a5363bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/contracts/zipball/64df81d3382d876f1c1d3d5481d89c93b61b8279", - "reference": "64df81d3382d876f1c1d3d5481d89c93b61b8279", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/403b24e356346c1cd13ad794d87ec7c57a5363bb", + "reference": "403b24e356346c1cd13ad794d87ec7c57a5363bb", "shasum": "" }, "require": { - "php": "^7.1.3", + "php": "^7.2", "psr/container": "^1.0", "psr/simple-cache": "^1.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.7-dev" + "dev-master": "6.0-dev" } }, "autoload": { @@ -160,43 +160,45 @@ ], "description": "The Illuminate Contracts package.", "homepage": "https://laravel.com", - "time": "2018-10-08T13:34:14+00:00" + "time": "2019-09-18T12:32:21+00:00" }, { "name": "illuminate/support", - "version": "v5.7.9", + "version": "v6.0.4", "source": { "type": "git", "url": "https://github.com/illuminate/support.git", - "reference": "ea95697233b06650382eb0f5798be22b4e520dea" + "reference": "b132b6cc61df520a4d8fc3ffec01e8b3ff4e8202" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/support/zipball/ea95697233b06650382eb0f5798be22b4e520dea", - "reference": "ea95697233b06650382eb0f5798be22b4e520dea", + "url": "https://api.github.com/repos/illuminate/support/zipball/b132b6cc61df520a4d8fc3ffec01e8b3ff4e8202", + "reference": "b132b6cc61df520a4d8fc3ffec01e8b3ff4e8202", "shasum": "" }, "require": { "doctrine/inflector": "^1.1", + "ext-json": "*", "ext-mbstring": "*", - "illuminate/contracts": "5.7.*", - "nesbot/carbon": "^1.26.3", - "php": "^7.1.3" + "illuminate/contracts": "^6.0", + "nesbot/carbon": "^2.0", + "php": "^7.2" }, "conflict": { "tightenco/collect": "<5.5.33" }, "suggest": { - "illuminate/filesystem": "Required to use the composer class (5.7.*).", + "illuminate/filesystem": "Required to use the composer class (^6.0).", "moontoast/math": "Required to use ordered UUIDs (^1.1).", "ramsey/uuid": "Required to use Str::uuid() (^3.7).", - "symfony/process": "Required to use the composer class (^4.1).", - "symfony/var-dumper": "Required to use the dd function (^4.1)." + "symfony/process": "Required to use the composer class (^4.3.4).", + "symfony/var-dumper": "Required to use the dd function (^4.3.4).", + "vlucas/phpdotenv": "Required to use the Env class and env helper (^3.3)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.7-dev" + "dev-master": "6.0-dev" } }, "autoload": { @@ -219,35 +221,38 @@ ], "description": "The Illuminate Support package.", "homepage": "https://laravel.com", - "time": "2018-10-07T15:51:39+00:00" + "time": "2019-09-18T12:32:21+00:00" }, { "name": "mongodb/mongodb", - "version": "1.4.2", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/mongodb/mongo-php-library.git", - "reference": "bd148eab0493e38354e45e2cd7db59b90fdcad79" + "reference": "6405e91ba63955bcd187d3bab9ec7327d17cc4ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/bd148eab0493e38354e45e2cd7db59b90fdcad79", - "reference": "bd148eab0493e38354e45e2cd7db59b90fdcad79", + "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/6405e91ba63955bcd187d3bab9ec7327d17cc4ce", + "reference": "6405e91ba63955bcd187d3bab9ec7327d17cc4ce", "shasum": "" }, "require": { "ext-hash": "*", "ext-json": "*", - "ext-mongodb": "^1.5.0", - "php": ">=5.5" + "ext-mongodb": "^1.6", + "php": "^5.6 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.36 || ^6.4" + "phpunit/phpunit": "^5.7.27 || ^6.4 || ^8.3", + "sebastian/comparator": "^1.0 || ^2.0 || ^3.0", + "squizlabs/php_codesniffer": "^3.4", + "symfony/phpunit-bridge": "^4.4@dev" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4.x-dev" + "dev-master": "1.5.x-dev" } }, "autoload": { @@ -264,12 +269,12 @@ ], "authors": [ { - "name": "Jeremy Mikola", - "email": "jmikola@gmail.com" + "name": "Andreas Braun", + "email": "andreas.braun@mongodb.com" }, { - "name": "Derick Rethans", - "email": "github@derickrethans.nl" + "name": "Jeremy Mikola", + "email": "jmikola@gmail.com" }, { "name": "Katherine Walker", @@ -284,30 +289,38 @@ "mongodb", "persistence" ], - "time": "2018-07-18T14:33:41+00:00" + "time": "2019-09-09T13:42:06+00:00" }, { "name": "nesbot/carbon", - "version": "1.34.0", + "version": "2.24.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "1dbd3cb01c5645f3e7deda7aa46ef780d95fcc33" + "reference": "934459c5ac0658bc765ad1e53512c7c77adcac29" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/1dbd3cb01c5645f3e7deda7aa46ef780d95fcc33", - "reference": "1dbd3cb01c5645f3e7deda7aa46ef780d95fcc33", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/934459c5ac0658bc765ad1e53512c7c77adcac29", + "reference": "934459c5ac0658bc765ad1e53512c7c77adcac29", "shasum": "" }, "require": { - "php": ">=5.3.9", - "symfony/translation": "~2.6 || ~3.0 || ~4.0" + "ext-json": "*", + "php": "^7.1.8 || ^8.0", + "symfony/translation": "^3.4 || ^4.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "~2", - "phpunit/phpunit": "^4.8.35 || ^5.7" + "friendsofphp/php-cs-fixer": "^2.14 || ^3.0", + "kylekatarnls/multi-tester": "^1.1", + "phpmd/phpmd": "dev-php-7.1-compatibility", + "phpstan/phpstan": "^0.11", + "phpunit/phpunit": "^7.5 || ^8.0", + "squizlabs/php_codesniffer": "^3.4" }, + "bin": [ + "bin/carbon" + ], "type": "library", "extra": { "laravel": { @@ -318,7 +331,7 @@ }, "autoload": { "psr-4": { - "": "src/" + "Carbon\\": "src/Carbon/" } }, "notification-url": "https://packagist.org/downloads/", @@ -330,16 +343,20 @@ "name": "Brian Nesbitt", "email": "brian@nesbot.com", "homepage": "http://nesbot.com" + }, + { + "name": "kylekatarnls", + "homepage": "http://github.com/kylekatarnls" } ], - "description": "A simple API extension for DateTime.", + "description": "A API extension for DateTime that supports 281 different languages.", "homepage": "http://carbon.nesbot.com", "keywords": [ "date", "datetime", "time" ], - "time": "2018-09-20T19:36:25+00:00" + "time": "2019-08-31T16:37:55+00:00" }, { "name": "psr/container", @@ -440,16 +457,16 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.10.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "c79c051f5b3a46be09205c73b80b346e4153e494" + "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494", - "reference": "c79c051f5b3a46be09205c73b80b346e4153e494", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17", + "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17", "shasum": "" }, "require": { @@ -461,7 +478,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -495,38 +512,45 @@ "portable", "shim" ], - "time": "2018-09-21T13:07:52+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { "name": "symfony/translation", - "version": "v4.1.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "9f0b61e339160a466ebcde167a6c5521c810e304" + "reference": "28498169dd334095fa981827992f3a24d50fed0f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/9f0b61e339160a466ebcde167a6c5521c810e304", - "reference": "9f0b61e339160a466ebcde167a6c5521c810e304", + "url": "https://api.github.com/repos/symfony/translation/zipball/28498169dd334095fa981827992f3a24d50fed0f", + "reference": "28498169dd334095fa981827992f3a24d50fed0f", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/polyfill-mbstring": "~1.0" + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^1.1.6" }, "conflict": { "symfony/config": "<3.4", "symfony/dependency-injection": "<3.4", "symfony/yaml": "<3.4" }, + "provide": { + "symfony/translation-implementation": "1.0" + }, "require-dev": { "psr/log": "~1.0", "symfony/config": "~3.4|~4.0", "symfony/console": "~3.4|~4.0", "symfony/dependency-injection": "~3.4|~4.0", "symfony/finder": "~2.8|~3.0|~4.0", + "symfony/http-kernel": "~3.4|~4.0", "symfony/intl": "~3.4|~4.0", + "symfony/service-contracts": "^1.1.2", + "symfony/var-dumper": "~3.4|~4.0", "symfony/yaml": "~3.4|~4.0" }, "suggest": { @@ -537,7 +561,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -564,28 +588,88 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2018-10-02T16:36:10+00:00" + "time": "2019-08-26T08:55:16+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v1.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "325b17c24f3ee23cbecfa63ba809c6d89b5fa04a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/325b17c24f3ee23cbecfa63ba809c6d89b5fa04a", + "reference": "325b17c24f3ee23cbecfa63ba809c6d89b5fa04a", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "suggest": { + "symfony/translation-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-08-02T12:15:04+00:00" } ], "packages-dev": [ { "name": "blackfire/php-sdk", - "version": "v1.17.1", + "version": "v1.19.1", "source": { "type": "git", "url": "https://github.com/blackfireio/php-sdk.git", - "reference": "a85703a57df9da0d840f98c4e044ea70d3621444" + "reference": "638dce007b7f7690aef5586d4e1dd1217586bd31" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/blackfireio/php-sdk/zipball/a85703a57df9da0d840f98c4e044ea70d3621444", - "reference": "a85703a57df9da0d840f98c4e044ea70d3621444", + "url": "https://api.github.com/repos/blackfireio/php-sdk/zipball/638dce007b7f7690aef5586d4e1dd1217586bd31", + "reference": "638dce007b7f7690aef5586d4e1dd1217586bd31", "shasum": "" }, "require": { "composer/ca-bundle": "^1.0", "php": ">=5.2.0" }, + "require-dev": { + "symfony/http-client": "^4.3" + }, "suggest": { "ext-blackfire": "The C version of the Blackfire probe", "ext-zlib": "To push config to remote profiling targets" @@ -621,29 +705,29 @@ "uprofiler", "xhprof" ], - "time": "2018-07-16T09:18:18+00:00" + "time": "2019-08-14T09:24:23+00:00" }, { "name": "composer/ca-bundle", - "version": "1.1.3", + "version": "1.2.4", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660" + "reference": "10bb96592168a0f8e8f6dcde3532d9fa50b0b527" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/8afa52cd417f4ec417b4bfe86b68106538a87660", - "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/10bb96592168a0f8e8f6dcde3532d9fa50b0b527", + "reference": "10bb96592168a0f8e8f6dcde3532d9fa50b0b527", "shasum": "" }, "require": { "ext-openssl": "*", "ext-pcre": "*", - "php": "^5.3.2 || ^7.0" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5", + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8", "psr/log": "^1.0", "symfony/process": "^2.5 || ^3.0 || ^4.0" }, @@ -677,7 +761,7 @@ "ssl", "tls" ], - "time": "2018-10-18T06:09:13+00:00" + "time": "2019-08-30T08:44:50+00:00" }, { "name": "dealerdirect/phpcodesniffer-composer-installer", @@ -749,27 +833,29 @@ }, { "name": "doctrine/instantiator", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" + "reference": "a2c590166b2133a4633738648b6b064edae0814a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", + "reference": "a2c590166b2133a4633738648b6b064edae0814a", "shasum": "" }, "require": { "php": "^7.1" }, "require-dev": { - "athletic/athletic": "~0.1.8", + "doctrine/coding-standard": "^6.0", "ext-pdo": "*", "ext-phar": "*", - "phpunit/phpunit": "^6.2.3", - "squizlabs/php_codesniffer": "^3.0.2" + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { @@ -794,12 +880,12 @@ } ], "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ "constructor", "instantiate" ], - "time": "2017-07-22T11:58:36+00:00" + "time": "2019-03-17T17:37:11+00:00" }, { "name": "guzzlehttp/guzzle", @@ -919,32 +1005,37 @@ }, { "name": "guzzlehttp/psr7", - "version": "1.4.2", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + "reference": "239400de7a173fe9901b9ac7c06497751f00727a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", - "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a", + "reference": "239400de7a173fe9901b9ac7c06497751f00727a", "shasum": "" }, "require": { "php": ">=5.4.0", - "psr/http-message": "~1.0" + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" }, "provide": { "psr/http-message-implementation": "1.0" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "ext-zlib": "*", + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" + }, + "suggest": { + "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -974,13 +1065,14 @@ "keywords": [ "http", "message", + "psr-7", "request", "response", "stream", "uri", "url" ], - "time": "2017-03-20T17:10:46+00:00" + "time": "2019-07-01T23:21:34+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -1125,16 +1217,16 @@ }, { "name": "mockery/mockery", - "version": "1.2.0", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "100633629bf76d57430b86b7098cd6beb996a35a" + "reference": "4eff936d83eb809bde2c57a3cea0ee9643769031" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/100633629bf76d57430b86b7098cd6beb996a35a", - "reference": "100633629bf76d57430b86b7098cd6beb996a35a", + "url": "https://api.github.com/repos/mockery/mockery/zipball/4eff936d83eb809bde2c57a3cea0ee9643769031", + "reference": "4eff936d83eb809bde2c57a3cea0ee9643769031", "shasum": "" }, "require": { @@ -1143,7 +1235,7 @@ "php": ">=5.6.0" }, "require-dev": { - "phpunit/phpunit": "~5.7.10|~6.5|~7.0" + "phpunit/phpunit": "~5.7.10|~6.5|~7.0|~8.0" }, "type": "library", "extra": { @@ -1186,20 +1278,20 @@ "test double", "testing" ], - "time": "2018-10-02T21:52:37+00:00" + "time": "2019-08-07T15:01:07+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.8.1", + "version": "1.9.3", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8" + "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", - "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/007c053ae6f31bba39dfa19a7726f56e9763bbea", + "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea", "shasum": "" }, "require": { @@ -1234,7 +1326,7 @@ "object", "object graph" ], - "time": "2018-06-11T23:09:50+00:00" + "time": "2019-08-09T12:45:53+00:00" }, { "name": "nikic/php-parser", @@ -1289,22 +1381,22 @@ }, { "name": "phar-io/manifest", - "version": "1.0.1", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", - "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", "shasum": "" }, "require": { "ext-dom": "*", "ext-phar": "*", - "phar-io/version": "^1.0.1", + "phar-io/version": "^2.0", "php": "^5.6 || ^7.0" }, "type": "library", @@ -1340,20 +1432,20 @@ } ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "time": "2017-03-05T18:14:27+00:00" + "time": "2018-07-08T19:23:20+00:00" }, { "name": "phar-io/version", - "version": "1.0.1", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", - "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", "shasum": "" }, "require": { @@ -1387,7 +1479,7 @@ } ], "description": "Library for handling version information and constraints", - "time": "2017-03-05T17:38:23+00:00" + "time": "2018-07-08T19:19:57+00:00" }, { "name": "php-coveralls/php-coveralls", @@ -1523,16 +1615,16 @@ }, { "name": "phpspec/prophecy", - "version": "1.8.0", + "version": "1.8.1", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/1927e75f4ed19131ec9bcc3b002e07fb1173ee76", + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76", "shasum": "" }, "require": { @@ -1553,8 +1645,8 @@ } }, "autoload": { - "psr-0": { - "Prophecy\\": "src/" + "psr-4": { + "Prophecy\\": "src/Prophecy" } }, "notification-url": "https://packagist.org/downloads/", @@ -1582,44 +1674,44 @@ "spy", "stub" ], - "time": "2018-08-05T17:53:17+00:00" + "time": "2019-06-13T12:50:23+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "5.3.2", + "version": "7.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "c89677919c5dd6d3b3852f230a663118762218ac" + "reference": "aa0d179a13284c7420fc281fc32750e6cc7c9e2f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c89677919c5dd6d3b3852f230a663118762218ac", - "reference": "c89677919c5dd6d3b3852f230a663118762218ac", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aa0d179a13284c7420fc281fc32750e6cc7c9e2f", + "reference": "aa0d179a13284c7420fc281fc32750e6cc7c9e2f", "shasum": "" }, "require": { "ext-dom": "*", "ext-xmlwriter": "*", - "php": "^7.0", - "phpunit/php-file-iterator": "^1.4.2", + "php": "^7.2", + "phpunit/php-file-iterator": "^2.0.2", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^2.0.1", + "phpunit/php-token-stream": "^3.1.1", "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^3.0", + "sebastian/environment": "^4.2.2", "sebastian/version": "^2.0.1", - "theseer/tokenizer": "^1.1" + "theseer/tokenizer": "^1.1.3" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^8.2.2" }, "suggest": { - "ext-xdebug": "^2.5.5" + "ext-xdebug": "^2.7.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.3.x-dev" + "dev-master": "7.0-dev" } }, "autoload": { @@ -1645,29 +1737,32 @@ "testing", "xunit" ], - "time": "2018-04-06T15:36:58+00:00" + "time": "2019-09-17T06:24:36+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "1.4.5", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + "reference": "050bedf145a257b1ff02746c31894800e5122946" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", - "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946", + "reference": "050bedf145a257b1ff02746c31894800e5122946", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -1682,7 +1777,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -1692,7 +1787,7 @@ "filesystem", "iterator" ], - "time": "2017-11-27T13:52:08+00:00" + "time": "2018-09-13T20:33:42+00:00" }, { "name": "phpunit/php-text-template", @@ -1737,28 +1832,28 @@ }, { "name": "phpunit/php-timer", - "version": "1.0.9", + "version": "2.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e", + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "2.1-dev" } }, "autoload": { @@ -1773,7 +1868,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -1782,33 +1877,33 @@ "keywords": [ "timer" ], - "time": "2017-02-26T11:10:40+00:00" + "time": "2019-06-07T04:22:29+00:00" }, { "name": "phpunit/php-token-stream", - "version": "2.0.2", + "version": "3.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "791198a2c6254db10131eecfe8c06670700904db" + "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", - "reference": "791198a2c6254db10131eecfe8c06670700904db", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/995192df77f63a59e47f025390d2d1fdf8f425ff", + "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": "^7.0" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^6.2.4" + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "3.1-dev" } }, "autoload": { @@ -1831,57 +1926,56 @@ "keywords": [ "tokenizer" ], - "time": "2017-11-27T05:48:46+00:00" + "time": "2019-09-17T06:23:10+00:00" }, { "name": "phpunit/phpunit", - "version": "6.5.13", + "version": "8.3.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "0973426fb012359b2f18d3bd1e90ef1172839693" + "reference": "302faed7059fde575cf3403a78c730c5e3a62750" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0973426fb012359b2f18d3bd1e90ef1172839693", - "reference": "0973426fb012359b2f18d3bd1e90ef1172839693", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/302faed7059fde575cf3403a78c730c5e3a62750", + "reference": "302faed7059fde575cf3403a78c730c5e3a62750", "shasum": "" }, "require": { + "doctrine/instantiator": "^1.2.0", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", - "myclabs/deep-copy": "^1.6.1", - "phar-io/manifest": "^1.0.1", - "phar-io/version": "^1.0", - "php": "^7.0", - "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^5.3", - "phpunit/php-file-iterator": "^1.4.3", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.9.1", + "phar-io/manifest": "^1.0.3", + "phar-io/version": "^2.0.1", + "php": "^7.2", + "phpspec/prophecy": "^1.8.1", + "phpunit/php-code-coverage": "^7.0.7", + "phpunit/php-file-iterator": "^2.0.2", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^1.0.9", - "phpunit/phpunit-mock-objects": "^5.0.9", - "sebastian/comparator": "^2.1", - "sebastian/diff": "^2.0", - "sebastian/environment": "^3.1", - "sebastian/exporter": "^3.1", - "sebastian/global-state": "^2.0", + "phpunit/php-timer": "^2.1.2", + "sebastian/comparator": "^3.0.2", + "sebastian/diff": "^3.0.2", + "sebastian/environment": "^4.2.2", + "sebastian/exporter": "^3.1.1", + "sebastian/global-state": "^3.0.0", "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^1.0", + "sebastian/resource-operations": "^2.0.1", + "sebastian/type": "^1.1.3", "sebastian/version": "^2.0.1" }, - "conflict": { - "phpdocumentor/reflection-docblock": "3.0.2", - "phpunit/dbunit": "<3.0" - }, "require-dev": { "ext-pdo": "*" }, "suggest": { + "ext-soap": "*", "ext-xdebug": "*", - "phpunit/php-invoker": "^1.1" + "phpunit/php-invoker": "^2.0.0" }, "bin": [ "phpunit" @@ -1889,7 +1983,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "6.5.x-dev" + "dev-master": "8.3-dev" } }, "autoload": { @@ -1915,66 +2009,7 @@ "testing", "xunit" ], - "time": "2018-09-08T15:10:43+00:00" - }, - { - "name": "phpunit/phpunit-mock-objects", - "version": "5.0.10", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/cd1cf05c553ecfec36b170070573e540b67d3f1f", - "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.5", - "php": "^7.0", - "phpunit/php-text-template": "^1.2.1", - "sebastian/exporter": "^3.1" - }, - "conflict": { - "phpunit/phpunit": "<6.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.5.11" - }, - "suggest": { - "ext-soap": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Mock Object library for PHPUnit", - "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", - "keywords": [ - "mock", - "xunit" - ], - "time": "2018-08-09T05:50:03+00:00" + "time": "2019-09-14T09:12:03+00:00" }, { "name": "pimple/pimple", @@ -2078,16 +2113,16 @@ }, { "name": "psr/log", - "version": "1.0.2", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", "shasum": "" }, "require": { @@ -2121,7 +2156,47 @@ "psr", "psr-3" ], - "time": "2016-10-10T12:19:37+00:00" + "time": "2018-11-20T15:27:04+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "time": "2019-03-08T08:55:37+00:00" }, { "name": "sami/sami", @@ -2233,30 +2308,30 @@ }, { "name": "sebastian/comparator", - "version": "2.1.3", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" + "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", - "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da", + "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da", "shasum": "" }, "require": { - "php": "^7.0", - "sebastian/diff": "^2.0 || ^3.0", + "php": "^7.1", + "sebastian/diff": "^3.0", "sebastian/exporter": "^3.1" }, "require-dev": { - "phpunit/phpunit": "^6.4" + "phpunit/phpunit": "^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -2293,32 +2368,33 @@ "compare", "equality" ], - "time": "2018-02-01T13:46:46+00:00" + "time": "2018-07-12T15:12:46+00:00" }, { "name": "sebastian/diff", - "version": "2.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd" + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", - "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29", "shasum": "" }, "require": { - "php": "^7.0" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^6.2" + "phpunit/phpunit": "^7.5 || ^8.0", + "symfony/process": "^2 || ^3.3 || ^4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -2343,34 +2419,40 @@ "description": "Diff implementation", "homepage": "https://github.com/sebastianbergmann/diff", "keywords": [ - "diff" + "diff", + "udiff", + "unidiff", + "unified diff" ], - "time": "2017-08-03T08:09:46+00:00" + "time": "2019-02-04T06:01:07+00:00" }, { "name": "sebastian/environment", - "version": "3.1.0", + "version": "4.2.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" + "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", - "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/f2a2c8e1c97c11ace607a7a667d73d47c19fe404", + "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404", "shasum": "" }, "require": { - "php": "^7.0" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^6.1" + "phpunit/phpunit": "^7.5" + }, + "suggest": { + "ext-posix": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1.x-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -2395,20 +2477,20 @@ "environment", "hhvm" ], - "time": "2017-07-01T08:51:00+00:00" + "time": "2019-05-05T09:05:15+00:00" }, { "name": "sebastian/exporter", - "version": "3.1.0", + "version": "3.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", - "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e", + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e", "shasum": "" }, "require": { @@ -2435,6 +2517,10 @@ "BSD-3-Clause" ], "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, { "name": "Jeff Welch", "email": "whatthejeff@gmail.com" @@ -2443,17 +2529,13 @@ "name": "Volker Dusch", "email": "github@wallbash.com" }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, { "name": "Adam Harvey", "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" } ], "description": "Provides the functionality to export PHP variables for visualization", @@ -2462,27 +2544,30 @@ "export", "exporter" ], - "time": "2017-04-03T13:19:02+00:00" + "time": "2019-09-14T09:02:43+00:00" }, { "name": "sebastian/global-state", - "version": "2.0.0", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", + "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", "shasum": "" }, "require": { - "php": "^7.0" + "php": "^7.2", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "ext-dom": "*", + "phpunit/phpunit": "^8.0" }, "suggest": { "ext-uopz": "*" @@ -2490,7 +2575,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -2513,7 +2598,7 @@ "keywords": [ "global state" ], - "time": "2017-04-27T15:39:26+00:00" + "time": "2019-02-01T05:30:01+00:00" }, { "name": "sebastian/object-enumerator", @@ -2662,25 +2747,25 @@ }, { "name": "sebastian/resource-operations", - "version": "1.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9", + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9", "shasum": "" }, "require": { - "php": ">=5.6.0" + "php": "^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -2700,7 +2785,53 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2015-07-28T20:34:47+00:00" + "time": "2018-10-04T04:07:39+00:00" + }, + { + "name": "sebastian/type", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/3aaaa15fa71d27650d62a948be022fe3b48541a3", + "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3", + "shasum": "" + }, + "require": { + "php": "^7.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "time": "2019-07-02T08:10:15+00:00" }, { "name": "sebastian/version", @@ -2747,21 +2878,21 @@ }, { "name": "slevomat/coding-standard", - "version": "4.8.5", + "version": "4.8.7", "source": { "type": "git", "url": "https://github.com/slevomat/coding-standard.git", - "reference": "057f3f154cf4888b60eb4cdffadc509a3ae9dccd" + "reference": "bff96313d8c7c2ba57a4edb13c1c141df8988c58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/057f3f154cf4888b60eb4cdffadc509a3ae9dccd", - "reference": "057f3f154cf4888b60eb4cdffadc509a3ae9dccd", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/bff96313d8c7c2ba57a4edb13c1c141df8988c58", + "reference": "bff96313d8c7c2ba57a4edb13c1c141df8988c58", "shasum": "" }, "require": { "php": "^7.1", - "squizlabs/php_codesniffer": "^3.3.0" + "squizlabs/php_codesniffer": "^3.4.0" }, "require-dev": { "jakub-onderka/php-parallel-lint": "1.0.0", @@ -2769,7 +2900,7 @@ "phpstan/phpstan": "0.9.2", "phpstan/phpstan-phpunit": "0.9.4", "phpstan/phpstan-strict-rules": "0.9", - "phpunit/phpunit": "7.3.5" + "phpunit/phpunit": "7.5.1" }, "type": "phpcodesniffer-standard", "autoload": { @@ -2782,20 +2913,20 @@ "MIT" ], "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "time": "2018-10-05T12:10:21+00:00" + "time": "2019-01-03T13:15:50+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.3.2", + "version": "3.4.2", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "6ad28354c04b364c3c71a34e4a18b629cc3b231e" + "reference": "b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/6ad28354c04b364c3c71a34e4a18b629cc3b231e", - "reference": "6ad28354c04b364c3c71a34e4a18b629cc3b231e", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8", + "reference": "b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8", "shasum": "" }, "require": { @@ -2828,25 +2959,25 @@ } ], "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "http://www.squizlabs.com/php-codesniffer", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", "keywords": [ "phpcs", "standards" ], - "time": "2018-09-23T23:08:17+00:00" + "time": "2019-04-10T23:49:02+00:00" }, { "name": "symfony/config", - "version": "v4.1.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "b3d4d7b567d7a49e6dfafb6d4760abc921177c96" + "reference": "07d49c0f823e0bc367c6d84e35b61419188a5ece" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/b3d4d7b567d7a49e6dfafb6d4760abc921177c96", - "reference": "b3d4d7b567d7a49e6dfafb6d4760abc921177c96", + "url": "https://api.github.com/repos/symfony/config/zipball/07d49c0f823e0bc367c6d84e35b61419188a5ece", + "reference": "07d49c0f823e0bc367c6d84e35b61419188a5ece", "shasum": "" }, "require": { @@ -2861,6 +2992,7 @@ "symfony/dependency-injection": "~3.4|~4.0", "symfony/event-dispatcher": "~3.4|~4.0", "symfony/finder": "~3.4|~4.0", + "symfony/messenger": "~4.1", "symfony/yaml": "~3.4|~4.0" }, "suggest": { @@ -2869,7 +3001,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -2896,40 +3028,47 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2018-09-08T13:24:10+00:00" + "time": "2019-08-26T08:26:39+00:00" }, { "name": "symfony/console", - "version": "v4.1.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "dc7122fe5f6113cfaba3b3de575d31112c9aa60b" + "reference": "de63799239b3881b8a08f8481b22348f77ed7b36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/dc7122fe5f6113cfaba3b3de575d31112c9aa60b", - "reference": "dc7122fe5f6113cfaba3b3de575d31112c9aa60b", + "url": "https://api.github.com/repos/symfony/console/zipball/de63799239b3881b8a08f8481b22348f77ed7b36", + "reference": "de63799239b3881b8a08f8481b22348f77ed7b36", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/polyfill-mbstring": "~1.0" + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/service-contracts": "^1.1" }, "conflict": { "symfony/dependency-injection": "<3.4", + "symfony/event-dispatcher": "<4.3", "symfony/process": "<3.3" }, + "provide": { + "psr/log-implementation": "1.0" + }, "require-dev": { "psr/log": "~1.0", "symfony/config": "~3.4|~4.0", "symfony/dependency-injection": "~3.4|~4.0", - "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/event-dispatcher": "^4.3", "symfony/lock": "~3.4|~4.0", - "symfony/process": "~3.4|~4.0" + "symfony/process": "~3.4|~4.0", + "symfony/var-dumper": "^4.3" }, "suggest": { - "psr/log-implementation": "For using the console logger", + "psr/log": "For using the console logger", "symfony/event-dispatcher": "", "symfony/lock": "", "symfony/process": "" @@ -2937,7 +3076,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -2964,20 +3103,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-10-03T08:15:46+00:00" + "time": "2019-08-26T08:26:39+00:00" }, { "name": "symfony/filesystem", - "version": "v4.1.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "596d12b40624055c300c8b619755b748ca5cf0b5" + "reference": "9abbb7ef96a51f4d7e69627bc6f63307994e4263" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/596d12b40624055c300c8b619755b748ca5cf0b5", - "reference": "596d12b40624055c300c8b619755b748ca5cf0b5", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/9abbb7ef96a51f4d7e69627bc6f63307994e4263", + "reference": "9abbb7ef96a51f4d7e69627bc6f63307994e4263", "shasum": "" }, "require": { @@ -2987,7 +3126,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -3014,20 +3153,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2018-10-02T12:40:59+00:00" + "time": "2019-08-20T14:07:54+00:00" }, { "name": "symfony/finder", - "version": "v4.1.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "1f17195b44543017a9c9b2d437c670627e96ad06" + "reference": "86c1c929f0a4b24812e1eb109262fc3372c8e9f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/1f17195b44543017a9c9b2d437c670627e96ad06", - "reference": "1f17195b44543017a9c9b2d437c670627e96ad06", + "url": "https://api.github.com/repos/symfony/finder/zipball/86c1c929f0a4b24812e1eb109262fc3372c8e9f2", + "reference": "86c1c929f0a4b24812e1eb109262fc3372c8e9f2", "shasum": "" }, "require": { @@ -3036,7 +3175,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -3063,20 +3202,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-10-03T08:47:56+00:00" + "time": "2019-08-14T12:26:46+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.10.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" + "reference": "550ebaac289296ce228a706d0867afc34687e3f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4", "shasum": "" }, "require": { @@ -3088,7 +3227,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -3104,13 +3243,13 @@ "MIT" ], "authors": [ - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - }, { "name": "Gert de Pagter", "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill for ctype functions", @@ -3121,20 +3260,78 @@ "polyfill", "portable" ], - "time": "2018-08-06T14:22:27+00:00" + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/2ceb49eaccb9352bff54d22570276bb75ba4a188", + "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-08-06T08:03:45+00:00" }, { "name": "symfony/process", - "version": "v4.1.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "ee33c0322a8fee0855afcc11fff81e6b1011b529" + "reference": "e89969c00d762349f078db1128506f7f3dcc0d4a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/ee33c0322a8fee0855afcc11fff81e6b1011b529", - "reference": "ee33c0322a8fee0855afcc11fff81e6b1011b529", + "url": "https://api.github.com/repos/symfony/process/zipball/e89969c00d762349f078db1128506f7f3dcc0d4a", + "reference": "e89969c00d762349f078db1128506f7f3dcc0d4a", "shasum": "" }, "require": { @@ -3143,7 +3340,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -3170,29 +3367,88 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2018-10-02T12:40:59+00:00" + "time": "2019-08-26T08:26:39+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v1.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "ea7263d6b6d5f798b56a45a5b8d686725f2719a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/ea7263d6b6d5f798b56a45a5b8d686725f2719a3", + "reference": "ea7263d6b6d5f798b56a45a5b8d686725f2719a3", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-08-20T14:44:19+00:00" }, { "name": "symfony/stopwatch", - "version": "v4.1.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "5bfc064125b73ff81229e19381ce1c34d3416f4b" + "reference": "1e4ff456bd625be5032fac9be4294e60442e9b71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5bfc064125b73ff81229e19381ce1c34d3416f4b", - "reference": "5bfc064125b73ff81229e19381ce1c34d3416f4b", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/1e4ff456bd625be5032fac9be4294e60442e9b71", + "reference": "1e4ff456bd625be5032fac9be4294e60442e9b71", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.1.3", + "symfony/service-contracts": "^1.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -3219,20 +3475,20 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2018-10-02T12:40:59+00:00" + "time": "2019-08-07T11:52:19+00:00" }, { "name": "symfony/yaml", - "version": "v4.1.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "367e689b2fdc19965be435337b50bc8adf2746c9" + "reference": "5a0b7c32dc3ec56fd4abae8a4a71b0cf05013686" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/367e689b2fdc19965be435337b50bc8adf2746c9", - "reference": "367e689b2fdc19965be435337b50bc8adf2746c9", + "url": "https://api.github.com/repos/symfony/yaml/zipball/5a0b7c32dc3ec56fd4abae8a4a71b0cf05013686", + "reference": "5a0b7c32dc3ec56fd4abae8a4a71b0cf05013686", "shasum": "" }, "require": { @@ -3251,7 +3507,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -3278,20 +3534,20 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2018-10-02T16:36:10+00:00" + "time": "2019-08-20T14:27:59+00:00" }, { "name": "theseer/tokenizer", - "version": "1.1.0", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", "shasum": "" }, "require": { @@ -3318,36 +3574,36 @@ } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2017-04-07T12:08:54+00:00" + "time": "2019-06-13T22:48:21+00:00" }, { "name": "twig/twig", - "version": "v2.5.0", + "version": "v2.11.3", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "6a5f676b77a90823c2d4eaf76137b771adf31323" + "reference": "699ed2342557c88789a15402de5eb834dedd6792" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/6a5f676b77a90823c2d4eaf76137b771adf31323", - "reference": "6a5f676b77a90823c2d4eaf76137b771adf31323", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/699ed2342557c88789a15402de5eb834dedd6792", + "reference": "699ed2342557c88789a15402de5eb834dedd6792", "shasum": "" }, "require": { "php": "^7.0", "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "~1.0" + "symfony/polyfill-mbstring": "^1.3" }, "require-dev": { "psr/container": "^1.0", "symfony/debug": "^2.7", - "symfony/phpunit-bridge": "^3.3" + "symfony/phpunit-bridge": "^3.4.19|^4.1.8|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.5-dev" + "dev-master": "2.11-dev" } }, "autoload": { @@ -3385,7 +3641,7 @@ "keywords": [ "templating" ], - "time": "2018-07-13T07:18:09+00:00" + "time": "2019-06-18T15:37:11+00:00" } ], "aliases": [], diff --git a/docker-compose.yml b/docker-compose.yml index 6d190663..fa1f6ca3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,11 +11,9 @@ services: - DB_HOST=db db: - image: mongo:4.0 - command: mongod --smallfiles + image: mongo:4.2 volumes: - db:/data/db - - .:/var/www/html mkdocs: image: polinux/mkdocs diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile index 464546fd..1d656b6a 100644 --- a/docker/php/Dockerfile +++ b/docker/php/Dockerfile @@ -1,9 +1,8 @@ -FROM leroymerlinbr/php:7.2 +FROM leroymerlinbr/php:7.3 USER root -RUN pecl install xdebug \ - && docker-php-ext-enable xdebug +RUN docker-php-ext-enable xdebug COPY custom.ini /usr/local/etc/php/conf.d/custom.ini diff --git a/phpunit.xml b/phpunit.xml index ae4a836b..20a587c1 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -9,7 +9,6 @@ convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" - syntaxCheck="false" > diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d2c88298..71524717 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -9,7 +9,6 @@ convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" - syntaxCheck="false" > diff --git a/src/Connection/Manager.php b/src/Connection/Manager.php index 4b508837..b0cb0b81 100644 --- a/src/Connection/Manager.php +++ b/src/Connection/Manager.php @@ -20,13 +20,6 @@ */ class Manager { - /** - * Singleton instance of the manager. - * - * @var Manager - */ - protected static $singleton; - /** * Container being used by Mongolid. * @@ -93,7 +86,5 @@ protected function init() $this->container = new IlluminateContainer(); Container::setContainer($this->container); - - static::$singleton = $this; } } diff --git a/tests/Integration/AttributesKeyTest.php b/tests/Integration/AttributesKeyTest.php index 720b1000..80ac72c9 100644 --- a/tests/Integration/AttributesKeyTest.php +++ b/tests/Integration/AttributesKeyTest.php @@ -5,7 +5,7 @@ class AttributesKeyTest extends IntegrationTestCase { - public function testAttributesKeysShouldNotBeReserved() + public function testAttributesKeysShouldNotBeReserved(): void { $user = new ReferencedUser(); $user->name = 'John'; diff --git a/tests/Integration/BulkWriteTest.php b/tests/Integration/BulkWriteTest.php index 86e24834..17de8c6e 100644 --- a/tests/Integration/BulkWriteTest.php +++ b/tests/Integration/BulkWriteTest.php @@ -8,7 +8,7 @@ class BulkWriteTest extends IntegrationTestCase { - public function testShouldRunMultipleUpdateOperations() + public function testShouldRunMultipleUpdateOperations(): void { $bob = $this->createUser('Bob'); $john = $this->createUser('John'); diff --git a/tests/Integration/DateQueriesTest.php b/tests/Integration/DateQueriesTest.php index 4fbd5c00..9ec4f0dc 100644 --- a/tests/Integration/DateQueriesTest.php +++ b/tests/Integration/DateQueriesTest.php @@ -7,7 +7,7 @@ class DateQueriesTest extends IntegrationTestCase { - public function testShouldRetrieveDocumentsUsingDateFilters() + public function testShouldRetrieveDocumentsUsingDateFilters(): void { // Set $user = new ReferencedUser(); @@ -47,7 +47,7 @@ public function testShouldRetrieveDocumentsUsingDateFilters() $this->assertCount(0, $emptyResult); } - public function testShouldRetrieveDocumentsUsingDateFiltersWithRelativeDates() + public function testShouldRetrieveDocumentsUsingDateFiltersWithRelativeDates(): void { // Set $user = new ReferencedUser(); diff --git a/tests/Integration/EmbedsManyRelationTest.php b/tests/Integration/EmbedsManyRelationTest.php index de2dd539..0d452109 100644 --- a/tests/Integration/EmbedsManyRelationTest.php +++ b/tests/Integration/EmbedsManyRelationTest.php @@ -1,6 +1,7 @@ createUser('Chuck'); @@ -65,7 +66,7 @@ public function testShouldRetrieveSiblingsOfUser() $this->assertSiblings([$chuck], $john); } - public function testShouldRetrieveGrandsonsOfUserUsingCustomKey() + public function testShouldRetrieveGrandsonsOfUserUsingCustomKey(): void { // create grandson $chuck = $this->createUser('Chuck'); @@ -128,8 +129,8 @@ public function testShouldRetrieveGrandsonsOfUserUsingCustomKey() $this->assertInstanceOf(EmbeddedUser::class, $john->grandsons->first()); $this->assertEquals( - array_except($chuck->toArray(), 'updated_at'), - array_except($john->grandsons->first()->toArray(), 'updated_at') + Arr::except($chuck->toArray(), 'updated_at'), + Arr::except($john->grandsons->first()->toArray(), 'updated_at') ); $chuck = $john->grandsons->first(); @@ -141,8 +142,8 @@ public function testShouldRetrieveGrandsonsOfUserUsingCustomKey() $this->assertInstanceOf(EmbeddedUser::class, $john->grandsons->first()); $this->assertEquals( - array_except($chuck->toArray(), 'updated_at'), - array_except($john->grandsons->first()->toArray(), 'updated_at') + Arr::except($chuck->toArray(), 'updated_at'), + Arr::except($john->grandsons->first()->toArray(), 'updated_at') ); } diff --git a/tests/Integration/EmbedsOneRelationTest.php b/tests/Integration/EmbedsOneRelationTest.php index fc2b150c..9ac385a1 100644 --- a/tests/Integration/EmbedsOneRelationTest.php +++ b/tests/Integration/EmbedsOneRelationTest.php @@ -8,7 +8,7 @@ class EmbedsOneRelationTest extends IntegrationTestCase { - public function testShouldRetrieveParentOfUser() + public function testShouldRetrieveParentOfUser(): void { // create parent $chuck = $this->createUser('Chuck'); @@ -57,7 +57,7 @@ public function testShouldRetrieveParentOfUser() $this->assertParent($chuck, $john); } - public function testShouldRetrieveSonOfUserUsingCustomKey() + public function testShouldRetrieveSonOfUserUsingCustomKey(): void { // create parent $chuck = $this->createUser('Chuck'); @@ -105,7 +105,7 @@ public function testShouldRetrieveSonOfUserUsingCustomKey() $this->assertSon($chuck, $john); } - public function testShouldCatchInvalidFieldNameOnRelations() + public function testShouldCatchInvalidFieldNameOnRelations(): void { // Set $user = new EmbeddedUser(); diff --git a/tests/Integration/IntegrationTestCase.php b/tests/Integration/IntegrationTestCase.php index 15227958..44839da3 100644 --- a/tests/Integration/IntegrationTestCase.php +++ b/tests/Integration/IntegrationTestCase.php @@ -10,17 +10,17 @@ class IntegrationTestCase extends TestCase use DropDatabaseTrait; use SetupConnectionTrait; - protected function setUp() + protected function setUp(): void { parent::setUp(); - $host = env('DB_HOST', 'localhost'); - $database = env('DB_DATABASE', 'testing'); + $host = getenv('DB_HOST') ?: 'localhost'; + $database = getenv('DB_DATABASE') ?: 'testing'; $this->setupConnection($host, $database); $this->dropDatabase(); } - protected function tearDown() + protected function tearDown(): void { $this->dropDatabase(); parent::tearDown(); diff --git a/tests/Integration/PersistedDataTest.php b/tests/Integration/PersistedDataTest.php index ef10218d..83287fc3 100644 --- a/tests/Integration/PersistedDataTest.php +++ b/tests/Integration/PersistedDataTest.php @@ -11,13 +11,13 @@ class PersistedDataTest extends IntegrationTestCase */ private $_id; - protected function setUp() + protected function setUp(): void { parent::setUp(); $this->_id = new ObjectId('5bcb310783a7fcdf1bf1a672'); } - public function testSaveInsertingData() + public function testSaveInsertingData(): void { // Set $user = $this->getUser(); @@ -50,7 +50,7 @@ public function testSaveInsertingData() $this->assertSame($expected, $result->toArray()); } - public function testSaveUpdatingData() + public function testSaveUpdatingData(): void { // Set $user = $this->getUser(true); @@ -92,7 +92,7 @@ public function testSaveUpdatingData() $this->assertEquals($expected, $result->toArray()); } - public function testUpdateData() + public function testUpdateData(): void { // Set $user = $this->getUser(true); diff --git a/tests/Integration/ReferencesManyRelationTest.php b/tests/Integration/ReferencesManyRelationTest.php index 28878b2d..85044ca0 100644 --- a/tests/Integration/ReferencesManyRelationTest.php +++ b/tests/Integration/ReferencesManyRelationTest.php @@ -7,7 +7,7 @@ class ReferencesManyRelationTest extends IntegrationTestCase { - public function testShouldRetrieveSiblingsOfUser() + public function testShouldRetrieveSiblingsOfUser(): void { // create sibling $chuck = $this->createUser('Chuck'); @@ -68,7 +68,7 @@ public function testShouldRetrieveSiblingsOfUser() $this->assertSiblings([$chuck], $john); } - public function testShouldRetrieveGrandsonsOfUserUsingCustomKey() + public function testShouldRetrieveGrandsonsOfUserUsingCustomKey(): void { // create sibling $chuck = $this->createUser('Chuck', '010'); diff --git a/tests/Integration/ReferencesOneRelationTest.php b/tests/Integration/ReferencesOneRelationTest.php index 977a0817..d2a53770 100644 --- a/tests/Integration/ReferencesOneRelationTest.php +++ b/tests/Integration/ReferencesOneRelationTest.php @@ -7,7 +7,7 @@ class ReferencesOneRelationTest extends IntegrationTestCase { - public function testShouldRetrieveParentOfUser() + public function testShouldRetrieveParentOfUser(): void { // create parent $chuck = $this->createUser('Chuck'); @@ -55,7 +55,7 @@ public function testShouldRetrieveParentOfUser() $this->assertParent($chuck, $john); } - public function testShouldRetrieveSonOfUserUsingCustomKey() + public function testShouldRetrieveSonOfUserUsingCustomKey(): void { // create parent $chuck = $this->createUser('Chuck', '010'); @@ -103,7 +103,7 @@ public function testShouldRetrieveSonOfUserUsingCustomKey() $this->assertSon($chuck, $john); } - public function testShouldCatchInvalidRelations() + public function testShouldCatchInvalidRelations(): void { // Set $user = new ReferencedUser(); diff --git a/tests/Integration/RewindableCursorTest.php b/tests/Integration/RewindableCursorTest.php index 2b02f0b4..9607e391 100644 --- a/tests/Integration/RewindableCursorTest.php +++ b/tests/Integration/RewindableCursorTest.php @@ -6,7 +6,7 @@ class RewindableCursorTest extends IntegrationTestCase { - public function testCursorShouldBeRewindableAndSerializable() + public function testCursorShouldBeRewindableAndSerializable(): void { $this->createUser('Bob'); $this->createUser('Mary'); diff --git a/tests/Unit/Connection/ConnectionTest.php b/tests/Unit/Connection/ConnectionTest.php index b8e82bb2..e130a360 100644 --- a/tests/Unit/Connection/ConnectionTest.php +++ b/tests/Unit/Connection/ConnectionTest.php @@ -6,7 +6,7 @@ class ConnectionTest extends TestCase { - public function testShouldConstructANewConnection() + public function testShouldConstructANewConnection(): void { // Set $server = 'mongodb://my-server/my_db'; @@ -15,13 +15,14 @@ public function testShouldConstructANewConnection() // Actions $connection = new Connection($server, $options, $driverOptions); + $result = $this->getProtected($connection, 'client'); // Assertions - $this->assertAttributeInstanceOf(Client::class, 'client', $connection); - $this->assertAttributeSame('my_db', 'defaultDatabase', $connection); + $this->assertInstanceOf(Client::class, $result); + $this->assertSame('my_db', $connection->defaultDatabase); } - public function testShouldDetermineDatabaseFromACluster() + public function testShouldDetermineDatabaseFromACluster(): void { // Set $server = 'mongodb://my-server,other-server/my_db?replicaSet=someReplica'; @@ -30,13 +31,14 @@ public function testShouldDetermineDatabaseFromACluster() // Actions $connection = new Connection($server, $options, $driverOptions); + $result = $this->getProtected($connection, 'client'); // Assertions - $this->assertAttributeInstanceOf(Client::class, 'client', $connection); - $this->assertAttributeSame('my_db', 'defaultDatabase', $connection); + $this->assertInstanceOf(Client::class, $result); + $this->assertSame('my_db', $connection->defaultDatabase); } - public function testShouldGetConnection() + public function testShouldGetConnection(): void { // Set $server = 'mongodb://my-server/my_db'; @@ -52,7 +54,7 @@ public function testShouldGetConnection() $client = $connection->getClient(); // Assertions - $this->assertAttributeSame($expectedParameters['uri'], 'uri', $client); - $this->assertAttributeSame($expectedParameters['typeMap'], 'typeMap', $client); + $this->assertSame($expectedParameters['uri'], (string) $client); + $this->assertSame($expectedParameters['typeMap'], $client->getTypeMap()); } } diff --git a/tests/Unit/Connection/ManagerTest.php b/tests/Unit/Connection/ManagerTest.php index 7bcdb9ee..ed95df5a 100644 --- a/tests/Unit/Connection/ManagerTest.php +++ b/tests/Unit/Connection/ManagerTest.php @@ -10,13 +10,7 @@ class ManagerTest extends TestCase { - protected function tearDown() - { - $this->setProtected(Manager::class, 'singleton', null); - parent::tearDown(); - } - - public function testShouldAddAndGetConnection() + public function testShouldAddAndGetConnection(): void { // Set $manager = new Manager(); @@ -35,7 +29,7 @@ public function testShouldAddAndGetConnection() $this->assertSame($client, $manager->getClient()); } - public function testShouldSetEventTrigger() + public function testShouldSetEventTrigger(): void { // Set $test = $this; @@ -50,14 +44,15 @@ public function testShouldSetEventTrigger() ->instance(EventTriggerService::class, m::type(EventTriggerService::class)) ->andReturnUsing(function ($class, $eventService) use ($test, $eventTrigger) { $test->assertSame(EventTriggerService::class, $class); - $test->assertAttributeSame($eventTrigger, 'dispatcher', $eventService); + $dispatcher = $this->getProtected($eventService, 'dispatcher'); + $test->assertSame($eventTrigger, $dispatcher); }); // Actions $manager->setEventTrigger($eventTrigger); } - public function testShouldInitializeOnce() + public function testShouldInitializeOnce(): void { // Set $manager = new Manager(); @@ -66,8 +61,7 @@ public function testShouldInitializeOnce() $this->callProtected($manager, 'init'); // Assertions - $this->assertAttributeSame($manager, 'singleton', Manager::class); - $this->assertAttributeInstanceOf(IlluminateContainer::class, 'container', $manager); + $this->assertInstanceOf(IlluminateContainer::class, $manager->container); // Actions $container = $manager->container; @@ -75,6 +69,6 @@ public function testShouldInitializeOnce() // Assertions // Initializes again to make sure that it will not instantiate a new container - $this->assertAttributeSame($container, 'container', $manager); + $this->assertSame($container, $manager->container); } } diff --git a/tests/Unit/Container/ContainerTest.php b/tests/Unit/Container/ContainerTest.php index 829796f4..542d179b 100644 --- a/tests/Unit/Container/ContainerTest.php +++ b/tests/Unit/Container/ContainerTest.php @@ -7,7 +7,7 @@ class ContainerTest extends TestCase { - protected function tearDown() + protected function tearDown(): void { Container::expects() ->flush(); @@ -15,7 +15,7 @@ protected function tearDown() parent::tearDown(); } - public function testShouldCallMethodsProperlyWithNoArguments() + public function testShouldCallMethodsProperlyWithNoArguments(): void { // Set $illuminateContainer = m::mock(IlluminateContainer::class); @@ -30,7 +30,7 @@ public function testShouldCallMethodsProperlyWithNoArguments() Container::method(); } - public function testShouldCallMethodsProperlyWithArguments() + public function testShouldCallMethodsProperlyWithArguments(): void { // Set $illuminateContainer = m::mock(IlluminateContainer::class); diff --git a/tests/Unit/Cursor/CursorTest.php b/tests/Unit/Cursor/CursorTest.php index 1ce36b13..ea04cd38 100644 --- a/tests/Unit/Cursor/CursorTest.php +++ b/tests/Unit/Cursor/CursorTest.php @@ -20,7 +20,7 @@ class CursorTest extends TestCase { - public function testShouldLimitDocumentQuantity() + public function testShouldLimitDocumentQuantity(): void { // Set $cursor = $this->getCursor(); @@ -28,63 +28,52 @@ public function testShouldLimitDocumentQuantity() // Actions $cursor->limit(10); + $result = $this->getProtected($cursor, 'params'); + // Assertions - $this->assertAttributeSame( - [[], ['limit' => 10]], - 'params', - $cursor - ); + $this->assertSame([[], ['limit' => 10]], $result); } - public function testShouldSortDocumentsOfCursor() + public function testShouldSortDocumentsOfCursor(): void { // Set $cursor = $this->getCursor(); // Actions $cursor->sort(['name' => 1]); + $result = $this->getProtected($cursor, 'params'); // Assertions - $this->assertAttributeSame( - [[], ['sort' => ['name' => 1]]], - 'params', - $cursor - ); + $this->assertSame([[], ['sort' => ['name' => 1]]], $result); } - public function testShouldSkipDocuments() + public function testShouldSkipDocuments(): void { // Set $cursor = $this->getCursor(); // Actions $cursor->skip(5); + $result = $this->getProtected($cursor, 'params'); // Assertions - $this->assertAttributeSame( - [[], ['skip' => 5]], - 'params', - $cursor - ); + $this->assertSame([[], ['skip' => 5]], $result); } - public function testShouldSetNoCursorTimeoutToTrue() + public function testShouldSetNoCursorTimeoutToTrue(): void { // Set $cursor = $this->getCursor(); // Actions $cursor->disableTimeout(); + $result = $this->getProtected($cursor, 'params'); // Assertions - $this->assertAttributeSame( - [[], ['noCursorTimeout' => true]], - 'params', - $cursor - ); + $this->assertSame([[], ['noCursorTimeout' => true]], $result); } - public function testShouldSetReadPreferenceParameterAccordingly() + public function testShouldSetReadPreferenceParameterAccordingly(): void { // Set $cursor = $this->getCursor(); @@ -100,7 +89,7 @@ public function testShouldSetReadPreferenceParameterAccordingly() $this->assertSame($mode, $result); } - public function testShouldBeAbleToSetReadPreferenceAndCursorTimeoutTogether() + public function testShouldBeAbleToSetReadPreferenceAndCursorTimeoutTogether(): void { // Set $cursor = $this->getCursor(); @@ -119,7 +108,7 @@ public function testShouldBeAbleToSetReadPreferenceAndCursorTimeoutTogether() $this->assertTrue($timeoutResult); } - public function testShouldCountDocuments() + public function testShouldCountDocuments(): void { // Set $collection = m::mock(Collection::class); @@ -137,7 +126,7 @@ public function testShouldCountDocuments() $this->assertSame(5, $result); } - public function testShouldCountDocumentsWithCountFunction() + public function testShouldCountDocumentsWithCountFunction(): void { // Set $collection = m::mock(Collection::class); @@ -155,7 +144,7 @@ public function testShouldCountDocumentsWithCountFunction() $this->assertSame(5, $result); } - public function testShouldRewind() + public function testShouldRewind(): void { // Set $collection = m::mock(Collection::class); @@ -170,12 +159,13 @@ public function testShouldRewind() // Actions $cursor->rewind(); + $result = $this->getProtected($cursor, 'position'); // Assertions - $this->assertAttributeSame(0, 'position', $cursor); + $this->assertSame(0, $result); } - public function testShouldRewindACursorThatHasAlreadyBeenInitialized() + public function testShouldRewindACursorThatHasAlreadyBeenInitialized(): void { // Set $collection = m::mock(Collection::class); @@ -197,12 +187,13 @@ function () use ($cursor) { // Actions $cursor->rewind(); + $result = $this->getProtected($cursor, 'position'); // Assertions - $this->assertAttributeSame(0, 'position', $cursor); + $this->assertSame(0, $result); } - public function testShouldGetCurrent() + public function testShouldGetCurrent(): void { // Set $collection = m::mock(Collection::class); @@ -221,7 +212,7 @@ public function testShouldGetCurrent() $this->assertSame('John Doe', $model->name); } - public function testShouldGetFirst() + public function testShouldGetFirst(): void { // Set $collection = m::mock(Collection::class); @@ -240,7 +231,7 @@ public function testShouldGetFirst() $this->assertSame('John Doe', $model->name); } - public function testShouldGetFirstWhenEmpty() + public function testShouldGetFirstWhenEmpty(): void { // Set $collection = m::mock(Collection::class); @@ -255,7 +246,7 @@ public function testShouldGetFirstWhenEmpty() $this->assertNull($result); } - public function testShouldRefreshTheCursor() + public function testShouldRefreshTheCursor(): void { // Set $driverCursor = new CachingIterator(new ArrayObject()); @@ -264,12 +255,13 @@ public function testShouldRefreshTheCursor() // Actions $cursor->fresh(); + $result = $this->getProtected($cursor, 'cursor'); // Assertions - $this->assertAttributeSame(null, 'cursor', $cursor); + $this->assertNull($result); } - public function testShouldImplementKeyMethodFromIterator() + public function testShouldImplementKeyMethodFromIterator(): void { // Set $cursor = $this->getCursor(); @@ -282,7 +274,7 @@ public function testShouldImplementKeyMethodFromIterator() $this->assertSame(7, $result); } - public function testShouldImplementNextMethodFromIterator() + public function testShouldImplementNextMethodFromIterator(): void { // Set $collection = m::mock(Collection::class); @@ -302,7 +294,7 @@ public function testShouldImplementNextMethodFromIterator() $this->assertSame(8, $cursor->key()); } - public function testShouldImplementValidMethodFromIterator() + public function testShouldImplementValidMethodFromIterator(): void { // Set $collection = m::mock(Collection::class); @@ -321,7 +313,7 @@ public function testShouldImplementValidMethodFromIterator() $this->assertTrue($result); } - public function testShouldWrapMongoDriverCursorWithIterator() + public function testShouldWrapMongoDriverCursorWithIterator(): void { // Set $collection = m::mock(Collection::class); @@ -363,7 +355,7 @@ public function testShouldWrapMongoDriverCursorWithIterator() $this->assertInstanceOf(CachingIterator::class, $result); } - public function testShouldReturnAllResults() + public function testShouldReturnAllResults(): void { // Set $collection = m::mock(Collection::class); @@ -398,7 +390,7 @@ public function testShouldReturnAllResults() $this->assertSame('tester', $nextModel->occupation); } - public function testShouldReturnResultsToArray() + public function testShouldReturnResultsToArray(): void { // Set $collection = m::mock(Collection::class); @@ -440,7 +432,7 @@ public function testShouldReturnResultsToArray() ); } - public function testShouldSerializeAnActiveCursor() + public function testShouldSerializeAnActiveCursor(): void { // Set $connection = $this->instance(Connection::class, m::mock(Connection::class)); diff --git a/tests/Unit/Cursor/EmbeddedCursorTest.php b/tests/Unit/Cursor/EmbeddedCursorTest.php index 8e5f22a4..24cea995 100644 --- a/tests/Unit/Cursor/EmbeddedCursorTest.php +++ b/tests/Unit/Cursor/EmbeddedCursorTest.php @@ -7,7 +7,7 @@ class EmbeddedCursorTest extends TestCase { - public function testShouldLimitDocumentQuantity() + public function testShouldLimitDocumentQuantity(): void { // Set $items = [ @@ -19,34 +19,29 @@ public function testShouldLimitDocumentQuantity() // Actions $cursor->limit(2); + $result = $this->getProtected($cursor, 'items'); // Assertions - $this->assertAttributeSame( - [ - ['name' => 'A'], - ['name' => 'B'], - ], - 'items', - $cursor - ); + $this->assertSame([['name' => 'A'], ['name' => 'B']], $result); } /** * @dataProvider getDocumentsToSort */ - public function testShouldSortDocuments(array $items, array $parameters, array $expected) + public function testShouldSortDocuments(array $items, array $parameters, array $expected): void { // Set $cursor = new EmbeddedCursor($items); // Actions $cursor->sort($parameters); + $result = $this->getProtected($cursor, 'items'); // Assertions - $this->assertAttributeSame($expected, 'items', $cursor); + $this->assertSame($expected, $result); } - public function testShouldSkipDocuments() + public function testShouldSkipDocuments(): void { // Set $items = [ @@ -58,18 +53,13 @@ public function testShouldSkipDocuments() // Actions $cursor->skip(2); + $result = $this->getProtected($cursor, 'items'); // Assertions - $this->assertAttributeSame( - [ - ['name' => 'C'], - ], - 'items', - $cursor - ); + $this->assertSame([['name' => 'C']], $result); } - public function testShouldCountDocuments() + public function testShouldCountDocuments(): void { // Set $items = [ @@ -86,7 +76,7 @@ public function testShouldCountDocuments() $this->assertSame(3, $result); } - public function testShouldCountDocumentsWithCountFunction() + public function testShouldCountDocumentsWithCountFunction(): void { // Set $items = [ @@ -103,19 +93,20 @@ public function testShouldCountDocumentsWithCountFunction() $this->assertSame(3, $result); } - public function testShouldRewind() + public function testShouldRewind(): void { // Set $cursor = new EmbeddedCursor([]); // Actions $cursor->rewind(); + $result = $this->getProtected($cursor, 'position'); // Assertions - $this->assertAttributeSame(0, 'position', $cursor); + $this->assertSame(0, $result); } - public function testShouldGetCurrent() + public function testShouldGetCurrent(): void { // Set $object = new class extends AbstractModel @@ -148,7 +139,7 @@ public function testShouldGetCurrent() $this->assertSame('B', $model->name); } - public function testShouldNotGetCurrentWhenCursorIsInvalid() + public function testShouldNotGetCurrentWhenCursorIsInvalid(): void { // Set $items = []; @@ -163,7 +154,7 @@ public function testShouldNotGetCurrentWhenCursorIsInvalid() $this->assertNull($model); } - public function testShouldGetCurrentUsingModelClass() + public function testShouldGetCurrentUsingModelClass(): void { // Set $object = new stdClass(); @@ -178,10 +169,10 @@ public function testShouldGetCurrentUsingModelClass() // Assertions $this->assertInstanceOf(stdClass::class, $model); - $this->assertAttributeSame('A', 'name', $model); + $this->assertSame('A', $model->name); } - public function testShouldGetCurrentUsingModelClassMorphingIt() + public function testShouldGetCurrentUsingModelClassMorphingIt(): void { // Set $model = new class() extends AbstractModel @@ -201,7 +192,7 @@ public function testShouldGetCurrentUsingModelClassMorphingIt() $this->assertSame('John', $result->name); } - public function testShouldGetFirst() + public function testShouldGetFirst(): void { // Set $object = new class extends AbstractModel @@ -231,7 +222,7 @@ public function testShouldGetFirst() $this->assertSame('A', $model->name); } - public function testShouldGetAllItems() + public function testShouldGetAllItems(): void { // Set $modelA = new class extends AbstractModel @@ -262,7 +253,7 @@ public function testShouldGetAllItems() $this->assertSame($expected, $result); } - public function testShouldGetAllInArrayFormat() + public function testShouldGetAllInArrayFormat(): void { // Set $items = [ @@ -280,7 +271,7 @@ public function testShouldGetAllInArrayFormat() $this->assertSame($items, $result); } - public function testShouldImplementKeyMethodFromIterator() + public function testShouldImplementKeyMethodFromIterator(): void { // Set $cursor = new EmbeddedCursor([]); @@ -293,7 +284,7 @@ public function testShouldImplementKeyMethodFromIterator() $this->assertSame(7, $result); } - public function testShouldImplementNextMethodFromIterator() + public function testShouldImplementNextMethodFromIterator(): void { // Set $cursor = new EmbeddedCursor([]); @@ -301,12 +292,13 @@ public function testShouldImplementNextMethodFromIterator() // Actions $cursor->next(); + $result = $this->getProtected($cursor, 'position'); // Assertions - $this->assertAttributeSame(8, 'position', $cursor); + $this->assertSame(8, $result); } - public function testShouldImplementValidMethodFromIterator() + public function testShouldImplementValidMethodFromIterator(): void { // Set $items = [ diff --git a/tests/Unit/Event/EventTriggerServiceTest.php b/tests/Unit/Event/EventTriggerServiceTest.php index 5c6c11d0..efa66866 100644 --- a/tests/Unit/Event/EventTriggerServiceTest.php +++ b/tests/Unit/Event/EventTriggerServiceTest.php @@ -6,7 +6,7 @@ class EventTriggerServiceTest extends TestCase { - public function testShouldSendTheEventsToTheExternalDispatcher() + public function testShouldSendTheEventsToTheExternalDispatcher(): void { // Set $dispatcher = m::mock(EventTriggerInterface::class); @@ -25,7 +25,7 @@ public function testShouldSendTheEventsToTheExternalDispatcher() $this->assertTrue($result); } - public function testShouldReturnTrueIfThereIsNoExternalDispatcher() + public function testShouldReturnTrueIfThereIsNoExternalDispatcher(): void { // Set $dispatcher = m::mock(EventTriggerInterface::class); diff --git a/tests/Unit/Model/AbstractModelTest.php b/tests/Unit/Model/AbstractModelTest.php index 3f68da88..85503e8a 100644 --- a/tests/Unit/Model/AbstractModelTest.php +++ b/tests/Unit/Model/AbstractModelTest.php @@ -23,7 +23,7 @@ class AbstractModelTest extends TestCase /** * {@inheritdoc} */ - protected function setUp() + protected function setUp(): void { parent::setUp(); $this->model = new class() extends AbstractModel @@ -43,13 +43,13 @@ public function unsetCollection() /** * {@inheritdoc} */ - protected function tearDown() + protected function tearDown(): void { unset($this->model); parent::tearDown(); } - public function testShouldImplementModelTraits() + public function testShouldImplementModelTraits(): void { // Actions $result = array_keys(class_uses(AbstractModel::class)); @@ -61,7 +61,7 @@ public function testShouldImplementModelTraits() ); } - public function testShouldImplementModelInterface() + public function testShouldImplementModelInterface(): void { // Actions $result = array_keys(class_implements(AbstractModel::class)); @@ -80,7 +80,7 @@ public function testShouldImplementModelInterface() ); } - public function testShouldSave() + public function testShouldSave(): void { // Set $builder = $this->instance(Builder::class, m::mock(Builder::class)); @@ -97,7 +97,7 @@ public function testShouldSave() $this->assertTrue($result); } - public function testShouldInsert() + public function testShouldInsert(): void { // Set $builder = $this->instance(Builder::class, m::mock(Builder::class)); @@ -114,7 +114,7 @@ public function testShouldInsert() $this->assertTrue($result); } - public function testShouldUpdate() + public function testShouldUpdate(): void { // Set $builder = $this->instance(Builder::class, m::mock(Builder::class)); @@ -131,7 +131,7 @@ public function testShouldUpdate() $this->assertTrue($result); } - public function testShouldDelete() + public function testShouldDelete(): void { // Set $builder = $this->instance(Builder::class, m::mock(Builder::class)); @@ -148,7 +148,7 @@ public function testShouldDelete() $this->assertTrue($result); } - public function testSaveShouldThrowExceptionIfCollectionIsNull() + public function testSaveShouldThrowExceptionIfCollectionIsNull(): void { // Set $this->model->unsetCollection(); @@ -161,7 +161,7 @@ public function testSaveShouldThrowExceptionIfCollectionIsNull() $this->model->save(); } - public function testUpdateShouldThrowExceptionIfCollectionIsNull() + public function testUpdateShouldThrowExceptionIfCollectionIsNull(): void { // Set $this->model->unsetCollection(); @@ -174,7 +174,7 @@ public function testUpdateShouldThrowExceptionIfCollectionIsNull() $this->model->update(); } - public function testInsertShouldThrowExceptionIfCollectionIsNull() + public function testInsertShouldThrowExceptionIfCollectionIsNull(): void { // Set $this->model->unsetCollection(); @@ -187,7 +187,7 @@ public function testInsertShouldThrowExceptionIfCollectionIsNull() $this->model->insert(); } - public function testDeleteShouldThrowExceptionIfCollectionIsNull() + public function testDeleteShouldThrowExceptionIfCollectionIsNull(): void { // Set $this->model->unsetCollection(); @@ -200,7 +200,7 @@ public function testDeleteShouldThrowExceptionIfCollectionIsNull() $this->model->delete(); } - public function testShouldGetWithWhereQuery() + public function testShouldGetWithWhereQuery(): void { // Set $query = ['foo' => 'bar']; @@ -221,7 +221,7 @@ public function testShouldGetWithWhereQuery() $this->assertSame($cursor, $result); } - public function testShouldGetAll() + public function testShouldGetAll(): void { // Set $builder = $this->instance(Builder::class, m::mock(Builder::class)); @@ -239,7 +239,7 @@ public function testShouldGetAll() $this->assertSame($cursor, $result); } - public function testShouldGetFirstWithQuery() + public function testShouldGetFirstWithQuery(): void { // Set $query = ['foo' => 'bar']; @@ -258,7 +258,7 @@ public function testShouldGetFirstWithQuery() $this->assertSame($this->model, $result); } - public function testShouldGetFirstOrFail() + public function testShouldGetFirstOrFail(): void { // Set $builder = $this->instance(Builder::class, m::mock(Builder::class)); @@ -277,7 +277,7 @@ public function testShouldGetFirstOrFail() $this->assertSame($this->model, $result); } - public function testShouldGetFirstOrNewAndReturnExistingModel() + public function testShouldGetFirstOrNewAndReturnExistingModel(): void { // Set $builder = $this->instance(Builder::class, m::mock(Builder::class)); @@ -295,7 +295,7 @@ public function testShouldGetFirstOrNewAndReturnExistingModel() $this->assertSame($this->model, $result); } - public function testShouldGetFirstOrNewAndReturnNewModel() + public function testShouldGetFirstOrNewAndReturnNewModel(): void { // Set $builder = $this->instance(Builder::class, m::mock(Builder::class)); @@ -313,7 +313,7 @@ public function testShouldGetFirstOrNewAndReturnNewModel() $this->assertNotEquals($this->model, $result); } - public function testShouldGetBuilder() + public function testShouldGetBuilder(): void { // Set $model = new class extends AbstractModel @@ -327,7 +327,7 @@ public function testShouldGetBuilder() $this->assertInstanceOf(Builder::class, $result); } - public function testShouldRaiseExceptionWhenHasNoCollectionAndTryToCallAllFunction() + public function testShouldRaiseExceptionWhenHasNoCollectionAndTryToCallAllFunction(): void { // Set $model = new class() extends AbstractModel @@ -341,7 +341,7 @@ public function testShouldRaiseExceptionWhenHasNoCollectionAndTryToCallAllFuncti $model->all(); } - public function testShouldRaiseExceptionWhenHasNoCollectionAndTryToCallFirstFunction() + public function testShouldRaiseExceptionWhenHasNoCollectionAndTryToCallFirstFunction(): void { // Set $model = new class() extends AbstractModel @@ -355,7 +355,7 @@ public function testShouldRaiseExceptionWhenHasNoCollectionAndTryToCallFirstFunc $model->first(); } - public function testShouldRaiseExceptionWhenHasNoCollectionAndTryToCallWhereFunction() + public function testShouldRaiseExceptionWhenHasNoCollectionAndTryToCallWhereFunction(): void { // Set $model = new class() extends AbstractModel @@ -369,7 +369,7 @@ public function testShouldRaiseExceptionWhenHasNoCollectionAndTryToCallWhereFunc $model->where(); } - public function testShouldGetCollectionName() + public function testShouldGetCollectionName(): void { // Actions $result = $this->model->getCollectionName(); @@ -378,7 +378,7 @@ public function testShouldGetCollectionName() $this->assertSame('mongolid', $result); } - public function testShouldHaveDefaultWriteConcern() + public function testShouldHaveDefaultWriteConcern(): void { // Actions $result = $this->model->getWriteConcern(); @@ -387,7 +387,7 @@ public function testShouldHaveDefaultWriteConcern() $this->assertSame(1, $result); } - public function testShouldSetWriteConcern() + public function testShouldSetWriteConcern(): void { // Actions $this->model->setWriteConcern(0); @@ -397,7 +397,7 @@ public function testShouldSetWriteConcern() $this->assertSame(0, $result); } - public function testShouldHaveDynamicSetters() + public function testShouldHaveDynamicSetters(): void { // Set $model = new class() extends AbstractModel @@ -423,7 +423,7 @@ public function testShouldHaveDynamicSetters() ); } - public function testShouldHaveDynamicGetters() + public function testShouldHaveDynamicGetters(): void { // Set $child = new class() extends AbstractModel @@ -450,7 +450,7 @@ public function testShouldHaveDynamicGetters() $this->assertSame(null, $model->nonexistant); } - public function testShouldCheckIfAttributeIsSet() + public function testShouldCheckIfAttributeIsSet(): void { // Set $model = new class() extends AbstractModel @@ -466,7 +466,7 @@ public function testShouldCheckIfAttributeIsSet() $this->assertFalse(isset($model->ignored)); } - public function testShouldCheckIfMutatedAttributeIsSet() + public function testShouldCheckIfMutatedAttributeIsSet(): void { // Set $model = new class() extends AbstractModel @@ -487,7 +487,7 @@ public function getNameDocumentAttribute() $this->assertFalse(isset($model->nonexistant)); } - public function testShouldUnsetAttributes() + public function testShouldUnsetAttributes(): void { // Set $model = new class() extends AbstractModel @@ -508,7 +508,7 @@ public function testShouldUnsetAttributes() $this->assertSame(['name' => 'John'], $result); } - public function testShouldGetAttributeFromMutator() + public function testShouldGetAttributeFromMutator(): void { // Set $model = new class() extends AbstractModel @@ -532,7 +532,7 @@ public function getShortNameDocumentAttribute() $this->assertSame('Other name', $result); } - public function testShouldIgnoreMutators() + public function testShouldIgnoreMutators(): void { // Set $model = new class() extends AbstractModel @@ -555,7 +555,7 @@ public function setShortNameDocumentAttribute($value) $this->assertSame('My awesome name', $model->short_name); } - public function testShouldSetAttributeFromMutator() + public function testShouldSetAttributeFromMutator(): void { // Set $model = new class() extends AbstractModel diff --git a/tests/Unit/Model/Exception/ModelNotFoundExceptionTest.php b/tests/Unit/Model/Exception/ModelNotFoundExceptionTest.php index e4b0963c..92f8b8e7 100644 --- a/tests/Unit/Model/Exception/ModelNotFoundExceptionTest.php +++ b/tests/Unit/Model/Exception/ModelNotFoundExceptionTest.php @@ -5,7 +5,7 @@ class ModelNotFoundExceptionTest extends TestCase { - public function testSetModel() + public function testSetModel(): void { // Set $object = new ModelNotFoundException(); diff --git a/tests/Unit/Model/HasAttributesTraitTest.php b/tests/Unit/Model/HasAttributesTraitTest.php index f409e02f..495afab9 100644 --- a/tests/Unit/Model/HasAttributesTraitTest.php +++ b/tests/Unit/Model/HasAttributesTraitTest.php @@ -8,7 +8,7 @@ class HasAttributesTraitTest extends TestCase { - public function testShouldGetAttributeFromMutator() + public function testShouldGetAttributeFromMutator(): void { // Set $model = new class() @@ -35,7 +35,7 @@ public function getShortNameDocumentAttribute() $this->assertSame('Other name', $result); } - public function testShouldIgnoreMutators() + public function testShouldIgnoreMutators(): void { // Set $model = new class() @@ -62,7 +62,7 @@ public function setShortNameDocumentAttribute($value) $this->assertSame('My awesome name', $result); } - public function testShouldSetAttributeFromMutator() + public function testShouldSetAttributeFromMutator(): void { // Set $model = new class() @@ -118,7 +118,7 @@ public function __construct(array $fillable, array $guarded) $this->assertSame($expected, $model->getDocumentAttributes()); } - public function testFillShouldRetrievePolymorphedModel() + public function testFillShouldRetrievePolymorphedModel(): void { // Set $input = [ @@ -134,7 +134,7 @@ public function testFillShouldRetrievePolymorphedModel() $this->assertSame('hello', $result->new_field); } - public function testFillShouldRetrievePolymorphedModelConsideringModelAttributes() + public function testFillShouldRetrievePolymorphedModelConsideringModelAttributes(): void { // Set $input = [ @@ -152,7 +152,7 @@ public function testFillShouldRetrievePolymorphedModelConsideringModelAttributes $this->assertSame('hello', $result->new_field); } - public function testFillShouldRetrievePolymorphedModelConsideringModelAttributesButPrioritizingInput() + public function testFillShouldRetrievePolymorphedModelConsideringModelAttributesButPrioritizingInput(): void { // Set $input = [ @@ -171,7 +171,7 @@ public function testFillShouldRetrievePolymorphedModelConsideringModelAttributes $this->assertSame('hello', $result->new_field); } - public function testFillShouldRetrievePolymorphedModelEvenWithExistingModel() + public function testFillShouldRetrievePolymorphedModelEvenWithExistingModel(): void { // Set $input = [ @@ -198,7 +198,7 @@ public function testFillShouldRetrievePolymorphedModelEvenWithExistingModel() $this->assertSame('other value', $result->other_exclusive); } - public function testFillShouldHoldValuesOnModel() + public function testFillShouldHoldValuesOnModel(): void { // Set $input = [ @@ -224,7 +224,7 @@ public function testFillShouldHoldValuesOnModel() ); } - public function testFillShouldNotHoldValuesOnModelIfPolymorphed() + public function testFillShouldNotHoldValuesOnModelIfPolymorphed(): void { // Set $input = [ @@ -258,7 +258,7 @@ public function testFillShouldNotHoldValuesOnModelIfPolymorphed() ); } - public function testShouldForceFillAttributes() + public function testShouldForceFillAttributes(): void { // Set $model = new class() implements HasAttributesInterface @@ -280,7 +280,7 @@ public function testShouldForceFillAttributes() $this->assertTrue($result); } - public function testShouldBeCastableToArray() + public function testShouldBeCastableToArray(): void { // Set $model = new class() @@ -299,7 +299,7 @@ public function testShouldBeCastableToArray() $this->assertSame(['name' => 'John', 'age' => 25], $result); } - public function testShouldSetOriginalAttributes() + public function testShouldSetOriginalAttributes(): void { // Set $model = new class() implements HasAttributesInterface @@ -319,7 +319,7 @@ public function testShouldSetOriginalAttributes() $this->assertSame($model->getDocumentAttributes(), $result); } - public function testShouldFallbackOriginalAttributesIfUnserializationFails() + public function testShouldFallbackOriginalAttributesIfUnserializationFails(): void { // Set $model = new class() implements HasAttributesInterface @@ -344,7 +344,7 @@ function () { $this->assertSame($model->getDocumentAttributes(), $result); } - public function testShouldCheckIfAttributeIsSet() + public function testShouldCheckIfAttributeIsSet(): void { // Set $model = new class() extends AbstractModel @@ -360,7 +360,7 @@ public function testShouldCheckIfAttributeIsSet() $this->assertFalse(isset($model->ignored)); } - public function testShouldCheckIfMutatedAttributeIsSet() + public function testShouldCheckIfMutatedAttributeIsSet(): void { // Set $model = new class() extends AbstractModel diff --git a/tests/Unit/Model/HasRelationsTraitTest.php b/tests/Unit/Model/HasRelationsTraitTest.php index 80485790..34dda78b 100644 --- a/tests/Unit/Model/HasRelationsTraitTest.php +++ b/tests/Unit/Model/HasRelationsTraitTest.php @@ -14,7 +14,7 @@ class HasRelationsTraitTest extends TestCase /** * @dataProvider referencesOneScenarios */ - public function testShouldReferenceOne($fieldValue, array $expectedQuery) + public function testShouldReferenceOne($fieldValue, array $expectedQuery): void { // Set $model = new ReferencedUser(); @@ -35,7 +35,7 @@ public function testShouldReferenceOne($fieldValue, array $expectedQuery) $this->assertSame($expected, $result); } - public function testShouldNotPerformQueryForNullReference() + public function testShouldNotPerformQueryForNullReference(): void { // Set $model = new ReferencedUser(); @@ -58,7 +58,7 @@ public function testShouldNotPerformQueryForNullReference() /** * @dataProvider referencesManyScenarios */ - public function testShouldReferenceMany($fieldValue, array $expectedQuery) + public function testShouldReferenceMany($fieldValue, array $expectedQuery): void { // Set $model = new ReferencedUser(); @@ -79,7 +79,7 @@ public function testShouldReferenceMany($fieldValue, array $expectedQuery) $this->assertSame($expected, $result); } - public function testShouldEmbedOne() + public function testShouldEmbedOne(): void { // Set $model = new EmbeddedUser(); @@ -99,7 +99,7 @@ public function testShouldEmbedOne() $this->assertSame($embeddedModel, $result); } - public function testEmbedOneShouldAllowOnlyOneEmbeddedModel() + public function testEmbedOneShouldAllowOnlyOneEmbeddedModel(): void { // Set $model = new EmbeddedUser(); @@ -128,7 +128,7 @@ public function testEmbedOneShouldAllowOnlyOneEmbeddedModel() /** * @dataProvider embedsManyScenarios */ - public function testShouldEmbedMany($fieldValue, array $expectedItems) + public function testShouldEmbedMany($fieldValue, array $expectedItems): void { // Set $model = new EmbeddedUser(); diff --git a/tests/Unit/Query/BuilderTest.php b/tests/Unit/Query/BuilderTest.php index c7d88109..d5b9a31a 100644 --- a/tests/Unit/Query/BuilderTest.php +++ b/tests/Unit/Query/BuilderTest.php @@ -18,23 +18,28 @@ class BuilderTest extends TestCase { - public function testShouldBeAbleToConstruct() + public function testShouldBeAbleToConstruct(): void { // Set $connection = m::mock(Connection::class); // Actions $builder = new Builder($connection); + $result = $this->getProtected($builder, 'connection'); // Assertions - $this->assertAttributeSame($connection, 'connection', $builder); + $this->assertSame($connection, $result); } /** * @dataProvider getWriteConcernVariations */ - public function testShouldSave(ReplaceCollectionModel $model, int $writeConcern, bool $shouldFireEventAfter, bool $expected) - { + public function testShouldSave( + ReplaceCollectionModel $model, + int $writeConcern, + bool $shouldFireEventAfter, + bool $expected + ): void { // Set $connection = m::mock(Connection::class); $builder = new Builder($connection); @@ -83,8 +88,12 @@ public function testShouldSave(ReplaceCollectionModel $model, int $writeConcern, /** * @dataProvider getWriteConcernVariations */ - public function testShouldInsert(ReplaceCollectionModel $model, int $writeConcern, bool $shouldFireEventAfter, bool $expected) - { + public function testShouldInsert( + ReplaceCollectionModel $model, + int $writeConcern, + bool $shouldFireEventAfter, + bool $expected + ): void { // Set $connection = m::mock(Connection::class); $builder = new Builder($connection); @@ -170,8 +179,12 @@ public function testShouldInsertWithoutFiringEvents( /** * @dataProvider getWriteConcernVariations */ - public function testShouldUpdate(ReplaceCollectionModel $model, int $writeConcern, bool $shouldFireEventAfter, bool $expected) - { + public function testShouldUpdate( + ReplaceCollectionModel $model, + int $writeConcern, + bool $shouldFireEventAfter, + bool $expected + ): void { // Set $connection = m::mock(Connection::class); $builder = new Builder($connection); @@ -214,7 +227,7 @@ public function testShouldUpdate(ReplaceCollectionModel $model, int $writeConcer $this->assertSame($expected, $result); } - public function testShouldUpdateUnsettingFields() + public function testShouldUpdateUnsettingFields(): void { // Set $connection = m::mock(Connection::class); @@ -274,7 +287,7 @@ public function testShouldUpdateUnsettingFields() $this->assertTrue($result); } - public function testUpdateShouldCalculateChangesAccordingly() + public function testUpdateShouldCalculateChangesAccordingly(): void { // Set $connection = m::mock(Connection::class); @@ -384,8 +397,12 @@ public function testUpdateShouldCallInsertWhenObjectHasNoId( /** * @dataProvider getWriteConcernVariations */ - public function testShouldDelete(ReplaceCollectionModel $model, int $writeConcern, bool $shouldFireEventAfter, bool $expected) - { + public function testShouldDelete( + ReplaceCollectionModel $model, + int $writeConcern, + bool $shouldFireEventAfter, + bool $expected + ): void { // Set $connection = m::mock(Connection::class); $builder = new Builder($connection); @@ -458,7 +475,7 @@ public function testDatabaseOperationsShouldBailOutIfTheEventHandlerReturnsFalse $this->assertFalse($result); } - public function testShouldGetWithWhereQuery() + public function testShouldGetWithWhereQuery(): void { // Set $connection = m::mock(Connection::class); @@ -479,19 +496,18 @@ public function testShouldGetWithWhereQuery() // Actions $result = $builder->where($model, $query, $projection); + $collectionResult = $this->getProtected($result, 'collection'); + $commandResult = $this->getProtected($result, 'command'); + $paramsResult = $this->getProtected($result, 'params'); // Assertions $this->assertInstanceOf(Cursor::class, $result); - $this->assertAttributeSame($collection, 'collection', $result); - $this->assertAttributeSame('find', 'command', $result); - $this->assertAttributeSame( - [$preparedQuery, ['projection' => $projection]], - 'params', - $result - ); + $this->assertSame($collection, $collectionResult); + $this->assertSame('find', $commandResult); + $this->assertSame([$preparedQuery, ['projection' => $projection]], $paramsResult); } - public function testShouldGetAll() + public function testShouldGetAll(): void { // Set $connection = m::mock(Connection::class); @@ -511,7 +527,7 @@ public function testShouldGetAll() $this->assertSame($mongolidCursor, $result); } - public function testShouldGetFirstWithQuery() + public function testShouldGetFirstWithQuery(): void { // Set $connection = m::mock(Connection::class); @@ -539,7 +555,7 @@ public function testShouldGetFirstWithQuery() $this->assertSame($model, $result); } - public function testFirstWithNullShouldNotHitTheDatabase() + public function testFirstWithNullShouldNotHitTheDatabase(): void { // Set $connection = m::mock(Connection::class); @@ -552,7 +568,7 @@ public function testFirstWithNullShouldNotHitTheDatabase() $this->assertNull($result); } - public function testFirstOrFailShouldGetFirst() + public function testFirstOrFailShouldGetFirst(): void { // Set $connection = m::mock(Connection::class); @@ -580,7 +596,7 @@ public function testFirstOrFailShouldGetFirst() $this->assertSame($model, $result); } - public function testFirstOrFailWithNullShouldFail() + public function testFirstOrFailWithNullShouldFail(): void { // Set $connection = m::mock(Connection::class); @@ -597,7 +613,7 @@ public function testFirstOrFailWithNullShouldFail() $builder->firstOrFail($model, null); } - public function testShouldGetNullIfFirstCantFindAnything() + public function testShouldGetNullIfFirstCantFindAnything(): void { // Set $connection = m::mock(Connection::class); @@ -625,7 +641,7 @@ public function testShouldGetNullIfFirstCantFindAnything() $this->assertNull($result); } - public function testShouldGetFirstProjectingFields() + public function testShouldGetFirstProjectingFields(): void { // Set $connection = m::mock(Connection::class); @@ -658,7 +674,7 @@ public function testShouldGetFirstProjectingFields() /** * @dataProvider queryValueScenarios */ - public function testShouldPrepareQueryValue($value, $expectation) + public function testShouldPrepareQueryValue($value, $expectation): void { // Set $connection = m::mock(Connection::class); @@ -674,7 +690,7 @@ public function testShouldPrepareQueryValue($value, $expectation) /** * @dataProvider getProjections */ - public function testPrepareProjectionShouldConvertArray($data, $expectation) + public function testPrepareProjectionShouldConvertArray($data, $expectation): void { // Set $connection = m::mock(Connection::class); @@ -687,7 +703,7 @@ public function testPrepareProjectionShouldConvertArray($data, $expectation) $this->assertSame($expectation, $result); } - public function testPrepareProjectionShouldThrownAnException() + public function testPrepareProjectionShouldThrownAnException(): void { // Set $connection = m::mock(Connection::class); diff --git a/tests/Unit/Query/ModelMapperTest.php b/tests/Unit/Query/ModelMapperTest.php index 135dba5d..c33a90d0 100644 --- a/tests/Unit/Query/ModelMapperTest.php +++ b/tests/Unit/Query/ModelMapperTest.php @@ -9,7 +9,7 @@ class ModelMapperTest extends TestCase { - public function testShouldClearDynamicFieldsIfModelIsNotDynamic() + public function testShouldClearDynamicFieldsIfModelIsNotDynamic(): void { // Set $model = new class extends AbstractModel @@ -36,7 +36,7 @@ public function testShouldClearDynamicFieldsIfModelIsNotDynamic() ); } - public function testShouldClearDynamicFieldsIfModelIsNotDynamicCheckingTimestamps() + public function testShouldClearDynamicFieldsIfModelIsNotDynamicCheckingTimestamps(): void { // Set $model = new class extends AbstractModel @@ -66,7 +66,7 @@ public function testShouldClearDynamicFieldsIfModelIsNotDynamicCheckingTimestamp ); } - public function testShouldNotClearDynamicFieldsIfModelIsDynamic() + public function testShouldNotClearDynamicFieldsIfModelIsDynamic(): void { // Set $model = new class extends AbstractModel @@ -94,7 +94,7 @@ public function testShouldNotClearDynamicFieldsIfModelIsDynamic() ); } - public function testShouldClearNullFields() + public function testShouldClearNullFields(): void { // Set $model = new class extends AbstractModel @@ -120,7 +120,7 @@ public function testShouldClearNullFields() ); } - public function testShouldGenerateAnIdIfModelDoesNotHaveOne() + public function testShouldGenerateAnIdIfModelDoesNotHaveOne(): void { // Set $model = new class extends AbstractModel @@ -139,7 +139,7 @@ public function testShouldGenerateAnIdIfModelDoesNotHaveOne() $this->assertInstanceOf(ObjectId::class, $model->_id); } - public function testShouldCastObjectId() + public function testShouldCastObjectId(): void { // Set $model = new class extends AbstractModel @@ -159,7 +159,7 @@ public function testShouldCastObjectId() $this->assertEquals(new ObjectId($id), $model->_id); } - public function testShouldHandleTimestampsCreatingCreatedAtField() + public function testShouldHandleTimestampsCreatingCreatedAtField(): void { // Set $model = new class extends AbstractModel @@ -183,7 +183,7 @@ public function testShouldHandleTimestampsCreatingCreatedAtField() $this->assertSame($model->updated_at, $model->created_at); } - public function testShouldHandleTimestampsOnlyUpdatingUpdatedAtField() + public function testShouldHandleTimestampsOnlyUpdatingUpdatedAtField(): void { // Set $model = new class extends AbstractModel diff --git a/tests/Unit/TestCase.php b/tests/Unit/TestCase.php index f42fb490..84ccacd4 100644 --- a/tests/Unit/TestCase.php +++ b/tests/Unit/TestCase.php @@ -10,13 +10,13 @@ class TestCase extends PHPUnitTestCase { - protected function setUp() + protected function setUp(): void { parent::setUp(); Container::setContainer(new IlluminateContainer()); } - protected function tearDown() + protected function tearDown(): void { Container::flush(); m::close(); diff --git a/tests/Unit/Util/LocalDateTimeTest.php b/tests/Unit/Util/LocalDateTimeTest.php index 50430a38..c810f25c 100644 --- a/tests/Unit/Util/LocalDateTimeTest.php +++ b/tests/Unit/Util/LocalDateTimeTest.php @@ -21,7 +21,7 @@ class LocalDateTimeTest extends TestCase /** * {@inheritdoc} */ - protected function setUp() + protected function setUp(): void { parent::setUp(); @@ -34,13 +34,13 @@ protected function setUp() /** * {@inheritdoc} */ - protected function tearDown() + protected function tearDown(): void { unset($this->date); parent::tearDown(); } - public function testGetShouldRetrievesDateUsingTimezone() + public function testGetShouldRetrievesDateUsingTimezone(): void { // Set $date = new UTCDateTime($this->date); @@ -52,7 +52,7 @@ public function testGetShouldRetrievesDateUsingTimezone() $this->assertEquals($this->date, $result); } - public function testFormatShouldRetrievesDateWithDefaultFormat() + public function testFormatShouldRetrievesDateWithDefaultFormat(): void { // Set $timezone = new DateTimeZone(date_default_timezone_get()); @@ -65,7 +65,7 @@ public function testFormatShouldRetrievesDateWithDefaultFormat() $this->assertSame($this->date->format($this->format), $result); } - public function testFormatShouldRetrieveDateUsingGivenFormat() + public function testFormatShouldRetrieveDateUsingGivenFormat(): void { // Set $timezone = new DateTimeZone(date_default_timezone_get()); @@ -79,7 +79,7 @@ public function testFormatShouldRetrieveDateUsingGivenFormat() $this->assertSame($this->date->format($format), $result); } - public function testTimestampShouldRetrievesTimestampUsingTimezone() + public function testTimestampShouldRetrievesTimestampUsingTimezone(): void { // Set $timestamp = $this->date->getTimestamp(); diff --git a/tests/Unit/Util/ObjectIdUtilsTest.php b/tests/Unit/Util/ObjectIdUtilsTest.php index 580e8b76..2bff33de 100644 --- a/tests/Unit/Util/ObjectIdUtilsTest.php +++ b/tests/Unit/Util/ObjectIdUtilsTest.php @@ -9,7 +9,7 @@ class ObjectIdUtilsTest extends TestCase /** * @dataProvider objectIdStringScenarios */ - public function testShouldEvaluateIfValueIsAnObjectId($value, bool $expectation) + public function testShouldEvaluateIfValueIsAnObjectId($value, bool $expectation): void { // Actions $result = ObjectIdUtils::isObjectId($value); diff --git a/tests/Unit/Util/SequenceServiceTest.php b/tests/Unit/Util/SequenceServiceTest.php index e0101e5c..e01a9b76 100644 --- a/tests/Unit/Util/SequenceServiceTest.php +++ b/tests/Unit/Util/SequenceServiceTest.php @@ -13,7 +13,7 @@ class SequenceServiceTest extends TestCase /** * @dataProvider sequenceScenarios */ - public function testShouldGetNextValue(string $sequenceName, int $currentValue, int $expectation) + public function testShouldGetNextValue(string $sequenceName, int $currentValue, int $expectation): void { // Set $connection = m::mock(Connection::class); @@ -42,7 +42,7 @@ public function testShouldGetNextValue(string $sequenceName, int $currentValue, $this->assertSame($expectation, $result); } - public function testShouldGetClient() + public function testShouldGetClient(): void { // Set $connection = m::mock(Connection::class); From 9a2b408359b36990fde9b53c4c1c3323e8cc4a32 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Thu, 26 Sep 2019 18:34:50 -0300 Subject: [PATCH 114/116] Bump php minimum version to 7.2 --- .travis.yml | 2 -- composer.json | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6fb3df30..d4e9793f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: php php: - - 7.1 - 7.2 - 7.3 @@ -12,7 +11,6 @@ cache: services: mongodb before_install: - - if [[ $(phpenv version-name) = "7.1" ]]; then pecl install mongodb; fi - echo "extension = mongodb.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini before_script: diff --git a/composer.json b/composer.json index 9f47c928..1849552f 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ } ], "require": { - "php": ">=7.1", + "php": ">=7.2", "ext-mongodb": "*", "mongodb/mongodb": "^1.4", "illuminate/container": "^5.4 || ^6.0" From eba50822a54290b56edcc818036cb8d7f7e2885e Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Thu, 26 Sep 2019 18:38:53 -0300 Subject: [PATCH 115/116] Attempt to bump mongodb driver version --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index d4e9793f..b3fd989f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,7 @@ services: mongodb before_install: - echo "extension = mongodb.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini + - pecl upgrade mongodb before_script: - composer install --no-interaction From 2928b07e61b2a045640e85ecc46c7352893caa94 Mon Sep 17 00:00:00 2001 From: Ravan Scafi Date: Thu, 26 Sep 2019 18:45:24 -0300 Subject: [PATCH 116/116] Configure mongodb extension on travis --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index b3fd989f..a4416fdd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,8 +11,7 @@ cache: services: mongodb before_install: - - echo "extension = mongodb.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini - - pecl upgrade mongodb + - pecl install mongodb > /dev/null before_script: - composer install --no-interaction @@ -22,4 +21,4 @@ script: - vendor/bin/phpunit -c phpunit.xml.dist after_success: - - if [[ $(phpenv version-name) = "7.2" ]]; then php vendor/bin/php-coveralls -v; fi + - if [[ $(phpenv version-name) = "7.3" ]]; then php vendor/bin/php-coveralls -v; fi