Laravel Resource controller for filtering - laravel

I have a resource Controller for an API that I'm developing for displaying records that can be filtered by Type and Customer and I have the following methods that can get this data:
index
show -> requires an parameter (id)
Can I therefore put a request inside the index method for filtering all of the entries back or is it bad practise for doing this? My code looks like the following:
public function index()
{
$entries = \App\Ent::where(function($en) {
$request = app()->make('request');
if($request->has('type')) {
$en->where('type', '=', $request->get('type'));
}
if($request->has('customer')) {
$en->where('customer', '=', $request->get('customer'));
}
})->get();
dd($entries);
}

Filtering in Laravel is very simple and you don't need to do this. In your Ent model, define the following:
public function scopeFilter($query, $filters)
{
if( isset($filters['type']) ){
$query->where('type', '=', $filters['type']);
}
// keep going for all of your filters
}
And on your Controller index method you can:
public function index()
{
$entries = \App\Ent::filter(['type', 'customer'])->get();
dd($entries);
}
EDIT
To help you make more sense of this, let's filter Ent on the type column in the database.
Route:
Route::get('/ent', 'EntController#index');
Ent Model:
class Ent extends Model
{
public function scopeFilter($query, $filters)
{
if( isset($filters['type']) ){
$query->where('type', '=', $filters['type']);
}
}
}
Ent Controller:
class EntController extends Controller {
index()
{
$entries = \App\Ent::filter(['type'])->get();
return view('ent.index', compact('entries'));
}
}
Let's say for the sake of this example we are just going to put a form on the same blade template we are outputting our list:
#foreach( $entries as $entry )
<p>{{ $entry->type }}</p>
#endforeach
<form method="GET" action="/ent">
{{ csrf_field() }}
<input type="text" name="type" />
<input type="submit" value="Filter" />
</form>
So now, if I were to go into that form and type 'Foo Bar' and hit submit, you would get what would look something like this in SQL
SELECT * FROM Ent WHERE type='foo bar'
Or in other words, all Ent with the type column = 'foo bar'.
When I give a user the ability to type raw text in to filter, I like to give them the benefit of the doubt and use LIKE instead of =. So for example we would just change our scopeFilter method:
if( isset($filters['type']) ){
$query->where('type', 'LIKE', '%' . $filters['type'] . '%');
}
Another thing to note here, the filters[name] is the name of the <input> field, NOT the name of the column in your database. You target the column in the $query extension, NOT in the $filters array. See below example:
if( isset($filters["ent_type"] ){
$query->where('type', '=', $filters["ent_type"]);
}
On my form that would be
<input name="ent_type" type="text" />

Related

Livewire component not updating

I have a simple component:
<input type="text" wire:model="search">
{{ $search }}
I load this like this:
#livewire('searchbar')
And this is my class:
class Searchbar extends Component
{
public $search;
public $results = [];
public function updatedSearch()
{
info($this->search);
$this->results = Client::search($this->search)->get();
}
public function render()
{
return view('livewire.searchbar', [
'results' => $this->results,
]);
}
}
When I type into the input field, the info() logs the correct value but in the view, the the {{ $search }} gets not updated. When I use protected $queryString = ['search']; the URL get's updated correctly as well and if I would use dd() instead of info() I'd see the view updating with the dd.
Why is the $search not updating in the view?
Wrap your component. Make sure your Blade view only has ONE root element.
<div>
<input type="text" wire:model="search">
{{ $search }}
</div>
First thing first, inside livewire render, you do not need to send variables.
This will be sufficient.
public function render()
{
return view('livewire.searchbar');
}
You already declared $results as public variable, just like $search.
Livewire will know when the content of those variables are updated.
And now please try again.
$search should be automatically updated based on any text you inserted in input text with wire:model attribute.

Livewire generating a collection on mount, but an array on refresh

I have a (relatively) simple Livewire controller which, on mount() generates a collection of games, grouped by their start dates, and assigns it to the $games public property.
public $games;
protected $rules = [];
...
public function mount() {
$now = Carbon::now();
$games = Game::where('start', '>', $now)->orderBy('start', 'ASC')->get();
$games = $games->groupBy(function($item) {
return $item->start->format("Y-m-d H:i:s");
})->collect();
$this->games = $games;
}
The corresponding component then loops through the dates, outputs the date, and then sends the games themselves to a Blade component (irrelevant styling removed):
#foreach($games as $date => $thegames)
<div>
{{ \Carbon\Carbon::createFromFormat("Y-m-d H:i:s", $date)->format("l jS \of F Y - g:i A") }}
</div>
<div>
<x-individual.game :allgames="$thegames" :user="Auth::user()"></x-individual.game>
</div>
#endforeach
The Blade component then loops through the games that it's been given and renders each of them (simplified below) :
#foreach($allgames as $game)
<div>
<div>
<h3>
{{ $game->game->name }}
</h3>
</div>
</div>
#endforeach
Within that component (not shown) are wire:click buttons which may add a person to the game, or remove them. That, in turn, fires the refresh() function of the original Livewire component, which is identical to the mount() function save that it emits the refreshComponent event at the end :
public function refreshThis() {
$now = Carbon::now();
$games = Game::where('start', '>', $now)->get();
$games = $games->groupBy(function($item) {
return $item->start->format("Y-m-d");
})->collect();
$this->games = $games;
$this->emit('refreshComponent');
}
That's where the problem starts. Rather than re-render the component, as it should, it generates an error of "Attempt to read property "game" on array" within the blade component :
{{ $game->game->name }}
and sure enough, at that point, $game is now an array, not an Eloquent object (or whatever it was first time around).
If I manually refresh the page, the changes are shown without issue. But why is it issuing me an array on refreshing, and (more importantly) how can I stop it?
You could add the $games to the render method instead and it will refresh the data:
public function render()
{
$now = Carbon::now();
$games = Game::where('start', '>', $now)->orderBy('start', 'ASC')->get();
$games = $games->groupBy(function($item) {
return $item->start->format("Y-m-d H:i:s");
})->collect();
return view('livewire.your_component_view', [
'games' => $games
]);
}
Then refresh the component (in your main component):
public function refreshThis() {
$this->render();
}
If you are calling refresh from a child component you can call it form there:
Child component:
$this->emit('refreshThis');

Why does the old() method not work in Laravel Blade?

My environment is Laravel 6.0 with PHP 7.3. I want to show the old search value in the text field. However, the old() method is not working. After searching, the old value of the search disappeared. Why isn't the old value displayed? I researched that in most cases, you can use redirect()->withInput() but I don't want to use redirect(). I would prefer to use the view(). method
Controller
class ClientController extends Controller
{
public function index()
{
$clients = Client::orderBy('id', 'asc')->paginate(Client::PAGINATE_NUMBER);
return view('auth.client.index', compact('clients'));
}
public function search()
{
$clientID = $request->input('clientID');
$status = $request->input('status');
$nameKana = $request->input('nameKana');
$registerStartDate = $request->input('registerStartDate');
$registerEndDate = $request->input('registerEndDate');
$query = Client::query();
if (isset($clientID)) {
$query->where('id', $clientID);
}
if ($status != "default") {
$query->where('status', (int) $status);
}
if (isset($nameKana)) {
$query->where('nameKana', 'LIKE', '%'.$nameKana.'%');
}
if (isset($registerStartDate)) {
$query->whereDate('registerDate', '>=', $registerStartDate);
}
if (isset($registerEndDate)) {
$query->whereDate('registerDate', '<=', $registerEndDate);
}
$clients = $query->paginate(Client::PAGINATE_NUMBER);
return view('auth.client.index', compact('clients'));
}
}
Routes
Route::get('/', 'ClientController#index')->name('client.index');
Route::get('/search', 'ClientController#search')->name('client.search');
You just need to pass the variables back to the view:
In Controller:
public function search(Request $request){
$clientID = $request->input('clientID');
$status = $request->input('status');
$nameKana = $request->input('nameKana');
$registerStartDate = $request->input('registerStartDate');
$registerEndDate = $request->input('registerEndDate');
...
return view('auth.client.index', compact('clients', 'clientID', 'status', 'nameKana', 'registerStartDate', 'registerEndDate'));
}
Then, in your index, just do an isset() check on the variables:
In index.blade.php:
<input name="clientID" value="{{ isset($clientID) ? $clientID : '' }}"/>
<input name="status" value="{{ isset($status) ? $status : '' }}"/>
<input name="nameKana" value="{{ isset($nameKana) ? $nameKana : '' }}"/>
...
Since you're returning the same view in both functions, but only passing the variables on one of them, you need to use isset() to ensure the variables exist before trying to use them as the value() attribute on your inputs.
Also, make sure you have Request $request in your method, public function search(Request $request){ ... } (see above) so that $request->input() is accessible.
Change the way you load your view and pass in the array as argument.
// Example:
// Create a newarray with new and old data
$dataSet = array (
'clients' => $query->paginate(Client::PAGINATE_NUMBER),
// OLD DATA
'clientID' => $clientID,
'status' => $status,
'nameKana' => $nameKana,
'registerStartDate' => $registerStartDate,
'registerEndDate' => $registerEndDate
);
// sent dataset
return view('auth.client.index', $dataSet);
Then you can access them in your view as variables $registerStartDate but better to check if it exists first using the isset() method.
example <input type='text' value='#if(isset($registerStartDate)) {{registerStartDate}} #endif />

Two actions on a function in Laravel, possible?

I have 2 users roles in my application, admin and former.
The admin can create several formers...
If, I connect me with the ID 1 ? I retrieve the information of the former.
So, my function index() allows to retrieve id of the user
public function index()
{
if($has_role = auth()->user()->hasRole('admin')){
$formers = Former::first()->paginate(5);
return view('admin.formers.index', compact('formers'));
} else{
$formers = Former::where('email', Auth::user()->email)->paginate(5);
return view('admin.formers.index', compact('formers'));
}
}
Well, for the user admin, I would like to create a search bar...
I had created before a function index() and which worked
public function index(Request $req)
{
if ($req->search == "") {
$formers = Former::paginate(5);
return view('admin.formers.index', compact('formers'));
} else {
$validated = $req->validate([
'search' => 'alpha',
]);
$formers = Former::where('nom', 'LIKE', '%' . $validated['search'] . '%')->paginate(5);
$formers->appends($req->only('search'));
return view('admin.formers.index', compact('formers'));
}
}
Now, I would like to adapte my 2 actions in a function, is it possible according you?
Do you think that I can get the user_id and make a search bar in the same function?
Thank you
What I would do is the following:
Add one action which serves both roles with data.
Display the search only to admins, but ignore this fact on the server-side as it doesn't matter from a security perspective whether non-admins can search or not. They are limited to their result anyway.
Basically, this is achievable in the following way:
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
public function index(Request $request)
{
$user = $request->user();
$formers = Former::query()
->when($user->hasRole('admin') !== true, function (Builder $query) use ($user) {
$query->where('email', $user->email);
})
->when($request->has('s'), function (Builder $query) use ($request) {
$query->where('nom', 'like', '%'.$request->input('s').'%');
})
->paginate(5);
return view('admin.formers.index', compact('formers'))
->with('display_search', $user->hasRole('admin'));
}
You can then in your view simply use the $display_search variable to decide whether or not you want to display the search:
#if($display_search)
<form method="post" action="...">
<input type="text" name="s" placeholder="Type to search...">
</form>
#endif
I would create a policy with a search method:
public function search($user)
{
if ($user->isAdmin()) {
return true;
}
}
now you may just edit your blade
#can('search')
<form method="post" action="/search">
<input type="text" name="search" placeholder="Recherche...">
</form>
#endcan
If you want to give search access to other users as well, you only have to modify the policy.

how do i pass data value to another page via link in laravel?

i am trying to make a list of locations that you can rent. but to rent the place you need to fill in some information. to fill in this information you excess another page. how do i make it so laravel knows the page belongs to a certain location
this is what ive done now but i keep getting the error:
Call to undefined method App\Reservation::location()
as soon as i have filled in the fields of information
this is the blade file that links to the the create reservation file
#foreach
($locations as $location => $data)
<tr>
<th>{{$data->id}}</th>
<th>{{$data->name}}</th>
<th>{{$data->type}}</th>
<th><a class="btn" href="{{route('Reservation.index', $data->id)}}">rent</a></th>
</tr>
#endforeach
this is the create reservations blade
<form action="{{ route('location.store') }}" method="post">
#csrf
<label>title</label>
<input type="text" class="form-control" name="name"/>
<label>type</label>
<select>
<option value="0">klein</option>
<option value="1">groot</option>
</select>
<button type="submit" class="btn">inschrijven</button>
</form>
this is what the location controller looks like
public function store(Request $request)
{
$location = new Reservation;
$location->name = $request->get('name');
$location->type = $request->get('type');
$location->location()->associate($request->location());
$location->save();
return redirect('/location');
}
and the relationships in my models should also work
class Reservation extends Model
{
public function locations()
{
return $this->belongsTo('Location::class');
}
}
class Location extends Model
{
public function reservations()
{
return $this->hasMany('Registration::class');
}
}
ive been stuck at this all day and i really dont know where to look anymore
The error you are getting is because of the wrong function name, you are calling location, while it is locations.
public function locations(){}
&
$location->location()->associate($request->location());
and you can pass the variable as a query parameter, you'll need to pass this data as an array in your blade file.
Web.php
Route::get('/somewhere/{id?}, function(){
//do something
})->name('test');
Blade
route('test', ['id' => $id]);
Controller Method
public function store(Request $request, $id) //Adding the query parameter for id passed in Route.
{
$location = new Reservation;
$location->name = $request->get('name');
$location->type = $request->get('type');
$location->location()->associate($id);
$location->save();
return redirect('/location');
}

Resources