Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cast data before saving it into database and mongoId as Relationships #834

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
afc57a0
add save cast to cast data before saving data into mongoDB
RTLer May 6, 2016
81d2279
fix addHasWhere to always have string in array_count_values
RTLer May 10, 2016
dc20406
fix addHasWhere to get proper id's from castAttribute
RTLer May 10, 2016
8ce502a
overwrite eloquent cast methods to make that work for set data too
RTLer May 10, 2016
ecf8feb
fix BelongsTo's match method
RTLer May 10, 2016
0aa0297
add HasOneOrManyTrait trait
RTLer May 10, 2016
879f087
set one and many relations to use HasOneOrManyTrait
RTLer May 10, 2016
c7f7f37
set test to run with use_mongo_id:true
RTLer May 10, 2016
427582e
add morph one and many classes to make it use HasOneOrManyTrait
RTLer May 10, 2016
6b0d504
fix buildDictionary in EloquentMorphTo class
RTLer May 10, 2016
c5aef81
cleanup Model class
RTLer May 10, 2016
b886303
add useMongoId to check if driver uses mongoId in relations
RTLer May 10, 2016
d05d237
add relation tests that tests new cast part
RTLer May 10, 2016
dd6923e
cleanup the code
RTLer May 10, 2016
dbbab06
add setter for casts
RTLer May 10, 2016
1bf882f
cleanup
RTLer May 10, 2016
68e798d
Merge branch 'jenssegers/master' into cast-data-befor-saving
RTLer Jul 19, 2016
67775a2
Merge branch 'jenssegers/master' into cast-data-befor-saving
RTLer Aug 24, 2016
27e36bb
Merge branch 'jenssegers/master' into cast-data-befor-saving
RTLer Sep 1, 2016
b3340b4
Merge branch 'jenssegers/master' into cast-data-befor-saving
RTLer Sep 2, 2016
5029a4f
fix RelationsWithMongoIdTest
RTLer Sep 2, 2016
54b9266
"StyleCI" code style fix
RTLer Sep 2, 2016
2c70388
remove unnecessary use_mongo_id that enable use_mongo_id for all tests
RTLer Sep 3, 2016
78707c0
remove missed patch file
RTLer Sep 3, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions patch.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
diff --git a/src/Jenssegers/Mongodb/Eloquent/Model.php b/src/Jenssegers/Mongodb/Eloquent/Model.php
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not this diff patch file be excluded from commit ?

index 3707c86cb7f732c97908d8ca3754118f4256e422..4a17b0f8c045b2d02f3f834ae0b207dccd2be311 100644
--- a/src/Jenssegers/Mongodb/Eloquent/Model.php
+++ b/src/Jenssegers/Mongodb/Eloquent/Model.php
@@ -615,7 +615,7 @@ abstract class Model extends BaseModel
public function castAttribute($key, $value, $castType = 'get')
{
if (is_null($value)) {
- return null;
+ return;
}

if (!$this->hasCast($key, null, $castType)) {
diff --git a/tests/RelationsWithMongoIdTest.php b/tests/RelationsWithMongoIdTest.php
index 8e78bf592b016b7a85609e699c6f0fa24c55dc0e..517c3be993b8eb68bae2349a061c6677be3cbfa7 100644
--- a/tests/RelationsWithMongoIdTest.php
+++ b/tests/RelationsWithMongoIdTest.php
@@ -533,15 +533,15 @@ class RelationsWithMongoIdTest extends TestCase
$user = new User;
$user->setCasts([
'last_seen' => 'UTCDatetime',
- 'age'=>'int',
- 'name'=>'string',
- 'rate'=>'float',
- 'birthday'=>'timestamp',
- 'isActive'=>'bool',
- 'default'=>'default',
+ 'age' => 'int',
+ 'name' => 'string',
+ 'rate' => 'float',
+ 'birthday' => 'timestamp',
+ 'isActive' => 'bool',
+ 'default' => 'default',
], 'set');
$user->setCasts([
- 'name'=>'string',
+ 'name' => 'string',
]);
$carbon = Carbon\Carbon::now();
$UTCDateTime = new \MongoDB\BSON\UTCDateTime($carbon->timestamp * 1000);
@@ -562,5 +562,4 @@ class RelationsWithMongoIdTest extends TestCase
$test = $user->castAttribute('default', 'test', 'set');
$this->assertEquals('test', $test);
}
-
}
1 change: 1 addition & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<testsuite name="relations">
<directory>tests/RelationsTest.php</directory>
<directory>tests/EmbeddedRelationsTest.php</directory>
<directory>tests/RelationsWithMongoIdTest.php</directory>
</testsuite>
<testsuite name="mysqlrelations">
<directory>tests/RelationsTest.php</directory>
Expand Down
17 changes: 13 additions & 4 deletions src/Jenssegers/Mongodb/Eloquent/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,10 @@ protected function addHasWhere(EloquentBuilder $hasQuery, Relation $relation, $o

// Get the number of related objects for each possible parent.
$relations = $query->pluck($relation->getHasCompareKey());
$relationCount = array_count_values(array_map(function ($id) {
return (string) $id; // Convert Back ObjectIds to Strings
}, is_array($relations) ? $relations : $relations->toArray()));
$relations = array_map(function ($id) {
return (string) $id;
}, is_array($relations) ? $relations : $relations->toArray());
$relationCount = array_count_values($relations);

// Remove unwanted related objects based on the operator and count.
$relationCount = array_filter($relationCount, function ($counted) use ($count, $operator) {
Expand Down Expand Up @@ -202,7 +203,15 @@ protected function addHasWhere(EloquentBuilder $hasQuery, Relation $relation, $o
}

// All related ids.
$relatedIds = array_keys($relationCount);
$relatedIds = array_map(function ($id) use ($relation) {
$relationModel = $relation->getRelated();
$relationModel->setRelationCast($relation->getHasCompareKey());
if ($relationModel->useMongoId()
&& $relationModel->hasCast($relation->getHasCompareKey(), null, 'set')) {
$id = $relationModel->castAttribute($relation->getHasCompareKey(), $id, 'set');
}
return $id;
}, array_keys($relationCount));

// Add whereIn to the query.
return $this->whereIn($this->model->getKeyName(), $relatedIds, $boolean, $not);
Expand Down
4 changes: 2 additions & 2 deletions src/Jenssegers/Mongodb/Eloquent/HybridRelations.php
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<?php namespace Jenssegers\Mongodb\Eloquent;

use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\Relations\MorphOne;
use Illuminate\Support\Str;
use Jenssegers\Mongodb\Relations\BelongsTo;
use Jenssegers\Mongodb\Relations\BelongsToMany;
use Jenssegers\Mongodb\Relations\HasMany;
use Jenssegers\Mongodb\Relations\HasOne;
use Jenssegers\Mongodb\Relations\MorphTo;
use Jenssegers\Mongodb\Relations\MorphMany;
use Jenssegers\Mongodb\Relations\MorphOne;

trait HybridRelations
{
Expand Down
188 changes: 171 additions & 17 deletions src/Jenssegers/Mongodb/Eloquent/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ abstract class Model extends BaseModel
*/
protected $parentRelation;

/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $saveCasts = [];

/**
* Custom accessor for the model's id.
*
Expand Down Expand Up @@ -290,15 +297,13 @@ protected function getAttributeFromArray($key)
*/
public function setAttribute($key, $value)
{
// Convert _id to ObjectID.
if ($key == '_id' and is_string($value)) {
$builder = $this->newBaseQueryBuilder();

$value = $builder->convertKey($value);
}
// cast data for saving.
// set _id to converted into ObjectID if its possible.
$this->setRelationCast($key);
$value = $this->castAttribute($key, $value, 'set');

// Support keys in dot notation.
elseif (str_contains($key, '.')) {
if (str_contains($key, '.')) {
if (in_array($key, $this->getDates()) && $value) {
$value = $this->fromDateTime($value);
}
Expand Down Expand Up @@ -340,16 +345,6 @@ public function attributesToArray()
return $attributes;
}

/**
* Get the casts array.
*
* @return array
*/
public function getCasts()
{
return $this->casts;
}

/**
* Determine if the new and old values for a given key are numerically equivalent.
*
Expand Down Expand Up @@ -552,4 +547,163 @@ public function __call($method, $parameters)

return parent::__call($method, $parameters);
}

/**
* setter for casts
* @param $cast
* @param string $castType
* @return void
*/
public function setCasts($cast, $castType = 'get')
{
if ($castType == 'set') {
$this->saveCasts = $cast;
return;
}
$this->casts = $cast;
}

/**
* Get the casts array.
*
* @param string $castType
* @return array
*/
public function getCasts($castType = 'get')
{
if ($castType == 'set') {
return $this->saveCasts;
}
return $this->casts;
}

/**
* Get the type of save cast for a model attribute.
*
* @param string $key
* @param string $castType
* @return string
*/
protected function getCastType($key, $castType = 'get')
{
return trim(strtolower($this->getCasts($castType)[$key]));
}

/**
* Determine whether an attribute should be cast to a native type.
*
* @param string $key
* @param array|string|null $types
* @param string $castType
* @return bool
*/
public function hasCast($key, $types = null, $castType = 'get')
{
if (array_key_exists($key, $this->getCasts($castType))) {
return $types ? in_array($this->getCastType($key, $castType), (array) $types, true) : true;
}

return false;
}
/**
* check if driver uses mongoId in relations.
*
* @return bool
*/
public function useMongoId()
{
return (bool) config('database.connections.mongodb.use_mongo_id', false);
}

/**
* Cast an attribute to a mongo type.
*
* @param string $key
* @param mixed $value
* @param string $castType
* @return mixed
*/
public function castAttribute($key, $value, $castType = 'get')
{
if (is_null($value)) {
return;
}

if (!$this->hasCast($key, null, $castType)) {
return $value;
}

switch ($this->getCastType($key, $castType)) {
case 'int':
case 'integer':
return (int) $value;
case 'real':
case 'float':
case 'double':
return (float) $value;
case 'string':
return (string) $value;
case 'bool':
case 'boolean':
return (bool) $value;
case 'date':
case 'utcdatetime':
case 'mongodate':
return $this->asMongoDate($value);
case 'mongoid':
case 'objectid':
return $this->asMongoID($value);
case 'timestamp':
return $this->asTimeStamp($value);
default:
return $value;
}
}

/**
* convert value into ObjectID if its possible
*
* @param $value
* @return UTCDatetime
*/
protected function asMongoID($value)
{
if (is_string($value) and strlen($value) === 24 and ctype_xdigit($value)) {
return new ObjectID($value);
}
return $value;
}

/**
* convert value into UTCDatetime
* @param $value
* @return UTCDatetime
*/
protected function asMongoDate($value)
{
if ($value instanceof UTCDatetime) {
return $value;
}

return new UTCDatetime($this->asTimeStamp($value) * 1000);
}

/**
* add relation that ended with _id into objectId
* if config allow it
*
* @param $key
*/
public function setRelationCast($key)
{
if ($key == '_id') {
$this->saveCasts['_id'] = 'ObjectID';
return;
}
if ($this->useMongoId()) {
if (ends_with($key, '_id')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about relations ending with _ids ?

$this->saveCasts[$key] = 'ObjectID';
}
}
}
}
30 changes: 30 additions & 0 deletions src/Jenssegers/Mongodb/Relations/BelongsTo.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,34 @@ public function addEagerConstraints(array $models)

$this->query->whereIn($key, $this->getEagerModelKeys($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, \Illuminate\Database\Eloquent\Collection $results, $relation)
{
$foreign = $this->foreignKey;
$other = $this->otherKey;
// 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($other)] = $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[(string) $model->$foreign])) {
$model->setRelation($relation, $dictionary[(string) $model->$foreign]);
}
}
return $models;
}
}
2 changes: 2 additions & 0 deletions src/Jenssegers/Mongodb/Relations/HasMany.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

class HasMany extends EloquentHasMany
{
use HasOneOrManyTrait;

/**
* Add the constraints for a relationship count query.
*
Expand Down
2 changes: 2 additions & 0 deletions src/Jenssegers/Mongodb/Relations/HasOne.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

class HasOne extends EloquentHasOne
{
use HasOneOrManyTrait;

/**
* Add the constraints for a relationship count query.
*
Expand Down
Loading