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
Related
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)
Info: All my routes look like this /locale/something for example /en/home
works fine.
In my controller I'm using the firstOrFail() function.
When the fail part is triggered the function tries to send me to /home.
Which doesn't work because it needs to be /en/home.
So how can I adjust the firstOrFail() function to send me to /locale/home ?
What needs to changed ?
You can treat it in several ways.
Specific approach
You could surround your query with a try-catch wherever you want to redirect to a specific view every time a record isn't found:
class MyCoolController extends Controller {
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\Redirect;
//
function myCoolFunction() {
try
{
$object = MyModel::where('column', 'value')->firstOrFail();
}
catch (ModelNotFoundException $e)
{
return Redirect::to('my_view');
// you could also:
// return redirect()->route('home');
}
// the rest of your code..
}
}
The only downside of this is that you need to handle this everywhere you want to use the firstOrFail() method.
The global way
As in the comments suggested, you could define it in the Global Exception Handler:
app/Exceptions/Handler.php
# app/Exceptions/Handler.php
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\Redirect;
// some code..
public function render($request, Exception $exception)
{
if ($exception instanceof ModelNotFoundException && ! $request->expectsJson())
{
return Redirect::to('my_view');
}
return parent::render($request, $exception);
}
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 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');
}
I am using zend framework and this is the skeleton of my model and controller methods
Model Class methods :
_validateRegisterForm($postData)
{
//validating data using Zend_Filter_Input
//Returns instance of Zend_Filter_Input
}
//Return true/false
protected _isNumberAlreadyExists()
{
// I dnt want to perform above validation using zend_validate_db_recordexists
//since i dnt want to mix my dblogic with in buisness logic
}
register($postData)
{
$input=$this->_validateRegisterForm($postData);
if(!$input->isValid())
{
//What should it return to controller
}
if(!$this->_isNumberAlreadyExists($input->number))
{
//What should it return to controller
}
}
controller class api
$this->_model->register($postData)
I want given below condition to return error in same format to controller class
if(!$input->isValid())
{
//What should it return to controller
}
if(!$this->_isNumberAlreadyExists($input->number))
{
//What should it return to controller
}
I can simply return false from model class but for that i have to save zend_filter_Input instance or error message(if number already exists).Thus in that case i have to create another error handler object which can convert error messages into common format and let controller to fetch those error
To me it does not look like a proper way of doing things in zend framework.
Would somoneone like to help me about passing error from model to controller in zend framework or suggest some other method to handle errors in zend framework
Two notes before we start:
First of all, the Model is not a class, but a Layer.
The code below is not ZF specific
To solve your problem you can do
public function register()
{
$this->assertThis($postData);
$this->assertThat($postData);
// do something with $postData
}
The methods assertThis and assertThat are your validation methods, just that instead of returning something, they will throw an appropriate Exception. Since register is a Command, it should not return anything at all.
In your controller, you simply catch the Exceptions and convert them into a format usable by your Presentation Layer (which is what C+V are)
public function registerAction()
{
try {
$this->foo->register($this->getRequest()->whatever());
} catch (ThisException $e) {
// handle here
} catch (ThatException $e) {
// handle here
}
}
Another option would be to collect any errors inside the object doing the registration. You'd then have to return of course and provide a method on that object to get the errors. So it adds cruft to your objects.
public function register()
{
if ($something === false) {
$this->errors = 'Something was false';
return false;
}
// do something with $postData
}
Your controller would then do
public function registerAction()
{
if (false === $this->foo->register($this->whatever())) {
$errors = $this->foo->getErrors();
// do something with $errors
}
}
A third option would be to use a Notification pattern
An object that collects together information about errors and other information in the domain layer and communicates it to the presentation.
See http://martinfowler.com/eaaDev/Notification.html for an in-depth explanation.