OOP Pattern with methods calling Repositories - php

I have a class DashboardService (defined as a service in symfony2), i use it to call some methods to get results (just queries) from some repositories and display data.
class DashboardService {
/**
* #var EntityManager
*/
private $em;
public function __construct(EntityManager $em) {
$this->em = $em;
}
public function getTotalActiveCampaignsByMonth($month) {
$campaigns = $this->em->getRepository("WMAdminBundle:Campaign")->countAllActiveCampaignsByMonth($month);
return $campaigns;
}
public function getTotalContactsByMonth($month) {
$contacts = $this->em->getRepository("WMAdminBundle:Contact")->countAllContactsSentByMonth($month);
return $contacts;
}
public function getTotalCAByMonth($month) {
$ca = $this->em->getRepository("WMAdminBundle:ContactCampaign")->getAllCAByMonth($month);
return $ca;
}
public function getTop10RentabilityCampaigns() {
$campaigns = $this->em->getRepository("WMAdminBundle:Campaign")->findAllTop10Rentability();
return $campaigns;
}
public function getTop10ContactCampaigns() {
$campaigns = $this->em->getRepository("WMAdminBundle:Campaign")->findAllTop10Contacts();
return $campaigns;
}
}
Is this class an OOP pattern or something ?

it's like a basic application service in a typical layered architecture.
Application Services : Used by external consumers to talk to your system (think Web Services). If consumers need access to CRUD operations, they would be exposed here.

Related

Select interface implementation according to parameter

What is correct way to select interface implementation according to passed parameter?
Let's have following code:
interface PaymentProcessor {
public function process(Order $order): void;
}
class CardPayment implements PaymentProcessor {}
class BankTransfer implements PaymentProcessor {}
class OrderService {
public function __construct( // Load payment processors from DI
private CardPayment $cardPayment,
private BankTransfer $bankTransfer,
) {}
// This is what is not clear to me
private function getPaymentProcessor(string $paymentMethod): PaymentProcessor
{
return match ($paymentMethod) {
'card' => $this->cardPayment,
'bank' => $this->bankTransfer,
}
}
public function payOrder(Order $order): void
{
$processor = $this->getPaymentProcessor($order->getPaymentMethod());
$processor->process($order);
}
}
What is not clear to me is how can I get PaymentProcessor by payment method name.
I would use code above, probably extracted to some "Factory". But according to answer at PHP - Using Interfaces, Strategy Pattern and Optional Method Parameters I am probably breaking open/close principle. Then what should be correct way?
The correct way to do this could be combination of Strategy pattern and Dependency Injection.
Solution posted in reffered answer is correct, but is missing one important point - the trick is that all the mapping can be done via Dependency Injection.
So you can have
interface PaymentStrategy {
public function process(): void;
}
class CardPayment implements PaymentStrategy {
public function process(): void;
}
class BankTransfer implements PaymentStrategy {
public function process(): void;
}
class PaymentService {
private PaymentStrategy $strategy;
/** #var array<string, PaymentStrategy> $processors */
public function __construct(private array $processors) {
}
public function setStrategy(string $paymentMethod): void {
if (!array_key_exists($paymentMethod, $this->processors)) {
throw new \InvalidArgumentException('Processor not found');
}
$this->strategy = $this->processors[$paymentMethod];
}
public function process(): void {
$this->strategy->process();
}
}
So all you need to do is convince your DI container to give you all of PaymentStrategy implementations. Ideally in paymentMethodName - strategy format. Alternatively you can add some function to interface and class which return it's payment method name - and then run foreach - if - return.

How can I use my CommandBus throughout the application?

I'm practicing with CQRS and making my own implementation of CommandBus to see how it works.
What I want to do?
I would like to have my own framework where I create my bootstrapping to instantiate my CommandBus with my array where I map each command with its handler. My intention was to do the following:
I create the CommandBus and instantiate it in my app.php
CommandBus:
final class CommandBus {`
private array $handlers;
public function __construct()
{
$this->handlers = [];
}
public function addHandler($commandName, $handler) {
$this->handlers[$commandName] = $handler;
}
public function handle($command) {
$commandHandler = $this->handlers[get_class($command)];
if($commandHandler === null) {
throw new \http\Exception\InvalidArgumentException();
}
return $commandHandler->handle($command);
}
}
app.php:
$commandBus = new CommandBus();
$commandBus->addHandler($commandName, $handler);
Controller:
final class Controller {
public function __construct()
{}
public function action() {
//...
$commandBus->handle($command);
//...
}
}
The problem is that I can't use the $commandBus variable inside my controller. What would be the best way to have that variable always available and have access to my array of handlers?

Inject EntityManager in a service from another service

I'm extracting code from Controller to a kind of ApplicationService in a Symfony 3.4 App.
I've a concrete class for scraping data and another concrete transformer to change some data.
src\App\Service
class CompanyScraping implements ScrapingInterface
{
private $crawler;
public function __construct(CrawlerInterface $crawler)
{
$this->crawler = $crawler;
}
public function extract()
{
...
}
public function transform()
{
$transformer = new concreteTransformer();
}
}
class concreteTransformer
{
private $em;
public __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
}
How could I pass the EntityManager to the concreteTransformer class if the EntityManager is not called in CompanyScraping class? I can't instantiating concreteTransformer with a new.
I'm thinking in this two options:
Pass EntityManager to CompanyScraping, but I thik that is a wrong idea because CompanyScraping doesn't need this dependency.
Extract transform method into a another class and pass the em from controller/console
$crawler = new CompanyScraping(new GoutteClient());
$rawData = $crawler->extract(...);
$data = new concreteTransformer($em, $rawData);
Any other idea?
Thanks.
The first solution I thought of is to inject (not the EM, but) the Transformer into the Scraper class, as posted in the comments.
Such solution would, however, not address the underlying problem: is the Scraper scraping, transforming or both? In the latter case, it's not adhering to the single responsibility principle, because it has the responsibility for both scraping and transforming.
A design pattern that could be pretty effective to tackle this, is the decorator pattern.
The idea in this case is to "decorate" the scraped results with a transformation.
The result would look somewhat like this:
class Transformer implements ScrapingInterface
{
private $scraper;
private $em;
public __construct(ScrapingInterface $scraper, EntityManagerInterface $em)
{
$this->scraper = $scraper;
$this->em = $em;
}
public function extract()
{
return $this->transform($this->scraper->extract());
}
private function transform() {...}
}
It can be constructed as:
$crawler = new Transformer(new CompanyScraping(new GoutteClient()), $em);
In case you have multiple transformer implementations, you can make the decorator more generic:
class TransformingScraper implements Scraper
{
private $scraper;
private $transformer;
public __construct(Scraper $scraper, Transformer $transformer)
{
$this->scraper = $scraper;
$this->transformer = $transformer;
}
public function extract()
{
return $this->transformer->transform($this->scraper->extract());
}
}
$crawler = new TransformingScraper(
new CompanyScraping(new GoutteClient()),
new ConcreteTransformer($em)
);

Symfony using run-time parameters in services

I need to initialize an object that will be injected into some services. Some of his arguments can be obtained during run-time from request parameters.
How should I work with such dynamic objects?
The first way I found was to make setters and pass arguments throw services that use the object. But I think it's smell bad.
The second approach is to mark my object as "synthetic service". So I must prepare object in a constructor and set it to the service container.
I'm not sure that it's the best approach. Can somebody suggest me the right way to act in such situations?
Pseudo code sample of first approach:
class Storage {
private $path;
private $project;
public function __construct($path) {
$this->path = $path;
}
public function setProject($project): void {
$this->project = $project;
}
public function showFullPath() {
echo "Full path: {$this->path}/{$this->project}";
}
}
class Service {
private $storage;
public function __construct(Storage $storage) {
$this->storage = $storage;
}
public function doSomething($project) {
$this->storage->setProject($project);
$this->storage->showFullPath();
}
}
// in controller
$service = $container->get('Service');
$service->doSomething($project);
Pseudo code sample of second approach:
class Storage {
private $path;
private $project;
public function __construct($path, $project) {
$this->path = $path;
$this->project = $project;
}
public function showFullPath() {
echo "Full path: {$this->path}/{$this->project}";
}
}
class Service {
private $storage;
public function __construct(Storage $storage) {
$this->storage = $storage;
}
public function doSomething() {
$this->storage->showFullPath();
}
}
// in controller
$storage = new Storage($path, $project);
$container->set('Storage', $storage);
$service = $container->get('Service');
$service->doSomething();
If the parameters you need are retrieved from the request, why don't you inject the request stack into Storage, and then on showFullPath just get the parameter you need from the request's stack master request?
use Symfony\Component\HttpFoundation\RequestStack;
class Storage {
private $path;
private $requestStack;
public function __construct($path, RequestStack $requestStack) {
$this->path = $path;
$this->requestStack = $requestStack;
}
public function showFullPath() {
$project = $this->requestStack->getMasterRequest()->getYourParametersHereFromRequest();
echo "Full path: {$this->path}/{$project";
}
}
I agree with gvf that the using the RequestStack approach is a good solution in this scenario. However, if you insist on using the request object from the controller, you can introduce a factory service to create the storage object:
class StorageFactory
{
public function createStorage(Request $request) : Storage
{
// ... create $storage from $request
return $storage;
}
}
class Service
{
public function doSomething(Storage $storage)
{
// ...
}
}
// in your controller
$storage = $container->get("storage_factory")->createStorage($request);
$container->get('Service')->doSomething($storage);
As you see, you will have to pass the $storage object as a parameter to the doSomething method. It is not possible to set it in the constructor, because you explicitely want to create the $storage object after the services have been set up.
By the way: setter injection sucks, because it allows services to be in an inconsistent state. Avoid it (and setters in general) whereever possible.

Symfony2 service

I have 2 tables in DB (question and answer). One question has many answers.
I get some Answers and depends on question.type I prepare results array.
In app without any framework I have Factory class which return specific object (SingleChoiceQuestion, OpenQuestion, MultipleChoiceQuestion) depends question.type from DB. All Questions extends abstract class Question which has declared abstract method getResults. All types have their own business logic to prepare results.
So in this situation when I have created object by factory I'm using method getResults and everything works well.
I would like to create it in Symfony and I read documentation. In my opinion I should create services for all my Question types.
I have created AggregatedResultsManager with method generate which returns results array. Depends on question.type it calls getResults method from specific service.
I would like to add, that I can't change DB structure.
My questions:
Am I creating and using services right? If I do it wrong, please help me understanding it and show me the right way.
I will have several services like AggregatedResultsManager and about 18 Question types.
In each service I will need to create switch with 18 choices, how to prevent that?
switch ($this->question->getType()) {
case Question::SINGLE:
$results = $this->container->get('app.single_choice_question')->getResults($answers);
break;
// other types
}
I have some idea to create array with types and service names:
$services = [
Question::SINGLE => 'app.single_choice_question',
Question::MULTIPLE => 'app.multiple_choice_question',
Question::OPEN => 'app.open_question',
];
and then use it in each service like that:
$results = $this->container->get($services[$this->question->getType()])->getResults($answers);
I think it's the best way to not use switch with 18 choices. But I will need to hardcode service names in array.
My code:
services.yml
app.question:
class: AppBundle\Questions\Question
abstract: true
arguments: ['#doctrine.orm.entity_manager']
app.single_choice_question:
class: AppBundle\Questions\SingleChoice
parent: app.question
app.agreggated_results_manager:
class: AppBundle\Results\AggregatedResultsManager
arguments: ['#doctrine.orm.entity_manager', '#service_container']
abstract Question
abstract class Question
{
/**
* #var EntityManager
*/
protected $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
abstract public function getResults($answers);
}
SingleChoice
class SingleChoice extends Question
{
public function getResults($answers)
{
$results = [];
// business logic
return $results;
}
}
Results
class AggregatedResultsManager
{
/**
* #var EntityManager
*/
private $em;
/**
* #var Question
*/
private $question;
/**
* #var ContainerInterface
*/
private $container;
public function __construct(EntityManager $em, ContainerInterface $container)
{
$this->em = $em;
$this->container = $container;
}
public function generate()
{
if (!$this->question) {
throw new \LogicException('Question is not set');
}
$answers = $this->em
->getRepository('AppBundle:Answer')
->findBy(['question' => $this->question]);
$results = [];
if (empty($answers)) {
return $results;
}
switch ($this->question->getType()) {
case Question::SINGLE:
$results = $this->container->get('app.single_choice_question')->getResults($answers);
break;
// other types
}
return $results;
}
public function setQuestion(Question $question)
{
$this->question = $question;
}
}
Controller
public function questionIdsAction(Question $question)
{
$resultsManager = $this->get('app.agreggated_results_manager');
$resultsManager->setQuestion($question);
$results = $resultsManager->generate();
return new JsonResponse($results);
}
I think you are saying that you have 18 QuestionTypes all extending an AbstractQuestion which needs the entity manager to do it's work? Instead of making 18 services and then using the container I would suggest making a question factory:
class QuestionFactory
public function __construct($entityManager)
$this->entityManager = $entityManager;
public function create($questionType)
switch($questionType) {
case Question::SINGLE: return new SingleQuestion($this->entityManager);
You would then inject the factory into the results manager.
This approach avoids the need to create a bunch of services and of needing to pass around the container. You still have a switch statement but that is okay.
The only problems that might arise is if some QuestionTypes need additional dependencies. In which case you might be back to using services.

Categories