The difficulty of extracting includes to their own classes depends on the number and complexity of the include calls remaining in our class files. If there are very few includes and they are relatively simple, the process will be easy to complete. If there are many complex interdependent includes, the process will be relatively difficult to work through.

In general, the process is as follows:

  1. Search the classes/ directory for an include call in a class.
  2. For that include call, search the entire codebase to find how many times the included file is used.
  3. If the included file is used only once, and only in that one class:
    1. Copy the contents of the included file code as-is directly over the include call.
    2. Test the modified class, and delete the include file.
    3. Refactor the copied code so that it follows all our existing rules: no globals, no new, inject dependencies, return instead of output, and no include calls.
  4. If the included file is used more than once:
    1. Copy the contents of the included file as-is to a new class method.
    2. Replace the discovered include call with inline instantiation of the new class and invocation of the new method.
    3. Test the class in which the include was replaced to find coupled variables; add these to the new method signature by reference.
    4. Search the entire codebase for include calls to that same file, and replace each with inline instantiation and invocation; spot check modified files and test modified classes.
    5. Delete the original include file; unit test and spot check the entire legacy application.
    6. Write a unit test for the new class, and refactor the new class so that it follows all our existing rules: no globals, no superglobals, no new, inject dependencies, return-not-output, and no includes.
    7. Finally, replace each inline instantiation of the new class in each of our class files with dependency injection, testing along the way.
  5. Commit, push, notify QA.
  6. Repeat until there are no include calls in any of our classes.

We have now successfully decoupled the calling code from the include file, but this leaves us with a problem. Because the calling code executed the include code inline, the variables needed by the newly-extracted code are no longer available. We need to pass into the new class method all the variables it needs for execution, and to make its variables available to the calling code when the method is done.

To do so, we run the unit tests for the class that called the include. The tests will reveal to us what variables are needed by the new method. We can then pass these into the method by reference. Using a reference makes sure that both blocks of code are operating on the exact same variables, just as if the include was still being executed inline. This minimizes the number of changes we need to make to the calling code and the newly extracted code.

For example, say we have extracted the code from an include file to this class and method:

When we test the class that calls this code in place of an include, the tests will fail, because the $user value is not available to the new method, and the $user_messages and $user_is_valid variables are not available to the calling code. We rejoice at the failure, because it tells us what we need to do next! We add each missing variable to the method signature by reference:

We then pass the variables to the method from the calling code:

We continue running the unit tests until they all pass, adding variables as needed. When all the tests pass, we rejoice! All the needed variables are now available in both scopes, and the code itself will remain decoupled and testable.

Once we have replaced all include calls to the file, we delete the file. We should now run all of our tests and spot checks for the entire legacy application to make sure that we did not miss an include call to that file. If a test or spot check fails, we need to remedy it before continuing.

When the unit test for our newly refactored class passes, we proceed to replace all our inline instantiations with dependency injection. We do so only in our class files; in our view files and other non-class files, the inline instantiation is not much of a problem

For example, we may see this inline instantiation and invocation in a class:

We move the $validator to a property injected via the constructor, and use the property in the method:

Now we need to search the codebase and replace every instantiation of the modified class to pass the new dependency object. We run our tests as we go to make sure everything continues to operate properly.