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 Jetpack_Protect_Module 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 Jetpack_Protect_Module, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 17 | class Jetpack_Protect_Module { |
||
| 18 | |||
| 19 | private static $__instance = null; |
||
| 20 | public $api_key; |
||
| 21 | public $api_key_error; |
||
| 22 | public $whitelist; |
||
| 23 | public $whitelist_error; |
||
| 24 | public $whitelist_saved; |
||
| 25 | private $user_ip; |
||
| 26 | private $local_host; |
||
| 27 | private $api_endpoint; |
||
| 28 | public $last_request; |
||
| 29 | public $last_response_raw; |
||
| 30 | public $last_response; |
||
| 31 | private $block_login_with_math; |
||
| 32 | |||
| 33 | /** |
||
| 34 | * Singleton implementation |
||
| 35 | * |
||
| 36 | * @return object |
||
| 37 | */ |
||
| 38 | public static function instance() { |
||
| 45 | |||
| 46 | /** |
||
| 47 | * Registers actions |
||
| 48 | */ |
||
| 49 | private function __construct() { |
||
| 50 | add_action( 'jetpack_activate_module_protect', array ( $this, 'on_activation' ) ); |
||
| 51 | add_action( 'jetpack_deactivate_module_protect', array ( $this, 'on_deactivation' ) ); |
||
| 52 | add_action( 'jetpack_modules_loaded', array ( $this, 'modules_loaded' ) ); |
||
| 53 | add_action( 'login_init', array ( $this, 'check_use_math' ) ); |
||
| 54 | add_filter( 'authenticate', array ( $this, 'check_preauth' ), 10, 3 ); |
||
| 55 | add_action( 'wp_login', array ( $this, 'log_successful_login' ), 10, 2 ); |
||
| 56 | add_action( 'wp_login_failed', array ( $this, 'log_failed_attempt' ) ); |
||
| 57 | add_action( 'admin_init', array ( $this, 'maybe_update_headers' ) ); |
||
| 58 | add_action( 'admin_init', array ( $this, 'maybe_display_security_warning' ) ); |
||
| 59 | |||
| 60 | // This is a backup in case $pagenow fails for some reason |
||
| 61 | add_action( 'login_head', array ( $this, 'check_login_ability' ) ); |
||
| 62 | |||
| 63 | // Runs a script every day to clean up expired transients so they don't |
||
| 64 | // clog up our users' databases |
||
| 65 | require_once( JETPACK__PLUGIN_DIR . '/modules/protect/transient-cleanup.php' ); |
||
| 66 | } |
||
| 67 | |||
| 68 | /** |
||
| 69 | * On module activation, try to get an api key |
||
| 70 | */ |
||
| 71 | public function on_activation() { |
||
| 81 | |||
| 82 | /** |
||
| 83 | * On module deactivation, unset protect_active |
||
| 84 | */ |
||
| 85 | public function on_deactivation() { |
||
| 90 | |||
| 91 | public function maybe_get_protect_key() { |
||
| 92 | if ( get_site_option( 'jetpack_protect_activating', false ) && ! get_site_option( 'jetpack_protect_key', false ) ) { |
||
| 93 | $key = $this->get_protect_key(); |
||
| 94 | delete_site_option( 'jetpack_protect_activating' ); |
||
| 95 | return $key; |
||
| 96 | } |
||
| 97 | |||
| 98 | return get_site_option( 'jetpack_protect_key' ); |
||
| 99 | } |
||
| 100 | |||
| 101 | /** |
||
| 102 | * Sends a "check_key" API call once a day. This call allows us to track IP-related |
||
| 103 | * headers for this server via the Protect API, in order to better identify the source |
||
| 104 | * IP for login attempts |
||
| 105 | */ |
||
| 106 | public function maybe_update_headers( $force = false ) { |
||
| 129 | |||
| 130 | public function maybe_display_security_warning() { |
||
| 131 | if ( is_multisite() && current_user_can( 'manage_network' ) ) { |
||
| 132 | if ( ! function_exists( 'is_plugin_active_for_network' ) ) { |
||
| 133 | require_once( ABSPATH . '/wp-admin/includes/plugin.php' ); |
||
| 134 | } |
||
| 135 | |||
| 136 | if ( ! ( is_plugin_active_for_network( 'jetpack/jetpack.php' ) || is_plugin_active_for_network( 'jetpack-dev/jetpack.php' ) ) ) { |
||
| 137 | add_action( 'load-index.php', array ( $this, 'prepare_jetpack_protect_multisite_notice' ) ); |
||
| 138 | } |
||
| 139 | } |
||
| 140 | } |
||
| 141 | |||
| 142 | public function prepare_jetpack_protect_multisite_notice() { |
||
| 146 | |||
| 147 | public function admin_banner_styles() { |
||
| 155 | |||
| 156 | public function admin_jetpack_manage_notice() { |
||
| 185 | |||
| 186 | /** |
||
| 187 | * Request an api key from wordpress.com |
||
| 188 | * |
||
| 189 | * @return bool | string |
||
| 190 | */ |
||
| 191 | public function get_protect_key() { |
||
| 263 | |||
| 264 | /** |
||
| 265 | * Called via WP action wp_login_failed to log failed attempt with the api |
||
| 266 | * |
||
| 267 | * Fires custom, plugable action jpp_log_failed_attempt with the IP |
||
| 268 | * |
||
| 269 | * @return void |
||
| 270 | */ |
||
| 271 | function log_failed_attempt() { |
||
| 298 | |||
| 299 | /** |
||
| 300 | * Set up the Protect configuration page |
||
| 301 | */ |
||
| 302 | public function modules_loaded() { |
||
| 308 | |||
| 309 | /** |
||
| 310 | * Logs a successful login back to our servers, this allows us to make sure we're not blocking |
||
| 311 | * a busy IP that has a lot of good logins along with some forgotten passwords. Also saves current user's ip |
||
| 312 | * to the ip address whitelist |
||
| 313 | */ |
||
| 314 | public function log_successful_login( $user_login, $user = null ) { |
||
| 315 | if ( ! $user ) { // For do_action( 'wp_login' ) calls that lacked passing the 2nd arg. |
||
| 316 | $user = get_user_by( 'login', $user_login ); |
||
| 317 | } |
||
| 318 | |||
| 319 | $this->protect_call( 'successful_login', array ( 'roles' => $user->roles ) ); |
||
| 320 | } |
||
| 321 | |||
| 322 | |||
| 323 | /** |
||
| 324 | * Checks for loginability BEFORE authentication so that bots don't get to go around the log in form. |
||
| 325 | * |
||
| 326 | * If we are using our math fallback, authenticate via math-fallback.php |
||
| 327 | * |
||
| 328 | * @param string $user |
||
| 329 | * @param string $username |
||
| 330 | * @param string $password |
||
| 331 | * |
||
| 332 | * @return string $user |
||
| 333 | */ |
||
| 334 | function check_preauth( $user = 'Not Used By Protect', $username = 'Not Used By Protect', $password = 'Not Used By Protect' ) { |
||
| 335 | $allow_login = $this->check_login_ability( true ); |
||
| 336 | $use_math = $this->get_transient( 'brute_use_math' ); |
||
| 337 | |||
| 338 | if ( ! $allow_login ) { |
||
| 339 | $this->block_with_math(); |
||
| 340 | } |
||
| 341 | |||
| 342 | if ( ( 1 == $use_math || 1 == $this->block_login_with_math ) && isset( $_POST['log'] ) ) { |
||
| 343 | include_once dirname( __FILE__ ) . '/protect/math-fallback.php'; |
||
| 344 | Jetpack_Protect_Math_Authenticate::math_authenticate(); |
||
| 345 | } |
||
| 346 | |||
| 347 | return $user; |
||
| 348 | } |
||
| 349 | |||
| 350 | /** |
||
| 351 | * Get all IP headers so that we can process on our server... |
||
| 352 | * |
||
| 353 | * @return string |
||
| 354 | */ |
||
| 355 | function get_headers() { |
||
| 356 | $ip_related_headers = array ( |
||
| 357 | 'GD_PHP_HANDLER', |
||
| 358 | 'HTTP_AKAMAI_ORIGIN_HOP', |
||
| 359 | 'HTTP_CF_CONNECTING_IP', |
||
| 360 | 'HTTP_CLIENT_IP', |
||
| 361 | 'HTTP_FASTLY_CLIENT_IP', |
||
| 362 | 'HTTP_FORWARDED', |
||
| 363 | 'HTTP_FORWARDED_FOR', |
||
| 364 | 'HTTP_INCAP_CLIENT_IP', |
||
| 365 | 'HTTP_TRUE_CLIENT_IP', |
||
| 366 | 'HTTP_X_CLIENTIP', |
||
| 367 | 'HTTP_X_CLUSTER_CLIENT_IP', |
||
| 368 | 'HTTP_X_FORWARDED', |
||
| 369 | 'HTTP_X_FORWARDED_FOR', |
||
| 370 | 'HTTP_X_IP_TRAIL', |
||
| 371 | 'HTTP_X_REAL_IP', |
||
| 372 | 'HTTP_X_VARNISH', |
||
| 373 | 'REMOTE_ADDR' |
||
| 374 | ); |
||
| 375 | |||
| 376 | foreach ( $ip_related_headers as $header ) { |
||
| 377 | if ( isset( $_SERVER[ $header ] ) ) { |
||
| 378 | $output[ $header ] = $_SERVER[ $header ]; |
||
|
|
|||
| 379 | } |
||
| 380 | } |
||
| 381 | |||
| 382 | return $output; |
||
| 383 | } |
||
| 384 | |||
| 385 | /* |
||
| 386 | * Checks if the IP address has been whitelisted |
||
| 387 | * |
||
| 388 | * @param string $ip |
||
| 389 | * |
||
| 390 | * @return bool |
||
| 391 | */ |
||
| 392 | function ip_is_whitelisted( $ip ) { |
||
| 393 | // If we found an exact match in wp-config |
||
| 394 | if ( defined( 'JETPACK_IP_ADDRESS_OK' ) && JETPACK_IP_ADDRESS_OK == $ip ) { |
||
| 395 | return true; |
||
| 396 | } |
||
| 397 | |||
| 398 | $whitelist = jetpack_protect_get_local_whitelist(); |
||
| 399 | |||
| 400 | if ( is_multisite() ) { |
||
| 401 | $whitelist = array_merge( $whitelist, get_site_option( 'jetpack_protect_global_whitelist', array () ) ); |
||
| 402 | } |
||
| 403 | |||
| 404 | if ( ! empty( $whitelist ) ) : |
||
| 405 | foreach ( $whitelist as $item ) : |
||
| 406 | // If the IPs are an exact match |
||
| 407 | if ( ! $item->range && isset( $item->ip_address ) && $item->ip_address == $ip ) { |
||
| 408 | return true; |
||
| 409 | } |
||
| 410 | |||
| 411 | if ( $item->range && isset( $item->range_low ) && isset( $item->range_high ) ) { |
||
| 412 | if ( jetpack_protect_ip_address_is_in_range( $ip, $item->range_low, $item->range_high ) ) { |
||
| 413 | return true; |
||
| 414 | } |
||
| 415 | } |
||
| 416 | endforeach; |
||
| 417 | endif; |
||
| 418 | |||
| 419 | return false; |
||
| 420 | } |
||
| 421 | |||
| 422 | /** |
||
| 423 | * Checks the status for a given IP. API results are cached as transients |
||
| 424 | * |
||
| 425 | * @param bool $preauth Whether or not we are checking prior to authorization |
||
| 426 | * |
||
| 427 | * @return bool Either returns true, fires $this->kill_login, or includes a math fallback and returns false |
||
| 428 | */ |
||
| 429 | function check_login_ability( $preauth = false ) { |
||
| 430 | $ip = jetpack_protect_get_ip(); |
||
| 431 | |||
| 432 | // Server is misconfigured and we can't get an IP |
||
| 433 | if ( ! $ip && class_exists( 'Jetpack' ) ) { |
||
| 434 | Jetpack::deactivate_module( 'protect' ); |
||
| 435 | ob_start(); |
||
| 436 | Jetpack::state( 'message', 'protect_misconfigured_ip' ); |
||
| 437 | ob_end_clean(); |
||
| 438 | return true; |
||
| 439 | } |
||
| 440 | |||
| 441 | /** |
||
| 442 | * Short-circuit check_login_ability. |
||
| 443 | * |
||
| 444 | * If there is an alternate way to validate the current IP such as |
||
| 445 | * a hard-coded list of IP addresses, we can short-circuit the rest |
||
| 446 | * of the login ability checks and return true here. |
||
| 447 | * |
||
| 448 | * @module protect |
||
| 449 | * |
||
| 450 | * @since 4.4.0 |
||
| 451 | * |
||
| 452 | * @param bool false Should we allow all logins for the current ip? Default: false |
||
| 453 | */ |
||
| 454 | if ( apply_filters( 'jpp_allow_login', false, $ip ) ) { |
||
| 455 | return true; |
||
| 456 | } |
||
| 457 | |||
| 458 | $headers = $this->get_headers(); |
||
| 459 | $header_hash = md5( json_encode( $headers ) ); |
||
| 460 | $transient_name = 'jpp_li_' . $header_hash; |
||
| 461 | $transient_value = $this->get_transient( $transient_name ); |
||
| 462 | |||
| 463 | if ( jetpack_protect_ip_is_private( $ip ) ) { |
||
| 464 | return true; |
||
| 465 | } |
||
| 466 | |||
| 467 | if ( $this->ip_is_whitelisted( $ip ) ) { |
||
| 468 | return true; |
||
| 469 | } |
||
| 470 | |||
| 471 | // Check out our transients |
||
| 472 | if ( isset( $transient_value ) && 'ok' == $transient_value['status'] ) { |
||
| 473 | return true; |
||
| 474 | } |
||
| 475 | |||
| 476 | if ( isset( $transient_value ) && 'blocked' == $transient_value['status'] ) { |
||
| 477 | $this->block_with_math(); |
||
| 478 | } |
||
| 479 | |||
| 480 | if ( isset( $transient_value ) && 'blocked-hard' == $transient_value['status'] ) { |
||
| 481 | $this->kill_login(); |
||
| 482 | } |
||
| 483 | |||
| 484 | // If we've reached this point, this means that the IP isn't cached. |
||
| 485 | // Now we check with the Protect API to see if we should allow login |
||
| 486 | $response = $this->protect_call( $action = 'check_ip' ); |
||
| 487 | |||
| 488 | if ( isset( $response['math'] ) && ! function_exists( 'brute_math_authenticate' ) ) { |
||
| 489 | include_once dirname( __FILE__ ) . '/protect/math-fallback.php'; |
||
| 490 | new Jetpack_Protect_Math_Authenticate; |
||
| 491 | |||
| 492 | return false; |
||
| 493 | } |
||
| 494 | |||
| 495 | if ( 'blocked' == $response['status'] ) { |
||
| 496 | $this->block_with_math(); |
||
| 497 | } |
||
| 498 | |||
| 499 | if ( 'blocked-hard' == $response['status'] ) { |
||
| 500 | $this->kill_login(); |
||
| 501 | } |
||
| 502 | |||
| 503 | return true; |
||
| 504 | } |
||
| 505 | |||
| 506 | function block_with_math() { |
||
| 507 | /** |
||
| 508 | * By default, Protect will allow a user who has been blocked for too |
||
| 509 | * many failed logins to start answering math questions to continue logging in |
||
| 510 | * |
||
| 511 | * For added security, you can disable this. |
||
| 512 | * |
||
| 513 | * @module protect |
||
| 514 | * |
||
| 515 | * @since 3.6.0 |
||
| 516 | * |
||
| 517 | * @param bool Whether to allow math for blocked users or not. |
||
| 518 | */ |
||
| 519 | |||
| 520 | $this->block_login_with_math = 1; |
||
| 521 | /** |
||
| 522 | * Allow Math fallback for blocked IPs. |
||
| 523 | * |
||
| 524 | * @module protect |
||
| 525 | * |
||
| 526 | * @since 3.6.0 |
||
| 527 | * |
||
| 528 | * @param bool true Should we fallback to the Math questions when an IP is blocked. Default to true. |
||
| 529 | */ |
||
| 530 | $allow_math_fallback_on_fail = apply_filters( 'jpp_use_captcha_when_blocked', true ); |
||
| 531 | if ( ! $allow_math_fallback_on_fail ) { |
||
| 532 | $this->kill_login(); |
||
| 533 | } |
||
| 534 | include_once dirname( __FILE__ ) . '/protect/math-fallback.php'; |
||
| 535 | new Jetpack_Protect_Math_Authenticate; |
||
| 536 | |||
| 537 | return false; |
||
| 538 | } |
||
| 539 | |||
| 540 | /* |
||
| 541 | * Kill a login attempt |
||
| 542 | */ |
||
| 543 | function kill_login() { |
||
| 544 | $ip = jetpack_protect_get_ip(); |
||
| 545 | /** |
||
| 546 | * Fires before every killed login. |
||
| 547 | * |
||
| 548 | * @module protect |
||
| 549 | * |
||
| 550 | * @since 3.4.0 |
||
| 551 | * |
||
| 552 | * @param string $ip IP flagged by Protect. |
||
| 553 | */ |
||
| 554 | do_action( 'jpp_kill_login', $ip ); |
||
| 555 | $help_url = 'https://jetpack.com/support/security-features/#unblock'; |
||
| 556 | |||
| 557 | $die_string = sprintf( __( 'Your IP (%1$s) has been flagged for potential security violations. <a href="%2$s">Find out more...</a>', 'jetpack' ), str_replace( 'http://', '', esc_url( 'http://' . $ip ) ), esc_url( $help_url ) ); |
||
| 558 | |||
| 559 | if( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) { |
||
| 560 | $die_string = sprintf( __( 'Your IP (%1$s) has been flagged for potential security violations.', 'jetpack' ), str_replace( 'http://', '', esc_url( 'http://' . $ip ) ) ); |
||
| 561 | } |
||
| 562 | |||
| 563 | wp_die( |
||
| 564 | $die_string, |
||
| 565 | __( 'Login Blocked by Jetpack', 'jetpack' ), |
||
| 566 | array ( 'response' => 403 ) |
||
| 567 | ); |
||
| 568 | } |
||
| 569 | |||
| 570 | /* |
||
| 571 | * Checks if the protect API call has failed, and if so initiates the math captcha fallback. |
||
| 572 | */ |
||
| 573 | public function check_use_math() { |
||
| 574 | $use_math = $this->get_transient( 'brute_use_math' ); |
||
| 575 | if ( $use_math ) { |
||
| 576 | include_once dirname( __FILE__ ) . '/protect/math-fallback.php'; |
||
| 577 | new Jetpack_Protect_Math_Authenticate; |
||
| 578 | } |
||
| 579 | } |
||
| 580 | |||
| 581 | /** |
||
| 582 | * Get or delete API key |
||
| 583 | */ |
||
| 584 | public function configuration_load() { |
||
| 585 | |||
| 586 | if ( isset( $_POST['action'] ) && $_POST['action'] == 'jetpack_protect_save_whitelist' && wp_verify_nonce( $_POST['_wpnonce'], 'jetpack-protect' ) ) { |
||
| 587 | $whitelist = str_replace( ' ', '', $_POST['whitelist'] ); |
||
| 588 | $whitelist = explode( PHP_EOL, $whitelist ); |
||
| 589 | $result = jetpack_protect_save_whitelist( $whitelist ); |
||
| 590 | $this->whitelist_saved = ! is_wp_error( $result ); |
||
| 591 | $this->whitelist_error = is_wp_error( $result ); |
||
| 592 | } |
||
| 593 | |||
| 594 | if ( isset( $_POST['action'] ) && 'get_protect_key' == $_POST['action'] && wp_verify_nonce( $_POST['_wpnonce'], 'jetpack-protect' ) ) { |
||
| 595 | $result = $this->get_protect_key(); |
||
| 596 | // Only redirect on success |
||
| 597 | // If it fails we need access to $this->api_key_error |
||
| 598 | if ( $result ) { |
||
| 599 | wp_safe_redirect( Jetpack::module_configuration_url( 'protect' ) ); |
||
| 600 | exit; |
||
| 601 | } |
||
| 602 | } |
||
| 603 | |||
| 604 | $this->api_key = get_site_option( 'jetpack_protect_key', false ); |
||
| 605 | $this->user_ip = jetpack_protect_get_ip(); |
||
| 606 | } |
||
| 607 | |||
| 608 | public function configuration_head() { |
||
| 611 | |||
| 612 | /** |
||
| 613 | * Prints the configuration screen |
||
| 614 | */ |
||
| 615 | public function configuration_screen() { |
||
| 618 | |||
| 619 | /** |
||
| 620 | * If we're in a multisite network, return the blog ID of the primary blog |
||
| 621 | * |
||
| 622 | * @return int |
||
| 623 | */ |
||
| 624 | public function get_main_blog_id() { |
||
| 634 | |||
| 635 | /** |
||
| 636 | * Get jetpack blog id, or the jetpack blog id of the main blog in the main network |
||
| 637 | * |
||
| 638 | * @return int |
||
| 639 | */ |
||
| 640 | public function get_main_blog_jetpack_id() { |
||
| 651 | |||
| 652 | public function check_api_key() { |
||
| 674 | |||
| 675 | /** |
||
| 676 | * Calls over to the api using wp_remote_post |
||
| 677 | * |
||
| 678 | * @param string $action 'check_ip', 'check_key', or 'failed_attempt' |
||
| 679 | * @param array $request Any custom data to post to the api |
||
| 680 | * |
||
| 681 | * @return array |
||
| 682 | */ |
||
| 683 | function protect_call( $action = 'check_ip', $request = array () ) { |
||
| 755 | |||
| 756 | |||
| 757 | /** |
||
| 758 | * Wrapper for WordPress set_transient function, our version sets |
||
| 759 | * the transient on the main site in the network if this is a multisite network |
||
| 760 | * |
||
| 761 | * We do it this way (instead of set_site_transient) because of an issue where |
||
| 762 | * sitewide transients are always autoloaded |
||
| 763 | * https://core.trac.wordpress.org/ticket/22846 |
||
| 764 | * |
||
| 765 | * @param string $transient Transient name. Expected to not be SQL-escaped. Must be |
||
| 766 | * 45 characters or fewer in length. |
||
| 767 | * @param mixed $value Transient value. Must be serializable if non-scalar. |
||
| 768 | * Expected to not be SQL-escaped. |
||
| 769 | * @param int $expiration Optional. Time until expiration in seconds. Default 0. |
||
| 770 | * |
||
| 771 | * @return bool False if value was not set and true if value was set. |
||
| 772 | */ |
||
| 773 | function set_transient( $transient, $value, $expiration ) { |
||
| 784 | |||
| 785 | /** |
||
| 786 | * Wrapper for WordPress delete_transient function, our version deletes |
||
| 787 | * the transient on the main site in the network if this is a multisite network |
||
| 788 | * |
||
| 789 | * @param string $transient Transient name. Expected to not be SQL-escaped. |
||
| 790 | * |
||
| 791 | * @return bool true if successful, false otherwise |
||
| 792 | */ |
||
| 793 | View Code Duplication | function delete_transient( $transient ) { |
|
| 804 | |||
| 805 | /** |
||
| 806 | * Wrapper for WordPress get_transient function, our version gets |
||
| 807 | * the transient on the main site in the network if this is a multisite network |
||
| 808 | * |
||
| 809 | * @param string $transient Transient name. Expected to not be SQL-escaped. |
||
| 810 | * |
||
| 811 | * @return mixed Value of transient. |
||
| 812 | */ |
||
| 813 | View Code Duplication | function get_transient( $transient ) { |
|
| 824 | |||
| 825 | function get_api_host() { |
||
| 835 | |||
| 836 | function get_local_host() { |
||
| 862 | |||
| 863 | } |
||
| 864 | |||
| 865 | Jetpack_Protect_Module::instance(); |
||
| 866 | |||
| 867 | if ( isset( $pagenow ) && 'wp-login.php' == $pagenow ) { |
||
| 868 | Jetpack_Protect_Module::check_login_ability(); |
||
| 869 | } |
||
| 870 |
Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.
Let’s take a look at an example:
As you can see in this example, the array
$myArrayis initialized the first time when the foreach loop is entered. You can also see that the value of thebarkey is only written conditionally; thus, its value might result from a previous iteration.This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.