How to create foreign key constraint without relationship in Doctrine - doctrine-orm

I have the following 2 tables:
+-------+ +------------------------------+
| users | | blocked_users |
+-------+ +------------------------------+
| id | | id, user_id, blocked_user_id |
+-------+ +------------------------------+
My User class has the following relationship defined:
/**
* Users the instance user has blocked.
*
* #ORM\OneToMany(
* targetEntity="BlockedUser",
* mappedBy="blocker",
* fetch="EXTRA_LAZY",
* cascade={"persist", "remove"},
* orphanRemoval=true
* )
*
* #var BlockedUser[]|Collection
*/
protected $blockedUsers;
I haven't defined a blockedByUsers relationship, since a user should't know who blocked them. However, when I deleted a User who has been blocked I get a foreign key exception. I could fix the exception by adding another User::$blockedByUsers relationship, but as I said I don't believe that should exist.
Is there any other way to create the cascading foreign key relationship?

You can set the foreign key on the blocked_users table when you define the relationship from blocked_user_id to user:
#JoinColumn(name="blocked_user_id", referencedColumnName="id", onDelete="cascade")
The onDelete option will cascade deletion as you expect.

Related

Efficient way of joining two query sets without foreign key

I know django doesn't allow joining without a foreign key relation and I can't specify a foreign key because there are entries in one table that are not in the other (populated using pyspark). I need an efficient way to query the following:
Let's say I have the following tables:
Company | Product | Total # Users | Total # Unique Users
and
Company | Product | # Licenses | # Estimated Users
I would like to join such that I can display a table like this on the frontend
Company View
Product|Total # Users|Total # Unique Users|#Licenses|# Estimated Users|
P1 | Num | Num | Num | Num |
P2 | Num | Num | Num | Num |
Currently loop through each product and perform a query (way too slow and inefficient) to populate a dictionary of lists
Way too inefficient
I'm not quite getting why you can't do a Foreign key in this situation, but if you can implement your query in a sql statement I would look at Q objects. See "Complex Lookups with Q Objects" in the documentation.
https://docs.djangoproject.com/en/2.2/topics/db/queries/#complex-lookups-with-q-objects

Doctrine ManyToMany relationship issues

I'm trying to add Doctrine entities and relationships to my existing database schema but I'm running into some issues.
I have 4 tables:
+-------------+ +-----------+ +-----------------+ +-------------------+
| customers | | acl_roles | | acl_permissions | | acl_customer_role |
--------------- ------------- ------------------- ---------------------
| customer_id | | id | | role_id | | customer_id |
+-------------+ | name | | resource_id | | acl_role_id |
+------------ | flags | +--------------------
+------------------
In my ACL customers can have many roles and each role can have many permissions. The mapping of customers/roles is done through the acl_customer_role table.
I'm currently having issues making this relationship work. These are my entities (removed some standard annotations for brevity):
class Customer {
/**
* #ORM\ManyToMany(targetEntity="AclRole", cascade="persist")
* #ORM\JoinTable(name="acl_customer_role",
* joinColumns={#ORM\JoinColumn(name="acl_role_id", referencedColumnName="customer_id")}
* )
*/
protected $roles;
}
class AclRole {
/**
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
}
As you can see, in my customer entity I'm defining the $roles. It's a ManyToMany relationship since many roles can belong to many customers. I'm setting up my join table to be acl_customer_role and I specify the columns on which the join needs to take place. However, I get the following error:
PHP Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[42S22]: Column not found: 1054 Unknown column 'acl_customer_role.aclrole_id' in 'on clause'' in vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php:641
It seems that Doctrine's query is trying to join on 'acl_customer_role.aclrole_id' which obviously doesn't exists.
How can I specify the relationship correctly?
UPDATE:
Somehow it seems that Doctrine is modifying my column name. When I specify acl_role_id Doctrine strips out the first underscore and assumes that the column name is aclrole_id (as demonstrated in the error message in the question above). However, when I add two underscores such as acl__role_id it leaves all underscores in there and gives pretty much the same error other than that now it can't join on acl__role_id.
I'm pretty much at a loss..
I know this question is old but recently i have came across the same error / problem and i have found the solution.
By default Doctrine is using DefaultNamingStrategy class for generating e.g. joinColumn, joinTableName, propertyToColumnName names:
...
public function joinColumnName($propertyName)
{
return $propertyName . '_' . $this->referenceColumnName();
}
That's why in your case aclrole_id column was generated.
To change that behavior all you have to do is to change naming strategy for Doctrine2 to underscore in you app/config.yml file:
doctrine:
orm:
# ...
naming_strategy: doctrine.orm.naming_strategy.underscore
UnderscoreNamingStrategy class
...
public function joinColumnName($propertyName)
{
return $this->underscore($propertyName) . '_' . $this->referenceColumnName();
}
This would generate: acl_role_id
You can also implement your custom naming strategy: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/namingstrategy.html
Note: This was introduced in version 2.3.

Doctrine ManyToMany: removing an object

I have got problem with unidirectional ManyToMany relationship in Doctrine. The case is very easy: Product has many Tags. Tag can be attached to Product but also to any "taggable" entity in my model. Here is snippet of my code:
/**
* #Entity
* #Table(name="products")
**/
class Product {
/** some other fields here */
/**
* #ManyToMany(targetEntity="Tag")
* #JoinTable(name="products_tags",
* joinColumns={#JoinColumn(name="product_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="tag_id", referencedColumnName="id")}
* )
*/
protected $tags;
}
Since its unidirectional relation code of Tag class is omitted.
For such defined association Doctrine generated the following SQL code (SQL for products table and tags table is skipped):
CREATE TABLE `products_tags` (
`product_id` int(11) NOT NULL,
`tag_id` int(11) NOT NULL,
PRIMARY KEY (`product_id`,`tag_id`),
KEY `IDX_E3AB5A2C4584665A` (`product_id`),
KEY `IDX_E3AB5A2CBAD26311` (`tag_id`),
CONSTRAINT `FK_E3AB5A2CBAD26311` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`),
CONSTRAINT `FK_E3AB5A2C4584665A` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci |
I would like to remove product that has some tags attached to it.
/* $product is already persisted, $em is an Entity Manager */
$em->remove($product);
$em->flush();
It obviously fails due to integrity constraint violation ("Cannot delete or update a parent row: a foreign key constraint fails (products_tags, CONSTRAINT FK_E3AB5A2CBAD26311 FOREIGN KEY (tag_id) REFERENCES tags (id))'").
When I alter products_tags table adding ON DELETE CASCADE to foreign keys it works as I want. I can EASILY remove TAG ($em->remove($tag)) and PRODUCT ($em->remove($product) that automatically removes referenced rows from products_tags table.
How my code should look like to obtain products_tags table with ON CASCADE DELETE foreign keys? I've already tired with cascade={"all"} but it failed.
I know, I can remove all tag from product's tags collection, but as I mentioned I would like to achieve it in one step, just by calling remove method of entity manager object.
Does Doctrine really lack of that?
Ok, I managed myself by digging in Doctrine2 docs ;) Solution is to add onDelete="cascade" to #JoinColumn.
/**
* #Entity
* #Table(name="products")
**/
class Product {
/** some other fileds here */
/**
* #ManyToMany(targetEntity="Tag")
* #JoinTable(name="products_tags",
* joinColumns={#JoinColumn(name="product_id", referencedColumnName="id", onDelete="cascade")},
* inverseJoinColumns={#JoinColumn(name="tag_id", referencedColumnName="id", onDelete="cascade")}
* )
*/
protected $tags;
}
Note that, cascade={"all"} is managed on object level (in your app), while onDelete="cascade" is on database level.

Delete associated/referenced entity

I have a table product, the items in these table are referenced in tables such as cart_item and order_item as well as shipping_item etc.
All these references are optional (the product_id is set to nullable in those tables).
I need to have a way to delete a product and still keeping the other tables's records. One way I can think of is to go into all those tables, set the product_id to null, then go back to the product table to delete. However, since I may not know all the tables that are referencing to product (many other bundles can have entities that are referencing to this product), is there a way that I can know all these association to loop through and set null?
(Or perhaps there is a better way?)
PS: the idea that this is a shopping cart and the owner may want to remove expired products to clean up but for ordered, shipped items they still need to keep records.
Edit1:
This is the definition of the product reference in the OrderItem entity:
/**
* #var \Product
*
* #ORM\ManyToOne(targetEntity="Product")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="product_id", referencedColumnName="id")
* })
*/
private $product;
The error I'm getting:
PDOException: SQLSTATE[23000]: Integrity constraint violation: 1451
Cannot delete or update a parent row: a foreign key constraint fails
(test.order_item, C ONSTRAINT fk_order_item_product1 FOREIGN KEY
(product_id) REFERENCES product (id) ON DELETE NO ACTION ON
UPDATE NO ACTION)
Edit2:
I initially set onupdate="SET NULL" to the order_item entity and thought that was enough, it was not:
/**
* #var \Product
*
* #ORM\ManyToOne(targetEntity="Product")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="product_id", referencedColumnName="id", nullable=true, onDelete="SET NULL")
* })
*/
private $product;
After that, I had to update db schema as well.
Assuming you have the proper relations set up between the owning entity product and the other entities e.g. cart_item that should have a foreign_key, your wanted behaviour is the default for doctrine 2.
Take a look here in the manual
As an example they show the deletion of a User entity and its corresponding Comments
$user = $em->find('User', $deleteUserId);
foreach ($user->getAuthoredComments() AS $comment) {
$em->remove($comment);
}
$em->remove($user);
$em->flush();
The example states:
Without the loop over all the authored comments Doctrine would use an UPDATE statement only to set the foreign key to NULL and only the User would be deleted from the database during the flush()-Operation.
This suggests to me that in your case you actually want that behaviour. So just remove the product entity and doctrine 2 will automatically find all other entities with a foreign_key belonging to that product and will set it to NULL
Edit
Your error message suggests that upon attempted removal of the product entity there are still foreign_keys present, i.e. they have not been set to null properly by Doctrine.
You need to be sure to add the cascade property, specifically remove to your entity relationship. It would look something like the following:
<?php
class Product
{
//...
/**
* Bidirectional - One-To-Many (INVERSE SIDE)
*
* #OneToMany(targetEntity="Cart", mappedBy="product", cascade={"remove"})
*/
private $carts;
//...
}

Symfony2 OneToOne relationship becomes either a Unique index or a foreign key?

I'm taking my first steps in Symfony2 entity relations.
I have an entity installation, which each has one meter, and one monitor.
This translates to a uni-directional relationship, which I defined as such:
/**
*
* #ORM\OneToOne(targetEntity="InfoMeter")
* #ORM\JoinColumn(name="meterid", referencedColumnName="id")
*/
private $meter;
/**
*
* #ORM\OneToOne(targetEntity="InstallationsRtu")
* #ORM\JoinColumn(name="monitorid", referencedColumnName="id")
*/
private $monitor;
Each monitor can only be assigned to one installation.
Each meter can be assigned to multiple installations.
When I update my database (app/console doctrine:schema:update --force), there are two outcomes.
In case of the monitor:
Everything goes alright, schema gets updated with a keyname which is prefixed by 'UNIQ_'.
In case of the meter:
I get the following error
PDOException]
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '1' for key 'UNIQ_43D26968860BE41D'
When I look at the table structure after this error occured, I can't find that mentioned constraint, but I do find FK_43D26968860BE41D, i.e. the same, but prefixed with 'FK'.
In the installation table, I now have these listed:
Keyname Type Unique Packed Field Cardinality
PRIMARY BTREE Yes No id 2
UNIQ_43D26968701EC1AB BTREE Yes No monitorid 2
FK_43D26968860BE41D BTREE No No meterid 2
So one saying 'Unique=Yes', and the other saying 'Unique=No'.
To arrive at my question:
How can I decide whether it is a UNIQ index or a FK index?
I assume that Doctrine saw that currently each monitorid is unique in the installation table, but that each meterid occurs several times in the installation table.
Hence it went with UNIQ for the first, and FK for the latter. But can I control this somehow?
If one metter can be assigned to multiple installations, shouldn't you define a OneToMany relationship?
In your installation entity:
/**
* #ORM\ManyToOne(targetEntity="InfoMeter", inversedBy="installations")
* #ORM\JoinColumn(name="infometer_id", referencedColumnName="id")
*/
protected $info_meter;
And then in your InfoMeter entity:
/**
* #ORM\OneToMany(targetEntity="Installation",mappedBy="info_meter")
*/
protected $installations;
Besides, you should add the following to your InfoMeter class constructor:
function __construct() {
[...]
$this->installations = new \Doctrine\Common\Collections\ArrayCollection();
}
I'm sure this approach can be improved depending on how you want the relation between "installations" and "meters", but this should work.