When PHP Framework Sucks Series: Business logic free Controllers
Tags: php, webdev, showdev, testing
Published: 2018-12-18 07:38:17

---
title: When PHP Framework Sucks Series: Business logic free Controllers
published: true
series: When PHP Framework Sucks
description: Rule number one - Do not put business logic in controllers at any cost
tags: php, webdev, showdev, testing
cover_image: https://thepracticaldev.s3.amazonaws.com/i/eycwqg7yhsop6oe37ek1.jpg
---
###Business logic in controller
Let’s start with Controllers and your business logic. *Rule number one: Do not put business logic in controllers at any cost!* Business logic in controller is particularly problematic because controllers usually derive from the framework. Also, when they extend the framework base controller class, they usually know too much (containers). As the name implies, business logic is your code which you want to reuse and make independent of libraries and frameworks. It should be well separated and encapsulated since you might probably call the same method from different places.

Here is the example of Symfony controller [best practice page](https://symfony.com/doc/current/best_practices/controllers.html):

```php
namespace App\Controller;

use App\Entity\Post;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

class DefaultController extends AbstractController
{
    /**
     * @Route("/", name="homepage")
     */
    public function index()
    {
        $posts = $this->getDoctrine()
            ->getRepository(Post::class)
            ->findLatest();

        return $this->render('default/index.html.twig', [
            'posts' => $posts,
        ]);
    }
}
```
Here, business logic sits in an inappropriate place. Instead, we should have a class which returns us the latest posts. We can call that class for the sake of simplicity PostsFromDatabase. 

```php
namespace ExampleNamespace;

class PostsFromDatabase
{
    /** @var ManagerRegistry**/
    private $doctrine;
    
    public function getDoctrine()
    {
        return $this->doctrine;
    }

    public function __construct(ManagerRegistry $doctrine)
    {
        $this->doctrine = $doctrine;
    }
    
    public function getLatest()
    {
        $posts = $this->getDoctrine()
            ->getRepository(Post::class)
            ->findLatest();

        return $posts;

    }

}
```
Test for this class, in this case, is straightforward 
```php
namespace ExampleNamespace;

use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\ORM\EntityRepository;

class PostsFromDatabaseTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers \ExampleNamespace\PostsFromDatabase
     */
    public function testGetLatestShouldReturnSameCollectionAsFindLatestReturns()
    {
        $managerRegistryMock = $this->createMock(ManagerRegistry::class);
        $repository = $this->createMock(EntityManagerInterface::class);

        $managerRegistryMock
            ->expects($this->once())
            ->method('getRepository')
            ->with(Post::class)
            ->willReturn($repository);
        $repository
            ->expects($this->once())
            ->method('findLatest')
            ->willReturn(['post1', 'post2']);

        $postsFromDatabase = new PostsFromDatabase($managerRegistryMock);
        $latestPosts = $postsFromDatabase->getLatest();

        $this->assertEquals(['post1', 'post2'], $latestPosts);
    }
}

```

Now our controller looks like this

```php
namespace App\Controller;

use ExampleNamespace\PostsFromDatabase
use App\Entity\Post;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

class DefaultController extends AbstractController
{
    /** @var PostsFromDatabase **/
    private $postsFromDatabase;

    public function getPostsFromDatabase(): PostsFromDatabase
    {
        if (null === $this->postsFromDatabase) {
            $this->postsFromDatabase = new PostsFromDatabase($this->getDoctrine());
        }

        return $this->postsFromDatabase;
    }

    public function setPostsFromDatabase(PostsFromDatabase $postsFromDatabase)
    {
        $this->postsFromDatabase = $postsFromDatabase;
    }

    /**
     * @Route("/", name="homepage")
     */
    public function index()
    {
        $posts = $this->getPostsFromDatabase()->getLatest();

        return $this->render('default/index.html.twig', [
            'posts' => $posts,
        ]);
    }
}
```
The controller is free of business logic and now only deal with routing, rendering and know which service to call. But there is one more benefit you might not spot so far:
###Half of our code, now have a unit tests 🤩🤩🤩🤩🤩🤩
And you should not stop there, now controller is super easy to test (that's why I left 'set' and 'get' methods for PostsFromDatabase), but that I will cover in the next chapter of this series.

Stay here for a second. Did you ever saw that framework tutorials in their examples, implies you should test your code? Most modern frameworks have a "placeholder" for tests. But I don't remember that I saw one framework which has implemented unit test measure, tests required, or tests in their tutorial examples. I'll leave this question here for some future blog posts.