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();
Related
My ide typhints a few different namespaces for rendering a view in my controller and i'm not sure which one i'm supposed to use:
class PostsController extends Controller
{
public function index() : View
{
return view('posts.index');
}
}
The "view" function returns multiple types:
#return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
So which one am i supposed to use? \Illuminate\Contracts\View\View or \Illuminate\Contracts\View\Factory
What is the difference?
Why is it returning two different types instead of one? I code php in a strict way because i prefer it for readable and less error prone code, in my opinion this is a bad way of doing it and as you can see is causing confusion; a method should only be allowed one return type, create multiple methods if need be.
EDIT
Thank you for your input everyone, i have come up with the following that allows me to use the facade and the contract without producing typhint errors in my ide:
<?php
namespace App\Http\Controllers;
use Illuminate\Contracts\View\View as ViewContract;
use Illuminate\Support\Facades\View as ViewFacade;
/**
* Class PostsController
* #package App\Http\Controllers
*/
class PostsController extends Controller
{
/**
* #return ViewContract
*/
public function index() : ViewContract
{
return ViewFacade::make('posts.index');
}
}
So i can call View:make() and return the contract that View:make() returns.
EDIT 2
Using the view() helper i can condense further, i am aliasing with ViewContract just for my benefit of knowing which namespace i'm using:
<?php
namespace App\Http\Controllers;
use Illuminate\Contracts\View\View as ViewContract;
/**
* Class PostsController
* #package App\Http\Controllers
*/
class PostsController extends Controller
{
/**
* #return ViewContract
*/
public function index() : ViewContract
{
return view('posts.index');
}
}
I’ll try and address each of your questions.
In your instance, type-hint Illuminate\Contracts\View\View or the concrete implementation (Illuminate\View\View).
I’ll cover this in 3.
You’re using the view global helper function. It can return different types because, well, it can. If you don’t pass a parameter to view() then it will return a view factory instance. If you do pass a parameter (like you have in your usage), then it will return an instance of the view named by the first parameter (if such a view exists). So that’s why the view() helper function is typed to return multiple different types. Because depending on how you use it, it can return a different type.
You mean: \Illuminate\View\View.
public function index(): \Illuminate\View\View
{
return view('posts.index');
}
view() is a helper of Illuminate\Support\Facades\View facade.
return view('posts.index');
Same as :
use Illuminate\Support\Facades\View;
return View::make('posts.index');
See the documentation of Creating & Rendering Views
I know I can define a relationship by
Class Users extends Model{
function profile(){
return $this->hasOne(Profile::Class);
}
}
is there a way like adding extra query to the relationship like other than foreign key and local key that is available to define, I want to only get those records of Profile model that field active contains a value of 1. Profile model has a field named active. Any help, ideas is greatly appreciated, thank you in advance.
you can simply try
return $this->hasOne(Profile::Class)->where('active', 1);
but better approach will be using Scope like this.
create a folder app/Scopes and add a new file ActiveUserOnly.php
place this code there
namespace App\Scopes;
use \Illuminate\Database\Eloquent\Builder;
use \Illuminate\Database\Eloquent\Scope;
use \Illuminate\Database\Eloquent\Model;
class ActiveUsersOnly implements Scope {
/**
* #inheritdoc
*
* #param Builder $builder
* #param Model $model
*
* #return Builder|void
*/
public function apply( Builder $builder, Model $model ) {
return $builder->where( 'active', '=', true );
}
}
add this code to the top of Profile model.
use App\Scopes\ActiveProfilesOnly;
add this code in your Profile model.
protected static function boot() {
parent::boot();
static::addGlobalScope( new ActiveProfilesOnly() );
}
then this code will work in your User model.
Class Users extends Model{
function profile(){
return $this->hasOne(Profile::Class);
}
}
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.
In my app, I use soft delete on a lot of object, but I still want to access them in my app, just showing a special message that this item has been deleted and give the opportunity to restore it.
Currently I have to do this for all my route parametters in my RouteServiceProvider:
/**
* Define your route model bindings, pattern filters, etc.
*
* #return void
*/
public function boot()
{
parent::boot();
Route::bind('user', function ($value) {
return User::withTrashed()->find($value);
});
Route::bind('post', function ($value) {
return Post::withTrashed()->find($value);
});
[...]
}
Is there a quicker and better way to add the trashed Object to the model binding ?
Jerodev's answer didn't work for me. The SoftDeletingScope continued to filter out the deleted items. So I just overrode that scope and the SoftDeletes trait:
SoftDeletingWithDeletesScope.php:
namespace App\Models\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletingScope;
class SoftDeletingWithDeletesScope extends SoftDeletingScope
{
public function apply(Builder $builder, Model $model)
{
}
}
SoftDeletesWithDeleted.php:
namespace App\Models\Traits;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Models\Scopes\SoftDeletingWithDeletesScope;
trait SoftDeletesWithDeleted
{
use SoftDeletes;
public static function bootSoftDeletes()
{
static::addGlobalScope(new SoftDeletingWithDeletesScope);
}
}
This effectively just removes the filter while still allowing me to use all the rest of the SoftDeletingScope extensions.
Then in my model I replaced the SoftDeletes trait with my new SoftDeletesWithDeleted trait:
use App\Models\Traits\SoftDeletesWithDeleted;
class MyModel extends Model
{
use SoftDeletesWithDeleted;
For Laravel 5.6 to 7
You can follow this doc https://laravel.com/docs/5.6/scout#soft-deleting. And set the soft_delete option of the config/scout.php configuration file to true.
'soft_delete' => true,
For Laravel 8+
You can follow this doc https://laravel.com/docs/8.x/routing#implicit-soft-deleted-models. And append ->withTrashed() to the route that should accept trashed models:
Ex:
Route::get('/users/{user}', function (User $user) {
return $user->email;
})->withTrashed();
You can add a Global Scope to the models that have to be visible even when trashed.
For example:
class WithTrashedScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
$builder->withTrashed();
}
}
class User extends Model
{
protected static function boot()
{
parent::boot();
static::addGlobalScope(new WithTrashedScope);
}
}
Update:
If you don't want to show the deleted objects you can still manually add ->whereNull('deleted_at') to your query.
Is it possible to create a middleware in laravel 5.2x to return data in controller only for specific user_id instead typing everywhere stuff like
->where('access_gallery','=',true)
For example I have a gallery on my webpage where users can upload photos crop them etc.
I check by middleware if their payment_datetime < current datatime, if true next step.
In next step i want to return/edit/delete/crop/..., only photos for specific user, to do that normally i would have to create a query with #up code, because we I dont want user_1 to edit user_2 page.
It's a little annoying to copy it everywhere, and also if i create an Admin account to access everything i have to create next query for every each function to return all data for them.
If it's not possible to create function like that in middleware, is it possible in controller?
I think what you're looking for is a scope - or even a global scope.
https://laravel.com/docs/5.2/eloquent#global-scopes
Create a Scopes directory under App. Create a file like so:
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class UserGallery implements Scope
{
/**
* Query if user has gallery
*
* #return void
*/
public function apply(Builder $builder, Model $model)
{
return $builder->where('access_gallery','=',true);
}
}
Then in your model (that you want this scope applied too), add a boot function to the beginning of your model class:
use App\Scopes\UserGallery;
/**
* The "booting" method of the model.
*
* #return void
*/
protected static function boot()
{
parent::boot();
static::addGlobalScope(new UserGallery);
}
You could even put the scope in a trait class...in my opinion, would look cleaner and easier to inject into your models.
PS: Limit the amount of logic you put in a middleware class. Consider middleware as a door to get into your main set of logic. That door is either open or locked for the user to access.