Frameworks are very helpful to solve common problems when developing an application. But what happens when we have to move to another framework? In this talk I will show how my company tries to keep independent of any framework, decoupling our business logic from symfony.
2. Who I am?
• Miguel Gallardo
• Cordoba, Argentina
• Spent more than 15 years writing software
• Software Engineer @ ProPoint Solutions, LLC
(http://propointglobal.com/)
• @mg3dem (twitter, linkedin, facebook, instagram)
• mgallardo@propointglobal.com
3. • Wrote a lot of Hello world scripts
• make it show dynamic content!
• Guestbook or todo list using a DB
• Inline HTML in php sucks, it becomes tedious to change logic. You
can separate both! presentation logic and the business logic.
• Discover Libraries, there is a library for everything!
• what happens if we get together a lot of libraries? A framework!
• Everyone tries to make a framework!
• Frameworks becomes a must in php development
6. –Wikipedia
“coupling is the degree of interdependence
between software modules; a measure of how
closely connected two routines or modules are; the
strength of the relationships between modules.”
8. Benefits of decoupling
• Minimize dependency of external frameworks, libraries
• Minimize backward compatibility breaks
• More portable
• More reusable
• Easier to change implementations
• Easier to maintain
• Easier to test
• More clean code and architecture
11. • Frameworks come and go, especially in the PHP world
• There are dozens if not hundreds of PHP frameworks,
most of which no one has heard of
• The popular frameworks, well they have gone through
major revamps at least once in their lifetime
• people do is keep their code with the old framework,
sacrificing years of potential bug and security fixes for not
updating
12. – Christopher Stea
“The whole point of framework independence is to
protect our business logic from these scenarios and
one can even update to the latest framework with
ease without major rewrites”
13.
14. Some rules
• All code MUST obey the Law of Demeter
• a given object should assume as little as possible about the structure or
properties of anything else.
• All code MUST follow the Single Responsibility Principal
• a given object should assume as little as possible about the structure or
properties of anything else.
• Software entities (classes, modules, functions, etc.)
should be open for extension, but closed for modification
• Can allow to be extended without modifying its source code
16. • Represents a single record of data
• Getters and Setters
• Can link to other entities that represent the relations
between them
• Must be independent of the hosting framework
• Must be kept lean with no business logic and no logic for
data persistence != ActiveRecord
17. <?php
class Customer
{
/**
* @var int
*/
private $id;
private $phoneNumber;
private $user;
/**
* @var CustomerStatus
*/
private $status;
public function __construct(User $user, int $phoneNumber) {}
public function getId(): ?int {}
public function getPhoneNumber(): int {}
/**
* Set status
*
* @param CustomerStatus $customerStatus CustomerStatus Entity.
*
* @return Customer
*/
public function setStatus(CustomerStatus $customerStatus): Customer
{
$this->status = $customerStatus;
return $this;
}
}
19. • Represents the data persistence layer
• Code pertaining to fetching and saving entities should be specified in the
repository
• Should be lean and free from business logic.
• It’s an Interface
• Implemented by data repository (DB abstraction layer, API
layer, etc)
20. <?php
namespace PropointSmsCustomerRepository;
use PropointSmsAccountEntityUser;
use PropointSmsCustomerEntityCustomer;
interface CustomerRepositoryInterface
{
/**
* Save customer entity.
*
* @param Customer $customer Customer entity.
*/
public function save(Customer $customer): void;
/**
* Get Customer record for a customer of a specific store.
*
* @param User $user User entity.
* @param int $phoneNumber Customer phone number.
*
* @return null|Customer
*/
public function getByPhoneNumber(User $user, int $phoneNumber): ?Customer;
/**
* Get all customer entries for a specified phone number
*
* @param int $phoneNumber Phone number.
*
* @return Customer[]
*/
public function getAllByPhoneNumber(int $phoneNumber): array;
}
22. • Contains all of the business logic of an application
• Should be completely agnostic to data persistence
• Act as a abstraction layer for repositories
• May use other services for data retrieval
• But NOT to persist data
• Use event subscribers instead!
• Must be independent of the framework
23. <?php
class CustomerService implements PropointSmsCustomerCustomerServiceInterface
{
private $eventDispatcher;
private $customerRepository;
private $customerStatusRepository;
public function getStatus(string $statusName): CustomerStatus
{
return $this->customerStatusRepository->getByName($statusName);
}
public function create(User $user, int $phoneNumber): Customer
{
$customer = new Customer($user, $phoneNumber);
$customer->setStatus($this->getStatus('ACTIVE'));
return $customer;
}
public function getByPhoneNumber(User $user, int $phoneNumber): Customer
{
return $this->customerRepository->getByPhoneNumber($user, $phoneNumber);
}
public function save(Customer $customer): void
{
$this->customerRepository->save($customer);
if (empty($customer->getId())) { //New customer
$this->eventDispatcher->dispatch(CustomerCreated::NAME, new CustomerCreated($customer));
}
$this->eventDispatcher->dispatch(CustomerUpdated::NAME, new CustomerUpdated($customer));
}
}
25. • Focus on receiving an incoming HTTP request, fetching
incoming parameters and returning an HTTP response.
• Must be kept lean and free from business logic.
• Pass parameter to services for processing
• And use the results to generate a response.
26. <?php
class MessageController extends FOSRestBundleControllerFOSRestController
{
public function postIndexAction(
Request $request,
MessageServiceInterface $messageService,
CustomerServiceInterface $customerService,
UserServiceInterface $userService
): Response {
$payload = json_decode($request->getContent(), true);
try {
if (empty($payload)) {
throw new SymfonyComponentHttpKernelExceptionBadRequestHttpException(
'Invalid JSON in request body.'
);
}
try {
$user = $userService->getByUsername($this->getUser()->getUserName());
$message = $messageService->loadFromArray($user, $payload);
$customer = $customerService->getOrCreateCustomer($user, $message->getPhoneNumber());
$canSend = $customer->getStatus()->canSendSms();
if (!$canSend) {
$message->setStatus($messageService->getStatus('FORBIDDEN'));
}
$message->setCustomer($customer);
$messageService->save($message);
if (!$canSend) {
throw new SymfonyComponentHttpKernelExceptionAccessDeniedHttpException(
'Cannot send SMS to opt-out customer'
);
}
} catch (InvalidArgumentException $exception) {
throw new SymfonyComponentHttpKernelExceptionUnprocessableEntityHttpException(
$exception->getMessage(),
$exception
);….
30. • Good protection against external changes, but doesn’t
mean that it should always be applied everywhere!
• We can’t always decouple from everything
• The decision of when to use it depends on the context
(project, team, industry, etc)
• Be pragmatic:
• use the right tool for the right job
• Doesn’t apply patterns without a problem solved by a design pattern
31. • Think of your modules as independent, as if the rest of
the program didn't exist
• Prefer abstractions - they tend to help you think of your
modules as independent
• Design patterns of course
• Cohesive code is often decoupled
• Prefer composition, over inheritance.
• Use layers of architecture