Symfony 3.1 - Doctrine: I am trying to add relationships between entities and thereby want to add foreign key to link tables in my example. I tried several settings, utilizing various combination of arguments, but none of them were able to ALTER the tables with Foreign key.
My entities are namely "Trips" and "Airlines" with the content attached below. I would like to use airline_id as a foreign key inside trips table. Here are the extract from my entity scripts:
Entity#1: Trips
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="trips")
*/
class Trips
{
/**
* #ORM\Column(name="trip_id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $trip_id;
/**
* #ORM\Column(name="airline_id", type="integer")
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Airlines")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="airline_id", referencedColumnName="airline_id")
* })
*/
private $airline_id;
}
Entity#2: Airlines
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="airlines")
*/
class Airlines
{
/**
* #ORM\Column(name="airline_id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $airline_id;
/**
* #ORM\Column(type="string", length=80)
*/
private $name;
}
I tried updating the schema from console , but all it says is
Nothing to update - your database is already in sync with the current entity metadata.
Also tried deleting the table and re-creating them with console; the result did not change much and the foreign key is still missing. :/
Any thoughts, or perhaps you know the solution?
I have a few suggestions, as I've gone through the same process of design.
First off, should I presume that "a trip has one airline" and also "an airline has many trips"? This seems logical, and since you show #ORM\ManyToOne annotation for the Trips class, that would equate to my same presumptions.
If this is so, then these are the suggestions:
First, don't call the classes "Trips" and "Airlines", but rather in the singular, that is "Trip" and "Airline". This is object oriented thinking. So when you create a "Trip" Doctrine object, it represents a trip for example. This also makes you code more readable and also supportable from a future standpoint; as because when people read your code, they will understand that a single instance of a Trip (from the Trip class), represents a trip (that a person takes). This is important when designing code.
Given the above, make these code changes.
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="trip")
*/
class Trip
{
/**
* #ORM\Id
* #ORM\Column(name="trip_id", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $trip_id;
/**
* #ORM\ManyToOne(targetEntity="Airline", inversedBy="trips")
* #ORM\JoinColumn(name="trip_airline_id", referencedColumnName="airline_id")
*/
private $airline;
...
/**
* Set airline - Adds this trip to the passed in airline and sets the airline
* property to resultant airline.
*
* #param \AppBundle\Entity\Airline
*/
public function setAirline($airline){
$airline->addTrip($this);
$this->airline = $airline;
}
}
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="airlines")
*/
class Airlines
{
/**
* #ORM\Column(name="airline_id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $airline_id;
/**
* #ORM\Column(type="string", length=80)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="Trip", mappedBy="airline")
*/
protected $trips = null;
...
public function __construct(){
// This is an array of trips.
$this->trips = new ArrayCollection();
}
...
/**
* Add trip - Adds a course to the array.
*/
public function addTrip($trip){
$this->trips[] = $trip;
}
}
Now you can get trips from the airline, and also you can do the other way around, get the airline from the trip.
When #ORM\Column is specified along with #ORM\JoinColumn on same column, then JoinColumn's association gets ignored and Foreign Key isn't created on table. so dont use both #ORM\Column and #ORM\JoinColumn in same column. There is no need to specify data type with #ORM\Column for associations, doctrine handles it and assign data type based on database engine. For MySql, it is int.
Related
Disable automatic Id field generation in Doctrine
this a part of my entity file:
<?php
namespace MyBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Variables
* #ORM\Table(name="variables")
* #ORM\Entity(repositoryClass="MyBundle\Repository\VariablesRepository")
*/
class Variables
{
/**
* #var int
* #ORM\Column(name="variablesRef", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $variablesRef;
/**
* #var string
* #ORM\Column(name="variablesLibelle", type="string", length=250)
*/
private $variablesLibelle;
when i try to create a schema :
erreur
Property MyBundle\Entity\Employeursecteur::$id does not exist
i want to Disable automatic Id field generation in Doctrine
i don't need the id Column
please help
If you want a primary key field without a generated value you just have to set the strategy of generation to none:
#ORM\GeneratedValue(strategy="NONE")
I currently have to Entities in my application:
Page
Block
A Page can have many Blocks, which are shared across many Pages, so it is quite obvious that the relation is a ManyToMany. However, I need to be able to add the same Block twice (or more) to the same Page. Doctrine creates the "page_block" join table automatically, but with page_id and block_id both as Primary Keys, therefore adding a duplicate throws an error.
Is it possible, without adding an additional Entity, to tell doctrine to allow duplicates on the Page--Block relation ?
Well, I'm not sure about that behavior in doctrine, but if that is the case, then you can do something that I almost always do. Represent the ManyToMany relation as two OneToMany-ManyToOne. You must create your own PageBlock entity and configure it's foreign keys.
class Page{
/**
* #var array
*
* #ORM\OneToMany(targetEntity="PageBlock", mappedBy="page", cascade={"all"})
*/
private $pageBlocks;
}
class Block{
/**
* #var array
*
* #ORM\OneToMany(targetEntity="PageBlock", mappedBy="block", cascade={"all"})
*/
private $pageBlocks;
}
class PageBlock{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var \stdClass
*
* #ORM\ManyToOne(targetEntity="Page", inversedBy="pageBlocks")
* #ORM\JoinColumn(name="id_page", referencedColumnName="id")
*/
private $page;
/**
* #var \stdClass
*
* #ORM\ManyToOne(targetEntity="Block", inversedBy="pageBlocks")
* #ORM\JoinColumn(name="id_block", referencedColumnName="id")
*/
private $block;
}
As you can see the primary key remains as ID, so problem resolved. I say almost always do because this is how I do it if I need an extra attribute in the relation(almost always it happens). I suspect that could be a way of do it with the ManyToMany annotation, but there is no difference with this approach.
Hope this help you.
I have an Entity Employee
class Employee
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="WorkHour", mappedBy="employee", cascade={"persist", "remove"})
*/
private $workHours;
}
and WorkHour
class WorkHour
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var Profile
*
* #ORM\ManyToOne(targetEntity="Employee", inversedBy="workHours")
* #ORM\JoinColumn(name="employee_id", referencedColumnName="id")
*/
protected $profile;
/**
* #var integer
*
* #ORM\Column(name="weekday", type="smallint")
*/
private $weekday;
/**
* #var \DateTime
*
* #ORM\Column(name="hour_from", type="time")
*/
private $hourFrom;
/**
* #var \DateTime
*
* #ORM\Column(name="hour_to", type="time")
*/
private $hourTo;
}
Now I'm confused when I'm going to add addWorkHour(), removeWorkHour() methods.
Usually one-to-many relation you can add as many relations as you want, but in my case, one employee can have only up-to-7 workHours, and for a specified weekday (from 0 to 6) can have only one (or no) record.
So I think what I need is something methods like,
public function setWorkHourByWeekday($hour_from, $hour_to, $weekday);
public function getWorkHourByWeekday($weekday);
And after set workhours for an employee, when you persist that employee,
I want doctrine delete those workhours that are no longer exist, update those workhours that are changed, and create new workhours that not exist before.
How can I implement this? Should I write these logic in class Employee or its Repository, or a WorkHourManager class?
I think WorkDay is a probably better name for your entity, so i'll use that :).
$workdays= $employee->getWorkDays();
$workdays->clear(); // Clear existing workdays
// Add new workdays
foreach(...) {
$workday = new WorkDay();
$workday ->setWeekday(0); // You could use a constant like `WorkDay::MONDAY`
$workday ->setStart('09:00');
$workday ->setEnd('17:00');
$workdays->add($workday);
}
Set orphanRemoval=true on $workHours to remove WorkHours without an Employee.
The setWeekday method in your Workday entity should throw an exception when an invalid weekday is supplied (other than 0-6). You could also use a Weekday value object in combination with Doctrine embeddables.
I'd go for a service or manager class in this case.
My advice is not to drop old workhours, maybe you don't needed now, but this data could be useful in the future. So, will be better just add workHours to the Employee and make a report the get the last workHours for today. About validations, there is a lot of ways of doing that. If you are working with forms and the rules are complex maybe you need read http://symfony.com/doc/current/cookbook/validation/custom_constraint.html , but maybe you can find alternatives in the action controller or the entity class itself.
I have this scenario.
Entity File, with some usefull properties like path for file and others.
Entity Image, which is file too, so it have same properties, but contains some more as width,height...
My implementation looks like this
Entity File.php
use Doctrine\ORM\Mapping as ORM;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* #ORM\Entity
* #ORM\Table(name="files")
* #Vich\Uploadable
*/
class File
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="SEQUENCE")
* #ORM\SequenceGenerator(sequenceName="file_seq")
* #var integer
*/
protected $id;
/**
* #ORM\Column(type="string", length=255)
* #var string
*/
protected $fileName;
.
.
.
Entity Image.php
use Doctrine\ORM\Mapping as ORM;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* #ORM\Entity
* #ORM\Table(name="images")
* #Vich\Uploadable
*/
class Image extends File
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="SEQUENCE")
* #ORM\SequenceGenerator(sequenceName="image_seq")
* #var integer
*/
protected $id;
.
.
other properties...
}
Everything works just fine except of property id. Table images is not joined to image_seq(which also is not even created) and takes nextval from file_seq. Is there any way how to get this scenario fully working and inherit entities? Thanks.
This is a perfect situation for implementing a mapped superclass.
You need to tell doctrine what type of inheritance you would like to apply. Read the docs for the different strategies Doctrine supports.
If you want to have a table for file as well as image then use 'Class Table Inheritance' - still read the docs you have been provided with by the other answers. This is suitable for you given your input!
You won't need to define $id again in your Image class and you shouldn't.
I have a recursive entity containing parent/children item
namespace Vendor\StructureBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Events;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* Vendor\StructureBundle\Entity\Structure
* #Gedmo\Tree(type="nested")
*
* #ORM\Table(name="lowbi_structure")
* #ORM\Entity(repositoryClass="Gedmo\Tree\Entity\Repository\NestedTreeRepository")
* #ORM\HasLifecycleCallbacks()
* #CustomAssert\ganttDate
*/
class Structure {
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
...
/**
* #ORM\Column(name="title", type="string", length=64)
*/
private $title;
/**
* #Gedmo\TreeParent
* #ORM\ManyToOne(targetEntity="Structure", inversedBy="children",cascade={"persist"})
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=true, onDelete="SET NULL")
*/
private $parent;
/*
* #ORM\OneToMany(targetEntity="Structure", mappedBy="parent",cascade={"persist","remove"})
* #ORM\OrderBy({"lft" = "ASC"})
*/
private $children;
...
}
and i want to update the "parent" when i persit this entity.
/**
* Set prePersist
*
* #ORM\PrePersist()
* #ORM\PreUpdate()
*
*/
public function prePersist()
{
$this->getParent()->setTitle('Foo');
}
The probleme is that my current entity is persisted but the parent entity is not. The title is not saved. How can i save the parent's properties?
PS : I simplified the code. In real world, i need to update parent start dates/end dates to fit the children (project management tree)
What you're trying to do here is not possible with a Lifecycle callback (i.e. #PrePersist) if you save only the child afterwards.
Doctrine only tracks and saves changes to the owning side of a relation.
When a bidirectional assocation is updated, Doctrine only checks on
one of both sides for these changes. This is called the owning side of
the association.
Therefore you can't persist an update of the parent from the child by persisting the child only.
You can find more information about the concepts of the inverse and owning side in this answer and in the documentation chapter Working with Associations.
To solve this issue ... you can either persist the parent instead of the child ... or (instead of using the lifecycle callback #PrePersist) create an event listener that will automatically persist the parent. This is a better practice in general because it keeps application logic out of your model.
The documentation chapter How to Register Event Listeners and Subscribers provides all the necessary information to get you started.
I don't know why, but if i return $this in the prePersist function, IT WORKS.
**
* Set prePersist
*
* #ORM\PrePersist()
* #ORM\PreUpdate()
*
*/
public function prePersist()
{
//$this->getParent()->setTitle('Foo');
return $this->getParent()->setTitle('Foo'); //solved this problem!!!!
}