Symfony: entity and sub-entity in a single form - forms

I have an entity, studyInformation which has a repetitive information for languages with language + level + hasCertificate. This is defined in 2 entities.
I define form type for both entities (using CRUD generator) and can include language form type inside studyInformation form type (see form code).
But my problem is, languageInformationType has an entity field to select its studyInformation. I don't want this to be visible as for the user this association is invisible. When editing it's easy to hide the languageInformationType selector, but, how to handle it on new studyInformation form? how to under the table relate both entities? I guess I can do all work in controller by hand, getting all request parameters and instantiating and persisting entity by hand, but, is there a way to relay in tha magic of doctrine to do it? (studyInformation has MANY fields to process them by hand)
ENTITIES
/**
* studyInformation
*
* #ORM\Table()
* #ORM\Entity
*/
class studyInformation
{
/**
* #var languageInformation
*
* #ORM\OneToMany(targetEntity="languageInformation", mappedBy="studyInformation", cascade={"persist", "remove"})
* #ORM\JoinColumn(name="languages", referencedColumnName="id")
**/
private $languages;
And
/**
* languageInformation
*
* #ORM\Table()
* #ORM\Entity
*/
class languageInformation
{
/**
* #var boolean
*
* #ORM\Column(name="hasCertificate", type="boolean")
*/
private $hasCertificate;
/**
* #var integer
*
* #ORM\Column(name="level", type="integer")
*/
private $level;
/**
* #var ListOfLanguages
*
* #ORM\ManyToOne(targetEntity="ListOfLanguages")
* #ORM\JoinColumn(name="languages", referencedColumnName="id")
**/
private $languages;
/**
* #var studyInformation
*
* #ORM\ManyToOne(targetEntity="studyInformation", inversedBy="languages")
* #ORM\JoinColumn(name="studyInformation", referencedColumnName="id")
**/
private $studyInformation;
FORM
class studyInformationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('languages', 'collection', array('type' => new languageInformationType()))
;
}

Related

Specific form with two enities with Symfony

I have a Category entity, this entity allow me to create a tree of my data :
I would like to create a DiscountGrid entity. A DiscountGrid is composed by a textfield name, and an array containing the discount percentage by category ($discount[idCategory] = $percentage).
In fact, I would like a form like this :
The output would be $discount[ 1 ] = 25, $discount[ 2 ] = 30,.....
I have no idea how handle this behavior with Symfony framework. Here is the declaration of my entities
class Category{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="slug", type="string", length=255, nullable=true)
*/
private $slug;
/**
* #var int
* #Gedmo\TreeLeft
* #ORM\Column(name="lft", type="integer")
*/
private $lft;
/**
* #var int
* #Gedmo\TreeLevel
* #ORM\Column(name="lvl", type="integer")
*/
private $lvl;
/**
* #var int
* #Gedmo\TreeRight
* #ORM\Column(name="rgt", type="integer")
*/
private $rgt;
/**
* #Gedmo\TreeRoot
* #ORM\ManyToOne(targetEntity="Category")
* #ORM\JoinColumn(name="root", referencedColumnName="id", onDelete="CASCADE")
*/
private $root;
/**
* #Gedmo\TreeParent
* #ORM\ManyToOne(targetEntity="Category", inversedBy="children")
* #ORM\JoinColumn(name="parent", referencedColumnName="id", onDelete="CASCADE")
*/
private $parent;
/**
* #ORM\OneToMany(targetEntity="Category", mappedBy="parent")
* #ORM\OrderBy({"lft" = "ASC"})
*/
private $children;
class DiscountGrid{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="slug", type="string", length=255)
*/
private $slug;
/**
* #var array
*
* #ORM\Column(name="grid", type="array")
*/
private $grid;
First of all, what you are trying to do will be a nightmare if you keep storing DiscountGrid::$grid as an array. Not only this will make your form creation extremely hard, but what will happen if someday you have to add a Category? Will you drag all the indexes to match the new Categories list? This was not your question, and I may be downvoted for suggesting that, but I definitely recommend you build your model a bit cleaner, since having a clean model is mandatory to use FormTypes properly.
What I would suggest is the following model:
DiscountGrid::$grid (rename it to $discounts) is a ManyToOne of a new entity called Discount.
Discount has an attribute $category which is a OneToMany towards Category and an attribute $reduction which is a float.
Once you have this, create a DiscountFormType with a single field reduction which is a PercentType.
Then, create another form DiscountGridFormType with a single field discounts as a CollectionType. This CollectionType should have the option entry_type set as DiscountFormType.
Finally, when you create your form in Controller, bind it a DiscountGrid entity with some Discounts in your discounts attribute. You should see a series of text boxes with percents at the end. That's the list of discounts where you can change values of the reductions.
After that, some form theming will help you display the category name next to the textbox. But I guess you already have some way to go before being at this point.

Unclear Symfony form error "This value is already used"

I use Symfony 2.7.3 and I have following form ("app_cargo_source"):
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('country', 'choice', [
'choices' => $this->worldManager->getCountriesByRegionIds([1, 2]),
'choice_label' => 'name',
'choice_value' => 'id',
'label' => false,
// *this line is important* see docs for choice type
'choices_as_values' => true
])
// ...
;
// ...
}
The form is used in another form (app_cargo):
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('source', 'app_cargo_source')
// ...
;
// ...
}
“choices” field in first form is an array of WorldCountry objects, which are selected from database by regions id. Here is a picture of the array from Profiler:
WorldCountry and WorldRegion have unidirectional ManyToOne relationship.
The problem is, when I select country in country field and submit the form, back I receive following error for country field:
Why that happens, and why Symfony at all try assign a value to region?
P.S. Please let me know if more information is needed.
Right! It seems that problem was in uniqueness of region field in my WorldCountry entity. My WorldRegion entity is following:
/**
* WorldRegion
*
* #ORM\Table(name="world_region")
* #ORM\Entity(repositoryClass="AppBundle\Repository\WorldRegionRepository")
*
* #UniqueEntity(fields={"code"}, message="Region code must be unique")
*/
class WorldRegion
{
/**
* #var integer
*
* #ORM\Column(name="region_id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
* #Assert\NotBlank()
*
* #ORM\Column(name="region_code", type="string", length=5,
* unique = true, nullable=false)
*/
protected $code;
// ...
}
And WorldCountry:
/**
* WorldCountry
*
* #ORM\Table(name="world_country")
* #ORM\Entity(repositoryClass="AppBundle\Repository\WorldCountryRepository")
*
* #UniqueEntity(fields={"iso2"})
* #UniqueEntity(fields={"iso3"})
* #UniqueEntity(fields={"region"}) // This line should be removed!
*/
class WorldCountry
{
/**
* #var integer
*
* #ORM\Column(name="country_id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var WorldRegion
*
* #ORM\ManyToOne(targetEntity="WorldRegion",
* cascade={"all"}
* )
* #ORM\JoinColumn(name="region_id", referencedColumnName="region_id")
*/
protected $region;
// ...
}
As seen WorldCountry has unique constraint on region field. In this case it is logical error. Because ManyToOne states that in database table for WorldCountry, countries are unique, but regions - not. But I, using #UniqueEntity, claim that regions also should be unique. So, annotation #UniqueEntity(fields={"region"}) should be removed from WorldCountry entity.
P.S. Please let me know if I can improve my answer.

Doctrine 2 Modular App With Entity Replacement

I'm building an application using ZF2 and Doctrine2.
The ideia is to have a base app entity (lets call it UserEntity).
But in one Module A, I will have another UserEntity-like entity that will "upgrade" the base one, with new fields. And another Module B that will add more fields.
Ex:
BaseUserEntity {
protected $id;
// ...
}
ModuleAUserEntity extends BaseUserEntity {
protected moduleAId;
}
ModuleBUserEntity extends BaseUserEntity {
protected moduleBUserName;
}
Is it possible, somehow, to get a method so when I call UserEntity, it will return the full, upgraded-by-module, entity? Ex:
UserEntity {
protected $id;
// ...
protected moduleAId;
protected moduleBUserName;
}
Is there another way to achieve something like this? The possibility to "extension" of an entity?
I have 2 different approaches:
1.First one:
you should take a look at:
http://docs.doctrine-project.org/en/latest/reference/inheritance-mapping.html
thats the legitimate and recommend by doctrine way of doing this.
2. Second one
If you dont want doctrine to mess with the database, you can use a different aproach:
Create an interface that defines the common behavior of all classes
Write your base class, implementing that behavior.
write your child classes, implementing the interface, containing the new methods, and wrapping an instance of the base class
You implement the methods that are defined in the interface, but since they are already implemented in the parent class, you just bypass the call to the wrapped object.
So, you are using composition over inheritance to avoid doctrine (and probably you) to get crazy
In order to have a really clean behavior with doctrine, the database that i imagine is:
a table with the parent entity
a table with the child entity, containing
a foreign key with the id of the related parent entity (this is, the row in the parent table that contains the values asociated to this, since the child has to have the parent and the child fields)
all the extra columns
For instance:
Interface:
namespace DBAL\Entity;
interface IProfesional
{
public function setName($name);
public function getName();
public function getId();
}
Parent class:
namespace DBAL\Entity;
use Doctrine\ORM\Mapping as ORM;
use DBAL\Entity\User\IUserAware;
/**
* Profesional
*
* #ORM\Table(name="profesional")
* #ORM\Entity
*/
class Profesional implements IProfesional
{
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=45, nullable=true)
*/
private $name;
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* Set nombre
*
* #param string $nombre
* #return Profesional
*/
public function setName($nombre)
{
$this->name = $nombre;
return $this;
}
/**
* Get nombre
*
* #return string
*/
public function getNombre()
{
return $this->name;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
}
child class:
namespace DBAL\Entity;
use DBAL\Entity\User\IUserAware;
use Doctrine\ORM\Mapping as ORM;
/**
* Jugador
*
* #ORM\Table(name="jugador")
* #ORM\Entity(repositoryClass="DBAL\Repository\JugadorRepository")
*/
class Player implements IProfesional
{
/**
* #var integer
*
* #ORM\Column(name="profesional_id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var \Profesional
*
* #ORM\OneToOne(targetEntity="Profesional")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="profesional_id", referencedColumnName="id", unique=true)
* })
*/
private $profesional;
/**
* Constructor: you create an empty Profesional,
so you are sure you will never use a reference to an in-existent object.
But anyways, if this is an entity loaded from the database by doctrine,
doctrine will fill that field whith an actual professional from the parent table,
based in the foreign key id
*/
public function __construct()
{
if(!isset($id)){
$this->profesional=new Profesional();
}
}
/**
* Set profesional
*
* #param \Profesional $profesional
* #return Jugador
*/
public function setProfesional( Profesional $profesional = null)
{
$this->profesional = $profesional;
return $this;
}
/**
* Get profesional
*
* #return \Profesional
*/
public function getProfesional()
{
return $this->profesional;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->profesional->getId();
}
///New fields, for instance:
/**
* #var integer
*
* #ORM\Column(name="height", type="integer", nullable=true)
*/
private $height;
public function getHeight()
{
return $this->height;
}
public function setHeight($h)
{
$this->height=$h;
}
}

Doctrine2 Association for ManyToMany Bidirectional and Insert operation for Unique OneToMany Relationships

I am using Symfony2 with Doctrine 2.
I have 5 entities in my application.
Book
Keyword (Bidirectional, should fetch all books having particular keyword)
Author (Bidirectional, should fetch all books having particular author(s))
Category (Bidirectional, should fetch all books falling into particular category)
BookExtra (Unidirectional, In Book Entity, should fetch BookExtra data)
Each book can have many keywords
Each book can have many authors
Each book can fall into a single category
Each book have exactly one BookExtra record.
The keyword and author tables should contain unique values
When a new Book is added, if the Author(s) and Keyword(s) exists, the respected Author ID and Keyword ID should be assigned to the Book, and if it doesn't exist, the new records will be created and the respected ID should be assigned to the Book.
I have the following Entity Classes:
Book.php
namespace Acme\StoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Book
*
* #ORM\Table(name="book")
* #ORM\Entity
*/
class Book {
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
*
* #ORM\Column(name="isbn", type="string", length=16)
*/
protected $isbn;
/**
* #var string
*
* #ORM\Column(name="title", type="string", length=255)
*/
protected $title;
/*
* #ORM\OneToOne(targetEntity="BookExtra")
* #ORM\JoinColumn(name="extra_id", referencedColumnName="id")
*
* ********* OR *********
* *********What should go HERE*********
*
*/
protected $bookextra;
/*
* #ORM\ManyToMany(targetEntity="Author", inversedBy="books")
* #ORM\JoinTable(name="authors_books")
*/
protected $author;
/*
* #ORM\OneToOne(targetEntity="Category")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
protected $category;
}
BookExtra.php
namespace Acme\StoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* BookExtra
*
* #ORM\Table(name="book_extra")
* #ORM\Entity
*/
class Detail {
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
*
* #ORM\Column(name="data", type="string", length=255)
*/
protected $data;
}
Author.php
namespace Bookhut\BookBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Author
*
* #ORM\Table(name="author")
* #ORM\Entity
*/
class Author {
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=100)
*/
protected $name;
// **********What should go HERE*********
protected $books;
}
Keyword & Category Entities are similar to Author Entity
The Problem is that, when i generate schema with cli, it never generates Relationships/Associations.
And what should be the proper Relationships/Associations for Book & Author Entity
I searched for this problem and proper Relationships/Associations
I found this:
Symfony2 app/console not generating properties or schema updates for Entity Relationships/Associations
Saving onetoone relation entities
doctrine2: in a one-to-many bidirectional relationship, how to save from the inverse side?
But it didn't help me.
Can somebody give an example of this type of Relationships/Associations and insert operations?
For author->books:
/**
* #ManyToMany(targetEntity="Author", inversedBy="books")
* #JoinTable(name="authors_books",
* joinColumns={#JoinColumn(name="book_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="author_id", referencedColumnName="id")}
* )
*/
protected $author;
For books->authors
/**
* #ManyToMany(targetEntity="Book", mappedBy="author")
*/
protected $books;
See http://docs.doctrine-project.org/en/latest/reference/association-mapping.html#many-to-many-bidirectional for documentation of many-to-many
See http://docs.doctrine-project.org/en/latest/reference/association-mapping.html for full documentation of associations
EDIT
Is assume all your entities will have setters and getters.
// Create new Author object
$Author = new Author();
// Use setters to set all attributes for this author
// Create new book
$Book = new Book();
// Use setters to set all attributes for book
// Attach book to author
$Author->addBook($Book);
$Book->addAuthor($Author);
The addBook and addAuthor will look like this:
// Inside author entity
public function addBook(Book $Book) {
$this->books->add($Book);
}
// Inside book entity
public function addAuthor($Author) {
$this->authors->add($Author);
}
And add a constructor to both the author and book entities:
public function __construct() {
$this->books = new ArrayCollection();
}
public function __construct() {
$this->authors = new ArrayCollection();
}
Have a look at the documentation to see more examples:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/working-with-associations.html

Zend - doctrine 2 insert data / associations mapping

i finally set up my mapping for my two tables, i can now join tables via the querybuilder..
however, i cant add data to the join column, its keeps saying null.
my account entity:
namespace Entities\Users;
/**
* #Entity(repositoryClass="\Entities\Users\Account")
* #Table(name="account")
* #HasLifecycleCallbacks
*/
class Account extends \Entities\AbstractEntity {
/**
* #Id #Column(name="accid", type="bigint",length=15)
* #GeneratedValue(strategy="AUTO")
*/
protected $accid;
/** #Column(name="name", type="string", length=255) */
protected $name;
/** #Column(name="profileid", type="integer", length=255) */
protected $profileid;
/** #Column(name="acc_added_date", type="datetime", columnDefinition="datetime", nullable=true) */
private $acc_added_date;
/**
* #ManyToOne(targetEntity="Profiledetails")
* #JoinColumn(name="profileid", referencedColumnName="pid")
*/
private $account;
and my profiledetails entity:
namespace Entities\Users;
/**
* #Entity(repositoryClass="\Entities\Users\Profiledetails")
* #Table(name="profiledetails")
* #HasLifecycleCallbacks
*/
class Profiledetails extends \Entities\AbstractEntity {
/**
* #Id #Column(name="pid", type="bigint",length=15)
* #GeneratedValue(strategy="AUTO")
*/
protected $accid;
/** #Column(name="name", type="string", length=255) */
protected $name;
/** #Column(name="profileid", type="integer", length=255) */
protected $profileid;
/** #Column(name="acc_added_date", type="datetime", columnDefinition="datetime", nullable=true) */
private $acc_added_date;
/**
* #OneToMany(targetEntity="Account", mappedBy="account")
* #JoinColumn(name="pid", referencedColumnName="pid")
*/
private $stances;
i use to use :
$postdata array ('name'=>'jason');
$entity =new \Entities\Users\Account;
$obj->setData($postdata);
$this->_doctrine->persist($obj);
$this->_doctrine->flush();
$this->_doctrine->clear();
and it doesnt add.. what the way to add data to the parent table where all linked tables get updated? because before i could enter a profileid and now its null because i used it as a the joined column..
Linked objects can be "updated" if you setup cascade=[persist] in your relationship definitions. You also need #mappedBy and #inversedBy to be set for both sides of the relations. Basically #mappedBy is set to the oneToMany side (called inverse side) and #inversedBy to the manyToOne side (called owning side)
http://www.doctrine-project.org/docs/orm/2.0/en/reference/association-mapping.html#one-to-many-bidirectional
The proper way is basically
//assume $this->_doctrine is instance of EntityManager
$user = new User();
$user->setEmail('john#example.com');
$account = new Account();
$account->setName('john');
$account->setUser($user);
$user->addAccount($account); //if no cascade set
$this->_doctrine->persist($account);
$this->_doctrine->persist($user); //if no cascade set
$this->_doctrine->flush();
http://www.doctrine-project.org/docs/orm/2.0/en/reference/working-with-associations.html#establishing-associations