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