I've taken over a Symfony 2.6 project and I'm busy trying to refactor the fat controllers by as much as possible.
Currently the jist of all the controllers follow a similar structure
/**
* @Method({"GET"})
* @Route("/", name="homepage")
* @Template()
*/
public function indexAction()
{
//Initialize the API
$api = $this->get('core_bundle.api_wrapper')->getApi();
//Retrieve data from various end points
$result_set = $api->get('/rest/resource');
$result_set2 = $api->get('/rest/resource2');
$result_set3 = $api->get('/rest/resource3');
//etc.. etc..
//Once all api calls have been completed, do some manipulation of the data if required
$result_set = DataHelper::getInstance()->setReadFlags($result_set);
//The api resultset keys are not always standard (It's just the way it is Stackoverflow, I cannot change the API code)
$result_set2 = Standardize::getInstance()->standardizeKeys($result_set2);
$result_set2 = DataHelper::getInstance()->setReadFlags($result_set2);
//Create the social links from the existing data
$result_set3 = SocialHelper::getInstance()->createSocialLinks($result_set3);
return [$result_set, $result_set2, $result_set3];
}
I'm struggling to find a good pattern to apply to this type of scenario. I need to clean up the controllers and move as much business logic out of it because some of the controllers have 100+ lines of code.
The problem I have is:
I feel that creating multiple models then passing in the API Result set, then calling a function to manipulate each result set will result in a smaller controller action, but confusing to follow as I'll be calling multiple models multiple times per result set.
It will end up in having to create a lot of model classes if I want to keep inline with Single Responsibility.
Any guidance will be appreciated.
You should create a service, pass it the endpoints depending on your need, make your logic into, and return the data to your controller.
Like this, your controller will just return the response containing the data fetched by your service.
It will be very lighten.
The service can look like this :
<?php
namespace AppBundle\Services;
class EndpointManager
{
public function __construct(YourApiService $apiWrapper)
{
$this->apiWrapper = $apiWrapper;
}
public function fetch(array $endpoints)
{
$data = array();
// Do your logic here
foreach ($endpoints as $end) {
$data[$end] = $apiWrapper->get($end);
}
// ...
// Return the data fetched
return $data;
}
}
Declare it with all services you need as argument :
services:
endpoint_manager:
class: AppBundle\Services\EndpointManager
arguments:
apiWrapper: "@core_bundle.api_wrapper"
(If you pass more arguments, don't forget to set it in the constructor of your service)
Then, use it :
$apiManager = $this->get('endpoint_manager');
return $apiManager->fetch(['api/endpoint1', 'api/endpoint2']);
For more informations, see the Services documentation.
Hope this helps you.
As with so many things, it depends on the details of your project. @chalasr has some very good suggestions.
I would look at defining my controllers as services. That would allow you to replace many of those get calls with constructor injection and might clean things up a bit.
I would then go one step further and replace my controller classes with action classes. I suspect that some of your controllers implement multiple actions making for quite a large file? And while some of the actions are similar they are different enough to start to get a bit confusing?
An action class differs from a controller class in that an action class implements one and only one action method. The method still generates a response using the request as input. You end up with more classes but each class becomes smaller and focuses on only one thing. I also found that action code from different controller classes can often be shared once the code is broken out into it's own class.
On a different note, I would also define your singletons (DataHelper, SocialHelper) as Symfony services just for consistency.