

<?php require_once __DIR__ . '/../../vendor/autoload.php'; class ListEntriesTest extends PHPUnit_Framework_TestCase { public function testEntriesNotExists() { $request = new FakeViewEntriesRequest(); $response = new FakeViewEntriesResponse(); $useCase = new ViewEntriesUseCase(); $useCase->process($request, $response); $this->assertEmpty($response->entries); } } useCase , input ports are request , output ports are response . All useCase contain a method in which there is a type hint for a specific request and response interface. <?php require_once __DIR__ . '/../../vendor/autoload.php'; use BlackScorp\GuestBook\Fake\Request\FakeViewEntriesRequest; use BlackScorp\GuestBook\Fake\Response\FakeViewEntriesResponse; use BlackScorp\GuestBook\UseCase\ViewEntriesUseCase; class ListEntriesTest extends PHPUnit_Framework_TestCase { public function testEntriesNotExists() { $request = new FakeViewEntriesRequest(); $response = new FakeViewEntriesResponse(); $useCase = new ViewEntriesUseCase(); $useCase->process($request, $response); $this->assertEmpty($response->entries); } public function testCanSeeEntries() { $request = new FakeViewEntriesRequest(); $response = new FakeViewEntriesResponse(); $useCase = new ViewEntriesUseCase(); $useCase->process($request, $response); $this->assertNotEmpty($response->entries); } } useCase . <?php namespace BlackScorp\GuestBook\UseCase; use BlackScorp\GuestBook\Request\ViewEntriesRequest; use BlackScorp\GuestBook\Response\ViewEntriesResponse; class ViewEntriesUseCase { public function process(ViewEntriesRequest $request, ViewEntriesResponse $response){ } }  <?php namespace BlackScorp\GuestBook\UseCase; use BlackScorp\GuestBook\Request\ViewEntriesRequest; use BlackScorp\GuestBook\Response\ViewEntriesResponse; class ViewEntriesUseCase { public function process(ViewEntriesRequest $request, ViewEntriesResponse $response){ $entries = $this->entryRepository->findAllPaginated($request->getOffset(), $request->getLimit()); if(!$entries){ return; } foreach($entries as $entry){ $entryView = $this->entryViewFactory->create($entry); $response->addEntry($entryView); } } } Entry entity into a view? The fact is that an entity should not leave the limits of the useCase level. We can find it only with the help of the repository, if necessary, change / copy and put it back into the repository. When we begin to move the entity to the outer layer, it is better to add additional methods to improve the interaction. However, in essence, there must be only the main business logic. $entryView = new EntryView($entry); $response->addEntry($entryView); useCase logic we need another view object, then we will have to rewrite the code. And with the help of the factory, you can easily implement different types with different formatting logic, and the same useCase will be used.useCase dependencies: $entryViewFactory and $entryRepository . The methods of these dependencies are also known. EntryViewFactory creates a method that gets the EntryEntity , and the EntryRepository has a findAll() method that returns an EntryEntities array. Now you can create interfaces for the methods and apply them to useCase .EntryRepository looks like this: <?php namespace BlackScorp\GuestBook\Repository; interface EntryRepository { public function findAllPaginated($offset,$limit); } useCase : <?php namespace BlackScorp\GuestBook\UseCase; use BlackScorp\GuestBook\Repository\EntryRepository; use BlackScorp\GuestBook\Request\ViewEntriesRequest; use BlackScorp\GuestBook\Response\ViewEntriesResponse; use BlackScorp\GuestBook\ViewFactory\EntryViewFactory; class ViewEntriesUseCase { /** * @var EntryRepository */ private $entryRepository; /** * @var EntryViewFactory */ private $entryViewFactory; /** * ViewEntriesUseCase constructor. * @param EntryRepository $entryRepository * @param EntryViewFactory $entryViewFactory */ public function __construct(EntryRepository $entryRepository, EntryViewFactory $entryViewFactory) { $this->entryRepository = $entryRepository; $this->entryViewFactory = $entryViewFactory; } public function process(ViewEntriesRequest $request, ViewEntriesResponse $response) { $entries = $this->entryRepository->findAllPaginated($request->getOffset(), $request->getLimit()); if (!$entries) { return; } foreach ($entries as $entry) { $entryView = $this->entryViewFactory->create($entry); $response->addEntry($entryView); } } }  <?php require_once __DIR__ . '/../../vendor/autoload.php'; use BlackScorp\GuestBook\Fake\Request\FakeViewEntriesRequest; use BlackScorp\GuestBook\Fake\Response\FakeViewEntriesResponse; use BlackScorp\GuestBook\UseCase\ViewEntriesUseCase; use BlackScorp\GuestBook\Entity\EntryEntity; use BlackScorp\GuestBook\Fake\Repository\FakeEntryRepository; use BlackScorp\GuestBook\Fake\ViewFactory\FakeEntryViewFactory; class ListEntriesTest extends PHPUnit_Framework_TestCase { public function testEntriesNotExists() { $entryRepository = new FakeEntryRepository(); $entryViewFactory = new FakeEntryViewFactory(); $request = new FakeViewEntriesRequest(); $response = new FakeViewEntriesResponse(); $useCase = new ViewEntriesUseCase($entryRepository, $entryViewFactory); $useCase->process($request, $response); $this->assertEmpty($response->entries); } public function testCanSeeEntries() { $entities = []; $entities[] = new EntryEntity(); $entryRepository = new FakeEntryRepository($entities); $entryViewFactory = new FakeEntryViewFactory(); $request = new FakeViewEntriesRequest(); $response = new FakeViewEntriesResponse(); $useCase = new ViewEntriesUseCase($entryRepository, $entryViewFactory); $useCase->process($request, $response); $this->assertNotEmpty($response->entries); } } request/response . <?php namespace BlackScorp\GuestBook\Fake\Repository; use BlackScorp\GuestBook\Repository\EntryRepository; class FakeEntryRepository implements EntryRepository { private $entries = []; public function __construct(array $entries = []) { $this->entries = $entries; } public function findAllPaginated($offset, $limit) { return array_splice($this->entries, $offset, $limit); } }  <?php namespace BlackScorp\GuestBook\Fake\ViewFactory; use BlackScorp\GuestBook\Entity\EntryEntity; use BlackScorp\GuestBook\Fake\View\FakeEntryView; use BlackScorp\GuestBook\View\EntryView; use BlackScorp\GuestBook\ViewFactory\EntryViewFactory; class FakeEntryViewFactory implements EntryViewFactory { /** * @param EntryEntity $entity * @return EntryView */ public function create(EntryEntity $entity) { $view = new FakeEntryView(); $view->author = $entity->getAuthor(); $view->text = $entity->getText(); return $view; } } useCase class, except in the test class.useCase processing to the useCase private function. <?php require_once __DIR__ . '/../../vendor/autoload.php'; use BlackScorp\GuestBook\Entity\EntryEntity; use BlackScorp\GuestBook\Fake\Repository\FakeEntryRepository; use BlackScorp\GuestBook\Fake\ViewFactory\FakeEntryViewFactory; use BlackScorp\GuestBook\Fake\Request\FakeViewEntriesRequest; use BlackScorp\GuestBook\Fake\Response\FakeViewEntriesResponse; use BlackScorp\GuestBook\UseCase\ViewEntriesUseCase; class ListEntriesTest extends PHPUnit_Framework_TestCase { public function testCanSeeEntries() { $entries = [ new EntryEntity('testAuthor','test text') ]; $response = $this->processUseCase($entries); $this->assertNotEmpty($response->entries); } public function testEntriesNotExists() { $entities = []; $response = $this->processUseCase($entities); $this->assertEmpty($response->entries); } /** * @param $entities * @return FakeViewEntriesResponse */ private function processUseCase($entities) { $entryRepository = new FakeEntryRepository($entities); $entryViewFactory = new FakeEntryViewFactory(); $request = new FakeViewEntriesRequest(); $response = new FakeViewEntriesResponse(); $useCase = new ViewEntriesUseCase($entryRepository, $entryViewFactory); $useCase->process($request, $response); return $response; } } useCase ready for use into a DI container and use it inside the framework. In this case, the logic will not depend on the framework.useCase . The logic will be independent of the database.request/response CLI objects and pass them to the same useCase used inside the controller. In this case, the logic will not be platform dependent.useCase , each of which changes the response object. class MainController extends BaseController { public function indexAction(Request $httpRequest) { $indexActionRequest = new IndexActionRequest($httpRequest); $indexActionResponse = new IndexActionResponse(); $this->getContainer('ViewNavigation')->process($indexActionRequest, $indexActionResponse); $this->getContainer('ViewNewsEntries')->process($indexActionRequest, $indexActionResponse); $this->getContainer('ViewUserAvatar')->process($indexActionRequest, $indexActionResponse); $this->render($indexActionResponse); } }   public function testCanSeeFiveEntries(){ $entities = []; for($i = 0;$i<10;$i++){ $entities[] = new EntryEntity('Author '.$i,'Text '.$i); } $response = $this->processUseCase($entities); $this->assertNotEmpty($response->entries); $this->assertSame(5,count($response->entries)); } process method in useCase , and at the same time rename the findAll method to findAllPaginated . public function process(ViewEntriesRequest $request, ViewEntriesResponse $response){ $entries = $this->entryRepository->findAllPaginated($request->getOffset(), $request->getLimit()); //.... } request interface.findAllPaginated method will change a bit in the repository:  public function findAllPaginated($offset, $limit) { return array_splice($this->entries, $offset, $limit); } request object to tests. Also for the request object constructor you will need a limit parameter. Thus, we will make setup create a constraint together with a new instance.  public function testCanSeeFiveEntries(){ $entities = []; for($i = 0;$i<10;$i++){ $entities[] = new EntryEntity(); } $request = new FakeViewEntriesRequest(5); $response = $this->processUseCase($request, $entities); $this->assertNotEmpty($response->entries); $this->assertSame(5,count($response->entries)); } setPage method to the request object. <?php namespace BlackScorp\GuestBook\Fake\Request; use BlackScorp\GuestBook\Request\ViewEntriesRequest; class FakeViewEntriesRequest implements ViewEntriesRequest{ private $offset = 0; private $limit = 0; /** * FakeViewEntriesRequest constructor. * @param int $limit */ public function __construct($limit) { $this->limit = $limit; } public function setPage($page = 1){ $this->offset = ($page-1) * $this->limit; } public function getOffset() { return $this->offset; } public function getLimit() { return $this->limit; } }   public function testCanSeeFiveEntriesOnSecondPage(){ $entities = []; $expectedEntries = []; $entryViewFactory = new FakeEntryViewFactory(); for($i = 0;$i<10;$i++){ $entryEntity = new EntryEntity(); if($i >= 5){ $expectedEntries[]=$entryViewFactory->create($entryEntity); } $entities[] =$entryEntity; } $request = new FakeViewEntriesRequest(5); $request->setPage(2); $response = $this->processUseCase($request,$entities); $this->assertNotEmpty($response->entries); $this->assertSame(5,count($response->entries)); $this->assertEquals($expectedEntries,$response->entries); } FakeEntryViewFactory transfer FakeEntryViewFactory to a setup method, and it is ready. The last test class looks like this: <?php require_once __DIR__ . '/../../vendor/autoload.php'; use BlackScorp\GuestBook\Entity\EntryEntity; use BlackScorp\GuestBook\Fake\Repository\FakeEntryRepository; use BlackScorp\GuestBook\Fake\Request\FakeViewEntriesRequest; use BlackScorp\GuestBook\Fake\Response\FakeViewEntriesResponse; use BlackScorp\GuestBook\Fake\ViewFactory\FakeEntryViewFactory; use BlackScorp\GuestBook\UseCase\ViewEntriesUseCase; class ListEntriesTest extends PHPUnit_Framework_TestCase { public function testEntriesNotExists() { $entries = []; $request = new FakeViewEntriesRequest(5); $response = $this->processUseCase($request, $entries); $this->assertEmpty($response->entries); } public function testCanSeeEntries() { $entries = [ new EntryEntity('testAuthor', 'test text') ]; $request = new FakeViewEntriesRequest(5); $response = $this->processUseCase($request, $entries); $this->assertNotEmpty($response->entries); } public function testCanSeeFiveEntries() { $entities = []; for ($i = 0; $i < 10; $i++) { $entities[] = new EntryEntity('Author ' . $i, 'Text ' . $i); } $request = new FakeViewEntriesRequest(5); $response = $this->processUseCase($request, $entities); $this->assertNotEmpty($response->entries); $this->assertSame(5, count($response->entries)); } public function testCanSeeFiveEntriesOnSecondPage() { $entities = []; $expectedEntries = []; $entryViewFactory = new FakeEntryViewFactory(); for ($i = 0; $i < 10; $i++) { $entryEntity = new EntryEntity('Author ' . $i, 'Text ' . $i); if ($i >= 5) { $expectedEntries[] = $entryViewFactory->create($entryEntity); } $entities[] = $entryEntity; } $request = new FakeViewEntriesRequest(5); $request->setPage(2); $response = $this->processUseCase($request, $entities); $this->assertNotEmpty($response->entries); $this->assertSame(5, count($response->entries)); $this->assertEquals($expectedEntries, $response->entries); } /** * @param $request * @param $entries * @return FakeViewEntriesResponse */ private function processUseCase($request, $entries) { $repository = new FakeEntryRepository($entries); $factory = new FakeEntryViewFactory(); $response = new FakeViewEntriesResponse(); $useCase = new ViewEntriesUseCase($repository, $factory); $useCase->process($request, $response); return $response; } } useCase , which led to interfaces, and they led to fake implementations. I repeat that the source code for this publication can be downloaded from Github . Pay attention to the tags denoting different stages.Source: https://habr.com/ru/post/277543/
All Articles