How to share a variable between master layout and a view - php

I have the following static method in my Guest model to retrieve all guests at location 1:
public static function getGuests()
{
return self::where('location_id', '=', 1)->get();
}
I also got the following static method to retrieve and display the number of guests at location 1.
public static function getCount()
{
return self::getGuests()->count();
}
I later call the getGuests() in my GuestController and pass all the guests to the view guests/index.blade.php to display data in a table.
I also call getCount() from my BaseController (see below) to make the count variable available both in the menubar in the layout/layout.blade.php file and the header of guests/index.blade.php.
View::share('numGuests', Guest::getCount());
By doing this i naturally end up with an expensive duplicated query to the DB since GetCount() calls getGuests(). Is there a better way to display the number of counts, both in the master layout file and the embedded view, without having to do the duplicate query call?

View::share shouldn't be calling the `Guest::getCount()' twice. It should just call it once and then pass it in. If not though can't you just cache the value in a variable and then pass it in? Then its definitely not getting queried twice.
$guestCount = Guest::getCount();
View::share('numGuests', $guestCount);
I might be misunderstanding here - are you also calling Guest::getCount() later on from your GuestController? Sorry - can't comment on main questions yet to ask.

I found a quick and low-cost solution by simply just calling the count() directly on the query, instead on the function call.
public static function getAll($locationId)
{
return self::whereLocationId($locationId)->with('country')->get();
}
public static function countAll($locationId)
{
return self::whereLocationId($locationId)->count();
}
This left me me with two separate queries, where the count is onsiderably lower cost:
select * from `guests` where `location_id` = '2'
and
select count(*) as aggregate from `guests` where `location_id` = '2'
It can probably be made even quicker by selecting only the neccesary columns instead of *. In cases where the query is more complicated and require more WHERE clauses, it can easily be kept DRY by using query scopes. Thanks for massaging my brain.

Related

Why query result suddenly changed after called from other medthod in Laravel

I have problem here with query result from Eloquent, I tried to query from DB and put in variable $contractList in my mount() method and the result as expected. But when I tried to retrieve specific data from $contractList with $contractList->find($id), the result not same as in mount() method.
Here is query from mount():
public function mount(){
$contractList = Kontrak::select(['id', 'mou_id'])->with(['order:id,kontrak_id', 'order.worklist:id', 'order.worklist.khs:id,mou_id,worklist_id,khs', 'amdNilai:id,kontrak_id,tgl_surat'])->withCount('amdNilai')->get()
}
Here the result:
But when I tried to find specific data from $contractList, properties that shown not same as in mount:
public function itemSelected($id)
{
//amd_nilai_count not showing
$kontrak = $this->contractList->find($id);
if ($kontrak->amd_nilai_count == 1) {
$this->nilai_amd = $this->calculateNilai($id);
}
}
Here is the result called from itemSelected():
I have tried use get() but the result still problem, how to get same properties same as in mount().By the way im use laravel & livewire.
As i read your comments you seem to mix up ActiveRecords ORM with an Data Mapper ORM. Laravel uses active records.
Laravel will always fetch models on every data operation.
Kontrak::select('name')->find(1); // select name from kontraks where id = 1;
Kontrak::find(1); // select * from kontraks where id = 1;
This will always execute two SQL calls no matter what and the objects on the heap will not be the same. If you worked with Doctrine or similar, this would be different.
To combat this you would often put your logic in services or similar.
class KontrakService
{
public function find(int $id) {
return Kontrak::select(['id', 'mou_id'])->find($id);
}
}
Whenever you want the same logic, use that service.
resolve(KontrakService::class)->find(1);
However, many relationship operations is hard to do with this and then it is fine to just fetch the model with all the attributes.

Getting the rows created before the selected one

I was wondering about the best way to get the count of all the rows created before the selected one. Right now I have defined an accessor that looks like this:
// In the model
public function getPositionAttribute() {
return self::where([
// Some other condition
['created_at', '<', $this->created_at->toDateTimeString()]
])->count();
}
// In the code
$model->position
It works correctly, but I'm worried about 2 things:
Is it a bad practice to call self on the model? Looks somehow off to me.
When called in a foreach this obviously generates a query for each element which is far from optimal. Is there any way to refactor this so that it can be eager loaded in a single query?
Bonus: I have totally discarded the idea of keeping a column with some kind of index because that initially sounded impossible to maintain, eg. when a record is deleted all the others should somehow shift position. Should I reconsider it? Is there a better way?
Pretty sure that using self here is the "best practice" because that is how that keyword was designed to be used.
In regards to refactoring, i personally can't think of optimizing the query as is but instead you could create a function that preloads all the position then use it normally. Assuming your model has a unique key 'id' and you are passing in a collection of model then, you can try something like this:
public static function populateOrderPositions($modelCollection){
// Optimize this query to include your "other condition"
$idCollection = Model::orderBy('created_at') // this will make it in the order of creation
->pluck('id'); // this will only retrieve the id field
// This array will contain an array with the model object ids as key and a numeric position (starts at 0)
$positionArr = $idCollection->flip()->all();
// Then just load all the position into the object and return it.
return $modelCollection->map(function($modelObj) use ($positionArr){
return $modelObj->position = $positionArr[$modelObj->id] + 1; // +1 because array key starts at 0
};
}
You would also need to adjust your attribute code to use the loaded attribute instead of ignoring the loaded attribute like so:
public function getPositionAttribute() {
return $this->attributes['position'] ?? self::where([
// Some other condition
['created_at', '<', $this->created_at->toDateTimeString()]
])->count();
}
With these changes, you can then prepopulate the position then use it afterward without the need to query the database.
These code are untested as i don't know how your model and query will be structured and is more of an example. Also you would need to compare the performance vs your original code.

Property [bId] does not exist on this collection instance

I have a pages controller.I wanna get some information stored in my db when /myshopping requested.
This is my controller code :
public function myshopping()
{
$Buylist=DB::table('orders')->where('id','=',Auth::user()->id)->orderBy('oId','desc')->get();
$Bookinfo=DB::table('books')->where('bId','=',$Buylist->bId)->first();
return view('shop.myshopping',compact('Buylist','Bookinfo'));
}
Thank you very much.
Using->get() on a QueryBuilder returns a Collection (fancy wrapper for PHP arrays), so $Buylist is a group of records from your orders table, as opposed to a single record.
If you change your logic to use ->first():
$Buylist=DB::table('orders')->where('id','=',Auth::user()->id)->orderBy('oId','desc')->first();
Then you can access $BuyList->bId without issue (unless $BuyList returns null, but that's a different issue).

Eloquent laravel --> Eager Loading

i have the next structure:
model call content
public function credits(){
return $this->belongsToMany('App\models\Credit');
}
model call Credit
public function content()
{
return $this->belongsToMany('App\models\Content');
}
What i am triying to do is extract all the credits of one content, but selecting only the columns that i want, with the below code:
$credits2=$this->content::with(array('credits'=>function($query){
$query->select('id','department','job');
}))->where('id',$id_content)->get();
The problem that i have is that when i execute all the code the result is:
[]
but if i do a normal query in mysql i can figure out that for the movie exists credits.
I have extracted this code, from other forums, and stackoverflow post.
There is another easy way to specify columns without using closure:
$credits2 = $this->content::with('credits:credits.id,department,job')
->find($id_content->id);
The problem was that $id_content was a result from a previous query, so if i put $id_content didn't work, for that i have to access to the column.
And the solution is:
$credits2=$this->content::with(array('credits'=>function($query){
$query->select('id','department','job');
}))->where('id',$id_content)->get();

what is the common practice on doing oo in db?

Here is situation.... ...
I have a DBManager, which is implement a DBInterface, in the DBInterface, I got 4 method:
-create(DBCmd);
-read(DBCmd);
-update(DBCmd);
-delete(DBCmd);
The DBCmd object is responsible for generate the SQL statement, and the DBCmd requires an object in sql statement:
class DBCmd{
public _constructor($aObj){
}
public executeCreate(){
}
public executeRead(){
}
public executeUpdate(){
}
public executeDelete(){
}
}
The flow will be like this:
aObject ---> put it into DBCmd ----> put the DBCmd in DBManager ---> execute
But the problems happen when I get some objects related to other tables, for example...a customer have a purchase record, and which purchase record have many items....
So, what do I do in my read method? should I read all the records related to the customer?? Do I need to loop all the items inside the purchase record too?
If yes, when I doing read customer, I need to query 3 tables, but some that may not need to see.....it waste the resource...
And I come up with another solution, I make a new set of DBCmd, that allow me to get the related DB items, for example:
class getReleatedPurchaseRecordDBCmd{
public _constructor($aCustomerObject){
}
//.... ....
}
But in this "solution", I got some problems, is I loss the relationship in the object customer...yes, I can read back all the records, get the customer object basically don't know any things about the purchase record....
Some may ask me to do something like this:
class customer{
//skip other methods...
public getPurchaseRecords(){
//query the db
}
}
It works, but I don't want the object structure have some strong relationship between the db....That's why I come up with the DBCmd stuff...
So, everything seems to be very coupling, how can solve it? Thank you.
for stuff like this i tend to get the count of sub objects with the initial query usually involving sql COUNT and JOIN, then have a seperate getSubObjects command that can be called if needed later. So for example:
$datamodel->getCustomer($id);//or some such method
returns
class Customer{
$id = 4;
$recordCount = 5;
$records = null;
}
I can then use the count for any display stuff as needed, and if i need the records populated call:
$customer->records = $datamodel->getCustomerRecords($customer->id);

Categories