I am using Doctrine2 ORM. I have an order table.
Name | Type | Attributes | Null | Default
--------------------------------------------------------------------------------
order_id | int | | No | None
date_created | timestamp | | No | CURRENT_TIMESTAMP
date_edited | timestamp | on update CURRENT_TIMESTAMP | No | CURRENT_TIMESTAMP
date_sent | timestamp | | No | 0000-00-00 00:00:00
date_cancelled | timestamp | | No | 0000-00-00 00:00:00
requested_ship_date | timestamp | | No | 0000-00-00 00:00:00
I have added the Timestampable Doctrine Extension to use for the date_created and date_edited fields like so:
/**
* #var \DateTime
*
* #Gedmo\Timestampable(on="create")
* #ORM\Column(type="datetime")
*/
private $date_created;
/**
* #var \DateTime
*
* #Gedmo\Timestampable(on="update")
* #ORM\Column(type="datetime")
*/
private $date_edited;
However, I'm not sure what to do for date_sent and date_cancelled. I want them both to default to 0000-00-00 00:00:00 instead of the current time. I saw this workaround for using timestamps in Doctrine2 without a plugin, but I don't want the default to be the current timestamp for these. The reason being, users can 'save' an order but not 'send' it to someone. Also, I obviously don't want the date_cancelled to be the current timestamp.
Note: I don't want to store nulls in the database, so please don't suggest that.
Thanks in advance.
Update
I currently have this for the three remaining fields in my Order entity.
/**
* #var \DateTime
*
* #ORM\Column(type="datetime", options={"default":"0000-00-00 00:00:00"})
* #ORM\Version
*/
private $date_sent;
/**
* #var \DateTime
*
* #ORM\Column(type="datetime", options={"default":"0000-00-00 00:00:00"})
* #ORM\Version
*/
private $date_cancelled;
/**
* #var \DateTime
*
* #ORM\Column(type="datetime", options={"default":"0000-00-00 00:00:00"})
* #ORM\Version
*/
private $requested_ship_date;
For some reason, when I try to update the requested_ship_date field, it ends up being "0000-00-00 00:00:00". I leave the date_sent and date_cancelled fields null and they update to my local time, not UTC time. However, the date_created and date_edited fields will be updated to the current time in UTC time (how I want it).
Basically, when I'm saving an order it looks like the first row, but I want it to look like the second row (assuming the user requested a ship date of 2015-06-08).
order_id | date_created | date_edited | date_sent | date_cancelled | requested_ship_date
-----------------------------------------------------------------------------------------------------------------------
1 | 2015-06-05 12:00:00 | 2015-06-05 12:00:00 | 2015-06-05 07:00:00 | 2015-06-05 07:00:00 | 0000-00-00 00:00:00
1 | 2015-06-05 12:00:00 | 2015-06-05 12:00:00 | 0000-00-00 00:00:00 | 0000-00-00 00:00:00 | 2015-06-08 00:00:00
I have set the timezone in my index.php file like so date_default_timezone_set('Zulu');. I don't reset the timezone anywhere else.
Okay, I found a crazy workaround for this, but I honestly couldn't find a better solution. So, for anyone else stuck in the same mess I am, you're welcome lol.
First, the rules:
date_created = updated only on creation
date_edited = updated on every update
date_sent = updated when user clicks "submit" instead of "save"
date_cancelled = updated when user decides to cancel the order
requested_ship_date = required when creating a new order, this is the date the customer wants the order to be shipped by.
Some code from my Order entity.
/**
* #var \DateTime
*
* #ORM\Column(type="datetime")
* #Gedmo\Timestampable(on="create")
*/
private $date_created;
/**
* #var \DateTime
*
* #ORM\Column(type="datetime")
* #Gedmo\Timestampable(on="update")
*/
private $date_edited;
/**
* #var \DateTime
*
* #ORM\Column(type="datetime", options={"default":"0000-00-00 00:00:00"})
*/
private $date_sent;
/**
* #var \DateTime
*
* #ORM\Column(type="datetime", options={"default":"0000-00-00 00:00:00"})
*/
private $date_cancelled;
/**
* #var \DateTime
*
* #ORM\Column(type="datetime")
* #Gedmo\Timestampable(on="change", field="requested_ship_date")
*/
private $requested_ship_date;
/*
* Constructor
*/
public function __construct()
{
if (!$this->date_sent) $this->date_sent = new DateTime("0000-00-00 00:00:00", new DateTimeZone("Zulu"));
if (!$this->date_cancelled) $this->date_cancelled = new DateTime("0000-00-00 00:00:00", new DateTimeZone("Zulu"));
}
/**
* Updates time sent
*
* #return Order
*/
public function send()
{
$this->date_sent = new DateTime("now");
return $this;
}
/**
* Updates time cancelled
*
* #return Order
*/
public function cancel()
{
$this->date_cancelled = new DateTime("now");
return $this;
}
Explanation:
I did not change anything of how I set up my database columns in my order table. I tried using the #Gedmo\Timestampable plugin for my date_sent and date_cancelled fields, but couldn't get it to work. I noticed that the timezones were off, for some weird reason, so I manually set them in the constructor. It is important to not automatically set it, or you will override values previously set. For some reason unknown to me, I couldn't get the requested_ship_date field working without using the #Gedmo\Timestampable plugin. If I took it out, every set up I tried to use with Doctrines type="datetime" would just result in the column equaling 0000-00-00 00:00:00.
If anybody has any questions, let me know. I had to basically poke at things for awhile until I figured it out. The date_sent and date_cancelled fields worked correctly, and were in the right timezone when I updated them.
Related
Is this OneToOne relation possible with Doctrine (in Symfony)?
User Time
+----------+ +-------+
| ... | | ... |
| time_id |o-----+-----o| user |
| | | | ... |
| time2_id |o-----+ | type |
| ... | +-------+
+----------+
That is, a User can potentially have two different Time entities associated to it:
one of type=0 and the other one of type=1.
I could have splitted Time in two different entities, but I thought that this way I
could spare replicating some code, as Time entity has an eventListener and more code associated...
I have coded it like this:
class Time
{
...
/**
* #ORM\OneToOne(targetEntity=User::class, mappedBy="time, cascade={"persist"})
* #ORM\OneToOne(targetEntity=User::class, mappedBy="time2", cascade={"persist"})
*/
private $user;
...}
class User
{
...
/**
* #ORM\OneToOne(targetEntity=Time::class, inversedBy="user", cascade={"persist"})
* #ORM\JoinColumn(name="time_id", referencedColumnName="id", nullable=true, onDelete="SET NULL")
*/
private $time;
/**
* #ORM\OneToOne(targetEntity=Time::class, inversedBy="user", cascade={"persist"})
* #ORM\JoinColumn(name="time2_id", referencedColumnName="id", nullable=true, onDelete="SET NULL")
*/
private $time2;
...}
And in fact I am flawlessly using it, but Symfony silently complains (in Symfony toolbar/Doctrine/Entities Mapping > Mapping errors) with:
App\Entity\User
The mappings App\Entity\User#time2 and App\Entity\Time#user are inconsistent with each other.
Obviously, Doctrine doesn't like the second #ORM\OneToOne on $user, and is discarding it.
What hypotethical misbehaviours could this code lead to?
Note: doctrine/common v2.11
FWIW, it is possible to make this relation, as far as one renounces to make a Bidirectional Association:
User Time
+----------+ +-------+
| ... | | ... |
| time_id |o-----+-----o| |
| | | | |
| time2_id |o-----+ | type |
| ... | +-------+
+----------+
implemented by:
class Time
{
...
}
class User
{
...
/**
* #ORM\OneToOne(targetEntity=Time::class, cascade={"persist"})
* #ORM\JoinColumn(name="time_id", referencedColumnName="id", nullable=true, onDelete="SET NULL")
*/
private $time;
/**
* #ORM\OneToOne(targetEntity=Time::class, cascade={"persist"})
* #ORM\JoinColumn(name="time2_id", referencedColumnName="id", nullable=true, onDelete="SET NULL")
*/
private $time2;
...}
Also look at this link for Owning vs Inverse relations.
In my case, this totally fits my needs, as the inverse part is for methods like:
$Time->getUser();
which I can prescind of.
I've got two entities mapped like this:
namespace App\Entity\Email;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Contract\Entity\BlameableInterface;
use Knp\DoctrineBehaviors\Contract\Entity\SoftDeletableInterface;
use Knp\DoctrineBehaviors\Contract\Entity\TimestampableInterface;
use Knp\DoctrineBehaviors\Model\Blameable\BlameableTrait;
use Knp\DoctrineBehaviors\Model\SoftDeletable\SoftDeletableTrait;
use Knp\DoctrineBehaviors\Model\Timestampable\TimestampableTrait;
/**
* #ORM\Entity(repositoryClass="App\Repository\Email\EmailRepository")
*/
class Email implements SoftDeletableInterface, TimestampableInterface, BlameableInterface
{
use SoftDeletableTrait;
use TimestampableTrait;
use BlameableTrait;
/**
* #ORM\Column(type="integer")
* #ORM\Id()
* #ORM\GeneratedValue()
*/
private ?int $id = null;
...
/**
* #var Collection|EmailAddress[]
* #ORM\OneToMany(targetEntity="App\Entity\Email\EmailAddress", mappedBy="sentEmail", cascade={"all"}, orphanRemoval=true)
*/
private ?Collection $senders = null;
/***
* #var Collection|EmailAddress[]
* #ORM\OneToMany(targetEntity="App\Entity\Email\EmailAddress", mappedBy="receivedEmail", cascade={"all"}, orphanRemoval=true)
*/
private ?Collection $recipients = null;
...
}
and
namespace App\Entity\Email;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Contract\Entity\BlameableInterface;
use Knp\DoctrineBehaviors\Contract\Entity\SoftDeletableInterface;
use Knp\DoctrineBehaviors\Contract\Entity\TimestampableInterface;
use Knp\DoctrineBehaviors\Model\Blameable\BlameableTrait;
use Knp\DoctrineBehaviors\Model\SoftDeletable\SoftDeletableTrait;
use Knp\DoctrineBehaviors\Model\Timestampable\TimestampableTrait;
/**
* #ORM\Entity()
*/
class EmailAddress implements SoftDeletableInterface, TimestampableInterface, BlameableInterface
{
use SoftDeletableTrait;
use TimestampableTrait;
use BlameableTrait;
/**
* #ORM\Column(type="integer")
* #ORM\Id()
* #ORM\GeneratedValue()
*/
private ?int $id = null;
...
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Email\Email", inversedBy="senders")
* #ORM\JoinColumn()
*/
private ?Email $sentEmail = null;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Email\Email", inversedBy="recipients")
* #ORM\JoinColumn()
*/
private ?Email $receivedEmail = null;
...
}
From unknown reason to me I'm getting message from Doctrine:
The association App\Entity\Email\EmailAddress#receivedEmail refers to the inverse side field App\Entity\Email\Email#recipients which does not exist.
What is wrong with my mapping? I really don't see any error. I've asked my colegues to check my code and they also don't see any problem. Strange thing is that relation sentEmail->senders is mapped according to doctrine right and it's working.
I've also tried to change OneToMany mapping to ManyToMany like this but I've still got same error.
EDIT 1:
Date in database:
Table email
id | created_by_id | updated_by_id | deleted_by_id | deleted_at | created_at | updated_at | subject | content
1 | NULL | NULL | NULL | NULL | 1616156920 | 1616156920 | Test | Test
Table email_address
id | created_by_id | updated_by_id | deleted_by_id | address | deleted_at | created_at | updated_at | sent_email_id | received_email_id
1 | NULL | NULL | NULL | test1#test.com | NULL | 1616156920 | 1616156920 | NULL | 1
2 | NULL | NULL | NULL | test2#test.com | NULL | 1616156920 | 1616156920 | 1 | NULL
This question/answer probably follows under the heading of a 'typo' but I thought it might be interesting to discuss.
The problem is here:
/***
* #var Collection|EmailAddress[]
* #ORM\OneToMany(targetEntity="App\Entity\Email\EmailAddress", mappedBy="receivedEmail", cascade={"all"}, orphanRemoval=true)
*/
private ?Collection $recipients = null;
The extra asterisk /*** in the annotation block opening caused the recipients property to be skipped by Doctrine. But everything still seemed to be okay. The database tables and indexes were all generated as expected.
I made a simple console command to insert an email entity and quickly noticed that the sender address was inserted but not the recipient address. Triple checked the various methods but still no go. Explicitly persisting the address worked but the cascade option should have taken care of that. And of course retrieving the recipient address was not working even after an entity was inserted.
At some point I noticed the /*** and changed it to /** and everything worked as expected. Using a console command to test helped quite a bit as opposed to refreshing a browser and hunting around in the debug bar.
On the plus side, if you ever need to temporarily remove an annotation then just adding an asterisk is basically the same as commenting it out.
I've implement Class Table Inheritance using Doctrine 2 in my Symfony 3 project, so as to have one base profile table, that houses both employee and company profiles.
When trying to persist a sub class (EmployeeProfile) of the mapped super class (AbstractProfile), I get the following error:
An exception occurred while executing 'INSERT INTO profile (id) VALUES
(?)' with params [27, 10, 85, \"employee\"]:\n\nSQLSTATE[HY093]:
Invalid parameter number: number of bound variables does not match
number of tokens
I'm not entirely sure what's going wrong, and why Doctrine is generating a query that's entirely ignoring the AbstractProfile's properties. Initially I thought it was due to said properties not being visible to the children, but even after setting the properties to protected, the error remains.
How exactly can I fix this, or am I trying to fit a square peg into a round hole by not using this functionality for what is was intended?
profile DB Table:
+------------+--------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+-------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user_id | int(11) | NO | | NULL | |
| type | varchar(100) | NO | | NULL | |
| status | int(11) | NO | | NULL | |
| created_at | datetime | NO | | CURRENT_TIMESTAMP | |
| updated_at | datetime | NO | | CURRENT_TIMESTAMP | |
+------------+--------------+------+-----+-------------------+----------------+
AbstractProfile Super Class:
/**
* AbstractProfile
*
* #ORM\Table(name="profile")
* #ORM\Entity(repositoryClass="ProfileBundle\Repository\ProfileRepository")
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", type="string")
* #ORM\DiscriminatorMap({
* "employee" = "EmployeeProfile",
* "company" = "CompanyProfile"
* })
*/
abstract class AbstractProfile
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var int
*
* #ORM\Column(name="status", type="integer")
*/
protected $status;
/**
* #ORM\OneToOne(targetEntity="User", inversedBy="profile")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
//... Getters, setters and all the rest
}
EmployeeProfile Sub Entity:
<?php
/**
* EmployeeProfile
*
* #ORM\Table(name="profile")
* #ORM\Entity
*/
class EmployeeProfile extends AbstractProfile
{
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Skill", inversedBy="profiles")
* #ORM\JoinTable(name="profile_skills",
* joinColumns={#ORM\JoinColumn(name="profile_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="skill_id", referencedColumnName="id", unique=true)}
* )
*/
private $skills;
public function __construct()
{
$this->skills = new ArrayCollection();
}
//... Getters, setters and all the rest
}
CompanyProfile Sub Entity:
<?php
/**
* CompanyProfile
*
* #ORM\Table(name="profile")
* #ORM\Entity
*/
class CompanyProfile extends AbstractProfile
{
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Event", inversedBy="profiles")
* #ORM\JoinTable(name="profile_events",
* joinColumns={#ORM\JoinColumn(name="profile_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="event_id", referencedColumnName="id", unique=true)}
* )
*/
private $events;
public function __construct()
{
$this->events = new ArrayCollection();
}
//... Getters, setters and all the rest
}
It looks like you are trying to use #ORM\InheritanceType("JOINED") with a single table. You use #ORM\Table(name="profile") in your 3 entities.
The result is that Doctrine don't know what to do with your entities.
You could try replacing #ORM\InheritanceType("JOINED") by #ORM\InheritanceType("SINGLE_TABLE").
So in my Symfony 3 project I need to create a sort of price calculator with takes 3 params: date_from, date_to, guest_number. Table in MySql looks like:
-----------------------------------------------------------------
id | date_from | date_to | people | price
-----------------------------------------------------------------
1 | 2016-01-15 | 2016-04-20 | 1 | 100
-----------------------------------------------------------------
2 | 2016-04-20 | 2016-08-15 | 1 | 200
-----------------------------------------------------------------
3 | 2016-04-20 | 2016-08-15 | 2 | 250
For example, someone choose 2016-01-01 till 2016-01-10 for 1 guest. Calculator should return 1000. And it is no big deal to create SQL statement for this
Example 2, someone choose 2016-04-15 til 2016-04-25 for 1 person.
The question is how can I build with Doctrine QueryBuilder statement which would calculate 'n' days from one period multiply price and 'n' days from another period multiply corresponding price?
Let's say you have an entity Calendar:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="CalendarRepository")
* #ORM\Table(name="calendar")
*/
class Calendar
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="date")
*/
private $dateFrom;
/**
* #ORM\Column(type="date")
*/
private $dateTo;
/**
* #ORM\Column(type="integer")
*/
private $people;
/**
* #ORM\Column(type="integer")
*/
private $price;
}
Then your repository class could look like this:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\EntityRepository;
class CalendarRepository extends EntityRepository
{
public function findPriceFor(\DateTime $dateFrom, \DateTime $dateTo, $nrOfPeople)
{
$qb = $this->createQueryBuilder('calendar');
$qb->select('SUM(
CASE
WHEN calendar.dateFrom >= :dateFromChosen AND calendar.dateTo >= :dateToChosen THEN DATE_DIFF(:dateToChosen, calendar.dateFrom)
WHEN calendar.dateFrom <= :dateFromChosen AND calendar.dateTo >= :dateToChosen THEN DATE_DIFF(:dateToChosen, :dateFromChosen)
WHEN calendar.dateFrom <= :dateFromChosen AND calendar.dateTo <= :dateToChosen THEN DATE_DIFF(calendar.dateTo, :dateFromChosen)
WHEN calendar.dateFrom >= :dateFromChosen AND calendar.dateTo <= :dateToChosen THEN DATE_DIFF(calendar.dateTo, calendar.dateFrom)
ELSE 0
END
)*calendar.price AS intervalPrice');
$qb->andWhere('calendar.people = :nrOfPeople')
->andWhere(
$qb->expr()->andX(
$qb->expr()->lt('calendar.dateFrom', ':dateToChosen'),
$qb->expr()->gt('calendar.dateTo', ':dateFromChosen')
)
);
$qb->setParameter('nrOfPeople', $nrOfPeople)
->setParameter('dateFromChosen', $dateFrom->format('Y-m-d'))
->setParameter('dateToChosen', $dateTo->format('Y-m-d'));
$qb->groupBy('calendar.id');
$query = $qb->getQuery();
$resultArray = $query->execute();
$totalPrice = array_sum(array_column($resultArray, 'intervalPrice'));
return $totalPrice;
}
}
If we took your example MySQL table, and decide to calculate a price for 1 person, from "2016-04-15" to "2016-04-25", then result would be this:
In my database I have an entity named Graphique. This the schema for:
class Graphique
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var decimal
*
* #ORM\Column(name="index", type="decimal", precision=9, scale=3, nullable=false)
*/
private $index;
/**
* #var datetime
*
* #ORM\Column(name="date", type="datetime", nullable=false)
*/
private $date;
/*getters and setters*/
This is some values for index, according to my database schema (example):
----------------------------------
id | index | dateTime |
----------------------------------
1 | 1700.000 | dateTime datas|
----------------------------------
2 | 1200.000 | dateTime datas|
----------------------------------
3 | 1200.000 | dateTime datas|
----------------------------------
4 | 1304.000 | dateTime datas|
----------------------------------
etc...| etc... | etc... |
I have this method into a controller:
$em=$this->getDoctrine()->getManager();
$queryIndex = $em->createQuery( 'SELECT g.index
FROM MySpaceMyBundle:Graphique g');
$array = array_map('current', $queryIndex);
$response = new Response();
$data = json_encode($array);
$response->headers->set('Content-Type', 'application/json');
$response->setContent($data);
return $response;
it returns me this into my json response:
["1700.000","1200.000","1200.000","1304.000","1800.000","2012.000","2048.000","1048.000","3000.000","5421.000"]
but I need to have this simple array result (instead the json response I give you just above):
[1700.000,1200.000,1200.000,1304.000,1800.000,2012.000,2048.000,1048.000,3000.000,5421.000]
I need to return a simple array in my json response in order to have this decimal values for displaying them into a highchart graphic.
How can I proceed? I already try some Doctrine methods like ->getArrayresult(), ->getScalarResult(), ->toArray(), but the results are the same. I need to make my query result to a simple array.
json_encode($array, JSON_NUMERIC_CHECK);
This solution should help you.