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;
Related
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 try to give '$_REQUEST['vider']' to an other controller like this :
return $this->forward('TestBundle:Rapport:bo', array('vider' => 'vider'));
//, array ($_REQUEST['vider'] => 'vider) doesn't work too
But in my function Rapport:bo, $_REQUEST['vider'] is null, i give it in the array, where i failed ?
edit :
my Rapport:bo function :
public function boAction(Request $request) {
var_dump($_REQUEST['vider']); // is null
if ( isset($_REQUEST['vider']) ) ) {
var_dump('test');
}
}
Try with:
public function boAction($vider) {
your logic...
In Sonata Admin, in one of my section, I would like to disable the delete action for 4 specific entries/lines.
I know I can do it for the entire listing with configureRoutes() but couldn't find anything for specific entries.
I tried the following but getSubject() is always empty.
protected function configureRoutes(RouteCollection $collection)
{
$product = $this->getSubject();
if ($product && $product->getIsBase())
{
$collection->clearExcept(array('list', 'edit'));
}
}
Thanks
You have to override the isGranted method in your admin class:
public function isGranted($name, $object = null)
{
if (in_array($name, array('LIST', 'EDIT')) && $object && $object->getIsBase()) {
return false;
}
return parent::isGranted($name, $object);
}
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;
}
How do I add a button to the backend of the CMS that fires an action? I can display the button where I want using:
public function getCMSFields()
{
$fields = parent::getCMSFields();
$fields->addFieldsToTab("Root.ButtonTest", array(
FormAction::create('doAction', 'Action button')
)
);
return $fields;
}
public function doAction()
{
//Do something
}
However the button added does nothing when clicked.
I've seen one example of how to put a button on the main action bar (next to save/publish) but that's not what I'm trying to do.
Looking at the only page of documentation I can find, do I need to do something within:
public function getCMSActions()
{
$actions = parent::getCMSActions();
//Something here?
}
It isn't very clear how to create the action that the button calls.
You'll have to extend/decorate LeftAndMain with your own extension and the action you want to call. Here's an example:
<?php
class MyExtension extends LeftAndMainExtension
{
private static $allowed_actions = array(
'doAction'
);
public function doAction($data, $form){
$className = $this->owner->stat('tree_class');
$SQL_id = Convert::raw2sql($data['ID']);
$record = DataObject::get_by_id($className, $SQL_id);
if(!$record || !$record->ID){
throw new SS_HTTPResponse_Exception(
"Bad record ID #" . (int)$data['ID'], 404);
}
// at this point you have a $record,
// which is your page you can work with!
// this generates a message that will show up in the CMS
$this->owner->response->addHeader(
'X-Status',
rawurlencode('Success message!')
);
return $this->owner->getResponseNegotiator()
->respond($this->owner->request);
}
}
Once you have written an extension like this, you'll have to apply it to LeftAndMain by adding the following to your mysite/_config/config.yml:
LeftAndMain:
extensions:
- MyExtension
That's it. Your doAction button should now actually do something!
Not sure if this is helpful, but here's how you can add action-buttons to a ModelAdmin.
(does reload the page)
...in the admin class:
public function getEditForm($id = null, $fields = null)
{
$form = parent::getEditForm($id, $fields);
$form
->Fields()
->fieldByName($this->sanitiseClassName($this->modelClass))
->getConfig()
->getComponentByType('GridFieldDetailForm')
->setItemRequestClass('MyGridFieldDetailForm_ItemRequest');
return $form;
}
MyGridFieldDetailForm_ItemRequest.php
class MyGridFieldDetailForm_ItemRequest extends GridFieldDetailForm_ItemRequest
{
function ItemEditForm()
{
$form = parent::ItemEditForm();
$formActions = $form->Actions();
$button = FormAction::create('myAction');
$button->setTitle('button label');
$button->addExtraClass('ss-ui-action-constructive');
$formActions->push($button);
$form->setActions($formActions);
return $form;
}
public function myAction(){ //do things }
}