Thus far, we have extracted our model domain logic and our view presentation logic. Only two kinds of logic remain in our page scripts:

In this chapter, we will extract a layer of Controller classes from our page scripts. These will handle the remaining action logic in our legacy application separately from our dependency-creation logic.

For an example of embedded action logic mixed with dependency logic, we can look at the ending example code from the last chapter in Appendix G, Code after Response View File. Therein, we do a little setup work, then we check some conditions and call different parts of our domain Transactions, and at the end we put together a Response object to send our response to the client.

As was the problem with mixed-in presentation logic, we cannot test the action logic separately from rest of the page script. Similarly, we cannot easily change the dependency creation logic to make the page script more testable.

We solve the problem of embedded action logic as we did with embedded presentation logic. We must extract the action code to a class of its own to separate the various remaining concerns of our page script. This will also allow us to test the action logic independently from the rest of the application.

When we have a candidate page script, we proceed to rearrange the code so that all setup and dependency-creation work is at the top, all the action logic is in the middle, and the $response->send() call is at the bottom. For our starting example here, we will use the code from the end of the last chapter as found in Appendix G, Code after Response View File.

Now we are ready to extract the action logic to our new Controller class.

First, we cut the controller block from the page script, and paste it into the __invoke() method as-is. We add one line to the end of the action logic, return $response, to send the Response object back to the calling code.

Next, we go back to the page script. In the place of the extracted action logic, we create an instance of our new Controller and call its __invoke() method, getting back a Response object.

We should always use the same variable name for the Controller object in all of our page scripts. All the examples here will use the name $controller. This is not because the name $controller is special, but because this level of consistency will be very important in a later chapter.

At this point, we have successfully decoupled the action logic from the page script. However, this decoupling fundamentally breaks the action logic, because the Controller depends on variables from the page script.

With that in mind, we begin a spot-check-and-modify cycle. We browse to or otherwise invoke the page script and discover that a particular variable is not available to the Controller. We add it to the __invoke() method signature, and spot check again. We continue adding variables to the __invoke() method until the Controller has everything it needs and our spot check runs become completely successful.

Given our rearranged page script in Appendix H, Code after Controller Rearrangement, the result of our initial extraction to a Controller can be seen in Appendix I, Code after Controller Extraction. It turns out that the extracted action logic needed four variables: $request, $response, $user, and $article_transactions.

Once we have a working block of action logic in the __invoke() method, we will convert the method parameters into constructor parameters so that the Controller can use dependency injection.

First, we cut the __invoke() parameters and paste them as a whole into the __construct() parameters. We then edit the class definition and __construct() method to retain the parameters as properties.

Next, we modify the __invoke() method to use the class properties instead of the method parameters. That means prefixing each of the needed variables with $this->.

Then, we go back to the page script. We cut the arguments to the __invoke() call, and paste them into the Controller instantiation.

Now that we have converted the Controller to dependency injection, we need to spot check the page script again to make sure everything works properly. If it does not, we need to undo and redo our conversion until our tests pass.

At this point, we can remove the /* DEPENDENCY */, /* CONTROLLER */, and /* FINISHED */ comments. They have served their purpose and are no longer needed.

Given the __invoke() usage in Appendix I, Code after Controller Extraction, we can see what converting the Controller to dependency injection looks like in Appendix J, Code after Controller Dependency Injection. We have moved the Controller __invoke() parameters up to __construct(), retained them as properties, used the new properties in the __invoke() method body, and modified the page script to pass the needed variables at new time instead of __invoke() time.

Once we have a working page script, we may wish to commit our work yet again so that we have a known correct state to which we can revert later, if needed.

In the examples, we remove all parameters from the __invoke() method. However, sometimes we will want to pass a parameter to that method as last-minute information for the controller logic.

In general, we should avoid doing so at this point in our modernization process. This is not because it is a poor practice, but because we need a very high level of consistency in our controller invocations for a later modernization step. The most-consistent thing is for there to be no __invoke() parameters at all.

If we need to pass extra information to the Controller, we should do so via the constructor. This is especially the case when we are passing request values.

For example, instead of this:

We could do this:

The __invoke() method body would then use $this->request->get['item_id'].