diff --git a/composer.json b/composer.json index ce32f6407..3f278f949 100644 --- a/composer.json +++ b/composer.json @@ -47,5 +47,7 @@ "Jenssegers\\Mongodb\\MongodbQueueServiceProvider" ] } - } + }, + "minimum-stability" : "dev", + "prefer-stable": true } diff --git a/src/Jenssegers/Mongodb/Eloquent/Builder.php b/src/Jenssegers/Mongodb/Eloquent/Builder.php index 1ee1c41fe..68b801079 100644 --- a/src/Jenssegers/Mongodb/Eloquent/Builder.php +++ b/src/Jenssegers/Mongodb/Eloquent/Builder.php @@ -2,8 +2,10 @@ namespace Jenssegers\Mongodb\Eloquent; +use Closure; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Jenssegers\Mongodb\Helpers\QueriesRelationships; +use Jenssegers\Mongodb\Relations\EmbedsOneOrMany; use MongoDB\Driver\Cursor; use MongoDB\Model\BSONDocument; @@ -184,4 +186,15 @@ public function getConnection() { return $this->query->getConnection(); } + + protected function eagerLoadRelation(array $models, $name, Closure $constraints) + { + $relation = $this->getRelation($name); + + if ($relation instanceof EmbedsOneOrMany) { + return $models; + } + + return parent::eagerLoadRelation($models, $name, $constraints); + } } diff --git a/src/Jenssegers/Mongodb/Eloquent/EmbedsRelations.php b/src/Jenssegers/Mongodb/Eloquent/EmbedsRelations.php index 307f5e330..f1c05f294 100644 --- a/src/Jenssegers/Mongodb/Eloquent/EmbedsRelations.php +++ b/src/Jenssegers/Mongodb/Eloquent/EmbedsRelations.php @@ -3,6 +3,7 @@ namespace Jenssegers\Mongodb\Eloquent; use Illuminate\Support\Str; +use Jenssegers\Mongodb\Relations\EmbeddedBy; use Jenssegers\Mongodb\Relations\EmbedsMany; use Jenssegers\Mongodb\Relations\EmbedsOne; @@ -44,7 +45,7 @@ protected function embedsMany($related, $localKey = null, $foreignKey = null, $r } /** - * Define an embedded one-to-many relationship. + * Define an embedded one-to-one relationship. * * @param string $related * @param string $localKey @@ -77,4 +78,33 @@ protected function embedsOne($related, $localKey = null, $foreignKey = null, $re return new EmbedsOne($query, $this, $instance, $localKey, $foreignKey, $relation); } + + /** + * Define an inverse embedded one-to-one relationship. + * + * @param string $related + * @param string $localKey + * @param string $foreignKey + * @param string $relation + * @return \Jenssegers\Mongodb\Relations\EmbeddedBy + */ + protected function embeddedBy($related, $foreignKey = null, $ownerKey = null, $relation = null) + { + // Use debug backtrace to extract the calling method's name and use that as + // the relationship name + list(, $caller) = debug_backtrace(false); + $relation = $caller['function']; + + $query = $this->parentRelation->getQuery(); + + if (is_null($foreignKey)) { + $foreignKey = Str::snake($relation) . '_id'; + } + + $instance = new $related; + + $ownerKey = $ownerKey ?: $instance->getKeyName(); + + return new EmbeddedBy($query, $this, $foreignKey, $ownerKey, $relation); + } } diff --git a/src/Jenssegers/Mongodb/Eloquent/Model.php b/src/Jenssegers/Mongodb/Eloquent/Model.php index 894ebe41a..fc67d6680 100644 --- a/src/Jenssegers/Mongodb/Eloquent/Model.php +++ b/src/Jenssegers/Mongodb/Eloquent/Model.php @@ -31,6 +31,13 @@ abstract class Model extends BaseModel * @var string */ protected $primaryKey = '_id'; + + /** + * The primary key type. + * + * @var string + */ + protected $keyType = 'string'; /** * The parent relation instance. @@ -81,7 +88,7 @@ public function fromDateTime($value) // Let Eloquent convert the value to a DateTime instance. if (!$value instanceof DateTime) { - $value = parent::asDateTime($value); + $value = $this->asDateTime($value); } return new UTCDateTime($value->getTimestamp() * 1000); @@ -138,9 +145,11 @@ public function getAttribute($key) return $this->getAttributeValue($key); } + $camelKey = camel_case($key); + // This checks for embedded relation support. - if (method_exists($this, $key) && !method_exists(self::class, $key)) { - return $this->getRelationValue($key); + if (method_exists($this, $camelKey) && !method_exists(self::class, $camelKey)) { + return $this->getRelationValue($camelKey); } return parent::getAttribute($key); @@ -188,6 +197,9 @@ public function setAttribute($key, $value) */ public function attributesToArray() { + // Initialise relationships so we get correctly formatted values from embedded models + $this->initRelations(); + $attributes = parent::attributesToArray(); // Because the original Eloquent never returns objects, we convert @@ -210,6 +222,22 @@ public function attributesToArray() return $attributes; } + /** + * Initialise the required relationships + */ + public function initRelations() + { + if (!property_exists($this, 'initRelations')) { + return; + } + + foreach ($this->initRelations as $name) { + if (method_exists($this, $name)) { + $this->getRelationValue($name); + } + } + } + /** * @inheritdoc */ @@ -221,7 +249,7 @@ public function getCasts() /** * @inheritdoc */ - protected function originalIsEquivalent($key, $current) + public function originalIsEquivalent($key, $current) { if (!array_key_exists($key, $this->original)) { return false; diff --git a/src/Jenssegers/Mongodb/Query/Builder.php b/src/Jenssegers/Mongodb/Query/Builder.php index a340976e3..6ee51a266 100644 --- a/src/Jenssegers/Mongodb/Query/Builder.php +++ b/src/Jenssegers/Mongodb/Query/Builder.php @@ -59,6 +59,13 @@ class Builder extends BaseBuilder */ public $paginating = false; + /** + * Indicate if we should return the cursor from a get(). + * + * @var bool + */ + public $cursor = false; + /** * All of the available clause operators. * @@ -393,6 +400,11 @@ public function getFresh($columns = []) // Execute query and get MongoCursor $cursor = $this->collection->find($wheres, $options); + // Just return the cursor if required + if ($this->cursor) { + return $cursor; + } + // Return results as an array with numeric keys $results = iterator_to_array($cursor, false); return $this->useCollections ? new Collection($results) : $results; @@ -475,6 +487,18 @@ public function distinct($column = false) return $this; } + /** + * @inheritdoc + */ + public function cursor($columns = []) + { + $this->cursor = true; + + foreach ($this->get($columns) as $doc) { + yield $doc; + } + } + /** * @inheritdoc */ diff --git a/src/Jenssegers/Mongodb/Relations/BelongsTo.php b/src/Jenssegers/Mongodb/Relations/BelongsTo.php index 78b5d63be..457e6bc1f 100644 --- a/src/Jenssegers/Mongodb/Relations/BelongsTo.php +++ b/src/Jenssegers/Mongodb/Relations/BelongsTo.php @@ -3,6 +3,7 @@ namespace Jenssegers\Mongodb\Relations; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model as EloquentModel; class BelongsTo extends \Illuminate\Database\Eloquent\Relations\BelongsTo { @@ -59,4 +60,16 @@ public function getOwnerKey() { return property_exists($this, 'ownerKey') ? $this->ownerKey : $this->otherKey; } + + /** + * 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/Jenssegers/Mongodb/Relations/BelongsToMany.php b/src/Jenssegers/Mongodb/Relations/BelongsToMany.php index 73816b049..0e1b5280b 100644 --- a/src/Jenssegers/Mongodb/Relations/BelongsToMany.php +++ b/src/Jenssegers/Mongodb/Relations/BelongsToMany.php @@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany as EloquentBelongsToMany; use Illuminate\Support\Arr; +use Illuminate\Database\Eloquent\Model as EloquentModel; class BelongsToMany extends EloquentBelongsToMany { @@ -337,4 +338,16 @@ public function getRelatedKey() { return property_exists($this, 'relatedPivotKey') ? $this->relatedPivotKey : $this->relatedKey; } + + /** + * 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/Jenssegers/Mongodb/Relations/EmbeddedBy.php b/src/Jenssegers/Mongodb/Relations/EmbeddedBy.php new file mode 100644 index 000000000..474da1dd7 --- /dev/null +++ b/src/Jenssegers/Mongodb/Relations/EmbeddedBy.php @@ -0,0 +1,105 @@ +query->where($this->getOwnerKey(), '=', $this->related->{$this->getOwnerKey()}); + } + } + + /** + * Set the constraints for an eager load of the relation. + * + * @param array $models + * @return void + */ + public function addEagerConstraints(array $models) + { + + } + + /** + * Initialize the relation on a set of models. + * + * @param array $models + * @param string $relation + * @return array + */ + public function initRelation(array $models, $relation) + { + foreach ($models as $model) { + $model->setRelation($relation, $this->getDefaultFor($model)); + } + + return $models; + } + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function match(array $models, Collection $results, $relation) + { + $foreign = $this->foreignKey; + + $owner = $this->ownerKey; + + // First we will get to build a dictionary of the child models by their primary + // key of the relationship, then we can easily match the children back onto + // the parents using that dictionary and the primary key of the children. + $dictionary = []; + + foreach ($results as $result) { + $dictionary[$result->getAttribute($owner)] = $result; + } + + // Once we have the dictionary constructed, we can loop through all the parents + // and match back onto their children using these keys of the dictionary and + // the primary key of the children to map them onto the correct instances. + foreach ($models as $model) { + if (isset($dictionary[$model->{$foreign}])) { + $model->setRelation($relation, $dictionary[$model->{$foreign}]); + } + } + + return $models; + } + + /** + * Get the results of the relationship. + * + * @return mixed + */ + public function getResults() + { + return $this->related; + } + + /** + * Touch all of the related models for the relationship. + * + * @return void + */ + public function touch() + { + $column = $this->getRelated()->getUpdatedAtColumn(); + + $this->update([$column => $this->getRelated()->freshTimestampString()]); + } +} diff --git a/src/Jenssegers/Mongodb/Relations/EmbedsMany.php b/src/Jenssegers/Mongodb/Relations/EmbedsMany.php index b0e40893d..273c49b8a 100644 --- a/src/Jenssegers/Mongodb/Relations/EmbedsMany.php +++ b/src/Jenssegers/Mongodb/Relations/EmbedsMany.php @@ -7,6 +7,7 @@ use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Pagination\Paginator; use MongoDB\BSON\ObjectID; +use Illuminate\Database\Eloquent\Model as EloquentModel; class EmbedsMany extends EmbedsOneOrMany { @@ -39,8 +40,8 @@ public function getResults() public function performInsert(Model $model) { // Generate a new key if needed. - if ($model->getKeyName() == '_id' && !$model->getKey()) { - $model->setAttribute('_id', new ObjectID); + if (!$model->getKey()) { + $model->setAttribute($model->getKeyName(), new ObjectID); } // For deeply nested documents, let the parent handle the changes. @@ -127,6 +128,8 @@ public function performDelete(Model $model) */ public function associate(Model $model) { + $model->setParentRelation($this); + if (!$this->contains($model)) { return $this->associateNew($model); } else { @@ -236,8 +239,8 @@ public function attach(Model $model) protected function associateNew($model) { // Create a new key if needed. - if (!$model->getAttribute('_id')) { - $model->setAttribute('_id', new ObjectID); + if (!$model->getKey()) { + $model->setAttribute($model->getKeyName(), new ObjectID()); } $records = $this->getEmbedded(); @@ -328,4 +331,16 @@ public function __call($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/Jenssegers/Mongodb/Relations/EmbedsOne.php b/src/Jenssegers/Mongodb/Relations/EmbedsOne.php index 2efbfe1df..5f58234b6 100644 --- a/src/Jenssegers/Mongodb/Relations/EmbedsOne.php +++ b/src/Jenssegers/Mongodb/Relations/EmbedsOne.php @@ -3,10 +3,14 @@ namespace Jenssegers\Mongodb\Relations; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\Concerns\SupportsDefaultModels; use MongoDB\BSON\ObjectID; +use Illuminate\Database\Eloquent\Model as EloquentModel; class EmbedsOne extends EmbedsOneOrMany { + use SupportsDefaultModels; + /** * @inheritdoc */ @@ -24,7 +28,7 @@ public function initRelation(array $models, $relation) */ public function getResults() { - return $this->toModel($this->getEmbedded()); + return $this->toModel($this->getEmbedded()) ?: $this->getDefaultFor($this->parent); } /** @@ -136,4 +140,24 @@ public function delete() { return $this->performDelete(); } + + /** + * @inheritdoc + */ + public function newRelatedInstanceFor(Model $parent) + { + return $this->related->newInstance(); + } + + /** + * 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/Jenssegers/Mongodb/Relations/EmbedsOneOrMany.php b/src/Jenssegers/Mongodb/Relations/EmbedsOneOrMany.php index a8f90544d..177160bf1 100644 --- a/src/Jenssegers/Mongodb/Relations/EmbedsOneOrMany.php +++ b/src/Jenssegers/Mongodb/Relations/EmbedsOneOrMany.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Relations\Relation; use Jenssegers\Mongodb\Eloquent\Model; +use Illuminate\Database\Eloquent\Model as EloquentModel; abstract class EmbedsOneOrMany extends Relation { @@ -403,4 +404,16 @@ 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/src/Jenssegers/Mongodb/Relations/HasMany.php b/src/Jenssegers/Mongodb/Relations/HasMany.php index 47e60e533..93a25425a 100644 --- a/src/Jenssegers/Mongodb/Relations/HasMany.php +++ b/src/Jenssegers/Mongodb/Relations/HasMany.php @@ -4,6 +4,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\HasMany as EloquentHasMany; +use Illuminate\Database\Eloquent\Model as EloquentModel; class HasMany extends EloquentHasMany { @@ -77,4 +78,16 @@ public function getRelationQuery(Builder $query, Builder $parent, $columns = ['* return $query->where($this->getHasCompareKey(), 'exists', true); } + + /** + * 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/Jenssegers/Mongodb/Relations/HasOne.php b/src/Jenssegers/Mongodb/Relations/HasOne.php index c91a65787..a8d65dba9 100644 --- a/src/Jenssegers/Mongodb/Relations/HasOne.php +++ b/src/Jenssegers/Mongodb/Relations/HasOne.php @@ -4,6 +4,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\HasOne as EloquentHasOne; +use Illuminate\Database\Eloquent\Model as EloquentModel; class HasOne extends EloquentHasOne { @@ -77,4 +78,16 @@ public function getRelationQuery(Builder $query, Builder $parent, $columns = ['* return $query->where($this->getForeignKeyName(), 'exists', true); } + + /** + * 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/Jenssegers/Mongodb/Relations/MorphTo.php b/src/Jenssegers/Mongodb/Relations/MorphTo.php index 08344f526..32f3d3ab6 100644 --- a/src/Jenssegers/Mongodb/Relations/MorphTo.php +++ b/src/Jenssegers/Mongodb/Relations/MorphTo.php @@ -3,6 +3,7 @@ namespace Jenssegers\Mongodb\Relations; use Illuminate\Database\Eloquent\Relations\MorphTo as EloquentMorphTo; +use Illuminate\Database\Eloquent\Model as EloquentModel; class MorphTo extends EloquentMorphTo { @@ -42,4 +43,16 @@ public function getOwnerKey() { return property_exists($this, 'ownerKey') ? $this->ownerKey : $this->otherKey; } + + /** + * 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'; + } }