Symfony2 / Doctrine / Mapping Converter / Relationship / PK Strange Behavior - symfony

After searching for a whole while i decided to show you my problem with the mapping converter implementation in Symfony2. First, i show you my setup:
The user tables having a relationship:
-- -----------------------------------------------------
-- Table `event_manager`.`user`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `event_manager`.`user` ;
CREATE TABLE IF NOT EXISTS `event_manager`.`user` (
`id` INT NOT NULL AUTO_INCREMENT ,
`email` VARCHAR(255) NULL ,
`salt` VARCHAR(255) NULL ,
`password` VARCHAR(255) NULL ,
`logged_in` TINYINT(1) NULL ,
`status` ENUM('active', 'inactive', 'deleted') NULL ,
PRIMARY KEY (`id`) ,
CONSTRAINT `fk_user_user_data1`
FOREIGN KEY (`id` )
REFERENCES `event_manager`.`user_data` (`user_id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `event_manager`.`user_data`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `event_manager`.`user_data` ;
CREATE TABLE IF NOT EXISTS `event_manager`.`user_data` (
`user_id` INT NOT NULL ,
`image_id` INT NULL ,
`gender` ENUM('male','female') NULL ,
`first_name` VARCHAR(255) NULL ,
`last_name` VARCHAR(255) NULL ,
`address` VARCHAR(255) NULL ,
`zip` VARCHAR(255) NULL ,
`city` VARCHAR(255) NULL ,
`phone_private` VARCHAR(255) NULL ,
`phone_mobile` VARCHAR(255) NULL ,
`phone_work` VARCHAR(255) NULL ,
`user_datacol` VARCHAR(45) NULL ,
PRIMARY KEY (`user_id`) ,
CONSTRAINT `fk_user_data_image1`
FOREIGN KEY (`image_id` )
REFERENCES `event_manager`.`image` (`id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
CREATE INDEX `fk_user_data_image1_idx` ON `event_manager`.`user_data` (`image_id` ASC) ;
With that on my DB, i use the doctrine converter with this command:
php app/console doctrine:mapping:convert yml ./src/path-to-bundle/Resources/config/doctrine --from-database --force --filter=User
Then i get this result on the user YAML:
User:
type: entity
table: user
fields:
email:
type: string
length: 255
fixed: false
nullable: true
salt:
type: string
length: 255
fixed: false
nullable: true
password:
type: string
length: 255
fixed: false
nullable: true
loggedIn:
type: boolean
nullable: true
column: logged_in
status:
type: string
length: null
fixed: false
nullable: true
manyToMany:
userGroup:
targetEntity: UserGroup
cascade: { }
mappedBy: null
inversedBy: user
joinTable:
name: user_has_user_group
joinColumns:
-
name: user_id
referencedColumnName: id
inverseJoinColumns:
-
name: user_group_id
referencedColumnName: id
orderBy: null
oneToOne:
id:
targetEntity: UserData
cascade: { }
mappedBy: null
inversedBy: null
joinColumns:
id:
referencedColumnName: user_id
orphanRemoval: false
lifecycleCallbacks: { }
As you can see, doctrine removes the "id" column as primary key and uses it instead as the name for the relationship which brings me finally to entity methods like this:
/**
* Set id
*
* #param \Parella\EventManagerBundle\Entity\UserData $id
* #return User
*/
public function setId(\Parella\EventManagerBundle\Entity\UserData $id = null)
{
$this->id = $id;
return $this;
}
/**
* Get id
*
* #return \Parella\EventManagerBundle\Entity\UserData
*/
public function getId()
{
return $this->id;
}
This is of course totally not what i want, and i have often to create many entites at once from the database, so manually fixing this is not really an option. Unfortunately i have no idea if i'm causing the problem or doctrine. Do i miss something?
Thanks for your responses.

Copied my comment to here because I confirmed the actual problem.
Doctrine does not support a single column being both primary id as well as participating in an owning relation. Think about it. What should $user->getId() return? The id or $userData?
Add a user_data_id column to user and put the relation on it.
And I would just edit the yml files and then use doctrine:schema:drop/update to generate the tables. Let Doctrine do things it's way unless you really need to maintain control.

Related

Doctrine. Cache. Entity relations

I have two Entities Company and Storage with One-To-Many Bidirectional relationship. Entities and their relations are cached (doctrine second level cache). The issue is that, when i create a new Storage entity, Company storages collection doesn't have this new entity until I clear the cache manually.
AppBundle\Entity\Main\Company:
type: entity
table: main.company
cache:
usage: NONSTRICT_READ_WRITE
id:
id:
type: integer
nullable: false
id: true
generator:
strategy: IDENTITY
fields:
legalName:
type: string
nullable: false
length: 255
options:
fixed: false
column: legal_name
oneToMany:
storages:
targetEntity: AppBundle\Entity\Main\Storage
mappedBy: company
cascade: ["all"]
orphanRemoval: true
cache:
usage: NONSTRICT_READ_WRITE
AppBundle\Entity\Main\Storage:
type: entity
table: main.storage
cache:
usage: NONSTRICT_READ_WRITE
id:
id:
type: integer
nullable: false
options:
unsigned: false
id: true
generator:
strategy: IDENTITY
fields:
storageName:
type: string
nullable: true
length: 255
options:
fixed: false
column: storage_name
manyToOne:
company:
targetEntity: AppBundle\Entity\Main\Company
cascade: ["all"]
fetch: LAZY
mappedBy: null
inversedBy: storages
joinColumns:
company_id:
referencedColumnName: id
orphanRemoval: false
cache:
usage: NONSTRICT_READ_WRITE
This is action where Storage is created. There is nothing unusual.
public function addAction(Request $request)
{
$form = $this->createForm(StorageAddType::class, null);
$form->handleRequest($request);
if (!$form->isSubmitted()) {
throw new \RuntimeException('Некорректный запрос');
}
if (!$form->isValid()) {
throw new \Symfony\Component\Validator\Exception\ValidatorException((string)$form->getErrors(true));
}
$doctrine = $this->getDoctrine();
/**
* #var Storage $storage
*/
$storage = $form->getData();
$manager = $doctrine->getManager();
$manager->persist($storage);
$manager->flush();
return $this->createAjaxDataResponse($this->createSuccessMessage('Storage successfully added'));
}
Such behavior is watched only when i try to create new Entity (Storage). Then on update/delete actions - Storages collection of Company are updated.
You are clearly wrong with persisting data. You try to persist unserialized object from form into uknown repository via manager.
Try this:
public function addAction(Request $request)
{
$form = $this->createForm(StorageAddType::class, null);
$form->handleRequest($request);
$em = $this->getDoctrine()->getManager();
if($form->isSubmitted() && $form->isValid())
{
$storage = new Storage();
$storage->setVal1($form->get('Val1'));
$storage->setVal2($form->get('Val2'));
$em->persist($storage);
$em->flush();
return $this->createAjaxDataResponse($this->createSuccessMessage('Storage successfully added'));
}
return $this->render('YOUR_TWIG_LAYOUT', [
'form' => $form->createView()
]);
}
You can also try to persist whole object, if form is seriaized properly by serializing data into entity. Write method like setValsFromForm($data) and serialize vars from $data form.
Then change these lines:
$storage->setVal1($form->get('Val1'));
$storage->setVal2($form->get('Val2'));
into
$storage->setValsFromForm($form->getData());
Also:
Exceptions and form validations should be handled by Form Validator in form class, not in controller. Exception is when you create form via formbuilderinterface in the controller, but you add logic there, not outside $form class.

Discriminator on referenced id

My title might not be the best description, but it's best I could come up with.
I have 4 entities; Page, PageElement, Image and an entity called TextWithImage. Page holds pageElements (array of PageElement entities). Those pageElements can be of numerous types, but for now I have only one called TextWithImage that's holding additional data to the data the PageElement entity holds.
The PageElement can be included on numerous pages and so, I have a ManyToMany in the PageElement.orm.yml. The TextWithImage has a manyToOne to reference to Image.
(More information: Another Entity ImageGallery might have a manyToMany relationship with the Image entity, while TextOnly shouldn't have any reference to the Image entity.)
I want to be able to get the Page and retrieve the PageElements with all their "attributes". So let's say I request to get a Page with only one TextWithImage type of PageElement, I want to return the following.
Page -> pageElements = array (
[0] => TextWithImage -> image = Image -> filename = "image.png"
-> alt = "An image!"
-> text = "There's an image too!"
)
All seems simple enough, but I need doctrine to understand that this PageElement is a TextWithImage type. Can I do this with a DiscriminatorColumn, say (rough sketch);
Table: pageelement
id | attributes | discr | TextWithImageId
Table: textwithimage
id | attributes
Keep in mind that I'll have more than just one type of PageElement, not only TextWithImage.
Is this possible and if so, how?
I've found the solution to my problem. These are the doctrine YML files. You can generate all entities with php app/console doctrine:generate:entities AppBundle/Entity. Make sure that the PageTextImageElement class extends the PageElement class.
Page.orm.yml
AppBundle\Entity\Page:
type: entity
table: null
repositoryClass: AppBundle\Repositories\PageRepository
manyToMany:
pageElements:
targetEntity: PageElement
cascade: ["all"]
joinTable:
name: null
joinColumns:
page_id:
referencedColumnName: id
onDelete: CASCADE
inverseJoinColumns:
page_element_id:
referencedColumnName: id
unique: true
onDelete: CASCADE
id:
id:
type: integer
id: true
generator:
strategy: AUTO
fields:
name:
type: string
length: '255'
unique: true
lifecycleCallbacks: { }
PageElement.orm.yml
AppBundle\Entity\PageElement:
type: entity
inheritanceType: SINGLE_TABLE
discriminatorColumn:
name: discr
type: string
discriminatorMap:
pageTextImageElement: PageTextImageElement
table: null
repositoryClass: AppBundle\Repositories\PageElementRepository
id:
id:
type: integer
id: true
generator:
strategy: AUTO
fields:
sortOrder:
type: integer
attributes:
type: array
nullable: true
lifecycleCallbacks: { }
PageTextImageElement.orm.yml
AppBundle\Entity\PageTextImageElement:
type: entity
table: null
oneToOne:
image:
targetEntity: AppBundle\Entity\Image
joinColumn:
name: imageId
referencedColumnName: id
fields:
passage:
type: string
length: '255'
lifecycleCallbacks: { }
Image.orm.yml
AppBundle\Entity\Image:
type: entity
table: null
repositoryClass: AppBundle\Repositories\ImageRepository
id:
id:
type: integer
id: true
generator:
strategy: AUTO
fields:
name:
type: string
length: '255'
unique: true
description:
type: string
length: '255'
lifecycleCallbacks: { }

Doctrine 2.4 Association Mapping Failure referenced column name primary key ignored

When running:
php app/console doctrine:schema:validate
I receive the error:
[Mapping] FAIL - The entity-class 'Path\ToBundle\Entity\Variant' mapping is invalid:
* The referenced column name 'localsku' has to be a primary key column on the target entity class 'Path\ToBundle\Entity\Inventory'.
The killer here is that 'localsku' is indeed a primary key. Am I missing something major here?
Thanks in advance for any assistance, and I apologize if I've missed some key piece of information.
Variant Entity is defined as:
Path\ToBundle\Entity\Variant:
type: entity
table: variant
uniqueConstraints:
sku:
columns:
- sku
id:
variantId:
type: integer
nullable: false
unsigned: false
comment: ''
id: true
column: variant_id
generator:
strategy: IDENTITY
fields:
name:
type: string
nullable: false
length: 255
fixed: false
comment: ''
sku:
type: string
nullable: false
length: 255
fixed: false
comment: ''
oneToOne:
inventory:
targetEntity: Inventory
cascade: { }
mappedBy: null
inversedBy: variant
joinColumns:
sku:
referencedColumnName: localsku
orphanRemoval: false
lifecycleCallbacks: { }
Inventory Entity is defined as:
Path\ToBundle\Entity\Inventory:
type: entity
table: Inventory
id:
localsku:
type: string
nullable: false
length: 255
fixed: false
comment: ''
id: true
column: LocalSKU
generator:
strategy: IDENTITY
fields:
itemname:
type: string
nullable: true
length: 255
fixed: false
comment: ''
column: ItemName
qoh:
type: integer
nullable: true
unsigned: false
comment: ''
column: QOH
location:
type: string
nullable: true
length: 250
fixed: false
comment: ''
column: Location
barcode:
type: string
nullable: true
length: 25
fixed: true
comment: ''
column: Barcode
oneToOne:
variant:
targetEntity: Variant
cascade: { }
mappedBy: inventory
inversedBy: null
joinColumns:
localsku:
referencedColumnName: sku
orphanRemoval: false
lifecycleCallbacks: { }
Variant Entity reference to Inventory:
/**
* Inventory
*
* #var \Path\ToBundle\Entity\Inventory
*
* #ORM\OneToOne(targetEntity="Path\ToBundle\Entity\Inventory", inversedBy="variant")
* #ORM\JoinColumn(name="sku", referencedColumnName="localsku")
*/
protected $inventory;
Inventory Entity reference to Variant:
/**
* Variant
*
* #var \Path\ToBundle\Entity\Variant
*
* #ORM\OneToOne(targetEntity="Path\ToBundle\Entity\Variant", inversedBy="inventory")
* #ORM\JoinColumn(name="localsku", referencedColumnName="sku")
*/
protected $variant;
Your association mapping is wrong.
It should be something like that, assuming Variant table have a "sku" field.
Variant Entity :
/**
* Inventory
*
* #var \Path\ToBundle\Entity\Inventory
*
* #ORM\OneToOne(targetEntity="Path\ToBundle\Entity\Inventory", inversedBy="variant")
* #ORM\JoinColumn(name="sku", referencedColumnName="localsku")
*/
protected $inventory;
Inventory Entity :
/**
* Variant
*
* #var \Path\ToBundle\Entity\Variant
*
* #ORM\OneToOne(targetEntity="Path\ToBundle\Entity\Variant", mappedBy="inventory")
*/
protected $variant;
http://docs.doctrine-project.org/en/2.0.x/reference/association-mapping.html#one-to-one-bidirectional
P.S : I'm curious. You are using both yml configuration and annotation for the same entities ? I'm not sure that doctrine is able to merge them, does it works ?

symfony 2 many-to-many relationship

I'm trying to realize a many-to-many relationship between two tables.
this is my configuration:
Mailer\EmpfaengerBundle\Entity\Empfaenger:
type: entity
table: empfaenger
id:
id:
type: integer
generator: { strategy: AUTO }
fields:
vorname:
type: string
length: 255
nullable: true
nachname:
type: string
length: 255
nullable: true
created_by:
type: integer
updated_by:
type: integer
manyToMany:
verteiler:
targetEntity: Verteiler
mappedBy: empfaenger
Mailer\EmpfaengerBundle\Entity\Verteiler:
type: entity
table: verteiler
id:
id:
type: integer
generator: { strategy: AUTO }
fields:
name:
type: string
length: 255
nullable: true
created_by:
type: integer
updated_by:
type: integer
manyToMany:
empfaenger:
targetEntity: Empfaenger
inversedBy: verteiler
joinTable:
name: verteiler_sys
joinColumns:
verteiler_id:
referencedColumnName: id
inverseJoinColumns:
empfaenger_id:
referencedColumnName: id
Now when I try to edit an entry, it works in the "verteiler" table, but not in the empfaenger table.
The form is shown in the "empfaenger" edit page and the entries are highlightet, but when i change ohne, it won't save the changes.
I tried various different configurations according to the doctrine documentation but always get the same result :-(
both entities have exactly the same structure:
/**
* Add empfaenger
*
* #param \Mailer\EmpfaengerBundle\Entity\Empfaenger $empfaenger
* #return Verteiler
*/
public function addEmpfaenger(\Mailer\EmpfaengerBundle\Entity\Empfaenger $empfaenger)
{
$this->empfaenger[] = $empfaenger;
return $this;
}
/**
* Remove empfaenger
*
* #param \Mailer\EmpfaengerBundle\Entity\Empfaenger $empfaenger
*/
public function removeEmpfaenger(\Mailer\EmpfaengerBundle\Entity\Empfaenger $empfaenger)
{
$this->empfaenger->removeElement($empfaenger);
}
/**
* Get empfaenger
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getEmpfaenger()
{
return $this->empfaenger;
}
and help would be appreciated.
all right, i found a solution, in case anybody faces this:
http://www.youtube.com/watch?v=kPrgoe3Jrjw

How can I filter on a nullable DateTime field using EF4.1?

I have a table with a nullable DateTime field:
CREATE TABLE [dbo].[myTable](
[ID] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,
...
[FinishedDate] [datetime] NULL,
etc...
When I try this:
var activeThings = from foo in _context.myTable
where foo.FinishedDate == null
select foo;
foreach ( var thing in activeThings ) {
... do some stuff ...
}
I get no values back. How can I filter this on null values?
var activeThings = from foo in _context.myTable
where !foo.FinishedDate.HasValue //foo.FinishedDate.HasValue==false
select foo;
Source
HasValue return boolean,
is true => not null
is false => null

Resources