Unverified Commit 20754f16 authored by Divine's avatar Divine Committed by GitHub

Merge pull request #1982 from divine/remove_embeds

[4.x] Remove embeds relations
parents a8041180 6ba4a6f2
...@@ -2,3 +2,5 @@ ...@@ -2,3 +2,5 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [Unreleased] ## [Unreleased]
### Removed
- EmbedsOne and EmbedsMany relations by [@divine](https://github.com/divine).
...@@ -30,8 +30,6 @@ This package adds functionalities to the Eloquent model and Query builder for Mo ...@@ -30,8 +30,6 @@ This package adds functionalities to the Eloquent model and Query builder for Mo
- [Relationships](#relationships) - [Relationships](#relationships)
- [Basic Usage](#basic-usage-1) - [Basic Usage](#basic-usage-1)
- [belongsToMany and pivots](#belongstomany-and-pivots) - [belongsToMany and pivots](#belongstomany-and-pivots)
- [EmbedsMany Relationship](#embedsmany-relationship)
- [EmbedsOne Relationship](#embedsone-relationship)
- [Query Builder](#query-builder) - [Query Builder](#query-builder)
- [Basic Usage](#basic-usage-2) - [Basic Usage](#basic-usage-2)
- [Available operations](#available-operations) - [Available operations](#available-operations)
...@@ -419,7 +417,7 @@ Aggregations can be also used on sub-documents: ...@@ -419,7 +417,7 @@ Aggregations can be also used on sub-documents:
$total = Order::max('suborder.price'); $total = Order::max('suborder.price');
``` ```
**NOTE**: This aggregation only works with single sub-documents (like `EmbedsOne`) not subdocument arrays (like `EmbedsMany`). **NOTE**: This aggregation only works with single sub-documents not arrays.
**Incrementing/Decrementing the value of a column** **Incrementing/Decrementing the value of a column**
...@@ -712,10 +710,6 @@ The only available relationships are: ...@@ -712,10 +710,6 @@ The only available relationships are:
- belongsTo - belongsTo
- belongsToMany - belongsToMany
The MongoDB-specific relationships are:
- embedsOne
- embedsMany
Here is a small example: Here is a small example:
```php ```php
...@@ -764,152 +758,6 @@ class User extends Model ...@@ -764,152 +758,6 @@ class User extends Model
} }
``` ```
### EmbedsMany Relationship
If you want to embed models, rather than referencing them, you can use the `embedsMany` relation. This relation is similar to the `hasMany` relation but embeds the models inside the parent object.
**REMEMBER**: These relations return Eloquent collections, they don't return query builder objects!
```php
use Jenssegers\Mongodb\Eloquent\Model;
class User extends Model
{
public function books()
{
return $this->embedsMany(Book::class);
}
}
```
You can access the embedded models through the dynamic property:
```php
$user = User::first();
foreach ($user->books as $book) {
//
}
```
The inverse relation is auto*magically* available. You don't need to define this reverse relation.
```php
$book = Book::first();
$user = $book->user;
```
Inserting and updating embedded models works similar to the `hasMany` relation:
```php
$book = $user->books()->save(
new Book(['title' => 'A Game of Thrones'])
);
// or
$book =
$user->books()
->create(['title' => 'A Game of Thrones']);
```
You can update embedded models using their `save` method (available since release 2.0.0):
```php
$book = $user->books()->first();
$book->title = 'A Game of Thrones';
$book->save();
```
You can remove an embedded model by using the `destroy` method on the relation, or the `delete` method on the model (available since release 2.0.0):
```php
$book->delete();
// Similar operation
$user->books()->destroy($book);
```
If you want to add or remove an embedded model, without touching the database, you can use the `associate` and `dissociate` methods.
To eventually write the changes to the database, save the parent object:
```php
$user->books()->associate($book);
$user->save();
```
Like other relations, embedsMany assumes the local key of the relationship based on the model name. You can override the default local key by passing a second argument to the embedsMany method:
```php
use Jenssegers\Mongodb\Eloquent\Model;
class User extends Model
{
public function books()
{
return $this->embedsMany(Book::class, 'local_key');
}
}
```
Embedded relations will return a Collection of embedded items instead of a query builder. Check out the available operations here: https://laravel.com/docs/master/collections
### EmbedsOne Relationship
The embedsOne relation is similar to the embedsMany relation, but only embeds a single model.
```php
use Jenssegers\Mongodb\Eloquent\Model;
class Book extends Model
{
public function author()
{
return $this->embedsOne(Author::class);
}
}
```
You can access the embedded models through the dynamic property:
```php
$book = Book::first();
$author = $book->author;
```
Inserting and updating embedded models works similar to the `hasOne` relation:
```php
$author = $book->author()->save(
new Author(['name' => 'John Doe'])
);
// Similar
$author =
$book->author()
->create(['name' => 'John Doe']);
```
You can update the embedded model using the `save` method (available since release 2.0.0):
```php
$author = $book->author;
$author->name = 'Jane Doe';
$author->save();
```
You can replace the embedded model with a new model like this:
```php
$newAuthor = new Author(['name' => 'Jane Doe']);
$book->author()->save($newAuthor);
```
Query Builder Query Builder
------------- -------------
...@@ -1098,39 +946,10 @@ Jenssegers\Mongodb\MongodbQueueServiceProvider::class, ...@@ -1098,39 +946,10 @@ Jenssegers\Mongodb\MongodbQueueServiceProvider::class,
Upgrading Upgrading
--------- ---------
#### Upgrading from version 2 to 3 #### Upgrading from version 3 to 4
In this new major release which supports the new MongoDB PHP extension, we also moved the location of the Model class and replaced the MySQL model class with a trait.
Please change all `Jenssegers\Mongodb\Model` references to `Jenssegers\Mongodb\Eloquent\Model` either at the top of your model files or your registered alias. This new major release contains breaking changes which is listed below:
```php - EmbedsOne and EmbedsMany relations has been removed completely. See explanation [here](https://github.com/jenssegers/laravel-mongodb/issues/1974#issuecomment-592859508)
use Jenssegers\Mongodb\Eloquent\Model;
class User extends Model For any other minor changes, please take a look at our [changelog](CHANGELOG.md)
{
//
}
```
If you are using hybrid relations, your MySQL classes should now extend the original Eloquent model class `Illuminate\Database\Eloquent\Model` instead of the removed `Jenssegers\Eloquent\Model`.
Instead use the new `Jenssegers\Mongodb\Eloquent\HybridRelations` trait. This should make things more clear as there is only one single model class in this package.
```php
use Jenssegers\Mongodb\Eloquent\HybridRelations;
class User extends Model
{
use HybridRelations;
protected $connection = 'mysql';
}
```
Embedded relations now return an `Illuminate\Database\Eloquent\Collection` rather than a custom Collection class. If you were using one of the special methods that were available, convert them to Collection operations.
```php
$books = $user->books()->sortBy('title')->get();
```
...@@ -31,7 +31,6 @@ ...@@ -31,7 +31,6 @@
</testsuite> </testsuite>
<testsuite name="relations"> <testsuite name="relations">
<file>tests/RelationsTest.php</file> <file>tests/RelationsTest.php</file>
<file>tests/EmbeddedRelationsTest.php</file>
</testsuite> </testsuite>
<testsuite name="mysqlrelations"> <testsuite name="mysqlrelations">
<file>tests/RelationsTest.php</file> <file>tests/RelationsTest.php</file>
......
...@@ -35,14 +35,6 @@ class Builder extends EloquentBuilder ...@@ -35,14 +35,6 @@ class Builder extends EloquentBuilder
*/ */
public function update(array $values, array $options = []) public function update(array $values, array $options = [])
{ {
// Intercept operations on embedded models and delegate logic
// to the parent relation instance.
if ($relation = $this->model->getParentRelation()) {
$relation->performUpdate($this->model, $values);
return 1;
}
return $this->toBase()->update($this->addUpdatedAtColumn($values), $options); return $this->toBase()->update($this->addUpdatedAtColumn($values), $options);
} }
...@@ -51,14 +43,6 @@ class Builder extends EloquentBuilder ...@@ -51,14 +43,6 @@ class Builder extends EloquentBuilder
*/ */
public function insert(array $values) 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 parent::insert($values); return parent::insert($values);
} }
...@@ -67,14 +51,6 @@ class Builder extends EloquentBuilder ...@@ -67,14 +51,6 @@ class Builder extends EloquentBuilder
*/ */
public function insertGetId(array $values, $sequence = null) 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 parent::insertGetId($values, $sequence); return parent::insertGetId($values, $sequence);
} }
...@@ -83,14 +59,6 @@ class Builder extends EloquentBuilder ...@@ -83,14 +59,6 @@ class Builder extends EloquentBuilder
*/ */
public function delete() 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 parent::delete(); return parent::delete();
} }
...@@ -99,23 +67,6 @@ class Builder extends EloquentBuilder ...@@ -99,23 +67,6 @@ class Builder extends EloquentBuilder
*/ */
public function increment($column, $amount = 1, array $extra = []) 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 parent::increment($column, $amount, $extra); return parent::increment($column, $amount, $extra);
} }
...@@ -124,21 +75,6 @@ class Builder extends EloquentBuilder ...@@ -124,21 +75,6 @@ class Builder extends EloquentBuilder
*/ */
public function decrement($column, $amount = 1, array $extra = []) 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 parent::decrement($column, $amount, $extra); return parent::decrement($column, $amount, $extra);
} }
......
<?php
namespace Jenssegers\Mongodb\Eloquent;
use Illuminate\Support\Str;
use Jenssegers\Mongodb\Relations\EmbedsMany;
use Jenssegers\Mongodb\Relations\EmbedsOne;
trait EmbedsRelations
{
/**
* Define an embedded one-to-many relationship.
* @param string $related
* @param string $localKey
* @param string $foreignKey
* @param string $relation
* @return \Jenssegers\Mongodb\Relations\EmbedsMany
*/
protected function embedsMany($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 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 \Jenssegers\Mongodb\Relations\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);
}
}
...@@ -17,7 +17,7 @@ use MongoDB\BSON\UTCDateTime; ...@@ -17,7 +17,7 @@ use MongoDB\BSON\UTCDateTime;
abstract class Model extends BaseModel abstract class Model extends BaseModel
{ {
use HybridRelations, EmbedsRelations; use HybridRelations;
/** /**
* The collection associated with the model. * The collection associated with the model.
...@@ -143,11 +143,6 @@ abstract class Model extends BaseModel ...@@ -143,11 +143,6 @@ abstract class Model extends BaseModel
return $this->getAttributeValue($key); return $this->getAttributeValue($key);
} }
// This checks for embedded relation support.
if (method_exists($this, $key) && !method_exists(self::class, $key)) {
return $this->getRelationValue($key);
}
return parent::getAttribute($key); return parent::getAttribute($key);
} }
......
<?php
namespace Jenssegers\Mongodb\Relations;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Model as EloquentModel;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\Paginator;
use MongoDB\BSON\ObjectID;
class EmbedsMany extends EmbedsOneOrMany
{
/**
* @inheritdoc
*/
public function initRelation(array $models, $relation)
{
foreach ($models as $model) {
$model->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';
}
}
<?php
namespace Jenssegers\Mongodb\Relations;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Model as EloquentModel;
use MongoDB\BSON\ObjectID;
class EmbedsOne extends EmbedsOneOrMany
{
/**
* @inheritdoc
*/
public function initRelation(array $models, $relation)
{
foreach ($models as $model) {
$model->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';
}
}
<?php
namespace Jenssegers\Mongodb\Relations;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model as EloquentModel;
use Illuminate\Database\Eloquent\Relations\Relation;
use Jenssegers\Mongodb\Eloquent\Model;
abstract class EmbedsOneOrMany extends Relation
{
/**
* The local key of the parent model.
* @var string
*/
protected $localKey;
/**
* The foreign key of the parent model.
* @var string
*/
protected $foreignKey;
/**
* The "name" of the relationship.
* @var string
*/
protected $relation;
/**
* Create a new embeds many relationship instance.
* @param Builder $query
* @param Model $parent
* @param Model $related
* @param string $localKey
* @param string $foreignKey
* @param string $relation
*/
public function __construct(Builder $query, Model $parent, Model $related, $localKey, $foreignKey, $relation)
{
$this->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';
}
}
<?php
declare(strict_types=1);
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Events\Dispatcher;
use MongoDB\BSON\ObjectId;
class EmbeddedRelationsTest extends TestCase
{
public function tearDown(): void
{
Mockery::close();
User::truncate();
Book::truncate();
Item::truncate();
Role::truncate();
Client::truncate();
Group::truncate();
Photo::truncate();
}
public function testEmbedsManySave()
{
$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(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());
}
}
...@@ -639,27 +639,6 @@ class QueryBuilderTest extends TestCase ...@@ -639,27 +639,6 @@ class QueryBuilderTest extends TestCase
$results = DB::collection('users')->where('name', 'not regexp', '/.*doe/i')->get(); $results = DB::collection('users')->where('name', 'not regexp', '/.*doe/i')->get();
$this->assertCount(1, $results); $this->assertCount(1, $results);
DB::collection('users')->insert([
[
'name' => 'John Doe',
'addresses' => [
['city' => 'Ghent'],
['city' => 'Paris'],
],
],
[
'name' => 'Jane Doe',
'addresses' => [
['city' => 'Brussels'],
['city' => 'Paris'],
],
],
]);
$users = DB::collection('users')->where('addresses', 'elemMatch', ['city' => 'Brussels'])->get();
$this->assertCount(1, $users);
$this->assertEquals('Jane Doe', $users[0]['name']);
} }
public function testIncrement() public function testIncrement()
......
...@@ -337,8 +337,6 @@ class RelationsTest extends TestCase ...@@ -337,8 +337,6 @@ class RelationsTest extends TestCase
$this->assertArrayHasKey('groups', $user->getAttributes()); $this->assertArrayHasKey('groups', $user->getAttributes());
// Assert they are attached // Assert they are attached
$this->assertContains($group->_id, $user->groups->pluck('_id')->toArray());
$this->assertContains($user->_id, $group->users->pluck('_id')->toArray());
$this->assertEquals($group->_id, $user->groups()->first()->_id); $this->assertEquals($group->_id, $user->groups()->first()->_id);
$this->assertEquals($user->_id, $group->users()->first()->_id); $this->assertEquals($user->_id, $group->users()->first()->_id);
} }
......
...@@ -2,15 +2,9 @@ ...@@ -2,15 +2,9 @@
declare(strict_types=1); declare(strict_types=1);
use Jenssegers\Mongodb\Eloquent\Model as Eloquent; use Jenssegers\Mongodb\Eloquent\Model as Eloquent;
use Jenssegers\Mongodb\Relations\EmbedsMany;
class Address extends Eloquent class Address extends Eloquent
{ {
protected $connection = 'mongodb'; protected $connection = 'mongodb';
protected static $unguarded = true; protected static $unguarded = true;
public function addresses(): EmbedsMany
{
return $this->embedsMany('Address');
}
} }
...@@ -67,16 +67,6 @@ class User extends Eloquent implements AuthenticatableContract, CanResetPassword ...@@ -67,16 +67,6 @@ class User extends Eloquent implements AuthenticatableContract, CanResetPassword
return $this->morphMany('Photo', 'imageable'); return $this->morphMany('Photo', 'imageable');
} }
public function addresses()
{
return $this->embedsMany('Address');
}
public function father()
{
return $this->embedsOne('User');
}
public function getDateFormat() public function getDateFormat()
{ {
return 'l jS \of F Y h:i:s A'; return 'l jS \of F Y h:i:s A';
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment