Laravel 5.3 - caching information schema queries - laravel

I use multiple domains for the same site structure, so almost all tables have the domain_id column. So, to NOT define the domain_id condition every time I am using the following approach (not sure if this is the best one)
I created BaseModel that other models are extending from and have this in boot method of that BaseModel
parent::boot();
static::addGlobalScope(new DomainScope());
and here is the apply method's content according to docs
if (Schema::hasColumn($model->getTable(), 'domain_id')) {
$builder->where('domain_id', '=', DOMAIN_ID);
}
This works great, however if I have e.g. 5 find queries on the same page, to the same table (just as an example) in debug panel I see 5 queries like this
select column_name from information_schema.columns where table_schema = 'my_db_name' and table_name = 'my_table_name'
Now, I perfectly understand that to check whether the column exists in the table it gets the information from information schema, but why it is making the same query for the same table over and over again. I would presume It should make one request and then cache it, and for subsequent requests just read from cache.
q1) Does laravel internally cache this query ? I am thinking maybe because debug is enabled, that is why each time its making a query ? but cant find any verification of this
q2) and if it does not cache, can I cache it manually. I checked the laravel docs for adding a cache, but the problem here that query is not done by me, so I cant figure out to simply use Cache::remember
Thanks

Well, I would suggest not to fire a query for checking if domain_id exists in the table every time. You can do this by simply adding a variable to your model that defines whether the model is a multi-domain model (has a domain_id column) or single domain model (no domain_id column) like so:
// In BaseModel.php
public $multiDomain = true; // override to false in your child model class if it's a single domain model
// In DomainScope apply method
if ($model->multiDomain)) {
$builder->where('domain_id', '=', DOMAIN_ID);
}
If you don't want to use the above, you can cache like so:
if(Cache::remember($model->getTable() . "_domain", $minutes, function() use($model) {
return Schema::hasColumn($model->getTable(), 'domain_id');
}) {
$builder->where('domain_id', '=', DOMAIN_ID);
}

Related

How to do a surgery on an Eloquent Query on-the-fly

is there any way to interrupt and change the components and scopes of an eloquent query?
in order to add multitenancy to an existing project, I've added a global scope to my models, filtering results by tenant_id. and it works fine.
the problem is I have found more than 500 hardcoded 'where conditions ' and 'create statements' all over the place. such as these:
$notification_type= NotificationTypes::where('id', '2')->get();
or
$tickets = Tickets::create( $title, $body, $sender, '1'); // 1 as statusID
etc.
It's a problem because I'm using the single DB approach for multitenancy and these IDs must be relative to the tenant. for Example in the first query above, I don't want the 'NotificationTypes' with Id of '2', But I want the 'second' NotificationType with that tenant_id (id of this column could be 4 or 7 or else).
I can figure out a way to properly calculate the exact amount of these Relative IDs. but is there any way to find the prior scope and conditions of an eloquent object and change them?
I'm looking for a way to add multitenancy to the project without changing much of the existing code. like a module or plugin.
You can use scope in Laravel
public function scopeActive($query)
{
$query->where('active', 1);
}
Then in your controller just call
$notification_type = NotificationTypes::active()->get();
More ref on doc: https://laravel.com/docs/9.x/eloquent#query-scopes
Dynamic Scope
public function scopeActive($query, $value)
{
return $query->where('active', $value);
}
$notification_type = NotificationTypes::active(value_you_want)->get();
Change value_you_want with what you want
Or you can use
$notification_type = NotificationTypes::where('id', 1)->orWhere('tenant_id', 1)->get();

Questions related to soft delete in laravel

I have some questions relating to soft delete in laravel. I have search up on what it does and what it means and the most understandable part about soft delete is from this sentence
"When models are soft deleted, they are not actually removed from your
database. Instead, a deleted_at attribute is set on the model and
inserted into the database. If a model has a non-null deleted_at
value, the model has been soft deleted. To enable soft deletes for a
model, use the Illuminate\Database\Eloquent\SoftDeletes trait on the
model and add the deleted_at column to your $dates property:"
So here are my questions based from that sentence:
Q1:
So when I use soft delete in my code, and when I try to delete some
data, does the data in the view page(blade.php) disappear while the
database still contain those data?
Q2:
I saw some people using something called static::deleting, I don't
really quite get how this work? Could you explain what it does? Thank
you
Q3:
How do you delete data using soft delete? I saw people just putting
some stuff into their model instead of using button, so does that mean
you can only delete it manually inside the model instead of just
clicking the delete button in the view page?
Question 1
By default: Yes.
It depends on your query. When using soft deletes, Laravel will query all models that are not soft-deleted by default. When you also want to get the soft-deleted models, you need to call the withTrashed() method on your query. Read more here:
https://laravel.com/docs/5.5/eloquent#querying-soft-deleted-models
To understand what withTrashed() does, you need to understand how soft-deleting works. Soft-deleting models works by adding a new column to your database tables called deleted_at. It's value defaults to null. When you soft-delete a model, Laravel will put the current timestamp into that column. Therefore, this field doesn't contain a null value anymore.
When querying models when using soft-deletes, Laravel appends a deleted_at is null condition to the query. Calling the withTrashed() method, removes that condition from the query.
Have a look on the source of the default query modifier and the withTrashed method.
Question 2
That are events. You can call that to tell Laravel, that it should execute that specific closure when this event happens. In your example, it is listening for the "deleting" event. See more on that here:
https://laravel.com/docs/5.5/eloquent#events
Question 3
You can entirely delete soft-deletable models with the forceDelete() method. See "Permanently Deleting Models" here:
https://laravel.com/docs/5.5/eloquent#querying-soft-deleted-models
Q1: So when I use soft delete in my code, and when I try to delete
some data, does the data in the view page(blade.php) disappear while
the database still contain those data?
Yes. The soft delete fill the deleted_at column in the database. Since that, Eloquent will not retrieve these data (except if you ask for). If you use custom SQL request, you'll need to add a WHERE deleted_at IS NULL
Q2: I saw some people using something called static::deleting, I don't
really quite get how this work? Could you explain what it does? Thank
you
I'm not using that day to day, but it's an event you can call (see here ) to automatically delete content related (for example, if you remove an user, you can also remove all his post. It's kind of cascading delete)
Q3: How do you delete data using soft delete? I saw people just
putting some stuff into their model instead of using button, so does
that mean you can only delete it manually inside the model instead of
just clicking the delete button in the view page?
To use the soft delete, you just $object->detroy($id) or $myEloquentRequest->where(...)->delete()
If you want to force a real delete (so the entries will be definitly removed from the database), you can use $flight->forceDelete();
See here for more.
You can do the delete wherever you want. The click on a button bring the user to the delete() method in your controller. You can delete there or call a method inside the model to trigger the delete (and maybe some more complex deleting like event ... )
Soft delete means not delete records in database.So we handle one flag
for manage records is deleted or not.
Let's i explain more with examples :
In our records many user so we add one fields delete_at into database and defaults it's value null so it's records is not deleted.
Now when we fetch all user data we write query like
Select * from user where delete_at = null
So this query return all user data which is not deleted.
Now we delete this user so when we click on delete button we create custom query and update this user delete_at fields with current datetime
Update delete_at=date() where user_id = 1
so now this records is soft delete.
Now i answer your question:
Q.1)No data not displaying after soft delete because when we fetch data it's check delete_at fields null or not.
clarifying your points.
1. So when we use soft delete in code, deleted_at will be updated to present timestamp from null, so while querying data from that particular table from anywhere in your project the eloquent model will automatically return data whose deleted_at is set NULL i.e., it is not soft deleted.
people using something called static::deleting.
you might have seen somewhat like this example if I am understanding your question in right way:
class X-Model extends Eloquent
{
public function xy()
{
return $this->has_many('XY_Model');
}
// this is a recommended way to declare event handlers
protected static function boot() {
parent::boot();
static::deleting(function($x) {
// before delete() method call this
$user->xy()->delete();
// do the rest of the cleanup...
});
}
}
This is a use-case for Eloquent events to delete a record which will use the "deleting" event to do the cleanup.
delete data using soft delete
now after calling this, you can soft delete the data from the table and also u can soft delete the data from other dependent tables if relationships are maintained properly in models. this is actually known as cascading effect. example for you to understand.
Model structure:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Utilities\Uuids;
use Illuminate\Database\Eloquent\SoftDeletes;
use Iatstuti\Database\Support\CascadeSoftDeletes;
class XFolder extends Model
{
use SoftDeletes, CascadeSoftDeletes;
use Uuids;
protected $connection = 'XDB';
protected $table = 'x_folder';
protected $dates = ['deleted_at'];
public $incrementing = false;
protected $cascadeDeletes = ['XReference']; //related table with foreign keys
protected $visible = [ 'id', 'x_id', 'xz_id', 'at_id', 'title', 'description', 'description', 'status', 'created_on' , 'is_active'];
protected $fillable = [
'x_id',
'xz_id',
'at_id',
'title',
'description',
'status',
'created_on',
'is_active'
];
public function XReference()
{
return $this->hasMany('App\Models\XReference', 'x_id');
}
}
now in the controller you can call like this:
public function deleteData($id)
{
$results = $this->getModel()->where('id', $id)->deleteData();
return $results;
}
this will delete data (soft delete in cascading format).
Q1: So when I use soft delete in my code, and when I try to delete some data, does the data in the view page(blade.php) disappear while the database still contain those data?
Yes, if your model use softDelete, than the search query will be like this: Select * from table_name where delete_at = null
Q2: I saw some people using something called static::deleting, I don't really quite get how this work? Could you explain what it does?
You can do it with: Modelname::destroy($id) static action
It destroys the model object where the $id is set. On destroying I mean: it updates the deleted_at column, so the aplication will see it as a soft deleted object
Q3: How do you delete data using soft delete? I saw people just putting some stuff into their model instead of using button, so does that mean you can only delete it manually inside the model instead of just clicking the delete button in the view page?
Example of one of my project:
I have a delete button at the partner screen what routes to partner/{{ id }}/delete
at the routes: Route::get('/partner/{id}/delete', 'PartnerController#deletePartner');
What goes to this action:
public function deletePartner($partnerId = 0){
if ($partnerId > 0){
Partner::destroy($partnerId);
}
return redirect("/partner");
}
So: If I click to delete button it check, that the ID is set and then "destroys it" (soft delete). After deletion it redirects back to the partner
EDIT:
For the example given in the question 3, when you delete the data, does the database data disappear or only the view?
It will dissapear only from the view. In the database it will be stored as:
id name ... created_at updated_at deleted_at
1 foo ... 2017-10-01 00:00 2017-10-01 00:00 NULL
2 bar ... 2017-10-01 00:00 2017-10-01 00:00 2017-10-25 16:00
The first one is a non-deleted the second one is a soft-deleted object and the view only will show the first one

Laravel Backpack : Storing Belongs To Many relationships using custom view

I have a flight class and this flight has a custom view field like so:
This represents a belongs to many relationship which stores website_id / flight_id and pricing as pivot data in a pivot table.
The custom view uses JS to send this data back to the controller in this format:
{"1":{"price_adult":"434","price_child":"545"},"2":{"price_adult":"323","price_child":"324"},"3":{"price_adult":"434","price_child":"43"}}
Trying to send this data with the request doesn't create the relations fields, and because I do not have a flight ID at the point of creating this within the controller I can not loop this JSON to make the relations manually.
Can anyone point out what the best course of action is or if there is support for this? I took a look at the docs but they are woefully short and patchy in terms of being much help.
EDIT:
I should have said I can probably make this work using a custom name attribute on the model for the relation, then add a set mutator to loop this data and update the prices relation but I don't want to go down this route if there is support for this I am missing out of the box in backpack.
EDIT2:
Someone asked about the relation:
$this->belongsToMany(Website::class, 'website_pricing')->withPivot('price_adult', 'price_child');
This is working fine its not a problem with the relation working its how can I get backpack to store the data as a relation when the flight has no ID yet, or how can I pass the data I posted above in such a way that the backpack crud controller can handle it?
You may need to create a flight first, if no flight id is being provided. Can you explain the database relational structure more?
Basically thought I should post what I did because no one could provide an answer to this.
So basically you have to copy the store / update functions from the parent, changing a few lines.
$this->crud->hasAccessOrFail('create');
// fallback to global request instance
if (is_null($request)) {
$request = \Request::instance();
}
// replace empty values with NULL, so that it will work with MySQL strict mode on
foreach ($request->input() as $key => $value) {
if (empty($value) && $value !== '0') {
$request->request->set($key, null);
}
}
// insert item in the db
$item = $this->crud->create($request->except(['save_action', '_token', '_method']));
$this->data['entry'] = $this->crud->entry = $item;
// show a success message
\Alert::success(trans('backpack::crud.insert_success'))->flash();
// save the redirect choice for next time
parent::setSaveAction();
return parent::performSaveAction($item->getKey());
Basically any line which references a function in the parent class using $this->method needs to be changed to parent::
This line is what I used to submit the relations JSON string passed to the controller as relations $item->prices()->sync(json_decode($request->input('prices'), true));
This is done after the line containing $item = $this->crud->create as the item id that just got stored will be available at that point.

Set global where clause when I retrive data using laravel eloquent

I have a table content. So when I retrive anything from that table using laravel eloquent, I need to set a global where clause Content::where('is_playlist', 1)->get();, so that every time I retrieve data from that table, I should automatically filtered with that particular where clause.
You can create a scope function in the model to filter that out e.g:
public function scopePlaylist($query)
{
return $query->where('is_playlist', 1);
}
This means you can easily call, Content::playlist()->get() and you'll have only playlist content.
I hope this is useful?

Querying multiple table to get specific data with Eloquent ORM

I am quite new to Laravel 4 and its great Eloquent ORM. I have four tables such as :
Sector (iSectorCode);
MailingSector (iSectorCode, iMailingCode);
Mailing (iMailingCode);
MailingLanguages(iMailingCode, sTranslation);
I have the sector id, and I want to get all Mailings associated. But I also need to reach the MailingLanguages table containing the content translations for a specific Mailing.
So for now I can get all Mailings for a specific sector doing :
Sector::find($iSectorCode)->mailings()->get()->toArray();
But doing Sector::find($iFormSectorCode)->mailings()->mailingsLanguages()->get()->toArray(); don't work even if the relation between Mailing and MailingLanguages is defined :
public function mailingsLanguages(){
return $this->hasMany('MailingLanguage','iMailingCode');
}
So I don't know how to get all translations for a specific Mailing, for a specific Sector.
Providing that you've setup relationships between all of the tables, you can request that they be grabbed with the initial request.
$sector = Sector::with('mailings', 'mailings.languages')->find($iSectorCode);
This will create a nice join that will include related records for Mailing, then their related records for MailingLanguage, as well as the requested Sector.
The above example does assume that Sector has a relationship called mailings and that Mailing has a relationship called languages.
You could also load them in after the fact.
$sector = Sector::find($iSectorCode);
$sector->load(['mailings', 'mailings.languages']);
I would recommend making use of the findOrFail method that laravel provides.
try {
$sector = Sector::with(['mailings', 'mailings.langauges'])
->findOrFail($iSectorCode);
} catch(ModelNotFoundException $e) {
// do something here
}
This saves having to check whether $sector returned anything, as an exception will be thrown.
Hope that helps.

Resources