Soft Deleting through relationships - laravel

I am trying to understand something.
I have a Project Model.
A Project can have many Document.
A Document has many DocumentData.
So this is straight forward, I have my models set up like so
class Project extends Model
{
protected $table = 'projects';
protected $guarded = [];
use SoftDeletes;
public function document()
{
return $this->hasMany('App\document', 'projectId');
}
public static function boot()
{
parent::boot();
static::deleted(function($document)
{
$document->delete();
});
}
}
class Document extends Model
{
use SoftDeletes;
protected $table = 'document';
protected $guarded = [];
public function project()
{
return $this->belongsTo('App\Project', 'projectId');
}
public function documentData()
{
return $this->hasMany('App\DocumentData', 'documentId');
}
public static function boot()
{
parent::boot();
static::deleted(function($document)
{
$document->documentData()->delete();
});
}
}
class DocumentData extends Model
{
use SoftDeletes;
protected $table = 'document_data';
protected $guarded = [];
public function document()
{
return $this->belongsTo('App\Document', 'documentId');
}
}
I am trying to understand the boot function and whether I have set it up properly? When I delete a project, its deleted_at timestamp is set. I am also looking for it to set the deleted at timestamp for all of that Projects Documents and DocumentData.
At the moment, when I delete a Project, on its deleted_at timestamp is set. The Document and DocumentData remains null.
How can I get it soft deleting through all related models?
Thanks

You're using the boot method correctly. The only thing i notice is a mistake in the handler of your Project's deleted event. You're trying to delete the instance again after it's been deleted. Instead, i suppose, you want to delete the associated Documents like so:
public static function boot()
{
parent::boot();
static::deleted(function($project)
{
$project->document()->delete();
});
}
Alternative Method:
The way i usually delete children is to override the delete() method on the parent models. I prefer the resulting code that way, but that's just a personal preference.
In your case that would be:
class Project extends Model {
public function delete()
{
parent::delete(); // first delete the Project instance itself
$this->document()->delete(); // delete children
}
}
class Document extends Model {
public function delete()
{
parent::delete();
$this->documentData()->delete();
}
}

Related

Override Eloquent Relation Create Method

I want to override create method, but with relation, it didn't touch the create method.
There are Two Models:
class User extends Model
{
public function user_detail()
{
return $this->hasOne(UserDetail::class);
}
}
class UserDetail extends Model
{
public static function create(array $attributes = [])
{
//I was trying to do something like
/*
if(isset($attributes['last_name']) && isset($attributes['first_name']))
{
$attributes['full_name']=$attributes['first_name'].' '.$attributes['last_name'];
}
unset($attributes['first_name'],$attributes['last_name']);
*/
Log::debug('create:',$attributes);
$model = static::query()->create($attributes);
return $model;
}
}
When I use UserDetail::create($validated), and there is a log in laravel.log, so I know the code touched my custom create method.
But if I use
$user = User::create($validated);
$user->user_detail()->create($validated);
There is no log in laravel.log, which means laravel didn't touch the create method, then how I supposed to do to override create method under this circumstance?(I'm using laravel 5.7)
Thank you #Jonas Staudenmeir, after I read the documentation, here is my solution.
If the $attributes are not in protected $fillable array, then I do it in the __construct method.
class UserDetail extends Model
{
protected $fillable=['full_name','updated_ip','created_ip'];
public function __construct(array $attributes = [])
{
if (isset($attributes['first_name']) && isset($attributes['last_name'])) {
$attributes['full_name'] = $attributes['first_name'].' '.$attributes['last_name'];
}
parent::__construct($attributes);
}
}
Otherwise, I do it in Observer.
namespace App\Observers;
use App\Models\UserDetail;
class UserDetailObserver
{
public function creating(UserDetail $userDetail)
{
$userDetail->created_ip = request()->ip();
}
public function updating(UserDetail $userDetail)
{
$userDetail->updated_ip = request()->ip();
}
}
Register Observer in AppServiceProvider.
namespace App\Providers;
use App\Models\UserDetail;
use App\Observers\UserDetailObserver;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
UserDetail::observe(UserDetailObserver::class);
}
}
I choose Observer instead of Event&Listener is for easy maintenance.

Laravel 5.6.17 Model hasOne over multiple tables

I'm quite new to Laravel and now I'm trying to move parts of a former application from a small self written framework into Laravel. The address book is multilingual therefor the table structure is a little more complicated.
db-structure
And this is my source code:
AddressBookController.php
namespace App\Http\Controllers;
use App\AddressBook as AB;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class AddressBookController extends Controller
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
$entries = AB::all();
return view('addressBook')->with([
'class' => __CLASS__,
'function' => __FUNCTION__,
'line' => __LINE__,
'entries' => $entries,
]);
}
}
Model AddressBook.php
namespace App;
use Illuminate\Database\Eloquent\Model;
class AddressBook extends Model
{
protected $table = 'address';
protected $primaryKey = 'address_id';
protected $keyType = 'int';
public $incrementing = true;
public $timestamps = false;
protected $searchable = [
'columns' => [
'address.address_surname' => 10,
'address.address_company' => 5,
'address.address_vatid' => 2,
],
];
public function country() {
return $this->hasOne('country', 'country_id', 'country_id');
}
public function addresstype() {
return $this->hasOne('addresstype', 'addresstype_id', 'addresstype_id');
}
}
Model Country.php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Country extends Model
{
protected $table = 'country';
protected $primaryKey = 'country_id';
protected $keyType = 'int';
public $incrementing = true;
public $timestamps = false;
public function translation() {
return $this->hasOne('translations', 'translations_id', 'translations_id');
}
public function addressbook() {
return $this->belongsTo('address', 'country_id', 'country_id');
}
}
Model AddressType
namespace App;
use Illuminate\Database\Eloquent\Model;
class AddressType extends Model
{
protected $table = 'addresstype';
protected $primaryKey = 'addresstype_id';
protected $keyType = 'int';
public $incrementing = true;
public $timestamps = false;
public function translation() {
return $this->hasOne('translations', 'translations_id', 'translations_id');
}
public function addressbook() {
return $this->belongsTo('address', 'addresstype_id', 'addresstype_id');
}
}
Model Translation.php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Translation extends Model
{
protected $table = 'translations';
protected $primaryKey = 'translations_id';
protected $keyType = 'int';
public $incrementing = true;
public $timestamps = false;
public function country() {
return $this->belongsTo('country', 'translations_id', 'translations_id');
}
public function addresstype() {
return $this->belongsTo('addresstype', 'translations_id', 'translations_id');
}
}
The request "$entries = AB::all();" works in general but I get the id's and maybe I'm completely wrong here but I thought that data from foreign keys will be replaced with the respective models (if configured correctly). so my question is:
a. did I make a mistake during configuration and if yes, where exactly is the error?
or
b. is my assumption of replacing id's with objects is completly wrong?
Thanks in advance!
Steve
Laravel Eloquent models do not replace the foreign keys of the active records with the relational data, it only appends a new property with the same name as the method relating the classes and in that property it puts all the Model instances of the resulted query, this ONLY if you access the property, its called Eager Loading.
It is explained here (Ofiicial Documentation)
$addressBook = App\AddressBook::find(1); //this only will return the active record with the id 1 and nothig more.
$addressBook->country; // The property "country" does not exist in the AddressBook Classs but eloquent models will return a "fake" property with the value of the result of the query by the method with the same name (only if the method returns an Eloquent Relation).
This Eloquent behavior is natural, and is a very clever way to minimize the number of queries, Eloquent will never load a relation if there is no need to.
If you want to load a relation in a set the models at the same time that those models are retriving from the db you need to explicitly especify wich relations you want to load.
$addressBook = App\AddressBook::with(['country', 'addresstype', 'anotherRelation'])->get(); // this will retrive all the addressBook models and in each one will attach the relations specified.
[EDITED]
Also you have to place the entire namespace of the related model class in the relation methods so you need to replaced like:
class Translation extends Model
{
protected $table = 'translations';
protected $primaryKey = 'translations_id';
protected $keyType = 'int';
public $incrementing = true;
public $timestamps = false;
// ****** You need to put the entire namespace of the Model class
public function country() {
return $this->belongsTo('App\Country', 'translations_id', 'translations_id');
}

Laravel 'boot' function is not firing

I have the following in an eloquent model
class Bucket extends \Eloquent {
protected $fillable = ['name'];
protected $appends = ['slug'];
public function __construct(){
$this->key = substr(str_shuffle(MD5(microtime())), 0, 24);
}
public static function boot(){
dd('check');
}
public function users(){
return $this->belongsToMany('User');
}
public function getSlugAttribute(){
return slugify($this->name);
}
}
However I'm able to read and update the model with no problem. I was under the impression boot was supposed to be called every time a model was instantiated, is that wrong?
Here's one of the controllers I use to view all the buckets
public function index()
{
return Auth::user()->buckets;
}
In your constructor, try calling
parent::__construct();

Eloquent deleting model event doesn't fire

I have 2 models defined:
Program model:
class Program extends \Eloquent {
protected $guarded = [];
public static function boot()
{
parent::boot();
static::deleting(function($program)
{
DB::table('descriptions')->where('id',$program->id)->delete();
return true;
});
}
public function description()
{
return $this->hasOne('Description');
}
}
Description model, where description for the program is defined
class Description extends \Eloquent {
protected $guarded = [];
public function program()
{
return $this->belongsTo('Program','id','id');
}
}
When I delete Program with specific name I want the description for that program to be deleted too.
So:
Program::where('name',Input::get('name'))->delete();
Unfortunately this code doesn't fire "deleting" event for Program model and the description isn't deleted.
Whats wrong ?
The solution is :
Program::where('name',Input::get('name'))->first()->delete();

Model Event not working

I have the following model:
class ProjectTwitterStatus extends Eloquent {
protected $table = 'project_twitter_statuses';
protected $softDelete = true;
protected $guarded = array('id');
public static function boot()
{
parent::boot();
ProjectTwitterStatus::deleting(function($projectTwitterStatus)
{
$projectTwitterStatus->deleted_by = Auth::user()->id;
});
}
public function twitterStatus() {
return $this->belongsTo('TwitterStatus');
}
public function twitterRetweet() {
return $this->belongsTo('TwitterRetweet','twitter_status_id','twitter_status_id');
}
public function project() {
return $this->belongsTo('Project');
}
}
Somewhere in my app an item is deleted using one of the following statements:
ProjectTwitterStatus::find($id)->delete();
ProjectTwitterStatus::whereIn('twitter_status_id', $twitterStatusIds)->delete();
I can see in the database the item had been (soft) deleted. But the deleted_by column is not filled.
Does anyone know what I am doing wrong?
Try using Late Static Binding, like following-
class ProjectTwitterStatus extends Eloquent {
public static function boot ()
{
parent::boot();
static::deleting(function($projectTwitterStatus)
{
$projectTwitterStatus->deleted_by = Auth::user()->id;
});
}
}

Resources