From d3fd0b2e612a5c1262bf59f1cdca06674ef8f4f6 Mon Sep 17 00:00:00 2001 From: Giacomo Fabbian Date: Wed, 22 Jul 2020 13:06:17 +0200 Subject: [PATCH 1/5] feat: upgrade laravel-monogodb --- composer.json | 22 +- src/Eloquent/Builder.php | 180 ++++++ src/Eloquent/EmbedsRelationships.php | 80 +++ src/Eloquent/Model.php | 59 ++ src/Http/Models/MDModel.php | 2 +- src/Relationships/EmbedsMany.php | 342 ++++++++++ src/Relationships/EmbedsOne.php | 145 +++++ src/Relationships/EmbedsOneOrMany.php | 397 ++++++++++++ tests/EmbeddedRelationsTest.php | 882 ++++++++++++++++++++++++++ tests/Models/Address.php | 16 + tests/Models/Book.php | 20 + tests/Models/Client.php | 12 + tests/Models/Group.php | 12 + tests/Models/Location.php | 12 + tests/Models/Photo.php | 13 + tests/Models/Role.php | 12 + tests/Models/Scoped.php | 21 + tests/Models/Soft.php | 20 + tests/Models/User.php | 44 ++ tests/QueryBuilderTest.php | 745 ++++++++++++++++++++++ 20 files changed, 3024 insertions(+), 12 deletions(-) create mode 100644 src/Eloquent/Builder.php create mode 100644 src/Eloquent/EmbedsRelationships.php create mode 100644 src/Eloquent/Model.php create mode 100644 src/Relationships/EmbedsMany.php create mode 100644 src/Relationships/EmbedsOne.php create mode 100644 src/Relationships/EmbedsOneOrMany.php create mode 100644 tests/EmbeddedRelationsTest.php create mode 100644 tests/Models/Address.php create mode 100644 tests/Models/Book.php create mode 100644 tests/Models/Client.php create mode 100644 tests/Models/Group.php create mode 100644 tests/Models/Location.php create mode 100644 tests/Models/Photo.php create mode 100644 tests/Models/Role.php create mode 100644 tests/Models/Scoped.php create mode 100644 tests/Models/Soft.php create mode 100644 tests/Models/User.php create mode 100644 tests/QueryBuilderTest.php diff --git a/composer.json b/composer.json index c8c94b0..e60bb0a 100644 --- a/composer.json +++ b/composer.json @@ -4,21 +4,21 @@ "type": "library", "keywords": ["laravel","database","mongodb","eloquent", "relationships"], "require": { - "php": ">=7.1", + "php": ">=7.2", "ext-json": "*", "ext-mongodb": "*", - "illuminate/support": "^5.8|^6.0", - "illuminate/container": "^5.8|^6.0", - "illuminate/database": "^5.8|^6.0", - "illuminate/events": "^5.8|^6.0", - "jenssegers/mongodb": "3.6.4" + "illuminate/support": "^7.0", + "illuminate/container": "^7.0", + "illuminate/database": "^7.0", + "illuminate/events": "^7.0", + "mongodb/mongodb": "^1.6", + "jenssegers/mongodb": "4.0.0-alpha.1" }, "require-dev": { - "phpunit/phpunit": "^6.0|^7.0|^8.0", - "orchestra/testbench": "^3.1|^4.0", - "mockery/mockery": "^1.0", - "doctrine/dbal": "^2.5", - "phpunit/phpcov": "^6.0", + "phpunit/phpunit": "^8.4", + "orchestra/testbench": "^5.0", + "mockery/mockery": "^1.3.1", + "doctrine/dbal": "^2.6", "cedx/coveralls": "^11.2" }, "license": "MIT", diff --git a/src/Eloquent/Builder.php b/src/Eloquent/Builder.php new file mode 100644 index 0000000..f65236d --- /dev/null +++ b/src/Eloquent/Builder.php @@ -0,0 +1,180 @@ +model->getParentRelation()) { + $relation->performUpdate($this->model, $values); + + return 1; + } + + return $this->toBase()->update($this->addUpdatedAtColumn($values), $options); + } + + /** + * @inheritdoc + */ + public function insert(array $values) + { + // Intercept operations on embedded models and delegate logic + // to the parent relation instance. + if ($relation = $this->model->getParentRelation()) { + $relation->performInsert($this->model, $values); + + return true; + } + + return EloquentBuilder::insert($values); + } + + /** + * @inheritdoc + */ + public function insertGetId(array $values, $sequence = null) + { + // Intercept operations on embedded models and delegate logic + // to the parent relation instance. + if ($relation = $this->model->getParentRelation()) { + $relation->performInsert($this->model, $values); + + return $this->model->getKey(); + } + + return EloquentBuilder::insertGetId($values, $sequence); + } + + /** + * @inheritdoc + */ + public function delete() + { + // Intercept operations on embedded models and delegate logic + // to the parent relation instance. + if ($relation = $this->model->getParentRelation()) { + $relation->performDelete($this->model); + + return $this->model->getKey(); + } + + return EloquentBuilder::delete(); + } + + /** + * @inheritdoc + */ + public function increment($column, $amount = 1, array $extra = []) + { + // Intercept operations on embedded models and delegate logic + // to the parent relation instance. + if ($relation = $this->model->getParentRelation()) { + $value = $this->model->{$column}; + + // When doing increment and decrements, Eloquent will automatically + // sync the original attributes. We need to change the attribute + // temporary in order to trigger an update query. + $this->model->{$column} = null; + + $this->model->syncOriginalAttribute($column); + + $result = $this->model->update([$column => $value]); + + return $result; + } + + return EloquentBuilder::increment($column, $amount, $extra); + } + + /** + * @inheritdoc + */ + public function decrement($column, $amount = 1, array $extra = []) + { + // Intercept operations on embedded models and delegate logic + // to the parent relation instance. + if ($relation = $this->model->getParentRelation()) { + $value = $this->model->{$column}; + + // When doing increment and decrements, Eloquent will automatically + // sync the original attributes. We need to change the attribute + // temporary in order to trigger an update query. + $this->model->{$column} = null; + + $this->model->syncOriginalAttribute($column); + + return $this->model->update([$column => $value]); + } + + return EloquentBuilder::decrement($column, $amount, $extra); + } + + /** + * @inheritdoc + */ + public function chunkById($count, callable $callback, $column = '_id', $alias = null) + { + return EloquentBuilder::chunkById($count, $callback, $column, $alias); + } + + /** + * @inheritdoc + */ + public function raw($expression = null) + { + // Get raw results from the query builder. + $results = $this->query->raw($expression); + + // Convert MongoCursor results to a collection of models. + if ($results instanceof Cursor) { + $results = iterator_to_array($results, false); + + return $this->model->hydrate($results); + } // Convert Mongo BSONDocument to a single object. + elseif ($results instanceof BSONDocument) { + $results = $results->getArrayCopy(); + + return $this->model->newFromBuilder((array) $results); + } // The result is a single object. + elseif (is_array($results) && array_key_exists('_id', $results)) { + return $this->model->newFromBuilder((array) $results); + } + + return $results; + } + + /** + * Add the "updated at" column to an array of values. + * TODO Remove if https://github.com/laravel/framework/commit/6484744326531829341e1ff886cc9b628b20d73e + * wiil be reverted + * Issue in laravel frawework https://github.com/laravel/framework/issues/27791 + * @param array $values + * @return array + */ + protected function addUpdatedAtColumn(array $values) + { + if (!$this->model->usesTimestamps() || $this->model->getUpdatedAtColumn() === null) { + return $values; + } + + $column = $this->model->getUpdatedAtColumn(); + $values = array_merge( + [$column => $this->model->freshTimestampString()], + $values + ); + + return $values; + } + +} diff --git a/src/Eloquent/EmbedsRelationships.php b/src/Eloquent/EmbedsRelationships.php new file mode 100644 index 0000000..7a39100 --- /dev/null +++ b/src/Eloquent/EmbedsRelationships.php @@ -0,0 +1,80 @@ +newQuery(); + + $instance = new $related; + + return new EmbedsMany($query, $this, $instance, $localKey, $foreignKey, $relation); + } + + /** + * Define an embedded one-to-many relationship. + * @param string $related + * @param string $localKey + * @param string $foreignKey + * @param string $relation + * @return EmbedsOne + */ + protected function embedsOne($related, $localKey = null, $foreignKey = null, $relation = null) + { + // If no relation name was given, we will use this debug backtrace to extract + // the calling method's name and use that as the relationship name as most + // of the time this will be what we desire to use for the relationships. + if ($relation === null) { + list(, $caller) = debug_backtrace(false); + + $relation = $caller['function']; + } + + if ($localKey === null) { + $localKey = $relation; + } + + if ($foreignKey === null) { + $foreignKey = Str::snake(class_basename($this)); + } + + $query = $this->newQuery(); + + $instance = new $related; + + return new EmbedsOne($query, $this, $instance, $localKey, $foreignKey, $relation); + } +} diff --git a/src/Eloquent/Model.php b/src/Eloquent/Model.php new file mode 100644 index 0000000..2af1156 --- /dev/null +++ b/src/Eloquent/Model.php @@ -0,0 +1,59 @@ +newBaseQueryBuilder(); + + $value = $builder->convertKey($value); + } // Support keys in dot notation. + elseif (Str::contains($key, '.')) { + if (in_array($key, $this->getDates()) && $value) { + $value = $this->fromDateTime($value); + } + + Arr::set($this->attributes, $key, $value); + + return; + } + + return BaseModel::setAttribute($key, $value); + } + + /** + * @inheritdoc + */ + public function getAttribute($key) + { + // This checks for embedded relation support. + if (method_exists($this, $key) && !method_exists(self::class, $key)) { + return $this->getRelationValue($key); + } + + return BaseModel::getAttribute($key); + } + + /** + * {@inheritdoc} + */ + public function newEloquentBuilder($query) + { + return new Builder($query); + } +} diff --git a/src/Http/Models/MDModel.php b/src/Http/Models/MDModel.php index 2ba666c..848eb1e 100644 --- a/src/Http/Models/MDModel.php +++ b/src/Http/Models/MDModel.php @@ -2,7 +2,7 @@ namespace OfflineAgency\MongoAutoSync\Http\Models; -use Jenssegers\Mongodb\Eloquent\Model as Eloquent; +use OfflineAgency\MongoAutoSync\Eloquent\Model as Eloquent; use OfflineAgency\MongoAutoSync\Traits\Helper; use OfflineAgency\MongoAutoSync\Traits\MainMongoTrait; use OfflineAgency\MongoAutoSync\Traits\ModelAdditionalMethod; diff --git a/src/Relationships/EmbedsMany.php b/src/Relationships/EmbedsMany.php new file mode 100644 index 0000000..412b557 --- /dev/null +++ b/src/Relationships/EmbedsMany.php @@ -0,0 +1,342 @@ +setRelation($relation, $this->related->newCollection()); + } + + return $models; + } + + /** + * @inheritdoc + */ + public function getResults() + { + return $this->toCollection($this->getEmbedded()); + } + + /** + * Save a new model and attach it to the parent model. + * @param Model $model + * @return Model|bool + */ + public function performInsert(Model $model) + { + // Generate a new key if needed. + if ($model->getKeyName() == '_id' && !$model->getKey()) { + $model->setAttribute('_id', new ObjectID); + } + + // For deeply nested documents, let the parent handle the changes. + if ($this->isNested()) { + $this->associate($model); + return $this->parent->save() ? $model : false; + } + + // Push the new model to the database. + $result = $this->getBaseQuery()->push($this->localKey, $model->getAttributes(), true); + + // Attach the model to its parent. + if ($result) { + $this->associate($model); + } + + return $result ? $model : false; + } + + /** + * Save an existing model and attach it to the parent model. + * @param Model $model + * @return Model|bool + */ + public function performUpdate(Model $model) + { + // For deeply nested documents, let the parent handle the changes. + if ($this->isNested()) { + $this->associate($model); + + return $this->parent->save(); + } + + // Get the correct foreign key value. + $foreignKey = $this->getForeignKeyValue($model); + + $values = $this->getUpdateValues($model->getDirty(), $this->localKey . '.$.'); + + // Update document in database. + $result = $this->getBaseQuery()->where($this->localKey . '.' . $model->getKeyName(), $foreignKey) + ->update($values); + + // Attach the model to its parent. + if ($result) { + $this->associate($model); + } + + return $result ? $model : false; + } + + /** + * Delete an existing model and detach it from the parent model. + * @param Model $model + * @return int + */ + public function performDelete(Model $model) + { + // For deeply nested documents, let the parent handle the changes. + if ($this->isNested()) { + $this->dissociate($model); + + return $this->parent->save(); + } + + // Get the correct foreign key value. + $foreignKey = $this->getForeignKeyValue($model); + + $result = $this->getBaseQuery()->pull($this->localKey, [$model->getKeyName() => $foreignKey]); + + if ($result) { + $this->dissociate($model); + } + + return $result; + } + + /** + * Associate the model instance to the given parent, without saving it to the database. + * @param Model $model + * @return Model + */ + public function associate(Model $model) + { + if (!$this->contains($model)) { + return $this->associateNew($model); + } + + return $this->associateExisting($model); + } + + /** + * Dissociate the model instance from the given parent, without saving it to the database. + * @param mixed $ids + * @return int + */ + public function dissociate($ids = []) + { + $ids = $this->getIdsArrayFrom($ids); + + $records = $this->getEmbedded(); + + $primaryKey = $this->related->getKeyName(); + + // Remove the document from the parent model. + foreach ($records as $i => $record) { + if (in_array($record[$primaryKey], $ids)) { + unset($records[$i]); + } + } + + $this->setEmbedded($records); + + // We return the total number of deletes for the operation. The developers + // can then check this number as a boolean type value or get this total count + // of records deleted for logging, etc. + return count($ids); + } + + /** + * Destroy the embedded models for the given IDs. + * @param mixed $ids + * @return int + */ + public function destroy($ids = []) + { + $count = 0; + + $ids = $this->getIdsArrayFrom($ids); + + // Get all models matching the given ids. + $models = $this->getResults()->only($ids); + + // Pull the documents from the database. + foreach ($models as $model) { + if ($model->delete()) { + $count++; + } + } + + return $count; + } + + /** + * Delete all embedded models. + * @return int + */ + public function delete() + { + // Overwrite the local key with an empty array. + $result = $this->query->update([$this->localKey => []]); + + if ($result) { + $this->setEmbedded([]); + } + + return $result; + } + + /** + * Destroy alias. + * @param mixed $ids + * @return int + */ + public function detach($ids = []) + { + return $this->destroy($ids); + } + + /** + * Save alias. + * @param Model $model + * @return Model + */ + public function attach(Model $model) + { + return $this->save($model); + } + + /** + * Associate a new model instance to the given parent, without saving it to the database. + * @param Model $model + * @return Model + */ + protected function associateNew($model) + { + // Create a new key if needed. + if ($model->getKeyName() === '_id' && !$model->getAttribute('_id')) { + $model->setAttribute('_id', new ObjectID); + } + + $records = $this->getEmbedded(); + + // Add the new model to the embedded documents. + $records[] = $model->getAttributes(); + + return $this->setEmbedded($records); + } + + /** + * Associate an existing model instance to the given parent, without saving it to the database. + * @param Model $model + * @return Model + */ + protected function associateExisting($model) + { + // Get existing embedded documents. + $records = $this->getEmbedded(); + + $primaryKey = $this->related->getKeyName(); + + $key = $model->getKey(); + + // Replace the document in the parent model. + foreach ($records as &$record) { + if ($record[$primaryKey] == $key) { + $record = $model->getAttributes(); + break; + } + } + + return $this->setEmbedded($records); + } + + /** + * @param null $perPage + * @param array $columns + * @param string $pageName + * @param null $page + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + */ + public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) + { + $page = $page ?: Paginator::resolveCurrentPage($pageName); + $perPage = $perPage ?: $this->related->getPerPage(); + + $results = $this->getEmbedded(); + $results = $this->toCollection($results); + $total = $results->count(); + $start = ($page - 1) * $perPage; + + $sliced = $results->slice( + $start, + $perPage + ); + + return new LengthAwarePaginator( + $sliced, + $total, + $perPage, + $page, + [ + 'path' => Paginator::resolveCurrentPath() + ] + ); + } + + /** + * @inheritdoc + */ + protected function getEmbedded() + { + return parent::getEmbedded() ?: []; + } + + /** + * @inheritdoc + */ + protected function setEmbedded($models) + { + if (!is_array($models)) { + $models = [$models]; + } + + return parent::setEmbedded(array_values($models)); + } + + /** + * @inheritdoc + */ + public function __call($method, $parameters) + { + if (method_exists(Collection::class, $method)) { + return call_user_func_array([$this->getResults(), $method], $parameters); + } + + return parent::__call($method, $parameters); + } + + /** + * Get the name of the "where in" method for eager loading. + * @param \Illuminate\Database\Eloquent\Model $model + * @param string $key + * @return string + */ + protected function whereInMethod(EloquentModel $model, $key) + { + return 'whereIn'; + } +} diff --git a/src/Relationships/EmbedsOne.php b/src/Relationships/EmbedsOne.php new file mode 100644 index 0000000..065f03b --- /dev/null +++ b/src/Relationships/EmbedsOne.php @@ -0,0 +1,145 @@ +setRelation($relation, null); + } + + return $models; + } + + /** + * @inheritdoc + */ + public function getResults() + { + return $this->toModel($this->getEmbedded()); + } + + /** + * Save a new model and attach it to the parent model. + * @param Model $model + * @return Model|bool + */ + public function performInsert(Model $model) + { + // Generate a new key if needed. + if ($model->getKeyName() == '_id' && !$model->getKey()) { + $model->setAttribute('_id', new ObjectID); + } + + // For deeply nested documents, let the parent handle the changes. + if ($this->isNested()) { + $this->associate($model); + return $this->parent->save() ? $model : false; + } + + $result = $this->getBaseQuery()->update([$this->localKey => $model->getAttributes()]); + + // Attach the model to its parent. + if ($result) { + $this->associate($model); + } + + return $result ? $model : false; + } + + /** + * Save an existing model and attach it to the parent model. + * @param Model $model + * @return Model|bool + */ + public function performUpdate(Model $model) + { + if ($this->isNested()) { + $this->associate($model); + + return $this->parent->save(); + } + + $values = $this->getUpdateValues($model->getDirty(), $this->localKey . '.'); + + $result = $this->getBaseQuery()->update($values); + + // Attach the model to its parent. + if ($result) { + $this->associate($model); + } + + return $result ? $model : false; + } + + /** + * Delete an existing model and detach it from the parent model. + * @return int + */ + public function performDelete() + { + // For deeply nested documents, let the parent handle the changes. + if ($this->isNested()) { + $this->dissociate(); + return $this->parent->save(); + } + + // Overwrite the local key with an empty array. + $result = $this->getBaseQuery()->update([$this->localKey => null]); + + // Detach the model from its parent. + if ($result) { + $this->dissociate(); + } + + return $result; + } + + /** + * Attach the model to its parent. + * @param Model $model + * @return Model + */ + public function associate(Model $model) + { + return $this->setEmbedded($model->getAttributes()); + } + + /** + * Detach the model from its parent. + * @return Model + */ + public function dissociate() + { + return $this->setEmbedded(null); + } + + /** + * Delete all embedded models. + * @return int + */ + public function delete() + { + return $this->performDelete(); + } + + /** + * Get the name of the "where in" method for eager loading. + * @param \Illuminate\Database\Eloquent\Model $model + * @param string $key + * @return string + */ + protected function whereInMethod(EloquentModel $model, $key) + { + return 'whereIn'; + } +} diff --git a/src/Relationships/EmbedsOneOrMany.php b/src/Relationships/EmbedsOneOrMany.php new file mode 100644 index 0000000..0cec414 --- /dev/null +++ b/src/Relationships/EmbedsOneOrMany.php @@ -0,0 +1,397 @@ +query = $query; + $this->parent = $parent; + $this->related = $related; + $this->localKey = $localKey; + $this->foreignKey = $foreignKey; + $this->relation = $relation; + + // If this is a nested relation, we need to get the parent query instead. + if ($parentRelation = $this->getParentRelation()) { + $this->query = $parentRelation->getQuery(); + } + + $this->addConstraints(); + } + + /** + * @inheritdoc + */ + public function addConstraints() + { + if (static::$constraints) { + $this->query->where($this->getQualifiedParentKeyName(), '=', $this->getParentKey()); + } + } + + /** + * @inheritdoc + */ + public function addEagerConstraints(array $models) + { + // There are no eager loading constraints. + } + + /** + * @inheritdoc + */ + public function match(array $models, Collection $results, $relation) + { + foreach ($models as $model) { + $results = $model->$relation()->getResults(); + + $model->setParentRelation($this); + + $model->setRelation($relation, $results); + } + + return $models; + } + + /** + * Shorthand to get the results of the relationship. + * @param array $columns + * @return Collection + */ + public function get($columns = ['*']) + { + return $this->getResults(); + } + + /** + * Get the number of embedded models. + * @return int + */ + public function count() + { + return count($this->getEmbedded()); + } + + /** + * Attach a model instance to the parent model. + * @param Model $model + * @return Model|bool + */ + public function save(Model $model) + { + $model->setParentRelation($this); + + return $model->save() ? $model : false; + } + + /** + * Attach a collection of models to the parent instance. + * @param Collection|array $models + * @return Collection|array + */ + public function saveMany($models) + { + foreach ($models as $model) { + $this->save($model); + } + + return $models; + } + + /** + * Create a new instance of the related model. + * @param array $attributes + * @return Model + */ + public function create(array $attributes = []) + { + // Here we will set the raw attributes to avoid hitting the "fill" method so + // that we do not have to worry about a mass accessor rules blocking sets + // on the models. Otherwise, some of these attributes will not get set. + $instance = $this->related->newInstance($attributes); + + $instance->setParentRelation($this); + + $instance->save(); + + return $instance; + } + + /** + * Create an array of new instances of the related model. + * @param array $records + * @return array + */ + public function createMany(array $records) + { + $instances = []; + + foreach ($records as $record) { + $instances[] = $this->create($record); + } + + return $instances; + } + + /** + * Transform single ID, single Model or array of Models into an array of IDs. + * @param mixed $ids + * @return array + */ + protected function getIdsArrayFrom($ids) + { + if ($ids instanceof \Illuminate\Support\Collection) { + $ids = $ids->all(); + } + + if (!is_array($ids)) { + $ids = [$ids]; + } + + foreach ($ids as &$id) { + if ($id instanceof Model) { + $id = $id->getKey(); + } + } + + return $ids; + } + + /** + * @inheritdoc + */ + protected function getEmbedded() + { + // Get raw attributes to skip relations and accessors. + $attributes = $this->parent->getAttributes(); + + // Get embedded models form parent attributes. + $embedded = isset($attributes[$this->localKey]) ? (array) $attributes[$this->localKey] : null; + + return $embedded; + } + + /** + * @inheritdoc + */ + protected function setEmbedded($records) + { + // Assign models to parent attributes array. + $attributes = $this->parent->getAttributes(); + $attributes[$this->localKey] = $records; + + // Set raw attributes to skip mutators. + $this->parent->setRawAttributes($attributes); + + // Set the relation on the parent. + return $this->parent->setRelation($this->relation, $records === null ? null : $this->getResults()); + } + + /** + * Get the foreign key value for the relation. + * @param mixed $id + * @return mixed + */ + protected function getForeignKeyValue($id) + { + if ($id instanceof Model) { + $id = $id->getKey(); + } + + // Convert the id to MongoId if necessary. + return $this->getBaseQuery()->convertKey($id); + } + + /** + * Convert an array of records to a Collection. + * @param array $records + * @return Collection + */ + protected function toCollection(array $records = []) + { + $models = []; + + foreach ($records as $attributes) { + $models[] = $this->toModel($attributes); + } + + if (count($models) > 0) { + $models = $this->eagerLoadRelations($models); + } + + return $this->related->newCollection($models); + } + + /** + * Create a related model instanced. + * @param array $attributes + * @return Model + */ + protected function toModel($attributes = []) + { + if ($attributes === null) { + return; + } + + $connection = $this->related->getConnection(); + + $model = $this->related->newFromBuilder( + (array) $attributes, + $connection ? $connection->getName() : null + ); + + $model->setParentRelation($this); + + $model->setRelation($this->foreignKey, $this->parent); + + // If you remove this, you will get segmentation faults! + $model->setHidden(array_merge($model->getHidden(), [$this->foreignKey])); + + return $model; + } + + /** + * Get the relation instance of the parent. + * @return Relation + */ + protected function getParentRelation() + { + return $this->parent->getParentRelation(); + } + + /** + * @inheritdoc + */ + public function getQuery() + { + // Because we are sharing this relation instance to models, we need + // to make sure we use separate query instances. + return clone $this->query; + } + + /** + * @inheritdoc + */ + public function getBaseQuery() + { + // Because we are sharing this relation instance to models, we need + // to make sure we use separate query instances. + return clone $this->query->getQuery(); + } + + /** + * Check if this relation is nested in another relation. + * @return bool + */ + protected function isNested() + { + return $this->getParentRelation() != null; + } + + /** + * Get the fully qualified local key name. + * @param string $glue + * @return string + */ + protected function getPathHierarchy($glue = '.') + { + if ($parentRelation = $this->getParentRelation()) { + return $parentRelation->getPathHierarchy($glue) . $glue . $this->localKey; + } + + return $this->localKey; + } + + /** + * @inheritdoc + */ + public function getQualifiedParentKeyName() + { + if ($parentRelation = $this->getParentRelation()) { + return $parentRelation->getPathHierarchy() . '.' . $this->parent->getKeyName(); + } + + return $this->parent->getKeyName(); + } + + /** + * Get the primary key value of the parent. + * @return string + */ + protected function getParentKey() + { + return $this->parent->getKey(); + } + + /** + * Return update values + * @param $array + * @param string $prepend + * @return array + */ + public static function getUpdateValues($array, $prepend = '') + { + $results = []; + + foreach ($array as $key => $value) { + $results[$prepend . $key] = $value; + } + + return $results; + } + + /** + * Get the foreign key for the relationship. + * @return string + */ + public function getQualifiedForeignKeyName() + { + return $this->foreignKey; + } + + /** + * Get the name of the "where in" method for eager loading. + * @param \Illuminate\Database\Eloquent\Model $model + * @param string $key + * @return string + */ + protected function whereInMethod(EloquentModel $model, $key) + { + return 'whereIn'; + } +} diff --git a/tests/EmbeddedRelationsTest.php b/tests/EmbeddedRelationsTest.php new file mode 100644 index 0000000..049b17d --- /dev/null +++ b/tests/EmbeddedRelationsTest.php @@ -0,0 +1,882 @@ + 'John Doe']); + $address = new Address(['city' => 'London']); + + $address->setEventDispatcher($events = Mockery::mock(Dispatcher::class)); + $events->shouldReceive('dispatch')->with('eloquent.retrieved: ' . get_class($address), Mockery::any()); + $events->shouldReceive('until') + ->once() + ->with('eloquent.saving: ' . get_class($address), $address) + ->andReturn(true); + $events->shouldReceive('until') + ->once() + ->with('eloquent.creating: ' . get_class($address), $address) + ->andReturn(true); + $events->shouldReceive('dispatch')->once()->with('eloquent.created: ' . get_class($address), $address); + $events->shouldReceive('dispatch')->once()->with('eloquent.saved: ' . get_class($address), $address); + + $address = $user->addresses()->save($address); + $address->unsetEventDispatcher(); + + $this->assertNotNull($user->addresses); + $this->assertInstanceOf(Collection::class, $user->addresses); + $this->assertEquals(['London'], $user->addresses->pluck('city')->all()); + $this->assertInstanceOf(DateTime::class, $address->created_at); + $this->assertInstanceOf(DateTime::class, $address->updated_at); + $this->assertNotNull($address->_id); + $this->assertIsString($address->_id); + + $raw = $address->getAttributes(); + $this->assertInstanceOf(ObjectId::class, $raw['_id']); + + $address = $user->addresses()->save(new Address(['city' => 'Paris'])); + + $user = User::find($user->_id); + $this->assertEquals(['London', 'Paris'], $user->addresses->pluck('city')->all()); + + $address->setEventDispatcher($events = Mockery::mock(Dispatcher::class)); + $events->shouldReceive('dispatch')->with('eloquent.retrieved: ' . get_class($address), Mockery::any()); + $events->shouldReceive('until') + ->once() + ->with('eloquent.saving: ' . get_class($address), $address) + ->andReturn(true); + $events->shouldReceive('until') + ->once() + ->with('eloquent.updating: ' . get_class($address), $address) + ->andReturn(true); + $events->shouldReceive('dispatch')->once()->with('eloquent.updated: ' . get_class($address), $address); + $events->shouldReceive('dispatch')->once()->with('eloquent.saved: ' . get_class($address), $address); + + $address->city = 'New York'; + $user->addresses()->save($address); + $address->unsetEventDispatcher(); + + $this->assertCount(2, $user->addresses); + $this->assertCount(2, $user->addresses()->get()); + $this->assertEquals(2, $user->addresses->count()); + $this->assertEquals(2, $user->addresses()->count()); + $this->assertEquals(['London', 'New York'], $user->addresses->pluck('city')->all()); + + $freshUser = User::find($user->_id); + $this->assertEquals(['London', 'New York'], $freshUser->addresses->pluck('city')->all()); + + $address = $user->addresses->first(); + $this->assertEquals('London', $address->city); + $this->assertInstanceOf(DateTime::class, $address->created_at); + $this->assertInstanceOf(DateTime::class, $address->updated_at); + $this->assertInstanceOf(User::class, $address->user); + $this->assertEmpty($address->relationsToArray()); // prevent infinite loop + + $user = User::find($user->_id); + $user->addresses()->save(new Address(['city' => 'Bruxelles'])); + $this->assertEquals(['London', 'New York', 'Bruxelles'], $user->addresses->pluck('city')->all()); + + $address = $user->addresses[1]; + $address->city = "Manhattan"; + $user->addresses()->save($address); + $this->assertEquals(['London', 'Manhattan', 'Bruxelles'], $user->addresses->pluck('city')->all()); + + $freshUser = User::find($user->_id); + $this->assertEquals(['London', 'Manhattan', 'Bruxelles'], $freshUser->addresses->pluck('city')->all()); + } + + public function testEmbedsToArray() + { + $user = User::create(['name' => 'John Doe']); + $user->addresses()->saveMany([new Address(['city' => 'London']), new Address(['city' => 'Bristol'])]); + + $array = $user->toArray(); + $this->assertArrayNotHasKey('_addresses', $array); + $this->assertArrayHasKey('addresses', $array); + } + + public function testEmbedsManyAssociate() + { + $user = User::create(['name' => 'John Doe']); + $address = new Address(['city' => 'London']); + + $user->addresses()->associate($address); + $this->assertEquals(['London'], $user->addresses->pluck('city')->all()); + $this->assertNotNull($address->_id); + + $freshUser = User::find($user->_id); + $this->assertEquals([], $freshUser->addresses->pluck('city')->all()); + + $address->city = 'Londinium'; + $user->addresses()->associate($address); + $this->assertEquals(['Londinium'], $user->addresses->pluck('city')->all()); + + $freshUser = User::find($user->_id); + $this->assertEquals([], $freshUser->addresses->pluck('city')->all()); + } + + public function testEmbedsManySaveMany() + { + $user = User::create(['name' => 'John Doe']); + $user->addresses()->saveMany([new Address(['city' => 'London']), new Address(['city' => 'Bristol'])]); + $this->assertEquals(['London', 'Bristol'], $user->addresses->pluck('city')->all()); + + $freshUser = User::find($user->id); + $this->assertEquals(['London', 'Bristol'], $freshUser->addresses->pluck('city')->all()); + } + + public function testEmbedsManyDuplicate() + { + $user = User::create(['name' => 'John Doe']); + $address = new Address(['city' => 'London']); + $user->addresses()->save($address); + $user->addresses()->save($address); + $this->assertEquals(1, $user->addresses->count()); + $this->assertEquals(['London'], $user->addresses->pluck('city')->all()); + + $user = User::find($user->id); + $this->assertEquals(1, $user->addresses->count()); + + $address->city = 'Paris'; + $user->addresses()->save($address); + $this->assertEquals(1, $user->addresses->count()); + $this->assertEquals(['Paris'], $user->addresses->pluck('city')->all()); + + $user->addresses()->create(['_id' => $address->_id, 'city' => 'Bruxelles']); + $this->assertEquals(1, $user->addresses->count()); + $this->assertEquals(['Bruxelles'], $user->addresses->pluck('city')->all()); + } + + public function testEmbedsManyCreate() + { + $user = User::create([]); + $address = $user->addresses()->create(['city' => 'Bruxelles']); + $this->assertInstanceOf(Address::class, $address); + $this->assertIsString($address->_id); + $this->assertEquals(['Bruxelles'], $user->addresses->pluck('city')->all()); + + $raw = $address->getAttributes(); + $this->assertInstanceOf(ObjectId::class, $raw['_id']); + + $freshUser = User::find($user->id); + $this->assertEquals(['Bruxelles'], $freshUser->addresses->pluck('city')->all()); + + $user = User::create([]); + $address = $user->addresses()->create(['_id' => '', 'city' => 'Bruxelles']); + $this->assertIsString($address->_id); + + $raw = $address->getAttributes(); + $this->assertInstanceOf(ObjectId::class, $raw['_id']); + } + + public function testEmbedsManyCreateMany() + { + $user = User::create([]); + list($bruxelles, $paris) = $user->addresses()->createMany([['city' => 'Bruxelles'], ['city' => 'Paris']]); + $this->assertInstanceOf(Address::class, $bruxelles); + $this->assertEquals('Bruxelles', $bruxelles->city); + $this->assertEquals(['Bruxelles', 'Paris'], $user->addresses->pluck('city')->all()); + + $freshUser = User::find($user->id); + $this->assertEquals(['Bruxelles', 'Paris'], $freshUser->addresses->pluck('city')->all()); + } + + public function testEmbedsManyDestroy() + { + $user = User::create(['name' => 'John Doe']); + $user->addresses()->saveMany([ + new Address(['city' => 'London']), + new Address(['city' => 'Bristol']), + new Address(['city' => 'Bruxelles']), + ]); + + $address = $user->addresses->first(); + + $address->setEventDispatcher($events = Mockery::mock(Dispatcher::class)); + $events->shouldReceive('dispatch')->with('eloquent.retrieved: ' . get_class($address), Mockery::any()); + $events->shouldReceive('until') + ->once() + ->with('eloquent.deleting: ' . get_class($address), Mockery::type(Address::class)) + ->andReturn(true); + $events->shouldReceive('dispatch') + ->once() + ->with('eloquent.deleted: ' . get_class($address), Mockery::type(Address::class)); + + $user->addresses()->destroy($address->_id); + $this->assertEquals(['Bristol', 'Bruxelles'], $user->addresses->pluck('city')->all()); + + $address->unsetEventDispatcher(); + + $address = $user->addresses->first(); + $user->addresses()->destroy($address); + $this->assertEquals(['Bruxelles'], $user->addresses->pluck('city')->all()); + + $user->addresses()->create(['city' => 'Paris']); + $user->addresses()->create(['city' => 'San Francisco']); + + $freshUser = User::find($user->id); + $this->assertEquals(['Bruxelles', 'Paris', 'San Francisco'], $freshUser->addresses->pluck('city')->all()); + + $ids = $user->addresses->pluck('_id'); + $user->addresses()->destroy($ids); + $this->assertEquals([], $user->addresses->pluck('city')->all()); + + $freshUser = User::find($user->id); + $this->assertEquals([], $freshUser->addresses->pluck('city')->all()); + + list($london, $bristol, $bruxelles) = $user->addresses()->saveMany([ + new Address(['city' => 'London']), + new Address(['city' => 'Bristol']), + new Address(['city' => 'Bruxelles']), + ]); + $user->addresses()->destroy([$london, $bruxelles]); + $this->assertEquals(['Bristol'], $user->addresses->pluck('city')->all()); + } + + public function testEmbedsManyDelete() + { + $user = User::create(['name' => 'John Doe']); + $user->addresses()->saveMany([ + new Address(['city' => 'London']), + new Address(['city' => 'Bristol']), + new Address(['city' => 'Bruxelles']), + ]); + + $address = $user->addresses->first(); + + $address->setEventDispatcher($events = Mockery::mock(Dispatcher::class)); + $events->shouldReceive('dispatch')->with('eloquent.retrieved: ' . get_class($address), Mockery::any()); + $events->shouldReceive('until') + ->once() + ->with('eloquent.deleting: ' . get_class($address), Mockery::type(Address::class)) + ->andReturn(true); + $events->shouldReceive('dispatch') + ->once() + ->with('eloquent.deleted: ' . get_class($address), Mockery::type(Address::class)); + + $address->delete(); + + $this->assertEquals(2, $user->addresses()->count()); + $this->assertEquals(2, $user->addresses->count()); + + $address->unsetEventDispatcher(); + + $address = $user->addresses->first(); + $address->delete(); + + $user = User::where('name', 'John Doe')->first(); + $this->assertEquals(1, $user->addresses()->count()); + $this->assertEquals(1, $user->addresses->count()); + } + + public function testEmbedsManyDissociate() + { + $user = User::create([]); + $cordoba = $user->addresses()->create(['city' => 'Cordoba']); + + $user->addresses()->dissociate($cordoba->id); + + $freshUser = User::find($user->id); + $this->assertEquals(0, $user->addresses->count()); + $this->assertEquals(1, $freshUser->addresses->count()); + } + + public function testEmbedsManyAliases() + { + $user = User::create(['name' => 'John Doe']); + $address = new Address(['city' => 'London']); + + $address = $user->addresses()->attach($address); + $this->assertEquals(['London'], $user->addresses->pluck('city')->all()); + + $user->addresses()->detach($address); + $this->assertEquals([], $user->addresses->pluck('city')->all()); + } + + public function testEmbedsManyCreatingEventReturnsFalse() + { + $user = User::create(['name' => 'John Doe']); + $address = new Address(['city' => 'London']); + + $address->setEventDispatcher($events = Mockery::mock(Dispatcher::class)); + $events->shouldReceive('dispatch')->with('eloquent.retrieved: ' . get_class($address), Mockery::any()); + $events->shouldReceive('until') + ->once() + ->with('eloquent.saving: ' . get_class($address), $address) + ->andReturn(true); + $events->shouldReceive('until') + ->once() + ->with('eloquent.creating: ' . get_class($address), $address) + ->andReturn(false); + + $this->assertFalse($user->addresses()->save($address)); + $address->unsetEventDispatcher(); + } + + public function testEmbedsManySavingEventReturnsFalse() + { + $user = User::create(['name' => 'John Doe']); + $address = new Address(['city' => 'Paris']); + $address->exists = true; + + $address->setEventDispatcher($events = Mockery::mock(Dispatcher::class)); + $events->shouldReceive('dispatch')->with('eloquent.retrieved: ' . get_class($address), Mockery::any()); + $events->shouldReceive('until') + ->once() + ->with('eloquent.saving: ' . get_class($address), $address) + ->andReturn(false); + + $this->assertFalse($user->addresses()->save($address)); + $address->unsetEventDispatcher(); + } + + public function testEmbedsManyUpdatingEventReturnsFalse() + { + $user = User::create(['name' => 'John Doe']); + $address = new Address(['city' => 'New York']); + $user->addresses()->save($address); + + $address->setEventDispatcher($events = Mockery::mock(Dispatcher::class)); + $events->shouldReceive('dispatch')->with('eloquent.retrieved: ' . get_class($address), Mockery::any()); + $events->shouldReceive('until') + ->once() + ->with('eloquent.saving: ' . get_class($address), $address) + ->andReturn(true); + $events->shouldReceive('until') + ->once() + ->with('eloquent.updating: ' . get_class($address), $address) + ->andReturn(false); + + $address->city = 'Warsaw'; + + $this->assertFalse($user->addresses()->save($address)); + $address->unsetEventDispatcher(); + } + + public function testEmbedsManyDeletingEventReturnsFalse() + { + $user = User::create(['name' => 'John Doe']); + $user->addresses()->save(new Address(['city' => 'New York'])); + + $address = $user->addresses->first(); + + $address->setEventDispatcher($events = Mockery::mock(Dispatcher::class)); + $events->shouldReceive('dispatch')->with('eloquent.retrieved: ' . get_class($address), Mockery::any()); + $events->shouldReceive('until') + ->once() + ->with('eloquent.deleting: ' . get_class($address), Mockery::mustBe($address)) + ->andReturn(false); + + $this->assertEquals(0, $user->addresses()->destroy($address)); + $this->assertEquals(['New York'], $user->addresses->pluck('city')->all()); + + $address->unsetEventDispatcher(); + } + + public function testEmbedsManyFindOrContains() + { + $user = User::create(['name' => 'John Doe']); + $address1 = $user->addresses()->save(new Address(['city' => 'New York'])); + $address2 = $user->addresses()->save(new Address(['city' => 'Paris'])); + + $address = $user->addresses()->find($address1->_id); + $this->assertEquals($address->city, $address1->city); + + $address = $user->addresses()->find($address2->_id); + $this->assertEquals($address->city, $address2->city); + + $this->assertTrue($user->addresses()->contains($address2->_id)); + $this->assertFalse($user->addresses()->contains('123')); + } + + public function testEmbedsManyEagerLoading() + { + $user1 = User::create(['name' => 'John Doe']); + $user1->addresses()->save(new Address(['city' => 'New York'])); + $user1->addresses()->save(new Address(['city' => 'Paris'])); + + $user2 = User::create(['name' => 'Jane Doe']); + $user2->addresses()->save(new Address(['city' => 'Berlin'])); + $user2->addresses()->save(new Address(['city' => 'Paris'])); + + $user = User::find($user1->id); + $relations = $user->getRelations(); + $this->assertArrayNotHasKey('addresses', $relations); + $this->assertArrayHasKey('addresses', $user->toArray()); + $this->assertIsArray($user->toArray()['addresses']); + + $user = User::with('addresses')->get()->first(); + $relations = $user->getRelations(); + $this->assertArrayHasKey('addresses', $relations); + $this->assertEquals(2, $relations['addresses']->count()); + $this->assertArrayHasKey('addresses', $user->toArray()); + $this->assertIsArray($user->toArray()['addresses']); + } + + public function testEmbedsManyDeleteAll() + { + $user1 = User::create(['name' => 'John Doe']); + $user1->addresses()->save(new Address(['city' => 'New York'])); + $user1->addresses()->save(new Address(['city' => 'Paris'])); + + $user2 = User::create(['name' => 'Jane Doe']); + $user2->addresses()->save(new Address(['city' => 'Berlin'])); + $user2->addresses()->save(new Address(['city' => 'Paris'])); + + $user1->addresses()->delete(); + $this->assertEquals(0, $user1->addresses()->count()); + $this->assertEquals(0, $user1->addresses->count()); + $this->assertEquals(2, $user2->addresses()->count()); + $this->assertEquals(2, $user2->addresses->count()); + + $user1 = User::find($user1->id); + $user2 = User::find($user2->id); + $this->assertEquals(0, $user1->addresses()->count()); + $this->assertEquals(0, $user1->addresses->count()); + $this->assertEquals(2, $user2->addresses()->count()); + $this->assertEquals(2, $user2->addresses->count()); + } + + public function testEmbedsManyCollectionMethods() + { + $user = User::create(['name' => 'John Doe']); + $user->addresses()->save(new Address([ + 'city' => 'Paris', + 'country' => 'France', + 'visited' => 4, + 'created_at' => new DateTime('3 days ago'), + ])); + $user->addresses()->save(new Address([ + 'city' => 'Bruges', + 'country' => 'Belgium', + 'visited' => 7, + 'created_at' => new DateTime('5 days ago'), + ])); + $user->addresses()->save(new Address([ + 'city' => 'Brussels', + 'country' => 'Belgium', + 'visited' => 2, + 'created_at' => new DateTime('4 days ago'), + ])); + $user->addresses()->save(new Address([ + 'city' => 'Ghent', + 'country' => 'Belgium', + 'visited' => 13, + 'created_at' => new DateTime('2 days ago'), + ])); + + $this->assertEquals(['Paris', 'Bruges', 'Brussels', 'Ghent'], $user->addresses()->pluck('city')->all()); + $this->assertEquals(['Bruges', 'Brussels', 'Ghent', 'Paris'], $user->addresses() + ->sortBy('city') + ->pluck('city') + ->all()); + $this->assertEquals([], $user->addresses()->where('city', 'New York')->pluck('city')->all()); + $this->assertEquals(['Bruges', 'Brussels', 'Ghent'], $user->addresses() + ->where('country', 'Belgium') + ->pluck('city') + ->all()); + $this->assertEquals(['Bruges', 'Brussels', 'Ghent'], $user->addresses() + ->where('country', 'Belgium') + ->sortBy('city') + ->pluck('city') + ->all()); + + $results = $user->addresses->first(); + $this->assertInstanceOf(Address::class, $results); + + $results = $user->addresses()->where('country', 'Belgium'); + $this->assertInstanceOf(Collection::class, $results); + $this->assertEquals(3, $results->count()); + + $results = $user->addresses()->whereIn('visited', [7, 13]); + $this->assertEquals(2, $results->count()); + } + + public function testEmbedsOne() + { + $user = User::create(['name' => 'John Doe']); + $father = new User(['name' => 'Mark Doe']); + + $father->setEventDispatcher($events = Mockery::mock(Dispatcher::class)); + $events->shouldReceive('dispatch')->with('eloquent.retrieved: ' . get_class($father), Mockery::any()); + $events->shouldReceive('until') + ->once() + ->with('eloquent.saving: ' . get_class($father), $father) + ->andReturn(true); + $events->shouldReceive('until') + ->once() + ->with('eloquent.creating: ' . get_class($father), $father) + ->andReturn(true); + $events->shouldReceive('dispatch')->once()->with('eloquent.created: ' . get_class($father), $father); + $events->shouldReceive('dispatch')->once()->with('eloquent.saved: ' . get_class($father), $father); + + $father = $user->father()->save($father); + $father->unsetEventDispatcher(); + + $this->assertNotNull($user->father); + $this->assertEquals('Mark Doe', $user->father->name); + $this->assertInstanceOf(DateTime::class, $father->created_at); + $this->assertInstanceOf(DateTime::class, $father->updated_at); + $this->assertNotNull($father->_id); + $this->assertIsString($father->_id); + + $raw = $father->getAttributes(); + $this->assertInstanceOf(ObjectId::class, $raw['_id']); + + $father->setEventDispatcher($events = Mockery::mock(Dispatcher::class)); + $events->shouldReceive('dispatch')->with('eloquent.retrieved: ' . get_class($father), Mockery::any()); + $events->shouldReceive('until') + ->once() + ->with('eloquent.saving: ' . get_class($father), $father) + ->andReturn(true); + $events->shouldReceive('until') + ->once() + ->with('eloquent.updating: ' . get_class($father), $father) + ->andReturn(true); + $events->shouldReceive('dispatch')->once()->with('eloquent.updated: ' . get_class($father), $father); + $events->shouldReceive('dispatch')->once()->with('eloquent.saved: ' . get_class($father), $father); + + $father->name = 'Tom Doe'; + $user->father()->save($father); + $father->unsetEventDispatcher(); + + $this->assertNotNull($user->father); + $this->assertEquals('Tom Doe', $user->father->name); + + $father = new User(['name' => 'Jim Doe']); + + $father->setEventDispatcher($events = Mockery::mock(Dispatcher::class)); + $events->shouldReceive('dispatch')->with('eloquent.retrieved: ' . get_class($father), Mockery::any()); + $events->shouldReceive('until') + ->once() + ->with('eloquent.saving: ' . get_class($father), $father) + ->andReturn(true); + $events->shouldReceive('until') + ->once() + ->with('eloquent.creating: ' . get_class($father), $father) + ->andReturn(true); + $events->shouldReceive('dispatch')->once()->with('eloquent.created: ' . get_class($father), $father); + $events->shouldReceive('dispatch')->once()->with('eloquent.saved: ' . get_class($father), $father); + + $father = $user->father()->save($father); + $father->unsetEventDispatcher(); + + $this->assertNotNull($user->father); + $this->assertEquals('Jim Doe', $user->father->name); + } + + public function testEmbedsOneAssociate() + { + $user = User::create(['name' => 'John Doe']); + $father = new User(['name' => 'Mark Doe']); + + $father->setEventDispatcher($events = Mockery::mock(Dispatcher::class)); + $events->shouldReceive('dispatch')->with('eloquent.retrieved: ' . get_class($father), Mockery::any()); + $events->shouldReceive('until')->times(0)->with('eloquent.saving: ' . get_class($father), $father); + + $father = $user->father()->associate($father); + $father->unsetEventDispatcher(); + + $this->assertNotNull($user->father); + $this->assertEquals('Mark Doe', $user->father->name); + } + + public function testEmbedsOneNullAssociation() + { + $user = User::create(); + $this->assertNull($user->father); + } + + public function testEmbedsOneDelete() + { + $user = User::create(['name' => 'John Doe']); + $father = $user->father()->save(new User(['name' => 'Mark Doe'])); + + $user->father()->delete(); + $this->assertNull($user->father); + } + + public function testEmbedsManyToArray() + { + /** @var User $user */ + $user = User::create(['name' => 'John Doe']); + $user->addresses()->save(new Address(['city' => 'New York'])); + $user->addresses()->save(new Address(['city' => 'Paris'])); + $user->addresses()->save(new Address(['city' => 'Brussels'])); + + $array = $user->toArray(); + $this->assertArrayHasKey('addresses', $array); + $this->assertIsArray($array['addresses']); + } + + public function testEmbeddedSave() + { + /** @var User $user */ + $user = User::create(['name' => 'John Doe']); + /** @var \Address $address */ + $address = $user->addresses()->create(['city' => 'New York']); + $father = $user->father()->create(['name' => 'Mark Doe']); + + $address->city = 'Paris'; + $address->save(); + + $father->name = 'Steve Doe'; + $father->save(); + + $this->assertEquals('Paris', $user->addresses->first()->city); + $this->assertEquals('Steve Doe', $user->father->name); + + $user = User::where('name', 'John Doe')->first(); + $this->assertEquals('Paris', $user->addresses->first()->city); + $this->assertEquals('Steve Doe', $user->father->name); + + $address = $user->addresses()->first(); + $father = $user->father; + + $address->city = 'Ghent'; + $address->save(); + + $father->name = 'Mark Doe'; + $father->save(); + + $this->assertEquals('Ghent', $user->addresses->first()->city); + $this->assertEquals('Mark Doe', $user->father->name); + + $user = User::where('name', 'John Doe')->first(); + $this->assertEquals('Ghent', $user->addresses->first()->city); + $this->assertEquals('Mark Doe', $user->father->name); + } + + public function testNestedEmbedsOne() + { + $user = User::create(['name' => 'John Doe']); + $father = $user->father()->create(['name' => 'Mark Doe']); + $grandfather = $father->father()->create(['name' => 'Steve Doe']); + $greatgrandfather = $grandfather->father()->create(['name' => 'Tom Doe']); + + $user->name = 'Tim Doe'; + $user->save(); + + $father->name = 'Sven Doe'; + $father->save(); + + $greatgrandfather->name = 'Ron Doe'; + $greatgrandfather->save(); + + $this->assertEquals('Tim Doe', $user->name); + $this->assertEquals('Sven Doe', $user->father->name); + $this->assertEquals('Steve Doe', $user->father->father->name); + $this->assertEquals('Ron Doe', $user->father->father->father->name); + + $user = User::where('name', 'Tim Doe')->first(); + $this->assertEquals('Tim Doe', $user->name); + $this->assertEquals('Sven Doe', $user->father->name); + $this->assertEquals('Steve Doe', $user->father->father->name); + $this->assertEquals('Ron Doe', $user->father->father->father->name); + } + + public function testNestedEmbedsMany() + { + $user = User::create(['name' => 'John Doe']); + $country1 = $user->addresses()->create(['country' => 'France']); + $country2 = $user->addresses()->create(['country' => 'Belgium']); + $city1 = $country1->addresses()->create(['city' => 'Paris']); + $city2 = $country2->addresses()->create(['city' => 'Ghent']); + $city3 = $country2->addresses()->create(['city' => 'Brussels']); + + $city3->city = 'Bruges'; + $city3->save(); + + $this->assertEquals(2, $user->addresses()->count()); + $this->assertEquals(1, $user->addresses()->first()->addresses()->count()); + $this->assertEquals(2, $user->addresses()->last()->addresses()->count()); + + $user = User::where('name', 'John Doe')->first(); + $this->assertEquals(2, $user->addresses()->count()); + $this->assertEquals(1, $user->addresses()->first()->addresses()->count()); + $this->assertEquals(2, $user->addresses()->last()->addresses()->count()); + } + + public function testNestedMixedEmbeds() + { + $user = User::create(['name' => 'John Doe']); + $father = $user->father()->create(['name' => 'Mark Doe']); + $country1 = $father->addresses()->create(['country' => 'France']); + $country2 = $father->addresses()->create(['country' => 'Belgium']); + + $country2->country = 'England'; + $country2->save(); + + $father->name = 'Steve Doe'; + $father->save(); + + $this->assertEquals('France', $user->father->addresses()->first()->country); + $this->assertEquals('England', $user->father->addresses()->last()->country); + $this->assertEquals('Steve Doe', $user->father->name); + + $user = User::where('name', 'John Doe')->first(); + $this->assertEquals('France', $user->father->addresses()->first()->country); + $this->assertEquals('England', $user->father->addresses()->last()->country); + $this->assertEquals('Steve Doe', $user->father->name); + } + + public function testNestedEmbedsOneDelete() + { + $user = User::create(['name' => 'John Doe']); + $father = $user->father()->create(['name' => 'Mark Doe']); + $grandfather = $father->father()->create(['name' => 'Steve Doe']); + $greatgrandfather = $grandfather->father()->create(['name' => 'Tom Doe']); + + $grandfather->delete(); + + $this->assertNull($user->father->father); + + $user = User::where(['name' => 'John Doe'])->first(); + $this->assertNull($user->father->father); + } + + public function testNestedEmbedsManyDelete() + { + $user = User::create(['name' => 'John Doe']); + $country = $user->addresses()->create(['country' => 'France']); + $city1 = $country->addresses()->create(['city' => 'Paris']); + $city2 = $country->addresses()->create(['city' => 'Nice']); + $city3 = $country->addresses()->create(['city' => 'Lyon']); + + $city2->delete(); + + $this->assertEquals(2, $user->addresses()->first()->addresses()->count()); + $this->assertEquals('Lyon', $country->addresses()->last()->city); + + $user = User::where('name', 'John Doe')->first(); + $this->assertEquals(2, $user->addresses()->first()->addresses()->count()); + $this->assertEquals('Lyon', $country->addresses()->last()->city); + } + + public function testNestedMixedEmbedsDelete() + { + $user = User::create(['name' => 'John Doe']); + $father = $user->father()->create(['name' => 'Mark Doe']); + $country1 = $father->addresses()->create(['country' => 'France']); + $country2 = $father->addresses()->create(['country' => 'Belgium']); + + $country1->delete(); + + $this->assertEquals(1, $user->father->addresses()->count()); + $this->assertEquals('Belgium', $user->father->addresses()->last()->country); + + $user = User::where('name', 'John Doe')->first(); + $this->assertEquals(1, $user->father->addresses()->count()); + $this->assertEquals('Belgium', $user->father->addresses()->last()->country); + } + + public function testDoubleAssociate() + { + $user = User::create(['name' => 'John Doe']); + $address = new Address(['city' => 'Paris']); + + $user->addresses()->associate($address); + $user->addresses()->associate($address); + $address = $user->addresses()->first(); + $user->addresses()->associate($address); + $this->assertEquals(1, $user->addresses()->count()); + + $user = User::where('name', 'John Doe')->first(); + $user->addresses()->associate($address); + $this->assertEquals(1, $user->addresses()->count()); + + $user->save(); + $user->addresses()->associate($address); + $this->assertEquals(1, $user->addresses()->count()); + } + + public function testSaveEmptyModel() + { + $user = User::create(['name' => 'John Doe']); + $user->addresses()->save(new Address); + $this->assertNotNull($user->addresses); + $this->assertEquals(1, $user->addresses()->count()); + } + + public function testIncrementEmbedded() + { + $user = User::create(['name' => 'John Doe']); + $address = $user->addresses()->create(['city' => 'New York', 'visited' => 5]); + + $address->increment('visited'); + $this->assertEquals(6, $address->visited); + $this->assertEquals(6, $user->addresses()->first()->visited); + + $user = User::where('name', 'John Doe')->first(); + $this->assertEquals(6, $user->addresses()->first()->visited); + + $user = User::where('name', 'John Doe')->first(); + $address = $user->addresses()->first(); + + $address->decrement('visited'); + $this->assertEquals(5, $address->visited); + $this->assertEquals(5, $user->addresses()->first()->visited); + + $user = User::where('name', 'John Doe')->first(); + $this->assertEquals(5, $user->addresses()->first()->visited); + } + + public function testPaginateEmbedsMany() + { + $user = User::create(['name' => 'John Doe']); + $user->addresses()->save(new Address(['city' => 'New York'])); + $user->addresses()->save(new Address(['city' => 'Paris'])); + $user->addresses()->save(new Address(['city' => 'Brussels'])); + + $results = $user->addresses()->paginate(2); + $this->assertEquals(2, $results->count()); + $this->assertEquals(3, $results->total()); + } + + public function testGetQueueableRelationsEmbedsMany() + { + $user = User::create(['name' => 'John Doe']); + $user->addresses()->save(new Address(['city' => 'New York'])); + $user->addresses()->save(new Address(['city' => 'Paris'])); + + $this->assertEquals(['addresses'], $user->getQueueableRelations()); + $this->assertEquals([], $user->addresses->getQueueableRelations()); + } + + public function testGetQueueableRelationsEmbedsOne() + { + $user = User::create(['name' => 'John Doe']); + $user->father()->save(new User(['name' => 'Mark Doe'])); + + $this->assertEquals(['father'], $user->getQueueableRelations()); + $this->assertEquals([], $user->father->getQueueableRelations()); + } +} diff --git a/tests/Models/Address.php b/tests/Models/Address.php new file mode 100644 index 0000000..a0f8807 --- /dev/null +++ b/tests/Models/Address.php @@ -0,0 +1,16 @@ +embedsMany(Address::class); + } +} diff --git a/tests/Models/Book.php b/tests/Models/Book.php new file mode 100644 index 0000000..049dca8 --- /dev/null +++ b/tests/Models/Book.php @@ -0,0 +1,20 @@ +where('favorite', true); + }); + } +} diff --git a/tests/Models/Soft.php b/tests/Models/Soft.php new file mode 100644 index 0000000..bf19eae --- /dev/null +++ b/tests/Models/Soft.php @@ -0,0 +1,20 @@ +embedsMany(Address::class); + } + + public function father() + { + return $this->embedsOne(User::class); + } + + public function getDateFormat() + { + return 'l jS \of F Y h:i:s A'; + } +} diff --git a/tests/QueryBuilderTest.php b/tests/QueryBuilderTest.php new file mode 100644 index 0000000..5b62bc8 --- /dev/null +++ b/tests/QueryBuilderTest.php @@ -0,0 +1,745 @@ +truncate(); + DB::collection('items')->truncate(); + } + + public function testDeleteWithId() + { + $user = DB::collection('users')->insertGetId([ + ['name' => 'Jane Doe', 'age' => 20], + ]); + + $user_id = (string) $user; + + DB::collection('items')->insert([ + ['name' => 'one thing', 'user_id' => $user_id], + ['name' => 'last thing', 'user_id' => $user_id], + ['name' => 'another thing', 'user_id' => $user_id], + ['name' => 'one more thing', 'user_id' => $user_id], + ]); + + $product = DB::collection('items')->first(); + + $pid = (string) ($product['_id']); + + DB::collection('items')->where('user_id', $user_id)->delete($pid); + + $this->assertEquals(3, DB::collection('items')->count()); + + $product = DB::collection('items')->first(); + + $pid = $product['_id']; + + DB::collection('items')->where('user_id', $user_id)->delete($pid); + + DB::collection('items')->where('user_id', $user_id)->delete(md5('random-id')); + + $this->assertEquals(2, DB::collection('items')->count()); + } + + public function testCollection() + { + $this->assertInstanceOf(Builder::class, DB::collection('users')); + } + + public function testGet() + { + $users = DB::collection('users')->get(); + $this->assertCount(0, $users); + + DB::collection('users')->insert(['name' => 'John Doe']); + + $users = DB::collection('users')->get(); + $this->assertCount(1, $users); + } + + public function testNoDocument() + { + $items = DB::collection('items')->where('name', 'nothing')->get()->toArray(); + $this->assertEquals([], $items); + + $item = DB::collection('items')->where('name', 'nothing')->first(); + $this->assertNull($item); + + $item = DB::collection('items')->where('_id', '51c33d8981fec6813e00000a')->first(); + $this->assertNull($item); + } + + public function testInsert() + { + DB::collection('users')->insert([ + 'tags' => ['tag1', 'tag2'], + 'name' => 'John Doe', + ]); + + $users = DB::collection('users')->get(); + $this->assertCount(1, $users); + + $user = $users[0]; + $this->assertEquals('John Doe', $user['name']); + $this->assertIsArray($user['tags']); + } + + public function testInsertGetId() + { + $id = DB::collection('users')->insertGetId(['name' => 'John Doe']); + $this->assertInstanceOf(ObjectId::class, $id); + } + + public function testBatchInsert() + { + DB::collection('users')->insert([ + [ + 'tags' => ['tag1', 'tag2'], + 'name' => 'Jane Doe', + ], + [ + 'tags' => ['tag3'], + 'name' => 'John Doe', + ], + ]); + + $users = DB::collection('users')->get(); + $this->assertCount(2, $users); + $this->assertIsArray($users[0]['tags']); + } + + public function testFind() + { + $id = DB::collection('users')->insertGetId(['name' => 'John Doe']); + + $user = DB::collection('users')->find($id); + $this->assertEquals('John Doe', $user['name']); + } + + public function testFindNull() + { + $user = DB::collection('users')->find(null); + $this->assertNull($user); + } + + public function testCount() + { + DB::collection('users')->insert([ + ['name' => 'Jane Doe'], + ['name' => 'John Doe'], + ]); + + $this->assertEquals(2, DB::collection('users')->count()); + } + + public function testUpdate() + { + DB::collection('users')->insert([ + ['name' => 'Jane Doe', 'age' => 20], + ['name' => 'John Doe', 'age' => 21], + ]); + + DB::collection('users')->where('name', 'John Doe')->update(['age' => 100]); + + $john = DB::collection('users')->where('name', 'John Doe')->first(); + $jane = DB::collection('users')->where('name', 'Jane Doe')->first(); + $this->assertEquals(100, $john['age']); + $this->assertEquals(20, $jane['age']); + } + + public function testDelete() + { + DB::collection('users')->insert([ + ['name' => 'Jane Doe', 'age' => 20], + ['name' => 'John Doe', 'age' => 25], + ]); + + DB::collection('users')->where('age', '<', 10)->delete(); + $this->assertEquals(2, DB::collection('users')->count()); + + DB::collection('users')->where('age', '<', 25)->delete(); + $this->assertEquals(1, DB::collection('users')->count()); + } + + public function testTruncate() + { + DB::collection('users')->insert(['name' => 'John Doe']); + $result = DB::collection('users')->truncate(); + $this->assertEquals(1, $result); + $this->assertEquals(0, DB::collection('users')->count()); + } + + public function testSubKey() + { + DB::collection('users')->insert([ + [ + 'name' => 'John Doe', + 'address' => ['country' => 'Belgium', 'city' => 'Ghent'], + ], + [ + 'name' => 'Jane Doe', + 'address' => ['country' => 'France', 'city' => 'Paris'], + ], + ]); + + $users = DB::collection('users')->where('address.country', 'Belgium')->get(); + $this->assertCount(1, $users); + $this->assertEquals('John Doe', $users[0]['name']); + } + + public function testInArray() + { + DB::collection('items')->insert([ + [ + 'tags' => ['tag1', 'tag2', 'tag3', 'tag4'], + ], + [ + 'tags' => ['tag2'], + ], + ]); + + $items = DB::collection('items')->where('tags', 'tag2')->get(); + $this->assertCount(2, $items); + + $items = DB::collection('items')->where('tags', 'tag1')->get(); + $this->assertCount(1, $items); + } + + public function testRaw() + { + DB::collection('users')->insert([ + ['name' => 'Jane Doe', 'age' => 20], + ['name' => 'John Doe', 'age' => 25], + ]); + + $cursor = DB::collection('users')->raw(function ($collection) { + return $collection->find(['age' => 20]); + }); + + $this->assertInstanceOf(Cursor::class, $cursor); + $this->assertCount(1, $cursor->toArray()); + + $collection = DB::collection('users')->raw(); + $this->assertInstanceOf(Collection::class, $collection); + + $collection = User::raw(); + $this->assertInstanceOf(Collection::class, $collection); + + $results = DB::collection('users')->whereRaw(['age' => 20])->get(); + $this->assertCount(1, $results); + $this->assertEquals('Jane Doe', $results[0]['name']); + } + + public function testPush() + { + $id = DB::collection('users')->insertGetId([ + 'name' => 'John Doe', + 'tags' => [], + 'messages' => [], + ]); + + DB::collection('users')->where('_id', $id)->push('tags', 'tag1'); + + $user = DB::collection('users')->find($id); + $this->assertIsArray($user['tags']); + $this->assertCount(1, $user['tags']); + $this->assertEquals('tag1', $user['tags'][0]); + + DB::collection('users')->where('_id', $id)->push('tags', 'tag2'); + $user = DB::collection('users')->find($id); + $this->assertCount(2, $user['tags']); + $this->assertEquals('tag2', $user['tags'][1]); + + // Add duplicate + DB::collection('users')->where('_id', $id)->push('tags', 'tag2'); + $user = DB::collection('users')->find($id); + $this->assertCount(3, $user['tags']); + + // Add unique + DB::collection('users')->where('_id', $id)->push('tags', 'tag1', true); + $user = DB::collection('users')->find($id); + $this->assertCount(3, $user['tags']); + + $message = ['from' => 'Jane', 'body' => 'Hi John']; + DB::collection('users')->where('_id', $id)->push('messages', $message); + $user = DB::collection('users')->find($id); + $this->assertIsArray($user['messages']); + $this->assertCount(1, $user['messages']); + $this->assertEquals($message, $user['messages'][0]); + + // Raw + DB::collection('users')->where('_id', $id)->push([ + 'tags' => 'tag3', + 'messages' => ['from' => 'Mark', 'body' => 'Hi John'], + ]); + $user = DB::collection('users')->find($id); + $this->assertCount(4, $user['tags']); + $this->assertCount(2, $user['messages']); + + DB::collection('users')->where('_id', $id)->push([ + 'messages' => [ + 'date' => new DateTime(), + 'body' => 'Hi John', + ], + ]); + $user = DB::collection('users')->find($id); + $this->assertCount(3, $user['messages']); + } + + public function testPull() + { + $message1 = ['from' => 'Jane', 'body' => 'Hi John']; + $message2 = ['from' => 'Mark', 'body' => 'Hi John']; + + $id = DB::collection('users')->insertGetId([ + 'name' => 'John Doe', + 'tags' => ['tag1', 'tag2', 'tag3', 'tag4'], + 'messages' => [$message1, $message2], + ]); + + DB::collection('users')->where('_id', $id)->pull('tags', 'tag3'); + + $user = DB::collection('users')->find($id); + $this->assertIsArray($user['tags']); + $this->assertCount(3, $user['tags']); + $this->assertEquals('tag4', $user['tags'][2]); + + DB::collection('users')->where('_id', $id)->pull('messages', $message1); + + $user = DB::collection('users')->find($id); + $this->assertIsArray($user['messages']); + $this->assertCount(1, $user['messages']); + + // Raw + DB::collection('users')->where('_id', $id)->pull(['tags' => 'tag2', 'messages' => $message2]); + $user = DB::collection('users')->find($id); + $this->assertCount(2, $user['tags']); + $this->assertCount(0, $user['messages']); + } + + public function testDistinct() + { + DB::collection('items')->insert([ + ['name' => 'knife', 'type' => 'sharp'], + ['name' => 'fork', 'type' => 'sharp'], + ['name' => 'spoon', 'type' => 'round'], + ['name' => 'spoon', 'type' => 'round'], + ]); + + $items = DB::collection('items')->distinct('name')->get()->toArray(); + sort($items); + $this->assertCount(3, $items); + $this->assertEquals(['fork', 'knife', 'spoon'], $items); + + $types = DB::collection('items')->distinct('type')->get()->toArray(); + sort($types); + $this->assertCount(2, $types); + $this->assertEquals(['round', 'sharp'], $types); + } + + public function testCustomId() + { + DB::collection('items')->insert([ + ['_id' => 'knife', 'type' => 'sharp', 'amount' => 34], + ['_id' => 'fork', 'type' => 'sharp', 'amount' => 20], + ['_id' => 'spoon', 'type' => 'round', 'amount' => 3], + ]); + + $item = DB::collection('items')->find('knife'); + $this->assertEquals('knife', $item['_id']); + + $item = DB::collection('items')->where('_id', 'fork')->first(); + $this->assertEquals('fork', $item['_id']); + + DB::collection('users')->insert([ + ['_id' => 1, 'name' => 'Jane Doe'], + ['_id' => 2, 'name' => 'John Doe'], + ]); + + $item = DB::collection('users')->find(1); + $this->assertEquals(1, $item['_id']); + } + + public function testTake() + { + DB::collection('items')->insert([ + ['name' => 'knife', 'type' => 'sharp', 'amount' => 34], + ['name' => 'fork', 'type' => 'sharp', 'amount' => 20], + ['name' => 'spoon', 'type' => 'round', 'amount' => 3], + ['name' => 'spoon', 'type' => 'round', 'amount' => 14], + ]); + + $items = DB::collection('items')->orderBy('name')->take(2)->get(); + $this->assertCount(2, $items); + $this->assertEquals('fork', $items[0]['name']); + } + + public function testSkip() + { + DB::collection('items')->insert([ + ['name' => 'knife', 'type' => 'sharp', 'amount' => 34], + ['name' => 'fork', 'type' => 'sharp', 'amount' => 20], + ['name' => 'spoon', 'type' => 'round', 'amount' => 3], + ['name' => 'spoon', 'type' => 'round', 'amount' => 14], + ]); + + $items = DB::collection('items')->orderBy('name')->skip(2)->get(); + $this->assertCount(2, $items); + $this->assertEquals('spoon', $items[0]['name']); + } + + public function testPluck() + { + DB::collection('users')->insert([ + ['name' => 'Jane Doe', 'age' => 20], + ['name' => 'John Doe', 'age' => 25], + ]); + + $age = DB::collection('users')->where('name', 'John Doe')->pluck('age')->toArray(); + $this->assertEquals([25], $age); + } + + public function testList() + { + DB::collection('items')->insert([ + ['name' => 'knife', 'type' => 'sharp', 'amount' => 34], + ['name' => 'fork', 'type' => 'sharp', 'amount' => 20], + ['name' => 'spoon', 'type' => 'round', 'amount' => 3], + ['name' => 'spoon', 'type' => 'round', 'amount' => 14], + ]); + + $list = DB::collection('items')->pluck('name')->toArray(); + sort($list); + $this->assertCount(4, $list); + $this->assertEquals(['fork', 'knife', 'spoon', 'spoon'], $list); + + $list = DB::collection('items')->pluck('type', 'name')->toArray(); + $this->assertCount(3, $list); + $this->assertEquals(['knife' => 'sharp', 'fork' => 'sharp', 'spoon' => 'round'], $list); + + $list = DB::collection('items')->pluck('name', '_id')->toArray(); + $this->assertCount(4, $list); + $this->assertEquals(24, strlen(key($list))); + } + + public function testAggregate() + { + DB::collection('items')->insert([ + ['name' => 'knife', 'type' => 'sharp', 'amount' => 34], + ['name' => 'fork', 'type' => 'sharp', 'amount' => 20], + ['name' => 'spoon', 'type' => 'round', 'amount' => 3], + ['name' => 'spoon', 'type' => 'round', 'amount' => 14], + ]); + + $this->assertEquals(71, DB::collection('items')->sum('amount')); + $this->assertEquals(4, DB::collection('items')->count('amount')); + $this->assertEquals(3, DB::collection('items')->min('amount')); + $this->assertEquals(34, DB::collection('items')->max('amount')); + $this->assertEquals(17.75, DB::collection('items')->avg('amount')); + + $this->assertEquals(2, DB::collection('items')->where('name', 'spoon')->count('amount')); + $this->assertEquals(14, DB::collection('items')->where('name', 'spoon')->max('amount')); + } + + public function testSubdocumentAggregate() + { + DB::collection('items')->insert([ + ['name' => 'knife', 'amount' => ['hidden' => 10, 'found' => 3]], + ['name' => 'fork', 'amount' => ['hidden' => 35, 'found' => 12]], + ['name' => 'spoon', 'amount' => ['hidden' => 14, 'found' => 21]], + ['name' => 'spoon', 'amount' => ['hidden' => 6, 'found' => 4]], + ]); + + $this->assertEquals(65, DB::collection('items')->sum('amount.hidden')); + $this->assertEquals(4, DB::collection('items')->count('amount.hidden')); + $this->assertEquals(6, DB::collection('items')->min('amount.hidden')); + $this->assertEquals(35, DB::collection('items')->max('amount.hidden')); + $this->assertEquals(16.25, DB::collection('items')->avg('amount.hidden')); + } + + public function testSubdocumentArrayAggregate() + { + DB::collection('items')->insert([ + ['name' => 'knife', 'amount' => [['hidden' => 10, 'found' => 3], ['hidden' => 5, 'found' => 2]]], + [ + 'name' => 'fork', + 'amount' => [ + ['hidden' => 35, 'found' => 12], + ['hidden' => 7, 'found' => 17], + ['hidden' => 1, 'found' => 19], + ], + ], + ['name' => 'spoon', 'amount' => [['hidden' => 14, 'found' => 21]]], + ['name' => 'teaspoon', 'amount' => []], + ]); + + $this->assertEquals(72, DB::collection('items')->sum('amount.*.hidden')); + $this->assertEquals(6, DB::collection('items')->count('amount.*.hidden')); + $this->assertEquals(1, DB::collection('items')->min('amount.*.hidden')); + $this->assertEquals(35, DB::collection('items')->max('amount.*.hidden')); + $this->assertEquals(12, DB::collection('items')->avg('amount.*.hidden')); + } + + public function testUpsert() + { + DB::collection('items')->where('name', 'knife') + ->update( + ['amount' => 1], + ['upsert' => true] + ); + + $this->assertEquals(1, DB::collection('items')->count()); + + Item::where('name', 'spoon') + ->update( + ['amount' => 1], + ['upsert' => true] + ); + + $this->assertEquals(2, DB::collection('items')->count()); + } + + public function testUnset() + { + $id1 = DB::collection('users')->insertGetId(['name' => 'John Doe', 'note1' => 'ABC', 'note2' => 'DEF']); + $id2 = DB::collection('users')->insertGetId(['name' => 'Jane Doe', 'note1' => 'ABC', 'note2' => 'DEF']); + + DB::collection('users')->where('name', 'John Doe')->unset('note1'); + + $user1 = DB::collection('users')->find($id1); + $user2 = DB::collection('users')->find($id2); + + $this->assertArrayNotHasKey('note1', $user1); + $this->assertArrayHasKey('note2', $user1); + $this->assertArrayHasKey('note1', $user2); + $this->assertArrayHasKey('note2', $user2); + + DB::collection('users')->where('name', 'Jane Doe')->unset(['note1', 'note2']); + + $user2 = DB::collection('users')->find($id2); + $this->assertArrayNotHasKey('note1', $user2); + $this->assertArrayNotHasKey('note2', $user2); + } + + public function testUpdateSubdocument() + { + $id = DB::collection('users')->insertGetId(['name' => 'John Doe', 'address' => ['country' => 'Belgium']]); + + DB::collection('users')->where('_id', $id)->update(['address.country' => 'England']); + + $check = DB::collection('users')->find($id); + $this->assertEquals('England', $check['address']['country']); + } + + public function testDates() + { + DB::collection('users')->insert([ + ['name' => 'John Doe', 'birthday' => new UTCDateTime(Date::parse('1980-01-01 00:00:00')->format('Uv'))], + ['name' => 'Jane Doe', 'birthday' => new UTCDateTime(Date::parse('1981-01-01 00:00:00')->format('Uv'))], + ['name' => 'Robert Roe', 'birthday' => new UTCDateTime(Date::parse('1982-01-01 00:00:00')->format('Uv'))], + ['name' => 'Mark Moe', 'birthday' => new UTCDateTime(Date::parse('1983-01-01 00:00:00')->format('Uv'))], + ]); + + $user = DB::collection('users') + ->where('birthday', new UTCDateTime(Date::parse('1980-01-01 00:00:00')->format('Uv'))) + ->first(); + $this->assertEquals('John Doe', $user['name']); + + $user = DB::collection('users')->where('birthday', '=', new DateTime('1980-01-01 00:00:00'))->first(); + $this->assertEquals('John Doe', $user['name']); + + $start = new UTCDateTime(1000 * strtotime('1981-01-01 00:00:00')); + $stop = new UTCDateTime(1000 * strtotime('1982-01-01 00:00:00')); + + $users = DB::collection('users')->whereBetween('birthday', [$start, $stop])->get(); + $this->assertCount(2, $users); + } + + public function testOperators() + { + DB::collection('users')->insert([ + ['name' => 'John Doe', 'age' => 30], + ['name' => 'Jane Doe'], + ['name' => 'Robert Roe', 'age' => 'thirty-one'], + ]); + + $results = DB::collection('users')->where('age', 'exists', true)->get(); + $this->assertCount(2, $results); + $resultsNames = [$results[0]['name'], $results[1]['name']]; + $this->assertContains('John Doe', $resultsNames); + $this->assertContains('Robert Roe', $resultsNames); + + $results = DB::collection('users')->where('age', 'exists', false)->get(); + $this->assertCount(1, $results); + $this->assertEquals('Jane Doe', $results[0]['name']); + + $results = DB::collection('users')->where('age', 'type', 2)->get(); + $this->assertCount(1, $results); + $this->assertEquals('Robert Roe', $results[0]['name']); + + $results = DB::collection('users')->where('age', 'mod', [15, 0])->get(); + $this->assertCount(1, $results); + $this->assertEquals('John Doe', $results[0]['name']); + + $results = DB::collection('users')->where('age', 'mod', [29, 1])->get(); + $this->assertCount(1, $results); + $this->assertEquals('John Doe', $results[0]['name']); + + $results = DB::collection('users')->where('age', 'mod', [14, 0])->get(); + $this->assertCount(0, $results); + + DB::collection('items')->insert([ + ['name' => 'fork', 'tags' => ['sharp', 'pointy']], + ['name' => 'spork', 'tags' => ['sharp', 'pointy', 'round', 'bowl']], + ['name' => 'spoon', 'tags' => ['round', 'bowl']], + ]); + + $results = DB::collection('items')->where('tags', 'all', ['sharp', 'pointy'])->get(); + $this->assertCount(2, $results); + + $results = DB::collection('items')->where('tags', 'all', ['sharp', 'round'])->get(); + $this->assertCount(1, $results); + + $results = DB::collection('items')->where('tags', 'size', 2)->get(); + $this->assertCount(2, $results); + + $results = DB::collection('items')->where('tags', '$size', 2)->get(); + $this->assertCount(2, $results); + + $results = DB::collection('items')->where('tags', 'size', 3)->get(); + $this->assertCount(0, $results); + + $results = DB::collection('items')->where('tags', 'size', 4)->get(); + $this->assertCount(1, $results); + + $regex = new Regex('.*doe', 'i'); + $results = DB::collection('users')->where('name', 'regex', $regex)->get(); + $this->assertCount(2, $results); + + $regex = new Regex('.*doe', 'i'); + $results = DB::collection('users')->where('name', 'regexp', $regex)->get(); + $this->assertCount(2, $results); + + $results = DB::collection('users')->where('name', 'REGEX', $regex)->get(); + $this->assertCount(2, $results); + + $results = DB::collection('users')->where('name', 'regexp', '/.*doe/i')->get(); + $this->assertCount(2, $results); + + $results = DB::collection('users')->where('name', 'not regexp', '/.*doe/i')->get(); + $this->assertCount(1, $results); + } + + public function testIncrement() + { + DB::collection('users')->insert([ + ['name' => 'John Doe', 'age' => 30, 'note' => 'adult'], + ['name' => 'Jane Doe', 'age' => 10, 'note' => 'minor'], + ['name' => 'Robert Roe', 'age' => null], + ['name' => 'Mark Moe'], + ]); + + $user = DB::collection('users')->where('name', 'John Doe')->first(); + $this->assertEquals(30, $user['age']); + + DB::collection('users')->where('name', 'John Doe')->increment('age'); + $user = DB::collection('users')->where('name', 'John Doe')->first(); + $this->assertEquals(31, $user['age']); + + DB::collection('users')->where('name', 'John Doe')->decrement('age'); + $user = DB::collection('users')->where('name', 'John Doe')->first(); + $this->assertEquals(30, $user['age']); + + DB::collection('users')->where('name', 'John Doe')->increment('age', 5); + $user = DB::collection('users')->where('name', 'John Doe')->first(); + $this->assertEquals(35, $user['age']); + + DB::collection('users')->where('name', 'John Doe')->decrement('age', 5); + $user = DB::collection('users')->where('name', 'John Doe')->first(); + $this->assertEquals(30, $user['age']); + + DB::collection('users')->where('name', 'Jane Doe')->increment('age', 10, ['note' => 'adult']); + $user = DB::collection('users')->where('name', 'Jane Doe')->first(); + $this->assertEquals(20, $user['age']); + $this->assertEquals('adult', $user['note']); + + DB::collection('users')->where('name', 'John Doe')->decrement('age', 20, ['note' => 'minor']); + $user = DB::collection('users')->where('name', 'John Doe')->first(); + $this->assertEquals(10, $user['age']); + $this->assertEquals('minor', $user['note']); + + DB::collection('users')->increment('age'); + $user = DB::collection('users')->where('name', 'John Doe')->first(); + $this->assertEquals(11, $user['age']); + $user = DB::collection('users')->where('name', 'Jane Doe')->first(); + $this->assertEquals(21, $user['age']); + $user = DB::collection('users')->where('name', 'Robert Roe')->first(); + $this->assertNull($user['age']); + $user = DB::collection('users')->where('name', 'Mark Moe')->first(); + $this->assertEquals(1, $user['age']); + } + + public function testProjections() + { + DB::collection('items')->insert([ + ['name' => 'fork', 'tags' => ['sharp', 'pointy']], + ['name' => 'spork', 'tags' => ['sharp', 'pointy', 'round', 'bowl']], + ['name' => 'spoon', 'tags' => ['round', 'bowl']], + ]); + + $results = DB::collection('items')->project(['tags' => ['$slice' => 1]])->get(); + + foreach ($results as $result) { + $this->assertEquals(1, count($result['tags'])); + } + } + + public function testValue() + { + DB::collection('books')->insert([ + ['title' => 'Moby-Dick', 'author' => ['first_name' => 'Herman', 'last_name' => 'Melville']], + ]); + + $this->assertEquals('Moby-Dick', DB::collection('books')->value('title')); + $this->assertEquals(['first_name' => 'Herman', 'last_name' => 'Melville'], DB::collection('books') + ->value('author')); + $this->assertEquals('Herman', DB::collection('books')->value('author.first_name')); + $this->assertEquals('Melville', DB::collection('books')->value('author.last_name')); + } + + public function testHintOptions() + { + DB::collection('items')->insert([ + ['name' => 'fork', 'tags' => ['sharp', 'pointy']], + ['name' => 'spork', 'tags' => ['sharp', 'pointy', 'round', 'bowl']], + ['name' => 'spoon', 'tags' => ['round', 'bowl']], + ]); + + $results = DB::collection('items')->hint(['$natural' => -1])->get(); + + $this->assertEquals('spoon', $results[0]['name']); + $this->assertEquals('spork', $results[1]['name']); + $this->assertEquals('fork', $results[2]['name']); + + $results = DB::collection('items')->hint(['$natural' => 1])->get(); + + $this->assertEquals('spoon', $results[2]['name']); + $this->assertEquals('spork', $results[1]['name']); + $this->assertEquals('fork', $results[0]['name']); + } +} From 170b0c37d1a28490c6d10a30dc784f99a1f1d873 Mon Sep 17 00:00:00 2001 From: Giacomo Fabbian Date: Wed, 22 Jul 2020 11:08:13 +0000 Subject: [PATCH 2/5] Apply fixes from StyleCI --- src/Eloquent/Builder.php | 24 ++++--- src/Eloquent/EmbedsRelationships.php | 6 +- src/Eloquent/Model.php | 7 +- src/Relationships/EmbedsMany.php | 25 +++---- src/Relationships/EmbedsOne.php | 10 +-- src/Relationships/EmbedsOneOrMany.php | 26 ++++---- tests/EmbeddedRelationsTest.php | 93 ++++++++++++++------------- tests/Models/Address.php | 2 +- tests/Models/Book.php | 1 - tests/Models/Photo.php | 1 - tests/Models/Soft.php | 2 +- tests/Models/User.php | 2 +- 12 files changed, 98 insertions(+), 101 deletions(-) diff --git a/src/Eloquent/Builder.php b/src/Eloquent/Builder.php index f65236d..e1f95e7 100644 --- a/src/Eloquent/Builder.php +++ b/src/Eloquent/Builder.php @@ -2,14 +2,13 @@ namespace OfflineAgency\MongoAutoSync\Eloquent; -use Jenssegers\Mongodb\Eloquent\Builder as MongoDbEloquentBuilder; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; - +use Jenssegers\Mongodb\Eloquent\Builder as MongoDbEloquentBuilder; class Builder extends MongoDbEloquentBuilder { /** - * @inheritdoc + * {@inheritdoc} */ public function update(array $values, array $options = []) { @@ -25,7 +24,7 @@ public function update(array $values, array $options = []) } /** - * @inheritdoc + * {@inheritdoc} */ public function insert(array $values) { @@ -41,7 +40,7 @@ public function insert(array $values) } /** - * @inheritdoc + * {@inheritdoc} */ public function insertGetId(array $values, $sequence = null) { @@ -57,7 +56,7 @@ public function insertGetId(array $values, $sequence = null) } /** - * @inheritdoc + * {@inheritdoc} */ public function delete() { @@ -73,7 +72,7 @@ public function delete() } /** - * @inheritdoc + * {@inheritdoc} */ public function increment($column, $amount = 1, array $extra = []) { @@ -98,7 +97,7 @@ public function increment($column, $amount = 1, array $extra = []) } /** - * @inheritdoc + * {@inheritdoc} */ public function decrement($column, $amount = 1, array $extra = []) { @@ -121,7 +120,7 @@ public function decrement($column, $amount = 1, array $extra = []) } /** - * @inheritdoc + * {@inheritdoc} */ public function chunkById($count, callable $callback, $column = '_id', $alias = null) { @@ -129,7 +128,7 @@ public function chunkById($count, callable $callback, $column = '_id', $alias = } /** - * @inheritdoc + * {@inheritdoc} */ public function raw($expression = null) { @@ -158,13 +157,13 @@ public function raw($expression = null) * Add the "updated at" column to an array of values. * TODO Remove if https://github.com/laravel/framework/commit/6484744326531829341e1ff886cc9b628b20d73e * wiil be reverted - * Issue in laravel frawework https://github.com/laravel/framework/issues/27791 + * Issue in laravel frawework https://github.com/laravel/framework/issues/27791. * @param array $values * @return array */ protected function addUpdatedAtColumn(array $values) { - if (!$this->model->usesTimestamps() || $this->model->getUpdatedAtColumn() === null) { + if (! $this->model->usesTimestamps() || $this->model->getUpdatedAtColumn() === null) { return $values; } @@ -176,5 +175,4 @@ protected function addUpdatedAtColumn(array $values) return $values; } - } diff --git a/src/Eloquent/EmbedsRelationships.php b/src/Eloquent/EmbedsRelationships.php index 7a39100..cb505d0 100644 --- a/src/Eloquent/EmbedsRelationships.php +++ b/src/Eloquent/EmbedsRelationships.php @@ -1,9 +1,7 @@ getRelationValue($key); } diff --git a/src/Relationships/EmbedsMany.php b/src/Relationships/EmbedsMany.php index 412b557..772d0ea 100644 --- a/src/Relationships/EmbedsMany.php +++ b/src/Relationships/EmbedsMany.php @@ -12,7 +12,7 @@ class EmbedsMany extends EmbedsOneOrMany { /** - * @inheritdoc + * {@inheritdoc} */ public function initRelation(array $models, $relation) { @@ -24,7 +24,7 @@ public function initRelation(array $models, $relation) } /** - * @inheritdoc + * {@inheritdoc} */ public function getResults() { @@ -39,13 +39,14 @@ public function getResults() public function performInsert(Model $model) { // Generate a new key if needed. - if ($model->getKeyName() == '_id' && !$model->getKey()) { + if ($model->getKeyName() == '_id' && ! $model->getKey()) { $model->setAttribute('_id', new ObjectID); } // For deeply nested documents, let the parent handle the changes. if ($this->isNested()) { $this->associate($model); + return $this->parent->save() ? $model : false; } @@ -77,10 +78,10 @@ public function performUpdate(Model $model) // Get the correct foreign key value. $foreignKey = $this->getForeignKeyValue($model); - $values = $this->getUpdateValues($model->getDirty(), $this->localKey . '.$.'); + $values = $this->getUpdateValues($model->getDirty(), $this->localKey.'.$.'); // Update document in database. - $result = $this->getBaseQuery()->where($this->localKey . '.' . $model->getKeyName(), $foreignKey) + $result = $this->getBaseQuery()->where($this->localKey.'.'.$model->getKeyName(), $foreignKey) ->update($values); // Attach the model to its parent. @@ -124,7 +125,7 @@ public function performDelete(Model $model) */ public function associate(Model $model) { - if (!$this->contains($model)) { + if (! $this->contains($model)) { return $this->associateNew($model); } @@ -227,7 +228,7 @@ public function attach(Model $model) protected function associateNew($model) { // Create a new key if needed. - if ($model->getKeyName() === '_id' && !$model->getAttribute('_id')) { + if ($model->getKeyName() === '_id' && ! $model->getAttribute('_id')) { $model->setAttribute('_id', new ObjectID); } @@ -292,13 +293,13 @@ public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $perPage, $page, [ - 'path' => Paginator::resolveCurrentPath() + 'path' => Paginator::resolveCurrentPath(), ] ); } /** - * @inheritdoc + * {@inheritdoc} */ protected function getEmbedded() { @@ -306,11 +307,11 @@ protected function getEmbedded() } /** - * @inheritdoc + * {@inheritdoc} */ protected function setEmbedded($models) { - if (!is_array($models)) { + if (! is_array($models)) { $models = [$models]; } @@ -318,7 +319,7 @@ protected function setEmbedded($models) } /** - * @inheritdoc + * {@inheritdoc} */ public function __call($method, $parameters) { diff --git a/src/Relationships/EmbedsOne.php b/src/Relationships/EmbedsOne.php index 065f03b..aa6b11e 100644 --- a/src/Relationships/EmbedsOne.php +++ b/src/Relationships/EmbedsOne.php @@ -9,7 +9,7 @@ class EmbedsOne extends EmbedsOneOrMany { /** - * @inheritdoc + * {@inheritdoc} */ public function initRelation(array $models, $relation) { @@ -21,7 +21,7 @@ public function initRelation(array $models, $relation) } /** - * @inheritdoc + * {@inheritdoc} */ public function getResults() { @@ -36,13 +36,14 @@ public function getResults() public function performInsert(Model $model) { // Generate a new key if needed. - if ($model->getKeyName() == '_id' && !$model->getKey()) { + if ($model->getKeyName() == '_id' && ! $model->getKey()) { $model->setAttribute('_id', new ObjectID); } // For deeply nested documents, let the parent handle the changes. if ($this->isNested()) { $this->associate($model); + return $this->parent->save() ? $model : false; } @@ -69,7 +70,7 @@ public function performUpdate(Model $model) return $this->parent->save(); } - $values = $this->getUpdateValues($model->getDirty(), $this->localKey . '.'); + $values = $this->getUpdateValues($model->getDirty(), $this->localKey.'.'); $result = $this->getBaseQuery()->update($values); @@ -90,6 +91,7 @@ public function performDelete() // For deeply nested documents, let the parent handle the changes. if ($this->isNested()) { $this->dissociate(); + return $this->parent->save(); } diff --git a/src/Relationships/EmbedsOneOrMany.php b/src/Relationships/EmbedsOneOrMany.php index 0cec414..1f1b46f 100644 --- a/src/Relationships/EmbedsOneOrMany.php +++ b/src/Relationships/EmbedsOneOrMany.php @@ -55,7 +55,7 @@ public function __construct(Builder $query, Model $parent, Model $related, $loca } /** - * @inheritdoc + * {@inheritdoc} */ public function addConstraints() { @@ -65,7 +65,7 @@ public function addConstraints() } /** - * @inheritdoc + * {@inheritdoc} */ public function addEagerConstraints(array $models) { @@ -73,7 +73,7 @@ public function addEagerConstraints(array $models) } /** - * @inheritdoc + * {@inheritdoc} */ public function match(array $models, Collection $results, $relation) { @@ -179,7 +179,7 @@ protected function getIdsArrayFrom($ids) $ids = $ids->all(); } - if (!is_array($ids)) { + if (! is_array($ids)) { $ids = [$ids]; } @@ -193,7 +193,7 @@ protected function getIdsArrayFrom($ids) } /** - * @inheritdoc + * {@inheritdoc} */ protected function getEmbedded() { @@ -207,7 +207,7 @@ protected function getEmbedded() } /** - * @inheritdoc + * {@inheritdoc} */ protected function setEmbedded($records) { @@ -295,7 +295,7 @@ protected function getParentRelation() } /** - * @inheritdoc + * {@inheritdoc} */ public function getQuery() { @@ -305,7 +305,7 @@ public function getQuery() } /** - * @inheritdoc + * {@inheritdoc} */ public function getBaseQuery() { @@ -331,19 +331,19 @@ protected function isNested() protected function getPathHierarchy($glue = '.') { if ($parentRelation = $this->getParentRelation()) { - return $parentRelation->getPathHierarchy($glue) . $glue . $this->localKey; + return $parentRelation->getPathHierarchy($glue).$glue.$this->localKey; } return $this->localKey; } /** - * @inheritdoc + * {@inheritdoc} */ public function getQualifiedParentKeyName() { if ($parentRelation = $this->getParentRelation()) { - return $parentRelation->getPathHierarchy() . '.' . $this->parent->getKeyName(); + return $parentRelation->getPathHierarchy().'.'.$this->parent->getKeyName(); } return $this->parent->getKeyName(); @@ -359,7 +359,7 @@ protected function getParentKey() } /** - * Return update values + * Return update values. * @param $array * @param string $prepend * @return array @@ -369,7 +369,7 @@ public static function getUpdateValues($array, $prepend = '') $results = []; foreach ($array as $key => $value) { - $results[$prepend . $key] = $value; + $results[$prepend.$key] = $value; } return $results; diff --git a/tests/EmbeddedRelationsTest.php b/tests/EmbeddedRelationsTest.php index 049b17d..4a8696b 100644 --- a/tests/EmbeddedRelationsTest.php +++ b/tests/EmbeddedRelationsTest.php @@ -1,4 +1,5 @@ 'London']); $address->setEventDispatcher($events = Mockery::mock(Dispatcher::class)); - $events->shouldReceive('dispatch')->with('eloquent.retrieved: ' . get_class($address), Mockery::any()); + $events->shouldReceive('dispatch')->with('eloquent.retrieved: '.get_class($address), Mockery::any()); $events->shouldReceive('until') ->once() - ->with('eloquent.saving: ' . get_class($address), $address) + ->with('eloquent.saving: '.get_class($address), $address) ->andReturn(true); $events->shouldReceive('until') ->once() - ->with('eloquent.creating: ' . get_class($address), $address) + ->with('eloquent.creating: '.get_class($address), $address) ->andReturn(true); - $events->shouldReceive('dispatch')->once()->with('eloquent.created: ' . get_class($address), $address); - $events->shouldReceive('dispatch')->once()->with('eloquent.saved: ' . get_class($address), $address); + $events->shouldReceive('dispatch')->once()->with('eloquent.created: '.get_class($address), $address); + $events->shouldReceive('dispatch')->once()->with('eloquent.saved: '.get_class($address), $address); $address = $user->addresses()->save($address); $address->unsetEventDispatcher(); @@ -68,17 +69,17 @@ public function testEmbedsManySave() $this->assertEquals(['London', 'Paris'], $user->addresses->pluck('city')->all()); $address->setEventDispatcher($events = Mockery::mock(Dispatcher::class)); - $events->shouldReceive('dispatch')->with('eloquent.retrieved: ' . get_class($address), Mockery::any()); + $events->shouldReceive('dispatch')->with('eloquent.retrieved: '.get_class($address), Mockery::any()); $events->shouldReceive('until') ->once() - ->with('eloquent.saving: ' . get_class($address), $address) + ->with('eloquent.saving: '.get_class($address), $address) ->andReturn(true); $events->shouldReceive('until') ->once() - ->with('eloquent.updating: ' . get_class($address), $address) + ->with('eloquent.updating: '.get_class($address), $address) ->andReturn(true); - $events->shouldReceive('dispatch')->once()->with('eloquent.updated: ' . get_class($address), $address); - $events->shouldReceive('dispatch')->once()->with('eloquent.saved: ' . get_class($address), $address); + $events->shouldReceive('dispatch')->once()->with('eloquent.updated: '.get_class($address), $address); + $events->shouldReceive('dispatch')->once()->with('eloquent.saved: '.get_class($address), $address); $address->city = 'New York'; $user->addresses()->save($address); @@ -105,7 +106,7 @@ public function testEmbedsManySave() $this->assertEquals(['London', 'New York', 'Bruxelles'], $user->addresses->pluck('city')->all()); $address = $user->addresses[1]; - $address->city = "Manhattan"; + $address->city = 'Manhattan'; $user->addresses()->save($address); $this->assertEquals(['London', 'Manhattan', 'Bruxelles'], $user->addresses->pluck('city')->all()); @@ -200,7 +201,7 @@ public function testEmbedsManyCreate() public function testEmbedsManyCreateMany() { $user = User::create([]); - list($bruxelles, $paris) = $user->addresses()->createMany([['city' => 'Bruxelles'], ['city' => 'Paris']]); + [$bruxelles, $paris] = $user->addresses()->createMany([['city' => 'Bruxelles'], ['city' => 'Paris']]); $this->assertInstanceOf(Address::class, $bruxelles); $this->assertEquals('Bruxelles', $bruxelles->city); $this->assertEquals(['Bruxelles', 'Paris'], $user->addresses->pluck('city')->all()); @@ -221,14 +222,14 @@ public function testEmbedsManyDestroy() $address = $user->addresses->first(); $address->setEventDispatcher($events = Mockery::mock(Dispatcher::class)); - $events->shouldReceive('dispatch')->with('eloquent.retrieved: ' . get_class($address), Mockery::any()); + $events->shouldReceive('dispatch')->with('eloquent.retrieved: '.get_class($address), Mockery::any()); $events->shouldReceive('until') ->once() - ->with('eloquent.deleting: ' . get_class($address), Mockery::type(Address::class)) + ->with('eloquent.deleting: '.get_class($address), Mockery::type(Address::class)) ->andReturn(true); $events->shouldReceive('dispatch') ->once() - ->with('eloquent.deleted: ' . get_class($address), Mockery::type(Address::class)); + ->with('eloquent.deleted: '.get_class($address), Mockery::type(Address::class)); $user->addresses()->destroy($address->_id); $this->assertEquals(['Bristol', 'Bruxelles'], $user->addresses->pluck('city')->all()); @@ -252,7 +253,7 @@ public function testEmbedsManyDestroy() $freshUser = User::find($user->id); $this->assertEquals([], $freshUser->addresses->pluck('city')->all()); - list($london, $bristol, $bruxelles) = $user->addresses()->saveMany([ + [$london, $bristol, $bruxelles] = $user->addresses()->saveMany([ new Address(['city' => 'London']), new Address(['city' => 'Bristol']), new Address(['city' => 'Bruxelles']), @@ -273,14 +274,14 @@ public function testEmbedsManyDelete() $address = $user->addresses->first(); $address->setEventDispatcher($events = Mockery::mock(Dispatcher::class)); - $events->shouldReceive('dispatch')->with('eloquent.retrieved: ' . get_class($address), Mockery::any()); + $events->shouldReceive('dispatch')->with('eloquent.retrieved: '.get_class($address), Mockery::any()); $events->shouldReceive('until') ->once() - ->with('eloquent.deleting: ' . get_class($address), Mockery::type(Address::class)) + ->with('eloquent.deleting: '.get_class($address), Mockery::type(Address::class)) ->andReturn(true); $events->shouldReceive('dispatch') ->once() - ->with('eloquent.deleted: ' . get_class($address), Mockery::type(Address::class)); + ->with('eloquent.deleted: '.get_class($address), Mockery::type(Address::class)); $address->delete(); @@ -327,14 +328,14 @@ public function testEmbedsManyCreatingEventReturnsFalse() $address = new Address(['city' => 'London']); $address->setEventDispatcher($events = Mockery::mock(Dispatcher::class)); - $events->shouldReceive('dispatch')->with('eloquent.retrieved: ' . get_class($address), Mockery::any()); + $events->shouldReceive('dispatch')->with('eloquent.retrieved: '.get_class($address), Mockery::any()); $events->shouldReceive('until') ->once() - ->with('eloquent.saving: ' . get_class($address), $address) + ->with('eloquent.saving: '.get_class($address), $address) ->andReturn(true); $events->shouldReceive('until') ->once() - ->with('eloquent.creating: ' . get_class($address), $address) + ->with('eloquent.creating: '.get_class($address), $address) ->andReturn(false); $this->assertFalse($user->addresses()->save($address)); @@ -348,10 +349,10 @@ public function testEmbedsManySavingEventReturnsFalse() $address->exists = true; $address->setEventDispatcher($events = Mockery::mock(Dispatcher::class)); - $events->shouldReceive('dispatch')->with('eloquent.retrieved: ' . get_class($address), Mockery::any()); + $events->shouldReceive('dispatch')->with('eloquent.retrieved: '.get_class($address), Mockery::any()); $events->shouldReceive('until') ->once() - ->with('eloquent.saving: ' . get_class($address), $address) + ->with('eloquent.saving: '.get_class($address), $address) ->andReturn(false); $this->assertFalse($user->addresses()->save($address)); @@ -365,14 +366,14 @@ public function testEmbedsManyUpdatingEventReturnsFalse() $user->addresses()->save($address); $address->setEventDispatcher($events = Mockery::mock(Dispatcher::class)); - $events->shouldReceive('dispatch')->with('eloquent.retrieved: ' . get_class($address), Mockery::any()); + $events->shouldReceive('dispatch')->with('eloquent.retrieved: '.get_class($address), Mockery::any()); $events->shouldReceive('until') ->once() - ->with('eloquent.saving: ' . get_class($address), $address) + ->with('eloquent.saving: '.get_class($address), $address) ->andReturn(true); $events->shouldReceive('until') ->once() - ->with('eloquent.updating: ' . get_class($address), $address) + ->with('eloquent.updating: '.get_class($address), $address) ->andReturn(false); $address->city = 'Warsaw'; @@ -389,10 +390,10 @@ public function testEmbedsManyDeletingEventReturnsFalse() $address = $user->addresses->first(); $address->setEventDispatcher($events = Mockery::mock(Dispatcher::class)); - $events->shouldReceive('dispatch')->with('eloquent.retrieved: ' . get_class($address), Mockery::any()); + $events->shouldReceive('dispatch')->with('eloquent.retrieved: '.get_class($address), Mockery::any()); $events->shouldReceive('until') ->once() - ->with('eloquent.deleting: ' . get_class($address), Mockery::mustBe($address)) + ->with('eloquent.deleting: '.get_class($address), Mockery::mustBe($address)) ->andReturn(false); $this->assertEquals(0, $user->addresses()->destroy($address)); @@ -526,17 +527,17 @@ public function testEmbedsOne() $father = new User(['name' => 'Mark Doe']); $father->setEventDispatcher($events = Mockery::mock(Dispatcher::class)); - $events->shouldReceive('dispatch')->with('eloquent.retrieved: ' . get_class($father), Mockery::any()); + $events->shouldReceive('dispatch')->with('eloquent.retrieved: '.get_class($father), Mockery::any()); $events->shouldReceive('until') ->once() - ->with('eloquent.saving: ' . get_class($father), $father) + ->with('eloquent.saving: '.get_class($father), $father) ->andReturn(true); $events->shouldReceive('until') ->once() - ->with('eloquent.creating: ' . get_class($father), $father) + ->with('eloquent.creating: '.get_class($father), $father) ->andReturn(true); - $events->shouldReceive('dispatch')->once()->with('eloquent.created: ' . get_class($father), $father); - $events->shouldReceive('dispatch')->once()->with('eloquent.saved: ' . get_class($father), $father); + $events->shouldReceive('dispatch')->once()->with('eloquent.created: '.get_class($father), $father); + $events->shouldReceive('dispatch')->once()->with('eloquent.saved: '.get_class($father), $father); $father = $user->father()->save($father); $father->unsetEventDispatcher(); @@ -552,17 +553,17 @@ public function testEmbedsOne() $this->assertInstanceOf(ObjectId::class, $raw['_id']); $father->setEventDispatcher($events = Mockery::mock(Dispatcher::class)); - $events->shouldReceive('dispatch')->with('eloquent.retrieved: ' . get_class($father), Mockery::any()); + $events->shouldReceive('dispatch')->with('eloquent.retrieved: '.get_class($father), Mockery::any()); $events->shouldReceive('until') ->once() - ->with('eloquent.saving: ' . get_class($father), $father) + ->with('eloquent.saving: '.get_class($father), $father) ->andReturn(true); $events->shouldReceive('until') ->once() - ->with('eloquent.updating: ' . get_class($father), $father) + ->with('eloquent.updating: '.get_class($father), $father) ->andReturn(true); - $events->shouldReceive('dispatch')->once()->with('eloquent.updated: ' . get_class($father), $father); - $events->shouldReceive('dispatch')->once()->with('eloquent.saved: ' . get_class($father), $father); + $events->shouldReceive('dispatch')->once()->with('eloquent.updated: '.get_class($father), $father); + $events->shouldReceive('dispatch')->once()->with('eloquent.saved: '.get_class($father), $father); $father->name = 'Tom Doe'; $user->father()->save($father); @@ -574,17 +575,17 @@ public function testEmbedsOne() $father = new User(['name' => 'Jim Doe']); $father->setEventDispatcher($events = Mockery::mock(Dispatcher::class)); - $events->shouldReceive('dispatch')->with('eloquent.retrieved: ' . get_class($father), Mockery::any()); + $events->shouldReceive('dispatch')->with('eloquent.retrieved: '.get_class($father), Mockery::any()); $events->shouldReceive('until') ->once() - ->with('eloquent.saving: ' . get_class($father), $father) + ->with('eloquent.saving: '.get_class($father), $father) ->andReturn(true); $events->shouldReceive('until') ->once() - ->with('eloquent.creating: ' . get_class($father), $father) + ->with('eloquent.creating: '.get_class($father), $father) ->andReturn(true); - $events->shouldReceive('dispatch')->once()->with('eloquent.created: ' . get_class($father), $father); - $events->shouldReceive('dispatch')->once()->with('eloquent.saved: ' . get_class($father), $father); + $events->shouldReceive('dispatch')->once()->with('eloquent.created: '.get_class($father), $father); + $events->shouldReceive('dispatch')->once()->with('eloquent.saved: '.get_class($father), $father); $father = $user->father()->save($father); $father->unsetEventDispatcher(); @@ -599,8 +600,8 @@ public function testEmbedsOneAssociate() $father = new User(['name' => 'Mark Doe']); $father->setEventDispatcher($events = Mockery::mock(Dispatcher::class)); - $events->shouldReceive('dispatch')->with('eloquent.retrieved: ' . get_class($father), Mockery::any()); - $events->shouldReceive('until')->times(0)->with('eloquent.saving: ' . get_class($father), $father); + $events->shouldReceive('dispatch')->with('eloquent.retrieved: '.get_class($father), Mockery::any()); + $events->shouldReceive('until')->times(0)->with('eloquent.saving: '.get_class($father), $father); $father = $user->father()->associate($father); $father->unsetEventDispatcher(); diff --git a/tests/Models/Address.php b/tests/Models/Address.php index a0f8807..b81e9ae 100644 --- a/tests/Models/Address.php +++ b/tests/Models/Address.php @@ -11,6 +11,6 @@ class Address extends MDModel public function addresses() { - return $this->embedsMany(Address::class); + return $this->embedsMany(self::class); } } diff --git a/tests/Models/Book.php b/tests/Models/Book.php index 049dca8..5ce1b9a 100644 --- a/tests/Models/Book.php +++ b/tests/Models/Book.php @@ -16,5 +16,4 @@ class Book extends MDModel protected $collection = 'books'; protected static $unguarded = true; protected $primaryKey = 'title'; - } diff --git a/tests/Models/Photo.php b/tests/Models/Photo.php index b0683a1..d89c47a 100644 --- a/tests/Models/Photo.php +++ b/tests/Models/Photo.php @@ -9,5 +9,4 @@ class Photo extends MDModel protected $connection = 'mongodb'; protected $collection = 'photos'; protected static $unguarded = true; - } diff --git a/tests/Models/Soft.php b/tests/Models/Soft.php index bf19eae..161e14a 100644 --- a/tests/Models/Soft.php +++ b/tests/Models/Soft.php @@ -2,8 +2,8 @@ namespace Tests\Models; -use OfflineAgency\MongoAutoSync\Http\Models\MDModel; use Jenssegers\Mongodb\Eloquent\SoftDeletes; +use OfflineAgency\MongoAutoSync\Http\Models\MDModel; /** * Class Soft. diff --git a/tests/Models/User.php b/tests/Models/User.php index 6fe167b..9e16ce3 100644 --- a/tests/Models/User.php +++ b/tests/Models/User.php @@ -34,7 +34,7 @@ public function addresses() public function father() { - return $this->embedsOne(User::class); + return $this->embedsOne(self::class); } public function getDateFormat() From 0d5ac4c00c706fe774b6800a1507b532cba2facb Mon Sep 17 00:00:00 2001 From: Giacomo Fabbian Date: Wed, 22 Jul 2020 16:43:18 +0200 Subject: [PATCH 3/5] fix: truncate items and user before start test --- tests/QueryBuilderTest.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/QueryBuilderTest.php b/tests/QueryBuilderTest.php index 5b62bc8..9286d1f 100644 --- a/tests/QueryBuilderTest.php +++ b/tests/QueryBuilderTest.php @@ -24,6 +24,9 @@ public function tearDown(): void public function testDeleteWithId() { + DB::collection('users')->truncate(); + DB::collection('items')->truncate(); + $user = DB::collection('users')->insertGetId([ ['name' => 'Jane Doe', 'age' => 20], ]); @@ -41,7 +44,8 @@ public function testDeleteWithId() $pid = (string) ($product['_id']); - DB::collection('items')->where('user_id', $user_id)->delete($pid); + + $result = DB::collection('items')->where('user_id', $user_id)->delete($pid); $this->assertEquals(3, DB::collection('items')->count()); From 651541f4d520be979d19efe3863cd469db5d05a6 Mon Sep 17 00:00:00 2001 From: Giacomo Fabbian Date: Wed, 22 Jul 2020 16:43:34 +0200 Subject: [PATCH 4/5] feat: add new pre-release --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 393f552..4d01a5b 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,13 @@ You can see the new article on the category page because the package synchronize ```bash composer require offlineagency/laravel-mongo-auto-sync ``` +### Laravel version Compatibility + +| Laravel | Package | +| ----------- | ----------- | +| 5.8.x | 1.x | +| 6.x | 1.x | +| 7.x | 2.0-alpha.1 (Pre-release) | ## Documentation You can find the documentation [here](https://docs.offlineagency.com/laravel-mongo-auto-sync/) From a1883320f9d2de5e85e4e9c6cea6078ea067f076 Mon Sep 17 00:00:00 2001 From: Giacomo Fabbian Date: Wed, 22 Jul 2020 14:49:25 +0000 Subject: [PATCH 5/5] Apply fixes from StyleCI --- tests/QueryBuilderTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/QueryBuilderTest.php b/tests/QueryBuilderTest.php index 9286d1f..bfdc667 100644 --- a/tests/QueryBuilderTest.php +++ b/tests/QueryBuilderTest.php @@ -44,7 +44,6 @@ public function testDeleteWithId() $pid = (string) ($product['_id']); - $result = DB::collection('items')->where('user_id', $user_id)->delete($pid); $this->assertEquals(3, DB::collection('items')->count());