I would like to factorize some code in my models repositories.
A really basic example
public function getPlayers()
{
$qb = $this->createQueryBuilder('p')
->innerJoin(...) // whatever the request
->where(...)
// I want to factorize this line because a lot of function use it
->andWhere('p.active = true');
return (...);
}
So I create a private function
private function getActivePlayer() {
return $this->andWhere('p.active = true');
}
And I would like to use it like that in any function
$qb = $this->createQueryBuilder('p')
->innerJoin(...)
->where(...)
->getActivePlayer()
But of course I have this error
Attempted to call method "getActivePlayer" on class "Doctrine\ORM\QueryBuilder"
It's possible to achieve this kind of factorization? What will be the syntaxe?
Thanks
You can try something like:
public function getPlayers()
{
$qb = $this->createQueryBuilder('p')
->innerJoin(...)
->where(...);
$qb = $this->getPlayerType($qb);
}
private function getActivePlayer(QueryBuilder $qb)
{
return $qb->andWhere('p.active = true');
}
Using the -> operator in the context of a QueryBuilder object will only call methods within the QueryBuilder class. You must define your own class that extends the QueryBuilder. Something like:
class MyQueryBuilder extends \Doctrine\ORM\QueryBuilder {
public function getActivePlayer() {
return $this->andWhere('p.active = true');
}
}
Then implement that builder instead of the default Query Builder:
$qb = new MyQueryBuilder();
$qb->select('p')
->from(...)
->innerJoin(...)
->where(...)
->getActivePlayer()
// ...
Note that the above code is just simple demonstration to show you what length of effort it takes to achieve exactly what you want to do - in actual practice, you'll need to do more than this.
You are best off starting with a basic QueryBuilder in a particular private method then amending it in your public getters:
private function getPlayerQueryBuilder()
{
$qb = $this->createQueryBuilder('p')
->innerJoin(...) // whatever the request
->where(...)
return $qb;
}
public function getActivePlayers() {
$result = $this->getPlayerQueryBuilder()
->andWhere('p.active = true')
->getQuery()->getResult();
return $result;
}
public function getAllPlayers() {
$result = $this->getPlayerQueryBuilder()
->getQuery()->getResult();
return $result;
}
Related
Here is the method to test.
public function performPoolRequest(RecreationRequestsCollection $requests): RecreatedPaymentsPoolReading
{
$count_request = count($requests);
if($count_request) {
$pool_recreate_payments = new RecreatedPaymentsPool();
$this->http_client->sendPoolRequest(
$this->generateRequests($requests),
$count_request,
function (ResponseInterface $response, $index) use ($requests, $pool_recreate_payments) {
$this->successHandler($response, $requests, $index, $pool_recreate_payments);
},
function (BadResponseException $reason, $index) use ($requests, $pool_recreate_payments) {
$this->failureHandler($reason, $requests, $index, $pool_recreate_payments);
}
);
return $pool_recreate_payments;
} else {
throw new PoolRequestException('Incorrect amount of requests: ' . $count_request);
}
}
Difficulties arose with this piece of code:
$this->http_client->sendPoolRequest(
$this->generateRequests($requests),
$count_request,
function (ResponseInterface $response, $index) use ($requests, $pool_recreate_payments) {
$this->successHandler($response, $requests, $index, $pool_recreate_payments);
},
function (BadResponseException $reason, $index) use ($requests, $pool_recreate_payments) {
$this->failureHandler($reason, $requests, $index, $pool_recreate_payments);
}
);
I made a mock object $this->http_client
But I don't know how to test methods in the argumets ($this->successHandler, $this->failureHandler, this->generateRequests($requests)) that are sent to the method sendPoolRequest.
All of these methods are protected. I understand how to test them using a reflection object, but I want to know if there is an option to test them within a single test by checking the values in $ pool_recreate_payments.
You have to fake what the http client is doing in some way. That means: actually calling the callback functions. While it might be possible to do that with a PHPUnit mock object, writing own test doubles is often easier.
Here are a few examples to get you started.
public function testAllFailing()
{
$client = new class implements HttpClient {
public function sendPoolRequest(array $requests, int $numRequests, callable $successHandler, callable $errorHandler)
{
foreach ($requests as $index => $request) {
$errorHandler(new BadResponseException(/*...*/), $index);
}
}
};
$myService = new MyService($client);
$result = $myService->performPoolRequest(/*...*/);
self::assertEquals(/*...*/, $result);
}
public function testAllSucceeding()
{
$client = new class implements HttpClient {
public function sendPoolRequest(array $requests, int $numRequests, callable $successHandler, callable $errorHandler)
{
foreach ($requests as $index => $request) {
$successHandler(new Response(/*...*/), $index);
}
}
};
$myService = new MyService($client);
$result = $myService->performPoolRequest(/*...*/);
self::assertEquals(/*...*/, $result);
}
public function testEveryOtherFails()
{
$client = new class implements HttpClient {
public function sendPoolRequest(array $requests, int $numRequests, callable $successHandler, callable $errorHandler)
{
foreach ($requests as $index => $request) {
if ($index % 2 === 0) {
$successHandler(new Response(/*...*/), $index);
} else {
$errorHandler(new BadResponseException(/*...*/), $index);
}
}
}
};
$myService = new MyService($client);
$result = $myService->performPoolRequest(/*...*/);
self::assertEquals(/*...*/, $result);
}
Since Guzzle is used as the http client, I decided to use its capabilities in terms of creating stubs for the response object. Here is a link to the documentation Guzzle documentation. The solution turned out to be simple and allowed us to extensively test the business logic of the response.
Symfony 4.3
Goal : import a CSV listing employees/company with doctrine and save it into the DB.
I have the Entity company :
<?php
// src/Entity/Company.php
class Company
{
/**
* #ORM\OneToMany(targetEntity="App\Entity\Employee", mappedBy="company")
*/
private $employees;
public function __construct()
{
$this->employees = new ArrayCollection();
}
public function getEmployees()
{
return $this->employees;
}
and the Employee entity :
<?php
// src/Entity/Employee.php
class Employee
{
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Company", inversedBy="employees")
*/
private $company;
public function getCompany()
{
return $this->company;
}
public function setCompany(?Company $company)
{
$this->company = $company;
return $this;
}
Below is my loop for each line of the CSV being imported :
<?php
// $csv = array made from the CSV file
// e.g. $csv[0]['employee_name'] = "John Doe"
// e.g. $csv[0]['employee_mail'] = "john.doe#bmw.com"
// e.g. $csv[0]['employee_company_name'] = "BMW"
// e.g. $csv[0]['employee_company_id'] = 77
foreach($csv as $key => $value)
{
if($company = $this->em->getRepository(Company::class)->find($value['employee_company_id']))
{
// if the employee doest not exist, create it
// IN MY TESTS, HERE IS MY PROBLEM
// DON'T KNOW HOW TO LOOP INSIDE THE EMPLOYEES LIST USING the MAIL
if ($company->getEmployees()->contains($value['employee_mail']))
{
// This employee for this company exists, let's update it
}
else
{
// This employee for this company does not exist, let's create it
}
}
else
{
// Create the company
}
I don't know how to loop inside the company employees list, in order to decide if I have to edit (employee already exists) or create a new employee. Maybe I should not use the ArrayCollection::contains method ?
As Employees is an Doctrine ArrayCollection, you can use the exists method on it. This method accepts a closure as argument which loops over all elements in the collection and returns true when the condition matches.
if ($company->getEmployees()->exists(function ($key, Employee $employee) use ($value) {
return $employee->getEmail() === $value['employee_mail'];
})) {
// Employee exists, update
} else {
// Employee does not exist
}
Alternatively, if you want to create/update the record right away, you can do the following. This returns the Employee if it exists, or create a new Employee object if it doesn't
$employee = $company
->getEmployees()
->filter(function (Employee $employee) use ($value) {
return $employee->getEmail() === $value['employee_mail'];
})
->first() ?? new Employee();
the naive approach would be to just look in the employee repository.
$employee = $em->getRepository(Employee::class)->findOneBy([
'company' => $company,
'email' => $value['employee_mail'],
]);
if($employee) {
// exists -> update
} else {
// create
}
Depending on the company-employee-ratio, it might be better to cycle through the company's employees instead:
$employee = null;
foreach($company->getEmployees() as $_employee) {
if($_employee->getEmail() == $value['employee_mail']) {
$employee = $_employee;
break; // short circuit the loop
}
}
// rest is the same
If there are is very large number of employees in your csv and/or the database, it might be even better to skip the ORM alltogether and go straight to the database instead. Otherwise, you might want to clear the entity manager once in a while, if you have a huge database / csv.
You should store first your list of employees :
$employees = $company->getEmployees()
then you loop inside $employees :
foreach($employees as $employee ) //supposedly you have 'email' property
{
if ($employee->getEmail() == $value['employee_mail'])
{
//your code here
}
}
don't forget to add this :
* #ORM\OneToMany(targetEntity="App\Entity\Employee", mappedBy="company", fetch="EAGER")
Update :
$qb = $repository->createQueryBuilder('a')
->where(':employee_mail OF a.emplyee_email')
->setParameter('employee_mail ', $value['employee_mail']);
->andWhere(':comapanyId MEMBER OF a.company_id');
->setParameter('comapanyId ', $value['employee_company_id']);
->getQuery()
->execute()
this will return null if the employee does not exist in the company, you should implement this as you need in your code.
I have this issue where I'm attempting to create a subrequest in Silex and basically forward my parameters to another controllers. Exhibit A is broken below (after attempts to refactor), and Exhibit B, the original version, works:
Exhibit A ($this->app is lost after creating the request):
class EntriesController {
private $app;
private $req;
public function __construct($app, $req) {
$this->app = $app;
$this->req = $req;
}
public function updateAction() {
//...
//$url defined here (eyesore-ingly long, so not shown)
$subRequest = Request::create($url, 'GET', $params, $this->req->cookies->all(), array(), $this->req->server->all());
//$this->app **no longer** exists here
return $this->app->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
}
}
EntriesController instance is created below:
class AppControllerProvider implements ControllerProviderInterface {
public function connect(Application $app) {
$controllers = $app['controllers_factory'];
//...
$controllers->patch('/edit', function (Request $req) use ($app) {
$entriesCtrl = new \EntriesController($app, $req);
return $entriesCtrl->updateAction();
});
//...
}
Exhibit B (works just fine):
class AppControllerProvider implements ControllerProviderInterface {
public function connect(Application $app) {
$controllers = $app['controllers_factory'];
$controllers->patch('/edit', function (Request $req) use ($app) {
//...
//$url defined here
$subRequest = Request::create($url, 'GET', $params, $this->req->cookies->all(), array(), $this->req->server->all());
return $this->app->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
}
//...
});
I basically just reorganized the logic from Exhibit B's 'PATCH' /edit method body into a controller class, and I passed the Silex Application instance $app to a new instance of the controller class.
The only difference between Exhibit A and Exhibit B as far as I can tell is that you instantiate a controller object in the path method callback. Maybe there is something wrong with how this controller is setup or a namespace issue? Shootin' in the dark here.
I can confirm that in my silex application the following code does not produce a null $app container:
GlobalControllerProvider.php
<?php
namespace Dev\Pub\Provider\Controller;
use Silex\Application;
use Silex\ControllerProviderInterface;
use Symfony\Component\HttpFoundation\Request;
class GlobalControllerProvider implements ControllerProviderInterface
{
public function connect(Application $app)
{
$controllers = $app['controllers_factory'];
$controllers
->get('/', 'Dev\Pub\Controller\GlobalController::indexAction')
->bind('homepage')
;
$controllers
->patch('/edit', function (Request $req) use ($app) {
$entriesCtrl = new \Dev\Pub\Controller\GlobalController();
return $entriesCtrl->updateAction($app, $req);
});
return $controllers;
}
}
GlobalController.php
<?php
namespace Dev\Pub\Controller;
use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface;
class GlobalController
{
public function indexAction(Application $app, Request $request)
{
return new Response($app['twig']->render('index.html.twig'));
}
public function updateAction(Application $app, Request $request)
{
$url = 'http://silex.local/index_dev.php/';
$params = array();
$subRequest = Request::create($url, 'GET', $params, $request->cookies->all(), array(), $request->server->all());
// outputs: 'Silex\Application'
error_log(print_r(get_class($app),1).' '.__FILE__.' '.__LINE__,0);
// outputs: 1
error_log(print_r(is_object($app),1).' '.__FILE__.' '.__LINE__,0);
return $app->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
}
}
index.main.js
$(function(){
console.log('index.main.js');
$.ajax({
url: "http://silex.local/index_dev.php/edit",
method: "PATCH"
}).done(function( data ) {
console.log(data);
}).fail(function( data ) {
console.log(data);
});
});
Please i need some help:
I have the following services:
SERVICE CONFIGURATION IN services.yml
services:
xpad.producto_repository:
class: Xpad\ProductoBundle\Entity\ProductRepository
factory_service: doctrine.orm.clientes_entity_manager
factory_method: getRepository
arguments:
- Xpad\ProductoBundle\Entity\Product
backend_cliente.producto_filters:
class: Xpad\BackendClienteBundle\Filters\ProductFilters
calls:
- [setRepository, ["#xpad.producto_repository="]]
scope: container
AND THE CLASS FOR backend_cliente.producto_filters IS:
namespace Xpad\BackendClienteBundle\Filters;
use Xpad\ProductoBundle\Entity\ProductRepository;
class ProductFilters
{
private $_queryBuilder;
public function getQueryBuilder()
{
return $this->_queryBuilder;
}
public function setQueryBuilder($queryBuilder)
{
$this->_queryBuilder = $queryBuilder;
}
public function setRepository(ProductRepository $productRepository = null)
{
if($this->_queryBuilder == null)
{
$this->_queryBuilder = $productRepository->createQueryBuilder('p');
}
}
}
AND I HAVE THE FOLLOWING ACTION IN ONE OF MY CONTROLLERS:
class ProductController extends Controller
{
............
public function indexAction(Request $request)
{
//SOME CODE
$service_filter = $this->container->get('backend_cliente.producto_filters');
$queryBuilder = $service_filter->getQueryBuilder();
//SOME OTHER CODE
}
MY PROBLEM IS: ANYTIME THAT THE indexAction is execute I GOT A NEW INSTANCE OF backend_cliente.producto_filters SERVICE AND I DON'T KNOW WHY. I NEED AND UNIQUE INSTANCE AS A SINGLETON BECAUSE A HAVE THE $_queryBuilder ATRIBUTTE AND I NEED TO GET THE VALUE OF IT JUST MODIFY ITS VALUE WHEN IS NEEDED;
PLEASE HELP I DON KNOW WHAT I'M DOING WRONG.
Do you have to use the class as a service?
Why don't you use a Singleton, without using a service? Like this:
namespace Xpad\BackendClienteBundle\Filters;
use Xpad\ProductoBundle\Entity\ProductRepository;
class ProductFilters {
private $_queryBuilder;
private static $reference = null;
public function getInstance(){
if (self::$reference === null)
self::$reference = new ProductFilters();
return self::$reference;
}
private function __construct(){}
public function getQueryBuilder()
{
return $this->_queryBuilder;
}
public function setQueryBuilder($queryBuilder)
{
$this->_queryBuilder = $queryBuilder;
}
public function setRepository(ProductRepository $productRepository = null)
{
if($this->_queryBuilder == null)
{
$this->_queryBuilder = $productRepository->createQueryBuilder('p');
}
}
}
And call it with:
...
$filter = ProductFilters::getInstance();
...
I'm trying to write a Twig filter to be able to sort a Doctrine ArrayCollection, but the returned array is not sorted :( Can you please help me to fix this:
class SortExtension extends \Twig_Extension
{
public function getFilters()
{
return array(
new \Twig_SimpleFilter('sortby', array($this, 'sortByFilter')),
);
}
public function sortbyname( $a, $b )
{
if ($a->getName() === $b->getName()) {
return 0;
}
if ( $a->getName() < $b->getName() ) {
return 1;
}
return -1;
}
public function sortByFilter($collection)
{
$iterator = $collection->getIterator();
$iterator->uasort(array($this, 'sortbyname'));
return $collection;
}
I'm not quite sure if the returned collection in sortByFilter is changed.
This is because you are getting the iterator and sorting it.
The method getIterator creates a new ArrayIterator which makes a copy of the array.
Then, you are returning the collection, which is not sorted.
Here is a little sample of what happens.
You just have to replace
return $collection;
By
return $iterator;