I have this update function I'm aware the findOrFail call returns a model not found exception sent to the user. Is there a way you can add some code to catch this if an ID is not found or does the call do that for me? Here is the function it needs applying too.
public function update(string $id)
{
$this->user = Auth::user();
$this->film = Film::findOrFail($id);
if (!$this->hasFilm()) {
abort(400, "You don't have a film configured");
}
$this->validation();
$this->transaction();
return $this->film->toJson();
}
You can manually do a try {...} catch {...} in that function.
If you want to do that for all the places that will throw ModelNotFoundException, you can look at app/Exceptions/Handler.php and change the render method
However, if you see you're doing this for many other exceptions, it's better if you create your own exceptions, as documented in the docs
If you want to throw a custom abort when the PodcastProfile is not found, just for this method, you can do something like:
public function update(string $id)
{
$this->user = Auth::user();
$this->podcastProfile = PodcastProfile::find($id);
if (!$this->podcastProfile) {
abort(400, "You don't have a podcast profile configured");
}
$this->validation();
$this->transaction();
return $this->podcastProfile->toJson();
}
If the PodcastProfile is not found it'll be empty, and then you'll get the abort(400)
Related
I am trying to apply a principle I read on Uncle Bob's book "Clean Code", which states that the optimal amount of parameters for a method is zero. I found this is very hard to accomplish, but anyways here I am trying it and I'd like your help to understand the best way to come closer to it in my code.
So far, I have a controller and a simple service to do the job I want. My controller is implemented as follows:
<?php
namespace App\Http\Controllers\Cms;
use App\Http\Controllers\Controller;
use App\Http\Requests\GroupRequest;
use App\Models\Group;
use App\Services\GroupsService;
use Illuminate\Http\Request;
class GroupsController extends Controller
{
protected $group_service;
public function __construct(GroupsService $group_service)
{
$this->group_service = $group_service;
}
public function index()
{
$groups = $this->group_service->listAll();
return view('cms.groups.index', compact('groups'));
}
public function store(GroupRequest $request)
{
$result = $this->group_service->createGroupWith($request->all());
return redirect()->back()->with('message', $result['msg']);
}
public function update(GroupRequest $request, Group $group)
{
$result = $this->group_service->updateAGroupWith($request->all(), $group);
return redirect()->back()->with('message', $result['msg']);
}
}
And bellow is my service
<?php
namespace App\Services;
use App\Models\Group;
use Illuminate\Support\Facades\DB;
class GroupsService
{
public function listAll()
{
$groups = Group::all();
return $groups;
}
public function createGroupWith($data)
{
try {
DB::beginTransaction();
$modules_id = array_pop($data);
$group = Group::create($data);
$group->modules()->attach($modules_id);
DB::commit();
return ['msg' => 'Grupo criado com sucesso!'];
} catch (\Throwable $error) {
DB::rollBack();
return ['msg' => $error->getMessage()];
}
}
public function updateAGroupWith($data, $group)
{
try {
DB::beginTransaction();
$modules_ids = array_pop($data);
$group->update($data);
$group->modules()->sync($modules_ids);
DB::commit();
return ['msg' => 'O grupo foi atualizado com sucesso!'];
} catch (\Throwable $error) {
DB::rollBack();
return ['msg' => $error->getMessage()];
}
}
}
As you can see, my service is instanciated by Laravel on the controller's __construct method and is used on the rest of my methods. The problem with this is that when storing or updating a register, I need to pass the data and (for the update) the gruop I want to update. This violates Uncle Bob's ideal of zero parameters and thus my question: is there a way to solve this?
I have come up with some ideas. The first one would be to continue instanciating the service as it is already done, but having properties such as group and data defined before calling the method I want. Something as implemented bellow. In this case the problem is that if I want to be really idealistic, I don't if I can keep accessing properties from an object and changing them, I believe I'd need to instanciate the object with it's state already defined anyways.
public function update(GroupRequest $request, Group $group)
{
$this->group_service->group = $group;
$this->group_service->data = $request->all();
$result = $this->group_service->updateAGroup();
return redirect()->back()->with('message', $result['msg']);
}
The second ideia would be to instanciate the service as I need it on each method:
public function update(GroupRequest $request, Group $group)
{
$service = new GroupService($data, $group);
$result = $service->updateAGroup();
return redirect()->back()->with('message', $result['msg']);
}
The only problem with the idea above is that in the fist method, the one for storing, I'd need to make the $group parameter for the $service = new GroupService($data, $group); statement optional. But I don't know if that's really a problem or a whim of mine.
And there's a third idea, but it seems way too much. It would be to make have a service per case (storing, updating and deleting maybe) and then I wouldn't have to worry with having optional parameters on the __construct method.
Anyways, I'd like to know your ideas, comments, and critiques too. Please take into consideration that I know this is very idealistic, but I'd really like to know how to come closer to it using with the patterns already defined on Laravel.
Thanks in advance.
When I try to put the redirect inside of a method, it won't redirect, but if I put the redirect directly, it will redirect. Please see my code below.
Checking method – This method will redirect when session is null
private function loadIdChecker()
{
if(session('load_id') === null) {
return redirect()->route('load-calculator');
}
}
Users view method (NOT WORKING)
public function users()
{
$this->loadIdChecker();
... The rest of the code here
}
Users view method (WORKING)
public function users()
{
if(session('load_id') === null) {
return redirect()->route('load-calculator');
}
}
Try: return $this->loadIdChecker(); instead of just $this->loadIdChecker(); in your users() function.
The bug occurs because in your original code the users function is not returning anything while it is expected to return something...
Redirects only work properly when you give the redirect instance to the response handler of Laravel. Right now, it just returns back an instance of Illuminate\Http\RedirectResponse. So, if for some reason, you don't wish to change the method body of load checker, you could just add an additional change as to returning true or whatever at the end to judge the difference in the response as below:
private function loadIdChecker(){
if(session('load_id') === null) {
return redirect()->route('load-calculator');
}
return true;
}
Then, in your code, you could check like below:
public function users()
{
$returned_val = $this->loadIdChecker();
if($returned_val instanceof Illuminate\Http\RedirectResponse){
return $returned_val;
}
// The rest of the code goes here
}
The best way to tackle this is to have a middleware that checks for load_id and redirects to the load-calculator page if load_id is null. Handling redirection via a method is not recommended, especially if it's some kind of auth check.
You will have to add a return statement (and maybe also take into account a case where load_id !== null).
public function users()
{
return $this->loadIdChecker();
}
this works for me
Public function index()
{
echo this->checksession();
"Your other code"
}
public function checksession()
{
if(Session::get('your_id')== '')
{
return redirect('Yoururl')->with('msg','your message')
}
}
logic is just remove return and add echo it will show a redirect link but it works
I have a custom non-Eloquent class called Item. In the constructor, the Item attempts to authenticate with my app. I then have an Auth() method to check whether the authentication was successful, and if not to fire a redirect.
public function Auth() {
if (isset($this->authenticated)) {
return $this;
} else {
return redirect('/auth');
}
}
I use the Auth method to access Item statically via a Item Facade. The intended outcome is that if the Item is authenticated, we proceed to the Index with that Item as a variable. If not the redirect would be triggered.
public function index() {
$item = Item::Auth();
return view('index',$item);
}
However, if not authenticated, all that happens is a Laravel RedirectResponse object is passed to the index view. How can I force that redirect to actually fire?
Solutions I have thought of but don't like
if ($item instanceOf RedirectResponse)... in the controller, but this feels clunky
$item = new Item; if ($item->authenticated)... this is fine for the controller but I want to trigger the redirect from multiple parts of the app so there would be a lot of code re-use which doesn't feel efficient.
Thanks for any help. Either on the Facade front or firing the Redirect object.
You can handle this case by using an AuthenticationException:
public function Auth() {
if (isset($this->authenticated)) {
return $this;
}
throw new AuthenticationException();
}
The authentication exception will generate either a redirect to login or a 401 error on JSON routes. You can of course throw a different exception and handle it as a redirect in your App\Exceptions\Handler class
For example:
public function Auth() {
if (isset($this->authenticated)) {
return $this;
}
throw new ItemNotAuthorizedException(); //Custom class you need to create
}
In Handler.php:
public function render($request, Exception $exception)
{
if ($exception instanceof ItemNotAuthorizedException) {
return redirect("/auth");
}
return parent::render($request, $exception);
}
Note: Laravel 5.6 might also allow exceptions to implement their own render function but you should refer to the documentation for that.
I want suggestion how to handle and which method is best one. Implicit Binding or Normal Binding method.
I'm using Laravel route implicit binding. when I post wrong ID, I got error No query results for model how to handle in controller not Exception Handler. Now I done with exception handler but need better solution to handle this or avoid Implicit binding.
//Web.php
Route::delete('/master/user/department/{department}/delete', ['as' => 'master.user.department.destroy', 'middleware' => 'permission:master.user.department.destroy', 'uses' => 'Master\User\DepartmentController#destroy']);
//DepartmentContrller.php
public function destroy(Department $department)
{
try {
$department->delete();
return redirect(route('master.user.department.index'))->with('success', array(' Department Deleted successfully'));
} catch (Exception $e) {
return back()->with('criticalError', array($e->getMessage()));
}
}
//Handler.php
if ($exception instanceof \Illuminate\Database\Eloquent\ModelNotFoundException)
{
return redirect()->back()->with('custom_modal', ['Model Not Found Exception', $exception->getMessage()]);
}
The below code is perfectly work, I would like to know which method is best one.
//DepartmentContrller.php
public function destroy($id)
{
try {
$department=Department::find($id);
if($department){
$department->delete();
return redirect(route('master.user.department.index'))->with('success', array(' Department Deleted successfully'));
}
else{
return back()->with('criticalError', array('Department is not found.'));
}
} catch (Exception $e) {
return back()->with('criticalError', array($e->getMessage()));
}
}
Both methods are valid. It is up to you to choose which method is appropriate for your situation.
Implicit model binding will let you get code out the door quicker, but you give up some control.
Explicit (normal) binding will take more code to write, but you have complete control over how the exceptions are caught and handled.
Just an FYI, if you stick with implicit binding, the ModelNotFoundException has a getModel() method that will give you the name of the model that caused the exception. This will let you customize your exception handling a little bit more, but still doesn't give you the same control as handling the exception where it happens.
All of above method be work for you but you can override elqoent method --find() in case of you in your respective model
// Put this in any model and use
// Modelname::find($id);
public static function findOrCreate($id)
{
$obj = static::find($id);
return $obj ?: new static;
}
in depth description
i have this blogsController, the create function is as follows.
public function create() {
if($this->reqLogin()) return $this->reqLogin();
return View::make('blogs.create');
}
In BaseController, i have this function which checks if user is logged in.
public function reqLogin(){
if(!Auth::check()){
Session::flash('message', 'You need to login');
return Redirect::to("login");
}
}
This code is working fine , but it is not what is need i want my create function as follows.
public function create() {
$this->reqLogin();
return View::make('blogs.create');
}
Can i do so?
Apart from that, can i set authantication rules , like we do in Yii framework, at the top of controller.
Beside organizing your code to fit better Laravel's architecture, there's a little trick you can use when returning a response is not possible and a redirect is absolutely needed.
The trick is to call \App::abort() and pass the approriate code and headers. This will work in most of the circumstances (excluding, notably, blade views and __toString() methods.
Here's a simple function that will work everywhere, no matter what, while still keeping your shutdown logic intact.
/**
* Redirect the user no matter what. No need to use a return
* statement. Also avoids the trap put in place by the Blade Compiler.
*
* #param string $url
* #param int $code http code for the redirect (should be 302 or 301)
*/
function redirect_now($url, $code = 302)
{
try {
\App::abort($code, '', ['Location' => $url]);
} catch (\Exception $exception) {
// the blade compiler catches exceptions and rethrows them
// as ErrorExceptions :(
//
// also the __toString() magic method cannot throw exceptions
// in that case also we need to manually call the exception
// handler
$previousErrorHandler = set_exception_handler(function () {
});
restore_error_handler();
call_user_func($previousErrorHandler, $exception);
die;
}
}
Usage in PHP:
redirect_now('/');
Usage in Blade:
{{ redirect_now('/') }}
You should put the check into a filter, then only let the user get to the controller if they are logged in in the first place.
Filter
Route::filter('auth', function($route, $request, $response)
{
if(!Auth::check()) {
Session::flash('message', 'You need to login');
return Redirect::to("login");
}
});
Route
Route::get('blogs/create', array('before' => 'auth', 'uses' => 'BlogsController#create'));
Controller
public function create() {
return View::make('blogs.create');
}
We can do like this,
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect('/to/another/route/')->with('status', 'An error occurred.'));
It's not a best practice to use this method, but to solve your question, you can use this gist.
Create a helper function like:
if(!function_exists('abortTo')) {
function abortTo($to = '/') {
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect($to));
}
}
then use it in your code:
public function reqLogin(){
if(!Auth::check()){
abortTo(route('login'));
}
}
public function create() {
$this->reqLogin();
return View::make('blogs.create');
}