Call to a member function roles() on null in Laravel - php

I'm getting started on mastering Laravel. I'm trying to create a system where the superuser can edit the roles of all the users. It has been going well until now.
My front end looks like this:
#section('content')
<table>
<thead>
<th>Voornaam</th>
<th>Achternaam</th>
<th>E-mail</th>
<th>User</th>
<th>Admin</th>
<th>Superuser</th>
</thead>
</table>
<tbody>
#foreach($users as $user)
<tr>
<form action="{{ route('superuser.assign') }}" method="post">
<td>{{$user->first_name}}</td>
<td>{{$user->name}}</td>
<td>{{$user->email}}</td>
<td><input type="checkbox" {{ $user->hasRole('User') ? 'checked' : '' }} name="role_user"></td>
<td><input type="checkbox" {{ $user->hasRole('Admin') ? 'checked' : '' }} name="role_admin"></td>
<td><input type="checkbox" {{ $user->hasRole('Superuser') ? 'checked' : '' }} name="role_superuser"></td>
{{ csrf_field() }}
<td><button type="submit">Assign Roles</button></td>
</form>
</tr>
#endforeach
</tbody>
#endsection
The problem is that when I try to assign a role to an existing user through my front end, it throws the error
Call to a member function roles() on null
I have a UserController that looks like this:
public function postAssignRoles(Request $request)
{
$user = User::where('email', $request['email'])->first();
$user->roles()->detach();
if ($request['role_user'])
{
$user->roles()->attach(Role::where('name', 'User')->first());
}
if ($request['role_admin'])
{
$user->roles()->attach(Role::where('name', 'Admin')->first());
}
if ($request['role_superuser'])
{
$user->roles()->attach(Role::where('name', 'Superuser')->first());
}
return redirect()->back();
}
}
And my User class contains the following:
public function roles()
{
return $this->belongsToMany('App\Role', 'user_role', 'user_id', 'role_id');
}
public function hasAnyRole($roles)
{
if (is_array($roles))
{
foreach ($roles as $role)
{
if ($this -> hasRole($role))
{
return true;
}
}
}
else
{
if ($this -> hasRole($roles))
{
return true;
}
}
return false;
}
public function hasRole($role)
{
if ($this->roles()->where('name', $role)->first())
{
return true;
}
return false;
}
}
I also set up this in my middleware
public function handle($request, Closure $next)
{
if ($request->user() === null)
{
return response("U bent niet bevoegd om deze actie uit te voeren.", 401);
}
$actions = $request->route()->getAction();
$roles = isset($actions['roles']) ? $actions['roles'] : null;
if ($request->user()->hasAnyRole($roles) || !$roles)
{
return $next($request);
}
return response("U bent niet bevoegd om deze actie uit te voeren.", 401);
}
and added this in the Kernel
'roles' => \App\Http\Middleware\CheckRole::class
I think the problem is that for one reason or another $user = User::where('email', $request['email'])->first(); returns null. I tried get()instead of first(), but then I get a Method Illuminate\Database\Eloquent\Collection::roles does not exist.error.
I'm sorry for the many code snippets, but I've been struggling with this for the past 8 hours and really don't know where to search the problem anymore. Please someone help me before I go cry myself to sleep in the corner of my room and never touch Laravel again.

You are right that calling first() on your query returns null, hence the reason why you get that error.
So what you can use to prevent calling roles() on null object is to use firstOrFail() method which will throw a ModelNotFoundException which you can catch using a try catch block and handle it gracefully.
And as I can see in your view you are not sending the value of the email in your form, so you should put the email in a hidden input:
<td>
<input type="hidden" name="email" value="{{$user->email}}" />
{{$user->email}}"
</td>
not having this is why you get null result.

You must check if the values you are intending to pass are set, You can do so with isset
example #isset('$user->hasRole('User')');

Related

Run Livewire click method based on whether a checkbox is checked or not

I have a Livewire component like below
<div class="mr-3">
<input type="checkbox" name="milestoneMark" value="{{ $milestoneId }}" x-model="milestone" wire:click="mark()">
</div>
The component class have below code
class CheckMilestone extends Component
{
public $milestoneId;
public function mark()
{
$milestone = Milestone::where('id', $this->milestoneId)
->first();
$user = User::where('id', auth()->id())
->first();
$user->milestones()->attach($milestone);
}
public function unMark()
{
$milestone = Milestone::where('id', $this->milestoneId)
->first();
$user = User::where('id', auth()->id())
->first();
$user->milestones()->detach($milestone);
}
public function render()
{
return view('livewire.check-milestone');
}
}
My requirement is execute mark() method when the checkbox is unchecked and execute unMark() when the checkbox is checked
I tried below but didn't work
<div class="mr-3">
<input type="checkbox" name="milestoneMark" value="{{ $milestoneId }}" x-model="milestone" {!! 'checked' ? wire:click="mark()" : wire:click="unMark()" !!} >
</div>
I try to use as less JavaScript as possible so if you can please give me a solution without JS, but if there isn't any other way to fix this I'm okay to use JS. TIA!
You have nothing to base your checked situation from. I personally would do the following:
class CheckMilestone extends Component
{
public $milestoneId;
public bool $checked = false;
public function processMark()
{
if ($this->checked) {
$this->mark();
} else {
$this->unMark();
}
}
// Rest of your code here
}
<div class="mr-3">
<input type="checkbox" wire:model="checked" wire:change="processMark()" wire:loading.attr="disabled">
</div>
To explain; we use wire:model to link the value of the checkbox to the Livewire component. We use wire:change to detect the change event triggered when you (de)select the checkbox, and fire the processMark method. Purposely we don't change the HTML as to not cause unexpected behaviour. We use wire:loading to add the disabled attribute while the checked variable is being updated, so we can't quickly uncheck it while it's processing.

How check if id exist in database before return?

Before looking for a page I wanted to check if the id exists, so if I don't find it, give up looking I tried as follows:
My controller product
public function search(Request $request)
{
$id = $request->input('id');
if($produto = Produto::find($id)) {
return view('produtos.show', compact('produto', 'id'));
}
// $search_results=Produto::findOrFail($id);
return 'Not found';
}
->My Route->
Route::get('/produtos/editar/{id?}','App\Http\Controllers\ProdutosController#search')->name('searchproduct');
->My Blade Form
<form id="search" method="GET" action="{{ route('searchproduct') }}" >
<input id="q" name="q" type="text" /></br>
<button type="submit" id="submitButton" >Alterar</button>
</form>
</div>
</div>
</div>
</div>
->My Jquery Script
jQuery(document).ready(function(){
jQuery("form#search").on('submit',function(e){
e.preventDefault();
var q = jQuery("#q").val();
window.location.href = jQuery(this).prop('action')+"/" + encodeURIComponent(q)
});
});
How can i check in database before? send It's always going to the default 404 page
It's enough to check $search_results variable. I changed findOrFail with find because findOrFail may throw an error.
public function search(Request $request) {
$search_results = Produto::find($request->input('id'));
if ($search_results == NULL) {
abort(404);
}
return view('produtos.show', ['produto' => $search_results, 'id' => $request->input('id')]);
}
Also yo can use:
public function search(Request $request) {
$search_results = Produto::where('id', '=', $request->input('id'))->first();
if ($search_results == NULL) {
abort(404);
}
return view('produtos.show', ['produto' => $search_results, 'id' => $request->input('id')]);
}
Two ways to go about it:
exists:
if (Produto::where('id', $id)->exists()) {
$search_results=Produto::find($id);
}
findOr:
$search_results = Produto::findOr($id, function () {
// whatever you want to do if no record is found
});
to show a 404 page use this code :
public function search(Request $request)
{
//if not found it will trigger 404 not found page
$produto = Produto::findOrFail($id = $request->input('id'));
//otherwise it will return the view of produtos.show
return view('produtos.show', compact('produto', 'id'));
}
or you can use this code too to use a custom return
public function search(Request $request)
{
$id = $request->input('id');
if($produto = Produto::find($id)) {
return view('produtos.show', compact('produto', 'id'));
}
//otherwise return your error view or message
return 'Not found';
}
-> your route must be get not post
Route::get('/produtos/editar/{id?}','ProdutosController#search')->name('searchproduct');
-> no need for #csrf for get method
<form id="search" method="GET" action="{{ route('searchproduct') }}" >
<input id="q" type="text" /></br>
<button type="submit" id="submitButton" >Alterar</button>
</form>

Destroy() in Basic Controller is not working

So I'm printing user complaints in table where I'm also printing a Delete button with every row. When I click that delete button, I want to delete that specific complaint from the table. I'm not using Resource Controller for this but a Basic Controller. Now, this is my code:
ViewComplaint.blade.php (Complaints Table with Delete Button):
<table id="cTable" class="table table-striped table-bordered">
<thead>
<tr>
<th>Student Name</th>
<th>Complaint Title</th>
<th>Complaint Description</th>
<th>Action</th>
</tr>
</thead>
<tbody>
#foreach($complaints as $complaint)
<tr>
<td>{{ $complaint->name }}</td>
<td>{{ $complaint->cname }}</td>
<td>{{ $complaint->cbody }}</td>
<td class="btn-group">
{!! Form::open(array('route'=>['complaint.destroy',$complaint->id],'method'=>'DELETE')) !!}
{!! Form::submit('Delete',['type'=>'submit','style'=>'border-radius: 0px;','class'=>'btn btn-danger btn-sm',$complaint->id]) !!}
{!! Form::close() !!}
</td>
</tr>
#endforeach
</tbody>
</table>
Web.php (Routes):
Route::get('/complaint/create','ComplaintController#create')->name('complaint.create');
Route::post('/complaint','ComplaintController#store')->name('complaint.store');
Route::get('/complaint','ComplaintController#index')->name('complaint.index');
Route::delete('/complaint/{$complaint->id}','ComplaintController#destroy')->name('complaint.destroy');
ComplaintController.php (Basic Controller):
class ComplaintController extends Controller
{
public function index() {
$complaints = Complaint::all();
return view('viewcomplaint',compact('complaints'));
}
public function create(User $user) {
$user = User::all();
$user->name = Auth::user()->name;
return view('createcomplaint',compact('user'));
}
public function store(Request $request, Complaint $complaint, User $user) {
$user = User::find($user);
$complaint->name = Auth::user()->name;
$complaint->cname = $request->input('cname');
$complaint->cbody = $request->input('cbody');
//update whichever fields you need to be updated
$complaint->save();
return redirect()->route('home.index');
}
public function destroy(Complaint $complaint,$id)
{
$complaint = Complaint::findOrFail($complaint->id);
$complaint->delete();
return redirect()->route('complaint.index');
}
}
Now when I click the Delete button on the table, it just gives me "404 | Not Found" error. What am I doing wrong here? I would really appreciate some help.
remove the $id from the route
Route::delete('/complain/{id}','ComplaintController#destroy')->name('complaint.destroy');
public function destroy($id) {
}
The route parameter is just a name; you are saying this particular route segment is dynamic and I want the parameter named complaint:
Route::delete('complaint/{complaint}', 'ComplaintController#destroy')->name('complaint.destroy');
Then you can adjust your destroy method to take the parameter complaint typehinted as Complaint $complaint to get the implicit binding:
public function destroy(Complaint $complaint)
{
$complaint->delete();
return redirect()->route('complaint.index');
}
Seems to me you're defining your route wrong. Change your route to:
Route::delete('/complaint/{id}','ComplaintController#destroy')->name('complaint.destroy');
You don't need an array() in your form opening, so hange your form opening to this:
{!! Form::open(['method' => 'DELETE', 'route' => ['complaint.destroy',$complaint->id]]) !!}
And remove the $complaint->id from your submit button, you don't need it there.
All you have to do now inside your function is to find Complaint that has the id you passed in your form:
public function destroy($id)
{
$complaint = Complaint::findOrFail($id);
$complaint->delete();
return redirect()->route('complaint.index');
}
Let me know if you stumble on any errors.

Laravel 5 check if pivot data is already in database

Got a ManyOnMany system (3 tables, projects, users, project_user)
Many users can work on a project, and a user can have many projects.
When checkbox = checked it sends the database to the pivot table.
Now I'm facing the problem that everytime I click the project/user id will get send to the project_user table.
And I need to have the checkbox already checked when the user is actually added to the project.
So how I see it: the form::checkbox has a third function checked or not checked, and with an if/else statement in my controller.edit I will have a validation somehow. Please help me!
Blade:
#foreach($users as $user)
<tr>
<td>
{{$user->firstname}} {{$user->middlename}} {{$user->lastname}}
</td>
<td>
{!! Form::checkbox('contribute['.$user->id.']', '1', $checkifinproject) !!}
</td>
</tr>
#endforeach
Controller:
public function edit($id, Project $project)
{
$users = User::all();
$project = $this->project->find($id);
if ($users == $project)
{
$checkifinproject = 'checked';
}
else {
}
return view('project.edit', ['project' => $project, 'id' => 'edit', 'project_id' => $id], compact('users'));
}
public function update(CreateProjectRequest $request)
{
if($request->get('contribute'))
{
foreach($request->get('contribute') as $k => $contribute)
{
if($contribute == 1)
{
$project = $this->project->find($request->project_id);
$project->users()->attach($k);
}
}
}
$project = $this->project->find($request->project_id);
$project->fill($request->input())->save();
return redirect('project');
}
Model:
User
public function projects()
{
return $this->belongsToMany('App\Project', 'project_user', 'user_id', 'project_id');
}
Project
public function users()
{
return $this->belongsToMany('App\User', 'project_user', 'project_id', 'user_id');
}
I think the issue is you are comparing two things that will never be the same and trying to determine if that user belongs to the project. A better thing to do would be to query all the users with their projects and as you loop through the users, check to see that the project you are modifying is one of the projects the user belongs to.
That can be done like this...
Controller
public function edit($id, Project $project)
{
$users = User::with('projects')->get();
$project = $this->project->find($id);
return view('projects.edit', ['users' => $users, 'project' => $project]);
}
View
#foreach($users as $user)
<tr>
<td>
{{$user->firstname}} {{$user->middlename}} {{$user->lastname}}
</td>
<td>
{!! Form::checkbox('contribute['.$user->id.']', '1', $user->projects->contains('id', $project->id)) !!}
</td>
</tr>
#endforeach

Laravel Error : ErrorException Missing argument 1?

I am using Laravel 4 and I am getting ERROR: when I visit admin/profile/: Missing argument 1 for AdminController::getProfile()
My AdminController code :
public function getProfile($id) {
if(is_null($id)) Redirect::to('admin/dashboard');
$user = User::find($id);
$this->layout->content = View::make('admin.profile', array('user' => $user));
}
My routes.php :
Route::controller('admin', 'AdminController');
My admin/profile (blade) view :
#if(!is_null($user->id))
{{ $user->id }}
#endif
How could I fix this? I want when they go to admin/profile without ($id) to redirect to dashboard.
You told Laravel that your getProfile method has one parameter:
public function getProfile($id) {
}
If you want to a request to succeed, you have to pass it in your URL:
http://appdev.local/admin/profile/1
If you want to see it fail (redirect to dashboard), you'll have to add a default value to your function argument:
public function getProfile($id = null) { ... }
But you better add this value to it anyway, since you can have bots (or even people) trying to access that route without the parameter.
Your view is too generic too, you have to check if your $user is set:
#if(isset($user) && !is_null($user->id))
{{ $user->id }}
#endif
As noted in the comments, the line
if(is_null($id)) Redirect::to('admin/dashboard');
Must have a return:
if(is_null($id)) return Redirect::to('admin/dashboard');
About sharing the user to your layout, the problem is that your getProfile($id) is already passing a $user to your view, so what you could do is to add this to your __construct():
if (Auth::check())
{
$user = Auth::getUser();
View::share('loggedUser', $user);
}
And in your view:
#if(isset($user) && !is_null($user->id))
{{ $user->id }}
#else
{{ $loggedUser->id }}
#endif
About the user not found problem, you have many options, this is one:
public function getProfile($id) {
if (is_null($id))
{
return Redirect::to('admin/dashboard');
}
if ($user = User::find($id))
{
$this->layout->content = View::make('admin.profile', array('user' => $user));
}
else
{
return Redirect::to('admin/dashboard')->withMessage('User not found');
}
Try setting a default null value to $id like this :
public function getProfile($id = null) {
...
}

Categories