1<?php
2
3namespace Elastica\Query;
4
5use Elastica\Script\AbstractScript;
6
7/**
8 * Class FunctionScore.
9 *
10 * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html
11 */
12class FunctionScore extends AbstractQuery
13{
14    const BOOST_MODE_MULTIPLY = 'multiply';
15    const BOOST_MODE_REPLACE = 'replace';
16    const BOOST_MODE_SUM = 'sum';
17    const BOOST_MODE_AVERAGE = 'avg';
18    const BOOST_MODE_MAX = 'max';
19    const BOOST_MODE_MIN = 'min';
20
21    const SCORE_MODE_MULTIPLY = 'multiply';
22    const SCORE_MODE_SUM = 'sum';
23    const SCORE_MODE_AVERAGE = 'avg';
24    const SCORE_MODE_FIRST = 'first';
25    const SCORE_MODE_MAX = 'max';
26    const SCORE_MODE_MIN = 'min';
27
28    const DECAY_GAUSS = 'gauss';
29    const DECAY_EXPONENTIAL = 'exp';
30    const DECAY_LINEAR = 'linear';
31
32    const FIELD_VALUE_FACTOR_MODIFIER_NONE = 'none';
33    const FIELD_VALUE_FACTOR_MODIFIER_LOG = 'log';
34    const FIELD_VALUE_FACTOR_MODIFIER_LOG1P = 'log1p';
35    const FIELD_VALUE_FACTOR_MODIFIER_LOG2P = 'log2p';
36    const FIELD_VALUE_FACTOR_MODIFIER_LN = 'ln';
37    const FIELD_VALUE_FACTOR_MODIFIER_LN1P = 'ln1p';
38    const FIELD_VALUE_FACTOR_MODIFIER_LN2P = 'ln2p';
39    const FIELD_VALUE_FACTOR_MODIFIER_SQUARE = 'square';
40    const FIELD_VALUE_FACTOR_MODIFIER_SQRT = 'sqrt';
41    const FIELD_VALUE_FACTOR_MODIFIER_RECIPROCAL = 'reciprocal';
42
43    const MULTI_VALUE_MODE_MIN = 'min';
44    const MULTI_VALUE_MODE_MAX = 'max';
45    const MULTI_VALUE_MODE_AVG = 'avg';
46    const MULTI_VALUE_MODE_SUM = 'sum';
47
48    const RANDOM_SCORE_FIELD_ID = '_id';
49    const RANDOM_SCORE_FIELD_SEQ_NO = '_seq_no';
50
51    protected $_functions = [];
52
53    /**
54     * Set the child query for this function_score query.
55     *
56     * @param AbstractQuery $query
57     *
58     * @return $this
59     */
60    public function setQuery(AbstractQuery $query): self
61    {
62        return $this->setParam('query', $query);
63    }
64
65    /**
66     * Add a function to the function_score query.
67     *
68     * @param string                     $functionType   valid values are DECAY_* constants and script_score
69     * @param array|float|AbstractScript $functionParams the body of the function. See documentation for proper syntax.
70     * @param AbstractQuery              $filter         filter to apply to the function
71     * @param float                      $weight         function weight
72     *
73     * @return $this
74     */
75    public function addFunction(
76        string $functionType,
77        $functionParams,
78        AbstractQuery $filter = null,
79        float $weight = null
80    ): self {
81        $function = [
82            $functionType => $functionParams,
83        ];
84
85        if (null !== $filter) {
86            $function['filter'] = $filter;
87        }
88
89        if (null !== $weight) {
90            $function['weight'] = $weight;
91        }
92
93        $this->_functions[] = $function;
94
95        return $this;
96    }
97
98    /**
99     * Add a script_score function to the query.
100     *
101     * @param AbstractScript $script a Script object
102     * @param AbstractQuery  $filter an optional filter to apply to the function
103     * @param float          $weight the weight of the function
104     *
105     * @return $this
106     */
107    public function addScriptScoreFunction(AbstractScript $script, AbstractQuery $filter = null, float $weight = null)
108    {
109        return $this->addFunction('script_score', $script, $filter, $weight);
110    }
111
112    /**
113     * Add a decay function to the query.
114     *
115     * @param string        $function       see DECAY_* constants for valid options
116     * @param string        $field          the document field on which to perform the decay function
117     * @param string        $origin         the origin value for this decay function
118     * @param string        $scale          a scale to define the rate of decay for this function
119     * @param string        $offset         If defined, this function will only be computed for documents with a distance from the origin greater than this value
120     * @param float         $decay          optionally defines how documents are scored at the distance given by the $scale parameter
121     * @param float         $weight         optional factor by which to multiply the score at the value provided by the $scale parameter
122     * @param AbstractQuery $filter         a filter associated with this function
123     * @param string        $multiValueMode see MULTI_VALUE_MODE_* constants for valid options
124     *
125     * @return $this
126     */
127    public function addDecayFunction(
128        string $function,
129        string $field,
130        string $origin,
131        string $scale,
132        string $offset = null,
133        float $decay = null,
134        float $weight = null,
135        AbstractQuery $filter = null,
136        string $multiValueMode = null
137    ) {
138        $functionParams = [
139            $field => [
140                'origin' => $origin,
141                'scale' => $scale,
142            ],
143        ];
144        if (null !== $offset) {
145            $functionParams[$field]['offset'] = $offset;
146        }
147        if (null !== $decay) {
148            $functionParams[$field]['decay'] = $decay;
149        }
150
151        if (null !== $multiValueMode) {
152            $functionParams['multi_value_mode'] = $multiValueMode;
153        }
154
155        return $this->addFunction($function, $functionParams, $filter, $weight);
156    }
157
158    /**
159     * @param string             $field
160     * @param float|null         $factor
161     * @param string|null        $modifier
162     * @param float|null         $missing
163     * @param float|null         $weight
164     * @param AbstractQuery|null $filter
165     *
166     * @return $this
167     */
168    public function addFieldValueFactorFunction(
169        string $field,
170        float $factor = null,
171        string $modifier = null,
172        float $missing = null,
173        float $weight = null,
174        AbstractQuery $filter = null
175    ): self {
176        $functionParams = [
177            'field' => $field,
178        ];
179
180        if (null !== $factor) {
181            $functionParams['factor'] = $factor;
182        }
183
184        if (null !== $modifier) {
185            $functionParams['modifier'] = $modifier;
186        }
187
188        if (null !== $missing) {
189            $functionParams['missing'] = $missing;
190        }
191
192        return $this->addFunction('field_value_factor', $functionParams, $filter, $weight);
193    }
194
195    /**
196     * @param float         $weight the weight of the function
197     * @param AbstractQuery $filter a filter associated with this function
198     *
199     * @return $this
200     */
201    public function addWeightFunction(float $weight, AbstractQuery $filter = null): self
202    {
203        return $this->addFunction('weight', $weight, $filter);
204    }
205
206    /**
207     * Add a random_score function to the query.
208     *
209     * @param int           $seed   the seed value
210     * @param AbstractQuery $filter a filter associated with this function
211     * @param float         $weight an optional boost value associated with this function
212     * @param string        $field  the field to be used for random number generation
213     *
214     * @return $this
215     */
216    public function addRandomScoreFunction(
217        int $seed,
218        AbstractQuery $filter = null,
219        float $weight = null,
220        string $field = null
221    ): self {
222        $functionParams = [
223            'seed' => $seed,
224        ];
225
226        if (null !== $field) {
227            $functionParams['field'] = $field;
228        }
229
230        return $this->addFunction('random_score', $functionParams, $filter, $weight);
231    }
232
233    /**
234     * Set an overall boost value for this query.
235     *
236     * @param float $boost
237     *
238     * @return $this
239     */
240    public function setBoost(float $boost): self
241    {
242        return $this->setParam('boost', $boost);
243    }
244
245    /**
246     * Restrict the combined boost of the function_score query and its child query.
247     *
248     * @param float $maxBoost
249     *
250     * @return $this
251     */
252    public function setMaxBoost(float $maxBoost): self
253    {
254        return $this->setParam('max_boost', $maxBoost);
255    }
256
257    /**
258     * The boost mode determines how the score of this query is combined with that of the child query.
259     *
260     * @param string $mode see BOOST_MODE_* constants for valid options. Default is multiply.
261     *
262     * @return $this
263     */
264    public function setBoostMode(string $mode = self::BOOST_MODE_MULTIPLY): self
265    {
266        return $this->setParam('boost_mode', $mode);
267    }
268
269    /**
270     * If set, this query will return results in random order.
271     *
272     * @param int $seed set a seed value to return results in the same random order for consistent pagination
273     *
274     * @return $this
275     */
276    public function setRandomScore(int $seed = null): self
277    {
278        $seedParam = new \stdClass();
279        if (null !== $seed) {
280            $seedParam->seed = $seed;
281        }
282
283        return $this->setParam('random_score', $seedParam);
284    }
285
286    /**
287     * Set the score method.
288     *
289     * @param string $mode see SCORE_MODE_* constants for valid options. Default is multiply.
290     *
291     * @return $this
292     */
293    public function setScoreMode(string $mode = self::SCORE_MODE_MULTIPLY): self
294    {
295        return $this->setParam('score_mode', $mode);
296    }
297
298    /**
299     * Set min_score option.
300     *
301     * @param float $minScore
302     *
303     * @return $this
304     */
305    public function setMinScore(float $minScore): self
306    {
307        return $this->setParam('min_score', $minScore);
308    }
309
310    /**
311     * {@inheritdoc}
312     */
313    public function toArray(): array
314    {
315        if (0 < \count($this->_functions)) {
316            $this->setParam('functions', $this->_functions);
317        }
318
319        return parent::toArray();
320    }
321}
322