I am trying to force delete model with one relation. I made a custom function for that in model.
/**
* Force delete active ingredient with relationships
* #return bool|null
* #throws \Exception
*/
public function bruteDelete()
{
$this->brandPresentations()->forceDelete();
return parent::forceDelete();
}
This is how I am calling the function
Active_ingredients::withTrashed()->find($request->get('elementID'))->bruteDelete();
Which force deletes brandPresentations perfectly. But it soft deletes the parent, which is active_ingredients.
I tried to use forceDelete() as function name to overwrite the Laravel method, but it gave me the same result.
How can I force delete both models?
You can use it in boot method like this.
protected static function boot()
{
parent::boot();
self::deleting(function (User $model) {
if ($model->forceDeleting){
$model->brandPresentations()->forceDelete();
}
});
}
And then run forceDelete
Active_ingredients::withTrashed()->find($request->get('elementID'))->forceDelete();
I hope it works :)
I would suggest using cascade on delete for your migrations
$table->foreign('model_id')->references('id')->on('models')->onDelete('cascade');
By calling $item->forceDelete() then all of its related foreign keys would be deleted as well.
I'm pretty sure this does not work for soft delete.
Related
I've been trying to create a Many To Many (Polymorphic) system that saves the state of every update done by a specific user on specific Models (for instance: Company, Address, Language models) with a note field and the updated_by. The goal is to keep track of who updated the model and when, and the note field is a text field that states where in the system the model was updated.
Below is what I created, but I'm open to getting a different solution that, in the end, allows me to accomplish the goal described above.
I've created the model Update (php artisan make:model Update -m) with the migration:
/**
* Run the migrations.
*
* #access public
* #return void
* #since
*/
public function up()
{
Schema::create('updates', function (Blueprint $table) {
$table->id()->comment('The record ID');
$table->bigInteger('updatable_id')->unsigned()->comment('THe model record id');
$table->string('updatable_type')->comment('THe model name');
$table->string('note')->nullable()->comment('The record note');
$table->integer('updated_by')->unsigned()->nullable()->comment('The user ID which updated the record');
$table->timestamps();
$table->softDeletes();
});
}
On the model Update all the $fillable, $dates properties are standard, and the method of the Update model:
class Update extends Model
{
/**
* Method to morph the records
*
* #access public
*/
public function updatable()
{
return $this->morphTo();
}
}
After trying several ways on the different models, my difficulty is getting the relation because when I save to the updates table, it saves correctly. For instance, in the Company model: Company::where('id', 1)->with('updates')->get(); as in the model Company I have the method:
public function updates()
{
return $this->morphToMany(Update::class, 'updatable', 'updates')->withPivot(['note', 'updated_by'])->withTimestamps();
}
Most certainly, I'm doing something wrong because when I call Company::where('id', 1)->with('updates')->get(); it throws an SQL error "Not unique table/alias".
Thanks in advance for any help.
The problem I see here is using morphToMany instead of morphMany.
return $this->morphToMany(Update::class, 'updateable', 'updates'); will use the intermediate table (and alias) updates (3rd argument) instead of using the default table name updateables. Here it will clash with the table (model) updates, so it will produce the error you are receiving.
return $this->morphMany(Update::class, 'updateable'); will use the table updates and should work with your setup.
Do notice that morphMany does not work with collecting pivot fields (e.g. withPivot([..]), it's not an intermediate table. Only morphToMany does.
I am trying to delete a parent record which then deletes a multi level relationship.
I have 3 tables: accounts -> services -> service_items
When I delete an account I want the services to be deleted and also all the service_items related to that service.
In my Account model I have the following code
public function delete()
{
$this->services()->delete();
return parent::delete();
}
This is working, when the account is deleted all the related services are deleted, but the service items are not being removed.
I have the following function in my Service model
public function delete()
{
$this->service_items()->delete();
return parent::delete();
}
It seems like this function isn't being triggered when I delete an account. I know I can loop over the services and delete each service_item but I was wondering if there was a way to just trigger the delete function in the Service model?
NOTE: If I directly delete a service the service_items are deleted
Alright, so here is the solution.
Instead of deleting each service_item individually, you should use the concept of the hook of the Laravel by declaring the booted method under the model
Account.php
<?php
class Account extends Model
{
/**
* The "booted" method of the model.
*
* #return void
*/
protected static function booted()
{
static::deleted(function($account) {
$account->services()->delete();
});
}
/**
* List of services associated with an account.
*/
public function services()
{
// or you may have some other relationship
return $this->hasMany('App\Service');
}
}
Service.php
<?php
class Service extends Model
{
/**
* The "booted" method of the model.
*
* #return void
*/
protected static function booted()
{
static::deleted(function($service) {
$service->items()->delete();
});
}
/**
* List of items associated with an account.
*/
public function items()
{
// or you may have some other relationship
return $this->hasMany('App\ServiceItem');
}
}
So whenever you delete an account then it will delete all services associated with that account and in turn, it will delete all the service items associated with each service.
On my project all models extend BaseModel class, which uses SoftDeletes trait by default. But in some specific cases, e.g. in class ShouldHardDelete I don't want my DB records to be deleted softly. Let's suppose, I can't deny extending BaseModel.
What changes should I make in my ShouldHardDelete class to prevent it from using soft deletes?
There are two things you should do:
There is a static method bootSoftDeletes() in SoftDeletes trait, which initializes soft-delete behaviour for the model:
/**
* Boot the soft deleting trait for a model.
*
* #return void
*/
public static function bootSoftDeletes()
{
static::addGlobalScope(new SoftDeletingScope);
}
Override it in the ShouldHardDelete class to an empty method:
/**
* Disable soft deletes for this model
*/
public static function bootSoftDeletes() {}
Set $forceDeleting field to true in ShouldHardDelete:
protected $forceDeleting = true;
Thus, you can disable soft-deleting behaviour, while still extending BaseModel which uses SoftDeletes trait.
if you are using softDelete trait but don't want to use it on a specific query then you can do using the withoutGlobalScope method in the model.
User::withoutGlobalScope(Illuminate\Database\Eloquent\SoftDeletingScope::class)->get();
if you want to use multiple models for a specific query then better to extends eloquent builder in AppServiceProvider
public function boot()
{
Builder::macro('withoutSoftDeletingScope', function () {
$this->withoutGlobalScope(Illuminate\Database\Eloquent\SoftDeletingScope::class);
return $this;
});
}
and then you can call as a method in any model
User::withoutSoftDeletingScope()->get();
I have a model Test as follows
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Test extends Model
{
use SoftDeletes;
protected $dates = ['deleted_at'];
public function __construct() {
if (!\App::environment('production')) {
$this->table='test_stage';
}
}
I have made sure that there is a 'deleted_at' column in my test_stage table. But the soft deletes are not working. Using the delete() method permanently removes the record from the table. As an additional step of verification I manually added 'deleted_at' value for some columns. But query the model still gives me the soft deleted record.
Moreover, removing the model constructor entirely and simply defining the table name using:
protected $table = 'test_stage';
Works like a charm! That is soft deletes magically start working again.
Or is there any way around to define the table name according to the environment without the need of defining a constructor?
I think the problem could be that you're overwriting the constructor, which is set in Illuminate\Database\Eloquent\Model. Have you tried
public function __construct(array $attributes = []) {
parent::__construct($attributes);
if (!\App::environment('production')) {
$this->table='test_stage';
}
}
Edit: more detailed explaination
As you overwrite the constructor of the class you're extending, the original does not get executed anymore. This means necessary functions for the eloquent model do not get executed. See the constructor for Illuminate\Database\Eloquent\Model below:
/**
* Create a new Eloquent model instance.
*
* #param array $attributes
* #return void
*/
public function __construct(array $attributes = [])
{
$this->bootIfNotBooted();
$this->syncOriginal();
$this->fill($attributes);
}
By making sure the extending class requires the same parameters for the constructor as the extended class and executes parent::__construct($attributes); first, the constructor of the extended class gets executed first. After which you can overwrite $this->table in the extending class.
How do you unlink a relation from a many-to-many table without deleting anything?
I have tried:
$getProject = $this->_helper->getDocRepo('Entities\Project')->findOneBy(array('id' => $projectId));
$getCat = $this->_doctrine->getReference('\Entities\Projectcat', $catId);
$getProject->getCategory()->removeElement($getCat);
$this->em->flush();
my Projectcat entity:
/**
* #ManyToMany(targetEntity="\Entities\Projectcat", cascade={"persist", "remove"})
* #JoinColumn(name="id", referencedColumnName="id")
*/
protected $getCategory;
A rather old post but wanted to provide a way to ensure the association was removed from the ORM Entity side of doctrine, rather than of having to manually execute each Entity's collection removeElement and to expand on the answer by #Rene Terstegen.
The issue is that Doctrine does not "auto-magically" tie together the associations, you can however update the entity's Add/Remove methods to do so.
https://gist.github.com/Ocramius/3121916
The below example is based on the OP's project/category schema.
It assumes that the table project_category is the ManyToMany relationship table, and the project and category tables use the primary key id.
class Project
{
/**
* #ORM\ManyToMany(targetEntity="Category", inversedBy="projects")
* #ORM\JoinTable(
* name="project_category",
* joinColumns={
* #ORM\JoinColumn(name="project", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="category", referencedColumnName="id")
* }
* )
*/
protected $categories;
public function __construct()
{
$this->categories = new ArrayCollection();
}
/**
* #param Category $category
*/
public function removeCategory(Category $category)
{
if (!$this->categories->contains($category)) {
return;
}
$this->categories->removeElement($category);
$category->removeProject($this);
}
}
class Category
{
/**
* #ORM\ManyToMany(targetEntity="Project", mappedBy="categories")
*/
protected $projects;
public function __construct()
{
$this->projects = new ArrayCollection();
}
/**
* #param Project $project
*/
public function removeProject(Project $project)
{
if (!$this->projects->contains($project)) {
return;
}
$this->projects->removeElement($project);
$project->removeCategory($this);
}
}
Then all you need to do is to call the removeCategory or removeProject method, instead of both. The same can be applied for addCategory and addProject methods as well.
$project = $em->find('Entities\Project', $projectId);
$category = $em->getReference('Entities\Category', $categoryId);
$project->removeCategory($category);
$em->flush();
Your information is a bit limited. Some extra information about the database scheme and Project would be nice. But to give it a try.
You have to remove it from both sides of the relationship. You removed it from the category, but you should also remove it from the project.
// Remove Category from Project
$Project->Category->removeElement($Category);
// Remove Project from Category
$Category->Project->removeElement($Project);
Good luck!
An old post, but the answer above helped me, but it may help to expand it a little bit, I have a project that can have many categories (and categories than can have many projects), so this code gets me all of them:
$project->getCategories();
If I wanted to delete all of the categories for a project, I simply do this:
foreach ($project->getCategories() as $category) {
$project->getCategories()->removeElement($category);
}
The issue with the original question was that I believe Doctrine wants you to pass in the category that is referenced by the project, not just a reference to the category that you grabbed independently using this code:
$getCat = $this->_doctrine->getReference('\Entities\Projectcat', $catId);
Hopefully that makes sense. I know I'm messing up the terminology slightly.