Askedio/laravel-soft-cascade

Error on destroy()

Closed this issue · 5 comments

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.

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.

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
{
/**
* The database table used by the model.
*
* @var string
*/
protected $table = 'staff';

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

/**
 * Attributes that should be mass-assignable.
 *
 * @var array
 */
protected $fillable = ['provider_id', 'first_name', 'last_name', 'title', 'credentials', 'position'];

public function provider(){

    return $this->belongsTo(Provider::class);
}

public function uploads(){

    return $this->hasMany(Upload::class, 'model_id')->where('model', 'App\Staff');
}

public function locations(){

    return $this->belongsToMany(Location::class);
}

/*
 * Scope
 */

public function scopeByKeyword ($query, $keyword)
{
    return $query->where(function ( $q ) use ( $keyword ) {
        $q
            ->where('first_name', 'like', '%' . $keyword . '%')
            ->orWhere('last_name', 'like', '%' . $keyword . '%')
            ->orWhere('title', 'like', '%' . $keyword . '%')
            ->orWhere('credentials', 'like', '%' . $keyword . '%');
    });
}

}

/** 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.

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. *

All models related must implement:

SoftDeletes

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

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!!