recursive symfony 3.4 complex form embed - symfony

Having this schema:
I Would like make a form like that
I don't know how can I build the form.
A company haves many persons.
In a company we have a corporate hierarchy : employee, boss, subordinate
A worker can have subordinate worker It's why I have added a recurve link inside company_person for parent child notion link.
The Role is here for saying what is the role of the worker.
Company
class Company
{
/**
* #ORM\Column(type="string", length=45, nullable=true)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="CompanyPerson", mappedBy="company")
*/
private $companypersons;
}
Person
class Person
{
/**
* #ORM\Column(type="string", length=45, nullable=true)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="CompanyPerson", mappedBy="person", cascade={"persist"})
* #var ArrayCollection
*/
private $companypersons;
}
Status
class Status
{
/**
* #ORM\Column(type="string", length=45, nullable=true)
*/
private $libelle;
/**
* #ORM\OneToMany(targetEntity="CompanyPerson", mappedBy="statut")
*/
protected $companypersons;
}
CompanyPerson
class CompanyPerson
{
/**
* #ORM\Column(type="datetime", nullable=true)
*/
private $dateAdded;
/**
* #ORM\ManyToOne(targetEntity="Company", inversedBy="companypersons",
cascade={"persist"})
*/
private $company;
/**
* #ORM\ManyToOne(targetEntity="Person",
inversedBy="companypersons", cascade={"persist"})
*/
private $person;
/**
* #ORM\ManyToOne(targetEntity="Status",
inversedBy="companypersons", cascade={"persist"})
*/
private $status;
/**
* #ORM\ManyToOne(targetEntity="CompanyPerson", inversedBy="childrens")
*/
private $parent;
/**
* #ORM\OneToMany(targetEntity="CompanyPerson", mappedBy="parent")
*/
private $childrens;
public function __construct(Company $companay= null, Person $person =
null, CompanyPerson $parent = null, Status $status = null)
{
$this->companay = $companay;
$this->person = $person;
$this->parent = $parent;
$this->status = $status;
$this->childrens = new ArrayCollection();
}
}
I worked on embed form with collections, but I can not create this form, because it is't very complex for me.

You have rational problems in your mapping. you should use the parent-children pattern inside Person , not CompanyPerson. a CompanyPerson here, is a joint-entity. It is there to persist things like:
$dateAdded : the date at which a relation between a Company and a Person and an Status was stablished.
Person
class Person
{
/**
* #ORM\Column(type="string", length=45, nullable=true)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="CompanyPerson", mappedBy="person", cascade={"persist"})
* #var ArrayCollection
*/
private $companypersons;
/**
* #ORM\ManyToOne(targetEntity="Person", inversedBy="childrens")
*/
private $parent;
/**
* #ORM\OneToMany(targetEntity="Person", mappedBy="parent")
*/
private $children;
}
Note to keep children without s, as it is already in plural form.
Also keep in mind to use cascade operations on mapping definition on the inverse side of association. So the
class CompanyPerson
{
/**
* #ORM\ManyToOne(targetEntity="Company", inversedBy="companypersons", cascade={"persist"})
*/
private $company;
/**
* #ORM\ManyToOne(targetEntity="Person", inversedBy="companypersons", cascade={"persist"})
*/
private $person;
}
should be
class CompanyPerson
{
/**
* #ORM\ManyToOne(targetEntity="Company", inversedBy="companypersons")
*/
private $company;
/**
* #ORM\ManyToOne(targetEntity="Person", inversedBy="companypersons")
*/
private $person;
}
Your Question
To get around the so called, complex form! you may use mapped=false setting attribute in your fields. using this, you may tell the symfony that this field, is not mapped to a property of the target entity of your form.
In your case you have CompanyType form (with Company as target entity) and want to create all those stuff in one go! So you may add unmapped fields to your form and capture theme inside your controller and parse theme as you wish.
Using this method, you should create joint-entities (like Company-Person) by your self (again inside your controller).

Related

Symfony OneToMany with associative array : new row inserted instead of update

I have to internationalize an app and particularly an entity called Program. To do so, I created an other entity ProgramIntl which contains a "locale" attribute (en_GB, fr_FR, etc) and strings which must be internationalized. I want the programIntl attribute in Program to be an associative array (with locale as key).
We have an API to read/write programs. GET and POST works fine but when I want to update data (PUT), the programIntl is not updated: an insert query is launched (and fails because of the unique constraint, but that's not the question).
Here is the code:
In Program.php:
/**
* #var
*
* #ORM\OneToMany(targetEntity="ProgramIntl", mappedBy="program", cascade={"persist", "remove", "merge"}, indexBy="locale", fetch="EAGER")
* #ORM\JoinColumn(nullable=false, onDelete="cascade")
* #Groups({"program_read", "program_write"})
*/
private $programIntl;
public function addProgramIntl($programIntl)
{
$this->programIntl[$programIntl->getLocale()] = $programIntl;
$programIntl->setProgram($this);
return $this;
}
public function setProgramIntl($programIntls)
{
$this->programIntl->clear();
foreach ($programIntls as $locale => $programIntl) {
$programIntl->setLocale($locale);
$this->addProgramIntl($programIntl);
}
}
public function getProgramIntl()
{
return $this->programIntl;
}
In ProgramIntl.php:
/**
* #ORM\Entity(repositoryClass="App\Repository\ProgramIntlRepository")
* #ORM\Table(name="program_intl",uniqueConstraints={#ORM\UniqueConstraint(name="program_intl_unique", columns={"program_id", "locale"})})
*/
class ProgramIntl
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
* #Groups({"program_read", "program_write"})
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Program", inversedBy="programIntl")
* #ORM\JoinColumn(nullable=false)
*/
private $program;
/**
* #ORM\Column(type="string", length=5, options={"fixed" = true})
*/
private $locale;
/**
* #ORM\Column(type="string", length=64)
* #Assert\NotBlank()
* #Groups({"program_read", "program_write"})
*/
private $some_attr;
/* ... */
}
Any idea of what could be the reason of the "insert" instead of "update" ?
Thanks
I forgot to mention that we use api-platform.
But I found the solution myself. In case anyone is interested, adding the following annotation to classes Program and ProgramIntl solved the problem:
/* #ApiResource(attributes={
* "normalization_context"={"groups"={"program_read", "program_write"}},
* "denormalization_context"={"groups"={"program_read", "program_write"}}
* }) */

Symfony Iterating over ArrayCollection

In my app I have 2 entities; User & Booking.
Booking entity:
namespace App\Entity;
/**
* #ORM\Table(name="booking")
* #ORM\Entity(repositoryClass="App\Repository\BookingRepository")
*/
class Booking
{
/**
* #ORM\Column(type="boolean")
* #Assert\NotBlank()
*/
private $isActive;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="bookings")
*/
private $user;
User entity:
/**
* #ORM\Table(name="app_user")
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
* #UniqueEntity(fields="email", message="This email address is already in use")
*/
class User implements AdvancedUserInterface
{
/**
* #ORM\Column(type="string", length=255, unique=true)
* #Assert\NotBlank()
* #Assert\Email()
*/
private $email;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Booking", mappedBy="user")
* #Expose
*/
private $bookings;
/**
* User constructor.
*/
public function __construct()
{
$this->bookings = new ArrayCollection();
}
I tried to add a function to my user entity that returns the active booking, I tried this:
/**
* #return mixed
*/
public function getActiveBooking()
{
foreach( $this->bookings as $booking ) {
if( $booking->getIsActive() ) {
return $booking;
}
}
}
But I get the following error: Error: Call to a member function getRoom() on null
When I call it using $user->getActiveBooking()->getRoom()->getId()
Make sure that the user you are working with has an active booking.
getActiveBooking() is returning null because it seems user does not have an active booking.
That's why you are getting an error that you cannot call getRoom() on null because the previous function has returned null.
Have you tried to add a joinColumn like this:
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="bookings")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false)
*/
private $user;

Doctrine doesn't update/generate fields of ManyToOne and OneToMany

I have a superclass that currently works fine (all relations and properties are updating to the database)
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Table;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\ManyToOne;
use Doctrine\ORM\Mapping\OneToMany;
use Doctrine\ORM\Mapping\JoinColumn;
use JMS\Serializer\Annotation as JMS;
/**
* Document
*
* #Table(name="document")
* #Entity(repositoryClass="AcmeBundleDocumentRepository")
*/
class Document
{
/**
* #var string
*
* #Column(name="id", type="string")
* #Id
* #GeneratedValue(strategy="UUID")
*/
protected $id;
/**
* #var string
* #Column(name="name", type="string", length=255)
*/
protected $name;
/**
* #var string
* #Column(name="type", type="string", length=255)
*/
protected $type;
/**
* #var boolean
* #Column(name="has_attachments", type="boolean")
*/
protected $hasAttachments;
/**
* #ManyToOne(targetEntity="Delivery")
* #JoinColumn(name="delivery_id", referencedColumnName="id", nullable=false)
* #JMS\Exclude()
*/
protected $delivery;
/**
* #OneToMany(targetEntity="Extension", mappedBy="document", cascade={"persist","remove"})
**/
protected $extensions;
public function __construct()
{
$this->extensions = new ArrayCollection();
}
/* getter and setters */
}
Now I've created a entity called Note that extends to Document entity
use Doctrine\ORM\Mapping\Table;
use Doctrine\ORM\Mapping\Entity;
/**
* Note
*
* #Table(name="note")
* #Entity(repositoryClass="NoteRepository")
*/
class Note extends Document
{
}
I am suppose that the table/entity note should generate the same things of the class that extends. But not do it
I run php bin/console doctrine:schema:update -f
this only generates properties and not FK (foreing Keys), in this case #ManyToOne and #OneToMany.
Additionally maybe help us, i have those entities on the same database
I am doing something wrong ?
As per docs I think you're missing the #MappedSuperclass annotation or you're using Doctrine inheritance in the wrong way. Be aware that a MappedSupperClass is not an entity by itself instead is just a class for share common methods and properties among it is children classes (same inheritance concept that you should already know).
/**
* #MappedSuperclass
*/
class DocumentSuperClass
{
...
}
/**
* #Table(name="document")
* #Entity(repositoryClass="AcmeBundleDocumentRepository")
*/
class Document extends DocumentSuperClass
{
...
}
/**
* #Table(name="note")
* #Entity(repositoryClass="NoteRepository")
*/
class Note extends DocumentSuperClass
{
...
}

Symfony2 - Database table relationships

I was working on an app and had everything set up nicely. I have an Alert entity which takes the following form.
/**
* Alert
*
* #ORM\Table(name="alert")
* #ORM\Entity(repositoryClass="Nick\AlertBundle\Repository\AlertRepository")
*
*/
class Alert
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="search_command", type="string", length=256, nullable=false)
*/
private $searchCommand;
/**
* #var string
*
* #ORM\Column(name="is_connecting", type="string", length=20, nullable=false)
*/
private $isConnecting;
/**
* #var \DateTime
*
* #ORM\Column(name="last_updated", type="datetime", nullable=false)
*/
private $lastUpdated;
/**
* #var boolean
*
* #ORM\Column(name="is_deleted", type="boolean", nullable=false)
*/
private $isDeleted;
/**
* #var string
*
* #ORM\Column(name="alert_status", type="string", length=11, nullable=false)
*/
private $alertStatus;
/**
* #ORM\OneToMany(targetEntity="Nick\AlertBundle\Entity\BookingClass", mappedBy="availabilityAlert")
*/
private $bookingClass;
/**
* #ORM\OneToMany(targetEntity="Nick\AlertBundle\Entity\Pseudos", mappedBy="availabilityAlert")
*/
private $pseudo;
/**
* #ORM\OneToMany(targetEntity="Nick\AlertBundle\Entity\FlightNumbers", mappedBy="availabilityAlert")
*/
private $flightNumbers;
/**
* #ORM\OneToMany(targetEntity="Nick\AlertBundle\Entity\Availability", mappedBy="availabilityAlert")
*/
private $availability;
/**
* Constructor
*/
public function __construct()
{
$this->bookingClass = new ArrayCollection();
$this->pseudo = new ArrayCollection();
$this->flightNumbers = new ArrayCollection();
$this->availability = new ArrayCollection();
}
//other methods
}
So I had this app working, but now I have decided to add a log in system. Each Alert should now be associated to a user - a user can have none to many alerts.
So I have created my user class and set up all the security. The entity looks like
/**
* User
*
* #ORM\Table(name="user_table")
* #ORM\Entity()
*
*/
class User implements UserInterface
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="username", type="string", length=255, nullable=false)
*/
private $username;
/**
* #var string
*
* #ORM\Column(name="password", type="string", length=255, nullable=false)
*/
private $password;
//other functions
}
So whats the best way to associate Alerts to a User? Should I set it up like I do the with the other Entities in my Alert class? Should I add an Alert variable within the user class which is an Array Collection of Alerts?
Really just looking for a bit of advice how to best handle this.
Thanks
Yes you should add one to many relation ship in user entity and link with alert entity and in alert entity point back to user entity in many to one way
class User
{
/**
* #ORM\OneToMany(targetEntity="Alert", mappedBy="user")
*/
protected $alerts;
public function __construct()
{
$this->alerts= new ArrayCollection();
}
//... other getters amd setters
}
class Alert
{
// ...
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="alerts")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
}
Reference
I dont really see here any other solutions besides using ManyToOne association in the Alert entity or ManyToMany in case one Alert has to be associated with more then 1 user.
Should I add an Alert variable within the user class which is an Array Collection of Alerts?
Heck, why not? That way you can easily get users with joined alerts.

Symfony2/Doctrine2 Inheritance

I'm attempting to accomplish BASIC inheritance in Doctrine 2, but I'm running into several major issues. Such a task should not be so complicated. Let's get down to business...
I have three classes, BaseFoodType, Drink, and Snack. My BaseFoodType has the following class definition:
/** #ORM\MappedSuperclass */
class BaseFoodType {
/**
* #ORM\Column(type="integer", length=7)
*/
public $budget = 0;
}
Which follows the instructions for inheritance on the doctrine website: http://docs.doctrine-project.org/en/2.0.x/reference/inheritance-mapping.html
Here is what the sub-classes look like prior to generating my entities:
namespace MySite\MainBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* MySite\MainBundle\Entity\EventDrink
*
* #ORM\Table(name="drink")
* #ORM\Entity
*/
class Drink extends BaseFoodType {
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="integer", length=5, nullable=true)
*/
public $people_count;
}
Both Drink, and Snack inherit from this base class but I'm running into numerous issues when attempting to build my entities using the doctrine:generate:entities command. First, Symfony inserts a private "budget" property into each subclass, along with getters and setters (THIS DEFEATS THE PURPOSE INHERITANCE)
/**
* #var integer
*/
private $budget;
/**
* Set budget
*
* #param integer $budget
*/
public function setBudget($budget)
{
$this->budget = $budget;
return $this;
}
/**
* Get budget
*
* #return integer
*/
public function getBudget()
{
return $this->budget;
}
Second, I'm getting a fatal error:
Fatal error: Access level to MySite\MainBundle\Entity\Drink::$budget
must be public (as in class MySite\MainBundle\Entity\BaseFoodType) in
C:\xampp\htdocs\MySite\src\MySite\MainBundle\Entity\Drink.php on line
197
I could probably make the generated properties public and be on my way, but again, that defeats the purpose of inheritance!
Thanks in advance for any insight.
Doctrine provides the means to specify the visibility of generated fields. Either protected or private. The default is private.
The problem is that the Symfony command that invokes Doctrine offers no way to change this.
Creating your own subclass of the standard Symfony command will allow you more control over the generation process. This might help you along.
namespace Foo\Bundle\FooBundle\Command;
use Doctrine\Bundle\DoctrineBundle\Command as DC;
use Doctrine\ORM\Tools\EntityGenerator;
class GenerateEntitiesDoctrineCommand extends DC\GenerateEntitiesDoctrineCommand
{
protected function configure()
{
parent::configure();
$this->setName('foo:generate:entities');
}
/**
* get a doctrine entity generator
*
* #return EntityGenerator
*/
protected function getEntityGenerator()
{
$entityGenerator = new EntityGenerator();
$entityGenerator->setGenerateAnnotations(true);
$entityGenerator->setGenerateStubMethods(true);
$entityGenerator->setRegenerateEntityIfExists(false);
$entityGenerator->setUpdateEntityIfExists(true);
$entityGenerator->setNumSpaces(4);
$entityGenerator->setAnnotationPrefix('ORM\\');
$entityGenerator->setFieldVisibility($entityGenerator::FIELD_VISIBLE_PROTECTED);
return $entityGenerator;
}
}
This does two things. It sets the property visibility to protected. This prevents php errors.
$entityGenerator->setFieldVisibility($entityGenerator::FIELD_VISIBLE_PROTECTED);
It also copies the annotations from mapped super class into the entity class.
$entityGenerator->setGenerateAnnotations(true);
Here's some example code where properties are inherited from a base class and their visibility and annotations copy correctly into the inheriting class
/** #ORM\MappedSuperclass */
class DataSuper {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Campaign", inversedBy="data")
* #ORM\JoinColumn(name="campaign_id", referencedColumnName="id")
* #Exclude
*/
protected $campaign;
/**
* #ORM\Column(type="text", nullable=true, name="data")
*/
protected $data;
/**
* #ORM\Column(type="datetime")
*/
protected $createdDate;
}
/**
* #ORM\Entity(repositoryClass="Foo\Bundle\FooBundle\Entity\DataRepository")
* #ORM\Table(name="data")
* #ExclusionPolicy("none")
*/
class Data extends DataSuper
{
}
After generation the Data class looks like:
class Data extends DataSuper
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", precision=0, scale=0, nullable=false, unique=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #var string
*
* #ORM\Column(name="data", type="text", precision=0, scale=0, nullable=true, unique=false)
*/
protected $data;
/**
* #var \DateTime
*
* #ORM\Column(name="createdDate", type="datetime", precision=0, scale=0, nullable=false, unique=false)
*/
protected $createdDate;
/**
* #var \Foo\Bundle\FooBundle\Entity\Campaign
*
* #ORM\ManyToOne(targetEntity="Foo\Bundle\FooBundle\Entity\Campaign", inversedBy="data")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="campaign_id", referencedColumnName="id", nullable=true)
* })
*/
protected $campaign;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set data
*
* #param string $data
* #return Data
*/
public function setData($data)
{
$this->data = $data;
return $this;
}
/**
* Get data
*
* #return string
*/
public function getData()
{
return $this->data;
}
/**
* Set createdDate
*
* #param \DateTime $createdDate
* #return Data
*/
public function setCreatedDate($createdDate)
{
$this->createdDate = $createdDate;
return $this;
}
/**
* Get createdDate
*
* #return \DateTime
*/
public function getCreatedDate()
{
return $this->createdDate;
}
/**
* Set campaign
*
* #param \Foo\Bundle\FooBundle\Entity\Campaign $campaign
* #return Data
*/
public function setCampaign(\Foo\Bundle\FooBundle\Entity\Campaign $campaign = null)
{
$this->campaign = $campaign;
return $this;
}
/**
* Get campaign
*
* #return \Foo\Bundle\FooBundle\Entity\Campaign
*/
public function getCampaign()
{
return $this->campaign;
}
}
And the table structure is correct once you do:
php app/console doctrine:schema:update --force
The exception is being thrown because BaseFoodType::budget is a public property and doctrine:generate:entities created a private property in your Drink / Snack classes extending BaseFoodType ( which is not correct but the way the command works by now ).
Property visibility in a subclass can only be the same level or more liberate ( private -> protected -> public ) but never more restrictive.
doctrine:generate:entities did not take superclass's public property into account when generating the getters/setters as the implementation with a public property is non-standard.
Therefore you will have to adjust the generated class manually.
I recommend using private/protected properties combined with getters & setters.

Resources