How can i add collection of entities in doctrine2 to main entity - symfony

I have the
class user
{
#manytomany
private $countries
}
and
class country {
#manytomany
private $user
}
Now when the new user is created then i want to add predefined list of countries to user
$countries = $em->getRepository(Country)->findBy(array('population'=>'2000'))
I want to know how can i add those all countries in user entity
Is it possible
$user->addCountry($countries)

Will it work?
class user
{
private $countries;
public function addCountries($countries) {
foreach ($countries as $country) {
$this->countries[] = $country;
}
}
}

Related

Symfony 5 select QueryBuilder select object return soft deleted propery

I have object: Shipment with property Supplier of class Company
App\Entity\Shipment id:1234 status:"wait" supplier:Proxies\__CG__\App\Entity\Company
when $qb = $this->createQueryBuilder('s')->select('s');
return shipment object with supplier that soft deleted
why?
I want the object shipment return with supplier: null
I tried to ask if supplier is deleted in shipment class, but did not help
public function getSupplier(): ?Company
{
try {
if (!($this->supplier instanceof Company) || ($this->supplier->isDeleted())) {
return null;
}
} catch (EntityNotFoundException $exception) {
return null;
}
return $this->supplier;
}

Symfony - ArrayCollection - Update or Create

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.

How to generate a password automatically and fill in password fields when creating a member in the CMS?

I have created a Model Admin called 'Clients'. Under the "Security" tab I created a new group called 'clients'. This Model Admin is managing just the clients and not other members.
When creating a new member in the CMS using a model admin, I want to automatically generate a password for them (instead of them having to create their own one) one for them and then email it to them.
What I want to happen:
After the staff member clicks "Add member" the password and password confirmation textboxs are automatically populated with the generated password. - This is the most ideal way I believe. - Then once the staff member clicks save it will send the client and email with the username and newly generated password.
Question is how do you do this?
ClientAdmin.php
<?php
class ClientAdmin extends ModelAdmin {
private static $menu_icon = 'themes/cadence/images/icons/person.png';
public $showImportForm = false;
private static $managed_models = array(
'Member'
);
private static $url_segment = 'clients';
private static $menu_title = 'Clients';
public function getList() {
$list = parent::getList();
$clientGroup = Group::get()->filter('code', 'clients')->first();
$list = $list->filter('Groups.ID', $clientGroup->ID);
return $list;
}
}
MemberClientExtension.php
<?php
class MemberClientExtension extends DataExtension implements PermissionProvider
{
private static $db = array(
);
public function providePermissions() {
return array(
'CLIENTS' => 'Can access the site as a client',
);
}
public function updateCMSFields(FieldList $fields) {
}
public function generatePasswordForClient(){
$plainPassword = $this->owner->create_new_password();
$encryptedPassword = $this->owner->encryptWithUserSettings($plainPassword);
// Need to set password in database here?
return $plainPassword;
}
public function sendClientWelcomeEmail() {
$email = new Email('email#gmail.com', 'email#gmail.com', 'New member sign up');
$email->setTemplate('NewClientSignUp');
$email->populateTemplate(array(
'Email' => $this->owner->Email,
'Password' => $this->generatePasswordForClient()
));
return $email->send();
}
public function onBeforeWrite()
{
parent::onBeforeWrite();
}
public function onAfterWrite()
{
parent::onAfterWrite();
// Seems to send 2x emails.. Only want to send one
$this->sendClientWelcomeEmail();
}
}
You should set temporary plain text password in SetPassword field, and manage the context when onBeforeWrite and onAfterWrite hooks are called.
class MemberClientExtension extends DataExtension
{
protected $sendWelcomeEmail = false;
...
// onBeforeWrite on extension is called after password is encrypted and set
public function validate(ValidationResult $validationResult) {
if (!$this->owner->isInDB()) {
$this->sendWelcomeEmail = true;
}
}
public function onAfterWrite() {
if ($this->sendWelcomeEmail) {
// reset for password change
$this->sendWelcomeEmail = false;
$password = $this->generatePasswordForClient();
$this->owner->changePassword($password);
$this->sendClientWelcomeEmail(array(
'Email' => $this->owner->Email,
'Password' => $password;
));
}
}
}
You could use the populateDefaults() function in your Member extension.
public function populateDefaults() {
$this->owner->changePassword($this->generatePasswordForClient());
}
But let me say this: This is a bad idea. You don't want to send plain text passwords over something as insecure as an email. To let the user to choose its own password is by far the better way.

How do i use abstraction to modify this class so it shows both colors of keys?

So Currently, our Key class can only produce white keys. This is because I have hard-coded the file names of the key images ("white-key.png" and "white-key-down.png"). How do I use abstraction to modify the Key class so that it can show either white or black keys?
import greenfoot.*; // (World, Actor, GreenfootImage, and Greenfoot)
public class Key extends Actor
{
private boolean isDown;
private String key;
private String sound;
/**
* Create a new key.
*/
public Key(String keyName, String soundFile)
{
key = keyName;
sound = soundFile;
}
/**
* Do the action for this key.
*/
public void act()
{
if ( !isDown && Greenfoot.isKeyDown(key))
{
play();
setImage("white-key-down.png");
isDown = true;
}
if ( isDown && !Greenfoot.isKeyDown(key))
{
setImage("white-key.png");
isDown = false;
}
}
/**
* Play the note of this key.
*/
public void play()
{
Greenfoot.playSound(sound);
}
}
I understand from your question that you want different classes that have different images, rather than an option for changing the image within the same class.
There are several ways to do this; here's a simple one just to give you an idea:
import greenfoot.*; // (World, Actor, GreenfootImage, and Greenfoot)
abstract public class Key extends Actor
{
private boolean isDown;
private String key;
private String sound;
abstract String getImageFileName();
abstract String getDownImageFileName();
/**
* Create a new key.
*/
public Key(String keyName, String soundFile)
{
key = keyName;
sound = soundFile;
}
/**
* Do the action for this key.
*/
public void act()
{
if ( !isDown && Greenfoot.isKeyDown(key))
{
play();
String image = getDownImageFileName();
setImage(image);
isDown = true;
}
if ( isDown && !Greenfoot.isKeyDown(key))
{
String image = getImageFileName();
setImage(image);
isDown = false;
}
}
/**
* Play the note of this key.
*/
public void play()
{
Greenfoot.playSound(sound);
}
}
Then you can add new classes, each with its own image:
public class WhiteKey extends Key
{
#Override
String getImageFileName()
{
return "white-key.png";
}
#Override
String getDownImageFileName()
{
return "white-key-down.png";
}
}
public class BlackKey extends Key
{
#Override
String getImageFileName()
{
return "black-key.png";
}
#Override
String getDownImageFileName()
{
return "black-key-down.png";
}
}

How can I split router lists into modules?

I have this router list:
$router[] = $adminRouter = new RouteList('Admin');
$adminRouter[] = new Route('admin/<presenter>/<action>', 'Dashboard:default');
$router[] = $frontRouter = new RouteList('Front');
$frontRouter[] = new Route('<presenter>/<action>[/<id>]', 'Homepage:default');
But it would be better to separate those two router lists into module folders. In the future I will probably create more modules with different router lists. How can I separate it and after how can I register it?
Use RouteFactory
In config.neon
services:
routeFactory.Front: FrontModule\RouteFactory
routeFactory.Admin: AdminModule\RouteFactory
route:
class: \RouteFactory
setup:
- addRouteFactory(#routeFactory.Front)
- addRouteFactory(#routeFactory.Admin)
Class RouteFactory:
class RouteFactory
{
/** #var array */
private $routerFactories = array();
public function addRouteFactory(IRouteFactory $routeFactory)
{
$this->routeFactories[] = $routeFactory;
}
public function createRouter()
{
$router = new RouteList();
foreach ($this->routeFactories as $routeFactory) {
$router[] = $routeFactory->createRouter();
}
return $router;
}
}
Interface IRouteFactory:
interface IRouteFactory
{
public function createRouter();
}
Module route factories
namespace FrontModule;
class RouteFactory implements \IRouteFactory
{
public function createRouter()
{
$router = new RouteList('Front');
// your routes
return $router;
}
}

Resources