Summary and generalization of PHP code refactoring methods

Source: Internet
Author: User
Tags configuration settings
This time to bring you a summary of the PHP code refactoring methods, PHP code refactoring considerations are what, the following is the actual case, take a look.

As PHP transforms from a simple scripting language into a mature programming language, the complexity of a typical PHP application's code base increases. To control the support and maintenance of these applications, we can use a variety of test tools to automate the process. One is unit testing, which allows you to directly test the correctness of the code you write. However, the usual legacy code base is not suitable for this kind of testing. This article describes refactoring strategies for PHP code that contains common problems to simplify the process of testing with popular unit test tools while reducing the dependency on improved code base.

Brief introduction

Reviewing the evolution of PHP, we found that it has evolved from a simple dynamic scripting language that replaces popular CGI scripts to a sophisticated modern programming language. As the code base grows, manual testing has become an impossible task, large or small, and all changes to the code will have an impact on the entire application. These effects can be as small as affecting the loading or form saving of a page, or it may be a problem that is difficult to detect, or an error that occurs only under certain conditions. Even, it may cause previously fixed issues to re-appear in the application. Many test tools have been developed to solve these problems.

One popular approach is the so-called feature or acceptance test, which tests the application through the typical user interaction of the application. This is a good way to test each process in your application, but the testing process can be very slow, and it is generally not possible to test whether the underlying classes and methods work as required. At this point, we need to use another test method, which is unit testing. The goal of unit testing is to test the functionality of the application's underlying code to ensure that they are executed to produce the correct results. In general, these "growing" Web applications slowly emerge with more and more legacy code that is difficult to test over time, making it difficult for development teams to guarantee the coverage of application testing. This is often referred to as "non-testable code." Now let's look at how to identify the non-testable code in the application and how to fix the code.

Identify non-testable code

The problem domain for which the code base is not testable is usually not obvious when writing code. When writing PHP application code, people tend to write code according to the process of WEB requests, which is often a more procedural approach to application design. Rushing to complete a project or quickly fix an application can prompt developers to "take shortcuts" to quickly complete the coding. Previously, poorly written or confusing code could exacerbate the problem of non-testability in your application, as developers typically make the least-risky fix, even if it may lead to subsequent support issues. These problem domains are not discoverable by normal unit tests.

Functions that depend on global state

Global variables are handy in PHP applications. They allow you to initialize some variables or objects in your application and then use them elsewhere in your application. However, this flexibility comes at a cost, and the overuse of global variables is a common problem with non-testable code. We can see this in Listing 1.

Listing 1. Functions that depend on the global state

<?phpfunction FormatNumber ($number) {  global $decimal _precision, $decimal _separator, $thousands _separator;  if (!isset ($decimal _precision)) $decimal _precision = 2;  if (!isset ($decimal _separator)) $decimal _separator = '. ';  if (!isset ($thousands _separator)) $thousands _separator = ', ';  Return Number_format ($number, $decimal _precision, $decimal _separator, $thousands _separator);}

These global variables lead to two different problems. The first problem is that you need to consider all of these global variables in your tests to ensure that they are set to a valid value that the function can accept. The second problem is even worse because you cannot modify the state of the subsequent tests and invalidate their results, and you need to ensure that the global state is reset to the state before the test run. PHPUnit has tools that can help you back up global variables and restore their values after a test run, which helps solve this problem. However, a better approach is to enable the test class to pass the values of these global variables directly to the method. Listing 2 shows an example of using this approach.

Listing 2. Modify this function to support overriding global variables

<?phpfunction FormatNumber ($number, $decimal _precision = null, $decimal _separator = null, $thousands _separator = null ) {  if (is_null ($decimal _precision)) global $decimal _precision;  if (Is_null ($decimal _separator)) global $decimal _separator;  if (Is_null ($thousands _separator)) global $thousands _separator;  if (!isset ($decimal _precision)) $decimal _precision = 2;  if (!isset ($decimal _separator)) $decimal _separator = '. ';  if (!isset ($thousands _separator)) $thousands _separator = ', ';  Return Number_format ($number, $decimal _precision, $decimal _separator, $thousands _separator);}

Not only does this make the code more testable, but it also makes it independent of the global variables of the method. This allows us to refactor the code and no longer use global variables.

A single instance that cannot be reset

A single instance refers to a class that is designed to have only one instance at a time in an application. They are a common pattern for global objects in an application, such as database connections and configuration settings. They are often considered an application taboo, because many developers think that creating an object that is always available is not very useful, so they don't pay much attention to it. This problem is mainly due to the overuse of a single instance, as it causes a large number of non-extensible so-called God objects. But from a test point of view, the biggest problem is that they are usually immutable. Listing 3 is one such example.

Listing 3. The Singleton object we want to test

<?phpclass singleton{  private static $instance;  protected function construct () {}  private final function clone () {} public  static function getinstance ()  {
  if (!isset (self:: $instance)) {self      :: $instance = new Singleton;    }    Return self:: $instance;  }}

As you can see, when the singleton instance is first instantiated, each call to the getinstance () method actually returns the same object, it does not create a new object, and if we modify the object, it can cause serious problems. The simplest solution is to add a reset method to the object. Listing 4 shows an example of this.

Listing 4. Added the Singleton object for the Reset method

<?phpclass singleton{  private static $instance;  protected function construct () {}  private final function clone () {} public  static function getinstance ()  {
  if (!isset (self:: $instance)) {self      :: $instance = new Singleton;    }    Return self:: $instance;  }  public static function reset ()  {self    :: $instance = null;  }}

Now, we can call the Reset method before each test to ensure that we execute the initialization code of the singleton object first in each test process. In summary, it is useful to add this method to an application because we can now easily modify a single instance.

Using class constructors

A good practice for unit testing is to test only the code that needs to be tested, avoiding the creation of unnecessary objects and variables. Each object and variable that you create needs to be removed after testing. This becomes a problem for cumbersome projects such as files and database tables, because in these cases, if you need to modify the state, you must be more careful to do some cleanup after the test is complete. The biggest obstacle to sticking to this rule is the constructor of the object itself, and all of the operations it performs are unrelated to testing. Listing 5 is one such example.

Listing 5. Class with a large Singleton method

<?phpclass myclass{  protected $results;  Public function construct ()  {    $dbconn = new DatabaseConnection (' localhost ', ' user ', ' password ');    $this->results = $dbconn->query (' Select name from MyTable ');  }  Public Function Getfirstresult ()  {    return $this->results[0];  }}

Here, in order to test the FDFDFD method of the object, we eventually need to establish a database connection, add some records to the table, and then clear all the resources after the test. If testing FDFDFD does not need these things at all, the process can be too complex. Therefore, we want to modify the constructor shown in Listing 6.

Listing 6. Classes modified to ignore all unnecessary initialization logic

<?phpclass myclass{  protected $results;  Public Function construct ($init = True)  {    if ($init) $this->init ();  }  Public function init ()  {    $dbconn = new DatabaseConnection (' localhost ', ' user ', ' password ');    $this->results = $dbconn->query (' Select name from MyTable ');  }  Public Function Getfirstresult ()  {    return $this->results[0];  }}

We refactored the bulk of the code in the constructor and moved them into an init () method, which is still called by the constructor by default to avoid breaking the logic of existing code. However, now we can only pass a Boolean value to the constructor during the test to avoid calling the Init () method and all unnecessary initialization logic. This refactoring of the class also improves the code because we separate the initialization logic from the object's constructor.

Hard-coded class dependencies

As we described in the previous section, the large number of class design problems that caused the test to be difficult are focused on initializing various objects that do not require testing. In the front, we know that heavy initialization logic can be a big burden on the writing of tests (especially when the tests do not need them at all), but if we create them directly in the class methods we test, it can cause another problem. Listing 7 shows the sample code that might be causing the problem.

Listing 7. Class that initializes another object directly in one method

<?phpclass myuserclass{public  function getuserlist ()  {    $dbconn = new DatabaseConnection (' localhost '), ' User ', ' password ');    $results = $dbconn->query (' Select name from user ');    Sort ($results);    return $results;  }}

Let's say we're testing the Getuserlist method above, but our test focus is to ensure that the returned user list is sorted in alphabetical order. In this case, our problem is not whether we can get these records from the database, because we want to test whether we can sort the returned records. The problem is that because we instantiate a database connection object directly in this method, we need to perform all of these tedious operations to be able to complete the test of the method. Therefore, we want to modify the method so that the object can be inserted in the middle, as shown in Listing 8.

Listing 8. This class has a method that instantiates another object directly, but also provides an overriding method

<?phpclass myuserclass{public  function getuserlist ($dbconn = null)  {    if (!isset ($dbconn) | | |! ($DBCONN i Nstanceof databaseconnection) {      $dbconn = new DatabaseConnection (' localhost ', ' user ', ' password ');    }    $results = $dbconn->query (' Select name from user ');    Sort ($results);    return $results;  }}

You can now pass directly to an object that is compatible with the intended database connection object, and then use the object directly instead of creating a new object. You can also pass in a mock object, in which we return the value we want in a hard-coded way in some calling methods. Here, we can simulate the database Connection object Query method, so that we just need to return the results, and do not need to really query the database. Such refactoring can also improve this approach, because it allows your application to insert different database connections when needed, instead of just binding a specified default database connection.

The benefits of testable code

Obviously, writing more testable code will certainly simplify the unit testing of your PHP application (as you can see in the example shown in this article), but in the process it can also improve the design, modularity, and stability of your application. We've all seen the "spaghetti" code, which has a lot of business and performance logic in one of the main processes of a PHP application, which undoubtedly poses a serious support problem for those who use the application. In the process of making the code more testable, we refactor some of the previously problematic code, which is not only problematic in design, but also functionally problematic. By making these functions and classes more versatile, and by removing hard-coded dependencies, we make it easier to reuse other parts of the application, and we improve the reusability of the code. In addition, we have replaced code that is poorly written to better-quality code, simplifying future support for the code base.

Believe that you have read the case of this article you have mastered the method, more exciting please pay attention to the PHP Chinese network other related articles!

Recommended reading:

PHP Unlimited comments Nested implementation steps

PHP implementation of database additions and deletions to the detailed steps

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.