I have a self-referencing entity Product:
<?php
/** #Entity #Table(name="products") **/
class Product
{
/** #Id #Column(type="integer") #GeneratedValue **/
protected $id;
/** #Column(type="string", nullable=true) **/
protected $name;
/**
* #ManyToMany(targetEntity="Product", mappedBy="connectedBy", cascade={"all"})
*/
protected $connectedWith;
/**
* #ManyToMany(targetEntity="Product", inversedBy="connectedWith", cascade={"all"})
* #JoinTable(name="connection",
* joinColumns={#JoinColumn(name="product_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="connected_product_id", referencedColumnName="id")}
* )
*/
protected $connectedBy;
public function __construct()
{
$this->connectedWith = new \Doctrine\Common\Collections\ArrayCollection();
$this->connectedBy = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getConnected()
{
return $this->connectedWith;
}
public function addConnection(Product $product)
{
$this->connectedWith->add($product);
$product->connectedBy->add($this);
}
public function removeConnection(Product $product)
{
$this->connectedBy->removeElement($product);
$this->connectedWith->removeElement($product);
}
}
Next I created two products (IDs 1 and 2) and a connection between the both products:
mysql> select * from products;
+----+------+
| id | name |
+----+------+
| 1 | NULL |
| 2 | NULL |
+----+------+
2 rows in set (0.00 sec)
mysql> select * from connection;
+------------+----------------------+
| product_id | connected_product_id |
+------------+----------------------+
| 2 | 1 |
+------------+----------------------+
1 row in set (0.01 sec)
Now I want to remove the connection with this code:
$product1 = $entityManager->find('Product', 1);
$product2 = $entityManager->find('Product', 2);
$product1->removeConnection($product2);
$entityManager->persist($product1);
$entityManager->flush();
$product3 = $entityManager->find('Product', 1);
print count($product3->getConnected()) . "\n";
As expected, the code prints 0 as its result. But when I look into the database, the connection entry still exists. What could be the cause any how could this be fixed?
I've already tried to $entityManager->persist($product2) but to no avail.
I researched a little more and found the solution myself:
My function removeConnection() has a bug: I removed the product from both lists, connectedBy and connectedWith, which is wrong. Instead, I should do it like in addConnection():
$this->connectedWith->removeElement($product);
$product->connectedBy->removeElement($this);
Related
I have two entities Modules and Orders where One order have Many modules and I'm wondering how to fetch an array collection of modules persisted as follow:
Table: Orders
id | modules | user_id | ... | created_at |
----------------------------------------------------
1 | [2,6,5] | 12 | ... | 2018-07-28 00:00:00 |
----------------------------------------------------
As you can see my modules are persisted as array. So after that how can I make Doctrine (with Symfony) to get my modules
I think you need a ManyToOne relationShip ... as I know, we never store an array in database.
in your example order can have many modules and module can have just one order ...
in this case order called owning side and module called invers side ...
and module keep id of order ...
look at this example
Table: Orders
id | user_id | ... | created_at |
----------------------------------------------------
1 | 12 | ... | 2018-07-28 00:00:00 |
----------------------------------------------------
Table: Modules
id | order_id | ... | created_at |
----------------------------------------------------
1 | 1 | ... | 2018-07-28 00:00:00 |
----------------------------------------------------
2 | 1 | ... | 2018-07-29 00:00:00 |
----------------------------------------------------
you must write your code like this...
Order Class
class Order implements OrderInterface
{
/**
* #var Collection
*
* #ORM\OneToMany(targetEntity="Module", mappedBy="order", cascade={"persist"})
*/
protected $modules;
/**
* Don't forget initial your collection property
* Order constructor.
*/
public function __construct()
{
$this->modules = new ArrayCollection();
}
/**
* #return Collection
*/
public function getModules(): Collection
{
return $this->modules;
}
/**
* #param ModuleInterface $module
*/
public function addModule(ModuleInterface $module): void
{
if ($this->getModules()->contains($module)) {
return;
} else {
$this->getModules()->add($module);
$module->setOrder($this);
}
}
/**
* #param ModuleInterface $module
*/
public function removeModule(ModuleInterface $module): void
{
if (!$this->getModules()->contains($module)) {
return;
} else {
$this->getModules()->removeElement($module);
$module->removeOrder($this);
}
}
}
Module Class
class Module implements ModuleInterface
{
/**
* #var OrderInterface
*
* #ORM\OneToMany(targetEntity="Order", mappedBy="modules", cascade={"persist"})
*/
protected $order;
/**
* #param OrderInterface $order
*/
public function setOrder(OrderInterface $order)
{
$this->order = order;
}
public function getOrder(): OrderInterface
{
return $this->order;
}
}
when you persist order object by doctrine... doctrine handle this and create items
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").
I have got two classes which are being associated using one to one uni-direction
{
id: 1,
name: "onetooneuniparent name",
onetooneunichild: {
id: 1,
name: "onetooneunichild name",
__initializer__: null,
__cloner__: null,
__isInitialized__: true
}
}
the above is the result when I do query like following
http://localhost:8000/onetooneRead?id=1
I want to know where and why the following come from
__initializer__: null,
__cloner__: null,
__isInitialized__: true
my expected result is just this
{
id: 1,
name: "onetooneuniparent name",
onetooneunichild: {
id: 1,
name: "onetooneunichild name"
}
}
OnetoOneUniParent.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="onetooneuniparent")
*/
class OnetoOneUniParent{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string",name="name")
*/
private $name;
/**
* #ORM\OneToOne(targetEntity="OnetoOneUniChild",cascade={"persist"})
* #ORM\JoinColumn(name="child_id", referencedColumnName="id")
*/
private $onetooneunichild;
<.... getter and setter here ...>
}
OnetoOneUniChild.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="onetooneunichild")
*/
class OnetoOneUniChild{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string",name="name")
*/
private $name;
<.... getter and setter here ...>
This is the method in controller
/**
* #Route("/onetooneRead")
* #Method("GET")
*/
public function onetooneReadAction(Request $request){
$logger = $this->get('logger');
$encoders = array(new XmlEncoder(), new JsonEncoder());
$normalizers = array(new ObjectNormalizer());
$serializer = new Serializer($normalizers, $encoders);
$logger->info('onetoone Read');
$id = $request->query->get("id");
$em = $this->getDoctrine()->getManager();
$onetooneuniparent = $em->getRepository('AppBundle:OnetoOneUniParent')->find($id);
$onetooneuniparentJson = $serializer->serialize($onetooneuniparent, 'json');
$response = new JsonResponse();
$response->setContent($onetooneuniparentJson);
return $response;
}
This is what is inside in MySQL
mysql> select * from onetooneuniparent;
+----+----------+------------------------+
| id | child_id | name |
+----+----------+------------------------+
| 1 | 1 | onetooneuniparent name |
| 2 | 2 | onetooneuniparent name |
| 3 | 3 | onetooneuniparent name |
+----+----------+------------------------+
3 rows in set (0.00 sec)
mysql> select * from onetooneunichild;
+----+-----------------------+
| id | name |
+----+-----------------------+
| 1 | onetooneunichild name |
| 2 | onetooneunichild name |
| 3 | onetooneunichild name |
+----+-----------------------+
3 rows in set (0.00 sec)
Those functions are part of the Doctrine proxy coding, since you are using Lazy Loading Doctrine needs to keep track of the child entity if it needs to be loaded or not. Part of that keeping track is these functions (I believe it is in this portion of Doctrine)
There may be a way around this which would be to avoid using lazy loading. To do that you can utilize EAGER loading if you always want the child to load with the parent. Alternatively if you only want to use EAGER for this one query and not every time you would have to switch to DQL as documented here or you could use the JOIN comma (second example down) here
The current table Structure is:
+---------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| title | varchar(25) | NO | UNI | NULL | |
| content | varchar(500) | NO | | NULL | |
+---------------+------------------+------+-----+---------+----------------+
Model:
<?php
namespace Com\Models;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Validator\Uniqueness;
class Articles extends Model
{
/**
*
* #var integer
*/
public $id;
/**
*
* #var string
*/
public $title;
/**
*
* #var string
*/
public $content;
/**
* Returns table name mapped in the model.
*
* #return string
*/
public function getSource()
{
return 'articles';
}
/**
* Allows to query a set of records that match the specified conditions
*
* #param mixed $parameters
* #return Articles[]
*/
public static function find($parameters = null)
{
return parent::find($parameters);
}
/**
* Allows to query the first record that match the specified conditions
*
* #param mixed $parameters
* #return Articles
*/
public static function findFirst($parameters = null)
{
return parent::findFirst($parameters);
}
Upon saving data from controller, I am receiving this error: id is required
EDIT
Here is the snippet for the save method:
$article = new Articles();
$article->title = $this->request->getPost('title', 'striptags');
$article->content = $this->request->getPost('content');
if (!$article->save()) {
$this->flash->error($article->getMessages());
} else {
$this->flash->success("Article created.");
Tag::resetInput();
}
I've never had a problem with auto increment fields and Phalcon.
Only thing I can suggest is setting default to AUTO_INCREMENT
delete cache file
ex. "~\cache\metaData\meta-bds_models_yourmodel-your_model.php"
I would like to know if exist a way to add fields on the fly to any entity on Symfony2. I'm searching on the big internet and I didn't find anything. When I said "a way", I mean if exist a Doctrine Extension with that behavior, a bundle that implement it, design pattern, etc.
My idea is something similar to Translatable behavior of Doctrine Extensions. Supouse I have a Address entity, so I would like to add some attributes on the fly like street, number, intersections, and others but at the begining I didn't know what fields could exist.
I'm thinking something as 2 entities: Address and AddressFieldValues. Address will have specifics attributes like id, foreing keys of relationships with others classess and will be used to inject the dynamic attributes (a collections of field-values). AddressFieldValue will have the reals fields-values of Address, with the following attributes: id, address_id, field_name, field_value.
So, entity Address could be like this:
/**
* Address
*
* #ORM\Entity(repositoryClass="AddressRepository")
* #ORM\Table(name="address")
*/
class Address
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(
* targetEntity="AddressFieldValues",
* mappedBy="object",
* cascade={"persist", "remove"}
* )
*/
private $field_value;
public function __construct()
{
$this->field_value = new ArrayCollection();
}
public function getFieldValue()
{
return $this->field_value;
}
public function addFieldValue(AddressFieldValues $fv)
{
if (!$this->field_value->contains($fv)) {
$this->field_value[] = $fv;
$fv->setObject($this);
}
}
public function getId()
{
return $this->id;
}
}
and AddressFieldValues entity could be like this:
/**
* #ORM\Entity
* #ORM\Table(name="address_field_values",
* uniqueConstraints={#ORM\UniqueConstraint(name="lookup_unique_idx", columns={
* "object_id", "field"
* })}
* )
*/
class AddressFieldValues
{
/**
* #var integer $id
*
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue
*/
protected $id;
/**
* #var string $field
*
* #ORM\Column(type="string", length=32)
*/
protected $field;
/**
* #ORM\ManyToOne(targetEntity="Address", inversedBy="field_value")
* #ORM\JoinColumn(name="object_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $object;
/**
* #var string $content
*
* #ORM\Column(type="text", nullable=true)
*/
protected $content;
/**
* Convenient constructor
*
* #param string $field
* #param string $value
*/
public function __construct($field, $value)
{
$this->setField($field);
$this->setContent($value);
}
/**
* Get id
*
* #return integer $id
*/
public function getId()
{
return $this->id;
}
/**
* Set field
*
* #param string $field
*/
public function setField($field)
{
$this->field = $field;
return $this;
}
/**
* Get field
*
* #return string $field
*/
public function getField()
{
return $this->field;
}
/**
* Set object related
*
* #param string $object
*/
public function setObject($object)
{
$this->object = $object;
return $this;
}
/**
* Get related object
*
* #return object $object
*/
public function getObject()
{
return $this->object;
}
/**
* Set content
*
* #param string $content
*/
public function setContent($content)
{
$this->content = $content;
return $this;
}
/**
* Get content
*
* #return string $content
*/
public function getContent()
{
return $this->content;
}
}
So, if I have the following values on table: address_field_values
id | object | field | content
1 | 1 | street | 1st Ave
2 | 1 | number | 12345
3 | 1 | intersections | 2sd Ave and 4th Ave
4 | 2 | street | 1st Ave
5 | 2 | number | 12347
6 | 2 | intersections | 2sd Ave and 4th Ave
7 | 3 | street | 1st Ave
8 | 3 | number | 12349
9 | 3 | intersections | 2sd Ave and 4th Ave
For now address table only have the following values:
| id |
| 1 |
| 2 |
| 3 |
I could like to inject those fields-values to a Address object on the fly, to do something like this:
// if I need get de Address with id = 2
$addressRepository = $em->getRepository('Address');
$address = $addressRepository->find(2);
sprintf('The address is: "%s", #"%s" between "%s".', $address->getStreet(), $address->getNumber(), $address->getIntersections());
// then it should show: The address is 1st Ave, #12347 between 2sd Ave and 4th Ave.
//
// or if I need add a new Address, do something like this:
$address = new Address();
$address->setStreet('1st Ave');
$address->setNumber('12351');
$address->setIntersections('2sd Ave and 4th Ave');
$em->persist($address);
$em->flush();
then it save the address and address_field_values, and the tables have the following values:
// address
| id |
| 1 |
| 2 |
| 3 |
| 4 |
// address_field_values
id | object | field | content
1 | 1 | street | 1st Ave
2 | 1 | number | 12345
3 | 1 | intersections | 2sd Ave and 4th Ave
4 | 2 | street | 1st Ave
5 | 2 | number | 12347
6 | 2 | intersections | 2sd Ave and 4th Ave
7 | 3 | street | 1st Ave
8 | 3 | number | 12349
9 | 3 | intersections | 2sd Ave and 4th Ave
10 | 4 | street | 1st Ave
11 | 4 | number | 12351
12 | 4 | intersections | 2sd Ave and 4th Ave
So, any ideas how can I do that?
Remember, I have as requirement in my bussiness logic that I didn't know what fields could have a Address at beginig so I need to inject the fields on the fly. I use Address as example but this behavior can be used for any entity.
Thanks in advance
I think that your request is similar to a collection in a form (Doctrine2 documentation).
In the documentation, a collection of Tags entities with name property) is linked to a Task entity. In your case, the entity AddressFieldValue will have the field and content properties and the collection of AddressFieldValue entities will be added to Address entity.
So, by using this documentation and replacing Task by Address and Tag by AddressFieldValue it should works.