I've got the following function in my controller that handles preparing and loading my home page.
public function index()
{
// GetBalance
$current_balance = $this->PayPal->getBalance();
if(Session::has('errors'))
{
return Redirect::to('error');
}
// TransactionSearch
$params = array(
'number_of_days' => 1
);
$recent_history = $this->PayPal->transactionSearch($params);
if(Session::has('errors'))
{
return Redirect::to('error');
}
// Make View
$data = array('current_balance' => $current_balance, 'recent_history' => $recent_history);
return View::make('index')->with('data', $data);
}
As you can see, I'm making 2 different calls to the PayPal API through my model, and after each one I'm checking for an error. When errors do occur I flash the error messages and redirect to an error page accordingly.
I'd like to improve upon that so I don't have to keep using this same snippet of code over and over again when I'm making a bunch of calls prior to loading a view.
if(Session::has('errors'))
{
return Redirect::to('error');
}
I tried moving this to its own function...
public function errorCheck()
{
if(Session::has('errors'))
{
return Redirect::to('error');
}
}
Then, I thought I could just do this within my index function...
// GetBalance
$current_balance = $this->PayPal->getBalance();
$this->errorCheck();
That doesn't work, though, I guess because errorCheck() is simply returning a value and not actually triggering the redirect, so I just end up at my home page with an error because none of the data it expects exists (since the API calls failed).
Any info on what I need to do here so that my errorCheck() function simply triggers the redirect when it should would be greatly appreciated.
Thanks!
The only way I can think of to avoid this is via the use of exceptions.
With your errorCheck() method you could try this:
// GetBalance
$current_balance = $this->PayPal->getBalance();
return $this->errorCheck();
but that's not what you want... it's going to exit your method after the first call. What you really need is a way to catch an error wherever it occurs and handle it - and that's what exceptions do.
I'm going to assume your PayPal class is a third-party package and you can't rewrite it to throw exceptions. Given that assumption, what you can do is this:
Rewrite your errorCheck() method like so:
public function errorCheck()
{
if(Session::has('errors'))
{
throw new Exception("Problem with Paypal!");
}
}
Then wrap all your Paypal access code in a try/catch block:
public function index()
{
try {
// GetBalance
$current_balance = $this->PayPal->getBalance();
$this->errorCheck();
// TransactionSearch
$params = array(
'number_of_days' => 1
);
$recent_history = $this->PayPal->transactionSearch($params);
$this->errorCheck();
// Make View
$data = array('current_balance' => $current_balance, 'recent_history' => $recent_history);
return View::make('index')->with('data', $data);
} catch(Exception $e) {
return Redirect::to('error');
}
}
Each time you call errorCheck() it will check for an error and throw an exception. In that case, execution will immediately jump to the catch block and redirect to the error page.
A better solution would be to throw the exceptions closer to the source of the error, ie. somewhere in the Paypal class when the error occurs. The idea here is that the exception includes a lot of useful information telling you what happened, like a stack trace. In the code I've given, the stack trace is going to show that the exception was thrown in the errorCheck() method which, while true, is not really helpful. If the exception could be thrown somewhere in the Paypal class, it would give you a better indication of what really went wrong.
While throwing an error is definitely the way to go, I'd say you go a step further and generalize the redirect. Instead of doing that try catch block every time the PayPal API is called, you can use App::error to do the redirect globally.
Create an exception class somewhere appropriate:
class PayPalApiException extends Exception {}
Then in your start/global.php add this (before the other App::error call):
App::error(function(PayPalApiException $exception)
{
return Redirect::to('error');
});
Then your code in the controller can become much simpler:
public function index()
{
$current_balance = $this->PayPal->getBalance();
$this->errorCheck();
$recent_history = $this->PayPal->transactionSearch([
'number_of_days' => 1
]);
$this->errorCheck();
$data = compact('current_balance', 'recent_history');
return View::make('index')->with('data', $data);
}
protected function errorCheck()
{
if (Session::has('errors'))
{
throw new PayPalApiException("Problem with Paypal!");
}
}
Why do you even need to check for the error twice? It doesnt seem to be related to each call? i.e. if doesnt seem to matter if the balance call fails, because you dont use the result in the transaction search.
I'd just do this
public function index()
{
// GetBalance
$current_balance = $this->PayPal->getBalance();
// TransactionSearch
$recent_history = $this->PayPal->transactionSearch(array('number_of_days' => 1));
if(Session::has('errors'))
{
return Redirect::to('error');
}
else
{
return View::make('index')->with('current_balance', $current_balance)
->with('recent_history', $recent_history);
}
}
Related
I'm on a project where I don't want to throw errors directly at the user. Instead I want customized messages for the error that accur.
For later I also need to keep an error number in order to customize the error messages from outside the class, like an array of error messages.
So I made my own thing where I set $error = null and then later set error to a number that later becomes a message.
Question
Is there any disadvantages with this approach? Am I better of with try/catch or something else? I would like to keep the code short and tidy if possible.
In this short code example the error handling seems to be a big part of the class. In my real code which is a few hundred lines, it's not a big part of the whole code
http://sandbox.onlinephpfunctions.com/code/623b388b70603bf7f020468aa9e310f7340cd108
<?php
class Project {
private $error = null;
public function callMeFirst($num) {
$this->nestedLevelOne($num);
$this->nestedLevelTwo($num);
$this->setResults();
}
public function callMeSecond($num) {
$this->nestedLevelTwo($num);
$this->setResults();
}
private function nestedLevelOne($num) {
// Do stuff
if($num !== 1) {
$this->error = ['id' => 1, 'value' => $num];
}
}
private function nestedLevelTwo($num) {
// Do stuff
if($num !== 20) {
$this->error = ['id' => 2, 'value' => $num];
}
}
private function message($args) {
extract($args);
$message = [
1 => "Nested level one error: $value",
2 => "Another error at level two: $value",
];
return ['id' => $id, 'message' => $message[$id]];
}
private function setResults() {
$results['success'] = ($this->error === null) ? true : false;
if($this->error !== null) {
$results['error'] = $this->message($this->error);
}
$this->results = $results;
}
}
$project = new Project();
$project->callMeFirst(1);
$project->callMeFirst(2);
print_r($project->results);
It will output
Array
(
[success] =>
[error] => Array
(
[id] => 2
[message] => Another error at level two: 2
)
)
The reason I'm asking is that I have a feeling of that I may reinvent the wheel in this case. Am I?
If there is a better solution, I would be thankful to see how that code looks like.
I would probably separate the business logic from the error handling to simplify each part more. By using exceptions, you keep your business logic simpler; you simply throw an exception whenever you encounter a case that is not permitted, thereby preventing getting into any sort of inconsistent state at all. The business logic class doesn't have to care about how this error will be processed further, it just needs to raise the error. You should then create a separate wrapper around that business logic class which simply cares about handling any errors and formatting them into an array or other sort of response which will be handled elsewhere. Something along these lines:
class ProjectException extends Exception {
public function __construct($num) {
parent::__construct(get_called_class() . ": $num");
}
}
class NestedLevelOneException extends ProjectException {
// customise __construct here if desired
}
class NestedLevelTwoException extends ProjectException {}
class Project {
public function callMeFirst($num) {
$this->nestedLevelOne($num);
$this->nestedLevelTwo($num);
}
public function callMeSecond($num) {
$this->nestedLevelTwo($num);
}
protected function nestedLevelOne($num) {
if ($num !== 1) {
throw new NestedLevelOneException($num);
}
// do stuff
}
protected function nestedLevelTwo($num) {
if ($num !== 20) {
throw new NestedLevelTwoException($num);
}
// do stuff
}
}
class ProjectService {
protected $project;
public function __construct(Project $project = null) {
$this->project = $project ?: new Project;
}
public function process($a, $b) {
try {
$this->project->callMeFirst($a);
$this->project->callMeSecond($b);
return ['success' => true];
} catch (ProjectException $e) {
return ['success' => false, 'error' => $e->getMessage()];
}
}
}
$api = new ProjectService;
print_r($api->process(1, 2));
By defining three separate exceptions, you get a lot of flexibility in how and where you want to handle errors. You can specifically catch NestedLevel*Exception, or you catch either of them with ProjectException.
By having your methods throw exceptions, you gain flexible error handling possibilities. You are free to not catch the exception and have the program terminate, as would be entirely reasonable if one of your business requirements isn't met. Alternatively, you can catch the exception at a level up that is prepared to deal with that error and turn it into something meaningful that can be acted upon.
By moving the generation of the error message into the exceptions, you keep the error type and its message self-contained. There's exactly one place where you define what kind of error may happen and what its error message will look like; instead of spreading that out over your entire codebase. And you're still free to choose some other error message in the UI, e.g. for localising different kinds of errors into multiple languages; just check the type of the exception object.
By using a separate ProjectService which cares about handling those exceptions and turning it into an array response, you narrow each class' responsibilities and make each class more flexible and simpler.
What is the best way to return errors from a PHP function, when the function has executed normally?
Example
public function login($user, $pw){
if(!$this->verifyUser($user))
// return error about invalid user
}
else if (!$this->verifyPw($pw)){
// return error about invalid pw
}
else {
// return OK
}
}
Caller - Return response as JSON for web UI
public function doLogin($user,$pw){
$res = $this->login($user, $pw);
return json_encode($res);
}
On one hand I could understand returning results as an array, but I feel like this does not make sense for such low level functions. Perhaps they should return error codes and then caller must lookup the error code string?
Assuming you are in an object, you basically have three major options:
store errors in something like $this->errors array and return false
have some kind of error-collector as a dependency for object, where you
call $this->collector->addError('blah blah'); and return false
throw an exception
For the first two approaches, you will have to check the return value, and based on that, pull the list of errors. But both of those options have the benefit of being able to collect multiple errors.
The exception approach is a bit lighter on coupling, but you can only get one error.
As for what to actually return, I would recommend going with error code + description string. But that string would not be returned by your class. Instead your error should be registered using some "placeholder", that later is translated:
$this->errors[] = [
'code' => 52,
'msg' => 'authentication.login.invalid-password',
];
When you pull the errors from your object, it would be basically a list of entries like this, And then you just run them through your translation service.
In a case of exception, that same information would reside in $e->getCode() and $e->getMessage(), when your object throws InvalidPassword exception.
For an API response the answer from tereško would be along the correct lines.
For a DOM response you can do the following:
I have used a response code only in the past for something so simple:
http://php.net/manual/en/function.http-response-code.php with code 401
public function login($user, $pw) {
header_remove(); # Clear all previous headers.
if( !$this->verifyUser($user) || !$this->verifyPw($pw) ){
http_response_code(401);
exit;
}
http_response_code(200);
exit;
}
jQuery:
$.ajax({
.......
statusCode: {
200: function() {
window.location.href = '/';
},
401: function() {
alert( "Login Failed" );
}
}
});
So I am messing around with symfony router component and I created a small wrapper.
One thing that came up was how do I get a request to throw a 500 in unit tests? The method in question is:
public function processRoutes(Request $request) {
try {
$request->attributes->add($this->_matcher->match($request->getPathInfo()));
return call_user_func_array($request->attributes->get('callback'), array($request));
} catch (ResourceNotFoundException $e) {
return new RedirectResponse('/404', 302);
} catch (Exception $e) {
return new RedirectResponse('/500', 302);
}
}
And the test in question is:
public function testFiveHundred() {
$router = new Router();
$router->get('/foo/{bar}', 'foo', function($request){
return 'hello ' . $request->attributes->get('bar');
});
$response = $router->processRoutes(Request::create('/foo/bar', 'GET'));
$this->assertEquals(500, $response->getStatusCode());
}
Right now the test will fail because we are defined and the status code will be 200. Is there something special I can do to the Request object I create, to make it throw a 500?
I think you got several options here you can play with:
Decide that a specific path will always throw an exception.
This will force you to make some changes in your code.
public function processRoutes(Request $request) {
...
if ($request->getRequestUri() == '/path/that/throws/exception') {
throw Exception('Forced to throw exception by URL');
}
...
}
public function testFiveHundred() {
...
$response = $router->processRoutes(Request::create('/path/that/throws/exception', 'GET'));
...
}
Make a DummyRequest object that will extends your original Request class and make sure this object will raise an Exception (for example - you know for sure that you use the getPathInfo(), so you can use this).
class DummyRequest extends Request {
public function getPathInfo() {
throw new Exception('This dummy request object should only throw an exception so we can test our routes for problems');
}
}
public function testFiveHundred() {
...
$dummyRequest = new DummyRequest();
$response = $router->processRoutes($dummyRequest);
...
}
Since the function getRequestUri of our $dummyRequest throws an exception, your call to $router->processRoutes will have our dummy to throw that exception.
This is a general idea, you would probably need to play a bit with the namespaces and the functions there (I didn't test it, however this should work).
I don't understand how to properly create and return useful error messages with PHP to the web.
I have a class
class Foo {
const OK_IT_WORKED = 0;
const ERR_IT_FAILED = 1;
const ERR_IT_TIMED_OUT = 3;
public function fooItUp(){
if(itFooed)
return OK_IT_WORKED;
elseif(itFooedUp)
return ERR_IT_FAILED;
elseif(itFooedOut)
return ERR_IT_TIMED_OUT;
}
}
And another class that uses this class to do something useful, then return the result to the user. I am just wondering where I put the string value for all my error messages.
class Bar {
public function doFooeyThings(stuff){
$res = $myFoo->fooItUp();
// now i need to tell the user what happened, but they don't understand error codes
if($res === Foo::OK_IT_WORKED)
return 'string result here? seems wrong';
elseif ($res === Foo::ERR_IT_FAILED)
return Foo::ERR_IT_FAILED_STRING; // seems redundant?
elseif($res === Foo:ERR_IT_TIMED_OUT)
return $res; // return number and have an "enum" in the client (js) ?
}
}
You should avoid returning error states whenever possible. Use exceptions instead. If you've never used exceptions before you can read about them here
There multiple ways you can utilize exceptions in your example. You could create custom exceptions for every error or for every category of error. More on custom exceptions here or you could create an instance of the default Exception class supplying it the error messages as strings.
The code below follows the second approach:
class Foo {
const OK_IT_WORKED = 0;
const ERR_IT_FAILED = 1;
const ERR_IT_TIMED_OUT = 3;
public function fooItUp(){
if(itFooed)
return OK_IT_WORKED;
else if(itFooedUp)
throw new Exception("It failed")
else if(itFooedOut)
throw new Exception("Request timed out");
}
}
I'm sure you can think of some more elegant messages than the ones I used. Anyway, you can then go ahead and handle those exceptions on the caller method using try/catch blocks:
class Bar {
public function doFooeyThings(stuff){
try
{
$res = myFoo->fooItUp();
}
catch(Exception $e)
{
//do something with the error message
}
}
}
Whatever exception is thrown from fooItUp will be "caught" by the catch block and handled by your code.
Two things you should also consider are:
It's best not to show your users detailed information about errors because those information could be used by users with malicious intent
Ideally you should have some kind of global exception handling
One solution is to use exceptions in conjunction with set_exception_handler().
<?php
set_exception_handler(function($e) {
echo "Error encountered: {$e->getMessage()}";
});
class ErrorMessageTest
{
public function isOk()
{
echo "This works okay. ";
}
public function isNotOkay()
{
echo "This will not work. ";
throw new RuntimeException("Violets are red, roses are blue!! Wha!?!?");
}
}
$test = new ErrorMessageTest();
$test->isOk();
$test->isNotOkay();
The set_exception_handler() method takes a callable that will accept an exception as its parameter. This let's you provide your own logic for a thrown exception in the event it isn't caught in a try/catch.
Live Demo
See also: set_exception_handler() documentation
It's my first time to use DB::transaction() but how exactly does it work if a transaction fails or is successful? In the example below, do I have to manually assign a value to return true, or if it fails will the method either return false or totally exit the transaction (therefore skipping the rest of the code)? The docs aren't so helpful on this.
use Exception;
use DB;
try {
$success = DB::transaction(function() {
// Run some queries
});
print_r($success);
} catch(Exception $e) {
echo 'Uh oh.';
}
Solution
I wrote down this solution for others who might be wondering.
Since I was more concerned about returning a boolean value depending on the success of my query, with a few modifications it now returns true/false depending on its success:
use Exception;
use DB;
try {
$exception = DB::transaction(function() {
// Run queries here
});
return is_null($exception) ? true : $exception;
} catch(Exception $e) {
return false;
}
Take note that the variable $exception is never returned since if something goes wrong with your query, the catch is immediately triggered returning false. Thanks to #ilaijin for showing that an Exception object is thrown if something goes wrong.
By giving a look at function transaction it does its process inside a try/catch block
public function transaction(Closure $callback)
{
$this->beginTransaction();
// We'll simply execute the given callback within a try / catch block
// and if we catch any exception we can rollback the transaction
// so that none of the changes are persisted to the database.
try
{
$result = $callback($this);
$this->commit();
}
// If we catch an exception, we will roll back so nothing gets messed
// up in the database. Then we'll re-throw the exception so it can
// be handled how the developer sees fit for their applications.
catch (\Exception $e)
{
$this->rollBack();
throw $e;
}
So throws an Exception (after the rollback) if fails or returns $result, which is the result of your callback
There is a short version if you want to use the default transaction method that ships with Laravel without handling it manually.
$result = DB::transaction(function () {
// logic here
return $somethingYouWantToCheckLater;
});
You can also use the following
DB::rollback();