<?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