Laravel Route Model Binding Reusability - laravel

I'm creating a like feature for my application where users can like posts, events, products etc.
I have a LikeController and a Trait which handles the like functionality
public function store(Post $post)
{
$post->like();
return back();
}
The code above is to like a post
I don't want to duplicate code and create separate functions to perfrom the same exact thing on events or products and I was wondering how to perform route model binding and get the application to just execute the one function, passing model information depending on what is being liked post, event or product.
The following code works fine but does not implement the DRY principle
public function store(Post $post)
{
$post->like();
return back();
}
public function store_event(Event $event)
{
$event->like();
return back();
}
The followinf is the trait
trait LikeTrait
{
public function getLikesCountAtrribute()
{
return $this->likes->count();
}
public function like()
{
if (!$this->likes()->where(['user_id' => auth()->id()])->exists()) {
$this->likes()->create(['user_id' => auth()->id()]);
}
}
public function likes()
{
return $this->morphMany(Like::class, 'likeable');
}
public function isLiked()
{
return !!$this->likes->where('user_id', auth()->id())->count();
}
}
My web routes file is as follows
Route::post('post/{post}/likes', 'LikeController#store');
Route::post('event/{event}/likes', 'LikeController#store_event');
So the outcome I want is to call the same method and pass the relevant model.
Thanks in advance

You can have a specific route for such actions, may it be 'actions':
Route::post('actions/like','ActionsController#like')->name('actions.like');
Then in the request you send the object you wish to perform the action on, i personal have it hashed, the hash contains the ID and the class_name (object type) in an stdClass.
That's how i personally do it:
Every Model i have inherits Base Model, which contains hash attribute, which contains
$hash = new stdClass;
$hash->id = $this->id;
$hash->type = get_class($this);
return encrypt($hash);
This will return a string value of what's there, and encrypted, you can have a password for that as well.
Then you let's say you have the like button inside a form or javascript you can do that:
<form action="{{ route('actions.like') }} method="post">
<input type="hidden" name="item" value="{{ $thisViewItem->hash }}">
<button type="submit">Like</button>
</form>
Doing so, when liking an object you send the hashed string as the data, thus getting a request of $request->get('item') containing the object (id and type). then process it in the controller however you like.
If you're sending this through javascript you may want to urlencode that.
then in ActionsController#like you can have something like that:
$item = decrypt($request->get('item'));
# Will result in:
# Item->id = 1;
# Item->type = 'App\Post';
$Type = $Item->type;
# Build the model from variable
# Get the model by $item->id
$Model = (new $Type)->find($item->id);
# Like the model
$Like = $Model->like();
// the rest...
I personally prefer to combine and encrypt the id+type in a string, but you can send the id and type in plain text and have a route named like so:
Route::post('actions/like/{type}/{id}','ActionsController#like');
Then build the model from the Type+ID followed by what you have in trait ($Model->like());
It's all up to you, but i'm trying to hint that if you want to reuse the like action in many places, you may want to start building the logic starting from the action itself(likes, comments) not from the target (posts, events).
The codes i placed in here are written in here and not pasted from what i actually do, I'm trying to get you the concept. You can write it however you prefer.

I don't know whether this is gonna work, but please have a go.
You could try explicit binding. Lets define explicit bindings in the App\Providers\RouteServiceProvider:
public function boot()
{
parent::boot();
Route::model('post', App\Post::class);
Route::model('event', App\Event::class);
}
Then defining the routes:
Route::post('/posts/{post}/likes', 'LikesController#store');
Route::post('/events/{event}/likes', 'LikesController#store');
Finally in the controller:
class LikesController extends Controller
{
public function store($object)
{
$object->like();
}
}

Related

How to use model class to edit , destroy and get single value in laravel 8 resource controller?

I want to develop an API with Laravel 8 with resource controller.
Previously we used id parameter to edit, delete and get single value from the database. But now, here is given model class as a parameter in show, edit, update and destroy method.
How can I use this model class to perform crud operations without id parameter?
I know I’m on a misconception and I want to get a clear idea.
public function show(Food $food)
{
//
}
public function edit(Food $food)
{
//
}
public function update(Request $request, Food $food)
{
//
}
public function destroy(Food $food)
{
//
}
This is just a better way of retrieving your data.
Instead of writing:
public function show($id)
{
echo $id; // 12
$food = Food::find($id); // your food instance with id 12
echo $food->id; //12
}
You write:
public function show(Food $food)
{
$food; // your food instance with id 12
echo $food->id; //12
}
Laravel will match the parameter name of your route with the argument name in your controller method declaration and will automatically gives you the correct Food instance.
Your routes should looks like this:
Route::get('foods/{food}', [FoodController::class, 'show'])->name('foods.show');
// for each verbs (index, show, update...)
// the "food" parameter will be internally mapped
// to the $food argument inside your controller methods declaration
// or even simpler:
Route::resource('foods', FoodController::class);
// which will declare all routes for this resource
This is called implicit model binding. The documentation on this topic can be find here: https://laravel.com/docs/8.x/routing#implicit-binding

Returning same variable to every controller in laravel

I need to send the same result to almost every view page, so I need to bind the variables and return with every controller.
My sample code
public function index()
{
$drcategory = DoctorCategory::orderBy('speciality', 'asc')->get();
$locations = Location::get();
return view('visitor.index', compact('drcategory','locations'));
}
public function contact()
{
$drcategory = DoctorCategory::orderBy('speciality', 'asc')->get();
$locations = Location::get();
return view('visitor.contact', compact('drcategory','locations'));
}
But as you see, I need to write same code over and over again. How can I write it once and include it any function whenever I need?
I thought about using a constructor, but I cannot figure out how I can implement this.
You are able to achieve this by using the View::share() function within the AppServicerProvider:
App\Providers\AppServiceProvider.php:
public function __construct()
{
use View::Share('variableName', $variableValue );
}
Then, within your controller, you call your view as normal:
public function myTestAction()
{
return view('view.name.here');
}
Now you can call your variable within the view:
<p>{{ variableName }}</p>
You can read more in the docs.
There are a few ways to implement this.
You can go with a service, a provider or, like you said, within the constructor.
I am guessing you will share this between more parts of your code, not just this controller and for such, I would do a service with static calls if the code is that short and focused.
If you are absolutely sure it is only a special case for this controller then you can do:
class YourController
{
protected $drcategory;
public function __construct()
{
$this->drcategory = DoctorCategory::orderBy('speciality', 'asc')->get();
}
// Your other functions here
}
In the end, I would still put your query under a Service or Provider and pass that to the controller instead of having it directly there. Maybe something extra to explore? :)
For this, you can use View Composer Binding feature of laravel
add this is in boot function of AppServiceProvider
View::composer('*', function ($view) {
$view->with('drcategory', DoctorCategory::orderBy('speciality', 'asc')->get());
$view->with('locations', Location::get());
}); //please import class...
when you visit on every page you can access drcategory and location object every time
and no need to send drcategory and location form every controller to view.
Edit your controller method
public function index()
{
return view('visitor.index');
}
#Sunil mentioned way View Composer Binding is the best way to achieve this.

Laravel Backpack - getting current record from crud controller

In my crud controller I am trying to get the name of the person who is currently being edited.
so
http://192.168.10.10/admin/people/93/edit
In the people crud controller
public function setup() {
dd(\App\Models\People::get()->first()->name)
}
This returns the first person not the person currently being edited.
How do I return the current person (with an id of 93 in this example)
Ok, So since you use backpack look into CrudController to see how the method looks:
public function edit($id)
{
$this->crud->hasAccessOrFail('update');
$this->data['entry'] = $this->crud->getEntry($id);
$this->data['crud'] = $this->crud;
$this->data['fields'] = $this->crud->getUpdateFields($id);
$this->data['id'] = $id;
return view('crud::edit', $this->data);
}
So now you can overwrite the edit function and change whatever you want. You can even create a custom edit page if you so wish.
Setup on the other hand is usually used to add things like
$this->crud->addClause(...);
Or you can even get the entire constructor and put it in the setup method because setup call looks like this:
public function __construct()
{
// call the setup function inside this closure to also have the request there
// this way, developers can use things stored in session (auth variables, etc)
$this->middleware(function ($request, $next) {
$this->setup();
return $next($request);
});
}
So you could do something like \Auth::user()->id;
Also it's normal to work like this. If you only use pure laravel you will only have access to the current id in the routes that you set accordingly.
Rahman said about find($id) method. If you want to abort 404 exception just use method findOrFail($id). In my opinion it's better way, because find($id)->name can throw
"Trying to get property of non-object error ..."
findOrFail($id) first fetch user with specified ID. If doesn't exists just throw 404, not 500.
The best answer is:
public function edit($id)
{
return \App\Models\People::findOrFail($id);
}
Good luck.
you need person against id, try below
public function setup($id) {
dd(\App\Models\People::find($id)->name);
}

How to design controllers communication

Lets say I have UserControler that handles user creation deletion etc and Comments conntroler that handles comment's adding deleting modyfing etc.
What If my user wants to add a comment? Should the userController have addComment method? Or should I handle this in commentsController(if so how do I pass user data)?
Maybe I don't need commentsController at all?
How do I design it properly according to MVC(I am using laravel)?
You can always get the authenticated user info using these methods:
//Will return the authenticated User object via Guard Facade.
$user = \Auth::user();
//Will return the User Object that generated the resquest via Request facade.
$user = \Request::user();
If you set your route to something like this:
Route::get('posts/{posts}/comments/create', 'CommentsController#create');
Then you can create a button (i'll use bootstrap here and hipotetical ids) that points to:
Create
On your CommentsController you can have something like this:
public function create($post_id)
{
$user = .... (use one of the methods above);
$post = ... (get the post to be commented, if thats the case)
... Call the create comment function
return redirect(url('posts/9'));
}
Immediate answer would be CommentController , this is the controller that should add/delete/edit comments.
Can any one else add/delete/edit comments other than users? If yes, are they going to go into same business/domain object?
Lets say if you have User Comments and Customer Comments have separate Business/Domain comment objects , in this case you may have separate UserCommentsController and CustomerCommentsController.
And as #Arthur Samarcos suggested you can get user info.
In a case like this where each comment belongs to only one user, I would set that up in the comment controller because the user id is really just another attribute of that comment.
Additionally, I find it best to abstract this logic to a repository in the case you will need to eventually create a comment from another controller or somewhere else in your app. Maybe if the user takes some action you want to auto-generate comments when those actions are taken. The repository could look like this...
class CommentRepository {
protected $comment;
public function __construct(Comment $comment)
{
$this->comment = $comment;
}
public function newComment($user_id, $content)
{
$comment = $this->comment->newInstance();
$comment->user_id = $user_id;
$comment->content = $content;
$comment->save();
return $comment;
}
}
Then you'd inject that repository into your controller which would look something like this...
class CommentController extends BaseController {
protected $cr;
public function __construct(CommentRepository $cr)
{
$this->cr = $cr;
}
public function create()
{
$comment = $this->cr->newComment(Auth::user()->id, Input::get('content'));
return Redirect::route('comments.index');
}
}
There are a few benefits to this approach. One as I said earlier, it makes your code reusable and easy to understand. All you need to do is inject the repository into your controller where you need it. Two is it becomes much more testable.

Controller method not called in laravel 4

I'm trying to learn laravel 4. I created a form(using view) and returned it via a controller(testController) using index method. I had created this controller using artisan command.
i created another method (dologin) in the controller which would process the form. In the form url parameter i gave the address of dologin method.
This is the route:
Route::resource('test', 'testController');
This is the controller
<?php
class testController extends \BaseController {
public function index()
{
return View::make('test.index');
}
public function dologin(){
echo "working";
}
and this is the index view file
{{ Form::open(array('url'=>'test/loginform')) }}
{{ Form::text('username', null, array('placeholder'=>'Username')) }}<br/>
{{ Form::password('password', array('placeholder'=>'Password')) }}<br/>
{{ Form::submit('Login') }}
{{ Form::close() }}
After submitting form, it should echo "working" in the browser. But after submitting the form, page is blank. The url changes though from
/laravel/public/index.php/test/
to
/laravel/public/index.php/test/loginform
umefarooq's answer is correct, however hopefully this answer should give you a bit more insight into getting a head-start in your Laravel development as well as a consistent best-practice programming style.
Firstly, class names should really start with a capital letter. Try to keep methods / function names starting with a lower case letter, and class names starting with a capital.
Secondly, you don't need the \ in front of BaseController. You only need the backslash if you are name-spacing your controller. e.g. if your controller is in the folder Admin\TestController.php, and you put your TestController in the Admin namespace by typing <?php namespace Admin at the beginning of the file. This is when you should use \BaseController because you are telling your TestController to extend BaseController from the Global Namespace. Alternatively, before you declare your class, you can type use BaseController; and you don't need to put a \ in every time.
Specifically related to your question:
When you use resource routes in your routes file, you are telling Laravel that the controller can have any or all of the following methods: index, show, create, store, edit, update and destroy.
As such, Route::resource('test', 'TestController'); will point to TestController.php inside your controllers folder.
Your TestController should be structured as follows, most restful controllers will use the below as some kind of boilerplate:
<?php
class TestController extends BaseController
{
public function __construct()
{
}
// Typically used for listing all or filtered subset of items
public function index()
{
$tests = Test::all();
return View::make('test.index', compact('tests'));
}
// Typically shows a specific item detail
public function show($id)
{
$test = Test::find($id);
return View::make('test.show', compact('test'));
}
// Typically used to show the form which creates a new resource.
public function create()
{
return View::make('test.create');
}
// Handles the post request from the create form
public function store()
{
$test = new Test;
$test->attribute1 = Input::get('attribute1');
$test->attribute2 = Input::get('attribute2');
$test->attribute3 = Input::get('attribute3');
$test->attribute4 = Input::get('attribute4');
if ($test->save())
{
return Redirect::route('test.show', $test->id);
}
}
// Shows the edit form
public function edit($id)
{
$test = Test::find($id);
return View::make('test.edit', compact('test'));
}
// Handles storing the submitted PUT request from the edit form.
public function update($id)
{
$test = Test::find($id);
$test->attribute1 = Input::get('attribute1');
$test->attribute2 = Input::get('attribute2');
$test->attribute3 = Input::get('attribute3');
$test->attribute4 = Input::get('attribute4');
if ($test->save())
{
return Redirect::route('test.show', [$id]);
}
}
// Used to delete a resource.
public function destroy($id)
{
$test = Test::find($id);
$test->delete();
return Redirect::route('test.index');
}
}
Also, the beauty of using Resource Controllers is that you can take advantage of named routes.
in the terminal window, type in php artisan routes.
You should see 7 named routes.
test.index
test.destroy
test.show
test.edit
test.destroy
test.create
test.update
So within your form, instead of doing
{{ Form::open(array('url'=>'test/loginform')) }} you can point the url to a named route instead:
{{ Form::open(array('route' => array('test.store')) }}
That way if you ever change the url, or need to move around your site structure, this will be easy, because the forms post url will auto bind to the named route within the routes file. You wont need to update every single one of your views to ensure that the url's are pointing to the correct location.
Finally, as a starting point, I would recommend using JefreyWay/Laravel-4-Generators package. https://github.com/JeffreyWay/Laravel-4-Generators . Use them to create your resources, controllers, views etc. and see how the generators scaffold your models, views and, controllers for you.
Here is another resource to help you get started:
https://laracasts.com/lessons/understanding-rest
Route::resource('test', 'testController');
will work for RESTful method of controller, like index, edit, destroy, create and now you are using custom method of controller for this you need to create another route
Route::post("test/loginform",'testController#dologin');
hope this will work for you. read route documentation http://laravel.com/docs/routing
In addition to what umefarooq said, which is 100% accurate. You need to look into flash messages as well.
public function dologin(){
//do login verification stuff
If login validated
Return redirect::to(logged/page)->with('message', 'You're logged in');
If login failed
Return redirect::to('test')->with('message', 'You login credentials fail');
}
For further research:
http://laravel.com/docs/responses

Resources