Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony 3 - Outsourcing Controller Code into Service Layer

I am very new in Symfony 3 and I want to avoid the business logic in my controllers. What I have done so far is this:

    <?php

    namespace RestBundle\Controller;

    use RestBundle\Entity\Attribute;
    use RestBundle\Entity\DistributorProduct;
    use RestBundle\Entity\AttributeValue;
    use RestBundle\Entity\ProductToImage;
    use Symfony\Component\HttpFoundation\Request;
    use RestBundle\Entity\Product;
    use FOS\RestBundle\Controller\FOSRestController;


        /**
         * Product controller.
         *
         */
        class ProductController extends FOSRestController
        {

                /**
                 * Creates a new Product entity.
                 *
                 */
                public function createProductAction(Request $request)
                {
                    // Doctrine Manager
                    $em = $this->getDoctrine()->getManager();

                    // todo: get the logged in distributor object
                    $distributor = $em->getRepository('RestBundle:Distributor')->find(1);

                    // Main Product
                    $product = new Product();
                    $product->setEan($request->get('ean'));
                    $product->setAsin($request->get('asin'));
                    $em->persist($product);

                    // New Distributor Product
                    $distributorProduct = new DistributorProduct();
                    $distributorProduct->setDTitle($request->get('title'));
                    $distributorProduct->setDDescription($request->get('description'));
                    $distributorProduct->setDPrice($request->get('price'));
                    $distributorProduct->setDProductId($request->get('product_id'));
                    $distributorProduct->setDStock($request->get('stock'));
                    // Relate this distributorProduct to the distributor
                    $distributorProduct->setDistributor($distributor);
                    // Relate this distributorProduct to the product
                    $distributorProduct->setProduct($product);
                    $em->persist($distributorProduct);

                    // Save it
                    $em->flush();

                    $response = $em->getRepository('RestBundle:Product')->find($product->getUuid());

                    return array('product' => $response);
                }
            }
        }

I know that this is not good code because all the business logic is in the controller.

But how and where can I put this code (set requests into model, persist and flush with doctrine, etc) into a service or use dependency injection for it? Or is service for this purpose not the right way?

I know this page and tutorial http://symfony.com/doc/current/best_practices/business-logic.html but it is not clearify for me where to put CRUD Actions. Do ONE service for save a whole project with all the related entities? And use the Symfony\Component\HttpFoundation\Request; in a service? So put the whole controller code where I get the request and assign to the models into a service? Thanks

like image 988
goldlife Avatar asked Jul 13 '16 08:07

goldlife


1 Answers

UPDATE 2: I've extended this answer in a post. Be sure to check it!

UPDATE: use Symfony 3.3 (May 2017) with PSR-4 service autodiscovery and PHP 7.1 types.


I will show you how I lecture controller repository decoupling in companies.

There are 2 simple rules:

  • there are no signs about Doctrine in controller
  • there is no new in the controller (static, non-DI approach) (there is now also Sniff for that)

Let's apply this to your controller

Note: this is pseudo code, I haven't tried that, but the logic should be easy to understand. If this is too many change, just check the steps 3 and 4.

We decouple create and save process. For both entities. This will lead us to 4 services:

# app/config/services.yml
services:
    _defaults:  
        autowire: true

    App\Domain\:
        resource: ../../App/Domain
    App\Repository\:
        resource: ../../App/Repository

1. Product Factory to decouple create process

// ProductFactory.php
namespace App\Domain\Product;

final class ProductFactory
{
    public function createFromRequest(Request $request): Product
    {
        $product = new Product();
        $product->setEan($request->get('ean'));
        $product->setAsin($request->get('asin'));
        return $product;
    }
}

2. Distributor Product Factory to decouple create process

// DistributorProductFactory.php
namespace App\Domain\Product;

final class DistributorProductFactory
{
    public function createFromRequestProductAndDistributor(
        Request $request,
        Product $product,
        Distributor $distributor
    ): DistributorProduct {
        $distributorProduct = new DistributorProduct();
        $distributorProduct->setDTitle($request->get('title'));
        $distributorProduct->setDDescription($request->get('description'));
        $distributorProduct->setDPrice($request->get('price'));
        $distributorProduct->setDProductId($request->get('product_id'));
        $distributorProduct->setDStock($request->get('stock'));

        // Relate this distributorProduct to the product
        $distributorProduct->setProduct($product);

        // Relate this distributorProduct to the product
        $distributorProduct->setDistributor($distributor);

        return $distributorProduct;
    }
}

3. Create own ProductRepository service

// ProductRepository.php
namespace App\Repository;

use RestBundle\Entity\Product;
use Doctrine\ORM\EntityManagerInterface;

final class ProductRepository
{
    /**
     * @var EntityManagerInterface
     */
    private $entityManager;

    public funtion __construct(EntityManagerInterface $entityManager)    
    {
        $this->entityManager = $entityManager;
    }

    public function save(Product $product): void
    {
        $this->entityManager->persist($product);
        $this->entityManager->flush();
    }
}

4. Create own DistributorProductRepository service

// DistributorProductRepository.php
namespace App\Repository;

use RestBundle\Entity\DistributorProduct;
use Doctrine\ORM\EntityManagerInterface;

final class DistributorProductRepository
{
    /**
     * @var EntityManagerInterface
     */
    private $entityManager;

    public funtion __construct(EntityManagerInterface $entityManager)    
    {
        $this->entityManager = $entityManager;
    }

    public function save(DistributorProduct $distributorProduct): void
    {
        $this->entityManager->persist($distributorProduct);
        $this->entityManager->flush();
    }
}

5. And we finish with nice and thin controller!

namespace RestBundle\Controller;

use Symfony\Component\HttpFoundation\Request;
use FOS\RestBundle\Controller\FOSRestController;

final class ProductController extends FOSRestController
{
    // get here dependencies via constructor

    public function createProductAction(Request $request): array
    {
        // todo: get the logged in distributor object
        $distributor = $em->getRepository('RestBundle:Distributor')->find(1);

        $product = $this->productFactory->createFromRequest($request);
        $distributorProduct = $this->distributorProductFactory->createFromRequestProductAndDistributor(
            $request,
            $product,
            $distributor
        );

        $this->productRepository->save($product);
        $this->distributorProductRepository->save($product);

        return [
            'product' => $product
        ];
    }
}

That's all!

like image 90
Tomas Votruba Avatar answered Oct 16 '22 11:10

Tomas Votruba