1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace Doctrine\ODM\MongoDB\Aggregation\Stage; |
6
|
|
|
|
7
|
|
|
use Doctrine\ODM\MongoDB\Aggregation\Builder; |
8
|
|
|
use GeoJson\Geometry\Point; |
9
|
|
|
use function is_array; |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* Fluent interface for adding a $geoNear stage to an aggregation pipeline. |
13
|
|
|
* |
14
|
|
|
*/ |
15
|
|
|
class GeoNear extends Match |
16
|
|
|
{ |
17
|
|
|
/** @var string */ |
18
|
|
|
private $distanceField; |
19
|
|
|
|
20
|
|
|
/** @var float */ |
21
|
|
|
private $distanceMultiplier; |
22
|
|
|
|
23
|
|
|
/** @var string */ |
24
|
|
|
private $includeLocs; |
25
|
|
|
|
26
|
|
|
/** @var float */ |
27
|
|
|
private $maxDistance; |
28
|
|
|
|
29
|
|
|
/** @var float */ |
30
|
|
|
private $minDistance; |
31
|
|
|
|
32
|
|
|
/** @var array */ |
33
|
|
|
private $near; |
34
|
|
|
|
35
|
|
|
/** @var int */ |
36
|
|
|
private $num; |
37
|
|
|
|
38
|
|
|
/** @var bool */ |
39
|
|
|
private $spherical = false; |
40
|
|
|
|
41
|
|
|
/** @var bool */ |
42
|
|
|
private $uniqueDocs; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* @param float|array|Point $x |
46
|
|
|
* @param float $y |
47
|
|
|
*/ |
48
|
11 |
|
public function __construct(Builder $builder, $x, $y = null) |
49
|
|
|
{ |
50
|
11 |
|
parent::__construct($builder); |
51
|
|
|
|
52
|
11 |
|
$this->near($x, $y); |
53
|
11 |
|
} |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* {@inheritdoc} |
57
|
|
|
*/ |
58
|
11 |
|
public function getExpression() |
59
|
|
|
{ |
60
|
|
|
$geoNear = [ |
61
|
11 |
|
'near' => $this->near, |
62
|
11 |
|
'spherical' => $this->spherical, |
63
|
11 |
|
'distanceField' => $this->distanceField, |
64
|
11 |
|
'query' => $this->query->getQuery(), |
65
|
11 |
|
'distanceMultiplier' => $this->distanceMultiplier, |
66
|
11 |
|
'includeLocs' => $this->includeLocs, |
67
|
11 |
|
'maxDistance' => $this->maxDistance, |
68
|
11 |
|
'minDistance' => $this->minDistance, |
69
|
11 |
|
'num' => $this->num, |
70
|
11 |
|
'uniqueDocs' => $this->uniqueDocs, |
71
|
|
|
]; |
72
|
|
|
|
73
|
11 |
|
foreach (['distanceMultiplier', 'includeLocs', 'maxDistance', 'minDistance', 'num', 'uniqueDocs'] as $option) { |
74
|
11 |
|
if ($geoNear[$option]) { |
75
|
8 |
|
continue; |
76
|
|
|
} |
77
|
|
|
|
78
|
11 |
|
unset($geoNear[$option]); |
79
|
|
|
} |
80
|
|
|
|
81
|
11 |
|
return ['$geoNear' => $geoNear]; |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* The output field that contains the calculated distance. To specify a field within an embedded document, use dot notation. |
86
|
|
|
* |
87
|
|
|
* @param string $distanceField |
88
|
|
|
* @return $this |
89
|
|
|
*/ |
90
|
3 |
|
public function distanceField($distanceField) |
91
|
|
|
{ |
92
|
3 |
|
$this->distanceField = (string) $distanceField; |
93
|
|
|
|
94
|
3 |
|
return $this; |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* The factor to multiply all distances returned by the query. |
99
|
|
|
* |
100
|
|
|
* @param float $distanceMultiplier |
101
|
|
|
* @return $this |
102
|
|
|
*/ |
103
|
1 |
|
public function distanceMultiplier($distanceMultiplier) |
104
|
|
|
{ |
105
|
1 |
|
$this->distanceMultiplier = (float) $distanceMultiplier; |
106
|
|
|
|
107
|
1 |
|
return $this; |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* This specifies the output field that identifies the location used to calculate the distance. |
112
|
|
|
* |
113
|
|
|
* @param string $includeLocs |
114
|
|
|
* @return $this |
115
|
|
|
*/ |
116
|
1 |
|
public function includeLocs($includeLocs) |
117
|
|
|
{ |
118
|
1 |
|
$this->includeLocs = (string) $includeLocs; |
119
|
|
|
|
120
|
1 |
|
return $this; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* The maximum number of documents to return. |
125
|
|
|
* |
126
|
|
|
* @param int $limit |
127
|
|
|
* @return $this |
128
|
|
|
*/ |
129
|
2 |
|
public function limit($limit) |
130
|
|
|
{ |
131
|
2 |
|
return $this->num($limit); |
|
|
|
|
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* The maximum distance from the center point that the documents can be. |
136
|
|
|
* |
137
|
|
|
* @param float $maxDistance |
138
|
|
|
* @return $this |
139
|
|
|
*/ |
140
|
1 |
|
public function maxDistance($maxDistance) |
141
|
|
|
{ |
142
|
1 |
|
$this->maxDistance = (float) $maxDistance; |
143
|
|
|
|
144
|
1 |
|
return $this; |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* The minimum distance from the center point that the documents can be. |
149
|
|
|
* |
150
|
|
|
* @param float $minDistance |
151
|
|
|
* @return $this |
152
|
|
|
* |
153
|
|
|
*/ |
154
|
1 |
|
public function minDistance($minDistance) |
155
|
|
|
{ |
156
|
1 |
|
$this->minDistance = (float) $minDistance; |
157
|
|
|
|
158
|
1 |
|
return $this; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* The point for which to find the closest documents. |
163
|
|
|
* |
164
|
|
|
* A GeoJSON point may be provided as the first and only argument for |
165
|
|
|
* 2dsphere queries. This single parameter may be a GeoJSON point object or |
166
|
|
|
* an array corresponding to the point's JSON representation. If GeoJSON is |
167
|
|
|
* used, the "spherical" option will default to true. |
168
|
|
|
* |
169
|
|
|
* @param float|array|Point $x |
170
|
|
|
* @param float $y |
171
|
|
|
* @return $this |
172
|
|
|
*/ |
173
|
11 |
|
public function near($x, $y = null) |
174
|
|
|
{ |
175
|
11 |
|
if ($x instanceof Point) { |
|
|
|
|
176
|
|
|
$x = $x->jsonSerialize(); |
177
|
|
|
} |
178
|
|
|
|
179
|
11 |
|
$this->near = is_array($x) ? $x : [$x, $y]; |
180
|
11 |
|
$this->spherical = is_array($x) && isset($x['type']); |
181
|
|
|
|
182
|
11 |
|
return $this; |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
/** |
186
|
|
|
* The maximum number of documents to return. |
187
|
|
|
* |
188
|
|
|
* @param int $num |
189
|
|
|
* @return $this |
190
|
|
|
*/ |
191
|
3 |
|
public function num($num) |
192
|
|
|
{ |
193
|
3 |
|
$this->num = (int) $num; |
194
|
|
|
|
195
|
3 |
|
return $this; |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* Required if using a 2dsphere index. Determines how MongoDB calculates the distance. |
200
|
|
|
* |
201
|
|
|
* @param bool $spherical |
202
|
|
|
* @return $this |
203
|
|
|
*/ |
204
|
|
|
public function spherical($spherical = true) |
205
|
|
|
{ |
206
|
|
|
$this->spherical = (bool) $spherical; |
207
|
|
|
|
208
|
|
|
return $this; |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
/** |
212
|
|
|
* If this value is true, the query returns a matching document once, even if more than one of the document’s location fields match the query. |
213
|
|
|
* |
214
|
|
|
* @param bool $uniqueDocs |
215
|
|
|
* @return $this |
216
|
|
|
*/ |
217
|
1 |
|
public function uniqueDocs($uniqueDocs = true) |
218
|
|
|
{ |
219
|
1 |
|
$this->uniqueDocs = (bool) $uniqueDocs; |
220
|
|
|
|
221
|
1 |
|
return $this; |
222
|
|
|
} |
223
|
|
|
} |
224
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.