Function reuse when using Class:method for routing

In my router, I have this:

$this->get('/types', \Core\Controller\TicketController::class . ':getTicketTypes');

Which maps to this function in my controller:

public function getTicketTypes($request, $response, $args) {
  $trans = $this->db->get('ticket_types');
  return $response->withJson($trans);
}

I now want to create a separate function in my controller that can call getTicketTypes as well as other methods so I can combine a number of calls into one returned JSON object like so:

public function getCommonData($request, $response, $args) {

  $statuses = $this->getTicketStatuses($request, $response, $args);
  $types = $this->getTicketTypes($request, $response, $args);

    $data = array(
      'statuses' => $statuses,
      'types' => $types
    );

    return $response->withJson($data);

}

This does not work because the getTicketTypes function is already configured to always return a withJson response. How do I make getTicketTypes reusable in my case? Thanks.

Hello @eddieaich

Be careful, that you don’t mix the MVC layers together. The controller layer should be very thin, and the model layer can be “fat”. This means, put all all the “businsess logic” (calculation) into the “domain service layer” and all the database queries into the “data access layer” Example. The controller just calls the service and the services fetches or updates data in the database (via the Repository). Then, within the controller, use the return value of the service to generate the json response. Then you can reuse all the Repository methods like you want.

Data flow: Client request > Router > Controller Action > Service class > Repository > and back

In your case I would create a Repository class e.g. TicketRepository.php with a public method like getTicketStatuses().

Example:

namespace App\Domain\Ticket;

class TicketRepository
{
    // e.g. PDO
    protected $connection;

    public function __construct(Connection $connection)
    {
        $this->connection= $connection;
    }
 
    public function getTicketStatuses(): array
    {
        // fetch data here 
        // $this->connection->...
        return [];
    }

    public function getTicketTypes(): array
    {
        // fetch data here 
        // $this->connection->...
        return [];
    }
}

If you have more logic (calculation, validation, logging) then create a service class and call it from the controller.

namespace App\Domain\Ticket;

class TicketService
{
    protected $repository;

    public function __construct(TicketRepository $repository)
    {
        $this->repository = $repository;
    }
 
    public function getCommonData(): array
    {
        // do something more complex here, like validation etc.
        return [
            'statuses' => $this->repository->getTicketStatuses(),
            'types' => $this->repository->getTicketTypes(),
       ];
    }
}
1 Like

Pardon my ignorance, but this seems overly bloated to me. If all the controller does is call the service, then what’s the point of a router which just calls the controller?

In other words, why separate controller and service? Or, why not just go from router to service?

Why? because of your use case

controller should return response on request
you are mixing response from one controller to create different response
imagine you have no class like “controller” but you have anonymous function or any other callable which is your controller - you can not do what you are trying in this architecture

that is why “business logic should be overly bloated”

it gives you flexibility exactly for that scenario
imagine you have some other access point to your app like CLI - which is basically same thing as your controller but from console ; do you want to reuse here your controllers and view answers in json? no, you want reuse data from business layer and view them differently

1 Like

I just discovered this real-world example of a slim implementation on github and their controllers are quite heavy. They don’t have a concept of a service and repository layer - Controller Example. They do have a Transformer layer that does data transformations and processing Transformer example.

So, suffice to say, perusing through these examples makes me a bit more confused about how to go about things. I think my main issue is that the way I have defined my routes implies exactly 3 arguments so I can’t reuse this. I think I’ll just need to add an intermediate layer as you mentioned to abstract it out one level. I just am confused about terminologies and implementations / opinions.

If needed, you can extend the above concept and extract out the domain result (payload) using a Responder and optional a Transformer within the Responder. I just tried to keep the example “as simple as possible”. :wink:

Thank you. Getting different perspectives was very useful and I think I’ve got it. Thanks again!!

1 Like