1
+ <?php
2
+
3
+ namespace Jenssegers \Mongodb \Helpers ;
4
+
5
+ use Closure ;
6
+ use Illuminate \Database \Eloquent \Relations \BelongsTo ;
7
+ use Illuminate \Database \Eloquent \Relations \HasOneOrMany ;
8
+ use Illuminate \Database \Eloquent \Relations \Relation ;
9
+ use Illuminate \Database \Eloquent \Builder as EloquentBuilder ;
10
+
11
+ trait QueriesRelationships
12
+ {
13
+ /**
14
+ * Add a relationship count / exists condition to the query.
15
+ *
16
+ * @param string $relation
17
+ * @param string $operator
18
+ * @param int $count
19
+ * @param string $boolean
20
+ * @param \Closure|null $callback
21
+ * @return \Illuminate\Database\Eloquent\Builder|static
22
+ */
23
+ public function has ($ relation , $ operator = '>= ' , $ count = 1 , $ boolean = 'and ' , Closure $ callback = null )
24
+ {
25
+ if (strpos ($ relation , '. ' ) !== false ) {
26
+ return $ this ->hasNested ($ relation , $ operator , $ count , $ boolean , $ callback );
27
+ }
28
+
29
+ $ relation = $ this ->getRelationWithoutConstraints ($ relation );
30
+
31
+ // If this is a hybrid relation then we can not use an existence query
32
+ // We need to use a `whereIn` query
33
+ if ($ relation ->getParent ()->getConnectionName () !== $ relation ->getRelated ()->getConnectionName ()) {
34
+ return $ this ->addHybridHas ($ relation , $ operator , $ count , $ boolean , $ callback );
35
+ }
36
+
37
+ // If we only need to check for the existence of the relation, then we can optimize
38
+ // the subquery to only run a "where exists" clause instead of this full "count"
39
+ // clause. This will make these queries run much faster compared with a count.
40
+ $ method = $ this ->canUseExistsForExistenceCheck ($ operator , $ count )
41
+ ? 'getRelationExistenceQuery '
42
+ : 'getRelationExistenceCountQuery ' ;
43
+
44
+ $ hasQuery = $ relation ->{$ method }(
45
+ $ relation ->getRelated ()->newQuery (), $ this
46
+ );
47
+
48
+ // Next we will call any given callback as an "anonymous" scope so they can get the
49
+ // proper logical grouping of the where clauses if needed by this Eloquent query
50
+ // builder. Then, we will be ready to finalize and return this query instance.
51
+ if ($ callback ) {
52
+ $ hasQuery ->callScope ($ callback );
53
+ }
54
+
55
+ return $ this ->addHasWhere (
56
+ $ hasQuery , $ relation , $ operator , $ count , $ boolean
57
+ );
58
+ }
59
+
60
+ /**
61
+ * Compare across databases
62
+ * @param $relation
63
+ * @param string $operator
64
+ * @param int $count
65
+ * @param string $boolean
66
+ * @param Closure|null $callback
67
+ * @return mixed
68
+ */
69
+ public function addHybridHas ($ relation , $ operator = '>= ' , $ count = 1 , $ boolean = 'and ' , Closure $ callback = null )
70
+ {
71
+ $ hasQuery = $ relation ->getQuery ();
72
+ if ($ callback ) {
73
+ $ hasQuery ->callScope ($ callback );
74
+ }
75
+
76
+ $ relations = $ hasQuery ->pluck ($ this ->getHasCompareKey ($ relation ));
77
+ $ constraintKey = $ this ->getRelatedConstraintKey ($ relation );
78
+
79
+ return $ this ->addRelatedCountConstraint ($ constraintKey , $ relations , $ operator , $ count , $ boolean );
80
+ }
81
+
82
+
83
+ /**
84
+ * Returns key we are constraining this parent model's query witth
85
+ * @param $relation
86
+ * @return string
87
+ * @throws \Exception
88
+ */
89
+ protected function getRelatedConstraintKey ($ relation )
90
+ {
91
+ if ($ relation instanceof HasOneOrMany) {
92
+ return $ relation ->getQualifiedParentKeyName ();
93
+ }
94
+
95
+ if ($ relation instanceof BelongsTo) {
96
+ return $ relation ->getForeignKey ();
97
+ }
98
+
99
+ throw new \Exception (class_basename ($ relation ).' Is Not supported for hybrid query constraints! ' );
100
+ }
101
+
102
+ /**
103
+ * @param $relation
104
+ * @return string
105
+ */
106
+ protected function getHasCompareKey ($ relation )
107
+ {
108
+ if ($ relation instanceof HasOneOrMany) {
109
+ return $ relation ->getForeignKeyName ();
110
+ }
111
+
112
+ $ keyMethods = ['getOwnerKey ' , 'getHasCompareKey ' ];
113
+ foreach ($ keyMethods as $ method ) {
114
+ if (method_exists ($ relation , $ method )) {
115
+ return $ relation ->$ method ();
116
+ }
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Add the "has" condition where clause to the query.
122
+ *
123
+ * @param \Illuminate\Database\Eloquent\Builder $hasQuery
124
+ * @param \Illuminate\Database\Eloquent\Relations\Relation $relation
125
+ * @param string $operator
126
+ * @param int $count
127
+ * @param string $boolean
128
+ * @return \Illuminate\Database\Eloquent\Builder|static
129
+ */
130
+ protected function addHasWhere (EloquentBuilder $ hasQuery , Relation $ relation , $ operator , $ count , $ boolean )
131
+ {
132
+ $ query = $ hasQuery ->getQuery ();
133
+ // Get the number of related objects for each possible parent.
134
+ $ relations = $ query ->pluck ($ relation ->getHasCompareKey ());
135
+
136
+ return $ this ->addRelatedCountConstraint ($ this ->model ->getKeyName (), $ relations , $ operator , $ count , $ boolean );
137
+ }
138
+
139
+ /**
140
+ * Consta
141
+ * @param $key
142
+ * @param $relations
143
+ * @param $operator
144
+ * @param $count
145
+ * @param $boolean
146
+ * @return mixed
147
+ */
148
+ protected function addRelatedCountConstraint ($ key , $ relations , $ operator , $ count , $ boolean )
149
+ {
150
+ $ relationCount = array_count_values (array_map (function ($ id ) {
151
+ return (string )$ id ; // Convert Back ObjectIds to Strings
152
+ }, is_array ($ relations ) ? $ relations : $ relations ->flatten ()->toArray ()));
153
+ // Remove unwanted related objects based on the operator and count.
154
+ $ relationCount = array_filter ($ relationCount , function ($ counted ) use ($ count , $ operator ) {
155
+ // If we are comparing to 0, we always need all results.
156
+ if ($ count == 0 ) {
157
+ return true ;
158
+ }
159
+ switch ($ operator ) {
160
+ case '>= ' :
161
+ case '< ' :
162
+ return $ counted >= $ count ;
163
+ case '> ' :
164
+ case '<= ' :
165
+ return $ counted > $ count ;
166
+ case '= ' :
167
+ case '!= ' :
168
+ return $ counted == $ count ;
169
+ }
170
+ });
171
+
172
+ // If the operator is <, <= or !=, we will use whereNotIn.
173
+ $ not = in_array ($ operator , ['< ' , '<= ' , '!= ' ]);
174
+ // If we are comparing to 0, we need an additional $not flip.
175
+ if ($ count == 0 ) {
176
+ $ not = ! $ not ;
177
+ }
178
+ // All related ids.
179
+ $ relatedIds = array_keys ($ relationCount );
180
+
181
+ // Add whereIn to the query.
182
+ return $ this ->whereIn ($ key , $ relatedIds , $ boolean , $ not );
183
+ }
184
+ }
0 commit comments