Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
L
laravel-mongodb
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
sinan
laravel-mongodb
Commits
266305ca
Commit
266305ca
authored
May 15, 2017
by
Eric Tucker
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add `whereHas()` support for hybrid relations
parent
3276bac2
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
280 additions
and
56 deletions
+280
-56
Builder.php
src/Jenssegers/Mongodb/Eloquent/Builder.php
+6
-51
HybridRelations.php
src/Jenssegers/Mongodb/Eloquent/HybridRelations.php
+9
-0
EloquentBuilder.php
src/Jenssegers/Mongodb/Helpers/EloquentBuilder.php
+10
-0
QueriesRelationships.php
src/Jenssegers/Mongodb/Helpers/QueriesRelationships.php
+184
-0
BelongsTo.php
src/Jenssegers/Mongodb/Relations/BelongsTo.php
+10
-0
HybridRelationsTest.php
tests/HybridRelationsTest.php
+56
-1
RelationsTest.php
tests/RelationsTest.php
+1
-0
database.php
tests/config/database.php
+4
-4
No files found.
src/Jenssegers/Mongodb/Eloquent/Builder.php
View file @
266305ca
<?php
namespace
Jenssegers\Mongodb\Eloquent
;
<?php
namespace
Jenssegers\Mongodb\Eloquent
;
use
Illuminate\Database\Eloquent\Builder
as
EloquentBuilder
;
use
Illuminate\Database\Eloquent\Builder
as
EloquentBuilder
;
use
Illuminate\Database\Eloquent\Relations\Relation
;
use
Jenssegers\Mongodb\Helpers\QueriesRelationships
;
use
MongoDB\Driver\Cursor
;
use
MongoDB\Driver\Cursor
;
use
MongoDB\Model\BSONDocument
;
use
MongoDB\Model\BSONDocument
;
class
Builder
extends
EloquentBuilder
class
Builder
extends
EloquentBuilder
{
{
use
QueriesRelationships
;
/**
/**
* The methods that should be returned from query builder.
* The methods that should be returned from query builder.
*
*
...
@@ -139,54 +140,6 @@ class Builder extends EloquentBuilder
...
@@ -139,54 +140,6 @@ class Builder extends EloquentBuilder
return
parent
::
decrement
(
$column
,
$amount
,
$extra
);
return
parent
::
decrement
(
$column
,
$amount
,
$extra
);
}
}
/**
* @inheritdoc
*/
protected
function
addHasWhere
(
EloquentBuilder
$hasQuery
,
Relation
$relation
,
$operator
,
$count
,
$boolean
)
{
$query
=
$hasQuery
->
getQuery
();
// 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
->
flatten
()
->
toArray
()));
// Remove unwanted related objects based on the operator and count.
$relationCount
=
array_filter
(
$relationCount
,
function
(
$counted
)
use
(
$count
,
$operator
)
{
// If we are comparing to 0, we always need all results.
if
(
$count
==
0
)
{
return
true
;
}
switch
(
$operator
)
{
case
'>='
:
case
'<'
:
return
$counted
>=
$count
;
case
'>'
:
case
'<='
:
return
$counted
>
$count
;
case
'='
:
case
'!='
:
return
$counted
==
$count
;
}
});
// If the operator is <, <= or !=, we will use whereNotIn.
$not
=
in_array
(
$operator
,
[
'<'
,
'<='
,
'!='
]);
// If we are comparing to 0, we need an additional $not flip.
if
(
$count
==
0
)
{
$not
=
!
$not
;
}
// All related ids.
$relatedIds
=
array_keys
(
$relationCount
);
// Add whereIn to the query.
return
$this
->
whereIn
(
$this
->
model
->
getKeyName
(),
$relatedIds
,
$boolean
,
$not
);
}
/**
/**
* @inheritdoc
* @inheritdoc
*/
*/
...
@@ -198,14 +151,16 @@ class Builder extends EloquentBuilder
...
@@ -198,14 +151,16 @@ class Builder extends EloquentBuilder
// Convert MongoCursor results to a collection of models.
// Convert MongoCursor results to a collection of models.
if
(
$results
instanceof
Cursor
)
{
if
(
$results
instanceof
Cursor
)
{
$results
=
iterator_to_array
(
$results
,
false
);
$results
=
iterator_to_array
(
$results
,
false
);
return
$this
->
model
->
hydrate
(
$results
);
return
$this
->
model
->
hydrate
(
$results
);
}
// Convert Mongo BSONDocument to a single object.
}
// Convert Mongo BSONDocument to a single object.
elseif
(
$results
instanceof
BSONDocument
)
{
elseif
(
$results
instanceof
BSONDocument
)
{
$results
=
$results
->
getArrayCopy
();
$results
=
$results
->
getArrayCopy
();
return
$this
->
model
->
newFromBuilder
((
array
)
$results
);
return
$this
->
model
->
newFromBuilder
((
array
)
$results
);
}
// The result is a single object.
}
// The result is a single object.
elseif
(
is_array
(
$results
)
and
array_key_exists
(
'_id'
,
$results
))
{
elseif
(
is_array
(
$results
)
and
array_key_exists
(
'_id'
,
$results
))
{
return
$this
->
model
->
newFromBuilder
((
array
)
$results
);
return
$this
->
model
->
newFromBuilder
((
array
)
$results
);
}
}
return
$results
;
return
$results
;
...
...
src/Jenssegers/Mongodb/Eloquent/HybridRelations.php
View file @
266305ca
...
@@ -3,6 +3,7 @@
...
@@ -3,6 +3,7 @@
use
Illuminate\Database\Eloquent\Relations\MorphMany
;
use
Illuminate\Database\Eloquent\Relations\MorphMany
;
use
Illuminate\Database\Eloquent\Relations\MorphOne
;
use
Illuminate\Database\Eloquent\Relations\MorphOne
;
use
Illuminate\Support\Str
;
use
Illuminate\Support\Str
;
use
Jenssegers\Mongodb\Helpers\EloquentBuilder
;
use
Jenssegers\Mongodb\Relations\BelongsTo
;
use
Jenssegers\Mongodb\Relations\BelongsTo
;
use
Jenssegers\Mongodb\Relations\BelongsToMany
;
use
Jenssegers\Mongodb\Relations\BelongsToMany
;
use
Jenssegers\Mongodb\Relations\HasMany
;
use
Jenssegers\Mongodb\Relations\HasMany
;
...
@@ -265,4 +266,12 @@ trait HybridRelations
...
@@ -265,4 +266,12 @@ trait HybridRelations
return
parent
::
guessBelongsToManyRelation
();
return
parent
::
guessBelongsToManyRelation
();
}
}
/**
* @inheritdoc
*/
public
function
newEloquentBuilder
(
$query
)
{
return
new
EloquentBuilder
(
$query
);
}
}
}
src/Jenssegers/Mongodb/Helpers/EloquentBuilder.php
0 → 100644
View file @
266305ca
<?php
namespace
Jenssegers\Mongodb\Helpers
;
use
Illuminate\Database\Eloquent\Builder
;
class
EloquentBuilder
extends
Builder
{
use
QueriesRelationships
;
}
\ No newline at end of file
src/Jenssegers/Mongodb/Helpers/QueriesRelationships.php
0 → 100644
View file @
266305ca
<?php
namespace
Jenssegers\Mongodb\Helpers
;
use
Closure
;
use
Illuminate\Database\Eloquent\Relations\BelongsTo
;
use
Illuminate\Database\Eloquent\Relations\HasOneOrMany
;
use
Illuminate\Database\Eloquent\Relations\Relation
;
use
Illuminate\Database\Eloquent\Builder
as
EloquentBuilder
;
trait
QueriesRelationships
{
/**
* Add a relationship count / exists condition to the query.
*
* @param string $relation
* @param string $operator
* @param int $count
* @param string $boolean
* @param \Closure|null $callback
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public
function
has
(
$relation
,
$operator
=
'>='
,
$count
=
1
,
$boolean
=
'and'
,
Closure
$callback
=
null
)
{
if
(
strpos
(
$relation
,
'.'
)
!==
false
)
{
return
$this
->
hasNested
(
$relation
,
$operator
,
$count
,
$boolean
,
$callback
);
}
$relation
=
$this
->
getRelationWithoutConstraints
(
$relation
);
// If this is a hybrid relation then we can not use an existence query
// We need to use a `whereIn` query
if
(
$relation
->
getParent
()
->
getConnectionName
()
!==
$relation
->
getRelated
()
->
getConnectionName
())
{
return
$this
->
addHybridHas
(
$relation
,
$operator
,
$count
,
$boolean
,
$callback
);
}
// If we only need to check for the existence of the relation, then we can optimize
// the subquery to only run a "where exists" clause instead of this full "count"
// clause. This will make these queries run much faster compared with a count.
$method
=
$this
->
canUseExistsForExistenceCheck
(
$operator
,
$count
)
?
'getRelationExistenceQuery'
:
'getRelationExistenceCountQuery'
;
$hasQuery
=
$relation
->
{
$method
}(
$relation
->
getRelated
()
->
newQuery
(),
$this
);
// Next we will call any given callback as an "anonymous" scope so they can get the
// proper logical grouping of the where clauses if needed by this Eloquent query
// builder. Then, we will be ready to finalize and return this query instance.
if
(
$callback
)
{
$hasQuery
->
callScope
(
$callback
);
}
return
$this
->
addHasWhere
(
$hasQuery
,
$relation
,
$operator
,
$count
,
$boolean
);
}
/**
* Compare across databases
* @param $relation
* @param string $operator
* @param int $count
* @param string $boolean
* @param Closure|null $callback
* @return mixed
*/
public
function
addHybridHas
(
$relation
,
$operator
=
'>='
,
$count
=
1
,
$boolean
=
'and'
,
Closure
$callback
=
null
)
{
$hasQuery
=
$relation
->
getQuery
();
if
(
$callback
)
{
$hasQuery
->
callScope
(
$callback
);
}
$relations
=
$hasQuery
->
pluck
(
$this
->
getHasCompareKey
(
$relation
));
$constraintKey
=
$this
->
getRelatedConstraintKey
(
$relation
);
return
$this
->
addRelatedCountConstraint
(
$constraintKey
,
$relations
,
$operator
,
$count
,
$boolean
);
}
/**
* Returns key we are constraining this parent model's query witth
* @param $relation
* @return string
* @throws \Exception
*/
protected
function
getRelatedConstraintKey
(
$relation
)
{
if
(
$relation
instanceof
HasOneOrMany
)
{
return
$relation
->
getQualifiedParentKeyName
();
}
if
(
$relation
instanceof
BelongsTo
)
{
return
$relation
->
getForeignKey
();
}
throw
new
\Exception
(
class_basename
(
$relation
)
.
' Is Not supported for hybrid query constraints!'
);
}
/**
* @param $relation
* @return string
*/
protected
function
getHasCompareKey
(
$relation
)
{
if
(
$relation
instanceof
HasOneOrMany
)
{
return
$relation
->
getForeignKeyName
();
}
$keyMethods
=
[
'getOwnerKey'
,
'getHasCompareKey'
];
foreach
(
$keyMethods
as
$method
)
{
if
(
method_exists
(
$relation
,
$method
))
{
return
$relation
->
$method
();
}
}
}
/**
* Add the "has" condition where clause to the query.
*
* @param \Illuminate\Database\Eloquent\Builder $hasQuery
* @param \Illuminate\Database\Eloquent\Relations\Relation $relation
* @param string $operator
* @param int $count
* @param string $boolean
* @return \Illuminate\Database\Eloquent\Builder|static
*/
protected
function
addHasWhere
(
EloquentBuilder
$hasQuery
,
Relation
$relation
,
$operator
,
$count
,
$boolean
)
{
$query
=
$hasQuery
->
getQuery
();
// Get the number of related objects for each possible parent.
$relations
=
$query
->
pluck
(
$relation
->
getHasCompareKey
());
return
$this
->
addRelatedCountConstraint
(
$this
->
model
->
getKeyName
(),
$relations
,
$operator
,
$count
,
$boolean
);
}
/**
* Consta
* @param $key
* @param $relations
* @param $operator
* @param $count
* @param $boolean
* @return mixed
*/
protected
function
addRelatedCountConstraint
(
$key
,
$relations
,
$operator
,
$count
,
$boolean
)
{
$relationCount
=
array_count_values
(
array_map
(
function
(
$id
)
{
return
(
string
)
$id
;
// Convert Back ObjectIds to Strings
},
is_array
(
$relations
)
?
$relations
:
$relations
->
flatten
()
->
toArray
()));
// Remove unwanted related objects based on the operator and count.
$relationCount
=
array_filter
(
$relationCount
,
function
(
$counted
)
use
(
$count
,
$operator
)
{
// If we are comparing to 0, we always need all results.
if
(
$count
==
0
)
{
return
true
;
}
switch
(
$operator
)
{
case
'>='
:
case
'<'
:
return
$counted
>=
$count
;
case
'>'
:
case
'<='
:
return
$counted
>
$count
;
case
'='
:
case
'!='
:
return
$counted
==
$count
;
}
});
// If the operator is <, <= or !=, we will use whereNotIn.
$not
=
in_array
(
$operator
,
[
'<'
,
'<='
,
'!='
]);
// If we are comparing to 0, we need an additional $not flip.
if
(
$count
==
0
)
{
$not
=
!
$not
;
}
// All related ids.
$relatedIds
=
array_keys
(
$relationCount
);
// Add whereIn to the query.
return
$this
->
whereIn
(
$key
,
$relatedIds
,
$boolean
,
$not
);
}
}
\ No newline at end of file
src/Jenssegers/Mongodb/Relations/BelongsTo.php
View file @
266305ca
...
@@ -4,6 +4,16 @@ use Illuminate\Database\Eloquent\Builder;
...
@@ -4,6 +4,16 @@ use Illuminate\Database\Eloquent\Builder;
class
BelongsTo
extends
\Illuminate\Database\Eloquent\Relations\BelongsTo
class
BelongsTo
extends
\Illuminate\Database\Eloquent\Relations\BelongsTo
{
{
/**
* Get the key for comparing against the parent key in "has" query.
*
* @return string
*/
public
function
getHasCompareKey
()
{
return
$this
->
getOwnerKey
();
}
/**
/**
* @inheritdoc
* @inheritdoc
*/
*/
...
...
tests/
Mysql
RelationsTest.php
→
tests/
Hybrid
RelationsTest.php
View file @
266305ca
<?php
<?php
class
Mysql
RelationsTest
extends
TestCase
class
Hybrid
RelationsTest
extends
TestCase
{
{
public
function
setUp
()
public
function
setUp
()
{
{
...
@@ -74,4 +74,59 @@ class MysqlRelationsTest extends TestCase
...
@@ -74,4 +74,59 @@ class MysqlRelationsTest extends TestCase
$role
=
$user
->
mysqlRole
()
->
first
();
// refetch
$role
=
$user
->
mysqlRole
()
->
first
();
// refetch
$this
->
assertEquals
(
'John Doe'
,
$role
->
user
->
name
);
$this
->
assertEquals
(
'John Doe'
,
$role
->
user
->
name
);
}
}
public
function
testRelationConstraints
()
{
$user
=
new
MysqlUser
;
$otherUser
=
new
MysqlUser
;
$this
->
assertInstanceOf
(
'MysqlUser'
,
$user
);
$this
->
assertInstanceOf
(
'Illuminate\Database\MySqlConnection'
,
$user
->
getConnection
());
$this
->
assertInstanceOf
(
'MysqlUser'
,
$otherUser
);
$this
->
assertInstanceOf
(
'Illuminate\Database\MySqlConnection'
,
$otherUser
->
getConnection
());
//MySql User
$user
->
name
=
"John Doe"
;
$user
->
id
=
2
;
$user
->
save
();
// Other user
$otherUser
->
name
=
'Other User'
;
$otherUser
->
id
=
3
;
$otherUser
->
save
();
// Make sure they are created
$this
->
assertTrue
(
is_int
(
$user
->
id
));
$this
->
assertTrue
(
is_int
(
$otherUser
->
id
));
// Clear to start
$user
->
books
()
->
truncate
();
$otherUser
->
books
()
->
truncate
();
// Create books
$otherUser
->
books
()
->
saveMany
([
new
Book
([
'title'
=>
'Harry Plants'
]),
new
Book
([
'title'
=>
'Harveys'
]),
]);
// SQL has many
$user
->
books
()
->
saveMany
([
new
Book
([
'title'
=>
'Game of Thrones'
]),
new
Book
([
'title'
=>
'Harry Potter'
]),
new
Book
([
'title'
=>
'Harry Planter'
]),
]);
$users
=
MysqlUser
::
whereHas
(
'books'
,
function
(
$query
)
{
return
$query
->
where
(
'title'
,
'LIKE'
,
'Har%'
);
})
->
get
();
$this
->
assertEquals
(
2
,
$users
->
count
());
$users
=
MysqlUser
::
whereHas
(
'books'
,
function
(
$query
)
{
return
$query
->
where
(
'title'
,
'LIKE'
,
'Harry%'
);
},
'>='
,
2
)
->
get
();
$this
->
assertEquals
(
1
,
$users
->
count
());
$books
=
Book
::
whereHas
(
'mysqlAuthor'
,
function
(
$query
)
{
return
$query
->
where
(
'name'
,
'LIKE'
,
'Other%'
);
})
->
get
();
$this
->
assertEquals
(
2
,
$books
->
count
());
}
}
}
tests/RelationsTest.php
View file @
266305ca
...
@@ -443,6 +443,7 @@ class RelationsTest extends TestCase
...
@@ -443,6 +443,7 @@ class RelationsTest extends TestCase
Role
::
create
([
'title'
=>
'Customer'
]);
Role
::
create
([
'title'
=>
'Customer'
]);
$users
=
User
::
has
(
'role'
)
->
get
();
$users
=
User
::
has
(
'role'
)
->
get
();
$this
->
assertCount
(
2
,
$users
);
$this
->
assertCount
(
2
,
$users
);
$this
->
assertEquals
(
'John Doe'
,
$users
[
0
]
->
name
);
$this
->
assertEquals
(
'John Doe'
,
$users
[
0
]
->
name
);
$this
->
assertEquals
(
'Jane Doe'
,
$users
[
1
]
->
name
);
$this
->
assertEquals
(
'Jane Doe'
,
$users
[
1
]
->
name
);
...
...
tests/config/database.php
View file @
266305ca
...
@@ -8,15 +8,15 @@ return [
...
@@ -8,15 +8,15 @@ return [
'name'
=>
'mongodb'
,
'name'
=>
'mongodb'
,
'driver'
=>
'mongodb'
,
'driver'
=>
'mongodb'
,
'host'
=>
'127.0.0.1'
,
'host'
=>
'127.0.0.1'
,
'database'
=>
'
unittest
'
,
'database'
=>
'
mongo
'
,
],
],
'mysql'
=>
[
'mysql'
=>
[
'driver'
=>
'mysql'
,
'driver'
=>
'mysql'
,
'host'
=>
'127.0.0.1'
,
'host'
=>
'127.0.0.1'
,
'database'
=>
'
unittest
'
,
'database'
=>
'
mongo
'
,
'username'
=>
'
travis
'
,
'username'
=>
'
homestead
'
,
'password'
=>
''
,
'password'
=>
'
secret
'
,
'charset'
=>
'utf8'
,
'charset'
=>
'utf8'
,
'collation'
=>
'utf8_unicode_ci'
,
'collation'
=>
'utf8_unicode_ci'
,
'prefix'
=>
''
,
'prefix'
=>
''
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment