Doctrine 2 One-To-One unidirectional relationship delete non-owning side - doctrine

I have two entities with a one-to-one unidirectional relationship:
class Foo {
...
/**
* #OneToOne(targetEntity="Bar")
*/
private $bar;
...
}
class Bar {
...
}
When I try to delete a Bar entity I get this error:
Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails
How do I keep a unidirectional relationship without loosing the ability to delete Bar entities?

With Doctrine 2, this is what you need to do:
class Foo {
...
/**
* #OneToOne(targetEntity="Bar")
* #JoinColumn(name="bar_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $bar;
...
}
class Bar {
...
}
The onDelete="Cascade" will do what CappY said in his answer (setup your foreign key on delete cascade). This way, when you delete your Bar entity the Foo entity associated will be removed too.
In case your prefer not to remove your Foo entity you can simply replace onDelete="Cascade" with onDelete="SET NULL".

You can use the Orphan Removal. It works with one-to-one, one-to-many and many-to-many associations.
You have just to add the orphanRemoval=true option like this :
#OneToOne(targetEntity="Bar", orphanRemoval=true)

Setup your Foreign Key ON DELETE cascade (this will delete Foo records too) or SET NULL (this will set column to NULL when u delete Bar)
http://dev.mysql.com/doc/refman/5.5/en/innodb-foreign-key-constraints.html

Related

using observers to delete all relations in Laravel

My tables structure is :
Shopsidname
Productsidnameshop_id
Product_tagsidlabelvalueproduct_id
Problem is: I want to delete products and product tags on deleting shop
I made two Observers and registered both :
ShopObserver
ProductObserver
ShopObserver :
public function deleting(Shop $shop)
{
$shop->products()->delete();
}
ProductObserver :
public function deleting(Product $product)
{
$product->tags()->delete();
}
But I have following error :
"SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (`tabadolha`.`product_tags`, CONSTRAINT `product_tags_product_id_foreign` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`)) (SQL: delete from `products` where `products`.`shop_id` = 26 and `products`.`shop_id` is not null)"
I don't want to use nullOnDelete on my database. I want to delete it by observer.
Any way?
The problem is that your second observer of tags should be fired FIRST, and you can't specify the order of observers in Laravel.
What I would to is delete the tags in the same ShopObserver, before deleting the products, and not create a separate ProductObserver.
Something like:
$shop->products->each(function($product) {
$products->tags()->delete();
});
$shop->products()->delete();
Please test, not sure about the syntax, typed it with phone from my memory :)

Laravel - How to delete child row when parent was deleted?

I have 4 tables as below:
class Job extend Model
{
public function candidates()
{
return $this->hasMany(App\Models\Candidate::class);
}
}
=========================================================
class Candidate extend Model
{
public function skills()
{
return $this->belongsToMany(App\Models\Skill::class);
}
public function job()
{
return $this->belongsTo(\App\Models\Job::class);
}
}
So, when I delete a job. I want to delete all candidates who apply to this job also. But, When I delete I got this message
SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (`evaluation_db`.`candidate_skill`, CONSTRAINT `candidate_skills_candidate_id_foreign` FOREIGN KEY (`candidate_id`) REFERENCES `candidates` (`id`)) (SQL: delete from `candidates` where `candidates`.`job_id` = 39 and `candidates`.`job_id` is not null)
I think, it because of skills relation inside candidate class. Could any one help me out? I really appreciate with your help.
Thank you!
You need to define the behaviour in your migration.
Doc : https://laravel.com/docs/8.x/migrations#foreign-key-constraints
$table->foreignId('user_id')
->constrained()
->onUpdate('cascade')
->onDelete('cascade');
Delete child row when parent delete (DeleteWithRelation)
Delete all customers (children) when deleting company (parent)
$companyDetails = Company::find($companyId);
if (!$companyDetails) {
return "Company Not Found.";
}
$companyDetails->customers()->delete();
$companyDetails->delete();

How to get Doctrine handling ENUMs correctly?

In an application I have a case of the Class Table Inheritance. The discriminator column is an ENUM:
/**
* Foo
*
* #ORM\Table(name="foos", ...)
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="`type`", type="string", columnDefinition="ENUM('bar', 'buz')")
* #ORM\DiscriminatorMap({
* "bar" = "Bar",
* "buz" = "Buz"
* })
*/
abstract class Foo
{
...
}
Doctrine works as expected (to begin with). The doctrine:migrations:diff command creates a migration for the tables and relationships and also defines the discriminator column correctly, as an ENUM.
Then I execute the migrations (doctrine:migrations:migrate). The schema looks well. But:
When I execute the diff command again (and expect no new migrations), I get a new migration generated:
final class Version20180619205625 extends AbstractMigration
{
public function up(Schema $schema) : void
{
$this->addSql('ALTER TABLE foos CHANGE type `type` ENUM(\'bar\', \'buz\')');
}
public function down(Schema $schema) : void
{
$this->addSql('ALTER TABLE tasks CHANGE `type` type VARCHAR(255) DEFAULT NULL COLLATE utf8mb4_unicode_ci');
}
}
Alright, I execute it. And try the diff command again. And get the same migration generated again... So, Doctrine seems to "think", the column is still VARCHAR.
I showed the issue here on example of an inheritance discriminator. But actually it doesn't matter, if the column is a discriminator or not -- this behavior is the same for every ENUM column.
How to solve this issue? Is there a way make Doctrine handle ENUM columns correctly?

Laravel delete all belongsToMany relations in belongsToMany relation

I have a Tournament model which contains a belongsToMany relationship with a Session model:
class Tournament extends Model{
public function sessions(){
return $this->belongsToMany(Session::class, 'tournament_has_sessions');
}
}
Then my sessions model contains a belongsToMany relationship with the User model:
class Session extends Model{
public function users(){
return $this->belongsToMany(User::class, 'session_has_users');
}
}
Now when I delete a Tournament, I want to delete all sessions and all information in the session_has_users table with it.
I have tried:
$tournament->sessions()->with('users')->delete(); -- < This
$tournament->sessions()->delete();
$tournament->users()->detach();
$tournament->delete();
But it does't work. The data in the session_has_users table persists
I realize i could do something like this:
DB::table('session_has_users')->whereIn('session_id', $this->sessions()->pluck('id'))->delete();
But is there a more efficient/handy (or even alternative if not more efficient) way to accomplish this?
Using RDBMS Referential Actions on the Foreign Key
In the database schema, you should define the foreign key with the ON DELETE CASCADE action. This will automatically delete the related records when the referenced id is deleted.
(See dbudimir's answer for the example lines from a Laravel migration file)
Using Eloquent to Detach All Related Models
If you aren't using foreign keys or your database lacks the support for referential actions, you can use this:
$tourament->sessions()->sync([]);
The sync method accepts an array of IDs to place on the intermediate table. Any IDs that are not in the given array will be removed from the intermediate table. So, after this operation is complete, only the IDs in the given array will exist in the intermediate table:
https://laravel.com/docs/5.5/eloquent-relationships#updating-many-to-many-relationships
Providing an empty array should then remove all the relations.
In the table 'tournament_has_sessions' you can set onDelete('cascade') like this
$table->integer('tournament_id')->unsigned()->nullable();
$table->foreign('tournament_id')->references('id')->on('tournaments')->onDelete('cascade');
$table->integer('session_id')->unsigned()->nullable();
$table->foreign('session_id')->references('id')->on('sessions')->onDelete('cascade');
And when delete tournaments automaticly deleted all records in this table
you can use any of these:
$tourament->sessions()->detach();
or
$tourament->sessions()->sync([]);
or you can define the foreign key with the ON DELETE CASCADE like the answers above

laravel 5.4 how to handle query exception with custom exception

whenever I try to delete an artist with the related song in the child table.
it returns this error.
QueryException in Connection.php line 647:
SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (`laravel`.`artist_song`, CONSTRAINT `artist_song_artist_id_foreign` FOREIGN KEY (`artist_id`) REFERENCES `artists` (`id`)) (SQL: delete from `artists` where `id` = 1)
this is what I want. to protect the parent table, but what I want is to make the end user see a message saying like this "yOU CAN NOT DELETE AN ARTIST WITH RELATED SONGS PLEASE DELETE ALL SONGS OF THIS ARTIST, FIRST'. so how can I catch this with the custom exception?
I don't think you need to rely here on database exception. When someone chooses deleting artist you should verify whether artist has any songs and if yes, you should then redirect with message.
Assuming you have in Artist model relationship defined like this:
public function songs()
{
return $this->hasMany(Song::class);
}
in controller you could use code like this:
public function destroy($artistId)
{
$artist = Artist::findOrFail($artistId);
if ($artist->songs()->count()) {
return redirect()->back()->with('message','You cannot delete ...');
}
$artist->delete();
return redirect()->route('artists.index');
}

Resources