I start in laravel and I would like to have your opinion on one point.
I have a controller. In this controller, I return a view and several functions that are all SQL queries.
My view must have all these queries because I display different types of foreach. Is this the right way to do or is there something shorter / more appropriate?
My controller :
protected function indexWeb()
{
return view('pages.web.program', [
'getAllCoursesBloc1' => $this->getAllCoursesBloc1(),
'getAllCoursesBloc2' => $this->getAllCoursesBloc2(),
'getAllCoursesBloc3' => $this->getAllCoursesBloc3(),
'getWebCoursesBloc1' => $this->getWebCoursesBloc1(),
'getWebCoursesBloc2' => $this->getWebCoursesBloc2(),
'getWebCoursesBloc3' => $this->getWebCoursesBloc3(),
$this->setMetasIndex()
]);
}
protected function getAllCoursesBloc1()
{
$courses = Course::where('bloc', 1)
->OrderBy('title', 'ASC')
->get();
return $courses;
}
protected function getAllCoursesBloc2()
{
$courses = Course::where('bloc', 2)
->OrderBy('title', 'ASC')
->get();
return $courses;
}
... and so on
An exemple of foreach in my view
#foreach($getWebCoursesBloc1 as $key => $course)
<tr class="link-row" data-href="{{ $course->slug }}">
<td class="program-table__orientation">{{ $course->orientation }}</td>
<td class="program-table__course">
<a href="{{ url('cours/'.$course->slug) }}" class="program-table__course__link">
<span class="program-table__course__name">{{ $course->title }}</span>
</a>
<span class="program-table__course__desc">{{ $course->shortdescription }}</span>
</td>
<td class="program-table__hour"><span>{{ $course->duration }}</span></td>
<td class="program-table__ects"><span>{{ $course->ects }}</span></td>
<td class="program-table__quad"><span>{{ $course->quadrimester }}</span></td>
</tr>
#endforeach
Thank you very much
Use whereIn() in Laravel's model.
public static function getAllCoursesByIds(array $ids)
{
return self::whereIn('bloc', $ids)
->orderBy('title', 'asc)
->get();
}
In controller you can get access by:
public function indexWeb()
{
return view('pages.web.program', [
'all_courses' => Course::getAllCoursesByIds([1,2,3,4,5]),
]);
}
Otherwise you can filter them by specific ids. In your view call $all_courses.
To get the Courses with "block" equals 1 use:
$all_courses->filter(function ($course, $key) {
return $course->bloc === 1;
});
What you're realizing is that all these DB calls are currently synchronous and there is no dependency between them. In theory, if you could call them asynchronously, the script would run quicker because it's rendering as soon as the last item returns.
To do this, you need to do the rendering in the client (javascript) instead of the server. Promises or Async/Await would probably work best. Ultimately your client would make all the async calls to your laravel based web service and once they're all completed it would render. You may find, that you don't even need to wait for all of them and the rendering can be asynchronous as well.
You could possibly accomplish this using only PHP with something like ReactPHP.
Related
I am having issues understanding the "belongsTo" method in a class I am working with.
I have an "Asset" model which wasn't written by me, but I'd guess it works, and it has this function where I am trying to access the 'name' property of the "AssetMake" table (Which foreign and primary key args look about right):
public function assetMake()
{
return $this->belongsTo(AssetMake::class, 'assetmake_id', 'id');
}
In a blade template that looks something like this, with the $asset variable injected in (and succesfuly already being used on the same page):
#foreach($assets as $asset)
<tr>
<td width="5%" class="filter_id">{{ $asset['unit_id'] }}</td>
<td width="20%" class="filter_type">{{ $asset['TypeName'] }}</td>
<td width="25%">{{ $asset['description'] }}</td>
<td width="20%">{{ $asset->assetMake()->get() }}</td>
</tr>
#endforeach
"AssetMake" looks like this, do I need a corresponding "hasMany" function?:
class AssetMake extends Model
{
use ModelDateSerializeNonISO;
protected $table = 'assetmake';
protected $primaryKey = 'id';
protected $hidden = ['updated', 'created'];
}
I have tried acessing the injected $asset variable in a blade template as such:
<td width="20%">{{ $asset->assetMake->get }}</td>
<td width="20%">{{ $asset->assetMake->get() }}</td>
<td width="20%">{{ $asset->assetMake()->get }}</td>
<td width="20%">{{ $asset->assetMake->name }}</td>
<td width="20%">{{ $asset->assetMake()->name }}</td>
The 'name' property of the assetmake table is what I really need access to here.
Is this some kind of lazy/eager loading problem? I'm just not sure exactly what's happening here, and why I can't access the property. I've checked in various sources, and nothing I've tried works, but I'm sure it's fairly straight forward. Any tips?
The way to access a related model is to call it as you would normally call a property. So something like $asset->assetMake->name should work.
Behind the scenes, I believe Laravel uses PHP's magic methods to create properties on the model based on the method names so that they point to the related model (parent or child).
Similarly, if you have a hasMany relationship like so:
public function children()
{
return $this->hasMany(Child::class, 'child_id',);
}
You can access the children just by calling $parent->children.
And if you need to access the Child query builder from the parent, you have to call the children() method.
E.g
$parent->children()->create($childData)
Ok, I worked it out. It was an issue with the controller. I'm still working this out and the magic in Laravel can be confusing to me. I added the line "->join('assetmake', 'assetmake.id', 'asset.assetmake_id')" to the controller query. And added to the select statement as well 'assetmake.name as AssetMakeName'
$assets = FleetFuel::where('fleet_fuel.customer_id', $user->customer_id)
->where('fleet_fuel.isOrphan', 0)
->where('fleet_fuel.hours', '>=', 0) // -1.00 = first ever record
->where('fleet_fuel.burn', '>=', 0) // -1.00 = first ever record
->join('asset', function($join) {
$join->on('fleet_fuel.unit_id', '=', 'asset.Unit_ID');
$join->on('fleet_fuel.customer_id', '=', 'asset.Customer_ID');
})
->join('assettype', 'assettype.ID', 'asset.assettype_id')
->join('assetmake', 'assetmake.id', 'asset.assetmake_id')
->select('fleet_fuel.unit_id', DB::raw('max(fleet_fuel.delivery) as lastfuel'), 'asset.description', 'asset.Rego', 'assettype.Name as TypeName', 'assetmake.name as AssetMakeName')
->groupBy('fleet_fuel.unit_id')->get();
return view('fleetFuel.assets',
[
'companyName' => $companyName,
'assets' => $assets
]
);
And then accesed it in the blade view:
<td width="20%" class="filter_make">{{ (isset($asset['AssetMakeName'])) ? ($asset['AssetMakeName']) : ("No make available")}}</td>
I cannot wrap my mind around something.
I have a table which I render in my tables.blade.php file like this:
<table>
<thead>
<tr>
<th scope="col">Country</th>
<th scope="col">Member</th>
</tr>
</thead>
<tbody>
#foreach ($members as $member)
#if ($member->continent == 'Europe')
<tr>
<td>
{{ $member->country }}
</td>
<td class="align-middle player-code copy-code">
{{ $member->name }}
</td>
</tr>
#endif
#endforeach
</tbody>
</table>
As you can see you can click on a country which will show you members from one country in a new view (code not shown here).
The route for that single country site in the web.php looks like this:
Route::get('/{country}', 'PageController#show')->name('country');
Everything worked fine until I realized that I could put anything as 'country' and still would get shown the site for the country just with an empty table.
So '/abcde' would get you the view with just a naked table.
So I changed the route like this:
Route::get('/{country}', 'PageController#show')->name('country')->where('country', ('United Kingdom|France|Belgium|South Africa);
//the list is much longer
Ok. So now I have constrained the 'country'-parameter in a pretty static way. And I have the feeling that is not the way it should be done. Because in the end I would like to have URLS that look like this '/united-kingdom', '/france', but now they're looking like this '/United%20Kingdom'.
I saw the answers to this question Laravel clean links without spaces or uppercase , but for me they're not so useful since I'm not working with Eloquent models but the Query Builder (The db tables I get are ready made, I only have to display them).
So my questions are:
How to limit a route parameter more dynamically?
How to display data one way (written like it's also in the table like this 'United Kingdom'), but have a route like this ('united-kingdom')?
I am willing to provide more code or info if you need, I'm just pretty confused and have the feeling I'm overlooking something (big).
Thank you for your time and help!
If you would like a route parameter to always be constrained by a
given regular expression, you may use the pattern method. You should
define these patterns in the boot method of your RouteServiceProvider:
use Illuminate\Support\Facades\DB;
//...
/**
* Define your route model bindings, pattern filters, etc.
*
* #return void
*/
public function boot()
{
$list_of_countries = DB::table('members')->whereNotNull('country')->pluck("country")->unique()->map(function ($name) {
return str_slug($name, '-');
})->reject(function ($name) {
return empty($name);
})->toArray();
$regex = '(' . implode('|', $list_of_countries) . ')';
Route::pattern('{country-slug}', $regex);
parent::boot();
}
routes\web.php
Route::get('/{country-slug}', 'PageController#show')->name('country');
Docs
I am using a hasMany in my Model class to retrive the clients notes, how ever i want to order these notes by the latest date created in laravel blade template.
My code is below and im getting an error on this.
Please advice me..
#foreach($clients->notes->orderBy('created_at', 'desc') as $note)
<table class="table table-bordered">
<tr>
<td class="col-xs-2 col-md-2"><b>Created On:</b> {{ date('d/m/y', strtotime($note->created_at)) }} <b>#</b> {{ date('g:i A', strtotime($note->created_at)) }} </td>
<td class="col-xs-14 col-md-12">{{ $note->notes }}</td>
</tr>
</table>
#endforeach
Guessing, because you haven't told us what error you're getting, but:
$clients->notes is an already-fetched collection of results. $clients->notes() is a query builder that you can apply further logic like ordering or additional criteria to.
You likely want:
$clients->notes()->orderBy('created_at', 'desc')->get()
but you should do that in the controller and pass it to the view instead of having the query directly in the Blade template.
(You can alternatively use Laravel's collection functions on $clients->notes, including the sortBy() function).
Data must be ordered within controller or models. If you have used hasMany validation in model you can do as mentioned below
In model write association
public function notes()
{
return $this->hasMany('Note')->orderBy('created_at', 'desc');
}
In your controller function associate client with notes like this
$clients = Client::with('notes')->get();
Hope you get your answer
Sorry for my English.
I want to make a record that would be deduced me the sum of all my orders, that is, folded string of orders and drew grouped by orders.
I have created a model "Sale", which comprises method AmountOrder
public function AmountOrder()
{
$AmountOrder = DB::table('goods')
->join('sale_lines', 'sale_lines.good_id', '=', 'goods.id')
->where('sale_id', $this->id)
->select(DB::raw('SUM(price*quantity) as total_sales'))
->value('total_sales');
return $AmountOrder;
}
and to deduce the code like this
#foreach ($sales as $sale)
<tr>
<td class="table-text"><div>{{ $sale->id }}</div></td>
<td>
{{ $sale->client->name }}
</td>
<td>
{{$sale->date}}
</td>
<td>
{{$sale->AmountOrder($sale)}}
</td>
<td>
{{$sale->debt($sale)}}
</td>
<td>
{{$sale->date_of_issue}}
</td>
</tr>
#endforeach
But the problem is that the query is performed on each line. I'm new to Laravel, but thought maybe you can solve this problem somehow more beautiful?
Thank you very much in advance!
You are probably talking about the Eager Loading.
From the docs:
When accessing Eloquent relationships as properties, the relationship data is "lazy loaded". This means the relationship data is not actually loaded until you first access the property. However, Eloquent can "eager load" relationships at the time you query the parent model. Eager loading alleviates the N + 1 query problem.
However, you will be not able to use the Eager Loading now, with this code in the AmountOrder method.
A simple google search, also, led me to this example of Eager Loading with aggregate functions/relationships.
It will be probably a good start to think and implement your solution.
you have wrong in your select :
$AmountOrder = DB::table('goods')
->join('sale_lines', 'sale_lines.good_id', '=', 'goods.id')
->where('sale_id', $this->id)
->select(DB::raw('SUM(sale_lines.price*sale_lines.quantity) as total_sales'))
->value('total_sales');
My relationship
class Sale extends Model
{
//Получаем товар в этой продаже
public function good()
{
return $this->belongsTo('App\Good');
}
}
class Good extends Model
{
//В каких закупках был этот товар
public function purchases()
{
return $this->hasMany('App\Purchase');
}
//Продажи с этим товаром
public function sales()
{
return $this->hasMany('App\Sale');
}
}
Is it correct?
In my model i create method
public function AmountOrderRelation()
{
return $this->belongsTo('App\Good')
->selectRaw('sum(price) as aggregate, id')
->groupBy('id');
}
In controller
$new_sales = Sale::with('AmountOrderRelation')->get();
#foreach ($new_sales as $sale)
<tr>
<td class="table-text"><div>{{ $sale->id }}</div></td>
<td>
{{ $sale->AmountOrderRelation }}
</td>
</tr>
#endforeach
But my relations is null. What's my mistake?
I did it!
public function AmountOrder()
{
return $this->HasOne('App\SaleLines')
->join('goods', 'sale_lines.good_id', '=', 'goods.id')
->selectRaw(DB::raw('SUM(price*quantity) as aggregate, sale_id'))
->groupBy('sale_id');
}
public function getAmountOrderAttribute()
{
// if relation is not loaded already, let's do it first
if ( ! array_key_exists('AmountOrder', $this->relations))
$this->load('AmountOrder');
$related = $this->getRelation('AmountOrder');
// then return the count directly
return ($related) ? (int) $related->aggregate : 0;
}
And in controller
$sales = Sale::with('AmountOrder')->get();
I'm trying to return multiple views using the laravel framework. When I return the variable, it only makes it through the loop once, therefore only one comment is displayed on the page.
foreach($index_comments as $comments){
$commentComment = $comments->comment;
$index_children = NULL;
$getUser = DB::table('users')->where('id', '=', $comments->from_user_id)->get();
foreach ($getUser as $user) {
$firstName = $user->first_name;
$lastName = $user->last_name;
}
return View::make('feeds.comments')->with(array(
'firstName' => $firstName,
'lastName' => $lastName,
'commentComment' => $commentComment,
'index_children' => $index_children
));
}
I just need a way of returning multiple views.
Thanks for any help!
Toby.
It seems that you don't quite understand the concepts of Laravel and/or PHP yet. So let's start it from scratch: We want to fetch all comments, output the comment and the name of the user who wrote the comment.
At a very basic level, we can just grab it straight from the DB with the query builder:
public function showComments()
{
$commentData = DB::table('comments')
->join('users', 'users.id', '=', 'comments.from_user_id')
->get(['text', 'firstName', 'lastName']);
return View::make('feeds.comments')->with('commentData', $commentData)
}
And in your view:
#foreach($commentData as $comment)
{{ $comment->text }}
<br />
written by {{ $comment->firstName }} {{ $comment->lastName }}
<hr />
#endforeach
That's it. You don't return the view on each iteration, the iteration happens in the view. The return statement terminates the function execution immediately. If you return within in a loop, it will always exit upon the first iteration, that's why you're getting only one result.
In the next step, you should play around with Models and Eloquent for even more powerful and readable data handling.