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
39f6516a
Commit
39f6516a
authored
Sep 05, 2017
by
Jens Segers
Committed by
GitHub
Sep 05, 2017
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1195 from Tucker-Eric/master
Add Hybrid `has` and `whereHas` functionality
parents
c4a5264b
ea3be777
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
399 additions
and
52 deletions
+399
-52
composer.json
composer.json
+1
-2
Builder.php
src/Jenssegers/Mongodb/Eloquent/Builder.php
+4
-49
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
+164
-0
BelongsTo.php
src/Jenssegers/Mongodb/Relations/BelongsTo.php
+10
-0
HybridRelationsTest.php
tests/HybridRelationsTest.php
+193
-0
RelationsTest.php
tests/RelationsTest.php
+1
-0
MysqlBook.php
tests/models/MysqlBook.php
+2
-1
MysqlUser.php
tests/models/MysqlUser.php
+5
-0
No files found.
composer.json
View file @
39f6516a
...
...
@@ -25,8 +25,7 @@
},
"autoload"
:
{
"psr-0"
:
{
"Jenssegers
\\
Mongodb"
:
"src/"
,
"Jenssegers
\\
Eloquent"
:
"src/"
"Jenssegers
\\
Mongodb"
:
"src/"
}
},
"autoload-dev"
:
{
...
...
src/Jenssegers/Mongodb/Eloquent/Builder.php
View file @
39f6516a
<?php
namespace
Jenssegers\Mongodb\Eloquent
;
use
Illuminate\Database\Eloquent\Builder
as
EloquentBuilder
;
use
Illuminate\Database\Eloquent\Relations\Relation
;
use
Jenssegers\Mongodb\Helpers\QueriesRelationships
;
use
MongoDB\Driver\Cursor
;
use
MongoDB\Model\BSONDocument
;
class
Builder
extends
EloquentBuilder
{
use
QueriesRelationships
;
/**
* The methods that should be returned from query builder.
*
...
...
@@ -139,54 +140,6 @@ class Builder extends EloquentBuilder
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
*/
...
...
@@ -198,10 +151,12 @@ class Builder extends EloquentBuilder
// Convert MongoCursor results to a collection of models.
if
(
$results
instanceof
Cursor
)
{
$results
=
iterator_to_array
(
$results
,
false
);
return
$this
->
model
->
hydrate
(
$results
);
}
// Convert Mongo BSONDocument to a single object.
elseif
(
$results
instanceof
BSONDocument
)
{
$results
=
$results
->
getArrayCopy
();
return
$this
->
model
->
newFromBuilder
((
array
)
$results
);
}
// The result is a single object.
elseif
(
is_array
(
$results
)
and
array_key_exists
(
'_id'
,
$results
))
{
...
...
src/Jenssegers/Mongodb/Eloquent/HybridRelations.php
View file @
39f6516a
...
...
@@ -3,6 +3,7 @@
use
Illuminate\Database\Eloquent\Relations\MorphMany
;
use
Illuminate\Database\Eloquent\Relations\MorphOne
;
use
Illuminate\Support\Str
;
use
Jenssegers\Mongodb\Helpers\EloquentBuilder
;
use
Jenssegers\Mongodb\Relations\BelongsTo
;
use
Jenssegers\Mongodb\Relations\BelongsToMany
;
use
Jenssegers\Mongodb\Relations\HasMany
;
...
...
@@ -265,4 +266,12 @@ trait HybridRelations
return
parent
::
guessBelongsToManyRelation
();
}
/**
* @inheritdoc
*/
public
function
newEloquentBuilder
(
$query
)
{
return
new
EloquentBuilder
(
$query
);
}
}
src/Jenssegers/Mongodb/Helpers/EloquentBuilder.php
0 → 100644
View file @
39f6516a
<?php
namespace
Jenssegers\Mongodb\Helpers
;
use
Illuminate\Database\Eloquent\Builder
;
class
EloquentBuilder
extends
Builder
{
use
QueriesRelationships
;
}
src/Jenssegers/Mongodb/Helpers/QueriesRelationships.php
0 → 100644
View file @
39f6516a
<?php
namespace
Jenssegers\Mongodb\Helpers
;
use
Closure
;
use
Illuminate\Database\Eloquent\Relations\BelongsTo
;
use
Illuminate\Database\Eloquent\Relations\HasOneOrMany
;
use
Jenssegers\Mongodb\Eloquent\Model
;
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 a normal whereExists() query that relies on a subquery
// We need to use a `whereIn` query
if
(
$this
->
getModel
()
instanceof
Model
||
$this
->
isAcrossConnections
(
$relation
))
{
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
);
}
/**
* @param $relation
* @return bool
*/
protected
function
isAcrossConnections
(
$relation
)
{
return
$relation
->
getParent
()
->
getConnectionName
()
!==
$relation
->
getRelated
()
->
getConnectionName
();
}
/**
* Compare across databases
* @param $relation
* @param string $operator
* @param int $count
* @param string $boolean
* @param Closure|null $callback
* @return mixed
* @throws \Exception
*/
public
function
addHybridHas
(
$relation
,
$operator
=
'>='
,
$count
=
1
,
$boolean
=
'and'
,
Closure
$callback
=
null
)
{
$hasQuery
=
$relation
->
getQuery
();
if
(
$callback
)
{
$hasQuery
->
callScope
(
$callback
);
}
// 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
;
}
$relations
=
$hasQuery
->
pluck
(
$this
->
getHasCompareKey
(
$relation
));
$relatedIds
=
$this
->
getConstrainedRelatedIds
(
$relations
,
$operator
,
$count
);
return
$this
->
whereIn
(
$this
->
getRelatedConstraintKey
(
$relation
),
$relatedIds
,
$boolean
,
$not
);
}
/**
* Returns key we are constraining this parent model's query with
* @param $relation
* @return string
* @throws \Exception
*/
protected
function
getRelatedConstraintKey
(
$relation
)
{
if
(
$relation
instanceof
HasOneOrMany
)
{
return
$this
->
model
->
getKeyName
();
}
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
(
method_exists
(
$relation
,
'getHasCompareKey'
))
{
return
$relation
->
getHasCompareKey
();
}
return
$relation
instanceof
HasOneOrMany
?
$relation
->
getForeignKeyName
()
:
$relation
->
getOwnerKey
();
}
/**
* @param $relations
* @param $operator
* @param $count
* @return array
*/
protected
function
getConstrainedRelatedIds
(
$relations
,
$operator
,
$count
)
{
$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
;
}
});
// All related ids.
return
array_keys
(
$relationCount
);
}
}
src/Jenssegers/Mongodb/Relations/BelongsTo.php
View file @
39f6516a
...
...
@@ -4,6 +4,16 @@ use Illuminate\Database\Eloquent\Builder;
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
*/
...
...
tests/
Mysql
RelationsTest.php
→
tests/
Hybrid
RelationsTest.php
View file @
39f6516a
<?php
class
Mysql
RelationsTest
extends
TestCase
class
Hybrid
RelationsTest
extends
TestCase
{
public
function
setUp
()
{
...
...
@@ -74,4 +74,120 @@ class MysqlRelationsTest extends TestCase
$role
=
$user
->
mysqlRole
()
->
first
();
// refetch
$this
->
assertEquals
(
'John Doe'
,
$role
->
user
->
name
);
}
public
function
testHybridWhereHas
()
{
$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
());
}
public
function
testHybridWith
()
{
$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
Book
::
truncate
();
MysqlBook
::
truncate
();
// Create books
// Mysql relation
$user
->
mysqlBooks
()
->
saveMany
([
new
MysqlBook
([
'title'
=>
'Game of Thrones'
]),
new
MysqlBook
([
'title'
=>
'Harry Potter'
]),
]);
$otherUser
->
mysqlBooks
()
->
saveMany
([
new
MysqlBook
([
'title'
=>
'Harry Plants'
]),
new
MysqlBook
([
'title'
=>
'Harveys'
]),
new
MysqlBook
([
'title'
=>
'Harry Planter'
]),
]);
// SQL has many Hybrid
$user
->
books
()
->
saveMany
([
new
Book
([
'title'
=>
'Game of Thrones'
]),
new
Book
([
'title'
=>
'Harry Potter'
]),
]);
$otherUser
->
books
()
->
saveMany
([
new
Book
([
'title'
=>
'Harry Plants'
]),
new
Book
([
'title'
=>
'Harveys'
]),
new
Book
([
'title'
=>
'Harry Planter'
]),
]);
MysqlUser
::
with
(
'books'
)
->
get
()
->
each
(
function
(
$user
)
{
$this
->
assertEquals
(
$user
->
id
,
$user
->
books
->
count
());
});
MysqlUser
::
whereHas
(
'mysqlBooks'
,
function
(
$query
)
{
return
$query
->
where
(
'title'
,
'LIKE'
,
'Harry%'
);
})
->
with
(
'books'
)
->
get
()
->
each
(
function
(
$user
)
{
$this
->
assertEquals
(
$user
->
id
,
$user
->
books
->
count
());
});
}
}
tests/RelationsTest.php
View file @
39f6516a
...
...
@@ -443,6 +443,7 @@ class RelationsTest extends TestCase
Role
::
create
([
'title'
=>
'Customer'
]);
$users
=
User
::
has
(
'role'
)
->
get
();
$this
->
assertCount
(
2
,
$users
);
$this
->
assertEquals
(
'John Doe'
,
$users
[
0
]
->
name
);
$this
->
assertEquals
(
'Jane Doe'
,
$users
[
1
]
->
name
);
...
...
tests/models/MysqlBook.php
View file @
39f6516a
...
...
@@ -27,7 +27,8 @@ class MysqlBook extends Eloquent
if
(
!
$schema
->
hasTable
(
'books'
))
{
Schema
::
connection
(
'mysql'
)
->
create
(
'books'
,
function
(
$table
)
{
$table
->
string
(
'title'
);
$table
->
string
(
'author_id'
);
$table
->
string
(
'author_id'
)
->
nullable
();
$table
->
integer
(
'mysql_user_id'
)
->
unsigned
()
->
nullable
();
$table
->
timestamps
();
});
}
...
...
tests/models/MysqlUser.php
View file @
39f6516a
...
...
@@ -21,6 +21,11 @@ class MysqlUser extends Eloquent
return
$this
->
hasOne
(
'Role'
);
}
public
function
mysqlBooks
()
{
return
$this
->
hasMany
(
MysqlBook
::
class
);
}
/**
* Check if we need to run the schema.
*/
...
...
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