How to design controllers communication - laravel

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.

Related

Laravel Route Model Binding Reusability

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();
}
}

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);
}

Models accessible only for authenticated user THAT CREATED THEM (Laravel)

I'm writing a software application to let the people have their own private archive of cooking recipes.
The RecipeController constructor contains:
$this->middleware('auth')
because only registered users can use recipes, but I need to protect also the access to the models.
The point is that users can view and modify only their own recipes.
Example: The user TortelliEngineer can create a recipe "Tortelli Secret Recipe" using the model Recipe; he can view, update and delete his recipe(s), but nobody else can see his precious "Tortelli Secret Recipe".
So, which is the cleanest way?
I added a user_id attribute to the model Recipe.
I must use this parameter every single time that I ask to the database for a Recipe (goodbye "findOrFail" by ID)
That means that every time I make a request I must access the Request object that contains User that contains User_id
using Auth::id() EVERY SINGLE TIME that I need one (or n) recipe
Like this:
class RecipeRepository{
public function all(){
return Recipe::where('user_id', Auth::id())
->orderBy('created_at', 'asc')
->get();
}
public function find($recipe_id){
return Recipe::where('user_id', Auth::id())
->where('id', $recipe_id)
->firstOrFail();
}
Is that correct? Do you hate me for this? Do you know better or more correct ways to do it?
Most of the time I make a method inside the model to check if someone is authorised, owner etc.. of something.
An example would be:
// User model
public function owns_recipe($recipe)
{
return ($recipe->user_id == $this->id);
}
You can call this at the very beginning in of the methods of your controller:
// Controller
public function index (Request $request)
{
$recipe = Recipe::find($request->id); // Get recipe
$user = ... // Get user somehow
if (!$recipe) App::abort(404); // Show 404 not found, or something
if (!$user->owns_recipe($recipe)) App::abort(403); // Show 403 permission denied, or something
... // Do whatever you want :)
}
While there are many ways of approaching this, Laravel does provide some built-in methods for handling general authentication of actions. In the first place I'd do something along the lines of what you intended (have a getRecipesByOwner method in RecipeRepository) and you can pass the user to it from the injected Request object:
// RecipeController
public function index(Request $request)
{
$recipes = $this->recipeRepo->findRecipesByOwner($request->user());
}
In addition though, I'd recommend creating policies to manage whether or not a user is capable of updating/deleting/viewing individual recipes. You can then authorize their actions in the controllers/blade templates/etc. via built-in methods like:
// Controller
public function update(Request $request, Recipe $recipe)
{
$this->authorize('update', $recipe);
}
// Blade template
#can('update', $recipe)
#endcan
The documentation is available at: https://laravel.com/docs/5.3/authorization#creating-policies

Laravel - Use Class like Auth

I have a Class Post. Created the Model, Restfull Resource Controller.
Now i implemented the show function in my Resource Controller.
So if someone routes to /post/234234 he will automatically see the post.
But now I want to create a new static function on myself. Let's call it myFunction(). I don't like to hand over the id all the time. Isn't there a possibilty to create a object or something like that, so I can use this like that Post::myFunction(), without hand over the id all the time.
It's similar to the Auth Class. I mean I can check if the user is logged in just like that Auth::check().
Update
Let's describe it more clearly. This is my PostController#show:
public function show($id)
{
//Get board information
$post = Post::find($id);
//Return view
return view('post')->with(array(
'posts' => $post
));
}
It shows just the post. In my view I have a button called Follow up. I only want to show it, if he didn't follow up the post yet. Now i thought about to edit the Model Post.php and add a static function followed() so i can just do this in the view:
#if(Post::followed())
<button>Follow up</button>
#endif
Yes. You can simply create a static function:
public static function myFunction(){
}
Just note that this function will be called in static context so you there's no $this available.
Alternatively you can create a Facade (Auth is a Facade). The process is described pretty well in the documentation about facades
As for your specific case I don't even think a static method is needed. Since you pass $post to the view you can just add a regular public method:
public function isFollowing(){
}
And then do this in your view:
#if(!$post->isFollowing())
<button>Follow up</button>
#endif

Resources