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)
);
Related
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.
I have thees repository classes which implement the same contracts and pattern, so basically I have a bunch of theese:
class EmployeeRepository {
private $transformer; //instance of EmployeeTransformer
public function find() {
return new ReturnObject($this->transformer, $employee);
}
}
class CustomerRepository {
private $transformer; //instance of CustomerTransformer
public function find() {
return new ReturnObject($this->transformer, $customer);
}
}
as you can see they all return a ReturnObject which basically takes the transformer object and the entity object as constructor parameters. The transformer is used for the ReturnObject to determine the output format for the controller classes.
class ReturnObject {
private $transformer;
private $response;
function __construct($transformer, $entity) {
$this->transformer = $transformer;
$this->entity = $entity;
$this->response = new ApiResponse();
}
public function apiResponse() {
return $this->response->array($this->transformer, $entity);
}
}
My question is regarding the constructor parameter for the transformer. Is there a way for the repositorys to be able to transfer the correct transformer class to the ReturnObject without having to send it as a constructor parameter? Maybe with a factory pattern design?
There are 2 reasons I want to remove the argument from the constructor:
The constructor already has a lot of arguments. (I have removed them from my examples above, to make the code simpler to understand, but in reality there are 6 parameters already to the constructor except for the transformer)
I have a lot of repositories with a lot of functions, and it would be neat to not having to send the transformer in all the returns to reduce code and potentially errors
I'm having some confusion with the adapter pattern and am wondering if it is the right tool for what I'm trying to accomplish.
Basically, I'm trying to get a class written by another developer to conform to an interface that I've written while also retaining the other methods from that class.
So I've written the following interface for a container object:
interface MyContainerInterface
{
public function has($key);
public function get($key);
public function add($key, $value);
public function remove($key);
}
I've also written an adapter that implements that interface:
class OtherContainerAdapter implements MyContainerInterface
{
protected $container;
public function __construct(ContainerInteface $container) {
$this->container = $container;
}
public function has($key) {
$this->container->isRegistered($key);
}
...
}
And am using it in my class as follows:
class MyClass implements \ArrayAccess
{
protected $container;
public function __construct(MyContainerInterface $container) {
$this->setContainer($container);
}
public function offsetExists($key) {
$this->container->has($key);
}
...
}
Then my application uses the class as so:
$myClass = new MyClass(new OtherContainerAdapter(new OtherContainer));
The issue I'm having is that in order to use the methods from the adapter I have to write the following:
$myClass->getContainer()->getContainer()->has('some_key');
When ideally it would just be:
$myClass->getContainer()->has('some_key');
$myClass->getContainer()
should return an instance of MyContainerInterface and that has a has() function. It shouldn't have a getContainer() function.
I don't think you need the Adapter Pattern for this. It looks to me like you're after a polymorphic solution, which can be accomplished by simply using an abstract class. No adapter needed.
The interface
interface MyContainerInterface
{
public function has($key);
public function get($key);
public function add($key, $value);
public function remove($key);
}
Then the abstract base class:
class MyContainerBaseClass implements MyContainerInterface, \ArrayAccess
{
public function offsetExists($key) {
$this->has($key);
}
...
}
Then, the sub-class from the other developer:
class ClassByOtherDeveloper extends MyContainerBaseClass
{
public function has($key) {
$this->isRegistered($key);
}
//you also need to implement get(), add(), and remove() since they are still abstract.
...
}
You can use it in your application like this:
$object = new ClassByOtherDeveloper();
$x = $object->has('some_key');
I'm assuming the isRegistered method lives in the implementation from the other developer.
To make it truly polymorphic you wouldn't hard-code the class name, but you'd use a variable that could come from a config file, database, or a Factory.
For example:
$className = "ClassByOtherDeveloper"; //this could be read from a database or some other dynamic source
$object = new $className();
$x = $object->has('some_key');
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.
I am writing codeception unit tests for a Manager class in my Symfony2 application, and I am wondering how to mock the entity manager. For example, let's say I have the following function in my AcmeManager service class:
<?php
namespace Acme\AcmeBundle\Manager;
use Doctrine\Common\Persistence\ObjectManager;
class AcmeManager
{
private $em;
public function __construct (ObjectManager $em)
{
$this->em = $em;
}
public function findMatches($index)
{
// Find and display matches.
$matches = $this->em
->getRepository('AcmeBundle:AssignMatch')
->findBy(array('assignIndex' => $index));
return $matches;
}
}
and I wanted to write the following test function:
<?php
use Codeception\Util\Stub;
class AutoManagerTest extends \Codeception\TestCase\Test
{
/**
* #var \CodeGuy
*/
protected $codeGuy;
protected function _before()
{
}
protected function _after()
{
}
/**
* Tests findMatches($index).
*/
public function testFindMatches()
{
//... $mockedEntityManager is our mocked em
$acmeManager = $this->getModule('Symfony2')->container->get('acme_manager');
$acmeManager->findMatches(0);
// $this->assert(isCalled($mockedEntityManager));
}
}
How can I mock the entity manager such that when I call $acmeManager->findMatches(0);, I can assert that the mocked entity manager is called (even though $acmeManager uses the regular Symfony2 entity manager in its normal implementation)?
I think the easiest way would be to skip the DIC part and simply instantinate the AcmeManager with the EM passed in the constructor.
The other way would be getting it from the DIC as you currently do, and setting AcmeManager::$em with reflection. Something like this:
$acmeManager = $this->getModule('Symfony2')->container->get('acme_manager');
$class = new \ReflectionClass('\Acme\AcmeBundle\Manager\AcmeManager');
$property = $reflection->getProperty('em');
$property->setAccessible(true);
$property->setValue($acmeManager, $mockedEntityManager);