Askedio / laravel-soft-cascade

Cascade Delete & Restore when using Laravel SoftDeletes
https://medium.com/asked-io/cascading-softdeletes-with-laravel-5-a1a9335a5b4d
MIT License
705 stars 63 forks source link

Error on destroy() #50

Closed enterlight closed 6 years ago

enterlight commented 6 years ago

Trying to delete a Model instance that has SoftCascadeTrait and get the following:

` SoftCascadeLogicException {#750 ▼

message: "Call to undefined method Illuminate\Database\Query\Builder::withTrashed()"

code: 0

file: "/home/vagrant/Code/medixaid/vendor/askedio/laravel5-soft-cascade/src/SoftCascade.php"

line: 38

-previous: BadMethodCallException {#755 ▶} trace: {▼ /home/vagrant/Code/medixaid/vendor/askedio/laravel5-soft-cascade/src/SoftCascade.php:38 {▶} /home/vagrant/Code/medixaid/vendor/askedio/laravel5-soft-cascade/src/Listeners/CascadeDeleteListener.php:20 {▶} Askedio\SoftCascade\Listeners\CascadeDeleteListener->handle() {} /home/vagrant/Code/medixaid/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php:364 {▶} /home/vagrant/Code/medixaid/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php:199 {▶} /home/vagrant/Code/medixaid/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php:159 {▶} /home/vagrant/Code/medixaid/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasEvents.php:148 {▶} /home/vagrant/Code/medixaid/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:742 {▼ › › if ($this->fireModelEvent('deleting') === false) { › return false; arguments: {▶} } /home/vagrant/Code/medixaid/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:714 {▶} /home/vagrant/Code/medixaid/app/Http/Controllers/Admin/ProvidersController.php:153 {▼ › try { › Provider::destroy($id); › } arguments: {▶} } `

The error disappear when I remove the SoftCascade Trait. The model has 4 relationships but the record to be deleted has no related records at the moment of the call.

Would appreciate some guidance. Many thanks in advance.

maguilar92 commented 6 years ago

Hello @enterlight, I need to know if you are using laravel/lumen with what version and Askedio/laravel5-soft-cascade version. I also need you to copy the code of the models involved and the query that gives the error.

Thanks.

enterlight commented 6 years ago

Thanks for your reply. Taken from my composer.lock:

        "name": "askedio/laravel5-soft-cascade",
        "version": "5.5.5",

        "name": "laravel/framework",
        "version": "v5.4.36",

` /* Provider Model ---------------------------------------------------- /

namespace App;

use App\Traits\CheckableAttributes; use App\Traits\CreatedByTrait; use Illuminate\Database\Eloquent\SoftDeletes; use Askedio\SoftCascade\Traits\SoftCascadeTrait; use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Storage; use OwenIt\Auditing\Auditable; use OwenIt\Auditing\Contracts\Auditable as AuditableContract;

class Provider extends Model implements AuditableContract { use Auditable, SoftDeletes, CreatedByTrait, CheckableAttributes, SoftCascadeTrait;

/**
 * The database table used by the model.
 *
 * @var string
 */
protected $table = 'providers';

/**
 * The database primary key value.
 *
 * @var string
 */
protected $primaryKey = 'id';

/**
 * Attributes that should be mass-assignable.
 *
 * @var array
 */
protected $fillable = [
    'name',
    'bio',
    'website',
    'phone',
    'spanish_speaking',
    'cms_rating_url',
    'healthgrades_rating_url',
    'patient_portal',
    'emr_ehr_system',
    'primary_contact_name',
    'payment_method',
    'approved',
    'created_by',
    'updated_by',
    'picture',
];

protected $casts = [
    'approved'         => 'bool',
    'spanish_speaking' => 'bool',
];

protected $appends = [
];

protected $softCascade = ['locations@restrict', 'staff@restrict', 'billings@restrict', 'users@restrict'];

/**
 * One provider has many locations.
 */
public function locations()
{
    return $this->hasMany('App\Location');
}

/**
 * One provider has many staff.
 */
public function staff()
{
    return $this->hasMany('App\Staff');
}

/**
 * One provider has many billings.
 */
public function billings()
{
    return $this->hasMany('App\Billing');
}

/**
 * One provider has many users Through the pivot ProviderUser
 */
public function users()
{
    return $this->hasMany('App\User');//->where('type','provider');
}

public function uploads()
{

    return $this->hasMany(Upload::class);
}

public static function Unapproved( $count = 0 )
{

    if ( $count == 1 )
        return self::where('approved', 0)->orderBy('created_at', 'DESC')->take(5)->get();

    return self::where('approved', 0)->count();

}

public function scopeApproved( $query )
{
    return $query->whereApproved(1);
}

public function activeBids( $locationId = null )
{

    $query = $this->hasManyThrough('App\Bid', 'App\Location')
        ->where('status', 'open')
        ->has('request')
        ->with([
            'request',
            'request.service',
            'location',
            'location.provider',
        ]);

    if ( !is_null($locationId) ) {
        $query = $query
            ->wherehas('location', function ( $locationQuery ) use ( $locationId ) {
                $locationQuery->where('id', $locationId);
            });
    }

    return $query;

}

public function wonBids( $locationId = null )
{

    $query = $this->hasManyThrough('App\Bid', 'App\Location')
        ->where('status', 'closed_won')
        ->with('request')
        ->with('request.service')
        ->with('location.provider');
    if ( !is_null($locationId) ) {
        $query = $query->wherehas('location', function ( $locationQuery ) use ( $locationId ) {
            $locationQuery->where('id', $locationId);
        });
    }

    return $query;

}

public function pendingApprovals()
{
    return $this->hasMany('App\PendingApproval');
}

public function availableBids()
{

    $locations = $this->locations()->get();

    $customerRequest = array();
    $uniqueServices = array();
    foreach ( $locations as $location ) {
        foreach ( $location->services as $service ) {

            $uniqueServices[$service->id] = $service->id;
        }

    }

    foreach ( $uniqueServices as $service ) {

        $customerRequest[] = CustomerRequest::where('service_id', $service)
            ->whereHas('bids', function ( $q ) {
                $q->where('status', 'open');
            })->where('winning_bid_id', 0)->get();
    }

    return $customerRequest;

}

public function hasLocation( $locationId )
{
    return !!$this->locations()->where('id', $locationId)->count();
}

public function getUpdatedAtAttribute($value)
{
    return Carbon::parse($value)->tz('EST');
}

public function getHealthgradesRatingAttribute($value)
{
    return ( $value / 10.0 );
}

public function getCmsRatingAttribute($value)
{
    return ( $value / 10.0  );
}

public function getBetterDoctorRatingAttribute($value)
{
    return ( $value / 10.0 );
}

public function getPictureAttribute($path)
{
    if ( !empty($path) ) {

        if ( Storage::disk('s3')->exists($path) ) {
            return(Storage::disk('s3')->url($path));
        }
    }
    return ('/img/unknown.jpg');
}

public function getFillables()
{
    return $this->fillable;
}

}

/* Location Model ----------------------------------------------------------------------------- /

namespace App;

use App\Traits\CheckableAttributes; use App\Traits\CreatedByTrait; use Askedio\SoftCascade\Traits\SoftCascadeTrait; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Notifications\Notifiable; use OwenIt\Auditing\Auditable; use OwenIt\Auditing\Contracts\Auditable as AuditableContract;

class Location extends Model implements AuditableContract { use Notifiable, Auditable, SoftDeletes, CreatedByTrait, CheckableAttributes, SoftCascadeTrait;

/**
 * The database table used by the model.
 *
 * @var string
 */
protected $table = 'locations';

/**
 * The database primary key value.
 *
 * @var string
 */
protected $primaryKey = 'id';

/**
 * Attributes that should be mass-assignable.
 *
 * @var array
 */
protected $fillable = [
    'provider_id',
    'name',
    'address',
    'address2',
    'state',
    'city',
    'zip',
    'phone',
    'website',
    'latitude',
    'website',
    'longitude',
    'public',
    'timezone',
    'working_hours',
    'active',
    'date_range_offpeak',
    'date_range_peak',
];

protected $casts = [
    'active'        => 'bool',
    'working_hours' => 'object',
];

protected $appends = [
    'short_address',
    'long_address',
];

protected $softCascade = ['services@restrict', 'bids@restrict', 'requests@restrict', 'uploads', 'restrict@peak_times', 'restrict@request_answers'];

public function receivesBroadcastNotificationsOn( $notifible )
{
    return "provider-{$this->provider_id}-{$this->id}";
}

/**
 * One provider has many locations.
 */
public function provider()
{
    return $this->belongsTo('App\Provider');
}

/**
 * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
 */
public function services()
{
    return $this->belongsToMany('App\Service', 'location_services')->withPivot('active', 'id', 'service_description', 'date_range_peak', 'date_range_offpeak');
}

public function activeServices()
{
    return $this->belongsToMany('App\Service', 'location_services')->withPivot('active', 'id')->wherePivot('active', 1);
}

/**
 * One location has many bids.
 */
public function bids()
{
    return $this->HasMany('App\Bid');
}

public function uploads()
{
    return $this->hasMany(Upload::class, 'model_id')->where('model', 'App\Location');
}

public function auto_bidding_instructions()
{
    return $this->hasMany(AutoBiddingInstruction::class);
}

/**
 * @deprecated
 * @return \Illuminate\Database\Eloquent\Relations\HasMany
 */
public function peak_times()
{
    return $this->hasMany(PeakTime::class);
}

public function weekly_schedules()
{
    return $this->hasMany(WeeklySchedule::class);
}

public function request_answers()
{
    return $this->hasMany(RequestQuestionAnswer::class);
}

public function requests()
{
    return $this->belongsToMany(CustomerRequest::class, 'request_location', 'location_id', 'request_id');
}

public function geolocation()
{
    return $this->hasOne(Lookup::class, 'zip', 'zip');
}

public function getShortAddressAttribute()
{
    return sprintf(
        '%s, %s %s',
        ucfirst($this->getAttribute('city')),
        ucfirst($this->getAttribute('state')),
        ucfirst($this->getAttribute('zip'))
    );
}

public function getLongAddressAttribute()
{
    return sprintf(
        '%s %s, %s, %s %s',
        ucfirst($this->getAttribute('address')),
        ucfirst($this->getAttribute('address2')),
        ucfirst($this->getAttribute('city')),
        ucfirst($this->getAttribute('state')),
        $this->getAttribute('zip')
    );
}

public function getDynamicNameAttribute()
{
    return $this->getAttribute('name')?: $this->short_address;
}

public function getAnswers( RequestQuestion $question )
{
    return RequestQuestionAnswer::where('request_question_id', $question->id)
        ->where('location_id', $this->id)
        ->get()
        ->pluck('option_id')
        ->toArray();
}

public function requestQuestions()
{
    $services = $this->services()->get()->pluck('id');

    return RequestQuestion::isComplete()->where(function ( $query ) use ( $services ) {
        $query->whereHas('services', function ( $q ) use ( $services ) {
            $q->whereIn('service_id', $services);
        })
            ->orWhere('is_location_specific', 1);
    })->get();
}

public function scopeIsActive ($query)
{
    return $query->where('active', true);
}

public function scopeHasService ($query, $serviceId)
{
    return $query->whereHas('services', function ( $serviceQuery ) use ( $serviceId ) {
        $serviceQuery
            ->where('services.id', $serviceId);
    });
}

}

/* Staff Model --------------------------------------------------------------------------------- /

namespace App;

use Illuminate\Database\Eloquent\Model;

class Staff extends Model { /**

/* Billing Model --------------------------------------------------------------------------------------- /

namespace App;

use App\Traits\CheckableAttributes; use App\Traits\CreatedByTrait; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes;

class Billing extends Model {

use SoftDeletes,
    CreatedByTrait,
    CheckableAttributes;

/**
 * The database table used by the model.
 *
 * @var string
 */
protected $table = 'billings';

/**
* The database primary key value.
*
* @var string
*/
protected $primaryKey = 'id';

/**
 * Attributes that should be mass-assignable.
 *
 * @var array
 */
protected $fillable = ['provider_id','bid_id', 'bidding_time', 'bid_amount', 'technology_fee'];

protected $dates = ['bidding_time'];

/**
 * Billing belong to provider.
 */
public function provider()
{
    return $this->belongsTo('App\Provider');
}

}

/* User Model /

namespace App;

use App\Traits\CheckableAttributes; use App\Traits\CreatedByTrait; use App\Traits\HasCustomerRequests; use App\Traits\useFilters; use Askedio\SoftCascade\Traits\SoftCascadeTrait; use Carbon\Carbon; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use OwenIt\Auditing\Models\Audit;

class User extends Authenticatable { use Notifiable, SoftDeletes, CreatedByTrait, CheckableAttributes, HasCustomerRequests, SoftCascadeTrait, useFilters;

/**
 * The attributes that are mass assignable.
 *
 * @var array
 */
protected $fillable = [
    'first_name',
    'last_name',
    'email',
    'phone',
    'password',
    'type',
    'provider_id',
    'bid_notification_type',
];

protected $softCascade = [ 'customer' ];

/**
 * The attributes that should be hidden for arrays.
 *
 * @var array
 */
protected $hidden = [
    'password',
    'remember_token',
];

public function receivesBroadcastNotificationsOn()
{
    return "user-{$this->id}";
}

/**
 * Many users of type provider belong to one provider
 */
public function provider()
{
    return $this->belongsTo('App\Provider');
}

/**
 * Users type customer has one customer
 */
public function customer()
{
    return $this->hasOne('App\Customer');
}

/**
 * Users type customer has many addresses
 */
public function address()
{
    return $this->belongsToMany('App\Address');
}

public function requests()
{
    return $this->hasManyThrough('App\CustomerRequest', 'App\Customer', 'user_id', 'customer_id');
}

/*
 * Mutators
 */

/*
 * Getters
 */

public function getIsAdminAttribute()
{
    return $this->attributes['type'] === 'admin' ? true : false;
}

public function getIsProviderAttribute()
{
    return $this->attributes['type'] === 'provider' ? true : false;
}

public function getIsCustomerAttribute()
{
    return $this->attributes['type'] === 'customer' ? true : false;
}

public function getFullNameAttribute()
{
    return $this->getAttribute('first_name') . ' ' . $this->getAttribute('last_name');
}

public function isType( $type )
{
    return $this->attributes['type'] === $type;
}

/*
 * Setters
 */

public function setPasswordAttribute( $password )
{
    $this->attributes['password'] = bcrypt($password);
}

public function setEmailAttribute( $email )
{
    $this->attributes['email'] = strtolower($email);
}

public function verification()
{

    return $this->hasOne(VerificationCode::class);
}

public function verifiedCustomer()
{
    if($this->customer) {
        return $this->customer->verified;
    }

    return false;
}

/*
 * Functions
 */

public function getLastLoginInDate()
{
    $loginRecord = Audit::where('user_id', $this->id)->where('event', 'login')->latest()->first();
    $date = $loginRecord ? $loginRecord->created_at : Carbon::now();

    return $date;
}

/*
 * Nexmo function
 */
/**
 * Route notifications for the Nexmo channel.
 *
 * @return string
 */
public function routeNotificationForNexmo()
{
    return $this->phone;
}

}

/* Function that gives me the error -------------------------------------------------------------- /

public function destroy($id)
{
    $success = true;
    try {
        Provider::destroy($id);
    }
    catch (\Exception $e) {
        //dd($e);
        $success = false;
        Session::flash('flash_error', $e->getMessage());
    }
    if ( $success ) {
        Session::flash('flash_message', 'Provider deleted!');
    }

    return redirect('admin/providers');
}

`

/** NOTE:

When I call destroy, I get $e->message : Call to undefined method Illuminate\Database\Query\Builder::withTrashed()

If I comment out protected $softCascade = ['locations@restrict', 'staff@restrict', 'billings@restrict', 'users@restrict']; In the Provider model, the code runs fine.

Finally, at the moment of the call, the Provider instance tdoes not have ANY Billing, Staff, Location, or User related record.

maguilar92 commented 6 years ago

You are using a version available only for laravel 5.5. *.

To use it on your website you need to use version 2.6 or update laravel to version 5.5. *

maguilar92 commented 6 years ago

All models related must implement:

SoftDeletes

As I can see staff model don't use it.

enterlight commented 6 years ago

Thanks! I let composer decide what version to install. Anyway I removed staff@restrict from protected $softCascade = ['locations@restrict', 'staff@restrict', 'billings@restrict', 'users@restrict']; and destroy() call goes though.

Thanks again!!