Partindo de um código legado com uma organização frágil e pouco reutilizável iremos aprimorá-lo de forma incremental e com foco nas regras de negócio. Utilizaremos princípios de projeto orientado a objetos para torná-lo mais fácil de testar e reutilizar deixando o seu código mais robusto e escalável.
7. Lista de Desejos para Produtos
Eu como um usuário convidado, quero adicionar um produto
indisponível dentro da minha lista de desejos, de modo que eu
receba uma notificação quando este estiver disponível.
Objetivo
reposição de estoque inteligente e
estreitar relacionamento com cliente
user story
25. // lib/models/wishlist.php
function addItemIntoWishlist(array $data);
function createWishListItem(array $data);
function findAllWishlistsToNotify();
function findAllWishProducts($email);
function isValidWishList(array $data);
function removeWishItem($id);
function wishlistNotified($id);
php estruturado
37. sem um front controller
GET /wishlist.php?email=email@test.com
com index.php como um front controller
GET /index.php/wishlist/email@test.com
com regras de sobrescrita no servidor web
GET /wishlist/email@test.com
php estruturado
52. PHPUnit 5.4.6 by Sebastian Bergmann and contributors.
................................................... 59 / 59 (100%)
Time: 349 ms, Memory: 6.00MB
OK (59 tests, 126 assertions)
testes automatizados
php orientado a objetos
53. PHPUnit 5.4.6 by Sebastian Bergmann and contributors.
..................................F................ 59 / 59 (100%)
Time: 374 ms, Memory: 6.00MB
There was 1 failure:
1) ProductTest::testUpdateProductFailed
Failed asserting that exception of type "ProductException" is
thrown.
FAILURES!
Tests: 59, Assertions: 125, Failures: 1.
testes automatizados
php orientado a objetos
54. não confie em arrays
evite abreviações
php orientado a objetos
55. /**
* Creates a new wish list data.
* @param array $data
* @return array
*/
function newWishList(array $data)
{
return array(
'email' => isset($data['email']) ? $data['email'] : null),
'product_id' => $data['product_id'],
'status' => 'P',
);
}
php orientado a objetos
58. class Status
{
const PENDING = 'P';
const SENT = 'S';
private $status;
public function __construct($status)
{
$this->validate($status);
$this->status = $status;
}
// … other methods
public function equalsTo(Status $status)
{
return $status === $this;
}
}
objetos de valor
php orientado a objetos
61. class Wishlist
{
public function __construct(
Id $id,
Email email,
WishlistItem $item,
Status $status
) {
$this->id = $id;
$this->email = $email;
$this->item = $item;
$this->status = $status;
}
}
entidades
php orientado a objetos
62. class Wishlist
{
public function __construct(
Id $id,
Email email,
WishlistItem $item,
Status $status
) {
$this->id = $id;
$this->email = $email;
$this->item = $item;
$this->status = $status;
}
}
por que um
objeto Item?
entidades
php orientado a objetos
63. class Wishlist
{
public function __construct(
Id $id,
Email email,
WishlistItem $item,
Status $status
) {
$this->id = $id;
$this->email = $email;
$this->item = $item;
$this->status = $status;
}
}
por que um
objeto Item?
Produtos
Presente
entidades
php orientado a objetos
64. interface WishlistItem
{
public function getId();
}
class WishlistProduct implements WishlistItem
{
public function __construct(
Id $id,
$name,
Available $availability
) {
$this->id = $id;
$this->name = $name;
$this->available = $availability;
}
}
entidades
php orientado a objetos
67. interface WishlistRepository
{
public function add(Wishlist $wishlist);
public function delete(Wishlist $wishlist);
public function find($id);
public function findAllByEmail($email);
public function findAllToNotify();
}
repositórios
php orientado a objetos
68. namespace DevelopBusinessApplicationProductWishlistRepositories;
class PdoRepository implements WishlistRepository
{
public function __construct(PDO $driver, Factory $factory);
public function findAllByEmail($email)
{
$query = "find all items by email";
$stm = $this->driver->prepare($query);
$stm->execute([$email]);
return $stm->fetchAll(
PDO::FETCH_FUNC, [$this->factory, 'create']
);
}
}
repositórios
php orientado a objetos
69. namespace DevelopBusinessApplicationProductWishlistRepositories;
class RedisRepository implements WishlistRepository
{
public function __construct(Predis $driver, Factory $factory);
public function find($id)
{
$wishlist = $this->driver->get($this->getKey($id));
if (!$wishlist) {
throw WishlistNotFoundException::byIdentifier($id);
}
return $this->factory->fromJson($wishlist);
}
}
repositórios
php orientado a objetos
70. class PdoRepositoryTest extends PHPUnit_Framework_TestCase
{
public function testAddNewRowSuccessful()
{
$wishlist = $this->factory->fromQueryResult(/* args */);
$pdoSpy = new PDOSpy();
(new Repository($pdoSpy))->add($wishlist);
$this->assertTrue($pdoSpy->beginTransactionCalled);
$this->assertTrue($pdoSpy->commitCalled);
$this->assertEquals(2, $wishlist->getId());
}
}
como testar repositórios?
php orientado a objetos
71. class PDOSpy extends PDO
{
public function beginTransaction()
{
$this->beginTransactionCalled = true;
}
public function prepare($statement, $options = null)
{
if ($statement == INSERT INTO wishlists VALUES (?, ?, ?)') {
return new WriteStatementStub($this->failureOnWrite);
}
throw new InvalidArgumentException(
"None Stub Statement was found for query: {$statement}"
);
}
}
como testar repositórios?
php orientado a objetos
72. function wishlistListAction($email)
{
$db = dbConnect();
$factory = new WishlistFactory();
$repository = new WishlistPdoRepository($db, $factory);
$wishlist = $repository->findAllByEmail($email);
include TEMPLATE_DIR . '/wishlists/list.php';
}
repositórios
php orientado a objetos
76. $db = dbConnect();
$factory = new WishlistFactory();
$repository = new WishlistRepository($db, $factory);
$resolver = new ItemResolver(new ProductRepository($db));
try {
$intention = createIntention($request['wish_item']);
$useCase = new AddItemWishlistUseCase(
$repository, $factory, $resolver
);
$wishlist = $useCase->execute($intention);
$successmsg = "{$wishlist->getItemName()} added!";
} catch (DomainException $e) {
$errormsg = $e->getMessage();
}
resolvendo
dependências
adicionando um item na lista de desejos
php orientado a objetos
77. $db = dbConnect();
$factory = new WishlistFactory();
$repository = new WishlistRepository($db, $factory);
$resolver = new ItemResolver(new ProductRepository($db));
try {
$intention = createIntention($request['wish_item']);
$useCase = new AddItemWishlistUseCase(
$repository, $factory, $resolver
);
$wishlist = $useCase->execute($intention);
$successmsg = "{$wishlist->getItemName()} added!";
} catch (DomainException $e) {
$errormsg = $e->getMessage();
}
parsing da
requisição
adicionando um item na lista de desejos
php orientado a objetos
78. $db = dbConnect();
$factory = new WishlistFactory();
$repository = new WishlistRepository($db, $factory);
$resolver = new ItemResolver(new ProductRepository($db));
try {
$intention = createIntention($request['wish_item']);
$useCase = new AddItemWishlistUseCase(
$repository, $factory, $resolver
);
$wishlist = $useCase->execute($intention);
$successmsg = "{$wishlist->getItemName()} added!";
} catch (DomainException $e) {
$errormsg = $e->getMessage();
}
criando
objeto de serviço
adicionando um item na lista de desejos
php orientado a objetos
79. $db = dbConnect();
$factory = new WishlistFactory();
$repository = new WishlistRepository($db, $factory);
$resolver = new ItemResolver(new ProductRepository($db));
try {
$intention = createIntention($request['wish_item']);
$useCase = new AddItemWishlistUseCase(
$repository, $factory, $resolver
);
$wishlist = $useCase->execute($intention);
$successmsg = "{$wishlist->getItemName()} added!";
} catch (DomainException $e) {
$errormsg = $e->getMessage();
}
executando
o serviço
adicionando um item na lista de desejos
php orientado a objetos
80. $db = dbConnect();
$factory = new WishlistFactory();
$repository = new WishlistRepository($db, $factory);
$resolver = new ItemResolver(new ProductRepository($db));
try {
$intention = createIntention($request['wish_item']);
$useCase = new AddItemWishlistUseCase(
$repository, $factory, $resolver
);
$wishlist = $useCase->execute($intention);
$successmsg = "{$wishlist->getItemName()} added!";
} catch (DomainException $e) {
$errormsg = $e->getMessage();
}
mensagem de
negócio
adicionando um item na lista de desejos
php orientado a objetos
81. class AddItemWishlistUseCase
{
public function __construct(
WishlistRepository $repository,
WishlistFactory $factory,
ItemResolver $resolver
) {
// do something
}
}
como adicionar um item na lista de desejos
php orientado a objetos
82. class AddItemWishlistUseCase
{
public function execute(AddItemIntention $intention)
{
$item = $this->resolver->resolve($intention->itemId);
try {
$wishlist = $this->repository->findOneByEmailAndItem(
$email, $item
);
throw WishlistException::itemAlreadyExists($wishlist);
} catch (WishlistNotFoundException $e) {
// do nothing
}
$wishlist = $this->factory->new($email, $item);
return $this->repository->add($wishlist);
}
}
php orientado a objetos
como adicionar um item na lista de desejos
83. class AddItemWishlistUseCase
{
public function execute(AddItemIntention $intention)
{
$item = $this->resolver->resolve($intention->itemId);
try {
$wishlist = $this->repository->findOneByEmailAndItem(
$email, $item
);
throw WishlistException::itemAlreadyExists($wishlist);
} catch (WishlistNotFoundException $e) {
// do nothing
}
$wishlist = $this->factory->new($email, $item);
return $this->repository->add($wishlist);
}
}
php orientado a objetos
como adicionar um item na lista de desejos
84. class AddItemWishlistUseCase
{
public function execute(AddItemIntention $intention)
{
$item = $this->resolver->resolve($intention->itemId);
try {
$wishlist = $this->repository->findOneByEmailAndItem(
$email, $item
);
throw WishlistException::itemAlreadyExists($wishlist);
} catch (WishlistNotFoundException $e) {
// do nothing
}
$wishlist = $this->factory->new($email, $item);
return $this->repository->add($wishlist);
}
}
php orientado a objetos
como adicionar um item na lista de desejos
86. // cli/wishlist_notify.php
require_once __DIR__ . '/../config/app.php';
require_once __DIR__ . '/../vendor/autoload.php';
$factory = new WishlistFactory();
$repository = new WishlistRepository(dbConnect(), $factory);
$notifier = new MailNotifier();
$useCase = (new NotifyItemsUseCase($repository, $notifier);
$useCase->execute(new NotifyItemsIntention(Status::PENDING));
notificar quando um item disponível
resolving
dependencies
php orientado a objetos
87. // cli/wishlist_notify.php
require_once __DIR__ . '/../config/app.php';
require_once __DIR__ . '/../vendor/autoload.php';
$factory = new WishlistFactory();
$repository = new WishlistRepository(dbConnect(), $factory);
$notifier = new MailNotifier();
$useCase = (new NotifyItemsUseCase($repository, $notifier);
$useCase->execute(new NotifyItemsIntention(Status::PENDING));
call service
notificar quando um item disponível
php orientado a objetos
88. class NotifyItemsUseCase
{
public function execute(NotifyItemsIntention $intention)
{
$status = $intention->getStatus();
$wishlists = $this->repository->findAllByStatus($status);
foreach ($wishlists as $wishlist) {
$this->notifier()->send($wishlist);
$wishlist->changeStatusTo(Status::sent());
$this->repository->update($wishlist);
}
}
}
como notificar quando um item disponível?
php orientado a objetos
89. class NotifyItemsUseCase
{
public function execute(NotifyItemsIntention $intention)
{
$status = $intention->getStatus();
$wishlists = $this->repository->findAllByStatus($status);
foreach ($wishlists as $wishlist) {
$this->notifier()->send($wishlist);
$wishlist->changeStatusTo(Status::sent());
$this->repository->update($wishlist);
}
}
}
como notificar quando um item disponível?
php orientado a objetos
90. class NotifyItemsUseCase
{
public function execute(NotifyItemsIntention $intention)
{
$status = $intention->getStatus();
$wishlists = $this->repository->findAllByStatus($status);
foreach ($wishlists as $wishlist) {
$this->notifier()->send($wishlist);
$wishlist->changeStatusTo(Status::sent());
$this->repository->update($wishlist);
}
}
}
como notificar quando um item disponível?
php orientado a objetos
101. class UpdateProductUseCase
{
public function __construct(/* args */, $eventDispatcher);
public function execute(Intention $intention)
{
$this->repository->update($updatedProduct);
$event = new ProductWasUpdated($updatedProduct);
$this->dispatch('product.updated', $event);
return $updatedProduct;
}
}
eventos
php orientado a objetos
102. class UpdateProductUseCase
{
public function __construct(/* args */, $eventDispatcher);
public function execute(Intention $intention)
{
$this->repository->update($updatedProduct);
$event = new ProductWasUpdated($updatedProduct);
$this->dispatch('product.updated', $event);
if ($this->isStockIncreased($product, $updatedProduct)) {
$this->dispatch('product.stock.increased', $event);
}
return $updatedProduct;
}
}
eventos
php orientado a objetos
103. class StockHasIncreasedListener
{
public function __construct(NotifyProductAvailable $useCase);
public function __invoke(ProductStockIncreasedEvent $event)
{
$productId = $event->getProduct()->getId();
$intention = new NotifyProductIntention($productId);
$this->useCase->execute($intention);
}
}
events
object oriented php
117. Implementing Domain-Driven Design
por Vaughn Vernon
Patterns of Enterprise Application Architecture
por Martin Fowler
Domain-Driven Design
por Eric Evans
DDD in PHP
por Carlos Buenosvinos, Christian Soronellas e Keyvan Akbary
referências