Laravel 8 - Using custom exceptions - php

I have a question about error reporting in Laravel 8.
According to the documentation, it should be possible to implement custom report() or render() methods in a custom exception.
I have created a custom exception as follows:
class AdminException extends Exception
{
protected $userId;
public function __construct($message='', $userId=0) {
parent::__construct($message);
$this->setUserId($userId);
}
public function getUserId() {
return $this->userId;
}
private function setUserId($userId) {
return $this->userId = $userId;
}
public function report(){
dd('in custom admin exception report');
}
public function render($request){
dd('in custom admin exception render');
}
}
The issue is, when I throw this error intentionally from a controller, I am not hitting any of these methods in the exception.
To try and compensate for that problem, I added methods in the Handler.php file as follows:
$this->reportable( function(AdminException $e){
dd('in reportable method from handler');
});
$this->renderable( function(AdminException $e, $request){
dd('in renderable method from handler');
});
but neither of these methods are being hit either. Does anyone have experience with custom exceptions in Laravel 8? The documentation is very unclear, how is this meant to work? Should I just use the report() and render() methods of the Handler.php file?
Thanks!

I am answering this in case anyone else stumbles upon this problem.
After tweeting back and forth with Taylor Otwell himself, I got some clarification on the subject.
When using the Hander.php file in Laravel, you can use the report() method but it is important to call parent::report($e) before doing anything else. Without doing this, the reporting logic will not be hit.
However, the preferred behavior is to not use the report() method in Handler.php but instead to register report or render callbacks in the register() method of Handler.php.
Just to clarify, it is possible to implement the report() method in Handler.php as a fallback but the parent::report($e); method must be called in the report() method.

Related

how to run method in model instead of controller in laravel 5.1?

I have following method in my controller:
public function store()
{
$data=Input::all();
User::create($data);
}
The above code works perfectly. My question is can we run the above method in model without writing in controller? And which is the best approach?
you can try following way
in your model
public function insetUser()
{
$input = Input::all();
User::create($input);
//here instead of User,you can use self like self::create($input);
}
in controller you can
public function store()
{
User::insetUser();
}
If it is in model, how you are going to trigger it?
It is in fact only one line of code
User::create(Input::all());
What it is here is the instance of model User and method create with injected model Input. Of couse you may set (model) User.php:
public function storedata()
{
return $this->create(Input::all());
}
And then run it in your controller:
User::storedata();
But is it better? ;-)
In this circumstance, I don't think you gain much from moving things arounds.
If this is your Controller method:
public function store()
{
$data=Input::all();
User::create($data);
}
then this makes sense. The input data is being handled by the controller method, and is being passed to the create method on your User Model.
If however, there was more logic required before creating a User record, then it'd be perfectly valid to abstract that logic to your User model. But as it stands though, I think your current implementation is appropriate.

different exceptions for each controller cakephp

I want to create exceptions for each controller in my project with cakephp. Example:
class UsersController extends AppController {
public function add(){
if(!$this->User->save($this->request->data)){
throw new UserException('Error save User/add');
}
}
}
class FriendsController extends AppController {
public function add(){
if(!$this->Friend->save($this->request->data)){
throw new FriendException('Error save Friend/add');
}
}
}
I'm trying this, but does not work...never paint the log in class exception user/friend
In app/Config/core.php
Configure::write('Exception.handler', 'UserException::handle');
Configure::write('Exception.handler', 'FriendException::handle');
In app/Config/bootstrap.php
App::uses('UserException', 'Lib');
App::uses('FriendException', 'Lib');
In app/Lib/UserException.php
class UserException {
public static function handle($error) {
$this->log($error)
}
}
In app/Lib/FriendException.php
class FriendException {
public static function handle($error) {
$this->log($error)
}
}
Any idea?
Regards!
An exception for each controller seems quite redundant. Why do you need 2 exception classes UserException and FriendException when they are identical, only difference being the message you pass? Also logging feature is inbuilt in core's ExceptionHandler. You can just throw a CakeException or RuntimeException with required message.
Do you realize your code:
Configure::write('Exception.handler', 'UserException::handle');
Configure::write('Exception.handler', 'FriendException::handle');
is overwriting the config same variable?
Anyway the 'Exception.handler' config is meant to specify the class with handles ALL your exceptions. You can confusing an exception class with an error handler class. Exception classes don't have any handle() method. Your exception classes should extend CakeException and override required method/property.
Use the CakeDC Utils plugin, it comes with an EmailErrorHandler that is configurable and can send an email depending on the error level.

How to make a REST API first web application in Laravel

I want to make an API first application in Laravel. I don't know what is the best approach to do this, I will explain what I am trying to do, but please feel free to give answers how to do this in a different way.
I don't want all my frontend to be written in javascript and parse the JSON output of the API with angular.js or something similar. I want my Laravel application to produce the HTML views. I am trying to go down the road of having two controllers one on for the API and one for the web. For the show User action my routes.php looks like this:
# the web controller
Route::controller('user', 'WebUserController');
# the api controller
Route::group(array('prefix' => 'api'), function() {
Route::resource('user', 'UserController');
});
So /user will take me to WebUserController and /api/user will take me to the UserController. Now I want to put all my logic in the API UserController, and call its actions from the WebUserController. Here is the code for both of them:
class UserController extends BaseController
{
public function show($id)
{
$user = User::find($id);
return Response::json(array('success'=>true,'user'=>$user->toArray()));
}
}
class WebUserController extends UserController
{
public function getView($id)
{
# call the show method of the API's User Controller
$response = $this->show($id);
return View::make('user.view')->with('data', $response->getData());
}
}
In the WebUserController I am able to get the json content of the response with getData(), but I am not able to get the headers and status code (they are protected properties of Illuminate\Http\JsonResponse).
I think that my approach might not be the best, so I am open to suggestions how to make this app.
EDIT: The question how to get the headers and status of the response has been answered by Drew Lewis, but I still think that there might be a better way how to design this
You should utilize the Repository / Gateway design pattern: please see the answers here.
For example, when dealing with the User model, first create a User Repository. The only responsibility of the user repository is to communicate with the database (performing CRUD operations). This User Repository extends a common base repository and implements an interface containing all methods you require:
class EloquentUserRepository extends BaseRepository implements UserRepository
{
public function __construct(User $user) {
$this->user = $user;
}
public function all() {
return $this->user->all();
}
public function get($id){}
public function create(array $data){}
public function update(array $data){}
public function delete($id){}
// Any other methods you need go here (getRecent, deleteWhere, etc)
}
Then, create a service provider, which binds your user repository interface to your eloquent user repository. Whenever you require the user repository (by resolving it through the IoC container or injecting the dependency in the constructor), Laravel automatically gives you an instance of the Eloquent user repository you just created. This is so that, if you change ORMs to something other than eloquent, you can simply change this service provider and no other changes to your codebase are required:
use Illuminate\Support\ServiceProvider;
class RepositoryServiceProvider extends ServiceProvider {
public function register() {
$this->app->bind(
'lib\Repositories\UserRepository', // Assuming you used these
'lib\Repositories\EloquentUserRepository' // namespaces
);
}
}
Next, create a User Gateway, who's purpose is to talk to any number of repositories and perform any business logic of your application:
use lib\Repositories\UserRepository;
class UserGateway {
protected $userRepository;
public function __construct(UserRepository $userRepository) {
$this->userRepository = $userRepository;
}
public function createUser(array $input)
{
// perform any sort of validation first
return $this->userRepository->create($input);
}
}
Finally, create your User web controller. This controller talks to your User Gateway:
class UserController extends BaseController
{
public function __construct(UserGatway $userGateway)
{
$this->userGateway = $userGateway;
}
public function create()
{
$user = $this->userGateway->createUser(Input::all());
}
}
By structuring the design of your application in this way, you get several benefits: you achieve a very clear separation of concerns, since your application will be adhering to the Single Responsibility Principle (by separating your business logic from your database logic) . This enables you to perform unit and integration testing in a much easier manner, makes your controllers as slim as possible, as well as allowing you to easily swap out Eloquent for any other database if you desire in the future.
For example, if changing from Eloquent to Mongo, the only things you need to change are the service provider binding as well as creating a MongoUserRepository which implements the UserRepository interface. This is because the repository is the only thing talking to your database - it has no knowledge of anything else. Therefore, the new MongoUserRepository might look something like:
class MongoUserRepository extends BaseRepository implements UserRepository
{
public function __construct(MongoUser $user) {
$this->user = $user;
}
public function all() {
// Retrieve all users from the mongo db
}
...
}
And the service provider will now bind the UserRepository interface to the new MongoUserRepository:
$this->app->bind(
'lib\Repositories\UserRepository',
'lib\Repositories\MongoUserRepository'
);
Throughout all your gateways you have been referencing the UserRepository, so by making this change you're essentially telling Laravel to use the new MongoUserRepository instead of the older Eloquent one. No other changes are required.
You should be use Repository for this design.
Example -
//UserRepository Class
class UserRepository {
public function getById($id)
{
return User::find($id);
}
}
// WebUser Controller
class WebUserController extends BaseController {
protected $user;
public function __construct(UserRepository $user)
{
$this->user = $user;
}
public function show($id)
{
return View::make('user.view')->with('data', $this->user->getById($id));
}
}
// APIUser Controller
class UserController extends BaseController {
protected $user;
public function __construct(UserRepository $user)
{
$this->user = $user;
}
public function show($id)
{
$data =>$this->user->getById($id);
return Response::json(array('success'=>true,'user'= $data->toArray()));
}
}
Checkout Laravel's RESTful controllers:
http://laravel.com/docs/controllers#restful-controllers
Their docs do a pretty good job.
But even better is this tutorial:
http://code.tutsplus.com/tutorials/laravel-4-a-start-at-a-restful-api-updated--net-29785
This is a video by Jeffrey Way he is one of the better Laravel developers. In this tutorial he is connecting a BackboneJS application to a RESTful service that he sets up in Laravel. It doesn't get any better then this. I can write you a lot of boilerplate, but just learn it by watching a nice video and having a coffee. ;)
https://www.youtube.com/watch?v=uykzCfu1RiQ
I have a response to the problem you are having with the Response.
You can get the headers, status code and data from the Response.
// your data
$response->getData();
// the status code of the Response
$response->getStatusCode();
// array of headers
$response->headers->all();
// array of headers with preserved case
$response->headers->allPreserveCase();
$response->headers is a Symfony\Component\HttpFoundation\ResponseHeaderBag which inherits from Symfony\Component\HttpFoundation\HeaderBag
I would also recommend using a repository.
Attempting to call one controller from another would be falling into a pattern called HMVC (Hierarchical model–view–controller).
This means that your entire application relies on lower modules.
In this case, your API would serve as a repository for your data (which isn't the worst thing in the world at first).
However, when you then modify the structure of how data is returned in your API, everything else relying on it would have to know how to respond.
Say you wanted to have authorization checks to see if a logged in user should be able to see the details of a returned user and there was an error.
In the API, you would return a Response object with a 403 forbidden code and some meta data.
Your HTML controller would have to know how to handle this.
Contrast this to a repository which could throw an exception.
public function findById ($id)
{
$user = User::findOrFail($id);
if (Auth::user->hasAccessTo($user)) {
return $user;
} else {
throw new UnauthorizedAccessException('you do not have sufficient access to this resource');
}
}
And your API controller would look more like this:
public function show($id)
{
try {
return $this->user->findById($id);
} catch (UnauthorizedAccessException $e) {
$message = $e->getMessage();
return Response::json('403', ['meta' => ['message' => $message]]));
}
}
Your HTML controller would then look like this:
public function show($id)
{
try {
$user = $this->user->findById($id);
} catch (UnauthorizedAccessException $e) {
Session::flash('error', $e->getMessage());
// Redirect wherever you would like
return Response::redirect('/');
}
}
This gives you very reusable code and let's you change your controller implementations independently without worry of changing the other's behavior.
I wrote more on how to implement the repository pattern in this post: you can ignore the interface and skip right to the implementations if you would like.

Yii: Throws exception on any function call when using Xdebug, works when not debugging

During debug, I'm experiencing a problem where any function call within my Controller action method will throw an exception, but when running the code without the debugger, it does not occur. I am using Xdebug 2.2.3, with Yii framework 1.1.14.
My Controller class is called TesterController, and this happens in the actionIndex:
class Controller extends CController
{
public $layout='//layouts/column1';
public $menu=array();
public $breadcrumbs=array();
}
class TesterController extends Controller {
public function actionIndex() {
printf("test\n");
$this->render('index');
}
}
When attempting to step into the printf function, execution goes to the following Yii exception handler:
public function handleException($exception)
{
// disable error capturing to avoid recursive errors
restore_error_handler();
restore_exception_handler();
...
where $exception complains that property TesterController.email does not exist. I've tried viewing the stack and exception traces, updating to the latest version of Xdebug, updating to the latest version of Yii, deleting all cache, grepping my code for any references to this property (email), and recreating my Controller and View classes from scratch to no avail. I've reduced it to this minimal demonstrable case, and I'm at a loss for what to try next besides rebuilding my Yii installation from scratch. Something tells me it might be related to PHP magic getters. Any insight would be greatly appreciated.

Illuminate components in packages

I have a self-made package. With the service provider I pass the $app instance to the actual class. Then I use:
$this->app['session']->get('key', 'value');
to use the Session component's functionality. However, when I run this, I get the following error:
Using $this when not in object context
So I tried just using
Session::get( ... )
but then it says it doesn't find the class.
How do I solve this?
This is my full class:
namespace AlexCogn\Messenger;
use Illuminate\Foundation\Application as Application;
class Messenger
{
protected $app;
public function __construct(Application $app)
{
$this->app = $app;
}
public static function messages()
{
$messages = $this->app['session']->get('messages', array());
$this->app['session']->forget('messages');
return $messages;
}
public function addMessage($message)
{
$messages = $this->app['session']->get('messages', array());
array_push($messages, $message);
$this->app['session']->put('messages', $messages);
}
}
EDIT:
apparently not everyone understands the question: I am calling Messenger through a Facade:
Messenger::addMessage('test');
and I thought Laravel converted this to
$app['messenger']->addMessage('test');
So shouldn't this be called via an instance?
You are trying to call $this from a static method within your class. This won't work. By PHP definition (and also because it'd be stupid otherwise), $this is only available in the following scopes:
Inside a method that has been called through an instance (i.e. $this->foo())
Inside a class constructor (inside __construct())
Inside a Callable where this has been redefined using the Runkit library
Anything else causes this to trigger the fatal error you have just received. I cannot suggest a fix, as you did not put your code up - however, if you do, I'll be more than happy to find a solution for you.

Categories