Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like Expression often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Expression, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 24 | class Expression implements Arrayable |
||
| 25 | { |
||
| 26 | protected $_expression = array(); |
||
| 27 | |||
| 28 | /** |
||
| 29 | * Create new instance of expression |
||
| 30 | * @return \Sokil\Mongo\Expression |
||
| 31 | */ |
||
| 32 | public function expression() |
||
| 36 | /** |
||
| 37 | * Return a expression |
||
| 38 | * @return \Sokil\Mongo\Cursor|\Sokil\Mongo\Expression |
||
| 39 | */ |
||
| 40 | public function where($field, $value) |
||
| 51 | |||
| 52 | View Code Duplication | public function whereEmpty($field) |
|
| 53 | { |
||
| 54 | return $this->where('$or', array( |
||
| 55 | array($field => null), |
||
| 56 | array($field => ''), |
||
| 57 | array($field => array()), |
||
| 58 | array($field => array('$exists' => false)) |
||
| 59 | )); |
||
| 60 | } |
||
| 61 | |||
| 62 | View Code Duplication | public function whereNotEmpty($field) |
|
| 63 | { |
||
| 64 | return $this->where('$nor', array( |
||
| 65 | array($field => null), |
||
| 66 | array($field => ''), |
||
| 67 | array($field => array()), |
||
| 68 | array($field => array('$exists' => false)) |
||
| 69 | )); |
||
| 70 | } |
||
| 71 | |||
| 72 | public function whereGreater($field, $value) |
||
| 76 | |||
| 77 | public function whereGreaterOrEqual($field, $value) |
||
| 81 | |||
| 82 | public function whereLess($field, $value) |
||
| 86 | |||
| 87 | public function whereLessOrEqual($field, $value) |
||
| 91 | |||
| 92 | public function whereNotEqual($field, $value) |
||
| 96 | |||
| 97 | /** |
||
| 98 | * Selects the documents where the value of a |
||
| 99 | * field equals any value in the specified array. |
||
| 100 | * |
||
| 101 | * @param string $field |
||
| 102 | * @param array $values |
||
| 103 | * @return \Sokil\Mongo\Expression |
||
| 104 | */ |
||
| 105 | public function whereIn($field, array $values) |
||
| 109 | |||
| 110 | public function whereNotIn($field, array $values) |
||
| 114 | |||
| 115 | public function whereExists($field) |
||
| 119 | |||
| 120 | public function whereNotExists($field) |
||
| 124 | |||
| 125 | public function whereHasType($field, $type) |
||
| 129 | |||
| 130 | public function whereDouble($field) |
||
| 134 | |||
| 135 | public function whereString($field) |
||
| 139 | |||
| 140 | public function whereObject($field) |
||
| 144 | |||
| 145 | public function whereBoolean($field) |
||
| 149 | |||
| 150 | public function whereArray($field) |
||
| 154 | |||
| 155 | public function whereArrayOfArrays($field) |
||
| 159 | |||
| 160 | public function whereObjectId($field) |
||
| 164 | |||
| 165 | public function whereDate($field) |
||
| 169 | |||
| 170 | public function whereNull($field) |
||
| 174 | |||
| 175 | public function whereJsCondition($condition) |
||
| 179 | |||
| 180 | public function whereLike($field, $regex, $caseInsensitive = true) |
||
| 181 | { |
||
| 182 | // regex |
||
| 183 | $expression = array( |
||
| 184 | '$regex' => $regex, |
||
| 185 | ); |
||
| 186 | |||
| 187 | // options |
||
| 188 | $options = ''; |
||
| 189 | |||
| 190 | if($caseInsensitive) { |
||
| 191 | $options .= 'i'; |
||
| 192 | } |
||
| 193 | |||
| 194 | $expression['$options'] = $options; |
||
| 195 | |||
| 196 | // query |
||
| 197 | return $this->where($field, $expression); |
||
| 198 | } |
||
| 199 | |||
| 200 | /** |
||
| 201 | * Find documents where the value of a field is an array |
||
| 202 | * that contains all the specified elements. |
||
| 203 | * This is equivalent of logical AND. |
||
| 204 | * |
||
| 205 | * @link http://docs.mongodb.org/manual/reference/operator/query/all/ |
||
| 206 | * |
||
| 207 | * @param string $field point-delimited field name |
||
| 208 | * @param array $values |
||
| 209 | * @return \Sokil\Mongo\Expression |
||
| 210 | */ |
||
| 211 | public function whereAll($field, array $values) |
||
| 215 | |||
| 216 | /** |
||
| 217 | * Find documents where the value of a field is an array |
||
| 218 | * that contains none of the specified elements. |
||
| 219 | * This is equivalent of logical AND. |
||
| 220 | * |
||
| 221 | * @link http://docs.mongodb.org/manual/reference/operator/query/all/ |
||
| 222 | * |
||
| 223 | * @param string $field point-delimited field name |
||
| 224 | * @param array $values |
||
| 225 | * @return \Sokil\Mongo\Expression |
||
| 226 | */ |
||
| 227 | public function whereNoneOf($field, array $values) |
||
| 228 | { |
||
| 229 | return $this->where($field, array( |
||
| 230 | '$not' => array( |
||
| 231 | '$all' => $values |
||
| 232 | ), |
||
| 233 | )); |
||
| 234 | } |
||
| 235 | |||
| 236 | /** |
||
| 237 | * Find documents where the value of a field is an array |
||
| 238 | * that contains any of the specified elements. |
||
| 239 | * This is equivalent of logical AND. |
||
| 240 | * |
||
| 241 | * @param string $field point-delimited field name |
||
| 242 | * @param array $values |
||
| 243 | * @return \Sokil\Mongo\Expression |
||
| 244 | */ |
||
| 245 | public function whereAny($field, array $values) |
||
| 249 | |||
| 250 | /** |
||
| 251 | * Matches documents in a collection that contain an array field with at |
||
| 252 | * least one element that matches all the specified query criteria. |
||
| 253 | * |
||
| 254 | * @param string $field point-delimited field name |
||
| 255 | * @param \Sokil\Mongo\Expression|callable|array $expression |
||
| 256 | * @return \Sokil\Mongo\Expression |
||
| 257 | */ |
||
| 258 | View Code Duplication | public function whereElemMatch($field, $expression) |
|
| 272 | |||
| 273 | /** |
||
| 274 | * Matches documents in a collection that contain an array field with elements |
||
| 275 | * that do not matches all the specified query criteria. |
||
| 276 | * |
||
| 277 | * @param type $field |
||
| 278 | * @param \Sokil\Mongo\Expression|callable|array $expression |
||
| 279 | * @return \Sokil\Mongo\Expression |
||
| 280 | */ |
||
| 281 | public function whereElemNotMatch($field, $expression) |
||
| 285 | |||
| 286 | /** |
||
| 287 | * Selects documents if the array field is a specified size. |
||
| 288 | * |
||
| 289 | * @param string $field |
||
| 290 | * @param integer $length |
||
| 291 | * @return \Sokil\Mongo\Expression |
||
| 292 | */ |
||
| 293 | public function whereArraySize($field, $length) |
||
| 297 | |||
| 298 | /** |
||
| 299 | * Selects the documents that satisfy at least one of the expressions |
||
| 300 | * |
||
| 301 | * @param array|\Sokil\Mongo\Expression $expressions Array of Expression instances or comma delimited expression list |
||
| 302 | * @return \Sokil\Mongo\Expression |
||
| 303 | */ |
||
| 304 | View Code Duplication | public function whereOr($expressions = null /**, ...**/) |
|
| 305 | { |
||
| 306 | if($expressions instanceof Expression) { |
||
| 307 | $expressions = func_get_args(); |
||
| 308 | } |
||
| 309 | |||
| 310 | return $this->where('$or', array_map(function(Expression $expression) { |
||
| 311 | return $expression->toArray(); |
||
| 312 | }, $expressions)); |
||
| 313 | } |
||
| 314 | |||
| 315 | /** |
||
| 316 | * Select the documents that satisfy all the expressions in the array |
||
| 317 | * |
||
| 318 | * @param array|\Sokil\Mongo\Expression $expressions Array of Expression instances or comma delimited expression list |
||
| 319 | * @return \Sokil\Mongo\Expression |
||
| 320 | */ |
||
| 321 | View Code Duplication | public function whereAnd($expressions = null /**, ...**/) |
|
| 322 | { |
||
| 323 | if($expressions instanceof Expression) { |
||
| 324 | $expressions = func_get_args(); |
||
| 325 | } |
||
| 326 | |||
| 327 | return $this->where('$and', array_map(function(Expression $expression) { |
||
| 328 | return $expression->toArray(); |
||
| 329 | }, $expressions)); |
||
| 330 | } |
||
| 331 | |||
| 332 | /** |
||
| 333 | * Selects the documents that fail all the query expressions in the array |
||
| 334 | * |
||
| 335 | * @param array|\Sokil\Mongo\Expression $expressions Array of Expression instances or comma delimited expression list |
||
| 336 | * @return \Sokil\Mongo\Expression |
||
| 337 | */ |
||
| 338 | View Code Duplication | public function whereNor($expressions = null /**, ...**/) |
|
| 339 | { |
||
| 340 | if($expressions instanceof Expression) { |
||
| 341 | $expressions = func_get_args(); |
||
| 342 | } |
||
| 343 | |||
| 344 | return $this->where('$nor', array_map(function(Expression $expression) { |
||
| 345 | return $expression->toArray(); |
||
| 346 | }, $expressions)); |
||
| 347 | } |
||
| 348 | |||
| 349 | public function whereNot(Expression $expression) |
||
| 364 | |||
| 365 | /** |
||
| 366 | * Select documents where the value of a field divided by a divisor has the specified remainder (i.e. perform a modulo operation to select documents) |
||
| 367 | * |
||
| 368 | * @param string $field |
||
| 369 | * @param int $divisor |
||
| 370 | * @param int $remainder |
||
| 371 | */ |
||
| 372 | public function whereMod($field, $divisor, $remainder) |
||
| 380 | |||
| 381 | /** |
||
| 382 | * Perform fulltext search |
||
| 383 | * |
||
| 384 | * @link https://docs.mongodb.org/manual/reference/operator/query/text/ |
||
| 385 | * @link https://docs.mongodb.org/manual/tutorial/specify-language-for-text-index/ |
||
| 386 | * |
||
| 387 | * If a collection contains documents or embedded documents that are in different languages, |
||
| 388 | * include a field named language in the documents or embedded documents and specify as its value the language |
||
| 389 | * for that document or embedded document. |
||
| 390 | * |
||
| 391 | * The specified language in the document overrides the default language for the text index. |
||
| 392 | * The specified language in an embedded document override the language specified in an enclosing document or |
||
| 393 | * the default language for the index. |
||
| 394 | * |
||
| 395 | * Case Insensitivity: |
||
| 396 | * @link https://docs.mongodb.org/manual/reference/operator/query/text/#text-operator-case-sensitivity |
||
| 397 | * |
||
| 398 | * Diacritic Insensitivity: |
||
| 399 | * @link https://docs.mongodb.org/manual/reference/operator/query/text/#text-operator-diacritic-sensitivity |
||
| 400 | * |
||
| 401 | * @param $search A string of terms that MongoDB parses and uses to query the text index. MongoDB performs a |
||
| 402 | * logical OR search of the terms unless specified as a phrase. |
||
| 403 | * @param $language Optional. The language that determines the list of stop words for the search and the |
||
| 404 | * rules for the stemmer and tokenizer. If not specified, the search uses the default language of the index. |
||
| 405 | * If you specify a language value of "none", then the text search uses simple tokenization |
||
| 406 | * with no list of stop words and no stemming. |
||
| 407 | * @param bool|false $caseSensitive Allowed from v.3.2 A boolean flag to enable or disable case |
||
| 408 | * sensitive search. Defaults to false; i.e. the search defers to the case insensitivity of the text index. |
||
| 409 | * @param bool|false $diacriticSensitive Allowed from v.3.2 A boolean flag to enable or disable diacritic |
||
| 410 | * sensitive search against version 3 text indexes. Defaults to false; i.e. the search defers to the diacritic |
||
| 411 | * insensitivity of the text index. Text searches against earlier versions of the text index are inherently |
||
| 412 | * diacritic sensitive and cannot be diacritic insensitive. As such, the $diacriticSensitive option has no |
||
| 413 | * effect with earlier versions of the text index. |
||
| 414 | * @return $this |
||
| 415 | */ |
||
| 416 | public function whereText( |
||
| 442 | |||
| 443 | /** |
||
| 444 | * Find document near points in flat surface |
||
| 445 | * |
||
| 446 | * @param string $field |
||
| 447 | * @param float $longitude |
||
| 448 | * @param float $latitude |
||
| 449 | * @param int|array $distance distance from point in meters. Array distance |
||
| 450 | * allowed only in MongoDB 2.6 |
||
| 451 | * @return \Sokil\Mongo\Expression |
||
| 452 | */ |
||
| 453 | View Code Duplication | public function nearPoint($field, $longitude, $latitude, $distance) |
|
| 479 | |||
| 480 | /** |
||
| 481 | * Find document near points in spherical surface |
||
| 482 | * |
||
| 483 | * @param string $field |
||
| 484 | * @param float $longitude |
||
| 485 | * @param float $latitude |
||
| 486 | * @param int|array $distance distance from point in meters. Array distance |
||
| 487 | * allowed only in MongoDB 2.6 |
||
| 488 | * @return \Sokil\Mongo\Expression |
||
| 489 | */ |
||
| 490 | View Code Duplication | public function nearPointSpherical($field, $longitude, $latitude, $distance) |
|
| 516 | |||
| 517 | /** |
||
| 518 | * Selects documents whose geospatial data intersects with a specified |
||
| 519 | * GeoJSON object; i.e. where the intersection of the data and the |
||
| 520 | * specified object is non-empty. This includes cases where the data |
||
| 521 | * and the specified object share an edge. Uses spherical geometry. |
||
| 522 | * |
||
| 523 | * @link http://docs.mongodb.org/manual/reference/operator/query/geoIntersects/ |
||
| 524 | * |
||
| 525 | * @param string $field |
||
| 526 | * @param Geometry $geometry |
||
| 527 | * @return \Sokil\Mongo\Expression |
||
| 528 | */ |
||
| 529 | public function intersects($field, Geometry $geometry) |
||
| 539 | |||
| 540 | /** |
||
| 541 | * Selects documents with geospatial data that exists entirely within a specified shape. |
||
| 542 | * |
||
| 543 | * @link http://docs.mongodb.org/manual/reference/operator/query/geoWithin/ |
||
| 544 | * @param string $field |
||
| 545 | * @param Geometry $geometry |
||
| 546 | * @return \Sokil\Mongo\Expression |
||
| 547 | */ |
||
| 548 | public function within($field, Geometry $geometry) |
||
| 558 | |||
| 559 | /** |
||
| 560 | * Select documents with geospatial data within circle defined |
||
| 561 | * by center point and radius in flat surface |
||
| 562 | * |
||
| 563 | * @param string $field |
||
| 564 | * @param float $longitude |
||
| 565 | * @param float $latitude |
||
| 566 | * @param float $radius |
||
| 567 | * @return \Sokil\Mongo\Expression |
||
| 568 | */ |
||
| 569 | public function withinCircle($field, $longitude, $latitude, $radius) |
||
| 582 | |||
| 583 | /** |
||
| 584 | * Select documents with geospatial data within circle defined |
||
| 585 | * by center point and radius in spherical surface |
||
| 586 | * |
||
| 587 | * To calculate distance in radians |
||
| 588 | * @see http://docs.mongodb.org/manual/tutorial/calculate-distances-using-spherical-geometry-with-2d-geospatial-indexes/ |
||
| 589 | * |
||
| 590 | * @param string $field |
||
| 591 | * @param float $longitude |
||
| 592 | * @param float $latitude |
||
| 593 | * @param float $radiusInRadians in radians. |
||
| 594 | * @return \Sokil\Mongo\Expression |
||
| 595 | */ |
||
| 596 | public function withinCircleSpherical($field, $longitude, $latitude, $radiusInRadians) |
||
| 609 | |||
| 610 | /** |
||
| 611 | * Return documents that are within the bounds of the rectangle, according |
||
| 612 | * to their point-based location data. |
||
| 613 | * |
||
| 614 | * Based on grid coordinates and does not query for GeoJSON shapes. |
||
| 615 | * |
||
| 616 | * Use planar geometry, so 2d index may be used but not required |
||
| 617 | * |
||
| 618 | * @param string $field |
||
| 619 | * @param array $bottomLeftCoordinate Bottom left coordinate of box |
||
| 620 | * @param array $upperRightCoordinate Upper right coordinate of box |
||
| 621 | * @return \Sokil\Mongo\Expression |
||
| 622 | */ |
||
| 623 | public function withinBox($field, array $bottomLeftCoordinate, array $upperRightCoordinate) |
||
| 636 | |||
| 637 | /** |
||
| 638 | * Return documents that are within the polygon, according |
||
| 639 | * to their point-based location data. |
||
| 640 | * |
||
| 641 | * Based on grid coordinates and does not query for GeoJSON shapes. |
||
| 642 | * |
||
| 643 | * Use planar geometry, so 2d index may be used but not required |
||
| 644 | * |
||
| 645 | * @param string $field |
||
| 646 | * @param array $points array of coordinates |
||
| 647 | * @return \Sokil\Mongo\Expression |
||
| 648 | */ |
||
| 649 | public function withinPolygon($field, array $points) |
||
| 659 | |||
| 660 | public function toArray() |
||
| 664 | |||
| 665 | public function merge(Expression $expression) |
||
| 670 | |||
| 671 | /** |
||
| 672 | * Transform extression in different formats to canonical array form |
||
| 673 | * |
||
| 674 | * @param mixed $mixed |
||
| 675 | * @return array |
||
| 676 | * @throws \Sokil\Mongo\Exception |
||
| 677 | */ |
||
| 678 | View Code Duplication | public static function convertToArray($mixed) |
|
| 696 | } |
||
| 697 |