Laravel Limit route parameter dynamically (but only show valid urls) - php

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

Related

Laravel "belongsTo" function. Not exactly sure how this works. Help to access related model info from Blade template

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>

Dynamic column/attribute. Stuck. Need inspiration

i’m looking for a solution to a problem that I’m having for a while now. Maybe you can inspire me to do this better. I’m trying not to make a basic mistake in the planning process therefore I’m asking you for advice.
I’m having a Contact::model which has few fixed attributes like id etc. Additionally I would like to have different attributes created dynamically for the whole Contact::model. Some user will be given the functionality to add attributes like name, email, address to the whole model. I’ve dropped the idea of programmatically updating the table itself by creating/dropping columns (this would introduce different problems). As for now i've created two additional tables. One with the additional column names [Columns::model] and a pivot table to assign the value to a Contact::model and Column::model.
To list all contacts i’m preparing the ContactColumn table as array where the first key is the contact_id and the second is the column_id, therefore i get the value. This introduces the n+1 issue. This would not be that bad, but with this approach it will be extremely hard (or resource consuming) to order the contacts by dynamic column values, filtering, searching etc.
Can you somehow guide me to a better solution. How can i merge the contact collection with the values for given columns so it looks like it was a fixed table?
<table>
<thead>
<tr>
<th>Fixed columns [i.e. ID]</th>
#foreach ($columns as $column)
<th>{{ $column->name }}</th>
#endforeach
</tr>
</thead>
<tbody>
#foreach ($contacts as $contact)
<tr>
<td>{{ $contact->id }}</td>
#foreach ($columns as $column)
<td>
#if (array_key_exists($column->id, $values[$contact->id]))
{{ $values[$contact->id][$column->id] }}
#endif
</td>
#endforeach
</tr>
#endforeach
</tbody>
</table>
And the $value array.
foreach (ColumnContact::all() as $pivot) {
$values[$pivot->contact_id][$pivot->column_id] = $pivot->value;
}
return $values;
Edit: I've solved it like this
$this->contacts = Contact::when($this->dynamicColumnName, function($query) {
$query->join('column_contact', function ($join) {
$join->on('id', '=', 'column_contact.contact_id')
->where('column_contact.column_id', '=', $this->dynamicColumnName->id);
})
->orderBy('value', $this->orderingDirection);
})
(...)
->paginate(self::PER_PAGE);
Apart from the fixed fields, add an extra JSON field in your schema called 'custom_fields'. Have a look into => https://github.com/stancl/virtualcolumn
Separate table for custom fields is not a good idea because then you have to handle model events separately and so on.

Creating function for laravel controller/view to load database values

I'm working on updating a laravel blade template to insert some database info into an html table. IN order to do this, I'm having to add new data to the controller for this blade and that's where I'm having some troubles.
I'm still trying to understand more with laravel, so I'm thinking my syntax or methods of creating this data are incorrect but I just can't put my finger on it right now.
In my function below, the $calls_allowed portion was already existing and it works on the page currently. I created the $contact_events portion of the function and that's where my problem is.
IN my view, I created a foreach loop and if statement around the html table in question. The table loads, but it's empty even though there are records in the database for the dealer.
I'm trying to say
if $dealer-> id matches contact_events.dealer_num, load all records for that dealer
contact_events is the table and dealer_num is the column I'm matching, then I'm trying to load the columns from that table (updated_at,method,notes) into the html table.
The affected code is below. The view/route/controller work, it's just this function I'm creating that isn't loading data. Any help is much appreciated.
Controller code:
public function show($id)
{
$d = Dealer::find($id);
if(!$d){
\Session::flash('warning_message', 'Sorry that resource can not be found.');
return redirect()->route('account.dealer.index');
}
$calls_allowed = DB::table('dealers.dealers')->
where('dealer_num', $id)->
pluck('calls_allowed');
$contact_events = DB::table('dealers.contact_events')->
where('dealer_num', $id)->
pluck('updated_at', 'method', 'notes');
if(!empty($calls_allowed)){
$d->calls_allowed = $calls_allowed[0];
} else {
$d->calls_allowed = null;
}
return view('Account.Dealer.show')->with('dealer', $d);
}
View code:
<thead>
<tr>
<th>Contacted Date</th>
<th>Type of Contact</th>
<th>Call Notes</th>
</tr>
</thead>
#foreach($dealer->contact_events as $events)
#if($events->dealer_num = $dealer->id)
<tbody>
<tr>
<td>{{$events->updated_at}}</td>
<td>{{$events->method}}</td>
<td>{{$events->notes}}</td>
</tr>
</tbody>
#endif
#endForeach
It looks like you are not assigning the data to the object after retrieving from database.
$contact_events = DB::table('dealers.contact_events')->
where('dealer_num', $id)->
pluck('updated_at', 'method', 'notes');
// add this
$d->contact_events = $contact_events;
This seems like a perfect time to use the power of Laravel's Eloquent ORM...
Check out the with and has in the Laravel docs
This will require some finessing based on your needs, but it will be something like this:
$d = Dealer::where('id', '=', $id)
->with('contact_events')->first();
This uses Eloquent to get all of the contact_events that belong to the dealer with the $id.
Then you can do something like this
note: this assumes that calls_allowed is a record on the dealer table. if I misunderstood that, you can still run than you can include that just as you have it.
#if(!is_null($dealer->calls_allowed)
#foreach($dealer->contact_events as $events)
<tbody>
<tr>
<td>{{$events->updated_at}}</td>
<td>{{$events->method}}</td>
<td>{{$events->notes}}</td>
</tr>
</tbody>
#endForeach
#endif

How to order my foreach in laravel blade

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

how to loop a collection inside a collection in laravel?

i want to list all the images for each project, i tried to do it like this, but it always return 1 image only.
public function home()
{
$projects = Project::all(); //result : 1
foreach($projects as $project){
$images = $project->images()->get();
// dd($images); //result : 4
foreach($images as $image){
return '<img src="'.$image->image_path.'">';
}
}
}
the code above is just for test purpose, what i want to do is to display it in a table, something like this in my controller :
<tbody>
'
$images = $project->images()->get();
foreach($images as $image){
'
<tr>
<td><img src="'.$image->image_path.'"></td>
</tr>
'
}
'
</tbody>
Since a function can only return once, you probably need to do something different on the inner foreach like concatenating the image html to an existing string.
That said, the right way to do this would generally be to return $images to be used directly in a template where you would build out the html.
I'm not sure what all is going on here, but it looks like you want to do something that is relatively simple in Laravel if you are able to use Controllers and Blade Templates.
First, you start with your controller (keep all of your application logic there):
// ProjectController.php
public function home()
{
//-- grab all the projects, with all of their images
// Using with() will allow you to eager load those images
// from the database if they are Eloquent relationships.
$projects = Project::with('images')->get();
//-- send those $projects to your view
// Using with() here will send that variable to your
// view accessible by the key.
return view('projects')->with(['projects' => $projects]);
}
Then, in your blade template, you can easily iterate over the projects and images:
// views/projects.blade.php
#foreach($projects as $project)
<table>
...
<tbody>
#foreach($project->images as $image)
<tr>
<td><img src="{{ $image->image_path }}"></td>
</tr>
#endforeach
</tbody>
...
</table>
#endforeach
</tbody>
Hope this helps. If none of this makes sense, you can always check out the docs, they are really well written!
Laravel Docs
Be sure to check out the Controllers, Blade Templates and Eloquent ORM - Relationships sections :)

Categories