Symfony2 service - php

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.

Related

PHP cast object in simple ORM

I would like to make a simple ORM in PHP for standard CRUD interaction with my db, I also want make it work in php5 for legacy compatibility.
I've written some classes to do this and it works, but not completely as I would.
This is the idea. I have an abstrac class called ModelBase which has a property (tableName) and some metods like select, insert, update and delete, plus has an abstract method, getData, that will be implemented by the classes that will be implement ModelBase and should return object of correct type.
So, for example, I could have a class Users which implements ModelBase and one another class UserData which is the model with the property.
Here is the code:
abstract class ModelBase{
private $tableName;
public function __construct($tableName) {
$this->tableName = $tableName;
}
public function select{
// make select query to db and retreive data
// ...
$resData = [];
while($dataRow = mysqli_fetch_array($res, MYSQLI_ASSOC)) {
$resData[] = $this->getObjectData($dataRow); // implemented in child class
}
return $resData;
}
public function insert(){ /* ... */}
public function update(){ /* ... */}
public function delete(){ /* ... */}
abstract function getObjectData($data); // maps the results
}
class UserData {
public $id;
public $name;
public $surname;
public $email;
// other fields
public function __construct() {}
}
class User implements ModelBase {
private $tableName = 'users';
public function __construct() {
parent::__construct($this->tableName);
}
public function getObjectData($dataRow) {
$o = new UserData ();
// mapping dataRow to object fields
$o->id = $dataRow['ID'];
// ....
return $o;
}
}
So I use my classes in this way:
$users = new Users();
$u = users->select();
$firstUser = $u[0]; // I get my user if exists
In $firstUser I'll get my object with property and correct data but I would like to have that also my IDE (vsCode in this case) would recognize the object type in order to suggest the correct properties. So if I write $firstUser-> I would like to see field suggestions (id, name, surname, ...) from UserData and for other xyzData classes as well.
What I should do to improve my classes in order to see property suggestions when I use my objects, also in php5?
Solution for PHP 8, tested on PHPStorm.
<?php
class Base {
/**
* #return static[]
*/
public function select() : array {
return [new self];
}
public function selectFirst() : static {
return $this->select()[0];
}
}
class User extends Base {
public ?string $userName = null;
}
#detects the current class via () : static
(new User)->selectFirst()->userName;
#detects the current class via #return static[]
(new User)->select()[0]->userName;
In line solution for PHP 5, define the variable directly with this comment
/** #var $a User */
$a->userName;
There is no benefit of supporting old PHP 5. You lose so mutch clean code and modern approach when supporting old php versions.
But when you have to, then go with the inline solution.
Not tested and not so clean for PHP 5:
class User extends Base {
public ?string $userName = null;
/**
* #return User[]
*/
public function select() : array {
return parent::select();
}
}

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)
);

PHP dependency injection - difference between a container, a controller or a factory

I still cannot tell the difference between a container, a controller or a factory. For instance, the code below, should it be considered as a container, a controller or a factory?
How should it be like if it is a container in this case?
namespace ioc
{
class LoggerContainer
{
use \Snippets;
/*
* Set props.
*/
protected $Database;
public $Logger;
/**
* Construct data.
*/
public function __construct($Database)
{
// Set DI.
$this->Database = $Database;
// Run private method.
$this->getLogger();
}
/**
* Set a new class (instantiate the class) as the return result.
*/
private function getLogger()
{
$signature = AUTHENTICATED_USER_SIGNATURE;
$this->Logger = new \core\model\Logger($this->Database);
if($_REQUEST['url'] === 'backoffice')
{
return $this->Logger->setLogger($signature)->addCategory()->addSomething();
}
else
{
return $this->Logger->setLogger($signature)->removeSomething();
}
}
}
}
This is a container because it is an object that holds another object that is being accessed, i.e. a Logger in this case.
A controller is a term that usually refers to the C of the MVC and it is what controls the model and the view
While a factory method refers to a routines that returns the correct specialization of an interface/abstract class
Note that this three terms refer to very different things

Mocking Symfony2 Entity Manager in Codeception

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);

in one class run many class, PHP OOP

i have class "User_registration" and in this class i need use many class: "Location", "Links", "Mail", "Module".
i create include all class in file:
include 'class/location.php';
include 'class/links.php';
include 'class/mail.php';
include 'class/modules.php';
Now create "User_registration" class.
<?php
class User_registration{
public function insert_to_db($name, $country_code, $home_page)
{
//work with data
return id;
}
public function show_info($id)
{
//work with data
}
}
$reg_u = new User_registration;
$res = $reg_u->insert_to_db($name, $country_code, $home_page);
if($res){
$reg_u->show_info($res);
}
?>
I need in method "insert_to_db" run class: "Location", "Links", "Mail" methods
and in "show_info" run some methods of "Location", "Links", "Module" class.
How? How in one class run another class (no't one)
Thanks for help ;)
There are a few ways to do this. If you have only a couple objects that another class needs to utilize, use dependency injection; Pass each object as an argument into a class's constructor and store those objects as a class property.
If only a single method needs the object, pass the object as an argument of the method. I discourage this approach though, because I feel it hinders expandability/code-cleanliness in the long run.
If you have many objects that are needed in several classes, I recommend a registry that you inject into a class's constructor. The registry is a singleton (it holds a single instance of each object you need to share). In the class that needs to utilize a shared object, you might call $this->registry->get('Some_Shared_Object')->doSomething().
Dependency Injection (at the constructor)
class Foo {
protected $dependency1;
protected $dependency2;
protected $dependency3;
public function __construct($dependency1, $dependency2, $dependency3) {
$this->dependency1 = $dependency1;
$this->dependency2 = $dependency2;
$this->dependency3 = $dependency3;
}
public function foo() {
$this->dependency1->doSomething();
}
}
$foo = new Foo($dependency1, $dependency2, $dependency3);
$foo->foo();
Dependency Injection (at the method, not recommended)
class Foo {
public function foo($dependency1) {
$dependency1->doSomething();
}
}
$foo = new Foo();
$foo->foo($dependency1);
Dependency Injection using a Registry
class Registry {
var $data = array();
function __get($key) {
return $this->get($key);
}
function __set($key, $value) {
$this->set($key, $value);
}
/**
* Retrieve a resource from the registry.
*
* #param string
* #return mixed|null
*/
function get($key) {
return isset($this->data[$key]) ? $this->data[$key] : NULL;
}
/**
* Store a resource in the registry.
*
* #param string
* #param mixed
*/
function set($key, &$value) {
$this->data[$key] = $value;
}
/**
* Check if a resource exists in the registry.
*
* #param string
* #return boolean
*/
function has($key) {
return isset($this->data[$key]);
}
}
class Foo {
protected $registry;
public function __construct($registry) {
$this->registry = $registry;
}
public function foo() {
$this->registry->dependency1->doSomething();
}
}
$dependency1 = new Dependency1();
$registry = new Registry();
$registry->set('dependency1', $dependency1);
$foo = new Foo($registry);
$foo->foo();
As good practice I always use an include_once/require_once when I call a class from a class. That way I know no matter where a use a class its references are taken care of and don't over lap.
Well initialize an instance of each and call your methods from there. Don't be afraid of static references.

Categories