Automattic /
jetpack
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | |||
| 3 | /** |
||
| 4 | * Provides an interface for easily building a complex search query that |
||
| 5 | * combines multiple ranking signals. |
||
| 6 | * |
||
| 7 | * |
||
| 8 | * $bldr = new Jetpack_WPES_Query_Builder(); |
||
| 9 | * $bldr->add_filter( ... ); |
||
| 10 | * $bldr->add_filter( ... ); |
||
| 11 | * $bldr->add_query( ... ); |
||
| 12 | * $es_query = $bldr->build_query(); |
||
| 13 | * |
||
| 14 | * |
||
| 15 | * All ES queries take a standard form with main query (with some filters), |
||
| 16 | * wrapped in a function_score |
||
| 17 | * |
||
| 18 | * Most functions are chainable, e.g. $bldr->add_filter( ... )->add_query( ... )->build_query(); |
||
| 19 | * |
||
| 20 | * Bucketed queries use an aggregation to diversify results. eg a bunch |
||
| 21 | * of separate filters where to get different sets of results. |
||
| 22 | * |
||
| 23 | */ |
||
| 24 | |||
| 25 | class Jetpack_WPES_Query_Builder { |
||
| 26 | |||
| 27 | protected $es_filters = array(); |
||
| 28 | |||
| 29 | // Custom boosting with function_score |
||
| 30 | protected $functions = array(); |
||
| 31 | protected $weighting_functions = array(); |
||
| 32 | protected $decays = array(); |
||
| 33 | protected $scripts = array(); |
||
| 34 | protected $functions_max_boost = 2.0; |
||
| 35 | protected $functions_score_mode = 'multiply'; |
||
| 36 | protected $functions_boost_mode = 'multiply'; |
||
| 37 | protected $query_bool_boost = null; |
||
| 38 | |||
| 39 | // General aggregations for buckets and metrics |
||
| 40 | protected $aggs_query = false; |
||
| 41 | protected $aggs = array(); |
||
| 42 | |||
| 43 | // The set of top level text queries to combine |
||
| 44 | protected $must_queries = array(); |
||
| 45 | protected $should_queries = array(); |
||
| 46 | protected $dis_max_queries = array(); |
||
| 47 | |||
| 48 | protected $diverse_buckets_query = false; |
||
| 49 | protected $bucket_filters = array(); |
||
| 50 | protected $bucket_sub_aggs = array(); |
||
| 51 | |||
| 52 | public function get_langs() { |
||
| 53 | if ( isset( $this->langs ) ) { |
||
| 54 | return $this->langs; |
||
|
0 ignored issues
–
show
|
|||
| 55 | } |
||
| 56 | return false; |
||
| 57 | } |
||
| 58 | |||
| 59 | //////////////////////////////////// |
||
| 60 | // Methods for building a query |
||
| 61 | |||
| 62 | public function add_filter( $filter ) { |
||
| 63 | $this->es_filters[] = $filter; |
||
| 64 | |||
| 65 | return $this; |
||
| 66 | } |
||
| 67 | |||
| 68 | public function add_query( $query, $type = 'must' ) { |
||
| 69 | switch ( $type ) { |
||
| 70 | case 'dis_max': |
||
| 71 | $this->dis_max_queries[] = $query; |
||
| 72 | break; |
||
| 73 | |||
| 74 | case 'should': |
||
| 75 | $this->should_queries[] = $query; |
||
| 76 | break; |
||
| 77 | |||
| 78 | case 'must': |
||
| 79 | default: |
||
| 80 | $this->must_queries[] = $query; |
||
| 81 | break; |
||
| 82 | } |
||
| 83 | |||
| 84 | return $this; |
||
| 85 | } |
||
| 86 | |||
| 87 | /** |
||
| 88 | * Add any weighting function to the query |
||
| 89 | * |
||
| 90 | * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html |
||
| 91 | * |
||
| 92 | * @param $function array A function structure to apply to the query |
||
| 93 | * |
||
| 94 | * @return void |
||
| 95 | */ |
||
| 96 | public function add_weighting_function( $function ) { |
||
| 97 | $this->weighting_functions[] = $function; |
||
| 98 | |||
| 99 | return $this; |
||
| 100 | } |
||
| 101 | |||
| 102 | /** |
||
| 103 | * Add a scoring function to the query |
||
| 104 | * |
||
| 105 | * NOTE: For decays (linear, exp, or gauss), use Jetpack_WPES_Query_Builder::add_decay() instead |
||
| 106 | * |
||
| 107 | * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html |
||
| 108 | * |
||
| 109 | * @param $function string name of the function |
||
| 110 | * @param $params array functions parameters |
||
| 111 | * |
||
| 112 | * @return void |
||
| 113 | */ |
||
| 114 | public function add_function( $function, $params ) { |
||
| 115 | $this->functions[ $function ][] = $params; |
||
| 116 | |||
| 117 | return $this; |
||
| 118 | } |
||
| 119 | |||
| 120 | /** |
||
| 121 | * Add a decay function to score results |
||
| 122 | * |
||
| 123 | * This method should be used instead of Jetpack_WPES_Query_Builder::add_function() for decays, as the internal ES structure |
||
| 124 | * is slightly different for them. |
||
| 125 | * |
||
| 126 | * @see https://www.elastic.co/guide/en/elasticsearch/guide/current/decay-functions.html |
||
| 127 | * |
||
| 128 | * @param $function string name of the decay function - linear, exp, or gauss |
||
| 129 | * @param $params array The decay functions parameters, passed to ES directly |
||
| 130 | * |
||
| 131 | * @return void |
||
| 132 | */ |
||
| 133 | public function add_decay( $function, $params ) { |
||
| 134 | $this->decays[ $function ][] = $params; |
||
| 135 | |||
| 136 | return $this; |
||
| 137 | } |
||
| 138 | |||
| 139 | /** |
||
| 140 | * Add a scoring mode to the query |
||
| 141 | * |
||
| 142 | * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html |
||
| 143 | * |
||
| 144 | * @param $mode string name of how to score |
||
| 145 | * |
||
| 146 | * @return void |
||
| 147 | */ |
||
| 148 | public function add_score_mode_to_functions( $mode='multiply' ) { |
||
| 149 | $this->functions_score_mode = $mode; |
||
| 150 | |||
| 151 | return $this; |
||
| 152 | } |
||
| 153 | |||
| 154 | public function add_boost_mode_to_functions( $mode='multiply' ) { |
||
| 155 | $this->functions_boost_mode = $mode; |
||
| 156 | |||
| 157 | return $this; |
||
| 158 | } |
||
| 159 | |||
| 160 | public function add_max_boost_to_functions( $boost ) { |
||
| 161 | $this->functions_max_boost = $boost; |
||
| 162 | |||
| 163 | return $this; |
||
| 164 | } |
||
| 165 | |||
| 166 | public function add_boost_to_query_bool( $boost ) { |
||
| 167 | $this->query_bool_boost = $boost; |
||
| 168 | |||
| 169 | return $this; |
||
| 170 | } |
||
| 171 | |||
| 172 | public function add_aggs( $aggs_name, $aggs ) { |
||
| 173 | $this->aggs_query = true; |
||
| 174 | $this->aggs[$aggs_name] = $aggs; |
||
| 175 | |||
| 176 | return $this; |
||
| 177 | } |
||
| 178 | |||
| 179 | public function set_all_aggs( $aggs ) { |
||
| 180 | $this->aggs_query = true; |
||
| 181 | $this->aggs = $aggs; |
||
| 182 | |||
| 183 | return $this; |
||
| 184 | } |
||
| 185 | |||
| 186 | public function add_aggs_sub_aggs( $aggs_name, $sub_aggs ) { |
||
| 187 | if ( ! array_key_exists( 'aggs', $this->aggs[$aggs_name] ) ) { |
||
| 188 | $this->aggs[$aggs_name]['aggs'] = array(); |
||
| 189 | } |
||
| 190 | $this->aggs[$aggs_name]['aggs'] = $sub_aggs; |
||
| 191 | |||
| 192 | return $this; |
||
| 193 | } |
||
| 194 | |||
| 195 | public function add_bucketed_query( $name, $query ) { |
||
| 196 | $this->_add_bucket_filter( $name, $query ); |
||
| 197 | |||
| 198 | $this->add_query( $query, 'dis_max' ); |
||
| 199 | |||
| 200 | return $this; |
||
| 201 | } |
||
| 202 | |||
| 203 | public function add_bucketed_terms( $name, $field, $terms, $boost = 1 ) { |
||
| 204 | if ( ! is_array( $terms ) ) { |
||
| 205 | $terms = array( $terms ); |
||
| 206 | } |
||
| 207 | |||
| 208 | $this->_add_bucket_filter( $name, array( |
||
| 209 | 'terms' => array( |
||
| 210 | $field => $terms, |
||
| 211 | ), |
||
| 212 | )); |
||
| 213 | |||
| 214 | $this->add_query( array( |
||
| 215 | 'constant_score' => array( |
||
| 216 | 'filter' => array( |
||
| 217 | 'terms' => array( |
||
| 218 | $field => $terms, |
||
| 219 | ), |
||
| 220 | ), |
||
| 221 | 'boost' => $boost, |
||
| 222 | ), |
||
| 223 | ), 'dis_max' ); |
||
| 224 | |||
| 225 | return $this; |
||
| 226 | } |
||
| 227 | |||
| 228 | public function add_bucket_sub_aggs( $agg ) { |
||
| 229 | $this->bucket_sub_aggs = array_merge( $this->bucket_sub_aggs, $agg ); |
||
| 230 | |||
| 231 | return $this; |
||
| 232 | } |
||
| 233 | |||
| 234 | protected function _add_bucket_filter( $name, $filter ) { |
||
| 235 | $this->diverse_buckets_query = true; |
||
| 236 | $this->bucket_filters[ $name ] = $filter; |
||
| 237 | } |
||
| 238 | |||
| 239 | //////////////////////////////////// |
||
| 240 | // Building Final Query |
||
| 241 | |||
| 242 | /** |
||
| 243 | * Combine all the queries, functions, decays, scripts, and max_boost into an ES query |
||
| 244 | * |
||
| 245 | * @return array Array representation of the built ES query |
||
| 246 | */ |
||
| 247 | public function build_query() { |
||
| 248 | $query = array(); |
||
| 249 | |||
| 250 | //dis_max queries just become a single must query |
||
| 251 | if ( ! empty( $this->dis_max_queries ) ) { |
||
| 252 | $this->must_queries[] = array( |
||
| 253 | 'dis_max' => array( |
||
| 254 | 'queries' => $this->dis_max_queries, |
||
| 255 | ), |
||
| 256 | ); |
||
| 257 | } |
||
| 258 | |||
| 259 | if ( empty( $this->must_queries ) ) { |
||
| 260 | $this->must_queries = array( |
||
| 261 | array( |
||
| 262 | 'match_all' => array(), |
||
| 263 | ), |
||
| 264 | ); |
||
| 265 | } |
||
| 266 | |||
| 267 | if ( empty( $this->should_queries ) ) { |
||
| 268 | $query = array( |
||
| 269 | 'bool' => array( |
||
| 270 | 'must' => $this->must_queries, |
||
| 271 | ), |
||
| 272 | ); |
||
| 273 | } else { |
||
| 274 | $query = array( |
||
| 275 | 'bool' => array( |
||
| 276 | 'must' => $this->must_queries, |
||
| 277 | 'should' => $this->should_queries, |
||
| 278 | ), |
||
| 279 | ); |
||
| 280 | } |
||
| 281 | |||
| 282 | if ( ! is_null( $this->query_bool_boost ) && isset( $query['bool'] ) ) { |
||
| 283 | $query['bool']['boost'] = $this->query_bool_boost; |
||
| 284 | } |
||
| 285 | |||
| 286 | // If there are any function score adjustments, then combine those |
||
| 287 | if ( $this->functions || $this->decays || $this->scripts || $this->weighting_functions ) { |
||
|
0 ignored issues
–
show
The expression
$this->functions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using Loading history...
The expression
$this->decays of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using Loading history...
The expression
$this->scripts of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using Loading history...
The expression
$this->weighting_functions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using Loading history...
|
|||
| 288 | $weighting_functions = array(); |
||
| 289 | |||
| 290 | if ( $this->functions ) { |
||
| 291 | foreach ( $this->functions as $function_type => $configs ) { |
||
| 292 | foreach ( $configs as $config ) { |
||
| 293 | foreach ( $config as $field => $params ) { |
||
| 294 | $func_arr = $params; |
||
| 295 | |||
| 296 | $func_arr['field'] = $field; |
||
| 297 | |||
| 298 | $weighting_functions[] = array( |
||
| 299 | $function_type => $func_arr, |
||
| 300 | ); |
||
| 301 | } |
||
| 302 | } |
||
| 303 | } |
||
| 304 | } |
||
| 305 | |||
| 306 | if ( $this->decays ) { |
||
| 307 | foreach ( $this->decays as $decay_type => $configs ) { |
||
| 308 | foreach ( $configs as $config ) { |
||
| 309 | foreach ( $config as $field => $params ) { |
||
| 310 | $weighting_functions[] = array( |
||
| 311 | $decay_type => array( |
||
| 312 | $field => $params, |
||
| 313 | ), |
||
| 314 | ); |
||
| 315 | } |
||
| 316 | } |
||
| 317 | } |
||
| 318 | } |
||
| 319 | |||
| 320 | if ( $this->scripts ) { |
||
| 321 | foreach ( $this->scripts as $script ) { |
||
| 322 | $weighting_functions[] = array( |
||
| 323 | 'script_score' => array( |
||
| 324 | 'script' => $script, |
||
| 325 | ), |
||
| 326 | ); |
||
| 327 | } |
||
| 328 | } |
||
| 329 | |||
| 330 | $query = array( |
||
| 331 | 'function_score' => array( |
||
| 332 | 'query' => $query, |
||
| 333 | 'functions' => $weighting_functions, |
||
| 334 | 'max_boost' => $this->functions_max_boost, |
||
| 335 | 'score_mode' => $this->functions_score_mode, |
||
| 336 | 'boost_mode' => $this->functions_boost_mode, |
||
| 337 | ), |
||
| 338 | ); |
||
| 339 | } // End if(). |
||
| 340 | |||
| 341 | return $query; |
||
| 342 | } |
||
| 343 | |||
| 344 | /** |
||
| 345 | * Assemble the 'filter' portion of an ES query, from all registered filters |
||
| 346 | * |
||
| 347 | * @return array|null Combined ES filters, or null if none have been defined |
||
| 348 | */ |
||
| 349 | public function build_filter() { |
||
| 350 | if ( empty( $this->es_filters ) ) { |
||
| 351 | $filter = null; |
||
| 352 | } elseif ( 1 == count( $this->es_filters ) ) { |
||
| 353 | $filter = $this->es_filters[0]; |
||
| 354 | } else { |
||
| 355 | $filter = array( |
||
| 356 | 'and' => $this->es_filters, |
||
| 357 | ); |
||
| 358 | } |
||
| 359 | |||
| 360 | return $filter; |
||
| 361 | } |
||
| 362 | |||
| 363 | /** |
||
| 364 | * Assemble the 'aggregation' portion of an ES query, from all general aggregations. |
||
| 365 | * |
||
| 366 | * @return array An aggregation query as an array of topics, filters, and bucket names |
||
| 367 | */ |
||
| 368 | public function build_aggregation() { |
||
| 369 | if ( empty( $this->bucket_sub_aggs ) && empty( $this->aggs_query ) ) { |
||
| 370 | return array(); |
||
| 371 | } |
||
| 372 | |||
| 373 | if ( ! $this->diverse_buckets_query && empty( $this->aggs_query ) ) { |
||
| 374 | return $this->bucket_sub_aggs; |
||
| 375 | } |
||
| 376 | |||
| 377 | $aggregations = array( |
||
| 378 | 'topics' => array( |
||
| 379 | 'filters' => array( |
||
| 380 | 'filters' => array(), |
||
| 381 | ), |
||
| 382 | ), |
||
| 383 | ); |
||
| 384 | |||
| 385 | if ( ! empty( $this->bucket_sub_aggs ) ) { |
||
| 386 | $aggregations['topics']['aggs'] = $this->bucket_sub_aggs; |
||
| 387 | } |
||
| 388 | |||
| 389 | foreach ( $this->bucket_filters as $bucket_name => $filter ) { |
||
| 390 | $aggregations['topics']['filters']['filters'][ $bucket_name ] = $filter; |
||
| 391 | } |
||
| 392 | |||
| 393 | if ( ! empty( $this->aggs_query ) ) { |
||
| 394 | $aggregations = $this->aggs; |
||
| 395 | } |
||
| 396 | |||
| 397 | return $aggregations; |
||
| 398 | } |
||
| 399 | |||
| 400 | } |
||
| 401 |
In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:
Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion: