Laravel 5 | For Loop w/Multiple Relations - php

I've setup a few model relationships and I'm trying to feed that data to a page that's displays a table with different columns to show the information. I've seeded 3 Tables (customers, jobs, steps) with Faker dummy data just for testing. How can I pull in the "Steps" table column data? My controller defines both (2) variables for the Job & Step model but I'm unsure how to pull in Step to the display. Do I have to do an array for the the variables called in the controller?
Here's my controller setup
public function index()
{
$jobs = Job::all();
$steps = Step::all();
return view('jobs.home', compact ('jobs', 'steps'));
}
Here's my blade setup
#foreach ($jobs as $job)
<tr>
<td>{{ $job->number }}</td>
<td>{{ $job->customer->name }}</td>
<td>NEED TO ADD STEPS COLUMN Here</td>
</tr>
#endforeach

There are several ways that this could be done, depending on what kind of data your "Step" model is providing for you.
If you just need 1 item from the "Step" model, you could first find just the specific data you need from the "Step" model instead of pulling in everything with Step::all(); for instance, you could use one of the more specific methods under "Collection Methods" that just return one item: https://github.com/susanBuck/notes/blob/master/05_Laravel/11_Databases_Eloquent_Collections.md
If you need more than 1 item, then you can use a more specific Collection method as well; however, you are going to have to loop through it to extract out each individual item. You will have to use "if" statements to determine which item is the one you want and then pull in its column data the same way you did with the "jobs" item like $job->number.
For your second question, you don't have to use an array to return the items from the controller. Just get the value you want there and return that if you like. For instance, to get the first "step" (if you just wanted the first one) then in the Controller do this to return a variable instead of an array:
public function index()
{
$firstStep = Step::first();
return view('jobs.home')->with('firstStep', $firstStep);
}
and then in your Blade file, you can access the data column rows just like you did with your $job variable:
{{ $firstStep->someColumn}}

Related

Laravel retrieves too many model on a simple paginate query with loaded relations

I have a simple database with couple of tables. orders - the main table, about 16_000 records. Each order has payments. The order_payments table is about 17_000 records. Also each could have zero or more travellers in a order_tourists table - 33_000 rows.
The Order model looks like this
namespace App;
class Order extends \Illuminate\Database\Eloquent\Model
{
public function payments()
{
return $this->hasMany('App\OrderPayment');
}
public function tourists()
{
return $this->hasMany('App\OrderTourist');
}
}
I have a simple task to display a paginated list of all orders. Each item in this list should display some order's information (from the orders table), the sum of all payments and names of all travellers.
The first that came to my mind was something like this.
Get all orders with payments and travellers and pass this data to a view
Route::get('search_offers', static function () {
$orders = \App\Order::query()->with(['payments', 'tourists']);
return view('orders', [
'orders' => $orders->paginate(),
]);
});
And my orders.blade.php is pretty straightforward
<ol>
#foreach($orders as $order)
<li>
{{$order->request_id}}; {{ $order->payments->sum('amount') }}; {{ $order->tourists->implode('fullName', ', ') }}
</li>
#endforeach
</ol>
However, even the number of queries is very small I have a lot of manipulations with models. I add a custom hook to track all eloquent.* events and in first case there are more than 23_000 of such events.
Then I try to remove the loading of relations so for each order Laravel have to run a separate query to get all order's payments and travellers. I simply did like this in my controller
return view('orders', [
'orders' => \App\Order::query()->paginate(),
]);
So, I get more queries but page speed improves and a number of affected models significantly decreased
To track affected models I add a listner in a AppServiceProvider::boot method and output a AppServiceProvider::$hydratedModels value in a debugbar panel
Event::listen('eloquent.*', static function ($event) {
if (strpos($event, 'eloquent.retrieved') !== false) {
AppServiceProvider::$hydratedModels++;
}
});
So, my question how it possible that so many models are retrieved even if I display only 15 items. Looks like Laravel somehow get all of them, processed but retruns only those for a required page.
Right now it's not a big deal to run extra queries to fetch some data but I'm wondering maybe I'm doing something wrong and it's possible to load relations but do not cause Laravel to retrieve all models.
I found the issue. Thanks, #dparoli for a suggestion.
My models have a primary key as UUID. But I did not change a Model::$keyType so internally Laravel cast it into an integer and queries look like this.
Instead of a UUID string, I got integers as an order_id for related records.
So, I add protected $keyType = 'string'; and everything starts work as expected.
PS. Although, I'm wondering how it works before.

Merging two models into one database call - laravel

I have 2 models, that I am calling via all:
$blocks = Block::all();
$cats = BlockCategory::all();
A block has a category associated with it and I want to associate the data so that I can display all categories that have certain blocks. I hope I am explaining this correctly.
so I can call:
#foreach($cats as $cats)
{{$cats->title}}
#foreach($cats->blocks as $blocks)
{{$block->title}}
#endforeach
#endforeach
any ideas how to merge the data? I am basically building a menu if that helps.
You need to define relationship between blocks and block category. Make sure that your db table has a foreign key relationship.
Add following code to your block category model
public function blocks()
{
return $this->hasMany('App\Block');
}
Make sure your block model is inside app/ directory.
Now, just retrieve categories in your controller,
$categories = BlockCategory::all();
Then you can display the data like this:
#foreach($categories as $category)
{{$category->title}}
#foreach($category->blocks as $block)
{{$block->title}}
#endforeach
#endforeach
You have a few options on how to approach this.
Define your relation like this:
Model
public function blocks()
{
return $this->hasMany('App\Block');
}
Controller
Using with()
$categories = Blockcategory::with('blocks')->get();
Once you have defined the relationship you can call the blocks() method from your model to retrieve the blocks
Examples
$categories = Blockcategory::find(1)->blocks()->get();
// another way
$blocks = Blockcategory::blocks()->get();
Sagar's answer is fine, but it is always better to get your categories and their relationships in the same query. By just calling Blockcategory::all() you can get the relation in your blade file, but laravel has to do an additional query for each block in your for each loop. This can give performance issues when having 1000s of records.
Laravel relationships

Fat Free Framework - Loading array of data from a query

I have no issues loading a unique record from my DB utilizing F3's ORM. However, when I want to retrieve multiple records for display or processing, the load method will still only fetch the first it finds.
I've found this on the docs:
By default, a data mapper's load() method retrieves only the first
record that matches the specified criteria. If you have more than one
that meets the same condition as the first record loaded, you can use
the skip() method for navigation
However the docs then continue only to show how to manipulate multiple records with built in find method, which returns an object filled with references to database entries.
I prefer not to loop through multiple records every time, I think there must be something I'm missing, how does one do this with fat free? In the docs it even references code where you can just do the following after a simply DB call, but I've yet to see how to do that with provided methods:
<repeat group="{{ #result }}" value="{{ #item }}">
<span>{{ #item.brandName }}</span>
</repeat>
What is the proper way to obtain a batch of records? For an example, please see below:
$productList = $products->load(array('created_on >= ?', $date->timestamp)); //Load most recently created products
or
$productList = $products->find(array('created_on >= ?',$date->timestamp),array('order'=>'created_on'));
In the example above, the date is utilizing Carbon, assume that is a proper timestamp.
I don't think there is a proper way to retrieve a bunch of records. The SQL mapper acts as a cursor. The best way to see how it works is with the load method: the mapper will retrieve all matching records and set its focus on the first one. You can do what you want with it and then call next to move on to the next one:
$products->load(array('created_on >= ?', $date->timestamp));
while (!$products->dry() ) {
// Do something with the record
$products->next();
}
The find method will return an array of records (actually they are also cursors, but each has only one record so you can treat them just as objects which are mapped to the records). Here, you will need to assign the result to a new variable like you did in your example:
$productList = $products->find(array('created_on >= ?',$date->timestamp),array('order'=>'created_on'));
// You can loop over the array to get some info or update the records:
foreach ($productList as $product) {
// Do something with the record
}
Or you can directly use the $productList variable in the template:
<repeat group="{{ #productList }}" value="{{ #item }}">
<span>{{ #item.brandName }}</span>
</repeat>

Paginate related models

I would like to paginate 2 models: Trip which hasMany PartialTrip.
The Trip model:
public function PartialTrips()
{
return $this->hasMany('PartialTrip', 'main_trip_id');
}
The tricky part is that, in the object that contains the queried information, I want to have each PartialTrip under the Trip that owns it. So, f.e.: query Trip -> query PartialTrip with whereMainTripId = the id of the current Trip -> paginate(5).
This way, when I list them, the partial trips will not be at the end of the array object, but each of them will be directly after its parent trip.
Update
I am doing this (just as a test to see whether the ::with('PartialTrips') works:
$trips = Trip::with('PartialTrips')
->select('id', 'driver_user_id', 'route_from', 'route_to', 'start_date')
->get();
var_dump(count($trips));
and it returns me the just the number of the Trips that I have (I counted how many trips and partial trips I have in the DB).
->toSql() on that query yields me the following:
"select 'id', 'driver_user_id', 'route_from', 'route_to', 'start_date' from 'trips'"
so I guess the ::with('PartialTrips') does not really work in this case...
This is the answer where trip_id come from
Take note that Eloquent assumes the foreign key of the relationship
based on the model name.
more detail: http://laravel.com/docs/4.2/eloquent#one-to-one
When you are using hasMany relationship without specific foreign key, the model is assume to use trip_id as a foreign key (table name + id). Anyway you can overwrite it. And you model might look like
//Trip model
public function PartialTrips()
{
return $this->hasMany('PartialTrip', 'main_trip_id'); // second parameter is your foreign key
}
more details: http://laravel.com/docs/4.2/eloquent#one-to-many
And you can get all the trips with partial trips by passing $trips variable to your view and render them like this
#foreach ($trips as $trip)
<div> Your trip id: {{ $trip->id }}</div>
<div> -- Partial Trips </div>
#foreach ($trip->partialTrips as $partialTrip)
<div> Partial trip id: {{ $partialTrip->id }}</div>
#endforeach
#endforeach
Note: when you are using with() (eager loading), $partialTrips will be nested inside $trips. That why you get only number of $trips when using count($trips). But you can get partialTrips by using code above.
source: http://laravel.com/docs/4.2/eloquent#eager-loading

Populating a dropdown menu with database results in Laravel 4

I'm trying to populate a drop down menu with database results in Laravel 4. I'm extremely new to Laravel. This is actually my first site and I'm learning as I go. So, please tell me if I'm using the wrong terminology or not enough information.
I've got a database of company info and I need users to be able to choose a company from a dropdown. Or if the company isn't in the database to add it.
For the select menu, it needs to go like this:
[company name result]
And I'm using this code in my controller:
$companies = RecordCompany::get();
$company_selector = array();
foreach($companies as $company) {
$company_selector[$company->id] = $company->id;
$company_selector[$company->company_name] = $company->company_name;
}
return View::make('admin.record_new', array('company_selector' => $company_selector));
And this is what I've got in my view:
#if(count($client_selector)>0)
{{ Form::select('company_id', $company_selector, array_values($company_selector)[0]) }}
#endif
Disclaimer: I found this code online.
First, I don't understand how it will populate the value and option text without my telling it where to put the data.
Second, the error that's coming back is unexpected . When I take out the [0] in the form code, it tells me that $company_selector is undefined.
What am I doing wrong here?
In order to populate a dropdown menu with all the records from the RecordCompany model, you can do the following, in your view:
{{ Form::select('company_id', RecordCompany::lists('company_name', 'id')) }}
Note: In Laravel 5, the method lists has been deprecated. Use
pluck instead.
Explanation of the code:
The Form::select methods creates a HTML select tag.
company_id is the name of the select tag.
The second parameter is the options for the select tag. The lists method in any model (RecordCompany in this case) generates an associative array containing the parameters passed to that method (id and company_name in this case) of all the records in the model's database table.
If you want, you can also call the lists method from the controller and then pass the value to the view, like following:
In Controller
$company_lists = RecordCompany::lists('company_name', 'id');
return View::make('admin.record_new', array('company_lists' => $company_lists));
In View
{{ Form::select('company_id', $company_lists) }}
You can view the Laravel 4 documentation for generating a drop down list here: http://laravel.com/docs/html#drop-down-lists
I'm severelly against using DB calls in views. And here is why:
It ain't made for that!.
Period.
If I where you (note the if clause) I'd like better to fulfill a regular array, being the company->id the array key and any other information you may wanna for that especific key as a value. On my blade code, I'd made that way:
{{ Form::select('company_id', $companies) }}
Where "companies" would be a array passed as argument to the view by the controller.
Views aren't made to make DB consults. They are made to display data. JUST IT!
That being said:
The first argument on the Form::select is the selector's name. The one you get on the Input::get.
The second argument is the list for fulfill the select attribute (we already talked about it up there!)
And the third, non less important, is where you say which one comes selected on loading page (used for editions). You have to reference the identifier (the company id, in that case). It's optional, for obvious reasons.
If I didn't made myself clear, please ask down here! =D
For Laravel 5, you can code like this :-
Controller Code
$company_lists = RecordCompany::pluck('company_name', 'id');
return View::make('admin.record_new', $company_lists);
View Code
{{ Form::select('company_id', $company_lists) }}

Categories