I am having issues with Laravel 4. When a user logs in, they are able to create or edit a saved form called a "Project". I need to be able to make it where a user can only edit the forms "projects" that they created. Currently, anyone can edit anyone elses project by changing the project id in the url.
Example: projects/3/edit edits the project with an id of 3
I tried using the following code but it returns an error "Trying to get property of non-object" but project is an object. Any advice is much appreciated.
filter.php
Route::filter('auth', function()
{
if (Auth::guest()) return Redirect::guest('login');
});
Route::filter('auth.project', function($route, $request)
{
if($route->parameter('project')->user_id !== Auth::user()->id)
{
return Redirect::route('/')->with('error','sorry, you can only access projects that you created');
}
return Redirect::route('/');
});
Route::filter('auth.basic', function()
{
return Auth::basic();
});
routes.php
Route::get('/projects/{projects}/edit','ProjectsController#edit', ['before' => 'auth.project']);
You have this filter:
Route::filter('auth.project', function($route, $request)
{
if($route->parameter('project')->user_id !== Auth::user()->id)
{
return Redirect::route('/')->with('error','sorry, you can only access projects that you created');
}
return Redirect::route('/');
});
Here if($route->parameter('project')->user_id !== Auth::user()->id) is not right because the first thing is that, your route is declared as given below:
// This is not right
Route::get('/projects/{projects}/edit','ProjectsController#edit', ['before' => 'auth.project']);
Try this:
Route::get('/projects/{projects}/edit', ['before' => 'auth.project', 'uses' => 'AuthorsController#edit']);
Also, the parameter is projects not project but you are using project in your filter and then, the second problem is that, you are trying to get property of non-object because $route->parameter('project') returns null and even if you use projects then it may return the 3 but still that will throw an error because 3 is not an object. You need to grab that Project whose id is 3 or you may use a route model binding. So, if you use something like this:
$project = Project::find($route->parameter('projects')); // Assumed that 3 is id
if($project->user_id !== Auth::user()->id) {
//...
}
Also you may use a route model binding like this:
Route::model('projects', 'Project'); // Project Model must be existed
Route::get('/projects/{projects}/edit', ['before' => 'auth.project', 'uses' => 'AuthorsController#edit']);
Then in your edit method:
public function edit(Project $project)
{
// $project is an Eloquent Object, will be injected automatically...
});
Well, just looking at what you've posted here, I see a couple issues.
First, you named your parameter projects in your route and try to access project in your filter, which is non-existent.
Second, $route is of type Illuminate\Routing\Route which has a method called getParameter, not parameter. More can be found in the documentation linked here.
Hope this helps.
Related
I am getting a "404 | Not Found" error when i try to access a specific item from my database. The items with the specific ID's do exist in the database, but i am not even sure if that even has any influence on the problem.
My routing looks likes this:
Route::prefix('departments')->group(function() {
Route::get('/{id}', [DepartmentController::class, 'showDepartment']);
});
And the related controller looks like this:
public function showDepartment() {
return '';
}
}
I haven't yet finished the function. I just wanted to check if the routing even worked by returning an empty string.
So what am i doing wrong? Is it the routing or the controller?
According to the Laravel documentation, you have to define the parameter in the route then use it in the controller as a parameter.
in your controller:
public function showDepartment($id) {
return 'Hello';
}
The id is not bound to any model to fetch from the database to do that you can use Laravel route model binding
for example when you have a model named Department you write your route like this:
Route::get('/{department}', [DepartmentController::class, 'showDepartment']);
and in the controller:
public function showDepartment(Department $department) {
return 'Hello from depratment';
}
When your department exists it returns the response otherwise return 404.
You may need a Department model class. Then you can find the item from database by id $department = Department::findOrFail($id);
you are send parameter in route and this function without any parameter
route :
Route::prefix('departments')->group(function() {
Route::get('/{id}', [DepartmentController::class, 'showDepartment']);
});
your function in controller should be
public function showDepartment($id) {
$department = Department::findOrFail($id);
}
}
Suppose I have these routes :
$api->group(['prefix' => 'Course'], function ($api) {
$api->group(['prefix' => '/{course}'], function ($api) {
$api->post('/', ['uses' => 'CourseController#course_details']);
$api->post('Register', ['uses' => 'CourseController#course_register']);
$api->post('Lessons', ['uses' => 'CourseController#course_lessons']);
});
});
As you can see all / , Register and Lessons route prefixed by a course required parameter.
course parameter is a ID of a Course model that I want to use for route model binding.
But In the other hand when I want use course parameter for example in course_details function, it returns null. like this :
public function course_details (\App\Course $course)
{
dd($course);
}
But if I use below, all things worked fine :
public function course_details ($course)
{
$course = Course::findOrFail($course);
return $course;
}
Seems that it can not bind model properly.
What is Problem ?
Update :
In fact I'm using dingo-api laravel package to create an API. all routes defined based it's configuration.
But there is an issue about route model binding where to support route model binding we must to add a middleware named binding to each route that need model binding. HERE is described it.
A bigger problem that exists is when I want to add binding middleware to a route group, it does not work and I must add it to each of routes.
In this case I do not know how can I solve the problem.
Solution:
After many Googling I found that :
I found that must to add bindings middleware in the same route group that added auth.api middleware instead adding it to each sub routes separately.
means like this :
$api->group(['middleware' => 'api.auth|bindings'], function ($api) {
});
add in kernel.php
use Illuminate\Routing\Middleware\SubstituteBindings;
protected $routeMiddleware = [
...
'bindings' => SubstituteBindings::class,
];
and in your group route:
Route::middleware(['auth:sanctum', 'bindings'])->group(function(){
... you routes here ...
});
this worked for me. thanks
As you said
course parameter is a ID of a Course
You can use Request to get id, try like this
public function course_details (Request $request)
{
return dd($request->course);
}
I came across a similar issue. I think you need to use the 'bindings' middleware on your routes.
See my answer here:
https://stackoverflow.com/a/55949930/2867894
Take a close look on:
// Here $course is the id of the Course
public function course_details ($course)
{
$course = Course::findOrFail($course);
return $course;
}
But here:
// Here $course is the object of the model \App\Course
public function course_details (\App\Course $course)
{
dd($course);
}
that should be
public function course_details ($course, \App\Course $_course)
{
// add your model here with object $_course
// now $course return the id in your route
dd($course);
}
After searching for 2 hours I got the issue details
For route binding to work, your type-hinted variable name must match the route placeholder name
For example my edit method
Here is my route URI for the edit
user/role/{role}/edit
As you can see there is {role} placeholder in the route definition, so the corresponding variable must be called $role.
public function edit(Role $role)
{
return view('role.edit',compact('role'));
}
I am working on a very simple API using laravel. Users have many tasks, and a task belongs to a user. My routes file is as follows :
<?php
Route::resource('users', 'UsersController');
Route::get('users/{user_id}/tasks', 'UsersController#show_tasks');
Route::resource('tasks', 'TasksController');
I want that the second route gets all the tasks related to that particular user. It is simple. I just added a relationship in my User model as follows :
public function tasks(){
return $this->hasMany('Task');
}
And the show_tasks method in my UsersController is as follows :
public function show_tasks($id){
if(!$user = $this->user->find($id)){
return Acme::response('error', 'Resource not found.', [], 404);
}
$tasks = $user->tasks();
dd($tasks);
return Acme::response('success', [], $tasks, 200);
}
I have placed the dd method because the response was returning an empty array and I wanted to see what is in the $tasks variable first hand. I thought it must be a collection object but when I ran this in my Postman REST client, it hanged due to the large data overflow I am assuming, screenshot below :
If anyone is curious about the Acme::response method, I am also including that code :
public static function response($status = [], $messages = [], $data = [], $httpStatusCode){
return Response::json([
'status' => $status,
'messages' => $messages,
'data' => $data,
], $httpStatusCode);
}
Why is the relationship not working? I have been this for hours and still can't figure out why! I am guessing I might be doing a very silly mistake somewhere but can't get to it anyway!
You shouldn't use () here. Instead of:
$tasks = $user->tasks();
you will get your tasks using:
$tasks = $user->tasks;
EDIT
When you have relation and you use $user->tasks it works as you would use $user->tasks()->get() so we can say it's a shortcut.
You could use parantheses you get relation you can add more conditions, for example you could do something like that:
$tasks = $user->tasks()->active()->get();
and define scope in your Task model:
function activeScope($q) {
return $q->where('status',1);
}
And now you can get only active tasks for user.
I have some user data I need to share with all views, a list of 'types' owned by the user. I have setup a view::share to accomplish this, and placed it inside my routes.php inside an 'auth' filtered group like so:
routes.php
/* Authenticated group */
Route::group( array('before' => 'auth'), function() {
// Bring list of user's Types into views for sidebar menu generation
View::share( 'types', User::find( Auth::user()->id )->types );
/* Some user routes */
});
This works properly when the user is logged in, however when they are logged out it throws Trying to get property of non-object error. Am I misunderstanding how route filtering works? I'm guessing this View::share is still being processed even though it is inside the 'auth' filtered route group.
What is the proper way to do this? Should I create a Route::filter to share the data and then apply it to the route group?
You might be able to just share the variable in your filters instead.
Assuming you use Laravel's default auth filters (in filters.php):
Route::filter('auth', function()
{
if (Auth::guest()) {
View::share('types', 'not_logged_in');
return Redirect::guest('login');
}
else {
View::share('types', User::find( Auth::user()->id )->types);
// If you are not returning anything, this filter passes
}
});
It's because Auth::user() returns non-object when not logged in. Need to account for this.
if ( Auth::check() ){
View::share( 'types', User::find( Auth::user()->id )->types );
} else {
View::share( 'types', $some_other_types_here );
}
I'm stuck with this annoying problem in my laravel 4 project wich has three types of users being students, teachers and moderator (I use Entrust as role management solution).
Each of them can browse the same route, but depending on the user type, another method should be called. So my route.php files was structured like this:
Route::group(array('before' => 'auth'), function(){
Route::group(array('before' => 'teacher'), function(){
Route::get('/tasks',array('as'=>'tasks','uses'=>'TasksController#tasksAsTeacher'));
Route::get('/task/{id}',array('as'=>'showTask','uses'=>'TasksController#showTaskAsTeacher'));
});
Route::group(array('before' => 'moderator'), function(){
Route::get('/tasks',array('as'=>'tasks','uses'=>'TasksController#tasksAsModerator'));
Route::get('/task/{id}',array('as'=>'showTask','uses'=>'TasksController#showTaskAsModerator'));
});
Route::group(array('before' => 'student'), function(){
Route::get('/tasks',array('as'=>'tasks','uses'=>'TasksController#tasksAsStudent'));
Route::get('/task/{id}',array('as'=>'showTask','uses'=>'TasksController#showTaskAsStudent'));
});
});
However, browsing these routes with a teacher or moderator account always returned 404 errors. I found that was because the routes were redefined in the two other filter groups.
So if I would redirect a teacher user to 'showTask', laravel would return tasks as a route for students,as that's the last time the 'showTask' route was redefined, and I would get a 404 error.
My question now was: what would be the best way to handle this error?
I hope this isn't too messy. Thanks in advance!
Taking from #Matthias S's answer, does this work? Instead of using the entrust filter, check the permissions for the route like this:
//routes.php
if(Entrust::hasRole('teacher')) {
Route::get('/tasks',array('as'=>'tasks','uses'=>'TasksController#tasksAsTeacher'));
Route::get('/task/{id}',array('as'=>'showTask','uses'=>'TasksController#showTaskAsTeacher'));
}
Repeat for different roles
EDIT: Also if you had the role of the user stored in a session, you could use a sort of automatic route like this:
//routes.php
if(Entrust::hasRole(Session::get('role'))) {
Route::get('/tasks',array('as'=>'tasks','uses'=>'TasksController#tasksAs'.Session::get('role')));
Route::get('/task/{id}',array('as'=>'showTask','uses'=>'TasksController#showTaskAs'.Session::get('role')));
}
This way you can add as many roles as you want once you add the correct controller function for the role.
EDIT #2:
Or I guess even better
//routes.php - UPDATED, verify role inside controller instead of defining routes based on role
Route::get('/tasks',array('as'=>'tasks','uses'=>'TasksController#tasks'));
Route::get('/task/{id}',array('as'=>'showTask','uses'=>'TasksController#showTask'));
//TasksController.php
public function __construct(){
if(!Session::get('role')){ //Make sure the user has a role assigned
return Redirect::to('login'); // Redirect to login or permission screen if not
}
}
public function tasks(){
if(Entrust::hasRole(Session::get('role')){
$tasks = Tasks::where('role_id', '=', $role->id); // Get tasks based on role
View::make('tasks.index')->with('tasks', $tasks);
} else{
// Show permissions error for user
}
}
public function showTask($task_id){
if(Entrust::hasRole(Session::get('role')){
$task = Tasks::where('role_id', '=', $role->id)->where('id', '=', $task_id)->first();
View::make('tasks.view')->with('task', $task);
}
}
Move the secondary routes group out of the main auth group, then use the pipe command to run auth before for each group e.g.
Route::group(array('before' => 'auth|teacher'), function(){
Route::group(array('before' => 'auth|moderator'), function(){
I am not sure if your approach is a good way to get this done. I would think that defining the same routes twice is not good practice, but I have no idea if that is true.
One way to work around this could be that you define only two routes and let the controller decide which action to perform based on the role of the user. This is not a direct solution to your problem but another way of handling the issue of having different roles performing different controller actions.
Route::group(array('before' => 'auth'), function(){
Route::get('/tasks',array('as'=>'tasks','uses'=>'TasksController#tasks'));
Route::get('/task/{id}',array('as'=>'showTask','uses'=>'TasksController#showTask'));
});
Then in your TasksController, you make the methods tasks and showTask something like this
class TasksController extends BaseController {
public function tasks() {
if(Entrust::hasRole('teacher')) {
return $this->tasksAsTeacher();
} else if(Entrust::hasRole('moderator')) {
return $this->tasksAsModerator();
} else if(Entrust::hasRole('student')) {
return $this->tasksAsStudent();
}
}
public function showTask($id) {
if(Entrust::hasRole('teacher')) {
return $this->showTaskAsTeacher($id);
} else if(Entrust::hasRole('moderator')) {
return $this->showTaskAsModerator($id);
} else if(Entrust::hasRole('student')) {
return $this->showTaskAsStudent($id);
}
}
}
Just another way to do this, makes your routes cleaner and puts the logic into the controller.