The whole purpose of this approach is to keep the logic in a as small classes as possible. It will allow us to maintain&refactor the code more easily because the classes will be smaller and help us to avoid creating a "God object". Technically one class will do one thing.


Structure


src/
├── Controller
│   └── CountryController.php
├── Entity
│   └── Country.php
├── Repository
│   ├── AbstractRepository.php
│   └── Country
│   └── Repository.php
└── Service
└── Country
├── Create.php
├── Delete.php
├── Read.php
└── Update.php

Files


CountryController


declare(strict_types=1);

namespace App\Controller;

use App\Service\Country\Read;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\JsonResponse;

/**
* @Route("/countries")
*/
class CountryController
{
private $read;

public function __construct(
Read $read
) {
$this->read = $read;
}

/**
* @Route("/{id}", methods="GET")
*/
public function getOne(int $id): JsonResponse
{
$result = $this->read->one($id);

return new JsonResponse($result);
}

/**
* @Route("", methods="GET")
*/
public function getAll(): JsonResponse
{
$result = $this->read->all();

return new JsonResponse($result);
}

// Create, Update and Delete endpoints go here
}

Create


declare(strict_types=1);

namespace App\Service\Country;

use App\Repository\Country\Repository;

final class Create
{
private $repository;

public function __construct(Repository $repository)
{
$this->repository = $repository;
}

public function one(int $name): void
{
}

public function batch(iterable $data): void
{
}
}

Read


declare(strict_types=1);

namespace App\Service\Country;

use App\Repository\Country\Repository;

final class Read
{
private $repository;

public function __construct(Repository $repository)
{
$this->repository = $repository;
}

public function one(int $id): iterable
{
return $this->repository->findOneById($id);
}

public function all(): iterable
{
return $this->repository->findAll();
}
}

Update


declare(strict_types=1);

namespace App\Service\Country;

use App\Repository\Country\Repository;

final class Update
{
private $repository;

public function __construct(Repository $repository)
{
$this->repository = $repository;
}

public function one(int $name): void
{
}

public function batch(iterable $data): void
{
}
}

Delete


declare(strict_types=1);

namespace App\Service\Country;

use App\Repository\Country\Repository;

final class Delete
{
private $repository;

public function __construct(Repository $repository)
{
$this->repository = $repository;
}

public function one(int $name): void
{
}
}

AbstractRepository


declare(strict_types=1);

namespace App\Repository;

use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;

abstract class AbstractRepository
{
private $entityManager;
private $entityName;

public function __construct(
EntityManagerInterface $entityManager,
string $entityName
) {
$this->entityManager = $entityManager;
$this->entityName = $entityName;
}

protected function getEntityManager(): EntityManagerInterface
{
return $this->entityManager;
}

protected function getRepository(): EntityRepository
{
return $this->entityManager->getRepository($this->entityName);
}
}

Repository


declare(strict_types=1);

namespace App\Repository\Country;

use App\Entity\Country;
use App\Repository\AbstractRepository;
use Doctrine\ORM\EntityManagerInterface;

class Repository extends AbstractRepository
{
public function __construct(EntityManagerInterface $entityManager)
{
parent::__construct($entityManager, Country::class);
}

public function findAll(): iterable
{
return $this
->getRepository()
->createQueryBuilder('c')
->getQuery()
->getArrayResult();
}

public function findOneById(int $id): iterable
{
return $this
->getRepository()
->createQueryBuilder('c')
->where('c.id = :id')
->setParameter('id', $id)
->getQuery()
->getArrayResult();
}
}